Index: net/cert/cert_verify_proc_win.cc |
diff --git a/net/cert/cert_verify_proc_win.cc b/net/cert/cert_verify_proc_win.cc |
index f67f8234fc0048b80002f2b4b786f44e925e9410..37cf19be9029bd04118bc4a2570409dbc2fa644b 100644 |
--- a/net/cert/cert_verify_proc_win.cc |
+++ b/net/cert/cert_verify_proc_win.cc |
@@ -11,6 +11,7 @@ |
#include "base/sha1.h" |
#include "base/strings/string_util.h" |
#include "base/strings/utf_string_conversions.h" |
+#include "base/threading/thread_local.h" |
#include "crypto/capi_util.h" |
#include "crypto/scoped_capi_types.h" |
#include "crypto/sha2.h" |
@@ -386,12 +387,130 @@ void GetCertPoliciesInfo( |
} |
enum CRLSetResult { |
- kCRLSetOk, |
+ // Indicates an error happened while attempting to determine CRLSet status. |
+ // For example, if the certificate's SPKI could not be extracted. |
+ kCRLSetError, |
+ |
+ // Indicates there is no fresh information about the certificate, or if the |
+ // CRLSet has expired. |
+ // In the case of certificate chains, this is only returned if the leaf |
+ // certificate is not covered by the CRLSet; this is because some |
+ // intermediates are fully covered, but after filtering, the issuer's CRL |
+ // is empty and thus omitted from the CRLSet. Since online checking is |
+ // performed for EV certificates when this status is returned, this would |
+ // result in needless online lookups for certificates known not-revoked. |
kCRLSetUnknown, |
+ |
+ // Indicates that the certificate (or a certificate in the chain) has been |
+ // revoked. |
kCRLSetRevoked, |
+ |
+ // The certificate (or certificate chain) has no revocations. |
+ kCRLSetOk, |
}; |
-// CheckRevocationWithCRLSet attempts to check each element of |chain| |
+// Determines if |subject_cert| is revoked within |crl_set|, |
+// storing the SubjectPublicKeyInfo hash of |subject_cert| in |
+// |*previous_hash|. |
+// |
+// CRLSets store revocations by both SPKI and by the tuple of Issuer SPKI |
+// Hash & Serial. While |subject_cert| contains enough information to check |
+// for SPKI revocations, to determine the issuer's SPKI, either |issuer_cert| |
+// must be supplied, or the hash of the issuer's SPKI provided in |
+// |previous_hash|. If |issuer_cert| is omitted, and |previous_hash| is empty, |
davidben
2016/01/21 02:37:39
Nit: Most of these |previous_hash|s should probabl
|
+// only SPKI checks are performed. |
+// |
+// To avoid recomputing SPKI hashes, the hash of |subject_cert| is stored in |
+// |*previous_hash|. This allows chaining revocation checking, by starting |
+// at the root and iterating to the leaf, supplying |previous_hash| each time. |
+// |
+// In the event of a parsing error, |previous_hash| is cleared, to prevent the |
+// wrong Issuer&Serial tuple from being used. |
+CRLSetResult CheckRevocationWithCRLSet(CRLSet* crl_set, |
+ PCCERT_CONTEXT subject_cert, |
+ PCCERT_CONTEXT issuer_cert, |
+ std::string* previous_hash) { |
+ DCHECK(crl_set); |
+ DCHECK(subject_cert); |
+ |
+ // Check to see if |subject_cert|'s SPKI is revoked. The actual revocation |
+ // is handled by the SHA-256 hash of the SPKI, so compute that. |
+ base::StringPiece der_bytes( |
+ reinterpret_cast<const char*>(subject_cert->pbCertEncoded), |
+ subject_cert->cbCertEncoded); |
+ |
+ base::StringPiece spki; |
+ if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { |
+ // Should not reach here; it indicates Windows accepted something that |
+ // could not even be remotely parsed. |
+ NOTREACHED(); |
+ previous_hash->clear(); |
+ return kCRLSetError; |
+ } |
+ std::string subject_hash = crypto::SHA256HashString(spki); |
davidben
2016/01/21 02:37:39
Nit: I'd probably take lines 436 through 450 and p
|
+ |
+ CRLSet::Result result = crl_set->CheckSPKI(subject_hash); |
+ if (result == CRLSet::REVOKED) { |
+ previous_hash->swap(subject_hash); |
davidben
2016/01/21 02:37:39
This is inconsistent with line 500's CRLSet::REVOK
Ryan Sleevi
2016/01/21 02:54:04
I agree, we shouldn't update it here, but I disagr
|
+ return kCRLSetRevoked; |
+ } |
+ |
+ // If no issuer cert is provided, nor a hash of the issuer's SPKI, no |
+ // further checks can be done. |
+ if (!issuer_cert && previous_hash->empty()) { |
+ previous_hash->swap(subject_hash); |
+ return kCRLSetUnknown; |
+ } |
+ |
+ // Compute the subject's serial. |
+ const CRYPT_INTEGER_BLOB* serial_blob = |
+ &subject_cert->pCertInfo->SerialNumber; |
+ scoped_ptr<uint8_t[]> serial_bytes(new uint8_t[serial_blob->cbData]); |
+ // The bytes of the serial number are stored little-endian. |
+ // Note: While MSDN implies that bytes are stripped from this serial, |
+ // they are not - only CertCompareIntegerBlob actually removes bytes. |
+ for (unsigned j = 0; j < serial_blob->cbData; j++) |
davidben
2016/01/21 02:37:39
Nit: unsigned -> DWORD, to match cbData's type?
Ryan Sleevi
2016/01/21 02:54:04
Yeah, good point; copy pasta.
|
+ serial_bytes[j] = serial_blob->pbData[serial_blob->cbData - j - 1]; |
+ base::StringPiece serial(reinterpret_cast<const char*>(serial_bytes.get()), |
+ serial_blob->cbData); |
+ |
+ // Compute the issuer's hash. If it was provided (via previous_hash), |
+ // use that; otherwise, compute it based on |issuer_cert|. |
davidben
2016/01/21 02:37:39
Nit: I would take this block and switch it with th
Ryan Sleevi
2016/01/21 02:54:04
I find that (the folding) substantially less reada
|
+ std::string issuer_hash_local; |
+ std::string* issuer_hash = previous_hash; |
+ if (issuer_hash->empty()) { |
+ der_bytes = base::StringPiece( |
+ reinterpret_cast<const char*>(issuer_cert->pbCertEncoded), |
+ issuer_cert->cbCertEncoded); |
+ |
+ if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { |
+ // Should not reach here; it indicates Windows accepted something that |
+ // could not even be remotely parsed. |
+ NOTREACHED(); |
+ previous_hash->clear(); |
+ return kCRLSetError; |
+ } |
+ |
+ issuer_hash_local = crypto::SHA256HashString(spki); |
+ issuer_hash = &issuer_hash_local; |
+ } |
+ |
+ // Look up by serial & issuer SPKI. |
+ result = crl_set->CheckSerial(serial, *issuer_hash); |
+ if (result == CRLSet::REVOKED) |
+ return kCRLSetRevoked; |
+ |
+ previous_hash->swap(subject_hash); |
+ if (result == CRLSet::GOOD) |
+ return kCRLSetOk; |
+ if (result == CRLSet::UNKNOWN) |
+ return kCRLSetUnknown; |
+ |
+ NOTREACHED(); |
+ return kCRLSetError; |
+} |
+ |
+// CheckChainRevocationWithCRLSet attempts to check each element of |chain| |
// against |crl_set|. It returns: |
// kCRLSetRevoked: if any element of the chain is known to have been revoked. |
// kCRLSetUnknown: if there is no fresh information about the leaf |
@@ -403,79 +522,29 @@ enum CRLSetResult { |
// that some EV sites would otherwise take the hit of an OCSP lookup for |
// no reason. |
// kCRLSetOk: otherwise. |
-CRLSetResult CheckRevocationWithCRLSet(PCCERT_CHAIN_CONTEXT chain, |
- CRLSet* crl_set) { |
- if (chain->cChain == 0) |
- return kCRLSetOk; |
- |
- const PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0]; |
- const PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; |
- |
- const int num_elements = first_chain->cElement; |
- if (num_elements == 0) |
+CRLSetResult CheckChainRevocationWithCRLSet(PCCERT_CHAIN_CONTEXT chain, |
+ CRLSet* crl_set) { |
+ if (chain->cChain == 0 || chain->rgpChain[0]->cElement == 0) |
return kCRLSetOk; |
- // error is set to true if any errors are found. It causes such chains to be |
- // considered as not covered. |
- bool error = false; |
- // last_covered is set to the coverage state of the previous certificate. The |
- // certificates are iterated over backwards thus, after the iteration, |
- // |last_covered| contains the coverage state of the leaf certificate. |
- bool last_covered = false; |
+ PCERT_CHAIN_ELEMENT* elements = chain->rgpChain[0]->rgpElement; |
+ DWORD num_elements = chain->rgpChain[0]->cElement; |
- // We iterate from the root certificate down to the leaf, keeping track of |
- // the issuer's SPKI at each step. |
+ bool had_error = false; |
+ CRLSetResult result = kCRLSetError; |
std::string issuer_spki_hash; |
- for (int i = num_elements - 1; i >= 0; i--) { |
- PCCERT_CONTEXT cert = element[i]->pCertContext; |
- |
- base::StringPiece der_bytes( |
- reinterpret_cast<const char*>(cert->pbCertEncoded), |
- cert->cbCertEncoded); |
- |
- base::StringPiece spki; |
- if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { |
- NOTREACHED(); |
- error = true; |
- continue; |
- } |
- |
- const std::string spki_hash = crypto::SHA256HashString(spki); |
- |
- const CRYPT_INTEGER_BLOB* serial_blob = &cert->pCertInfo->SerialNumber; |
- scoped_ptr<uint8_t[]> serial_bytes(new uint8_t[serial_blob->cbData]); |
- // The bytes of the serial number are stored little-endian. |
- for (unsigned j = 0; j < serial_blob->cbData; j++) |
- serial_bytes[j] = serial_blob->pbData[serial_blob->cbData - j - 1]; |
- base::StringPiece serial(reinterpret_cast<const char*>(serial_bytes.get()), |
- serial_blob->cbData); |
- |
- CRLSet::Result result = crl_set->CheckSPKI(spki_hash); |
- |
- if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) |
- result = crl_set->CheckSerial(serial, issuer_spki_hash); |
- |
- issuer_spki_hash = spki_hash; |
- |
- switch (result) { |
- case CRLSet::REVOKED: |
- return kCRLSetRevoked; |
- case CRLSet::UNKNOWN: |
- last_covered = false; |
- continue; |
- case CRLSet::GOOD: |
- last_covered = true; |
- continue; |
- default: |
- NOTREACHED(); |
- error = true; |
- continue; |
- } |
+ for (DWORD i = 0; i < num_elements; ++i) { |
+ PCCERT_CONTEXT subject = elements[num_elements - i - 1]->pCertContext; |
davidben
2016/01/21 02:37:39
Optional: If you want to avoid the num_elements -
Ryan Sleevi
2016/01/21 02:54:04
I intentionally wanted to avoid relying on "stupid
|
+ result = |
+ CheckRevocationWithCRLSet(crl_set, subject, nullptr, &issuer_spki_hash); |
+ if (result == kCRLSetRevoked) |
+ return result; |
+ if (result == kCRLSetError) |
+ had_error = true; |
} |
- |
- if (error || !last_covered || crl_set->IsExpired()) |
+ if (had_error || crl_set->IsExpired()) |
return kCRLSetUnknown; |
- return kCRLSetOk; |
+ return result; |
} |
void AppendPublicKeyHashes(PCCERT_CHAIN_CONTEXT chain, |
@@ -551,6 +620,243 @@ bool CheckEV(PCCERT_CHAIN_CONTEXT chain_context, |
return metadata->HasEVPolicyOID(fingerprint, policy_oid); |
} |
+// Custom revocation provider function that compares incoming certificates with |
+// those in CRLSets. This is called BEFORE the default CRL & OCSP handling |
+// is invoked (which is handled by the revocation provider function |
+// "CertDllVerifyRevocation" in cryptnet.dll) |
+BOOL WINAPI |
+CertDllVerifyRevocationWithCRLSet(DWORD encoding_type, |
+ DWORD revocation_type, |
+ DWORD num_contexts, |
+ void* rgpvContext[], |
+ DWORD flags, |
+ PCERT_REVOCATION_PARA revocation_params, |
+ PCERT_REVOCATION_STATUS revocation_status); |
+ |
+// Helper class that installs the CRLSet-based Revocation Provider as the |
+// default revocation provider. Because it is installed as a function address |
+// (meaning only scoped to the process, and not stored in the registry), it |
+// will be used before any registry-based providers, including Microsoft's |
+// default provider. |
+class RevocationInjector { |
+ public: |
+ CRLSet* GetCRLSet() { return thread_local_crlset.Get(); } |
+ |
+ void SetCRLSet(CRLSet* crl_set) { thread_local_crlset.Set(crl_set); } |
+ |
+ private: |
+ friend struct base::DefaultLazyInstanceTraits<RevocationInjector>; |
+ |
+ RevocationInjector() { |
+ const CRYPT_OID_FUNC_ENTRY kInterceptFunction[] = { |
+ {CRYPT_DEFAULT_OID, &CertDllVerifyRevocationWithCRLSet}, |
+ }; |
+ BOOL ok = CryptInstallOIDFunctionAddress( |
+ NULL, X509_ASN_ENCODING, CRYPT_OID_VERIFY_REVOCATION_FUNC, |
+ arraysize(kInterceptFunction), kInterceptFunction, |
+ CRYPT_INSTALL_OID_FUNC_BEFORE_FLAG); |
+ DCHECK(ok); |
+ } |
+ |
+ ~RevocationInjector() {} |
+ |
+ // As the revocation parameters passed to CertVerifyProc::VerifyInternal |
+ // cannot be officially smuggled to the Revocation Provider |
+ base::ThreadLocalPointer<CRLSet> thread_local_crlset; |
+}; |
+ |
+// Leaky, as CertVerifyProc workers are themselves leaky. |
+base::LazyInstance<RevocationInjector>::Leaky g_revocation_injector = |
+ LAZY_INSTANCE_INITIALIZER; |
+ |
+BOOL WINAPI |
+CertDllVerifyRevocationWithCRLSet(DWORD encoding_type, |
+ DWORD revocation_type, |
+ DWORD num_contexts, |
+ void* rgpvContext[], |
+ DWORD flags, |
+ PCERT_REVOCATION_PARA revocation_params, |
+ PCERT_REVOCATION_STATUS revocation_status) { |
+ PCERT_CONTEXT* cert_contexts = reinterpret_cast<PCERT_CONTEXT*>(rgpvContext); |
davidben
2016/01/21 02:37:39
Nit: newline here? Comments that aren't preceded b
Ryan Sleevi
2016/01/21 02:54:05
Agreed.
|
+ // The dummy CRLSet provider never returns that something is affirmatively |
+ // *un*revoked, as this would disable other revocation providers from being |
+ // checked for this certificate (much like an OCSP "Good" status would). |
+ // Instead, it merely indicates that insufficient information existed to |
+ // determine if the certificate was revoked (in the good case), or that a cert |
+ // is affirmatively revoked in the event it appears within the CRLSet. |
+ // Because of this, set up some basic bookkeeping for the results. |
+ CHECK(revocation_status); |
+ revocation_status->dwIndex = 0; |
+ revocation_status->dwError = static_cast<DWORD>(CRYPT_E_NO_REVOCATION_CHECK); |
+ revocation_status->dwReason = 0; |
+ |
+ if (num_contexts == 0 || !cert_contexts[0]) { |
+ SetLastError(static_cast<DWORD>(E_INVALIDARG)); |
+ return FALSE; |
+ } |
+ |
+ if ((GET_CERT_ENCODING_TYPE(encoding_type) != X509_ASN_ENCODING) || |
davidben
2016/01/21 02:37:39
Style nit: Unnecessary parens. (Alternatively, mis
|
+ revocation_type != CERT_CONTEXT_REVOCATION_TYPE) { |
+ SetLastError(static_cast<DWORD>(CRYPT_E_NO_REVOCATION_CHECK)); |
+ return FALSE; |
+ } |
+ |
+ // No revocation checking possible if there is no associated |
+ // CRLSet. |
+ CRLSet* crl_set = g_revocation_injector.Get().GetCRLSet(); |
+ if (!crl_set) |
+ return FALSE; |
+ |
+ // |revocation_params| is an optional structure; to make life simple and avoid |
+ // the need to constantly check whether or not it was supplied, create a local |
+ // copy. If the caller didn't supply anything, it will be empty; otherwise, |
+ // it will be (non-owning) copies of the caller's original params. |
+ CERT_REVOCATION_PARA local_params; |
+ memset(&local_params, 0, sizeof(local_params)); |
+ if (revocation_params) { |
+ DWORD bytes_to_copy = std::min(revocation_params->cbSize, |
+ static_cast<DWORD>(sizeof(local_params))); |
+ memcpy(&local_params, revocation_params, bytes_to_copy); |
+ } |
+ local_params.cbSize = sizeof(local_params); |
+ |
+ PCERT_CONTEXT subject_cert = cert_contexts[0]; |
+ |
+ if ((flags & CERT_VERIFY_REV_CHAIN_FLAG) && num_contexts > 1) { |
Ryan Sleevi
2016/01/15 01:12:55
I just went ahead and did the iterative code, alth
|
+ // Verifying a chain; first verify from the last certificate in the |
+ // chain to the first, and then leave the last certificate (which |
+ // is presumably self-issued, although it may simply be a trust |
+ // anchor) as the |subject_cert| in order to scan for more |
+ // revocations. |
+ std::string issuer_hash; |
+ PCCERT_CONTEXT issuer_cert = nullptr; |
+ for (DWORD i = num_contexts; i > 0; --i) { |
davidben
2016/01/21 02:37:39
Optional: Another option is to write this as:
f
|
+ subject_cert = cert_contexts[i - 1]; |
+ if (!subject_cert) { |
davidben
2016/01/21 02:37:39
Why does this function check for NULL while CheckC
Ryan Sleevi
2016/01/21 02:54:04
Yes. CertVerifyRevocation is caller-exposed, and n
|
+ SetLastError(static_cast<DWORD>(E_INVALIDARG)); |
+ return FALSE; |
+ } |
+ CRLSetResult result = CheckRevocationWithCRLSet( |
+ crl_set, subject_cert, issuer_cert, &issuer_hash); |
+ if (result == kCRLSetRevoked) { |
+ revocation_status->dwIndex = i - 1; |
+ revocation_status->dwError = static_cast<DWORD>(CRYPT_E_REVOKED); |
+ revocation_status->dwReason = CRL_REASON_UNSPECIFIED; |
+ SetLastError(revocation_status->dwError); |
+ return FALSE; |
+ } |
+ issuer_cert = subject_cert; |
+ } |
+ // Verified all certificates from the trust anchor to the leaf, and none |
+ // were explicitly revoked. Now do a second pass to attempt to determine |
+ // the issuer for cert_contexts[num_contexts - 1], so that the |
+ // Issuer SPKI+Serial can be checked for that certificate. |
+ // |
+ // This code intentionally ignores the flag |
+ subject_cert = cert_contexts[num_contexts - 1]; |
+ // Reset local_params.pIssuerCert, since it would contain the issuer |
+ // for cert_contexts[0]. |
+ local_params.pIssuerCert = nullptr; |
+ // Fixup the revocation index to point to this cert (in the event it is |
+ // revoked). If it isn't revoked, this will be done undone later. |
+ revocation_status->dwIndex = num_contexts - 1; |
+ } |
+ |
+ // Determine the issuer cert for the incoming cert |
+ ScopedPCCERT_CONTEXT issuer_cert; |
+ if (local_params.pIssuerCert && |
davidben
2016/01/21 02:37:39
[I love how they don't say anything useful about t
|
+ CryptVerifyCertificateSignatureEx( |
+ NULL, subject_cert->dwCertEncodingType, |
+ CRYPT_VERIFY_CERT_SIGN_SUBJECT_CERT, subject_cert, |
+ CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, |
+ const_cast<PCERT_CONTEXT>(local_params.pIssuerCert), 0, nullptr)) { |
+ // Caller has already supplied the issuer cert via the revocation params; |
+ // just use that. |
+ issuer_cert.reset( |
+ CertDuplicateCertificateContext(local_params.pIssuerCert)); |
+ } else if (CertCompareCertificateName(subject_cert->dwCertEncodingType, |
+ &subject_cert->pCertInfo->Subject, |
+ &subject_cert->pCertInfo->Issuer) && |
+ CryptVerifyCertificateSignatureEx( |
+ NULL, subject_cert->dwCertEncodingType, |
+ CRYPT_VERIFY_CERT_SIGN_SUBJECT_CERT, subject_cert, |
+ CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, subject_cert, 0, |
+ nullptr)) { |
+ // Certificate is self-signed; use it as its own issuer. |
+ issuer_cert.reset(CertDuplicateCertificateContext(subject_cert)); |
+ } else { |
+ // Scan the caller-supplied stores first, to try and find the issuer cert. |
+ for (DWORD i = 0; i < local_params.cCertStore && !issuer_cert; ++i) { |
+ PCCERT_CONTEXT previous_cert = nullptr; |
+ for (;;) { |
+ DWORD store_search_flags = CERT_STORE_SIGNATURE_FLAG; |
+ previous_cert = CertGetIssuerCertificateFromStore( |
+ local_params.rgCertStore[i], subject_cert, previous_cert, |
+ &store_search_flags); |
+ if (!previous_cert) |
+ break; |
+ // If a cert is found and meets the criteria, the flag will be reset to |
+ // zero. Thus NOT having the bit set is equivalent to having found a |
+ // matching certificate. |
+ if (!(store_search_flags & CERT_STORE_SIGNATURE_FLAG)) { |
+ // No need to dupe; reference is held. |
+ issuer_cert.reset(previous_cert); |
+ break; |
+ } |
+ } |
+ if (issuer_cert) |
+ break; |
+ if (GetLastError() == CRYPT_E_SELF_SIGNED) { |
+ issuer_cert.reset(CertDuplicateCertificateContext(subject_cert)); |
+ break; |
+ } |
+ } |
+ |
+ // At this point, the Microsoft provider opens up the "CA", "Root", and |
+ // "SPC" stores to search for the issuer certificate, if not found in the |
+ // caller-supplied stores. It is unclear whether that is necessary here. |
+ } |
+ |
+ if (!issuer_cert) { |
+ // Rather than return CRYPT_E_NO_REVOCATION_CHECK (indicating everything |
+ // is fine to try the next provider), return CRYPT_E_REVOCATION_OFFLINE. |
+ // This propogates up to the caller as an error while checking revocation, |
+ // which is the desired intent if there are certificates that cannot |
+ // be checked. |
+ revocation_status->dwIndex = 0; |
+ revocation_status->dwError = static_cast<DWORD>(CRYPT_E_REVOCATION_OFFLINE); |
+ SetLastError(revocation_status->dwError); |
+ return FALSE; |
+ } |
+ |
+ std::string unused; |
+ CRLSetResult result = CheckRevocationWithCRLSet(crl_set, subject_cert, |
+ issuer_cert.get(), &unused); |
+ if (result == kCRLSetRevoked) { |
+ revocation_status->dwError = static_cast<DWORD>(CRYPT_E_REVOKED); |
+ revocation_status->dwReason = CRL_REASON_UNSPECIFIED; |
+ SetLastError(revocation_status->dwError); |
+ return FALSE; |
+ } |
+ |
+ // The result is ALWAYS FALSE in order to allow the next revocation provider |
+ // a chance to examine. The only difference is whether or not an error is |
+ // indicated via dwError (and SetLastError()). |
+ // Reset the error index so that Windows does not believe this code has |
+ // examined the entire chain and found no issues until the last cert (thus |
+ // skipping other revocation providers). |
+ revocation_status->dwIndex = 0; |
+ return FALSE; |
+} |
+ |
+class ScopedThreadLocalCRLSet { |
+ public: |
+ explicit ScopedThreadLocalCRLSet(CRLSet* crl_set) { |
+ g_revocation_injector.Get().SetCRLSet(crl_set); |
+ } |
+ ~ScopedThreadLocalCRLSet() { g_revocation_injector.Get().SetCRLSet(nullptr); } |
+}; |
+ |
} // namespace |
CertVerifyProcWin::CertVerifyProcWin() {} |
@@ -578,6 +884,10 @@ int CertVerifyProcWin::VerifyInternal( |
CRLSet* crl_set, |
const CertificateList& additional_trust_anchors, |
CertVerifyResult* verify_result) { |
+ // Ensure the Revocation Provider has been installed and configured for this |
+ // CRLSet. |
+ ScopedThreadLocalCRLSet thread_local_crlset(crl_set); |
+ |
PCCERT_CONTEXT cert_handle = cert->os_cert_handle(); |
if (!cert_handle) |
return ERR_UNEXPECTED; |
@@ -621,33 +931,41 @@ int CertVerifyProcWin::VerifyInternal( |
} |
} |
- // We can set CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS to get more chains. |
- DWORD chain_flags = CERT_CHAIN_CACHE_END_CERT | |
- CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; |
+ // Revocation checking is always enabled, in order to enable CRLSets to be |
+ // evaluated as part of a revocation provider. However, when the caller did |
+ // not explicitly request revocation checking (which is to say, online |
+ // revocation checking), then only enable cached results. This disables OCSP |
+ // and CRL fetching, but still allows the revocation provider to be called. |
+ // Note: The root cert is also checked for revocation status, so that CRLSets |
+ // will cover revoked SPKIs. |
+ DWORD chain_flags = CERT_CHAIN_REVOCATION_CHECK_CHAIN; |
bool rev_checking_enabled = |
(flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED); |
- |
if (rev_checking_enabled) { |
verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; |
} else { |
chain_flags |= CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY; |
} |
- // For non-test scenarios, use the default HCERTCHAINENGINE, NULL, which |
- // corresponds to HCCE_CURRENT_USER and is is initialized as needed by |
- // crypt32. However, when testing, it is necessary to create a new |
- // HCERTCHAINENGINE and use that instead. This is because each |
- // HCERTCHAINENGINE maintains a cache of information about certificates |
- // encountered, and each test run may modify the trust status of a |
- // certificate. |
+ // By default, use the default HCERTCHAINENGINE (aka HCCE_CURRENT_USER). When |
+ // running tests, use a dynamic HCERTCHAINENGINE. All of the status and cache |
+ // of verified certificates and chains is tied to the HCERTCHAINENGINE. As |
+ // each invocation may have changed the set of known roots, invalid the cache |
+ // between runs. |
+ // |
+ // This is not the most efficient means of doing so; it's possible to mark the |
+ // Root store used by TestRootCerts as changed, via CertControlStore with the |
+ // CERT_STORE_CTRL_NOTIFY_CHANGE / CERT_STORE_CTRL_RESYNC, but that's more |
+ // complexity for what is test-only code. |
ScopedHCERTCHAINENGINE chain_engine(NULL); |
if (TestRootCerts::HasInstance()) |
chain_engine.reset(TestRootCerts::GetInstance()->GetChainEngine()); |
ScopedPCCERT_CONTEXT cert_list(cert->CreateOSCertChainForCert()); |
+ // Add stapled OCSP response data, which will be preferred over online checks |
+ // and used when in cache-only mode. |
if (!ocsp_response.empty()) { |
- // Attach the OCSP response to the chain. |
CRYPT_DATA_BLOB ocsp_response_blob; |
ocsp_response_blob.cbData = ocsp_response.size(); |
ocsp_response_blob.pbData = |
@@ -657,10 +975,7 @@ int CertVerifyProcWin::VerifyInternal( |
CERT_SET_PROPERTY_IGNORE_PERSIST_ERROR_FLAG, &ocsp_response_blob); |
} |
- PCCERT_CHAIN_CONTEXT chain_context; |
- // IE passes a non-NULL pTime argument that specifies the current system |
- // time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the |
- // chain_flags argument. |
+ PCCERT_CHAIN_CONTEXT chain_context = nullptr; |
if (!CertGetCertificateChain( |
chain_engine, |
cert_list.get(), |
@@ -674,9 +989,13 @@ int CertVerifyProcWin::VerifyInternal( |
return MapSecurityError(GetLastError()); |
} |
+ // Perform a second check with CRLSets. Although the Revocation Provider |
+ // should have prevented invalid paths from being built, the behaviour and |
+ // timing of how a Revocation Provider is invoked is not well documented. This |
+ // is just defense in depth. |
CRLSetResult crl_set_result = kCRLSetUnknown; |
if (crl_set) |
- crl_set_result = CheckRevocationWithCRLSet(chain_context, crl_set); |
+ crl_set_result = CheckChainRevocationWithCRLSet(chain_context, crl_set); |
if (crl_set_result == kCRLSetRevoked) { |
verify_result->cert_status |= CERT_STATUS_REVOKED; |