X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5c19dc3ae3bd8e40a9c028b0deddd50ff337692c..refs/heads/master:/OSX/libsecurity_codesigning/lib/StaticCode.cpp?ds=sidebyside diff --git a/OSX/libsecurity_codesigning/lib/StaticCode.cpp b/OSX/libsecurity_codesigning/lib/StaticCode.cpp index 6bcfb500..85abf01a 100644 --- a/OSX/libsecurity_codesigning/lib/StaticCode.cpp +++ b/OSX/libsecurity_codesigning/lib/StaticCode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved. + * Copyright (c) 2006-2015 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -27,31 +27,47 @@ #include "StaticCode.h" #include "Code.h" #include "reqmaker.h" +#if TARGET_OS_OSX #include "drmaker.h" +#include "notarization.h" +#endif #include "reqdumper.h" #include "reqparser.h" #include "sigblob.h" #include "resources.h" #include "detachedrep.h" +#include "signerutils.h" +#if TARGET_OS_OSX #include "csdatabase.h" +#endif #include "dirscanner.h" #include #include #include #include +#if TARGET_OS_OSX #include +#endif +#import #include #include #include +#if TARGET_OS_OSX #include +#endif #include #include +#include #include #include #include +#include #include #include #include +#include +#include +#import namespace Security { @@ -86,29 +102,33 @@ static inline OSStatus errorForSlot(CodeDirectory::SpecialSlot slot) // // Construct a SecStaticCode object given a disk representation object // -SecStaticCode::SecStaticCode(DiskRep *rep) - : mRep(rep), +SecStaticCode::SecStaticCode(DiskRep *rep, uint32_t flags) + : mCheckfix30814861builder1(NULL), + mRep(rep), mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL), - mProgressQueue("com.apple.security.validation-progress", false, DISPATCH_QUEUE_PRIORITY_DEFAULT), + mProgressQueue("com.apple.security.validation-progress", false, QOS_CLASS_UNSPECIFIED), mOuterScope(NULL), mResourceScope(NULL), - mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mLimitedAsync(NULL), mEvalDetails(NULL) + mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mLimitedAsync(NULL), + mFlags(flags), mNotarizationChecked(false), mStaplingChecked(false), mNotarizationDate(NAN) + , mTrustedSigningCertChain(false) + { CODESIGN_STATIC_CREATE(this, rep); - CFRef codeDirectory = rep->codeDirectory(); - if (codeDirectory && CFDataGetLength(codeDirectory) <= 0) - MacOSError::throwMe(errSecCSSignatureInvalid); +#if TARGET_OS_OSX checkForSystemSignature(); +#endif } // // Clean up a SecStaticCode object // -SecStaticCode::~SecStaticCode() throw() +SecStaticCode::~SecStaticCode() _NOEXCEPT try { ::free(const_cast(mDesignatedReq)); delete mResourcesValidContext; delete mLimitedAsync; + delete mCheckfix30814861builder1; } catch (...) { return; } @@ -161,7 +181,7 @@ CFTypeRef SecStaticCode::reportEvent(CFStringRef stage, CFDictionaryRef info) void SecStaticCode::prepareProgress(unsigned int workload) { dispatch_sync(mProgressQueue, ^{ - mCancelPending = false; // not cancelled + mCancelPending = false; // not canceled }); if (mValidationFlags & kSecCSReportProgress) { mCurrentWork = 0; // nothing done yet @@ -248,6 +268,7 @@ void SecStaticCode::detachedSignature(CFDataRef sigData) // void SecStaticCode::checkForSystemSignature() { +#if TARGET_OS_OSX if (!this->isSigned()) { SignatureDatabase db; if (db.isOpen()) @@ -259,6 +280,9 @@ void SecStaticCode::checkForSystemSignature() } catch (...) { } } +#else + MacOSError::throwMe(errSecUnimplemented); +#endif } @@ -319,6 +343,7 @@ void SecStaticCode::resetValidity() mResourcesValidContext = NULL; } mDir = NULL; + mCodeDirectories.clear(); mSignature = NULL; for (unsigned n = 0; n < cdSlotCount; n++) mCache[n] = NULL; @@ -330,11 +355,15 @@ void SecStaticCode::resetValidity() mGotResourceBase = false; mTrust = NULL; mCertChain = NULL; - mEvalDetails = NULL; + mNotarizationChecked = false; + mStaplingChecked = false; + mNotarizationDate = NAN; mRep->flush(); +#if TARGET_OS_OSX // we may just have updated the system database, so check again checkForSystemSignature(); +#endif } @@ -356,7 +385,7 @@ CFDataRef SecStaticCode::component(CodeDirectory::SpecialSlot slot, OSStatus fai return NULL; if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), // ... and it's no good - CFDataGetLength(data), -slot)) + CFDataGetLength(data), -slot, false)) MacOSError::throwMe(errorForSlot(slot)); // ... then bail } cache = data; // it's okay, cache it @@ -371,20 +400,67 @@ CFDataRef SecStaticCode::component(CodeDirectory::SpecialSlot slot, OSStatus fai } +// +// Get the CodeDirectories. +// Throws (if check==true) or returns NULL (check==false) if there are none. +// Always throws if the CodeDirectories exist but are invalid. +// NEVER validates against the signature. +// +const SecStaticCode::CodeDirectoryMap * +SecStaticCode::codeDirectories(bool check /* = true */) const +{ + if (mCodeDirectories.empty()) { + try { + loadCodeDirectories(mCodeDirectories); + } catch (...) { + if (check) + throw; + // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectories. + // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory. + if (!mCodeDirectories.empty()) { + assert(false); + Syslog::warning("code signing internal problem: mCodeDirectories set despite exception exit"); + MacOSError::throwMe(errSecCSInternalError); + } + } + } else { + return &mCodeDirectories; + } + if (!mCodeDirectories.empty()) { + return &mCodeDirectories; + } + if (check) { + MacOSError::throwMe(errSecCSUnsigned); + } + return NULL; +} + // // Get the CodeDirectory. // Throws (if check==true) or returns NULL (check==false) if there is none. // Always throws if the CodeDirectory exists but is invalid. // NEVER validates against the signature. // -const CodeDirectory *SecStaticCode::codeDirectory(bool check /* = true */) +const CodeDirectory *SecStaticCode::codeDirectory(bool check /* = true */) const { if (!mDir) { - if (mDir.take(mRep->codeDirectory())) { - const CodeDirectory *dir = reinterpret_cast(CFDataGetBytePtr(mDir)); - if (!dir->validateBlob(CFDataGetLength(mDir))) - MacOSError::throwMe(errSecCSSignatureInvalid); - dir->checkIntegrity(); + // pick our favorite CodeDirectory from the choices we've got + try { + CodeDirectoryMap const *candidates = codeDirectories(check); + if (candidates != NULL) { + CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms); + mDir = candidates->at(type); // and the winner is... + } + } catch (...) { + if (check) + throw; + // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectory. + // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory. + if (mDir) { + assert(false); + Syslog::warning("code signing internal problem: mDir set despite exception exit"); + MacOSError::throwMe(errSecCSInternalError); + } } } if (mDir) @@ -395,6 +471,46 @@ const CodeDirectory *SecStaticCode::codeDirectory(bool check /* = true */) } +// +// Fetch an array of all available CodeDirectories. +// Returns false if unsigned (no classic CD slot), true otherwise. +// +bool SecStaticCode::loadCodeDirectories(CodeDirectoryMap& cdMap) const +{ + __block CodeDirectoryMap candidates; + __block CodeDirectory::HashAlgorithms hashAlgorithms; + __block CFRef baseDir; + auto add = ^bool (CodeDirectory::SpecialSlot slot){ + CFRef cdData = diskRep()->component(slot); + if (!cdData) + return false; + const CodeDirectory* cd = reinterpret_cast(CFDataGetBytePtr(cdData)); + if (!cd->validateBlob(CFDataGetLength(cdData))) + MacOSError::throwMe(errSecCSSignatureFailed); // no recovery - any suspect CD fails + cd->checkIntegrity(); + auto result = candidates.insert(make_pair(cd->hashType, cdData.get())); + if (!result.second) + MacOSError::throwMe(errSecCSSignatureInvalid); // duplicate hashType, go to heck + hashAlgorithms.insert(cd->hashType); + if (slot == cdCodeDirectorySlot) + baseDir = cdData; + return true; + }; + if (!add(cdCodeDirectorySlot)) + return false; // no classic slot CodeDirectory -> unsigned + for (CodeDirectory::SpecialSlot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; slot++) + if (!add(slot)) // no CodeDirectory at this slot -> end of alternates + break; + if (candidates.empty()) + MacOSError::throwMe(errSecCSSignatureFailed); // no viable CodeDirectory in sight + // commit to cached values + cdMap.swap(candidates); + mHashAlgorithms.swap(hashAlgorithms); + mBaseDir = baseDir; + return true; +} + + // // Get the hash of the CodeDirectory. // Returns NULL if there is none. @@ -409,6 +525,45 @@ CFDataRef SecStaticCode::cdHash() } return mCDHash; } + + +// +// Get an array of the cdhashes for all digest types in this signature +// The array is sorted by cd->hashType. +// +CFArrayRef SecStaticCode::cdHashes() +{ + if (!mCDHashes) { + CFRef cdList = makeCFMutableArray(0); + for (auto it = mCodeDirectories.begin(); it != mCodeDirectories.end(); ++it) { + const CodeDirectory *cd = (const CodeDirectory *)CFDataGetBytePtr(it->second); + if (CFRef hash = cd->cdhash()) + CFArrayAppendValue(cdList, hash); + } + mCDHashes = cdList.get(); + } + return mCDHashes; +} + +// +// Get a dictionary of untruncated cdhashes for all digest types in this signature. +// +CFDictionaryRef SecStaticCode::cdHashesFull() +{ + if (!mCDHashFullDict) { + CFRef cdDict = makeCFMutableDictionary(); + for (auto const &it : mCodeDirectories) { + CodeDirectory::HashAlgorithm alg = it.first; + const CodeDirectory *cd = (const CodeDirectory *)CFDataGetBytePtr(it.second); + CFRef hash = cd->cdhash(false); + if (hash) { + CFDictionaryAddValue(cdDict, CFTempNumber(alg), hash); + } + } + mCDHashFullDict = cdDict.get(); + } + return mCDHashFullDict; +} // @@ -452,12 +607,17 @@ void SecStaticCode::validateDirectory() mValidationResult = err.osStatus(); throw; } catch (...) { - secdebug("staticCode", "%p validation threw non-common exception", this); + secinfo("staticCode", "%p validation threw non-common exception", this); mValidated = true; + Syslog::notice("code signing internal problem: unknown exception thrown by validation"); mValidationResult = errSecCSInternalError; throw; } assert(validated()); + // XXX: Embedded doesn't have CSSMERR_TP_CERT_EXPIRED so we can't throw it + // XXX: This should be implemented for embedded once we implement + // XXX: verifySignature and see how we're going to handle expired certs +#if TARGET_OS_OSX if (mValidationResult == errSecSuccess) { if (mValidationExpired) if ((mValidationFlags & kSecCSConsiderExpiration) @@ -465,6 +625,7 @@ void SecStaticCode::validateDirectory() MacOSError::throwMe(CSSMERR_TP_CERT_EXPIRED); } else MacOSError::throwMe(mValidationResult); +#endif } @@ -484,6 +645,33 @@ void SecStaticCode::validateNonResourceComponents() break; } } + + +// +// Check that any "top index" sealed into the signature conforms to what's actually here. +// +void SecStaticCode::validateTopDirectory() +{ + assert(mDir); // must already have loaded CodeDirectories + if (CFDataRef topDirectory = component(cdTopDirectorySlot)) { + const auto topData = (const Endian *)CFDataGetBytePtr(topDirectory); + const auto topDataEnd = topData + CFDataGetLength(topDirectory) / sizeof(*topData); + std::vector signedVector(topData, topDataEnd); + + std::vector foundVector; + foundVector.push_back(cdCodeDirectorySlot); // mandatory + for (CodeDirectory::Slot slot = 1; slot <= cdSlotMax; ++slot) + if (component(slot)) + foundVector.push_back(slot); + int alternateCount = int(mCodeDirectories.size() - 1); // one will go into cdCodeDirectorySlot + for (int n = 0; n < alternateCount; n++) + foundVector.push_back(cdAlternateCodeDirectorySlots + n); + foundVector.push_back(cdSignatureSlot); // mandatory (may be empty) + + if (signedVector != foundVector) + MacOSError::throwMe(errSecCSSignatureFailed); + } +} // @@ -506,9 +694,61 @@ CFAbsoluteTime SecStaticCode::signingTimestamp() return mSigningTimestamp; } +#if TARGET_OS_OSX +#define kSecSHA256HashSize 32 +// subject:/C=US/ST=California/L=San Jose/O=Adobe Systems Incorporated/OU=Information Systems/OU=Digital ID Class 3 - Microsoft Software Validation v2/CN=Adobe Systems Incorporated +// issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 Code Signing 2010 CA +// Not Before: Dec 15 00:00:00 2010 GMT +// Not After : Dec 14 23:59:59 2012 GMT +static const unsigned char ASI_CS_12[] = { + 0x77,0x82,0x9C,0x64,0x33,0x45,0x2E,0x4A,0xD3,0xA8,0xE4,0x6F,0x00,0x6C,0x27,0xEA, + 0xFB,0xD3,0xF2,0x6D,0x50,0xF3,0x6F,0xE0,0xE9,0x6D,0x06,0x59,0x19,0xB5,0x46,0xFF +}; + +bool SecStaticCode::checkfix41082220(OSStatus cssmTrustResult) +{ + // only applicable to revoked results + if (cssmTrustResult != CSSMERR_TP_CERT_REVOKED) { + return false; + } + + // only this leaf certificate + if (CFArrayGetCount(mCertChain) == 0) { + return false; + } + CFRef leafHash(SecCertificateCopySHA256Digest((SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, 0))); + if (memcmp(ASI_CS_12, CFDataGetBytePtr(leafHash), kSecSHA256HashSize) != 0) { + return false; + } + + // detached dmg signature + if (!isDetached() || format() != std::string("disk image")) { + return false; + } + + // sha-1 signed + if (hashAlgorithms().size() != 1 || hashAlgorithm() != kSecCodeSignatureHashSHA1) { + return false; + } + + // not a privileged binary - no TeamID and no entitlements + if (component(cdEntitlementSlot) || teamID()) { + return false; + } + + // no flags and old version + if (codeDirectory()->version != 0x20100 || codeDirectory()->flags != 0) { + return false; + } + + Security::Syslog::warning("CodeSigning: Check-fix enabled for dmg '%s' with identifier '%s' signed with revoked certificates", + mainExecutablePath().c_str(), identifier().c_str()); + return true; +} +#endif // TARGET_OS_OSX // -// Verify the CMS signature on the CodeDirectory. +// Verify the CMS signature. // This performs the cryptographic tango. It returns if the signature is valid, // or throws if it is not. As a side effect, a successful return sets up the // cached certificate chain for future use. @@ -524,214 +764,387 @@ bool SecStaticCode::verifySignature() } DTRACK(CODESIGN_EVAL_STATIC_SIGNATURE, this, (char*)this->mainExecutablePath().c_str()); - - // decode CMS and extract SecTrust for verification - CFRef cms; - MacOSError::check(CMSDecoderCreate(&cms.aref())); // create decoder - CFDataRef sig = this->signature(); - MacOSError::check(CMSDecoderUpdateMessage(cms, CFDataGetBytePtr(sig), CFDataGetLength(sig))); - this->codeDirectory(); // load CodeDirectory (sets mDir) - MacOSError::check(CMSDecoderSetDetachedContent(cms, mDir)); - MacOSError::check(CMSDecoderFinalizeMessage(cms)); - MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray())); - CFRef vf_policies = verificationPolicies(); - CFRef ts_policies = SecPolicyCreateAppleTimeStampingAndRevocationPolicies(vf_policies); - CMSSignerStatus status; - MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, vf_policies, - false, &status, &mTrust.aref(), NULL)); - - if (status != kCMSSignerValid) { - const char *reason; - switch (status) { - case kCMSSignerUnsigned: reason="kCMSSignerUnsigned"; break; - case kCMSSignerNeedsDetachedContent: reason="kCMSSignerNeedsDetachedContent"; break; - case kCMSSignerInvalidSignature: reason="kCMSSignerInvalidSignature"; break; - case kCMSSignerInvalidCert: reason="kCMSSignerInvalidCert"; break; - case kCMSSignerInvalidIndex: reason="kCMSSignerInvalidIndex"; break; - default: reason="unknown"; break; +#if TARGET_OS_OSX + if (!(mValidationFlags & kSecCSApplyEmbeddedPolicy)) { + // decode CMS and extract SecTrust for verification + CFRef cms; + MacOSError::check(CMSDecoderCreate(&cms.aref())); // create decoder + CFDataRef sig = this->signature(); + MacOSError::check(CMSDecoderUpdateMessage(cms, CFDataGetBytePtr(sig), CFDataGetLength(sig))); + this->codeDirectory(); // load CodeDirectory (sets mDir) + MacOSError::check(CMSDecoderSetDetachedContent(cms, mBaseDir)); + MacOSError::check(CMSDecoderFinalizeMessage(cms)); + MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray())); + CFRef vf_policies(createVerificationPolicies()); + CFRef ts_policies(createTimeStampingAndRevocationPolicies()); + + CMSSignerStatus status; + MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, vf_policies, + false, &status, &mTrust.aref(), NULL)); + + if (status != kCMSSignerValid) { + const char *reason; + switch (status) { + case kCMSSignerUnsigned: reason="kCMSSignerUnsigned"; break; + case kCMSSignerNeedsDetachedContent: reason="kCMSSignerNeedsDetachedContent"; break; + case kCMSSignerInvalidSignature: reason="kCMSSignerInvalidSignature"; break; + case kCMSSignerInvalidCert: reason="kCMSSignerInvalidCert"; break; + case kCMSSignerInvalidIndex: reason="kCMSSignerInvalidIndex"; break; + default: reason="unknown"; break; + } + Security::Syslog::error("CMSDecoderCopySignerStatus failed with %s error (%d)", + reason, (int)status); + MacOSError::throwMe(errSecCSSignatureFailed); } - Security::Syslog::error("CMSDecoderCopySignerStatus failed with %s error (%d)", - reason, (int)status); - MacOSError::throwMe(errSecCSSignatureFailed); - } - - // internal signing time (as specified by the signer; optional) - mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-) - switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) { - case errSecSuccess: - case errSecSigningTimeMissing: - break; - default: - Security::Syslog::error("Could not get signing time (error %d)", (int)rc); - MacOSError::throwMe(rc); - } - - // certified signing time (as specified by a TSA; optional) - mSigningTimestamp = 0; - switch (OSStatus rc = CMSDecoderCopySignerTimestampWithPolicy(cms, ts_policies, 0, &mSigningTimestamp)) { - case errSecSuccess: - case errSecTimestampMissing: - break; - default: - Security::Syslog::error("Could not get timestamp (error %d)", (int)rc); - MacOSError::throwMe(rc); - } - // set up the environment for SecTrust - if (mValidationFlags & kSecCSNoNetworkAccess) { - MacOSError::check(SecTrustSetNetworkFetchAllowed(mTrust,false)); // no network? - } - MacOSError::check(SecTrustSetKeychains(mTrust, cfEmptyArray())); // no keychains - - CSSM_APPLE_TP_ACTION_DATA actionData = { - CSSM_APPLE_TP_ACTION_VERSION, // version of data structure - 0 // action flags - }; - - if (!(mValidationFlags & kSecCSCheckTrustedAnchors)) { - /* no need to evaluate anchor trust when building cert chain */ - MacOSError::check(SecTrustSetAnchorCertificates(mTrust, cfEmptyArray())); // no anchors - actionData.ActionFlags |= CSSM_TP_ACTION_IMPLICIT_ANCHORS; // action flags - } + // retrieve auxiliary v1 data bag and verify against current state + CFRef hashAgilityV1; + switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgility(cms, 0, &hashAgilityV1.aref())) { + case noErr: + if (hashAgilityV1) { + CFRef hashDict = makeCFDictionaryFrom(hashAgilityV1); + CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes"))); + CFArrayRef myCdList = this->cdHashes(); + + /* Note that this is not very "agile": There's no way to calculate the exact + * list for comparison if it contains hash algorithms we don't know yet... */ + if (cdList == NULL || !CFEqual(cdList, myCdList)) + MacOSError::throwMe(errSecCSSignatureFailed); + } + break; + case -1: /* CMS used to return this for "no attribute found", so tolerate it. Now returning noErr/NULL */ + break; + default: + MacOSError::throwMe(rc); + } - for (;;) { // at most twice - MacOSError::check(SecTrustSetParameters(mTrust, - CSSM_TP_ACTION_DEFAULT, CFTempData(&actionData, sizeof(actionData)))); + // retrieve auxiliary v2 data bag and verify against current state + CFRef hashAgilityV2; + switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgilityV2(cms, 0, &hashAgilityV2.aref())) { + case noErr: + if (hashAgilityV2) { + /* Require number of code directoris and entries in the hash agility + * dict to be the same size (no stripping out code directories). + */ + if (CFDictionaryGetCount(hashAgilityV2) != mCodeDirectories.size()) { + MacOSError::throwMe(errSecCSSignatureFailed); + } - // evaluate trust and extract results - SecTrustResultType trustResult; - MacOSError::check(SecTrustEvaluate(mTrust, &trustResult)); - MacOSError::check(SecTrustGetResult(mTrust, &trustResult, &mCertChain.aref(), &mEvalDetails)); - - // if this is an Apple developer cert.... - if (teamID() && SecStaticCode::isAppleDeveloperCert(mCertChain)) { - CFRef teamIDFromCert; - if (CFArrayGetCount(mCertChain) > 0) { - /* Note that SecCertificateCopySubjectComponent sets the out parameter to NULL if there is no field present */ - MacOSError::check(SecCertificateCopySubjectComponent((SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, Requirement::leafCert), - &CSSMOID_OrganizationalUnitName, - &teamIDFromCert.aref())); - - if (teamIDFromCert) { - CFRef teamIDFromCD = CFStringCreateWithCString(NULL, teamID(), kCFStringEncodingUTF8); - if (!teamIDFromCD) { - Security::Syslog::error("Could not get team identifier (%s)", teamID()); - MacOSError::throwMe(errSecCSInternalError); + /* Require every cdhash of every code directory whose hash + * algorithm we know to be in the agility dictionary. + * + * We check untruncated cdhashes here because we can. + */ + bool foundOurs = false; + for (auto& entry : mCodeDirectories) { + SECOidTag tag = CodeDirectorySet::SECOidTagForAlgorithm(entry.first); + + if (tag == SEC_OID_UNKNOWN) { + // Unknown hash algorithm, ignore. + continue; + } + + CFRef key = makeCFNumber(int(tag)); + CFRef entryCdhash; + entryCdhash = (CFDataRef)CFDictionaryGetValue(hashAgilityV2, (void*)key.get()); + + CodeDirectory const *cd = (CodeDirectory const*)CFDataGetBytePtr(entry.second); + CFRef ourCdhash = cd->cdhash(false); // Untruncated cdhash! + if (!CFEqual(entryCdhash, ourCdhash)) { + MacOSError::throwMe(errSecCSSignatureFailed); + } + + if (entry.first == this->hashAlgorithm()) { + foundOurs = true; + } } - if (CFStringCompare(teamIDFromCert, teamIDFromCD, 0) != kCFCompareEqualTo) { - Security::Syslog::error("Team identifier in the signing certificate (%s) does not match the team identifier (%s) in the code directory", cfString(teamIDFromCert).c_str(), teamID()); - MacOSError::throwMe(errSecCSSignatureInvalid); + /* Require the cdhash of our chosen code directory to be in the dictionary. + * In theory, the dictionary could be full of unsupported cdhashes, but we + * really want ours, which is bound to be supported, to be covered. + */ + if (!foundOurs) { + MacOSError::throwMe(errSecCSSignatureFailed); } } - } + break; + case -1: /* CMS used to return this for "no attribute found", so tolerate it. Now returning noErr/NULL */ + break; + default: + MacOSError::throwMe(rc); + } + + // internal signing time (as specified by the signer; optional) + mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-) + switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) { + case errSecSuccess: + case errSecSigningTimeMissing: + break; + default: + Security::Syslog::error("Could not get signing time (error %d)", (int)rc); + MacOSError::throwMe(rc); } - CODESIGN_EVAL_STATIC_SIGNATURE_RESULT(this, trustResult, mCertChain ? (int)CFArrayGetCount(mCertChain) : 0); - switch (trustResult) { - case kSecTrustResultProceed: - case kSecTrustResultUnspecified: - break; // success - case kSecTrustResultDeny: - MacOSError::throwMe(CSSMERR_APPLETP_TRUST_SETTING_DENY); // user reject - case kSecTrustResultInvalid: - assert(false); // should never happen - MacOSError::throwMe(CSSMERR_TP_NOT_TRUSTED); + // certified signing time (as specified by a TSA; optional) + mSigningTimestamp = 0; + switch (OSStatus rc = CMSDecoderCopySignerTimestampWithPolicy(cms, ts_policies, 0, &mSigningTimestamp)) { + case errSecSuccess: + case errSecTimestampMissing: + break; default: - { - OSStatus result; - MacOSError::check(SecTrustGetCssmResultCode(mTrust, &result)); - // if we have a valid timestamp, CMS validates against (that) signing time and all is well. - // If we don't have one, may validate against *now*, and must be able to tolerate expiration. - if (mSigningTimestamp == 0) { // no timestamp available - if (((result == CSSMERR_TP_CERT_EXPIRED) || (result == CSSMERR_TP_CERT_NOT_VALID_YET)) - && !(actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED)) { - CODESIGN_EVAL_STATIC_SIGNATURE_EXPIRED(this); - actionData.ActionFlags |= CSSM_TP_ACTION_ALLOW_EXPIRED; // (this also allows postdated certs) - continue; // retry validation while tolerating expiration + Security::Syslog::error("Could not get timestamp (error %d)", (int)rc); + MacOSError::throwMe(rc); + } + + // set up the environment for SecTrust + if (mValidationFlags & kSecCSNoNetworkAccess) { + MacOSError::check(SecTrustSetNetworkFetchAllowed(mTrust,false)); // no network? + } + MacOSError::check(SecTrustSetKeychainsAllowed(mTrust, false)); + + CSSM_APPLE_TP_ACTION_DATA actionData = { + CSSM_APPLE_TP_ACTION_VERSION, // version of data structure + 0 // action flags + }; + + if (!(mValidationFlags & kSecCSCheckTrustedAnchors)) { + /* no need to evaluate anchor trust when building cert chain */ + MacOSError::check(SecTrustSetAnchorCertificates(mTrust, cfEmptyArray())); // no anchors + actionData.ActionFlags |= CSSM_TP_ACTION_IMPLICIT_ANCHORS; // action flags + } + + for (;;) { // at most twice + MacOSError::check(SecTrustSetParameters(mTrust, + CSSM_TP_ACTION_DEFAULT, CFTempData(&actionData, sizeof(actionData)))); + + // evaluate trust and extract results + SecTrustResultType trustResult; + MacOSError::check(SecTrustEvaluate(mTrust, &trustResult)); + mCertChain.take(copyCertChain(mTrust)); + + // if this is an Apple developer cert.... + if (teamID() && SecStaticCode::isAppleDeveloperCert(mCertChain)) { + CFRef teamIDFromCert; + if (CFArrayGetCount(mCertChain) > 0) { + SecCertificateRef leaf = (SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, Requirement::leafCert); + CFArrayRef organizationalUnits = SecCertificateCopyOrganizationalUnit(leaf); + if (organizationalUnits) { + teamIDFromCert.take((CFStringRef)CFRetain(CFArrayGetValueAtIndex(organizationalUnits, 0))); + CFRelease(organizationalUnits); + } else { + teamIDFromCert = NULL; + } + + if (teamIDFromCert) { + CFRef teamIDFromCD = CFStringCreateWithCString(NULL, teamID(), kCFStringEncodingUTF8); + if (!teamIDFromCD) { + Security::Syslog::error("Could not get team identifier (%s)", teamID()); + MacOSError::throwMe(errSecCSInvalidTeamIdentifier); + } + + if (CFStringCompare(teamIDFromCert, teamIDFromCD, 0) != kCFCompareEqualTo) { + Security::Syslog::error("Team identifier in the signing certificate (%s) does not match the team identifier (%s) in the code directory", + cfString(teamIDFromCert).c_str(), teamID()); + MacOSError::throwMe(errSecCSBadTeamIdentifier); + } } } - Security::Syslog::error("SecStaticCode: verification failed (trust result %d, error %d)", trustResult, (int)result); - MacOSError::throwMe(result); } - } - if (mSigningTimestamp) { - CFIndex rootix = CFArrayGetCount(mCertChain); - if (SecCertificateRef mainRoot = SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, rootix-1))) - if (isAppleCA(mainRoot)) { - // impose policy: if the signature itself draws to Apple, then so must the timestamp signature - CFRef tsCerts; - OSStatus result = CMSDecoderCopySignerTimestampCertificates(cms, 0, &tsCerts.aref()); - if (result) { - Security::Syslog::error("SecStaticCode: could not get timestamp certificates (error %d)", (int)result); - MacOSError::check(result); + CODESIGN_EVAL_STATIC_SIGNATURE_RESULT(this, trustResult, mCertChain ? (int)CFArrayGetCount(mCertChain) : 0); + switch (trustResult) { + case kSecTrustResultProceed: + case kSecTrustResultUnspecified: + break; // success + case kSecTrustResultDeny: + MacOSError::throwMe(CSSMERR_APPLETP_TRUST_SETTING_DENY); // user reject + case kSecTrustResultInvalid: + assert(false); // should never happen + MacOSError::throwMe(CSSMERR_TP_NOT_TRUSTED); + default: + { + OSStatus result; + MacOSError::check(SecTrustGetCssmResultCode(mTrust, &result)); + // if we have a valid timestamp, CMS validates against (that) signing time and all is well. + // If we don't have one, may validate against *now*, and must be able to tolerate expiration. + if (mSigningTimestamp == 0) { // no timestamp available + if (((result == CSSMERR_TP_CERT_EXPIRED) || (result == CSSMERR_TP_CERT_NOT_VALID_YET)) + && !(actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED)) { + CODESIGN_EVAL_STATIC_SIGNATURE_EXPIRED(this); + actionData.ActionFlags |= CSSM_TP_ACTION_ALLOW_EXPIRED; // (this also allows postdated certs) + continue; // retry validation while tolerating expiration + } } - CFIndex tsn = CFArrayGetCount(tsCerts); - bool good = tsn > 0 && isAppleCA(SecCertificateRef(CFArrayGetValueAtIndex(tsCerts, tsn-1))); - if (!good) { - result = CSSMERR_TP_NOT_TRUSTED; - Security::Syslog::error("SecStaticCode: timestamp policy verification failed (error %d)", (int)result); - MacOSError::throwMe(result); + if (checkfix41082220(result)) { + break; // success } + Security::Syslog::error("SecStaticCode: verification failed (trust result %d, error %d)", trustResult, (int)result); + MacOSError::throwMe(result); } + } + + if (mSigningTimestamp) { + CFIndex rootix = CFArrayGetCount(mCertChain); + if (SecCertificateRef mainRoot = SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, rootix-1))) + if (isAppleCA(mainRoot)) { + // impose policy: if the signature itself draws to Apple, then so must the timestamp signature + CFRef tsCerts; + OSStatus result = CMSDecoderCopySignerTimestampCertificates(cms, 0, &tsCerts.aref()); + if (result) { + Security::Syslog::error("SecStaticCode: could not get timestamp certificates (error %d)", (int)result); + MacOSError::check(result); + } + CFIndex tsn = CFArrayGetCount(tsCerts); + bool good = tsn > 0 && isAppleCA(SecCertificateRef(CFArrayGetValueAtIndex(tsCerts, tsn-1))); + if (!good) { + result = CSSMERR_TP_NOT_TRUSTED; + Security::Syslog::error("SecStaticCode: timestamp policy verification failed (error %d)", (int)result); + MacOSError::throwMe(result); + } + } + } + + return actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED; + } + + } else +#endif + { + // Do some pre-verification initialization + CFDataRef sig = this->signature(); + this->codeDirectory(); // load CodeDirectory (sets mDir) + mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-) + + CFRef attrs; + CFRef vf_policies(createVerificationPolicies()); + + // Verify the CMS signature against mBaseDir (SHA1) + MacOSError::check(SecCMSVerifyCopyDataAndAttributes(sig, mBaseDir, vf_policies, &mTrust.aref(), NULL, &attrs.aref())); + + // Copy the signing time + mSigningTime = SecTrustGetVerifyTime(mTrust); + + // Validate the cert chain + SecTrustResultType trustResult; + MacOSError::check(SecTrustEvaluate(mTrust, &trustResult)); + + // retrieve auxiliary data bag and verify against current state + CFRef hashBag; + hashBag = CFDataRef(CFDictionaryGetValue(attrs, kSecCMSHashAgility)); + if (hashBag) { + CFRef hashDict = makeCFDictionaryFrom(hashBag); + CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes"))); + CFArrayRef myCdList = this->cdHashes(); + if (cdList == NULL || !CFEqual(cdList, myCdList)) + MacOSError::throwMe(errSecCSSignatureFailed); } - return actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED; + /* + * Populate mCertChain with the certs. If we failed validation, the + * signer's cert will be checked installed provisioning profiles as an + * alternative to verification against the policy for store-signed binaries + */ + mCertChain.take(copyCertChain(mTrust)); + + // Did we implicitly trust the signer? + mTrustedSigningCertChain = (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed); + + return false; // XXX: Not checking for expired certs } } - +#if TARGET_OS_OSX // // Return the TP policy used for signature verification. // This may be a simple SecPolicyRef or a CFArray of policies. // The caller owns the return value. // -static SecPolicyRef makeCRLPolicy() +static SecPolicyRef makeRevocationPolicy(CFOptionFlags flags) { - CFRef policy; - MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref())); - CSSM_APPLE_TP_CRL_OPTIONS options; - memset(&options, 0, sizeof(options)); - options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; - options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT; - CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; - MacOSError::check(SecPolicySetValue(policy, &optData)); + CFRef policy(SecPolicyCreateRevocation(flags)); return policy.yield(); } +#endif -static SecPolicyRef makeOCSPPolicy() +CFArrayRef SecStaticCode::createVerificationPolicies() { - CFRef policy; - MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref())); - CSSM_APPLE_TP_OCSP_OPTIONS options; - memset(&options, 0, sizeof(options)); - options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; - options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; - CSSM_DATA optData = { sizeof(options), (uint8 *)&options }; - MacOSError::check(SecPolicySetValue(policy, &optData)); - return policy.yield(); -} + if (mValidationFlags & kSecCSUseSoftwareSigningCert) { + CFRef ssRef = SecPolicyCreateAppleSoftwareSigning(); + return makeCFArray(1, ssRef.get()); + } +#if TARGET_OS_OSX + if (mValidationFlags & kSecCSApplyEmbeddedPolicy) { + CFRef iOSRef = SecPolicyCreateiPhoneApplicationSigning(); + return makeCFArray(1, iOSRef.get()); + } -CFArrayRef SecStaticCode::verificationPolicies() -{ CFRef core; MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, - &CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref())); - if (mValidationFlags & kSecCSNoNetworkAccess) { - // Skips all revocation since they require network connectivity - // therefore annihilates kSecCSEnforceRevocationChecks if present - CFRef no_revoc = SecPolicyCreateRevocation(kSecRevocationNetworkAccessDisabled); - return makeCFArray(2, core.get(), no_revoc.get()); - } + &CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref())); + if (mValidationFlags & kSecCSNoNetworkAccess) { + // Skips all revocation since they require network connectivity + // therefore annihilates kSecCSEnforceRevocationChecks if present + CFRef no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled); + return makeCFArray(2, core.get(), no_revoc.get()); + } else if (mValidationFlags & kSecCSEnforceRevocationChecks) { - // Add CRL and OCSPPolicies - CFRef crl = makeCRLPolicy(); - CFRef ocsp = makeOCSPPolicy(); - return makeCFArray(3, core.get(), crl.get(), ocsp.get()); + // Add CRL and OCSP policies + CFRef revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod); + return makeCFArray(2, core.get(), revoc.get()); } else { return makeCFArray(1, core.get()); } +#elif TARGET_OS_TV + CFRef tvOSRef = SecPolicyCreateAppleTVOSApplicationSigning(); + return makeCFArray(1, tvOSRef.get()); +#else + CFRef iOSRef = SecPolicyCreateiPhoneApplicationSigning(); + return makeCFArray(1, iOSRef.get()); +#endif + +} + +CFArrayRef SecStaticCode::createTimeStampingAndRevocationPolicies() +{ + CFRef tsPolicy = SecPolicyCreateAppleTimeStamping(); +#if TARGET_OS_OSX + if (mValidationFlags & kSecCSNoNetworkAccess) { + // Skips all revocation since they require network connectivity + // therefore annihilates kSecCSEnforceRevocationChecks if present + CFRef no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled); + return makeCFArray(2, tsPolicy.get(), no_revoc.get()); + } + else if (mValidationFlags & kSecCSEnforceRevocationChecks) { + // Add CRL and OCSP policies + CFRef revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod); + return makeCFArray(2, tsPolicy.get(), revoc.get()); + } + else { + return makeCFArray(1, tsPolicy.get()); + } +#else + return makeCFArray(1, tsPolicy.get()); +#endif + +} + +CFArrayRef SecStaticCode::copyCertChain(SecTrustRef trust) +{ + SecCertificateRef leafCert = SecTrustGetCertificateAtIndex(trust, 0); + if (leafCert != NULL) { + CFIndex count = SecTrustGetCertificateCount(trust); + + CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, count, + &kCFTypeArrayCallBacks); + + CFArrayAppendValue(certs, leafCert); + for (CFIndex i = 1; i < count; ++i) { + CFArrayAppendValue(certs, SecTrustGetCertificateAtIndex(trust, i)); + } + + return certs; + } + return NULL; } @@ -749,7 +1162,7 @@ void SecStaticCode::validateComponent(CodeDirectory::SpecialSlot slot, OSStatus if (codeDirectory()->slotIsPresent(-slot)) // was supposed to be there... MacOSError::throwMe(fail); // ... and is missing } else { - if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot)) + if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot, false)) MacOSError::throwMe(fail); } } @@ -776,15 +1189,25 @@ void SecStaticCode::validateExecutable() if (Universal *fat = mRep->mainExecutableImage()) fd.seek(fat->archOffset()); size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0; - size_t remaining = cd->codeLimit; + size_t remaining = cd->signingLimit(); for (uint32_t slot = 0; slot < cd->nCodeSlots; ++slot) { - size_t size = min(remaining, pageSize); - if (!cd->validateSlot(fd, size, slot)) { + size_t thisPage = remaining; + if (pageSize) + thisPage = min(thisPage, pageSize); + __block bool good = true; + CodeDirectory::multipleHashFileData(fd, thisPage, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) { + const CodeDirectory* cd = (const CodeDirectory*)CFDataGetBytePtr(mCodeDirectories[type]); + if (!hasher->verify(cd->getSlot(slot, + mValidationFlags & kSecCSValidatePEH))) + good = false; + }); + if (!good) { CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, (int)slot); MacOSError::throwMe(errSecCSSignatureFailed); } - remaining -= size; + remaining -= thisPage; } + assert(remaining == 0); mExecutableValidated = true; mExecutableValidResult = errSecSuccess; } catch (const CommonError &err) { @@ -792,9 +1215,10 @@ void SecStaticCode::validateExecutable() mExecutableValidResult = err.osStatus(); throw; } catch (...) { - secdebug("staticCode", "%p executable validation threw non-common exception", this); + secinfo("staticCode", "%p executable validation threw non-common exception", this); mExecutableValidated = true; mExecutableValidResult = errSecCSInternalError; + Syslog::notice("code signing internal problem: unknown exception thrown by validation"); throw; } } @@ -803,7 +1227,6 @@ void SecStaticCode::validateExecutable() MacOSError::throwMe(mExecutableValidResult); } - // // Perform static validation of sealed resources and nested code. // @@ -831,23 +1254,29 @@ void SecStaticCode::validateResources(SecCSFlags flags) } if (doit) { + string root = cfStringRelease(copyCanonicalPath()); + bool itemIsOnRootFS = isOnRootFilesystem(root.c_str()); + bool skipRootVolumeExceptions = (mValidationFlags & kSecCSSkipRootVolumeExceptions); + bool useRootFSPolicy = itemIsOnRootFS && !skipRootVolumeExceptions; + + bool itemMightUseXattrFiles = pathFileSystemUsesXattrFiles(root.c_str()); + bool skipXattrFiles = itemMightUseXattrFiles && (mValidationFlags & kSecCSSkipXattrFiles); + + secinfo("staticCode", "performing resource validation for %s (%d, %d, %d, %d, %d)", root.c_str(), + itemIsOnRootFS, skipRootVolumeExceptions, useRootFSPolicy, itemMightUseXattrFiles, skipXattrFiles); + if (mLimitedAsync == NULL) { - mLimitedAsync = new LimitedAsync(diskRep()->fd().mediumType() == kIOPropertyMediumTypeSolidStateKey); + bool runMultiThreaded = ((flags & kSecCSSingleThreaded) == kSecCSSingleThreaded) ? false : + (diskRep()->fd().mediumType() == kIOPropertyMediumTypeSolidStateKey); + mLimitedAsync = new LimitedAsync(runMultiThreaded); } try { - // sanity first - CFDictionaryRef sealedResources = resourceDictionary(); - if (this->resourceBase()) // disk has resources - if (sealedResources) - /* go to work below */; - else - MacOSError::throwMe(errSecCSResourcesNotFound); - else // disk has no resources - if (sealedResources) - MacOSError::throwMe(errSecCSResourcesNotFound); - else - return; // no resources, not sealed - fine (no work) + CFDictionaryRef rules; + CFDictionaryRef files; + uint32_t version; + if (!loadResources(rules, files, version)) + return; // validly no resources; nothing to do (ok) // found resources, and they are sealed DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this, @@ -856,31 +1285,17 @@ void SecStaticCode::validateResources(SecCSFlags flags) // scan through the resources on disk, checking each against the resourceDirectory mResourcesValidContext = new CollectingContext(*this); // collect all failures in here - // use V2 resource seal if available, otherwise fall back to V1 - CFDictionaryRef rules; - CFDictionaryRef files; - uint32_t version; - if (CFDictionaryGetValue(sealedResources, CFSTR("files2"))) { // have V2 signature - rules = cfget(sealedResources, "rules2"); - files = cfget(sealedResources, "files2"); - version = 2; - } else { // only V1 available - rules = cfget(sealedResources, "rules"); - files = cfget(sealedResources, "files"); - version = 1; - } - if (!rules || !files) - MacOSError::throwMe(errSecCSResourcesInvalid); - // check for weak resource rules bool strict = flags & kSecCSStrictValidate; - if (strict) { - if (hasWeakResourceRules(rules, version, mAllowOmissions)) - if (mTolerateErrors.find(errSecCSWeakResourceRules) == mTolerateErrors.end()) - MacOSError::throwMe(errSecCSWeakResourceRules); - if (version == 1) - if (mTolerateErrors.find(errSecCSWeakResourceEnvelope) == mTolerateErrors.end()) - MacOSError::throwMe(errSecCSWeakResourceEnvelope); + if (!useRootFSPolicy) { + if (strict) { + if (hasWeakResourceRules(rules, version, mAllowOmissions)) + if (mTolerateErrors.find(errSecCSWeakResourceRules) == mTolerateErrors.end()) + MacOSError::throwMe(errSecCSWeakResourceRules); + if (version == 1) + if (mTolerateErrors.find(errSecCSWeakResourceEnvelope) == mTolerateErrors.end()) + MacOSError::throwMe(errSecCSWeakResourceEnvelope); + } } Dispatch::Group group; @@ -889,7 +1304,7 @@ void SecStaticCode::validateResources(SecCSFlags flags) // scan through the resources on disk, checking each against the resourceDirectory __block CFRef resourceMap = makeCFMutableDictionary(files); string base = cfString(this->resourceBase()); - ResourceBuilder resources(base, base, rules, codeDirectory()->hashType, strict, mTolerateErrors); + ResourceBuilder resources(base, base, rules, strict, mTolerateErrors); this->mResourceScope = &resources; diskRep()->adjustResources(resources); @@ -898,7 +1313,26 @@ void SecStaticCode::validateResources(SecCSFlags flags) bool isSymlink = (ent->fts_info == FTS_SL); void (^validate)() = ^{ - validateResource(files, relpath, isSymlink, *mResourcesValidContext, flags, version); + bool needsValidation = true; + + if (skipXattrFiles && pathIsValidXattrFile(cfString(resourceBase()) + "/" + relpath, "staticCode")) { + secinfo("staticCode", "resource validation on xattr file skipped: %s", relpath.c_str()); + needsValidation = false; + } + + if (useRootFSPolicy) { + CFRef itemURL = makeCFURL(relpath, false, resourceBase()); + string itemPath = cfString(itemURL); + if (isOnRootFilesystem(itemPath.c_str())) { + secinfo("staticCode", "resource validation on root volume skipped: %s", itemPath.c_str()); + needsValidation = false; + } + } + + if (needsValidation) { + secinfo("staticCode", "performing resource validation on item: %s", relpath.c_str()); + validateResource(files, relpath, isSymlink, *mResourcesValidContext, flags, version); + } reportProgress(); }; @@ -906,10 +1340,15 @@ void SecStaticCode::validateResources(SecCSFlags flags) }); group.wait(); // wait until all async resources have been validated as well - unsigned leftovers = unsigned(CFDictionaryGetCount(resourceMap)); - if (leftovers > 0) { - secdebug("staticCode", "%d sealed resource(s) not found in code", int(leftovers)); - CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext); + if (useRootFSPolicy) { + // It's ok to allow leftovers on the root filesystem for now. + } else { + // Look through the leftovers and make sure they're all properly optional resources. + unsigned leftovers = unsigned(CFDictionaryGetCount(resourceMap)); + if (leftovers > 0) { + secinfo("staticCode", "%d sealed resource(s) not found in code", int(leftovers)); + CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext); + } } // now check for any errors found in the reporting context @@ -923,10 +1362,11 @@ void SecStaticCode::validateResources(SecCSFlags flags) mResourcesValidResult = err.osStatus(); throw; } catch (...) { - secdebug("staticCode", "%p executable validation threw non-common exception", this); + secinfo("staticCode", "%p executable validation threw non-common exception", this); mResourcesValidated = true; mResourcesDeep = flags & kSecCSCheckNestedCode; mResourcesValidResult = errSecCSInternalError; + Syslog::notice("code signing internal problem: unknown exception thrown by validation"); throw; } } @@ -938,6 +1378,38 @@ void SecStaticCode::validateResources(SecCSFlags flags) } +bool SecStaticCode::loadResources(CFDictionaryRef& rules, CFDictionaryRef& files, uint32_t& version) +{ + // sanity first + CFDictionaryRef sealedResources = resourceDictionary(); + if (this->resourceBase()) { // disk has resources + if (sealedResources) + /* go to work below */; + else + MacOSError::throwMe(errSecCSResourcesNotFound); + } else { // disk has no resources + if (sealedResources) + MacOSError::throwMe(errSecCSResourcesNotFound); + else + return false; // no resources, not sealed - fine (no work) + } + + // use V2 resource seal if available, otherwise fall back to V1 + if (CFDictionaryGetValue(sealedResources, CFSTR("files2"))) { // have V2 signature + rules = cfget(sealedResources, "rules2"); + files = cfget(sealedResources, "files2"); + version = 2; + } else { // only V1 available + rules = cfget(sealedResources, "rules"); + files = cfget(sealedResources, "files"); + version = 1; + } + if (!rules || !files) + MacOSError::throwMe(errSecCSResourcesInvalid); + return true; +} + + void SecStaticCode::checkOptionalResource(CFTypeRef key, CFTypeRef value, void *context) { ValidationContext *ctx = static_cast(context); @@ -969,8 +1441,10 @@ bool SecStaticCode::hasWeakResourceRules(CFDictionaryRef rulesDict, uint32_t ver { // compute allowed omissions CFRef defaultOmissions = this->diskRep()->allowedResourceOmissions(); - if (!defaultOmissions) + if (!defaultOmissions) { + Syslog::notice("code signing internal problem: diskRep returned no allowedResourceOmissions"); MacOSError::throwMe(errSecCSInternalError); + } CFRef allowed = CFArrayCreateMutableCopy(NULL, 0, defaultOmissions); if (allowedOmissions) CFArrayAppendArray(allowed, allowedOmissions, CFRangeMake(0, CFArrayGetCount(allowedOmissions))); @@ -1003,7 +1477,7 @@ CFDictionaryRef SecStaticCode::infoDictionary() { if (!mInfoDict) { mInfoDict.take(getDictionary(cdInfoSlot, errSecCSInfoPlistFailed)); - secdebug("staticCode", "%p loaded InfoDict %p", this, mInfoDict.get()); + secinfo("staticCode", "%p loaded InfoDict %p", this, mInfoDict.get()); } return mInfoDict; } @@ -1017,7 +1491,7 @@ CFDictionaryRef SecStaticCode::entitlements() const EntitlementBlob *blob = reinterpret_cast(CFDataGetBytePtr(entitlementData)); if (blob->validateBlob()) { mEntitlements.take(blob->entitlements()); - secdebug("staticCode", "%p loaded Entitlements %p", this, mEntitlements.get()); + secinfo("staticCode", "%p loaded Entitlements %p", this, mEntitlements.get()); } // we do not consider a different blob type to be an error. We think it's a new format we don't understand } @@ -1031,13 +1505,31 @@ CFDictionaryRef SecStaticCode::resourceDictionary(bool check /* = true */) return mResourceDict; if (CFRef dict = getDictionary(cdResourceDirSlot, check)) if (cfscan(dict, "{rules=%Dn,files=%Dn}")) { - secdebug("staticCode", "%p loaded ResourceDict %p", + secinfo("staticCode", "%p loaded ResourceDict %p", this, mResourceDict.get()); return mResourceDict = dict; } // bad format return NULL; } + + +CFDataRef SecStaticCode::copyComponent(CodeDirectory::SpecialSlot slot, CFDataRef hash) +{ + const CodeDirectory* cd = this->codeDirectory(); + if (CFCopyRef component = this->component(slot)) { + if (hash) { + const void *slotHash = cd->getSlot(slot, false); + if (cd->hashSize != CFDataGetLength(hash) || 0 != memcmp(slotHash, CFDataGetBytePtr(hash), cd->hashSize)) { + Syslog::notice("copyComponent hash mismatch slot %d length %d", slot, int(CFDataGetLength(hash))); + return NULL; // mismatch + } + } + return component.yield(); + } + return NULL; +} + // @@ -1075,57 +1567,84 @@ CFDictionaryRef SecStaticCode::getDictionary(CodeDirectory::SpecialSlot slot, bo return NULL; } - -// -// Load, validate, and return a sealed resource. -// The resource data (loaded in to memory as a blob) is returned and becomes -// the responsibility of the caller; it is NOT cached by SecStaticCode. // -// A resource that is not sealed will not be returned, and an error will be thrown. -// A missing resource will cause an error unless it's marked optional in the Directory. -// Under no circumstances will a corrupt resource be returned. -// NULL will only be returned for a resource that is neither sealed nor present -// (or that is sealed, absent, and marked optional). -// If the ResourceDictionary itself is not sealed, this function will always fail. // -// There is currently no interface for partial retrieval of the resource data. -// (Since the ResourceDirectory does not currently support segmentation, all the -// data would have to be read anyway, but it could be read into a reusable buffer.) // -CFDataRef SecStaticCode::resource(string path, ValidationContext &ctx) +CFDictionaryRef SecStaticCode::diskRepInformation() { - if (CFDictionaryRef rdict = resourceDictionary()) { - if (CFTypeRef file = cfget(rdict, "files.%s", path.c_str())) { - ResourceSeal seal = file; - if (!resourceBase()) // no resources in DiskRep - MacOSError::throwMe(errSecCSResourcesNotFound); - if (seal.nested()) - MacOSError::throwMe(errSecCSResourcesNotSealed); // (it's nested code) - CFRef fullpath = makeCFURL(path, false, resourceBase()); - if (CFRef data = cfLoadFile(fullpath)) { - MakeHash hasher(this->codeDirectory()); - hasher->update(CFDataGetBytePtr(data), CFDataGetLength(data)); - if (hasher->verify(seal.hash())) - return data.yield(); // good - else - ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered - } else { - if (!seal.optional()) - ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); // was sealed but is now missing - else - return NULL; // validly missing - } - } else - ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase())); - return NULL; - } else - MacOSError::throwMe(errSecCSResourcesNotSealed); + return mRep->diskRepInformation(); } -CFDataRef SecStaticCode::resource(string path) -{ - ValidationContext ctx(*this); - return resource(path, ctx); +bool SecStaticCode::checkfix30814861(string path, bool addition) { + // v2 resource rules don't match v1 resource rules + + //// Condition 1: Is the app an iOS app that was built with an SDK lower than 9.0? + + // We started signing correctly in 2014, 9.0 was first seeded mid-2016. + + CFRef inf = diskRepInformation(); + try { + CFDictionary info(diskRepInformation(), errSecCSNotSupported); + uint32_t platform = + cfNumber(info.get(kSecCodeInfoDiskRepVersionPlatform, errSecCSNotSupported), 0); + uint32_t sdkVersion = + cfNumber(info.get(kSecCodeInfoDiskRepVersionSDK, errSecCSNotSupported), 0); + + if (platform != PLATFORM_IOS || sdkVersion >= 0x00090000) { + return false; + } + } catch (const MacOSError &error) { + return false; + } + + //// Condition 2: Is it a .sinf/.supf/.supp file at the right location? + + static regex_t pathre_sinf; + static regex_t pathre_supp_supf; + static dispatch_once_t once; + + dispatch_once(&once, ^{ + os_assert_zero(regcomp(&pathre_sinf, + "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|())SC_Info/[^/]+\\.sinf$", + REG_EXTENDED | REG_NOSUB)); + os_assert_zero(regcomp(&pathre_supp_supf, + "^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|())SC_Info/[^/]+\\.(supf|supp)$", + REG_EXTENDED | REG_NOSUB)); + }); + + // .sinf is added, .supf/.supp are modified. + const regex_t &pathre = addition ? pathre_sinf : pathre_supp_supf; + + const int result = regexec(&pathre, path.c_str(), 0, NULL, 0); + + if (result == REG_NOMATCH) { + return false; + } else if (result != 0) { + // Huh? + secerror("unexpected regexec result %d for path '%s'", result, path.c_str()); + return false; + } + + //// Condition 3: Do the v1 rules actually exclude the file? + + dispatch_once(&mCheckfix30814861builder1_once, ^{ + // Create the v1 resource builder lazily. + CFDictionaryRef rules1 = cfget(resourceDictionary(), "rules"); + const string base = cfString(resourceBase()); + + mCheckfix30814861builder1 = new ResourceBuilder(base, base, rules1, false, mTolerateErrors); + }); + + ResourceBuilder::Rule const * const matchingRule = mCheckfix30814861builder1->findRule(path); + + if (matchingRule == NULL || !(matchingRule->flags & ResourceBuilder::omitted)) { + return false; + } + + //// All matched, this file is a check-fixed sinf/supf/supp. + + return true; + } void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool isSymlink, ValidationContext &ctx, SecCSFlags flags, uint32_t version) @@ -1133,8 +1652,14 @@ void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool is if (!resourceBase()) // no resources in DiskRep MacOSError::throwMe(errSecCSResourcesNotFound); CFRef fullpath = makeCFURL(path, false, resourceBase()); + if (version > 1 && ((flags & (kSecCSStrictValidate|kSecCSRestrictSidebandData)) == (kSecCSStrictValidate|kSecCSRestrictSidebandData))) { + AutoFileDesc fd(cfString(fullpath)); + if (fd.hasExtendedAttribute(XATTR_RESOURCEFORK_NAME) || fd.hasExtendedAttribute(XATTR_FINDERINFO_NAME)) + ctx.reportProblem(errSecCSInvalidAssociatedFileData, kSecCFErrorResourceSideband, fullpath); + } if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) { - ResourceSeal seal = file; + ResourceSeal seal(file); + const ResourceSeal& rseal = seal; if (seal.nested()) { if (isSymlink) return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type @@ -1146,17 +1671,23 @@ void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool is if (!isSymlink) return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type validateSymlinkResource(cfString(fullpath), cfString(seal.link()), ctx, flags); - } else if (seal.hash()) { // genuine file + } else if (seal.hash(hashAlgorithm())) { // genuine file if (isSymlink) return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // changed type AutoFileDesc fd(cfString(fullpath), O_RDONLY, FileDesc::modeMissingOk); // open optional file if (fd) { - MakeHash hasher(this->codeDirectory()); - hashFileData(fd, hasher.get()); - if (hasher->verify(seal.hash())) - return; // verify good - else - ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered + __block bool good = true; + CodeDirectory::multipleHashFileData(fd, 0, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) { + if (!hasher->verify(rseal.hash(type))) + good = false; + }); + if (!good) { + if (version == 2 && checkfix30814861(path, false)) { + secinfo("validateResource", "%s check-fixed (altered).", path.c_str()); + } else { + ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered + } + } } else { if (!seal.optional()) ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); // was sealed but is now missing @@ -1172,7 +1703,29 @@ void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool is if (::readlink(cfString(fullpath).c_str(), target, sizeof(target)) > 0) return; } - ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase())); + if (version == 2 && checkfix30814861(path, true)) { + secinfo("validateResource", "%s check-fixed (added).", path.c_str()); + } else { + ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase())); + } +} + +void SecStaticCode::validatePlainMemoryResource(string path, CFDataRef fileData, SecCSFlags flags) +{ + CFDictionaryRef rules; + CFDictionaryRef files; + uint32_t version; + if (!loadResources(rules, files, version)) + MacOSError::throwMe(errSecCSResourcesNotFound); // no resources sealed; this can't be right + if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) { + ResourceSeal seal(file); + const Byte *sealHash = seal.hash(hashAlgorithm()); + if (sealHash) { + if (codeDirectory()->verifyMemoryContent(fileData, sealHash)) + return; // success + } + } + MacOSError::throwMe(errSecCSBadResource); } void SecStaticCode::validateSymlinkResource(std::string fullpath, std::string seal, ValidationContext &ctx, SecCSFlags flags) @@ -1236,14 +1789,14 @@ void SecStaticCode::validateNestedCode(CFURLRef path, const ResourceSeal &seal, // recursively verify this nested code try { if (!(flags & kSecCSCheckNestedCode)) - flags |= kSecCSBasicValidateOnly; + flags |= kSecCSBasicValidateOnly | kSecCSQuickCheck; SecPointer code = new SecStaticCode(DiskRep::bestGuess(cfString(path))); code->initializeFromParent(*this); - code->staticValidate(flags, SecRequirement::required(req)); + code->staticValidate(flags & (~kSecCSRestrictToAppLike), SecRequirement::required(req)); if (isFramework && (flags & kSecCSStrictValidate)) try { - validateOtherVersions(path, flags, req, code); + validateOtherVersions(path, flags & (~kSecCSRestrictToAppLike), req, code); } catch (const CSError &err) { MacOSError::throwMe(errSecCSBadFrameworkVersion); } catch (const MacOSError &err) { @@ -1289,10 +1842,7 @@ void SecStaticCode::validateOtherVersions(CFURLRef path, SecCSFlags flags, SecRe while ((entry = scanner.getNext()) != NULL) { std::ostringstream fullPath; - if (entry->d_type != DT_DIR || - strcmp(entry->d_name, ".") == 0 || - strcmp(entry->d_name, "..") == 0 || - strcmp(entry->d_name, "Current") == 0) + if (entry->d_type != DT_DIR || strcmp(entry->d_name, "Current") == 0) continue; fullPath << versionsPath.str() << entry->d_name; @@ -1384,25 +1934,40 @@ const Requirement *SecStaticCode::defaultDesignatedRequirement() Requirement::Maker::Chain chain(maker, opOr); // insert cdhash requirement for all architectures - chain.add(); - maker.cdhash(this->cdHash()); - handleOtherArchitectures(^(SecStaticCode *subcode) { - if (CFDataRef cdhash = subcode->cdHash()) { - chain.add(); - maker.cdhash(cdhash); - } + __block CFRef allHashes = CFArrayCreateMutableCopy(NULL, 0, this->cdHashes()); + handleOtherArchitectures(^(SecStaticCode *other) { + CFArrayRef hashes = other->cdHashes(); + CFArrayAppendArray(allHashes, hashes, CFRangeMake(0, CFArrayGetCount(hashes))); }); + CFIndex count = CFArrayGetCount(allHashes); + for (CFIndex n = 0; n < count; ++n) { + chain.add(); + maker.cdhash(CFDataRef(CFArrayGetValueAtIndex(allHashes, n))); + } return maker.make(); } else { +#if TARGET_OS_OSX // full signature: Gin up full context and let DRMaker do its thing validateDirectory(); // need the cert chain + CFRef secureTimestamp; + if (CFAbsoluteTime time = this->signingTimestamp()) { + secureTimestamp.take(CFDateCreate(NULL, time)); + } Requirement::Context context(this->certificates(), this->infoDictionary(), this->entitlements(), this->identifier(), - this->codeDirectory() + this->codeDirectory(), + NULL, + kSecCodeSignatureNoHash, + false, + secureTimestamp, + this->teamID() ); return DRMaker(context).make(); +#else + MacOSError::throwMe(errSecCSUnimplemented); +#endif } } @@ -1422,9 +1987,6 @@ void SecStaticCode::validateRequirements(SecRequirementType type, SecStaticCode /* accept it */; } -/* Public Key Hash for root:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority */ -static const UInt8 retryRootBytes[] = {0x00,0xd8,0x5a,0x4c,0x25,0xc1,0x22,0xe5,0x8b,0x31,0xef,0x6d,0xba,0xf3,0xcc,0x5f,0x29,0xf1,0x0d,0x61}; - // // Validate this StaticCode against an external Requirement // @@ -1433,35 +1995,15 @@ bool SecStaticCode::satisfiesRequirement(const Requirement *req, OSStatus failur bool result = false; assert(req); validateDirectory(); - result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()->identifier(), codeDirectory()), failure); - if (result == false) { - /* Fix for rdar://problem/21437632: Work around untrusted root in validation chain */ - CFArrayRef certs = certificates(); - if (!certs || ((int)CFArrayGetCount(certs) < 1)) { - return false; - } - SecCertificateRef root = cert((int)CFArrayGetCount(certs) - 1); - if (!root) { - return false; - } - CFDataRef rootHash = SecCertificateCopyPublicKeySHA1Digest(root); - if (!rootHash) { - return false; - } - - if ((CFDataGetLength(rootHash) == sizeof(retryRootBytes)) && - !memcmp(CFDataGetBytePtr(rootHash), retryRootBytes, sizeof(retryRootBytes))) { - // retry with a rebuilt certificate chain, this time evaluating anchor trust - Security::Syslog::debug("Requirements validation failed: retrying"); - mResourcesValidated = mValidated = false; - setValidationFlags(mValidationFlags | kSecCSCheckTrustedAnchors); - - validateDirectory(); - result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()->identifier(), codeDirectory()), failure); - } - CFRelease(rootHash); + CFRef secureTimestamp; + if (CFAbsoluteTime time = this->signingTimestamp()) { + secureTimestamp.take(CFDateCreate(NULL, time)); } - + result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), + codeDirectory()->identifier(), codeDirectory(), + NULL, kSecCodeSignatureNoHash, mRep->appleInternalForcePlatform(), + secureTimestamp, teamID()), + failure); return result; } @@ -1529,10 +2071,17 @@ CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags) CFDictionaryAddValue(dict, kSecCodeInfoFormat, CFTempString(this->format())); CFDictionaryAddValue(dict, kSecCodeInfoSource, CFTempString(this->signatureSource())); CFDictionaryAddValue(dict, kSecCodeInfoUnique, this->cdHash()); + CFDictionaryAddValue(dict, kSecCodeInfoCdHashes, this->cdHashes()); + CFDictionaryAddValue(dict, kSecCodeInfoCdHashesFull, this->cdHashesFull()); const CodeDirectory* cd = this->codeDirectory(false); CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithm, CFTempNumber(cd->hashType)); + CFRef digests = makeCFArrayFrom(^CFTypeRef(CodeDirectory::HashAlgorithm type) { return CFTempNumber(type); }, hashAlgorithms()); + CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithms, digests); if (cd->platform) CFDictionaryAddValue(dict, kSecCodeInfoPlatformIdentifier, CFTempNumber(cd->platform)); + if (cd->runtimeVersion()) { + CFDictionaryAddValue(dict, kSecCodeInfoRuntimeVersion, CFTempNumber(cd->runtimeVersion())); + } // // Deliver any Info.plist only if it looks intact @@ -1547,26 +2096,29 @@ CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags) // if (flags & kSecCSSigningInformation) try { - if (CFArrayRef certs = this->certificates()) - CFDictionaryAddValue(dict, kSecCodeInfoCertificates, certs); if (CFDataRef sig = this->signature()) CFDictionaryAddValue(dict, kSecCodeInfoCMS, sig); + if (const char *teamID = this->teamID()) + CFDictionaryAddValue(dict, kSecCodeInfoTeamIdentifier, CFTempString(teamID)); if (mTrust) CFDictionaryAddValue(dict, kSecCodeInfoTrust, mTrust); + if (CFArrayRef certs = this->certificates()) + CFDictionaryAddValue(dict, kSecCodeInfoCertificates, certs); if (CFAbsoluteTime time = this->signingTime()) if (CFRef date = CFDateCreate(NULL, time)) CFDictionaryAddValue(dict, kSecCodeInfoTime, date); if (CFAbsoluteTime time = this->signingTimestamp()) if (CFRef date = CFDateCreate(NULL, time)) CFDictionaryAddValue(dict, kSecCodeInfoTimestamp, date); - if (const char *teamID = this->teamID()) - CFDictionaryAddValue(dict, kSecCodeInfoTeamIdentifier, CFTempString(teamID)); } catch (...) { } // // kSecCSRequirementInformation adds information on requirements // if (flags & kSecCSRequirementInformation) + +//DR not currently supported on iOS +#if TARGET_OS_OSX try { if (const Requirements *reqs = this->internalRequirements()) { CFDictionaryAddValue(dict, kSecCodeInfoRequirements, @@ -1584,14 +2136,31 @@ CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags) CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, dreqRef); } } catch (...) { } +#endif - try { - if (CFDataRef ent = this->component(cdEntitlementSlot)) { - CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent); - if (CFDictionaryRef entdict = this->entitlements()) + try { + if (CFDataRef ent = this->component(cdEntitlementSlot)) { + CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent); + if (CFDictionaryRef entdict = this->entitlements()) { + if (needsCatalystEntitlementFixup(entdict)) { + // If this entitlement dictionary needs catalyst entitlements, make a copy and stick that into the + // output dictionary instead. + secinfo("staticCode", "%p fixed catalyst entitlements", this); + CFRef tempEntitlements = makeCFMutableDictionary(entdict); + updateCatalystEntitlements(tempEntitlements); + CFRef newEntitlements = CFDictionaryCreateCopy(NULL, tempEntitlements); + if (newEntitlements) { + CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, newEntitlements.get()); + } else { + secerror("%p unable to fixup entitlement dictionary", this); + CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, entdict); + } + } else { CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, entdict); + } } - } catch (...) { } + } + } catch (...) { } // // kSecCSInternalInformation adds internal information meant to be for Apple internal @@ -1599,22 +2168,49 @@ CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags) // to reliably transmit through the API wall so that code outside the Security.framework // can use it without having to play nasty tricks to get it. // - if (flags & kSecCSInternalInformation) + if (flags & kSecCSInternalInformation) { try { if (mDir) CFDictionaryAddValue(dict, kSecCodeInfoCodeDirectory, mDir); CFDictionaryAddValue(dict, kSecCodeInfoCodeOffset, CFTempNumber(mRep->signingBase())); - if (CFRef rdict = getDictionary(cdResourceDirSlot, false)) // suppress validation - CFDictionaryAddValue(dict, kSecCodeInfoResourceDirectory, rdict); + if (!(flags & kSecCSSkipResourceDirectory)) { + if (CFRef rdict = getDictionary(cdResourceDirSlot, false)) // suppress validation + CFDictionaryAddValue(dict, kSecCodeInfoResourceDirectory, rdict); + } + if (CFRef ddict = diskRepInformation()) + CFDictionaryAddValue(dict, kSecCodeInfoDiskRepInfo, ddict); } catch (...) { } + if (mNotarizationChecked && !isnan(mNotarizationDate)) { + CFRef date = CFDateCreate(NULL, mNotarizationDate); + if (date) { + CFDictionaryAddValue(dict, kSecCodeInfoNotarizationDate, date.get()); + } else { + secerror("Error creating date from timestamp: %f", mNotarizationDate); + } + } + if (this->codeDirectory()) { + uint32_t version = this->codeDirectory()->version; + CFDictionaryAddValue(dict, kSecCodeInfoSignatureVersion, CFTempNumber(version)); + } + } + if (flags & kSecCSCalculateCMSDigest) { + try { + CFDictionaryAddValue(dict, kSecCodeInfoCMSDigestHashType, CFTempNumber(cmsDigestHashType())); + + CFRef cmsDigest = createCmsDigest(); + if (cmsDigest) { + CFDictionaryAddValue(dict, kSecCodeInfoCMSDigest, cmsDigest.get()); + } + } catch (...) { } + } // // kSecCSContentInformation adds more information about the physical layout // of the signed code. This is (only) useful for packaging or patching-oriented // applications. // - if (flags & kSecCSContentInformation) + if (flags & kSecCSContentInformation && !(flags & kSecCSSkipResourceDirectory)) if (CFRef files = mRep->modifiedFiles()) CFDictionaryAddValue(dict, kSecCodeInfoChangedFiles, files); @@ -1667,8 +2263,8 @@ void SecStaticCode::CollectingContext::throwMe() // // SecStaticCode exposes an a la carte menu of topical validators applying // to a given object. The static validation API pulls them together reliably, -// but it also adds two matrix dimensions: architecture (for "fat" Mach-O binaries) -// and nested code. This function will crawl a suitable cross-section of this +// but it also adds three matrix dimensions: architecture (for "fat" Mach-O binaries), +// nested code, and multiple digests. This function will crawl a suitable cross-section of this // validation matrix based on which options it is given, creating temporary // SecStaticCode objects on the fly to complete the task. // (The point, of course, is to do as little duplicate work as possible.) @@ -1677,11 +2273,31 @@ void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req) { setValidationFlags(flags); +#if TARGET_OS_OSX + if (!mStaplingChecked) { + mRep->registerStapledTicket(); + mStaplingChecked = true; + } + + if (mFlags & kSecCSForceOnlineNotarizationCheck) { + if (!mNotarizationChecked) { + if (this->cdHash()) { + bool is_revoked = checkNotarizationServiceForRevocation(this->cdHash(), (SecCSDigestAlgorithm)this->hashAlgorithm(), &mNotarizationDate); + if (is_revoked) { + MacOSError::throwMe(errSecCSRevokedNotarization); + } + } + mNotarizationChecked = true; + } + } +#endif // TARGET_OS_OSX + // initialize progress/cancellation state if (flags & kSecCSReportProgress) prepareProgress(estimateResourceWorkload() + 2); // +1 head, +1 tail - // core components: once per architecture (if any) + + // core components: once per architecture (if any) this->staticValidateCore(flags, req); if (flags & kSecCSCheckAllArchitectures) handleOtherArchitectures(^(SecStaticCode* subcode) { @@ -1692,7 +2308,7 @@ void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req) if ((arch.cpuType() & ~CPU_ARCH_MASK) == CPU_TYPE_POWERPC) return; // irrelevant to Gatekeeper } - subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) architecture + subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature subcode->staticValidateCore(flags, req); }); reportProgress(); @@ -1701,13 +2317,17 @@ void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req) reportEvent(CFSTR("prepared"), NULL); // resources: once for all architectures - if (!(flags & kSecCSDoNotValidateResources)) + if (!(flags & kSecCSDoNotValidateResources)) { this->validateResources(flags); + } // perform strict validation if desired - if (flags & kSecCSStrictValidate) - mRep->strictValidate(codeDirectory(), mTolerateErrors); + if (flags & kSecCSStrictValidate) { + mRep->strictValidate(codeDirectory(), mTolerateErrors, mValidationFlags); reportProgress(); + } else if (flags & kSecCSStrictValidateStructure) { + mRep->strictValidateStructure(codeDirectory(), mTolerateErrors, mValidationFlags); + } // allow monitor intervention if (CFRef veto = reportEvent(CFSTR("validated"), NULL)) { @@ -1722,6 +2342,7 @@ void SecStaticCode::staticValidateCore(SecCSFlags flags, const SecRequirement *r { try { this->validateNonResourceComponents(); // also validates the CodeDirectory + this->validateTopDirectory(); if (!(flags & kSecCSDoNotValidateExecutable)) this->validateExecutable(); if (req) @@ -1759,21 +2380,24 @@ void SecStaticCode::handleOtherArchitectures(void (^handle)(SecStaticCode* other fat->architectures(architectures); if (architectures.size() > 1) { DiskRep::Context ctx; - size_t activeOffset = fat->archOffset(); + off_t activeOffset = fat->archOffset(); for (Universal::Architectures::const_iterator arch = architectures.begin(); arch != architectures.end(); ++arch) { - ctx.offset = fat->archOffset(*arch); - if (ctx.offset > SIZE_MAX) - MacOSError::throwMe(errSecCSInternalError); - ctx.size = fat->lengthOfSlice((size_t)ctx.offset); - if (ctx.offset != activeOffset) { // inactive architecture; check it - SecPointer subcode = new SecStaticCode(DiskRep::bestGuess(this->mainExecutablePath(), &ctx)); - subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature - if (this->teamID() == NULL || subcode->teamID() == NULL) { - if (this->teamID() != subcode->teamID()) + try { + ctx.offset = int_cast(fat->archOffset(*arch)); + ctx.size = fat->lengthOfSlice(int_cast(ctx.offset)); + if (ctx.offset != activeOffset) { // inactive architecture; check it + SecPointer subcode = new SecStaticCode(DiskRep::bestGuess(this->mainExecutablePath(), &ctx)); + subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature + if (this->teamID() == NULL || subcode->teamID() == NULL) { + if (this->teamID() != subcode->teamID()) + MacOSError::throwMe(errSecCSSignatureInvalid); + } else if (strcmp(this->teamID(), subcode->teamID()) != 0) MacOSError::throwMe(errSecCSSignatureInvalid); - } else if (strcmp(this->teamID(), subcode->teamID()) != 0) - MacOSError::throwMe(errSecCSSignatureInvalid); - handle(subcode); + handle(subcode); + } + } catch(std::out_of_range e) { + // some of our int_casts fell over. + MacOSError::throwMe(errSecCSBadObjectFormat); } } } @@ -1789,10 +2413,35 @@ bool SecStaticCode::isAppleDeveloperCert(CFArrayRef certs) { static const std::string appleDeveloperRequirement = "(" + std::string(WWDRRequirement) + ") or (" + MACWWDRRequirement + ") or (" + developerID + ") or (" + distributionCertificate + ") or (" + iPhoneDistributionCert + ")"; SecPointer req = new SecRequirement(parseRequirement(appleDeveloperRequirement), true); - Requirement::Context ctx(certs, NULL, NULL, "", NULL); + Requirement::Context ctx(certs, NULL, NULL, "", NULL, NULL, kSecCodeSignatureNoHash, false, NULL, ""); return req->requirement()->validates(ctx); } +CFDataRef SecStaticCode::createCmsDigest() +{ + /* + * The CMS digest is a hash of the primary (first, most compatible) code directory, + * but its hash algorithm is fixed and not related to the code directory's + * hash algorithm. + */ + + auto it = codeDirectories()->begin(); + + if (it == codeDirectories()->end()) { + return NULL; + } + + CodeDirectory const * const cd = reinterpret_cast(CFDataGetBytePtr(it->second)); + + RefPointer hash = cd->hashFor(mCMSDigestHashType); + CFMutableDataRef data = CFDataCreateMutable(NULL, hash->digestLength()); + CFDataSetLength(data, hash->digestLength()); + hash->update(cd, cd->length()); + hash->finish(CFDataGetMutableBytePtr(data)); + + return data; +} + } // end namespace CodeSigning } // end namespace Security