codesign_wrapper.c   [plain text]


/*
   cc -g -O0 -Wall -Werror -Wmost -stabs -o codesign_wrapper codesign_wrapper.c -framework CoreFoundation
 */

#include <paths.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <getopt.h>
#include <stdbool.h>
#include <limits.h>

#include <CoreFoundation/CFData.h>
#include <CoreFoundation/CFDictionary.h>
#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFString.h>

/* for CMS */
#include <Security/SecCmsMessage.h>
#include <Security/SecCmsSignedData.h>
#include <Security/SecCmsContentInfo.h>
#include <Security/SecCmsSignerInfo.h>
#include <Security/SecCmsEncoder.h>
#include <Security/SecCmsDecoder.h>
#include <Security/SecCmsDigestContext.h>
#include <Security/oidsalg.h>
#include <Security/cmspriv.h>

#include <Security/SecCMS.h>

#include <Security/SecPolicy.h>
#include <Security/SecCertificate.h>
#include <Security/SecCertificatePriv.h>

/* entitlement whitelist checking */
#include <MISEntitlement.h>

#define DEBUG_ASSERT_PRODUCTION_CODE 0
#include <AssertMacros.h>

#include "codesign_wrapper.h"
#include "codesign.h"

extern bool do_verify(const char *path, CFArrayRef certificates);

static char * codesign_binary = "/usr/bin/codesign";
static char * processing_path = "/var/tmp/signingbox";
static char * processing_file = "codesign_wrapper";
static char * processing_prefix = NULL;
static char * auditing_postfix = "_auditing.plist";
static char * audition_plist_path = NULL;
static char * entitlements_plist_path = NULL;
static char * entitlements_postfix = "_entitlements.plist";


#define CODESIGN_WRAPPER_VERSION "0.7.10"
#define log(format, args...)    \
    fprintf(stderr, "codesign_wrapper-" CODESIGN_WRAPPER_VERSION ": " format "\n", ##args);
#define cflog(format, args...) do { \
CFStringRef logstr = CFStringCreateWithFormat(NULL, NULL, CFSTR(format), ##args);\
if (logstr) { CFShow(logstr); CFRelease(logstr); } \
} while(0); \

const char *_root_ca_name = ANCHOR;

static pid_t kill_child = -1;
static void child_timeout(int sig)
{
    if (kill_child != -1) {
        kill(kill_child, sig);
        kill_child = -1;
    }
}

static void
close_all_fd(void *arg __unused)
/* close down any files that might have been open at this point
   but make sure 0, 1 and 2 are set to /dev/null so they don't
   get reused */
{
    int maxDescriptors = getdtablesize ();
    int i;

    int devnull = open(_PATH_DEVNULL, O_RDWR, 0);

    if (devnull >= 0) for (i = 0; i < 3; ++i)
        dup2(devnull, i);

    for (i = 3; i < maxDescriptors; ++i)
        close (i);
}


static pid_t
fork_child(void (*pre_exec)(void *arg), void *pre_exec_arg,
        const char * const argv[])
{
    unsigned delay = 1, maxDelay = 60;
    for (;;) {
        pid_t pid;
        switch (pid = fork()) {
            case -1: /* fork failed */
                switch (errno) {
                    case EINTR:
                        continue; /* no problem */
                    case EAGAIN:
                        if (delay < maxDelay) {
                            sleep(delay);
                            delay *= 2;
                            continue;
                        }
                        /* fall through */
                    default:
                        perror("fork");
                        return -1;
                }
                assert(-1); /* unreached */

            case 0: /* child */
                if (pre_exec)
                    pre_exec(pre_exec_arg);
                execv(argv[0], (char * const *)argv);
                perror("execv");
                _exit(1);

            default: /* parent */
                return pid;
                break;
        }
        break;
    }
    return -1;
}


static int
fork_child_timeout(void (*pre_exec)(), char *pre_exec_arg,
        const char * const argv[], int timeout)
{
    int exit_status = -1;
    pid_t child_pid = fork_child(pre_exec, pre_exec_arg, argv);
    if (timeout) {
        kill_child = child_pid;
        alarm(timeout);
    }
    while (1) {
        int err = wait4(child_pid, &exit_status, 0, NULL);
        if (err == -1) {
            perror("wait4");
            if (errno == EINTR)
                continue;
        }
        if (err == child_pid) {
            if (WIFSIGNALED(exit_status)) {
                log("child %d received signal %d", child_pid, WTERMSIG(exit_status));
                kill(child_pid, SIGHUP);
                return -2;
            }
            if (WIFEXITED(exit_status))
                return WEXITSTATUS(exit_status);
            return -1;
        }
    }
}


static void
dup_io(int arg[])
{
    dup2(arg[0], arg[1]);
    close(arg[0]);
}


static int
fork_child_timeout_output(int child_fd, int *parent_fd, const char * const argv[], int timeout)
{
    int output[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, output))
        return -1;
    fcntl(output[1], F_SETFD, 1); /* close in child */
    int redirect_child[] = { output[0], child_fd };
    int err = fork_child_timeout(dup_io, (void*)redirect_child, argv, timeout);
    if (!err) {
        close(output[0]); /* close the child side in the parent */
        *parent_fd = output[1];
    }
    return err;
}


