]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
3 | * |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
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. | |
12 | * | |
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. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | // | |
25 | // codedirectory - format and operations for code signing "code directory" structures | |
26 | // | |
27 | #include "codedirectory.h" | |
28 | #include "csutilities.h" | |
29 | #include "CSCommonPriv.h" | |
e3d460c9 | 30 | #include <vector> |
b1ab9ed8 A |
31 | |
32 | using namespace UnixPlusPlus; | |
33 | ||
34 | ||
35 | namespace Security { | |
36 | namespace CodeSigning { | |
37 | ||
38 | ||
39 | // | |
40 | // Highest understood special slot in this CodeDirectory. | |
41 | // | |
42 | CodeDirectory::SpecialSlot CodeDirectory::maxSpecialSlot() const | |
43 | { | |
44 | SpecialSlot slot = this->nSpecialSlots; | |
45 | if (slot > cdSlotMax) | |
46 | slot = cdSlotMax; | |
47 | return slot; | |
48 | } | |
49 | ||
50 | ||
51 | // | |
52 | // Canonical filesystem names for select slot numbers. | |
53 | // These are variously used for filenames, extended attribute names, etc. | |
54 | // to get some consistency in naming. These are for storing signing-related | |
55 | // data; they have no bearing on the actual hash slots in the CodeDirectory. | |
56 | // | |
57 | const char *CodeDirectory::canonicalSlotName(SpecialSlot slot) | |
58 | { | |
59 | switch (slot) { | |
60 | case cdRequirementsSlot: | |
61 | return kSecCS_REQUIREMENTSFILE; | |
e3d460c9 A |
62 | case cdAlternateCodeDirectorySlots: |
63 | return kSecCS_REQUIREMENTSFILE "-1"; | |
64 | case cdAlternateCodeDirectorySlots+1: | |
65 | return kSecCS_REQUIREMENTSFILE "-2"; | |
66 | case cdAlternateCodeDirectorySlots+2: | |
67 | return kSecCS_REQUIREMENTSFILE "-3"; | |
68 | case cdAlternateCodeDirectorySlots+3: | |
69 | return kSecCS_REQUIREMENTSFILE "-4"; | |
70 | case cdAlternateCodeDirectorySlots+4: | |
71 | return kSecCS_REQUIREMENTSFILE "-5"; | |
b1ab9ed8 A |
72 | case cdResourceDirSlot: |
73 | return kSecCS_RESOURCEDIRFILE; | |
74 | case cdCodeDirectorySlot: | |
75 | return kSecCS_CODEDIRECTORYFILE; | |
76 | case cdSignatureSlot: | |
77 | return kSecCS_SIGNATUREFILE; | |
e3d460c9 A |
78 | case cdTopDirectorySlot: |
79 | return kSecCS_TOPDIRECTORYFILE; | |
b1ab9ed8 A |
80 | case cdEntitlementSlot: |
81 | return kSecCS_ENTITLEMENTFILE; | |
90dc47c2 A |
82 | case cdEntitlementDERSlot: |
83 | return kSecCS_ENTITLEMENTDERFILE; | |
e3d460c9 A |
84 | case cdRepSpecificSlot: |
85 | return kSecCS_REPSPECIFICFILE; | |
b1ab9ed8 A |
86 | default: |
87 | return NULL; | |
88 | } | |
89 | } | |
90 | ||
91 | ||
92 | // | |
93 | // Canonical attributes of SpecialSlots. | |
94 | // | |
95 | unsigned CodeDirectory::slotAttributes(SpecialSlot slot) | |
96 | { | |
97 | switch (slot) { | |
98 | case cdRequirementsSlot: | |
99 | return cdComponentIsBlob; // global | |
100 | case cdCodeDirectorySlot: | |
e3d460c9 A |
101 | case cdAlternateCodeDirectorySlots: |
102 | case cdAlternateCodeDirectorySlots+1: | |
103 | case cdAlternateCodeDirectorySlots+2: | |
104 | case cdAlternateCodeDirectorySlots+3: | |
105 | case cdAlternateCodeDirectorySlots+4: | |
106 | return cdComponentPerArchitecture | cdComponentIsBlob; | |
b1ab9ed8 A |
107 | case cdSignatureSlot: |
108 | return cdComponentPerArchitecture; // raw | |
109 | case cdEntitlementSlot: | |
90dc47c2 | 110 | case cdEntitlementDERSlot: |
b1ab9ed8 A |
111 | return cdComponentIsBlob; // global |
112 | case cdIdentificationSlot: | |
113 | return cdComponentPerArchitecture; // raw | |
79b9da22 A |
114 | case cdTicketSlot: |
115 | return 0; // global, raw | |
b1ab9ed8 A |
116 | default: |
117 | return 0; // global, raw | |
118 | } | |
119 | } | |
120 | ||
121 | ||
122 | // | |
123 | // Symbolic names for code directory special slots. | |
124 | // These are only used for debug output. They are not API-official. | |
125 | // Needs to be coordinated with the cd*Slot enumeration in codedirectory.h. | |
126 | // | |
127 | #if !defined(NDEBUG) | |
128 | const char * const CodeDirectory::debugSlotName[] = { | |
129 | "codedirectory", | |
130 | "info", | |
131 | "requirements", | |
132 | "resources", | |
fa7225c8 | 133 | "rep-specific", |
b1ab9ed8 A |
134 | "entitlement" |
135 | }; | |
136 | #endif //NDEBUG | |
137 | ||
138 | ||
139 | // | |
140 | // Check a CodeDirectory for basic integrity. This should ensure that the | |
141 | // version is understood by our code, and that the internal structure | |
142 | // (offsets etc.) is intact. In particular, it must make sure that no offsets | |
143 | // point outside the CodeDirectory. | |
144 | // Throws if the directory is corrupted or out of versioning bounds. | |
145 | // Returns if the version is usable (perhaps with degraded features due to | |
146 | // compatibility hacks). | |
147 | // | |
148 | // Note: There are some things we don't bother checking because they won't | |
149 | // cause crashes, and will just be flagged as nonsense later. For example, | |
150 | // a Bad Guy could overlap the identifier and hash fields, which is nonsense | |
151 | // but not dangerous. | |
152 | // | |
153 | void CodeDirectory::checkIntegrity() const | |
154 | { | |
155 | // check version for support | |
156 | if (!this->validateBlob()) | |
157 | MacOSError::throwMe(errSecCSSignatureInvalid); // busted | |
158 | if (version > compatibilityLimit) | |
159 | MacOSError::throwMe(errSecCSSignatureUnsupported); // too new - no clue | |
160 | if (version < earliestVersion) | |
161 | MacOSError::throwMe(errSecCSSignatureUnsupported); // too old - can't support | |
162 | if (version > currentVersion) | |
fa7225c8 | 163 | secinfo("codedir", "%p version 0x%x newer than current 0x%x", |
b1ab9ed8 | 164 | this, uint32_t(version), currentVersion); |
90dc47c2 A |
165 | |
166 | bool hasPreEncryptHashes = version >= supportsPreEncrypt && preEncryptOffset != 0; | |
167 | ||
b1ab9ed8 A |
168 | // now check interior offsets for validity |
169 | if (!stringAt(identOffset)) | |
170 | MacOSError::throwMe(errSecCSSignatureFailed); // identifier out of blob range | |
420ff9d9 A |
171 | if (version >= supportsTeamID && teamIDOffset != 0 && !stringAt(teamIDOffset)) |
172 | MacOSError::throwMe(errSecCSSignatureFailed); // identifier out of blob range | |
427c49bc | 173 | if (!contains(hashOffset - int64_t(hashSize) * nSpecialSlots, hashSize * (int64_t(nSpecialSlots) + nCodeSlots))) |
b1ab9ed8 | 174 | MacOSError::throwMe(errSecCSSignatureFailed); // hash array out of blob range |
90dc47c2 A |
175 | if (hasPreEncryptHashes && !contains(preEncryptOffset, hashSize * (int64_t(nCodeSlots)))) |
176 | MacOSError::throwMe(errSecCSSignatureFailed); // pre-encrypt array out of blob range | |
b1ab9ed8 A |
177 | if (const Scatter *scatter = this->scatterVector()) { |
178 | // the optional scatter vector is terminated with an element having (count == 0) | |
179 | unsigned int pagesConsumed = 0; | |
427c49bc | 180 | for (;; scatter++) { |
b1ab9ed8 A |
181 | if (!contains(scatter, sizeof(Scatter))) |
182 | MacOSError::throwMe(errSecCSSignatureFailed); | |
427c49bc A |
183 | if (scatter->count == 0) |
184 | break; | |
b1ab9ed8 | 185 | pagesConsumed += scatter->count; |
b1ab9ed8 | 186 | } |
90dc47c2 A |
187 | if (!contains(getSlot(pagesConsumed-1, false), hashSize) || |
188 | (hasPreEncryptHashes && !contains(getSlot(pagesConsumed-1, true), hashSize))) // referenced too many main hash slots | |
b1ab9ed8 A |
189 | MacOSError::throwMe(errSecCSSignatureFailed); |
190 | } | |
60c433a9 A |
191 | |
192 | // check consistency between the page-coverage fields | |
e3d460c9 | 193 | size_t limit = signingLimit(); |
60c433a9 | 194 | if (pageSize) { |
e3d460c9 | 195 | if (limit == 0) // can't have paged signatures with no covered data |
60c433a9 | 196 | MacOSError::throwMe(errSecCSSignatureFailed); |
e3d460c9 | 197 | size_t coveredPages = ((limit-1) >> pageSize) + 1; // page slots required to cover signingLimit |
60c433a9 A |
198 | if (coveredPages != nCodeSlots) |
199 | MacOSError::throwMe(errSecCSSignatureFailed); | |
200 | } else { | |
e3d460c9 | 201 | if ((limit > 0) != nCodeSlots) // must have one code slot, or none if no code |
60c433a9 A |
202 | MacOSError::throwMe(errSecCSSignatureFailed); |
203 | } | |
b1ab9ed8 A |
204 | } |
205 | ||
206 | ||
207 | // | |
208 | // Validate a slot against data in memory. | |
209 | // | |
90dc47c2 | 210 | bool CodeDirectory::validateSlot(const void *data, size_t length, Slot slot, bool preEncrypt) const |
b1ab9ed8 | 211 | { |
fa7225c8 | 212 | secinfo("codedir", "%p validating slot %d", this, int(slot)); |
b1ab9ed8 | 213 | MakeHash<CodeDirectory> hasher(this); |
0e1db9d1 A |
214 | vector<Hashing::Byte> digest_vector(hasher->digestLength()); |
215 | generateHash(hasher, data, length, digest_vector.data()); | |
216 | return memcmp(digest_vector.data(), getSlot(slot, preEncrypt), hasher->digestLength()) == 0; | |
b1ab9ed8 A |
217 | } |
218 | ||
219 | ||
220 | // | |
221 | // Validate a slot against the contents of an open file. At most 'length' bytes | |
222 | // will be read from the file. | |
223 | // | |
90dc47c2 | 224 | bool CodeDirectory::validateSlot(FileDesc fd, size_t length, Slot slot, bool preEncrypt) const |
b1ab9ed8 A |
225 | { |
226 | MakeHash<CodeDirectory> hasher(this); | |
0e1db9d1 A |
227 | vector<Hashing::Byte> digest_vector(hasher->digestLength()); |
228 | generateHash(hasher, fd, digest_vector.data(), length); | |
229 | return memcmp(digest_vector.data(), getSlot(slot, preEncrypt), hasher->digestLength()) == 0; | |
b1ab9ed8 A |
230 | } |
231 | ||
232 | ||
233 | // | |
234 | // Check whether a particular slot is present. | |
235 | // Absense is indicated by either a zero hash, or by lying outside | |
236 | // the slot range. | |
237 | // | |
238 | bool CodeDirectory::slotIsPresent(Slot slot) const | |
239 | { | |
240 | if (slot >= -Slot(nSpecialSlots) && slot < Slot(nCodeSlots)) { | |
90dc47c2 | 241 | const Hashing::Byte *digest = getSlot(slot, false); |
b1ab9ed8 A |
242 | for (unsigned n = 0; n < hashSize; n++) |
243 | if (digest[n]) | |
244 | return true; // non-zero digest => present | |
245 | } | |
246 | return false; // absent | |
247 | } | |
248 | ||
249 | ||
250 | // | |
251 | // Given a hash type code, create an appropriate subclass of DynamicHash | |
252 | // and return it. The caller owns the object and must delete it when done. | |
253 | // This function never returns NULL. It throws if the hashType is unsuupported, | |
254 | // or if there's an error creating the hasher. | |
255 | // | |
256 | DynamicHash *CodeDirectory::hashFor(HashAlgorithm hashType) | |
257 | { | |
b1ab9ed8 | 258 | switch (hashType) { |
5c19dc3a A |
259 | case kSecCodeSignatureHashSHA1: return new CCHashInstance(kCCDigestSHA1); |
260 | case kSecCodeSignatureHashSHA256: return new CCHashInstance(kCCDigestSHA256); | |
e3d460c9 | 261 | case kSecCodeSignatureHashSHA384: return new CCHashInstance(kCCDigestSHA384); |
5c19dc3a | 262 | case kSecCodeSignatureHashSHA256Truncated: return new CCHashInstance(kCCDigestSHA256, SHA1::digestLength); |
b1ab9ed8 A |
263 | default: |
264 | MacOSError::throwMe(errSecCSSignatureUnsupported); | |
265 | } | |
5c19dc3a A |
266 | } |
267 | ||
268 | ||
e3d460c9 A |
269 | // |
270 | // Determine which of a set of possible digest types should be chosen as the "best" one | |
271 | // | |
272 | static const CodeDirectory::HashAlgorithm hashPriorities[] = { | |
273 | kSecCodeSignatureHashSHA384, | |
274 | kSecCodeSignatureHashSHA256, | |
275 | kSecCodeSignatureHashSHA256Truncated, | |
276 | kSecCodeSignatureHashSHA1, | |
277 | kSecCodeSignatureNoHash // sentinel | |
278 | }; | |
279 | ||
280 | bool CodeDirectory::viableHash(HashAlgorithm type) | |
281 | { | |
282 | for (const HashAlgorithm* tp = hashPriorities; *tp != kSecCodeSignatureNoHash; tp++) | |
283 | if (*tp == type) | |
284 | return true; | |
285 | return false; | |
286 | ||
287 | } | |
288 | ||
289 | CodeDirectory::HashAlgorithm CodeDirectory::bestHashOf(const HashAlgorithms &types) | |
290 | { | |
291 | for (const HashAlgorithm* type = hashPriorities; *type != kSecCodeSignatureNoHash; type++) | |
292 | if (types.find(*type) != types.end()) | |
293 | return *type; | |
294 | MacOSError::throwMe(errSecCSUnsupportedDigestAlgorithm); | |
295 | } | |
296 | ||
297 | ||
298 | // | |
299 | // Hash a file range with multiple digest algorithms and then pass the resulting | |
300 | // digests to a per-algorithm block. | |
301 | // | |
302 | void CodeDirectory::multipleHashFileData(FileDesc fd, size_t limit, CodeDirectory::HashAlgorithms types, void (^action)(HashAlgorithm type, DynamicHash* hasher)) | |
303 | { | |
304 | assert(!types.empty()); | |
866f8763 | 305 | map<HashAlgorithm, RefPointer<DynamicHash> > hashes; |
e3d460c9 A |
306 | for (auto it = types.begin(); it != types.end(); ++it) { |
307 | if (CodeDirectory::viableHash(*it)) | |
866f8763 | 308 | hashes[*it] = CodeDirectory::hashFor(*it); |
e3d460c9 A |
309 | } |
310 | scanFileData(fd, limit, ^(const void *buffer, size_t size) { | |
866f8763 A |
311 | for (auto it = hashes.begin(); it != hashes.end(); ++it) { |
312 | it->second->update(buffer, size); | |
e3d460c9 A |
313 | } |
314 | }); | |
315 | CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary(); | |
866f8763 A |
316 | for (auto it = hashes.begin(); it != hashes.end(); ++it) { |
317 | action(it->first, it->second); | |
e3d460c9 A |
318 | } |
319 | } | |
fa7225c8 A |
320 | |
321 | ||
322 | // | |
323 | // Hash data in memory using our hashAlgorithm() | |
324 | // | |
325 | bool CodeDirectory::verifyMemoryContent(CFDataRef data, const Byte* digest) const | |
326 | { | |
327 | RefPointer<DynamicHash> hasher = CodeDirectory::hashFor(this->hashType); | |
328 | hasher->update(CFDataGetBytePtr(data), CFDataGetLength(data)); | |
329 | return hasher->verify(digest); | |
330 | } | |
e3d460c9 A |
331 | |
332 | ||
5c19dc3a A |
333 | // |
334 | // Generate the canonical cdhash - the internal hash of the CodeDirectory itself. | |
90dc47c2 | 335 | // With 'truncate' truncates to 20 bytes, because that's what's commonly used. |
5c19dc3a | 336 | // |
90dc47c2 | 337 | CFDataRef CodeDirectory::cdhash(bool truncate) const |
5c19dc3a A |
338 | { |
339 | MakeHash<CodeDirectory> hash(this); | |
0e1db9d1 | 340 | vector<Hashing::Byte> digest_vector(hash->digestLength()); |
5c19dc3a | 341 | hash->update(this, this->length()); |
0e1db9d1 A |
342 | hash->finish(digest_vector.data()); |
343 | return makeCFData(digest_vector.data(), | |
90dc47c2 A |
344 | truncate ? min(hash->digestLength(), size_t(kSecCodeCDHashLength)) : |
345 | hash->digestLength()); | |
b1ab9ed8 A |
346 | } |
347 | ||
348 | ||
349 | // | |
350 | // Hash the next limit bytes of a file and return the digest. | |
351 | // If the file is shorter, hash as much as you can. | |
352 | // Limit==0 means unlimited (to end of file). | |
353 | // Return how many bytes were actually hashed. | |
354 | // Throw on any errors. | |
355 | // | |
356 | size_t CodeDirectory::generateHash(DynamicHash *hasher, FileDesc fd, Hashing::Byte *digest, size_t limit) | |
357 | { | |
358 | size_t size = hashFileData(fd, hasher, limit); | |
359 | hasher->finish(digest); | |
360 | return size; | |
361 | } | |
362 | ||
363 | ||
364 | // | |
365 | // Ditto, but hash a memory buffer instead. | |
366 | // | |
367 | size_t CodeDirectory::generateHash(DynamicHash *hasher, const void *data, size_t length, Hashing::Byte *digest) | |
368 | { | |
369 | hasher->update(data, length); | |
370 | hasher->finish(digest); | |
371 | return length; | |
372 | } | |
373 | ||
374 | ||
313fa17b A |
375 | // |
376 | // Turn a hash of canonical type into a hex string | |
377 | // | |
378 | std::string CodeDirectory::hexHash(const unsigned char *hash) const | |
379 | { | |
380 | size_t size = this->hashSize; | |
381 | char result[2*size+1]; | |
382 | for (unsigned n = 0; n < size; n++) | |
383 | sprintf(result+2*n, "%02.2x", hash[n]); | |
384 | return result; | |
385 | } | |
386 | ||
387 | ||
388 | // | |
389 | // Generate a screening code string from a (complete) CodeDirectory. | |
390 | // This can be used to make a lightweight pre-screening code from (just) a CodeDirectory. | |
391 | // | |
392 | std::string CodeDirectory::screeningCode() const | |
393 | { | |
394 | if (slotIsPresent(-cdInfoSlot)) // has Info.plist | |
90dc47c2 | 395 | return "I" + hexHash(getSlot(-cdInfoSlot, false)); // use Info.plist hash |
e3d460c9 | 396 | if (slotIsPresent(-cdRepSpecificSlot)) // has Info.plist |
90dc47c2 | 397 | return "R" + hexHash(getSlot(-cdRepSpecificSlot, false)); // use Info.plist hash |
313fa17b | 398 | if (pageSize == 0) // good-enough proxy for "not a Mach-O file" |
90dc47c2 | 399 | return "M" + hexHash(getSlot(0, false)); // use hash of main executable |
313fa17b A |
400 | return "N"; // no suitable screening code |
401 | } | |
402 | ||
403 | ||
b1ab9ed8 A |
404 | } // CodeSigning |
405 | } // Security | |
406 | ||
407 | ||
408 | // | |
409 | // Canonical text form for user-settable code directory flags. | |
410 | // Note: This table is actually exported from Security.framework. | |
411 | // | |
412 | const SecCodeDirectoryFlagTable kSecCodeDirectoryFlagTable[] = { | |
413 | { "host", kSecCodeSignatureHost, true }, | |
414 | { "adhoc", kSecCodeSignatureAdhoc, false }, | |
415 | { "hard", kSecCodeSignatureForceHard, true }, | |
416 | { "kill", kSecCodeSignatureForceKill, true }, | |
427c49bc A |
417 | { "expires", kSecCodeSignatureForceExpiration, true }, |
418 | { "restrict", kSecCodeSignatureRestrict, true }, | |
419 | { "enforcement", kSecCodeSignatureEnforcement, true }, | |
420ff9d9 | 420 | { "library-validation", kSecCodeSignatureLibraryValidation, true }, |
90dc47c2 | 421 | { "runtime", kSecCodeSignatureRuntime, true }, |
b1ab9ed8 A |
422 | { NULL } |
423 | }; |