/* * Copyright (c) 2002 Apple Computer, Inc. All Rights Reserved. * * The contents of this file constitute Original Code as defined in and are * subject to the Apple Public Source License Version 1.2 (the 'License'). * You may not use this file except in compliance with the License. Please obtain * a copy of the License at http://www.apple.com/publicsource and read it before * using this file. * * This 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. */ // // Trust.cpp // #include #include #include #include #include #include "SecBridge.h" using namespace KeychainCore; // // For now, we use a global TrustStore // ModuleNexus Trust::gStore; // // @@@ For some reason, the C++ type system won't resolve an operator from Security namespace. // Drag it in here explicitly (the hard way). Someone bored might want to investigate which // language rules ambiguates the Security::operator == inside the Security::KeychainCore namespace. // inline bool operator == (const CSSM_DL_DB_HANDLE &h1, const CSSM_DL_DB_HANDLE &h2) { return Security::operator == (h1, h2); } // // 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)), mResult(kSecTrustResultInvalid) { // set default search list from user's default globals().storageManager.getSearchList(mSearchLibs); } // // Clean up a Trust object // Trust::~Trust() throw() { clearResults(); } // // 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 CssmData cfCertificateData(SecCertificateRef certificate) { return Certificate::required(certificate)->data(); } // SecPolicyRef -> CssmField (CFDataRef/NULL or oid/value of a SecPolicy) CssmField cfField(SecPolicyRef item) { SecPointer policy = Policy::required(SecPolicyRef(item)); return CssmField(policy->oid(), policy->value()); } // SecKeychain -> CssmDlDbHandle CSSM_DL_DB_HANDLE cfKeychain(SecKeychainRef ref) { Keychain keychain = KeychainImpl::required(ref); return keychain->database()->handle(); } // // 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() { // if we have evaluated before, release prior result clearResults(); // build the target cert group CFToVector subjects(mCerts); 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); if (mActionData) context.actionData() = cfData(mActionData); // policies (one at least, please) CFToVector policies(mPolicies); if (policies.empty()) MacOSError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS); context.setPolicies(policies, policies); // anchor certificates CFCopyRef anchors(mAnchors); if (!anchors) anchors = gStore().copyRootCertificates(); // retains CFToVector roots(anchors); context.anchors(roots, roots); // dlDbList (keychain list) vector dlDbList; for (StorageManager::KeychainList::const_iterator it = mSearchLibs.begin(); it != mSearchLibs.end(); it++) { try { dlDbList.push_back((*it)->database()->handle()); } catch (...) { } } context.setDlDbList(dlDbList.size(), &dlDbList[0]); // verification time char timeString[15]; if (mVerifyTime) { CssmUniformDate(static_cast(mVerifyTime)).convertTo( timeString, sizeof(timeString)); context.time(timeString); } // Go TP! try { mTP->certGroupVerify(subjectCertGroup, context, &mTpResult); mTpReturn = noErr; } catch (CssmCommonError &err) { mTpReturn = err.osStatus(); } 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"); } } // // Classify the TP outcome in terms of a SecTrustResultType // SecTrustResultType Trust::diagnoseOutcome() { switch (mTpReturn) { case noErr: // peachy return kSecTrustResultUnspecified; case CSSMERR_TP_CERT_EXPIRED: // expired cert case CSSMERR_TP_CERT_NOT_VALID_YET: // mis-expired cert case CSSMERR_TP_NOT_TRUSTED: // no root, no anchor case CSSMERR_TP_VERIFICATION_FAILURE: // root does not self-verify case CSSMERR_TP_INVALID_ANCHOR_CERT: // valid is not an anchor case CSSMERR_TP_VERIFY_ACTION_FAILED: // policy action failed return kSecTrustResultRecoverableTrustFailure; case CSSMERR_TP_INVALID_CERTIFICATE: // bad certificate return kSecTrustResultFatalTrustFailure; default: return kSecTrustResultOtherError; // unknown } } // // Assuming a good evidence chain, check user trust // settings and set mResult accordingly. // void Trust::evaluateUserTrust(const CertGroup &chain, const CSSM_TP_APPLE_EVIDENCE_INFO *infoList, CFCopyRef anchors) { // 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 #%ld from keychain \"%s\"", n, keychain->name()); *static_cast(uniqueId) = info.UniqueRecord; uniqueId->activate(); // transfers ownership mCertChain[n] = safe_cast(keychain->item(CSSM_DL_DB_RECORD_X509_CERTIFICATE, uniqueId).get()); } else if (info.status(CSSM_CERT_STATUS_IS_IN_INPUT_CERTS)) { secdebug("trusteval", "evidence %ld from input cert %ld", n, 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 %ld from anchor cert %ld", n, 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 %ld from unknown source", 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 = Policy::required(SecPolicyRef(CFArrayGetValueAtIndex(mPolicies, 0))); for (mResultIndex = 0; mResult == kSecTrustResultUnspecified && mResultIndex < mCertChain.size(); mResultIndex++) { if (!mCertChain[mResultIndex]) { assert(false); continue; } mResult = store.find(mCertChain[mResultIndex], policy); } } // // 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, CssmAllocator &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() { if (mResult != kSecTrustResultInvalid) { releaseTPEvidence(mTpResult, mTP.allocator()); mResult = kSecTrustResultInvalid; } } // Convert a SecPointer to a CF object. static SecCertificateRef convert(const SecPointer &certificate) { return *certificate; } // // Build evidence information // void Trust::buildEvidence(CFArrayRef &certChain, TPEvidenceInfo * &statusChain) { if (mResult == kSecTrustResultInvalid) MacOSError::throwMe(errSecTrustNotAvailable); certChain = mEvidenceReturned = makeCFArray(convert, mCertChain); statusChain = mTpResult[2].as(); } // // Given a DL_DB_HANDLE, locate the Keychain object (from the search list) // Keychain Trust::keychainByDLDb(const CSSM_DL_DB_HANDLE &handle) const { for (StorageManager::KeychainList::const_iterator it = mSearchLibs.begin(); it != mSearchLibs.end(); it++) { try { if ((*it)->database()->handle() == handle) return *it; } catch (...) { } } // could not find in search list - internal error assert(false); return Keychain(); }