// Copyright 2012 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/nss_cert_database.h" #include #include #include #include #include #include #include #include #include #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/observer_list_threadsafe.h" #include "base/task/thread_pool.h" #include "base/threading/scoped_blocking_call.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "crypto/nss_util_internal.h" #include "crypto/scoped_nss_types.h" #include "net/base/net_errors.h" #include "net/cert/cert_database.h" #include "net/cert/internal/trust_store_nss.h" #include "net/cert/x509_certificate.h" #include "net/cert/x509_util_nss.h" #include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h" #include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h" #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) #include "crypto/chaps_support.h" #endif // PSM = Mozilla's Personal Security Manager. namespace psm = mozilla_security_manager; namespace net { namespace { using PK11HasAttributeSetFunction = CK_BBOOL (*)(PK11SlotInfo* slot, CK_OBJECT_HANDLE id, CK_ATTRIBUTE_TYPE type, PRBool haslock); // TODO(pneubeck): Move this class out of NSSCertDatabase and to the caller of // the c'tor of NSSCertDatabase, see https://crbug.com/395983 . // Helper that observes events from the NSSCertDatabase and forwards them to // the given CertDatabase. class CertNotificationForwarder : public NSSCertDatabase::Observer { public: explicit CertNotificationForwarder(CertDatabase* cert_db) : cert_db_(cert_db) {} CertNotificationForwarder(const CertNotificationForwarder&) = delete; CertNotificationForwarder& operator=(const CertNotificationForwarder&) = delete; ~CertNotificationForwarder() override = default; void OnTrustStoreChanged() override { cert_db_->NotifyObserversTrustStoreChanged(); } void OnClientCertStoreChanged() override { cert_db_->NotifyObserversClientCertStoreChanged(); } private: raw_ptr cert_db_; }; // TODO(https://crbug.com/1412591): once the other IsUntrusted impl is deleted, // rename this. bool IsUntrustedUsingTrustStore(const CERTCertificate* cert, bssl::CertificateTrust trust) { if (trust.IsDistrusted()) { return true; } // Self-signed certificates that don't have any trust bits set are untrusted. // Other certificates that don't have any trust bits set may still be trusted // if they chain up to a trust anchor. // TODO(mattm): this is weird, but just match the behavior of the existing // IsUntrusted function for now. if (SECITEM_CompareItem(&cert->derIssuer, &cert->derSubject) == SECEqual) { return !trust.IsTrustAnchor(); } return false; } } // namespace NSSCertDatabase::CertInfo::CertInfo() = default; NSSCertDatabase::CertInfo::CertInfo(CertInfo&& other) = default; NSSCertDatabase::CertInfo::~CertInfo() = default; NSSCertDatabase::CertInfo& NSSCertDatabase::CertInfo::operator=( NSSCertDatabase::CertInfo&& other) = default; NSSCertDatabase::ImportCertFailure::ImportCertFailure( ScopedCERTCertificate cert, int err) : certificate(std::move(cert)), net_error(err) {} NSSCertDatabase::ImportCertFailure::ImportCertFailure( ImportCertFailure&& other) = default; NSSCertDatabase::ImportCertFailure::~ImportCertFailure() = default; NSSCertDatabase::NSSCertDatabase(crypto::ScopedPK11Slot public_slot, crypto::ScopedPK11Slot private_slot) : public_slot_(std::move(public_slot)), private_slot_(std::move(private_slot)), observer_list_( base::MakeRefCounted>()) { CHECK(public_slot_); CertDatabase* cert_db = CertDatabase::GetInstance(); cert_notification_forwarder_ = std::make_unique(cert_db); AddObserver(cert_notification_forwarder_.get()); psm::EnsurePKCS12Init(); } NSSCertDatabase::~NSSCertDatabase() = default; void NSSCertDatabase::ListCerts(ListCertsCallback callback) { base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, base::BindOnce(&NSSCertDatabase::ListCertsImpl, crypto::ScopedPK11Slot()), std::move(callback)); } void NSSCertDatabase::ListCertsInSlot(ListCertsCallback callback, PK11SlotInfo* slot) { DCHECK(slot); base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, base::BindOnce(&NSSCertDatabase::ListCertsImpl, crypto::ScopedPK11Slot(PK11_ReferenceSlot(slot))), std::move(callback)); } void NSSCertDatabase::ListCertsInfo(ListCertsInfoCallback callback, NSSRootsHandling nss_roots_handling) { base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, base::BindOnce(&NSSCertDatabase::ListCertsInfoImpl, /*slot=*/nullptr, /*add_certs_info=*/true, nss_roots_handling), std::move(callback)); } #if BUILDFLAG(IS_CHROMEOS) crypto::ScopedPK11Slot NSSCertDatabase::GetSystemSlot() const { return crypto::ScopedPK11Slot(); } // static bool NSSCertDatabase::IsCertificateOnSlot(CERTCertificate* cert, PK11SlotInfo* slot) { if (!slot) return false; return PK11_FindCertInSlot(slot, cert, nullptr) != CK_INVALID_HANDLE; } #endif // BUILDFLAG(IS_CHROMEOS) crypto::ScopedPK11Slot NSSCertDatabase::GetPublicSlot() const { return crypto::ScopedPK11Slot(PK11_ReferenceSlot(public_slot_.get())); } crypto::ScopedPK11Slot NSSCertDatabase::GetPrivateSlot() const { if (!private_slot_) return crypto::ScopedPK11Slot(); return crypto::ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get())); } void NSSCertDatabase::ListModules(std::vector* modules, bool need_rw) const { modules->clear(); // The wincx arg is unused since we don't call PK11_SetIsLoggedInFunc. crypto::ScopedPK11SlotList slot_list( PK11_GetAllTokens(CKM_INVALID_MECHANISM, need_rw ? PR_TRUE : PR_FALSE, // needRW PR_TRUE, // loadCerts (unused) nullptr)); // wincx if (!slot_list) { LOG(ERROR) << "PK11_GetAllTokens failed: " << PORT_GetError(); return; } PK11SlotListElement* slot_element = PK11_GetFirstSafe(slot_list.get()); while (slot_element) { modules->push_back( crypto::ScopedPK11Slot(PK11_ReferenceSlot(slot_element->slot))); slot_element = PK11_GetNextSafe(slot_list.get(), slot_element, PR_FALSE); // restart } } bool NSSCertDatabase::SetCertTrust(CERTCertificate* cert, CertType type, TrustBits trust_bits) { bool success = psm::SetCertTrust(cert, type, trust_bits); if (success) { NotifyObserversTrustStoreChanged(); } return success; } int NSSCertDatabase::ImportFromPKCS12( PK11SlotInfo* slot_info, const std::string& data, const std::u16string& password, bool is_extractable, ScopedCERTCertificateList* imported_certs) { int result = psm::nsPKCS12Blob_Import(slot_info, data.data(), data.size(), password, is_extractable, imported_certs); if (result == OK) { NotifyObserversClientCertStoreChanged(); } return result; } // static int NSSCertDatabase::ExportToPKCS12(const ScopedCERTCertificateList& certs, const std::u16string& password, std::string* output) { return psm::nsPKCS12Blob_Export(output, certs, password); } CERTCertificate* NSSCertDatabase::FindRootInList( const ScopedCERTCertificateList& certificates) const { DCHECK_GT(certificates.size(), 0U); if (certificates.size() == 1) return certificates[0].get(); CERTCertificate* cert0 = certificates[0].get(); CERTCertificate* cert1 = certificates[1].get(); CERTCertificate* certn_2 = certificates[certificates.size() - 2].get(); CERTCertificate* certn_1 = certificates[certificates.size() - 1].get(); // Using CERT_CompareName is an alternative, except that it is broken until // NSS 3.32 (see https://bugzilla.mozilla.org/show_bug.cgi?id=1361197 ). if (SECITEM_CompareItem(&cert1->derIssuer, &cert0->derSubject) == SECEqual) return cert0; if (SECITEM_CompareItem(&certn_2->derIssuer, &certn_1->derSubject) == SECEqual) { return certn_1; } LOG(WARNING) << "certificate list is not a hierarchy"; return cert0; } int NSSCertDatabase::ImportUserCert(const std::string& data) { ScopedCERTCertificateList certificates = x509_util::CreateCERTCertificateListFromBytes( data.c_str(), data.size(), net::X509Certificate::FORMAT_AUTO); if (certificates.empty()) return ERR_CERT_INVALID; int result = psm::ImportUserCert(certificates[0].get(), GetPublicSlot()); if (result == OK) { NotifyObserversClientCertStoreChanged(); } return result; } int NSSCertDatabase::ImportUserCert(CERTCertificate* cert) { int result = psm::ImportUserCert(cert, GetPublicSlot()); if (result == OK) { NotifyObserversClientCertStoreChanged(); } return result; } bool NSSCertDatabase::ImportCACerts( const ScopedCERTCertificateList& certificates, TrustBits trust_bits, ImportCertFailureList* not_imported) { crypto::ScopedPK11Slot slot(GetPublicSlot()); CERTCertificate* root = FindRootInList(certificates); bool success = psm::ImportCACerts(slot.get(), certificates, root, trust_bits, not_imported); if (success) { NotifyObserversTrustStoreChanged(); } return success; } bool NSSCertDatabase::ImportServerCert( const ScopedCERTCertificateList& certificates, TrustBits trust_bits, ImportCertFailureList* not_imported) { crypto::ScopedPK11Slot slot(GetPublicSlot()); return psm::ImportServerCert(slot.get(), certificates, trust_bits, not_imported); // TODO(mattm): should generate OnTrustStoreChanged notification? The ability // to set a server cert as trusted isn't hooked up anywhere currently, but // technically we should generate a notification. } NSSCertDatabase::TrustBits NSSCertDatabase::GetCertTrust( const CERTCertificate* cert, CertType type) const { CERTCertTrust trust; SECStatus srv = CERT_GetCertTrust(cert, &trust); if (srv != SECSuccess) { LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError(); return TRUST_DEFAULT; } // We define our own more "friendly" TrustBits, which means we aren't able to // round-trip all possible NSS trust flag combinations. We try to map them in // a sensible way. switch (type) { case CA_CERT: { const unsigned kTrustedCA = CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA; const unsigned kCAFlags = kTrustedCA | CERTDB_TERMINAL_RECORD; TrustBits trust_bits = TRUST_DEFAULT; if ((trust.sslFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) trust_bits |= DISTRUSTED_SSL; else if (trust.sslFlags & kTrustedCA) trust_bits |= TRUSTED_SSL; if ((trust.emailFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) trust_bits |= DISTRUSTED_EMAIL; else if (trust.emailFlags & kTrustedCA) trust_bits |= TRUSTED_EMAIL; if ((trust.objectSigningFlags & kCAFlags) == CERTDB_TERMINAL_RECORD) trust_bits |= DISTRUSTED_OBJ_SIGN; else if (trust.objectSigningFlags & kTrustedCA) trust_bits |= TRUSTED_OBJ_SIGN; return trust_bits; } case SERVER_CERT: if (trust.sslFlags & CERTDB_TERMINAL_RECORD) { if (trust.sslFlags & CERTDB_TRUSTED) return TRUSTED_SSL; return DISTRUSTED_SSL; } return TRUST_DEFAULT; default: return TRUST_DEFAULT; } } bool NSSCertDatabase::DeleteCertAndKey(CERTCertificate* cert) { // This makes the assumption that if there was a matching private key, the // cert was probably a client cert, and if not, it may have been a trust // anchor or intemediate CA cert. This is used as a simple approximation as // otherwise this requires checking and combining multiple things // (basicConstraints if present, trust settings, etc). switch (DeleteCertAndKeyImpl(cert)) { case DeleteCertAndKeyResult::OK_NO_KEY: NotifyObserversTrustStoreChanged(); return true; case DeleteCertAndKeyResult::OK_FOUND_KEY: NotifyObserversClientCertStoreChanged(); return true; case DeleteCertAndKeyResult::ERROR: return false; } } void NSSCertDatabase::DeleteCertAndKeyAsync(ScopedCERTCertificate cert, DeleteCertCallback callback) { base::ThreadPool::PostTaskAndReplyWithResult( FROM_HERE, {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, base::BindOnce(&NSSCertDatabase::DeleteCertAndKeyImplScoped, std::move(cert)), base::BindOnce(&NSSCertDatabase::NotifyCertRemovalAndCallBack, weak_factory_.GetWeakPtr(), std::move(callback))); } // static bool NSSCertDatabase::IsUntrusted(const CERTCertificate* cert) { CERTCertTrust nsstrust; SECStatus rv = CERT_GetCertTrust(cert, &nsstrust); if (rv != SECSuccess) { LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError(); return false; } // The CERTCertTrust structure contains three trust records: // sslFlags, emailFlags, and objectSigningFlags. The three // trust records are independent of each other. // // If the CERTDB_TERMINAL_RECORD bit in a trust record is set, // then that trust record is a terminal record. A terminal // record is used for explicit trust and distrust of an // end-entity or intermediate CA cert. // // In a terminal record, if neither CERTDB_TRUSTED_CA nor // CERTDB_TRUSTED is set, then the terminal record means // explicit distrust. On the other hand, if the terminal // record has either CERTDB_TRUSTED_CA or CERTDB_TRUSTED bit // set, then the terminal record means explicit trust. // // For a root CA, the trust record does not have // the CERTDB_TERMINAL_RECORD bit set. static const unsigned int kTrusted = CERTDB_TRUSTED_CA | CERTDB_TRUSTED; if ((nsstrust.sslFlags & CERTDB_TERMINAL_RECORD) != 0 && (nsstrust.sslFlags & kTrusted) == 0) { return true; } if ((nsstrust.emailFlags & CERTDB_TERMINAL_RECORD) != 0 && (nsstrust.emailFlags & kTrusted) == 0) { return true; } if ((nsstrust.objectSigningFlags & CERTDB_TERMINAL_RECORD) != 0 && (nsstrust.objectSigningFlags & kTrusted) == 0) { return true; } // Self-signed certificates that don't have any trust bits set are untrusted. // Other certificates that don't have any trust bits set may still be trusted // if they chain up to a trust anchor. if (SECITEM_CompareItem(&cert->derIssuer, &cert->derSubject) == SECEqual) { return (nsstrust.sslFlags & kTrusted) == 0 && (nsstrust.emailFlags & kTrusted) == 0 && (nsstrust.objectSigningFlags & kTrusted) == 0; } return false; } // static bool NSSCertDatabase::IsWebTrustAnchor(const CERTCertificate* cert) { CERTCertTrust nsstrust; SECStatus rv = CERT_GetCertTrust(cert, &nsstrust); if (rv != SECSuccess) { LOG(ERROR) << "CERT_GetCertTrust failed with error " << PORT_GetError(); return false; } // Note: This should return true iff a net::TrustStoreNSS instantiated with // SECTrustType trustSSL would classify |cert| as a trust anchor. const unsigned int ssl_trust_flags = nsstrust.sslFlags; // Determine if the certificate is a trust anchor. if ((ssl_trust_flags & CERTDB_TRUSTED_CA) == CERTDB_TRUSTED_CA) { return true; } return false; } // static bool NSSCertDatabase::IsReadOnly(const CERTCertificate* cert) { PK11SlotInfo* slot = cert->slot; return slot && PK11_IsReadOnly(slot); } // static bool NSSCertDatabase::IsHardwareBacked(const CERTCertificate* cert) { PK11SlotInfo* slot = cert->slot; if (!slot) return false; #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) // For keys in Chaps, it's possible that they are truly hardware backed, or // they can be software-backed, such as if the creator requested it, or if the // TPM does not support the key algorithm. Chaps sets a kKeyInSoftware // attribute to true for private keys that aren't wrapped by the TPM. if (crypto::IsSlotProvidedByChaps(slot)) { constexpr CK_ATTRIBUTE_TYPE kKeyInSoftware = CKA_VENDOR_DEFINED + 5; SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert( slot, const_cast(cert), nullptr); // PK11_HasAttributeSet returns true if the object in the given slot has // the attribute set to true. Otherwise it returns false. if (private_key && PK11_HasAttributeSet(slot, private_key->pkcs11ID, kKeyInSoftware, /*haslock=*/PR_FALSE)) { return false; } // All keys in chaps without the attribute are hardware backed. return true; } #endif return PK11_IsHW(slot); } void NSSCertDatabase::AddObserver(Observer* observer) { observer_list_->AddObserver(observer); } void NSSCertDatabase::RemoveObserver(Observer* observer) { observer_list_->RemoveObserver(observer); } // static ScopedCERTCertificateList NSSCertDatabase::ExtractCertificates( CertInfoList certs_info) { ScopedCERTCertificateList certs; certs.reserve(certs_info.size()); for (auto& cert_info : certs_info) certs.push_back(std::move(cert_info.cert)); return certs; } // static ScopedCERTCertificateList NSSCertDatabase::ListCertsImpl( crypto::ScopedPK11Slot slot) { CertInfoList certs_info = ListCertsInfoImpl( std::move(slot), /*add_certs_info=*/false, NSSRootsHandling::kInclude); return ExtractCertificates(std::move(certs_info)); } // static NSSCertDatabase::CertInfoList NSSCertDatabase::ListCertsInfoImpl( crypto::ScopedPK11Slot slot, bool add_certs_info, NSSRootsHandling nss_roots_handling) { // This method may acquire the NSS lock or reenter this code via extension // hooks (such as smart card UI). To ensure threads are not starved or // deadlocked, the base::ScopedBlockingCall below increments the thread pool // capacity if this method takes too much time to run. base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); if (nss_roots_handling == NSSRootsHandling::kExclude) { // This assumes that using a new TrustStoreNSS instance on each // ListCertsInfo call is not expensive. If that ever changes this might // need to be rethought. TrustStoreNSS trust_store_nss( slot ? TrustStoreNSS::UserSlotTrustSetting( crypto::ScopedPK11Slot(PK11_ReferenceSlot(slot.get()))) : TrustStoreNSS::UseTrustFromAllUserSlots()); std::vector cert_list( trust_store_nss.ListCertsIgnoringNSSRoots()); CertInfoList certs_info; for (const auto& node : cert_list) { CertInfo cert_info; cert_info.cert = x509_util::DupCERTCertificate(node.cert.get()); if (add_certs_info) { cert_info.untrusted = IsUntrustedUsingTrustStore(cert_info.cert.get(), node.trust); cert_info.web_trust_anchor = node.trust.IsTrustAnchor(); cert_info.on_read_only_slot = IsReadOnly(cert_info.cert.get()); cert_info.hardware_backed = IsHardwareBacked(cert_info.cert.get()); } certs_info.push_back(std::move(cert_info)); } return certs_info; } else { CertInfoList certs_info; crypto::ScopedCERTCertList cert_list = nullptr; if (slot) { cert_list.reset(PK11_ListCertsInSlot(slot.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) << (slot ? "PK11_ListCertsInSlot" : "PK11_ListCerts") << " returned null"; return certs_info; } CERTCertListNode* node; for (node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { CertInfo cert_info; cert_info.cert = x509_util::DupCERTCertificate(node->cert); if (add_certs_info) { cert_info.on_read_only_slot = IsReadOnly(cert_info.cert.get()); cert_info.untrusted = IsUntrusted(cert_info.cert.get()); cert_info.web_trust_anchor = IsWebTrustAnchor(cert_info.cert.get()); cert_info.hardware_backed = IsHardwareBacked(cert_info.cert.get()); } certs_info.push_back(std::move(cert_info)); } return certs_info; } } void NSSCertDatabase::NotifyCertRemovalAndCallBack( DeleteCertCallback callback, DeleteCertAndKeyResult result) { // This makes the assumption that if there was a matching private key, the // cert was probably a client cert, and if not, it may have been a trust // anchor or intemediate CA cert. switch (result) { case DeleteCertAndKeyResult::OK_NO_KEY: NotifyObserversTrustStoreChanged(); std::move(callback).Run(true); break; case DeleteCertAndKeyResult::OK_FOUND_KEY: NotifyObserversClientCertStoreChanged(); std::move(callback).Run(true); break; case DeleteCertAndKeyResult::ERROR: std::move(callback).Run(false); break; } } void NSSCertDatabase::NotifyObserversTrustStoreChanged() { observer_list_->Notify(FROM_HERE, &Observer::OnTrustStoreChanged); } void NSSCertDatabase::NotifyObserversClientCertStoreChanged() { observer_list_->Notify(FROM_HERE, &Observer::OnClientCertStoreChanged); } // static NSSCertDatabase::DeleteCertAndKeyResult NSSCertDatabase::DeleteCertAndKeyImpl( CERTCertificate* cert) { // This method may acquire the NSS lock or reenter this code via extension // hooks (such as smart card UI). To ensure threads are not starved or // deadlocked, the base::ScopedBlockingCall below increments the thread pool // capacity if this method takes too much time to run. base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); // For some reason, PK11_DeleteTokenCertAndKey only calls // SEC_DeletePermCertificate if the private key is found. So, we check // whether a private key exists before deciding which function to call to // delete the cert. SECKEYPrivateKey* privKey = PK11_FindKeyByAnyCert(cert, nullptr); if (privKey) { SECKEY_DestroyPrivateKey(privKey); if (PK11_DeleteTokenCertAndKey(cert, nullptr)) { LOG(ERROR) << "PK11_DeleteTokenCertAndKey failed: " << PORT_GetError(); return DeleteCertAndKeyResult::ERROR; } return DeleteCertAndKeyResult::OK_FOUND_KEY; } else { if (SEC_DeletePermCertificate(cert)) { LOG(ERROR) << "SEC_DeletePermCertificate failed: " << PORT_GetError(); return DeleteCertAndKeyResult::ERROR; } return DeleteCertAndKeyResult::OK_NO_KEY; } } // static NSSCertDatabase::DeleteCertAndKeyResult NSSCertDatabase::DeleteCertAndKeyImplScoped(ScopedCERTCertificate cert) { return NSSCertDatabase::DeleteCertAndKeyImpl(cert.get()); } } // namespace net