+++ /dev/null
-/*
- * Copyright (c) 2000-2011 Apple 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
- */
-
-#include "TPCertInfo.h"
-#include "tpdebugging.h"
-#include "tpTime.h"
-#include "certGroupUtils.h"
-#include "TPDatabase.h"
-#include "TPNetwork.h"
-#include <Security/cssmapi.h>
-#include <Security/x509defs.h>
-#include <Security/oidscert.h>
-#include <Security/oidsalg.h>
-#include <string.h> /* for memcmp */
-#include <security_utilities/threading.h> /* for Mutex */
-#include <security_utilities/globalizer.h>
-#include <security_utilities/debugging.h>
-#include <security_cdsa_utilities/cssmerrors.h>
-#include <Security/cssmapple.h>
-#include <Security/SecCertificate.h>
-#include <Security/SecImportExport.h>
-#include <Security/SecTrustSettingsPriv.h>
-
-#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),
- mSubjectKeyID(NULL),
- mAuthorityKeyID(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...
- * Issuer 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);
-
- /* Attempt to fetch authority key id and subject key id,
- * ignore error if they do not exist.
- */
- fetchField(&CSSMOID_SubjectKeyIdentifier, &mSubjectKeyID);
- fetchField(&CSSMOID_AuthorityKeyIdentifier, &mAuthorityKeyID);
-
- 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(mSubjectKeyID) {
- freeField(&CSSMOID_SubjectKeyIdentifier, mSubjectKeyID);
- mSubjectKeyID = NULL;
- }
- if(mAuthorityKeyID) {
- freeField(&CSSMOID_AuthorityKeyIdentifier, mAuthorityKeyID);
- mAuthorityKeyID = 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) {
- tpCertInfoDbg("TPClItemInfo::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<CSSM_DATA *>(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, (unsigned)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, (unsigned)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<Mutex> tpTimeLock;
-
-CSSM_RETURN TPClItemInfo::calculateCurrent(
- const char *verifyString)
-{
- CFDateRef refTime = NULL;
-
- if(verifyString != NULL) {
- /* caller specifies verification time base */
- if(timeStringToCfDate(verifyString, (unsigned)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;
- }
-}
-
-/*
- * Does my subjectKeyID match the authorityKeyID of the specified subject?
- * Returns true if so (and if both fields are available).
- */
-bool TPCertInfo::isAuthorityKeyOf(
- const TPClItemInfo &subject)
-{
- const CSSM_DATA *subjectKeyID = this->subjectKeyID();
- const CSSM_DATA *authorityKeyID = subject.authorityKeyID();
- if(!subjectKeyID || !authorityKeyID) {
- tpDebug("isAuthorityKeyOf FALSE (one or both key ids missing)");
- return false;
- }
- CSSM_X509_EXTENSION *ske = (CSSM_X509_EXTENSION *)subjectKeyID->Data;
- CSSM_X509_EXTENSION *ake = (CSSM_X509_EXTENSION *)authorityKeyID->Data;
- if( !ske || ske->format != CSSM_X509_DATAFORMAT_PARSED ||
- !ake || ake->format != CSSM_X509_DATAFORMAT_PARSED ||
- !ske->value.parsedValue || !ake->value.parsedValue) {
- tpDebug("isAuthorityKeyOf FALSE (no parsed value present)");
- return false;
- }
-
- const CE_SubjectKeyID *skid = (CE_SubjectKeyID *)ske->value.parsedValue;
- const CE_AuthorityKeyID *akid = (CE_AuthorityKeyID *)ake->value.parsedValue;
-
- if(!akid->keyIdentifierPresent) {
- tpDebug("isAuthorityKeyOf FALSE (no key identifier present)");
- return false;
- }
- if(tpCompareCssmData(skid, &akid->keyIdentifier)) {
- #ifndef NDEBUG
- tpDebug("isAuthorityKeyOf TRUE (len:s=%lu/a=%lu, %08lX../%08lX..)",
- skid->Length,
- akid->keyIdentifier.Length,
- (skid->Data) ? *((unsigned long *)skid->Data) : 0L,
- (akid->keyIdentifier.Data) ? *((unsigned long *)akid->keyIdentifier.Data) : 0L);
- #endif
- return true;
- }
- else {
- #ifndef NDEBUG
- tpDebug("isAuthorityKeyOf FALSE (len:s=%lu/a=%lu, %08lX../%08lX..)",
- skid->Length,
- akid->keyIdentifier.Length,
- (skid->Data) ? *((unsigned long *)skid->Data) : 0L,
- (akid->keyIdentifier.Data) ? *((unsigned long *)akid->keyIdentifier.Data) : 0L);
- #endif
- 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::hasStatusCode(CSSM_RETURN code)
-{
- for(unsigned dex=0; dex<mNumStatusCodes; dex++) {
- if(mStatusCodes[dex] == code) {
- return true;
- }
- }
- return false;
-}
-
-bool TPCertInfo::isStatusFatal(CSSM_RETURN code)
-{
- for(unsigned dex=0; dex<mNumAllowedErrs; dex++) {
- if(mAllowedErrs[dex] == code) {
- tpTrustSettingsDbg("isStatusFatal(%ld): ALLOWED", (unsigned long)code);
- mIgnoredError = true;
- return false;
- }
- }
- return true;
-}
-
-/*
- * Indicate whether this cert's public key is a CSSM_KEYATTR_PARTIAL
- * key.
- */
-bool TPCertInfo::hasPartialKey()
-{
- if(mPublicKey->KeyHeader.KeyAttr & CSSM_KEYATTR_PARTIAL) {
- return true;
- }
- else {
- return false;
- }
-}
-
-/*
- * <rdar://9145531>
- */
-bool TPCertInfo::shouldReject()
-{
- static unsigned char _UTN_UF_H_ISSUER_BYTES[154] = {
- 0x30, 0x81, 0x97, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
- 0x13, 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
- 0x08, 0x13, 0x02, 0x55, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55,
- 0x04, 0x07, 0x13, 0x0e, 0x53, 0x41, 0x4c, 0x54, 0x20, 0x4c, 0x41, 0x4b,
- 0x45, 0x20, 0x43, 0x49, 0x54, 0x59, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03,
- 0x55, 0x04, 0x0a, 0x13, 0x15, 0x54, 0x48, 0x45, 0x20, 0x55, 0x53, 0x45,
- 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f,
- 0x52, 0x4b, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13,
- 0x18, 0x48, 0x54, 0x54, 0x50, 0x3a, 0x2f, 0x2f, 0x57, 0x57, 0x57, 0x2e,
- 0x55, 0x53, 0x45, 0x52, 0x54, 0x52, 0x55, 0x53, 0x54, 0x2e, 0x43, 0x4f,
- 0x4d, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x16,
- 0x55, 0x54, 0x4e, 0x2d, 0x55, 0x53, 0x45, 0x52, 0x46, 0x49, 0x52, 0x53,
- 0x54, 0x2d, 0x48, 0x41, 0x52, 0x44, 0x57, 0x41, 0x52, 0x45
- };
- CSSM_DATA _UTN_UF_H_ISSUER = { sizeof(_UTN_UF_H_ISSUER_BYTES), _UTN_UF_H_ISSUER_BYTES };
-
- static CSSM_DATA _UTN_UF_H_SERIALS[] = {
- { 17, (uint8*)"\x00\x92\x39\xd5\x34\x8f\x40\xd1\x69\x5a\x74\x54\x70\xe1\xf2\x3f\x43" }, // amo
- { 17, (uint8*)"\x00\xd8\xf3\x5f\x4e\xb7\x87\x2b\x2d\xab\x06\x92\xe3\x15\x38\x2f\xb0" }, // gt
- { 17, (uint8*)"\x00\xb0\xb7\x13\x3e\xd0\x96\xf9\xb5\x6f\xae\x91\xc8\x74\xbd\x3a\xc0" }, // llc
- { 17, (uint8*)"\x00\xe9\x02\x8b\x95\x78\xe4\x15\xdc\x1a\x71\x0a\x2b\x88\x15\x44\x47" }, // lsc
- { 17, (uint8*)"\x00\xd7\x55\x8f\xda\xf5\xf1\x10\x5b\xb2\x13\x28\x2b\x70\x77\x29\xa3" }, // lyc
- { 16, (uint8*)"\x39\x2a\x43\x4f\x0e\x07\xdf\x1f\x8a\xa3\x05\xde\x34\xe0\xc2\x29" }, // lyc1
- { 16, (uint8*)"\x3e\x75\xce\xd4\x6b\x69\x30\x21\x21\x88\x30\xae\x86\xa8\x2a\x71" }, // lyc2
- { 16, (uint8*)"\x04\x7e\xcb\xe9\xfc\xa5\x5f\x7b\xd0\x9e\xae\x36\xe1\x0c\xae\x1e" }, // mgc
- { 17, (uint8*)"\x00\xf5\xc8\x6a\xf3\x61\x62\xf1\x3a\x64\xf5\x4f\x6d\xc9\x58\x7c\x06" }, // wgc
- { 0, NULL }
- };
-
- const CSSM_DATA *issuer=issuerName();
- if(!issuer || !(tpCompareCssmData(issuer, &_UTN_UF_H_ISSUER)))
- return false;
-
- CSSM_DATA *serialNumber=NULL;
- CSSM_RETURN crtn = fetchField(&CSSMOID_X509V1SerialNumber, &serialNumber);
- if(crtn || !serialNumber)
- return false;
-
- CSSM_DATA *p=_UTN_UF_H_SERIALS;
- bool matched=false;
- while(p->Length) {
- if(tpCompareCssmData(serialNumber, p)) {
- matched=true;
- addStatusCode(CSSMERR_TP_CERT_REVOKED);
- break;
- }
- ++p;
- }
- freeField(&CSSMOID_X509V1SerialNumber, serialNumber);
- return matched;
-}
-
-/*
- * 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
- /* one more thing... */
- if(shouldReject()) {
- return CSSMERR_TP_INVALID_CERTIFICATE;
- }
- }
- *foundMatchingEntry = mTrustSettingsFoundMatchingEntry;
- *foundAnyEntry = mTrustSettingsFoundAnyEntry;
-
- return errSecSuccess;
-}
-
-/* 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; certDex<CertGroupFrag.NumCerts; certDex++) {
- try {
- certInfo = new TPCertInfo(clHand,
- cspHand,
- &CertGroupFrag.GroupList.CertList[certDex],
- TIC_NoCopy, // caller owns
- verifyTime);
- }
- catch (...) {
- if((certDex == 0) && firstCertMustBeValid) {
- CssmError::throwMe(CSSMERR_TP_INVALID_CERTIFICATE);
- }
- /* else just ignore this cert */
- continue;
- }
- certInfo->index(certDex);
- appendCert(certInfo);
- }
-}
-
-/*
- * Deletes contents of mCertInfo[] if appropriate.
- */
-TPCertGroup::~TPCertGroup()
-{
- if(mWhoOwns == TGO_Group) {
- unsigned i;
- for(i=0; i<mNumCerts; i++) {
- delete mCertInfo[i];
- }
- }
- mAlloc.free(mCertInfo);
-}
-
-/* add/remove/access TPTCertInfo's. */
-/*
- * NOTE: I am aware that most folks would just use an array<> 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; i<mNumCerts; i++) {
- tpCopyCssmData(mAlloc, mCertInfo[i]->itemData(),
- &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; i<mNumCerts; i++) {
- TPCertInfo *certInfo = mCertInfo[i];
- CSSM_TP_APPLE_EVIDENCE_INFO *evInfo = &infoArray[i];
-
- /* first the booleans */
- if(certInfo->isExpired()) {
- 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; j<numCodes; j++) {
- evInfo->StatusCodes[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 postdated = false;
- bool allowExpiredRoot = (actionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED_ROOT) ?
- true : false;
- bool allowExpired = (actionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED) ? true : false;
- bool allowPostdated = allowExpired; // flag overrides any temporal invalidity
- bool requireRevPerCert = (actionFlags & CSSM_TP_ACTION_REQUIRE_REV_PER_CERT) ?
- true : false;
-
- /* check for expired, not valid yet */
- for(unsigned i=0; i<mNumCerts; i++) {
- TPCertInfo *ci = mCertInfo[i];
- /*
- * Note avoidVerify = true for isSelfSigned(); if it were appropriate to
- * verify the signature, that would have happened in
- * buildCssmEvidenceInfo() at the latest.
- */
- if(ci->isExpired() &&
- !(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)) {
- postdated = true;
- }
- }
- if(expired && !allowExpired) {
- return CSSMERR_TP_CERT_EXPIRED;
- }
- if(postdated && !allowPostdated) {
- return CSSMERR_TP_CERT_NOT_VALID_YET;
- }
-
- /* Check for missing revocation check */
- if(requireRevPerCert) {
- for(unsigned i=0; i<mNumCerts; i++) {
- TPCertInfo *ci = mCertInfo[i];
- if(ci->isSelfSigned(true)) {
- /* revocation check meaningless for a root cert */
- tpDebug("getReturnCode: ignoring revocation for self-signed cert %d", i);
- continue;
- }
- if(!ci->revokeCheckGood() &&
- ci->isStatusFatal(CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK)) {
- tpDebug("getReturnCode: FATAL: revocation check incomplete for cert %d", i);
- return CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK;
- }
- #ifndef NDEBUG
- else {
- tpDebug("getReturnCode: revocation check %s for cert %d",
- (ci->revokeCheckGood()) ? "GOOD" : "OK", i);
- }
- #endif
- }
- }
- return policyStatus;
-}
-
-/* set all TPCertInfo.mUsed flags false */
-void TPCertGroup::setAllUnused()
-{
- for(unsigned dex=0; dex<mNumCerts; dex++) {
- mCertInfo[dex]->used(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; dex<mNumCerts; dex++) {
- if(!mCertInfo[dex]->isStatusFatal(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; dex<mNumCerts; dex++) {
- if(tpCompareCssmData(certInfo.itemData(), mCertInfo[dex]->itemData())) {
- return true;
- }
- }
- return false;
-}
-
-/*
- * Encode issuing certs in this group as a PEM-encoded data blob.
- * Caller must free.
- */
-void TPCertGroup::encodeIssuers(CSSM_DATA &issuers)
-{
- /* FIXME: probably want to rewrite this using pemEncode() from libsecurity_cdsa_utils,
- * since use of Sec* APIs from this layer violates the API reentrancy contract.
- */
- issuers.Data = NULL;
- issuers.Length = 0;
- CFMutableArrayRef certArray = CFArrayCreateMutable(kCFAllocatorDefault,
- 0, &kCFTypeArrayCallBacks);
- if(!certArray) {
- return;
- }
- for(unsigned certDex=0; certDex<mNumCerts; certDex++) {
- TPCertInfo *certInfo = certAtIndex(certDex);
- if(!certDex && mNumCerts > 1) {
- continue; /* don't need the leaf */
- }
- CSSM_DATA *cssmData = (CSSM_DATA*)((certInfo) ? certInfo->itemData() : NULL);
- if(!cssmData || !cssmData->Data || !cssmData->Length) {
- continue;
- }
- CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
- (const UInt8 *)cssmData->Data, cssmData->Length,
- kCFAllocatorNull);
- if(!dataRef) {
- continue;
- }
- SecCertificateRef certRef = SecCertificateCreateWithData(kCFAllocatorDefault,
- dataRef);
- if(!certRef) {
- CFRelease(dataRef);
- continue;
- }
- CFArrayAppendValue(certArray, certRef);
- CFRelease(certRef);
- CFRelease(dataRef);
- }
- CFDataRef exportedPEMData = NULL;
- OSStatus status = SecItemExport(certArray,
- kSecFormatPEMSequence,
- kSecItemPemArmour,
- NULL,
- &exportedPEMData);
- CFRelease(certArray);
-
- if(!status) {
- uint8 *dataPtr = (uint8*)CFDataGetBytePtr(exportedPEMData);
- size_t dataLen = CFDataGetLength(exportedPEMData);
- issuers.Data = (uint8*)malloc(dataLen);
- memmove(issuers.Data, dataPtr, dataLen);
- issuers.Length = dataLen;
- CFRelease(exportedPEMData);
- }
-}
-
-/*
- * 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;
- TPCertInfo *unmatchedKeyIDIssuer = NULL;
-
- for(unsigned certDex=0; certDex<mNumCerts; certDex++) {
- TPCertInfo *certInfo = certAtIndex(certDex);
-
- /* has this one already been used in this search? */
- if(certInfo->used()) {
- 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;
- }
- }
- /* Authority key identifier check: if we can't match subject key id,
- * hold onto this cert and keep going.
- */
- if(unmatchedKeyIDIssuer == NULL) {
- if(!certInfo->isAuthorityKeyOf(subject)) {
- tpDebug("findIssuerForCertOrCrl: holding issuer without key id match %p",
- certInfo);
- unmatchedKeyIDIssuer = 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(unmatchedKeyIDIssuer != NULL) {
- /* OK, we'll use this one (preferred over an expired issuer) */
- tpDbDebug("findIssuerForCertOrCrl: using issuer without key id match %p", unmatchedKeyIDIssuer);
- unmatchedKeyIDIssuer->used(true);
- return unmatchedKeyIDIssuer;
- }
- 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;
- bool attemptNetworkFetch = 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;
-
- /* and the case of an issuer without a matching subject key id */
- TPCertInfo *unmatchedKeyIDIssuer = 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 (self-signed)
- * -- find a non-root cert which is also in the anchors list
- * -- 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 temporally invalid (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() || subjCert->isNotValidYet()) &&
- (!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 */
-
- /*
- * If this non-root cert is in the provided anchors list,
- * we can stop building the chain at this point.
- *
- * If this cert is a leaf, the chain ends in an anchor, but if it's
- * also temporally invalid, we can't do anything further. However,
- * if it's not a leaf, then we need to roll back the chain to a
- * point just before this cert, so Case 1 will subsequently find
- * the anchor (and handle the anchor correctly if it's expired.)
- */
- if(numAnchorCerts && anchorCerts) {
- bool foundNonRootAnchor = false;
- for(certDex=0; certDex<numAnchorCerts; certDex++) {
- if(tp_CompareCerts(subjCert->itemData(), &anchorCerts[certDex])) {
- foundNonRootAnchor = true;
- /* if it's not the leaf, remove it from the outgoing cert group. */
- if(!firstSubjectIsInGroup || (mNumCerts > 1)) {
- if(mNumCerts) {
- /* roll back to previous cert */
- mNumCerts--;
- }
- if(mNumCerts == 0) {
- /* roll back to caller's initial condition */
- thisSubject = &subjectItem;
- }
- else {
- thisSubject = lastCert();
- }
- tpAnchorDebug("buildCertGroup: CA cert in input AND anchors");
- } /* not leaf */
- else {
- if(subjCert->isExpired() || subjCert->isNotValidYet()) {
- crtn = CSSM_CERT_STATUS_EXPIRED;
- } else {
- crtn = CSSM_OK;
- }
- subjCert->isAnchor(true);
- verifiedToAnchor = CSSM_TRUE;
- tpAnchorDebug("buildCertGroup: leaf cert in input AND anchors");
- } /* leaf */
- break; /* out of anchor-checking loop */
- }
- }
- if(foundNonRootAnchor) {
- break; /* out of main loop */
- }
- } /* non-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) {
- bool stashedIssuer = false;
- /* Check whether candidate issuer is expired or not yet valid */
- if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
- if(expiredIssuer == NULL) {
- tpDebug("buildCertGroup: saving expired cert %p (1)", issuerCert);
- expiredIssuer = issuerCert;
- stashedIssuer = true;
- }
- /* 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;
- }
- /* Check whether candidate issuer failed to match authority key id in thisSubject */
- if(!issuerCert->isAuthorityKeyOf(*thisSubject)) {
- if(unmatchedKeyIDIssuer == NULL) {
- tpDebug("buildCertGroup: saving unmatched key id issuer %p (1)", issuerCert);
- unmatchedKeyIDIssuer = issuerCert;
- stashedIssuer = true;
- }
- /* else we already have an unmatched key id issuer candidate */
- }
- else {
- /* unconditionally done with possible unmatchedKeyIDIssuer */
- #ifndef NDEBUG
- if(unmatchedKeyIDIssuer != NULL) {
- tpDebug("buildCertGroup: DISCARDING unmatched key id issuer %p (1)", unmatchedKeyIDIssuer);
- }
- #endif
- unmatchedKeyIDIssuer = NULL;
- }
- if(stashedIssuer) {
- issuerCert = NULL; /* keep looking */
- }
- }
-
- 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) {
- bool stashedIssuer = false;
- /* Check whether candidate issuer is expired or not yet valid */
- if(issuerCert->isExpired() || issuerCert->isNotValidYet()) {
- if(expiredIssuer == NULL) {
- tpDebug("buildCertGroup: saving expired cert %p (2)", issuerCert);
- expiredIssuer = issuerCert;
- stashedIssuer = true;
- }
- /* 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;
- }
- /* Check whether candidate issuer failed to match authority key id in thisSubject */
- if(!issuerCert->isAuthorityKeyOf(*thisSubject)) {
- if(unmatchedKeyIDIssuer == NULL) {
- tpDebug("buildCertGroup: saving unmatched key id issuer %p (2)", issuerCert);
- unmatchedKeyIDIssuer = issuerCert;
- stashedIssuer = true;
- }
- /* else we already have an unmatched key id issuer candidate */
- }
- else {
- /* unconditionally done with possible unmatchedKeyIdIssuer */
- #ifndef NDEBUG
- if(unmatchedKeyIDIssuer != NULL) {
- tpDebug("buildCertGroup: DISCARDING unmatched key id issuer %p (2)", unmatchedKeyIDIssuer);
- }
- #endif
- unmatchedKeyIDIssuer = NULL;
- }
- if(stashedIssuer) {
- issuerCert = NULL; /* keep looking */
- }
- }
-
- 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;
- /* unconditionally done with possible unmatchedKeyIDIssuer */
- #ifndef NDEBUG
- if(unmatchedKeyIDIssuer != NULL) {
- tpDebug("buildCertGroup: DISCARDING unmatched key id issuer %p (3)", unmatchedKeyIDIssuer);
- }
- #endif
- unmatchedKeyIDIssuer = 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 */
- (unmatchedKeyIDIssuer != NULL)) { /* but we have an unmatched keyID candidate */
- /*
- * OK, we'll take the unmatched key id issuer.
- * Note we don't have to free unmatchedKeyIDIssuer if we found a good one since
- * unmatchedKeyIDIssuer can only come from inCertGroup or gatheredCerts (not from
- * dbList).
- */
- tpDebug("buildCertGroup: USING unmatched key id issuer %p", unmatchedKeyIDIssuer);
- issuerCert = unmatchedKeyIDIssuer;
- unmatchedKeyIDIssuer = NULL;
- }
- 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; certDex<numAnchorCerts; certDex++) {
-
- try {
- anchorInfo = new TPCertInfo(clHand,
- cspHand,
- &anchorCerts[certDex],
- TIC_NoCopy,
- verifyTime);
- }
- catch(...) {
- /* bad anchor cert - ignore it */
- anchorInfo = NULL;
- continue;
- }
-
- /*
- * We must subsequently delete anchorInfo one way or the other.
- * If we add it to tpCertGroup, we also add it to certsToBeFreed.
- * Otherwise we delete it.
- */
- if(!anchorInfo->isIssuerOf(*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: Check whether endCert is present in anchor certs.
- *
- * 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 || 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; certDex<numAnchorCerts; certDex++) {
- if(tp_CompareCerts(theRoot->itemData(), &anchorCerts[certDex])) {
- /* one fully successful return */
- tpAnchorDebug("buildCertGroup: end cert 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: end cert in input, NOT anchors");
-
- if(!expiredRoot && endCert->isSelfSigned()) {
- /* 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, determine if fetching the issuer from the network
- * should be attempted: <rdar://6113890&7419584&7422356>
- */
- attemptNetworkFetch = (actionFlags & CSSM_TP_ACTION_FETCH_CERT_FROM_NET);
- if( (!dbList || (dbList->NumHandles == 0)) &&
- (!anchorCerts || (numAnchorCerts == 0)) ) {
- /* DB list is empty *and* anchors are empty; there is no point in going
- * out to the network, since we cannot build a chain to a trusted root.
- * (This can occur when the caller wants to evaluate a single certificate
- * without trying to build the chain, e.g. to check its key usage.)
- */
- attemptNetworkFetch = false;
- }
-
- /*
- * 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) && attemptNetworkFetch) {
- 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; dex<mNumCerts; dex++) {
- TPCertInfo *certInfo = mCertInfo[dex];
- certInfo->freeUniqueRecord();
- }
-}