codedirectory.cpp
1 /* 2 * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved. 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" 30 #include <vector> 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; 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"; 72 case cdResourceDirSlot: 73 return kSecCS_RESOURCEDIRFILE; 74 case cdCodeDirectorySlot: 75 return kSecCS_CODEDIRECTORYFILE; 76 case cdSignatureSlot: 77 return kSecCS_SIGNATUREFILE; 78 case cdTopDirectorySlot: 79 return kSecCS_TOPDIRECTORYFILE; 80 case cdEntitlementSlot: 81 return kSecCS_ENTITLEMENTFILE; 82 case cdEntitlementDERSlot: 83 return kSecCS_ENTITLEMENTDERFILE; 84 case cdRepSpecificSlot: 85 return kSecCS_REPSPECIFICFILE; 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: 101 case cdAlternateCodeDirectorySlots: 102 case cdAlternateCodeDirectorySlots+1: 103 case cdAlternateCodeDirectorySlots+2: 104 case cdAlternateCodeDirectorySlots+3: 105 case cdAlternateCodeDirectorySlots+4: 106 return cdComponentPerArchitecture | cdComponentIsBlob; 107 case cdSignatureSlot: 108 return cdComponentPerArchitecture; // raw 109 case cdEntitlementSlot: 110 case cdEntitlementDERSlot: 111 return cdComponentIsBlob; // global 112 case cdIdentificationSlot: 113 return cdComponentPerArchitecture; // raw 114 case cdTicketSlot: 115 return 0; // global, raw 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", 133 "rep-specific", 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) 163 secinfo("codedir", "%p version 0x%x newer than current 0x%x", 164 this, uint32_t(version), currentVersion); 165 166 bool hasPreEncryptHashes = version >= supportsPreEncrypt && preEncryptOffset != 0; 167 168 // now check interior offsets for validity 169 if (!stringAt(identOffset)) 170 MacOSError::throwMe(errSecCSSignatureFailed); // identifier out of blob range 171 if (version >= supportsTeamID && teamIDOffset != 0 && !stringAt(teamIDOffset)) 172 MacOSError::throwMe(errSecCSSignatureFailed); // identifier out of blob range 173 if (!contains(hashOffset - int64_t(hashSize) * nSpecialSlots, hashSize * (int64_t(nSpecialSlots) + nCodeSlots))) 174 MacOSError::throwMe(errSecCSSignatureFailed); // hash array out of blob range 175 if (hasPreEncryptHashes && !contains(preEncryptOffset, hashSize * (int64_t(nCodeSlots)))) 176 MacOSError::throwMe(errSecCSSignatureFailed); // pre-encrypt array out of blob range 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; 180 for (;; scatter++) { 181 if (!contains(scatter, sizeof(Scatter))) 182 MacOSError::throwMe(errSecCSSignatureFailed); 183 if (scatter->count == 0) 184 break; 185 pagesConsumed += scatter->count; 186 } 187 if (!contains(getSlot(pagesConsumed-1, false), hashSize) || 188 (hasPreEncryptHashes && !contains(getSlot(pagesConsumed-1, true), hashSize))) // referenced too many main hash slots 189 MacOSError::throwMe(errSecCSSignatureFailed); 190 } 191 192 // check consistency between the page-coverage fields 193 size_t limit = signingLimit(); 194 if (pageSize) { 195 if (limit == 0) // can't have paged signatures with no covered data 196 MacOSError::throwMe(errSecCSSignatureFailed); 197 size_t coveredPages = ((limit-1) >> pageSize) + 1; // page slots required to cover signingLimit 198 if (coveredPages != nCodeSlots) 199 MacOSError::throwMe(errSecCSSignatureFailed); 200 } else { 201 if ((limit > 0) != nCodeSlots) // must have one code slot, or none if no code 202 MacOSError::throwMe(errSecCSSignatureFailed); 203 } 204 } 205 206 207 // 208 // Validate a slot against data in memory. 209 // 210 bool CodeDirectory::validateSlot(const void *data, size_t length, Slot slot, bool preEncrypt) const 211 { 212 secinfo("codedir", "%p validating slot %d", this, int(slot)); 213 MakeHash<CodeDirectory> hasher(this); 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; 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 // 224 bool CodeDirectory::validateSlot(FileDesc fd, size_t length, Slot slot, bool preEncrypt) const 225 { 226 MakeHash<CodeDirectory> hasher(this); 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; 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)) { 241 const Hashing::Byte *digest = getSlot(slot, false); 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 { 258 switch (hashType) { 259 case kSecCodeSignatureHashSHA1: return new CCHashInstance(kCCDigestSHA1); 260 case kSecCodeSignatureHashSHA256: return new CCHashInstance(kCCDigestSHA256); 261 case kSecCodeSignatureHashSHA384: return new CCHashInstance(kCCDigestSHA384); 262 case kSecCodeSignatureHashSHA256Truncated: return new CCHashInstance(kCCDigestSHA256, SHA1::digestLength); 263 default: 264 MacOSError::throwMe(errSecCSSignatureUnsupported); 265 } 266 } 267 268 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()); 305 map<HashAlgorithm, RefPointer<DynamicHash> > hashes; 306 for (auto it = types.begin(); it != types.end(); ++it) { 307 if (CodeDirectory::viableHash(*it)) 308 hashes[*it] = CodeDirectory::hashFor(*it); 309 } 310 scanFileData(fd, limit, ^(const void *buffer, size_t size) { 311 for (auto it = hashes.begin(); it != hashes.end(); ++it) { 312 it->second->update(buffer, size); 313 } 314 }); 315 CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary(); 316 for (auto it = hashes.begin(); it != hashes.end(); ++it) { 317 action(it->first, it->second); 318 } 319 } 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 } 331 332 333 // 334 // Generate the canonical cdhash - the internal hash of the CodeDirectory itself. 335 // With 'truncate' truncates to 20 bytes, because that's what's commonly used. 336 // 337 CFDataRef CodeDirectory::cdhash(bool truncate) const 338 { 339 MakeHash<CodeDirectory> hash(this); 340 vector<Hashing::Byte> digest_vector(hash->digestLength()); 341 hash->update(this, this->length()); 342 hash->finish(digest_vector.data()); 343 return makeCFData(digest_vector.data(), 344 truncate ? min(hash->digestLength(), size_t(kSecCodeCDHashLength)) : 345 hash->digestLength()); 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 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 395 return "I" + hexHash(getSlot(-cdInfoSlot, false)); // use Info.plist hash 396 if (slotIsPresent(-cdRepSpecificSlot)) // has Info.plist 397 return "R" + hexHash(getSlot(-cdRepSpecificSlot, false)); // use Info.plist hash 398 if (pageSize == 0) // good-enough proxy for "not a Mach-O file" 399 return "M" + hexHash(getSlot(0, false)); // use hash of main executable 400 return "N"; // no suitable screening code 401 } 402 403 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 }, 417 { "expires", kSecCodeSignatureForceExpiration, true }, 418 { "restrict", kSecCodeSignatureRestrict, true }, 419 { "enforcement", kSecCodeSignatureEnforcement, true }, 420 { "library-validation", kSecCodeSignatureLibraryValidation, true }, 421 { "runtime", kSecCodeSignatureRuntime, true }, 422 { "linker-signed", kSecCodeSignatureLinkerSigned, true }, 423 { NULL } 424 };