// Copyright 2016 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/internal/path_builder.h" #include #include #include "base/logging.h" #include "net/base/net_errors.h" #include "net/cert/internal/cert_issuer_source.h" #include "net/cert/internal/certificate_policies.h" #include "net/cert/internal/parse_certificate.h" #include "net/cert/internal/parse_name.h" // For CertDebugString. #include "net/cert/internal/trust_store.h" #include "net/cert/internal/verify_certificate_chain.h" #include "net/cert/internal/verify_name_match.h" #include "net/der/parser.h" #include "net/der/tag.h" namespace net { namespace { using CertIssuerSources = std::vector; // TODO(mattm): decide how much debug logging to keep. std::string CertDebugString(const ParsedCertificate* cert) { RDNSequence subject, issuer; std::string subject_str, issuer_str; if (!ParseName(cert->tbs().subject_tlv, &subject) || !ConvertToRFC2253(subject, &subject_str)) subject_str = "???"; if (!ParseName(cert->tbs().issuer_tlv, &issuer) || !ConvertToRFC2253(issuer, &issuer_str)) issuer_str = "???"; return subject_str + "(" + issuer_str + ")"; } // This structure describes a certificate and its trust level. Note that |cert| // may be null to indicate an "empty" entry. struct IssuerEntry { scoped_refptr cert; CertificateTrust trust; }; // Simple comparator of IssuerEntry that defines the order in which issuers // should be explored. It puts trust anchors ahead of unknown or distrusted // ones. struct IssuerEntryComparator { bool operator()(const IssuerEntry& issuer1, const IssuerEntry& issuer2) { return CertificateTrustToOrder(issuer1.trust) < CertificateTrustToOrder(issuer2.trust); } static int CertificateTrustToOrder(const CertificateTrust& trust) { switch (trust.type) { case CertificateTrustType::TRUSTED_ANCHOR: case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: return 1; case CertificateTrustType::UNSPECIFIED: return 2; case CertificateTrustType::DISTRUSTED: return 4; } NOTREACHED(); return 5; } }; // CertIssuersIter iterates through the intermediates from |cert_issuer_sources| // which may be issuers of |cert|. class CertIssuersIter { public: // Constructs the CertIssuersIter. |*cert_issuer_sources| and |*trust_store| // must be valid for the lifetime of the CertIssuersIter. CertIssuersIter(scoped_refptr cert, CertIssuerSources* cert_issuer_sources, const TrustStore* trust_store); // Gets the next candidate issuer, or clears |*out| when all issuers have been // exhausted. void GetNextIssuer(IssuerEntry* out); // Returns the |cert| for which issuers are being retrieved. const ParsedCertificate* cert() const { return cert_.get(); } scoped_refptr reference_cert() const { return cert_; } private: void AddIssuers(ParsedCertificateList issuers); void DoAsyncIssuerQuery(); // Returns true if |issuers_| contains unconsumed certificates. bool HasCurrentIssuer() const { return cur_issuer_ < issuers_.size(); } // Sorts the remaining entries in |issuers_| in the preferred order to // explore. Does not change the ordering for indices before cur_issuer_. void SortRemainingIssuers(); scoped_refptr cert_; CertIssuerSources* cert_issuer_sources_; const TrustStore* trust_store_; // The list of issuers for |cert_|. This is added to incrementally (first // synchronous results, then possibly multiple times as asynchronous results // arrive.) The issuers may be re-sorted each time new issuers are added, but // only the results from |cur_| onwards should be sorted, since the earlier // results were already returned. // Elements should not be removed from |issuers_| once added, since // |present_issuers_| will point to data owned by the certs. std::vector issuers_; // The index of the next cert in |issuers_| to return. size_t cur_issuer_ = 0; // Set to true whenever new issuers are appended at the end, to indicate the // ordering needs to be checked. bool issuers_needs_sort_ = false; // Set of DER-encoded values for the certs in |issuers_|. Used to prevent // duplicates. This is based on the full DER of the cert to allow different // versions of the same certificate to be tried in different candidate paths. // This points to data owned by |issuers_|. std::unordered_set present_issuers_; // Tracks which requests have been made yet. bool did_initial_query_ = false; bool did_async_issuer_query_ = false; // Index into pending_async_requests_ that is the next one to process. size_t cur_async_request_ = 0; // Owns the Request objects for any asynchronous requests so that they will be // cancelled if CertIssuersIter is destroyed. std::vector> pending_async_requests_; DISALLOW_COPY_AND_ASSIGN(CertIssuersIter); }; CertIssuersIter::CertIssuersIter(scoped_refptr in_cert, CertIssuerSources* cert_issuer_sources, const TrustStore* trust_store) : cert_(in_cert), cert_issuer_sources_(cert_issuer_sources), trust_store_(trust_store) { DVLOG(1) << "CertIssuersIter(" << CertDebugString(cert()) << ") created"; } void CertIssuersIter::GetNextIssuer(IssuerEntry* out) { if (!did_initial_query_) { did_initial_query_ = true; for (auto* cert_issuer_source : *cert_issuer_sources_) { ParsedCertificateList new_issuers; cert_issuer_source->SyncGetIssuersOf(cert(), &new_issuers); AddIssuers(std::move(new_issuers)); } } // If there aren't any issuers, block until async results are ready. if (!HasCurrentIssuer()) { if (!did_async_issuer_query_) { // Now issue request(s) for async ones (AIA, etc). DoAsyncIssuerQuery(); } // TODO(eroman): Rather than blocking on the async requests in FIFO order, // consume in the order they become ready. while (!HasCurrentIssuer() && cur_async_request_ < pending_async_requests_.size()) { ParsedCertificateList new_issuers; pending_async_requests_[cur_async_request_]->GetNext(&new_issuers); if (new_issuers.empty()) { // Request is exhausted, no more results pending from that // CertIssuerSource. pending_async_requests_[cur_async_request_++].reset(); } else { AddIssuers(std::move(new_issuers)); } } } if (HasCurrentIssuer()) { SortRemainingIssuers(); DVLOG(1) << "CertIssuersIter(" << CertDebugString(cert()) << "): returning issuer " << cur_issuer_ << " of " << issuers_.size(); // Still have issuers that haven't been returned yet, return the highest // priority one (head of remaining list). A reference to the returned issuer // is retained, since |present_issuers_| points to data owned by it. *out = issuers_[cur_issuer_++]; return; } DVLOG(1) << "CertIssuersIter(" << CertDebugString(cert()) << ") Reached the end of all available issuers."; // Reached the end of all available issuers. *out = IssuerEntry(); } void CertIssuersIter::AddIssuers(ParsedCertificateList new_issuers) { for (scoped_refptr& issuer : new_issuers) { if (present_issuers_.find(issuer->der_cert().AsStringPiece()) != present_issuers_.end()) continue; present_issuers_.insert(issuer->der_cert().AsStringPiece()); // Look up the trust for this issuer. IssuerEntry entry; entry.cert = std::move(issuer); trust_store_->GetTrust(entry.cert, &entry.trust); issuers_.push_back(std::move(entry)); issuers_needs_sort_ = true; } } void CertIssuersIter::DoAsyncIssuerQuery() { DCHECK(!did_async_issuer_query_); did_async_issuer_query_ = true; cur_async_request_ = 0; for (auto* cert_issuer_source : *cert_issuer_sources_) { std::unique_ptr request; cert_issuer_source->AsyncGetIssuersOf(cert(), &request); if (request) { DVLOG(1) << "AsyncGetIssuersOf(" << CertDebugString(cert()) << ") pending..."; pending_async_requests_.push_back(std::move(request)); } } } void CertIssuersIter::SortRemainingIssuers() { // TODO(mattm): sort by notbefore, etc (eg if cert issuer matches a trust // anchor subject (or is a trust anchor), that should be sorted higher too. // See big list of possible sorting hints in RFC 4158.) // (Update PathBuilderKeyRolloverTest.TestRolloverBothRootsTrusted once that // is done) if (!issuers_needs_sort_) return; std::stable_sort(issuers_.begin() + cur_issuer_, issuers_.end(), IssuerEntryComparator()); issuers_needs_sort_ = false; } // CertIssuerIterPath tracks which certs are present in the path and prevents // paths from being built which repeat any certs (including different versions // of the same cert, based on Subject+SubjectAltName+SPKI). // (RFC 5280 forbids duplicate certificates per section 6.1, and RFC 4158 // further recommends disallowing the same Subject+SubjectAltName+SPKI in // section 2.4.2.) class CertIssuerIterPath { public: // Returns true if |cert| is already present in the path. bool IsPresent(const ParsedCertificate* cert) const { return present_certs_.find(GetKey(cert)) != present_certs_.end(); } // Appends |cert_issuers_iter| to the path. The cert referred to by // |cert_issuers_iter| must not be present in the path already. void Append(std::unique_ptr cert_issuers_iter) { bool added = present_certs_.insert(GetKey(cert_issuers_iter->cert())).second; DCHECK(added); cur_path_.push_back(std::move(cert_issuers_iter)); } // Pops the last CertIssuersIter off the path. void Pop() { size_t num_erased = present_certs_.erase(GetKey(cur_path_.back()->cert())); DCHECK_EQ(num_erased, 1U); cur_path_.pop_back(); } // Copies the ParsedCertificate elements of the current path to |*out_path|. void CopyPath(ParsedCertificateList* out_path) { out_path->clear(); for (const auto& node : cur_path_) out_path->push_back(node->reference_cert()); } // Returns true if the path is empty. bool Empty() const { return cur_path_.empty(); } // Returns the last CertIssuersIter in the path. CertIssuersIter* back() { return cur_path_.back().get(); } std::string PathDebugString() { std::string s; for (const auto& node : cur_path_) { if (!s.empty()) s += " <- "; s += CertDebugString(node->cert()); } return s; } private: using Key = std::tuple; static Key GetKey(const ParsedCertificate* cert) { // TODO(mattm): ideally this would use a normalized version of // SubjectAltName, but it's not that important just for LoopChecker. // // Note that subject_alt_names_extension().value will be empty if the cert // had no SubjectAltName extension, so there is no need for a condition on // has_subject_alt_names(). return Key(cert->normalized_subject().AsStringPiece(), cert->subject_alt_names_extension().value.AsStringPiece(), cert->tbs().spki_tlv.AsStringPiece()); } std::vector> cur_path_; // This refers to data owned by |cur_path_|. // TODO(mattm): use unordered_set. Requires making a hash function for Key. std::set present_certs_; }; } // namespace const ParsedCertificate* CertPathBuilderResultPath::GetTrustedCert() const { if (certs.empty()) return nullptr; switch (last_cert_trust.type) { case CertificateTrustType::TRUSTED_ANCHOR: case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: return certs.back().get(); case CertificateTrustType::UNSPECIFIED: case CertificateTrustType::DISTRUSTED: return nullptr; } NOTREACHED(); return nullptr; } // CertPathIter generates possible paths from |cert| to a trust anchor in // |trust_store|, using intermediates from the |cert_issuer_source| objects if // necessary. class CertPathIter { public: CertPathIter(scoped_refptr cert, const TrustStore* trust_store); // Adds a CertIssuerSource to provide intermediates for use in path building. // The |*cert_issuer_source| must remain valid for the lifetime of the // CertPathIter. void AddCertIssuerSource(CertIssuerSource* cert_issuer_source); // Gets the next candidate path, and fills it into |out_certs| and // |out_last_cert_trust|. Note that the returned path is unverified and must // still be run through a chain validator. Once all paths have been exhausted // returns false. bool GetNextPath(ParsedCertificateList* out_certs, CertificateTrust* out_last_cert_trust); private: // Stores the next candidate issuer, until it is used during the // STATE_GET_NEXT_ISSUER_COMPLETE step. IssuerEntry next_issuer_; // The current path being explored, made up of CertIssuerIters. Each node // keeps track of the state of searching for issuers of that cert, so that // when backtracking it can resume the search where it left off. CertIssuerIterPath cur_path_; // The CertIssuerSources for retrieving candidate issuers. CertIssuerSources cert_issuer_sources_; // The TrustStore for checking if a path ends in a trust anchor. const TrustStore* trust_store_; DISALLOW_COPY_AND_ASSIGN(CertPathIter); }; CertPathIter::CertPathIter(scoped_refptr cert, const TrustStore* trust_store) : trust_store_(trust_store) { // Initialize |next_issuer_| to the target certificate. next_issuer_.cert = std::move(cert); trust_store_->GetTrust(next_issuer_.cert, &next_issuer_.trust); } void CertPathIter::AddCertIssuerSource(CertIssuerSource* cert_issuer_source) { cert_issuer_sources_.push_back(cert_issuer_source); } bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs, CertificateTrust* out_last_cert_trust) { while (true) { if (!next_issuer_.cert) { if (cur_path_.Empty()) { DVLOG(1) << "CertPathIter exhausted all paths..."; return false; } cur_path_.back()->GetNextIssuer(&next_issuer_); if (!next_issuer_.cert) { // TODO(mattm): should also include such paths in // CertPathBuilder::Result, maybe with a flag to enable it. Or use a // visitor pattern so the caller can decide what to do with any failed // paths. No more issuers for current chain, go back up and see if there // are any more for the previous cert. DVLOG(1) << "CertPathIter backtracking..."; cur_path_.Pop(); // Continue exploring issuers of the previous path... continue; } } // If the cert is trusted but is the leaf, treat it as having unspecified // trust. This may allow a successful path to be built to a different root // (or to the same cert if it's self-signed). switch (next_issuer_.trust.type) { case CertificateTrustType::TRUSTED_ANCHOR: case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: if (cur_path_.Empty()) { DVLOG(1) << "Leaf is a trust anchor, considering as UNSPECIFIED"; next_issuer_.trust = CertificateTrust::ForUnspecified(); } break; case CertificateTrustType::DISTRUSTED: case CertificateTrustType::UNSPECIFIED: // No override necessary. break; } switch (next_issuer_.trust.type) { // If the trust for this issuer is "known" (either becuase it is // distrusted, or because it is trusted) then stop building and return the // path. case CertificateTrustType::DISTRUSTED: case CertificateTrustType::TRUSTED_ANCHOR: case CertificateTrustType::TRUSTED_ANCHOR_WITH_CONSTRAINTS: { // If the issuer has a known trust level, can stop building the path. DVLOG(1) << "CertPathIter got anchor: " << CertDebugString(next_issuer_.cert.get()); cur_path_.CopyPath(out_certs); out_certs->push_back(std::move(next_issuer_.cert)); *out_last_cert_trust = next_issuer_.trust; next_issuer_ = IssuerEntry(); return true; } case CertificateTrustType::UNSPECIFIED: { // Skip this cert if it is already in the chain. if (cur_path_.IsPresent(next_issuer_.cert.get())) { DVLOG(1) << "CertPathIter skipping dupe cert: " << CertDebugString(next_issuer_.cert.get()); next_issuer_ = IssuerEntry(); continue; } cur_path_.Append(std::make_unique( std::move(next_issuer_.cert), &cert_issuer_sources_, trust_store_)); next_issuer_ = IssuerEntry(); DVLOG(1) << "CertPathIter cur_path_ = " << cur_path_.PathDebugString(); // Continue descending the tree. continue; } } } } CertPathBuilderResultPath::CertPathBuilderResultPath() = default; CertPathBuilderResultPath::~CertPathBuilderResultPath() = default; bool CertPathBuilderResultPath::IsValid() const { return GetTrustedCert() && !errors.ContainsHighSeverityErrors(); } CertPathBuilder::Result::Result() = default; CertPathBuilder::Result::~Result() = default; bool CertPathBuilder::Result::HasValidPath() const { return GetBestValidPath() != nullptr; } const CertPathBuilderResultPath* CertPathBuilder::Result::GetBestValidPath() const { DCHECK((paths.empty() && best_result_index == 0) || best_result_index < paths.size()); if (best_result_index >= paths.size()) return nullptr; const CertPathBuilderResultPath* result_path = paths[best_result_index].get(); if (result_path->IsValid()) return result_path; return nullptr; } void CertPathBuilder::Result::Clear() { paths.clear(); best_result_index = 0; } CertPathBuilder::CertPathBuilder( scoped_refptr cert, TrustStore* trust_store, CertPathBuilderDelegate* delegate, const der::GeneralizedTime& time, KeyPurpose key_purpose, InitialExplicitPolicy initial_explicit_policy, const std::set& user_initial_policy_set, InitialPolicyMappingInhibit initial_policy_mapping_inhibit, InitialAnyPolicyInhibit initial_any_policy_inhibit, Result* result) : cert_path_iter_(new CertPathIter(std::move(cert), trust_store)), delegate_(delegate), time_(time), key_purpose_(key_purpose), initial_explicit_policy_(initial_explicit_policy), user_initial_policy_set_(user_initial_policy_set), initial_policy_mapping_inhibit_(initial_policy_mapping_inhibit), initial_any_policy_inhibit_(initial_any_policy_inhibit), out_result_(result) { DCHECK(delegate); result->Clear(); // The TrustStore also implements the CertIssuerSource interface. AddCertIssuerSource(trust_store); } CertPathBuilder::~CertPathBuilder() = default; void CertPathBuilder::AddCertIssuerSource( CertIssuerSource* cert_issuer_source) { cert_path_iter_->AddCertIssuerSource(cert_issuer_source); } void CertPathBuilder::Run() { while (true) { std::unique_ptr result_path = std::make_unique(); if (!cert_path_iter_->GetNextPath(&result_path->certs, &result_path->last_cert_trust)) { // No more paths to check. return; } // Verify the entire certificate chain. VerifyCertificateChain( result_path->certs, result_path->last_cert_trust, delegate_, time_, key_purpose_, initial_explicit_policy_, user_initial_policy_set_, initial_policy_mapping_inhibit_, initial_any_policy_inhibit_, &result_path->user_constrained_policy_set, &result_path->errors); DVLOG(1) << "CertPathBuilder VerifyCertificateChain errors:\n" << result_path->errors.ToDebugString(result_path->certs); // Give the delegate a chance to add errors to the path. delegate_->CheckPathAfterVerification(result_path.get()); bool path_is_good = result_path->IsValid(); AddResultPath(std::move(result_path)); if (path_is_good) { // Found a valid path, return immediately. // TODO(mattm): add debug/test mode that tries all possible paths. return; } // Path did not verify. Try more paths. } } void CertPathBuilder::AddResultPath( std::unique_ptr result_path) { // TODO(mattm): set best_result_index based on number or severity of errors. if (result_path->IsValid()) out_result_->best_result_index = out_result_->paths.size(); // TODO(mattm): add flag to only return a single path or all attempted paths? out_result_->paths.push_back(std::move(result_path)); } } // namespace net