// Copyright 2016 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/cert/internal/trust_store_nss.h" #include #include #include #include #include #include #include #include #include #include "base/hash/sha1.h" #include "base/logging.h" #include "base/notreached.h" #include "base/strings/string_number_conversions.h" #include "crypto/nss_util.h" #include "crypto/nss_util_internal.h" #include "crypto/scoped_nss_types.h" #include "net/base/features.h" #include "net/cert/internal/trust_store_features.h" #include "net/cert/known_roots_nss.h" #include "net/cert/pki/cert_errors.h" #include "net/cert/pki/parsed_certificate.h" #include "net/cert/pki/trust_store.h" #include "net/cert/scoped_nss_types.h" #include "net/cert/x509_util.h" #include "net/cert/x509_util_nss.h" namespace net { namespace { const void* kResultDebugDataKey = &kResultDebugDataKey; TrustStoreNSS::ResultDebugData::SlotFilterType GetSlotFilterType( const TrustStoreNSS::UserSlotTrustSetting& user_slot_trust_setting) { if (absl::holds_alternative( user_slot_trust_setting)) { return TrustStoreNSS::ResultDebugData::SlotFilterType::kDontFilter; } if (absl::get(user_slot_trust_setting) == nullptr) { return TrustStoreNSS::ResultDebugData::SlotFilterType::kDoNotAllowUserSlots; } return TrustStoreNSS::ResultDebugData::SlotFilterType:: kAllowSpecifiedUserSlot; } struct FreePK11GenericObjects { void operator()(PK11GenericObject* x) const { if (x) { PK11_DestroyGenericObjects(x); } } }; using ScopedPK11GenericObjects = std::unique_ptr; // Get the list of all slots `nss_cert` is present in, along with the object // handle of the cert in each of those slots. // // (Note that there is a PK11_GetAllSlotsForCert function that *seems* like it // would be useful here, however it does not actually return all relevant // slots.) std::vector> GetAllSlotsAndHandlesForCert(CERTCertificate* nss_cert) { std::vector> r; crypto::AutoSECMODListReadLock lock_id; for (const SECMODModuleList* item = SECMOD_GetDefaultModuleList(); item != nullptr; item = item->next) { for (int i = 0; i < item->module->slotCount; ++i) { PK11SlotInfo* slot = item->module->slots[i]; if (PK11_IsPresent(slot)) { CK_OBJECT_HANDLE handle = PK11_FindCertInSlot(slot, nss_cert, nullptr); if (handle != CK_INVALID_HANDLE) { r.emplace_back(PK11_ReferenceSlot(slot), handle); } } } } return r; } bool IsMozillaCaPolicyProvided(PK11SlotInfo* slot, CK_OBJECT_HANDLE cert_handle) { return PK11_HasRootCerts(slot) && PK11_HasAttributeSet(slot, cert_handle, CKA_NSS_MOZILLA_CA_POLICY, /*haslock=*/PR_FALSE) == CK_TRUE; } bool IsCertOnlyInNSSRoots(CERTCertificate* cert) { std::vector> slots_and_handles_for_cert = GetAllSlotsAndHandlesForCert(cert); for (const auto& [slot, handle] : slots_and_handles_for_cert) { if (IsMozillaCaPolicyProvided(slot.get(), handle)) { // Cert is an NSS root. Continue looking to see if it also is present in // another slot. continue; } // Found cert in a non-NSS roots slot. return false; } // Cert was only found in NSS roots (or was not in any slots, but that // shouldn't happen.) return true; } } // namespace TrustStoreNSS::ResultDebugData::ResultDebugData( bool ignore_system_trust_settings, SlotFilterType slot_filter_type) : ignore_system_trust_settings_(ignore_system_trust_settings), slot_filter_type_(slot_filter_type) {} // static const TrustStoreNSS::ResultDebugData* TrustStoreNSS::ResultDebugData::Get( const base::SupportsUserData* debug_data) { return static_cast( debug_data->GetUserData(kResultDebugDataKey)); } // static void TrustStoreNSS::ResultDebugData::Create( bool ignore_system_trust_settings, SlotFilterType slot_filter_type, base::SupportsUserData* debug_data) { debug_data->SetUserData(kResultDebugDataKey, std::make_unique( ignore_system_trust_settings, slot_filter_type)); } std::unique_ptr TrustStoreNSS::ResultDebugData::Clone() { return std::make_unique(*this); } TrustStoreNSS::ListCertsResult::ListCertsResult(ScopedCERTCertificate cert, CertificateTrust trust) : cert(std::move(cert)), trust(trust) {} TrustStoreNSS::ListCertsResult::~ListCertsResult() = default; TrustStoreNSS::ListCertsResult::ListCertsResult(ListCertsResult&& other) = default; TrustStoreNSS::ListCertsResult& TrustStoreNSS::ListCertsResult::operator=( ListCertsResult&& other) = default; TrustStoreNSS::TrustStoreNSS(SystemTrustSetting system_trust_setting, UserSlotTrustSetting user_slot_trust_setting) : ignore_system_trust_settings_(system_trust_setting == kIgnoreSystemTrust), user_slot_trust_setting_(std::move(user_slot_trust_setting)) {} TrustStoreNSS::~TrustStoreNSS() = default; void TrustStoreNSS::SyncGetIssuersOf(const ParsedCertificate* cert, ParsedCertificateList* issuers) { 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 = cert->tbs().issuer_tlv.Length(); name.data = const_cast(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. crypto::ScopedCERTCertList found_certs(CERT_CreateSubjectCertList( nullptr /* certList */, CERT_GetDefaultCertDB(), &name, PR_Now() /* sorttime */, PR_FALSE /* validOnly */)); if (!found_certs) return; for (CERTCertListNode* node = CERT_LIST_HEAD(found_certs); !CERT_LIST_END(node, found_certs); node = CERT_LIST_NEXT(node)) { CertErrors parse_errors; std::shared_ptr cur_cert = ParsedCertificate::Create( x509_util::CreateCryptoBuffer(base::make_span( node->cert->derCert.data, node->cert->derCert.len)), {}, &parse_errors); if (!cur_cert) { // TODO(crbug.com/634443): return errors better. LOG(ERROR) << "Error parsing issuer certificate:\n" << parse_errors.ToDebugString(); continue; } issuers->push_back(std::move(cur_cert)); } } CertificateTrust TrustStoreNSS::GetTrust(const ParsedCertificate* cert, base::SupportsUserData* debug_data) { crypto::EnsureNSSInit(); if (debug_data) { ResultDebugData::Create(ignore_system_trust_settings_, GetSlotFilterType(user_slot_trust_setting_), debug_data); } // In theory we could also do better multi-profile slot filtering using a // similar approach as GetTrustIgnoringSystemTrust, however it makes the // logic more complicated and isn't really worth doing since we'll be // removing the old path entirely. Also keeping the old path unmodified is // better for ensuring that the temporary fallback policy actually falls back // to the same old behavior. if (ignore_system_trust_settings_) { return GetTrustIgnoringSystemTrust(cert, debug_data); } else { return GetTrustWithSystemTrust(cert, debug_data); } } std::vector TrustStoreNSS::ListCertsIgnoringNSSRoots() { std::vector results; crypto::ScopedCERTCertList cert_list; if (absl::holds_alternative( user_slot_trust_setting_)) { if (absl::get(user_slot_trust_setting_) == nullptr) { return results; } cert_list.reset(PK11_ListCertsInSlot( absl::get(user_slot_trust_setting_).get())); } else { cert_list.reset(PK11_ListCerts(PK11CertListUnique, nullptr)); } // PK11_ListCerts[InSlot] can return nullptr, e.g. because the PKCS#11 token // that was backing the specified slot is not available anymore. // Treat it as no certificates being present on the slot. if (!cert_list) { LOG(WARNING) << (absl::holds_alternative( user_slot_trust_setting_) ? "PK11_ListCertsInSlot" : "PK11_ListCerts") << " returned null"; return results; } CERTCertListNode* node; for (node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { if (IsCertOnlyInNSSRoots(node->cert)) { continue; } results.emplace_back(x509_util::DupCERTCertificate(node->cert), GetTrustIgnoringSystemTrust(node->cert, nullptr)); } return results; } // TODO(https://crbug.com/1340420): add histograms? (how often hits fast vs // medium vs slow path, timing of fast/medium/slow path/all, etc?) // TODO(https://crbug.com/1340420): NSS also seemingly has some magical // trusting of any self-signed cert with CKA_ID=0, if it doesn't have a // matching trust object. Do we need to do that too? (this pk11_isID0 thing: // https://searchfox.org/nss/source/lib/pk11wrap/pk11cert.c#357) CertificateTrust TrustStoreNSS::GetTrustIgnoringSystemTrust( const ParsedCertificate* cert, base::SupportsUserData* debug_data) const { // If trust settings are only being used from a specified slot, and that slot // is nullptr, there's nothing to do. This corresponds to the case where we // wanted to get the builtin roots from NSS still but not user-added roots. // Since the built-in roots are now coming from Chrome Root Store in this // case, there is nothing to do here. // // (This ignores slots that would have been allowed by the "read-only // internal slots" part of IsCertAllowedForTrust, I don't think that actually // matters though.) // // TODO(https://crbug.com/1412591): once the non-CRS paths have been removed, // perhaps remove this entirely and just have the caller not create a // TrustStoreNSS at all in this case (or does it still need the // SyncGetIssuersOf to find NSS temp certs in that case?) if (absl::holds_alternative( user_slot_trust_setting_) && absl::get(user_slot_trust_setting_) == nullptr) { return CertificateTrust::ForUnspecified(); } SECItem der_cert; der_cert.data = const_cast(cert->der_cert().UnsafeData()); der_cert.len = base::checked_cast(cert->der_cert().Length()); der_cert.type = siDERCertBuffer; // Find a matching NSS certificate object, if any. Note that NSS trust // objects can also be keyed on issuer+serial and match any such cert. This // is only used for distrust and apparently only in the NSS builtin roots // certs module. Therefore, it should be safe to use the more efficient // CERT_FindCertByDERCert to avoid having to have NSS parse the certificate // and create a structure for it if the cert doesn't already exist in any of // the loaded NSS databases. ScopedCERTCertificate nss_cert( CERT_FindCertByDERCert(CERT_GetDefaultCertDB(), &der_cert)); if (!nss_cert) { DVLOG(1) << "skipped cert that has no CERTCertificate already"; return CertificateTrust::ForUnspecified(); } return GetTrustIgnoringSystemTrust(nss_cert.get(), debug_data); } CertificateTrust TrustStoreNSS::GetTrustIgnoringSystemTrust( CERTCertificate* nss_cert, base::SupportsUserData* debug_data) const { // See if NSS has any trust settings for the certificate at all. If not, // there is no point in doing further work. CERTCertTrust nss_cert_trust; if (CERT_GetCertTrust(nss_cert, &nss_cert_trust) != SECSuccess) { DVLOG(1) << "skipped cert that has no trust settings"; return CertificateTrust::ForUnspecified(); } // If there were trust settings, we may not be able to use the NSS calculated // trust settings directly, since we don't know which slot those settings // came from. Do a more careful check to only honor trust settings from slots // we care about. std::vector> slots_and_handles_for_cert = GetAllSlotsAndHandlesForCert(nss_cert); // Generally this shouldn't happen, though it is possible (ex, a builtin // distrust record with no matching cert in the builtin trust store could // match a NSS temporary cert that doesn't exist in any slot. Ignoring that // is okay. Theoretically there maybe could be trust records with no matching // cert in user slots? I don't know how that can actually happen though.) if (slots_and_handles_for_cert.empty()) { DVLOG(1) << "skipped cert that has no slots"; return CertificateTrust::ForUnspecified(); } // List of trustOrder, slot pairs. std::vector> slots_to_check; for (const auto& [slotref, handle] : slots_and_handles_for_cert) { PK11SlotInfo* slot = slotref.get(); DVLOG(1) << "found cert in slot:" << PK11_GetSlotName(slot) << " token:" << PK11_GetTokenName(slot) << " module trustOrder: " << PK11_GetModule(slot)->trustOrder; if (absl::holds_alternative( user_slot_trust_setting_) && slot != absl::get(user_slot_trust_setting_).get()) { DVLOG(1) << "skipping slot " << PK11_GetSlotName(slot) << ", it's not user_slot_trust_setting_"; continue; } if (IsMozillaCaPolicyProvided(slot, handle)) { DVLOG(1) << "skipping slot " << PK11_GetSlotName(slot) << ", this is mozilla ca policy provided"; continue; } int trust_order = PK11_GetModule(slot)->trustOrder; slots_to_check.emplace_back(trust_order, slot); } if (slots_to_check.size() == slots_and_handles_for_cert.size()) { DVLOG(1) << "cert is only in allowed slots, using NSS calculated trust"; return GetTrustForNSSTrust(nss_cert_trust); } if (slots_to_check.empty()) { DVLOG(1) << "cert is only in disallowed slots, skipping"; return CertificateTrust::ForUnspecified(); } DVLOG(1) << "cert is in both allowed and disallowed slots, doing manual " "trust calculation"; // Use PK11_FindGenericObjects + PK11_ReadRawAttribute to calculate the trust // using only the slots we care about. (Some example code: // https://searchfox.org/nss/source/gtests/pk11_gtest/pk11_import_unittest.cc#131) // // TODO(https://crbug.com/1340420): consider adding caching here if metrics // show a need. If caching is added, note that NSS has no change notification // APIs so we'd at least want to listen for CertDatabase notifications to // clear the cache. (There are multiple approaches possible, could cache the // hash->trust mappings on a per-slot basis, or just cache the end result for // each cert, etc.) base::SHA1Digest cert_sha1 = base::SHA1HashSpan( base::make_span(nss_cert->derCert.data, nss_cert->derCert.len)); // Check the slots in trustOrder ordering. Lower trustOrder values are higher // priority, so we can return as soon as we find a matching trust object. std::sort(slots_to_check.begin(), slots_to_check.end()); for (const auto& [_, slot] : slots_to_check) { DVLOG(1) << "looking for trust in slot " << PK11_GetSlotName(slot) << " token " << PK11_GetTokenName(slot); ScopedPK11GenericObjects objs(PK11_FindGenericObjects(slot, CKO_NSS_TRUST)); if (!objs) { DVLOG(1) << "no trust objects in slot"; continue; } for (PK11GenericObject* obj = objs.get(); obj != nullptr; obj = PK11_GetNextGenericObject(obj)) { crypto::ScopedSECItem sha1_hash_attr(SECITEM_AllocItem(/*arena=*/nullptr, /*item=*/nullptr, /*len=*/0)); SECStatus rv = PK11_ReadRawAttribute( PK11_TypeGeneric, obj, CKA_CERT_SHA1_HASH, sha1_hash_attr.get()); if (rv != SECSuccess) { DVLOG(1) << "trust object has no CKA_CERT_SHA1_HASH attr"; continue; } base::span trust_obj_sha1 = base::make_span( sha1_hash_attr->data, sha1_hash_attr->data + sha1_hash_attr->len); DVLOG(1) << "found trust object for sha1 " << base::HexEncode(trust_obj_sha1); if (!std::equal(trust_obj_sha1.begin(), trust_obj_sha1.end(), cert_sha1.begin(), cert_sha1.end())) { DVLOG(1) << "trust object does not match target cert hash, skipping"; continue; } DVLOG(1) << "trust object matches target cert hash"; crypto::ScopedSECItem trust_attr(SECITEM_AllocItem(/*arena=*/nullptr, /*item=*/nullptr, /*len=*/0)); rv = PK11_ReadRawAttribute(PK11_TypeGeneric, obj, CKA_TRUST_SERVER_AUTH, trust_attr.get()); if (rv != SECSuccess) { DVLOG(1) << "trust object for " << base::HexEncode(trust_obj_sha1) << "has no CKA_TRUST_x attr"; continue; } DVLOG(1) << "trust " << base::HexEncode(base::make_span( trust_attr->data, trust_attr->data + trust_attr->len)) << " for sha1 " << base::HexEncode(trust_obj_sha1); CK_TRUST trust; if (trust_attr->len != sizeof(trust)) { DVLOG(1) << "trust is wrong size? skipping"; continue; } // This matches how pk11_GetTrustField in NSS converts the raw trust // object to a CK_TRUST (actually an unsigned long). // https://searchfox.org/nss/source/lib/pk11wrap/pk11nobj.c#37 memcpy(&trust, trust_attr->data, trust_attr->len); // This doesn't handle the "TrustAnchorOrLeaf" combination, it's unclear // how that is represented. But it doesn't really matter since the only // case that would come up is if someone took one of the NSS builtin // roots and then also locally marked it as trusted as both a CA and a // leaf, which is non-sensical. Testing shows that will end up marked as // CKT_NSS_TRUSTED_DELEGATOR, which is fine. switch (trust) { case CKT_NSS_TRUSTED: if (base::FeatureList::IsEnabled( features::kTrustStoreTrustedLeafSupport)) { DVLOG(1) << "CKT_NSS_TRUSTED -> trusted leaf"; return CertificateTrust::ForTrustedLeaf(); } else { DVLOG(1) << "CKT_NSS_TRUSTED -> unspecified"; return CertificateTrust::ForUnspecified(); } case CKT_NSS_TRUSTED_DELEGATOR: { DVLOG(1) << "CKT_NSS_TRUSTED_DELEGATOR -> trust anchor"; const bool enforce_anchor_constraints = IsLocalAnchorConstraintsEnforcementEnabled(); return CertificateTrust::ForTrustAnchor() .WithEnforceAnchorConstraints(enforce_anchor_constraints) .WithEnforceAnchorExpiry(enforce_anchor_constraints); } case CKT_NSS_MUST_VERIFY_TRUST: case CKT_NSS_VALID_DELEGATOR: DVLOG(1) << "CKT_NSS_MUST_VERIFY_TRUST or CKT_NSS_VALID_DELEGATOR -> " "unspecified"; return CertificateTrust::ForUnspecified(); case CKT_NSS_NOT_TRUSTED: DVLOG(1) << "CKT_NSS_NOT_TRUSTED -> distrusted"; return CertificateTrust::ForDistrusted(); case CKT_NSS_TRUST_UNKNOWN: DVLOG(1) << "CKT_NSS_TRUST_UNKNOWN trust value - skip"; break; default: DVLOG(1) << "unhandled trust value - skip"; break; } } } DVLOG(1) << "no suitable NSS trust record found"; return CertificateTrust::ForUnspecified(); } CertificateTrust TrustStoreNSS::GetTrustWithSystemTrust( const ParsedCertificate* cert, base::SupportsUserData* debug_data) const { // TODO(eroman): Inefficient -- path building will convert between // CERTCertificate and ParsedCertificate representations multiple times // (when getting the issuers, and again here). // Note that trust records in NSS are keyed on issuer + serial, and there // exist builtin distrust records for which a matching certificate is not // included in the builtin cert list. Therefore, create a temp NSS cert even // if no existing cert matches. (Eg, this uses CERT_NewTempCertificate, not // CERT_FindCertByDERCert.) ScopedCERTCertificate nss_cert(x509_util::CreateCERTCertificateFromBytes( cert->der_cert().UnsafeData(), cert->der_cert().Length())); if (!nss_cert) { return CertificateTrust::ForUnspecified(); } if (!IsCertAllowedForTrust(nss_cert.get())) { return CertificateTrust::ForUnspecified(); } // Determine the trustedness of the matched certificate. CERTCertTrust nss_trust; if (CERT_GetCertTrust(nss_cert.get(), &nss_trust) != SECSuccess) { return CertificateTrust::ForUnspecified(); } CertificateTrust trust = GetTrustForNSSTrust(nss_trust); if (trust.enforce_anchor_constraints && IsKnownRoot(nss_cert.get())) { trust.enforce_anchor_constraints = false; trust.enforce_anchor_expiry = false; } return trust; } CertificateTrust TrustStoreNSS::GetTrustForNSSTrust( const CERTCertTrust& trust) const { unsigned int trust_flags = SEC_GET_TRUST_FLAGS(&trust, trustSSL); // Determine if the certificate is distrusted. if ((trust_flags & (CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED_CA | CERTDB_TRUSTED)) == CERTDB_TERMINAL_RECORD) { return CertificateTrust::ForDistrusted(); } bool is_trusted_ca = false; bool is_trusted_leaf = false; const bool enforce_anchor_constraints = IsLocalAnchorConstraintsEnforcementEnabled(); // Determine if the certificate is a trust anchor. if ((trust_flags & CERTDB_TRUSTED_CA) == CERTDB_TRUSTED_CA) { is_trusted_ca = true; } if (base::FeatureList::IsEnabled(features::kTrustStoreTrustedLeafSupport)) { constexpr unsigned int kTrustedPeerBits = CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED; if ((trust_flags & kTrustedPeerBits) == kTrustedPeerBits) { is_trusted_leaf = true; } } if (is_trusted_ca && is_trusted_leaf) { return CertificateTrust::ForTrustAnchorOrLeaf() .WithEnforceAnchorConstraints(enforce_anchor_constraints) .WithEnforceAnchorExpiry(enforce_anchor_constraints); } else if (is_trusted_ca) { return CertificateTrust::ForTrustAnchor() .WithEnforceAnchorConstraints(enforce_anchor_constraints) .WithEnforceAnchorExpiry(enforce_anchor_constraints); } else if (is_trusted_leaf) { return CertificateTrust::ForTrustedLeaf(); } return CertificateTrust::ForUnspecified(); } bool TrustStoreNSS::IsCertAllowedForTrust(CERTCertificate* cert) const { if (absl::holds_alternative( user_slot_trust_setting_)) { return true; } crypto::ScopedPK11SlotList slots_for_cert( PK11_GetAllSlotsForCert(cert, nullptr)); if (!slots_for_cert) return false; for (PK11SlotListElement* slot_element = PK11_GetFirstSafe(slots_for_cert.get()); slot_element; slot_element = PK11_GetNextSafe(slots_for_cert.get(), slot_element, /*restart=*/PR_FALSE)) { PK11SlotInfo* slot = slot_element->slot; bool allow_slot = // Allow the root certs module. PK11_HasRootCerts(slot) || // Allow read-only internal slots. (PK11_IsInternal(slot) && !PK11_IsRemovable(slot)) || // Allow configured user slot if specified. (absl::holds_alternative( user_slot_trust_setting_) && slot == absl::get(user_slot_trust_setting_).get()); if (allow_slot) { PK11_FreeSlotListElement(slots_for_cert.get(), slot_element); return true; } } return false; } } // namespace net