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