/* * 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. */ /* * TPCrlInfo.h - TP's private CRL and CRL group * * Written 9/30/2002 by Doug Mitchell. */ #include "TPCrlInfo.h" #include "tpdebugging.h" #include "certGroupUtils.h" #include "tpCrlVerify.h" #include "tpPolicies.h" #include #include #include #include #include /* for memcmp */ #include /* * Replacement for CSSM_CL_CrlGetFirstCachedFieldValue for use with * TPCrlItemInfo's generic getFirstCachedField mechanism. */ static CSSM_RETURN tpGetFirstCachedFieldValue (CSSM_CL_HANDLE CLHandle, CSSM_HANDLE CrlHandle, const CSSM_OID *CrlField, CSSM_HANDLE_PTR ResultsHandle, uint32 *NumberOfMatchedFields, CSSM_DATA_PTR *Value) { return CSSM_CL_CrlGetFirstCachedFieldValue(CLHandle, CrlHandle, NULL, // const CSSM_DATA *CrlRecordIndex, CrlField, ResultsHandle, NumberOfMatchedFields, Value); } static const TPClItemCalls tpCrlClCalls = { tpGetFirstCachedFieldValue, CSSM_CL_CrlAbortQuery, CSSM_CL_CrlCache, CSSM_CL_CrlAbortCache, CSSM_CL_CrlVerify, &CSSMOID_X509V1CRLThisUpdate, &CSSMOID_X509V1CRLNextUpdate, CSSMERR_TP_INVALID_CRL_POINTER, CSSMERR_APPLETP_CRL_EXPIRED, CSSMERR_APPLETP_CRL_NOT_VALID_YET }; /* * No default constructor - this is the only way. * This caches the cert and fetches subjectName and issuerName * to ensure the incoming certData is well-constructed. */ TPCrlInfo::TPCrlInfo( CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand, const CSSM_DATA *crlData, TPItemCopy copyCrlData, // true: we copy, we free // false - caller owns const char *verifyTime) // = NULL : TPClItemInfo(clHand, cspHand, tpCrlClCalls, crlData, copyCrlData, verifyTime), mRefCount(0), mToBeDeleted(false), mFromWhere(CFW_Nowhere), mX509Crl(NULL), mCrlFieldToFree(NULL), mVerifyState(CVS_Unknown), mVerifyError(CSSMERR_TP_INTERNAL_ERROR) { CSSM_RETURN crtn; mUri.Data = NULL; mUri.Length = 0; /* fetch parsed CRL */ crtn = fetchField(&CSSMOID_X509V2CRLSignedCrlCStruct, &mCrlFieldToFree); if(crtn) { /* bad CRL */ releaseResources(); CssmError::throwMe(crtn); } if(mCrlFieldToFree->Length != sizeof(CSSM_X509_SIGNED_CRL)) { tpErrorLog("fetchField(SignedCrlCStruct) length error\n"); releaseResources(); CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } mX509Crl = (CSSM_X509_SIGNED_CRL *)mCrlFieldToFree->Data; /* any other other commonly used fields? */ } TPCrlInfo::~TPCrlInfo() { releaseResources(); } void TPCrlInfo::releaseResources() { if(mCrlFieldToFree) { freeField(&CSSMOID_X509V2CRLSignedCrlCStruct, mCrlFieldToFree); mCrlFieldToFree = NULL; } if(mUri.Data) { CssmAllocator::standard().free(mUri.Data); mUri.Data = NULL; mUri.Length = 0; } TPClItemInfo::releaseResources(); } void TPCrlInfo::uri(const CSSM_DATA &uri) { tpCopyCssmData(CssmAllocator::standard(), &uri, &mUri); } /* * List of extensions we understand and can accept as critical. */ static const CSSM_OID *const TPGoodCrlExtens[] = { &CSSMOID_CrlNumber, /* Note NOT CSSMOID_DeltaCrlIndicator! That's fatal */ &CSSMOID_CrlReason, &CSSMOID_CertIssuer, &CSSMOID_IssuingDistributionPoint, &CSSMOID_HoldInstructionCode, &CSSMOID_InvalidityDate, &CSSMOID_AuthorityKeyIdentifier, &CSSMOID_SubjectAltName, &CSSMOID_IssuerAltName }; #define NUM_KNOWN_EXTENS (sizeof(TPGoodCrlExtens) / sizeof(CSSM_OID_PTR)) /* * Do our best to understand all the entries in a CSSM_X509_EXTENSIONS, * which may be per-CRL or per-entry. * * For now, we just ensure that for every critical extension, * we actually understand it and can deal it. */ CSSM_RETURN TPCrlInfo::parseExtensions( TPCrlVerifyContext &vfyCtx, bool isPerEntry, uint32 entryIndex, // if isPerEntry const CSSM_X509_EXTENSIONS &extens, TPCertInfo *forCert, // optional bool &isIndirectCrl) // RETURNED { isIndirectCrl = false; for(uint32 dex=0; dexcritical) { /* critical: is it in our list of understood extensions? */ unsigned i; for(i=0; iextnId, TPGoodCrlExtens[i])) { /* we're cool with this one */ break; } } if(i == NUM_KNOWN_EXTENS) { tpCrlDebug("parseExtensions: Unknown Critical Extension\n"); return CSSMERR_APPLETP_UNKNOWN_CRL_EXTEN; } } /* Specific extension handling. */ if(tpCompareOids(&exten->extnId, &CSSMOID_IssuingDistributionPoint)) { /* * If this assertion fails, we're out of sync with the CL */ assert(exten->format == CSSM_X509_DATAFORMAT_PAIR); CE_IssuingDistributionPoint *idp = (CE_IssuingDistributionPoint *) exten->value.parsedValue; /* * Snag indirectCrl flag for caller in any case */ if(idp->indirectCrlPresent && idp->indirectCrl) { isIndirectCrl = true; } if(forCert != NULL) { /* If no target cert, i.e., we're just verifying a CRL, * skip the remaining IDP checks. */ /* verify onlyCACerts/onlyUserCerts */ bool isUserCert; if(forCert->isLeaf() && !(vfyCtx.actionFlags && CSSM_TP_ACTION_LEAF_IS_CA)) { isUserCert = true; } else { isUserCert = false; } if((idp->onlyUserCertsPresent) && (idp->onlyUserCerts)) { if(!isUserCert) { tpCrlDebug("parseExtensions: onlyUserCerts, " "!leaf\n"); return CSSMERR_APPLETP_IDP_FAIL; } } if((idp->onlyCACertsPresent) && (idp->onlyCACerts)) { if(isUserCert) { tpCrlDebug("parseExtensions: onlyCACerts, leaf\n"); return CSSMERR_APPLETP_IDP_FAIL; } } } /* IDP */ } /* have target cert */ } return CSSM_OK; } /* * The heavyweight "perform full verification of this CRL" op. * Must verify to an anchor cert in tpVerifyContext. * Intermediate certs can come from signerCerts or dBList. */ CSSM_RETURN TPCrlInfo::verifyWithContext( TPCrlVerifyContext &tpVerifyContext, TPCertInfo *forCert, // optional bool doCrlVerify) { /* * Step 1: this CRL must be current. Caller might have re-evaluated * expired/notValidYet since our construction via calculateCurrent(). */ if(isExpired()) { return CSSMERR_APPLETP_CRL_EXPIRED; } if(isNotValidYet()) { return CSSMERR_APPLETP_CRL_NOT_VALID_YET; } /* subsequent verify state is cached */ switch(mVerifyState) { case CVS_Good: return CSSM_OK; case CVS_Bad: return mVerifyError; case CVS_Unknown: break; default: tpErrorLog("verifyWithContext: bad verifyState\n"); return CSSMERR_TP_INTERNAL_ERROR; } /* * Step 2: parse & understand all critical CRL extensions. */ CSSM_RETURN crtn; bool isIndirectCrl; crtn = parseExtensions(tpVerifyContext, false, 0, mX509Crl->tbsCertList.extensions, forCert, isIndirectCrl); if(crtn) { mVerifyState = CVS_Bad; if(forCert) { forCert->addStatusCode(crtn); } return crtn; } CSSM_X509_REVOKED_CERT_LIST_PTR revoked = mX509Crl->tbsCertList.revokedCertificates; if(revoked != NULL) { for(uint32 dex=0; dexnumberOfRevokedCertEntries; dex++) { bool dummyIsIndirect; // can't be set here crtn = parseExtensions(tpVerifyContext, true, dex, revoked->revokedCertEntry[dex].extensions, forCert, dummyIsIndirect); if(crtn) { mVerifyState = CVS_Bad; if(forCert) { forCert->addStatusCode(crtn); } return crtn; } } } /* * Step 3: obtain a fully verified cert chain which verifies this CRL. */ CSSM_BOOL verifiedToRoot; CSSM_BOOL verifiedToAnchor; TPCertGroup outCertGroup(tpVerifyContext.alloc, TGO_Caller); // CRLs owned by inCertGroup /* set up for disposal of TPCertInfos created by * CertGroupConstructPriv */ TPCertGroup certsToBeFreed(tpVerifyContext.alloc, TGO_Group); if(tpVerifyContext.signerCerts) { /* start from scratch with this group */ tpVerifyContext.signerCerts->setAllUnused(); } crtn = outCertGroup.buildCertGroup( *this, // subject item tpVerifyContext.signerCerts, // inCertGroup, optional tpVerifyContext.dbList, // optional tpVerifyContext.clHand, tpVerifyContext.cspHand, tpVerifyContext.verifyTime, tpVerifyContext.numAnchorCerts, tpVerifyContext.anchorCerts, certsToBeFreed, tpVerifyContext.gatheredCerts, CSSM_FALSE, // subjectIsInGroup tpVerifyContext.actionFlags, verifiedToRoot, verifiedToAnchor); if(crtn) { tpCrlDebug("TPCrlInfo::verifyWithContext buildCertGroup failure " "index %u", index()); if(forCert) { forCert->addStatusCode(crtn); } return crtn; } if(!verifiedToAnchor) { /* required */ mVerifyState = CVS_Bad; if(verifiedToRoot) { /* verified to root which is not an anchor */ tpCrlDebug("TPCrlInfo::verifyWithContext root, no anchor, " "index %u", index()); crtn = CSSMERR_APPLETP_CRL_INVALID_ANCHOR_CERT; } else { /* partial chain, no root, not verifiable by anchor */ tpCrlDebug("TPCrlInfo::verifyWithContext no root, no anchor, " "index %u", index()); crtn = CSSMERR_APPLETP_CRL_NOT_TRUSTED; } if(forCert) { forCert->addStatusCode(crtn); } return crtn; } /* * Step 4: policy verification on the returned cert group * We need to (temporarily) assert the "leaf cert is a CA" flag * here. */ outCertGroup.certAtIndex(0)->isLeaf(true); crtn = tp_policyVerify(kCrlPolicy, tpVerifyContext.alloc, tpVerifyContext.clHand, tpVerifyContext.cspHand, &outCertGroup, verifiedToRoot, tpVerifyContext.actionFlags | CSSM_TP_ACTION_LEAF_IS_CA, NULL, // sslOpts NULL); // policyOpts, not currently used if(crtn) { tpCrlDebug(" ...verifyWithContext policy FAILURE CRL %u", index()); if(forCert) { forCert->addStatusCode(CSSMERR_APPLETP_CRL_POLICY_FAIL); } mVerifyState = CVS_Bad; return crtn; } /* * Step 5: recursively perform CRL verification on the certs * gathered to verify this CRL. * Only performed if this CRL is an indirect CRL or the caller * explicitly told us to do this (i.e., caller is verifying a * CRL, not a cert chain). */ if(isIndirectCrl || doCrlVerify) { tpCrlDebug("verifyWithContext recursing to " "tpVerifyCertGroupWithCrls"); crtn = tpVerifyCertGroupWithCrls(outCertGroup, tpVerifyContext); if(crtn) { tpCrlDebug(" ...verifyWithContext CRL reverify FAILURE CRL %u", index()); if(forCert) { forCert->addStatusCode(crtn); } mVerifyState = CVS_Bad; return crtn; } } tpCrlDebug(" ...verifyWithContext CRL %u SUCCESS", index()); mVerifyState = CVS_Good; return crtn; } /* * Do I have the same issuer as the specified subject cert? Returns * true if so. */ bool TPCrlInfo::hasSameIssuer( const TPCertInfo &subject) { assert(subject.issuerName() != NULL); if(tpCompareCssmData(issuerName(), subject.issuerName())) { return true; } else { return false; } } /* * Determine if specified cert has been revoked. Assumes that * the current CRL has been fully verified. * * Assumes current CRL is verified good and that issuer names of * the cert and CRL match. * * This duplicates similar logic in the CL, but to avoid re-parsing * the subject cert (which we have parsed and cached), we just do it * here. * * Possible errors are * CSSMERR_TP_CERT_REVOKED * CSSMERR_TP_CERT_SUSPENDED * TBD * * Error status is added to subjectCert. */ CSSM_RETURN TPCrlInfo::isCertRevoked( TPCertInfo &subjectCert) { assert(mVerifyState == CVS_Good); CSSM_X509_TBS_CERTLIST_PTR tbs = &mX509Crl->tbsCertList; /* trivial case - empty CRL */ if((tbs->revokedCertificates == NULL) || (tbs->revokedCertificates->numberOfRevokedCertEntries == 0)) { tpCrlDebug(" isCertRevoked: empty CRL at index %u", index()); return CSSM_OK; } /* is subject cert's serial number in this CRL? */ CSSM_DATA_PTR subjSerial = NULL; CSSM_RETURN crtn; crtn = subjectCert.fetchField(&CSSMOID_X509V1SerialNumber, &subjSerial); if(crtn) { /* should never happen */ tpErrorLog("TPCrlInfo:isCertRevoked: error fetching serial number\n"); subjectCert.addStatusCode(crtn); return crtn; } /* subsequent errors to errOut: */ uint32 numEntries = tbs->revokedCertificates->numberOfRevokedCertEntries; CSSM_X509_REVOKED_CERT_ENTRY_PTR entries = tbs->revokedCertificates->revokedCertEntry; crtn = CSSM_OK; for(uint32 dex=0; dexcertificateSerialNumber)) { /* * It's in there. * FIXME: we're assuming that we don't have to compare * the "current verification time" (the verifyTime argument * to both our and the TPCertInfo's constructor) to this * entry's revocationDate. That would imply that a CRL could * contain a future revocation, and I don't think that * X509//RFC2459 intends this. */ /* * REQUIRED TBD: parse the entry's extensions, specifically to * get a reason. This will entail a bunch of new TP/cert specific * CSSM_RETURNS. * For now, just flag it revoked. */ crtn = CSSMERR_TP_CERT_REVOKED; tpCrlDebug(" isCertRevoked: cert %u REVOKED by CRL %u", subjectCert.index(), index()); break; } } subjectCert.freeField(&CSSMOID_X509V1SerialNumber, subjSerial); if(crtn) { subjectCert.addStatusCode(crtn); } return crtn; } /*** *** TPCrlGroup class ***/ /* build empty group */ TPCrlGroup::TPCrlGroup( CssmAllocator &alloc, TPGroupOwner whoOwns) : mAlloc(alloc), mCrlInfo(NULL), mNumCrls(0), mSizeofCrlInfo(0), mWhoOwns(whoOwns) { /* nothing for now */ } /* * Construct from unordered, untrusted CSSM_CRLGROUP. Resulting * TPCrlInfos are more or less in the same order as the incoming * CRLs, though incoming CRLs are discarded if they don't parse. * No verification of any sort is performed. */ TPCrlGroup::TPCrlGroup( const CSSM_CRLGROUP *cssmCrlGroup, // optional CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand, CssmAllocator &alloc, const char *verifyTime, // may be NULL TPGroupOwner whoOwns) : mAlloc(alloc), mCrlInfo(NULL), mNumCrls(0), mSizeofCrlInfo(0), mWhoOwns(whoOwns) { /* verify input args */ if((cssmCrlGroup == NULL) || (cssmCrlGroup->NumberOfCrls == 0)) { return; } 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(cssmCrlGroup->CrlGroupType != CSSM_CRLGROUP_DATA) { CssmError::throwMe(CSSMERR_TP_INVALID_CERTGROUP); } switch(cssmCrlGroup->CrlType) { case CSSM_CRL_TYPE_X_509v1: case CSSM_CRL_TYPE_X_509v2: break; default: CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT); } switch(cssmCrlGroup->CrlEncoding) { case CSSM_CRL_ENCODING_BER: case CSSM_CRL_ENCODING_DER: break; default: CssmError::throwMe(CSSMERR_TP_UNKNOWN_FORMAT); } /* * Add remaining input certs to mCrlInfo. */ TPCrlInfo *crlInfo = NULL; for(unsigned crlDex=0; crlDexNumberOfCrls; crlDex++) { try { crlInfo = new TPCrlInfo(clHand, cspHand, &cssmCrlGroup->GroupCrlList.CrlList[crlDex], TIC_NoCopy, // don't copy data verifyTime); } catch (...) { /* just ignore this CRL */ continue; } crlInfo->index(crlDex); appendCrl(*crlInfo); } } /* * Deletes all TPCrlInfo's if appropriate. */ TPCrlGroup::~TPCrlGroup() { 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 TPCrlGroup::appendCrl( TPCrlInfo &crlInfo) { if(mNumCrls == mSizeofCrlInfo) { if(mSizeofCrlInfo == 0) { /* appending to empty array */ mSizeofCrlInfo = 1; } else { mSizeofCrlInfo *= 2; } mCrlInfo = (TPCrlInfo **)mAlloc.realloc(mCrlInfo, mSizeofCrlInfo * sizeof(TPCrlInfo *)); } mCrlInfo[mNumCrls++] = &crlInfo; } TPCrlInfo *TPCrlGroup::crlAtIndex( unsigned index) { if(index > (mNumCrls - 1)) { CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } return mCrlInfo[index]; } TPCrlInfo &TPCrlGroup::removeCrlAtIndex( unsigned index) // doesn't delete the cert, just // removes it from our list { if(index > (mNumCrls - 1)) { CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } TPCrlInfo &rtn = *mCrlInfo[index]; /* removed requested element and compact remaining array */ unsigned i; for(i=index; i<(mNumCrls - 1); i++) { mCrlInfo[i] = mCrlInfo[i+1]; } mNumCrls--; return rtn; } void TPCrlGroup::removeCrl( TPCrlInfo &crlInfo) { for(unsigned dex=0; dexhasSameIssuer(subject)) { return crl; } } return NULL; }