static void
pass_signal_to_children(int sig)
{
    signal(sig, SIG_DFL);
    kill(0, sig);
}


static int
mk_temp_dir(const char *path)
{
    char *pos = NULL, *tmp_path = strdup(path);
    if (!path) return -1;
    pos = index(tmp_path, '/');
    if (!pos) return -1;
    while ((pos = index(pos + 1, '/'))) {
        *pos = '\0';
        if ((0 != mkdir(tmp_path, 0755)) &&
                errno != EEXIST)
            return -1;
        *pos = '/';
    }
    if ((0 != mkdir(tmp_path, 0755)) &&
            errno != EEXIST)
        return -1;
    return 0;
}


static int
lock_file(const char *lock_file_prefix, const char *lock_filename)
{
    int err = -1;
    pid_t pid;
    char *tempfile = NULL;
    do {
        if (!asprintf(&tempfile, "%s.%d", lock_file_prefix, getpid()))
            break;
        FILE *temp = fopen(tempfile, "w");
        if (temp == NULL)
            break;
        if (fprintf(temp, "%d\n", getpid()) <= 0)
            break;
        fclose(temp);
        if(!link(tempfile, lock_filename)) {
            unlink(tempfile);
            err = 0;
            break;
        }
        FILE* lock = fopen(lock_filename, "r");
        if (lock == NULL)
            break;
        if (fscanf(lock, "%d\n", &pid) <= 0)
            break;
        if (kill(pid, 0)) {
            if (!unlink(lock_filename) &&
                    !link(tempfile, lock_filename)) {
                unlink(tempfile);
                err = 0;
                break;
            }
        }
    } while(0);
    unlink(tempfile);
    if (tempfile)
        free(tempfile);

    return err;
}


static ssize_t
read_fd(int fd, void **buffer)
{
    int err = -1;
    size_t capacity = 1024;
    char * data = malloc(capacity);
    size_t size = 0;
    while (1) {
        int bytes_left = capacity - size;
        int bytes_read = read(fd, data + size, bytes_left);
        if (bytes_read >= 0) {
            size += bytes_read;
            if (capacity == size) {
                capacity *= 2;
                data = realloc(data, capacity);
                if (!data) {
                    err = -1;
                    break;
                }
                continue;
            }
            err = 0;
        } else
            err = -1;
        break;
    }
    if (0 == size) {
        if (data)
            free(data);
        return err;
    }

    *buffer = data;
    return size;
}

enum { CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171 };

typedef struct {
    uint32_t type;
    uint32_t offset;
} cs_blob_index;

static CFDataRef
extract_entitlements_blob(const uint8_t *data, size_t length)
{
    CFDataRef entitlements = NULL;
    cs_blob_index *csbi = (cs_blob_index *)data;

    require(data && length, out);
    require(csbi->type == ntohl(CSMAGIC_EMBEDDED_ENTITLEMENTS), out);
    require(length == ntohl(csbi->offset), out);
    entitlements = CFDataCreate(kCFAllocatorDefault,
        (uint8_t*)(data + sizeof(cs_blob_index)),
        (CFIndex)(length - sizeof(cs_blob_index)));
out:
    return entitlements;
}

