//+--------------------------------------------------------------------------- // // Microsoft Windows NT Security // Copyright (C) Microsoft Corporation, 1997 - 1999 // // File: chain.cpp // // Contents: Certificate Chaining Infrastructure // // History: 15-Jan-98 kirtd Created // //---------------------------------------------------------------------------- #include #include //+=========================================================================== // CCertObject methods //============================================================================ //+--------------------------------------------------------------------------- // // Member: CCertObject::CCertObject, public // // Synopsis: Constructor // // Leaves the engine's critical section to create an object of // dwObjectType = CERT_END_OBJECT_TYPE. For a self-signed root // may also leave the critical section to retrieve and validate // the AuthRoot Auto Update CTL and add such a root to the // AuthRoot store. // // Assumption: Chain engine is locked once in the calling thread. // //---------------------------------------------------------------------------- CCertObject::CCertObject ( IN DWORD dwObjectType, IN PCCHAINCALLCONTEXT pCallContext, IN PCCERT_CONTEXT pCertContext, IN BYTE rgbCertHash[CHAINHASHLEN], OUT BOOL& rfResult ) { BOOL fLocked = TRUE; CRYPT_DATA_BLOB DataBlob; DWORD cbData; if (CERT_END_OBJECT_TYPE == dwObjectType) { pCallContext->ChainEngine()->UnlockEngine(); fLocked = FALSE; } m_dwObjectType = dwObjectType; m_cRefs = 1; // NOTE: The chain engine is NOT addref'd m_pChainEngine = pCallContext->ChainEngine(); m_dwIssuerMatchFlags = 0; m_dwCachedMatchFlags = 0; m_dwIssuerStatusFlags = 0; m_dwInfoFlags = 0; m_pCtlCacheHead = NULL; m_pCertContext = CertDuplicateCertificateContext( pCertContext ); memset(&m_PoliciesInfo, 0, sizeof(m_PoliciesInfo)); m_pBasicConstraintsInfo = NULL; m_pKeyUsage = NULL; m_pIssuerNameConstraintsInfo = NULL; m_fAvailableSubjectNameConstraintsInfo = FALSE; memset(&m_SubjectNameConstraintsInfo, 0, sizeof(m_SubjectNameConstraintsInfo)); m_pAuthKeyIdentifier = NULL; // m_ObjectIdentifier; memcpy(m_rgbCertHash, rgbCertHash, CHAINHASHLEN); m_cbKeyIdentifier = 0; m_pbKeyIdentifier = NULL; // m_rgbPublicKeyHash[ CHAINHASHLEN ]; // m_rgbIssuerPublicKeyHash[ CHAINHASHLEN ]; // m_rgbIssuerExactMatchHash[ CHAINHASHLEN ]; // m_rgbIssuerNameMatchHash[ CHAINHASHLEN ]; m_hHashEntry = NULL; m_hIdentifierEntry = NULL; m_hSubjectNameEntry = NULL; m_hKeyIdEntry = NULL; m_hPublicKeyHashEntry = NULL; m_hEndHashEntry = NULL; if (!CertGetCertificateContextProperty( pCertContext, CERT_KEY_IDENTIFIER_PROP_ID, NULL, &m_cbKeyIdentifier )) goto GetKeyIdentifierPropertyError; m_pbKeyIdentifier = new BYTE [ m_cbKeyIdentifier ]; if (NULL == m_pbKeyIdentifier) goto OutOfMemory; if (!CertGetCertificateContextProperty( pCertContext, CERT_KEY_IDENTIFIER_PROP_ID, m_pbKeyIdentifier, &m_cbKeyIdentifier )) goto GetKeyIdentifierPropertyError; cbData = CHAINHASHLEN; if (!CertGetCertificateContextProperty( pCertContext, CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID, m_rgbPublicKeyHash, &cbData ) || CHAINHASHLEN != cbData) goto GetSubjectPublicKeyHashPropertyError; cbData = CHAINHASHLEN; if (CertGetCertificateContextProperty( pCertContext, CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID, m_rgbIssuerPublicKeyHash, &cbData ) && CHAINHASHLEN == cbData) m_dwIssuerStatusFlags |= CERT_ISSUER_PUBKEY_FLAG; ChainGetPoliciesInfo(pCertContext, &m_PoliciesInfo); if (!ChainGetBasicConstraintsInfo(pCertContext, &m_pBasicConstraintsInfo)) m_dwInfoFlags |= CHAIN_INVALID_BASIC_CONSTRAINTS_INFO_FLAG; if (!ChainGetKeyUsage(pCertContext, &m_pKeyUsage)) m_dwInfoFlags |= CHAIN_INVALID_KEY_USAGE_FLAG; if (!ChainGetIssuerNameConstraintsInfo(pCertContext, &m_pIssuerNameConstraintsInfo)) m_dwInfoFlags |= CHAIN_INVALID_ISSUER_NAME_CONSTRAINTS_INFO_FLAG; if (CERT_CACHED_ISSUER_OBJECT_TYPE == dwObjectType) { DataBlob.cbData = CHAINHASHLEN; DataBlob.pbData = m_rgbCertHash; if (!I_CryptCreateLruEntry( m_pChainEngine->CertObjectCache()->HashIndex(), &DataBlob, this, &m_hHashEntry )) goto CreateHashLruEntryError; // Need to double check this, only needed for issuer caching ??? ChainCreateCertificateObjectIdentifier( &pCertContext->pCertInfo->Issuer, &pCertContext->pCertInfo->SerialNumber, m_ObjectIdentifier ); DataBlob.cbData = sizeof( CERT_OBJECT_IDENTIFIER ); DataBlob.pbData = m_ObjectIdentifier; if (!I_CryptCreateLruEntry( m_pChainEngine->CertObjectCache()->IdentifierIndex(), &DataBlob, this, &m_hIdentifierEntry )) goto CreateIdentifierLruEntryError; DataBlob.cbData = pCertContext->pCertInfo->Subject.cbData; DataBlob.pbData = pCertContext->pCertInfo->Subject.pbData; if (!I_CryptCreateLruEntry( m_pChainEngine->CertObjectCache()->SubjectNameIndex(), &DataBlob, this, &m_hSubjectNameEntry )) goto CreateSubjectNameLruEntryError; DataBlob.cbData = m_cbKeyIdentifier; DataBlob.pbData = m_pbKeyIdentifier; if (!I_CryptCreateLruEntry( m_pChainEngine->CertObjectCache()->KeyIdIndex(), &DataBlob, this, &m_hKeyIdEntry )) goto CreateKeyIdLruEntryError; DataBlob.cbData = CHAINHASHLEN; DataBlob.pbData = m_rgbPublicKeyHash; if (!I_CryptCreateLruEntry( m_pChainEngine->CertObjectCache()->PublicKeyHashIndex(), &DataBlob, this, &m_hPublicKeyHashEntry )) goto CreatePublicKeyHashLruEntryError; } ChainGetIssuerMatchInfo( pCertContext, &m_dwIssuerMatchFlags, &m_pAuthKeyIdentifier ); ChainGetSelfSignedStatus(pCallContext, this, &m_dwIssuerStatusFlags); if (m_dwIssuerStatusFlags & CERT_ISSUER_SELF_SIGNED_FLAG) { // // NOTE: This means that only self-signed roots are supported // if (!fLocked) { pCallContext->ChainEngine()->LockEngine(); fLocked = TRUE; } ChainGetRootStoreStatus( m_pChainEngine->RootStore(), m_pChainEngine->RealRootStore(), rgbCertHash, &m_dwIssuerStatusFlags ); if (!(m_dwIssuerStatusFlags & CERT_ISSUER_TRUSTED_ROOT_FLAG)) { if (!ChainGetAuthRootAutoUpdateStatus( pCallContext, this, &m_dwIssuerStatusFlags )) goto AuthRootAutoUpdateError; } if (!(m_dwIssuerStatusFlags & CERT_ISSUER_TRUSTED_ROOT_FLAG)) { // Get all cached CTLs we are a member of CERT_OBJECT_CTL_CACHE_ENUM_DATA EnumData; memset(&EnumData, 0, sizeof(EnumData)); EnumData.fResult = TRUE; EnumData.pCertObject = this; m_pChainEngine->SSCtlObjectCache()->EnumObjects( ChainFillCertObjectCtlCacheEnumFn, &EnumData ); if (!EnumData.fResult) { SetLastError(EnumData.dwLastError); goto FillCertObjectCtlCacheError; } } } rfResult = TRUE; CommonReturn: if (!fLocked) pCallContext->ChainEngine()->LockEngine(); return; ErrorReturn: rfResult = FALSE; goto CommonReturn; TRACE_ERROR(GetKeyIdentifierPropertyError) SET_ERROR(OutOfMemory, E_OUTOFMEMORY) TRACE_ERROR(GetSubjectPublicKeyHashPropertyError) TRACE_ERROR(CreateHashLruEntryError) TRACE_ERROR(CreateIdentifierLruEntryError) TRACE_ERROR(CreateSubjectNameLruEntryError) TRACE_ERROR(CreateKeyIdLruEntryError) TRACE_ERROR(CreatePublicKeyHashLruEntryError) TRACE_ERROR(AuthRootAutoUpdateError) TRACE_ERROR(FillCertObjectCtlCacheError) } //+--------------------------------------------------------------------------- // // Member: CCertObject::~CCertObject, public // // Synopsis: Destructor // //---------------------------------------------------------------------------- CCertObject::~CCertObject () { if ( m_hKeyIdEntry != NULL ) { I_CryptReleaseLruEntry( m_hKeyIdEntry ); } if ( m_hSubjectNameEntry != NULL ) { I_CryptReleaseLruEntry( m_hSubjectNameEntry ); } if ( m_hIdentifierEntry != NULL ) { I_CryptReleaseLruEntry( m_hIdentifierEntry ); } if ( m_hPublicKeyHashEntry != NULL ) { I_CryptReleaseLruEntry( m_hPublicKeyHashEntry ); } if ( m_hHashEntry != NULL ) { I_CryptReleaseLruEntry( m_hHashEntry ); } if ( m_hEndHashEntry != NULL ) { I_CryptReleaseLruEntry( m_hEndHashEntry ); } ChainFreeCertObjectCtlCache(m_pCtlCacheHead); delete m_pbKeyIdentifier; ChainFreeAuthorityKeyIdentifier( m_pAuthKeyIdentifier ); ChainFreePoliciesInfo( &m_PoliciesInfo ); ChainFreeBasicConstraintsInfo( m_pBasicConstraintsInfo ); ChainFreeKeyUsage( m_pKeyUsage ); ChainFreeIssuerNameConstraintsInfo( m_pIssuerNameConstraintsInfo ); ChainFreeSubjectNameConstraintsInfo( &m_SubjectNameConstraintsInfo ); CertFreeCertificateContext( m_pCertContext ); } //+--------------------------------------------------------------------------- // // Member: CCertObject::CacheEndObject, public // // Synopsis: Convert a CERT_END_OBJECT_TYPE to a CERT_CACHED_END_OBJECT_TYPE. // //---------------------------------------------------------------------------- BOOL CCertObject::CacheEndObject( IN PCCHAINCALLCONTEXT pCallContext ) { BOOL fResult; CRYPT_DATA_BLOB DataBlob; assert(CERT_END_OBJECT_TYPE == m_dwObjectType); DataBlob.cbData = CHAINHASHLEN; DataBlob.pbData = m_rgbCertHash; fResult = I_CryptCreateLruEntry( m_pChainEngine->CertObjectCache()->EndHashIndex(), &DataBlob, this, &m_hEndHashEntry ); if (fResult) m_dwObjectType = CERT_CACHED_END_OBJECT_TYPE; return fResult; } //+--------------------------------------------------------------------------- // // Member: CCertObject::SubjectNameConstraintsInfo, public // // Synopsis: return the subject name constraints info // // allocation and getting of info is deferred until the // first name constraint check is done. // // Assumption: chain engine isn't locked upon entry. // //---------------------------------------------------------------------------- PCHAIN_SUBJECT_NAME_CONSTRAINTS_INFO CCertObject::SubjectNameConstraintsInfo () { if (!m_fAvailableSubjectNameConstraintsInfo) { CHAIN_SUBJECT_NAME_CONSTRAINTS_INFO Info; memset(&Info, 0, sizeof(Info)); ChainGetSubjectNameConstraintsInfo(m_pCertContext, &Info); // Must do the update while holding the engine's critical section m_pChainEngine->LockEngine(); if (m_fAvailableSubjectNameConstraintsInfo) // Another thread already did the update ChainFreeSubjectNameConstraintsInfo(&Info); else { memcpy(&m_SubjectNameConstraintsInfo, &Info, sizeof(m_SubjectNameConstraintsInfo)); // Must be set last!!! m_fAvailableSubjectNameConstraintsInfo = TRUE; } m_pChainEngine->UnlockEngine(); } return &m_SubjectNameConstraintsInfo; } //+--------------------------------------------------------------------------- // // Member: CCertObject::GetIssuerExactMatchHash, public // // Synopsis: if the cert has an Authority Key Info extension with // the optional issuer and serial number, returns the count and // pointer to the MD5 hash of the issuer name and serial number. // Otherwise, pMatchHash->cbData is set to 0. // // MD5 hash calculation is deferred until the first call. // // Assumption: Chain engine is locked once in the calling thread. // //---------------------------------------------------------------------------- VOID CCertObject::GetIssuerExactMatchHash( OUT PCRYPT_DATA_BLOB pMatchHash ) { if (!(m_dwIssuerStatusFlags & CERT_ISSUER_EXACT_MATCH_HASH_FLAG)) { PCERT_AUTHORITY_KEY_ID_INFO pAKI = m_pAuthKeyIdentifier; if (pAKI && 0 != pAKI->CertIssuer.cbData && 0 != pAKI->CertSerialNumber.cbData) { ChainCreateCertificateObjectIdentifier( &pAKI->CertIssuer, &pAKI->CertSerialNumber, m_rgbIssuerExactMatchHash ); m_dwIssuerStatusFlags |= CERT_ISSUER_EXACT_MATCH_HASH_FLAG; } else { pMatchHash->cbData = 0; pMatchHash->pbData = NULL; return; } } // else // We have already calculated the MD5 hash pMatchHash->cbData = CHAINHASHLEN; pMatchHash->pbData = m_rgbIssuerExactMatchHash; } //+--------------------------------------------------------------------------- // // Member: CCertObject::GetIssuerKeyMatchHash, public // // Synopsis: if the cert has an Authority Key Info extension with // the optional key id, returns the key id. // Otherwise, pMatchHash->cbData is set to 0. // //---------------------------------------------------------------------------- VOID CCertObject::GetIssuerKeyMatchHash( OUT PCRYPT_DATA_BLOB pMatchHash ) { PCERT_AUTHORITY_KEY_ID_INFO pAKI = m_pAuthKeyIdentifier; if (pAKI) *pMatchHash = pAKI->KeyId; else { pMatchHash->cbData = 0; pMatchHash->pbData = NULL; } } //+--------------------------------------------------------------------------- // // Member: CCertObject::GetIssuerNameMatchHash, public // // Synopsis: if the cert has an issuer name, returns the count and // pointer to the MD5 hash of the issuer name. // Otherwise, pMatchHash->cbData is set to 0. // // MD5 hash calculation is deferred until the first call. // // Assumption: Chain engine is locked once in the calling thread. // //---------------------------------------------------------------------------- VOID CCertObject::GetIssuerNameMatchHash( OUT PCRYPT_DATA_BLOB pMatchHash ) { if (!(m_dwIssuerStatusFlags & CERT_ISSUER_NAME_MATCH_HASH_FLAG)) { PCERT_INFO pCertInfo = m_pCertContext->pCertInfo; if (0 != pCertInfo->Issuer.cbData) { MD5_CTX md5ctx; MD5Init( &md5ctx ); MD5Update( &md5ctx, pCertInfo->Issuer.pbData, pCertInfo->Issuer.cbData ); MD5Final( &md5ctx ); assert(CHAINHASHLEN == MD5DIGESTLEN); memcpy(m_rgbIssuerNameMatchHash, md5ctx.digest, CHAINHASHLEN); m_dwIssuerStatusFlags |= CERT_ISSUER_NAME_MATCH_HASH_FLAG; } else { pMatchHash->cbData = 0; pMatchHash->pbData = NULL; return; } } pMatchHash->cbData = CHAINHASHLEN; pMatchHash->pbData = m_rgbIssuerNameMatchHash; } //+=========================================================================== // CChainPathObject methods //============================================================================ //+--------------------------------------------------------------------------- // // Member: CChainPathObject::CChainPathObject, public // // Synopsis: Constructor // // Once successfully added to the call context cache, rfAddedToCreationCache // is set. This object will be deleted when CChainCallContext gets destroyed. // // Since this object is per call, no AddRef'ing is required. // //---------------------------------------------------------------------------- CChainPathObject::CChainPathObject ( IN PCCHAINCALLCONTEXT pCallContext, IN BOOL fCyclic, IN LPVOID pvObject, // fCyclic : pPathObject ? pCertObject IN OPTIONAL HCERTSTORE hAdditionalStore, OUT BOOL& rfResult, OUT BOOL& rfAddedToCreationCache ) { PCCERTOBJECT pCertObject; PCCHAINPATHOBJECT pPathObject; DWORD dwIssuerStatusFlags; rfAddedToCreationCache = FALSE; if (fCyclic) { pPathObject = (PCCHAINPATHOBJECT) pvObject; pCertObject = pPathObject->CertObject(); } else { pPathObject = NULL; pCertObject = (PCCERTOBJECT) pvObject; } m_pCertObject = pCertObject; pCertObject->AddRef(); memset( &m_TrustStatus, 0, sizeof( m_TrustStatus ) ); m_dwPass1Quality = 0; m_dwChainIndex = 0; m_dwElementIndex = 0; m_pDownIssuerElement = NULL; m_pDownPathObject = NULL; m_pUpIssuerElement = NULL; m_fHasAdditionalStatus = FALSE; memset( &m_AdditionalStatus, 0, sizeof( m_AdditionalStatus ) ); m_fHasRevocationInfo = FALSE; memset( &m_RevocationInfo, 0, sizeof( m_RevocationInfo ) ); memset( &m_RevocationCrlInfo, 0, sizeof( m_RevocationCrlInfo ) ); m_pIssuerList = NULL; m_pwszExtendedErrorInfo = NULL; m_fCompleted = FALSE; if (!ChainCreateIssuerList( this, &m_pIssuerList )) goto CreateIssuerListError; if (!pCallContext->AddPathObjectToCreationCache( this )) goto AddPathObjectToCreationCacheError; rfAddedToCreationCache = TRUE; if (fCyclic) { m_TrustStatus = pPathObject->m_TrustStatus; m_TrustStatus.dwInfoStatus |= ChainGetMatchInfoStatusForNoIssuer( pCertObject->IssuerMatchFlags()); m_TrustStatus.dwErrorStatus |= CERT_TRUST_IS_CYCLIC; goto SuccessReturn; } dwIssuerStatusFlags = pCertObject->IssuerStatusFlags(); if (dwIssuerStatusFlags & CERT_ISSUER_SELF_SIGNED_FLAG) { m_TrustStatus.dwInfoStatus |= CERT_TRUST_IS_SELF_SIGNED; ChainGetMatchInfoStatus(pCertObject, pCertObject, &m_TrustStatus.dwInfoStatus); m_dwPass1Quality |= CERT_QUALITY_COMPLETE_CHAIN | CERT_QUALITY_NOT_CYCLIC; if (dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG) { m_dwPass1Quality |= CERT_QUALITY_SIGNATURE_VALID; } else { m_TrustStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_SIGNATURE_VALID; m_TrustStatus.dwInfoStatus &= ~CERT_TRUST_HAS_PREFERRED_ISSUER; } if (dwIssuerStatusFlags & CERT_ISSUER_TRUSTED_ROOT_FLAG) { m_dwPass1Quality |= CERT_QUALITY_HAS_TRUSTED_ROOT; // Check if we have a time valid root. This is an extra // check necessary to determine if we will need to do // AuthRoot Auto Update. FILETIME RequestedTime; PCERT_INFO pCertInfo = pCertObject->CertContext()->pCertInfo; pCallContext->RequestedTime(&RequestedTime); if ((0 == (pCallContext->CallFlags() & CERT_CHAIN_TIMESTAMP_TIME)) && 0 == CertVerifyTimeValidity(&RequestedTime, pCertInfo)) { m_dwPass1Quality |= CERT_QUALITY_HAS_TIME_VALID_TRUSTED_ROOT; } else { // Use current time for timestamping or try again using the // current time. This is necessary for cross certificate // chains. FILETIME CurrentTime; pCallContext->CurrentTime(&CurrentTime); if (0 == CertVerifyTimeValidity(&CurrentTime, pCertInfo)) { m_dwPass1Quality |= CERT_QUALITY_HAS_TIME_VALID_TRUSTED_ROOT; } } } else { m_TrustStatus.dwErrorStatus |= CERT_TRUST_IS_UNTRUSTED_ROOT; if (!FindAndAddCtlIssuersFromCache(pCallContext, hAdditionalStore)) goto FindAndCtlIssuersFromCacheError; if (hAdditionalStore) { if (!FindAndAddCtlIssuersFromAdditionalStore( pCallContext, hAdditionalStore )) goto FindAndCtlIssuersFromAdditionalStoreError; } if (!(dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG)) m_dwPass1Quality &= ~CERT_QUALITY_SIGNATURE_VALID; } } else { if (!FindAndAddIssuers ( pCallContext, hAdditionalStore, NULL // hIssuerUrlStore )) goto FindAndAddIssuersError; dwIssuerStatusFlags = pCertObject->IssuerStatusFlags(); if (m_pIssuerList->IsEmpty() || (!(dwIssuerStatusFlags & CERT_ISSUER_URL_FLAG) && (!(dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG) || !(m_dwPass1Quality & CERT_QUALITY_SIGNATURE_VALID)))) { DWORD i; // Try the following 2 URL cases: // 0 - AIA cache // 1 - AIA wire // Continue through the cases until finding a "good" issuer. for (i = 0; i <= 1; i++) { HCERTSTORE hIssuerUrlStore = NULL; DWORD dwRetrievalFlags; if (0 == i) dwRetrievalFlags = CRYPT_CACHE_ONLY_RETRIEVAL; else { if (!pCallContext->IsOnline()) break; dwRetrievalFlags = CRYPT_WIRE_ONLY_RETRIEVAL; } // The following leaves the engine's critical section to do // URL fetching. If the engine was touched by another // thread, it fails with LastError set to // ERROR_CAN_NOT_COMPLETE. if (!pCallContext->ChainEngine()->GetIssuerUrlStore( pCallContext, pCertObject->CertContext(), dwRetrievalFlags, &hIssuerUrlStore )) goto GetIssuerUrlStoreError; if (hIssuerUrlStore) { BOOL fResult; fResult = FindAndAddIssuers ( pCallContext, hAdditionalStore, hIssuerUrlStore ); CertCloseStore(hIssuerUrlStore, 0); if (!fResult) goto FindAndAddIssuersFromUrlStoreError; dwIssuerStatusFlags = pCertObject->IssuerStatusFlags(); if (!m_pIssuerList->IsEmpty() && (dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG)) { assert(dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG); // Try to find all issuers having the same public key. if (!FindAndAddIssuersByMatchType( CERT_PUBKEY_ISSUER_MATCH_TYPE, pCallContext, hAdditionalStore, NULL // hIssuerUrlStore )) goto FindIssuersByPubKeyError; if (m_dwPass1Quality & CERT_QUALITY_SIGNATURE_VALID) break; } } } pCertObject->OrIssuerStatusFlags(CERT_ISSUER_URL_FLAG); } // Check if we have a time valid, signature valid, trusted root if ((CERT_QUALITY_HAS_TIME_VALID_TRUSTED_ROOT | CERT_QUALITY_SIGNATURE_VALID) != (m_dwPass1Quality & (CERT_QUALITY_HAS_TIME_VALID_TRUSTED_ROOT | CERT_QUALITY_SIGNATURE_VALID)) && pCallContext->IsOnline()) { HCERTSTORE hIssuerUrlStore = NULL; // The following leaves the engine's critical section to do // URL fetching. If the engine was touched by another // thread, it fails with LastError set to // ERROR_CAN_NOT_COMPLETE. // Note, we only hit the wire to fetch AuthRoots stored // on Microsoft's web server if (!GetAuthRootAutoUpdateUrlStore( pCallContext, &hIssuerUrlStore )) goto GetAuthRootAutoUpdateUrlStoreError; if (hIssuerUrlStore) { BOOL fResult; fResult = FindAndAddIssuers ( pCallContext, hAdditionalStore, hIssuerUrlStore ); CertCloseStore(hIssuerUrlStore, 0); if (!fResult) goto FindAndAddIssuersFromUrlStoreError; } } if (m_pIssuerList->IsEmpty()) { m_TrustStatus.dwInfoStatus |= ChainGetMatchInfoStatusForNoIssuer( pCertObject->IssuerMatchFlags()); assert(0 == (m_dwPass1Quality & (CERT_QUALITY_HAS_TRUSTED_ROOT | CERT_QUALITY_COMPLETE_CHAIN))); // Unable to verify our signature, default to being valid. // Also, we can't be cyclic. m_dwPass1Quality |= CERT_QUALITY_SIGNATURE_VALID | CERT_QUALITY_NOT_CYCLIC; } } SuccessReturn: rfResult = TRUE; CommonReturn: m_fCompleted = TRUE; return; ErrorReturn: rfResult = FALSE; goto CommonReturn; TRACE_ERROR(CreateIssuerListError) TRACE_ERROR(AddPathObjectToCreationCacheError) TRACE_ERROR(FindAndCtlIssuersFromCacheError) TRACE_ERROR(FindAndCtlIssuersFromAdditionalStoreError) TRACE_ERROR(FindAndAddIssuersError) TRACE_ERROR(GetIssuerUrlStoreError) TRACE_ERROR(GetAuthRootAutoUpdateUrlStoreError) TRACE_ERROR(FindAndAddIssuersFromUrlStoreError) TRACE_ERROR(FindIssuersByPubKeyError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::~CChainPathObject, public // // Synopsis: Destructor // //---------------------------------------------------------------------------- CChainPathObject::~CChainPathObject () { if (m_pCertObject) m_pCertObject->Release(); if (m_fHasRevocationInfo) { if (m_RevocationCrlInfo.pBaseCrlContext) CertFreeCRLContext(m_RevocationCrlInfo.pBaseCrlContext); if (m_RevocationCrlInfo.pDeltaCrlContext) CertFreeCRLContext(m_RevocationCrlInfo.pDeltaCrlContext); } if (m_pIssuerList) ChainFreeIssuerList( m_pIssuerList ); if (m_pwszExtendedErrorInfo) PkiFree(m_pwszExtendedErrorInfo); } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::FindAndAddIssuers, public // // Synopsis: find and add issuers for all matching types // //---------------------------------------------------------------------------- BOOL CChainPathObject::FindAndAddIssuers ( IN PCCHAINCALLCONTEXT pCallContext, IN OPTIONAL HCERTSTORE hAdditionalStore, IN OPTIONAL HCERTSTORE hIssuerUrlStore ) { BOOL fResult; PCCERTOBJECT pCertObject = m_pCertObject; DWORD dwIssuerMatchFlags; DWORD i; static const rgdwMatchType[] = { CERT_EXACT_ISSUER_MATCH_TYPE, CERT_KEYID_ISSUER_MATCH_TYPE, CERT_NAME_ISSUER_MATCH_TYPE }; #define FIND_MATCH_TYPE_CNT (sizeof(rgdwMatchType) / sizeof(rgdwMatchType[0])) if (pCertObject->IssuerStatusFlags() & CERT_ISSUER_PUBKEY_FLAG) { // We know the issuer's public key. First, attempt to find all issuers // having that public key. if (!FindAndAddIssuersByMatchType( CERT_PUBKEY_ISSUER_MATCH_TYPE, pCallContext, hAdditionalStore, hIssuerUrlStore )) goto FindIssuersByPubKeyError; if (!m_pIssuerList->IsEmpty() && (pCertObject->IssuerStatusFlags() & CERT_ISSUER_VALID_SIGNATURE_FLAG)) goto SuccessReturn; } dwIssuerMatchFlags = pCertObject->IssuerMatchFlags(); for (i = 0; i < FIND_MATCH_TYPE_CNT; i++) { if (dwIssuerMatchFlags & CERT_MATCH_TYPE_TO_FLAG(rgdwMatchType[i])) { DWORD dwIssuerStatusFlags; if (!FindAndAddIssuersByMatchType( rgdwMatchType[i], pCallContext, hAdditionalStore, hIssuerUrlStore )) goto FindIssuersByMatchTypeError; dwIssuerStatusFlags = pCertObject->IssuerStatusFlags(); if (!m_pIssuerList->IsEmpty() && (dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG)) { assert(dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG); // We can now find all issuers having the same public key. if (!FindAndAddIssuersByMatchType( CERT_PUBKEY_ISSUER_MATCH_TYPE, pCallContext, hAdditionalStore, hIssuerUrlStore )) goto FindIssuersByPubKeyError; break; } } } SuccessReturn: fResult = TRUE; CommonReturn: return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(FindIssuersByPubKeyError) TRACE_ERROR(FindIssuersByMatchTypeError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::FindAndAddIssuersByMatchType, public // // Synopsis: find and add issuers for the specified match type // //---------------------------------------------------------------------------- BOOL CChainPathObject::FindAndAddIssuersByMatchType( IN DWORD dwMatchType, IN PCCHAINCALLCONTEXT pCallContext, IN OPTIONAL HCERTSTORE hAdditionalStore, IN OPTIONAL HCERTSTORE hIssuerUrlStore ) { BOOL fResult; PCCERTOBJECT pCertObject = m_pCertObject; if (NULL == hIssuerUrlStore) { DWORD dwIssuerStatusFlags; DWORD dwCachedMatchFlags; // Note, we need to get the cached match flags before finding // in the cache. Due to recursive, doing a find further up the // chain may result in another issuer being inserted at the beginning // of the cache bucket list. Pretty remote, but possible. dwCachedMatchFlags = pCertObject->CachedMatchFlags(); if (!FindAndAddIssuersFromCacheByMatchType( dwMatchType, pCallContext, hAdditionalStore )) goto FindIssuersFromCacheError; dwIssuerStatusFlags = pCertObject->IssuerStatusFlags(); if (CERT_PUBKEY_ISSUER_MATCH_TYPE != dwMatchType && !m_pIssuerList->IsEmpty() && (dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG)) { assert(dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG); // We will be called again using the PUBKEY match goto SuccessReturn; } if (!(dwCachedMatchFlags & CERT_MATCH_TYPE_TO_FLAG(dwMatchType))) { if (!FindAndAddIssuersFromStoreByMatchType( dwMatchType, pCallContext, FALSE, // fExternalStore hAdditionalStore, NULL // hIssuerUrlStore )) goto FindIssuersFromEngineStoreError; dwIssuerStatusFlags = pCertObject->IssuerStatusFlags(); if (CERT_PUBKEY_ISSUER_MATCH_TYPE != dwMatchType && !m_pIssuerList->IsEmpty() && (dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG)) { assert(dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG); // We will be called again using the PUBKEY match goto SuccessReturn; } } } if (NULL != hAdditionalStore || NULL != hIssuerUrlStore) { if (!FindAndAddIssuersFromStoreByMatchType( dwMatchType, pCallContext, TRUE, // fExternalStore hAdditionalStore, hIssuerUrlStore )) goto FindIssuersFromAdditionalOrUrlStoreError; } SuccessReturn: fResult = TRUE; CommonReturn: return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(FindIssuersFromCacheError) TRACE_ERROR(FindIssuersFromEngineStoreError) TRACE_ERROR(FindIssuersFromAdditionalOrUrlStoreError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::FindAndAddIssuersFromCacheByMatchType, public // // Synopsis: find and add cached issuers for the specified match type // //---------------------------------------------------------------------------- BOOL CChainPathObject::FindAndAddIssuersFromCacheByMatchType( IN DWORD dwMatchType, IN PCCHAINCALLCONTEXT pCallContext, IN OPTIONAL HCERTSTORE hAdditionalStore ) { BOOL fResult; PCCERTOBJECT pCertObject = m_pCertObject; PCCERTCHAINENGINE pChainEngine = pCertObject->ChainEngine(); PCCERTOBJECTCACHE pCertObjectCache = pChainEngine->CertObjectCache(); PCCERTOBJECT pIssuer = NULL; HLRUCACHE hCache; HLRUENTRY hEntry; PCRYPT_DATA_BLOB pIdentifier; CRYPT_DATA_BLOB DataBlob; PCERT_AUTHORITY_KEY_ID_INFO pAuthKeyIdentifier; switch (dwMatchType) { case CERT_EXACT_ISSUER_MATCH_TYPE: hCache = pCertObjectCache->IdentifierIndex(); pCertObject->GetIssuerExactMatchHash(&DataBlob); pIdentifier = &DataBlob; break; case CERT_KEYID_ISSUER_MATCH_TYPE: hCache = pCertObjectCache->KeyIdIndex(); pAuthKeyIdentifier = pCertObject->AuthorityKeyIdentifier(); pIdentifier = &pAuthKeyIdentifier->KeyId; break; case CERT_NAME_ISSUER_MATCH_TYPE: hCache = pCertObjectCache->SubjectNameIndex(); pIdentifier = &pCertObject->CertContext()->pCertInfo->Issuer; break; case CERT_PUBKEY_ISSUER_MATCH_TYPE: hCache = pCertObjectCache->PublicKeyHashIndex(); DataBlob.cbData = CHAINHASHLEN; DataBlob.pbData = pCertObject->IssuerPublicKeyHash(); pIdentifier = &DataBlob; break; default: goto InvalidMatchType; } pIssuer = pCertObjectCache->FindIssuerObject(hCache, pIdentifier); while (pIssuer) { DWORD dwIssuerStatusFlags; if (!m_pIssuerList->AddIssuer( pCallContext, hAdditionalStore, pIssuer )) goto AddIssuerError; dwIssuerStatusFlags = pCertObject->IssuerStatusFlags(); if (CERT_PUBKEY_ISSUER_MATCH_TYPE != dwMatchType && (dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG)) { assert(dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG); // We will be called again using the PUBKEY match goto SuccessReturn; } switch (dwMatchType) { case CERT_EXACT_ISSUER_MATCH_TYPE: hEntry = pIssuer->IdentifierIndexEntry(); break; case CERT_KEYID_ISSUER_MATCH_TYPE: hEntry = pIssuer->KeyIdIndexEntry(); break; case CERT_NAME_ISSUER_MATCH_TYPE: hEntry = pIssuer->SubjectNameIndexEntry(); break; case CERT_PUBKEY_ISSUER_MATCH_TYPE: hEntry = pIssuer->PublicKeyHashIndexEntry(); break; default: goto InvalidMatchType; } pIssuer = pCertObjectCache->NextMatchingIssuerObject(hEntry, pIssuer); } SuccessReturn: fResult = TRUE; CommonReturn: if (pIssuer) { DWORD dwErr = GetLastError(); pIssuer->Release(); SetLastError(dwErr); } return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; SET_ERROR(InvalidMatchType, E_UNEXPECTED) TRACE_ERROR(AddIssuerError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::FindAndAddIssuersFromStoreByMatchType, public // // Synopsis: find and add issuers from either the engine's or an // external store for the specified match type // //---------------------------------------------------------------------------- BOOL CChainPathObject::FindAndAddIssuersFromStoreByMatchType( IN DWORD dwMatchType, IN PCCHAINCALLCONTEXT pCallContext, IN BOOL fExternalStore, IN OPTIONAL HCERTSTORE hAdditionalStore, IN OPTIONAL HCERTSTORE hIssuerUrlStore ) { BOOL fResult; PCCERTOBJECT pCertObject = m_pCertObject; PCCERTCHAINENGINE pChainEngine = pCertObject->ChainEngine(); HCERTSTORE hAdditionalStoreToUse = NULL; HCERTSTORE hStore = NULL; PCCERT_CONTEXT pCertContext = NULL; DWORD dwFindType; const void *pvFindPara; CRYPT_DATA_BLOB DataBlob; CERT_INFO CertInfo; PCERT_AUTHORITY_KEY_ID_INFO pAuthKeyIdentifier; if (fExternalStore) { if (hIssuerUrlStore) { hStore = CertDuplicateStore(hIssuerUrlStore); if (hAdditionalStore) { hAdditionalStoreToUse = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL ); if (NULL == hAdditionalStoreToUse) goto OpenCollectionStoreError; if (!CertAddStoreToCollection(hAdditionalStoreToUse, hIssuerUrlStore, 0, 0)) goto AddToCollectionStoreError; if (!CertAddStoreToCollection(hAdditionalStoreToUse, hAdditionalStore, 0, 0)) goto AddToCollectionStoreError; } else hAdditionalStoreToUse = CertDuplicateStore(hIssuerUrlStore); } else { assert(hAdditionalStore); hStore = CertDuplicateStore(hAdditionalStore); hAdditionalStoreToUse = CertDuplicateStore(hAdditionalStore); } } else { hStore = CertDuplicateStore(pChainEngine->OtherStore()); if (hAdditionalStore) hAdditionalStoreToUse = CertDuplicateStore(hAdditionalStore); } switch (dwMatchType) { case CERT_EXACT_ISSUER_MATCH_TYPE: dwFindType = CERT_FIND_SUBJECT_CERT; pAuthKeyIdentifier = pCertObject->AuthorityKeyIdentifier(); CertInfo.Issuer = pAuthKeyIdentifier->CertIssuer; CertInfo.SerialNumber = pAuthKeyIdentifier->CertSerialNumber; pvFindPara = (const void *) &CertInfo; break; case CERT_KEYID_ISSUER_MATCH_TYPE: dwFindType = CERT_FIND_KEY_IDENTIFIER; pAuthKeyIdentifier = pCertObject->AuthorityKeyIdentifier(); pvFindPara = (const void *) &pAuthKeyIdentifier->KeyId; break; case CERT_NAME_ISSUER_MATCH_TYPE: dwFindType = CERT_FIND_SUBJECT_NAME; pvFindPara = (const void *) &pCertObject->CertContext()->pCertInfo->Issuer; break; case CERT_PUBKEY_ISSUER_MATCH_TYPE: dwFindType = CERT_FIND_PUBKEY_MD5_HASH; DataBlob.cbData = CHAINHASHLEN; DataBlob.pbData = pCertObject->IssuerPublicKeyHash(); pvFindPara = (const void *) &DataBlob; break; default: goto InvalidMatchType; } while (pCertContext = CertFindCertificateInStore( hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, // dwFindFlags dwFindType, pvFindPara, pCertContext )) { DWORD dwIssuerStatusFlags; PCCERTOBJECT pIssuer = NULL; if (!ChainCreateCertObject ( fExternalStore ? CERT_EXTERNAL_ISSUER_OBJECT_TYPE : CERT_CACHED_ISSUER_OBJECT_TYPE, pCallContext, pCertContext, NULL, // rgbCertHash &pIssuer )) goto CreateIssuerObjectError; fResult = m_pIssuerList->AddIssuer( pCallContext, hAdditionalStoreToUse, pIssuer ); pIssuer->Release(); if (!fResult) goto AddIssuerError; dwIssuerStatusFlags = pCertObject->IssuerStatusFlags(); if (CERT_PUBKEY_ISSUER_MATCH_TYPE != dwMatchType && (dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG)) { assert(dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG); // We will be called again using the PUBKEY match goto SuccessReturn; } } if (CRYPT_E_NOT_FOUND != GetLastError()) goto FindCertificateInStoreError; if (!fExternalStore) // All matching issuers from the engine's store should be in // the cache now. pCertObject->OrCachedMatchFlags(CERT_MATCH_TYPE_TO_FLAG(dwMatchType)); SuccessReturn: fResult = TRUE; CommonReturn: if (pCertContext) CertFreeCertificateContext(pCertContext); if (hAdditionalStoreToUse) CertCloseStore(hAdditionalStoreToUse, 0); if (hStore) CertCloseStore(hStore, 0); return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(OpenCollectionStoreError) TRACE_ERROR(AddToCollectionStoreError) SET_ERROR(InvalidMatchType, E_UNEXPECTED) TRACE_ERROR(CreateIssuerObjectError) TRACE_ERROR(AddIssuerError) TRACE_ERROR(FindCertificateInStoreError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::FindAndAddCtlIssuersFromCache, public // // Synopsis: find and add matching CTL issuers from the cache // //---------------------------------------------------------------------------- BOOL CChainPathObject::FindAndAddCtlIssuersFromCache ( IN PCCHAINCALLCONTEXT pCallContext, IN OPTIONAL HCERTSTORE hAdditionalStore ) { PCERT_OBJECT_CTL_CACHE_ENTRY pEntry; assert(m_pCertObject->IssuerStatusFlags() & CERT_ISSUER_SELF_SIGNED_FLAG); assert(!(m_pCertObject->IssuerStatusFlags() & CERT_ISSUER_TRUSTED_ROOT_FLAG)); pEntry = NULL; while (pEntry = m_pCertObject->NextCtlCacheEntry(pEntry)) { PCERT_TRUST_LIST_INFO pTrustListInfo = NULL; if (!SSCtlAllocAndCopyTrustListInfo( pEntry->pTrustListInfo, &pTrustListInfo )) return FALSE; if (!m_pIssuerList->AddCtlIssuer( pCallContext, hAdditionalStore, pEntry->pSSCtlObject, pTrustListInfo )) { SSCtlFreeTrustListInfo(pTrustListInfo); return FALSE; } } return TRUE; } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::FindAndAddCtlIssuersFromAdditionalStore, public // // Synopsis: find and add matching Ctl issuers from an additional store // //---------------------------------------------------------------------------- BOOL CChainPathObject::FindAndAddCtlIssuersFromAdditionalStore ( IN PCCHAINCALLCONTEXT pCallContext, IN HCERTSTORE hAdditionalStore ) { BOOL fResult; PCCTL_CONTEXT pCtlContext = NULL; PCSSCTLOBJECT pSSCtlObject = NULL; assert(hAdditionalStore); while (pCtlContext = CertEnumCTLsInStore(hAdditionalStore, pCtlContext)) { PCERT_TRUST_LIST_INFO pTrustListInfo = NULL; pSSCtlObject = NULL; if (!SSCtlCreateCtlObject( m_pCertObject->ChainEngine(), pCtlContext, TRUE, // fAdditionalStore &pSSCtlObject )) // Should look at the different errors continue; if (!pSSCtlObject->GetTrustListInfo( m_pCertObject->CertContext(), &pTrustListInfo )) { DWORD dwErr = GetLastError(); if (CRYPT_E_NOT_FOUND != dwErr) goto GetTrustListInfoError; else { pSSCtlObject->Release(); continue; } } if (!m_pIssuerList->AddCtlIssuer( pCallContext, hAdditionalStore, pSSCtlObject, pTrustListInfo )) { SSCtlFreeTrustListInfo(pTrustListInfo); goto AddCtlIssuerError; } pSSCtlObject->Release(); } fResult = TRUE; CommonReturn: return fResult; ErrorReturn: if (pCtlContext) CertFreeCTLContext(pCtlContext); if (pSSCtlObject) pSSCtlObject->Release(); fResult = FALSE; goto CommonReturn; TRACE_ERROR(GetTrustListInfoError) TRACE_ERROR(AddCtlIssuerError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::NextPath, public // // Synopsis: Get the next top path object for this end path object. // //---------------------------------------------------------------------------- PCCHAINPATHOBJECT CChainPathObject::NextPath ( IN PCCHAINCALLCONTEXT pCallContext, IN OPTIONAL PCCHAINPATHOBJECT pPrevTopPathObject ) { PCCHAINPATHOBJECT pTopPathObject; PCERT_ISSUER_ELEMENT pSubjectIssuerElement; PCCHAINPATHOBJECT pSubjectPathObject; DWORD dwFlags = pCallContext->CallFlags(); if (NULL == pPrevTopPathObject) { pSubjectIssuerElement = NULL; pSubjectPathObject = NULL; } else { // Find the next issuer for the issuer's subject certificate. // We iterate downward toward the end certificate while (TRUE) { pSubjectIssuerElement = pPrevTopPathObject->m_pDownIssuerElement; pSubjectPathObject = pPrevTopPathObject->m_pDownPathObject; // Set to NULL so it can be reused. Used to determine if // cyclic. pPrevTopPathObject->m_pDownPathObject = NULL; pPrevTopPathObject->m_fHasAdditionalStatus = FALSE; if (NULL == pSubjectPathObject) { // We have reached the end certificate without having a // next path SetLastError((DWORD) CRYPT_E_NOT_FOUND); goto NoPath; } assert(pSubjectIssuerElement); if (pSubjectIssuerElement->pCyclicSaveIssuer) { // Restore the issuer replaced by the cyclic issuer pSubjectIssuerElement->pIssuer = pSubjectIssuerElement->pCyclicSaveIssuer; pSubjectIssuerElement->pCyclicSaveIssuer = NULL; } // Move on to the next issuer for the subject. Skip low // quality issuers while (pSubjectIssuerElement = pSubjectPathObject->m_pIssuerList->NextElement( pSubjectIssuerElement)) { if ((dwFlags & CERT_CHAIN_DISABLE_PASS1_QUALITY_FILTERING) || pSubjectIssuerElement->dwPass1Quality >= pSubjectPathObject->m_dwPass1Quality) break; } if (pSubjectIssuerElement) // The subject has another issuer break; // Note, a untrusted self signed root without CTLs is equal and // possibly higher quality than having untrusted CTLs if ((pSubjectPathObject->m_TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) && (dwFlags & CERT_CHAIN_DISABLE_PASS1_QUALITY_FILTERING) && !(pSubjectPathObject->m_dwPass1Quality & CERT_QUALITY_HAS_TRUSTED_ROOT)) { pTopPathObject = pSubjectPathObject; pTopPathObject->m_pUpIssuerElement = NULL; goto SelfSignedRootInsteadOfCtlPathReturn; } // Find the next issuer for my subject pPrevTopPathObject = pSubjectPathObject; } } // Iterate upward until the TopPathObject's issuer list is empty or // we have detected a cyclic PathObject while (TRUE) { if (NULL == pSubjectIssuerElement) { // End (bottom) certificate pTopPathObject = this; pTopPathObject->m_dwChainIndex = 0; pTopPathObject->m_dwElementIndex = 0; } else { pTopPathObject = pSubjectIssuerElement->pIssuer; // Determine if cyclic. if (pTopPathObject->m_pDownPathObject || pTopPathObject == this) { // The returned Cyclic path won't have any issuers if (!ChainCreateCyclicPathObject( pCallContext, pTopPathObject, &pTopPathObject )) goto CreateCyclicPathObjectError; pSubjectIssuerElement->pCyclicSaveIssuer = pSubjectIssuerElement->pIssuer; pSubjectIssuerElement->pIssuer = pTopPathObject; } if (pSubjectPathObject->m_TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) { pTopPathObject->m_dwChainIndex = pSubjectPathObject->m_dwChainIndex + 1; pTopPathObject->m_dwElementIndex = 0; } else { pTopPathObject->m_dwChainIndex = pSubjectPathObject->m_dwChainIndex; pTopPathObject->m_dwElementIndex = pSubjectPathObject->m_dwElementIndex + 1; } pSubjectPathObject->m_pUpIssuerElement = pSubjectIssuerElement; } pTopPathObject->m_pDownIssuerElement = pSubjectIssuerElement; pTopPathObject->m_pDownPathObject = pSubjectPathObject; pSubjectPathObject = pTopPathObject; // Find the first issuer having sufficient quality pSubjectIssuerElement = NULL; while (pSubjectIssuerElement = pSubjectPathObject->m_pIssuerList->NextElement( pSubjectIssuerElement)) { if ((dwFlags & CERT_CHAIN_DISABLE_PASS1_QUALITY_FILTERING) || pSubjectIssuerElement->dwPass1Quality >= pSubjectPathObject->m_dwPass1Quality) { // For a CTL, check that we have an issuer if (NULL != pSubjectIssuerElement->pIssuer) break; else { assert(pSubjectIssuerElement->fCtlIssuer); } } } if (NULL == pSubjectIssuerElement) { pTopPathObject->m_pUpIssuerElement = NULL; break; } } SelfSignedRootInsteadOfCtlPathReturn: CommonReturn: return pTopPathObject; NoPath: ErrorReturn: pTopPathObject = NULL; goto CommonReturn; TRACE_ERROR(CreateCyclicPathObjectError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::CalculateAdditionalStatus, public // // Synopsis: calculate additional status bits based on time, usage, // revocation, ... // //---------------------------------------------------------------------------- VOID CChainPathObject::CalculateAdditionalStatus ( IN PCCHAINCALLCONTEXT pCallContext, IN HCERTSTORE hAllStore ) { PCERT_INFO pCertInfo = m_pCertObject->CertContext()->pCertInfo; FILETIME RequestedTime; FILETIME CurrentTime; assert(!m_fHasAdditionalStatus); memset(&m_AdditionalStatus, 0, sizeof(m_AdditionalStatus)); if (m_pwszExtendedErrorInfo) { PkiFree(m_pwszExtendedErrorInfo); m_pwszExtendedErrorInfo = NULL; } pCallContext->RequestedTime(&RequestedTime); pCallContext->CurrentTime(&CurrentTime); if (0 == m_dwChainIndex) { // First simple chain if (0 == m_dwElementIndex) { // End cert if (pCallContext->CallFlags() & CERT_CHAIN_TIMESTAMP_TIME) { // For time stamping, the end certificate needs to be valid // for both the time stamped and current times. if (0 != CertVerifyTimeValidity(&RequestedTime, pCertInfo) || 0 != CertVerifyTimeValidity(&CurrentTime, pCertInfo)) m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_TIME_VALID; } else { // End certificate needs to be valid for the requested time if (0 != CertVerifyTimeValidity(&RequestedTime, pCertInfo)) m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_TIME_VALID; } } else { // CA or root if (pCallContext->CallFlags() & CERT_CHAIN_TIMESTAMP_TIME) { // For time stamping, the CA or root needs to be valid using // current time if (0 != CertVerifyTimeValidity(&CurrentTime, pCertInfo)) m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_TIME_VALID; } else { // The CA or root needs to be valid using either the requested // or current time. Allowing current time is necessary for // cross certificate chains. if (!(0 == CertVerifyTimeValidity(&RequestedTime, pCertInfo) || 0 == CertVerifyTimeValidity(&CurrentTime, pCertInfo))) m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_TIME_VALID; } } } else { // CTL signer chains. Must be valid using current time. if (0 != CertVerifyTimeValidity(&CurrentTime, pCertInfo)) m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_TIME_VALID; } if (m_pDownIssuerElement) { PCERT_USAGE_MATCH pUsageToUse; CERT_USAGE_MATCH CtlUsage; LPSTR pszUsage = szOID_KP_CTL_USAGE_SIGNING; // Update subject's issuer status assert (m_pDownIssuerElement->pIssuer = this); if (0 != m_pDownPathObject->m_dwChainIndex) { // CTL path object memset(&CtlUsage, 0, sizeof(CtlUsage)); CtlUsage.dwType = USAGE_MATCH_TYPE_AND; CtlUsage.Usage.cUsageIdentifier = 1; CtlUsage.Usage.rgpszUsageIdentifier = &pszUsage; pUsageToUse = &CtlUsage; } else pUsageToUse = &pCallContext->ChainPara()->RequestedUsage; if (m_pDownIssuerElement->fCtlIssuer) { FILETIME CurrentTime; memset(&m_pDownIssuerElement->SubjectStatus, 0, sizeof(m_pDownIssuerElement->SubjectStatus)); pCallContext->CurrentTime(&CurrentTime); m_pDownIssuerElement->pCtlIssuerData->pSSCtlObject-> CalculateStatus( &CurrentTime, pUsageToUse, &m_pDownIssuerElement->SubjectStatus ); } else { CalculatePolicyConstraintsStatus(); CalculateBasicConstraintsStatus(); CalculateKeyUsageStatus(); CalculateNameConstraintsStatus(pUsageToUse); } } if (pCallContext->CallFlags() & CERT_CHAIN_REVOCATION_CHECK_ALL) { // For CTL signer chains, always use current time CalculateRevocationStatus( pCallContext, hAllStore, 0 == m_dwChainIndex ? &RequestedTime : &CurrentTime ); } m_fHasAdditionalStatus = TRUE; } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::CalculatePolicyConstraintsStatus, public // // Synopsis: calculate policy constraints additional status for this // issuer // //---------------------------------------------------------------------------- VOID CChainPathObject::CalculatePolicyConstraintsStatus () { PCHAIN_POLICIES_INFO pPoliciesInfo; DWORD i; assert (0 != m_dwElementIndex); pPoliciesInfo = m_pCertObject->PoliciesInfo(); for (i = 0; i < CHAIN_ISS_OR_APP_COUNT; i++ ) { PCERT_POLICY_CONSTRAINTS_INFO pConstraints = pPoliciesInfo->rgIssOrAppInfo[i].pConstraints; DWORD dwRequireSkipCerts; DWORD dwInhibitSkipCerts; PCCHAINPATHOBJECT pPathObject; if (NULL == pConstraints) continue; dwRequireSkipCerts = pConstraints->dwRequireExplicitPolicySkipCerts; dwInhibitSkipCerts = pConstraints->dwInhibitPolicyMappingSkipCerts; for (pPathObject = m_pDownPathObject; NULL != pPathObject && pPathObject->m_dwChainIndex == m_dwChainIndex; pPathObject = pPathObject->m_pDownPathObject) { PCHAIN_POLICIES_INFO pSubjectPoliciesInfo; pSubjectPoliciesInfo = pPathObject->m_pCertObject->PoliciesInfo(); if (pConstraints->fRequireExplicitPolicy) { if (0 < dwRequireSkipCerts) dwRequireSkipCerts--; else { if (NULL == pSubjectPoliciesInfo->rgIssOrAppInfo[i].pPolicy) { m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_INVALID_POLICY_CONSTRAINTS; goto RequireExplicitPolicyError; } } } if (pConstraints->fInhibitPolicyMapping) { if (0 < dwInhibitSkipCerts) dwInhibitSkipCerts--; else { if (pSubjectPoliciesInfo->rgIssOrAppInfo[i].pMappings) { m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_INVALID_POLICY_CONSTRAINTS; goto InhibitPolicyMappingError; } } } } } CommonReturn: return; ErrorReturn: goto CommonReturn; TRACE_ERROR(RequireExplicitPolicyError) TRACE_ERROR(InhibitPolicyMappingError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::CalculateBasicConstraintsStatus, public // // Synopsis: calculate basic constraints additional status for this // issuer // //---------------------------------------------------------------------------- VOID CChainPathObject::CalculateBasicConstraintsStatus () { PCERT_BASIC_CONSTRAINTS2_INFO pInfo; assert (0 != m_dwElementIndex); if (m_pCertObject->InfoFlags() & CHAIN_INVALID_BASIC_CONSTRAINTS_INFO_FLAG) { m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_INVALID_BASIC_CONSTRAINTS; } pInfo = m_pCertObject->BasicConstraintsInfo(); if (NULL == pInfo) return; if (!pInfo->fCA || (pInfo->fPathLenConstraint && m_dwElementIndex > pInfo->dwPathLenConstraint + 1)) { m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_INVALID_BASIC_CONSTRAINTS; goto BasicConstraintsError; } CommonReturn: return; ErrorReturn: goto CommonReturn; TRACE_ERROR(BasicConstraintsError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::CalculateKeyUsageStatus, public // // Synopsis: calculate key usage additional status for this // issuer // //---------------------------------------------------------------------------- VOID CChainPathObject::CalculateKeyUsageStatus () { PCRYPT_BIT_BLOB pKeyUsage; assert (0 != m_dwElementIndex); if (m_pCertObject->InfoFlags() & CHAIN_INVALID_KEY_USAGE_FLAG) { m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_IS_NOT_VALID_FOR_USAGE; } pKeyUsage = m_pCertObject->KeyUsage(); if (NULL == pKeyUsage) return; if (1 > pKeyUsage->cbData || 0 == (pKeyUsage->pbData[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) { m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_VALID_FOR_USAGE; goto KeyUsageError; } CommonReturn: return; ErrorReturn: goto CommonReturn; TRACE_ERROR(KeyUsageError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::CalculateNameConstraintsStatus, public // // Synopsis: calculate name constraints additional status for this // issuer // //---------------------------------------------------------------------------- VOID CChainPathObject::CalculateNameConstraintsStatus ( IN PCERT_USAGE_MATCH pUsageToUse ) { PCERT_NAME_CONSTRAINTS_INFO pIssuerInfo; PCHAIN_SUBJECT_NAME_CONSTRAINTS_INFO pSubjectInfo; PCERT_BASIC_CONSTRAINTS2_INFO pSubjectBasicInfo; PCCHAINPATHOBJECT pSubjectObject; DWORD dwErrorStatus = 0; assert (0 != m_dwElementIndex); if (m_pCertObject->InfoFlags() & CHAIN_INVALID_ISSUER_NAME_CONSTRAINTS_INFO_FLAG) { m_AdditionalStatus.dwErrorStatus |= CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_INVALID_NAME_CONSTRAINTS; ChainFormatAndAppendExtendedErrorInfo( &m_pwszExtendedErrorInfo, IDS_INVALID_ISSUER_NAME_CONSTRAINT_EXT ); } pIssuerInfo = m_pCertObject->IssuerNameConstraintsInfo(); if (NULL == pIssuerInfo) // No NameConstraint check return; // We only verify the name constraints on the end cert for (pSubjectObject = m_pDownPathObject; NULL != pSubjectObject && 0 != pSubjectObject->m_dwElementIndex; pSubjectObject = pSubjectObject->m_pDownPathObject) ; assert(pSubjectObject); assert(pSubjectObject->m_dwChainIndex == m_dwChainIndex); if (NULL == pSubjectObject) return; pSubjectBasicInfo = pSubjectObject->m_pCertObject->BasicConstraintsInfo(); if (pSubjectBasicInfo && pSubjectBasicInfo->fCA) // End cert is a CA. return; pSubjectInfo = pSubjectObject->m_pCertObject->SubjectNameConstraintsInfo(); if (pSubjectInfo->fInvalid) { dwErrorStatus |= CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_INVALID_NAME_CONSTRAINTS; ChainFormatAndAppendExtendedErrorInfo( &m_pwszExtendedErrorInfo, IDS_INVALID_SUBJECT_NAME_CONSTRAINT_INFO ); goto InvalidNameConstraints; } if (pSubjectInfo->pAltNameInfo) { // Loop through all the AltName entries. There needs to be a // name constraint for each entry. DWORD cEntry; PCERT_ALT_NAME_ENTRY pEntry; cEntry = pSubjectInfo->pAltNameInfo->cAltEntry; pEntry = pSubjectInfo->pAltNameInfo->rgAltEntry; for ( ; 0 < cEntry; cEntry--, pEntry++) { BOOL fSupported; // Check if a NameConstraint for this entry choice is supported fSupported = FALSE; switch (pEntry->dwAltNameChoice) { case CERT_ALT_NAME_OTHER_NAME: // Only support the UPN OID if (0 == strcmp(pEntry->pOtherName->pszObjId, szOID_NT_PRINCIPAL_NAME)) fSupported = TRUE; break; case CERT_ALT_NAME_RFC822_NAME: case CERT_ALT_NAME_DNS_NAME: case CERT_ALT_NAME_URL: case CERT_ALT_NAME_DIRECTORY_NAME: fSupported = TRUE; break; case CERT_ALT_NAME_IP_ADDRESS: // Only support 4 or 16 byte IP addresses if (4 == pEntry->IPAddress.cbData || 16 == pEntry->IPAddress.cbData) fSupported = TRUE; break; case CERT_ALT_NAME_X400_ADDRESS: case CERT_ALT_NAME_EDI_PARTY_NAME: case CERT_ALT_NAME_REGISTERED_ID: default: // Not supported break; } if (!fSupported) { dwErrorStatus |= CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT; ChainFormatAndAppendNameConstraintsAltNameEntryFixup( &m_pwszExtendedErrorInfo, pEntry, IDS_NOT_SUPPORTED_ENTRY_NAME_CONSTRAINT ); } else dwErrorStatus |= ChainCalculateNameConstraintsErrorStatusForAltNameEntry( pEntry, pIssuerInfo, &m_pwszExtendedErrorInfo); } } if (pSubjectInfo->pUnicodeNameInfo) { // Check as a DIRECTORY_NAME AltNameEntry choice. The DIRECTORY_NAME // fixup expects the DirectoryName.pbData to be the decoded and // fixup'ed UnicodeNameInfo. CERT_ALT_NAME_ENTRY Entry; Entry.dwAltNameChoice = CERT_ALT_NAME_DIRECTORY_NAME; Entry.DirectoryName.pbData = (BYTE *) pSubjectInfo->pUnicodeNameInfo; dwErrorStatus |= ChainCalculateNameConstraintsErrorStatusForAltNameEntry( &Entry, pIssuerInfo, &m_pwszExtendedErrorInfo); } if (pSubjectInfo->pEmailAttr) { // The SubjectAltName doesn't have an email choice. However, there is an // email attribute in the Subject UnicodeNameInfo. // // Check as a CERT_ALT_NAME_RFC822_NAME AltNameEntry choice. The // RFC822 fixup uses the DirectoryName.pbData and DirectoryName.cbData // to contain the pointer to and length of the unicode string. CERT_ALT_NAME_ENTRY Entry; Entry.dwAltNameChoice = CERT_ALT_NAME_RFC822_NAME; Entry.DirectoryName = pSubjectInfo->pEmailAttr->Value; dwErrorStatus |= ChainCalculateNameConstraintsErrorStatusForAltNameEntry( &Entry, pIssuerInfo, &m_pwszExtendedErrorInfo); } if (!pSubjectInfo->fHasDnsAltNameEntry && NULL != pSubjectInfo->pUnicodeNameInfo && ChainIsOIDInUsage(szOID_PKIX_KP_SERVER_AUTH, &pUsageToUse->Usage)) { // The SubjectAltName doesn't have a DNS choice and we are building // a ServerAuth chain. // Need to check all the CN components in the UnicodeNameInfo. DWORD cRDN; PCERT_RDN pRDN; cRDN = pSubjectInfo->pUnicodeNameInfo->cRDN; pRDN = pSubjectInfo->pUnicodeNameInfo->rgRDN; for ( ; cRDN > 0; cRDN--, pRDN++) { DWORD cAttr = pRDN->cRDNAttr; PCERT_RDN_ATTR pAttr = pRDN->rgRDNAttr; for ( ; cAttr > 0; cAttr--, pAttr++) { if (!IS_CERT_RDN_CHAR_STRING(pAttr->dwValueType)) continue; if (0 == strcmp(pAttr->pszObjId, szOID_COMMON_NAME)) { // // Check as a CERT_ALT_NAME_DNS_NAME AltNameEntry choice. // The DNS fixup uses the DirectoryName.pbData and // DirectoryName.cbData to contain the pointer to and // length of the unicode string. CERT_ALT_NAME_ENTRY Entry; Entry.dwAltNameChoice = CERT_ALT_NAME_DNS_NAME; Entry.DirectoryName = pAttr->Value; dwErrorStatus |= ChainCalculateNameConstraintsErrorStatusForAltNameEntry( &Entry, pIssuerInfo, &m_pwszExtendedErrorInfo); } } } } CommonReturn: if (0 == dwErrorStatus) m_AdditionalStatus.dwInfoStatus |= CERT_TRUST_HAS_VALID_NAME_CONSTRAINTS; else m_AdditionalStatus.dwErrorStatus |= dwErrorStatus; return; ErrorReturn: goto CommonReturn; TRACE_ERROR(InvalidNameConstraints) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::CalculateRevocationStatus, public // // Synopsis: calculate additional status bits based on revocation // //---------------------------------------------------------------------------- VOID CChainPathObject::CalculateRevocationStatus ( IN PCCHAINCALLCONTEXT pCallContext, IN HCERTSTORE hCrlStore, IN LPFILETIME pTime ) { CERT_REVOCATION_PARA RevPara; CERT_REVOCATION_STATUS RevStatus; DWORD dwRevFlags; DWORD dwFlags = pCallContext->CallFlags(); PCERT_CHAIN_PARA pChainPara = pCallContext->ChainPara(); FILETIME CurrentTime; assert(dwFlags & CERT_CHAIN_REVOCATION_CHECK_ALL); memset( &RevPara, 0, sizeof( RevPara ) ); RevPara.cbSize = sizeof( RevPara ); RevPara.hCrlStore = hCrlStore; RevPara.pftTimeToUse = pTime; RevPara.dwUrlRetrievalTimeout = pCallContext->RevocationUrlRetrievalTimeout(); RevPara.fCheckFreshnessTime = pChainPara->fCheckRevocationFreshnessTime; RevPara.dwFreshnessTime = pChainPara->dwRevocationFreshnessTime; pCallContext->CurrentTime(&CurrentTime); RevPara.pftCurrentTime = &CurrentTime; memset( &RevStatus, 0, sizeof( RevStatus ) ); RevStatus.cbSize = sizeof( RevStatus ); dwRevFlags = 0; if (dwFlags & CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY) dwRevFlags |= CERT_VERIFY_CACHE_ONLY_BASED_REVOCATION; if (dwFlags & CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT) dwRevFlags |= CERT_VERIFY_REV_ACCUMULATIVE_TIMEOUT_FLAG; if (!m_fHasRevocationInfo) { BOOL fHasRevocationInfo = FALSE; if (m_TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) { BOOL fDoRevocation = FALSE; if (dwFlags & CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT) { ; } else if (dwFlags & CERT_CHAIN_REVOCATION_CHECK_END_CERT) { if (0 == m_dwChainIndex && 0 == m_dwElementIndex) fDoRevocation = TRUE; } else { assert(dwFlags & CERT_CHAIN_REVOCATION_CHECK_CHAIN); fDoRevocation = TRUE; } if (fDoRevocation) { PCCERT_CONTEXT pSubjectCert = m_pCertObject->CertContext(); RevPara.pIssuerCert = m_pCertObject->CertContext(); RevPara.pCrlInfo = &m_RevocationCrlInfo; m_RevocationCrlInfo.cbSize = sizeof(m_RevocationCrlInfo); RevStatus.dwError = (DWORD) CRYPT_E_REVOCATION_OFFLINE; CertVerifyRevocation( X509_ASN_ENCODING, CERT_CONTEXT_REVOCATION_TYPE, 1, (LPVOID *) &pSubjectCert, dwRevFlags, &RevPara, &RevStatus ); fHasRevocationInfo = TRUE; } } else if (NULL == m_pUpIssuerElement) { if (dwFlags & CERT_CHAIN_REVOCATION_CHECK_END_CERT) { if (0 == m_dwChainIndex && 0 == m_dwElementIndex) fHasRevocationInfo = TRUE; } else { fHasRevocationInfo = TRUE; } if (fHasRevocationInfo) { RevStatus.dwError = (DWORD) CRYPT_E_REVOCATION_OFFLINE; } } if (fHasRevocationInfo) { ChainUpdateRevocationInfo(&RevStatus, &m_RevocationInfo, &m_TrustStatus); m_fHasRevocationInfo = TRUE; memset( &RevStatus, 0, sizeof( RevStatus ) ); RevStatus.cbSize = sizeof( RevStatus ); } } if (m_pDownIssuerElement && !m_pDownIssuerElement->fCtlIssuer && !m_pDownIssuerElement->fHasRevocationInfo) { BOOL fDoRevocation = FALSE; if (dwFlags & CERT_CHAIN_REVOCATION_CHECK_END_CERT) { if (0 == m_dwChainIndex && 1 == m_dwElementIndex) fDoRevocation = TRUE; } else { fDoRevocation = TRUE; } if (fDoRevocation) { PCCERT_CONTEXT pSubjectCert = m_pDownPathObject->m_pCertObject->CertContext(); RevPara.pIssuerCert = m_pCertObject->CertContext(); RevPara.pCrlInfo = &m_pDownIssuerElement->RevocationCrlInfo; m_pDownIssuerElement->RevocationCrlInfo.cbSize = sizeof(m_pDownIssuerElement->RevocationCrlInfo); RevStatus.dwError = (DWORD) CRYPT_E_REVOCATION_OFFLINE; CertVerifyRevocation( X509_ASN_ENCODING, CERT_CONTEXT_REVOCATION_TYPE, 1, (LPVOID *) &pSubjectCert, dwRevFlags, &RevPara, &RevStatus ); ChainUpdateRevocationInfo(&RevStatus, &m_pDownIssuerElement->RevocationInfo, &m_pDownIssuerElement->SubjectStatus); m_pDownIssuerElement->fHasRevocationInfo = TRUE; } } } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::CreateChainContextFromPath, public // // Synopsis: Create the chain context for chain path ending in the // specified top path object. Also calculates the chain's // quality value. // //---------------------------------------------------------------------------- PINTERNAL_CERT_CHAIN_CONTEXT CChainPathObject::CreateChainContextFromPath ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCHAINPATHOBJECT pTopPathObject ) { // Single PkiZeroAlloc for all of the following: PINTERNAL_CERT_CHAIN_CONTEXT pContext = NULL; PCERT_SIMPLE_CHAIN *ppChain; PCERT_SIMPLE_CHAIN pChain; PCERT_CHAIN_ELEMENT *ppElement; PCERT_CHAIN_ELEMENT pElement; DWORD cChain; DWORD cTotalElement; DWORD cbTotal; PCCHAINPATHOBJECT pPathObject; DWORD dwQuality; DWORD dwChainErrorStatus; DWORD dwChainInfoStatus; PCERT_ENHKEY_USAGE pAppUsage; BOOL fHasContextRevocationFreshnessTime; // Restricted usage info that gets propogated downward CHAIN_RESTRICTED_USAGE_INFO RestrictedUsageInfo; memset(&RestrictedUsageInfo, 0, sizeof(RestrictedUsageInfo)); cChain = pTopPathObject->m_dwChainIndex + 1; if (1 == cChain) { cTotalElement = pTopPathObject->m_dwElementIndex + 1; } else { cTotalElement = 0; for (pPathObject = pTopPathObject; NULL != pPathObject; pPathObject = pPathObject->m_pDownPathObject) cTotalElement++; } cbTotal = sizeof(INTERNAL_CERT_CHAIN_CONTEXT) + sizeof(PCERT_SIMPLE_CHAIN) * cChain + sizeof(CERT_SIMPLE_CHAIN) * cChain + sizeof(PCERT_CHAIN_ELEMENT) * cTotalElement + sizeof(CERT_CHAIN_ELEMENT) * cTotalElement; pContext = (PINTERNAL_CERT_CHAIN_CONTEXT) PkiZeroAlloc(cbTotal); if (NULL == pContext) goto OutOfMemory; ppChain = (PCERT_SIMPLE_CHAIN *) &pContext[1]; pChain = (PCERT_SIMPLE_CHAIN) &ppChain[cChain]; ppElement = (PCERT_CHAIN_ELEMENT *) &pChain[cChain]; pElement = (PCERT_CHAIN_ELEMENT) &ppElement[cTotalElement]; pContext->cRefs = 1; pContext->ChainContext.cbSize = sizeof(CERT_CHAIN_CONTEXT); pContext->ChainContext.cChain = cChain; pContext->ChainContext.rgpChain = ppChain; if (1 < cChain ) pContext->ChainContext.TrustStatus.dwInfoStatus |= CERT_TRUST_IS_COMPLEX_CHAIN; // Default to having preferred issuers pContext->ChainContext.TrustStatus.dwInfoStatus |= CERT_TRUST_HAS_PREFERRED_ISSUER; // Default to having revocation freshness time fHasContextRevocationFreshnessTime = TRUE; // Work our way from the top downward pPathObject = pTopPathObject; ppChain += cChain - 1; pChain += cChain - 1; ppElement += cTotalElement - 1; pElement += cTotalElement - 1; if (!(pTopPathObject->m_TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED)) pChain->TrustStatus.dwErrorStatus |= CERT_TRUST_IS_PARTIAL_CHAIN; for ( ; 0 < cChain; cChain--, ppChain--, pChain--) { BOOL fHasChainRevocationFreshnessTime; DWORD cElement; *ppChain = pChain; pChain->cbSize = sizeof(CERT_SIMPLE_CHAIN); // Default to having preferred issuers pChain->TrustStatus.dwInfoStatus |= CERT_TRUST_HAS_PREFERRED_ISSUER; // Default to having revocation freshness time fHasChainRevocationFreshnessTime = TRUE; cElement = pPathObject->m_dwElementIndex + 1; pChain->cElement = cElement; pChain->rgpElement = ppElement - (cElement - 1); for ( ; 0 < cElement; cElement--, cTotalElement--, ppElement--, pElement--, pPathObject = pPathObject->m_pDownPathObject) { assert(pPathObject); *ppElement = pElement; pElement->cbSize = sizeof(CERT_CHAIN_ELEMENT); if (!pPathObject->UpdateChainContextUsageForPathObject ( pCallContext, pChain, pElement, &RestrictedUsageInfo )) goto UpdateChainContextUsageForPathObjectError; // This must be last. It updates the chain's TrustStatus // from the element's TrustStatus. if (!pPathObject->UpdateChainContextFromPathObject ( pCallContext, pChain, pElement )) goto UpdateChainContextFromPathObjectError; // Remember the largest revocation freshness time for the // simple chain and the chain context. if (pElement->pRevocationInfo && fHasChainRevocationFreshnessTime) { PCERT_REVOCATION_INFO pRevInfo = pElement->pRevocationInfo; if (pRevInfo->fHasFreshnessTime) { if (pRevInfo->dwFreshnessTime > pChain->dwRevocationFreshnessTime) pChain->dwRevocationFreshnessTime = pRevInfo->dwFreshnessTime; pChain->fHasRevocationFreshnessTime = TRUE; if (fHasContextRevocationFreshnessTime) { if (pRevInfo->dwFreshnessTime > pContext->ChainContext.dwRevocationFreshnessTime) pContext->ChainContext.dwRevocationFreshnessTime = pRevInfo->dwFreshnessTime; pContext->ChainContext.fHasRevocationFreshnessTime = TRUE; } } else if (CRYPT_E_NO_REVOCATION_CHECK != pRevInfo->dwRevocationResult) { fHasChainRevocationFreshnessTime = FALSE; pChain->fHasRevocationFreshnessTime = FALSE; fHasContextRevocationFreshnessTime = FALSE; pContext->ChainContext.fHasRevocationFreshnessTime = FALSE; } } CertPerfIncrementChainElementCount(); } ChainUpdateSummaryStatusByTrustStatus( &pContext->ChainContext.TrustStatus, &pChain->TrustStatus); ChainFreeAndClearRestrictedUsageInfo(&RestrictedUsageInfo); } assert(0 == cTotalElement); // Calculate chain quality value dwQuality = 0; dwChainErrorStatus = pContext->ChainContext.TrustStatus.dwErrorStatus; dwChainInfoStatus = pContext->ChainContext.TrustStatus.dwInfoStatus; if (!(dwChainErrorStatus & CERT_TRUST_IS_NOT_TIME_VALID) && !(dwChainErrorStatus & CERT_TRUST_CTL_IS_NOT_TIME_VALID)) dwQuality |= CERT_QUALITY_TIME_VALID; if (!(dwChainErrorStatus & CERT_TRUST_IS_NOT_VALID_FOR_USAGE) && !(dwChainErrorStatus & CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE)) dwQuality |= CERT_QUALITY_MEETS_USAGE_CRITERIA; pAppUsage = pContext->ChainContext.rgpChain[0]->rgpElement[0]->pApplicationUsage; if (NULL == pAppUsage || 0 != pAppUsage->cUsageIdentifier) dwQuality |= CERT_QUALITY_HAS_APPLICATION_USAGE; if (!(dwChainErrorStatus & CERT_TRUST_IS_UNTRUSTED_ROOT)) dwQuality |= CERT_QUALITY_HAS_TRUSTED_ROOT; if (!(dwChainErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID) && !(dwChainErrorStatus & CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID)) dwQuality |= CERT_QUALITY_SIGNATURE_VALID; if (!(dwChainErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN)) dwQuality |= CERT_QUALITY_COMPLETE_CHAIN; if (!(dwChainErrorStatus & CERT_TRUST_IS_REVOKED)) dwQuality |= CERT_QUALITY_NOT_REVOKED; if (!(dwChainErrorStatus & CERT_TRUST_IS_OFFLINE_REVOCATION) && !(dwChainErrorStatus & CERT_TRUST_IS_REVOKED)) dwQuality |= CERT_QUALITY_ONLINE_REVOCATION; if (!(dwChainErrorStatus & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) && !(dwChainErrorStatus & CERT_TRUST_IS_REVOKED)) dwQuality |= CERT_QUALITY_CHECK_REVOCATION; if (!(dwChainInfoStatus & CERT_TRUST_IS_COMPLEX_CHAIN)) dwQuality |= CERT_QUALITY_SIMPLE_CHAIN; if (dwChainInfoStatus & CERT_TRUST_HAS_PREFERRED_ISSUER) dwQuality |= CERT_QUALITY_PREFERRED_ISSUER; if (dwChainInfoStatus & CERT_TRUST_HAS_ISSUANCE_CHAIN_POLICY) dwQuality |= CERT_QUALITY_HAS_ISSUANCE_CHAIN_POLICY; if (!(dwChainErrorStatus & (CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY | CERT_TRUST_INVALID_POLICY_CONSTRAINTS))) dwQuality |= CERT_QUALITY_POLICY_CONSTRAINTS_VALID; if (!(dwChainErrorStatus & CERT_TRUST_INVALID_BASIC_CONSTRAINTS)) dwQuality |= CERT_QUALITY_BASIC_CONSTRAINTS_VALID; if (dwChainInfoStatus & CERT_TRUST_HAS_VALID_NAME_CONSTRAINTS) dwQuality |= CERT_QUALITY_HAS_NAME_CONSTRAINTS; if (!(dwChainErrorStatus & (CERT_TRUST_INVALID_NAME_CONSTRAINTS | CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT | CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT))) dwQuality |= CERT_QUALITY_NAME_CONSTRAINTS_VALID; if (!(dwChainErrorStatus & (CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT | CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT))) dwQuality |= CERT_QUALITY_NAME_CONSTRAINTS_MET; pContext->dwQuality = dwQuality; CertPerfIncrementChainCount(); CommonReturn: return pContext; ErrorReturn: if (pContext) { ChainReleaseInternalChainContext(pContext); pContext = NULL; } ChainFreeAndClearRestrictedUsageInfo(&RestrictedUsageInfo); goto CommonReturn; SET_ERROR(OutOfMemory, E_OUTOFMEMORY) TRACE_ERROR(UpdateChainContextUsageForPathObjectError) TRACE_ERROR(UpdateChainContextFromPathObjectError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::UpdateChainContextUsageForPathObject, public // // Synopsis: update the chain context usage information for this // path object. // //---------------------------------------------------------------------------- BOOL CChainPathObject::UpdateChainContextUsageForPathObject ( IN PCCHAINCALLCONTEXT pCallContext, IN OUT PCERT_SIMPLE_CHAIN pChain, IN OUT PCERT_CHAIN_ELEMENT pElement, IN OUT PCHAIN_RESTRICTED_USAGE_INFO pRestrictedUsageInfo ) { BOOL fResult; PCHAIN_POLICIES_INFO pPoliciesInfo = m_pCertObject->PoliciesInfo(); CERT_USAGE_MATCH CtlUsage; PCERT_USAGE_MATCH pUsageToUse; LPSTR pszUsage = szOID_KP_CTL_USAGE_SIGNING; PCERT_ENHKEY_USAGE pIssUsage; PCERT_ENHKEY_USAGE pAppUsage; PCERT_ENHKEY_USAGE pPropUsage; DWORD dwIssFlags; DWORD dwAppFlags; static const CERT_ENHKEY_USAGE NoUsage = { 0, NULL }; // Update the usage to use for the second and subsequent chains if (0 != m_dwChainIndex) { // CTL path object memset(&CtlUsage, 0, sizeof(CtlUsage)); CtlUsage.dwType = USAGE_MATCH_TYPE_AND; CtlUsage.Usage.cUsageIdentifier = 1; CtlUsage.Usage.rgpszUsageIdentifier = &pszUsage; pUsageToUse = &CtlUsage; } else { pUsageToUse = &pCallContext->ChainPara()->RequestedUsage; } dwIssFlags = pPoliciesInfo->rgIssOrAppInfo[CHAIN_ISS_INDEX].dwFlags; dwAppFlags = pPoliciesInfo->rgIssOrAppInfo[CHAIN_APP_INDEX].dwFlags; // Update TrustStatus to reflect any policy decoding errors if ((dwIssFlags & CHAIN_INVALID_POLICY_FLAG) || (dwAppFlags & CHAIN_INVALID_POLICY_FLAG)) pElement->TrustStatus.dwErrorStatus |= CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_INVALID_POLICY_CONSTRAINTS; // Issuance :: restricted and mapped usage pIssUsage = pPoliciesInfo->rgIssOrAppInfo[CHAIN_ISS_INDEX].pUsage; if (NULL == pIssUsage) { // NULL => Any Usage // Only allow any usage for self signed roots or certs having // the CertPolicies extension. Otherwise, treat as having no usage. if (!(m_TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) && NULL == pPoliciesInfo->rgIssOrAppInfo[CHAIN_ISS_INDEX].pPolicy) pIssUsage = (PCERT_ENHKEY_USAGE) &NoUsage; } if (!ChainCalculateRestrictedUsage ( pIssUsage, pPoliciesInfo->rgIssOrAppInfo[CHAIN_ISS_INDEX].pMappings, &pRestrictedUsageInfo->pIssuanceRestrictedUsage, &pRestrictedUsageInfo->pIssuanceMappedUsage, &pRestrictedUsageInfo->rgdwIssuanceMappedIndex )) goto CalculateIssuanceRestrictedUsageError; if (!ChainAllocAndCopyUsage( pRestrictedUsageInfo->pIssuanceRestrictedUsage, &pElement->pIssuanceUsage )) goto AllocAndCopyUsageError; if (0 != m_dwElementIndex) { PCERT_POLICY_CONSTRAINTS_INFO pConstraints = pPoliciesInfo->rgIssOrAppInfo[CHAIN_ISS_INDEX].pConstraints; if (pConstraints && pConstraints->fRequireExplicitPolicy && m_dwElementIndex > pConstraints->dwRequireExplicitPolicySkipCerts) pRestrictedUsageInfo->fRequireIssuancePolicy = TRUE; } else { // For the end cert, update the require issuance chain policy // TrustStatus. Also, check the requested issuance policy. if (pRestrictedUsageInfo->fRequireIssuancePolicy) { if (pRestrictedUsageInfo->pIssuanceRestrictedUsage && 0 == pRestrictedUsageInfo->pIssuanceRestrictedUsage->cUsageIdentifier) { // Must have either ANY_POLICY or some policy OIDs pChain->TrustStatus.dwErrorStatus |= CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY; } else if (pPoliciesInfo->rgIssOrAppInfo[CHAIN_ISS_INDEX].pPolicy) { pChain->TrustStatus.dwInfoStatus |= CERT_TRUST_HAS_ISSUANCE_CHAIN_POLICY; } } pIssUsage = pElement->pIssuanceUsage; if (pIssUsage) { PCERT_USAGE_MATCH pRequestedIssuancePolicy = &pCallContext->ChainPara()->RequestedIssuancePolicy; ChainGetUsageStatus( &pRequestedIssuancePolicy->Usage, pIssUsage, pRequestedIssuancePolicy->dwType, &pElement->TrustStatus ); } } if (USAGE_MATCH_TYPE_OR == pUsageToUse->dwType && 1 < pUsageToUse->Usage.cUsageIdentifier) { // For "OR" match type request, we can't use restricted property usage pPropUsage = pPoliciesInfo->pPropertyUsage; // For "OR" match type request, we only use restricted application // usage upon seeing policy mappings. if (pRestrictedUsageInfo->pApplicationMappedUsage || pPoliciesInfo->rgIssOrAppInfo[CHAIN_APP_INDEX].pMappings) { if (!ChainCalculateRestrictedUsage ( pPoliciesInfo->rgIssOrAppInfo[CHAIN_APP_INDEX].pUsage, pPoliciesInfo->rgIssOrAppInfo[CHAIN_APP_INDEX].pMappings, &pRestrictedUsageInfo->pApplicationRestrictedUsage, &pRestrictedUsageInfo->pApplicationMappedUsage, &pRestrictedUsageInfo->rgdwApplicationMappedIndex )) goto CalculateApplicationRestrictedUsageError; pAppUsage = pRestrictedUsageInfo->pApplicationRestrictedUsage; } else pAppUsage = pPoliciesInfo->rgIssOrAppInfo[CHAIN_APP_INDEX].pUsage; } else { // Restricted property and application usage PCERT_ENHKEY_USAGE pPropMappedUsage = NULL; LPDWORD pdwPropMappedIndex = NULL; fResult = ChainCalculateRestrictedUsage ( pPoliciesInfo->pPropertyUsage, NULL, // pMappings &pRestrictedUsageInfo->pPropertyRestrictedUsage, &pPropMappedUsage, &pdwPropMappedIndex ); assert(NULL == pPropMappedUsage && NULL == pdwPropMappedIndex); if (!fResult) goto CalculatePropertyRestrictedUsageError; pPropUsage = pRestrictedUsageInfo->pPropertyRestrictedUsage; if (!ChainCalculateRestrictedUsage ( pPoliciesInfo->rgIssOrAppInfo[CHAIN_APP_INDEX].pUsage, pPoliciesInfo->rgIssOrAppInfo[CHAIN_APP_INDEX].pMappings, &pRestrictedUsageInfo->pApplicationRestrictedUsage, &pRestrictedUsageInfo->pApplicationMappedUsage, &pRestrictedUsageInfo->rgdwApplicationMappedIndex )) goto CalculateApplicationRestrictedUsageError; pAppUsage = pRestrictedUsageInfo->pApplicationRestrictedUsage; } // The element's application usage includes the intersection with // the property usage if (NULL == pAppUsage) { if (!ChainAllocAndCopyUsage( pPropUsage, &pElement->pApplicationUsage )) goto AllocAndCopyUsageError; } else { if (!ChainAllocAndCopyUsage( pAppUsage, &pElement->pApplicationUsage )) goto AllocAndCopyUsageError; if (pPropUsage) // Remove OIDs not also in the property usage ChainIntersectUsages(pPropUsage, pElement->pApplicationUsage); } // Check the requested usage pAppUsage = pElement->pApplicationUsage; if (pAppUsage) ChainGetUsageStatus( &pUsageToUse->Usage, pAppUsage, pUsageToUse->dwType, &pElement->TrustStatus ); fResult = TRUE; CommonReturn: return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(CalculateIssuanceRestrictedUsageError) TRACE_ERROR(AllocAndCopyUsageError) TRACE_ERROR(CalculateApplicationRestrictedUsageError) TRACE_ERROR(CalculatePropertyRestrictedUsageError) } //+--------------------------------------------------------------------------- // // Member: CChainPathObject::UpdateChainContextFromPathObject, public // // Synopsis: update the chain context using information from this // path object. // //---------------------------------------------------------------------------- BOOL CChainPathObject::UpdateChainContextFromPathObject ( IN PCCHAINCALLCONTEXT pCallContext, IN OUT PCERT_SIMPLE_CHAIN pChain, IN OUT PCERT_CHAIN_ELEMENT pElement ) { BOOL fResult; PCERT_REVOCATION_INFO pRevocationInfo = NULL; PCERT_REVOCATION_CRL_INFO pRevocationCrlInfo = NULL; ChainOrInStatusBits(&pElement->TrustStatus, &m_TrustStatus); assert(m_fHasAdditionalStatus); ChainOrInStatusBits(&pElement->TrustStatus, &m_AdditionalStatus); if (m_pUpIssuerElement) { if (m_pUpIssuerElement->fCtlIssuer) { ChainOrInStatusBits(&pChain->TrustStatus, &m_pUpIssuerElement->SubjectStatus); assert(pElement->TrustStatus.dwErrorStatus & CERT_TRUST_IS_UNTRUSTED_ROOT); pElement->TrustStatus.dwErrorStatus &= ~CERT_TRUST_IS_UNTRUSTED_ROOT; if (!SSCtlAllocAndCopyTrustListInfo( m_pUpIssuerElement->pCtlIssuerData->pTrustListInfo, &pChain->pTrustListInfo )) goto AllocAndCopyTrustListInfoError; } else { ChainOrInStatusBits(&pElement->TrustStatus, &m_pUpIssuerElement->SubjectStatus); } } pRevocationInfo = NULL; if (m_fHasRevocationInfo) { pRevocationInfo = &m_RevocationInfo; pRevocationCrlInfo = &m_RevocationCrlInfo; } else if (m_pUpIssuerElement && m_pUpIssuerElement->fHasRevocationInfo) { pRevocationInfo = &m_pUpIssuerElement->RevocationInfo; pRevocationCrlInfo = &m_pUpIssuerElement->RevocationCrlInfo; } if (pRevocationInfo) { pElement->pRevocationInfo = new CERT_REVOCATION_INFO; if (NULL == pElement->pRevocationInfo) goto OutOfMemory; memset(pElement->pRevocationInfo, 0, sizeof(CERT_REVOCATION_INFO)); pElement->pRevocationInfo->cbSize = sizeof(CERT_REVOCATION_INFO); pElement->pRevocationInfo->dwRevocationResult = pRevocationInfo->dwRevocationResult; pElement->pRevocationInfo->fHasFreshnessTime = pRevocationInfo->fHasFreshnessTime; pElement->pRevocationInfo->dwFreshnessTime = pRevocationInfo->dwFreshnessTime; if (NULL != pRevocationCrlInfo->pBaseCrlContext) { PCERT_REVOCATION_CRL_INFO pCrlInfo; pCrlInfo = new CERT_REVOCATION_CRL_INFO; if (NULL == pCrlInfo) goto OutOfMemory; pElement->pRevocationInfo->pCrlInfo = pCrlInfo; memcpy(pCrlInfo, pRevocationCrlInfo, sizeof(*pCrlInfo)); assert(pCrlInfo->cbSize = sizeof(*pCrlInfo)); pCrlInfo->pBaseCrlContext = CertDuplicateCRLContext( pRevocationCrlInfo->pBaseCrlContext); if (NULL != pRevocationCrlInfo->pDeltaCrlContext) pCrlInfo->pDeltaCrlContext = CertDuplicateCRLContext( pRevocationCrlInfo->pDeltaCrlContext); } } if (m_pwszExtendedErrorInfo) { DWORD cbExtendedErrorInfo; LPWSTR pwszExtendedErrorInfo; cbExtendedErrorInfo = (wcslen(m_pwszExtendedErrorInfo) + 1) * sizeof(WCHAR); if (NULL == (pwszExtendedErrorInfo = (LPWSTR) PkiNonzeroAlloc( cbExtendedErrorInfo))) goto OutOfMemory; memcpy(pwszExtendedErrorInfo, m_pwszExtendedErrorInfo, cbExtendedErrorInfo); pElement->pwszExtendedErrorInfo = pwszExtendedErrorInfo; } pElement->pCertContext = CertDuplicateCertificateContext( m_pCertObject->CertContext()); ChainUpdateSummaryStatusByTrustStatus(&pChain->TrustStatus, &pElement->TrustStatus); fResult = TRUE; CommonReturn: return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(AllocAndCopyTrustListInfoError) SET_ERROR(OutOfMemory, E_OUTOFMEMORY) } //+=========================================================================== // CCertIssuerList methods //============================================================================ //+--------------------------------------------------------------------------- // // Member: CCertIssuerList::CCertIssuerList, public // // Synopsis: Constructor // //---------------------------------------------------------------------------- CCertIssuerList::CCertIssuerList (IN PCCHAINPATHOBJECT pSubject) { m_pSubject = pSubject; m_pHead = NULL; } //+--------------------------------------------------------------------------- // // Member: CCertIssuerList::~CCertIssuerList, public // // Synopsis: Destructor // //---------------------------------------------------------------------------- CCertIssuerList::~CCertIssuerList () { PCERT_ISSUER_ELEMENT pElement; while ( ( pElement = NextElement( NULL ) ) != NULL ) { RemoveElement( pElement ); DeleteElement( pElement ); } } //+--------------------------------------------------------------------------- // // Member: CCertIssuerList::AddIssuer, public // // Synopsis: add an issuer to the list // //---------------------------------------------------------------------------- BOOL CCertIssuerList::AddIssuer( IN PCCHAINCALLCONTEXT pCallContext, IN OPTIONAL HCERTSTORE hAdditionalStore, IN PCCERTOBJECT pIssuer ) { BOOL fResult; PCCHAINPATHOBJECT pIssuerPathObject = NULL; PCERT_ISSUER_ELEMENT pElement = NULL; if (CheckForDuplicateElement(pIssuer->CertHash(), FALSE)) return TRUE; // Don't add ourself as an issuer. if (0 == memcmp(m_pSubject->CertObject()->CertHash(), pIssuer->CertHash(), CHAINHASHLEN)) return TRUE; // Mainly for certs generated by tstore2.exe that mostly contain // the same public key, need to add an additional filter to // discard certs that only match via the public key, ie no // AKI, name or basic constraints match. if (!ChainIsValidPubKeyMatchForIssuer(pIssuer, m_pSubject->CertObject())) return TRUE; if (!ChainCreatePathObject( pCallContext, pIssuer, hAdditionalStore, &pIssuerPathObject )) return FALSE; fResult = CreateElement( pCallContext, FALSE, // fCtlIssuer pIssuerPathObject, hAdditionalStore, NULL, // pSSCtlObject NULL, // pTrustListInfo &pElement ); if (!fResult) { return( FALSE ); } AddElement( pElement ); return( TRUE ); } //+--------------------------------------------------------------------------- // // Member: CCertIssuerList::AddCtlIssuer, public // // Synopsis: add an issuer to the list // //---------------------------------------------------------------------------- BOOL CCertIssuerList::AddCtlIssuer( IN PCCHAINCALLCONTEXT pCallContext, IN OPTIONAL HCERTSTORE hAdditionalStore, IN PCSSCTLOBJECT pSSCtlObject, IN PCERT_TRUST_LIST_INFO pTrustListInfo ) { PCERT_ISSUER_ELEMENT pElement = NULL; if (CheckForDuplicateElement(pSSCtlObject->CtlHash(), TRUE)) return TRUE; if (!CreateElement( pCallContext, TRUE, // fCtlIssuer NULL, // pIssuerPathObject hAdditionalStore, pSSCtlObject, pTrustListInfo, &pElement )) return FALSE; AddElement( pElement ); return( TRUE ); } //+--------------------------------------------------------------------------- // // Member: CCertIssuerList::CreateElement, public // // Synopsis: create an element // //---------------------------------------------------------------------------- BOOL CCertIssuerList::CreateElement( IN PCCHAINCALLCONTEXT pCallContext, IN BOOL fCtlIssuer, IN OPTIONAL PCCHAINPATHOBJECT pIssuer, IN OPTIONAL HCERTSTORE hAdditionalStore, IN OPTIONAL PCSSCTLOBJECT pSSCtlObject, IN OPTIONAL PCERT_TRUST_LIST_INFO pTrustListInfo, // allocated by caller OUT PCERT_ISSUER_ELEMENT* ppElement ) { BOOL fResult; BOOL fCtlSignatureValid = FALSE; PCERT_ISSUER_ELEMENT pElement; pElement = new CERT_ISSUER_ELEMENT; if (NULL == pElement) goto OutOfMemory; memset( pElement, 0, sizeof( CERT_ISSUER_ELEMENT ) ); pElement->fCtlIssuer = fCtlIssuer; if (!fCtlIssuer) { pElement->pIssuer = pIssuer; // The following may leave the engine's critical section to verify the // signature. If the engine was touched by another thread, it fails with // LastError set to ERROR_CAN_NOT_COMPLETE. if (!ChainGetSubjectStatus( pCallContext, pIssuer, m_pSubject, &pElement->SubjectStatus )) goto GetSubjectStatusError; } else { pElement->pCtlIssuerData = new CTL_ISSUER_DATA; if (NULL == pElement->pCtlIssuerData) goto OutOfMemory; memset( pElement->pCtlIssuerData, 0, sizeof( CTL_ISSUER_DATA ) ); pSSCtlObject->AddRef(); pElement->pCtlIssuerData->pSSCtlObject = pSSCtlObject; pElement->pCtlIssuerData->pTrustListInfo = pTrustListInfo; // The following may leave the engine's critical section to verify a // signature or do URL retrieval. If the engine was touched by // another thread, it fails with LastError set to // ERROR_CAN_NOT_COMPLETE. if (!pSSCtlObject->GetSigner( m_pSubject, pCallContext, hAdditionalStore, &pElement->pIssuer, &fCtlSignatureValid )) { if (GetLastError() != CRYPT_E_NOT_FOUND) goto GetSignerError; } } if (pElement->pIssuer) { // If the Issuer hasn't completed yet, then, we are cyclic. if (!pElement->pIssuer->IsCompleted()) pElement->dwPass1Quality = 0; else { pElement->dwPass1Quality = pElement->pIssuer->Pass1Quality(); if (!fCtlIssuer) { if (pElement->SubjectStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID) { pElement->dwPass1Quality &= ~CERT_QUALITY_SIGNATURE_VALID; } } else if (!fCtlSignatureValid) { pElement->dwPass1Quality &= ~CERT_QUALITY_SIGNATURE_VALID; } } } else { assert(fCtlIssuer); pElement->dwPass1Quality = 0; } // Remember highest quality issuer if (pElement->dwPass1Quality > m_pSubject->Pass1Quality()) m_pSubject->SetPass1Quality(pElement->dwPass1Quality); fResult = TRUE; CommonReturn: *ppElement = pElement; return fResult; ErrorReturn: if (pElement) { DeleteElement(pElement); pElement = NULL; } fResult = FALSE; goto CommonReturn; SET_ERROR(OutOfMemory, E_OUTOFMEMORY) TRACE_ERROR(GetSubjectStatusError) TRACE_ERROR(GetSignerError) } //+--------------------------------------------------------------------------- // // Member: CCertIssuerList::DeleteElement, public // // Synopsis: delete an element // //---------------------------------------------------------------------------- VOID CCertIssuerList::DeleteElement (IN PCERT_ISSUER_ELEMENT pElement) { if ( pElement->pCtlIssuerData ) { ChainFreeCtlIssuerData( pElement->pCtlIssuerData ); } if (pElement->fHasRevocationInfo) { if (pElement->RevocationCrlInfo.pBaseCrlContext) CertFreeCRLContext(pElement->RevocationCrlInfo.pBaseCrlContext); if (pElement->RevocationCrlInfo.pDeltaCrlContext) CertFreeCRLContext(pElement->RevocationCrlInfo.pDeltaCrlContext); } delete pElement; } //+--------------------------------------------------------------------------- // // Member: CCertIssuerList::CheckForDuplicateElement, public // // Synopsis: check for a duplicate element // //---------------------------------------------------------------------------- BOOL CCertIssuerList::CheckForDuplicateElement ( IN BYTE rgbHash[ CHAINHASHLEN ], IN BOOL fCtlIssuer ) { PCERT_ISSUER_ELEMENT pElement = NULL; while ( ( pElement = NextElement( pElement ) ) != NULL ) { if ( pElement->fCtlIssuer == fCtlIssuer ) { if ( fCtlIssuer == FALSE ) { if ( memcmp( rgbHash, pElement->pIssuer->CertObject()->CertHash(), CHAINHASHLEN ) == 0 ) { return( TRUE ); } } else { if ( memcmp( rgbHash, pElement->pCtlIssuerData->pSSCtlObject->CtlHash(), CHAINHASHLEN ) == 0 ) { return( TRUE ); } } } } return( FALSE ); } //+=========================================================================== // CCertObjectCache methods //============================================================================ //+--------------------------------------------------------------------------- // // Member: CCertObjectCache::CCertObjectCache, public // // Synopsis: Constructor // //---------------------------------------------------------------------------- CCertObjectCache::CCertObjectCache ( IN DWORD MaxIndexEntries, OUT BOOL& rfResult ) { LRU_CACHE_CONFIG Config; memset( &Config, 0, sizeof( Config ) ); Config.dwFlags = LRU_CACHE_NO_SERIALIZE | LRU_CACHE_NO_COPY_IDENTIFIER; Config.cBuckets = DEFAULT_CERT_OBJECT_CACHE_BUCKETS; m_hHashIndex = NULL; m_hIdentifierIndex = NULL; m_hKeyIdIndex = NULL; m_hSubjectNameIndex = NULL; m_hPublicKeyHashIndex = NULL; m_hEndHashIndex = NULL; Config.pfnHash = CertObjectCacheHashNameIdentifier; rfResult = I_CryptCreateLruCache( &Config, &m_hSubjectNameIndex ); Config.pfnHash = CertObjectCacheHashMd5Identifier; if ( rfResult == TRUE ) { rfResult = I_CryptCreateLruCache( &Config, &m_hIdentifierIndex ); } if ( rfResult == TRUE ) { rfResult = I_CryptCreateLruCache( &Config, &m_hKeyIdIndex ); } if ( rfResult == TRUE ) { rfResult = I_CryptCreateLruCache( &Config, &m_hPublicKeyHashIndex ); } Config.pfnOnRemoval = CertObjectCacheOnRemovalFromPrimaryIndex; if ( rfResult == TRUE ) { rfResult = I_CryptCreateLruCache( &Config, &m_hHashIndex ); } Config.MaxEntries = MaxIndexEntries; Config.pfnOnRemoval = CertObjectCacheOnRemovalFromEndHashIndex; if ( rfResult == TRUE ) { rfResult = I_CryptCreateLruCache( &Config, &m_hEndHashIndex ); } } //+--------------------------------------------------------------------------- // // Member: CCertObjectCache::~CCertObjectCache, public // // Synopsis: Destructor // //---------------------------------------------------------------------------- CCertObjectCache::~CCertObjectCache () { I_CryptFreeLruCache( m_hHashIndex, 0, NULL ); I_CryptFreeLruCache( m_hSubjectNameIndex, LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); I_CryptFreeLruCache( m_hIdentifierIndex, LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); I_CryptFreeLruCache( m_hKeyIdIndex, LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); I_CryptFreeLruCache( m_hPublicKeyHashIndex, LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); I_CryptFreeLruCache( m_hEndHashIndex, 0, NULL ); } //+--------------------------------------------------------------------------- // // Member: CCertObjectCache::AddIssuerObject, public // // Synopsis: add an issuer object to the cache // Increments engine's touch count // //---------------------------------------------------------------------------- VOID CCertObjectCache::AddIssuerObject ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCERTOBJECT pCertObject ) { assert(CERT_CACHED_ISSUER_OBJECT_TYPE == pCertObject->ObjectType()); pCertObject->AddRef(); I_CryptInsertLruEntry( pCertObject->HashIndexEntry(), pCallContext ); I_CryptInsertLruEntry( pCertObject->IdentifierIndexEntry(), pCallContext ); I_CryptInsertLruEntry( pCertObject->SubjectNameIndexEntry(), pCallContext ); I_CryptInsertLruEntry( pCertObject->KeyIdIndexEntry(), pCallContext ); I_CryptInsertLruEntry( pCertObject->PublicKeyHashIndexEntry(), pCallContext ); pCallContext->TouchEngine(); CertPerfIncrementChainCertCacheCount(); } //+--------------------------------------------------------------------------- // // Member: CCertObjectCache::AddEndObject, public // // Synopsis: add an end object to the cache // //---------------------------------------------------------------------------- VOID CCertObjectCache::AddEndObject ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCERTOBJECT pCertObject ) { PCCERTOBJECT pDuplicate; if (CERT_END_OBJECT_TYPE != pCertObject->ObjectType()) return; pDuplicate = FindEndObjectByHash(pCertObject->CertHash()); if (pDuplicate) { pDuplicate->Release(); return; } if (pCertObject->CacheEndObject(pCallContext)) { pCertObject->AddRef(); I_CryptInsertLruEntry( pCertObject->EndHashIndexEntry(), pCallContext ); CertPerfIncrementChainCertCacheCount(); CertPerfIncrementChainCacheEndCertCount(); } } //+--------------------------------------------------------------------------- // // Member: CCertObjectCache::FindIssuerObject, public // // Synopsis: find object // // Note, also called by FindEndObjectByHash // //---------------------------------------------------------------------------- PCCERTOBJECT CCertObjectCache::FindIssuerObject ( IN HLRUCACHE hIndex, IN PCRYPT_DATA_BLOB pIdentifier ) { HLRUENTRY hFound; PCCERTOBJECT pFound = NULL; hFound = I_CryptFindLruEntry( hIndex, pIdentifier ); if ( hFound != NULL ) { pFound = (PCCERTOBJECT)I_CryptGetLruEntryData( hFound ); pFound->AddRef(); I_CryptReleaseLruEntry( hFound ); } return( pFound ); } //+--------------------------------------------------------------------------- // // Member: CCertObjectCache::FindIssuerObjectByHash, public // // Synopsis: find object by hash // //---------------------------------------------------------------------------- PCCERTOBJECT CCertObjectCache::FindIssuerObjectByHash ( IN BYTE rgbCertHash[ CHAINHASHLEN ] ) { CRYPT_DATA_BLOB DataBlob; DataBlob.cbData = CHAINHASHLEN; DataBlob.pbData = rgbCertHash; return( FindIssuerObject( m_hHashIndex, &DataBlob ) ); } //+--------------------------------------------------------------------------- // // Member: CCertObjectCache::FindEndObjectByHash, public // // Synopsis: find object by hash // //---------------------------------------------------------------------------- PCCERTOBJECT CCertObjectCache::FindEndObjectByHash ( IN BYTE rgbCertHash[ CHAINHASHLEN ] ) { CRYPT_DATA_BLOB DataBlob; DataBlob.cbData = CHAINHASHLEN; DataBlob.pbData = rgbCertHash; return( FindIssuerObject( m_hEndHashIndex, &DataBlob ) ); } //+--------------------------------------------------------------------------- // // Member: CCertObjectCache::NextMatchingIssuerObject, public // // Synopsis: next matching issuer object // //---------------------------------------------------------------------------- PCCERTOBJECT CCertObjectCache::NextMatchingIssuerObject ( IN HLRUENTRY hObjectEntry, IN PCCERTOBJECT pCertObject ) { HLRUENTRY hFound; PCCERTOBJECT pFound = NULL; I_CryptAddRefLruEntry( hObjectEntry ); hFound = I_CryptEnumMatchingLruEntries( hObjectEntry ); if ( hFound != NULL ) { pFound = (PCCERTOBJECT)I_CryptGetLruEntryData( hFound ); pFound->AddRef(); I_CryptReleaseLruEntry( hFound ); } pCertObject->Release(); return( pFound ); } //+=========================================================================== // CCertChainEngine methods //============================================================================ //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::CCertChainEngine, public // // Synopsis: Constructor // //---------------------------------------------------------------------------- CCertChainEngine::CCertChainEngine ( IN PCERT_CHAIN_ENGINE_CONFIG pConfig, IN BOOL fDefaultEngine, OUT BOOL& rfResult ) { HCERTSTORE hWorld = NULL; DWORD dwStoreFlags = CERT_SYSTEM_STORE_CURRENT_USER; assert( pConfig->cbSize == sizeof( CERT_CHAIN_ENGINE_CONFIG ) ); rfResult = TRUE; m_cRefs = 1; m_hRootStore = NULL; m_hRealRootStore = NULL; m_hTrustStore = NULL; m_hOtherStore = NULL; m_hCAStore = NULL; m_hEngineStore = NULL; m_hEngineStoreChangeEvent = NULL; m_pCertObjectCache = NULL; m_pSSCtlObjectCache = NULL; m_dwFlags = pConfig->dwFlags; if (0 == pConfig->dwUrlRetrievalTimeout) { m_dwUrlRetrievalTimeout = DEFAULT_ENGINE_URL_RETRIEVAL_TIMEOUT; m_fDefaultUrlRetrievalTimeout = TRUE; } else { m_dwUrlRetrievalTimeout = pConfig->dwUrlRetrievalTimeout; m_fDefaultUrlRetrievalTimeout = FALSE; } m_dwTouchEngineCount = 0; m_pCrossCertDPEntry = NULL; m_pCrossCertDPLink = NULL; m_hCrossCertStore = NULL; m_dwCrossCertDPResyncIndex = 0; m_pAuthRootAutoUpdateInfo = NULL; if ( !Pki_InitializeCriticalSection( &m_Lock )) { rfResult = FALSE; return; } if ( pConfig->dwFlags & CERT_CHAIN_USE_LOCAL_MACHINE_STORE ) { dwStoreFlags = CERT_SYSTEM_STORE_LOCAL_MACHINE; } if ( pConfig->dwFlags & CERT_CHAIN_ENABLE_SHARE_STORE ) { dwStoreFlags |= CERT_STORE_SHARE_STORE_FLAG; } dwStoreFlags |= CERT_STORE_SHARE_CONTEXT_FLAG; m_hRealRootStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, dwStoreFlags | CERT_STORE_MAXIMUM_ALLOWED_FLAG, L"root" ); if ( m_hRealRootStore == NULL ) { rfResult = FALSE; return; } m_hCAStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, dwStoreFlags | CERT_STORE_MAXIMUM_ALLOWED_FLAG, L"ca" ); if ( pConfig->hRestrictedRoot != NULL ) { if ( ChainIsProperRestrictedRoot( m_hRealRootStore, pConfig->hRestrictedRoot ) == TRUE ) { m_hRootStore = CertDuplicateStore( pConfig->hRestrictedRoot ); // Having restricted roots implicitly disables the auto // updating of roots m_dwFlags |= CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE; } } else { m_hRootStore = CertDuplicateStore( m_hRealRootStore ); } if ( m_hRootStore == NULL ) { rfResult = FALSE; return; } if ( ( pConfig->hRestrictedTrust == NULL ) || ( pConfig->hRestrictedOther == NULL ) ) { rfResult = ChainCreateWorldStore( m_hRootStore, m_hCAStore, pConfig->cAdditionalStore, pConfig->rghAdditionalStore, dwStoreFlags, &hWorld ); if ( rfResult == FALSE ) { return; } } if ( pConfig->hRestrictedTrust != NULL ) { m_hTrustStore = CertDuplicateStore( pConfig->hRestrictedTrust ); } else { m_hTrustStore = CertDuplicateStore( hWorld ); } m_hOtherStore = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL ); if ( m_hOtherStore != NULL ) { if ( pConfig->hRestrictedOther != NULL ) { rfResult = CertAddStoreToCollection( m_hOtherStore, pConfig->hRestrictedOther, 0, 0 ); if ( rfResult == TRUE ) { rfResult = CertAddStoreToCollection( m_hOtherStore, m_hRootStore, 0, 0 ); } } else { rfResult = CertAddStoreToCollection( m_hOtherStore, hWorld, 0, 0 ); if ( ( rfResult == TRUE ) && ( pConfig->hRestrictedTrust != NULL ) ) { rfResult = CertAddStoreToCollection( m_hOtherStore, pConfig->hRestrictedTrust, 0, 0 ); } } } else { rfResult = FALSE; } if ( hWorld != NULL ) { CertCloseStore( hWorld, 0 ); } if ( rfResult == TRUE ) { rfResult = ChainCreateEngineStore( m_hRootStore, m_hTrustStore, m_hOtherStore, fDefaultEngine, pConfig->dwFlags, &m_hEngineStore, &m_hEngineStoreChangeEvent ); } if ( rfResult == TRUE ) { rfResult = ChainCreateCertificateObjectCache( pConfig->MaximumCachedCertificates, &m_pCertObjectCache ); } if ( rfResult == TRUE ) { rfResult = SSCtlCreateObjectCache( &m_pSSCtlObjectCache ); } if ( rfResult == TRUE ) { rfResult = m_pSSCtlObjectCache->PopulateCache( this ); } assert( m_hRootStore != NULL ); // Beginning of cross certificate stuff if ( rfResult == FALSE ) { return; } m_hCrossCertStore = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL ); if ( m_hCrossCertStore == NULL ) { rfResult = FALSE; return; } rfResult = GetCrossCertDistPointsForStore( m_hEngineStore, &m_pCrossCertDPLink ); if ( rfResult == FALSE ) { return; } rfResult = CertAddStoreToCollection( m_hOtherStore, m_hCrossCertStore, 0, 0 ); // End of cross certificate stuff CertPerfIncrementChainEngineCurrentCount(); CertPerfIncrementChainEngineTotalCount(); } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::~CCertChainEngine, public // // Synopsis: Destructor // //---------------------------------------------------------------------------- CCertChainEngine::~CCertChainEngine () { CertPerfDecrementChainEngineCurrentCount(); // Beginning of cross certificate stuff FreeCrossCertDistPoints( &m_pCrossCertDPLink ); assert( NULL == m_pCrossCertDPLink ); assert( NULL == m_pCrossCertDPEntry ); if ( m_hCrossCertStore != NULL ) { CertCloseStore( m_hCrossCertStore, 0 ); } // End of cross certificate stuff FreeAuthRootAutoUpdateInfo(m_pAuthRootAutoUpdateInfo); ChainFreeCertificateObjectCache( m_pCertObjectCache ); SSCtlFreeObjectCache( m_pSSCtlObjectCache ); if ( m_hRootStore != NULL ) { CertCloseStore( m_hRootStore, 0 ); } if ( m_hRealRootStore != NULL ) { CertCloseStore( m_hRealRootStore, 0 ); } if ( m_hTrustStore != NULL ) { CertCloseStore( m_hTrustStore, 0 ); } if ( m_hOtherStore != NULL ) { CertCloseStore( m_hOtherStore, 0 ); } if ( m_hCAStore != NULL ) { CertCloseStore( m_hCAStore, 0 ); } if ( m_hEngineStore != NULL ) { if ( m_hEngineStoreChangeEvent != NULL ) { CertControlStore( m_hEngineStore, 0, // dwFlags CERT_STORE_CTRL_CANCEL_NOTIFY, &m_hEngineStoreChangeEvent ); } CertCloseStore( m_hEngineStore, 0 ); } if ( m_hEngineStoreChangeEvent != NULL ) { CloseHandle( m_hEngineStoreChangeEvent ); } DeleteCriticalSection( &m_Lock ); } // "CrossCA" const BYTE rgbEncodedCrossCAUnicodeString[] = { 0x1E, 0x0E, 0x00, 0x43, 0x00, 0x72, 0x00, 0x6F, 0x00, 0x73, 0x00, 0x73, 0x00, 0x43, 0x00, 0x41 }; //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::GetChainContext, public // // Synopsis: get a certificate chain context // // NOTE: This method acquires the engine lock // //---------------------------------------------------------------------------- BOOL CCertChainEngine::GetChainContext ( IN PCCERT_CONTEXT pCertContext, IN LPFILETIME pTime, IN OPTIONAL HCERTSTORE hAdditionalStore, IN OPTIONAL PCERT_CHAIN_PARA pChainPara, IN DWORD dwFlags, IN LPVOID pvReserved, OUT PCCERT_CHAIN_CONTEXT* ppChainContext ) { BOOL fResult; DWORD dwLastError = 0; PCCHAINCALLCONTEXT pCallContext = NULL; PCCERT_CHAIN_CONTEXT pChainContext = NULL; if (!CallContextCreateCallObject( this, pTime, pChainPara, dwFlags, &pCallContext )) goto CallContextCreateCallObjectError; if (!CreateChainContextFromPathGraph( pCallContext, pCertContext, hAdditionalStore, &pChainContext )) goto CreateChainContextFromPathGraphError; if ((pChainContext->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) && pCallContext->IsOnline()) { // For a revoked CA, try to retrieve a newer CA cert via the subject's // AIA extension. // // Note, will only try for the first revoked CA cert in the first // simple chain. HCERTSTORE hNewerIssuerUrlStore = NULL; PCERT_SIMPLE_CHAIN pChain = pChainContext->rgpChain[0]; DWORD cEle = pChain->cElement; PCERT_CHAIN_ELEMENT *ppEle = pChain->rgpElement; DWORD i; for (i = 1; i < cEle; i++) { PCERT_CHAIN_ELEMENT pIssuerEle = ppEle[i]; if (pIssuerEle->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) { // First Revoked CA PCCERT_CONTEXT pIssuerCert = pIssuerEle->pCertContext; PCERT_EXTENSION pExt; // Ignore CrossCA's. If the CA cert has a Certificate // Template Name extension we will check if its set to // "CrossCA". Note, this is only a hint. Its not a // requirement to have this extension for a cross cert. pExt = CertFindExtension( szOID_ENROLL_CERTTYPE_EXTENSION, pIssuerCert->pCertInfo->cExtension, pIssuerCert->pCertInfo->rgExtension ); if (pExt && pExt->Value.cbData == sizeof(rgbEncodedCrossCAUnicodeString) && 0 == memcmp(pExt->Value.pbData, rgbEncodedCrossCAUnicodeString, sizeof(rgbEncodedCrossCAUnicodeString))) break; hNewerIssuerUrlStore = GetNewerIssuerUrlStore( pCallContext, ppEle[i - 1]->pCertContext, // Subject pIssuerCert ); break; } } if (hNewerIssuerUrlStore) { // Rebuild the chain using the newer AIA retrieved Issuer cert HCERTSTORE hNewerAdditionalStore = NULL; if (hAdditionalStore) { hNewerAdditionalStore = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL ); if (hNewerAdditionalStore) { if (!CertAddStoreToCollection(hNewerAdditionalStore, hNewerIssuerUrlStore, 0, 0) || !CertAddStoreToCollection(hNewerAdditionalStore, hAdditionalStore, 0, 0)) { CertCloseStore(hNewerAdditionalStore, 0); hNewerAdditionalStore = NULL; } } } else hNewerAdditionalStore = CertDuplicateStore(hNewerIssuerUrlStore); if (hNewerAdditionalStore) { PCCERT_CHAIN_CONTEXT pNewerChainContext = NULL; LockEngine(); pCallContext->FlushObjectsInCreationCache( ); UnlockEngine(); if (CreateChainContextFromPathGraph( pCallContext, pCertContext, hNewerAdditionalStore, &pNewerChainContext )) { assert(pNewerChainContext); CertFreeCertificateChain(pChainContext); pChainContext = pNewerChainContext; } CertCloseStore(hNewerAdditionalStore, 0); } CertCloseStore(hNewerIssuerUrlStore, 0); } } fResult = TRUE; CommonReturn: if (pCallContext) { LockEngine(); CallContextFreeCallObject(pCallContext); UnlockEngine(); } if (0 != dwLastError) SetLastError(dwLastError); *ppChainContext = pChainContext; return fResult; ErrorReturn: dwLastError = GetLastError(); assert(NULL == pChainContext); fResult = FALSE; goto CommonReturn; TRACE_ERROR(CallContextCreateCallObjectError) TRACE_ERROR(CreateChainContextFromPathGraphError) } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::CreateChainContextFromPathGraph, public // // Synopsis: builds a chain path graph and returns quality ordered // chain contexts // // NOTE: This method acquires the engine lock // //---------------------------------------------------------------------------- BOOL CCertChainEngine::CreateChainContextFromPathGraph ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCERT_CONTEXT pCertContext, IN OPTIONAL HCERTSTORE hAdditionalStore, OUT PCCERT_CHAIN_CONTEXT* ppChainContext ) { BOOL fResult; DWORD dwLastError = 0; BOOL fLocked = FALSE; BYTE rgbCertHash[CHAINHASHLEN]; DWORD cbCertHash; PCCERTOBJECT pEndCertObject = NULL; PCCHAINPATHOBJECT pEndPathObject = NULL; PCCHAINPATHOBJECT pTopPathObject = NULL; HCERTSTORE hAdditionalStoreToUse = NULL; HCERTSTORE hAllStore = NULL; PINTERNAL_CERT_CHAIN_CONTEXT pNewChainContext = NULL; // don't release PINTERNAL_CERT_CHAIN_CONTEXT pChainContext = NULL; DWORD cChainContext = 0; DWORD dwFlags = pCallContext->CallFlags(); cbCertHash = CHAINHASHLEN; if (!CertGetCertificateContextProperty( pCertContext, CERT_MD5_HASH_PROP_ID, rgbCertHash, &cbCertHash ) || CHAINHASHLEN != cbCertHash) goto GetCertHashError; if (hAdditionalStore) { if (!ChainCreateCollectionIncludingCtlCertificates( hAdditionalStore, &hAdditionalStoreToUse )) goto CreateAdditionalStoreCollectionError; hAllStore = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL ); if (NULL == hAllStore) goto OpenAllCollectionError; if (!CertAddStoreToCollection(hAllStore, OtherStore(), 0, 0 )) goto AddToAllCollectionError; if (!CertAddStoreToCollection(hAllStore, hAdditionalStoreToUse, 0, 0 )) goto AddToAllCollectionError; } else hAllStore = CertDuplicateStore(OtherStore()); LockEngine(); fLocked = TRUE; // We're in this loop to handle the case where we leave the engine's // critical section and another thread has entered the engine's // critical section and done a resync or added a cached issuer cert object. while (TRUE) { if (!Resync(pCallContext, FALSE)) goto ResyncError; pCallContext->ResetTouchEngine(); assert(NULL == pEndCertObject); pEndCertObject = m_pCertObjectCache->FindIssuerObjectByHash( rgbCertHash); fResult = TRUE; if (NULL == pEndCertObject) { pEndCertObject = m_pCertObjectCache->FindEndObjectByHash( rgbCertHash); if (NULL == pEndCertObject) { fResult = ChainCreateCertObject( CERT_END_OBJECT_TYPE, pCallContext, pCertContext, rgbCertHash, &pEndCertObject ); } else { CertPerfIncrementChainEndCertInCacheCount(); } } if (pCallContext->IsTouchedEngine()) { // The chain engine was touched at some point when we left // the engine's lock to create the end cert object if (pEndCertObject) { pEndCertObject->Release(); pEndCertObject = NULL; } continue; } if (!fResult) goto CreateCertObjectError; assert(pEndCertObject); // This will create the entire path graph fResult = ChainCreatePathObject( pCallContext, pEndCertObject, hAdditionalStoreToUse, &pEndPathObject ); if (pCallContext->IsTouchedEngine()) { // The chain engine was touched at some point when we left // the engine's lock to verify a signature or do URL fetching. pEndCertObject->Release(); pEndCertObject = NULL; pEndPathObject = NULL; pCallContext->FlushObjectsInCreationCache( ); } else break; } if (!fResult) goto CreatePathObjectError; if (pCallContext->CallOrEngineFlags() & CERT_CHAIN_CACHE_END_CERT) m_pCertObjectCache->AddEndObject(pCallContext, pEndCertObject); // Create the ChainContext without holding the engine lock UnlockEngine(); fLocked = FALSE; // Loop through all the certificate paths: // - Calculate additional status // - Create chain context and its quality value // - Determine highest quality chain // - Optionally, maintain a linked list of the lower quality chains while (pTopPathObject = pEndPathObject->NextPath( pCallContext, pTopPathObject )) { PCCHAINPATHOBJECT pPathObject; // Loop downward to calculate additional status for (pPathObject = pTopPathObject; pPathObject && !pPathObject->HasAdditionalStatus(); pPathObject = pPathObject->DownPathObject()) { pPathObject->CalculateAdditionalStatus( pCallContext, hAllStore ); } // Also calculates the chain's quality value pNewChainContext = pEndPathObject->CreateChainContextFromPath( pCallContext, pTopPathObject ); if (NULL == pNewChainContext) goto CreateChainContextFromPathError; // Fixup end cert ChainUpdateEndEntityCertContext(pNewChainContext, pCertContext); // Add logic to call either the chain engine's or the caller's // callback function here to provide additional chain context // quality if (NULL == pChainContext) { pChainContext = pNewChainContext; cChainContext = 1; } else { BOOL fNewHigherQuality = FALSE; if (pNewChainContext->dwQuality > pChainContext->dwQuality) fNewHigherQuality = TRUE; else if (pNewChainContext->dwQuality == pChainContext->dwQuality) { BOOL fDupPublicKey = FALSE; PCERT_SIMPLE_CHAIN pChain = pChainContext->ChainContext.rgpChain[0]; PCERT_SIMPLE_CHAIN pNewChain = pNewChainContext->ChainContext.rgpChain[0]; DWORD cElement = pChain->cElement; DWORD cNewElement = pNewChain->cElement; if (cElement != cNewElement) { // Check if the longer chain has any duplicate public // keys. This could happen if we have 2 sets of cross // certificates PCERT_SIMPLE_CHAIN pLongChain; DWORD cLongElement; DWORD i; if (cElement > cNewElement) { pLongChain = pChain; cLongElement = cElement; } else { pLongChain = pNewChain; cLongElement = cNewElement; } // Start with the CA and compare all keys up to and // including the root for (i = 1; i + 1 < cLongElement; i++) { DWORD j; DWORD cbHash; BYTE rgbHash0[ CHAINHASHLEN ]; cbHash = CHAINHASHLEN; if (!CertGetCertificateContextProperty( pLongChain->rgpElement[i]->pCertContext, CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID, rgbHash0, &cbHash ) || CHAINHASHLEN != cbHash) break; for (j = i + 1; j < cLongElement; j++) { BYTE rgbHash1[ CHAINHASHLEN ]; cbHash = CHAINHASHLEN; if (!CertGetCertificateContextProperty( pLongChain->rgpElement[j]->pCertContext, CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID, rgbHash1, &cbHash ) || CHAINHASHLEN != cbHash) break; if (0 == memcmp(rgbHash0, rgbHash1, CHAINHASHLEN)) { fDupPublicKey = TRUE; break; } } if (fDupPublicKey) break; } } if (fDupPublicKey) { if (cElement > cNewElement) fNewHigherQuality = TRUE; } else { DWORD i; DWORD cMinElement; // Chains having certs with later NotAfter/NotBefore dates // starting with the first CA cert are considered higher // quality when dwQuality is the same. Will only compare // the first simple chain. cMinElement = min(cElement, cNewElement); for (i = 1; i < cMinElement; i++) { LONG lCmp; PCERT_INFO pCertInfo = pChain->rgpElement[i]->pCertContext->pCertInfo; PCERT_INFO pNewCertInfo = pNewChain->rgpElement[i]->pCertContext->pCertInfo; lCmp = CompareFileTime(&pNewCertInfo->NotAfter, &pCertInfo->NotAfter); if (0 < lCmp) { fNewHigherQuality = TRUE; break; } else if (0 > lCmp) { break; } else { // Same NotAfter. Check NotBefore. lCmp = CompareFileTime(&pNewCertInfo->NotBefore, &pCertInfo->NotBefore); if (0 < lCmp) { fNewHigherQuality = TRUE; break; } else if (0 > lCmp) break; // else // Same } } } } // else // fNewHigherQuality = FALSE; if (fNewHigherQuality) { if (dwFlags & CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS) { pNewChainContext->pNext = pChainContext; pChainContext = pNewChainContext; cChainContext++; } else { ChainReleaseInternalChainContext(pChainContext); pChainContext = pNewChainContext; } } else { if (dwFlags & CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS) { PINTERNAL_CERT_CHAIN_CONTEXT p; // Insert according to quality for (p = pChainContext; p->pNext && p->pNext->dwQuality >= pNewChainContext->dwQuality; p = p->pNext) { ; } pNewChainContext->pNext = p->pNext; p->pNext = pNewChainContext; cChainContext++; } else { ChainReleaseInternalChainContext(pNewChainContext); } } } } if (GetLastError() != CRYPT_E_NOT_FOUND) goto NextPathError; assert(pChainContext && cChainContext); if (cChainContext > 1) { PINTERNAL_CERT_CHAIN_CONTEXT p; PCCERT_CHAIN_CONTEXT *ppLower; // Create array of lower quality chain contexts ppLower = new PCCERT_CHAIN_CONTEXT [ cChainContext - 1]; if (NULL == ppLower) goto OutOfMemory; pChainContext->ChainContext.cLowerQualityChainContext = cChainContext - 1; pChainContext->ChainContext.rgpLowerQualityChainContext = ppLower; for (p = pChainContext->pNext; p; p = p->pNext, ppLower++) { assert(cChainContext > 1); cChainContext--; *ppLower = (PCCERT_CHAIN_CONTEXT) p; } } assert(1 == cChainContext); fResult = TRUE; CommonReturn: if (!fLocked) LockEngine(); if (pEndCertObject) pEndCertObject->Release(); if (hAllStore) CertCloseStore(hAllStore, 0); if (hAdditionalStoreToUse) CertCloseStore(hAdditionalStoreToUse, 0); *ppChainContext = (PCCERT_CHAIN_CONTEXT) pChainContext; UnlockEngine(); if (0 != dwLastError) SetLastError(dwLastError); return fResult; ErrorReturn: dwLastError = GetLastError(); if (pChainContext) { PINTERNAL_CERT_CHAIN_CONTEXT p; while (p = pChainContext->pNext) { pChainContext->pNext = p->pNext; ChainReleaseInternalChainContext(p); } ChainReleaseInternalChainContext(pChainContext); pChainContext = NULL; } fResult = FALSE; goto CommonReturn; TRACE_ERROR(GetCertHashError) TRACE_ERROR(CreateAdditionalStoreCollectionError) TRACE_ERROR(OpenAllCollectionError) TRACE_ERROR(AddToAllCollectionError) TRACE_ERROR(ResyncError) TRACE_ERROR(CreateCertObjectError) TRACE_ERROR(CreatePathObjectError) TRACE_ERROR(CreateChainContextFromPathError) TRACE_ERROR(NextPathError) SET_ERROR(OutOfMemory, E_OUTOFMEMORY) } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::GetIssuerUrlStore, public // // Synopsis: if the certificate has an Authority Info Access extension, // return a store containing the issuing certificates // // Leaves the engine's critical section to do the URL // fetching. If the engine was touched by another thread, // it fails with LastError set to ERROR_CAN_NOT_COMPLETE. // // Assumption: Chain engine is locked once in the calling thread. // //---------------------------------------------------------------------------- BOOL CCertChainEngine::GetIssuerUrlStore( IN PCCHAINCALLCONTEXT pCallContext, IN PCCERT_CONTEXT pSubjectCertContext, IN DWORD dwRetrievalFlags, OUT HCERTSTORE *phIssuerUrlStore ) { BOOL fTouchedResult = TRUE; BOOL fResult; DWORD cbUrlArray; PCRYPT_URL_ARRAY pUrlArray = NULL; DWORD cCount; DWORD dwCacheResultFlag; *phIssuerUrlStore = NULL; dwRetrievalFlags |= CRYPT_RETRIEVE_MULTIPLE_OBJECTS | CRYPT_LDAP_SCOPE_BASE_ONLY_RETRIEVAL | CRYPT_OFFLINE_CHECK_RETRIEVAL; fResult = ChainGetObjectUrl( URL_OID_CERTIFICATE_ISSUER, (LPVOID) pSubjectCertContext, CRYPT_GET_URL_FROM_EXTENSION, NULL, &cbUrlArray, NULL, NULL, NULL ); if ( fResult == TRUE ) { pUrlArray = (PCRYPT_URL_ARRAY)new BYTE [ cbUrlArray ]; if ( pUrlArray == NULL ) { SetLastError( (DWORD) E_OUTOFMEMORY ); return( FALSE ); } fResult = ChainGetObjectUrl( URL_OID_CERTIFICATE_ISSUER, (LPVOID) pSubjectCertContext, CRYPT_GET_URL_FROM_EXTENSION, pUrlArray, &cbUrlArray, NULL, NULL, NULL ); } if ( fResult == TRUE ) { BOOL fLocked = FALSE; // // We are about to go on the wire to retrieve the issuer certificate. // At this time we will release the chain engine lock so others can // go about there business while we wait for the protocols to do the // fetching. // UnlockEngine(); for ( cCount = 0; cCount < pUrlArray->cUrl; cCount++ ) { if ( !( dwRetrievalFlags & CRYPT_CACHE_ONLY_RETRIEVAL ) && ( ChainIsFileOrLdapUrl( pUrlArray->rgwszUrl[ cCount ] ) == TRUE ) ) { dwCacheResultFlag = CRYPT_DONT_CACHE_RESULT; } else { dwCacheResultFlag = 0; } fResult = ChainRetrieveObjectByUrlW( pUrlArray->rgwszUrl[ cCount ], CONTEXT_OID_CERTIFICATE, dwRetrievalFlags | dwCacheResultFlag, pCallContext->ChainPara()->dwUrlRetrievalTimeout, (LPVOID *)phIssuerUrlStore, NULL, NULL, NULL, NULL ); if ( fResult == TRUE ) { CertPerfIncrementChainUrlIssuerCount(); if (dwRetrievalFlags & CRYPT_CACHE_ONLY_RETRIEVAL) CertPerfIncrementChainCacheOnlyUrlIssuerCount(); // // Retake the engine lock. Also check if the engine was // touched during our absence. // LockEngine(); if (pCallContext->IsTouchedEngine()) { fTouchedResult = FALSE; SetLastError( (DWORD) ERROR_CAN_NOT_COMPLETE ); } fLocked = TRUE; ChainCopyToCAStore( this, *phIssuerUrlStore ); if (!fTouchedResult) { CertCloseStore(*phIssuerUrlStore, 0); *phIssuerUrlStore = NULL; } break; } } // // Retake the engine lock if necessary // if ( fLocked == FALSE ) { LockEngine(); if (pCallContext->IsTouchedEngine()) { fTouchedResult = FALSE; SetLastError( (DWORD) ERROR_CAN_NOT_COMPLETE ); } } } delete (LPBYTE)pUrlArray; // NOTE: Need to somehow log that we tried to retrieve the issuer but // it was inaccessible return( fTouchedResult ); } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::GetNewerIssuerUrlStore, public // // Synopsis: if the subject certificate has an Authority Info Access // extension, attempts an online URL retrieval of the // issuer certificate(s). If any of the URL retrieved // certs are different from the input Issuer cert, // returns a store containing the issuing certificates. // Otherwise, returns NULL store. // // Assumption: Chain engine isn't locked in the calling thread. Also, // only called if online. // //---------------------------------------------------------------------------- HCERTSTORE CCertChainEngine::GetNewerIssuerUrlStore( IN PCCHAINCALLCONTEXT pCallContext, IN PCCERT_CONTEXT pSubjectCertContext, IN PCCERT_CONTEXT pIssuerCertContext ) { HCERTSTORE hNewIssuerUrlStore = NULL; LockEngine(); while (TRUE) { pCallContext->ResetTouchEngine(); GetIssuerUrlStore( pCallContext, pSubjectCertContext, CRYPT_WIRE_ONLY_RETRIEVAL, &hNewIssuerUrlStore ); if (!pCallContext->IsTouchedEngine()) break; assert(NULL == hNewIssuerUrlStore); } UnlockEngine(); if (hNewIssuerUrlStore) { // Discard if it doesn't contain more than just the input // pIssuerCertContext PCCERT_CONTEXT pCert; pCert = NULL; while (pCert = CertEnumCertificatesInStore(hNewIssuerUrlStore, pCert)) { if (!CertCompareCertificate( pCert->dwCertEncodingType, pCert->pCertInfo, pIssuerCertContext->pCertInfo )) { CertFreeCertificateContext(pCert); return hNewIssuerUrlStore; } } CertCloseStore(hNewIssuerUrlStore, 0); } return NULL; } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::Resync, public // // Synopsis: resync the store if necessary // // Leaves the engine's critical section to do the URL // fetching. If the engine was touched by another thread, // it fails with LastError set to ERROR_CAN_NOT_COMPLETE. // // A resync increments the engine's touch count. // // Assumption: Chain engine is locked once in the calling thread. // //---------------------------------------------------------------------------- BOOL CCertChainEngine::Resync (IN PCCHAINCALLCONTEXT pCallContext, BOOL fForce) { BOOL fResync = FALSE; BOOL fResult = TRUE; if ( fForce == FALSE ) { if ( WaitForSingleObject( m_hEngineStoreChangeEvent, 0 ) == WAIT_OBJECT_0 ) { fResync = TRUE; } } else { fResync = TRUE; } if ( fResync ) { CertControlStore( m_hEngineStore, CERT_STORE_CTRL_INHIBIT_DUPLICATE_HANDLE_FLAG, CERT_STORE_CTRL_RESYNC, &m_hEngineStoreChangeEvent ); m_pCertObjectCache->FlushObjects( pCallContext ); fResult = m_pSSCtlObjectCache->Resync( this ); assert( fResult == TRUE ); assert( m_hCrossCertStore ); // Remove CrossCert collection from engine's list. Don't want to // also search it for cross cert distribution points CertRemoveStoreFromCollection( m_hOtherStore, m_hCrossCertStore ); fResult = GetCrossCertDistPointsForStore( m_hEngineStore, &m_pCrossCertDPLink ); CertAddStoreToCollection( m_hOtherStore, m_hCrossCertStore, 0, 0 ); pCallContext->TouchEngine(); CertPerfIncrementChainEngineResyncCount(); } if ( fResult ) { while (TRUE ) { pCallContext->ResetTouchEngine(); // The following 2 updates leave the engine's critical // section to do the URL fetching. If the engine was touched by // another thread, it fails with LastError set to // ERROR_CAN_NOT_COMPLETE and IsTouchedEngine() is TRUE. UpdateCrossCerts(pCallContext); if (pCallContext->IsTouchedEngine()) continue; m_pSSCtlObjectCache->UpdateCache(this, pCallContext); if (!pCallContext->IsTouchedEngine()) break; } } return( TRUE ); } //+=========================================================================== // CCertObject helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainCreateCertObject // // Synopsis: create a cert object, note since it is a ref-counted // object, freeing occurs by doing a pCertObject->Release // //---------------------------------------------------------------------------- BOOL WINAPI ChainCreateCertObject ( IN DWORD dwObjectType, IN PCCHAINCALLCONTEXT pCallContext, IN PCCERT_CONTEXT pCertContext, IN OPTIONAL LPBYTE pbCertHash, OUT PCCERTOBJECT *ppCertObject ) { BOOL fResult = TRUE; PCCERTOBJECT pCertObject; BYTE rgbHash[CHAINHASHLEN]; if (NULL == pbCertHash) { DWORD cbHash = CHAINHASHLEN; if (!CertGetCertificateContextProperty( pCertContext, CERT_MD5_HASH_PROP_ID, rgbHash, &cbHash ) || CHAINHASHLEN != cbHash) { *ppCertObject = NULL; return FALSE; } pbCertHash = rgbHash; } if (CERT_CACHED_ISSUER_OBJECT_TYPE == dwObjectType) { pCertObject = pCallContext->ChainEngine()->CertObjectCache()->FindIssuerObjectByHash( pbCertHash); if (NULL != pCertObject) { *ppCertObject = pCertObject; return TRUE; } } else { PCCHAINPATHOBJECT pPathObject; pPathObject = pCallContext->FindPathObjectInCreationCache( pbCertHash); if (NULL != pPathObject) { pCertObject = pPathObject->CertObject(); pCertObject->AddRef(); *ppCertObject = pCertObject; return TRUE; } } pCertObject = new CCertObject( dwObjectType, pCallContext, pCertContext, pbCertHash, fResult ); if (NULL != pCertObject) { if (!fResult) { pCertObject->Release(); pCertObject = NULL; } else if (CERT_CACHED_ISSUER_OBJECT_TYPE == dwObjectType) { // Following add increments the engine's touch count pCallContext->ChainEngine()->CertObjectCache()->AddIssuerObject( pCallContext, pCertObject ); } } else { fResult = FALSE; } *ppCertObject = pCertObject; return fResult; } //+--------------------------------------------------------------------------- // // Function: ChainFillCertObjectCtlCacheEnumFn // // Synopsis: CSSCtlObjectCache::EnumObjects callback used to create // the linked list of CTL cache entries. // //---------------------------------------------------------------------------- BOOL WINAPI ChainFillCertObjectCtlCacheEnumFn( IN LPVOID pvParameter, IN PCSSCTLOBJECT pSSCtlObject ) { PCERT_OBJECT_CTL_CACHE_ENUM_DATA pEnumData = (PCERT_OBJECT_CTL_CACHE_ENUM_DATA) pvParameter; PCERT_TRUST_LIST_INFO pTrustListInfo = NULL; PCERT_OBJECT_CTL_CACHE_ENTRY pEntry = NULL; if (!pEnumData->fResult) return FALSE; if (!pSSCtlObject->GetTrustListInfo( pEnumData->pCertObject->CertContext(), &pTrustListInfo )) { DWORD dwErr = GetLastError(); if (CRYPT_E_NOT_FOUND == dwErr) return TRUE; else { pEnumData->fResult = FALSE; pEnumData->dwLastError = dwErr; return FALSE; } } pEntry = new CERT_OBJECT_CTL_CACHE_ENTRY; if (NULL == pEntry) { SSCtlFreeTrustListInfo(pTrustListInfo); pEnumData->fResult = FALSE; pEnumData->dwLastError = (DWORD) E_OUTOFMEMORY; return FALSE; } pSSCtlObject->AddRef(); pEntry->pSSCtlObject = pSSCtlObject; pEntry->pTrustListInfo = pTrustListInfo; pEnumData->pCertObject->InsertCtlCacheEntry(pEntry); return TRUE; } //+--------------------------------------------------------------------------- // // Function: ChainFreeCertObjectCtlCache // // Synopsis: free the linked list of CTL cache entries. // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeCertObjectCtlCache( IN PCERT_OBJECT_CTL_CACHE_ENTRY pCtlCacheHead ) { PCERT_OBJECT_CTL_CACHE_ENTRY pCtlCache; while (pCtlCache = pCtlCacheHead) { pCtlCacheHead = pCtlCacheHead->pNext; if (pCtlCache->pTrustListInfo) SSCtlFreeTrustListInfo(pCtlCache->pTrustListInfo); if (pCtlCache->pSSCtlObject) pCtlCache->pSSCtlObject->Release(); delete pCtlCache; } } //+--------------------------------------------------------------------------- // // Function: ChainAllocAndDecodeObject // // Synopsis: allocate and decodes the ASN.1 encoded data structure. // // NULL is returned for a decoding or allocation error. // PkiFree must be called to free the allocated data structure. // //---------------------------------------------------------------------------- LPVOID WINAPI ChainAllocAndDecodeObject( IN LPCSTR lpszStructType, IN const BYTE *pbEncoded, IN DWORD cbEncoded ) { DWORD cbStructInfo; void *pvStructInfo; if (!CryptDecodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, lpszStructType, pbEncoded, cbEncoded, CRYPT_DECODE_SHARE_OID_STRING_FLAG | CRYPT_DECODE_NOCOPY_FLAG | CRYPT_DECODE_ALLOC_FLAG, &PkiDecodePara, (void *) &pvStructInfo, &cbStructInfo )) goto DecodeError; CommonReturn: return pvStructInfo; ErrorReturn: pvStructInfo = NULL; goto CommonReturn; TRACE_ERROR(DecodeError) } //+--------------------------------------------------------------------------- // // Function: ChainGetIssuerMatchInfo // // Synopsis: return match bits specifying the types of issuer matching // that can be done for this certificate and if available return // the decoded authority key identifier extension // //---------------------------------------------------------------------------- VOID WINAPI ChainGetIssuerMatchInfo ( IN PCCERT_CONTEXT pCertContext, OUT DWORD *pdwIssuerMatchFlags, OUT PCERT_AUTHORITY_KEY_ID_INFO* ppAuthKeyIdentifier ) { PCERT_EXTENSION pExt; LPVOID pv = NULL; BOOL fV1AuthKeyIdInfo = TRUE; PCERT_AUTHORITY_KEY_ID_INFO pAuthKeyIdentifier = NULL; DWORD dwIssuerMatchFlags = 0; pExt = CertFindExtension( szOID_AUTHORITY_KEY_IDENTIFIER, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension ); if ( pExt == NULL ) { fV1AuthKeyIdInfo = FALSE; pExt = CertFindExtension( szOID_AUTHORITY_KEY_IDENTIFIER2, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension ); } if ( pExt != NULL ) { pv = ChainAllocAndDecodeObject( pExt->pszObjId, pExt->Value.pbData, pExt->Value.cbData ); } if ( pv ) { if ( fV1AuthKeyIdInfo == FALSE ) { // NOTENOTE: Yes, this is a bit backwards but, right now but the // V1 structure is a bit easier to deal with and we // only support the V1 version of the V2 structure // anyway ChainConvertAuthKeyIdentifierFromV2ToV1( (PCERT_AUTHORITY_KEY_ID2_INFO)pv, &pAuthKeyIdentifier ); } else { pAuthKeyIdentifier = (PCERT_AUTHORITY_KEY_ID_INFO)pv; pv = NULL; } if ( pAuthKeyIdentifier != NULL ) { if ( ( pAuthKeyIdentifier->CertIssuer.cbData != 0 ) && ( pAuthKeyIdentifier->CertSerialNumber.cbData != 0 ) ) { dwIssuerMatchFlags |= CERT_EXACT_ISSUER_MATCH_FLAG; } if ( pAuthKeyIdentifier->KeyId.cbData != 0 ) { dwIssuerMatchFlags |= CERT_KEYID_ISSUER_MATCH_FLAG; } if (0 == dwIssuerMatchFlags) { delete (LPBYTE) pAuthKeyIdentifier; pAuthKeyIdentifier = NULL; } } } dwIssuerMatchFlags |= CERT_NAME_ISSUER_MATCH_FLAG; if (pv) PkiFree(pv); *pdwIssuerMatchFlags = dwIssuerMatchFlags; *ppAuthKeyIdentifier = pAuthKeyIdentifier; } //+--------------------------------------------------------------------------- // // Function: ChainConvertAuthKeyIdentifierFromV2ToV1 // // Synopsis: convert authority key identifier from V2 to V1 // //---------------------------------------------------------------------------- BOOL WINAPI ChainConvertAuthKeyIdentifierFromV2ToV1 ( IN PCERT_AUTHORITY_KEY_ID2_INFO pAuthKeyIdentifier2, OUT PCERT_AUTHORITY_KEY_ID_INFO* ppAuthKeyIdentifier ) { DWORD cb; PCERT_AUTHORITY_KEY_ID_INFO pAuthKeyIdentifier; BOOL fExactMatchAvailable = FALSE; if ( ( pAuthKeyIdentifier2->AuthorityCertIssuer.cAltEntry == 1 ) && ( pAuthKeyIdentifier2->AuthorityCertIssuer.rgAltEntry[0].dwAltNameChoice == CERT_ALT_NAME_DIRECTORY_NAME ) ) { fExactMatchAvailable = TRUE; } cb = sizeof( CERT_AUTHORITY_KEY_ID_INFO ); cb += pAuthKeyIdentifier2->KeyId.cbData; if ( fExactMatchAvailable == TRUE ) { cb += pAuthKeyIdentifier2->AuthorityCertIssuer.rgAltEntry[0].DirectoryName.cbData; cb += pAuthKeyIdentifier2->AuthorityCertSerialNumber.cbData; } pAuthKeyIdentifier = (PCERT_AUTHORITY_KEY_ID_INFO)PkiZeroAlloc(cb); if ( pAuthKeyIdentifier == NULL ) { return( FALSE ); } pAuthKeyIdentifier->KeyId.cbData = pAuthKeyIdentifier2->KeyId.cbData; pAuthKeyIdentifier->KeyId.pbData = (LPBYTE)pAuthKeyIdentifier + sizeof( CERT_AUTHORITY_KEY_ID_INFO ); memcpy( pAuthKeyIdentifier->KeyId.pbData, pAuthKeyIdentifier2->KeyId.pbData, pAuthKeyIdentifier->KeyId.cbData ); if ( fExactMatchAvailable == TRUE ) { pAuthKeyIdentifier->CertIssuer.cbData = pAuthKeyIdentifier2->AuthorityCertIssuer.rgAltEntry[0].DirectoryName.cbData; pAuthKeyIdentifier->CertIssuer.pbData = pAuthKeyIdentifier->KeyId.pbData + pAuthKeyIdentifier->KeyId.cbData; memcpy( pAuthKeyIdentifier->CertIssuer.pbData, pAuthKeyIdentifier2->AuthorityCertIssuer.rgAltEntry[0].DirectoryName.pbData, pAuthKeyIdentifier->CertIssuer.cbData ); pAuthKeyIdentifier->CertSerialNumber.cbData = pAuthKeyIdentifier2->AuthorityCertSerialNumber.cbData; pAuthKeyIdentifier->CertSerialNumber.pbData = pAuthKeyIdentifier->CertIssuer.pbData + pAuthKeyIdentifier->CertIssuer.cbData; memcpy( pAuthKeyIdentifier->CertSerialNumber.pbData, pAuthKeyIdentifier2->AuthorityCertSerialNumber.pbData, pAuthKeyIdentifier->CertSerialNumber.cbData ); } *ppAuthKeyIdentifier = pAuthKeyIdentifier; return( TRUE ); } //+--------------------------------------------------------------------------- // // Function: ChainFreeAuthorityKeyIdentifier // // Synopsis: free the authority key identifier // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeAuthorityKeyIdentifier ( IN PCERT_AUTHORITY_KEY_ID_INFO pAuthKeyIdInfo ) { PkiFree(pAuthKeyIdInfo); } //+--------------------------------------------------------------------------- // // Function: ChainProcessSpecialOrDuplicateOIDsInUsage // // Synopsis: process and removes special or duplicate OIDs from the usage // // For szOID_ANY_CERT_POLICY, frees the usage // //---------------------------------------------------------------------------- VOID WINAPI ChainProcessSpecialOrDuplicateOIDsInUsage ( IN OUT PCERT_ENHKEY_USAGE *ppUsage, IN OUT DWORD *pdwFlags ) { PCERT_ENHKEY_USAGE pUsage = *ppUsage; DWORD dwFlags = *pdwFlags; LPSTR *ppszOID; DWORD cOID; DWORD i; cOID = pUsage->cUsageIdentifier; ppszOID = pUsage->rgpszUsageIdentifier; i = 0; while (i < cOID) { BOOL fSpecialOrDuplicate = TRUE; LPSTR pszOID = ppszOID[i]; if (0 == strcmp(pszOID, szOID_ANY_CERT_POLICY)) dwFlags |= CHAIN_ANY_POLICY_FLAG; else { // Check for duplicate OID DWORD j; fSpecialOrDuplicate = FALSE; for (j = 0; j < i; j++) { if (0 == strcmp(ppszOID[j], ppszOID[i])) { fSpecialOrDuplicate = TRUE; break; } } } if (fSpecialOrDuplicate) { // Remove the special or duplicate OID string and move the remaining // strings up one. DWORD j; for (j = i; j + 1 < cOID; j++) ppszOID[j] = ppszOID[j + 1]; cOID--; pUsage->cUsageIdentifier = cOID; } else i++; } if (dwFlags & CHAIN_ANY_POLICY_FLAG) { PkiFree(pUsage); *ppUsage = NULL; } *pdwFlags = dwFlags; } //+--------------------------------------------------------------------------- // // Function: ChainConvertPoliciesToUsage // // Synopsis: extract the usage OIDs from the cert policies // //---------------------------------------------------------------------------- VOID WINAPI ChainConvertPoliciesToUsage ( IN PCERT_POLICIES_INFO pPolicy, IN OUT DWORD *pdwFlags, OUT PCERT_ENHKEY_USAGE *ppUsage ) { PCERT_ENHKEY_USAGE pUsage; LPSTR *ppszOID; DWORD cOID; DWORD i; cOID = pPolicy->cPolicyInfo; pUsage = (PCERT_ENHKEY_USAGE) PkiNonzeroAlloc( sizeof(CERT_ENHKEY_USAGE) + sizeof(LPSTR) * cOID); if (NULL == pUsage) { *pdwFlags |= CHAIN_INVALID_POLICY_FLAG; *ppUsage = NULL; return; } ppszOID = (LPSTR *) &pUsage[1]; pUsage->cUsageIdentifier = cOID; pUsage->rgpszUsageIdentifier = ppszOID; for (i = 0; i < cOID; i++) ppszOID[i] = pPolicy->rgPolicyInfo[i].pszPolicyIdentifier; *ppUsage = pUsage; ChainProcessSpecialOrDuplicateOIDsInUsage(ppUsage, pdwFlags); } //+--------------------------------------------------------------------------- // // Function: ChainRemoveDuplicatePolicyMappings // // Synopsis: remove any duplicate mappings // //---------------------------------------------------------------------------- VOID WINAPI ChainRemoveDuplicatePolicyMappings ( IN OUT PCERT_POLICY_MAPPINGS_INFO pInfo ) { DWORD cMap = pInfo->cPolicyMapping; PCERT_POLICY_MAPPING pMap = pInfo->rgPolicyMapping; DWORD i; i = 0; while (i < cMap) { DWORD j; for (j = 0; j < i; j++) { if (0 == strcmp(pMap[i].pszSubjectDomainPolicy, pMap[j].pszSubjectDomainPolicy)) break; } if (j < i) { // Duplicate // // Remove the duplicate mapping and move the remaining // mappings up one. for (j = i; j + 1 < cMap; j++) pMap[j] = pMap[j + 1]; cMap--; pInfo->cPolicyMapping = cMap; } else i++; } } //+--------------------------------------------------------------------------- // // Function: ChainGetPoliciesInfo // // Synopsis: allocate and return the policies and usage info // //---------------------------------------------------------------------------- VOID WINAPI ChainGetPoliciesInfo ( IN PCCERT_CONTEXT pCertContext, IN OUT PCHAIN_POLICIES_INFO pPoliciesInfo ) { DWORD cExt = pCertContext->pCertInfo->cExtension; PCERT_EXTENSION rgExt = pCertContext->pCertInfo->rgExtension; DWORD i; DWORD cbData; for (i = 0; i < CHAIN_ISS_OR_APP_COUNT; i++ ) { PCHAIN_ISS_OR_APP_INFO pInfo = &pPoliciesInfo->rgIssOrAppInfo[i]; PCERT_EXTENSION pExt; pExt = CertFindExtension( CHAIN_ISS_INDEX == i ? szOID_CERT_POLICIES : szOID_APPLICATION_CERT_POLICIES, cExt, rgExt); if (pExt) { pInfo->pPolicy = (PCERT_POLICIES_INFO) ChainAllocAndDecodeObject( X509_CERT_POLICIES, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pInfo->pPolicy) pInfo->dwFlags |= CHAIN_INVALID_POLICY_FLAG; else ChainConvertPoliciesToUsage(pInfo->pPolicy, &pInfo->dwFlags, &pInfo->pUsage); } else if (CHAIN_APP_INDEX == i) { pExt = CertFindExtension(szOID_ENHANCED_KEY_USAGE, cExt, rgExt); if (pExt) { pInfo->pUsage = (PCERT_ENHKEY_USAGE) ChainAllocAndDecodeObject( X509_ENHANCED_KEY_USAGE, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pInfo->pUsage) pInfo->dwFlags |= CHAIN_INVALID_POLICY_FLAG; else ChainProcessSpecialOrDuplicateOIDsInUsage( &pInfo->pUsage, &pInfo->dwFlags); } } pExt = CertFindExtension( CHAIN_ISS_INDEX == i ? szOID_POLICY_MAPPINGS : szOID_APPLICATION_POLICY_MAPPINGS, cExt, rgExt); if (pExt) { pInfo->pMappings = (PCERT_POLICY_MAPPINGS_INFO) ChainAllocAndDecodeObject( X509_POLICY_MAPPINGS, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pInfo->pMappings) pInfo->dwFlags |= CHAIN_INVALID_POLICY_FLAG; else ChainRemoveDuplicatePolicyMappings(pInfo->pMappings); } pExt = CertFindExtension( CHAIN_ISS_INDEX == i ? szOID_POLICY_CONSTRAINTS : szOID_APPLICATION_POLICY_CONSTRAINTS, cExt, rgExt); if (pExt) { pInfo->pConstraints = (PCERT_POLICY_CONSTRAINTS_INFO) ChainAllocAndDecodeObject( X509_POLICY_CONSTRAINTS, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pInfo->pConstraints) pInfo->dwFlags |= CHAIN_INVALID_POLICY_FLAG; } } cbData = 0; if (CertGetCertificateContextProperty( pCertContext, CERT_ENHKEY_USAGE_PROP_ID, NULL, // pbData &cbData ) && 0 != cbData) { BYTE *pbData; pbData = (BYTE *) PkiNonzeroAlloc(cbData); if (pbData) { if (CertGetCertificateContextProperty( pCertContext, CERT_ENHKEY_USAGE_PROP_ID, pbData, &cbData )) pPoliciesInfo->pPropertyUsage = (PCERT_ENHKEY_USAGE) ChainAllocAndDecodeObject( X509_ENHANCED_KEY_USAGE, pbData, cbData ); PkiFree(pbData); } if (NULL == pPoliciesInfo->pPropertyUsage) pPoliciesInfo->rgIssOrAppInfo[CHAIN_APP_INDEX].dwFlags |= CHAIN_INVALID_POLICY_FLAG; } } //+--------------------------------------------------------------------------- // // Function: ChainFreePoliciesInfo // // Synopsis: free the policies and usage info // //---------------------------------------------------------------------------- VOID WINAPI ChainFreePoliciesInfo ( IN OUT PCHAIN_POLICIES_INFO pPoliciesInfo ) { DWORD i; for (i = 0; i < CHAIN_ISS_OR_APP_COUNT; i++ ) { PCHAIN_ISS_OR_APP_INFO pInfo = &pPoliciesInfo->rgIssOrAppInfo[i]; PkiFree(pInfo->pPolicy); PkiFree(pInfo->pUsage); PkiFree(pInfo->pMappings); PkiFree(pInfo->pConstraints); } PkiFree(pPoliciesInfo->pPropertyUsage); } //+--------------------------------------------------------------------------- // // Function: ChainGetBasicConstraintsInfo // // Synopsis: alloc and return the basic constraints info. // //---------------------------------------------------------------------------- BOOL WINAPI ChainGetBasicConstraintsInfo ( IN PCCERT_CONTEXT pCertContext, IN OUT PCERT_BASIC_CONSTRAINTS2_INFO *ppInfo ) { BOOL fResult; PCERT_EXTENSION pExt; PCERT_BASIC_CONSTRAINTS2_INFO pInfo = NULL; PCERT_BASIC_CONSTRAINTS_INFO pLegacyInfo = NULL; pExt = CertFindExtension( szOID_BASIC_CONSTRAINTS2, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension ); if (pExt) { pInfo = (PCERT_BASIC_CONSTRAINTS2_INFO) ChainAllocAndDecodeObject( X509_BASIC_CONSTRAINTS2, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pInfo) goto DecodeError; } else { // Try to find the legacy extension pExt = CertFindExtension( szOID_BASIC_CONSTRAINTS, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension ); if (pExt) { pLegacyInfo = (PCERT_BASIC_CONSTRAINTS_INFO) ChainAllocAndDecodeObject( X509_BASIC_CONSTRAINTS, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pLegacyInfo) goto DecodeError; // Convert to new format pInfo = (PCERT_BASIC_CONSTRAINTS2_INFO) PkiZeroAlloc( sizeof(CERT_BASIC_CONSTRAINTS2_INFO)); if (NULL == pInfo) goto OutOfMemory; if (pLegacyInfo->SubjectType.cbData > 0 && (pLegacyInfo->SubjectType.pbData[0] & CERT_CA_SUBJECT_FLAG)) { pInfo->fCA = TRUE; pInfo->fPathLenConstraint = pLegacyInfo->fPathLenConstraint; pInfo->dwPathLenConstraint = pLegacyInfo->dwPathLenConstraint; } } } fResult = TRUE; CommonReturn: if (pLegacyInfo) PkiFree(pLegacyInfo); *ppInfo = pInfo; return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(DecodeError) TRACE_ERROR(OutOfMemory) } //+--------------------------------------------------------------------------- // // Function: ChainFreeBasicConstraintsInfo // // Synopsis: free the basic constraints info // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeBasicConstraintsInfo ( IN OUT PCERT_BASIC_CONSTRAINTS2_INFO pInfo ) { PkiFree(pInfo); } //+--------------------------------------------------------------------------- // // Function: ChainGetKeyUsage // // Synopsis: alloc and return the key usage. // //---------------------------------------------------------------------------- BOOL WINAPI ChainGetKeyUsage ( IN PCCERT_CONTEXT pCertContext, IN OUT PCRYPT_BIT_BLOB *ppKeyUsage ) { BOOL fResult; PCERT_EXTENSION pExt; PCRYPT_BIT_BLOB pKeyUsage = NULL; pExt = CertFindExtension( szOID_KEY_USAGE, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension ); if (pExt) { pKeyUsage = (PCRYPT_BIT_BLOB) ChainAllocAndDecodeObject( X509_KEY_USAGE, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pKeyUsage) goto DecodeError; } fResult = TRUE; CommonReturn: *ppKeyUsage = pKeyUsage; return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(DecodeError) } //+--------------------------------------------------------------------------- // // Function: ChainFreeKeyUsage // // Synopsis: free the key usage // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeKeyUsage ( IN OUT PCRYPT_BIT_BLOB pKeyUsage ) { PkiFree(pKeyUsage); } //+--------------------------------------------------------------------------- // // Function: ChainGetSelfSignedStatus // // Synopsis: return status bits specifying if the certificate is self signed // and if so, if it is signature valid // //---------------------------------------------------------------------------- VOID WINAPI ChainGetSelfSignedStatus ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCERTOBJECT pCertObject, IN OUT DWORD *pdwIssuerStatusFlags ) { DWORD dwInfoStatus = 0; // If the certificate has an AKI, then, ignore name matching if (ChainGetMatchInfoStatus(pCertObject, pCertObject, &dwInfoStatus) && (CERT_TRUST_HAS_NAME_MATCH_ISSUER != dwInfoStatus)) { *pdwIssuerStatusFlags |= CERT_ISSUER_SELF_SIGNED_FLAG; if (CryptVerifyCertificateSignatureEx( NULL, // hCryptProv X509_ASN_ENCODING, CRYPT_VERIFY_CERT_SIGN_SUBJECT_CERT, (void *) pCertObject->CertContext(), CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void *) pCertObject->CertContext(), 0, // dwFlags NULL // pvReserved )) *pdwIssuerStatusFlags |= CERT_ISSUER_VALID_SIGNATURE_FLAG; CertPerfIncrementChainVerifyCertSignatureCount(); } } //+--------------------------------------------------------------------------- // // Function: ChainGetRootStoreStatus // // Synopsis: determine if the certificate with the given hash is in the // root store // // Assumption: Chain engine is locked once in the calling thread. //---------------------------------------------------------------------------- VOID WINAPI ChainGetRootStoreStatus ( IN HCERTSTORE hRoot, IN HCERTSTORE hRealRoot, IN BYTE rgbCertHash[ CHAINHASHLEN ], IN OUT DWORD *pdwIssuerStatusFlags ) { CRYPT_HASH_BLOB HashBlob; PCCERT_CONTEXT pCertContext; HashBlob.cbData = CHAINHASHLEN; HashBlob.pbData = rgbCertHash; pCertContext = CertFindCertificateInStore( hRoot, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_MD5_HASH, (LPVOID) &HashBlob, NULL ); if ( pCertContext ) { CertFreeCertificateContext( pCertContext ); if ( hRoot == hRealRoot ) { *pdwIssuerStatusFlags |= CERT_ISSUER_TRUSTED_ROOT_FLAG; return; } pCertContext = CertFindCertificateInStore( hRealRoot, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_MD5_HASH, (LPVOID) &HashBlob, NULL ); if ( pCertContext ) { CertFreeCertificateContext( pCertContext ); *pdwIssuerStatusFlags |= CERT_ISSUER_TRUSTED_ROOT_FLAG; } } } //+=========================================================================== // CCertObjectCache helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainCreateCertificateObjectCache // // Synopsis: create certificate object cache object // //---------------------------------------------------------------------------- BOOL WINAPI ChainCreateCertificateObjectCache ( IN DWORD MaxIndexEntries, OUT PCCERTOBJECTCACHE* ppCertObjectCache ) { BOOL fResult = FALSE; PCCERTOBJECTCACHE pCertObjectCache = NULL; pCertObjectCache = new CCertObjectCache( MaxIndexEntries, fResult ); if ( pCertObjectCache != NULL ) { if ( fResult == TRUE ) { *ppCertObjectCache = pCertObjectCache; } else { delete pCertObjectCache; } } else { SetLastError( (DWORD) E_OUTOFMEMORY ); } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainFreeCertificateObjectCache // // Synopsis: free the certificate object cache object // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeCertificateObjectCache ( IN PCCERTOBJECTCACHE pCertObjectCache ) { delete pCertObjectCache; } //+--------------------------------------------------------------------------- // // Function: CertObjectCacheOnRemovalFromPrimaryIndex // // Synopsis: removes the cert object from all other indexes and also // removes the reference on the cert object. // //---------------------------------------------------------------------------- VOID WINAPI CertObjectCacheOnRemovalFromPrimaryIndex ( IN LPVOID pv, IN OPTIONAL LPVOID pvRemovalContext ) { PCCERTOBJECT pCertObject = (PCCERTOBJECT) pv; I_CryptRemoveLruEntry( pCertObject->IdentifierIndexEntry(), LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); I_CryptRemoveLruEntry( pCertObject->SubjectNameIndexEntry(), LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); I_CryptRemoveLruEntry( pCertObject->KeyIdIndexEntry(), LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); I_CryptRemoveLruEntry( pCertObject->PublicKeyHashIndexEntry(), LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); pCertObject->Release(); CertPerfDecrementChainCertCacheCount(); } //+--------------------------------------------------------------------------- // // Function: CertObjectCacheOnRemovalFromEndHashIndex // // Synopsis: removes the reference on the end cert object. // //---------------------------------------------------------------------------- VOID WINAPI CertObjectCacheOnRemovalFromEndHashIndex ( IN LPVOID pv, IN LPVOID pvRemovalContext ) { PCCERTOBJECT pCertObject = (PCCERTOBJECT) pv; pCertObject->Release(); CertPerfDecrementChainCertCacheCount(); } //+--------------------------------------------------------------------------- // // Function: CertObjectCacheHashMd5Identifier // // Synopsis: DWORD hash an MD5 identifier. This is done by taking the // first four bytes of the MD5 hash since there is enough // randomness already // //---------------------------------------------------------------------------- DWORD WINAPI CertObjectCacheHashMd5Identifier ( IN PCRYPT_DATA_BLOB pIdentifier ) { if ( sizeof(DWORD) > pIdentifier->cbData ) { return 0; } else { return( *( (DWORD UNALIGNED *)pIdentifier->pbData ) ); } } //+--------------------------------------------------------------------------- // // Function: CertObjectCacheHashNameIdentifier // // Synopsis: DWORD hash a subject or issuer name. // //---------------------------------------------------------------------------- DWORD WINAPI CertObjectCacheHashNameIdentifier ( IN PCRYPT_DATA_BLOB pIdentifier ) { DWORD dwHash = 0; DWORD cb = pIdentifier->cbData; LPBYTE pb = pIdentifier->pbData; while ( cb-- ) { if ( dwHash & 0x80000000 ) { dwHash = ( dwHash << 1 ) | 1; } else { dwHash = dwHash << 1; } dwHash += *pb++; } return( dwHash ); } //+--------------------------------------------------------------------------- // // Function: ChainCreateCertificateObjectIdentifier // // Synopsis: create an object identifier given the issuer name and serial // number. This is done using an MD5 hash over the content // //---------------------------------------------------------------------------- VOID WINAPI ChainCreateCertificateObjectIdentifier ( IN PCERT_NAME_BLOB pIssuer, IN PCRYPT_INTEGER_BLOB pSerialNumber, OUT CERT_OBJECT_IDENTIFIER ObjectIdentifier ) { MD5_CTX md5ctx; MD5Init( &md5ctx ); MD5Update( &md5ctx, pIssuer->pbData, pIssuer->cbData ); MD5Update( &md5ctx, pSerialNumber->pbData, pSerialNumber->cbData ); MD5Final( &md5ctx ); assert(CHAINHASHLEN == MD5DIGESTLEN); memcpy( ObjectIdentifier, md5ctx.digest, CHAINHASHLEN ); } //+=========================================================================== // CChainPathObject helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainCreatePathObject // // Synopsis: create a path object, note since it is a ref-counted // object, freeing occurs by doing a pCertObject->Release // //---------------------------------------------------------------------------- BOOL WINAPI ChainCreatePathObject ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCERTOBJECT pCertObject, IN OPTIONAL HCERTSTORE hAdditionalStore, OUT PCCHAINPATHOBJECT *ppPathObject ) { BOOL fResult = TRUE; BOOL fAddedToCreationCache = TRUE; PCCHAINPATHOBJECT pPathObject = NULL; pPathObject = pCallContext->FindPathObjectInCreationCache( pCertObject->CertHash() ); if ( pPathObject != NULL ) { *ppPathObject = pPathObject; return( TRUE ); } pPathObject = new CChainPathObject( pCallContext, FALSE, // fCyclic (LPVOID) pCertObject, hAdditionalStore, fResult, fAddedToCreationCache ); if ( pPathObject != NULL ) { if (!fResult) { if (!fAddedToCreationCache) { delete pPathObject; } pPathObject = NULL; } } else { fResult = FALSE; } *ppPathObject = pPathObject; return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainCreateCyclicPathObject // // Synopsis: create a path object, note since it is a ref-counted // object, freeing occurs by doing a pCertObject->Release // //---------------------------------------------------------------------------- BOOL WINAPI ChainCreateCyclicPathObject ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCHAINPATHOBJECT pPathObject, OUT PCCHAINPATHOBJECT *ppCyclicPathObject ) { BOOL fResult = TRUE; BOOL fAddedToCreationCache = TRUE; PCCHAINPATHOBJECT pCyclicPathObject = NULL; pCyclicPathObject = new CChainPathObject( pCallContext, TRUE, // fCyclic (LPVOID) pPathObject, NULL, // hAdditionalStore fResult, fAddedToCreationCache ); if ( pCyclicPathObject != NULL ) { if (!fResult) { if (!fAddedToCreationCache) { delete pCyclicPathObject; } pCyclicPathObject = NULL; } } else { fResult = FALSE; } *ppCyclicPathObject = pCyclicPathObject; return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainAllocAndCopyOID // // Synopsis: allocate and copy OID // //---------------------------------------------------------------------------- LPSTR WINAPI ChainAllocAndCopyOID ( IN LPSTR pszSrcOID ) { DWORD cchOID; LPSTR pszDstOID; cchOID = strlen(pszSrcOID) + 1; pszDstOID = (LPSTR) PkiNonzeroAlloc(cchOID); if (NULL == pszDstOID) return NULL; memcpy(pszDstOID, pszSrcOID, cchOID); return pszDstOID; } //+--------------------------------------------------------------------------- // // Function: ChainFreeOID // // Synopsis: free allocated OID // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeOID ( IN OUT LPSTR pszOID ) { PkiFree(pszOID); } //+--------------------------------------------------------------------------- // // Function: ChainAllocAndCopyUsage // // Synopsis: allocates and copies usage OIDs. // //---------------------------------------------------------------------------- BOOL WINAPI ChainAllocAndCopyUsage ( IN PCERT_ENHKEY_USAGE pSrcUsage, OUT PCERT_ENHKEY_USAGE *ppDstUsage ) { BOOL fResult; PCERT_ENHKEY_USAGE pDstUsage = NULL; DWORD cOID; LPSTR *ppszDstOID; DWORD i; if (NULL == pSrcUsage) goto SuccessReturn; cOID = pSrcUsage->cUsageIdentifier; pDstUsage = (PCERT_ENHKEY_USAGE) PkiZeroAlloc( sizeof(CERT_ENHKEY_USAGE) + sizeof(LPSTR) * cOID); if (NULL == pDstUsage) goto OutOfMemory; ppszDstOID = (LPSTR *) &pDstUsage[1]; pDstUsage->cUsageIdentifier = cOID; pDstUsage->rgpszUsageIdentifier = ppszDstOID; for (i = 0; i < cOID; i++) { ppszDstOID[i] = ChainAllocAndCopyOID(pSrcUsage->rgpszUsageIdentifier[i]); if (NULL == ppszDstOID[i]) goto OutOfMemory; } SuccessReturn: fResult = TRUE; CommonReturn: *ppDstUsage = pDstUsage; return fResult; ErrorReturn: if (pDstUsage) { ChainFreeUsage(pDstUsage); pDstUsage = NULL; } fResult = FALSE; goto CommonReturn; SET_ERROR(OutOfMemory, E_OUTOFMEMORY) } //+--------------------------------------------------------------------------- // // Function: ChainFreeUsage // // Synopsis: frees usage OIDs allocated by ChainAllocAndCopyUsage // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeUsage ( IN OUT PCERT_ENHKEY_USAGE pUsage ) { if (pUsage) { DWORD cOID = pUsage->cUsageIdentifier; LPSTR *ppszOID = pUsage->rgpszUsageIdentifier; DWORD i; for (i = 0; i < cOID; i++) ChainFreeOID(ppszOID[i]); PkiFree(pUsage); } } //+--------------------------------------------------------------------------- // // Function: ChainIsOIDInUsage // // Synopsis: returns TRUE if the OID is in the usage // //---------------------------------------------------------------------------- BOOL WINAPI ChainIsOIDInUsage ( IN LPSTR pszOID, IN PCERT_ENHKEY_USAGE pUsage ) { DWORD cOID; DWORD i; assert(pUsage); cOID = pUsage->cUsageIdentifier; for (i = 0; i < cOID; i++){ if (0 == strcmp(pszOID, pUsage->rgpszUsageIdentifier[i])) return TRUE; } return FALSE; } //+--------------------------------------------------------------------------- // // Function: ChainIntersectUsages // // Synopsis: returns the intersection of the two usages // //---------------------------------------------------------------------------- VOID WINAPI ChainIntersectUsages ( IN PCERT_ENHKEY_USAGE pCertUsage, IN OUT PCERT_ENHKEY_USAGE pRestrictedUsage ) { LPSTR *ppszOID; DWORD cOID; DWORD i; cOID = pRestrictedUsage->cUsageIdentifier; ppszOID = pRestrictedUsage->rgpszUsageIdentifier; i = 0; while (i < cOID) { if (ChainIsOIDInUsage(ppszOID[i], pCertUsage)) i++; else { // Remove the OID string and move the remaining // strings up one. DWORD j; ChainFreeOID(ppszOID[i]); for (j = i; j + 1 < cOID; j++) ppszOID[j] = ppszOID[j + 1]; cOID--; pRestrictedUsage->cUsageIdentifier = cOID; } } } //+--------------------------------------------------------------------------- // // Function: ChainFreeAndClearRestrictedUsageInfo // // Synopsis: frees allocated restricted usage info // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeAndClearRestrictedUsageInfo( IN OUT PCHAIN_RESTRICTED_USAGE_INFO pInfo ) { ChainFreeUsage(pInfo->pIssuanceRestrictedUsage); ChainFreeUsage(pInfo->pIssuanceMappedUsage); PkiFree(pInfo->rgdwIssuanceMappedIndex); // fRequireIssuancePolicy ChainFreeUsage(pInfo->pApplicationRestrictedUsage); ChainFreeUsage(pInfo->pApplicationMappedUsage); PkiFree(pInfo->rgdwApplicationMappedIndex); ChainFreeUsage(pInfo->pPropertyRestrictedUsage); memset(pInfo, 0, sizeof(*pInfo)); } //+--------------------------------------------------------------------------- // // Function: ChainCalculateRestrictedUsage // // Synopsis: update the restricted and mapped usage using the cert's // usage and optional policy mappings // //---------------------------------------------------------------------------- BOOL WINAPI ChainCalculateRestrictedUsage ( IN PCERT_ENHKEY_USAGE pCertUsage, IN OPTIONAL PCERT_POLICY_MAPPINGS_INFO pMappings, IN OUT PCERT_ENHKEY_USAGE *ppRestrictedUsage, IN OUT PCERT_ENHKEY_USAGE *ppMappedUsage, IN OUT LPDWORD *ppdwMappedIndex ) { BOOL fResult; PCERT_ENHKEY_USAGE pNewMappedUsage = NULL; LPDWORD pdwNewMappedIndex = NULL; if (pCertUsage) { if (NULL == *ppRestrictedUsage) { // Top most, first certificate with a usage restriction assert(NULL == *ppMappedUsage); assert(NULL == *ppdwMappedIndex); if (!ChainAllocAndCopyUsage(pCertUsage, ppRestrictedUsage)) goto AllocAndCopyUsageError; } else { PCERT_ENHKEY_USAGE pRestrictedUsage = *ppRestrictedUsage; PCERT_ENHKEY_USAGE pMappedUsage = *ppMappedUsage; if (NULL == pMappedUsage) { // Take the intersection of the restricted and cert's // usage ChainIntersectUsages(pCertUsage, pRestrictedUsage); } else { // Take the intersection of the mapped and cert's // usage. If removed from the mapped usage, // we might also need to remove from the restricted usage. LPDWORD pdwMappedIndex = *ppdwMappedIndex; LPSTR *ppszOID; DWORD cOID; DWORD i; assert(pdwMappedIndex); cOID = pMappedUsage->cUsageIdentifier; ppszOID = pMappedUsage->rgpszUsageIdentifier; i = 0; while (i < cOID) { if (ChainIsOIDInUsage(ppszOID[i], pCertUsage)) i++; else { // If no other mappings to the restricted OID, then, // remove the restricted OID. DWORD j; BOOL fRemoveRestricted; if ((0 == i || pdwMappedIndex[i - 1] != pdwMappedIndex[i]) && (i + 1 == cOID || pdwMappedIndex[i] != pdwMappedIndex[i + 1])) { // Remove the restricted OID we are mapped to. LPSTR *ppszRestrictedOID = pRestrictedUsage->rgpszUsageIdentifier; DWORD cRestrictedOID = pRestrictedUsage->cUsageIdentifier; fRemoveRestricted = TRUE; j = pdwMappedIndex[i]; assert(j < cRestrictedOID); if (j < cRestrictedOID) ChainFreeOID(ppszRestrictedOID[j]); for ( ; j + 1 < cRestrictedOID; j++) ppszRestrictedOID[j] = ppszRestrictedOID[j + 1]; cRestrictedOID--; pRestrictedUsage->cUsageIdentifier = cRestrictedOID; } else fRemoveRestricted = FALSE; // Remove the OID string and mapped index. Move the // remaining strings and indices up one. ChainFreeOID(ppszOID[i]); for (j = i; j + 1 < cOID; j++) { ppszOID[j] = ppszOID[j + 1]; pdwMappedIndex[j] = pdwMappedIndex[j + 1]; if (fRemoveRestricted) { assert(0 < pdwMappedIndex[j]); pdwMappedIndex[j] -= 1; } } cOID--; pMappedUsage->cUsageIdentifier = cOID; } } } } } // else // No restrictions added by certificate if (pMappings) { PCERT_ENHKEY_USAGE pRestrictedUsage = *ppRestrictedUsage; PCERT_ENHKEY_USAGE pMappedUsage = *ppMappedUsage; if (NULL == pRestrictedUsage || 0 == pRestrictedUsage->cUsageIdentifier) { // Nothing to be mapped. assert(NULL == pMappedUsage || 0 == pMappedUsage->cUsageIdentifier); } else { LPDWORD pdwMappedIndex; PCERT_ENHKEY_USAGE pSrcUsage; LPSTR *ppszSrcOID; DWORD cSrcOID; DWORD iSrc; DWORD cMap; PCERT_POLICY_MAPPING pMap; DWORD cNewOID; LPSTR *ppszNewOID; if (pMappedUsage) { // Subsequent mapping assert(0 < pMappedUsage->cUsageIdentifier); pSrcUsage = pMappedUsage; pdwMappedIndex = *ppdwMappedIndex; assert(pdwMappedIndex); } else { // First mapping pSrcUsage = pRestrictedUsage; pdwMappedIndex = NULL; } cSrcOID = pSrcUsage->cUsageIdentifier; ppszSrcOID = pSrcUsage->rgpszUsageIdentifier; cMap = pMappings->cPolicyMapping; pMap = pMappings->rgPolicyMapping; // Note, all duplicates have been remove from usage and // mappings cNewOID = cSrcOID + cMap; pNewMappedUsage = (PCERT_ENHKEY_USAGE) PkiZeroAlloc( sizeof(CERT_ENHKEY_USAGE) + sizeof(LPSTR) * cNewOID); if (NULL == pNewMappedUsage) goto OutOfMemory; ppszNewOID = (LPSTR *) &pNewMappedUsage[1]; pNewMappedUsage->cUsageIdentifier = cNewOID; pNewMappedUsage->rgpszUsageIdentifier = ppszNewOID; pdwNewMappedIndex = (LPDWORD) PkiZeroAlloc( sizeof(DWORD) * cNewOID); if (NULL == pdwNewMappedIndex) goto OutOfMemory; cNewOID = 0; for (iSrc = 0; iSrc < cSrcOID; iSrc++) { DWORD iMap; BOOL fMapped = FALSE; for (iMap = 0; iMap < cMap; iMap++) { if (0 == strcmp(ppszSrcOID[iSrc], pMap[iMap].pszIssuerDomainPolicy)) { assert(cNewOID < pNewMappedUsage->cUsageIdentifier); ppszNewOID[cNewOID] = ChainAllocAndCopyOID( pMap[iMap].pszSubjectDomainPolicy); if (NULL == ppszNewOID[cNewOID]) goto OutOfMemory; if (pdwMappedIndex) pdwNewMappedIndex[cNewOID] = pdwMappedIndex[iSrc]; else pdwNewMappedIndex[cNewOID] = iSrc; cNewOID++; fMapped = TRUE; } } if (!fMapped) { assert(cNewOID < pNewMappedUsage->cUsageIdentifier); ppszNewOID[cNewOID] = ChainAllocAndCopyOID(ppszSrcOID[iSrc]); if (NULL == ppszNewOID[cNewOID]) goto OutOfMemory; if (pdwMappedIndex) pdwNewMappedIndex[cNewOID] = pdwMappedIndex[iSrc]; else pdwNewMappedIndex[cNewOID] = iSrc; cNewOID++; } } assert(cNewOID >= cSrcOID); pNewMappedUsage->cUsageIdentifier = cNewOID; if (pMappedUsage) { ChainFreeUsage(pMappedUsage); PkiFree(pdwMappedIndex); } *ppMappedUsage = pNewMappedUsage; *ppdwMappedIndex = pdwNewMappedIndex; } } fResult = TRUE; CommonReturn: return fResult; ErrorReturn: ChainFreeUsage(pNewMappedUsage); PkiFree(pdwNewMappedIndex); fResult = FALSE; goto CommonReturn; TRACE_ERROR(AllocAndCopyUsageError) TRACE_ERROR(OutOfMemory) } //+--------------------------------------------------------------------------- // // Function: ChainGetUsageStatus // // Synopsis: get the usage status // //---------------------------------------------------------------------------- VOID WINAPI ChainGetUsageStatus ( IN PCERT_ENHKEY_USAGE pRequestedUsage, IN PCERT_ENHKEY_USAGE pAvailableUsage, IN DWORD dwMatchType, IN OUT PCERT_TRUST_STATUS pStatus ) { DWORD cRequested; DWORD cAvailable; DWORD cFound; BOOL fFound; if ( pAvailableUsage == NULL ) { return; } if ( ( pRequestedUsage->cUsageIdentifier > pAvailableUsage->cUsageIdentifier ) && ( dwMatchType == USAGE_MATCH_TYPE_AND ) ) { pStatus->dwErrorStatus |= CERT_TRUST_IS_NOT_VALID_FOR_USAGE; return; } for ( cRequested = 0, cFound = 0; cRequested < pRequestedUsage->cUsageIdentifier; cRequested++ ) { for ( cAvailable = 0, fFound = FALSE; ( cAvailable < pAvailableUsage->cUsageIdentifier ) && ( fFound == FALSE ); cAvailable++ ) { // NOTE: Optimize compares of OIDs. Perhaps with a different // encoding if ( strcmp( pRequestedUsage->rgpszUsageIdentifier[ cRequested ], pAvailableUsage->rgpszUsageIdentifier[ cAvailable ] ) == 0 ) { fFound = TRUE; } } if ( fFound == TRUE ) { cFound += 1; } } if ( ( dwMatchType == USAGE_MATCH_TYPE_AND ) && ( cFound != pRequestedUsage->cUsageIdentifier ) ) { pStatus->dwErrorStatus |= CERT_TRUST_IS_NOT_VALID_FOR_USAGE; } else if ( ( dwMatchType == USAGE_MATCH_TYPE_OR ) && ( cFound == 0 ) ) { pStatus->dwErrorStatus |= CERT_TRUST_IS_NOT_VALID_FOR_USAGE; } } //+--------------------------------------------------------------------------- // // Function: ChainOrInStatusBits // // Synopsis: bit or in the status bits from the source into the destination // //---------------------------------------------------------------------------- VOID WINAPI ChainOrInStatusBits ( IN PCERT_TRUST_STATUS pDestStatus, IN PCERT_TRUST_STATUS pSourceStatus ) { pDestStatus->dwErrorStatus |= pSourceStatus->dwErrorStatus; pDestStatus->dwInfoStatus |= pSourceStatus->dwInfoStatus; } //+--------------------------------------------------------------------------- // // Function: ChainGetMatchInfoStatus // // Synopsis: return the info status used to match the issuer // // For a match returns TRUE, where dwInfoStatus can be // one of the following: // - CERT_TRUST_HAS_EXACT_MATCH_ISSUER | // CERT_TRUST_HAS_PREFERRED_ISSUER // - CERT_TRUST_HAS_KEY_MATCH_ISSUER | // CERT_TRUST_HAS_PREFERRED_ISSUER // - CERT_TRUST_HAS_KEY_MATCH_ISSUER (nonmatching AKI exact match) // - CERT_TRUST_HAS_NAME_MATCH_ISSUER | // CERT_TRUST_HAS_PREFERRED_ISSUER // - CERT_TRUST_HAS_NAME_MATCH_ISSUER (nonmatching AKI) // // For no match returns FALSE with dwInfoStatus set to the // following: // - CERT_TRUST_HAS_KEY_MATCH_ISSUER // //---------------------------------------------------------------------------- BOOL WINAPI ChainGetMatchInfoStatus ( IN PCCERTOBJECT pIssuerObject, IN PCCERTOBJECT pSubjectObject, IN OUT DWORD *pdwInfoStatus ) { BOOL fResult = FALSE; DWORD dwInfoStatus = 0; DWORD dwPreferredStatus = CERT_TRUST_HAS_PREFERRED_ISSUER; PCERT_INFO pSubjectInfo = pSubjectObject->CertContext()->pCertInfo; PCERT_AUTHORITY_KEY_ID_INFO pAKI = pSubjectObject->AuthorityKeyIdentifier(); PCERT_INFO pIssuerInfo = pIssuerObject->CertContext()->pCertInfo; if (pAKI) { if ( ( pAKI->CertIssuer.cbData != 0 ) && ( pAKI->CertSerialNumber.cbData != 0 ) ) { DWORD cbAuthIssuerName; LPBYTE pbAuthIssuerName; DWORD cbAuthSerialNumber; LPBYTE pbAuthSerialNumber; cbAuthIssuerName = pAKI->CertIssuer.cbData; pbAuthIssuerName = pAKI->CertIssuer.pbData; cbAuthSerialNumber = pAKI->CertSerialNumber.cbData; pbAuthSerialNumber = pAKI->CertSerialNumber.pbData; if ( ( cbAuthIssuerName == pIssuerInfo->Issuer.cbData ) && ( memcmp( pbAuthIssuerName, pIssuerInfo->Issuer.pbData, cbAuthIssuerName ) == 0 ) && ( cbAuthSerialNumber == pIssuerInfo->SerialNumber.cbData ) && ( memcmp( pbAuthSerialNumber, pIssuerInfo->SerialNumber.pbData, cbAuthSerialNumber ) == 0 ) ) { dwInfoStatus = CERT_TRUST_HAS_EXACT_MATCH_ISSUER | CERT_TRUST_HAS_PREFERRED_ISSUER; goto SuccessReturn; } else { // Doesn't have preferred match dwPreferredStatus = 0; } } if ( pAKI->KeyId.cbData != 0 ) { DWORD cbAuthKeyIdentifier; LPBYTE pbAuthKeyIdentifier; DWORD cbIssuerKeyIdentifier; LPBYTE pbIssuerKeyIdentifier; cbAuthKeyIdentifier = pAKI->KeyId.cbData; pbAuthKeyIdentifier = pAKI->KeyId.pbData; cbIssuerKeyIdentifier = pIssuerObject->KeyIdentifierSize(); pbIssuerKeyIdentifier = pIssuerObject->KeyIdentifier(); if ( ( cbAuthKeyIdentifier == cbIssuerKeyIdentifier ) && ( memcmp( pbAuthKeyIdentifier, pbIssuerKeyIdentifier, cbAuthKeyIdentifier ) == 0 ) ) { dwInfoStatus = dwPreferredStatus | CERT_TRUST_HAS_KEY_MATCH_ISSUER; goto SuccessReturn; } else { // Doesn't have preferred match dwPreferredStatus = 0; } } } if ( ( pSubjectInfo->Issuer.cbData == pIssuerInfo->Subject.cbData ) && ( pSubjectInfo->Issuer.cbData != 0) && ( memcmp( pSubjectInfo->Issuer.pbData, pIssuerInfo->Subject.pbData, pIssuerInfo->Subject.cbData ) == 0 ) ) { dwInfoStatus = dwPreferredStatus | CERT_TRUST_HAS_NAME_MATCH_ISSUER; goto SuccessReturn; } // Default to nonPreferred public key match dwInfoStatus = CERT_TRUST_HAS_KEY_MATCH_ISSUER; goto ErrorReturn; SuccessReturn: fResult = TRUE; CommonReturn: *pdwInfoStatus |= dwInfoStatus; return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; } //+--------------------------------------------------------------------------- // // Function: ChainGetMatchInfoStatusForNoIssuer // // Synopsis: return the info status when unable to find our issuer // //---------------------------------------------------------------------------- DWORD WINAPI ChainGetMatchInfoStatusForNoIssuer ( IN DWORD dwIssuerMatchFlags ) { if (dwIssuerMatchFlags & CERT_EXACT_ISSUER_MATCH_FLAG) return CERT_TRUST_HAS_EXACT_MATCH_ISSUER; else if (dwIssuerMatchFlags & CERT_KEYID_ISSUER_MATCH_TYPE) return CERT_TRUST_HAS_KEY_MATCH_ISSUER; else return CERT_TRUST_HAS_NAME_MATCH_ISSUER; } //+--------------------------------------------------------------------------- // // Function: ChainIsValidPubKeyMatchForIssuer // // Synopsis: returns TRUE if the issuer matches more than just the // public key match criteria // // This logic is mainly here to handle tstore2.exe and regress.bat // which has end, CA and root certificates using the same // public key. // //---------------------------------------------------------------------------- BOOL WINAPI ChainIsValidPubKeyMatchForIssuer ( IN PCCERTOBJECT pIssuer, IN PCCERTOBJECT pSubject ) { BOOL fResult = TRUE; BOOL fCheckMatchInfo; PCERT_BASIC_CONSTRAINTS2_INFO pIssuerBasicConstraints; fCheckMatchInfo = FALSE; // Check if the issuer has a basic constraints extension. If it does // and it isn't a CA, then, we will need to do an additional issuer match. pIssuerBasicConstraints = pIssuer->BasicConstraintsInfo(); if (pIssuerBasicConstraints && !pIssuerBasicConstraints->fCA) fCheckMatchInfo = TRUE; else { // Check if the issuer has the same public key as the subject. If it // does, then, will need to do an additional issuer match. BYTE *pbIssuerPublicKeyHash; BYTE *pbSubjectPublicKeyHash; pbIssuerPublicKeyHash = pIssuer->PublicKeyHash(); pbSubjectPublicKeyHash = pSubject->PublicKeyHash(); if (0 == memcmp(pbIssuerPublicKeyHash, pbSubjectPublicKeyHash, CHAINHASHLEN)) fCheckMatchInfo = TRUE; } if (fCheckMatchInfo) { // Check that the issuer matches the subject's AKI or subject's // issuer name. DWORD dwInfoStatus = 0; // Following returns FALSE if only has the public key match fResult = ChainGetMatchInfoStatus(pIssuer, pSubject, &dwInfoStatus); } return fResult; } //+--------------------------------------------------------------------------- // // Function: ChainGetSubjectStatus // // Synopsis: get the subject status bits by checking the time nesting and // signature validity // // For CERT_END_OBJECT_TYPE or CERT_EXTERNAL_ISSUER_OBJECT_TYPE // CCertObject types, leaves the engine's critical section to // verify the signature. If the engine was touched by another // thread, it fails with LastError set to ERROR_CAN_NOT_COMPLETE. // // Assumption: Chain engine is locked once in the calling thread. // //---------------------------------------------------------------------------- BOOL WINAPI ChainGetSubjectStatus ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCHAINPATHOBJECT pIssuerPathObject, IN PCCHAINPATHOBJECT pSubjectPathObject, IN OUT PCERT_TRUST_STATUS pStatus ) { BOOL fResult; PCCERTOBJECT pIssuerObject = pIssuerPathObject->CertObject(); PCCERTOBJECT pSubjectObject = pSubjectPathObject->CertObject(); PCCERT_CONTEXT pIssuerContext = pIssuerObject->CertContext(); PCCERT_CONTEXT pSubjectContext = pSubjectObject->CertContext(); DWORD dwIssuerStatusFlags; ChainGetMatchInfoStatus( pIssuerObject, pSubjectObject, &pStatus->dwInfoStatus ); dwIssuerStatusFlags = pSubjectObject->IssuerStatusFlags(); if (!(dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG)) { DWORD dwObjectType; dwObjectType = pSubjectObject->ObjectType(); if (CERT_END_OBJECT_TYPE == dwObjectType || CERT_EXTERNAL_ISSUER_OBJECT_TYPE == dwObjectType) pCallContext->ChainEngine()->UnlockEngine(); fResult = CryptVerifyCertificateSignatureEx( NULL, // hCryptProv X509_ASN_ENCODING, CRYPT_VERIFY_CERT_SIGN_SUBJECT_CERT, (void *) pSubjectContext, CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void *) pIssuerContext, 0, // dwFlags NULL // pvReserved ); if (CERT_END_OBJECT_TYPE == dwObjectType || CERT_EXTERNAL_ISSUER_OBJECT_TYPE == dwObjectType) { pCallContext->ChainEngine()->LockEngine(); if (pCallContext->IsTouchedEngine()) goto TouchedDuringSignatureVerification; } if (!fResult) { pStatus->dwErrorStatus |= CERT_TRUST_IS_NOT_SIGNATURE_VALID; pStatus->dwInfoStatus &= ~CERT_TRUST_HAS_PREFERRED_ISSUER; } else { if (dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG) { // Verify the issuer's public key hash if (0 != memcmp(pSubjectObject->IssuerPublicKeyHash(), pIssuerObject->PublicKeyHash(), CHAINHASHLEN)) dwIssuerStatusFlags &= ~CERT_ISSUER_PUBKEY_FLAG; } if (!(dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG)) { CRYPT_DATA_BLOB DataBlob; memcpy(pSubjectObject->IssuerPublicKeyHash(), pIssuerObject->PublicKeyHash(), CHAINHASHLEN); DataBlob.pbData = pSubjectObject->IssuerPublicKeyHash(), DataBlob.cbData = CHAINHASHLEN; CertSetCertificateContextProperty( pSubjectContext, CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID, CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG, &DataBlob ); } pSubjectObject->OrIssuerStatusFlags( CERT_ISSUER_PUBKEY_FLAG | CERT_ISSUER_VALID_SIGNATURE_FLAG ); } CertPerfIncrementChainVerifyCertSignatureCount(); } else { // also need to check public key parameters assert(dwIssuerStatusFlags & CERT_ISSUER_PUBKEY_FLAG); if (0 != memcmp(pSubjectObject->IssuerPublicKeyHash(), pIssuerObject->PublicKeyHash(), CHAINHASHLEN)) { pStatus->dwErrorStatus |= CERT_TRUST_IS_NOT_SIGNATURE_VALID; pStatus->dwInfoStatus &= ~CERT_TRUST_HAS_PREFERRED_ISSUER; } CertPerfIncrementChainCompareIssuerPublicKeyCount(); } fResult = TRUE; CommonReturn: return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; SET_ERROR(TouchedDuringSignatureVerification, ERROR_CAN_NOT_COMPLETE) } //+--------------------------------------------------------------------------- // // Function: ChainUpdateSummaryStatusByTrustStatus // // Synopsis: update the summary status bits given new trust status bits // //---------------------------------------------------------------------------- VOID WINAPI ChainUpdateSummaryStatusByTrustStatus( IN OUT PCERT_TRUST_STATUS pSummaryStatus, IN PCERT_TRUST_STATUS pTrustStatus ) { pSummaryStatus->dwErrorStatus |= pTrustStatus->dwErrorStatus; pSummaryStatus->dwInfoStatus |= pTrustStatus->dwInfoStatus & ~(CERT_TRUST_CERTIFICATE_ONLY_INFO_STATUS | CERT_TRUST_HAS_PREFERRED_ISSUER); if (!(pTrustStatus->dwInfoStatus & CERT_TRUST_HAS_PREFERRED_ISSUER)) pSummaryStatus->dwInfoStatus &= ~CERT_TRUST_HAS_PREFERRED_ISSUER; if (pSummaryStatus->dwErrorStatus & CERT_TRUST_ANY_NAME_CONSTRAINT_ERROR_STATUS) pSummaryStatus->dwInfoStatus &= ~CERT_TRUST_HAS_VALID_NAME_CONSTRAINTS; } //+=========================================================================== // Format and append extended error information helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainAllocAndEncodeObject // // Synopsis: allocate and ASN.1 encodes the data structure. // // PkiFree must be called to free the encoded bytes // //---------------------------------------------------------------------------- BOOL WINAPI ChainAllocAndEncodeObject( IN LPCSTR lpszStructType, IN const void *pvStructInfo, OUT BYTE **ppbEncoded, OUT DWORD *pcbEncoded ) { return CryptEncodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, lpszStructType, pvStructInfo, CRYPT_ENCODE_ALLOC_FLAG, &PkiEncodePara, (void *) ppbEncoded, pcbEncoded ); } //+--------------------------------------------------------------------------- // // Function: ChainAppendExtendedErrorInfo // // Synopsis: PkiReallocate and append an already localization formatted // line of extended error information // //---------------------------------------------------------------------------- VOID WINAPI ChainAppendExtendedErrorInfo( IN OUT LPWSTR *ppwszExtErrorInfo, IN LPWSTR pwszAppend, IN DWORD cchAppend // Includes NULL terminator ) { LPWSTR pwszExtErrorInfo = *ppwszExtErrorInfo; DWORD cchExtErrorInfo; if (pwszExtErrorInfo) cchExtErrorInfo = wcslen(pwszExtErrorInfo); else cchExtErrorInfo = 0; assert(0 < cchAppend); if (pwszExtErrorInfo = (LPWSTR) PkiRealloc(pwszExtErrorInfo, (cchExtErrorInfo + cchAppend) * sizeof(WCHAR))) { memcpy(&pwszExtErrorInfo[cchExtErrorInfo], pwszAppend, cchAppend * sizeof(WCHAR)); *ppwszExtErrorInfo = pwszExtErrorInfo; } } //+--------------------------------------------------------------------------- // // Function: ChainFormatAndAppendExtendedErrorInfo // // Synopsis: localization format a line of extended error information // and append via the above ChainAppendExtendedErrorInfo // //---------------------------------------------------------------------------- VOID WINAPI ChainFormatAndAppendExtendedErrorInfo( IN OUT LPWSTR *ppwszExtErrorInfo, IN UINT nFormatID, ... ) { DWORD cchMsg = 0; LPWSTR pwszMsg = NULL; WCHAR wszFormat[256]; wszFormat[0] = '\0'; va_list argList; // get format string from resources if(0 == LoadStringU(g_hChainInst, nFormatID, wszFormat, sizeof(wszFormat)/sizeof(wszFormat[0]))) return; __try { // format message into requested buffer va_start(argList, nFormatID); cchMsg = FormatMessageU( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING, wszFormat, 0, // dwMessageId 0, // dwLanguageId (LPWSTR) &pwszMsg, 0, // minimum size to allocate &argList); va_end(argList); // Must at least have the L'\n' terminator if (1 < cchMsg && pwszMsg) ChainAppendExtendedErrorInfo( ppwszExtErrorInfo, pwszMsg, cchMsg + 1 ); } __except(EXCEPTION_EXECUTE_HANDLER) { } if (pwszMsg) LocalFree(pwszMsg); } //+=========================================================================== // Name Constraint helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainIsWhiteSpace // // Synopsis: returns TRUE for a white space character // //---------------------------------------------------------------------------- static inline BOOL ChainIsWhiteSpace(WCHAR wc) { return wc == L' ' || (wc >= 0x09 && wc <= 0x0d); } //+--------------------------------------------------------------------------- // // Function: ChainRemoveLeadingAndTrailingWhiteSpace // // Synopsis: advances the pointer past any leading white space. Removes // any trailing white space by inserting the L'\0' and updating // the character count. // //---------------------------------------------------------------------------- VOID WINAPI ChainRemoveLeadingAndTrailingWhiteSpace( IN LPWSTR pwszIn, OUT LPWSTR *ppwszOut, OUT DWORD *pcchOut ) { LPWSTR pwszOut; DWORD cchOut; WCHAR wc; // Remove leading white space for (pwszOut = pwszIn ; L'\0' != (wc = *pwszOut); pwszOut++) { if (!ChainIsWhiteSpace(wc)) break; } for (cchOut = wcslen(pwszOut); 0 < cchOut; cchOut--) { if (!ChainIsWhiteSpace(pwszOut[cchOut - 1])) break; } pwszOut[cchOut] = L'\0'; *ppwszOut = pwszOut; *pcchOut = cchOut; } #define NO_LOCALE MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) //+--------------------------------------------------------------------------- // // Function: ChainIsRightStringInString // // Synopsis: returns TRUE for a case insensitive match of the // "Right" string with the right most characters of the // string. // //---------------------------------------------------------------------------- BOOL WINAPI ChainIsRightStringInString( IN LPCWSTR pwszRight, IN DWORD cchRight, IN LPCWSTR pwszString, IN DWORD cchString ) { if (0 == cchRight) return TRUE; if (cchRight > cchString) return FALSE; if (CSTR_EQUAL == CompareStringU( NO_LOCALE, NORM_IGNORECASE, pwszRight, cchRight, pwszString + (cchString - cchRight), cchRight )) return TRUE; else return FALSE; } //+--------------------------------------------------------------------------- // // Function: ChainFixupNameConstraintsUPN // // Synopsis: fixup the CERT_ALT_NAME_OTHER_NAME AltName entry choice // for szOID_NT_PRINCIPAL_NAME by allocating and converting // to a PCERT_NAME_VALUE containing the unicode string // with leading and trailing white space removed. // // The pOtherName->Value.pbData is updated to point to the // PCERT_NAME_VALUE instead of the original ASN.1 encoded // bytes. // //---------------------------------------------------------------------------- BOOL WINAPI ChainFixupNameConstraintsUPN( IN OUT PCRYPT_OBJID_BLOB pUPN ) { BOOL fResult; PCERT_NAME_VALUE pNameValue; LPWSTR pwsz; DWORD cch; pNameValue = (PCERT_NAME_VALUE) ChainAllocAndDecodeObject( X509_UNICODE_ANY_STRING, pUPN->pbData, pUPN->cbData ); if (NULL == pNameValue) goto DecodeError; if (!IS_CERT_RDN_CHAR_STRING(pNameValue->dwValueType)) { PkiFree(pNameValue); goto InvalidUPNStringType; } ChainRemoveLeadingAndTrailingWhiteSpace( (LPWSTR) pNameValue->Value.pbData, &pwsz, &cch ); pNameValue->Value.pbData = (BYTE *) pwsz; pNameValue->Value.cbData = cch * sizeof(WCHAR); pUPN->pbData = (BYTE *) pNameValue; fResult = TRUE; CommonReturn: return fResult; ErrorReturn: pUPN->pbData = NULL; fResult = FALSE; goto CommonReturn; TRACE_ERROR(DecodeError) SET_ERROR(InvalidUPNStringType, CRYPT_E_BAD_ENCODE) } //+--------------------------------------------------------------------------- // // Function: ChainAllocDecodeAndFixupNameConstraintsDirectoryName // // Synopsis: fixup the CERT_ALT_NAME_DIRECTORY_NAME AltName entry choice // or the encoded certificate Subject name by allocating and // converting to a unicode PCERT_NAME_INFO where // leading and trailing white space has been removed from // all the attributes. // // The DirectoryName.pbData is updated to point to the // PCERT_NAME_INFO instead of the original ASN.1 encoded // bytes. // //---------------------------------------------------------------------------- BOOL WINAPI ChainAllocDecodeAndFixupNameConstraintsDirectoryName( IN PCERT_NAME_BLOB pDirName, OUT PCERT_NAME_INFO *ppNameInfo ) { BOOL fResult; PCERT_NAME_INFO pNameInfo = NULL; DWORD cRDN; PCERT_RDN pRDN; if (0 == pDirName->cbData) goto SuccessReturn; pNameInfo = (PCERT_NAME_INFO) ChainAllocAndDecodeObject( X509_UNICODE_NAME, pDirName->pbData, pDirName->cbData ); if (NULL == pNameInfo) goto DecodeError; if (0 == pNameInfo->cRDN) { PkiFree(pNameInfo); pNameInfo = NULL; goto SuccessReturn; } // Iterate through all the attributes and remove leading and trailing // white space. cRDN = pNameInfo->cRDN; pRDN = pNameInfo->rgRDN; for ( ; cRDN > 0; cRDN--, pRDN++) { DWORD cAttr = pRDN->cRDNAttr; PCERT_RDN_ATTR pAttr = pRDN->rgRDNAttr; for ( ; cAttr > 0; cAttr--, pAttr++) { LPWSTR pwsz; DWORD cch; if (!IS_CERT_RDN_CHAR_STRING(pAttr->dwValueType)) continue; ChainRemoveLeadingAndTrailingWhiteSpace( (LPWSTR) pAttr->Value.pbData, &pwsz, &cch ); pAttr->Value.pbData = (BYTE *) pwsz; pAttr->Value.cbData = cch * sizeof(WCHAR); } } SuccessReturn: fResult = TRUE; CommonReturn: *ppNameInfo = pNameInfo; return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(DecodeError) } //+--------------------------------------------------------------------------- // // Function: ChainFixupNameConstraintsAltNameEntry // // Synopsis: fixup the AltName entry choices as follows: // CERT_ALT_NAME_OTHER_NAME // For szOID_NT_PRINCIPAL_NAME, pOtherName->Value.pbData // is updated to point to the allocated // PCERT_NAME_VALUE containing the decoded unicode string. // // CERT_ALT_NAME_RFC822_NAME // CERT_ALT_NAME_DNS_NAME // CERT_ALT_NAME_URL // Uses DirectoryName.pbData and DirectoryName.cbData // to contain the pointer to and length of the unicode // string. // // For the subject URL, the DirectoryName.pbData's // unicode string is the allocated host name. // // CERT_ALT_NAME_DIRECTORY_NAME: // DirectoryName.pbData is updated to point to the // allocated and decoded unicode PCERT_NAME_INFO. // // For the above choices, leading and trailing white space // has been removed. cbData is number of bytes and not number // of characters, ie, cbData = cch * sizeof(WCHAR) // //---------------------------------------------------------------------------- BOOL WINAPI ChainFixupNameConstraintsAltNameEntry( IN BOOL fSubjectConstraint, IN OUT PCERT_ALT_NAME_ENTRY pEntry ) { BOOL fResult = TRUE; LPWSTR pwsz = NULL; DWORD cch = 0; switch (pEntry->dwAltNameChoice) { case CERT_ALT_NAME_OTHER_NAME: if (0 == strcmp(pEntry->pOtherName->pszObjId, szOID_NT_PRINCIPAL_NAME)) fResult = ChainFixupNameConstraintsUPN( &pEntry->pOtherName->Value); break; case CERT_ALT_NAME_RFC822_NAME: case CERT_ALT_NAME_DNS_NAME: ChainRemoveLeadingAndTrailingWhiteSpace( pEntry->pwszRfc822Name, &pwsz, &cch ); // Use the directory name's BLOB choice to contain both // the pointer to and length of the string pEntry->DirectoryName.pbData = (BYTE *) pwsz; pEntry->DirectoryName.cbData = cch * sizeof(WCHAR); break; case CERT_ALT_NAME_URL: if (fSubjectConstraint) { WCHAR rgwszHostName[MAX_PATH + 1]; LPWSTR pwszHostName; rgwszHostName[0] = L'\0'; fResult = ChainGetHostNameFromUrl( pEntry->pwszURL, MAX_PATH, rgwszHostName); if (fResult) { ChainRemoveLeadingAndTrailingWhiteSpace( rgwszHostName, &pwszHostName, &cch ); pwsz = (LPWSTR) PkiNonzeroAlloc((cch + 1) * sizeof(WCHAR)); if (NULL == pwsz) fResult = FALSE; else memcpy(pwsz, pwszHostName, (cch + 1) * sizeof(WCHAR)); } if (!fResult) { pwsz = NULL; cch = 0; } } else { ChainRemoveLeadingAndTrailingWhiteSpace( pEntry->pwszURL, &pwsz, &cch ); } // Use the directory name's BLOB choice to contain both // the pointer to and length of the string pEntry->DirectoryName.pbData = (BYTE *) pwsz; pEntry->DirectoryName.cbData = cch * sizeof(WCHAR); break; case CERT_ALT_NAME_DIRECTORY_NAME: { PCERT_NAME_INFO pNameInfo = NULL; fResult = ChainAllocDecodeAndFixupNameConstraintsDirectoryName( &pEntry->DirectoryName, &pNameInfo); // Update the directory name's BLOB to contain the pointer // to the decoded name info pEntry->DirectoryName.pbData = (BYTE *) pNameInfo; } break; case CERT_ALT_NAME_X400_ADDRESS: case CERT_ALT_NAME_EDI_PARTY_NAME: case CERT_ALT_NAME_IP_ADDRESS: case CERT_ALT_NAME_REGISTERED_ID: default: break; } return fResult; } //+--------------------------------------------------------------------------- // // Function: ChainFreeNameConstraintsAltNameEntryFixup // // Synopsis: free memory allocated by the above // ChainFixupNameConstraintsAltNameEntry // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeNameConstraintsAltNameEntryFixup( IN BOOL fSubjectConstraint, IN OUT PCERT_ALT_NAME_ENTRY pEntry ) { switch (pEntry->dwAltNameChoice) { case CERT_ALT_NAME_OTHER_NAME: if (0 == strcmp(pEntry->pOtherName->pszObjId, szOID_NT_PRINCIPAL_NAME)) // pbData :: PCERT_NAME_VALUE PkiFree(pEntry->pOtherName->Value.pbData); break; case CERT_ALT_NAME_RFC822_NAME: case CERT_ALT_NAME_DNS_NAME: break; case CERT_ALT_NAME_URL: if (fSubjectConstraint) // pbData :: LPWSTR PkiFree(pEntry->DirectoryName.pbData); break; case CERT_ALT_NAME_DIRECTORY_NAME: // pbData :: PCERT_NAME_INFO PkiFree(pEntry->DirectoryName.pbData); break; case CERT_ALT_NAME_X400_ADDRESS: case CERT_ALT_NAME_EDI_PARTY_NAME: case CERT_ALT_NAME_IP_ADDRESS: case CERT_ALT_NAME_REGISTERED_ID: default: break; } } //+--------------------------------------------------------------------------- // // Function: ChainFormatNameConstraintsAltNameEntryFixup // // Synopsis: localization format and allocate a previously fixed up // AltName entry. // // The returned string must be freed via PkiFree(). // //---------------------------------------------------------------------------- LPWSTR WINAPI ChainFormatNameConstraintsAltNameEntryFixup( IN PCERT_ALT_NAME_ENTRY pEntry ) { DWORD dwExceptionCode; LPWSTR pwszFormat = NULL; DWORD cbFormat = 0; CERT_ALT_NAME_ENTRY AltEntry; const CERT_ALT_NAME_INFO AltNameInfo = { 1, &AltEntry }; CERT_OTHER_NAME OtherName; BYTE *pbEncoded = NULL; DWORD cbEncoded; BYTE *pbEncoded2 = NULL; DWORD cbEncoded2; __try { AltEntry = *pEntry; // Restore fixed up entries so we can re-encode switch (AltEntry.dwAltNameChoice) { case CERT_ALT_NAME_OTHER_NAME: if (0 == strcmp(pEntry->pOtherName->pszObjId, szOID_NT_PRINCIPAL_NAME)) { // Restore from the following fixup: // pEntry->pOtherName->Value.pbData :: PCERT_NAME_VALUE if (NULL == pEntry->pOtherName->Value.pbData) goto InvalidUPN; if (!ChainAllocAndEncodeObject( X509_UNICODE_ANY_STRING, (PCERT_NAME_VALUE) pEntry->pOtherName->Value.pbData, &pbEncoded2, &cbEncoded2 )) goto EncodedUPNError; OtherName.pszObjId = pEntry->pOtherName->pszObjId; OtherName.Value.pbData = pbEncoded2; OtherName.Value.cbData = cbEncoded2; AltEntry.pOtherName = &OtherName; } break; case CERT_ALT_NAME_RFC822_NAME: case CERT_ALT_NAME_DNS_NAME: case CERT_ALT_NAME_URL: // Restore from the following fixup: // pEntry->DirectoryName.pbData = (BYTE *) pwsz; // pEntry->DirectoryName.cbData = cch * sizeof(WCHAR); if (NULL == pEntry->DirectoryName.pbData || 0 == pEntry->DirectoryName.cbData) AltEntry.pwszRfc822Name = L"???"; else AltEntry.pwszRfc822Name = (LPWSTR) pEntry->DirectoryName.pbData; break; case CERT_ALT_NAME_DIRECTORY_NAME: // Restore from the following fixup: // pEntry->DirectoryName.pbData :: PCERT_NAME_INFO if (NULL == pEntry->DirectoryName.pbData) goto InvalidDirName; if (!ChainAllocAndEncodeObject( X509_UNICODE_NAME, (PCERT_NAME_INFO) pEntry->DirectoryName.pbData, &pbEncoded2, &cbEncoded2 )) goto EncodeDirNameError; AltEntry.DirectoryName.pbData = pbEncoded2; AltEntry.DirectoryName.cbData = cbEncoded2; break; case CERT_ALT_NAME_X400_ADDRESS: case CERT_ALT_NAME_EDI_PARTY_NAME: case CERT_ALT_NAME_IP_ADDRESS: case CERT_ALT_NAME_REGISTERED_ID: default: break; } if (!ChainAllocAndEncodeObject( X509_ALTERNATE_NAME, &AltNameInfo, &pbEncoded, &cbEncoded )) goto EncodeAltNameError; if (!CryptFormatObject( X509_ASN_ENCODING, 0, // dwFormatType 0, // dwFormatStrType NULL, // pFormatStruct X509_ALTERNATE_NAME, pbEncoded, cbEncoded, NULL, // pwszFormat &cbFormat )) goto FormatAltNameError; if (NULL == (pwszFormat = (LPWSTR) PkiZeroAlloc( cbFormat + sizeof(WCHAR)))) goto OutOfMemory; if (!CryptFormatObject( X509_ASN_ENCODING, 0, // dwFormatType 0, // dwFormatStrType NULL, // pFormatStruct X509_ALTERNATE_NAME, pbEncoded, cbEncoded, pwszFormat, &cbFormat )) goto FormatAltNameError; } __except(EXCEPTION_EXECUTE_HANDLER) { dwExceptionCode = GetExceptionCode(); goto ExceptionError; } CommonReturn: PkiFree(pbEncoded); PkiFree(pbEncoded2); return pwszFormat; ErrorReturn: if (pwszFormat) { PkiFree(pwszFormat); pwszFormat = NULL; } goto CommonReturn; SET_ERROR(InvalidUPN, ERROR_INVALID_DATA) TRACE_ERROR(EncodedUPNError) TRACE_ERROR(InvalidDirName) TRACE_ERROR(EncodeDirNameError) TRACE_ERROR(EncodeAltNameError) TRACE_ERROR(FormatAltNameError) TRACE_ERROR(OutOfMemory) SET_ERROR_VAR(ExceptionError, dwExceptionCode) } //+--------------------------------------------------------------------------- // // Function: ChainFormatAndAppendNameConstraintsAltNameEntryFixup // // Synopsis: localization format a previously fixed up // AltName entry and append to the extended error information. // //---------------------------------------------------------------------------- VOID WINAPI ChainFormatAndAppendNameConstraintsAltNameEntryFixup( IN OUT LPWSTR *ppwszExtErrorInfo, IN PCERT_ALT_NAME_ENTRY pEntry, IN UINT nFormatID, IN OPTIONAL DWORD dwSubtreeIndex // 0 => no subtree parameter ) { LPWSTR pwszAllocFormatEntry = NULL; LPWSTR pwszFormatEntry; pwszAllocFormatEntry = ChainFormatNameConstraintsAltNameEntryFixup(pEntry); if (pwszAllocFormatEntry) pwszFormatEntry = pwszAllocFormatEntry; else pwszFormatEntry = L"???"; if (0 == dwSubtreeIndex) ChainFormatAndAppendExtendedErrorInfo( ppwszExtErrorInfo, nFormatID, pwszFormatEntry ); else ChainFormatAndAppendExtendedErrorInfo( ppwszExtErrorInfo, nFormatID, dwSubtreeIndex, pwszFormatEntry ); PkiFree(pwszAllocFormatEntry); } //+--------------------------------------------------------------------------- // // Function: ChainGetIssuerNameConstraintsInfo // // Synopsis: alloc and return the issuer name constraints info. // //---------------------------------------------------------------------------- BOOL WINAPI ChainGetIssuerNameConstraintsInfo ( IN PCCERT_CONTEXT pCertContext, IN OUT PCERT_NAME_CONSTRAINTS_INFO *ppInfo ) { BOOL fResult; PCERT_EXTENSION pExt; PCERT_NAME_CONSTRAINTS_INFO pInfo = NULL; PCERT_GENERAL_SUBTREE pSubtree; DWORD cSubtree; pExt = CertFindExtension( szOID_NAME_CONSTRAINTS, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension ); if (NULL == pExt) goto SuccessReturn; pInfo = (PCERT_NAME_CONSTRAINTS_INFO) ChainAllocAndDecodeObject( X509_NAME_CONSTRAINTS, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pInfo) goto DecodeError; // Fixup all the AltName entries // Note, even for an error we need to fixup all the entries. // ChainFreeIssuerNameConstraintsInfo iterates through all the entries. fResult = TRUE; cSubtree = pInfo->cPermittedSubtree; pSubtree = pInfo->rgPermittedSubtree; for ( ; 0 < cSubtree; cSubtree--, pSubtree++) { if (!ChainFixupNameConstraintsAltNameEntry(FALSE, &pSubtree->Base)) fResult = FALSE; } cSubtree = pInfo->cExcludedSubtree; pSubtree = pInfo->rgExcludedSubtree; for ( ; 0 < cSubtree; cSubtree--, pSubtree++) { if (!ChainFixupNameConstraintsAltNameEntry(FALSE, &pSubtree->Base)) fResult = FALSE; } if (!fResult) goto FixupAltNameEntryError; SuccessReturn: fResult = TRUE; CommonReturn: *ppInfo = pInfo; return fResult; ErrorReturn: fResult = FALSE; goto CommonReturn; TRACE_ERROR(DecodeError) TRACE_ERROR(FixupAltNameEntryError) } //+--------------------------------------------------------------------------- // // Function: ChainFreeIssuerNameConstraintsInfo // // Synopsis: free the issuer name constraints info // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeIssuerNameConstraintsInfo ( IN OUT PCERT_NAME_CONSTRAINTS_INFO pInfo ) { PCERT_GENERAL_SUBTREE pSubtree; DWORD cSubtree; if (NULL == pInfo) return; cSubtree = pInfo->cPermittedSubtree; pSubtree = pInfo->rgPermittedSubtree; for ( ; 0 < cSubtree; cSubtree--, pSubtree++) ChainFreeNameConstraintsAltNameEntryFixup(FALSE, &pSubtree->Base); cSubtree = pInfo->cExcludedSubtree; pSubtree = pInfo->rgExcludedSubtree; for ( ; 0 < cSubtree; cSubtree--, pSubtree++) ChainFreeNameConstraintsAltNameEntryFixup(FALSE, &pSubtree->Base); PkiFree(pInfo); } //+--------------------------------------------------------------------------- // // Function: ChainGetSubjectNameConstraintsInfo // // Synopsis: alloc and return the subject name constraints info. // //---------------------------------------------------------------------------- VOID WINAPI ChainGetSubjectNameConstraintsInfo ( IN PCCERT_CONTEXT pCertContext, IN OUT PCHAIN_SUBJECT_NAME_CONSTRAINTS_INFO pSubjectInfo ) { PCERT_EXTENSION pExt; BOOL fHasEmailAltNameEntry = FALSE; pExt = CertFindExtension( szOID_SUBJECT_ALT_NAME2, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension ); if (NULL == pExt) { pExt = CertFindExtension( szOID_SUBJECT_ALT_NAME, pCertContext->pCertInfo->cExtension, pCertContext->pCertInfo->rgExtension ); } if (pExt) { PCERT_ALT_NAME_INFO pAltNameInfo; pAltNameInfo = (PCERT_ALT_NAME_INFO) ChainAllocAndDecodeObject( X509_ALTERNATE_NAME, pExt->Value.pbData, pExt->Value.cbData ); if (NULL == pAltNameInfo) pSubjectInfo->fInvalid = TRUE; else { DWORD cEntry; PCERT_ALT_NAME_ENTRY pEntry; pSubjectInfo->pAltNameInfo = pAltNameInfo; // Fixup all the AltName entries // Note, even for an error we need to fixup all the entries. // ChainFreeSubjectNameConstraintsInfo iterates through all // the entries. cEntry = pAltNameInfo->cAltEntry; pEntry = pAltNameInfo->rgAltEntry; for ( ; 0 < cEntry; cEntry--, pEntry++) { if (CERT_ALT_NAME_RFC822_NAME == pEntry->dwAltNameChoice) fHasEmailAltNameEntry = TRUE; else if (CERT_ALT_NAME_DNS_NAME == pEntry->dwAltNameChoice) pSubjectInfo->fHasDnsAltNameEntry = TRUE; if (!ChainFixupNameConstraintsAltNameEntry(TRUE, pEntry)) pSubjectInfo->fInvalid = TRUE; } } } if (!ChainAllocDecodeAndFixupNameConstraintsDirectoryName( &pCertContext->pCertInfo->Subject, &pSubjectInfo->pUnicodeNameInfo )) pSubjectInfo->fInvalid = TRUE; if (!fHasEmailAltNameEntry && pSubjectInfo->pUnicodeNameInfo) { DWORD cRDN; PCERT_RDN pRDN; cRDN = pSubjectInfo->pUnicodeNameInfo->cRDN; pRDN = pSubjectInfo->pUnicodeNameInfo->rgRDN; for ( ; cRDN > 0; cRDN--, pRDN++) { DWORD cAttr = pRDN->cRDNAttr; PCERT_RDN_ATTR pAttr = pRDN->rgRDNAttr; for ( ; cAttr > 0; cAttr--, pAttr++) { if (!IS_CERT_RDN_CHAR_STRING(pAttr->dwValueType)) continue; if (0 == strcmp(pAttr->pszObjId, szOID_RSA_emailAddr)) { pSubjectInfo->pEmailAttr = pAttr; break; } } if (cAttr > 0) break; } } } //+--------------------------------------------------------------------------- // // Function: ChainFreeSubjectNameConstraintsInfo // // Synopsis: free the subject name constraints info // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeSubjectNameConstraintsInfo ( IN OUT PCHAIN_SUBJECT_NAME_CONSTRAINTS_INFO pSubjectInfo ) { PCERT_ALT_NAME_INFO pAltNameInfo; pAltNameInfo = pSubjectInfo->pAltNameInfo; if (pAltNameInfo) { DWORD cEntry; PCERT_ALT_NAME_ENTRY pEntry; cEntry = pAltNameInfo->cAltEntry; pEntry = pAltNameInfo->rgAltEntry; for ( ; 0 < cEntry; cEntry--, pEntry++) ChainFreeNameConstraintsAltNameEntryFixup(TRUE, pEntry); PkiFree(pAltNameInfo); } PkiFree(pSubjectInfo->pUnicodeNameInfo); } //+--------------------------------------------------------------------------- // // Function: ChainCompareNameConstraintsDirectoryName // // Synopsis: returns TRUE if all the subtree RDN attributes match // the RDN attributes at the beginning of the subject // directory name. A case insensitive match // is performed on each RDN attribute that is a string type. // A binary compare is performed on nonstring attribute types. // // The OIDs of the RDN attributes must match. // // Note, a NULL subtree or a subtree with no RDNs matches // any subject directory name. Also, an empty subtree // RDN attribute matches any subject attribute. // //---------------------------------------------------------------------------- BOOL WINAPI ChainCompareNameConstraintsDirectoryName( IN PCERT_NAME_INFO pSubjectInfo, IN PCERT_NAME_INFO pSubtreeInfo ) { DWORD cSubjectRDN; PCERT_RDN pSubjectRDN; DWORD cSubtreeRDN; PCERT_RDN pSubtreeRDN; if (NULL == pSubtreeInfo || 0 == pSubtreeInfo->cRDN) // Match any subject return TRUE; if (NULL == pSubjectInfo) return FALSE; cSubjectRDN = pSubjectInfo->cRDN; cSubtreeRDN = pSubtreeInfo->cRDN; if (cSubtreeRDN > cSubjectRDN) return FALSE; pSubjectRDN = pSubjectInfo->rgRDN; pSubtreeRDN = pSubtreeInfo->rgRDN; for ( ; cSubtreeRDN > 0; cSubtreeRDN--, pSubtreeRDN++, pSubjectRDN++) { DWORD cSubjectAttr = pSubjectRDN->cRDNAttr; PCERT_RDN_ATTR pSubjectAttr = pSubjectRDN->rgRDNAttr; DWORD cSubtreeAttr = pSubtreeRDN->cRDNAttr; PCERT_RDN_ATTR pSubtreeAttr = pSubtreeRDN->rgRDNAttr; if (1 < cSubtreeRDN) { if (cSubtreeAttr != cSubjectAttr) return FALSE; } else { if (cSubtreeAttr > cSubjectAttr) return FALSE; } for ( ; cSubtreeAttr > 0; cSubtreeAttr--, pSubtreeAttr++, pSubjectAttr++) { if (0 != strcmp(pSubtreeAttr->pszObjId, pSubjectAttr->pszObjId)) return FALSE; if (IS_CERT_RDN_CHAR_STRING(pSubtreeAttr->dwValueType) != IS_CERT_RDN_CHAR_STRING(pSubjectAttr->dwValueType)) return FALSE; if (IS_CERT_RDN_CHAR_STRING(pSubtreeAttr->dwValueType)) { DWORD cchSubtree = pSubtreeAttr->Value.cbData / sizeof(WCHAR); if (0 == cchSubtree) { // Match any attribute ; } else if (cchSubtree != pSubjectAttr->Value.cbData / sizeof(WCHAR)) { // For X.509, must match entire attribute return FALSE; } else if (!ChainIsRightStringInString( (LPCWSTR) pSubtreeAttr->Value.pbData, cchSubtree, (LPCWSTR) pSubjectAttr->Value.pbData, cchSubtree )) { return FALSE; } } else { if (pSubtreeAttr->Value.cbData != pSubjectAttr->Value.cbData) return FALSE; if (0 != memcmp(pSubtreeAttr->Value.pbData, pSubjectAttr->Value.pbData, pSubtreeAttr->Value.cbData )) return FALSE; } } } return TRUE; } //+--------------------------------------------------------------------------- // // Function: ChainCompareNameConstraintsIPAddress // // Synopsis: returns TRUE if the subject IP address is within the IP // range specified by subtree IP address and mask. // // The subtree IP contains the octet bytes for both the // IP address and its mask. // // For IPv4, there are 4 address bytes followed by 4 mask bytes. // See RFC 2459 for more details. // // Here's my interpretation: // // For a match: SubtreeIPAddr == (SubjectIPAddr & SubtreeIPMask) // //---------------------------------------------------------------------------- BOOL WINAPI ChainCompareNameConstraintsIPAddress( IN PCRYPT_DATA_BLOB pSubjectIPAddress, IN PCRYPT_DATA_BLOB pSubtreeIPAddress ) { BYTE *pbSubject = pSubjectIPAddress->pbData; DWORD cbSubject = pSubjectIPAddress->cbData; BYTE *pbSubtree = pSubtreeIPAddress->pbData; DWORD cbSubtree = pSubtreeIPAddress->cbData; BYTE *pbSubtreeMask = pbSubtree + cbSubject; DWORD i; if (0 == cbSubtree) // Match any IP address return TRUE; // Only compare if the number of subtree bytes is twice the length of // the subject. Second half contains the mask. if (cbSubtree != 2 * cbSubject) return FALSE; for (i = 0; i < cbSubject; i++) { if (pbSubtree[i] != (pbSubject[i] & pbSubtreeMask[i])) return FALSE; } return TRUE; } //+--------------------------------------------------------------------------- // // Function: ChainCompareNameConstraintsUPN // // Synopsis: returns TRUE if the subtree UPN string matches the right most // characters of the subject's UPN doing a case insensitive // match. // // Note, the Value.pbData points to the decoded PCERT_NAME_VALUE. // //---------------------------------------------------------------------------- BOOL WINAPI ChainCompareNameConstraintsUPN( IN PCRYPT_OBJID_BLOB pSubjectValue, IN PCRYPT_OBJID_BLOB pSubtreeValue ) { // The UPN's Value.pbData is used to point to the decoded // PCERT_NAME_VALUE BOOL fCompare; PCERT_NAME_VALUE pSubjectNameValue; PCERT_NAME_VALUE pSubtreeNameValue; pSubjectNameValue = (PCERT_NAME_VALUE) pSubjectValue->pbData; pSubtreeNameValue = (PCERT_NAME_VALUE) pSubtreeValue->pbData; if (pSubjectNameValue && pSubtreeNameValue) fCompare = ChainIsRightStringInString( (LPCWSTR) pSubtreeNameValue->Value.pbData, pSubtreeNameValue->Value.cbData / sizeof(WCHAR), (LPCWSTR) pSubjectNameValue->Value.pbData, pSubjectNameValue->Value.cbData / sizeof(WCHAR) ); else fCompare = FALSE; return fCompare; } //+--------------------------------------------------------------------------- // // Function: ChainCalculateNameConstraintsSubtreeErrorStatusForAltNameEntry // // Synopsis: calculates the name constraints error status by seeing if // the subject AltName entry matches any subtree AltName entry. // //---------------------------------------------------------------------------- DWORD WINAPI ChainCalculateNameConstraintsSubtreeErrorStatusForAltNameEntry( IN PCERT_ALT_NAME_ENTRY pSubjectEntry, IN BOOL fExcludedSubtree, IN DWORD cSubtree, IN PCERT_GENERAL_SUBTREE pSubtree, IN OUT LPWSTR *ppwszExtErrorInfo ) { DWORD dwErrorStatus = 0; BOOL fHasSubtreeEntry = FALSE; DWORD dwAltNameChoice = pSubjectEntry->dwAltNameChoice; DWORD i; for (i = 0; i < cSubtree; i++, pSubtree++) { PCERT_ALT_NAME_ENTRY pSubtreeEntry; BOOL fCompare; if (0 != pSubtree->dwMinimum || pSubtree->fMaximum) { dwErrorStatus |= CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT; ChainFormatAndAppendExtendedErrorInfo( ppwszExtErrorInfo, fExcludedSubtree ? IDS_NOT_SUPPORTED_EXCLUDED_NAME_CONSTRAINT : IDS_NOT_SUPPORTED_PERMITTED_NAME_CONSTRAINT, i + 1 ); continue; } pSubtreeEntry = &pSubtree->Base; if (dwAltNameChoice != pSubtreeEntry->dwAltNameChoice) continue; fCompare = FALSE; switch (dwAltNameChoice) { case CERT_ALT_NAME_OTHER_NAME: // Only support the UPN OID if (0 != strcmp(pSubtreeEntry->pOtherName->pszObjId, szOID_NT_PRINCIPAL_NAME)) { dwErrorStatus |= CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT; ChainFormatAndAppendExtendedErrorInfo( ppwszExtErrorInfo, fExcludedSubtree ? IDS_NOT_SUPPORTED_EXCLUDED_NAME_CONSTRAINT : IDS_NOT_SUPPORTED_PERMITTED_NAME_CONSTRAINT, i + 1 ); } else { assert(0 == strcmp(pSubjectEntry->pOtherName->pszObjId, szOID_NT_PRINCIPAL_NAME)); fHasSubtreeEntry = TRUE; fCompare = ChainCompareNameConstraintsUPN( &pSubjectEntry->pOtherName->Value, &pSubtreeEntry->pOtherName->Value ); } break; case CERT_ALT_NAME_RFC822_NAME: case CERT_ALT_NAME_DNS_NAME: case CERT_ALT_NAME_URL: fHasSubtreeEntry = TRUE; // The directory name's BLOB choice is used to contain both // the pointer to and length of the string fCompare = ChainIsRightStringInString( (LPCWSTR) pSubtreeEntry->DirectoryName.pbData, pSubtreeEntry->DirectoryName.cbData / sizeof(WCHAR), (LPCWSTR) pSubjectEntry->DirectoryName.pbData, pSubjectEntry->DirectoryName.cbData / sizeof(WCHAR) ); break; case CERT_ALT_NAME_DIRECTORY_NAME: fHasSubtreeEntry = TRUE; fCompare = ChainCompareNameConstraintsDirectoryName( (PCERT_NAME_INFO) pSubjectEntry->DirectoryName.pbData, (PCERT_NAME_INFO) pSubtreeEntry->DirectoryName.pbData ); break; case CERT_ALT_NAME_IP_ADDRESS: fHasSubtreeEntry = TRUE; fCompare = ChainCompareNameConstraintsIPAddress( &pSubjectEntry->IPAddress, &pSubtreeEntry->IPAddress); break; case CERT_ALT_NAME_X400_ADDRESS: case CERT_ALT_NAME_EDI_PARTY_NAME: case CERT_ALT_NAME_REGISTERED_ID: default: assert(0); break; } if (fCompare) { if (fExcludedSubtree) { dwErrorStatus |= CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT; ChainFormatAndAppendNameConstraintsAltNameEntryFixup( ppwszExtErrorInfo, pSubjectEntry, IDS_EXCLUDED_ENTRY_NAME_CONSTRAINT, i + 1 ); } return dwErrorStatus; } } if (!fExcludedSubtree) { if (fHasSubtreeEntry) { dwErrorStatus |= CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT; ChainFormatAndAppendNameConstraintsAltNameEntryFixup( ppwszExtErrorInfo, pSubjectEntry, IDS_NOT_PERMITTED_ENTRY_NAME_CONSTRAINT ); } else { dwErrorStatus |= CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT; ChainFormatAndAppendNameConstraintsAltNameEntryFixup( ppwszExtErrorInfo, pSubjectEntry, IDS_NOT_DEFINED_ENTRY_NAME_CONSTRAINT ); } } return dwErrorStatus; } //+--------------------------------------------------------------------------- // // Function: ChainCalculateNameConstraintsErrorStatusForAltNameEntry // // Synopsis: calculates the name constraints error status by seeing if // the subject AltName entry matches either an excluded // or permitted subtree AltName entry. // //---------------------------------------------------------------------------- DWORD WINAPI ChainCalculateNameConstraintsErrorStatusForAltNameEntry( IN PCERT_ALT_NAME_ENTRY pSubjectEntry, IN PCERT_NAME_CONSTRAINTS_INFO pNameConstraintsInfo, IN OUT LPWSTR *ppwszExtErrorInfo ) { DWORD dwErrorStatus; dwErrorStatus = ChainCalculateNameConstraintsSubtreeErrorStatusForAltNameEntry( pSubjectEntry, TRUE, // fExcludedSubtree pNameConstraintsInfo->cExcludedSubtree, pNameConstraintsInfo->rgExcludedSubtree, ppwszExtErrorInfo ); if (!(dwErrorStatus & CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT)) dwErrorStatus = ChainCalculateNameConstraintsSubtreeErrorStatusForAltNameEntry( pSubjectEntry, FALSE, // fExcludedSubtree pNameConstraintsInfo->cPermittedSubtree, pNameConstraintsInfo->rgPermittedSubtree, ppwszExtErrorInfo ); return dwErrorStatus; } //+=========================================================================== // CCertIssuerList helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainCreateIssuerList // // Synopsis: create the issuer list object for the given subject // //---------------------------------------------------------------------------- BOOL WINAPI ChainCreateIssuerList ( IN PCCHAINPATHOBJECT pSubject, OUT PCCERTISSUERLIST* ppIssuerList ) { PCCERTISSUERLIST pIssuerList; pIssuerList = new CCertIssuerList( pSubject ); if ( pIssuerList == NULL ) { SetLastError( (DWORD) E_OUTOFMEMORY ); return( FALSE ); } *ppIssuerList = pIssuerList; return( TRUE ); } //+--------------------------------------------------------------------------- // // Function: ChainFreeIssuerList // // Synopsis: free the issuer list object // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeIssuerList ( IN PCCERTISSUERLIST pIssuerList ) { delete pIssuerList; } //+--------------------------------------------------------------------------- // // Function: ChainFreeCtlIssuerData // // Synopsis: free CTL issuer data // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeCtlIssuerData ( IN PCTL_ISSUER_DATA pCtlIssuerData ) { if ( pCtlIssuerData->pTrustListInfo != NULL ) { SSCtlFreeTrustListInfo( pCtlIssuerData->pTrustListInfo ); } if ( pCtlIssuerData->pSSCtlObject != NULL ) { pCtlIssuerData->pSSCtlObject->Release(); } delete pCtlIssuerData; } //+=========================================================================== // INTERNAL_CERT_CHAIN_CONTEXT helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainAddRefInternalChainContext // // Synopsis: addref the internal chain context // //---------------------------------------------------------------------------- VOID WINAPI ChainAddRefInternalChainContext ( IN PINTERNAL_CERT_CHAIN_CONTEXT pChainContext ) { InterlockedIncrement( &pChainContext->cRefs ); } //+--------------------------------------------------------------------------- // // Function: ChainReleaseInternalChainContext // // Synopsis: release the internal chain context // //---------------------------------------------------------------------------- VOID WINAPI ChainReleaseInternalChainContext ( IN PINTERNAL_CERT_CHAIN_CONTEXT pChainContext ) { if ( InterlockedDecrement( &pChainContext->cRefs ) == 0 ) { ChainFreeInternalChainContext( pChainContext ); } } //+--------------------------------------------------------------------------- // // Function: ChainFreeInternalChainContext // // Synopsis: free the internal chain context // //---------------------------------------------------------------------------- VOID WINAPI ChainFreeInternalChainContext ( IN PINTERNAL_CERT_CHAIN_CONTEXT pContext ) { PCERT_SIMPLE_CHAIN *ppChain; DWORD cChain; PINTERNAL_CERT_CHAIN_CONTEXT *ppLowerContext; if (NULL == pContext) return; cChain = pContext->ChainContext.cChain; ppChain = pContext->ChainContext.rgpChain; for ( ; 0 < cChain; cChain--, ppChain++) { PCERT_SIMPLE_CHAIN pChain; DWORD cElement; PCERT_CHAIN_ELEMENT *ppElement; pChain = *ppChain; if (NULL == pChain) continue; if (pChain->pTrustListInfo) SSCtlFreeTrustListInfo(pChain->pTrustListInfo); cElement = pChain->cElement; ppElement = pChain->rgpElement; for ( ; 0 < cElement; cElement--, ppElement++) { PCERT_CHAIN_ELEMENT pElement; pElement = *ppElement; if (NULL == pElement) continue; if (pElement->pRevocationInfo) { PCERT_REVOCATION_CRL_INFO pCrlInfo = pElement->pRevocationInfo->pCrlInfo; if (pCrlInfo) { if (pCrlInfo->pBaseCrlContext) CertFreeCRLContext(pCrlInfo->pBaseCrlContext); if (pCrlInfo->pDeltaCrlContext) CertFreeCRLContext(pCrlInfo->pDeltaCrlContext); delete pCrlInfo; } delete pElement->pRevocationInfo; } if (pElement->pCertContext) CertFreeCertificateContext(pElement->pCertContext); ChainFreeUsage(pElement->pIssuanceUsage); ChainFreeUsage(pElement->pApplicationUsage); if (pElement->pwszExtendedErrorInfo) PkiFree((LPWSTR) pElement->pwszExtendedErrorInfo); } } ppLowerContext = (PINTERNAL_CERT_CHAIN_CONTEXT*) pContext->ChainContext.rgpLowerQualityChainContext; if (ppLowerContext) { DWORD cLowerContext; DWORD i; cLowerContext = pContext->ChainContext.cLowerQualityChainContext; for (i = 0; i < cLowerContext; i++) ChainReleaseInternalChainContext(ppLowerContext[i]); delete ppLowerContext; } PkiFree(pContext); } //+--------------------------------------------------------------------------- // // Function: ChainUpdateEndEntityCertContext // // Synopsis: update the end entity cert context in the chain context // //---------------------------------------------------------------------------- VOID ChainUpdateEndEntityCertContext( IN OUT PINTERNAL_CERT_CHAIN_CONTEXT pChainContext, IN OUT PCCERT_CONTEXT pEndCertContext ) { PCCERT_CONTEXT pCertContext = pChainContext->ChainContext.rgpChain[0]->rgpElement[0]->pCertContext; if (pCertContext == pEndCertContext) return; pChainContext->ChainContext.rgpChain[0]->rgpElement[0]->pCertContext = pEndCertContext; { DWORD cbData; DWORD cbEndData; // If the chain context's end context has the public key parameter // property and the end context passed in to CertGetCertificateChain // doesn't, then copy the public key parameter property. if (CertGetCertificateContextProperty( pCertContext, CERT_PUBKEY_ALG_PARA_PROP_ID, NULL, // pvData &cbData) && 0 < cbData && !CertGetCertificateContextProperty( pEndCertContext, CERT_PUBKEY_ALG_PARA_PROP_ID, NULL, // pvData &cbEndData)) { BYTE *pbData; __try { pbData = (BYTE *) _alloca(cbData); } __except(EXCEPTION_EXECUTE_HANDLER) { pbData = NULL; } if (pbData) { if (CertGetCertificateContextProperty( pCertContext, CERT_PUBKEY_ALG_PARA_PROP_ID, pbData, &cbData)) { CRYPT_DATA_BLOB Para; Para.pbData = pbData; Para.cbData = cbData; CertSetCertificateContextProperty( pEndCertContext, CERT_PUBKEY_ALG_PARA_PROP_ID, CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG, &Para ); } } } } CertDuplicateCertificateContext(pEndCertContext); CertFreeCertificateContext(pCertContext); } //+=========================================================================== // CERT_REVOCATION_INFO helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainUpdateRevocationInfo // // Synopsis: update the revocation information on the element // //---------------------------------------------------------------------------- VOID WINAPI ChainUpdateRevocationInfo ( IN PCERT_REVOCATION_STATUS pRevStatus, IN OUT PCERT_REVOCATION_INFO pRevocationInfo, IN OUT PCERT_TRUST_STATUS pTrustStatus ) { CertPerfIncrementChainRevocationCount(); if (ERROR_SUCCESS == pRevStatus->dwError) { ; } else if (CRYPT_E_REVOKED == pRevStatus->dwError) { pTrustStatus->dwErrorStatus |= CERT_TRUST_IS_REVOKED; CertPerfIncrementChainRevokedCount(); } else { pTrustStatus->dwErrorStatus |= CERT_TRUST_REVOCATION_STATUS_UNKNOWN; if (CRYPT_E_NO_REVOCATION_CHECK == pRevStatus->dwError) { CertPerfIncrementChainNoRevocationCheckCount(); } else { pTrustStatus->dwErrorStatus |= CERT_TRUST_IS_OFFLINE_REVOCATION; CertPerfIncrementChainRevocationOfflineCount(); } } pRevocationInfo->cbSize = sizeof(CERT_REVOCATION_INFO); pRevocationInfo->dwRevocationResult = pRevStatus->dwError; pRevocationInfo->fHasFreshnessTime = pRevStatus->fHasFreshnessTime; pRevocationInfo->dwFreshnessTime = pRevStatus->dwFreshnessTime; } //+=========================================================================== // CCertChainEngine helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainCreateWorldStore // // Synopsis: create the world store // //---------------------------------------------------------------------------- BOOL WINAPI ChainCreateWorldStore ( IN HCERTSTORE hRoot, IN HCERTSTORE hCA, IN DWORD cAdditionalStore, IN HCERTSTORE* rghAdditionalStore, IN DWORD dwStoreFlags, OUT HCERTSTORE* phWorld ) { BOOL fResult; HCERTSTORE hWorld; HCERTSTORE hStore; DWORD cCount; hWorld = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL ); if ( hWorld == NULL ) { return( FALSE ); } fResult = CertAddStoreToCollection( hWorld, hRoot, 0, 0 ); for ( cCount = 0; ( cCount < cAdditionalStore ) && ( fResult == TRUE ); cCount++ ) { fResult = CertAddStoreToCollection( hWorld, rghAdditionalStore[ cCount ], 0, 0 ); } dwStoreFlags |= CERT_STORE_MAXIMUM_ALLOWED_FLAG | CERT_STORE_SHARE_CONTEXT_FLAG; if ( fResult == TRUE ) { hStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, dwStoreFlags, L"trust" ); if ( hStore != NULL ) { fResult = CertAddStoreToCollection( hWorld, hStore, 0, 0 ); CertCloseStore( hStore, 0 ); } else { fResult = FALSE; } } if ( fResult == TRUE ) { if ( hCA != NULL ) { fResult = CertAddStoreToCollection( hWorld, hCA, 0, 0 ); } else { fResult = FALSE; } } if ( fResult == TRUE ) { hStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, dwStoreFlags, L"my" ); if ( hStore != NULL ) { fResult = CertAddStoreToCollection( hWorld, hStore, 0, 0 ); CertCloseStore( hStore, 0 ); } else { fResult = FALSE; } } if ( fResult == TRUE ) { *phWorld = hWorld; } else { CertCloseStore( hWorld, 0 ); } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainCreateEngineStore // // Synopsis: create the engine store and the change event handle // //---------------------------------------------------------------------------- BOOL WINAPI ChainCreateEngineStore ( IN HCERTSTORE hRootStore, IN HCERTSTORE hTrustStore, IN HCERTSTORE hOtherStore, IN BOOL fDefaultEngine, IN DWORD dwFlags, OUT HCERTSTORE* phEngineStore, OUT HANDLE* phEngineStoreChangeEvent ) { BOOL fResult = TRUE; HCERTSTORE hEngineStore; HANDLE hEvent; hEngineStore = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL ); hEvent = CreateEventA( NULL, FALSE, FALSE, NULL ); if ( ( hEngineStore == NULL ) || ( hEvent == NULL ) ) { fResult = FALSE; } if ( fResult == TRUE ) { fResult = CertAddStoreToCollection( hEngineStore, hRootStore, 0, 0 ); } if ( fResult == TRUE ) { fResult = CertAddStoreToCollection( hEngineStore, hTrustStore, 0, 0 ); } if ( fResult == TRUE ) { fResult = CertAddStoreToCollection( hEngineStore, hOtherStore, 0, 0 ); } if ( ( fResult == TRUE ) && ( dwFlags & CERT_CHAIN_ENABLE_CACHE_AUTO_UPDATE ) ) { // Someday support a let me know about errors flag CertControlStore( hEngineStore, CERT_STORE_CTRL_INHIBIT_DUPLICATE_HANDLE_FLAG, CERT_STORE_CTRL_NOTIFY_CHANGE, &hEvent ); } if ( fResult == TRUE ) { *phEngineStore = hEngineStore; *phEngineStoreChangeEvent = hEvent; } else { if ( hEngineStore != NULL ) { CertCloseStore( hEngineStore, 0 ); } if ( hEvent != NULL ) { CloseHandle( hEvent ); } } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainIsProperRestrictedRoot // // Synopsis: check to see if this restricted root store is a proper subset // of the real root store // //---------------------------------------------------------------------------- BOOL WINAPI ChainIsProperRestrictedRoot ( IN HCERTSTORE hRealRoot, IN HCERTSTORE hRestrictedRoot ) { PCCERT_CONTEXT pCertContext = NULL; PCCERT_CONTEXT pFound = NULL; DWORD cbData = CHAINHASHLEN; BYTE CertificateHash[ CHAINHASHLEN ]; CRYPT_HASH_BLOB HashBlob; HashBlob.cbData = cbData; HashBlob.pbData = CertificateHash; while ( ( pCertContext = CertEnumCertificatesInStore( hRestrictedRoot, pCertContext ) ) != NULL ) { if ( CertGetCertificateContextProperty( pCertContext, CERT_MD5_HASH_PROP_ID, CertificateHash, &cbData ) == TRUE ) { pFound = CertFindCertificateInStore( hRealRoot, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_MD5_HASH, &HashBlob, NULL ); if ( pFound == NULL ) { CertFreeCertificateContext( pCertContext ); return( FALSE ); } else { CertFreeCertificateContext( pFound ); } } } return( TRUE ); } //+--------------------------------------------------------------------------- // // Function: ChainCreateCollectionIncludingCtlCertificates // // Synopsis: create a collection which includes the source store hStore and // any CTL certificates from it // //---------------------------------------------------------------------------- BOOL WINAPI ChainCreateCollectionIncludingCtlCertificates ( IN HCERTSTORE hStore, OUT HCERTSTORE* phCollection ) { BOOL fResult = FALSE; HCERTSTORE hCollection; PCCTL_CONTEXT pCtlContext = NULL; HCERTSTORE hCtlStore; hCollection = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL ); if ( hCollection == NULL ) { return( FALSE ); } fResult = CertAddStoreToCollection( hCollection, hStore, 0, 0 ); while ( ( fResult == TRUE ) && ( ( pCtlContext = CertEnumCTLsInStore( hStore, pCtlContext ) ) != NULL ) ) { hCtlStore = CertOpenStore( CERT_STORE_PROV_MSG, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, 0, pCtlContext->hCryptMsg ); if ( hCtlStore != NULL ) { fResult = CertAddStoreToCollection( hCollection, hCtlStore, 0, 0 ); CertCloseStore( hCtlStore, 0 ); } } if ( fResult == TRUE ) { *phCollection = hCollection; } else { CertCloseStore( hCollection, 0 ); } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainCopyToCAStore // // Synopsis: copies the hStore to the m_hCAStore of the engine // //---------------------------------------------------------------------------- BOOL WINAPI ChainCopyToCAStore ( PCCERTCHAINENGINE pChainEngine, HCERTSTORE hStore ) { PCCERT_CONTEXT pCertContext = NULL; if ( pChainEngine->CAStore() == NULL ) { SetLastError( ERROR_INVALID_PARAMETER ); return( FALSE ); } while ( ( pCertContext = CertEnumCertificatesInStore( hStore, pCertContext ) ) != NULL ) { // Don't add self signed certificates to the CA store if (!CertCompareCertificateName( pCertContext->dwCertEncodingType, &pCertContext->pCertInfo->Subject, &pCertContext->pCertInfo->Issuer )) { CertAddCertificateContextToStore( pChainEngine->CAStore(), pCertContext, CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES, NULL ); } } return( TRUE ); } //+=========================================================================== // URL helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Function: ChainGetObjectUrl // // Synopsis: thunk to CryptGetObjectUrl in cryptnet.dll // //---------------------------------------------------------------------------- BOOL WINAPI ChainGetObjectUrl ( IN LPCSTR pszUrlOid, IN LPVOID pvPara, IN DWORD dwFlags, OUT OPTIONAL PCRYPT_URL_ARRAY pUrlArray, IN OUT DWORD* pcbUrlArray, OUT OPTIONAL PCRYPT_URL_INFO pUrlInfo, IN OUT OPTIONAL DWORD* pcbUrlInfo, IN OPTIONAL LPVOID pvReserved ) { BOOL fResult = FALSE; HMODULE hModule; PFN_GETOBJECTURL pfn = NULL; hModule = ChainGetCryptnetModule(); if ( hModule != NULL ) { pfn = (PFN_GETOBJECTURL)GetProcAddress( hModule, "CryptGetObjectUrl" ); } if ( pfn != NULL ) { fResult = ( *pfn )( pszUrlOid, pvPara, dwFlags, pUrlArray, pcbUrlArray, pUrlInfo, pcbUrlInfo, pvReserved ); } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainRetrieveObjectByUrlW // // Synopsis: thunk to CryptRetrieveObjectByUrlW in cryptnet.dll // //---------------------------------------------------------------------------- BOOL WINAPI ChainRetrieveObjectByUrlW ( IN LPCWSTR pszUrl, IN LPCSTR pszObjectOid, IN DWORD dwRetrievalFlags, IN DWORD dwTimeout, OUT LPVOID* ppvObject, IN HCRYPTASYNC hAsyncRetrieve, IN PCRYPT_CREDENTIALS pCredentials, IN LPVOID pvVerify, IN OPTIONAL PCRYPT_RETRIEVE_AUX_INFO pAuxInfo ) { BOOL fResult = FALSE; HMODULE hModule; PFN_RETRIEVEOBJECTBYURLW pfn = NULL; hModule = ChainGetCryptnetModule(); if ( hModule != NULL ) { pfn = (PFN_RETRIEVEOBJECTBYURLW)GetProcAddress( hModule, "CryptRetrieveObjectByUrlW" ); } if ( pfn != NULL ) { fResult = ( *pfn )( pszUrl, pszObjectOid, dwRetrievalFlags, dwTimeout, ppvObject, hAsyncRetrieve, pCredentials, pvVerify, pAuxInfo ); } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainIsConnected // // Synopsis: thunk to I_CryptNetIsConnected in cryptnet.dll // //---------------------------------------------------------------------------- BOOL WINAPI ChainIsConnected() { BOOL fResult = FALSE; HMODULE hModule; PFN_I_CRYPTNET_IS_CONNECTED pfn = NULL; hModule = ChainGetCryptnetModule(); if ( hModule != NULL ) { pfn = (PFN_I_CRYPTNET_IS_CONNECTED)GetProcAddress( hModule, "I_CryptNetIsConnected" ); } if ( pfn != NULL ) { fResult = ( *pfn )(); } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainGetHostNameFromUrl // // Synopsis: thunk to I_CryptNetGetHostNameFromUrl in cryptnet.dll // //---------------------------------------------------------------------------- BOOL WINAPI ChainGetHostNameFromUrl ( IN LPWSTR pwszUrl, IN DWORD cchHostName, OUT LPWSTR pwszHostName ) { BOOL fResult = FALSE; HMODULE hModule; PFN_I_CRYPTNET_GET_HOST_NAME_FROM_URL pfn = NULL; hModule = ChainGetCryptnetModule(); if ( hModule != NULL ) { pfn = (PFN_I_CRYPTNET_GET_HOST_NAME_FROM_URL)GetProcAddress( hModule, "I_CryptNetGetHostNameFromUrl" ); } if ( pfn != NULL ) { fResult = ( *pfn )( pwszUrl, cchHostName, pwszHostName ); } return( fResult ); } //+--------------------------------------------------------------------------- // // Function: ChainIsFileOrLdapUrl // // Synopsis: check if the URL given is a file or ldap one // //---------------------------------------------------------------------------- BOOL WINAPI ChainIsFileOrLdapUrl ( IN LPCWSTR pwszUrl ) { LPWSTR pwsz; pwsz = wcschr( pwszUrl, L':' ); if ( pwsz != NULL ) { if ( ( _wcsnicmp( pwszUrl, L"file", 4 ) == 0 ) || ( _wcsnicmp( pwszUrl, L"ldap", 4 ) == 0 ) ) { return( TRUE ); } else { return( FALSE ); } } return( TRUE ); } //+--------------------------------------------------------------------------- // // Function: ChainGetOfflineUrlDeltaSeconds // // Synopsis: given the number of unsuccessful attempts to retrieve the // Url, returns the number of seconds to wait before the // next attempt. // //---------------------------------------------------------------------------- const DWORD rgdwChainOfflineUrlDeltaSeconds[] = { 15, // 15 seconds 15, // 15 seconds 60, // 1 minute 60 * 5, // 5 minutes 60 * 10, // 10 minutes 60 * 30, // 30 minutes }; #define CHAIN_OFFLINE_URL_DELTA_SECONDS_CNT \ (sizeof(rgdwChainOfflineUrlDeltaSeconds) / \ sizeof(rgdwChainOfflineUrlDeltaSeconds[0])) DWORD WINAPI ChainGetOfflineUrlDeltaSeconds ( IN DWORD dwOfflineCnt ) { if (0 == dwOfflineCnt) return 0; if (CHAIN_OFFLINE_URL_DELTA_SECONDS_CNT < dwOfflineCnt) dwOfflineCnt = CHAIN_OFFLINE_URL_DELTA_SECONDS_CNT; return rgdwChainOfflineUrlDeltaSeconds[dwOfflineCnt - 1]; } //+=========================================================================== // AuthRoot Auto Update methods and helper functions //============================================================================ //+--------------------------------------------------------------------------- // // Member: CChainPathObject::GetAuthRootAutoUpdateUrlStore, public // // Synopsis: attempts to get a time valid AuthRoot Auto Update CTL. // Checks if there is CTL entry matching the subject // certificate's AKI exact match, key identifier or name // match. For a match URL retrieves the certificate and // returns a store containing the retrieved certificates // // Leaves the engine's critical section to do the URL // fetching. If the engine was touched by another thread, // it fails with LastError set to ERROR_CAN_NOT_COMPLETE. // // Assumption: Chain engine is locked once in the calling thread. // // Only returns FALSE, if the engine was touched when // leaving the critical section. // // The caller has already checked that we are online. // //---------------------------------------------------------------------------- // CN=Root Agency const BYTE rgbRootAgencyIssuerName[] = { 0x30, 0x16, // SEQUENCE 0x31, 0x14, // SET 0x30, 0x12, // SEQUENCE 0x06, 0x03, 0x55, 0x04, 0x03, // OID // PRINTABLE STRING 0x13, 0x0b, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x41, 0x67, 0x65, 0x6e, 0x63, 0x79 }; // CN=Root SGC Authority const BYTE rgbRootSGCAuthorityIssuerName[] = { 0x30, 0x1d, // SEQUENCE 0x31, 0x1b, // SET 0x30, 0x19, // SEQUENCE 0x06, 0x03, 0x55, 0x04, 0x03, // OID // PRINTABLE STRING 0x13, 0x12, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x53, 0x47, 0x43, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79 }; const CRYPT_DATA_BLOB rgSkipPartialIssuer[] = { sizeof(rgbRootAgencyIssuerName), (BYTE *) rgbRootAgencyIssuerName, sizeof(rgbRootSGCAuthorityIssuerName), (BYTE *) rgbRootSGCAuthorityIssuerName }; #define SKIP_PARTIAL_ISSUER_CNT (sizeof(rgSkipPartialIssuer)/ \ sizeof(rgSkipPartialIssuer[0])) BOOL CChainPathObject::GetAuthRootAutoUpdateUrlStore( IN PCCHAINCALLCONTEXT pCallContext, OUT HCERTSTORE *phIssuerUrlStore ) { BOOL fTouchedResult = TRUE; PCCERTCHAINENGINE pChainEngine = pCallContext->ChainEngine(); PCERT_INFO pCertInfo = m_pCertObject->CertContext()->pCertInfo; PCCTL_CONTEXT pCtl = NULL; HCERTSTORE hIssuerUrlStore = NULL; CRYPT_DATA_BLOB rgAuthRootMatchHash[AUTH_ROOT_MATCH_CNT]; DWORD cEntry = 0; PCTL_ENTRY *rgpEntry = NULL; PCCERT_CONTEXT pCert; DWORD cCert; DWORD i; *phIssuerUrlStore = NULL; // Loop and skip known issuers such as, "Root Agency". Don't want all // clients in the world hiting the wire when building these chains for (i = 0; i < SKIP_PARTIAL_ISSUER_CNT; i++) { if (pCertInfo->Issuer.cbData == rgSkipPartialIssuer[i].cbData && 0 == memcmp(pCertInfo->Issuer.pbData, rgSkipPartialIssuer[i].pbData, rgSkipPartialIssuer[i].cbData)) return TRUE; } fTouchedResult = pChainEngine->GetAuthRootAutoUpdateCtl( pCallContext, &pCtl ); if (!fTouchedResult || NULL == pCtl) { #if 0 // This logs too many test failures if (fTouchedResult) { PAUTH_ROOT_AUTO_UPDATE_INFO pInfo = pChainEngine->AuthRootAutoUpdateInfo(); if (NULL == pInfo || !(pInfo->dwFlags & CERT_AUTH_ROOT_AUTO_UPDATE_DISABLE_PARTIAL_CHAIN_LOGGING_FLAG)) IPR_LogCertInformation( MSG_PARTIAL_CHAIN_INFORMATIONAL, m_pCertObject->CertContext(), TRUE // fFormatIssuerName ); } #endif return fTouchedResult; } // We have a valid AuthRoot Auto Update CTL. // See if we can find any matching AuthRoots memset(rgAuthRootMatchHash, 0, sizeof(rgAuthRootMatchHash)); m_pCertObject->GetIssuerKeyMatchHash( &rgAuthRootMatchHash[AUTH_ROOT_KEY_MATCH_IDX]); m_pCertObject->GetIssuerNameMatchHash( &rgAuthRootMatchHash[AUTH_ROOT_NAME_MATCH_IDX]); pChainEngine->FindAuthRootAutoUpdateMatchingCtlEntries( rgAuthRootMatchHash, &pCtl, &cEntry, &rgpEntry ); if (0 == cEntry) { #if 0 // This logs too many test failures PAUTH_ROOT_AUTO_UPDATE_INFO pInfo = pChainEngine->AuthRootAutoUpdateInfo(); if (NULL == pInfo || !(pInfo->dwFlags & CERT_AUTH_ROOT_AUTO_UPDATE_DISABLE_PARTIAL_CHAIN_LOGGING_FLAG)) IPR_LogCertInformation( MSG_PARTIAL_CHAIN_INFORMATIONAL, m_pCertObject->CertContext(), TRUE // fFormatIssuerName ); #endif goto NoAutoUpdateCtlEntry; } hIssuerUrlStore = CertOpenStore( CERT_STORE_PROV_MEMORY, 0, // dwEncodingType NULL, // hCryptProv 0, // dwFlags NULL // pvPara ); if (NULL == hIssuerUrlStore) goto OpenMemoryStoreError; for (i = 0; i < cEntry; i++) { PCTL_ENTRY pEntry = rgpEntry[i]; // If already in our store, no need to hit the wire and retrieve. if (pCert = CertFindCertificateInStore( pChainEngine->OtherStore(), X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SHA1_HASH, (LPVOID) &pEntry->SubjectIdentifier, NULL )) { CertFreeCertificateContext(pCert); continue; } fTouchedResult = pChainEngine->GetAuthRootAutoUpdateCert( pCallContext, pEntry, hIssuerUrlStore ); if (!fTouchedResult) goto TouchedDuringUrlRetrievalOfAuthRoots; } pCert = NULL; cCert = 0; while (pCert = CertEnumCertificatesInStore(hIssuerUrlStore, pCert)) cCert++; if (0 == cCert) goto NoAuthRootAutoUpdateCerts; if (1 < cCert) { // If more than one root in the list, explicitly add them all here. // While building the chain using the returned AuthRoots we might // leave the critical section and restart. After restarting may // have a trusted root and won't redo this URL retrieval. pChainEngine->UnlockEngine(); pCert = NULL; while (pCert = CertEnumCertificatesInStore(hIssuerUrlStore, pCert)) IPR_AddCertInAuthRootAutoUpdateCtl(pCert, pCtl); pChainEngine->LockEngine(); if (pCallContext->IsTouchedEngine()) { fTouchedResult = FALSE; goto TouchedDuringAddOfAuthRoots; } } *phIssuerUrlStore = hIssuerUrlStore; CommonReturn: if (rgpEntry) PkiFree(rgpEntry); if (pCtl) CertFreeCTLContext(pCtl); return fTouchedResult; ErrorReturn: if (hIssuerUrlStore) CertCloseStore(hIssuerUrlStore, 0); goto CommonReturn; TRACE_ERROR(NoAutoUpdateCtlEntry) TRACE_ERROR(OpenMemoryStoreError) TRACE_ERROR(TouchedDuringUrlRetrievalOfAuthRoots) TRACE_ERROR(NoAuthRootAutoUpdateCerts) TRACE_ERROR(TouchedDuringAddOfAuthRoots) } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::RetrieveAuthRootAutoUpdateObjectByUrlW, public // // Synopsis: URL retrieves an AuthRoot Auto Update object. For wire // retrieval, logs the event. // // Leaves the engine's critical section to do the URL // fetching. If the engine was touched by another thread, // it fails with LastError set to ERROR_CAN_NOT_COMPLETE. // // Assumption: Chain engine is locked once in the calling thread. // // If the object was successfully retrieved, // *ppvObject != NULL. Otherwise, *ppvObject = NULL. // // Only returns FALSE, if the engine was touched when // leaving the critical section. *ppvObject may be != NULL // when touched. // //---------------------------------------------------------------------------- BOOL CCertChainEngine::RetrieveAuthRootAutoUpdateObjectByUrlW( IN PCCHAINCALLCONTEXT pCallContext, IN DWORD dwSuccessEventID, IN DWORD dwFailEventID, IN LPCWSTR pwszUrl, IN LPCSTR pszObjectOid, IN DWORD dwRetrievalFlags, IN DWORD dwTimeout, // 0 => use default OUT LPVOID* ppvObject, IN OPTIONAL PCRYPT_RETRIEVE_AUX_INFO pAuxInfo ) { BOOL fTouchedResult = TRUE; BOOL fResult; *ppvObject = NULL; if (0 == dwTimeout) dwTimeout = pCallContext->ChainPara()->dwUrlRetrievalTimeout; // // We are about to go on the wire to retrieve the object. // At this time we will release the chain engine lock so others can // go about there business while we wait for the protocols to do the // fetching. // UnlockEngine(); // Note, the windows update server doesn't require authentication. // wininet sometimes calls us within a critical section. NO_AUTH // normally will fix this deadlock. // // On 09-May-01 the above was fixed by wininet. // Removed setting CRYPT_NO_AUTH_RETRIEVAL. // // Authentication may be required by a proxy. fResult = ChainRetrieveObjectByUrlW( pwszUrl, pszObjectOid, dwRetrievalFlags, dwTimeout, ppvObject, NULL, // hAsyncRetrieve NULL, // pCredentials NULL, // pvVerify pAuxInfo ); if (dwRetrievalFlags & CRYPT_WIRE_ONLY_RETRIEVAL) { // Only log wire retrievals if (fResult) { LPCWSTR rgpwszStrings[1] = { pwszUrl }; IPR_LogCrypt32Event( EVENTLOG_INFORMATION_TYPE, dwSuccessEventID, 1, // wNumStrings rgpwszStrings ); } else IPR_LogCrypt32Error( dwFailEventID, pwszUrl, GetLastError() ); } LockEngine(); if (pCallContext->IsTouchedEngine()) { fTouchedResult = FALSE; goto TouchedDuringAuthRootObjectUrlRetrieval; } if (fResult) assert(*ppvObject); else assert(NULL == *ppvObject); CommonReturn: return fTouchedResult; ErrorReturn: goto CommonReturn; SET_ERROR(TouchedDuringAuthRootObjectUrlRetrieval, ERROR_CAN_NOT_COMPLETE) } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::GetAuthRootAutoUpdateCtl, public // // Synopsis: if auto update hasn't been disabled, // returns the AuthRoot Auto Update CTL. Hits the wire // if necessary to get a "fresh" CTL. // // Note, 2 URL fetches. One for the SequenceNumber file. The // other for the CTL cab file. The SequenceNumber file // is small and bounded in size. If it matches the SequenceNumber // in an already retrieved CTL, then, no need to hit the // wire to retrive the larger CTL file. This optimization will // reduce the number of bytes needing to be fetched across the // wire. The CTL won't be updated that often. // // Leaves the engine's critical section to do the URL // fetching. If the engine was touched by another thread, // it fails with LastError set to ERROR_CAN_NOT_COMPLETE. // // Assumption: Chain engine is locked once in the calling thread. // // If auto update has been disabled, returns TRUE and // *ppCtl = NULL. // // Only returns FALSE, if the engine was touched when // leaving the critical section. // // The returned pCtl is AddRef'ed. // //---------------------------------------------------------------------------- BOOL CCertChainEngine::GetAuthRootAutoUpdateCtl( IN PCCHAINCALLCONTEXT pCallContext, OUT PCCTL_CONTEXT *ppCtl ) { BOOL fTouchedResult = TRUE; FILETIME CurrentTime; PAUTH_ROOT_AUTO_UPDATE_INFO pInfo; PCRYPT_BLOB_ARRAY pcbaSeq = NULL; PCRYPT_BLOB_ARRAY pcbaCab = NULL; PCCTL_CONTEXT pNewCtl = NULL; CRYPT_RETRIEVE_AUX_INFO RetrieveAuxInfo; DWORD i; *ppCtl = NULL; if ((pCallContext->CallOrEngineFlags() & CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE) || IPR_IsAuthRootAutoUpdateDisabled()) return TRUE; if (NULL == (pInfo = m_pAuthRootAutoUpdateInfo)) { if (NULL == (pInfo = CreateAuthRootAutoUpdateInfo())) return TRUE; m_pAuthRootAutoUpdateInfo = pInfo; } pCallContext->CurrentTime(&CurrentTime); memset(&RetrieveAuxInfo, 0, sizeof(RetrieveAuxInfo)); RetrieveAuxInfo.cbSize = sizeof(RetrieveAuxInfo); // First try the cache. If unable to retrieve the seq file or // find a time valid CTL cab in the cache, hit the wire. for (i = 0; i <= 1; i++) { BOOL fResult; DWORD dwRetrievalFlags; DWORD dwCtlTimeout = 0; PCRYPT_INTEGER_BLOB pSequenceNumber; FILETIME NewLastSyncTime; FILETIME CtlLastSyncTime; PCTL_INFO pNewCtlInfo; if (pInfo->pCtl && 0 < CompareFileTime(&pInfo->NextSyncTime, &CurrentTime)) // We already have a time valid CTL break; if (0 == i) dwRetrievalFlags = CRYPT_CACHE_ONLY_RETRIEVAL; else { if (!pCallContext->IsOnline()) break; dwRetrievalFlags = CRYPT_WIRE_ONLY_RETRIEVAL; } // First try to fetch the CTL's sequence number file RetrieveAuxInfo.pLastSyncTime = &NewLastSyncTime; fTouchedResult = RetrieveAuthRootAutoUpdateObjectByUrlW( pCallContext, MSG_ROOT_SEQUENCE_NUMBER_AUTO_UPDATE_URL_RETRIEVAL_INFORMATIONAL, MSG_ROOT_SEQUENCE_NUMBER_AUTO_UPDATE_URL_RETRIEVAL_ERROR, pInfo->pwszSeqUrl, NULL, // pszObjectOid, dwRetrievalFlags | CRYPT_OFFLINE_CHECK_RETRIEVAL | CRYPT_STICKY_CACHE_RETRIEVAL, 0, // dwTimeout (use default) (LPVOID*) &pcbaSeq, &RetrieveAuxInfo ); if (!fTouchedResult) goto TouchedDuringAuthRootSeqUrlRetrieval; pSequenceNumber = NULL; if (NULL == pcbaSeq) { // SequenceNumber retrieval failed if (0 != i) // For wire retrieval failure, don't try to fetch the CTL continue; } else if (0 > CompareFileTime(&NewLastSyncTime, &pInfo->LastSyncTime)) { // An older sync time CryptMemFree(pcbaSeq); pcbaSeq = NULL; } else { // Extract the Sequence Number from the retrieved blob. // Convert the ascii hex characters to binary. Overwrite // the ascii hex with the converted bytes. // Convert binary to little endian. DWORD cchSeq; BOOL fUpperNibble = TRUE; DWORD cb = 0; DWORD j; pSequenceNumber = pcbaSeq->rgBlob; if (0 == pcbaSeq->cBlob) cchSeq = 0; else cchSeq = pSequenceNumber->cbData; for (j = 0; j < cchSeq; j++) { char ch = (char) pSequenceNumber->pbData[j]; BYTE b; // only convert ascii hex characters 0..9, a..f, A..F // silently ignore all others if (ch >= '0' && ch <= '9') b = (BYTE) (ch - '0'); else if (ch >= 'a' && ch <= 'f') b = (BYTE) (10 + ch - 'a'); else if (ch >= 'A' && ch <= 'F') b = (BYTE) (10 + ch - 'A'); else continue; if (fUpperNibble) { pSequenceNumber->pbData[cb] = b << 4; fUpperNibble = FALSE; } else { pSequenceNumber->pbData[cb] |= b; cb++; fUpperNibble = TRUE; } } if (0 == cb) { // Empty sequence number. CryptMemFree(pcbaSeq); pcbaSeq = NULL; } else { pSequenceNumber->cbData = cb; PkiAsn1ReverseBytes(pSequenceNumber->pbData, pSequenceNumber->cbData); // Check if we already have a CTL corresponding to this // fetched SequenceNumber if (pInfo->pCtl) { PCTL_INFO pCtlInfo = pInfo->pCtl->pCtlInfo; if (pCtlInfo->SequenceNumber.cbData == pSequenceNumber->cbData && 0 == memcmp(pCtlInfo->SequenceNumber.pbData, pSequenceNumber->pbData, pSequenceNumber->cbData)) { // Same CTL pInfo->LastSyncTime = NewLastSyncTime; I_CryptIncrementFileTimeBySeconds( &pInfo->LastSyncTime, pInfo->dwSyncDeltaTime, &pInfo->NextSyncTime ); CryptMemFree(pcbaSeq); pcbaSeq = NULL; continue; } } // The SequenceNumber consists of the FILETIME followed by // an optional byte containing a hint for the CTL URL // retrieval timeout (in seconds). If we are using the // default retrieval timeout, use the hint if it exceeds // the default timeout. if (sizeof(FILETIME) < cb && pCallContext->HasDefaultUrlRetrievalTimeout()) { dwCtlTimeout = ((DWORD) pSequenceNumber->pbData[sizeof(FILETIME)]) * 1000; if (dwCtlTimeout < pCallContext->ChainPara()->dwUrlRetrievalTimeout) dwCtlTimeout = pCallContext->ChainPara()->dwUrlRetrievalTimeout; } } } // After retrieving the sequence number file, now // try to fetch the cab containing the CTL RetrieveAuxInfo.pLastSyncTime = &CtlLastSyncTime; fTouchedResult = RetrieveAuthRootAutoUpdateObjectByUrlW( pCallContext, MSG_ROOT_LIST_AUTO_UPDATE_URL_RETRIEVAL_INFORMATIONAL, MSG_ROOT_LIST_AUTO_UPDATE_URL_RETRIEVAL_ERROR, pInfo->pwszCabUrl, NULL, // pszObjectOid, dwRetrievalFlags | CRYPT_OFFLINE_CHECK_RETRIEVAL | CRYPT_STICKY_CACHE_RETRIEVAL, dwCtlTimeout, (LPVOID*) &pcbaCab, &RetrieveAuxInfo ); if (!fTouchedResult) goto TouchedDuringAuthRootCabUrlRetrieval; if (NULL == pcbaCab) { // Cab Retrieval failed if (pcbaSeq) { CryptMemFree(pcbaSeq); pcbaSeq = NULL; } continue; } // Leave the engine to extract the CTL from the cab UnlockEngine(); pNewCtl = ExtractAuthRootAutoUpdateCtlFromCab(pcbaCab); if (NULL == pNewCtl) IPR_LogCrypt32Error( MSG_ROOT_LIST_AUTO_UPDATE_EXTRACT_ERROR, pInfo->pwszCabUrl, GetLastError() ); CryptMemFree(pcbaCab); pcbaCab = NULL; LockEngine(); if (pCallContext->IsTouchedEngine()) { fTouchedResult = FALSE; goto TouchedDuringExtractAuthRootCtl; } if (NULL == pNewCtl) { // Ctl Extraction failed if (pcbaSeq) { CryptMemFree(pcbaSeq); pcbaSeq = NULL; } continue; } // If the SequenceNumber is the same as the one in the retrieved // Ctl, then, use the lastest sync of the 2 URL fetches. Otherwise, // use the Ctl sync time pNewCtlInfo = pNewCtl->pCtlInfo; if (NULL == pcbaSeq || pNewCtlInfo->SequenceNumber.cbData != pSequenceNumber->cbData || 0 != memcmp(pNewCtlInfo->SequenceNumber.pbData, pSequenceNumber->pbData, pSequenceNumber->cbData) || 0 < CompareFileTime(&CtlLastSyncTime, &NewLastSyncTime)) NewLastSyncTime = CtlLastSyncTime; // We are done with the SequenceNumber info if (pcbaSeq) { CryptMemFree(pcbaSeq); pcbaSeq = NULL; } if (0 >= CompareFileTime(&NewLastSyncTime, &pInfo->LastSyncTime)) { // Not a newer sync CertFreeCTLContext(pNewCtl); pNewCtl = NULL; continue; } if (pInfo->pCtl && pInfo->pCtl->cbCtlEncoded == pNewCtl->cbCtlEncoded && 0 == memcmp(pInfo->pCtl->pbCtlEncoded, pNewCtl->pbCtlEncoded, pNewCtl->cbCtlEncoded)) { // Same CTL pInfo->LastSyncTime = NewLastSyncTime; I_CryptIncrementFileTimeBySeconds( &pInfo->LastSyncTime, pInfo->dwSyncDeltaTime, &pInfo->NextSyncTime ); CertFreeCTLContext(pNewCtl); pNewCtl = NULL; continue; } // Leave the engine to verify the CTL UnlockEngine(); fResult = IRL_VerifyAuthRootAutoUpdateCtl(pNewCtl); if (!fResult) IPR_LogCrypt32Error( MSG_ROOT_LIST_AUTO_UPDATE_EXTRACT_ERROR, pInfo->pwszCabUrl, GetLastError() ); LockEngine(); if (fResult && 0 < CompareFileTime(&NewLastSyncTime, &pInfo->LastSyncTime)) { // Valid CTL that is newer pInfo->LastSyncTime = NewLastSyncTime; I_CryptIncrementFileTimeBySeconds( &pInfo->LastSyncTime, pInfo->dwSyncDeltaTime, &pInfo->NextSyncTime ); FreeAuthRootAutoUpdateMatchCaches(pInfo->rghMatchCache); if (pInfo->pCtl) CertFreeCTLContext(pInfo->pCtl); pInfo->pCtl = pNewCtl; pNewCtl = NULL; } if (pCallContext->IsTouchedEngine()) { fTouchedResult = FALSE; goto TouchedDuringVerifyAuthRootCtl; } } if (pInfo->pCtl) *ppCtl = CertDuplicateCTLContext(pInfo->pCtl); CommonReturn: if (pcbaSeq) CryptMemFree(pcbaSeq); if (pcbaCab) CryptMemFree(pcbaCab); if (pNewCtl) CertFreeCTLContext(pNewCtl); return fTouchedResult; ErrorReturn: goto CommonReturn; TRACE_ERROR(TouchedDuringAuthRootSeqUrlRetrieval) TRACE_ERROR(TouchedDuringAuthRootCabUrlRetrieval) SET_ERROR(TouchedDuringExtractAuthRootCtl, ERROR_CAN_NOT_COMPLETE) SET_ERROR(TouchedDuringVerifyAuthRootCtl, ERROR_CAN_NOT_COMPLETE) } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::FindAuthRootAutoUpdateMatchingCtlEntries, public // // Synopsis: If the CTL hash match cache doesn't exist its created. // Iterates through the key and name hash cache entries. // Returns matching entries. Removes duplicates. // // Assumption: Chain engine is locked once in the calling thread. // // The returned prgpCtlEntry must be PkiFree()'ed. // // Note, if the engine's pCtl is different then the passed in // pCtl, the passed in pCtl is free'ed and updated with the // engine's. // //---------------------------------------------------------------------------- VOID CCertChainEngine::FindAuthRootAutoUpdateMatchingCtlEntries( IN CRYPT_DATA_BLOB rgMatchHash[AUTH_ROOT_MATCH_CNT], IN OUT PCCTL_CONTEXT *ppCtl, OUT DWORD *pcCtlEntry, OUT PCTL_ENTRY **prgpCtlEntry ) { PAUTH_ROOT_AUTO_UPDATE_INFO pInfo; PCCTL_CONTEXT pCtl; DWORD cCtlEntry = 0; PCTL_ENTRY *rgpCtlEntry = NULL; DWORD i; pInfo = m_pAuthRootAutoUpdateInfo; if (NULL == pInfo || NULL == pInfo->pCtl) goto InvalidCtl; pCtl = *ppCtl; if (pCtl != pInfo->pCtl) { assert(pCtl); CertFreeCTLContext(pCtl); *ppCtl = pCtl = pInfo->pCtl; CertDuplicateCTLContext(pCtl); } if (!CreateAuthRootAutoUpdateMatchCaches( pCtl, pInfo->rghMatchCache )) goto CreateMatchCachesError; assert(pInfo->rghMatchCache[0]); assert(pInfo->rghMatchCache[AUTH_ROOT_MATCH_CNT - 1]); // Loop through the exact, key and name match hashes and try to find an // entry in the corresponding CTL match cache for (i = 0; i < AUTH_ROOT_MATCH_CNT; i++) { HLRUENTRY hEntry; if (0 == rgMatchHash[i].cbData) continue; hEntry = I_CryptFindLruEntry(pInfo->rghMatchCache[i], &rgMatchHash[i]); while (NULL != hEntry) { PCTL_ENTRY pCtlEntry; PCTL_ENTRY *rgpNewCtlEntry; DWORD j; pCtlEntry = (PCTL_ENTRY) I_CryptGetLruEntryData(hEntry); hEntry = I_CryptEnumMatchingLruEntries(hEntry); assert(pCtlEntry); if (NULL == pCtlEntry) continue; // Check if we already have this Ctl Entry for (j = 0; j < cCtlEntry; j++) { if (pCtlEntry == rgpCtlEntry[j]) break; } if (j < cCtlEntry) continue; if (NULL == (rgpNewCtlEntry = (PCTL_ENTRY *) PkiRealloc( rgpCtlEntry, (cCtlEntry + 1) * sizeof(PCTL_ENTRY)))) continue; rgpCtlEntry = rgpNewCtlEntry; rgpCtlEntry[cCtlEntry++] = pCtlEntry; } } CommonReturn: *pcCtlEntry = cCtlEntry; *prgpCtlEntry = rgpCtlEntry; return; ErrorReturn: goto CommonReturn; TRACE_ERROR(InvalidCtl) TRACE_ERROR(CreateMatchCachesError) } //+--------------------------------------------------------------------------- // // Member: CCertChainEngine::GetAuthRootAutoUpdateCert, public // // Synopsis: URL retrieval of the AuthRoot from the Microsoft web // server. // // Leaves the engine's critical section to do the URL // fetching. If the engine was touched by another thread, // it fails with LastError set to ERROR_CAN_NOT_COMPLETE. // // Assumption: Chain engine is locked once in the calling thread. // // Only returns FALSE, if the engine was touched when // leaving the critical section. // //---------------------------------------------------------------------------- BOOL CCertChainEngine::GetAuthRootAutoUpdateCert( IN PCCHAINCALLCONTEXT pCallContext, IN PCTL_ENTRY pCtlEntry, IN OUT HCERTSTORE hStore ) { BOOL fTouchedResult = TRUE; LPWSTR pwszCertUrl = NULL; HCERTSTORE hUrlStore = NULL; assert(m_pAuthRootAutoUpdateInfo); if (SHA1_HASH_LEN != pCtlEntry->SubjectIdentifier.cbData) goto InvalidCtlEntryError; if (NULL == (pwszCertUrl = FormatAuthRootAutoUpdateCertUrl( pCtlEntry->SubjectIdentifier.pbData, m_pAuthRootAutoUpdateInfo ))) goto FormatCertUrlError; fTouchedResult = RetrieveAuthRootAutoUpdateObjectByUrlW( pCallContext, MSG_ROOT_CERT_AUTO_UPDATE_URL_RETRIEVAL_INFORMATIONAL, MSG_ROOT_CERT_AUTO_UPDATE_URL_RETRIEVAL_ERROR, pwszCertUrl, CONTEXT_OID_CERTIFICATE, CRYPT_RETRIEVE_MULTIPLE_OBJECTS | CRYPT_LDAP_SCOPE_BASE_ONLY_RETRIEVAL | CRYPT_OFFLINE_CHECK_RETRIEVAL | CRYPT_WIRE_ONLY_RETRIEVAL | CRYPT_DONT_CACHE_RESULT, 0, // dwTimeout (use default) (LPVOID *) &hUrlStore, NULL // pAuxInfo ); if (!fTouchedResult) goto TouchedDuringAuthRootCertUrlRetrieval; if (hUrlStore) I_CertUpdateStore(hStore, hUrlStore, 0, NULL); CommonReturn: PkiFree(pwszCertUrl); if (hUrlStore) CertCloseStore(hUrlStore, 0); return fTouchedResult; ErrorReturn: goto CommonReturn; SET_ERROR(InvalidCtlEntryError, ERROR_INVALID_DATA) TRACE_ERROR(FormatCertUrlError) TRACE_ERROR(TouchedDuringAuthRootCertUrlRetrieval) } //+--------------------------------------------------------------------------- // // Function: CreateAuthRootAutoUpdateInfo // // Synopsis: creates and initializes the AuthRoot Auto Update info // //---------------------------------------------------------------------------- PAUTH_ROOT_AUTO_UPDATE_INFO WINAPI CreateAuthRootAutoUpdateInfo() { HKEY hKey = NULL; PAUTH_ROOT_AUTO_UPDATE_INFO pInfo = NULL; DWORD cchDir; DWORD cchUrl; if (NULL == (pInfo = (PAUTH_ROOT_AUTO_UPDATE_INFO) PkiZeroAlloc( sizeof(AUTH_ROOT_AUTO_UPDATE_INFO)))) goto OutOfMemory; if (ERROR_SUCCESS != RegOpenKeyExU( HKEY_LOCAL_MACHINE, CERT_AUTH_ROOT_AUTO_UPDATE_LOCAL_MACHINE_REGPATH, 0, // dwReserved KEY_READ, &hKey )) hKey = NULL; if (hKey) { // Attempt to get values from registry ILS_ReadDWORDValueFromRegistry( hKey, CERT_AUTH_ROOT_AUTO_UPDATE_SYNC_DELTA_TIME_VALUE_NAME, &pInfo->dwSyncDeltaTime ); ILS_ReadDWORDValueFromRegistry( hKey, CERT_AUTH_ROOT_AUTO_UPDATE_FLAGS_VALUE_NAME, &pInfo->dwFlags ); pInfo->pwszRootDirUrl = ILS_ReadSZValueFromRegistry( hKey, CERT_AUTH_ROOT_AUTO_UPDATE_ROOT_DIR_URL_VALUE_NAME ); if (pInfo->pwszRootDirUrl && L'\0' == *pInfo->pwszRootDirUrl) { PkiFree(pInfo->pwszRootDirUrl); pInfo->pwszRootDirUrl = NULL; } } // If not defined in registry, use our defaults if (0 == pInfo->dwSyncDeltaTime) pInfo->dwSyncDeltaTime = AUTH_ROOT_AUTO_UPDATE_SYNC_DELTA_TIME; if (NULL == pInfo->pwszRootDirUrl) { if (NULL == (pInfo->pwszRootDirUrl = ILS_AllocAndCopyString( AUTH_ROOT_AUTO_UPDATE_ROOT_DIR_URL))) goto OutOfMemory; } // Construct the CTL and Seq Urls cchDir = wcslen(pInfo->pwszRootDirUrl); cchUrl = cchDir + 1 + wcslen(CERT_AUTH_ROOT_CAB_FILENAME) + 1; if (NULL == (pInfo->pwszCabUrl = (LPWSTR) PkiNonzeroAlloc( sizeof(WCHAR) * cchUrl))) goto OutOfMemory; wcscpy(pInfo->pwszCabUrl, pInfo->pwszRootDirUrl); pInfo->pwszCabUrl[cchDir] = L'/'; wcscpy(pInfo->pwszCabUrl + cchDir + 1, CERT_AUTH_ROOT_CAB_FILENAME); cchUrl = cchDir + 1 + wcslen(CERT_AUTH_ROOT_SEQ_FILENAME) + 1; if (NULL == (pInfo->pwszSeqUrl = (LPWSTR) PkiNonzeroAlloc( sizeof(WCHAR) * cchUrl))) goto OutOfMemory; wcscpy(pInfo->pwszSeqUrl, pInfo->pwszRootDirUrl); pInfo->pwszSeqUrl[cchDir] = L'/'; wcscpy(pInfo->pwszSeqUrl + cchDir + 1, CERT_AUTH_ROOT_SEQ_FILENAME); CommonReturn: ILS_CloseRegistryKey(hKey); return pInfo; ErrorReturn: FreeAuthRootAutoUpdateInfo(pInfo); pInfo = NULL; goto CommonReturn; TRACE_ERROR(OutOfMemory) } //+--------------------------------------------------------------------------- // // Function: FreeAuthRootAutoUpdateInfo // // Synopsis: frees the AuthRoot Auto Update info // //---------------------------------------------------------------------------- VOID WINAPI FreeAuthRootAutoUpdateInfo( IN OUT PAUTH_ROOT_AUTO_UPDATE_INFO pInfo ) { if (NULL == pInfo) return; PkiFree(pInfo->pwszRootDirUrl); PkiFree(pInfo->pwszCabUrl); PkiFree(pInfo->pwszSeqUrl); FreeAuthRootAutoUpdateMatchCaches(pInfo->rghMatchCache); if (pInfo->pCtl) CertFreeCTLContext(pInfo->pCtl); PkiFree(pInfo); } const LPCSTR rgpszAuthRootMatchOID[AUTH_ROOT_MATCH_CNT] = { szOID_CERT_KEY_IDENTIFIER_PROP_ID, szOID_CERT_SUBJECT_NAME_MD5_HASH_PROP_ID }; //+--------------------------------------------------------------------------- // // Function: CreateAuthRootAutoUpdateMatchCaches // // Synopsis: if not already created, iterates through the CTL entries // and creates key and name match caches entries from // the associated entry hash attribute values. // //---------------------------------------------------------------------------- BOOL WINAPI CreateAuthRootAutoUpdateMatchCaches( IN PCCTL_CONTEXT pCtl, IN OUT HLRUCACHE rghMatchCache[AUTH_ROOT_MATCH_CNT] ) { BOOL fResult; LRU_CACHE_CONFIG Config; DWORD i; DWORD cEntry; PCTL_ENTRY pEntry; if (NULL != rghMatchCache[0]) // Already created. return TRUE; memset( &Config, 0, sizeof( Config ) ); Config.dwFlags = LRU_CACHE_NO_SERIALIZE | LRU_CACHE_NO_COPY_IDENTIFIER; Config.pfnHash = CertObjectCacheHashMd5Identifier; Config.cBuckets = AUTH_ROOT_MATCH_CACHE_BUCKETS; for (i = 0; i < AUTH_ROOT_MATCH_CNT; i++) { if (!I_CryptCreateLruCache(&Config, &rghMatchCache[i])) goto CreateLruCacheError; } // Loop through the CTL entries and add the exact, key and name match // hash cache entries cEntry = pCtl->pCtlInfo->cCTLEntry; pEntry = pCtl->pCtlInfo->rgCTLEntry; for ( ; cEntry > 0; cEntry--, pEntry++) { DWORD cAttr; PCRYPT_ATTRIBUTE pAttr; cAttr = pEntry->cAttribute; pAttr = pEntry->rgAttribute; // Skip a remove entry if (CertFindAttribute( szOID_REMOVE_CERTIFICATE, cAttr, pAttr )) continue; for ( ; cAttr > 0; cAttr--, pAttr++) { for (i = 0; i < AUTH_ROOT_MATCH_CNT; i++) { if (0 == strcmp(rgpszAuthRootMatchOID[i], pAttr->pszObjId)) break; } if (i < AUTH_ROOT_MATCH_CNT) { PCRYPT_ATTR_BLOB pValue; DWORD cbHash; const BYTE *pbHash; CRYPT_DATA_BLOB DataBlob; HLRUENTRY hEntry = NULL; // Check that we have a single valued attribute encoded as an // OCTET STRING if (1 != pAttr->cValue) continue; pValue = pAttr->rgValue; if (2 > pValue->cbData || ASN1UTIL_TAG_OCTETSTRING != pValue->pbData[0]) continue; // Extract the hash bytes from the encoded OCTET STRING if (0 >= Asn1UtilExtractContent( pValue->pbData, pValue->cbData, &cbHash, &pbHash ) || CMSG_INDEFINITE_LENGTH == cbHash || 0 == cbHash) continue; DataBlob.cbData = cbHash; DataBlob.pbData = (BYTE *) pbHash; if (!I_CryptCreateLruEntry( rghMatchCache[i], &DataBlob, pEntry, &hEntry )) goto CreateLruEntryError; I_CryptInsertLruEntry(hEntry, NULL); I_CryptReleaseLruEntry(hEntry); } } } fResult = TRUE; CommonReturn: return fResult; ErrorReturn: FreeAuthRootAutoUpdateMatchCaches(rghMatchCache); fResult = FALSE; goto CommonReturn; TRACE_ERROR(CreateLruCacheError) TRACE_ERROR(CreateLruEntryError) } //+--------------------------------------------------------------------------- // // Function: FreeAuthRootAutoUpdateMatchCaches // // Synopsis: frees the AuthRoot Auto Match Caches // //---------------------------------------------------------------------------- VOID WINAPI FreeAuthRootAutoUpdateMatchCaches( IN OUT HLRUCACHE rghMatchCache[AUTH_ROOT_MATCH_CNT] ) { DWORD i; for (i = 0; i < AUTH_ROOT_MATCH_CNT; i++) { if (NULL != rghMatchCache[i]) { I_CryptFreeLruCache( rghMatchCache[i], LRU_SUPPRESS_REMOVAL_NOTIFICATION, NULL ); rghMatchCache[i] = NULL; } } } //+--------------------------------------------------------------------------- // // Function: FormatAuthRootAutoUpdateCertUrl // // Synopsis: allocates and formats the URL to retrieve the auth root cert // // returns "RootDir" "/" "AsciiHexHash" ".cer" // for example, // "http://www.xyz.com/roots/216B2A29E62A00CE820146D8244141B92511B279.cer" // //---------------------------------------------------------------------------- LPWSTR WINAPI FormatAuthRootAutoUpdateCertUrl( IN BYTE rgbSha1Hash[SHA1_HASH_LEN], IN PAUTH_ROOT_AUTO_UPDATE_INFO pInfo ) { LPWSTR pwszUrl; DWORD cchDir; DWORD cchUrl; assert(pInfo->pwszRootDirUrl); cchDir = wcslen(pInfo->pwszRootDirUrl); cchUrl = cchDir + 1 + SHA1_HASH_NAME_LEN + wcslen(CERT_AUTH_ROOT_CERT_EXT) + 1; if (NULL == (pwszUrl = (LPWSTR) PkiNonzeroAlloc(sizeof(WCHAR) * cchUrl))) return NULL; wcscpy(pwszUrl, pInfo->pwszRootDirUrl); pwszUrl[cchDir] = L'/'; ILS_BytesToWStr(SHA1_HASH_LEN, rgbSha1Hash, pwszUrl + cchDir + 1); wcscpy(pwszUrl + cchDir + 1 + SHA1_HASH_NAME_LEN, CERT_AUTH_ROOT_CERT_EXT); return pwszUrl; } // Known invalid roots BYTE AuthRootInvalidList[][SHA1_HASH_LEN] = { // verisign "timestamp" - '97 { 0xD4, 0x73, 0x5D, 0x8A, 0x9A, 0xE5, 0xBC, 0x4B, 0x0A, 0x0D, 0xC2, 0x70, 0xD6, 0xA6, 0x25, 0x38, 0xA5, 0x87, 0xD3, 0x2F }, // Root Agency (test root) { 0xFE, 0xE4, 0x49, 0xEE, 0x0E, 0x39, 0x65, 0xA5, 0x24, 0x6F, 0x00, 0x0E, 0x87, 0xFD, 0xE2, 0xA0, 0x65, 0xFD, 0x89, 0xD4 }, }; #define AUTH_ROOT_INVALID_LIST_CNT (sizeof(AuthRootInvalidList) / \ sizeof(AuthRootInvalidList[0])) //+--------------------------------------------------------------------------- // // Function: ChainGetAuthRootAutoUpdateStatus // // Synopsis: return status bits specifying if the root is // trusted via the AuthRoot Auto Update CTL. // // Leaves the engine's critical section to URL retrieve and // validate the CTL. Also leaves critical section to // add the cert to the AuthRoot store via crypt32 service. // If the engine was touched by another thread, // it fails with LastError set to ERROR_CAN_NOT_COMPLETE. // // Assumption: Chain engine is locked once in the calling thread. // // Only returns FALSE, if the engine was touched when // leaving the critical section. // //---------------------------------------------------------------------------- BOOL WINAPI ChainGetAuthRootAutoUpdateStatus ( IN PCCHAINCALLCONTEXT pCallContext, IN PCCERTOBJECT pCertObject, IN OUT DWORD *pdwIssuerStatusFlags ) { BOOL fTouchedResult = TRUE; BOOL fResult; PCCERTCHAINENGINE pChainEngine = pCallContext->ChainEngine(); PCCERT_CONTEXT pCert = pCertObject->CertContext(); PCCTL_CONTEXT pCtl = NULL; PCTL_ENTRY pCtlEntry; PCERT_BASIC_CONSTRAINTS2_INFO pBasicConstraintsInfo; DWORD i; DWORD cbData; BYTE rgbSha1Hash[SHA1_HASH_LEN]; // Check if the root has an end entity basic constraint. These can't // be used for roots. pBasicConstraintsInfo = pCertObject->BasicConstraintsInfo(); if (pBasicConstraintsInfo && !pBasicConstraintsInfo->fCA) return TRUE; // Check if a known invalid root, such as, expired timestamp // root or the "Root Agency" test root. Don't want all clients in the // world hiting the wire for these guys. cbData = SHA1_HASH_LEN; if (!CertGetCertificateContextProperty( pCert, CERT_SHA1_HASH_PROP_ID, rgbSha1Hash, &cbData ) || SHA1_HASH_LEN != cbData) goto GetSha1HashPropertyError; for (i = 0; i < AUTH_ROOT_INVALID_LIST_CNT; i++) { if (0 == memcmp(AuthRootInvalidList[i], rgbSha1Hash, SHA1_HASH_LEN)) return TRUE; } // Check if this certificate has an associated private key. Such // certificates are generated by EFS. cbData = 0; if (CertGetCertificateContextProperty( pCert, CERT_KEY_PROV_INFO_PROP_ID, NULL, // pbData &cbData) && 0 < cbData) return TRUE; fTouchedResult = pChainEngine->GetAuthRootAutoUpdateCtl( pCallContext, &pCtl ); if (!fTouchedResult || NULL == pCtl) { #if 0 // This logs too many test failures if (fTouchedResult) { PAUTH_ROOT_AUTO_UPDATE_INFO pInfo = pChainEngine->AuthRootAutoUpdateInfo(); if (NULL == pInfo || !(pInfo->dwFlags & CERT_AUTH_ROOT_AUTO_UPDATE_DISABLE_UNTRUSTED_ROOT_LOGGING_FLAG)) IPR_LogCertInformation( MSG_UNTRUSTED_ROOT_INFORMATIONAL, pCert, FALSE // fFormatIssuerName ); } #endif return fTouchedResult; } if (NULL == (pCtlEntry = CertFindSubjectInCTL( pCert->dwCertEncodingType, CTL_CERT_SUBJECT_TYPE, (void *) pCert, pCtl, 0 // dwFlags ))) { #if 0 // This logs too many test failures PAUTH_ROOT_AUTO_UPDATE_INFO pInfo = pChainEngine->AuthRootAutoUpdateInfo(); if (NULL == pInfo || !(pInfo->dwFlags & CERT_AUTH_ROOT_AUTO_UPDATE_DISABLE_UNTRUSTED_ROOT_LOGGING_FLAG)) IPR_LogCertInformation( MSG_UNTRUSTED_ROOT_INFORMATIONAL, pCert, FALSE // fFormatIssuerName ); #endif goto CommonReturn; } // Check if a remove entry if (CertFindAttribute( szOID_REMOVE_CERTIFICATE, pCtlEntry->cAttribute, pCtlEntry->rgAttribute )) goto CommonReturn; pChainEngine->UnlockEngine(); fResult = IPR_AddCertInAuthRootAutoUpdateCtl(pCert, pCtl); pChainEngine->LockEngine(); if (pCallContext->IsTouchedEngine()) { fTouchedResult = FALSE; goto TouchedDuringAddAuthRootInCtl; } if (fResult && CertSetCertificateContextPropertiesFromCTLEntry( pCert, pCtlEntry, CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG )) *pdwIssuerStatusFlags |= CERT_ISSUER_TRUSTED_ROOT_FLAG; CommonReturn: if (pCtl) CertFreeCTLContext(pCtl); return fTouchedResult; ErrorReturn: goto CommonReturn; TRACE_ERROR(GetSha1HashPropertyError) SET_ERROR(TouchedDuringAddAuthRootInCtl, ERROR_CAN_NOT_COMPLETE) }