In CryptoAPI one can use the CertGetCertificateChain API to do the path building and basic chain validation, this validation may include revocation checking depending on which flags you pass via dwFlags; for example these flags control if revocation checking occurs, and if so, on which certificates:
- CERT_CHAIN_REVOCATION_CHECK_END_CERT
- CERT_CHAIN_REVOCATION_CHECK_CHAIN
- CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
Typically you would specify CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT which ensures the whole chain is checked (where possible – e.g. one shouldn’t bother asking the root if he considers himself revoked).
But in the context of OCSP what are the potential revocation related returns we might see?
- Revoked – I have received a signed response from the CA or have had policy pushed to me that tells me that this certificate is not to be trusted.
- Not Revoked – I have received a signed response from the CA that says this certificate was not revoked.
- Unknown – I have received signed response from the CA that says it doesn’t know anything about this certificate.
- Offline – I was unable to reach the responder to verify the status of the certificate.
Each of these cases are clearly represented in the API that is used by CertGetCertificateChain to perform the revocation check, this API is CertVerifyRevocation. The higher level CertGetCertificateChain however only has two possible returns: Revoked and Unknown outside the “not revoked” case.
One might assume the OCSP Unknown would get mapped into the Revoked state, this unfortunately is not the case, it is returned as unknown, as does the Offline error.
This means that if OCSP was used you cannot tell what the actual status was, this is especially problematic since IE and Chrome both default to modes where they ignore “Unknown” revocations due to concerns over Revocation responder performance and reliability.
It is possible to work around this platform behavior though — the problem is that it’s not documented anywhere, let’s take a quick stab at doing that here.
First since CertGetCertificateChain doesn’t tell us what method was used to do revocation checking we have to use heuristics to figure it out. Thankfully to enable OCSP stapling in higher level protocols like TLS the OCSP response is passed back to the caller if OCSP was used; to get that we need to know:
1. The CERT_CHAIN_ELEMENT in the returned CHAIN_CONTEXT points to the following revocation information:
- PCERT_REVOCATION_INFO pRevocationInfo;
- PCERT_REVOCATION_CRL_INFO pCrlInfo;
- PCCRL_CONTEXT pBaseCrlContext;
2. For an OCSP response, the CRL_CONTEXT is specially created to contain the full OCSP response in the following critical extension szOID_PKIX_OCSP_BASIC_SIGNED_RESPONSE.
- The presence of this extension indicates an OCSP response was used.
- The Issuer name in the CRL is the name of the OCSP signer.
- The signature algorithm is sha1NoSign.
- ThisUpdate and NextUpdate contain the response validity period.
With this we can take the OCSP response from the CRL_CONTEXT and then look at the “ResponseData” within it, you will need to look within the “responses” here, you will need to find the right “SingleResponse” based on its “CertID”.
NOTE: Some responders will return the status of multiple certificates in a response even if the status of only one was requested.
You now can determine what the responder said the status of the certificate was by inspecting “certStatus” element.
This is a fair amount of work unfortunately but it does enable you to do the right thing with authoritative “Unknown” responses.
Ryan