static CFDataRef
build_entitlements_blob(const uint8_t *data, size_t length)
{
    cs_blob_index csbi = { htonl(CSMAGIC_EMBEDDED_ENTITLEMENTS),
        htonl(length+sizeof(csbi)) };
    CFMutableDataRef blob = CFDataCreateMutable(kCFAllocatorDefault, sizeof(csbi)+length);
    if (data) {
        CFDataAppendBytes(blob, (uint8_t*)&csbi, sizeof(csbi));
        CFDataAppendBytes(blob, data, length);
    }
    return blob;
}

static CFMutableDictionaryRef
dump_auditing_info(const char *path)
{
    int exit_status;
    CFMutableDictionaryRef dict = NULL;
    void *requirements = NULL;
    ssize_t requirements_size = 0;
    void *entitlements = NULL;
    ssize_t entitlements_size = 0;

    do {
        const char * const extract_requirements[] =
        { codesign_binary, "--display", "-v", "-v", path, NULL };
        int requirements_fd;
        if ((exit_status = fork_child_timeout_output(STDERR_FILENO, &requirements_fd,
                        extract_requirements, 0))) {
            fprintf(stderr, "failed to extract requirements data: %d\n", exit_status);
            break;
        }

        requirements_size = read_fd(requirements_fd, &requirements);
        if (requirements_size == -1)
            break;
        close(requirements_fd);

    } while(0);

    do {
        const char * const extract_entitlements[] =
        { codesign_binary, "--display", "--entitlements", "-", path, NULL };
        int entitlements_fd;
        if ((exit_status = fork_child_timeout_output(STDOUT_FILENO, &entitlements_fd,
                        extract_entitlements, 0))) {
            fprintf(stderr, "failed to extract entitlements: %d\n", exit_status);
            break;
        }

        entitlements_size = read_fd(entitlements_fd, &entitlements);
        if (entitlements_size == -1)
            break;
        close(entitlements_fd);

    } while(0);

    do {
        dict = CFDictionaryCreateMutable(
                kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
                &kCFTypeDictionaryValueCallBacks);

        if (requirements && requirements_size) {
            CFDataRef req = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
                requirements, requirements_size, kCFAllocatorMalloc);
            CFDictionarySetValue(dict, CFSTR("Requirements"), req);
            CFRelease(req);
        }

        if (entitlements && entitlements_size) {
            CFDataRef ent = extract_entitlements_blob(entitlements, entitlements_size);
            free(entitlements);
            require(ent, out);
            CFPropertyListRef entitlements_dict =
                CFPropertyListCreateWithData(kCFAllocatorDefault,
                ent, kCFPropertyListImmutable, NULL, NULL);
            CFRelease(ent);
            require(entitlements_dict, out);
            CFDictionarySetValue(dict, CFSTR("Entitlements"), entitlements_dict);
            CFRelease(entitlements_dict);
        }
    } while (0);

    return dict;
out:
    return NULL;
}

static int
write_data(const char *path, CFDataRef data)
{
    int fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644);
    ssize_t length = CFDataGetLength(data);
    if (fd < 0)
        return -1;
    int bytes_written = write(fd, CFDataGetBytePtr(data), length);
    close(fd);
    CFRelease(data);
    if (bytes_written != length) {
        fprintf(stderr, "failed to write auditing info to %s\n", path);
        unlink(path);
        return -1;
    }
    return 0;

}

static int
write_auditing_data(const char *path, CFMutableDictionaryRef info)
{
    CFTypeRef entitlements = CFDictionaryGetValue(info, CFSTR("Entitlements"));
    if (entitlements) {
        CFDataRef entitlements_xml = CFPropertyListCreateXMLData(kCFAllocatorDefault, entitlements);
        if (!entitlements_xml)
            return -1;
        CFDictionarySetValue(info, CFSTR("Entitlements"), entitlements_xml);
        CFRelease(entitlements_xml);
    }

    CFDataRef plist = CFPropertyListCreateXMLData(kCFAllocatorDefault, info);
    if (!plist)
        return -1;

    return write_data(path, plist); /* consumes plist */
}

static int
write_filtered_entitlements(const char *path, CFDictionaryRef info)
{
    CFDataRef plist = CFPropertyListCreateXMLData(kCFAllocatorDefault, info);
    if (!plist)
        return -1;
    CFDataRef entitlements_blob =
        build_entitlements_blob(CFDataGetBytePtr(plist), CFDataGetLength(plist));
    CFRelease(plist);
    if (!entitlements_blob)
        return -1;
    return write_data(path, entitlements_blob); /* consumes entitlements_blob */

}

