]> git.saurik.com Git - apple/security.git/blob - codesign_wrapper/codesign_wrapper.c
Security-57337.40.85.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 SecurityClient *
799 SecSecurityClientGet(void)
800 {
801 static SecurityClient client;
802 static dispatch_once_t onceToken;
803 dispatch_once(&onceToken, ^{
804 client.task = SecTaskGetSelf();
805 client.accessGroups = NULL;
806 client.allowSystemKeychain = false;
807 client.allowSyncBubbleKeychain = false;
808 client.isNetworkExtension = false,
809 });
810 return &client;
811 }
812
813 CFArrayRef SecAccessGroupsGetCurrent(void) {
814 return NULL;
815 }
816
817 OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out);
818 OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out)
819 {
820 return -1;
821 }
822
823
824
825 #ifndef UNIT_TESTING
826 int
827 main(int argc, char *argv[])
828 {
829 int err = 0;
830
831 int ch;
832 bool sign_op = false, noprofile = false,
833 verify_op = false;
834 int timeout = 180;
835
836 securityd_init();
837
838 while ((ch = getopt_long(argc, argv, "fvr:s:R:", options, NULL)) != -1)
839 {
840 switch (ch) {
841 case 's': sign_op = true; break;
842 case 'z': { log("codesign_wrapper reserves the entitlements option for itself");
843 exit(1); /* XXX load entitlements from optarg */
844 break; }
845 case 'Z': noprofile = true; break;
846 case 'V': verify_op = true; break;
847 case 't': timeout = atoi(optarg); break;
848 }
849 }
850 int arg_index_files = optind;
851 if ((!sign_op && !verify_op) || arg_index_files == argc) {
852 log("not a signing/verify operation, or no file to sign given");
853 return 1; /* short circuit to codesign binary: not signing, no files */
854 }
855 if (arg_index_files + 1 != argc) {
856 log("cannot sign more than one file in an operation");
857 return 1; /* we don't do more than one file at a time, so we can rejigger */
858 }
859
860 init();
861 if (mk_temp_dir(processing_path)) {
862 log("failed to create directory %s", processing_path);
863 return 1;
864 }
865
866 CFMutableDictionaryRef auditing_info =
867 dump_auditing_info(argv[arg_index_files]);
868 if (!auditing_info) {
869 log("failed to extract auditing_info from %s", argv[arg_index_files]);
870 return 1;
871 }
872
873 /* load up entitlements requested */
874 CFDictionaryRef entitlements_requested =
875 CFDictionaryGetValue(auditing_info, CFSTR("Entitlements"));
876 require_string(entitlements_requested, out, "At least need an application-identifier entitlements");
877 CFMutableDictionaryRef allowable_entitlements = NULL;
878
879 if (noprofile) {
880 /* XXX if (verify_op) require it to be store signed */
881 if (verify_op) {
882 /* load the code signature */
883 CFArrayRef code_signatures =
884 load_code_signatures(argv[arg_index_files]);
885 require(code_signatures, out);
886 require(verify_code_signatures(code_signatures, NULL), out);
887 CFRelease(code_signatures);
888 }
889
890 if (sign_op) {
891 /* do the same checks, pass signed in entitlements along for audit */
892 require(CFDictionaryGetValue(entitlements_requested,
893 CFSTR("application-identifier")), out);
894
895 CFDictionarySetValue(auditing_info, CFSTR("Entitlements"),
896 entitlements_requested);
897
898 allowable_entitlements = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, entitlements_requested);
899
900 /* For the 2-pass signing, where the app is signed first, then encrypted
901 and then resigned, we need to by pass the initial validation, so we
902 allow signing without checking the entitlements to the profile. */
903 #if 0
904 log("You shouldn't want to sign without a profile.");
905 exit(1);
906 #endif
907 }
908
909 } else {
910 /* load up the profile */
911 char profile_path[_POSIX_PATH_MAX] = {};
912 snprintf(profile_path, sizeof(profile_path), "%s/embedded.mobileprovision", argv[arg_index_files]);
913 CFDictionaryRef profile = load_profile(profile_path);
914 require_action(profile, out, log("Failed to load provision profile from: %s", profile_path));
915 CFDictionaryRef entitlements_whitelist = CFDictionaryGetValue(profile, CFSTR("Entitlements"));
916 require(entitlements_whitelist, out);
917 CFArrayRef certificates = CFDictionaryGetValue(profile, CFSTR("Certificates"));
918 require(certificates, out);
919
920 if (sign_op)
921 require_noerr(unlink(profile_path), out);
922
923 /* only allow identifiers whitelisted by profile */
924 allowable_entitlements = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
925 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
926 require(allowable_entitlements, out);
927 require(filter_entitlements(entitlements_whitelist,
928 entitlements_requested, allowable_entitlements), out);
929
930 /* must have valid application-identifier */
931 require(CFDictionaryGetValue(allowable_entitlements,
932 CFSTR("application-identifier")), out);
933
934 CFDictionarySetValue(auditing_info, CFSTR("Entitlements"),
935 allowable_entitlements);
936
937 if (verify_op) {
938 /* load the code signature */
939 CFArrayRef code_signatures =
940 load_code_signatures(argv[arg_index_files]);
941 require(code_signatures, out);
942 require(verify_code_signatures(code_signatures, certificates), out);
943 CFRelease(code_signatures);
944 }
945 }
946
947 char *lock_filename = NULL;
948
949 if (sign_op) {
950 if (!asprintf(&lock_filename, "%s.lock", processing_prefix)) {
951 log("failed to alloc %s.lock", processing_prefix);
952 return 1;
953 }
954
955 while (lock_file(processing_prefix, lock_filename)) {
956 log("waiting for lock");
957 sleep(1);
958 }
959
960 err = write_auditing_data(audition_plist_path, auditing_info);
961
962 if (!err && allowable_entitlements) {
963 err |= write_filtered_entitlements(entitlements_plist_path, allowable_entitlements);
964 }
965
966 if (err)
967 log("failed to write auditing data");
968 }
969
970 if (!err) {
971 char *orig_args[argc+1+2];
972 /* size_t argv_size = argc * sizeof(*argv); args = malloc(argv_size); */
973 memcpy(orig_args, argv, (argc-1) * sizeof(*argv));
974
975 int arg = 0, argo = 0;
976 while (arg < argc - 1) {
977 if (strcmp("--no-profile", orig_args[arg]) &&
978 strncmp("--timeout", orig_args[arg], strlen("--timeout"))) {
979 orig_args[argo] = argv[arg];
980 argo++;
981 }
982 arg++;
983 }
984 if (entitlements_requested && allowable_entitlements) {
985 orig_args[argo++] = "--entitlements";
986 orig_args[argo++] = entitlements_plist_path;
987 }
988 orig_args[argo++] = argv[arg_index_files];
989 orig_args[argo++] = NULL;
990 orig_args[0] = codesign_binary;
991 #if DEBUG
992 log("Caling codesign with the following args:");
993 int ix;
994 for(ix = 0; ix <= argc; ix++)
995 log(" %s", orig_args[ix] ? orig_args[ix] : "NULL");
996 #endif
997 err = fork_child_timeout(NULL, NULL, (const char * const *)orig_args, timeout);
998 }
999
1000 if (sign_op) {
1001 unlink(audition_plist_path);
1002 unlink(entitlements_plist_path);
1003
1004 free(audition_plist_path);
1005 free(entitlements_plist_path);
1006
1007 if (err == -2) {
1008 log("executing codesign(1) timed out");
1009 const char * const kill_tokens[] = { "/usr/bin/killall", "Ingrian", NULL };
1010 fork_child_timeout(close_all_fd, NULL, kill_tokens, 0);
1011 const char * const load_tokens[] = { "/usr/bin/killall", "-USR2", "securityd", NULL };
1012 fork_child_timeout(close_all_fd, NULL, load_tokens, 0);
1013 }
1014
1015 unlink(lock_filename);
1016 free(lock_filename);
1017
1018 if (err == -2) {
1019 sleep(10);
1020 log("delayed exit with timeout return value now we've tried to reload tokens");
1021 return 2;
1022 }
1023 }
1024
1025 if (!err)
1026 return 0;
1027 else
1028 log("failed to execute codesign(1)");
1029 out:
1030 return 1;
1031 }
1032 #endif /* UNIT_TESTING */
1033
1034 /* vim: set et : set sw=4 : set ts=4 : set sts=4 : */