]>
Commit | Line | Data |
---|---|---|
427c49bc | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2008,2010,2013-2014 Apple Inc. All Rights Reserved. |
427c49bc A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
d8f41ccd | 5 | * |
427c49bc A |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
d8f41ccd | 12 | * |
427c49bc A |
13 | * The Original Code and all software distributed under the License are |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
d8f41ccd | 20 | * |
427c49bc A |
21 | * @APPLE_LICENSE_HEADER_END@ |
22 | */ | |
23 | ||
24 | #include <TargetConditionals.h> | |
25 | #if TARGET_OS_EMBEDDED | |
26 | ||
27 | #include "SecurityCommands.h" | |
28 | ||
29 | #include <AssertMacros.h> | |
30 | #include <mach-o/loader.h> | |
31 | #include <mach-o/fat.h> | |
32 | #include <CoreFoundation/CoreFoundation.h> | |
33 | #include <Security/SecCMS.h> | |
34 | #include <Security/SecPolicyPriv.h> | |
35 | #include <CommonCrypto/CommonDigest.h> | |
36 | #include <CommonCrypto/CommonDigestSPI.h> | |
d8f41ccd | 37 | #include <utilities/SecCFRelease.h> |
427c49bc A |
38 | |
39 | /* | |
40 | * Magic numbers used by Code Signing | |
41 | */ | |
42 | enum { | |
43 | CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */ | |
44 | CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */ | |
45 | CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */ | |
46 | CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */ | |
47 | CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */ | |
48 | ||
49 | CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */ | |
50 | }; | |
51 | ||
52 | ||
53 | /* | |
54 | * Structure of an embedded-signature SuperBlob | |
55 | */ | |
56 | typedef struct __BlobIndex { | |
57 | uint32_t type; /* type of entry */ | |
58 | uint32_t offset; /* offset of entry */ | |
59 | } CS_BlobIndex; | |
60 | ||
61 | typedef struct __SuperBlob { | |
62 | uint32_t magic; /* magic number */ | |
63 | uint32_t length; /* total length of SuperBlob */ | |
64 | uint32_t count; /* number of index entries following */ | |
65 | CS_BlobIndex index[]; /* (count) entries */ | |
66 | /* followed by Blobs in no particular order as indicated by offsets in index */ | |
67 | } CS_SuperBlob; | |
68 | ||
69 | ||
70 | /* | |
71 | * C form of a CodeDirectory. | |
72 | */ | |
73 | typedef struct __CodeDirectory { | |
74 | uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ | |
75 | uint32_t length; /* total length of CodeDirectory blob */ | |
76 | uint32_t version; /* compatibility version */ | |
77 | uint32_t flags; /* setup and mode flags */ | |
78 | uint32_t hashOffset; /* offset of hash slot element at index zero */ | |
79 | uint32_t identOffset; /* offset of identifier string */ | |
80 | uint32_t nSpecialSlots; /* number of special hash slots */ | |
81 | uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ | |
82 | uint32_t codeLimit; /* limit to main image signature range */ | |
83 | uint8_t hashSize; /* size of each hash in bytes */ | |
84 | uint8_t hashType; /* type of hash (cdHashType* constants) */ | |
85 | uint8_t spare1; /* unused (must be zero) */ | |
86 | uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ | |
87 | uint32_t spare2; /* unused (must be zero) */ | |
88 | /* followed by dynamic content as located by offset fields above */ | |
89 | } CS_CodeDirectory; | |
90 | ||
91 | ||
92 | //assert(page < ntohl(cd->nCodeSlots)); | |
93 | //return base + ntohl(cd->hashOffset) + page * 20; | |
94 | ||
95 | #if 0 | |
96 | static void debug_data(uint8_t *data, size_t length) | |
97 | { | |
98 | uint32_t i, j; | |
99 | for (i = 0; i < length; i+=16) { | |
100 | fprintf(stderr, "%p ", (void*)(data+i)); | |
101 | for (j = 0; (j < 16) && (j+i < length); j++) { | |
102 | uint8_t byte = *(uint8_t*)(data+i+j); | |
103 | fprintf(stderr, "%.02x %c|", byte, isprint(byte) ? byte : '?'); | |
104 | } | |
105 | fprintf(stderr, "\n"); | |
106 | } | |
107 | } | |
108 | ||
109 | static void write_data(const char *path, uint8_t *data, size_t length) | |
110 | { | |
111 | int fd = open(path, O_RDWR|O_TRUNC|O_CREAT, 0644); | |
112 | require(fd>0, out); | |
113 | write(fd, data, length); | |
114 | close(fd); | |
115 | out: | |
116 | return; | |
117 | } | |
118 | #endif | |
119 | ||
120 | static void fprint_digest(FILE *file, unsigned char *digest, size_t length) { | |
121 | size_t ix; | |
122 | for (ix = 0; ix < length; ++ix) { | |
123 | fprintf(file, "%02x", digest[ix]); | |
124 | } | |
125 | } | |
126 | ||
127 | static CFMutableDictionaryRef lc_code_sig(uint8_t *lc_code_signature, size_t lc_code_signature_len) | |
128 | { | |
129 | CFMutableDictionaryRef code_signature = | |
130 | CFDictionaryCreateMutable(kCFAllocatorDefault, 0, | |
131 | &kCFTypeDictionaryKeyCallBacks, | |
132 | &kCFTypeDictionaryValueCallBacks); | |
133 | require(code_signature, out); | |
134 | ||
135 | CS_SuperBlob *sb = (CS_SuperBlob*)lc_code_signature; | |
136 | require(ntohl(sb->magic) == CSMAGIC_EMBEDDED_SIGNATURE, out); | |
137 | uint32_t count; | |
138 | for (count = 0; count < ntohl(sb->count); count++) { | |
139 | //uint32_t type = ntohl(sb->index[count].type); | |
140 | uint32_t offset = ntohl(sb->index[count].offset); | |
141 | uint8_t *bytes = lc_code_signature + offset; | |
142 | //fprintf(stderr, "blob[%d]: (type: 0x%.08x, offset: %p)\n", count, type, (void*)offset); | |
143 | uint32_t magic = ntohl(*(uint32_t*)bytes); | |
144 | uint32_t length = ntohl(*(uint32_t*)(bytes+4)); | |
145 | //fprintf(stderr, " magic: 0x%.08x length: %d\n", magic, length); | |
146 | switch(magic) { | |
147 | case 0xfade0c01: //write_data("requirements", bytes, length); | |
148 | break; | |
149 | case 0xfade0c02: //write_data("codedir", bytes, length); | |
150 | { | |
151 | const CS_CodeDirectory *cd = (const CS_CodeDirectory *)bytes; | |
152 | CFDataRef codedir = CFDataCreate(kCFAllocatorDefault, bytes, length); | |
153 | require(codedir, out); | |
154 | CFDictionarySetValue(code_signature, CFSTR("CodeDirectory"), codedir); | |
155 | CFRelease(codedir); | |
156 | require_string(ntohl(cd->version) >= 0x20001, out, "incompatible version"); | |
157 | require_string(ntohl(cd->version) <= 0x2F000, out, "incompatible version"); | |
158 | require_string(cd->hashSize == 20, out, "unexpected hash size"); | |
159 | require_string(cd->hashType == 1, out, "unexpected hash type"); | |
160 | ||
161 | uint32_t hash_offset = ntohl(cd->hashOffset); | |
162 | uint32_t entitlement_slot = 5; | |
163 | ||
164 | if (ntohl(cd->nSpecialSlots) >= entitlement_slot) { | |
165 | CFDataRef message = CFDataCreate(kCFAllocatorDefault, bytes+hash_offset-entitlement_slot*cd->hashSize, cd->hashSize); | |
166 | require(message, out); | |
167 | CFDictionarySetValue(code_signature, CFSTR("EntitlementsCDHash"), message); | |
168 | CFRelease(message); | |
169 | } else | |
170 | fprintf(stderr, "no entitlements slot yet\n"); | |
171 | } | |
172 | break; | |
173 | case 0xfade0b01: //write_data("signed", lc_code_signature, bytes-lc_code_signature); | |
174 | if (length != 8) { | |
175 | CFDataRef message = CFDataCreate(kCFAllocatorDefault, bytes+8, length-8); | |
176 | require(message, out); | |
177 | CFDictionarySetValue(code_signature, CFSTR("SignedData"), message); | |
178 | CFRelease(message); | |
179 | } | |
180 | break; | |
181 | case 0xfade7171: | |
182 | { | |
183 | unsigned char digest[CC_SHA1_DIGEST_LENGTH]; | |
184 | CCDigest(kCCDigestSHA1, bytes, length, digest); | |
185 | ||
186 | CFDataRef message = CFDataCreate(kCFAllocatorDefault, digest, sizeof(digest)); | |
187 | require(message, out); | |
188 | CFDictionarySetValue(code_signature, CFSTR("EntitlementsHash"), message); | |
189 | CFRelease(message); | |
190 | message = CFDataCreate(kCFAllocatorDefault, bytes+8, length-8); | |
191 | require(message, out); | |
192 | CFDictionarySetValue(code_signature, CFSTR("Entitlements"), message); | |
193 | CFRelease(message); | |
194 | break; | |
195 | } | |
196 | default: | |
197 | fprintf(stderr, "Skipping block with magic: 0x%x\n", magic); | |
198 | break; | |
199 | } | |
200 | } | |
201 | return code_signature; | |
202 | out: | |
203 | if (code_signature) CFRelease(code_signature); | |
204 | return NULL; | |
205 | } | |
206 | ||
207 | static FILE * | |
208 | open_bundle(const char * path, const char * mode) | |
209 | { | |
210 | char full_path[1024] = {}; | |
211 | CFStringRef path_cfstring = NULL; | |
212 | CFURLRef path_url = NULL; | |
213 | CFBundleRef bundle = NULL; | |
d8f41ccd | 214 | CFURLRef exec = NULL; |
427c49bc A |
215 | |
216 | path_cfstring = CFStringCreateWithFileSystemRepresentation(kCFAllocatorDefault, path); | |
217 | require_quiet(path_cfstring, out); | |
218 | path_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_cfstring, kCFURLPOSIXPathStyle, true); | |
219 | require_quiet(path_url, out); | |
220 | bundle = CFBundleCreate(kCFAllocatorDefault, path_url); | |
221 | require_quiet(bundle, out); | |
d8f41ccd | 222 | exec = CFBundleCopyExecutableURL(bundle); |
427c49bc A |
223 | require(exec, out); |
224 | require(CFURLGetFileSystemRepresentation(exec, true, (uint8_t*)full_path, sizeof(full_path)), out); | |
225 | out: | |
d8f41ccd A |
226 | CFReleaseSafe(path_cfstring); |
227 | CFReleaseSafe(path_url); | |
228 | CFReleaseSafe(bundle); | |
229 | CFReleaseSafe(exec); | |
427c49bc A |
230 | |
231 | return fopen(full_path, "r"); | |
232 | } | |
233 | ||
234 | static CFMutableDictionaryRef load_code_signature(FILE *binary, size_t slice_offset) | |
235 | { | |
236 | bool signature_found = false; | |
237 | CFMutableDictionaryRef result = NULL; | |
238 | struct load_command lc; | |
239 | do { | |
240 | require(1 == fread(&lc, sizeof(lc), 1, binary), out); | |
241 | if (lc.cmd == LC_CODE_SIGNATURE) { | |
242 | struct { uint32_t offset; uint32_t size; } sig; | |
243 | require(1 == fread(&sig, sizeof(sig), 1, binary), out); | |
244 | require_noerr(fseek(binary, slice_offset+sig.offset, SEEK_SET), out); | |
245 | size_t length = sig.size; | |
246 | uint8_t *data = malloc(length); | |
247 | require(length && data, out); | |
248 | require(1 == fread(data, length, 1, binary), out); | |
249 | signature_found = true; | |
250 | result = lc_code_sig(data, length); | |
251 | free(data); | |
252 | break; | |
253 | } | |
254 | require_noerr(fseek(binary, lc.cmdsize-sizeof(lc), SEEK_CUR), out); | |
255 | } while(lc.cmd || lc.cmdsize); /* count lc */ | |
256 | out: | |
257 | if (!signature_found) | |
258 | fprintf(stderr, "No LC_CODE_SIGNATURE segment found\n"); | |
259 | return result; | |
260 | } | |
261 | ||
d8f41ccd | 262 | static CF_RETURNS_RETAINED CFArrayRef load_code_signatures(const char *path) |
427c49bc A |
263 | { |
264 | bool fully_parsed_binary = false; | |
265 | CFMutableDictionaryRef result = NULL; | |
266 | CFMutableArrayRef results = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); | |
267 | ||
268 | FILE *binary = open_bundle(path, "r"); | |
269 | if (!binary) binary = fopen(path, "r"); | |
270 | require(binary, out); | |
271 | ||
272 | struct mach_header header; | |
273 | require(1 == fread(&header, sizeof(header), 1, binary), out); | |
274 | if ((header.magic == MH_MAGIC) || (header.magic == MH_MAGIC_64)) { | |
275 | if (header.magic == MH_MAGIC_64) | |
276 | fseek(binary, sizeof(struct mach_header_64) - sizeof(struct mach_header), SEEK_CUR); | |
277 | result = load_code_signature(binary, 0 /*non fat*/); | |
278 | require(result, out); | |
279 | CFStringRef type = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("CPU type: (%d,%d)"), header.cputype, header.cpusubtype); | |
280 | CFDictionarySetValue(result, CFSTR("ARCH"), type); | |
281 | CFRelease(type); | |
282 | CFArrayAppendValue(results, result); | |
283 | } | |
284 | else | |
285 | { | |
286 | struct fat_header fat; | |
287 | require(!fseek(binary, 0L, SEEK_SET), out); | |
288 | require(1 == fread(&fat, sizeof(fat), 1, binary), out); | |
289 | require(ntohl(fat.magic) == FAT_MAGIC, out); | |
290 | uint32_t slice, slices = ntohl(fat.nfat_arch); | |
291 | struct fat_arch *archs = calloc(slices, sizeof(struct fat_arch)); | |
292 | require(slices == fread(archs, sizeof(struct fat_arch), slices, binary), out); | |
293 | for (slice = 0; slice < slices; slice++) { | |
294 | uint32_t slice_offset = ntohl(archs[slice].offset); | |
295 | require(!fseek(binary, slice_offset, SEEK_SET), out); | |
296 | require(1 == fread(&header, sizeof(header), 1, binary), out); | |
297 | require((header.magic == MH_MAGIC) || (header.magic == MH_MAGIC_64), out); | |
298 | if (header.magic == MH_MAGIC_64) | |
299 | fseek(binary, sizeof(struct mach_header_64) - sizeof(struct mach_header), SEEK_CUR); | |
300 | result = load_code_signature(binary, slice_offset); | |
301 | require(result, out); | |
302 | CFStringRef type = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("CPU type: (%d,%d)"), header.cputype, header.cpusubtype); | |
303 | CFDictionarySetValue(result, CFSTR("ARCH"), type); | |
304 | CFRelease(type); | |
305 | CFArrayAppendValue(results, result); | |
306 | CFRelease(result); | |
307 | } | |
308 | } | |
309 | fully_parsed_binary = true; | |
310 | out: | |
311 | if (!fully_parsed_binary) { | |
312 | if (results) { | |
313 | CFRelease(results); | |
314 | results = NULL; | |
315 | } | |
316 | } | |
317 | if (binary) | |
318 | fclose(binary); | |
319 | return results; | |
320 | } | |
321 | ||
322 | ||
323 | extern int codesign_util(int argc, char * const *argv) | |
324 | { | |
325 | int result = 1, verbose = 0; | |
326 | char ch; | |
327 | ||
328 | while ((ch = getopt(argc, argv, "v")) != -1) | |
329 | { | |
330 | switch (ch) | |
331 | { | |
332 | case 'v': | |
333 | verbose++; | |
334 | break; | |
335 | default: | |
336 | return 2; /* Trigger usage message. */ | |
337 | } | |
338 | } | |
339 | ||
340 | argc -= optind; | |
341 | argv += optind; | |
342 | ||
343 | if (argc != 1) | |
344 | return 2; /* Trigger usage message. */ | |
345 | ||
346 | CFArrayRef sigs = load_code_signatures(argv[0]); | |
347 | require(sigs, out); | |
348 | ||
349 | if (verbose >= 2) | |
350 | CFShow(sigs); | |
351 | ||
352 | CFIndex i, count = CFArrayGetCount(sigs); | |
353 | ||
354 | for (i = 0; i < count; i++) { | |
355 | CFDictionaryRef signature = CFArrayGetValueAtIndex(sigs, i); | |
356 | ||
357 | CFDataRef code_dir = CFDictionaryGetValue(signature, CFSTR("CodeDirectory")); | |
358 | const CS_CodeDirectory *cd = (CS_CodeDirectory *)CFDataGetBytePtr(code_dir); | |
359 | ||
360 | CFDataRef signed_data = CFDictionaryGetValue(signature, CFSTR("SignedData")); | |
361 | ||
362 | CFDataRef entitlements = CFDictionaryGetValue(signature, CFSTR("Entitlements")); | |
363 | CFDataRef entitlements_cd_hash = CFDictionaryGetValue(signature, CFSTR("EntitlementsCDHash")); | |
364 | CFDataRef entitlements_hash = CFDictionaryGetValue(signature, CFSTR("EntitlementsHash")); | |
365 | ||
366 | CFStringRef arch = CFDictionaryGetValue(signature, CFSTR("ARCH")); | |
367 | ||
368 | CFShow(arch); | |
369 | ||
370 | SecPolicyRef policy = SecPolicyCreateiPhoneApplicationSigning(); | |
371 | ||
372 | if (signed_data) { | |
373 | if (SecCMSVerify(signed_data, code_dir, policy, NULL, NULL)) { | |
374 | fprintf(stderr, "Failed to verify signature\n"); | |
375 | result = -1; | |
376 | } else | |
377 | fprintf(stderr, "Signature ok\n"); | |
378 | ||
379 | } else | |
380 | fprintf(stderr, "Ad-hoc signed binary\n"); | |
381 | ||
382 | if (entitlements_cd_hash) { | |
383 | if (entitlements_hash && entitlements_cd_hash && CFEqual(entitlements_hash, entitlements_cd_hash)) | |
384 | fprintf(stderr, "Entitlements ok\n"); | |
385 | else | |
386 | fprintf(stderr, "Entitlements modified\n"); | |
387 | } | |
388 | ||
389 | if (verbose >= 2) { | |
390 | fprintf(stderr, "magic: 0x%x length: %u(%lu)\n", ntohl(cd->magic), ntohl(cd->length), CFDataGetLength(code_dir)); | |
391 | fprintf(stderr, "code directory version/flags: 0x%x/0x%x special/code hash slots: %u/%u\n" | |
392 | "codelimit: %u hash size/type: %u/%u hash/ident offset: %u/%u\n", | |
393 | ntohl(cd->version), ntohl(cd->flags), ntohl(cd->nSpecialSlots), ntohl(cd->nCodeSlots), | |
394 | ntohl(cd->codeLimit), cd->hashSize, cd->hashType, ntohl(cd->hashOffset), ntohl(cd->identOffset)); | |
395 | fprintf(stderr, "ident: '%s'\n", CFDataGetBytePtr(code_dir) + ntohl(cd->identOffset)); | |
396 | ||
397 | uint32_t ix; | |
398 | uint8_t *hashes = (uint8_t *)CFDataGetBytePtr(code_dir) + ntohl(cd->hashOffset); | |
399 | for (ix = 0; ix < ntohl(cd->nSpecialSlots); ++ix) { | |
400 | fprint_digest(stderr, hashes, cd->hashSize); | |
401 | fprintf(stderr, "\n"); | |
402 | hashes += cd->hashSize; | |
403 | } | |
404 | } | |
405 | ||
406 | if (verbose >= 1) { | |
407 | if (entitlements) | |
408 | fprintf(stderr, "Entitlements\n%.*s", (int)CFDataGetLength(entitlements)-8, CFDataGetBytePtr(entitlements)+8); | |
409 | } | |
410 | ||
411 | if (verbose >= 2) { | |
412 | if (entitlements_hash) { | |
413 | fprintf(stderr, "digest: "); | |
414 | fprint_digest(stderr, (uint8_t *)CFDataGetBytePtr(entitlements_hash), CC_SHA1_DIGEST_LENGTH); | |
415 | fprintf(stderr, "\n"); | |
416 | } | |
417 | } | |
418 | } | |
419 | ||
d8f41ccd | 420 | CFReleaseSafe(sigs); |
427c49bc A |
421 | |
422 | return result; | |
423 | out: | |
424 | return -1; | |
425 | } | |
426 | ||
427 | #endif // TARGET_OS_EMBEDDED |