static CFDataRef
cfdata_read_file(const char *filename)
{
    int data_file = open(filename, O_RDONLY);
    if (data_file == -1)
        return NULL;
    void *data = NULL;
    ssize_t size = read_fd(data_file, &data);
    if (size > 0)
        return CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
                data, size, kCFAllocatorMalloc);

    return NULL;
}

static CFDictionaryRef
load_profile(const char *profile_path)
{
    SecCmsMessageRef cmsg = NULL;
    CFDictionaryRef entitlements = NULL;
    CFArrayRef certificates = NULL;
    CFDictionaryRef profile = NULL;

    CFDataRef message = cfdata_read_file(profile_path);
    require(message, out);
    SecAsn1Item encoded_message = { CFDataGetLength(message),
        (uint8_t*)CFDataGetBytePtr(message) };
    require_noerr(SecCmsMessageDecode(&encoded_message,
                NULL, NULL, NULL, NULL, NULL, NULL, &cmsg), out);

    /* expected to be a signed data message at the top level */
    SecCmsContentInfoRef cinfo;
    SecCmsSignedDataRef sigd;
    require(cinfo = SecCmsMessageContentLevel(cmsg, 0), out);
    require(SecCmsContentInfoGetContentTypeTag(cinfo) ==
            SEC_OID_PKCS7_SIGNED_DATA, out);
    require(sigd = (SecCmsSignedDataRef)SecCmsContentInfoGetContent(cinfo), out);

    SecPolicyRef policy = NULL;
    SecTrustRef trust = NULL;
    policy = SecPolicyCreateBasicX509();
    int nsigners = SecCmsSignedDataSignerInfoCount(sigd);
    require(nsigners == 1, out);
    require_noerr(SecCmsSignedDataVerifySignerInfo(sigd, 0, NULL, policy, &trust), out);
    SecCertificateRef apple_ca_cert = NULL;
    CFArrayRef apple_ca_cert_anchors = NULL;
    require(apple_ca_cert = SecCertificateCreateWithBytes(NULL, _profile_anchor, sizeof(_profile_anchor)), out);
    require(apple_ca_cert_anchors = CFArrayCreate(kCFAllocatorDefault, (const void **)&apple_ca_cert, 1, NULL), out);
    require_noerr(SecTrustSetAnchorCertificates(trust, apple_ca_cert_anchors), out);
    log("using %s for profile evaluation", _root_ca_name);
    SecTrustResultType trust_result;
    require_noerr(SecTrustEvaluate(trust, &trust_result), out);
#if WWDR
    /* doesn't mean much, but I don't have the root */
    require(trust_result == kSecTrustResultRecoverableTrustFailure, out);
#else
    require(trust_result == kSecTrustResultUnspecified, out);
#endif
    CFRelease(apple_ca_cert_anchors);

    // FIXME require proper intermediate and leaf certs
    // require_noerr(SecCertificateCopyCommonName(SecCertificateRef certificate, CFStringRef *commonName);
    CFRelease(trust);

    CFRelease(policy);
    SecCmsSignerInfoRef sinfo = SecCmsSignedDataGetSignerInfo(sigd, 0);
    require(sinfo, out);
    CFStringRef commonname = SecCmsSignerInfoGetSignerCommonName(sinfo);
    require(commonname, out);
#if WWDR
    require(CFEqual(CFSTR("Alpha Config Profile Signing Certificate"), commonname), out);
#else
    require(CFEqual(CFSTR("Apple iPhone OS Provisioning Profile Signing"), commonname) ||
            CFEqual(CFSTR("TEST Apple iPhone OS Provisioning Profile Signing TEST"), commonname), out);
#endif
    CFRelease(commonname);

    /* attached CMS */
    const SecAsn1Item *content = SecCmsMessageGetContent(cmsg);
    require(content && content->Length && content->Data, out);

    CFDataRef attached_contents = CFDataCreate(kCFAllocatorDefault,
            content->Data, content->Length);
    CFPropertyListRef plist = CFPropertyListCreateWithData(kCFAllocatorDefault,
            attached_contents, kCFPropertyListImmutable, NULL, NULL);
    CFRelease(attached_contents);
    require(plist && CFGetTypeID(plist) == CFDictionaryGetTypeID(), out);

    CFTypeRef profile_certificates = CFDictionaryGetValue(plist, CFSTR("DeveloperCertificates"));
    if (profile_certificates && CFGetTypeID(profile_certificates) == CFArrayGetTypeID())
    {
        certificates = CFArrayCreateCopy(kCFAllocatorDefault, profile_certificates);
#if 0
        CFIndex i, cert_count = CFArrayGetCount(certificates);
        for (i = 0; i < cert_count; i++) {
            SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, CFArrayGetValueAtIndex(certificates, i));
            CFShow(cert);
            CFRelease(cert);
        }
#endif
    }

    CFTypeRef profile_entitlements = CFDictionaryGetValue(plist, CFSTR("Entitlements"));
    if (profile_entitlements && CFGetTypeID(profile_entitlements) == CFDictionaryGetTypeID())
    {
        entitlements = CFDictionaryCreateCopy(kCFAllocatorDefault,
                (CFDictionaryRef)profile_entitlements);
    }
    CFRelease(plist);

