1/*
2   cc -g -O0 -Wall -Werror -Wmost -stabs -o codesign_wrapper codesign_wrapper.c -framework CoreFoundation
3 */
4
5#include <paths.h>
6#include <stdlib.h>
7#include <stdio.h>
8#include <fcntl.h>
9#include <unistd.h>
10#include <errno.h>
11#include <string.h>
12#include <stdlib.h>
13#include <assert.h>
14#include <signal.h>
15#include <sys/stat.h>
16#include <sys/socket.h>
17#include <getopt.h>
18#include <stdbool.h>
19#include <limits.h>
20
21#include <CoreFoundation/CFData.h>
22#include <CoreFoundation/CFDictionary.h>
23#include <CoreFoundation/CFPropertyList.h>
24#include <CoreFoundation/CFString.h>
25
26/* for CMS */
27#include <Security/SecCmsMessage.h>
28#include <Security/SecCmsSignedData.h>
29#include <Security/SecCmsContentInfo.h>
30#include <Security/SecCmsSignerInfo.h>
31#include <Security/SecCmsEncoder.h>
32#include <Security/SecCmsDecoder.h>
33#include <Security/SecCmsDigestContext.h>
34#include <Security/oidsalg.h>
35#include <Security/cmspriv.h>
36
37#include <Security/SecCMS.h>
38
39#include <Security/SecPolicy.h>
40#include <Security/SecCertificate.h>
41#include <Security/SecCertificatePriv.h>
42
43/* entitlement whitelist checking */
44#include <MISEntitlement.h>
45
46#define DEBUG_ASSERT_PRODUCTION_CODE 0
47#include <AssertMacros.h>
48
49#include "codesign_wrapper.h"
50#include "codesign.h"
51
52extern bool do_verify(const char *path, CFArrayRef certificates);
53
54static char * codesign_binary = "/usr/bin/codesign";
55static char * processing_path = "/var/tmp/signingbox";
56static char * processing_file = "codesign_wrapper";
57static char * processing_prefix = NULL;
58static char * auditing_postfix = "_auditing.plist";
59static char * audition_plist_path = NULL;
60static char * entitlements_plist_path = NULL;
61static char * entitlements_postfix = "_entitlements.plist";
62
63
64#define CODESIGN_WRAPPER_VERSION "0.7.10"
65#define log(format, args...)    \
66    fprintf(stderr, "codesign_wrapper-" CODESIGN_WRAPPER_VERSION ": " format "\n", ##args);
67#define cflog(format, args...) do { \
68CFStringRef logstr = CFStringCreateWithFormat(NULL, NULL, CFSTR(format), ##args);\
69if (logstr) { CFShow(logstr); CFRelease(logstr); } \
70} while(0); \
71
72const char *_root_ca_name = ANCHOR;
73
74static pid_t kill_child = -1;
75static void child_timeout(int sig)
76{
77    if (kill_child != -1) {
78        kill(kill_child, sig);
79        kill_child = -1;
80    }
81}
82
83static void
84close_all_fd(void *arg __unused)
85/* close down any files that might have been open at this point
86   but make sure 0, 1 and 2 are set to /dev/null so they don't
87   get reused */
88{
89    int maxDescriptors = getdtablesize ();
90    int i;
91
92    int devnull = open(_PATH_DEVNULL, O_RDWR, 0);
93
94    if (devnull >= 0) for (i = 0; i < 3; ++i)
95        dup2(devnull, i);
96
97    for (i = 3; i < maxDescriptors; ++i)
98        close (i);
99}
100
101
102static pid_t
103fork_child(void (*pre_exec)(void *arg), void *pre_exec_arg,
104        const char * const argv[])
105{
106    unsigned delay = 1, maxDelay = 60;
107    for (;;) {
108        pid_t pid;
109        switch (pid = fork()) {
110            case -1: /* fork failed */
111                switch (errno) {
112                    case EINTR:
113                        continue; /* no problem */
114                    case EAGAIN:
115                        if (delay < maxDelay) {
116                            sleep(delay);
117                            delay *= 2;
118                            continue;
119                        }
120                        /* fall through */
121                    default:
122                        perror("fork");
123                        return -1;
124                }
125                assert(-1); /* unreached */
126
127            case 0: /* child */
128                if (pre_exec)
129                    pre_exec(pre_exec_arg);
130                execv(argv[0], (char * const *)argv);
131                perror("execv");
132                _exit(1);
133
134            default: /* parent */
135                return pid;
136                break;
137        }
138        break;
139    }
140    return -1;
141}
142
143
144static int
145fork_child_timeout(void (*pre_exec)(), char *pre_exec_arg,
146        const char * const argv[], int timeout)
147{
148    int exit_status = -1;
149    pid_t child_pid = fork_child(pre_exec, pre_exec_arg, argv);
150    if (timeout) {
151        kill_child = child_pid;
152        alarm(timeout);
153    }
154    while (1) {
155        int err = wait4(child_pid, &exit_status, 0, NULL);
156        if (err == -1) {
157            perror("wait4");
158            if (errno == EINTR)
159                continue;
160        }
161        if (err == child_pid) {
162            if (WIFSIGNALED(exit_status)) {
163                log("child %d received signal %d", child_pid, WTERMSIG(exit_status));
164                kill(child_pid, SIGHUP);
165                return -2;
166            }
167            if (WIFEXITED(exit_status))
168                return WEXITSTATUS(exit_status);
169            return -1;
170        }
171    }
172}
173
174
175static void
176dup_io(int arg[])
177{
178    dup2(arg[0], arg[1]);
179    close(arg[0]);
180}
181
182
183static int
184fork_child_timeout_output(int child_fd, int *parent_fd, const char * const argv[], int timeout)
185{
186    int output[2];
187    if (socketpair(AF_UNIX, SOCK_STREAM, 0, output))
188        return -1;
189    fcntl(output[1], F_SETFD, 1); /* close in child */
190    int redirect_child[] = { output[0], child_fd };
191    int err = fork_child_timeout(dup_io, (void*)redirect_child, argv, timeout);
192    if (!err) {
193        close(output[0]); /* close the child side in the parent */
194        *parent_fd = output[1];
195    }
196    return err;
197}
198
199
200static void
201pass_signal_to_children(int sig)
202{
203    signal(sig, SIG_DFL);
204    kill(0, sig);
205}
206
207
208static int
209mk_temp_dir(const char *path)
210{
211    char *pos = NULL, *tmp_path = strdup(path);
212    if (!path) return -1;
213    pos = index(tmp_path, '/');
214    if (!pos) return -1;
215    while ((pos = index(pos + 1, '/'))) {
216        *pos = '\0';
217        if ((0 != mkdir(tmp_path, 0755)) &&
218                errno != EEXIST)
219            return -1;
220        *pos = '/';
221    }
222    if ((0 != mkdir(tmp_path, 0755)) &&
223            errno != EEXIST)
224        return -1;
225    return 0;
226}
227
228
229static int
230lock_file(const char *lock_file_prefix, const char *lock_filename)
231{
232    int err = -1;
233    pid_t pid;
234    char *tempfile = NULL;
235    do {
236        if (!asprintf(&tempfile, "%s.%d", lock_file_prefix, getpid()))
237            break;
238        FILE *temp = fopen(tempfile, "w");
239        if (temp == NULL)
240            break;
241        if (fprintf(temp, "%d\n", getpid()) <= 0)
242            break;
243        fclose(temp);
244        if(!link(tempfile, lock_filename)) {
245            unlink(tempfile);
246            err = 0;
247            break;
248        }
249        FILE* lock = fopen(lock_filename, "r");
250        if (lock == NULL)
251            break;
252        if (fscanf(lock, "%d\n", &pid) <= 0)
253            break;
254        if (kill(pid, 0)) {
255            if (!unlink(lock_filename) &&
256                    !link(tempfile, lock_filename)) {
257                unlink(tempfile);
258                err = 0;
259                break;
260            }
261        }
262    } while(0);
263    unlink(tempfile);
264    if (tempfile)
265        free(tempfile);
266
267    return err;
268}
269
270
271static ssize_t
272read_fd(int fd, void **buffer)
273{
274    int err = -1;
275    size_t capacity = 1024;
276    char * data = malloc(capacity);
277    size_t size = 0;
278    while (1) {
279        int bytes_left = capacity - size;
280        int bytes_read = read(fd, data + size, bytes_left);
281        if (bytes_read >= 0) {
282            size += bytes_read;
283            if (capacity == size) {
284                capacity *= 2;
285                data = realloc(data, capacity);
286                if (!data) {
287                    err = -1;
288                    break;
289                }
290                continue;
291            }
292            err = 0;
293        } else
294            err = -1;
295        break;
296    }
297    if (0 == size) {
298        if (data)
299            free(data);
300        return err;
301    }
302
303    *buffer = data;
304    return size;
305}
306
307enum { CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171 };
308
309typedef struct {
310    uint32_t type;
311    uint32_t offset;
312} cs_blob_index;
313
314static CFDataRef
315extract_entitlements_blob(const uint8_t *data, size_t length)
316{
317    CFDataRef entitlements = NULL;
318    cs_blob_index *csbi = (cs_blob_index *)data;
319
320    require(data && length, out);
321    require(csbi->type == ntohl(CSMAGIC_EMBEDDED_ENTITLEMENTS), out);
322    require(length == ntohl(csbi->offset), out);
323    entitlements = CFDataCreate(kCFAllocatorDefault,
324        (uint8_t*)(data + sizeof(cs_blob_index)),
325        (CFIndex)(length - sizeof(cs_blob_index)));
326out:
327    return entitlements;
328}
329
330static CFDataRef
331build_entitlements_blob(const uint8_t *data, size_t length)
332{
333    cs_blob_index csbi = { htonl(CSMAGIC_EMBEDDED_ENTITLEMENTS),
334        htonl(length+sizeof(csbi)) };
335    CFMutableDataRef blob = CFDataCreateMutable(kCFAllocatorDefault, sizeof(csbi)+length);
336    if (data) {
337        CFDataAppendBytes(blob, (uint8_t*)&csbi, sizeof(csbi));
338        CFDataAppendBytes(blob, data, length);
339    }
340    return blob;
341}
342
343static CFMutableDictionaryRef
344dump_auditing_info(const char *path)
345{
346    int exit_status;
347    CFMutableDictionaryRef dict = NULL;
348    void *requirements = NULL;
349    ssize_t requirements_size = 0;
350    void *entitlements = NULL;
351    ssize_t entitlements_size = 0;
352
353    do {
354        const char * const extract_requirements[] =
355        { codesign_binary, "--display", "-v", "-v", path, NULL };
356        int requirements_fd;
357        if ((exit_status = fork_child_timeout_output(STDERR_FILENO, &requirements_fd,
358                        extract_requirements, 0))) {
359            fprintf(stderr, "failed to extract requirements data: %d\n", exit_status);
360            break;
361        }
362
363        requirements_size = read_fd(requirements_fd, &requirements);
364        if (requirements_size == -1)
365            break;
366        close(requirements_fd);
367
368    } while(0);
369
370    do {
371        const char * const extract_entitlements[] =
372        { codesign_binary, "--display", "--entitlements", "-", path, NULL };
373        int entitlements_fd;
374        if ((exit_status = fork_child_timeout_output(STDOUT_FILENO, &entitlements_fd,
375                        extract_entitlements, 0))) {
376            fprintf(stderr, "failed to extract entitlements: %d\n", exit_status);
377            break;
378        }
379
380        entitlements_size = read_fd(entitlements_fd, &entitlements);
381        if (entitlements_size == -1)
382            break;
383        close(entitlements_fd);
384
385    } while(0);
386
387    do {
388        dict = CFDictionaryCreateMutable(
389                kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
390                &kCFTypeDictionaryValueCallBacks);
391
392        if (requirements && requirements_size) {
393            CFDataRef req = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
394                requirements, requirements_size, kCFAllocatorMalloc);
395            CFDictionarySetValue(dict, CFSTR("Requirements"), req);
396            CFRelease(req);
397        }
398
399        if (entitlements && entitlements_size) {
400            CFDataRef ent = extract_entitlements_blob(entitlements, entitlements_size);
401            free(entitlements);
402            require(ent, out);
403            CFPropertyListRef entitlements_dict =
404                CFPropertyListCreateWithData(kCFAllocatorDefault,
405                ent, kCFPropertyListImmutable, NULL, NULL);
406            CFRelease(ent);
407            require(entitlements_dict, out);
408            CFDictionarySetValue(dict, CFSTR("Entitlements"), entitlements_dict);
409            CFRelease(entitlements_dict);
410        }
411    } while (0);
412
413    return dict;
414out:
415    return NULL;
416}
417
418static int
419write_data(const char *path, CFDataRef data)
420{
421    int fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644);
422    ssize_t length = CFDataGetLength(data);
423    if (fd < 0)
424        return -1;
425    int bytes_written = write(fd, CFDataGetBytePtr(data), length);
426    close(fd);
427    CFRelease(data);
428    if (bytes_written != length) {
429        fprintf(stderr, "failed to write auditing info to %s\n", path);
430        unlink(path);
431        return -1;
432    }
433    return 0;
434
435}
436
437static int
438write_auditing_data(const char *path, CFMutableDictionaryRef info)
439{
440    CFTypeRef entitlements = CFDictionaryGetValue(info, CFSTR("Entitlements"));
441    if (entitlements) {
442        CFDataRef entitlements_xml = CFPropertyListCreateXMLData(kCFAllocatorDefault, entitlements);
443        if (!entitlements_xml)
444            return -1;
445        CFDictionarySetValue(info, CFSTR("Entitlements"), entitlements_xml);
446        CFRelease(entitlements_xml);
447    }
448
449    CFDataRef plist = CFPropertyListCreateXMLData(kCFAllocatorDefault, info);
450    if (!plist)
451        return -1;
452
453    return write_data(path, plist); /* consumes plist */
454}
455
456static int
457write_filtered_entitlements(const char *path, CFDictionaryRef info)
458{
459    CFDataRef plist = CFPropertyListCreateXMLData(kCFAllocatorDefault, info);
460    if (!plist)
461        return -1;
462    CFDataRef entitlements_blob =
463        build_entitlements_blob(CFDataGetBytePtr(plist), CFDataGetLength(plist));
464    CFRelease(plist);
465    if (!entitlements_blob)
466        return -1;
467    return write_data(path, entitlements_blob); /* consumes entitlements_blob */
468
469}
470
471static CFDataRef
472cfdata_read_file(const char *filename)
473{
474    int data_file = open(filename, O_RDONLY);
475    if (data_file == -1)
476        return NULL;
477    void *data = NULL;
478    ssize_t size = read_fd(data_file, &data);
479    if (size > 0)
480        return CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
481                data, size, kCFAllocatorMalloc);
482
483    return NULL;
484}
485
486static CFDictionaryRef
487load_profile(const char *profile_path)
488{
489    SecCmsMessageRef cmsg = NULL;
490    CFDictionaryRef entitlements = NULL;
491    CFArrayRef certificates = NULL;
492    CFDictionaryRef profile = NULL;
493
494    CFDataRef message = cfdata_read_file(profile_path);
495    require(message, out);
496    SecAsn1Item encoded_message = { CFDataGetLength(message),
497        (uint8_t*)CFDataGetBytePtr(message) };
498    require_noerr(SecCmsMessageDecode(&encoded_message,
499                NULL, NULL, NULL, NULL, NULL, NULL, &cmsg), out);
500
501    /* expected to be a signed data message at the top level */
502    SecCmsContentInfoRef cinfo;
503    SecCmsSignedDataRef sigd;
504    require(cinfo = SecCmsMessageContentLevel(cmsg, 0), out);
505    require(SecCmsContentInfoGetContentTypeTag(cinfo) ==
506            SEC_OID_PKCS7_SIGNED_DATA, out);
507    require(sigd = (SecCmsSignedDataRef)SecCmsContentInfoGetContent(cinfo), out);
508
509    SecPolicyRef policy = NULL;
510    SecTrustRef trust = NULL;
511    policy = SecPolicyCreateBasicX509();
512    int nsigners = SecCmsSignedDataSignerInfoCount(sigd);
513    require(nsigners == 1, out);
514    require_noerr(SecCmsSignedDataVerifySignerInfo(sigd, 0, NULL, policy, &trust), out);
515    SecCertificateRef apple_ca_cert = NULL;
516    CFArrayRef apple_ca_cert_anchors = NULL;
517    require(apple_ca_cert = SecCertificateCreateWithBytes(NULL, _profile_anchor, sizeof(_profile_anchor)), out);
518    require(apple_ca_cert_anchors = CFArrayCreate(kCFAllocatorDefault, (const void **)&apple_ca_cert, 1, NULL), out);
519    require_noerr(SecTrustSetAnchorCertificates(trust, apple_ca_cert_anchors), out);
520    log("using %s for profile evaluation", _root_ca_name);
521    SecTrustResultType trust_result;
522    require_noerr(SecTrustEvaluate(trust, &trust_result), out);
523#if WWDR
524    /* doesn't mean much, but I don't have the root */
525    require(trust_result == kSecTrustResultRecoverableTrustFailure, out);
526#else
527    require(trust_result == kSecTrustResultUnspecified, out);
528#endif
529    CFRelease(apple_ca_cert_anchors);
530
531    // FIXME require proper intermediate and leaf certs
532    // require_noerr(SecCertificateCopyCommonName(SecCertificateRef certificate, CFStringRef *commonName);
533    CFRelease(trust);
534
535    CFRelease(policy);
536    SecCmsSignerInfoRef sinfo = SecCmsSignedDataGetSignerInfo(sigd, 0);
537    require(sinfo, out);
538    CFStringRef commonname = SecCmsSignerInfoGetSignerCommonName(sinfo);
539    require(commonname, out);
540#if WWDR
541    require(CFEqual(CFSTR("Alpha Config Profile Signing Certificate"), commonname), out);
542#else
543    require(CFEqual(CFSTR("Apple iPhone OS Provisioning Profile Signing"), commonname) ||
544            CFEqual(CFSTR("TEST Apple iPhone OS Provisioning Profile Signing TEST"), commonname), out);
545#endif
546    CFRelease(commonname);
547
548    /* attached CMS */
549    const SecAsn1Item *content = SecCmsMessageGetContent(cmsg);
550    require(content && content->Length && content->Data, out);
551
552    CFDataRef attached_contents = CFDataCreate(kCFAllocatorDefault,
553            content->Data, content->Length);
554    CFPropertyListRef plist = CFPropertyListCreateWithData(kCFAllocatorDefault,
555            attached_contents, kCFPropertyListImmutable, NULL, NULL);
556    CFRelease(attached_contents);
557    require(plist && CFGetTypeID(plist) == CFDictionaryGetTypeID(), out);
558
559    CFTypeRef profile_certificates = CFDictionaryGetValue(plist, CFSTR("DeveloperCertificates"));
560    if (profile_certificates && CFGetTypeID(profile_certificates) == CFArrayGetTypeID())
561    {
562        certificates = CFArrayCreateCopy(kCFAllocatorDefault, profile_certificates);
563#if 0
564        CFIndex i, cert_count = CFArrayGetCount(certificates);
565        for (i = 0; i < cert_count; i++) {
566            SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorDefault, CFArrayGetValueAtIndex(certificates, i));
567            CFShow(cert);
568            CFRelease(cert);
569        }
570#endif
571    }
572
573    CFTypeRef profile_entitlements = CFDictionaryGetValue(plist, CFSTR("Entitlements"));
574    if (profile_entitlements && CFGetTypeID(profile_entitlements) == CFDictionaryGetTypeID())
575    {
576        entitlements = CFDictionaryCreateCopy(kCFAllocatorDefault,
577                (CFDictionaryRef)profile_entitlements);
578    }
579    CFRelease(plist);
580
581out:
582    if (cmsg) SecCmsMessageDestroy(cmsg);
583    if (entitlements && certificates) {
584        const void *keys[] = { CFSTR("Entitlements"), CFSTR("Certificates") };
585        const void *vals[] = { entitlements, certificates };
586        profile = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2,
587            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
588    }
589    if (entitlements) CFRelease(entitlements);
590    if (certificates) CFRelease(certificates);
591    return profile;
592}
593
594typedef struct {
595    CFDictionaryRef whitelist;
596    CFMutableDictionaryRef filtered_list;
597    bool allowed_entitlements;
598} filter_whitelist_ctx;
599
600static void
601filter_entitlement(const void *key, const void *value,
602        filter_whitelist_ctx *ctx)
603{
604    /* filter out get-task-allow, no error */
605    if (CFEqual(key, CFSTR("get-task-allow")))
606        return;
607
608    /* whitelist data protection entitlement, otherwise validate */
609    if (!CFEqual(key, CFSTR("DataProtectionClass")) &&
610            !CFEqual(key, CFSTR("data-protection-class")) &&
611            !MISEntitlementDictionaryAllowsEntitlementValue(ctx->whitelist, key, value)) {
612        ctx->allowed_entitlements = false;
613        cflog("Illegal entitlement key/value pair: %@, %@", key, value);
614        return;
615    }
616
617    if (ctx->filtered_list)
618        CFDictionarySetValue(ctx->filtered_list, key, value);
619}
620
621static bool
622filter_entitlements(CFDictionaryRef whitelist, CFDictionaryRef entitlements,
623    CFMutableDictionaryRef filtered_entitlements)
624{
625    if (!entitlements)
626        return true;
627
628    filter_whitelist_ctx ctx = { whitelist, filtered_entitlements, true };
629    CFDictionaryApplyFunction(entitlements,
630            (CFDictionaryApplierFunction)filter_entitlement, &ctx);
631    return ctx.allowed_entitlements;
632}
633
634static SecCertificateRef
635cms_verify_signer(CFDataRef message, CFDataRef detached)
636{
637    SecCertificateRef signer_cert = NULL;
638    require(message, out);
639
640    SecPolicyRef policy = NULL;
641    SecTrustRef trust = NULL;
642    policy = SecPolicyCreateBasicX509();
643
644    SecCmsMessageRef cmsg = NULL;
645    SecCmsContentInfoRef cinfo;
646    SecCmsSignedDataRef sigd = NULL;
647
648    SecAsn1Item encoded_message = { CFDataGetLength(message), (uint8_t*)CFDataGetBytePtr(message) };
649    require_noerr(SecCmsMessageDecode(&encoded_message, NULL, NULL, NULL, NULL, NULL, NULL, &cmsg),
650        out);
651    /* expected to be a signed data message at the top level */
652    require(cinfo = SecCmsMessageContentLevel(cmsg, 0), out);
653    require(SecCmsContentInfoGetContentTypeTag(cinfo) == SEC_OID_PKCS7_SIGNED_DATA, out);
654    require(sigd = (SecCmsSignedDataRef)SecCmsContentInfoGetContent(cinfo), out);
655
656    if (detached) {
657        require(!SecCmsSignedDataHasDigests(sigd), out);
658        SECAlgorithmID **digestalgs = SecCmsSignedDataGetDigestAlgs(sigd);
659        SecCmsDigestContextRef digcx = SecCmsDigestContextStartMultiple(digestalgs);
660        SecCmsDigestContextUpdate(digcx, CFDataGetBytePtr(detached), CFDataGetLength(detached));
661        SecCmsSignedDataSetDigestContext(sigd, digcx);
662        SecCmsDigestContextDestroy(digcx);
663    }
664
665    int nsigners = SecCmsSignedDataSignerInfoCount(sigd);
666    require_quiet(nsigners == 1, out);
667    require_noerr_string(SecCmsSignedDataVerifySignerInfo(sigd, 0, NULL, policy, &trust), out, "bad signature");
668
669    signer_cert = SecTrustGetCertificateAtIndex(trust, 0);
670    CFRetain(signer_cert);
671    CFRelease(policy);
672    CFRelease(trust);
673
674out:
675    return signer_cert;
676
677}
678
679static bool
680cms_verify(CFDataRef message, CFDataRef detached, CFArrayRef certificates)
681{
682    bool result = false;
683    SecCertificateRef signer_cert = cms_verify_signer(message, detached);
684    require(signer_cert, out);
685    if (certificates) {
686        CFDataRef cert_cfdata = SecCertificateCopyData(signer_cert);
687        CFRange all_certs = CFRangeMake(0, CFArrayGetCount(certificates));
688        result = CFArrayContainsValue(certificates, all_certs, cert_cfdata);
689        CFRelease(cert_cfdata);
690    } else {
691        CFArrayRef commonNames = SecCertificateCopyCommonNames(signer_cert);
692        require(CFArrayGetCount(commonNames) == 1, out);
693        CFStringRef commonName = (CFStringRef)CFArrayGetValueAtIndex(commonNames, 0);
694        require(commonName, out);
695        result = CFEqual(CFSTR("Apple iPhone OS Application Signing"), commonName)
696            || CFEqual(CFSTR("TEST Apple iPhone OS Application Signing TEST"), commonName);
697        CFRelease(commonNames);
698    }
699
700    if (!result)
701        fprintf(stderr, "Disallowed signer\n");
702
703out:
704    if (signer_cert) CFRelease(signer_cert);
705    return result;
706
707}
708
709
710static bool
711verify_code_signatures(CFArrayRef code_signatures, CFArrayRef certificates)
712{
713    require(code_signatures, out);
714    CFIndex i, signature_count = CFArrayGetCount(code_signatures);
715
716    /* Each slice can have their own entitlements and be properly signed
717       but codesign(1) picks the first when listing and smashes that one
718       down when re-signing */
719    CFDataRef first_entitlement_hash = NULL;
720    for (i = 0; i < signature_count; i++) {
721        CFDictionaryRef code_signature = CFArrayGetValueAtIndex(code_signatures, i);
722
723        CFDataRef signature = CFDictionaryGetValue(code_signature, CFSTR("SignedData"));
724        require(signature, out);
725        CFDataRef code_directory = CFDictionaryGetValue(code_signature, CFSTR("CodeDirectory"));
726        require(code_directory, out);
727        CFDataRef entitlements = CFDictionaryGetValue(code_signature, CFSTR("Entitlements"));
728        CFDataRef entitlements_hash = CFDictionaryGetValue(code_signature, CFSTR("EntitlementsHash"));
729        CFDataRef entitlements_cdhash = CFDictionaryGetValue(code_signature, CFSTR("EntitlementsCDHash"));
730        require(entitlements, out);
731        require(entitlements_hash, out);
732        require(entitlements_cdhash, out);
733        require(CFEqual(entitlements_hash, entitlements_cdhash), out);
734
735        if (!first_entitlement_hash)
736            first_entitlement_hash = entitlements_hash;
737        else
738            require(entitlements_hash && CFEqual(first_entitlement_hash, entitlements_hash), out);
739
740        /* was the application signed by a certificate in the profile */
741        require(cms_verify(signature, code_directory, certificates), out);
742    }
743    return true;
744out:
745    return false;
746}
747
748
749static void
750init()
751{
752    signal(SIGHUP, pass_signal_to_children);
753    signal(SIGINT, pass_signal_to_children);
754    signal(SIGTERM, pass_signal_to_children);
755    //signal(SIGCHLD, SIG_IGN);
756    signal(SIGALRM, child_timeout);
757
758    const char *codesign_binary_env = getenv("CODESIGN");
759    if (codesign_binary_env)
760        codesign_binary = strdup(codesign_binary_env);
761
762    const char *processing_path_env = getenv("PROCESS_PATH");
763    if (processing_path_env)
764        processing_path = strdup(processing_path_env);
765
766    processing_prefix = calloc(1, strlen(processing_path) +
767            strlen(processing_file) + 1/*'/'*/ + 1/*'\0'*/);
768    strcat(processing_prefix, processing_path);
769    strcat(processing_prefix, "/");
770    strcat(processing_prefix, processing_file);
771
772    audition_plist_path = calloc(1, strlen(processing_prefix) +
773            strlen(auditing_postfix) + 1);
774    strcat(audition_plist_path, processing_prefix);
775    strcat(audition_plist_path, auditing_postfix);
776
777    entitlements_plist_path = calloc(1, strlen(processing_prefix) +
778            strlen(entitlements_postfix) + 1);
779    strcat(entitlements_plist_path, processing_prefix);
780    strcat(entitlements_plist_path, entitlements_postfix);
781
782}
783
784
785const struct option options[] = {
786    { "sign", required_argument, NULL, 's' },
787    { "entitlements", required_argument, NULL, 'z' },
788    { "no-profile", no_argument, NULL, 'Z' },
789    { "verify", no_argument, NULL, 'V' }, /* map to V to let verbose v pass */
790    { "timeout", required_argument, NULL, 't' },
791    {}
792};
793
794struct securityd *gSecurityd;
795void securityd_init();
796CFArrayRef SecAccessGroupsGetCurrent(void);
797
798CFArrayRef SecAccessGroupsGetCurrent(void) {
799    return NULL;
800}
801
802OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out);
803OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out)
804{
805    return -1;
806}
807
808
809
810#ifndef UNIT_TESTING
811int
812main(int argc, char *argv[])
813{
814    int err = 0;
815
816    int ch;
817    bool sign_op = false, noprofile = false,
818        verify_op = false;
819    int timeout = 180;
820
821    securityd_init();
822
823    while ((ch = getopt_long(argc, argv, "fvr:s:R:", options, NULL)) != -1)
824    {
825        switch (ch) {
826            case 's': sign_op = true; break;
827            case 'z': { log("codesign_wrapper reserves the entitlements option for itself");
828                        exit(1); /* XXX load entitlements from optarg */
829                        break; }
830            case 'Z': noprofile = true; break;
831            case 'V': verify_op = true; break;
832            case 't': timeout = atoi(optarg); break;
833        }
834    }
835    int arg_index_files = optind;
836    if ((!sign_op && !verify_op) || arg_index_files == argc) {
837        log("not a signing/verify operation, or no file to sign given");
838        return 1; /* short circuit to codesign binary: not signing, no files */
839    }
840    if (arg_index_files + 1 != argc) {
841        log("cannot sign more than one file in an operation");
842        return 1; /* we don't do more than one file at a time, so we can rejigger */
843    }
844
845    init();
846    if (mk_temp_dir(processing_path)) {
847        log("failed to create directory %s", processing_path);
848        return 1;
849    }
850
851    CFMutableDictionaryRef auditing_info =
852        dump_auditing_info(argv[arg_index_files]);
853    if (!auditing_info) {
854        log("failed to extract auditing_info from %s", argv[arg_index_files]);
855        return 1;
856    }
857
858    /* load up entitlements requested */
859    CFDictionaryRef entitlements_requested =
860        CFDictionaryGetValue(auditing_info, CFSTR("Entitlements"));
861    require_string(entitlements_requested, out, "At least need an application-identifier entitlements");
862    CFMutableDictionaryRef allowable_entitlements = NULL;
863
864    if (noprofile) {
865        /* XXX if (verify_op) require it to be store signed */
866        if (verify_op) {
867            /* load the code signature */
868            CFArrayRef code_signatures =
869                load_code_signatures(argv[arg_index_files]);
870            require(code_signatures, out);
871            require(verify_code_signatures(code_signatures, NULL), out);
872            CFRelease(code_signatures);
873        }
874
875        if (sign_op) {
876            /* do the same checks, pass signed in entitlements along for audit */
877            require(CFDictionaryGetValue(entitlements_requested,
878                                         CFSTR("application-identifier")), out);
879
880             CFDictionarySetValue(auditing_info, CFSTR("Entitlements"),
881                                  entitlements_requested);
882
883             allowable_entitlements = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, entitlements_requested);
884
885             /* For the 2-pass signing, where the app is signed first, then encrypted
886                and then resigned, we need to by pass the initial validation, so we
887                allow signing without checking the entitlements to the profile. */
888#if 0
889            log("You shouldn't want to sign without a profile.");
890            exit(1);
891#endif
892        }
893
894    } else {
895        /* load up the profile */
896        char profile_path[_POSIX_PATH_MAX] = {};
897        snprintf(profile_path, sizeof(profile_path), "%s/embedded.mobileprovision", argv[arg_index_files]);
898        CFDictionaryRef profile = load_profile(profile_path);
899        require_action(profile, out, log("Failed to load provision profile from: %s", profile_path));
900        CFDictionaryRef entitlements_whitelist = CFDictionaryGetValue(profile, CFSTR("Entitlements"));
901        require(entitlements_whitelist, out);
902        CFArrayRef certificates = CFDictionaryGetValue(profile, CFSTR("Certificates"));
903        require(certificates, out);
904
905        if (sign_op)
906            require_noerr(unlink(profile_path), out);
907
908        /* only allow identifiers whitelisted by profile */
909        allowable_entitlements = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
910            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
911        require(allowable_entitlements, out);
912        require(filter_entitlements(entitlements_whitelist,
913                    entitlements_requested, allowable_entitlements), out);
914
915        /* must have valid application-identifier */
916        require(CFDictionaryGetValue(allowable_entitlements,
917            CFSTR("application-identifier")), out);
918
919        CFDictionarySetValue(auditing_info, CFSTR("Entitlements"),
920            allowable_entitlements);
921
922        if (verify_op) {
923            /* load the code signature */
924            CFArrayRef code_signatures =
925                load_code_signatures(argv[arg_index_files]);
926            require(code_signatures, out);
927            require(verify_code_signatures(code_signatures, certificates), out);
928            CFRelease(code_signatures);
929        }
930    }
931
932    char *lock_filename = NULL;
933
934    if (sign_op) {
935        if (!asprintf(&lock_filename, "%s.lock", processing_prefix)) {
936            log("failed to alloc %s.lock", processing_prefix);
937            return 1;
938        }
939
940        while (lock_file(processing_prefix, lock_filename)) {
941            log("waiting for lock");
942            sleep(1);
943        }
944
945        err = write_auditing_data(audition_plist_path, auditing_info);
946
947        if (!err && allowable_entitlements) {
948            err |= write_filtered_entitlements(entitlements_plist_path, allowable_entitlements);
949        }
950
951        if (err)
952            log("failed to write auditing data");
953    }
954
955    if (!err) {
956        char *orig_args[argc+1+2];
957        /* size_t argv_size = argc * sizeof(*argv); args = malloc(argv_size); */
958        memcpy(orig_args, argv, (argc-1) * sizeof(*argv));
959
960        int arg = 0, argo = 0;
961        while (arg < argc - 1) {
962            if (strcmp("--no-profile", orig_args[arg]) &&
963                strncmp("--timeout", orig_args[arg], strlen("--timeout"))) {
964                orig_args[argo] = argv[arg];
965                argo++;
966            }
967            arg++;
968        }
969        if (entitlements_requested && allowable_entitlements) {
970            orig_args[argo++] = "--entitlements";
971            orig_args[argo++] = entitlements_plist_path;
972        }
973        orig_args[argo++] = argv[arg_index_files];
974        orig_args[argo++] = NULL;
975        orig_args[0] = codesign_binary;
976#if DEBUG
977        log("Caling codesign with the following args:");
978        int ix;
979        for(ix = 0; ix <= argc; ix++)
980            log("   %s", orig_args[ix] ? orig_args[ix] : "NULL");
981#endif
982        err = fork_child_timeout(NULL, NULL, (const char * const *)orig_args, timeout);
983    }
984
985    if (sign_op) {
986        unlink(audition_plist_path);
987        unlink(entitlements_plist_path);
988
989        free(audition_plist_path);
990        free(entitlements_plist_path);
991
992        if (err == -2) {
993            log("executing codesign(1) timed out");
994            const char * const kill_tokens[] = { "/usr/bin/killall", "Ingrian", NULL };
995            fork_child_timeout(close_all_fd, NULL, kill_tokens, 0);
996            const char * const load_tokens[] = { "/usr/bin/killall", "-USR2", "securityd", NULL };
997            fork_child_timeout(close_all_fd, NULL, load_tokens, 0);
998        }
999
1000        unlink(lock_filename);
1001        free(lock_filename);
1002
1003        if (err == -2) {
1004            sleep(10);
1005            log("delayed exit with timeout return value now we've tried to reload tokens");
1006            return 2;
1007        }
1008    }
1009
1010    if (!err)
1011        return 0;
1012    else
1013        log("failed to execute codesign(1)");
1014out:
1015    return 1;
1016}
1017#endif /* UNIT_TESTING */
1018
1019/* vim: set et : set sw=4 : set ts=4 : set sts=4 : */
1020