/* * Copyright (c) 2000-2001 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. */ /* * TPCertInfo.cpp - TP's private certificate info classes * * Written 10/23/2000 by Doug Mitchell. */ #include "TPCertInfo.h" #include "tpdebugging.h" #include "tpTime.h" #include "certGroupUtils.h" #include "TPDatabase.h" #include "TPNetwork.h" #include #include #include #include #include /* for memcmp */ #include /* for Mutex */ #include #include #include #include #include #define tpTimeDbg(args...) secdebug("tpTime", ## args) #define tpCertInfoDbg(args...) secdebug("tpCert", ## args) static const TPClItemCalls tpCertClCalls = { CSSM_CL_CertGetFirstCachedFieldValue, CSSM_CL_CertAbortQuery, CSSM_CL_CertCache, CSSM_CL_CertAbortCache, CSSM_CL_CertVerify, &CSSMOID_X509V1ValidityNotBefore, &CSSMOID_X509V1ValidityNotAfter, CSSMERR_TP_INVALID_CERT_POINTER, CSSMERR_TP_CERT_EXPIRED, CSSMERR_TP_CERT_NOT_VALID_YET }; TPClItemInfo::TPClItemInfo( CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand, const TPClItemCalls &clCalls, const CSSM_DATA *itemData, TPItemCopy copyItemData, const char *verifyTime) // may be NULL : mClHand(clHand), mCspHand(cspHand), mClCalls(clCalls), mWeOwnTheData(false), mCacheHand(0), mIssuerName(NULL), mItemData(NULL), mSigAlg(CSSM_ALGID_NONE), mNotBefore(NULL), mNotAfter(NULL), mIsExpired(false), mIsNotValidYet(false), mIndex(0) { try { CSSM_RETURN crtn = cacheItem(itemData, copyItemData); if(crtn) { CssmError::throwMe(crtn); } /* * Fetch standard fields... * Issue name assumes same OID for Certs and CRLs! */ crtn = fetchField(&CSSMOID_X509V1IssuerName, &mIssuerName); if(crtn) { CssmError::throwMe(crtn); } /* * Signing algorithm, infer from TBS algId * Note this assumes that the OID for fetching this field is the * same for CRLs and Certs. */ CSSM_DATA_PTR algField; crtn = fetchField(&CSSMOID_X509V1SignatureAlgorithmTBS, &algField); if(crtn) { releaseResources(); CssmError::throwMe(crtn); } if(algField->Length != sizeof(CSSM_X509_ALGORITHM_IDENTIFIER)) { tpErrorLog("TPClItemInfo: bad CSSM_X509_ALGORITHM_IDENTIFIER\n"); CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } CSSM_X509_ALGORITHM_IDENTIFIER *algId = (CSSM_X509_ALGORITHM_IDENTIFIER *)algField->Data; bool algFound = cssmOidToAlg(&algId->algorithm, &mSigAlg); if(!algFound) { tpErrorLog("TPClItemInfo: unknown signature algorithm\n"); CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT); } if(mSigAlg == CSSM_ALGID_ECDSA_SPECIFIED) { /* Further processing needed to get digest algorithm */ if(decodeECDSA_SigAlgParams(&algId->parameters, &mSigAlg)) { tpErrorLog("TPClItemInfo: incomplete/unknown ECDSA signature algorithm\n"); CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT); } } freeField(&CSSMOID_X509V1SignatureAlgorithmTBS, algField); fetchNotBeforeAfter(); calculateCurrent(verifyTime); } catch(...) { releaseResources(); throw; } } TPClItemInfo::~TPClItemInfo() { tpCertInfoDbg("TPClItemInfo destruct this %p", this); releaseResources(); } void TPClItemInfo::releaseResources() { if(mWeOwnTheData && (mItemData != NULL)) { tpFreeCssmData(Allocator::standard(), mItemData, CSSM_TRUE); mWeOwnTheData = false; mItemData = NULL; } if(mIssuerName) { freeField(&CSSMOID_X509V1IssuerName, mIssuerName); mIssuerName = NULL; } if(mCacheHand != 0) { mClCalls.abortCache(mClHand, mCacheHand); mCacheHand = 0; } if(mNotBefore) { CFRelease(mNotBefore); mNotBefore = NULL; } if(mNotAfter) { CFRelease(mNotAfter); mNotAfter = NULL; } } /* fetch arbitrary field from cached cert */ CSSM_RETURN TPClItemInfo::fetchField( const CSSM_OID *fieldOid, CSSM_DATA_PTR *fieldData) // mallocd by CL and RETURNED { CSSM_RETURN crtn; uint32 NumberOfFields = 0; CSSM_HANDLE resultHand = 0; *fieldData = NULL; assert(mClCalls.getField != NULL); assert(mCacheHand != 0); crtn = mClCalls.getField( mClHand, mCacheHand, fieldOid, &resultHand, &NumberOfFields, fieldData); if(crtn) { return crtn; } if(NumberOfFields != 1) { tpErrorLog("TPCertInfo::fetchField: numFields %d, expected 1\n", (int)NumberOfFields); } mClCalls.abortQuery(mClHand, resultHand); return CSSM_OK; } /* free arbitrary field obtained from fetchField() */ CSSM_RETURN TPClItemInfo::freeField( const CSSM_OID *fieldOid, CSSM_DATA_PTR fieldData) { return CSSM_CL_FreeFieldValue(mClHand, fieldOid, fieldData); } /* * Verify with an issuer cert - works on certs and CRLs. * Issuer/subject name match already performed by caller. * Optional paramCert is used to provide parameters when issuer * has a partial public key. */ CSSM_RETURN TPClItemInfo::verifyWithIssuer( TPCertInfo *issuerCert, TPCertInfo *paramCert /* = NULL */) const { CSSM_RETURN crtn; assert(mClHand != 0); assert(issuerCert->isIssuerOf(*this)); assert(mCspHand != 0); /* * Special case: detect partial public key right now; don't even * bother trying the cert verify in that case. */ if(issuerCert->hasPartialKey() && (paramCert == NULL)) { /* caller deals with this later */ tpVfyDebug("verifyWithIssuer PUBLIC_KEY_INCOMPLETE"); return CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE; } CSSM_CC_HANDLE ccHand; crtn = CSSM_CSP_CreateSignatureContext(mCspHand, mSigAlg, NULL, // Access Creds issuerCert->pubKey(), &ccHand); if(crtn != CSSM_OK) { tpErrorLog("verifyWithIssuer: CreateSignatureContext error\n"); CssmError::throwMe(crtn); } if(paramCert != NULL) { assert(issuerCert->hasPartialKey()); /* add in parameter-bearing key */ CSSM_CONTEXT_ATTRIBUTE newAttr; newAttr.AttributeType = CSSM_ATTRIBUTE_PARAM_KEY; newAttr.AttributeLength = sizeof(CSSM_KEY); newAttr.Attribute.Key = paramCert->pubKey(); crtn = CSSM_UpdateContextAttributes(ccHand, 1, &newAttr); if(crtn) { tpErrorLog("verifyWithIssuer: CSSM_UpdateContextAttributes error\n"); CssmError::throwMe(crtn); } } crtn = mClCalls.itemVerify(mClHand, ccHand, mItemData, NULL, // issuer cert NULL, // VerifyScope 0); // ScopeSize switch(crtn) { case CSSM_OK: // success case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: // caller handles tpVfyDebug("verifyWithIssuer GOOD"); break; default: /* all others appear here as general cert verify error */ crtn = CSSMERR_TP_VERIFICATION_FAILURE; tpVfyDebug("verifyWithIssuer BAD"); break; } CSSM_DeleteContext(ccHand); return crtn; } CSSM_RETURN TPClItemInfo::cacheItem( const CSSM_DATA *itemData, TPItemCopy copyItemData) { switch(copyItemData) { case TIC_NoCopy: mItemData = const_cast(itemData); break; case TIC_CopyData: mItemData = tpMallocCopyCssmData(Allocator::standard(), itemData); mWeOwnTheData = true; break; default: assert(0); CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } /* cache the cert/CRL in the CL */ return mClCalls.cacheItem(mClHand, mItemData, &mCacheHand); } /* * Calculate not before/after times as struct tm. Only throws on * gross error (CSSMERR_TP_INVALID_CERT_POINTER, etc.). * * Only differences between Cert and CRL flavors of this are the * OIDs used to fetch the appropriate before/after times, both of * which are expressed as CSSM_X509_TIME structs for both Certs * and CRLS. */ void TPClItemInfo::fetchNotBeforeAfter() { CSSM_DATA_PTR notBeforeField = NULL; CSSM_DATA_PTR notAfterField = NULL; CSSM_RETURN crtn = CSSM_OK; CSSM_X509_TIME *xTime; assert(cacheHand() != CSSM_INVALID_HANDLE); crtn = fetchField(mClCalls.notBeforeOid, ¬BeforeField); if(crtn) { tpErrorLog("fetchNotBeforeAfter: GetField error\n"); CssmError::throwMe(mClCalls.invalidItemRtn); } /* subsequent errors to errOut */ xTime = (CSSM_X509_TIME *)notBeforeField->Data; if(timeStringToCfDate((char *)xTime->time.Data, xTime->time.Length, &mNotBefore)) { tpErrorLog("fetchNotBeforeAfter: malformed notBefore time\n"); crtn = mClCalls.invalidItemRtn; goto errOut; } crtn = fetchField(mClCalls.notAfterOid, ¬AfterField); if(crtn) { /* * Tolerate a missing NextUpdate in CRL only */ if(mClCalls.notAfterOid == &CSSMOID_X509V1ValidityNotAfter) { tpErrorLog("fetchNotBeforeAfter: GetField error\n"); crtn = mClCalls.invalidItemRtn; goto errOut; } else { /* * Fake NextUpdate to be "at the end of time" */ timeStringToCfDate(CSSM_APPLE_CRL_END_OF_TIME, strlen(CSSM_APPLE_CRL_END_OF_TIME), &mNotAfter); } } else { xTime = (CSSM_X509_TIME *)notAfterField->Data; if(timeStringToCfDate((char *)xTime->time.Data, xTime->time.Length, &mNotAfter)) { tpErrorLog("fetchNotBeforeAfter: malformed notAfter time\n"); crtn = mClCalls.invalidItemRtn; goto errOut; } } crtn = CSSM_OK; errOut: if(notAfterField) { freeField(mClCalls.notAfterOid, notAfterField); } if(notBeforeField) { freeField(mClCalls.notBeforeOid, notBeforeField); } if(crtn != CSSM_OK) { CssmError::throwMe(crtn); } } /* * Verify validity (not before/after) by comparing the reference * time (verifyString if present, or "now" if NULL) to the * not before/after fields fetched from the item at construction. * * Called implicitly at construction; can be called again any time * to re-establish validity (e.g. after fetching an item from a cache). * * We use some stdlib time calls over in tpTime.c; the stdlib function * gmtime() is not thread-safe, so we do the protection here. Note that * this makes *our* calls to gmtime() thread-safe, but if the app has * other threads which are also calling gmtime, we're out of luck. */ ModuleNexus tpTimeLock; CSSM_RETURN TPClItemInfo::calculateCurrent( const char *verifyString) { CFDateRef refTime = NULL; if(verifyString != NULL) { /* caller specifies verification time base */ if(timeStringToCfDate(verifyString, strlen(verifyString), &refTime)) { tpErrorLog("calculateCurrent: timeStringToCfDate error\n"); return CSSMERR_TP_INVALID_TIMESTRING; } } else { /* time base = right now */ refTime = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent()); } if(compareTimes(refTime, mNotBefore) < 0) { mIsNotValidYet = true; tpTimeDbg("\nTP_CERT_NOT_VALID_YET: now %g notBefore %g", CFDateGetAbsoluteTime(refTime), CFDateGetAbsoluteTime(mNotBefore)); CFRelease(refTime); return mClCalls.notValidYetRtn; } else { mIsNotValidYet = false; } if(compareTimes(refTime, mNotAfter) > 0) { mIsExpired = true; tpTimeDbg("\nTP_CERT_EXPIRED: now %g notBefore %g", CFDateGetAbsoluteTime(refTime), CFDateGetAbsoluteTime(mNotBefore)); CFRelease(refTime); return mClCalls.expiredRtn; } else { mIsExpired = false; CFRelease(refTime); return CSSM_OK; } } /* * No default constructor - this is the only way. * This caches the cert and fetches subjectName, issuerName, and * mPublicKey to ensure the incoming certData is well-constructed. */ TPCertInfo::TPCertInfo( CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand, const CSSM_DATA *certData, TPItemCopy copyCertData, // true: we copy, we free // false - caller owns const char *verifyTime) // may be NULL : TPClItemInfo(clHand, cspHand, tpCertClCalls, certData, copyCertData, verifyTime), mSubjectName(NULL), mPublicKeyData(NULL), mPublicKey(NULL), mIsAnchor(false), mIsFromInputCerts(false), mIsFromNet(false), mNumStatusCodes(0), mStatusCodes(NULL), mUniqueRecord(NULL), mUsed(false), mIsLeaf(false), mIsRoot(TRS_Unknown), mRevCheckGood(false), mRevCheckComplete(false), mTrustSettingsEvaluated(false), mTrustSettingsDomain(kSecTrustSettingsDomainSystem), mTrustSettingsResult(kSecTrustSettingsResultInvalid), mTrustSettingsFoundAnyEntry(false), mTrustSettingsFoundMatchingEntry(false), mAllowedErrs(NULL), mNumAllowedErrs(0), mIgnoredError(false), mTrustSettingsKeyUsage(0), mCertHashStr(NULL) { CSSM_RETURN crtn; tpCertInfoDbg("TPCertInfo construct this %p", this); mDlDbHandle.DLHandle = 0; mDlDbHandle.DBHandle = 0; /* fetch subject name */ crtn = fetchField(&CSSMOID_X509V1SubjectName, &mSubjectName); if(crtn) { /* bad cert */ releaseResources(); CssmError::throwMe(crtn); } /* this cert's public key */ crtn = fetchField(&CSSMOID_CSSMKeyStruct, &mPublicKeyData); if(crtn || (mPublicKeyData->Length != sizeof(CSSM_KEY))) { /* bad cert */ releaseResources(); CssmError::throwMe(crtn); } mPublicKey = (CSSM_KEY_PTR)mPublicKeyData->Data; /* calculate other commonly used fields */ if(tpCompareCssmData(mSubjectName, issuerName())) { /* * Per Radar 3374978, perform complete signature verification * lazily - just check subject/issuer match here. */ tpAnchorDebug("TPCertInfo potential anchor"); mIsRoot = TRS_NamesMatch; } else { mIsRoot = TRS_NotRoot; } } /* frees mSubjectName, mIssuerName, mCacheHand via mClHand */ TPCertInfo::~TPCertInfo() { tpCertInfoDbg("TPCertInfo destruct this %p", this); releaseResources(); } void TPCertInfo::releaseResources() { if(mSubjectName) { freeField(&CSSMOID_X509V1SubjectName, mSubjectName); mSubjectName = NULL; } if(mPublicKeyData) { freeField(&CSSMOID_CSSMKeyStruct, mPublicKeyData); mPublicKey = NULL; mPublicKeyData = NULL; } if(mStatusCodes) { free(mStatusCodes); mStatusCodes = NULL; } if(mAllowedErrs) { free(mAllowedErrs); } if(mCertHashStr) { CFRelease(mCertHashStr); } TPClItemInfo::releaseResources(); } const CSSM_DATA *TPCertInfo::subjectName() { assert(mSubjectName != NULL); return mSubjectName; } /* * Perform semi-lazy evaluation of "rootness". Subject and issuer names * compared at constructor. * If avoidVerify is true, we won't do the signature verify: caller * just wants to know if the subject and issuer names match. */ bool TPCertInfo::isSelfSigned(bool avoidVerify) { switch(mIsRoot) { case TRS_NotRoot: // known not to be root return false; case TRS_IsRoot: return true; case TRS_NamesMatch: if(avoidVerify) { return true; } /* else drop through and verify */ case TRS_Unknown: // actually shouldn't happen, but to be safe... default: /* do the signature verify */ if(verifyWithIssuer(this) == CSSM_OK) { tpAnchorDebug("isSelfSigned anchor verified"); mIsRoot = TRS_IsRoot; return true; } else { tpAnchorDebug("isSelfSigned anchor vfy FAIL"); mIsRoot = TRS_NotRoot; return false; } } } /* * Am I the issuer of the specified subject item? Returns true if so. * Works for subject certs as well as CRLs. */ bool TPCertInfo::isIssuerOf( const TPClItemInfo &subject) { assert(mSubjectName != NULL); assert(subject.issuerName() != NULL); if(tpCompareCssmData(mSubjectName, subject.issuerName())) { return true; } else { return false; } } bool TPCertInfo::addStatusCode(CSSM_RETURN code) { mNumStatusCodes++; mStatusCodes = (CSSM_RETURN *)realloc(mStatusCodes, mNumStatusCodes * sizeof(CSSM_RETURN)); mStatusCodes[mNumStatusCodes - 1] = code; return isStatusFatal(code); } bool TPCertInfo::isStatusFatal(CSSM_RETURN code) { for(unsigned dex=0; dexKeyHeader.KeyAttr & CSSM_KEYATTR_PARTIAL) { return true; } else { return false; } } /* * Evaluate trust settings; returns true in *foundMatchingEntry if positive * match found - i.e., cert chain construction is done. */ OSStatus TPCertInfo::evaluateTrustSettings( const CSSM_OID &policyOid, const char *policyString, // optional uint32 policyStringLen, SecTrustSettingsKeyUsage keyUse, // required bool *foundMatchingEntry, // RETURNED bool *foundAnyEntry) // RETURNED { /* * We might have to force a re-evaluation if the requested key usage * is not a subset of what we already checked for (and cached). */ if(mTrustSettingsEvaluated) { bool doFlush = false; if(mTrustSettingsKeyUsage != kSecTrustSettingsKeyUseAny) { if(keyUse == kSecTrustSettingsKeyUseAny) { /* now want "any", checked something else before */ doFlush = true; } else if((keyUse & mTrustSettingsKeyUsage) != keyUse) { /* want bits that we didn't ask for before */ doFlush = true; } } if(doFlush) { tpTrustSettingsDbg("evaluateTrustSettings: flushing cached trust for " "%p due to keyUse 0x%x", this, (int)keyUse); mTrustSettingsEvaluated = false; mTrustSettingsFoundAnyEntry = false; mTrustSettingsResult = kSecTrustSettingsResultInvalid; mTrustSettingsFoundMatchingEntry = false; if(mAllowedErrs != NULL) { free(mAllowedErrs); } mNumAllowedErrs = 0; } /* else we can safely use the cached values */ } if(!mTrustSettingsEvaluated) { if(mCertHashStr == NULL) { const CSSM_DATA *certData = itemData(); mCertHashStr = SecTrustSettingsCertHashStrFromData(certData->Data, certData->Length); } OSStatus ortn = SecTrustSettingsEvaluateCert(mCertHashStr, &policyOid, policyString, policyStringLen, keyUse, /* * This is the purpose of the avoidVerify option, right here. * If this is a root cert and it has trust settings, we avoid * the signature verify. If it turns out there are no trust * settings and this is a root, we'll verify the signature * elsewhere (e.g. post_trust_setting: in buildCertGroup()). */ isSelfSigned(true), &mTrustSettingsDomain, &mAllowedErrs, &mNumAllowedErrs, &mTrustSettingsResult, &mTrustSettingsFoundMatchingEntry, &mTrustSettingsFoundAnyEntry); if(ortn) { tpTrustSettingsDbg("evaluateTrustSettings: SecTrustSettingsEvaluateCert error!"); return ortn; } mTrustSettingsEvaluated = true; mTrustSettingsKeyUsage = keyUse; #ifndef NDEBUG if(mTrustSettingsFoundMatchingEntry) { tpTrustSettingsDbg("evaluateTrustSettings: found for %p result %d", this, (int)mTrustSettingsResult); } #endif } *foundMatchingEntry = mTrustSettingsFoundMatchingEntry; *foundAnyEntry = mTrustSettingsFoundAnyEntry; return noErr; } /* true means "verification terminated due to user trust setting" */ bool TPCertInfo::trustSettingsFound() { switch(mTrustSettingsResult) { case kSecTrustSettingsResultUnspecified: /* entry but not definitive */ case kSecTrustSettingsResultInvalid: /* no entry */ return false; default: return true; } } /* * Determine if this has an empty SubjectName field. Returns true if so. */ bool TPCertInfo::hasEmptySubjectName() { /* * A "pure" empty subject is two bytes (0x30 00) - constructed sequence, * short form length, length 0. We'll be robust and tolerate a missing * field, as well as a possible BER-encoded subject with some extra cruft. */ if((mSubjectName == NULL) || (mSubjectName->Length <= 4)) { return true; } else { return false; } } /* * Free mUniqueRecord if it exists. * This is *not* done in our destructor because this record sometimes * has to persist in the form of a CSSM evidence chain. */ void TPCertInfo::freeUniqueRecord() { if(mUniqueRecord == NULL) { return; } tpDbDebug("freeUniqueRecord: freeing cert record %p", mUniqueRecord); CSSM_DL_FreeUniqueRecord(mDlDbHandle, mUniqueRecord); } /*** *** TPCertGroup class ***/ /* build empty group */ TPCertGroup::TPCertGroup( Allocator &alloc, TPGroupOwner whoOwns) : mAlloc(alloc), mCertInfo(NULL), mNumCerts(0), mSizeofCertInfo(0), mWhoOwns(whoOwns) { tpCertInfoDbg("TPCertGroup simple construct this %p", this); /* nothing for now */ } /* * Construct from unordered, untrusted CSSM_CERTGROUP. Resulting * TPCertInfos are more or less in the same order as the incoming * certs, though incoming certs are discarded if they don't parse. * No verification of any sort is performed. */ TPCertGroup::TPCertGroup( const CSSM_CERTGROUP &CertGroupFrag, CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand, Allocator &alloc, const char *verifyTime, // may be NULL bool firstCertMustBeValid, TPGroupOwner whoOwns) : mAlloc(alloc), mCertInfo(NULL), mNumCerts(0), mSizeofCertInfo(0), mWhoOwns(whoOwns) { tpCertInfoDbg("TPCertGroup hard construct this %p", this); /* verify input args */ if(cspHand == CSSM_INVALID_HANDLE) { CssmError::throwMe(CSSMERR_TP_INVALID_CSP_HANDLE); } if(clHand == CSSM_INVALID_HANDLE) { CssmError::throwMe(CSSMERR_TP_INVALID_CL_HANDLE); } if(firstCertMustBeValid) { if( (CertGroupFrag.NumCerts == 0) || (CertGroupFrag.GroupList.CertList[0].Data == NULL) || (CertGroupFrag.GroupList.CertList[0].Length == 0)) { CssmError::throwMe(CSSMERR_TP_INVALID_CERTIFICATE); } } if(CertGroupFrag.CertGroupType != CSSM_CERTGROUP_DATA) { CssmError::throwMe(CSSMERR_TP_INVALID_CERTGROUP); } switch(CertGroupFrag.CertType) { case CSSM_CERT_X_509v1: case CSSM_CERT_X_509v2: case CSSM_CERT_X_509v3: break; default: CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT); } switch(CertGroupFrag.CertEncoding) { case CSSM_CERT_ENCODING_BER: case CSSM_CERT_ENCODING_DER: break; default: CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT); } /* * Add remaining input certs to mCertInfo. */ TPCertInfo *certInfo = NULL; for(unsigned certDex=0; certDexindex(certDex); appendCert(certInfo); } } /* * Deletes contents of mCertInfo[] if appropriate. */ TPCertGroup::~TPCertGroup() { if(mWhoOwns == TGO_Group) { unsigned i; for(i=0; i here, but * gdb is so lame that it doesn't even let one examine the contents * of an array<> (or just about anything else in the STL). I prefer * debuggability over saving a few lines of trivial code. */ void TPCertGroup::appendCert( TPCertInfo *certInfo) // appends to end of mCertInfo { if(mNumCerts == mSizeofCertInfo) { if(mSizeofCertInfo == 0) { /* appending to empty array */ mSizeofCertInfo = 1; } else { mSizeofCertInfo *= 2; } mCertInfo = (TPCertInfo **)mAlloc.realloc(mCertInfo, mSizeofCertInfo * sizeof(TPCertInfo *)); } mCertInfo[mNumCerts++] = certInfo; } TPCertInfo *TPCertGroup::certAtIndex( unsigned index) { if(index > (mNumCerts - 1)) { CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } return mCertInfo[index]; } TPCertInfo *TPCertGroup::removeCertAtIndex( unsigned index) // doesn't delete the cert, just // removes it from out list { if(index > (mNumCerts - 1)) { CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } TPCertInfo *rtn = mCertInfo[index]; /* removed requested element and compact remaining array */ unsigned i; for(i=index; i<(mNumCerts - 1); i++) { mCertInfo[i] = mCertInfo[i+1]; } mNumCerts--; return rtn; } TPCertInfo *TPCertGroup::firstCert() { if(mNumCerts == 0) { /* the caller really should not do this... */ CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } else { return mCertInfo[0]; } } TPCertInfo *TPCertGroup::lastCert() { if(mNumCerts == 0) { return NULL; } else { return mCertInfo[mNumCerts - 1]; } } /* build a CSSM_CERTGROUP corresponding with our mCertInfo */ CSSM_CERTGROUP_PTR TPCertGroup::buildCssmCertGroup() { CSSM_CERTGROUP_PTR cgrp = (CSSM_CERTGROUP_PTR)mAlloc.malloc(sizeof(CSSM_CERTGROUP)); cgrp->NumCerts = mNumCerts; cgrp->CertGroupType = CSSM_CERTGROUP_DATA; cgrp->CertType = CSSM_CERT_X_509v3; cgrp->CertEncoding = CSSM_CERT_ENCODING_DER; if(mNumCerts == 0) { /* legal */ cgrp->GroupList.CertList = NULL; return cgrp; } cgrp->GroupList.CertList = (CSSM_DATA_PTR)mAlloc.calloc(mNumCerts, sizeof(CSSM_DATA)); for(unsigned i=0; iitemData(), &cgrp->GroupList.CertList[i]); } return cgrp; } /* build a CSSM_TP_APPLE_EVIDENCE_INFO array */ CSSM_TP_APPLE_EVIDENCE_INFO *TPCertGroup::buildCssmEvidenceInfo() { CSSM_TP_APPLE_EVIDENCE_INFO *infoArray; infoArray = (CSSM_TP_APPLE_EVIDENCE_INFO *)mAlloc.calloc(mNumCerts, sizeof(CSSM_TP_APPLE_EVIDENCE_INFO)); for(unsigned i=0; iisExpired()) { evInfo->StatusBits |= CSSM_CERT_STATUS_EXPIRED; } if(certInfo->isNotValidYet()) { evInfo->StatusBits |= CSSM_CERT_STATUS_NOT_VALID_YET; } if(certInfo->isAnchor()) { tpAnchorDebug("buildCssmEvidenceInfo: flagging IS_IN_ANCHORS"); evInfo->StatusBits |= CSSM_CERT_STATUS_IS_IN_ANCHORS; } if(certInfo->dlDbHandle().DLHandle == 0) { if(certInfo->isFromNet()) { evInfo->StatusBits |= CSSM_CERT_STATUS_IS_FROM_NET; } else if(certInfo->isFromInputCerts()) { evInfo->StatusBits |= CSSM_CERT_STATUS_IS_IN_INPUT_CERTS; } } /* If trust settings apply to a root, skip verifying the signature */ bool avoidVerify = false; switch(certInfo->trustSettingsResult()) { case kSecTrustSettingsResultTrustRoot: case kSecTrustSettingsResultTrustAsRoot: /* these two can be disambiguated by IS_ROOT */ evInfo->StatusBits |= CSSM_CERT_STATUS_TRUST_SETTINGS_TRUST; avoidVerify = true; break; case kSecTrustSettingsResultDeny: evInfo->StatusBits |= CSSM_CERT_STATUS_TRUST_SETTINGS_DENY; avoidVerify = true; break; case kSecTrustSettingsResultUnspecified: case kSecTrustSettingsResultInvalid: default: break; } if(certInfo->isSelfSigned(avoidVerify)) { evInfo->StatusBits |= CSSM_CERT_STATUS_IS_ROOT; } if(certInfo->ignoredError()) { evInfo->StatusBits |= CSSM_CERT_STATUS_TRUST_SETTINGS_IGNORED_ERROR; } unsigned numCodes = certInfo->numStatusCodes(); if(numCodes) { evInfo->NumStatusCodes = numCodes; evInfo->StatusCodes = (CSSM_RETURN *)mAlloc.calloc(numCodes, sizeof(CSSM_RETURN)); for(unsigned j=0; jStatusCodes[j] = (certInfo->statusCodes())[j]; } } if(evInfo->StatusBits & (CSSM_CERT_STATUS_TRUST_SETTINGS_TRUST | CSSM_CERT_STATUS_TRUST_SETTINGS_DENY | CSSM_CERT_STATUS_TRUST_SETTINGS_IGNORED_ERROR)) { /* Something noteworthy happened involving TrustSettings */ uint32 whichDomain = 0; switch(certInfo->trustSettingsDomain()) { case kSecTrustSettingsDomainUser: whichDomain = CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_USER; break; case kSecTrustSettingsDomainAdmin: whichDomain = CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_ADMIN; break; case kSecTrustSettingsDomainSystem: whichDomain = CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_SYSTEM; break; } evInfo->StatusBits |= whichDomain; } evInfo->Index = certInfo->index(); evInfo->DlDbHandle = certInfo->dlDbHandle(); evInfo->UniqueRecord = certInfo->uniqueRecord(); } return infoArray; } /* Given a status for basic construction of a cert group and a status * of (optional) policy verification, plus the implicit notBefore/notAfter * status in the certs, calculate a global return code. This just * encapsulates a policy for CertGroupConstruct and CertGroupVerify. */ CSSM_RETURN TPCertGroup::getReturnCode( CSSM_RETURN constructStatus, CSSM_RETURN policyStatus, CSSM_APPLE_TP_ACTION_FLAGS actionFlags) { if(constructStatus) { /* CSSMERR_TP_NOT_TRUSTED, CSSMERR_TP_INVALID_ANCHOR_CERT, gross errors */ return constructStatus; } bool expired = false; bool notValid = false; bool allowExpiredRoot = (actionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED_ROOT) ? true : false; bool allowExpired = (actionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED) ? true : false; bool requireRevPerCert = (actionFlags & CSSM_TP_ACTION_REQUIRE_REV_PER_CERT) ? true : false; /* check for expired, not valid yet */ for(unsigned i=0; iisExpired() && !(allowExpiredRoot && ci->isSelfSigned(true)) && // allowed globally ci->isStatusFatal(CSSMERR_TP_CERT_EXPIRED)) { // allowed for this cert expired = true; } if(ci->isNotValidYet() && ci->isStatusFatal(CSSMERR_TP_CERT_NOT_VALID_YET)) { notValid = true; } } if(expired && !allowExpired) { return CSSMERR_TP_CERT_EXPIRED; } if(notValid) { return CSSMERR_TP_CERT_NOT_VALID_YET; } /* Check for missing revocation check */ if(requireRevPerCert) { for(unsigned i=0; iisSelfSigned(true)) { /* revocation check meaningless for a root cert */ continue; } if(!ci->revokeCheckGood() && ci->isStatusFatal(CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK)) { return CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK; } } } return policyStatus; } /* set all TPCertINfo.mUsed flags false */ void TPCertGroup::setAllUnused() { for(unsigned dex=0; dexused(false); } } /* * See if the specified error status is allowed (return true) or * fatal (return false) per each cert's mAllowedErrs[]. Returns * true if any cert returns false for its isStatusFatal() call. * The list of errors which can apply to cert-chain-wide allowedErrors * is right here; if the incoming error is not in that list, we * return false. If the incoming error code is CSSM_OK we return * true as a convenience for our callers. */ bool TPCertGroup::isAllowedError( CSSM_RETURN code) { switch(code) { case CSSM_OK: return true; case CSSMERR_TP_NOT_TRUSTED: case CSSMERR_TP_INVALID_ANCHOR_CERT: case CSSMERR_TP_VERIFY_ACTION_FAILED: case CSSMERR_TP_INVALID_CERT_AUTHORITY: case CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH: case CSSMERR_APPLETP_RS_BAD_CERT_CHAIN_LENGTH: /* continue processing these candidates */ break; default: /* not a candidate for cert-chain-wide allowedErrors */ return false; } for(unsigned dex=0; dexisStatusFatal(code)) { tpTrustSettingsDbg("TPCertGroup::isAllowedError: allowing for cert %u", dex); return true; } } /* every cert thought this was fatal; it is. */ return false; } /* * Determine if we already have the specified cert in this group. */ bool TPCertGroup::isInGroup(TPCertInfo &certInfo) { for(unsigned dex=0; dexitemData())) { return true; } } return false; } /* * Search unused incoming certs to find an issuer of specified cert or CRL. * WARNING this assumes a valid "used" state for all certs in this group. * If partialIssuerKey is true on return, caller must re-verify signature * of subject later when sufficient info is available. */ TPCertInfo *TPCertGroup::findIssuerForCertOrCrl( const TPClItemInfo &subject, bool &partialIssuerKey) { partialIssuerKey = false; TPCertInfo *expiredIssuer = NULL; for(unsigned certDex=0; certDexused()) { continue; } /* subject/issuer names match? */ if(certInfo->isIssuerOf(subject)) { /* yep, do a sig verify */ tpVfyDebug("findIssuerForCertOrCrl issuer/subj match checking sig"); CSSM_RETURN crtn = subject.verifyWithIssuer(certInfo); switch(crtn) { case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: /* issuer OK, check sig later */ partialIssuerKey = true; /* and fall thru */ case CSSM_OK: /* * Temporal validity check: if we're not already holding an expired * issuer, and this one's invalid, hold it and keep going. */ if((crtn == CSSM_OK) && (expiredIssuer == NULL)) { if(certInfo->isExpired() || certInfo->isNotValidYet()) { tpDebug("findIssuerForCertOrCrl: holding expired cert %p", certInfo); expiredIssuer = certInfo; break; } } /* YES */ certInfo->used(true); return certInfo; default: /* just skip this one and keep looking */ tpVfyDebug("findIssuerForCertOrCrl issuer/subj match BAD SIG"); break; } } /* names match */ } if(expiredIssuer != NULL) { /* OK, we'll use this one */ tpDbDebug("findIssuerForCertOrCrl: using expired cert %p", expiredIssuer); expiredIssuer->used(true); return expiredIssuer; } /* not found */ return NULL; } /* * Construct ordered, verified cert chain from a variety of inputs. * Time validity does not affect the function return or any status, * we always try to find a valid cert to replace an expired or * not-yet-valid cert if we can. Final temporal validity of each * cert must be checked by caller (it's stored in each TPCertInfo * we add to ourself during construction). * * Only possible error returns are: * CSSMERR_TP_CERTIFICATE_CANT_OPERATE : issuer cert was found with a partial * public key, rendering full verification impossible. * CSSMERR_TP_INVALID_CERT_AUTHORITY : issuer cert was found with a partial * public key and which failed to perform subsequent signature * verification. * * Other interesting status is returned via the verifiedToRoot and * verifiedToAnchor flags. * * NOTE: is it the caller's responsibility to call setAllUnused() for both * incoming cert groups (inCertGroup and gatheredCerts). We don't do that * here because we may call ourself recursively. */ CSSM_RETURN TPCertGroup::buildCertGroup( const TPClItemInfo &subjectItem, // Cert or CRL TPCertGroup *inCertGroup, // optional const CSSM_DL_DB_LIST *dbList, // optional CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand, const char *verifyTime, // optional, for establishing // validity of new TPCertInfos /* trusted anchors, optional */ /* FIXME - maybe this should be a TPCertGroup */ uint32 numAnchorCerts, const CSSM_DATA *anchorCerts, /* * Certs to be freed by caller (i.e., TPCertInfo which we allocate * as a result of using a cert from anchorCerts or dbList) are added * to this group. */ TPCertGroup &certsToBeFreed, /* * Other certificates gathered during the course of this operation, * currently consisting of certs fetched from DBs and from the net. * This is not used when called by AppleTPSession::CertGroupConstructPriv; * it's an optimization for the case when we're building a cert group * for TPCrlInfo::verifyWithContext - we avoid re-fetching certs from * the net which are needed to verify both the subject cert and a CRL. * We don't modify this TPCertGroup, we only use certs from it. */ TPCertGroup *gatheredCerts, /* * Indicates that subjectItem is a cert in this cert group. * If true, that cert will be tested for "root-ness", including * -- subject/issuer compare * -- signature self-verify * -- anchor compare */ CSSM_BOOL subjectIsInGroup, /* * CSSM_TP_ACTION_FETCH_CERT_FROM_NET, * CSSM_TP_ACTION_TRUST_SETTING, * CSSM_TP_ACTION_IMPLICIT_ANCHORS are interesting */ CSSM_APPLE_TP_ACTION_FLAGS actionFlags, /* CSSM_TP_ACTION_TRUST_SETTING parameters */ const CSSM_OID *policyOid, const char *policyStr, uint32 policyStrLen, SecTrustSettingsKeyUsage leafKeyUse, // usage of *first* cert in chain /* returned */ CSSM_BOOL &verifiedToRoot, // end of chain self-verifies CSSM_BOOL &verifiedToAnchor, // end of chain in anchors CSSM_BOOL &verifiedViaTrustSettings) // chain ends per User Trust setting { const TPClItemInfo *thisSubject = &subjectItem; CSSM_RETURN crtn = CSSM_OK; TPCertInfo *issuerCert = NULL; unsigned certDex; TPCertInfo *anchorInfo = NULL; bool foundPartialIssuer = false; CSSM_BOOL firstSubjectIsInGroup = subjectIsInGroup; TPCertInfo *endCert; tpVfyDebug("buildCertGroup top"); /* possible expired root which we'll only use if we can't find * a better one */ TPCertInfo *expiredRoot = NULL; /* and the general case of an expired or not yet valid cert */ TPCertInfo *expiredIssuer = NULL; verifiedToRoot = CSSM_FALSE; verifiedToAnchor = CSSM_FALSE; verifiedViaTrustSettings = CSSM_FALSE; /*** main loop to seach inCertGroup and dbList *** * * Exit loop on: * -- find a root cert in the chain * -- find a cert which is trusted per Trust Settings (if enabled) * -- memory error * -- or no more certs to add to chain. */ for(;;) { /* * Top of loop: thisSubject is the item we're trying to verify. */ /* is thisSubject a root cert or listed in user trust list? */ if(subjectIsInGroup) { TPCertInfo *subjCert = lastCert(); assert(subjCert != NULL); if(actionFlags & CSSM_TP_ACTION_TRUST_SETTINGS) { assert(policyOid != NULL); /* * Figure out key usage. If this is a leaf cert, the caller - actually * the per-policy code - inferred the usage. Else it could be for * verifying a cert or a CRL. * * We want to avoid multiple calls to the effective portion of * evaluateTrustSettings(), but a CA cert could be usable for only * signing certs and not CRLs. Thus we're evaluating a CA cert, * try to evaluate for signing certs *and* CRLs in case we come * this way again later when performing CRL verification. If that * fails, then retry with just cert signing. */ SecTrustSettingsKeyUsage localKeyUse; bool doRetry = false; if(subjCert == firstCert()) { /* leaf - use caller's spec */ localKeyUse = leafKeyUse; /* FIXME - add in CRL if this is cert checking? */ } else { localKeyUse = kSecTrustSettingsKeyUseSignCert | kSecTrustSettingsKeyUseSignRevocation; /* and if necessary */ doRetry = true; } /* this lets us avoid searching for the same thing twice when there * is in fact no entry for it */ bool foundEntry = false; bool trustSettingsFound = false; OSStatus ortn = subjCert->evaluateTrustSettings(*policyOid, policyStr, policyStrLen, localKeyUse, &trustSettingsFound, &foundEntry); if(ortn) { /* this is only a dire error */ crtn = ortn; goto final_out; } if(!trustSettingsFound && foundEntry && doRetry) { tpTrustSettingsDbg("buildCertGroup: retrying evaluateTrustSettings with Cert only"); ortn = subjCert->evaluateTrustSettings(*policyOid, policyStr, policyStrLen, kSecTrustSettingsKeyUseSignCert, &trustSettingsFound, &foundEntry); if(ortn) { crtn = ortn; goto final_out; } } if(trustSettingsFound) { switch(subjCert->trustSettingsResult()) { case kSecTrustSettingsResultInvalid: /* should not happen... */ assert(0); crtn = CSSMERR_TP_INTERNAL_ERROR; break; case kSecTrustSettingsResultTrustRoot: case kSecTrustSettingsResultTrustAsRoot: tpTrustSettingsDbg("Trust[As]Root found"); crtn = CSSM_OK; break; case kSecTrustSettingsResultDeny: tpTrustSettingsDbg("TrustResultDeny found"); crtn = CSSMERR_APPLETP_TRUST_SETTING_DENY; break; case kSecTrustSettingsResultUnspecified: /* special case here: this means "keep going, we don't trust or * distrust this cert". Typically used to express allowed errors * only. */ tpTrustSettingsDbg("TrustResultUnspecified found"); goto post_trust_setting; default: tpTrustSettingsDbg("Unknown TrustResult (%d)", (int)subjCert->trustSettingsResult()); crtn = CSSMERR_TP_INTERNAL_ERROR; break; } /* cleanup partial key processing */ verifiedViaTrustSettings = CSSM_TRUE; goto final_out; } } /* CSSM_TP_ACTION_TRUST_SETTING */ post_trust_setting: if(subjCert->isSelfSigned()) { /* We're at the end of the chain. */ verifiedToRoot = CSSM_TRUE; /* * Special case if this root is expired (and it's not the * leaf): remove it from the outgoing cert group, save it, * and proceed, looking another (good) root in anchors. * There's no way we'll find another good one in this loop. */ if(subjCert->isExpired() && (!firstSubjectIsInGroup || (mNumCerts > 1))) { tpDebug("buildCertGroup: EXPIRED ROOT %p, looking for good one", subjCert); expiredRoot = subjCert; if(mNumCerts) { /* roll back to previous cert */ mNumCerts--; } if(mNumCerts == 0) { /* roll back to caller's initial condition */ thisSubject = &subjectItem; } else { thisSubject = lastCert(); } } break; /* out of main loop */ } /* root */ } /* subjectIsInGroup */ /* * Search unused incoming certs to find an issuer. * Both cert groups are optional. * We'll add issuer to outCertGroup below. * If we find a cert that's expired or not yet valid, we hold on to it * and look for a better one. If we don't find it here we drop back to the * expired one at the end of the loop. If that expired cert is a root * cert, we'll use the expiredRoot mechanism (see above) to roll back and * see if we can find a good root in the incoming anchors. */ if(inCertGroup != NULL) { bool partial = false; issuerCert = inCertGroup->findIssuerForCertOrCrl(*thisSubject, partial); if(issuerCert) { issuerCert->isFromInputCerts(true); if(partial) { /* deal with this later */ foundPartialIssuer = true; tpDebug("buildCertGroup: PARTIAL Cert FOUND in inCertGroup"); } else { tpDebug("buildCertGroup: Cert FOUND in inCertGroup"); } } } if(issuerCert != NULL) { if(issuerCert->isExpired() || issuerCert->isNotValidYet()) { if(expiredIssuer == NULL) { tpDebug("buildCertGroup: saving expired cert %p (1)", issuerCert); expiredIssuer = issuerCert; issuerCert = NULL; } /* else we already have an expired issuer candidate */ } else { /* unconditionally done with possible expiredIssuer */ #ifndef NDEBUG if(expiredIssuer != NULL) { tpDebug("buildCertGroup: DISCARDING expired cert %p (1)", expiredIssuer); } #endif expiredIssuer = NULL; } } if((issuerCert == NULL) && (gatheredCerts != NULL)) { bool partial = false; issuerCert = gatheredCerts->findIssuerForCertOrCrl(*thisSubject, partial); if(issuerCert) { if(partial) { /* deal with this later */ foundPartialIssuer = true; tpDebug("buildCertGroup: PARTIAL Cert FOUND in gatheredCerts"); } else { tpDebug("buildCertGroup: Cert FOUND in gatheredCerts"); } } } if(issuerCert != NULL) { if(issuerCert->isExpired() || issuerCert->isNotValidYet()) { if(expiredIssuer == NULL) { tpDebug("buildCertGroup: saving expired cert %p (2)", issuerCert); expiredIssuer = issuerCert; issuerCert = NULL; } /* else we already have an expired issuer candidate */ } else { /* unconditionally done with possible expiredIssuer */ #ifndef NDEBUG if(expiredIssuer != NULL) { tpDebug("buildCertGroup: DISCARDING expired cert %p (2)", expiredIssuer); } #endif expiredIssuer = NULL; } } if((issuerCert == NULL) && (dbList != NULL)) { /* Issuer not in incoming cert group or gathered certs. Search DBList. */ bool partial = false; try { issuerCert = tpDbFindIssuerCert(mAlloc, clHand, cspHand, thisSubject, dbList, verifyTime, partial); } catch (...) {} if(issuerCert) { /* unconditionally done with possible expiredIssuer */ #ifndef NDEBUG if(expiredIssuer != NULL) { tpDebug("buildCertGroup: DISCARDING expired cert %p (3)", expiredIssuer); } #endif expiredIssuer = NULL; /* * Handle Radar 4566041, endless loop of cross-signed certs. * This can only happen when fetching certs from a DLDB or * from the net; we prevent that from happening when the certs * are in inCertGroup or gatheredCerts by keeping track of those * certs' mUsed state. */ if(isInGroup(*issuerCert)) { tpDebug("buildCertGroup: Multiple instances of cert"); delete issuerCert; issuerCert = NULL; } else { /* caller must free */ certsToBeFreed.appendCert(issuerCert); if(partial) { /* deal with this later */ foundPartialIssuer = true; tpDebug("buildCertGroup: PARTIAL Cert FOUND in dbList"); } else { tpDebug("buildCertGroup: Cert FOUND in dbList"); } } } } /* searching DLDB list */ /* * Note: we don't handle an expired cert returned from tpDbFindIssuerCert() * in any special way like we do with findIssuerForCertOrCrl(). * tpDbFindIssuerCert() does its best to give us a temporally valid cert; if * it returns an expired cert (or, if findIssuerForCertOrCrl() gave us an * expired cert and tpDbFindIssuerCert() could not do any better), that's all * we have to work with at this point. We'll go back to the top of the loop * and apply trust settings if enabled; if an expired cert is trusted per * Trust Settings, we're done. (Note that anchors are fetched from a DLDB * when Trust Settings are enabled, so even if two roots with the same key * and subject name are in DLDBs, and one of them is expired, we'll have the * good one at this time because of tpDbFindIssuerCert()'s ability to find * the best cert.) * * If Trust Settings are not enabled, and we have an expired root at this * point, the expiredRoot mechanism is used to roll back and search for * an anchor that verifies the last good cert. */ if((issuerCert == NULL) && /* tpDbFindIssuerCert() hasn't found one and * we don't have a good one */ (expiredIssuer != NULL)) { /* but we have an expired candidate */ /* * OK, we'll take the expired issuer. * Note we don't have to free expiredIssuer if we found a good one since * expiredIssuer can only come from inCertGroup or gatheredCerts (not from * dbList). */ tpDebug("buildCertGroup: USING expired cert %p", expiredIssuer); issuerCert = expiredIssuer; expiredIssuer = NULL; } if(issuerCert == NULL) { /* end of search, broken chain */ break; } /* * One way or the other, we've found a cert which verifies subjectCert. * Add the issuer to outCertGroup and make it the new thisSubject for * the next pass. */ appendCert(issuerCert); thisSubject = issuerCert; subjectIsInGroup = CSSM_TRUE; issuerCert = NULL; } /* main loop */ /* * This can be NULL if we're evaluating a CRL (and we haven't * gotten very far). */ endCert = lastCert(); /* * This, on the other hand, is always valid. It could be a CRL. */ assert(thisSubject != NULL); if( (actionFlags & CSSM_TP_ACTION_IMPLICIT_ANCHORS) && ( (endCert && endCert->isSelfSigned()) || expiredRoot) ) { /* * Caller will be satisfied with this; skip further anchor processing. */ tpAnchorDebug("buildCertGroup: found IMPLICIT anchor"); goto post_anchor; } if(numAnchorCerts == 0) { /* we're probably done */ goto post_anchor; } assert(anchorCerts != NULL); /*** anchor cert handling ***/ /* * Case 1: If thisSubject is not a root cert, try to validate with incoming anchor certs. */ expiredIssuer = NULL; if(!(endCert && endCert->isSelfSigned())) { for(certDex=0; certDexisIssuerOf(*thisSubject)) { /* not this anchor */ tpAnchorDebug("buildCertGroup anchor not issuer"); delete anchorInfo; anchorInfo = NULL; continue; } crtn = thisSubject->verifyWithIssuer(anchorInfo); if(crtn == CSSM_OK) { if(anchorInfo->isExpired() || anchorInfo->isNotValidYet()) { if(expiredIssuer == NULL) { /* * Hang on to this one; keep looking for a better one. */ tpDebug("buildCertGroup: saving expired anchor %p", anchorInfo); expiredIssuer = anchorInfo; /* flag this condition for the switch below */ crtn = CSSM_CERT_STATUS_EXPIRED; expiredIssuer->isAnchor(true); assert(!anchorInfo->isFromInputCerts()); expiredIssuer->index(certDex); certsToBeFreed.appendCert(expiredIssuer); } /* else we already have an expired candidate anchor */ } else { /* * Done with possible expiredIssuer. We don't delete it, since we already added * it to certsToBeFreed, above. */ if(expiredIssuer != NULL) { tpDebug("buildCertGroup: DISCARDING expired anchor %p", expiredIssuer); expiredIssuer = NULL; } } } switch(crtn) { case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: /* * A bit of a corner case. Found an issuer in AnchorCerts, but * we can't do a signature verify since the issuer has a partial * public key. Proceed but return * CSSMERR_TP_CERTIFICATE_CANT_OPERATE. */ if(anchorInfo->addStatusCode(CSSMERR_TP_CERTIFICATE_CANT_OPERATE)) { foundPartialIssuer = true; crtn = CSSMERR_TP_CERTIFICATE_CANT_OPERATE; } else { /* ignore */ crtn = CSSM_OK; } /* drop thru */ case CSSM_OK: /* A fully successful return. */ verifiedToAnchor = CSSM_TRUE; if(anchorInfo->isSelfSigned()) { verifiedToRoot = CSSM_TRUE; } /* * Add this anchor cert to the output group * and to certsToBeFreed. */ appendCert(anchorInfo); anchorInfo->isAnchor(true); assert(!anchorInfo->isFromInputCerts()); anchorInfo->index(certDex); certsToBeFreed.appendCert(anchorInfo); tpDebug("buildCertGroup: Cert FOUND by signer in AnchorList"); tpAnchorDebug("buildCertGroup: Cert FOUND by signer in AnchorList"); /* one more thing: partial public key processing needed? */ if(foundPartialIssuer) { return verifyWithPartialKeys(subjectItem); } else { return crtn; } default: /* continue to next anchor */ if(crtn != CSSM_CERT_STATUS_EXPIRED) { /* Expired means we're saving it in expiredIssuer */ tpVfyDebug("buildCertGroup found issuer in anchor, BAD SIG"); delete anchorInfo; } anchorInfo = NULL; break; } } /* for each anchor */ } /* thisSubject not a root cert */ /* * Case 2: last cert in output is a root cert. See if the root cert is in * AnchorCerts. * * Also used to validate an expiredRoot that we pulled off the chain in * hopes of finding something better (which, if we're here, we haven't done). * * Note that the main loop above did the actual root self-verify test. */ if((endCert && endCert->isSelfSigned()) || expiredRoot) { TPCertInfo *theRoot; if(expiredRoot) { /* this is NOT in our outgoing cert group (yet) */ theRoot = expiredRoot; } else { theRoot = endCert; } /* see if that root cert is identical to one of the anchor certs */ for(certDex=0; certDexitemData(), &anchorCerts[certDex])) { /* one fully successful return */ tpAnchorDebug("buildCertGroup: root in input AND anchors"); verifiedToAnchor = CSSM_TRUE; theRoot->isAnchor(true); if(!theRoot->isFromInputCerts()) { /* Don't override index into input certs */ theRoot->index(certDex); } if(expiredRoot) { /* verified to anchor but caller will see * CSSMERR_TP_CERT_EXPIRED */ appendCert(expiredRoot); } /* one more thing: partial public key processing needed? */ if(foundPartialIssuer) { return verifyWithPartialKeys(subjectItem); } else { return CSSM_OK; } } } tpAnchorDebug("buildCertGroup: root in input, NOT anchors"); if(!expiredRoot) { /* verified to a root cert which is not an anchor */ /* Generally maps to CSSMERR_TP_INVALID_ANCHOR_CERT by caller */ /* one more thing: partial public key processing needed? */ if(foundPartialIssuer) { return verifyWithPartialKeys(subjectItem); } else { return CSSM_OK; } } /* else try finding a good anchor */ } /* regardless of anchor search status... */ crtn = CSSM_OK; if(!verifiedToAnchor && (expiredIssuer != NULL)) { /* expiredIssuer here is always an anchor */ tpDebug("buildCertGroup: accepting expired anchor %p", expiredIssuer); appendCert(expiredIssuer); verifiedToAnchor = CSSM_TRUE; if(expiredIssuer->isSelfSigned()) { verifiedToRoot = CSSM_TRUE; } /* no matter what, we don't want this one */ expiredRoot = NULL; } post_anchor: if(expiredRoot) { /* * One remaining special case: expiredRoot found in input certs, but * no luck resolving the problem with the anchors. Go ahead and (re-)append * the expired root and return. */ tpDebug("buildCertGroup: accepting EXPIRED root"); appendCert(expiredRoot); if(foundPartialIssuer) { return verifyWithPartialKeys(subjectItem); } else { return CSSM_OK; } } /* If we get here, enable fetching issuer from network: */ /* ... unless DB list is empty *and* anchors are empty: */ if(!( (!dbList || (dbList->NumHandles == 0)) && (!anchorCerts || (numAnchorCerts == 0))) ) { actionFlags |= CSSM_TP_ACTION_FETCH_CERT_FROM_NET; } /* * If we haven't verified to a root, and net fetch of certs is enabled, * try to get the issuer of the last cert in the chain from the net. * If that succeeds, then call ourself recursively to perform the * whole search again (including comparing to or verifying against * anchor certs). */ if(!verifiedToRoot && !verifiedToAnchor && (endCert != NULL) && (actionFlags & CSSM_TP_ACTION_FETCH_CERT_FROM_NET)) { TPCertInfo *issuer = NULL; CSSM_RETURN cr = tpFetchIssuerFromNet(*endCert, clHand, cspHand, verifyTime, issuer); switch(cr) { case CSSMERR_TP_CERTGROUP_INCOMPLETE: /* no issuerAltName, no reason to log this */ break; default: /* gross error */ endCert->addStatusCode(cr); break; case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: /* use this one but re-verify later */ foundPartialIssuer = true; /* and drop thru */ case CSSM_OK: if (!issuer) break; tpDebug("buildCertGroup: Cert FOUND from Net; recursing"); if(isInGroup(*issuer)) { tpDebug("buildCertGroup: Multiple instances of cert from net"); delete issuer; issuer = NULL; crtn = CSSMERR_TP_CERTGROUP_INCOMPLETE; break; } /* add this fetched cert to constructed group */ appendCert(issuer); issuer->isFromNet(true); certsToBeFreed.appendCert(issuer); /* and go again */ cr = buildCertGroup(*issuer, inCertGroup, dbList, clHand, cspHand, verifyTime, numAnchorCerts, anchorCerts, certsToBeFreed, gatheredCerts, CSSM_TRUE, // subjectIsInGroup actionFlags, policyOid, policyStr, policyStrLen, leafKeyUse, // actually don't care since the leaf will not // be evaluated verifiedToRoot, verifiedToAnchor, verifiedViaTrustSettings); if(cr) { return cr; } /* one more thing: partial public key processing needed? */ if(foundPartialIssuer) { return verifyWithPartialKeys(subjectItem); } else { return CSSM_OK; } } } final_out: /* regardless of outcome, check for partial keys to log per-cert status */ CSSM_RETURN partRtn = CSSM_OK; if(foundPartialIssuer) { partRtn = verifyWithPartialKeys(subjectItem); } if(crtn) { return crtn; } else { return partRtn; } } /* * Called from buildCertGroup as final processing of a constructed * group when CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE has been * detected. Perform partial public key processing. * * We don't have to verify every element, just the ones whose * issuers have partial public keys. * * Returns: * CSSMERR_TP_CERTIFICATE_CANT_OPERATE in the case of an issuer cert * with a partial public key which can't be completed. * CSSMERR_TP_INVALID_CERT_AUTHORITY if sig verify failed with * a (supposedly) completed partial key */ CSSM_RETURN TPCertGroup::verifyWithPartialKeys( const TPClItemInfo &subjectItem) // Cert or CRL { TPCertInfo *lastFullKeyCert = NULL; tpDebug("verifyWithPartialKeys top"); /* start from the end - it's easier */ for(int dex=mNumCerts-1; dex >= 0; dex--) { TPCertInfo *thisCert = mCertInfo[dex]; /* * If this is the start of the cert chain, and it's not being * used to verify subjectItem, then we're done. */ if(dex == 0) { if((void *)thisCert == (void *)&subjectItem) { tpDebug("verifyWithPartialKeys: success at leaf cert"); return CSSM_OK; } } if(!thisCert->hasPartialKey()) { /* * Good to know. Record this and move on. */ lastFullKeyCert = thisCert; tpDebug("full key cert found at index %d", dex); continue; } if(lastFullKeyCert == NULL) { /* * No full keys between here and the end! */ tpDebug("UNCOMPLETABLE cert at index %d", dex); if(thisCert->addStatusCode(CSSMERR_TP_CERTIFICATE_CANT_OPERATE)) { return CSSMERR_TP_CERTIFICATE_CANT_OPERATE; } else { break; } } /* do the verify - of next cert in chain or of subjectItem */ const TPClItemInfo *subject; if(dex == 0) { subject = &subjectItem; tpDebug("...verifying subject item with partial cert 0"); } else { subject = mCertInfo[dex - 1]; tpDebug("...verifying with partial cert %d", dex); } CSSM_RETURN crtn = subject->verifyWithIssuer(thisCert, lastFullKeyCert); if(crtn) { tpDebug("CERT VERIFY ERROR with partial cert at index %d", dex); if(thisCert->addStatusCode(CSSMERR_TP_CERTIFICATE_CANT_OPERATE)) { return CSSMERR_TP_INVALID_CERT_AUTHORITY; } else { break; } } } /* we just verified subjectItem - right? */ assert((void *)mCertInfo[0] != (void *)&subjectItem); tpDebug("verifyWithPartialKeys: success at subjectItem"); return CSSM_OK; } /* * Free records obtained from DBs. Called when these records are not going to * be passed to caller of CertGroupConstruct or CertGroupVerify. */ void TPCertGroup::freeDbRecords() { for(unsigned dex=0; dexfreeUniqueRecord(); } }