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