]> git.saurik.com Git - apple/security.git/blob - codesign_wrapper/codesign_wrapper.c
Security-57031.1.35.tar.gz
[apple/security.git] / codesign_wrapper / codesign_wrapper.c
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
52 extern bool do_verify(const char *path, CFArrayRef certificates);
53
54 static char * codesign_binary = "/usr/bin/codesign";
55 static char * processing_path = "/var/tmp/signingbox";
56 static char * processing_file = "codesign_wrapper";
57 static char * processing_prefix = NULL;
58 static char * auditing_postfix = "_auditing.plist";
59 static char * audition_plist_path = NULL;
60 static char * entitlements_plist_path = NULL;
61 static 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 { \
68 CFStringRef logstr = CFStringCreateWithFormat(NULL, NULL, CFSTR(format), ##args);\
69 if (logstr) { CFShow(logstr); CFRelease(logstr); } \
70 } while(0); \
71
72 const char *_root_ca_name = ANCHOR;
73
74 static pid_t kill_child = -1;
75 static void child_timeout(int sig)
76 {
77 if (kill_child != -1) {
78 kill(kill_child, sig);
79 kill_child = -1;
80 }
81 }
82
83 static void
84 close_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
102 static pid_t
103 fork_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
144 static int
145 fork_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
175 static void
176 dup_io(int arg[])
177 {
178 dup2(arg[0], arg[1]);
179 close(arg[0]);
180 }
181
182
183 static int
184 fork_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
200 static void
201 pass_signal_to_children(int sig)
202 {
203 signal(sig, SIG_DFL);
204 kill(0, sig);
205 }
206
207
208 static int
209 mk_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
229 static int
230 lock_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
271 static ssize_t
272 read_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
307 enum { CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171 };
308
309 typedef struct {
310 uint32_t type;
311 uint32_t offset;
312 } cs_blob_index;
313
314 static CFDataRef
315 extract_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)));
326 out:
327 return entitlements;
328 }
329
330 static CFDataRef
331 build_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
343 static CFMutableDictionaryRef
344 dump_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;
414 out:
415 return NULL;
416 }
417
418 static int
419 write_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
437 static int
438 write_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
456 static int
457 write_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
471 static CFDataRef
472 cfdata_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
486 static CFDictionaryRef
487 load_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
581 out:
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
594 typedef struct {
595 CFDictionaryRef whitelist;
596 CFMutableDictionaryRef filtered_list;
597 bool allowed_entitlements;
598 } filter_whitelist_ctx;
599
600 static void
601 filter_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
621 static bool
622 filter_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
634 static SecCertificateRef
635 cms_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
674 out:
675 return signer_cert;
676
677 }
678
679 static bool
680 cms_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
703 out:
704 if (signer_cert) CFRelease(signer_cert);
705 return result;
706
707 }
708
709
710 static bool
711 verify_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;
744 out:
745 return false;
746 }
747
748
749 static void
750 init()
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
785 const 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
794 struct securityd *gSecurityd;
795 void securityd_init();
796 CFArrayRef SecAccessGroupsGetCurrent(void);
797
798 CFArrayRef SecAccessGroupsGetCurrent(void) {
799 return NULL;
800 }
801
802 OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out);
803 OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out)
804 {
805 return -1;
806 }
807
808
809
810 #ifndef UNIT_TESTING
811 int
812 main(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)");
1014 out:
1015 return 1;
1016 }
1017 #endif /* UNIT_TESTING */
1018
1019 /* vim: set et : set sw=4 : set ts=4 : set sts=4 : */