out:
    if (cmsg) SecCmsMessageDestroy(cmsg);
    if (entitlements && certificates) {
        const void *keys[] = { CFSTR("Entitlements"), CFSTR("Certificates") };
        const void *vals[] = { entitlements, certificates };
        profile = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2,
            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    }
    if (entitlements) CFRelease(entitlements);
    if (certificates) CFRelease(certificates);
    return profile;
}

typedef struct {
    CFDictionaryRef whitelist;
    CFMutableDictionaryRef filtered_list;
    bool allowed_entitlements;
} filter_whitelist_ctx;

static void
filter_entitlement(const void *key, const void *value,
        filter_whitelist_ctx *ctx)
{
    /* filter out get-task-allow, no error */
    if (CFEqual(key, CFSTR("get-task-allow")))
        return;

    /* whitelist data protection entitlement, otherwise validate */
    if (!CFEqual(key, CFSTR("DataProtectionClass")) && 
            !CFEqual(key, CFSTR("data-protection-class")) &&
            !MISEntitlementDictionaryAllowsEntitlementValue(ctx->whitelist, key, value)) {
        ctx->allowed_entitlements = false;
        cflog("Illegal entitlement key/value pair: %@, %@", key, value);
        return;
    }

    if (ctx->filtered_list)
        CFDictionarySetValue(ctx->filtered_list, key, value);
}

static bool
filter_entitlements(CFDictionaryRef whitelist, CFDictionaryRef entitlements,
    CFMutableDictionaryRef filtered_entitlements)
{
    if (!entitlements)
        return true;

    filter_whitelist_ctx ctx = { whitelist, filtered_entitlements, true };
    CFDictionaryApplyFunction(entitlements,
            (CFDictionaryApplierFunction)filter_entitlement, &ctx);
    return ctx.allowed_entitlements;
}

static SecCertificateRef
cms_verify_signer(CFDataRef message, CFDataRef detached)
{
    SecCertificateRef signer_cert = NULL;
    require(message, out);

    SecPolicyRef policy = NULL;
    SecTrustRef trust = NULL;
    policy = SecPolicyCreateBasicX509();

    SecCmsMessageRef cmsg = NULL;
    SecCmsContentInfoRef cinfo;
    SecCmsSignedDataRef sigd = NULL;

    SecAsn1Item encoded_message = { CFDataGetLength(message), (uint8_t*)CFDataGetBytePtr(message) };
    require_noerr(SecCmsMessageDecode(&encoded_message, NULL, NULL, NULL, NULL, NULL, NULL, &cmsg),
        out);
    /* expected to be a signed data message at the top level */
    require(cinfo = SecCmsMessageContentLevel(cmsg, 0), out);
    require(SecCmsContentInfoGetContentTypeTag(cinfo) == SEC_OID_PKCS7_SIGNED_DATA, out);
    require(sigd = (SecCmsSignedDataRef)SecCmsContentInfoGetContent(cinfo), out);

    if (detached) {
        require(!SecCmsSignedDataHasDigests(sigd), out);
        SECAlgorithmID **digestalgs = SecCmsSignedDataGetDigestAlgs(sigd);
        SecCmsDigestContextRef digcx = SecCmsDigestContextStartMultiple(digestalgs);
        SecCmsDigestContextUpdate(digcx, CFDataGetBytePtr(detached), CFDataGetLength(detached));
        SecCmsSignedDataSetDigestContext(sigd, digcx);
        SecCmsDigestContextDestroy(digcx);
    }

    int nsigners = SecCmsSignedDataSignerInfoCount(sigd);
    require_quiet(nsigners == 1, out);
    require_noerr_string(SecCmsSignedDataVerifySignerInfo(sigd, 0, NULL, policy, &trust), out, "bad signature");

    signer_cert = SecTrustGetCertificateAtIndex(trust, 0);
    CFRetain(signer_cert);
    CFRelease(policy);
    CFRelease(trust);

out:
    return signer_cert;

}

