Chromium Code Reviews| Index: net/cert/cert_verify_proc_builtin.cc |
| diff --git a/net/cert/cert_verify_proc_builtin.cc b/net/cert/cert_verify_proc_builtin.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..be4c1e9f2a63c2b05240db284dd6096bec5d1579 |
| --- /dev/null |
| +++ b/net/cert/cert_verify_proc_builtin.cc |
| @@ -0,0 +1,448 @@ |
| +// Copyright (c) 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "net/cert/cert_verify_proc_builtin.h" |
| + |
| +#include <string> |
| +#include <vector> |
| + |
| +#if defined(USE_NSS_CERTS) |
| +#include <cert.h> |
| +#include <pk11pub.h> |
| +#endif |
| + |
| +#include "base/logging.h" |
| +#include "base/memory/ptr_util.h" |
| +#include "base/sha1.h" |
| +#include "base/strings/string_piece.h" |
| +#include "crypto/sha2.h" |
| +#include "net/base/net_errors.h" |
| +#include "net/cert/asn1_util.h" |
| +#include "net/cert/cert_status_flags.h" |
| +#include "net/cert/cert_verify_proc.h" |
| +#include "net/cert/cert_verify_result.h" |
| +#include "net/cert/internal/cert_errors.h" |
| +#include "net/cert/internal/cert_issuer_source_static.h" |
| +#include "net/cert/internal/parsed_certificate.h" |
| +#include "net/cert/internal/path_builder.h" |
| +#include "net/cert/internal/signature_policy.h" |
| +#include "net/cert/internal/trust_store_collection.h" |
| +#include "net/cert/internal/trust_store_in_memory.h" |
| +#include "net/cert/internal/verify_certificate_chain.h" |
| +#include "net/cert/x509_certificate.h" |
| +#include "net/cert/x509_util.h" |
| +#include "net/der/encode_values.h" |
| + |
| +#if defined(USE_NSS_CERTS) |
| +#include "crypto/nss_util.h" |
| +#include "net/cert/internal/cert_issuer_source_nss.h" |
| +#include "net/cert/internal/trust_store_nss.h" |
| +#endif |
| + |
| +namespace net { |
| + |
| +namespace { |
| + |
| +class CertVerifyProcBuiltin : public CertVerifyProc { |
| + public: |
| + CertVerifyProcBuiltin(); |
| + |
| + bool SupportsAdditionalTrustAnchors() const override; |
| + bool SupportsOCSPStapling() const override; |
| + |
| + protected: |
| + ~CertVerifyProcBuiltin() override; |
| + |
| + private: |
| + int VerifyInternal(X509Certificate* cert, |
| + const std::string& hostname, |
| + const std::string& ocsp_response, |
| + int flags, |
| + CRLSet* crl_set, |
| + const CertificateList& additional_trust_anchors, |
| + CertVerifyResult* verify_result) override; |
| +}; |
| + |
| +CertVerifyProcBuiltin::CertVerifyProcBuiltin() {} |
| + |
| +CertVerifyProcBuiltin::~CertVerifyProcBuiltin() {} |
| + |
| +bool CertVerifyProcBuiltin::SupportsAdditionalTrustAnchors() const { |
| + return true; |
| +} |
| + |
| +bool CertVerifyProcBuiltin::SupportsOCSPStapling() const { |
| + // TODO(crbug.com/649017): Implement. |
| + return false; |
| +} |
| + |
| +scoped_refptr<ParsedCertificate> ParseCertificateFromOSHandle( |
| + X509Certificate::OSCertHandle cert_handle, |
| + CertErrors* errors) { |
| + std::string cert_bytes; |
| + if (!X509Certificate::GetDEREncoded(cert_handle, &cert_bytes)) |
| + return nullptr; |
| + return ParsedCertificate::Create(x509_util::CreateCryptoBuffer(cert_bytes), |
| + {}, errors); |
| +} |
| + |
| +void AddIntermediatesToIssuerSource(X509Certificate* cert, |
| + CertIssuerSourceStatic* intermediates) { |
| + auto cert_handles = cert->GetIntermediateCertificates(); |
|
mattm
2017/03/16 02:58:24
Overuse of auto? (I think it should be "const auto
eroman
2017/03/16 17:35:40
Done.
|
| + CertErrors errors; |
| + for (auto it = cert_handles.begin(); it != cert_handles.end(); ++it) { |
| + auto cert = ParseCertificateFromOSHandle(*it, &errors); |
|
mattm
2017/03/16 02:58:24
I believe there was also concern over using auto f
mattm
2017/03/16 02:58:24
avoid rebinding of |cert| variable name
eroman
2017/03/16 17:35:41
Done.
eroman
2017/03/16 17:35:41
Done.
|
| + if (cert) |
| + intermediates->AddCert(std::move(cert)); |
| + // TODO(crbug.com/634443): Surface these parsing errors? |
| + } |
| +} |
| + |
| +// The SystemTrustStore interface augments the TrustStore interface with some |
| +// additional functionality: |
| +// |
| +// * Determine if a trust anchor was one of the known roots |
| +// * Determine if a trust anchor was one of the "extra" ones that |
| +// was specified during verification. |
| +// |
| +// Implementations of SystemTrustStore create an effective trust |
| +// store that is the composition of: |
| +// |
| +// (1) System trust store |
| +// (2) |additional_trust_anchors|. |
| +// (3) Test certificates (if they are separate from system trust store) |
| +class SystemTrustStore { |
| + public: |
| + virtual ~SystemTrustStore() {} |
| + |
| + virtual TrustStore* GetTrustStore() = 0; |
| + |
| + // TODO(eroman): Can this be exposed through the TrustStore |
| + // interface instead? |
| + virtual CertIssuerSource* GetCertIssuerSource() = 0; |
| + |
| + // IsKnownRoot returns true if the given trust anchor is a standard one (as |
| + // opposed to a user-installed root) |
| + virtual bool IsKnownRoot( |
| + const scoped_refptr<TrustAnchor>& trust_anchor) const = 0; |
| + |
| + virtual bool IsAdditionalTrustAnchor( |
| + const scoped_refptr<TrustAnchor>& trust_anchor) const = 0; |
| +}; |
| + |
| +#if defined(USE_NSS_CERTS) |
| +class SystemTrustStoreNSS : public SystemTrustStore { |
| + public: |
| + explicit SystemTrustStoreNSS(const CertificateList& additional_trust_anchors) |
| + : trust_store_nss_(trustSSL) { |
| + CertErrors errors; |
| + |
| + trust_store_.AddTrustStore(&additional_trust_store_); |
| + for (const auto& x590_cert : additional_trust_anchors) { |
|
mattm
2017/03/16 02:58:24
x509_cert ?
eroman
2017/03/16 17:35:41
Done.
|
| + scoped_refptr<ParsedCertificate> cert = |
| + ParseCertificateFromOSHandle(x590_cert->os_cert_handle(), &errors); |
| + if (cert) { |
| + additional_trust_store_.AddTrustAnchor( |
| + TrustAnchor::CreateFromCertificateNoConstraints(cert)); |
|
mattm
2017/03/16 02:58:24
std::move(cert) ?
eroman
2017/03/16 17:35:40
Done (however I wonder now if that should take a c
|
| + } |
| + // TODO(eroman): Surface parsing errors of additional trust anchor. |
| + } |
| + |
| + trust_store_.AddTrustStore(&trust_store_nss_); |
| + } |
| + |
| + TrustStore* GetTrustStore() override { return &trust_store_; } |
| + |
| + CertIssuerSource* GetCertIssuerSource() override { |
| + return &cert_issuer_source_nss_; |
| + } |
| + |
| + // IsKnownRoot returns true if the given trust anchor is a standard one (as |
| + // opposed to a user-installed root) |
| + bool IsKnownRoot( |
| + const scoped_refptr<TrustAnchor>& trust_anchor) const override { |
| + // TODO(eroman): Based on how the TrustAnchors are created by this |
| + // integration, there will always be an associated certificate. However this |
| + // contradicts the API for TrustAnchor that states it is optional. |
| + DCHECK(trust_anchor->cert()); |
| + |
| + // TODO(eroman): The overall approach of IsKnownRoot() is inefficient -- it |
| + // requires searching for the trust anchor by name in NSS (path building |
| + // already did this), and then testing matches for equality. |
| + crypto::EnsureNSSInit(); |
| + SECItem name; |
| + // Use the original issuer value instead of the normalized version. NSS does |
| + // a less extensive normalization in its Name comparisons, so our normalized |
| + // version may not match the unnormalized version. |
| + name.len = trust_anchor->cert()->tbs().subject_tlv.Length(); |
| + name.data = const_cast<uint8_t*>( |
| + trust_anchor->cert()->tbs().issuer_tlv.UnsafeData()); |
| + // |validOnly| in CERT_CreateSubjectCertList controls whether to return only |
| + // certs that are valid at |sorttime|. Expiration isn't meaningful for trust |
| + // anchors, so request all the matches. |
| + CERTCertList* found_certs = CERT_CreateSubjectCertList( |
| + nullptr /* certList */, CERT_GetDefaultCertDB(), &name, |
| + PR_Now() /* sorttime */, PR_FALSE /* validOnly */); |
| + if (!found_certs) |
| + return false; |
| + |
| + // Search through the matches and find the first one that matches the given |
| + // |trust_anchor|. Equality is determined by having the two certificates' |
| + // DER match. |
| + CERTCertificate* root_cert = nullptr; |
| + for (CERTCertListNode* node = CERT_LIST_HEAD(found_certs); |
| + !CERT_LIST_END(node, found_certs); node = CERT_LIST_NEXT(node)) { |
| + if (trust_anchor->cert()->der_cert() == |
| + der::Input(node->cert->derCert.data, node->cert->derCert.len)) { |
| + root_cert = node->cert; |
| + break; |
| + } |
| + } |
| + |
| + return IsKnownRoot(root_cert); |
| + } |
| + |
| + bool IsAdditionalTrustAnchor( |
| + const scoped_refptr<TrustAnchor>& trust_anchor) const override { |
| + return additional_trust_store_.Contains(trust_anchor.get()); |
| + } |
| + |
| + private: |
| + // TODO(eroman): This function was copied verbatim from |
| + // cert_verify_proc_nss.cc |
| + // |
| + // IsKnownRoot returns true if the given certificate is one that we believe |
| + // is a standard (as opposed to user-installed) root. |
| + bool IsKnownRoot(CERTCertificate* root) const { |
| + if (!root || !root->slot) |
| + return false; |
| + |
| + // This magic name is taken from |
| + // http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/security/nss/lib/ckfw/builtins/constants.c&rev=1.13&mark=86,89#79 |
| + return 0 == strcmp(PK11_GetSlotName(root->slot), "NSS Builtin Objects"); |
| + } |
| + |
| + TrustStoreCollection trust_store_; |
| + TrustStoreInMemory additional_trust_store_; |
| + |
| + TrustStoreNSS trust_store_nss_; |
| + CertIssuerSourceNSS cert_issuer_source_nss_; |
| +}; |
| +#endif |
| + |
| +std::unique_ptr<SystemTrustStore> CreateSystemTrustStore( |
| + const CertificateList& additional_trust_anchors) { |
| +#if defined(USE_NSS_CERTS) |
| + return base::MakeUnique<SystemTrustStoreNSS>(additional_trust_anchors); |
| +#else |
| + LOG(ERROR) |
| + << "TODO(crbug.com/649017): Integrate with other system trust stores."; |
| + return nullptr; |
| +#endif |
| +} |
| + |
| +// Appends the SHA1 and SHA256 hashes of |spki_bytes| to |*hashes|. |
| +void AppendPublicKeyHashes(const der::Input& spki_bytes, |
| + HashValueVector* hashes) { |
| + HashValue sha1(HASH_VALUE_SHA1); |
| + base::SHA1HashBytes(spki_bytes.UnsafeData(), spki_bytes.Length(), |
| + sha1.data()); |
| + hashes->push_back(sha1); |
| + |
| + HashValue sha256(HASH_VALUE_SHA256); |
| + crypto::SHA256HashString(spki_bytes.AsStringPiece(), sha256.data(), |
| + crypto::kSHA256Length); |
| + hashes->push_back(sha256); |
| +} |
| + |
| +// Appends the SubjectPublicKeyInfo hashes for all certificates (and trust |
| +// anchor) in |partial_path| to |*hashes|. |
| +void AppendPublicKeyHashes(const CertPathBuilder::ResultPath& partial_path, |
| + HashValueVector* hashes) { |
| + for (const scoped_refptr<ParsedCertificate>& cert : partial_path.path.certs) |
| + AppendPublicKeyHashes(cert->tbs().spki_tlv, hashes); |
| + |
| + if (partial_path.path.trust_anchor) |
| + AppendPublicKeyHashes(partial_path.path.trust_anchor->spki(), hashes); |
| +} |
| + |
| +// Sets the bits on |cert_status| for all the errors encountered during the path |
| +// building of |partial_path|. |
| +void MapPathBuilderErrorsToCertStatus( |
| + const CertPathBuilder::ResultPath& partial_path, |
| + CertStatus* cert_status) { |
| + // If path building was successful, there are no errors to map (there may have |
| + // been warnings but they do not map to CertStatus). |
| + if (partial_path.valid) |
| + return; |
| + |
| + VLOG(1) << partial_path.errors.ToDebugString(); |
| + |
| + if (partial_path.errors.ContainsError(kRsaModulusTooSmall)) |
| + *cert_status |= CERT_STATUS_WEAK_KEY; |
| + |
| + if (partial_path.errors.ContainsError(kValidityFailedNotAfter) || |
| + partial_path.errors.ContainsError(kValidityFailedNotBefore)) { |
| + *cert_status |= CERT_STATUS_DATE_INVALID; |
| + } |
| + |
| + // IMPORTANT: If the path was invalid for a reason that was not |
| + // explicity checked above, set a general error. This is important as |
| + // |cert_status| is what ultimately indicates whether verification was |
| + // successful or not (absense of errors implies success). |
| + if (!IsCertStatusError(*cert_status)) |
| + *cert_status |= CERT_STATUS_INVALID; |
| +} |
| + |
| +X509Certificate::OSCertHandle CreateOSCertHandle( |
| + const scoped_refptr<ParsedCertificate>& certificate) { |
| + return X509Certificate::CreateOSCertHandleFromBytes( |
| + reinterpret_cast<const char*>(certificate->der_cert().UnsafeData()), |
| + certificate->der_cert().Length()); |
| +} |
| + |
| +// Creates a X509Certificate (chain) to return as the verified result. |
| +// |
| +// * |target_cert|: The original X509Certificate that was passed in to |
| +// VerifyInternal() |
| +// * |path|: The result (possibly failed) from path building. |
| +scoped_refptr<X509Certificate> CreateVerifiedCertChain( |
| + X509Certificate* target_cert, |
| + const CertPathBuilder::ResultPath& path) { |
| + X509Certificate::OSCertHandles intermediates; |
| + |
| + // Skip the first certificate in the path as that is the target certificate |
| + for (size_t i = 1; i < path.path.certs.size(); ++i) |
| + intermediates.push_back(CreateOSCertHandle(path.path.certs[i])); |
| + |
| + if (path.path.trust_anchor) { |
| + // TODO(eroman): This assumes that TrustAnchor::cert() cannot be null, |
| + // which disagrees with the documentation. |
| + intermediates.push_back(CreateOSCertHandle(path.path.trust_anchor->cert())); |
| + } |
| + |
| + scoped_refptr<X509Certificate> result = X509Certificate::CreateFromHandle( |
| + target_cert->os_cert_handle(), intermediates); |
| + |
| + for (const X509Certificate::OSCertHandle handle : intermediates) |
| + X509Certificate::FreeOSCertHandle(handle); |
| + |
| + return result; |
| +} |
| + |
| +// TODO(crbug.com/649017): Make use of |flags|, |crl_set|, and |ocsp_response|. |
| +// Also handle key usages, policies and EV. |
| +// |
| +// Any failure short-circuits from the function must set |
| +// |verify_result->cert_status|. |
| +void DoVerify(X509Certificate* input_cert, |
| + const std::string& hostname, |
| + const std::string& ocsp_response, |
| + int flags, |
| + CRLSet* crl_set, |
| + const CertificateList& additional_trust_anchors, |
| + CertVerifyResult* verify_result) { |
| + CertErrors errors; |
| + |
| + // Parse the target certificate. |
| + scoped_refptr<ParsedCertificate> target = |
| + ParseCertificateFromOSHandle(input_cert->os_cert_handle(), &errors); |
| + if (!target) { |
| + // TODO(crbug.com/634443): Surface these parsing errors? |
| + verify_result->cert_status |= CERT_STATUS_INVALID; |
| + return; |
| + } |
| + |
| + std::unique_ptr<SystemTrustStore> trust_store = |
| + CreateSystemTrustStore(additional_trust_anchors); |
| + |
| + // TODO(eroman): The path building code in this file enforces its idea of weak |
| + // keys, and separately cert_verify_proc.cc also checks the chains with its |
| + // own policy. These policies should be aligned, to give path building the |
| + // best chance of finding a good path. |
| + // Another difference to resolve is the path building here does not check the |
| + // target certificate's key strength, whereas cert_verify_proc.cc does. |
| + SimpleSignaturePolicy signature_policy(1024); |
| + |
| + // Use the current time. |
| + der::GeneralizedTime verification_time; |
| + if (!der::EncodeTimeAsGeneralizedTime(base::Time::Now(), |
| + &verification_time)) { |
| + // This really shouldn't be possible unless Time::Now() returned |
| + // something crazy. |
| + verify_result->cert_status |= CERT_STATUS_DATE_INVALID; |
| + return; |
| + } |
| + |
| + // Initialize the path builder. |
| + CertPathBuilder::Result result; |
| + CertPathBuilder path_builder(target, trust_store->GetTrustStore(), |
| + &signature_policy, verification_time, &result); |
| + |
| + // Allow the path builder to discover intermediates from the trust store. |
| + if (trust_store->GetCertIssuerSource()) |
| + path_builder.AddCertIssuerSource(trust_store->GetCertIssuerSource()); |
| + |
| + // Allow the path builder to discover the explicitly provided intermediates in |
| + // |input_cert|. |
| + CertIssuerSourceStatic intermediates; |
| + AddIntermediatesToIssuerSource(input_cert, &intermediates); |
| + path_builder.AddCertIssuerSource(&intermediates); |
| + |
| + // TODO(crbug.com/649017): Allow the path builder to discover intermediates |
| + // through AIA fetching. |
| + |
| + path_builder.Run(); |
| + |
| + if (result.best_result_index >= result.paths.size()) { |
| + // TODO(eroman): Is this case reachable? |
|
mattm
2017/03/16 02:58:24
Yeah, the path builder currently only returns path
eroman
2017/03/16 17:35:41
Removed the TODO.
eroman
2017/03/16 17:36:26
Correction -- I didn't remove the TODO, i just rew
|
| + verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; |
| + return; |
| + } |
| + |
| + // Use the best path that was built. This could be a partial path, or it could |
| + // be a valid complete path. |
| + const CertPathBuilder::ResultPath& partial_path = |
| + *result.paths[result.best_result_index].get(); |
| + |
| + if (partial_path.path.trust_anchor) { |
| + verify_result->is_issued_by_known_root = |
| + trust_store->IsKnownRoot(partial_path.path.trust_anchor); |
| + |
| + verify_result->is_issued_by_additional_trust_anchor = |
| + trust_store->IsAdditionalTrustAnchor(partial_path.path.trust_anchor); |
| + } else { |
| + verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; |
| + } |
| + |
| + verify_result->verified_cert = |
| + CreateVerifiedCertChain(input_cert, partial_path); |
| + |
| + AppendPublicKeyHashes(partial_path, &verify_result->public_key_hashes); |
| + MapPathBuilderErrorsToCertStatus(partial_path, &verify_result->cert_status); |
| +} |
| + |
| +int CertVerifyProcBuiltin::VerifyInternal( |
| + X509Certificate* input_cert, |
| + const std::string& hostname, |
| + const std::string& ocsp_response, |
| + int flags, |
| + CRLSet* crl_set, |
| + const CertificateList& additional_trust_anchors, |
| + CertVerifyResult* verify_result) { |
| + DoVerify(input_cert, hostname, ocsp_response, flags, crl_set, |
| + additional_trust_anchors, verify_result); |
| + |
| + return IsCertStatusError(verify_result->cert_status) |
| + ? MapCertStatusToNetError(verify_result->cert_status) |
| + : OK; |
| +} |
| + |
| +} // namespace |
| + |
| +scoped_refptr<CertVerifyProc> CreateCertVerifyProcBuiltin() { |
| + return scoped_refptr<CertVerifyProc>(new CertVerifyProcBuiltin()); |
| +} |
| + |
| +} // namespace net |