X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/Trust.cpp?ds=inline diff --git a/Security/libsecurity_keychain/lib/Trust.cpp b/Security/libsecurity_keychain/lib/Trust.cpp new file mode 100644 index 00000000..1a6f96c2 --- /dev/null +++ b/Security/libsecurity_keychain/lib/Trust.cpp @@ -0,0 +1,907 @@ +/* + * Copyright (c) 2002-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +// +// Trust.cpp +// +#include +#include +#include +#include +#include +#include +#include +#include "SecBridge.h" +#include "TrustAdditions.h" +#include "TrustKeychains.h" +#include + + +using namespace Security; +using namespace KeychainCore; + +// +// Translate CFDataRef to CssmData. The output shares the input's buffer. +// +static inline CssmData cfData(CFDataRef data) +{ + return CssmData(const_cast(CFDataGetBytePtr(data)), + CFDataGetLength(data)); +} + +// +// Convert a SecPointer to a CF object. +// +static SecCertificateRef +convert(const SecPointer &certificate) +{ + return *certificate; +} + +// +// For now, we use a global TrustStore +// +ModuleNexus Trust::gStore; + +#pragma mark -- TrustKeychains -- + +static const CSSM_DL_DB_HANDLE nullCSSMDLDBHandle = {0,}; +// +// TrustKeychains maintains a global reference to standard system keychains, +// to avoid having them be opened anew for each Trust instance. +// +class TrustKeychains +{ +public: + TrustKeychains(); + ~TrustKeychains() {} + CSSM_DL_DB_HANDLE rootStoreHandle() { return mRootStoreHandle; } + CSSM_DL_DB_HANDLE systemKcHandle() { return mSystem ? mSystem->database()->handle() : nullCSSMDLDBHandle; } + Keychain &systemKc() { return mSystem; } + Keychain &rootStore() { return *mRootStore; } + +private: + DL* mRootStoreDL; + Db* mRootStoreDb; + Keychain* mRootStore; + CSSM_DL_DB_HANDLE mRootStoreHandle; + Keychain mSystem; +}; + +// +// Singleton maintaining open references to standard system keychains, +// to avoid having them be opened anew every time SecTrust is used. +// + +static ModuleNexus trustKeychains; +static ModuleNexus trustKeychainsMutex; + +extern "C" bool GetServerMode(); + +TrustKeychains::TrustKeychains() : + mRootStoreHandle(nullCSSMDLDBHandle), + mSystem(globals().storageManager.make(ADMIN_CERT_STORE_PATH, false)) +{ + if (GetServerMode()) // in server mode? Don't make a keychain for the root store + { + mRootStoreDL = new DL(gGuidAppleFileDL), + mRootStoreDb = new Db(*mRootStoreDL, SYSTEM_ROOT_STORE_PATH), + (*mRootStoreDb)->activate(); + mRootStoreHandle = (*mRootStoreDb)->handle(); + } + else + { + mRootStore = new Keychain(globals().storageManager.make(SYSTEM_ROOT_STORE_PATH, false)); + (*mRootStore)->database()->activate(); + mRootStoreHandle = (*mRootStore)->database()->handle(); + } +} + +RecursiveMutex& SecTrustKeychainsGetMutex() +{ + return trustKeychainsMutex(); +} + +#pragma mark -- Trust -- +// +// Construct a Trust object with suitable defaults. +// Use setters for additional arguments before calling evaluate(). +// +Trust::Trust(CFTypeRef certificates, CFTypeRef policies) + : mTP(gGuidAppleX509TP), mAction(CSSM_TP_ACTION_DEFAULT), + mCerts(cfArrayize(certificates)), mPolicies(cfArrayize(policies)), + mSearchLibs(NULL), mSearchLibsSet(false), mResult(kSecTrustResultInvalid), + mUsingTrustSettings(false), mAnchorPolicy(useAnchorsDefault), mMutex(Mutex::recursive) +{ + if (!mPolicies) { + mPolicies.take(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); + } +} + + +// +// Clean up a Trust object +// +Trust::~Trust() +{ + clearResults(); + if (mSearchLibs) { + delete mSearchLibs; + } + + mPolicies = NULL; +} + + +// +// Get searchLibs (a vector of Keychain objects); +// normally initialized to default search list +// +StorageManager::KeychainList& Trust::searchLibs(bool init) +{ + if (!mSearchLibs) { + mSearchLibs = new StorageManager::KeychainList; + if (init) { + globals().storageManager.getSearchList(*mSearchLibs); + } + } + return *mSearchLibs; +} + + +// +// Set searchLibs to provided vector of Keychain objects +// +void Trust::searchLibs(StorageManager::KeychainList &libs) +{ + searchLibs(false) = libs; + mSearchLibsSet = true; +} + + +// +// Retrieve the last TP evaluation result, if any +// +CSSM_TP_VERIFY_CONTEXT_RESULT_PTR Trust::cssmResult() +{ + if (mResult == kSecTrustResultInvalid) + MacOSError::throwMe(errSecTrustNotAvailable); + return &mTpResult; +} + + +// SecCertificateRef -> CssmData +static +CssmData cfCertificateData(SecCertificateRef certificate) +{ + return Certificate::required(certificate)->data(); +} + +// SecPolicyRef -> CssmField (CFDataRef/NULL or oid/value of a SecPolicy) +static +CssmField cfField(SecPolicyRef item) +{ + SecPointer policy = Policy::required(SecPolicyRef(item)); + return CssmField(policy->oid(), policy->value()); +} + +// SecKeychain -> CssmDlDbHandle +#if 0 +static +CSSM_DL_DB_HANDLE cfKeychain(SecKeychainRef ref) +{ + Keychain keychain = KeychainImpl::required(ref); + return keychain->database()->handle(); +} +#endif + +#if !defined(NDEBUG) +void showCertSKID(const void *value, void *context); +#endif + +// +// Here's the big "E" - evaluation. +// We build most of the CSSM-layer input structures dynamically right here; +// they will auto-destruct when we're done. The output structures are kept +// around (in our data members) for later analysis. +// Note that evaluate() can be called repeatedly, so we must be careful to +// dispose of prior results. +// +void Trust::evaluate(bool disableEV) +{ + bool isEVCandidate=false; + // begin evaluation block with stack-based mutex + { + StLock_(mMutex); + // if we have evaluated before, release prior result + clearResults(); + + // determine whether the leaf certificate is an EV candidate + CFArrayRef allowedAnchors = NULL; + if (!disableEV) { + allowedAnchors = allowedEVRootsForLeafCertificate(mCerts); + isEVCandidate = (allowedAnchors != NULL); + } + CFArrayRef filteredCerts = NULL; + if (isEVCandidate) { + secdebug("evTrust", "Trust::evaluate() certificate is EV candidate"); + filteredCerts = potentialEVChainWithCertificates(mCerts); + mCerts = filteredCerts; + } else { + secdebug("evTrust", "Trust::evaluate() performing standard evaluation"); + if (mCerts) { + filteredCerts = CFArrayCreateMutableCopy(NULL, 0, mCerts); + } + if (mAnchors) { + allowedAnchors = CFArrayCreateMutableCopy(NULL, 0, mAnchors); + } + } + // retain these certs as long as we potentially could have results involving them + // (note that assignment to a CFRef type performs an implicit retain) + mAllowedAnchors = allowedAnchors; + mFilteredCerts = filteredCerts; + + if (allowedAnchors) + CFRelease(allowedAnchors); + if (filteredCerts) + CFRelease(filteredCerts); + + if (mAllowedAnchors) + { + secdebug("trusteval", "Trust::evaluate: anchors: %ld", CFArrayGetCount(mAllowedAnchors)); +#if !defined(NDEBUG) + CFArrayApplyFunction(mAllowedAnchors, CFRangeMake(0, CFArrayGetCount(mAllowedAnchors)), showCertSKID, NULL); +#endif + } + + // set default search list from user's default, if caller did not explicitly supply it + if(!mSearchLibsSet) { + globals().storageManager.getSearchList(searchLibs()); + mSearchLibsSet = true; + } + + // build the target cert group + CFToVector subjects(mFilteredCerts); + CertGroup subjectCertGroup(CSSM_CERT_X_509v3, + CSSM_CERT_ENCODING_BER, CSSM_CERTGROUP_DATA); + subjectCertGroup.count() = subjects; + subjectCertGroup.blobCerts() = subjects; + + // build a TP_VERIFY_CONTEXT, a veritable nightmare of a data structure + TPBuildVerifyContext context(mAction); + + /* + * Guarantee *some* action data... + * NOTE this only works with the local X509 TP. When this module can deal + * with other TPs, this must be revisited. + */ + CSSM_APPLE_TP_ACTION_DATA localActionData; + memset(&localActionData, 0, sizeof(localActionData)); + CssmData localActionCData((uint8 *)&localActionData, sizeof(localActionData)); + CSSM_APPLE_TP_ACTION_DATA *actionDataP = &localActionData; + if (mActionData) { + context.actionData() = cfData(mActionData); + actionDataP = (CSSM_APPLE_TP_ACTION_DATA *)context.actionData().data(); + } + else { + context.actionData() = localActionCData; + } + + if (!mAnchors) { + // always check trust settings if caller did not provide explicit trust anchors + actionDataP->ActionFlags |= CSSM_TP_ACTION_TRUST_SETTINGS; + } + + if (mNetworkPolicy == useNetworkDefault) { + if (policySpecified(mPolicies, CSSMOID_APPLE_TP_SSL)) { + // enable network cert fetch for SSL only: + actionDataP->ActionFlags |= CSSM_TP_ACTION_FETCH_CERT_FROM_NET; + } + } + else if (mNetworkPolicy == useNetworkEnabled) + actionDataP->ActionFlags |= CSSM_TP_ACTION_FETCH_CERT_FROM_NET; + else if (mNetworkPolicy == useNetworkDisabled) + actionDataP->ActionFlags &= ~(CSSM_TP_ACTION_FETCH_CERT_FROM_NET); + + /* + * Policies (one at least, please). + * For revocation policies, see if any have been explicitly specified... + */ + CFMutableArrayRef allPolicies = NULL; + uint32 numRevocationAdded = 0; + bool requirePerCert = (actionDataP->ActionFlags & CSSM_TP_ACTION_REQUIRE_REV_PER_CERT); + bool avoidRevChecks = (policySpecified(mPolicies, CSSMOID_APPLE_TP_EAP)); + + // If a new unified revocation policy was explicitly specified, + // convert into old-style individual OCSP and CRL policies. + // Note that the caller could configure revocation policy options + // to explicitly disable both methods, so 0 policies might be added, + // in which case we must no longer consider the cert an EV candidate. + + allPolicies = convertRevocationPolicy(numRevocationAdded, context.allocator); + if (allPolicies) { + // caller has explicitly set the revocation policy they want to use + secdebug("evTrust", "Trust::evaluate() using explicit revocation policy (%d)", + numRevocationAdded); + if (numRevocationAdded == 0) + isEVCandidate = false; + } + else if (mAnchors && (CFArrayGetCount(mAnchors)==0) && (searchLibs().size()==0)) { + // caller explicitly provided empty anchors and no keychain list, + // and did not explicitly specify the revocation policy; + // override global revocation check setting for this evaluation + secdebug("evTrust", "Trust::evaluate() has empty anchors and no keychains"); + allPolicies = NULL; // use only mPolicies + isEVCandidate = false; + } + else if ((isEVCandidate && !avoidRevChecks) || requirePerCert) { + // force revocation checking for this evaluation + secdebug("evTrust", "Trust::evaluate() forcing OCSP/CRL revocation check"); + allPolicies = forceRevocationPolicies(numRevocationAdded, + context.allocator, requirePerCert); + } + else if(!(revocationPolicySpecified(mPolicies)) && !avoidRevChecks) { + // none specified in mPolicies; try preferences + allPolicies = addPreferenceRevocationPolicies(numRevocationAdded, + context.allocator); + } + if (allPolicies == NULL) { + // use mPolicies; no revocation checking will be performed + secdebug("evTrust", "Trust::evaluate() will not perform revocation check"); + CFIndex numPolicies = CFArrayGetCount(mPolicies); + CFAllocatorRef allocator = CFGetAllocator(mPolicies); + allPolicies = CFArrayCreateMutableCopy(allocator, numPolicies, mPolicies); + } + orderRevocationPolicies(allPolicies); + CFToVector policies(allPolicies); +#if 0 + // error exit here if empty policies are not supported + if (policies.empty()) + MacOSError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS); +#endif + context.setPolicies(policies, policies); + + // anchor certificates (if caller provides them, or if cert requires EV) + CFCopyRef anchors(mAllowedAnchors); + CFToVector roots(anchors); + if (!anchors) { + // no anchor certificates were provided; + // built-in anchors will be trusted unless explicitly disabled. + mUsingTrustSettings = (mAnchorPolicy < useAnchorsOnly); + secdebug("userTrust", "Trust::evaluate() %s", + (mUsingTrustSettings) ? "using UserTrust" : "has no trusted anchors!"); + } + else { + // anchor certificates were provided; + // built-in anchors will NOT also be trusted unless explicitly enabled. + mUsingTrustSettings = (mAnchorPolicy == useAnchorsAndBuiltIns); + secdebug("userTrust", "Trust::evaluate() using %s %s anchors", + (mUsingTrustSettings) ? "UserTrust AND" : "only", + (isEVCandidate) ? "EV" : "caller"); + context.anchors(roots, roots); + } + + // dlDbList (keychain list) + vector dlDbList; + { + StLock _(SecTrustKeychainsGetMutex()); + StorageManager::KeychainList& list = searchLibs(); + for (StorageManager::KeychainList::const_iterator it = list.begin(); + it != list.end(); it++) + { + try + { + // For the purpose of looking up intermediate certificates to establish trust, + // do not include the network-based LDAP or DotMac pseudo-keychains. (The only + // time the network should be consulted for certificates is if there is an AIA + // extension with a specific URL, which will be handled by the TP code.) + CSSM_DL_DB_HANDLE dldbHandle = (*it)->database()->handle(); + if (dldbHandle.DLHandle) { + CSSM_GUID guid = {}; + CSSM_RETURN crtn = CSSM_GetModuleGUIDFromHandle(dldbHandle.DLHandle, &guid); + if (crtn == CSSM_OK) { + if ((memcmp(&guid, &gGuidAppleLDAPDL, sizeof(CSSM_GUID))==0) || + (memcmp(&guid, &gGuidAppleDotMacDL, sizeof(CSSM_GUID))==0)) { + continue; // don't add to dlDbList + } + } + } + // This DB is OK to search for intermediate certificates. + dlDbList.push_back(dldbHandle); + } + catch (...) + { + } + } + if(mUsingTrustSettings) { + /* Append system anchors for use with Trust Settings */ + try { + CSSM_DL_DB_HANDLE rootStoreHandle = trustKeychains().rootStoreHandle(); + if (rootStoreHandle.DBHandle) + dlDbList.push_back(rootStoreHandle); + actionDataP->ActionFlags |= CSSM_TP_ACTION_TRUST_SETTINGS; + } + catch (...) { + // no root store or system keychain; don't use trust settings but continue + mUsingTrustSettings = false; + } + try { + CSSM_DL_DB_HANDLE systemKcHandle = trustKeychains().systemKcHandle(); + if (systemKcHandle.DBHandle) + dlDbList.push_back(systemKcHandle); + } + catch(...) { + /* Oh well, at least we got the root store DB */ + } + } + context.setDlDbList((uint32)dlDbList.size(), &dlDbList[0]); + } + + // verification time + char timeString[15]; + if (mVerifyTime) { + CssmUniformDate(static_cast(mVerifyTime)).convertTo( + timeString, sizeof(timeString)); + context.time(timeString); + } + + // to avoid keychain open/close thrashing, hold a copy of the search list + StorageManager::KeychainList *holdSearchList = NULL; + if (searchLibs().size() > 0) { + holdSearchList = new StorageManager::KeychainList; + globals().storageManager.getSearchList(*holdSearchList); + } + + // Go TP! + try { + mTP->certGroupVerify(subjectCertGroup, context, &mTpResult); + mTpReturn = errSecSuccess; + } catch (CommonError &err) { + mTpReturn = err.osStatus(); + secdebug("trusteval", "certGroupVerify exception: %d", (int)mTpReturn); + } + mResult = diagnoseOutcome(); + + // see if we can use the evidence + if (mTpResult.count() > 0 + && mTpResult[0].form() == CSSM_EVIDENCE_FORM_APPLE_HEADER + && mTpResult[0].as()->Version == CSSM_TP_APPLE_EVIDENCE_VERSION + && mTpResult.count() == 3 + && mTpResult[1].form() == CSSM_EVIDENCE_FORM_APPLE_CERTGROUP + && mTpResult[2].form() == CSSM_EVIDENCE_FORM_APPLE_CERT_INFO) { + evaluateUserTrust(*mTpResult[1].as(), + mTpResult[2].as(), anchors); + } else { + // unexpected evidence information. Can't use it + secdebug("trusteval", "unexpected evidence ignored"); + } + + /* do post-processing for the evaluated certificate chain */ + CFArrayRef fullChain = makeCFArray(convert, mCertChain); + CFDictionaryRef etResult = extendedTrustResults(fullChain, mResult, mTpReturn, isEVCandidate); + mExtendedResult = etResult; // assignment to CFRef type is an implicit retain + if (etResult) { + CFRelease(etResult); + } + if (fullChain) { + CFRelease(fullChain); + } + + if (allPolicies) { + /* clean up revocation policies we created implicitly */ + if(numRevocationAdded) { + freeAddedRevocationPolicyData(allPolicies, numRevocationAdded, context.allocator); + } + CFRelease(allPolicies); + } + + if (holdSearchList) { + delete holdSearchList; + holdSearchList = NULL; + } + } // end evaluation block with mutex; releases all temporary allocations in this scope + + + if (isEVCandidate && mResult == kSecTrustResultRecoverableTrustFailure && + (mTpReturn == CSSMERR_TP_NOT_TRUSTED || isRevocationServerMetaError(mTpReturn))) { + // re-do the evaluation, this time disabling EV + evaluate(true); + } +} + +// CSSM_RETURN values that map to kSecTrustResultRecoverableTrustFailure. +static const CSSM_RETURN recoverableErrors[] = +{ + CSSMERR_TP_INVALID_ANCHOR_CERT, + CSSMERR_TP_NOT_TRUSTED, + CSSMERR_TP_VERIFICATION_FAILURE, + CSSMERR_TP_VERIFY_ACTION_FAILED, + CSSMERR_TP_INVALID_REQUEST_INPUTS, + CSSMERR_TP_CERT_EXPIRED, + CSSMERR_TP_CERT_NOT_VALID_YET, + CSSMERR_TP_CERTIFICATE_CANT_OPERATE, + CSSMERR_TP_INVALID_CERT_AUTHORITY, + CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK, + CSSMERR_APPLETP_HOSTNAME_MISMATCH, + CSSMERR_TP_VERIFY_ACTION_FAILED, + CSSMERR_APPLETP_SMIME_EMAIL_ADDRS_NOT_FOUND, + CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS, + CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE, + CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH, + CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS, + CSSMERR_APPLETP_CS_BAD_PATH_LENGTH, + CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE, + CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE, + CSSMERR_APPLETP_CODE_SIGN_DEVELOPMENT, + CSSMERR_APPLETP_RS_BAD_CERT_CHAIN_LENGTH, + CSSMERR_APPLETP_UNKNOWN_CRITICAL_EXTEN, + CSSMERR_APPLETP_CRL_NOT_FOUND, + CSSMERR_APPLETP_CRL_SERVER_DOWN, + CSSMERR_APPLETP_CRL_NOT_VALID_YET, + CSSMERR_APPLETP_OCSP_UNAVAILABLE, + CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK, + CSSMERR_APPLETP_NETWORK_FAILURE, + CSSMERR_APPLETP_OCSP_RESP_TRY_LATER, + CSSMERR_APPLETP_IDENTIFIER_MISSING, +}; +#define NUM_RECOVERABLE_ERRORS (sizeof(recoverableErrors) / sizeof(CSSM_RETURN)) + +// +// Classify the TP outcome in terms of a SecTrustResultType +// +SecTrustResultType Trust::diagnoseOutcome() +{ + StLock_(mMutex); + + uint32 chainLength = 0; + if (mTpResult.count() == 3 && + mTpResult[1].form() == CSSM_EVIDENCE_FORM_APPLE_CERTGROUP && + mTpResult[2].form() == CSSM_EVIDENCE_FORM_APPLE_CERT_INFO) + { + const CertGroup &chain = *mTpResult[1].as(); + chainLength = chain.count(); + } + + switch (mTpReturn) { + case errSecSuccess: // peachy + if (mUsingTrustSettings) + { + if (chainLength) + { + const CSSM_TP_APPLE_EVIDENCE_INFO *infoList = mTpResult[2].as(); + const TPEvidenceInfo &info = TPEvidenceInfo::overlay(infoList[chainLength-1]); + const CSSM_TP_APPLE_CERT_STATUS resultCertStatus = info.status(); + bool hasUserDomainTrust = ((resultCertStatus & CSSM_CERT_STATUS_TRUST_SETTINGS_TRUST) && + (resultCertStatus & CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_USER)); + bool hasAdminDomainTrust = ((resultCertStatus & CSSM_CERT_STATUS_TRUST_SETTINGS_TRUST) && + (resultCertStatus & CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_ADMIN)); + if (hasUserDomainTrust || hasAdminDomainTrust) + { + return kSecTrustResultProceed; // explicitly allowed + } + } + } + return kSecTrustResultUnspecified; // cert evaluates OK + case CSSMERR_TP_INVALID_CERTIFICATE: // bad certificate + return kSecTrustResultFatalTrustFailure; + case CSSMERR_APPLETP_TRUST_SETTING_DENY: // authoritative denial + return kSecTrustResultDeny; + default: + break; + } + + // a known list of returns maps to kSecTrustResultRecoverableTrustFailure + const CSSM_RETURN *errp=recoverableErrors; + for(unsigned dex=0; dex anchors) +{ + StLock_(mMutex); + // extract cert chain as Certificate objects + mCertChain.resize(chain.count()); + for (uint32 n = 0; n < mCertChain.size(); n++) { + const TPEvidenceInfo &info = TPEvidenceInfo::overlay(infoList[n]); + if (info.recordId()) { + Keychain keychain = keychainByDLDb(info.DlDbHandle); + DbUniqueRecord uniqueId(keychain->database()->newDbUniqueRecord()); + secdebug("trusteval", "evidence %lu from keychain \"%s\"", (unsigned long)n, keychain->name()); + *static_cast(uniqueId) = info.UniqueRecord; + uniqueId->activate(); // transfers ownership + Item ii = keychain->item(CSSM_DL_DB_RECORD_X509_CERTIFICATE, uniqueId); + Certificate* cert = dynamic_cast(ii.get()); + if (cert == NULL) { + CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER); + } + mCertChain[n] = cert; + } else if (info.status(CSSM_CERT_STATUS_IS_IN_INPUT_CERTS)) { + secdebug("trusteval", "evidence %lu from input cert %lu", (unsigned long)n, (unsigned long)info.index()); + assert(info.index() < uint32(CFArrayGetCount(mCerts))); + SecCertificateRef cert = SecCertificateRef(CFArrayGetValueAtIndex(mCerts, + info.index())); + mCertChain[n] = Certificate::required(cert); + } else if (info.status(CSSM_CERT_STATUS_IS_IN_ANCHORS)) { + secdebug("trusteval", "evidence %lu from anchor cert %lu", (unsigned long)n, (unsigned long)info.index()); + assert(info.index() < uint32(CFArrayGetCount(anchors))); + SecCertificateRef cert = SecCertificateRef(CFArrayGetValueAtIndex(anchors, + info.index())); + mCertChain[n] = Certificate::required(cert); + } else { + // unknown source; make a new Certificate for it + secdebug("trusteval", "evidence %lu from unknown source", (unsigned long)n); + mCertChain[n] = + new Certificate(chain.blobCerts()[n], + CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_BER); + } + } + + // now walk the chain, leaf-to-root, checking for user settings + TrustStore &store = gStore(); + SecPointer policy = (CFArrayGetCount(mPolicies)) ? + Policy::required(SecPolicyRef(CFArrayGetValueAtIndex(mPolicies, 0))) : NULL; + for (mResultIndex = 0; + mResult == kSecTrustResultUnspecified && mResultIndex < mCertChain.size() && policy; + mResultIndex++) { + if (!mCertChain[mResultIndex]) { + assert(false); + continue; + } + mResult = store.find(mCertChain[mResultIndex], policy, searchLibs()); + secdebug("trusteval", "trustResult=%d from cert %d", (int)mResult, (int)mResultIndex); + } +} + + +// +// Release TP evidence information. +// This information is severely under-defined by CSSM, so we proceed +// as follows: +// (a) If the evidence matches an Apple-defined pattern, use specific +// knowledge of that format. +// (b) Otherwise, assume that the void * are flat blocks of memory. +// +void Trust::releaseTPEvidence(TPVerifyResult &result, Allocator &allocator) +{ + if (result.count() > 0) { // something to do + if (result[0].form() == CSSM_EVIDENCE_FORM_APPLE_HEADER) { + // Apple defined evidence form -- use intimate knowledge + if (result[0].as()->Version == CSSM_TP_APPLE_EVIDENCE_VERSION + && result.count() == 3 + && result[1].form() == CSSM_EVIDENCE_FORM_APPLE_CERTGROUP + && result[2].form() == CSSM_EVIDENCE_FORM_APPLE_CERT_INFO) { + // proper format + CertGroup& certs = *result[1].as(); + CSSM_TP_APPLE_EVIDENCE_INFO *evidence = result[2].as(); + uint32 count = certs.count(); + allocator.free(result[0].data()); // just a struct + certs.destroy(allocator); // certgroup contents + allocator.free(result[1].data()); // the CertGroup itself + for (uint32 n = 0; n < count; n++) + allocator.free(evidence[n].StatusCodes); + allocator.free(result[2].data()); // array of (flat) info structs + } else { + secdebug("trusteval", "unrecognized Apple TP evidence format"); + // drop it -- better leak than kill + } + } else { + // unknown format -- blindly assume flat blobs + secdebug("trusteval", "destroying unknown TP evidence format"); + for (uint32 n = 0; n < result.count(); n++) + { + allocator.free(result[n].data()); + } + } + + allocator.free (result.Evidence); + } +} + + +// +// Clear evaluation results unless state is initial (invalid) +// +void Trust::clearResults() +{ + StLock_(mMutex); + if (mResult != kSecTrustResultInvalid) { + releaseTPEvidence(mTpResult, mTP.allocator()); + mResult = kSecTrustResultInvalid; + } +} + + +// +// Build evidence information +// +void Trust::buildEvidence(CFArrayRef &certChain, TPEvidenceInfo * &statusChain) +{ + StLock_(mMutex); + if (mResult == kSecTrustResultInvalid) + MacOSError::throwMe(errSecTrustNotAvailable); + certChain = mEvidenceReturned = + makeCFArray(convert, mCertChain); + if(mTpResult.count() >= 3) { + statusChain = mTpResult[2].as(); + } + else { + statusChain = NULL; + } +} + + +// +// Return extended result dictionary +// +void Trust::extendedResult(CFDictionaryRef &result) +{ + if (mResult == kSecTrustResultInvalid) + MacOSError::throwMe(errSecTrustNotAvailable); + if (mExtendedResult) + CFRetain(mExtendedResult); // retain before handing out to caller + result = mExtendedResult; +} + + +// +// Return properties array (a CFDictionaryRef for each certificate in chain) +// +CFArrayRef Trust::properties() +{ + // Builds and returns an array which the caller must release. + StLock_(mMutex); + CFMutableArrayRef properties = CFArrayCreateMutable(NULL, 0, + &kCFTypeArrayCallBacks); + if (mResult == kSecTrustResultInvalid) // chain not built or evaluated + return properties; + + // Walk the chain from leaf to anchor, building properties dictionaries + for (uint32 idx=0; idx < mCertChain.size(); idx++) { + CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (dict) { + CFStringRef title = NULL; + mCertChain[idx]->inferLabel(false, &title); + if (title) { + CFDictionarySetValue(dict, (const void *)kSecPropertyTypeTitle, (const void *)title); + CFRelease(title); + } + if (idx == 0 && mTpReturn != errSecSuccess) { + CFStringRef error = SecCopyErrorMessageString(mTpReturn, NULL); + if (error) { + CFDictionarySetValue(dict, (const void *)kSecPropertyTypeError, (const void *)error); + CFRelease(error); + } + } + CFArrayAppendValue(properties, (const void *)dict); + CFRelease(dict); + } + } + + return properties; +} + +// +// Return dictionary of evaluation results +// +CFDictionaryRef Trust::results() +{ + // Builds and returns a dictionary which the caller must release. + StLock_(mMutex); + CFMutableDictionaryRef results = CFDictionaryCreateMutable(NULL, 0, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + // kSecTrustResultValue + CFNumberRef numValue = CFNumberCreate(NULL, kCFNumberSInt32Type, &mResult); + if (numValue) { + CFDictionarySetValue(results, (const void *)kSecTrustResultValue, (const void *)numValue); + CFRelease(numValue); + } + if (mResult == kSecTrustResultInvalid || !mExtendedResult) + return results; // we have nothing more to add + + // kSecTrustEvaluationDate + CFTypeRef evaluationDate; + if (CFDictionaryGetValueIfPresent(mExtendedResult, kSecTrustEvaluationDate, &evaluationDate)) + CFDictionarySetValue(results, (const void *)kSecTrustEvaluationDate, (const void *)evaluationDate); + + // kSecTrustExtendedValidation, kSecTrustOrganizationName + CFTypeRef organizationName; + if (CFDictionaryGetValueIfPresent(mExtendedResult, kSecEVOrganizationName, &organizationName)) { + CFDictionarySetValue(results, (const void *)kSecTrustOrganizationName, (const void *)organizationName); + CFDictionarySetValue(results, (const void *)kSecTrustExtendedValidation, (const void *)kCFBooleanTrue); + } + + // kSecTrustRevocationChecked, kSecTrustRevocationValidUntilDate + CFTypeRef expirationDate; + if (CFDictionaryGetValueIfPresent(mExtendedResult, kSecTrustExpirationDate, &expirationDate)) { + CFDictionarySetValue(results, (const void *)kSecTrustRevocationValidUntilDate, (const void *)expirationDate); + CFDictionarySetValue(results, (const void *)kSecTrustRevocationChecked, (const void *)kCFBooleanTrue); + } + + return results; +} + + + +//* =========================================================================== +//* We need a way to compare two CSSM_DL_DB_HANDLEs WITHOUT using a operator +//* overload +//* =========================================================================== +static +bool Compare_CSSM_DL_DB_HANDLE(const CSSM_DL_DB_HANDLE &h1, const CSSM_DL_DB_HANDLE &h2) +{ + return (h1.DLHandle == h2.DLHandle && h1.DBHandle == h2.DBHandle); +} + + + +// +// Given a DL_DB_HANDLE, locate the Keychain object (from the search list) +// +Keychain Trust::keychainByDLDb(const CSSM_DL_DB_HANDLE &handle) +{ + StLock_(mMutex); + StorageManager::KeychainList& list = searchLibs(); + for (StorageManager::KeychainList::const_iterator it = list.begin(); + it != list.end(); it++) + { + try + { + + if (Compare_CSSM_DL_DB_HANDLE((*it)->database()->handle(), handle)) + return *it; + } + catch (...) + { + } + } + if(mUsingTrustSettings) { + try { + if(Compare_CSSM_DL_DB_HANDLE(trustKeychains().rootStoreHandle(), handle)) { + return trustKeychains().rootStore(); + } + if(Compare_CSSM_DL_DB_HANDLE(trustKeychains().systemKcHandle(), handle)) { + return trustKeychains().systemKc(); + } + } + catch(...) { + /* one of those is missing; proceed */ + } + } + + // could not find in search list - internal error + + // we now throw an error here rather than assert and silently fail. That way our application won't crash... + MacOSError::throwMe(errSecInternal); +}