static bool
cms_verify(CFDataRef message, CFDataRef detached, CFArrayRef certificates)
{
    bool result = false;
    SecCertificateRef signer_cert = cms_verify_signer(message, detached);
    require(signer_cert, out);
    if (certificates) {
        CFDataRef cert_cfdata = SecCertificateCopyData(signer_cert);
        CFRange all_certs = CFRangeMake(0, CFArrayGetCount(certificates));
        result = CFArrayContainsValue(certificates, all_certs, cert_cfdata);
        CFRelease(cert_cfdata);
    } else {
        CFArrayRef commonNames = SecCertificateCopyCommonNames(signer_cert);
        require(CFArrayGetCount(commonNames) == 1, out);
        CFStringRef commonName = (CFStringRef)CFArrayGetValueAtIndex(commonNames, 0);
        require(commonName, out);
        result = CFEqual(CFSTR("Apple iPhone OS Application Signing"), commonName)
            || CFEqual(CFSTR("TEST Apple iPhone OS Application Signing TEST"), commonName);
        CFRelease(commonNames);
    }

    if (!result)
        fprintf(stderr, "Disallowed signer\n");

out:
    if (signer_cert) CFRelease(signer_cert);
    return result;

}


static bool
verify_code_signatures(CFArrayRef code_signatures, CFArrayRef certificates)
{
    require(code_signatures, out);
    CFIndex i, signature_count = CFArrayGetCount(code_signatures);

    /* Each slice can have their own entitlements and be properly signed
       but codesign(1) picks the first when listing and smashes that one
       down when re-signing */
    CFDataRef first_entitlement_hash = NULL;
    for (i = 0; i < signature_count; i++) {
        CFDictionaryRef code_signature = CFArrayGetValueAtIndex(code_signatures, i);

        CFDataRef signature = CFDictionaryGetValue(code_signature, CFSTR("SignedData"));
        require(signature, out);
        CFDataRef code_directory = CFDictionaryGetValue(code_signature, CFSTR("CodeDirectory"));
        require(code_directory, out);
        CFDataRef entitlements = CFDictionaryGetValue(code_signature, CFSTR("Entitlements"));
        CFDataRef entitlements_hash = CFDictionaryGetValue(code_signature, CFSTR("EntitlementsHash"));
        CFDataRef entitlements_cdhash = CFDictionaryGetValue(code_signature, CFSTR("EntitlementsCDHash"));
        require(entitlements, out);
        require(entitlements_hash, out);
        require(entitlements_cdhash, out);
        require(CFEqual(entitlements_hash, entitlements_cdhash), out);

        if (!first_entitlement_hash)
            first_entitlement_hash = entitlements_hash;
        else
            require(entitlements_hash && CFEqual(first_entitlement_hash, entitlements_hash), out);

        /* was the application signed by a certificate in the profile */
        require(cms_verify(signature, code_directory, certificates), out);
    }
    return true;
out:
    return false;
}


