You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
11349 lines
368 KiB
11349 lines
368 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows NT Security
|
|
// Copyright (C) Microsoft Corporation, 1997 - 1999
|
|
//
|
|
// File: chain.cpp
|
|
//
|
|
// Contents: Certificate Chaining Infrastructure
|
|
//
|
|
// History: 15-Jan-98 kirtd Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
#include <global.hxx>
|
|
#include <dbgdef.h>
|
|
|
|
|
|
//+===========================================================================
|
|
// 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_dwPass1DuplicateKeyDepth = 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;
|
|
|
|
if (0 == (pCallContext->CallFlags() &
|
|
CERT_CHAIN_DISABLE_PASS1_QUALITY_FILTERING))
|
|
m_dwPass1Quality |= CERT_QUALITY_NO_DUPLICATE_KEY;
|
|
|
|
// 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;
|
|
|
|
if (0 == (pCallContext->CallFlags() &
|
|
CERT_CHAIN_DISABLE_PASS1_QUALITY_FILTERING) &&
|
|
m_pIssuerList->IsEmpty())
|
|
m_dwPass1Quality |= CERT_QUALITY_NO_DUPLICATE_KEY;
|
|
}
|
|
} else {
|
|
DWORD iLast;
|
|
BOOL fGetIssuerUrlStore;
|
|
|
|
if (!FindAndAddIssuers (
|
|
pCallContext,
|
|
hAdditionalStore,
|
|
NULL // hIssuerUrlStore
|
|
))
|
|
goto FindAndAddIssuersError;
|
|
|
|
dwIssuerStatusFlags = pCertObject->IssuerStatusFlags();
|
|
iLast = 1; // Default to allow AIA wire
|
|
fGetIssuerUrlStore = FALSE;
|
|
|
|
if (m_pIssuerList->IsEmpty())
|
|
fGetIssuerUrlStore = TRUE;
|
|
else if (!(dwIssuerStatusFlags & CERT_ISSUER_VALID_SIGNATURE_FLAG) ||
|
|
!(m_dwPass1Quality & CERT_QUALITY_SIGNATURE_VALID)) {
|
|
fGetIssuerUrlStore = TRUE;
|
|
|
|
if (dwIssuerStatusFlags & CERT_ISSUER_URL_FLAG)
|
|
iLast = 0; // Only do AIA cache
|
|
}
|
|
|
|
if (fGetIssuerUrlStore) {
|
|
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 <= iLast; 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;
|
|
|
|
if (0 == (pCallContext->CallFlags() &
|
|
CERT_CHAIN_DISABLE_PASS1_QUALITY_FILTERING))
|
|
m_dwPass1Quality |= CERT_QUALITY_NO_DUPLICATE_KEY;
|
|
}
|
|
}
|
|
|
|
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.
|
|
// If CERT_QUALITY_NO_DUPLICATE_KEY is set in the
|
|
// Pass1Quality, advances past paths containing duplicate keys.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
PCCHAINPATHOBJECT
|
|
CChainPathObject::NextPath (
|
|
IN PCCHAINCALLCONTEXT pCallContext,
|
|
IN OPTIONAL PCCHAINPATHOBJECT pPrevTopPathObject
|
|
)
|
|
{
|
|
PCCHAINPATHOBJECT pTopPathObject = pPrevTopPathObject;
|
|
|
|
while (pTopPathObject = NextPathWithoutDuplicateKeyCheck(
|
|
pCallContext,
|
|
pTopPathObject
|
|
)) {
|
|
BOOL fDuplicateKey = FALSE;
|
|
PCCHAINPATHOBJECT pSubjectObject;
|
|
|
|
for (pSubjectObject = pTopPathObject->m_pDownPathObject;
|
|
NULL != pSubjectObject;
|
|
pSubjectObject = pSubjectObject->m_pDownPathObject) {
|
|
if (0 == (pSubjectObject->m_dwPass1Quality &
|
|
CERT_QUALITY_NO_DUPLICATE_KEY))
|
|
break;
|
|
|
|
LPBYTE pbSubjectPublicKeyHash =
|
|
pSubjectObject->m_pCertObject->PublicKeyHash();
|
|
PCERT_ISSUER_ELEMENT pIssuerElement;
|
|
PCCHAINPATHOBJECT pIssuerObject;
|
|
|
|
for (pIssuerElement = pSubjectObject->m_pUpIssuerElement;
|
|
NULL != pIssuerElement &&
|
|
NULL != (pIssuerObject = pIssuerElement->pIssuer);
|
|
pIssuerElement = pIssuerObject->m_pUpIssuerElement) {
|
|
if (0 == memcmp(pbSubjectPublicKeyHash,
|
|
pIssuerObject->m_pCertObject->PublicKeyHash(),
|
|
CHAINHASHLEN)) {
|
|
fDuplicateKey = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fDuplicateKey)
|
|
break;
|
|
|
|
}
|
|
|
|
if (!fDuplicateKey)
|
|
break;
|
|
}
|
|
|
|
return pTopPathObject;
|
|
}
|
|
|
|
VOID
|
|
CChainPathObject::ResetNextPath (
|
|
IN PCCHAINCALLCONTEXT pCallContext,
|
|
IN OPTIONAL PCCHAINPATHOBJECT pTopPathObject
|
|
)
|
|
{
|
|
while (pTopPathObject) {
|
|
PCERT_ISSUER_ELEMENT pSubjectIssuerElement =
|
|
pTopPathObject->m_pDownIssuerElement;
|
|
PCCHAINPATHOBJECT pSubjectPathObject =
|
|
pTopPathObject->m_pDownPathObject;
|
|
|
|
pTopPathObject->m_pDownPathObject = NULL;
|
|
pTopPathObject->m_fHasAdditionalStatus = FALSE;
|
|
pTopPathObject->m_pDownIssuerElement = NULL;
|
|
|
|
if (pSubjectIssuerElement &&
|
|
pSubjectIssuerElement->pCyclicSaveIssuer) {
|
|
|
|
// Remove and delete the cyclic path object
|
|
ChainDeleteCyclicPathObject(
|
|
pCallContext,
|
|
pSubjectIssuerElement->pIssuer
|
|
);
|
|
|
|
// Restore the issuer replaced by the cyclic issuer
|
|
pSubjectIssuerElement->pIssuer =
|
|
pSubjectIssuerElement->pCyclicSaveIssuer;
|
|
pSubjectIssuerElement->pCyclicSaveIssuer = NULL;
|
|
}
|
|
|
|
|
|
pTopPathObject = pSubjectPathObject;
|
|
}
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Member: CChainPathObject::NextPathWithoutDuplicateKeyCheck, public
|
|
//
|
|
// Synopsis: Get the next top path object for this end path object
|
|
// without checking for duplicate keys.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
PCCHAINPATHOBJECT
|
|
CChainPathObject::NextPathWithoutDuplicateKeyCheck (
|
|
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) {
|
|
|
|
// Remove and delete the cyclic path object
|
|
ChainDeleteCyclicPathObject(
|
|
pCallContext,
|
|
pSubjectIssuerElement->pIssuer
|
|
);
|
|
|
|
// 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) &&
|
|
(pSubjectIssuerElement->dwPass1DuplicateKeyDepth <=
|
|
pSubjectPathObject->m_dwPass1DuplicateKeyDepth))) {
|
|
// For a CTL, check that we have an issuer
|
|
if (NULL != pSubjectIssuerElement->pIssuer)
|
|
break;
|
|
else {
|
|
assert(pSubjectIssuerElement->fCtlIssuer);
|
|
}
|
|
}
|
|
}
|
|
|
|
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) &&
|
|
(pSubjectIssuerElement->dwPass1DuplicateKeyDepth <=
|
|
pSubjectPathObject->m_dwPass1DuplicateKeyDepth))) {
|
|
// 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;
|
|
PCCHAINPATHOBJECT pIssuerObject;
|
|
|
|
if (NULL == pConstraints)
|
|
continue;
|
|
|
|
dwRequireSkipCerts = pConstraints->dwRequireExplicitPolicySkipCerts;
|
|
dwInhibitSkipCerts = pConstraints->dwInhibitPolicyMappingSkipCerts;
|
|
for (pIssuerObject = this, pPathObject = m_pDownPathObject;
|
|
NULL != pPathObject &&
|
|
pPathObject->m_dwChainIndex == m_dwChainIndex;
|
|
pIssuerObject = pPathObject,
|
|
pPathObject = pPathObject->m_pDownPathObject) {
|
|
PCHAIN_POLICIES_INFO pSubjectPoliciesInfo;
|
|
|
|
if (ChainIsKeyRolloverSubject(pIssuerObject, pPathObject))
|
|
continue;
|
|
|
|
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) {
|
|
if (0 != (m_TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) ||
|
|
CertObject()->ChainEngine()->DisableMandatoryBasicConstraints())
|
|
return;
|
|
|
|
m_AdditionalStatus.dwErrorStatus |=
|
|
CERT_TRUST_INVALID_BASIC_CONSTRAINTS;
|
|
goto BasicConstraintsError;
|
|
}
|
|
|
|
if (!pInfo->fCA) {
|
|
m_AdditionalStatus.dwErrorStatus |=
|
|
CERT_TRUST_INVALID_BASIC_CONSTRAINTS;
|
|
goto BasicConstraintsError;
|
|
}
|
|
|
|
if (pInfo->fPathLenConstraint &&
|
|
m_dwElementIndex > pInfo->dwPathLenConstraint + 1) {
|
|
|
|
DWORD dwElementIndex;
|
|
PCCHAINPATHOBJECT pIssuer;
|
|
PCCHAINPATHOBJECT pSubject;
|
|
|
|
// Remove any key rollover entries
|
|
for (pIssuer = this,
|
|
pSubject = m_pDownPathObject,
|
|
dwElementIndex = m_dwElementIndex;
|
|
NULL != pSubject &&
|
|
pSubject->m_dwChainIndex == m_dwChainIndex;
|
|
pIssuer = pSubject,
|
|
pSubject = pSubject->m_pDownPathObject) {
|
|
if (ChainIsKeyRolloverSubject(pIssuer, pSubject))
|
|
dwElementIndex--;
|
|
}
|
|
|
|
if (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:
|
|
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;
|
|
if (IsValidCertQualityForRevocationCheck(m_dwPass1Quality))
|
|
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;
|
|
|
|
if (IsValidCertQualityForRevocationCheck(
|
|
m_pDownIssuerElement->dwPass1Quality)) {
|
|
BOOL fRevokedIssuer = FALSE;
|
|
PCCHAINPATHOBJECT pIssuerObject = this;
|
|
|
|
while (TRUE) {
|
|
PCERT_ISSUER_ELEMENT pIssuerElement =
|
|
pIssuerObject->m_pUpIssuerElement;
|
|
|
|
if (NULL == pIssuerElement) {
|
|
if (pIssuerObject->m_TrustStatus.dwErrorStatus &
|
|
CERT_TRUST_IS_REVOKED)
|
|
fRevokedIssuer = TRUE;
|
|
break;
|
|
} else {
|
|
if (pIssuerElement->SubjectStatus.dwErrorStatus &
|
|
CERT_TRUST_IS_REVOKED) {
|
|
fRevokedIssuer = TRUE;
|
|
break;
|
|
}
|
|
pIssuerObject = pIssuerElement->pIssuer;
|
|
}
|
|
}
|
|
|
|
if (!fRevokedIssuer)
|
|
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) {
|
|
DWORD dwElementIndex;
|
|
PCCHAINPATHOBJECT pIssuer;
|
|
PCCHAINPATHOBJECT pSubject;
|
|
|
|
// Remove any key rollover entries
|
|
for (pIssuer = this,
|
|
pSubject = m_pDownPathObject,
|
|
dwElementIndex = m_dwElementIndex;
|
|
NULL != pSubject &&
|
|
pSubject->m_dwChainIndex == m_dwChainIndex;
|
|
pIssuer = pSubject,
|
|
pSubject = pSubject->m_pDownPathObject) {
|
|
if (ChainIsKeyRolloverSubject(pIssuer, pSubject))
|
|
dwElementIndex--;
|
|
}
|
|
if (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;
|
|
CERT_REVOCATION_INFO DisallowedRevocationInfo;
|
|
|
|
ChainOrInStatusBits(&pElement->TrustStatus, &m_TrustStatus);
|
|
assert(m_fHasAdditionalStatus);
|
|
ChainOrInStatusBits(&pElement->TrustStatus, &m_AdditionalStatus);
|
|
|
|
if ((pElement->TrustStatus.dwErrorStatus &
|
|
CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT)
|
|
&&
|
|
(pChain->TrustStatus.dwInfoStatus &
|
|
CERT_TRUST_HAS_VALID_NAME_CONSTRAINTS)) {
|
|
// If one of our parents has a valid name constraint, then,
|
|
// it isn't mandatory that we have a constraint for all name spaces.
|
|
pElement->TrustStatus.dwErrorStatus &=
|
|
~CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT;
|
|
}
|
|
|
|
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 (0 == m_dwElementIndex && 0 == m_dwChainIndex &&
|
|
!(pElement->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) &&
|
|
NULL != m_pCertObject->ChainEngine()->DisallowedStore()) {
|
|
// Check if the end certificate has been explicitly disallowed.
|
|
// Since the signature component can be altered, must use the signature
|
|
// hash
|
|
|
|
BYTE rgbSigHash[CHAIN_MAX_SIG_HASH_LEN];
|
|
CRYPT_DATA_BLOB SigHashBlob;
|
|
SigHashBlob.pbData = rgbSigHash;
|
|
SigHashBlob.cbData = CHAIN_MAX_SIG_HASH_LEN;
|
|
|
|
if (CertGetCertificateContextProperty(
|
|
m_pCertObject->CertContext(),
|
|
CERT_SIGNATURE_HASH_PROP_ID,
|
|
rgbSigHash,
|
|
&SigHashBlob.cbData
|
|
) && CHAIN_MIN_SIG_HASH_LEN <= SigHashBlob.cbData) {
|
|
PCCERT_CONTEXT pDisallowedCert;
|
|
|
|
pDisallowedCert = CertFindCertificateInStore(
|
|
m_pCertObject->ChainEngine()->DisallowedStore(),
|
|
0, // dwCertEncodingType
|
|
0, // dwFindFlags
|
|
CERT_FIND_SIGNATURE_HASH,
|
|
(const void *) &SigHashBlob,
|
|
NULL //pPrevCertContext
|
|
);
|
|
|
|
if (pDisallowedCert) {
|
|
CertFreeCertificateContext(pDisallowedCert);
|
|
|
|
memset(&DisallowedRevocationInfo, 0,
|
|
sizeof(DisallowedRevocationInfo));
|
|
DisallowedRevocationInfo.cbSize =
|
|
sizeof(DisallowedRevocationInfo);
|
|
DisallowedRevocationInfo.dwRevocationResult =
|
|
(DWORD) CRYPT_E_REVOKED;
|
|
DisallowedRevocationInfo.fHasFreshnessTime = TRUE;
|
|
// DisallowedRevocationInfo.dwFreshnessTime = 0;
|
|
|
|
pRevocationInfo = &DisallowedRevocationInfo;
|
|
pRevocationCrlInfo = NULL;
|
|
|
|
pElement->TrustStatus.dwErrorStatus |= CERT_TRUST_IS_REVOKED;
|
|
pElement->TrustStatus.dwErrorStatus &=
|
|
~(CERT_TRUST_REVOCATION_STATUS_UNKNOWN |
|
|
CERT_TRUST_IS_OFFLINE_REVOCATION);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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 &&
|
|
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;
|
|
}
|
|
|
|
if (0 == (pElement->dwPass1Quality &
|
|
CERT_QUALITY_NO_DUPLICATE_KEY)) {
|
|
// Add to the duplicate key depth
|
|
pElement->dwPass1DuplicateKeyDepth =
|
|
pElement->pIssuer->Pass1DuplicateKeyDepth() + 1;
|
|
} else {
|
|
// Check that we have a no duplicate key path to the top
|
|
|
|
PCCHAINPATHOBJECT pTopPathObject = NULL;
|
|
LPBYTE pbPathPublicKeyHash =
|
|
m_pSubject->CertObject()->PublicKeyHash();
|
|
|
|
while (pTopPathObject = pElement->pIssuer->NextPath(
|
|
pCallContext,
|
|
pTopPathObject
|
|
)) {
|
|
BOOL fDuplicateKey = FALSE;
|
|
|
|
PCERT_ISSUER_ELEMENT pIssuerElement;
|
|
PCCHAINPATHOBJECT pIssuerObject;
|
|
|
|
for (pIssuerElement = pElement;
|
|
NULL != pIssuerElement &&
|
|
NULL != (pIssuerObject = pIssuerElement->pIssuer);
|
|
pIssuerElement =
|
|
pIssuerObject->UpIssuerElement()) {
|
|
|
|
assert(0 != (pIssuerElement->dwPass1Quality &
|
|
CERT_QUALITY_NO_DUPLICATE_KEY));
|
|
|
|
if (0 == memcmp(pbPathPublicKeyHash,
|
|
pIssuerObject->CertObject()->PublicKeyHash(),
|
|
CHAINHASHLEN)) {
|
|
fDuplicateKey = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!fDuplicateKey)
|
|
break;
|
|
}
|
|
|
|
if (pTopPathObject)
|
|
pElement->pIssuer->ResetNextPath(
|
|
pCallContext,
|
|
pTopPathObject
|
|
);
|
|
else {
|
|
pElement->dwPass1Quality &= ~CERT_QUALITY_NO_DUPLICATE_KEY;
|
|
// Start the duplicate key depth count
|
|
pElement->dwPass1DuplicateKeyDepth = 1;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
assert(fCtlIssuer);
|
|
pElement->dwPass1Quality = 0;
|
|
}
|
|
|
|
// Remember highest quality issuer and lowest duplicate key depth
|
|
if (pElement->dwPass1Quality > m_pSubject->Pass1Quality()) {
|
|
m_pSubject->SetPass1Quality(pElement->dwPass1Quality);
|
|
m_pSubject->SetPass1DuplicateKeyDepth(
|
|
pElement->dwPass1DuplicateKeyDepth);
|
|
} else if (pElement->dwPass1Quality == m_pSubject->Pass1Quality()) {
|
|
if (IsEmpty() || pElement->dwPass1DuplicateKeyDepth <
|
|
m_pSubject->Pass1DuplicateKeyDepth()) {
|
|
m_pSubject->SetPass1DuplicateKeyDepth(
|
|
pElement->dwPass1DuplicateKeyDepth);
|
|
}
|
|
}
|
|
|
|
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;
|
|
HKEY hConfigKey = NULL;
|
|
|
|
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_hDisallowedStore = 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;
|
|
|
|
m_Config.fDisableMandatoryBasicConstraints = FALSE;
|
|
|
|
m_Config.fDisableAIAUrlRetrieval = FALSE;
|
|
m_Config.dwMaxAIAUrlCountInCert =
|
|
CERT_CHAIN_MAX_AIA_URL_COUNT_IN_CERT_DEFAULT;
|
|
m_Config.dwMaxAIAUrlRetrievalCountPerChain =
|
|
CERT_CHAIN_MAX_AIA_URL_RETRIEVAL_COUNT_PER_CHAIN_DEFAULT;
|
|
m_Config.dwMaxAIAUrlRetrievalByteCount =
|
|
CERT_CHAIN_MAX_AIA_URL_RETRIEVAL_BYTE_COUNT_DEFAULT;
|
|
m_Config.dwMaxAIAUrlRetrievalCertCount =
|
|
CERT_CHAIN_MAX_AIA_URL_RETRIEVAL_CERT_COUNT_DEFAULT;
|
|
|
|
if (ERROR_SUCCESS == RegOpenKeyExU(
|
|
HKEY_LOCAL_MACHINE,
|
|
CERT_CHAIN_CONFIG_REGPATH,
|
|
0, // dwReserved
|
|
KEY_READ,
|
|
&hConfigKey
|
|
)) {
|
|
DWORD dwValue;
|
|
|
|
ILS_ReadDWORDValueFromRegistry(
|
|
hConfigKey,
|
|
L"DisableMandatoryBasicConstraints",
|
|
&dwValue
|
|
);
|
|
if (0 != dwValue)
|
|
m_Config.fDisableMandatoryBasicConstraints = TRUE;
|
|
|
|
ILS_ReadDWORDValueFromRegistry(
|
|
hConfigKey,
|
|
CERT_CHAIN_DISABLE_AIA_URL_RETRIEVAL_VALUE_NAME,
|
|
&dwValue
|
|
);
|
|
if (0 != dwValue)
|
|
m_Config.fDisableAIAUrlRetrieval = TRUE;
|
|
|
|
ILS_ReadDWORDValueFromRegistry(
|
|
hConfigKey,
|
|
CERT_CHAIN_MAX_AIA_URL_COUNT_IN_CERT_VALUE_NAME,
|
|
&dwValue
|
|
);
|
|
if (0 != dwValue)
|
|
m_Config.dwMaxAIAUrlCountInCert = dwValue;
|
|
|
|
ILS_ReadDWORDValueFromRegistry(
|
|
hConfigKey,
|
|
CERT_CHAIN_MAX_AIA_URL_RETRIEVAL_COUNT_PER_CHAIN_VALUE_NAME,
|
|
&dwValue
|
|
);
|
|
if (0 != dwValue)
|
|
m_Config.dwMaxAIAUrlRetrievalCountPerChain = dwValue;
|
|
|
|
ILS_ReadDWORDValueFromRegistry(
|
|
hConfigKey,
|
|
CERT_CHAIN_MAX_AIA_URL_RETRIEVAL_BYTE_COUNT_VALUE_NAME,
|
|
&dwValue
|
|
);
|
|
if (0 != dwValue)
|
|
m_Config.dwMaxAIAUrlRetrievalByteCount = dwValue;
|
|
|
|
ILS_ReadDWORDValueFromRegistry(
|
|
hConfigKey,
|
|
CERT_CHAIN_MAX_AIA_URL_RETRIEVAL_CERT_COUNT_VALUE_NAME,
|
|
&dwValue
|
|
);
|
|
if (0 != dwValue)
|
|
m_Config.dwMaxAIAUrlRetrievalCertCount = dwValue;
|
|
|
|
ILS_CloseRegistryKey(hConfigKey);
|
|
}
|
|
|
|
|
|
if ( !Pki_InitializeCriticalSection( &m_Lock ))
|
|
{
|
|
m_fInitializedLock = FALSE;
|
|
rfResult = FALSE;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
m_fInitializedLock = TRUE;
|
|
}
|
|
|
|
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"
|
|
);
|
|
|
|
m_hDisallowedStore = CertOpenStore(
|
|
CERT_STORE_PROV_SYSTEM_W,
|
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
|
NULL,
|
|
dwStoreFlags |
|
|
CERT_STORE_MAXIMUM_ALLOWED_FLAG,
|
|
L"disallowed"
|
|
);
|
|
|
|
if ( m_hDisallowedStore != NULL )
|
|
{
|
|
CertControlStore(
|
|
m_hDisallowedStore,
|
|
0, // dwFlags
|
|
CERT_STORE_CTRL_AUTO_RESYNC,
|
|
NULL // pvCtrlPara
|
|
);
|
|
}
|
|
|
|
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,
|
|
TRUE, // fOnlyLMSystemStore
|
|
&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_hDisallowedStore != NULL )
|
|
{
|
|
CertCloseStore( m_hDisallowedStore, 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 );
|
|
}
|
|
|
|
if ( m_fInitializedLock )
|
|
{
|
|
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;
|
|
PCERT_SIMPLE_CHAIN pChain;
|
|
DWORD cEle;
|
|
PCERT_CHAIN_ELEMENT *ppEle;
|
|
|
|
#define RETRY_AIA_ERROR_STATUS \
|
|
(CERT_TRUST_IS_REVOKED | \
|
|
CERT_TRUST_IS_NOT_TIME_VALID | \
|
|
CERT_TRUST_IS_UNTRUSTED_ROOT)
|
|
|
|
#define DISABLE_AIA_ADD_CERT_STATUS \
|
|
(CERT_TRUST_IS_NOT_TIME_VALID | \
|
|
CERT_TRUST_IS_REVOKED | \
|
|
CERT_TRUST_IS_NOT_SIGNATURE_VALID | \
|
|
CERT_TRUST_IS_UNTRUSTED_ROOT | \
|
|
CERT_TRUST_IS_CYCLIC | \
|
|
CERT_TRUST_IS_PARTIAL_CHAIN | \
|
|
CERT_TRUST_CTL_IS_NOT_TIME_VALID | \
|
|
CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID)
|
|
|
|
if (!CallContextCreateCallObject(
|
|
this,
|
|
pTime,
|
|
pChainPara,
|
|
dwFlags,
|
|
&pCallContext
|
|
))
|
|
goto CallContextCreateCallObjectError;
|
|
|
|
if (!CreateChainContextFromPathGraph(
|
|
pCallContext,
|
|
pCertContext,
|
|
hAdditionalStore,
|
|
&pChainContext
|
|
))
|
|
goto CreateChainContextFromPathGraphError;
|
|
|
|
if (0 == (pChainContext->TrustStatus.dwErrorStatus &
|
|
RETRY_AIA_ERROR_STATUS))
|
|
goto SuccessReturn;
|
|
|
|
|
|
pChain = pChainContext->rgpChain[0];
|
|
cEle = pChain->cElement;
|
|
ppEle = pChain->rgpElement;
|
|
|
|
// If the end certificate is time invalid, revoked or the untrusted root,
|
|
// then, no need to retry via AIA retrieval.
|
|
if (ppEle[0]->TrustStatus.dwErrorStatus & RETRY_AIA_ERROR_STATUS)
|
|
goto SuccessReturn;
|
|
|
|
if (!pCallContext->IsOnline())
|
|
goto SuccessReturn;
|
|
|
|
|
|
{
|
|
HCERTSTORE hNewerIssuerUrlStore = NULL;
|
|
|
|
if (CERT_TRUST_IS_UNTRUSTED_ROOT ==
|
|
(pChainContext->TrustStatus.dwErrorStatus &
|
|
RETRY_AIA_ERROR_STATUS)) {
|
|
// For a potential key rollover root attempt to retrieve
|
|
// the key rollover cross cert using the subject's AIA.
|
|
|
|
if (2 <= cEle &&
|
|
IsPotentialKeyRolloverRoot(ppEle[cEle - 1]->pCertContext)) {
|
|
hNewerIssuerUrlStore = GetNewerIssuerUrlStore(
|
|
pCallContext,
|
|
ppEle[cEle - 2]->pCertContext, // Subject
|
|
ppEle[cEle - 1]->pCertContext // Root, Issuer
|
|
);
|
|
}
|
|
} else {
|
|
// Try to retrieve a newer CA cert via the subject's AIA extension.
|
|
//
|
|
// Note, will only try for the first revoked or time
|
|
// invalid CA cert in the first simple chain.
|
|
|
|
DWORD i;
|
|
|
|
for (i = 1; i < cEle; i++) {
|
|
PCERT_CHAIN_ELEMENT pIssuerEle = ppEle[i];
|
|
|
|
if (pIssuerEle->TrustStatus.dwErrorStatus &
|
|
RETRY_AIA_ERROR_STATUS) {
|
|
// First Revoked or Time Invalid 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);
|
|
}
|
|
}
|
|
|
|
|
|
SuccessReturn:
|
|
if (0 < pCallContext->AIAUrlRetrievalCount() &&
|
|
0 == (pChainContext->TrustStatus.dwErrorStatus &
|
|
DISABLE_AIA_ADD_CERT_STATUS) &&
|
|
NULL != CAStore()) {
|
|
|
|
DWORD i;
|
|
|
|
// Add any AIA retrieved CA certificates to the CA store
|
|
pChain = pChainContext->rgpChain[0];
|
|
cEle = pChain->cElement;
|
|
ppEle = pChain->rgpElement;
|
|
|
|
// Ignore end entity and self signed root certificates
|
|
for (i = 1; i < cEle - 1; i++) {
|
|
PCCERT_CONTEXT pAIACert = ppEle[i]->pCertContext;
|
|
DWORD cbData;
|
|
|
|
if (CertGetCertificateContextProperty(
|
|
pAIACert,
|
|
CERT_AIA_URL_RETRIEVED_PROP_ID,
|
|
NULL,
|
|
&cbData
|
|
)) {
|
|
|
|
// Delete the property
|
|
CertSetCertificateContextProperty(
|
|
pAIACert,
|
|
CERT_AIA_URL_RETRIEVED_PROP_ID,
|
|
CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG,
|
|
NULL
|
|
);
|
|
|
|
CertAddCertificateContextToStore(
|
|
CAStore(),
|
|
pAIACert,
|
|
CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES,
|
|
NULL
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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 fDupPublicKeyOrName = 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 or names. This could happen if we have 2 sets of
|
|
// cross certificates or root rollever certs
|
|
|
|
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 and names up to
|
|
// and including the root
|
|
for (i = 1; i + 1 < cLongElement; i++) {
|
|
DWORD j;
|
|
DWORD cbHash;
|
|
BYTE rgbHash0[ CHAINHASHLEN ];
|
|
PCCERT_CONTEXT pCert0 =
|
|
pLongChain->rgpElement[i]->pCertContext;
|
|
|
|
cbHash = CHAINHASHLEN;
|
|
if (!CertGetCertificateContextProperty(
|
|
pCert0,
|
|
CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID,
|
|
rgbHash0,
|
|
&cbHash
|
|
) || CHAINHASHLEN != cbHash)
|
|
break;
|
|
|
|
for (j = i + 1; j < cLongElement; j++) {
|
|
BYTE rgbHash1[ CHAINHASHLEN ];
|
|
PCCERT_CONTEXT pCert1 =
|
|
pLongChain->rgpElement[j]->pCertContext;
|
|
|
|
cbHash = CHAINHASHLEN;
|
|
if (!CertGetCertificateContextProperty(
|
|
pCert1,
|
|
CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID,
|
|
rgbHash1,
|
|
&cbHash
|
|
) || CHAINHASHLEN != cbHash)
|
|
break;
|
|
|
|
if (0 == memcmp(rgbHash0, rgbHash1, CHAINHASHLEN)
|
|
||
|
|
CertCompareCertificateName(
|
|
pCert0->dwCertEncodingType,
|
|
&pCert0->pCertInfo->Subject,
|
|
&pCert1->pCertInfo->Subject)) {
|
|
fDupPublicKeyOrName = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fDupPublicKeyOrName)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fDupPublicKeyOrName) {
|
|
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::IsPotentialKeyRolloverRoot, public
|
|
//
|
|
// Synopsis: checks if the root certificate is a potential key rollover
|
|
// candidate by trying to find a matching cross certificate
|
|
// having the same subject name with a different key. Returns TRUE
|
|
// if such a matching cross certificate is found in the engine's
|
|
// OtherStore (really WorldStore).
|
|
//
|
|
// NOTE: This method acquires the engine lock
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL
|
|
CCertChainEngine::IsPotentialKeyRolloverRoot (
|
|
IN PCCERT_CONTEXT pRootCertContext
|
|
)
|
|
{
|
|
BOOL fPotentialRollover = FALSE;
|
|
PCCERT_CONTEXT pCert = NULL;
|
|
|
|
LockEngine();
|
|
|
|
while (pCert = CertFindCertificateInStore(
|
|
m_hOtherStore,
|
|
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
|
0, // dwFindFlags
|
|
CERT_FIND_SUBJECT_NAME,
|
|
(const void *) &pRootCertContext->pCertInfo->Subject,
|
|
pCert
|
|
)) {
|
|
PCRYPT_BIT_BLOB pRootKey =
|
|
&pRootCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey;
|
|
PCRYPT_BIT_BLOB pCertKey =
|
|
&pCert->pCertInfo->SubjectPublicKeyInfo.PublicKey;
|
|
|
|
if (pRootKey->cbData != pCertKey->cbData ||
|
|
0 != memcmp(pRootKey->pbData, pCertKey->pbData,
|
|
pRootKey->cbData)) {
|
|
fPotentialRollover = TRUE;
|
|
CertFreeCertificateContext(pCert);
|
|
break;
|
|
}
|
|
}
|
|
|
|
UnlockEngine();
|
|
return fPotentialRollover;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// 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;
|
|
|
|
if (m_Config.fDisableAIAUrlRetrieval ||
|
|
pCallContext->AIAUrlRetrievalCount() >=
|
|
m_Config.dwMaxAIAUrlRetrievalCountPerChain)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
dwRetrievalFlags |= CRYPT_RETRIEVE_MULTIPLE_OBJECTS |
|
|
CRYPT_LDAP_SCOPE_BASE_ONLY_RETRIEVAL |
|
|
CRYPT_OFFLINE_CHECK_RETRIEVAL |
|
|
CRYPT_AIA_RETRIEVAL;
|
|
|
|
fResult = ChainGetObjectUrl(
|
|
URL_OID_CERTIFICATE_ISSUER,
|
|
(LPVOID) pSubjectCertContext,
|
|
CRYPT_GET_URL_FROM_EXTENSION,
|
|
NULL,
|
|
&cbUrlArray,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if ( fResult )
|
|
{
|
|
pUrlArray = (PCRYPT_URL_ARRAY)new BYTE [ cbUrlArray ];
|
|
if ( pUrlArray == NULL )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
fResult = ChainGetObjectUrl(
|
|
URL_OID_CERTIFICATE_ISSUER,
|
|
(LPVOID) pSubjectCertContext,
|
|
CRYPT_GET_URL_FROM_EXTENSION,
|
|
pUrlArray,
|
|
&cbUrlArray,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if ( fResult )
|
|
{
|
|
if (pUrlArray->cUrl > m_Config.dwMaxAIAUrlCountInCert)
|
|
{
|
|
ChainOutputDebugStringA("CRYPT32.DLL --> Exceeded MaxAIAUrlCountInCert\n");
|
|
fResult = FALSE;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if ( fResult )
|
|
{
|
|
BOOL fLocked = FALSE;
|
|
CRYPT_RETRIEVE_AUX_INFO RetrieveAuxInfo;
|
|
|
|
memset(&RetrieveAuxInfo, 0, sizeof(RetrieveAuxInfo));
|
|
RetrieveAuxInfo.cbSize = sizeof(RetrieveAuxInfo);
|
|
RetrieveAuxInfo.dwMaxUrlRetrievalByteCount =
|
|
m_Config.dwMaxAIAUrlRetrievalByteCount;
|
|
|
|
//
|
|
// 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++ )
|
|
{
|
|
dwCacheResultFlag = 0;
|
|
|
|
if (!(dwRetrievalFlags & CRYPT_CACHE_ONLY_RETRIEVAL)) {
|
|
if (pCallContext->AIAUrlRetrievalCount() >=
|
|
m_Config.dwMaxAIAUrlRetrievalCountPerChain)
|
|
{
|
|
ChainOutputDebugStringA("CRYPT32.DLL --> Exceeded MaxAIAUrlRetrievalCountPerChain\n");
|
|
break;
|
|
}
|
|
pCallContext->IncrementAIAUrlRetrievalCount();
|
|
|
|
if (0 != _wcsnicmp(pUrlArray->rgwszUrl[ cCount ], L"http:", 5))
|
|
{
|
|
dwCacheResultFlag = CRYPT_DONT_CACHE_RESULT;
|
|
}
|
|
} else if (0 == pCallContext->AIAUrlRetrievalCount()) {
|
|
// Need a nonzero retrieval count so we can add to the CA
|
|
// store
|
|
pCallContext->IncrementAIAUrlRetrievalCount();
|
|
}
|
|
|
|
|
|
fResult = ChainRetrieveObjectByUrlW(
|
|
pUrlArray->rgwszUrl[ cCount ],
|
|
CONTEXT_OID_CERTIFICATE,
|
|
dwRetrievalFlags | dwCacheResultFlag,
|
|
(dwRetrievalFlags & CRYPT_CACHE_ONLY_RETRIEVAL) ?
|
|
0 : pCallContext->AIAUrlRetrievalTimeout(),
|
|
(LPVOID *)phIssuerUrlStore,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&RetrieveAuxInfo
|
|
);
|
|
|
|
if ( fResult )
|
|
{
|
|
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;
|
|
|
|
|
|
if (!fTouchedResult) {
|
|
CertCloseStore(*phIssuerUrlStore, 0);
|
|
*phIssuerUrlStore = NULL;
|
|
} else {
|
|
// Check that we don't exceed the maximum allowed number
|
|
// of certificates per AIA retrieval. Also, set
|
|
// the AIA property on each certificate. If these
|
|
// certificates are used in the returned chain context,
|
|
// then, they will be added to the CA store.
|
|
|
|
CRYPT_DATA_BLOB DataBlob = {0, NULL};
|
|
PCCERT_CONTEXT pAIACert = NULL;
|
|
DWORD cAIACert = 0;
|
|
|
|
while (pAIACert = CertEnumCertificatesInStore(
|
|
*phIssuerUrlStore, pAIACert)) {
|
|
cAIACert++;
|
|
|
|
CertSetCertificateContextProperty(
|
|
pAIACert,
|
|
CERT_AIA_URL_RETRIEVED_PROP_ID,
|
|
CERT_SET_PROPERTY_INHIBIT_PERSIST_FLAG,
|
|
&DataBlob
|
|
);
|
|
}
|
|
|
|
if (cAIACert > m_Config.dwMaxAIAUrlRetrievalCertCount) {
|
|
ChainOutputDebugStringA("CRYPT32.DLL --> Exceeded MaxAIAUrlRetrievalCertCount\n");
|
|
CertCloseStore(*phIssuerUrlStore, 0);
|
|
*phIssuerUrlStore = NULL;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Retake the engine lock if necessary
|
|
//
|
|
|
|
if ( !fLocked )
|
|
{
|
|
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,
|
|
TRUE, // fOnlyLMSystemStore
|
|
&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: ChainDeleteCyclicPathObject
|
|
//
|
|
// Synopsis: delete a previously created cyclic path object.
|
|
// Also, remove the creation cache.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID WINAPI
|
|
ChainDeleteCyclicPathObject (
|
|
IN PCCHAINCALLCONTEXT pCallContext,
|
|
IN OUT PCCHAINPATHOBJECT pCyclicPathObject
|
|
)
|
|
{
|
|
pCallContext->RemovePathObjectFromCreationCache(pCyclicPathObject);
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ChainIsKeyRolloverSubject
|
|
//
|
|
// Synopsis: a subject is considered to be a key rollover cert if its
|
|
// subject name == issuer name == issuer cert's subject name
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL WINAPI
|
|
ChainIsKeyRolloverSubject(
|
|
IN PCCHAINPATHOBJECT pIssuerPathObject,
|
|
IN PCCHAINPATHOBJECT pSubjectPathObject
|
|
)
|
|
{
|
|
PCERT_INFO pIssuerInfo =
|
|
pIssuerPathObject->CertObject()->CertContext()->pCertInfo;
|
|
PCERT_INFO pSubjectInfo =
|
|
pSubjectPathObject->CertObject()->CertContext()->pCertInfo;
|
|
DWORD cbData = pSubjectInfo->Subject.cbData;
|
|
|
|
if (0 != cbData &&
|
|
cbData == pSubjectInfo->Issuer.cbData &&
|
|
cbData == pIssuerInfo->Subject.cbData
|
|
&&
|
|
0 == memcmp(pSubjectInfo->Subject.pbData,
|
|
pSubjectInfo->Issuer.pbData, cbData)
|
|
&&
|
|
0 == memcmp(pSubjectInfo->Subject.pbData,
|
|
pIssuerInfo->Subject.pbData, cbData))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//+===========================================================================
|
|
// 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: ChainIsSpecialAtCharacterMatch
|
|
//
|
|
// Synopsis: returns TRUE if the "Right" string satisfies one of the
|
|
// following conditions:
|
|
// - doesn't contain an "@" (at character)
|
|
// - "@" is the left-most character
|
|
// - "Right" string has the same number of characters as the
|
|
// string (indicates an exact case insensitive match)
|
|
//
|
|
// alternatively, returns FALSE if the "Right" string contains
|
|
// a non-leading "@" and isn't an exact case insensitive match
|
|
// of the string.
|
|
//
|
|
// Assumes that ChainIsRightStringInString() was previously
|
|
// called and returned TRUE.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL WINAPI
|
|
ChainIsSpecialAtCharacterMatch(
|
|
IN LPCWSTR pwszRight,
|
|
IN DWORD cchRight,
|
|
IN DWORD cchString
|
|
)
|
|
{
|
|
BOOL fMatch = TRUE;
|
|
|
|
if (cchString > cchRight) {
|
|
DWORD i;
|
|
|
|
for (i = 0; i < cchRight; i++) {
|
|
if (L'@' == pwszRight[i]) {
|
|
if (0 != i)
|
|
fMatch = FALSE;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return fMatch;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ChainIsEmptyOrStringEncodedValue
|
|
//
|
|
// Synopsis: Checks if the encoded value is empty (only contains
|
|
// the tag and length octets) or is a string (has string tag).
|
|
//
|
|
// Returns one of the following values:
|
|
// +1 - The encoded value is a string
|
|
// 0 - The encoded value is empty (takes precedence over being
|
|
// a string)
|
|
// -1 - The encoded value isn't a string
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
const BYTE rgbChainStringTag[] = {
|
|
0x0C, // UTF8STRING ::= UTF8String -- tag 0x0C (12)
|
|
0x1E, // BMPSTRING ::= BMPString -- tag 0x1E (30)
|
|
0x16, // IA5STRING ::= IA5String -- tag 0x16 (22)
|
|
0x13, // PRINTABLESTRING ::= PrintableString -- tag 0x13 (19)
|
|
0x1C, // UNIVERSALSTRING ::= UniversalString -- tag 0x1C (28)
|
|
0x14, // TELETEXSTRING ::= TeletexString -- tag 0x14 (20)
|
|
// 0x14, // T61STRING ::= T61String -- tag 0x14 (20)
|
|
0x12, // NUMERICSTRING ::= NumericString -- tag 0x12 (18)
|
|
0x1B, // GENERALSTRING ::= GeneralString -- tag 0x1B (27)
|
|
0x15, // VIDEOTEXSTRING ::= VideotexString -- tag 0x15 (21)
|
|
0x19, // GRAPHICSTRING ::= GraphicString -- tag 0x19 (25)
|
|
0x1A, // VISIBLESTRING ::= VisibleString -- tag 0x1A (26)
|
|
// 0x1A, // ISO646STRING ::= ISO646String -- tag 0x1A (26)
|
|
};
|
|
|
|
#define CHAIN_STRING_TAB_CNT \
|
|
(sizeof(rgbChainStringTag) / sizeof(rgbChainStringTag[0]))
|
|
|
|
int WINAPI
|
|
ChainIsEmptyOrStringEncodedValue(
|
|
IN PCRYPT_OBJID_BLOB pEncodedValue
|
|
)
|
|
{
|
|
DWORD i;
|
|
BYTE bTag;
|
|
|
|
if (CHAIN_OTHER_NAME_MAX_EMPTY_LENGTH >= pEncodedValue->cbData)
|
|
return 0;
|
|
|
|
bTag = pEncodedValue->pbData[0];
|
|
|
|
for (i = 0; i < CHAIN_STRING_TAB_CNT; i++) {
|
|
if (bTag == rgbChainStringTag[i])
|
|
return 1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: ChainFixupNameConstraintsOtherNameValue
|
|
//
|
|
// Synopsis: fixup the CERT_ALT_NAME_OTHER_NAME AltName entry choice
|
|
// for values encoded as strings 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.
|
|
//
|
|
// pOtherName->Value.cbData is set to
|
|
// CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH for a successful
|
|
// fixup.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL WINAPI
|
|
ChainFixupNameConstraintsOtherNameValue(
|
|
IN OUT PCRYPT_OBJID_BLOB pOtherValue
|
|
)
|
|
{
|
|
BOOL fResult;
|
|
PCERT_NAME_VALUE pNameValue;
|
|
LPWSTR pwsz;
|
|
DWORD cch;
|
|
|
|
if (0 >= ChainIsEmptyOrStringEncodedValue(pOtherValue))
|
|
// Empty or not a string. No Fixup.
|
|
goto SuccessReturn;
|
|
|
|
pNameValue = (PCERT_NAME_VALUE) ChainAllocAndDecodeObject(
|
|
X509_UNICODE_ANY_STRING,
|
|
pOtherValue->pbData,
|
|
pOtherValue->cbData
|
|
);
|
|
if (NULL == pNameValue)
|
|
goto DecodeError;
|
|
|
|
if (!IS_CERT_RDN_CHAR_STRING(pNameValue->dwValueType)) {
|
|
PkiFree(pNameValue);
|
|
goto InvalidStringType;
|
|
}
|
|
|
|
ChainRemoveLeadingAndTrailingWhiteSpace(
|
|
(LPWSTR) pNameValue->Value.pbData,
|
|
&pwsz,
|
|
&cch
|
|
);
|
|
|
|
pNameValue->Value.pbData = (BYTE *) pwsz;
|
|
pNameValue->Value.cbData = cch * sizeof(WCHAR);
|
|
|
|
pOtherValue->pbData = (BYTE *) pNameValue;
|
|
pOtherValue->cbData = CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH;
|
|
|
|
SuccessReturn:
|
|
fResult = TRUE;
|
|
CommonReturn:
|
|
return fResult;
|
|
|
|
ErrorReturn:
|
|
fResult = FALSE;
|
|
goto CommonReturn;
|
|
|
|
TRACE_ERROR(DecodeError)
|
|
SET_ERROR(InvalidStringType, 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 values encoded as strings, pOtherName->Value.pbData
|
|
// is updated to point to the allocated
|
|
// PCERT_NAME_VALUE containing the decoded unicode string.
|
|
// For a fixup, pOtherName->Value.cbData is set to
|
|
// CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH.
|
|
//
|
|
// 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:
|
|
fResult = ChainFixupNameConstraintsOtherNameValue(
|
|
&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 (CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH ==
|
|
pEntry->pOtherName->Value.cbData)
|
|
// 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 (CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH ==
|
|
pEntry->pOtherName->Value.cbData) {
|
|
// Restore from the following fixup:
|
|
// pEntry->pOtherName->Value.pbData :: PCERT_NAME_VALUE
|
|
if (NULL == pEntry->pOtherName->Value.pbData)
|
|
goto InvalidOtherName;
|
|
if (!ChainAllocAndEncodeObject(
|
|
X509_UNICODE_ANY_STRING,
|
|
(PCERT_NAME_VALUE) pEntry->pOtherName->Value.pbData,
|
|
&pbEncoded2,
|
|
&cbEncoded2
|
|
))
|
|
goto EncodedOtherNameError;
|
|
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(InvalidOtherName, ERROR_INVALID_DATA)
|
|
TRACE_ERROR(EncodedOtherNameError)
|
|
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: ChainCompareNameConstraintsOtherNameValue
|
|
//
|
|
// Synopsis: returns TRUE if the subtree OtherName value is empty.
|
|
// If both the subtree and subject OtherName values are strings,
|
|
// returns TRUE if the subtree OtherName string matches the
|
|
// right most characters of the subject's OtherName
|
|
// doing a case insensitive match.
|
|
//
|
|
// For the szOID_NT_PRINCIPAL_NAME (UPN) OtherName also
|
|
// does a special "@" (At character) match.
|
|
//
|
|
// For CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH == Value.cbData,
|
|
// the Value.pbData points to the decoded PCERT_NAME_VALUE.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL WINAPI
|
|
ChainCompareNameConstraintsOtherNameValue(
|
|
IN LPCSTR pszOtherNameOID,
|
|
IN PCRYPT_OBJID_BLOB pSubjectValue,
|
|
IN PCRYPT_OBJID_BLOB pSubtreeValue
|
|
)
|
|
{
|
|
BOOL fCompare;
|
|
PCERT_NAME_VALUE pSubjectNameValue;
|
|
PCERT_NAME_VALUE pSubtreeNameValue;
|
|
|
|
if (CHAIN_OTHER_NAME_MAX_EMPTY_LENGTH >= pSubtreeValue->cbData)
|
|
// Subtree has an empty value. Match any subject.
|
|
return TRUE;
|
|
|
|
if (CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH != pSubjectValue->cbData ||
|
|
CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH != pSubtreeValue->cbData)
|
|
// Only support string matching
|
|
return FALSE;
|
|
|
|
// The OtherName's Value.pbData is used to point to the decoded
|
|
// PCERT_NAME_VALUE
|
|
|
|
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)
|
|
);
|
|
|
|
if (fCompare && 0 == strcmp(pszOtherNameOID, szOID_NT_PRINCIPAL_NAME))
|
|
fCompare = ChainIsSpecialAtCharacterMatch(
|
|
(LPCWSTR) pSubtreeNameValue->Value.pbData,
|
|
pSubtreeNameValue->Value.cbData / sizeof(WCHAR),
|
|
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 empty or string OtherName Values.
|
|
if (CHAIN_OTHER_NAME_MAX_EMPTY_LENGTH <
|
|
pSubtreeEntry->pOtherName->Value.cbData
|
|
&&
|
|
CHAIN_OTHER_NAME_FIXUP_STRING_LENGTH !=
|
|
pSubtreeEntry->pOtherName->Value.cbData) {
|
|
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 if (0 == strcmp(pSubtreeEntry->pOtherName->pszObjId,
|
|
pSubjectEntry->pOtherName->pszObjId)) {
|
|
fHasSubtreeEntry = TRUE;
|
|
|
|
fCompare = ChainCompareNameConstraintsOtherNameValue(
|
|
pSubtreeEntry->pOtherName->pszObjId,
|
|
&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)
|
|
);
|
|
if (fCompare && CERT_ALT_NAME_RFC822_NAME == dwAltNameChoice)
|
|
fCompare = ChainIsSpecialAtCharacterMatch(
|
|
(LPCWSTR) pSubtreeEntry->DirectoryName.pbData,
|
|
pSubtreeEntry->DirectoryName.cbData / sizeof(WCHAR),
|
|
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 if (!IPR_IsNotDefinedNameConstraintDisabled()) {
|
|
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 );
|
|
}
|
|
|
|
|
|
//+===========================================================================
|
|
// 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: 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];
|
|
}
|
|
|
|
//+===========================================================================
|
|
// Debug helper functions
|
|
//============================================================================
|
|
DWORD
|
|
WINAPI
|
|
ChainGetDebugFlags()
|
|
{
|
|
HKEY hKey = NULL;
|
|
DWORD dwType = 0;
|
|
DWORD dwValue = 0;
|
|
DWORD cbValue = sizeof(dwValue);
|
|
|
|
DWORD dwLastErr = GetLastError();
|
|
|
|
if (ERROR_SUCCESS != RegOpenKeyExA(
|
|
HKEY_LOCAL_MACHINE,
|
|
"SYSTEM\\CurrentControlSet\\Services\\crypt32",
|
|
0, // dwReserved
|
|
KEY_READ,
|
|
&hKey
|
|
))
|
|
goto ErrorReturn;
|
|
|
|
if (ERROR_SUCCESS != RegQueryValueExA(
|
|
hKey,
|
|
"DebugFlags",
|
|
NULL, // pdwReserved
|
|
&dwType,
|
|
(BYTE *) &dwValue,
|
|
&cbValue
|
|
))
|
|
goto ErrorReturn;
|
|
|
|
if (dwType != REG_DWORD || cbValue != sizeof(dwValue))
|
|
goto ErrorReturn;
|
|
|
|
CommonReturn:
|
|
if (hKey)
|
|
RegCloseKey(hKey);
|
|
|
|
SetLastError(dwLastErr);
|
|
|
|
return dwValue;
|
|
|
|
ErrorReturn:
|
|
dwValue = 0;
|
|
goto CommonReturn;
|
|
}
|
|
|
|
VOID
|
|
WINAPI
|
|
ChainOutputDebugStringA(
|
|
LPCSTR lpOutputString
|
|
)
|
|
{
|
|
if (ChainGetDebugFlags() & 0x1) {
|
|
DWORD dwLastErr = GetLastError();
|
|
|
|
OutputDebugStringA(lpOutputString);
|
|
|
|
SetLastError(dwLastErr);
|
|
}
|
|
}
|
|
|
|
//+===========================================================================
|
|
// 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)
|
|
}
|