You have an X.509 certificate, and you want to verify its validity using Microsoft's CryptoAPI on Windows.
CryptoAPI represents an X.509 certificate using a
CERT_CONTEXT
object. Another object, referenced by a
HCERTSTORE
handle, must be created to hold the
certificates that will be required for verification, as well as any
certificate revocation lists (CRLs) that may be necessary. The actual
certificate verification is performed by calling the
CertGetIssuerCertificateFromStore(
)
function for each certificate in the
hierarchy. This function will verify the signature, certificate
validity times, and revocation status of each certificate as it
obtains the issuer for each call. The last certificate in the
hierarchy will have no issuing certificate and should be self-signed.
Call the CertGetIssuerCertificateFromStore( )
function for each certificate in the hierarchy, beginning with the
subject certificate at the end of the chain. Each time
CertGetIssuerCertificateFromStore( )
is called,
CryptoAPI will attempt to locate the issuer of the subject
certificate passed into it. If the issuer certificate is found, the
signature of the subject certificate will be verified with the public
key of the issuer certificate. In addition, time validity checks will
be performed on the subject certificate, and the subject certificate
will be compared against the issuer's CRL if it is
present in the store.
#include <windows.h> #include <wincrypt.h> BOOL SpcVerifyCert(HCERTSTORE hCertStore, PCCERT_CONTEXT pSubjectContext) { DWORD dwFlags; PCCERT_CONTEXT pIssuerContext; if (!(pSubjectContext = CertDuplicateCertificateContext(pSubjectContext))) return FALSE; do { dwFlags = CERT_STORE_REVOCATION_FLAG | CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG; pIssuerContext = CertGetIssuerCertificateFromStore(hCertStore, pSubjectContext, 0, &dwFlags); CertFreeCertificateContext(pSubjectContext); if (pIssuerContext) { pSubjectContext = pIssuerContext; if (dwFlags & CERT_STORE_NO_CRL_FLAG) dwFlags &= ~(CERT_STORE_NO_CRL_FLAG | CERT_STORE_REVOCATION_FLAG); if (dwFlags) break; } else if (GetLastError( ) = = CRYPT_E_SELF_SIGNED) return TRUE; } while (pIssuerContext); return FALSE; }
Every certificate returned by
CertGetIssuerCertificateFromStore( )
must be freed
with a call to CertFreeCertificateContext(
)
. To make things a bit simpler, a copy of the
original subject certificate is made so that the subject certificate
can always be freed after the call to
CertGetIssuerCertificateFromStore( )
. If an issuer
certificate is returned, the subject becomes the issuer for the next
iteration through the loop.
When CertGetIssuerCertificateFromStore( )
cannot
find the issuing certificate for the subject certificate in the
store, it returns NULL
. This could mean that the
end of the certificate hierarchy has been reached, in which case
GetLastError( )
will return
CRYPT_E_SELF_SIGNED
because the root certificate
in any hierarchy must always be self-signed. A
NULL
return from
CertGetIssuerCertificateFromStore( )
might also
indicate that there may be an issuer certificate for the subject
certificate, but that one wasn't present in the
certificate store; this is an error condition that results in the
verification failure of the subject certificate.
The call to CertGetIssuerCertificateFromStore( )
requires a set of flags to be passed into it that determines what
verification checks are to be performed on the subject certificate.
Upon return from the call, this set of flags is modified, leaving the
bits set for the types of verification checks that failed.
SpcVerifyCert( )
checks the set of flags after the
successful return from CertGetIssuerCertificateFromStore(
)
to see if CERT_STORE_NO_CRL_FLAG
is
set. If it is, this indicates that no CRL could be found in the store
against which the subject certificate could be compared. At this
point, the flags indicating failure as a result of there being no CRL
are cleared. If any flags remain set, this means that verification of
the subject certificate failed; the loop is terminated, and failure
is returned.
Several special certificate stores are
available for use. In addition, private stores can be created that
reside in memory, in the registry, or in a disk file. To use one of
the special certificate stores, use the CryptoAPI function
CertOpenSystemStore(
)
. This function requires a handle to a
Cryptographic Services Provider (CSP) and the name of the certificate
store to open. In the majority of cases, the CSP handle can be passed
as NULL
, in which case the default CSP will be
used. One of the names listed in Table 10-2 may be
opened for use.
Table 10-2. System certificate stores and their contents
Certificate store name |
Types of certificates in the store |
---|---|
|
Contains certificates that are owned by the current user. For each certificate in this store, the associated private key is also available. |
|
Contains CA certificates that are not self-signed root certificates. These certificates are capable of issuing certificates. |
|
Contains root CA certificates that are trusted. All of the certificates in this store should be self-signed. |
|
Contains trusted software publisher certificates. The certificates in this store are used by Microsoft's Authenticode. |
For the purposes of verification using SpcVerifyCert(
)
as presented, you'll need to create a
temporary certificate store that contains all the certificates that
will be needed to verify a subject certificate. At a minimum, the
certificate that you want to verify must be in the store, but
verification will only succeed if the only certificate in the store
is the subject certificate and is self-signed, which in the vast
majority of cases isn't all that useful.
If you do not have all the certificates and need to use certificates
from one of the system stores, a copy of the needed certificate from
the system store can be made for insertion into the temporary store
being used for verification. Otherwise, certificates in memory as
CERT_CONTEXT
objects can be added to the temporary
store, or encoded certificates residing in memory as a blob (binary
large object) can be added.
#include <windows.h> #include <wincrypt.h> static PCCERT_CONTEXT FindIssuerInSystemStore(LPCTSTR pszStoreName, PCCERT_CONTEXT pSubjectContext) { HCERTSTORE hCertStore; PCCERT_CONTEXT pIssuerContext if (!(hCertStore = CertOpenSystemStore(0, pszStoreName))) return 0; pIssuerContext = CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING, 0, CERT_FIND_ISSUER_OF, pSubjectContext, 0); CertCloseStore(hCertStore, 0); return pIssuerContext; } static LPCTSTR SpcSystemStoreList[ ] = { TEXT("MY"), TEXT("CA"), TEXT("ROOT"), TEXT("SPC"), 0 }; HCERTSTORE SpcNewStoreForCert(PCCERT_CONTEXT pSubjectContext) { LPCTSTR pszStoreName; HCERTSTORE hCertStore; PCCERT_CONTEXT pIssuerContext; /* First create an in-memory store, and add the subject certificate to it */ if (!(hCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, 0))) return 0; if (!CertAddCertificateContextToStore(hCertStore, pSubjectContext, CERT_STORE_ADD_REPLACE_EXISTING, 0)) { CertCloseStore(hCertStore, 0); return 0; } pSubjectContext = CertDuplicateCertificateContext(pSubjectContext); while (!CertCompareCertificateName(X509_ASN_ENCODING, pSubjectContext->pCertInfo->Issuer, pSubjectContext->pCertInfo->Subject)){ for (pszStoreName = SpcSystemStoreList; pszStoreName; pszStoreName++) { pIssuerContext = FindIssuerInSystemStore(pszStoreName, pSubjectContext); if (pIssuerContext) { if (!CertAddCertificateContextToStore(hCertStore, pIssuerContext, CERT_STORE_ADD_REPLACE_EXISTING, 0)) { CertFreeCertificateContext(pSubjectContext); CertFreeCertificateContext(pIssuerContext); CertCloseStore(hCertStore, 0); return 0; } CertFreeCertificateContext(pSubjectContext); pSubjectContext = pIssuerContext; break; } } if (!pszStoreName) { CertFreeCertificateContext(pSubjectContext); CertCloseStore(hCertStore, 0); return 0; } } CertFreeCertificateContext(pSubjectContext); return hCertStore; }
The SpcNewStoreForCert(
)
function creates a temporary in-memory
certificate store that can be used with SpcVerifyCert(
)
. Only a single argument is required: the
subject certificate that is, presumably, at the end of a certificate
hierarchy. The subject certificate is added to the new certificate
store, and for each issuing certificate in the hierarchy, the system
stores are searched for a copy of the certificate. If one cannot be
found, the new certificate store is destroyed and
SpcNewStoreForCert( )
returns
NULL
; otherwise, the found certificate will be
added to the new certificate store.
Once the store has been created, it can now be passed directly into
the SpcVerifyCert( )
function, along with the
subject certificate to be verified. If there are CRLs for any of the
certificates in the hierarchy, add them to the store before calling
SpcVerifyCert( )
(see Recipe 10.11 for obtaining
CRLs with CryptoAPI). You can enumerate the contents of the
certificate store created by SpcNewStoreForCert( )
using CertEnumCertificatesInStore(
)
:
BOOL bResult; HCERTSTORE hCertStore; PCCRL_CONTEXT pCRLContext; PCCERT_CONTEXT pCertContext = 0; if (!(hCertStore = SpcNewStoreForCert(pSubjectContext))) { /* handle an error condition--could not create the store */ abort( ); } while ((pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext))) { /* do something with the certificate retrieved from the store. * if an error occurs, and enumeration must be terminated prematurely, the last * certificate retrieved must be freed manually. * * For example, attempt to retrieve the CRL for the certificate using the code * the can be found in Recipe 10.11. If no CRL can be retrieved, or the CRL * cannot be added to the certificate store, consider it a failure and break * out of the enumeration. */ if (!(pCRLContext = SpcRetrieveCRL(pCertContext, 0)) || !CertAddCRLContextToStore(hCertStore, pCRLContext, CERT_ADD_USE_EXISTING, 0)) { if (pCRLContext) CertFreeCRLContext(pCRLContext); break; } CertFreeCRLContext(pCRLContext); } if (pCertContext) { CertFreeCertificateContext(pCertContext); CertCloseStore(hCertStore, 0); abort( ); } bResult = SpcVerifyCert(hCertStore, pSubjectContext); CertCloseStore(hCertStore, 0); return bResult;