static void
init()
{
    signal(SIGHUP, pass_signal_to_children);
    signal(SIGINT, pass_signal_to_children);
    signal(SIGTERM, pass_signal_to_children);
    //signal(SIGCHLD, SIG_IGN);
    signal(SIGALRM, child_timeout);

    const char *codesign_binary_env = getenv("CODESIGN");
    if (codesign_binary_env)
        codesign_binary = strdup(codesign_binary_env);

    const char *processing_path_env = getenv("PROCESS_PATH");
    if (processing_path_env)
        processing_path = strdup(processing_path_env);

    processing_prefix = calloc(1, strlen(processing_path) +
            strlen(processing_file) + 1/*'/'*/ + 1/*'\0'*/);
    strcat(processing_prefix, processing_path);
    strcat(processing_prefix, "/");
    strcat(processing_prefix, processing_file);

    audition_plist_path = calloc(1, strlen(processing_prefix) +
            strlen(auditing_postfix) + 1);
    strcat(audition_plist_path, processing_prefix);
    strcat(audition_plist_path, auditing_postfix);

    entitlements_plist_path = calloc(1, strlen(processing_prefix) +
            strlen(entitlements_postfix) + 1);
    strcat(entitlements_plist_path, processing_prefix);
    strcat(entitlements_plist_path, entitlements_postfix);

}


const struct option options[] = {
    { "sign", required_argument, NULL, 's' },
    { "entitlements", required_argument, NULL, 'z' },
    { "no-profile", no_argument, NULL, 'Z' },
    { "verify", no_argument, NULL, 'V' }, /* map to V to let verbose v pass */
    { "timeout", required_argument, NULL, 't' },
    {}
};

struct securityd *gSecurityd;
void securityd_init();
CFArrayRef SecAccessGroupsGetCurrent(void);

CFArrayRef SecAccessGroupsGetCurrent(void) {
    return NULL;
}

OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out);
OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out)
{
    return -1;
}



