]> git.saurik.com Git - apple/security.git/blame - OSX/libsecurity_codesigning/lib/codedirectory.cpp
Security-59754.80.3.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / codedirectory.cpp
CommitLineData
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
32using namespace UnixPlusPlus;
33
34
35namespace Security {
36namespace CodeSigning {
37
38
39//
40// Highest understood special slot in this CodeDirectory.
41//
42CodeDirectory::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//
57const 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//
95unsigned 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)
128const 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//
153void 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 210bool 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 224bool 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//
238bool 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//
256DynamicHash *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//
272static const CodeDirectory::HashAlgorithm hashPriorities[] = {
273 kSecCodeSignatureHashSHA384,
274 kSecCodeSignatureHashSHA256,
275 kSecCodeSignatureHashSHA256Truncated,
276 kSecCodeSignatureHashSHA1,
277 kSecCodeSignatureNoHash // sentinel
278};
279
280bool 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
289CodeDirectory::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//
302void 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 //
325bool 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 337CFDataRef 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//
356size_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//
367size_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//
378std::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//
392std::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//
412const 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 },
d64be36e 422 { "linker-signed", kSecCodeSignatureLinkerSigned, true },
b1ab9ed8
A
423 { NULL }
424};