#ifndef UNIT_TESTING
int
main(int argc, char *argv[])
{
    int err = 0;

    int ch;
    bool sign_op = false, noprofile = false,
        verify_op = false;
    int timeout = 180;

    securityd_init();

    while ((ch = getopt_long(argc, argv, "fvr:s:R:", options, NULL)) != -1)
    {
        switch (ch) {
            case 's': sign_op = true; break;
            case 'z': { log("codesign_wrapper reserves the entitlements option for itself");
                        exit(1); /* XXX load entitlements from optarg */
                        break; }
            case 'Z': noprofile = true; break;
            case 'V': verify_op = true; break;
            case 't': timeout = atoi(optarg); break;
        }
    }
    int arg_index_files = optind;
    if ((!sign_op && !verify_op) || arg_index_files == argc) {
        log("not a signing/verify operation, or no file to sign given");
        return 1; /* short circuit to codesign binary: not signing, no files */
    }
    if (arg_index_files + 1 != argc) {
        log("cannot sign more than one file in an operation");
        return 1; /* we don't do more than one file at a time, so we can rejigger */
    }

    init();
    if (mk_temp_dir(processing_path)) {
        log("failed to create directory %s", processing_path);
        return 1;
    }

    CFMutableDictionaryRef auditing_info =
        dump_auditing_info(argv[arg_index_files]);
    if (!auditing_info) {
        log("failed to extract auditing_info from %s", argv[arg_index_files]);
        return 1;
    }

    /* load up entitlements requested */
    CFDictionaryRef entitlements_requested =
        CFDictionaryGetValue(auditing_info, CFSTR("Entitlements"));
    require_string(entitlements_requested, out, "At least need an application-identifier entitlements");
    CFMutableDictionaryRef allowable_entitlements = NULL;

    if (noprofile) {
        /* XXX if (verify_op) require it to be store signed */
        if (verify_op) {
            /* load the code signature */
            CFArrayRef code_signatures =
                load_code_signatures(argv[arg_index_files]);
            require(code_signatures, out);
            require(verify_code_signatures(code_signatures, NULL), out);
            CFRelease(code_signatures);
        }

        if (sign_op) {
            /* do the same checks, pass signed in entitlements along for audit */
            require(CFDictionaryGetValue(entitlements_requested, 
                                         CFSTR("application-identifier")), out);

             CFDictionarySetValue(auditing_info, CFSTR("Entitlements"),
                                  entitlements_requested);

             allowable_entitlements = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, entitlements_requested);

             /* For the 2-pass signing, where the app is signed first, then encrypted
                and then resigned, we need to by pass the initial validation, so we
                allow signing without checking the entitlements to the profile. */
#if 0
            log("You shouldn't want to sign without a profile.");
            exit(1);
#endif
        }

    } else {
        /* load up the profile */
        char profile_path[_POSIX_PATH_MAX] = {};
        snprintf(profile_path, sizeof(profile_path), "%s/embedded.mobileprovision", argv[arg_index_files]);
        CFDictionaryRef profile = load_profile(profile_path);
        require_action(profile, out, log("Failed to load provision profile from: %s", profile_path));
        CFDictionaryRef entitlements_whitelist = CFDictionaryGetValue(profile, CFSTR("Entitlements"));
        require(entitlements_whitelist, out);
        CFArrayRef certificates = CFDictionaryGetValue(profile, CFSTR("Certificates"));
        require(certificates, out);

        if (sign_op)
            require_noerr(unlink(profile_path), out);

        /* only allow identifiers whitelisted by profile */
        allowable_entitlements = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        require(allowable_entitlements, out);
        require(filter_entitlements(entitlements_whitelist,
                    entitlements_requested, allowable_entitlements), out);

        /* must have valid application-identifier */
        require(CFDictionaryGetValue(allowable_entitlements,
            CFSTR("application-identifier")), out);

        CFDictionarySetValue(auditing_info, CFSTR("Entitlements"),
            allowable_entitlements);

        if (verify_op) {
            /* load the code signature */
            CFArrayRef code_signatures =
                load_code_signatures(argv[arg_index_files]);
            require(code_signatures, out);
            require(verify_code_signatures(code_signatures, certificates), out);
            CFRelease(code_signatures);
        }
    }

    char *lock_filename = NULL;

    if (sign_op) {
        if (!asprintf(&lock_filename, "%s.lock", processing_prefix)) {
            log("failed to alloc %s.lock", processing_prefix);
            return 1;
        }

        while (lock_file(processing_prefix, lock_filename)) {
            log("waiting for lock");
            sleep(1);
        }

        err = write_auditing_data(audition_plist_path, auditing_info);

        if (!err && allowable_entitlements) {
            err |= write_filtered_entitlements(entitlements_plist_path, allowable_entitlements);
        }

        if (err)
            log("failed to write auditing data");
    }

    if (!err) {
        char *orig_args[argc+1+2];
        /* size_t argv_size = argc * sizeof(*argv); args = malloc(argv_size); */
        memcpy(orig_args, argv, (argc-1) * sizeof(*argv));

        int arg = 0, argo = 0;
        while (arg < argc - 1) {
            if (strcmp("--no-profile", orig_args[arg]) &&
                strncmp("--timeout", orig_args[arg], strlen("--timeout"))) {
                orig_args[argo] = argv[arg];
                argo++;
            }
            arg++;
        }
        if (entitlements_requested && allowable_entitlements) {
            orig_args[argo++] = "--entitlements";
            orig_args[argo++] = entitlements_plist_path;
        }
        orig_args[argo++] = argv[arg_index_files];
        orig_args[argo++] = NULL;
        orig_args[0] = codesign_binary;
#if DEBUG
        log("Caling codesign with the following args:");
        int ix;
        for(ix = 0; ix <= argc; ix++)
            log("   %s", orig_args[ix] ? orig_args[ix] : "NULL");
#endif
        err = fork_child_timeout(NULL, NULL, (const char * const *)orig_args, timeout);
    }

    if (sign_op) {
        unlink(audition_plist_path);
        unlink(entitlements_plist_path);

        free(audition_plist_path);
        free(entitlements_plist_path);

        if (err == -2) {
            log("executing codesign(1) timed out");
            const char * const kill_tokens[] = { "/usr/bin/killall", "Ingrian", NULL };
            fork_child_timeout(close_all_fd, NULL, kill_tokens, 0);
            const char * const load_tokens[] = { "/usr/bin/killall", "-USR2", "securityd", NULL };
            fork_child_timeout(close_all_fd, NULL, load_tokens, 0);
        }

        unlink(lock_filename);
        free(lock_filename);

        if (err == -2) {
            sleep(10);
            log("delayed exit with timeout return value now we've tried to reload tokens");
            return 2;
        }
    }

    if (!err)
        return 0;
    else
        log("failed to execute codesign(1)");
out:
    return 1;
}
#endif /* UNIT_TESTING */

/* vim: set et : set sw=4 : set ts=4 : set sts=4 : */