From 50887394d6bc8ea7aacf128df78961761baf21fe Mon Sep 17 00:00:00 2001 From: Mark Goodwin Date: Wed, 20 Mar 2019 17:00:47 +0000 Subject: [PATCH] Bug 1429796 Cleanup storage in CertBlocklist to allow easy addition of new types of pair (e.g. whitelist entries) r=keeler Differential Revision: https://phabricator.services.mozilla.com/D17668 --HG-- extra : moz-landing-system : lando --- Cargo.lock | 15 + netwerk/base/nsNetUtil.cpp | 4 +- .../certverifier/NSSCertDBTrustDomain.cpp | 11 +- security/certverifier/NSSCertDBTrustDomain.h | 4 +- security/manager/ssl/CSTrustDomain.cpp | 12 +- security/manager/ssl/CSTrustDomain.h | 4 +- security/manager/ssl/CertBlocklist.cpp | 626 -------------- security/manager/ssl/CertBlocklist.h | 89 -- .../manager/ssl/ContentSignatureVerifier.h | 2 + security/manager/ssl/DataStorage.cpp | 1 + security/manager/ssl/NSSErrorsService.h | 2 + security/manager/ssl/cert_storage/Cargo.toml | 14 + .../ssl/cert_storage/src/cert_storage.cpp | 23 + .../ssl/cert_storage/src/cert_storage.h | 29 + security/manager/ssl/cert_storage/src/lib.rs | 766 ++++++++++++++++++ security/manager/ssl/components.conf | 8 +- security/manager/ssl/moz.build | 4 +- security/manager/ssl/nsICertBlocklist.idl | 64 -- security/manager/ssl/nsICertStorage.idl | 116 +++ security/manager/ssl/nsNSSModule.cpp | 7 +- .../mochitest/browser/browser_certViewer.js | 9 +- ...cert_blocklist.js => test_cert_storage.js} | 109 +-- security/manager/ssl/tests/unit/xpcshell.ini | 2 +- services/common/blocklist-clients.js | 33 +- toolkit/library/rust/shared/Cargo.toml | 1 + toolkit/library/rust/shared/lib.rs | 1 + xpcom/build/Services.py | 5 + 27 files changed, 1041 insertions(+), 920 deletions(-) delete mode 100644 security/manager/ssl/CertBlocklist.cpp delete mode 100644 security/manager/ssl/CertBlocklist.h create mode 100644 security/manager/ssl/cert_storage/Cargo.toml create mode 100644 security/manager/ssl/cert_storage/src/cert_storage.cpp create mode 100644 security/manager/ssl/cert_storage/src/cert_storage.h create mode 100644 security/manager/ssl/cert_storage/src/lib.rs delete mode 100644 security/manager/ssl/nsICertBlocklist.idl create mode 100644 security/manager/ssl/nsICertStorage.idl rename security/manager/ssl/tests/unit/{test_cert_blocklist.js => test_cert_storage.js} (73%) diff --git a/Cargo.lock b/Cargo.lock index a1789743e6e7..b0ab3a582e4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,6 +376,20 @@ name = "cc" version = "1.0.23" source = "git+https://github.com/glandium/cc-rs?branch=1.0.23-clang-cl-aarch64#2aa71628b1261b5515bd8668afca591669ba195d" +[[package]] +name = "cert_storage" +version = "0.0.1" +dependencies = [ + "base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nserror 0.1.0", + "nsstring 0.1.0", + "rkv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "style 0.0.1", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "xpcom 0.1.0", +] + [[package]] name = "cexpr" version = "0.3.3" @@ -1127,6 +1141,7 @@ dependencies = [ "arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "audioipc-client 0.4.0", "audioipc-server 0.2.3", + "cert_storage 0.0.1", "cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "cubeb-pulse 0.2.0", "cubeb-sys 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/netwerk/base/nsNetUtil.cpp b/netwerk/base/nsNetUtil.cpp index ac8093143c20..982ab8f931f4 100644 --- a/netwerk/base/nsNetUtil.cpp +++ b/netwerk/base/nsNetUtil.cpp @@ -80,7 +80,7 @@ #include "nsHttpHandler.h" #include "nsNSSComponent.h" #include "nsIRedirectHistoryEntry.h" -#include "nsICertBlocklist.h" +#include "nsICertStorage.h" #include "nsICertOverrideService.h" #include "nsQueryObject.h" #include "mozIThirdPartyUtil.h" @@ -2572,7 +2572,7 @@ void net_EnsurePSMInit() { MOZ_ASSERT(NS_SUCCEEDED(rv)); nsCOMPtr sss = do_GetService(NS_SSSERVICE_CONTRACTID); - nsCOMPtr cbl = do_GetService(NS_CERTBLOCKLIST_CONTRACTID); + nsCOMPtr cbl = do_GetService(NS_CERTSTORAGE_CONTRACTID); nsCOMPtr cos = do_GetService(NS_CERTOVERRIDE_CONTRACTID); } diff --git a/security/certverifier/NSSCertDBTrustDomain.cpp b/security/certverifier/NSSCertDBTrustDomain.cpp index a756e4ddd782..2616378b3aa3 100644 --- a/security/certverifier/NSSCertDBTrustDomain.cpp +++ b/security/certverifier/NSSCertDBTrustDomain.cpp @@ -14,6 +14,7 @@ #include "PublicKeyPinningService.h" #include "cert.h" #include "certdb.h" +#include "cert_storage/src/cert_storage.h" #include "mozilla/Assertions.h" #include "mozilla/Casting.h" #include "mozilla/Move.h" @@ -85,7 +86,7 @@ NSSCertDBTrustDomain::NSSCertDBTrustDomain( mBuiltChain(builtChain), mPinningTelemetryInfo(pinningTelemetryInfo), mHostname(hostname), - mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)), + mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)), mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED), mSCTListFromCertificate(), mSCTListFromOCSPStapling() {} @@ -195,7 +196,7 @@ Result NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, // The certificate blocklist currently only applies to TLS server // certificates. if (mCertDBTrustType == trustSSL) { - bool isCertRevoked; + int16_t revocationState; nsAutoCString encIssuer; nsAutoCString encSerial; @@ -209,13 +210,13 @@ Result NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, return Result::FATAL_ERROR_LIBRARY_FAILURE; } - nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject, - encPubKey, &isCertRevoked); + nsrv = mCertBlocklist->GetRevocationState(encIssuer, encSerial, encSubject, + encPubKey, &revocationState); if (NS_FAILED(nsrv)) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } - if (isCertRevoked) { + if (revocationState == nsICertStorage::STATE_ENFORCE) { MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("NSSCertDBTrustDomain: certificate is in blocklist")); return Result::ERROR_REVOKED_CERTIFICATE; diff --git a/security/certverifier/NSSCertDBTrustDomain.h b/security/certverifier/NSSCertDBTrustDomain.h index 9ed16a356858..6370cf61fabc 100644 --- a/security/certverifier/NSSCertDBTrustDomain.h +++ b/security/certverifier/NSSCertDBTrustDomain.h @@ -11,7 +11,7 @@ #include "ScopedNSSTypes.h" #include "mozilla/BasePrincipal.h" #include "mozilla/TimeStamp.h" -#include "nsICertBlocklist.h" +#include "nsICertStorage.h" #include "nsString.h" #include "mozpkix/pkixtypes.h" #include "secmodt.h" @@ -223,7 +223,7 @@ class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain { UniqueCERTCertList& mBuiltChain; // non-owning PinningTelemetryInfo* mPinningTelemetryInfo; const char* mHostname; // non-owning - only used for pinning checks - nsCOMPtr mCertBlocklist; + nsCOMPtr mCertBlocklist; CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus; // Certificate Transparency data extracted during certificate verification UniqueSECItem mSCTListFromCertificate; diff --git a/security/manager/ssl/CSTrustDomain.cpp b/security/manager/ssl/CSTrustDomain.cpp index f17b2460a996..3a01d30f7ee0 100644 --- a/security/manager/ssl/CSTrustDomain.cpp +++ b/security/manager/ssl/CSTrustDomain.cpp @@ -4,9 +4,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "cert_storage/src/cert_storage.h" #include "CSTrustDomain.h" #include "mozilla/Base64.h" #include "mozilla/Preferences.h" +#include "nsDirectoryServiceUtils.h" #include "nsNSSCertificate.h" #include "nsNSSComponent.h" #include "NSSCertDBTrustDomain.h" @@ -24,7 +26,7 @@ static LazyLogModule gTrustDomainPRLog("CSTrustDomain"); CSTrustDomain::CSTrustDomain(UniqueCERTCertList& certChain) : mCertChain(certChain), - mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)) {} + mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)) {} Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId& policy, @@ -53,14 +55,14 @@ Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, return Result::FATAL_ERROR_LIBRARY_FAILURE; } - bool isCertRevoked; - nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject, - encPubKey, &isCertRevoked); + int16_t revocationState; + nsrv = mCertBlocklist->GetRevocationState(encIssuer, encSerial, encSubject, + encPubKey, &revocationState); if (NS_FAILED(nsrv)) { return Result::FATAL_ERROR_LIBRARY_FAILURE; } - if (isCertRevoked) { + if (revocationState == nsICertStorage::STATE_ENFORCE) { CSTrust_LOG(("CSTrustDomain: certificate is revoked\n")); return Result::ERROR_REVOKED_CERTIFICATE; } diff --git a/security/manager/ssl/CSTrustDomain.h b/security/manager/ssl/CSTrustDomain.h index cef838b2b607..3cdf0bf7088d 100644 --- a/security/manager/ssl/CSTrustDomain.h +++ b/security/manager/ssl/CSTrustDomain.h @@ -11,7 +11,7 @@ #include "mozilla/StaticMutex.h" #include "mozilla/UniquePtr.h" #include "nsDebug.h" -#include "nsICertBlocklist.h" +#include "nsICertStorage.h" #include "nsIX509CertDB.h" #include "ScopedNSSTypes.h" @@ -73,7 +73,7 @@ class CSTrustDomain final : public mozilla::pkix::TrustDomain { private: /*out*/ UniqueCERTCertList& mCertChain; - nsCOMPtr mCertBlocklist; + nsCOMPtr mCertBlocklist; }; } // namespace psm diff --git a/security/manager/ssl/CertBlocklist.cpp b/security/manager/ssl/CertBlocklist.cpp deleted file mode 100644 index e01378ce6605..000000000000 --- a/security/manager/ssl/CertBlocklist.cpp +++ /dev/null @@ -1,626 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "CertBlocklist.h" - -#include "mozilla/Assertions.h" -#include "mozilla/Base64.h" -#include "mozilla/Casting.h" -#include "mozilla/IntegerPrintfMacros.h" -#include "mozilla/Logging.h" -#include "mozilla/Preferences.h" -#include "mozilla/Unused.h" -#include "nsAppDirectoryServiceDefs.h" -#include "nsDependentString.h" -#include "nsDirectoryServiceUtils.h" -#include "nsICryptoHash.h" -#include "nsIFileStreams.h" -#include "nsILineInputStream.h" -#include "nsISafeOutputStream.h" -#include "nsIX509Cert.h" -#include "nsNetCID.h" -#include "nsNetUtil.h" -#include "nsPromiseFlatString.h" -#include "nsTHashtable.h" -#include "nsThreadUtils.h" -#include "mozpkix/Input.h" -#include "prtime.h" - -NS_IMPL_ISUPPORTS(CertBlocklist, nsICertBlocklist) - -using namespace mozilla; -using namespace mozilla::pkix; - -#define PREF_BACKGROUND_UPDATE_TIMER \ - "app.update.lastUpdateTime.blocklist-background-update-timer" -#define PREF_BLOCKLIST_ONECRL_CHECKED "services.blocklist.onecrl.checked" -#define PREF_MAX_STALENESS_IN_SECONDS \ - "security.onecrl.maximum_staleness_in_seconds" - -static LazyLogModule gCertBlockPRLog("CertBlock"); - -uint32_t CertBlocklist::sLastBlocklistUpdate = 0U; -uint32_t CertBlocklist::sMaxStaleness = 0U; - -CertBlocklistItem::CertBlocklistItem(const uint8_t* DNData, size_t DNLength, - const uint8_t* otherData, - size_t otherLength, - CertBlocklistItemMechanism itemMechanism) - : mIsCurrent(false), mItemMechanism(itemMechanism) { - mDNData = new uint8_t[DNLength]; - memcpy(mDNData, DNData, DNLength); - mDNLength = DNLength; - - mOtherData = new uint8_t[otherLength]; - memcpy(mOtherData, otherData, otherLength); - mOtherLength = otherLength; -} - -CertBlocklistItem::CertBlocklistItem(const CertBlocklistItem& aItem) { - mDNLength = aItem.mDNLength; - mDNData = new uint8_t[mDNLength]; - memcpy(mDNData, aItem.mDNData, mDNLength); - - mOtherLength = aItem.mOtherLength; - mOtherData = new uint8_t[mOtherLength]; - memcpy(mOtherData, aItem.mOtherData, mOtherLength); - - mItemMechanism = aItem.mItemMechanism; - - mIsCurrent = aItem.mIsCurrent; -} - -CertBlocklistItem::~CertBlocklistItem() { - delete[] mDNData; - delete[] mOtherData; -} - -nsresult CertBlocklistItem::ToBase64(nsACString& b64DNOut, - nsACString& b64OtherOut) { - nsDependentCSubstring DNString(BitwiseCast(mDNData), - mDNLength); - nsDependentCSubstring otherString(BitwiseCast(mOtherData), - mOtherLength); - nsresult rv = Base64Encode(DNString, b64DNOut); - if (NS_FAILED(rv)) { - return rv; - } - rv = Base64Encode(otherString, b64OtherOut); - return rv; -} - -bool CertBlocklistItem::operator==(const CertBlocklistItem& aItem) const { - if (aItem.mItemMechanism != mItemMechanism) { - return false; - } - if (aItem.mDNLength != mDNLength || aItem.mOtherLength != mOtherLength) { - return false; - } - return memcmp(aItem.mDNData, mDNData, mDNLength) == 0 && - memcmp(aItem.mOtherData, mOtherData, mOtherLength) == 0; -} - -uint32_t CertBlocklistItem::Hash() const { - uint32_t hash; - // there's no requirement for a serial to be as large as the size of the hash - // key; if it's smaller, fall back to the first octet (otherwise, the last - // four) - if (mItemMechanism == BlockByIssuerAndSerial && - mOtherLength >= sizeof(hash)) { - memcpy(&hash, mOtherData + mOtherLength - sizeof(hash), sizeof(hash)); - } else { - hash = *mOtherData; - } - return hash; -} - -CertBlocklist::CertBlocklist() - : mMutex("CertBlocklist::mMutex"), - mModified(false), - mBackingFileIsInitialized(false), - mBackingFile(nullptr) {} - -CertBlocklist::~CertBlocklist() { - Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged, - PREF_MAX_STALENESS_IN_SECONDS, this); - Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged, - PREF_BLOCKLIST_ONECRL_CHECKED, this); -} - -nsresult CertBlocklist::Init() { - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, ("CertBlocklist::Init")); - - // Init must be on main thread for getting the profile directory - if (!NS_IsMainThread()) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::Init - called off main thread")); - return NS_ERROR_NOT_SAME_THREAD; - } - - // Register preference callbacks - nsresult rv = Preferences::RegisterCallbackAndCall( - CertBlocklist::PreferenceChanged, PREF_MAX_STALENESS_IN_SECONDS, this); - if (NS_FAILED(rv)) { - return rv; - } - rv = Preferences::RegisterCallbackAndCall( - CertBlocklist::PreferenceChanged, PREF_BLOCKLIST_ONECRL_CHECKED, this); - if (NS_FAILED(rv)) { - return rv; - } - - // Get the profile directory - rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, - getter_AddRefs(mBackingFile)); - if (NS_FAILED(rv) || !mBackingFile) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::Init - couldn't get profile dir")); - // Since we're returning NS_OK here, set mBackingFile to a safe value. - // (We need initialization to succeed and CertBlocklist to be in a - // well-defined state if the profile directory doesn't exist.) - mBackingFile = nullptr; - return NS_OK; - } - rv = mBackingFile->Append(NS_LITERAL_STRING("revocations.txt")); - if (NS_FAILED(rv)) { - return rv; - } - nsAutoCString path; - rv = mBackingFile->GetPersistentDescriptor(path); - if (NS_FAILED(rv)) { - return rv; - } - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::Init certList path: %s", path.get())); - - return NS_OK; -} - -nsresult CertBlocklist::EnsureBackingFileInitialized(MutexAutoLock& lock) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::EnsureBackingFileInitialized")); - if (mBackingFileIsInitialized || !mBackingFile) { - return NS_OK; - } - - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::EnsureBackingFileInitialized - not initialized")); - - bool exists = false; - nsresult rv = mBackingFile->Exists(&exists); - if (NS_FAILED(rv)) { - return rv; - } - if (!exists) { - MOZ_LOG( - gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::EnsureBackingFileInitialized no revocations file")); - return NS_OK; - } - - // Load the revocations file into the cert blocklist - nsCOMPtr fileStream( - do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv)); - if (NS_FAILED(rv)) { - return rv; - } - - rv = fileStream->Init(mBackingFile, -1, -1, false); - if (NS_FAILED(rv)) { - return rv; - } - - nsCOMPtr lineStream(do_QueryInterface(fileStream, &rv)); - nsAutoCString line; - nsAutoCString DN; - nsAutoCString other; - CertBlocklistItemMechanism mechanism; - // read in the revocations file. The file format is as follows: each line - // contains a comment, base64 encoded DER for a DN, base64 encoded DER for a - // serial number or a Base64 encoded SHA256 hash of a public key. Comment - // lines start with '#', serial number lines, ' ' (a space), public key hashes - // with '\t' (a tab) and anything else is assumed to be a DN. - bool more = true; - do { - rv = lineStream->ReadLine(line, &more); - if (NS_FAILED(rv)) { - break; - } - // ignore comments and empty lines - if (line.IsEmpty() || line.First() == '#') { - continue; - } - if (line.First() != ' ' && line.First() != '\t') { - DN = line; - continue; - } - other = line; - if (line.First() == ' ') { - mechanism = BlockByIssuerAndSerial; - } else { - mechanism = BlockBySubjectAndPubKey; - } - other.Trim(" \t", true, false, false); - // Serial numbers and public key hashes 'belong' to the last DN line seen; - // if no DN has been seen, the serial number or public key hash is ignored. - if (DN.IsEmpty() || other.IsEmpty()) { - continue; - } - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::EnsureBackingFileInitialized adding: %s %s", - DN.get(), other.get())); - - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::EnsureBackingFileInitialized - pre-decode")); - - rv = AddRevokedCertInternal(DN, other, mechanism, CertOldFromLocalCache, - lock); - - if (NS_FAILED(rv)) { - // we warn here, rather than abandoning, since we need to - // ensure that as many items as possible are read - MOZ_LOG( - gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::EnsureBackingFileInitialized adding revoked cert " - "failed")); - } - } while (more); - mBackingFileIsInitialized = true; - return NS_OK; -} - -NS_IMETHODIMP -CertBlocklist::RevokeCertBySubjectAndPubKey(const nsACString& aSubject, - const nsACString& aPubKeyHash) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::RevokeCertBySubjectAndPubKey - subject is: %s and " - "pubKeyHash: %s", - PromiseFlatCString(aSubject).get(), - PromiseFlatCString(aPubKeyHash).get())); - MutexAutoLock lock(mMutex); - - return AddRevokedCertInternal(aSubject, aPubKeyHash, BlockBySubjectAndPubKey, - CertNewFromBlocklist, lock); -} - -NS_IMETHODIMP -CertBlocklist::RevokeCertByIssuerAndSerial(const nsACString& aIssuer, - const nsACString& aSerialNumber) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::RevokeCertByIssuerAndSerial - issuer is: %s and " - "serial: %s", - PromiseFlatCString(aIssuer).get(), - PromiseFlatCString(aSerialNumber).get())); - MutexAutoLock lock(mMutex); - - return AddRevokedCertInternal(aIssuer, aSerialNumber, BlockByIssuerAndSerial, - CertNewFromBlocklist, lock); -} - -nsresult CertBlocklist::AddRevokedCertInternal( - const nsACString& aEncodedDN, const nsACString& aEncodedOther, - CertBlocklistItemMechanism aMechanism, CertBlocklistItemState aItemState, - MutexAutoLock& /*proofOfLock*/) { - nsCString decodedDN; - nsCString decodedOther; - - nsresult rv = Base64Decode(aEncodedDN, decodedDN); - if (NS_FAILED(rv)) { - return rv; - } - rv = Base64Decode(aEncodedOther, decodedOther); - if (NS_FAILED(rv)) { - return rv; - } - - CertBlocklistItem item( - BitwiseCast(decodedDN.get()), - decodedDN.Length(), - BitwiseCast(decodedOther.get()), - decodedOther.Length(), aMechanism); - - if (aItemState == CertNewFromBlocklist) { - // We want SaveEntries to be a no-op if no new entries are added. - nsGenericHashKey* entry = mBlocklist.GetEntry(item); - if (!entry) { - mModified = true; - } else { - // Ensure that any existing item is replaced by a fresh one so we can - // use mIsCurrent to decide which entries to write out. - mBlocklist.RemoveEntry(entry); - } - item.mIsCurrent = true; - } - mBlocklist.PutEntry(item); - - return NS_OK; -} - -// Write a line for a given string in the output stream -nsresult WriteLine(nsIOutputStream* outputStream, const nsACString& string) { - nsAutoCString line(string); - line.Append('\n'); - - const char* data = line.get(); - uint32_t length = line.Length(); - nsresult rv = NS_OK; - while (NS_SUCCEEDED(rv) && length) { - uint32_t bytesWritten = 0; - rv = outputStream->Write(data, length, &bytesWritten); - if (NS_FAILED(rv)) { - return rv; - } - // if no data is written, something is wrong - if (!bytesWritten) { - return NS_ERROR_FAILURE; - } - length -= bytesWritten; - data += bytesWritten; - } - return rv; -} - -// void saveEntries(); -// Store the blockist in a text file containing base64 encoded issuers and -// serial numbers. -// -// Each item is stored on a separate line; each issuer is followed by its -// revoked serial numbers, indented by one space. -// -// lines starting with a # character are ignored -NS_IMETHODIMP -CertBlocklist::SaveEntries() { - MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, - ("CertBlocklist::SaveEntries - not initialized")); - MutexAutoLock lock(mMutex); - if (!mModified) { - return NS_OK; - } - - nsresult rv = EnsureBackingFileInitialized(lock); - if (NS_FAILED(rv)) { - return rv; - } - - if (!mBackingFile) { - // We allow this to succeed with no profile directory for tests - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::SaveEntries no file in profile to write to")); - return NS_OK; - } - - // Data needed for writing blocklist items out to the revocations file - IssuerTable issuerTable; - BlocklistStringSet issuers; - nsCOMPtr outputStream; - - rv = NS_NewAtomicFileOutputStream(getter_AddRefs(outputStream), mBackingFile, - -1, -1, 0); - if (NS_FAILED(rv)) { - return rv; - } - - rv = WriteLine(outputStream, - NS_LITERAL_CSTRING("# Auto generated contents. Do not edit.")); - if (NS_FAILED(rv)) { - return rv; - } - - // Sort blocklist items into lists of serials for each issuer - for (auto iter = mBlocklist.Iter(); !iter.Done(); iter.Next()) { - CertBlocklistItem item = iter.Get()->GetKey(); - if (!item.mIsCurrent) { - continue; - } - - nsAutoCString encDN; - nsAutoCString encOther; - - nsresult rv = item.ToBase64(encDN, encOther); - if (NS_FAILED(rv)) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::SaveEntries writing revocation data failed")); - return NS_ERROR_FAILURE; - } - - // If it's a subject / public key block, write it straight out - if (item.mItemMechanism == BlockBySubjectAndPubKey) { - WriteLine(outputStream, encDN); - WriteLine(outputStream, NS_LITERAL_CSTRING("\t") + encOther); - continue; - } - - // Otherwise, we have to group entries by issuer - issuers.PutEntry(encDN); - BlocklistStringSet* issuerSet = issuerTable.Get(encDN); - if (!issuerSet) { - issuerSet = new BlocklistStringSet(); - issuerTable.Put(encDN, issuerSet); - } - issuerSet->PutEntry(encOther); - } - - for (auto iter = issuers.Iter(); !iter.Done(); iter.Next()) { - nsCStringHashKey* hashKey = iter.Get(); - nsAutoPtr issuerSet; - issuerTable.Remove(hashKey->GetKey(), &issuerSet); - - nsresult rv = WriteLine(outputStream, hashKey->GetKey()); - if (NS_FAILED(rv)) { - break; - } - - // Write serial data to the output stream - for (auto iter = issuerSet->Iter(); !iter.Done(); iter.Next()) { - nsresult rv = WriteLine(outputStream, - NS_LITERAL_CSTRING(" ") + iter.Get()->GetKey()); - if (NS_FAILED(rv)) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::SaveEntries writing revocation data failed")); - return NS_ERROR_FAILURE; - } - } - } - - nsCOMPtr safeStream = do_QueryInterface(outputStream); - MOZ_ASSERT(safeStream, "expected a safe output stream!"); - if (!safeStream) { - return NS_ERROR_FAILURE; - } - rv = safeStream->Finish(); - if (NS_FAILED(rv)) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::SaveEntries saving revocation data failed")); - return rv; - } - mModified = false; - return NS_OK; -} - -NS_IMETHODIMP -CertBlocklist::IsCertRevoked(const nsACString& aIssuerString, - const nsACString& aSerialNumberString, - const nsACString& aSubjectString, - const nsACString& aPubKeyString, bool* _retval) { - MutexAutoLock lock(mMutex); - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, ("CertBlocklist::IsCertRevoked")); - - nsCString decodedIssuer; - nsCString decodedSerial; - nsCString decodedSubject; - nsCString decodedPubKey; - - nsresult rv = Base64Decode(aIssuerString, decodedIssuer); - if (NS_FAILED(rv)) { - return rv; - } - rv = Base64Decode(aSerialNumberString, decodedSerial); - if (NS_FAILED(rv)) { - return rv; - } - rv = Base64Decode(aSubjectString, decodedSubject); - if (NS_FAILED(rv)) { - return rv; - } - rv = Base64Decode(aPubKeyString, decodedPubKey); - if (NS_FAILED(rv)) { - return rv; - } - - rv = EnsureBackingFileInitialized(lock); - if (NS_FAILED(rv)) { - return rv; - } - - CertBlocklistItem issuerSerial( - BitwiseCast(decodedIssuer.get()), - decodedIssuer.Length(), - BitwiseCast(decodedSerial.get()), - decodedSerial.Length(), BlockByIssuerAndSerial); - - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::IsCertRevoked issuer %s - serial %s", - PromiseFlatCString(aIssuerString).get(), - PromiseFlatCString(aSerialNumberString).get())); - - *_retval = mBlocklist.Contains(issuerSerial); - - if (*_retval) { - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("certblocklist::IsCertRevoked found by issuer / serial")); - return NS_OK; - } - - nsCOMPtr crypto; - crypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); - - rv = crypto->Init(nsICryptoHash::SHA256); - if (NS_FAILED(rv)) { - return rv; - } - - rv = crypto->Update( - BitwiseCast(decodedPubKey.get()), - decodedPubKey.Length()); - if (NS_FAILED(rv)) { - return rv; - } - - nsCString hashString; - rv = crypto->Finish(false, hashString); - if (NS_FAILED(rv)) { - return rv; - } - - CertBlocklistItem subjectPubKey( - BitwiseCast(decodedSubject.get()), - decodedSubject.Length(), - BitwiseCast(hashString.get()), - hashString.Length(), BlockBySubjectAndPubKey); - - nsCString encodedHash; - rv = Base64Encode(hashString, encodedHash); - if (NS_FAILED(rv)) { - return rv; - } - - MOZ_LOG( - gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::IsCertRevoked subject %s - pubKeyHash %s (pubKey %s)", - PromiseFlatCString(aSubjectString).get(), - PromiseFlatCString(encodedHash).get(), - PromiseFlatCString(aPubKeyString).get())); - *_retval = mBlocklist.Contains(subjectPubKey); - - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::IsCertRevoked by subject / pubkey? %s", - *_retval ? "true" : "false")); - - return NS_OK; -} - -NS_IMETHODIMP -CertBlocklist::IsBlocklistFresh(bool* _retval) { - MutexAutoLock lock(mMutex); - *_retval = false; - - uint32_t now = uint32_t(PR_Now() / PR_USEC_PER_SEC); - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::IsBlocklistFresh ? lastUpdate is %i", - sLastBlocklistUpdate)); - - if (now > sLastBlocklistUpdate) { - int64_t interval = now - sLastBlocklistUpdate; - MOZ_LOG( - gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::IsBlocklistFresh we're after the last BlocklistUpdate " - "interval is %" PRId64 ", staleness %u", - interval, sMaxStaleness)); - *_retval = sMaxStaleness > interval; - } - MOZ_LOG( - gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::IsBlocklistFresh ? %s", *_retval ? "true" : "false")); - return NS_OK; -} - -/* static */ -void CertBlocklist::PreferenceChanged(const char* aPref, - CertBlocklist* aBlocklist) - -{ - MutexAutoLock lock(aBlocklist->mMutex); - - MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, - ("CertBlocklist::PreferenceChanged %s changed", aPref)); - if (strcmp(aPref, PREF_BLOCKLIST_ONECRL_CHECKED) == 0) { - sLastBlocklistUpdate = - Preferences::GetUint(PREF_BLOCKLIST_ONECRL_CHECKED, uint32_t(0)); - } else if (strcmp(aPref, PREF_MAX_STALENESS_IN_SECONDS) == 0) { - sMaxStaleness = - Preferences::GetUint(PREF_MAX_STALENESS_IN_SECONDS, uint32_t(0)); - } -} diff --git a/security/manager/ssl/CertBlocklist.h b/security/manager/ssl/CertBlocklist.h deleted file mode 100644 index bef8f2346e1d..000000000000 --- a/security/manager/ssl/CertBlocklist.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef CertBlocklist_h -#define CertBlocklist_h - -#include "mozilla/Mutex.h" -#include "nsCOMPtr.h" -#include "nsClassHashtable.h" -#include "nsICertBlocklist.h" -#include "nsIOutputStream.h" -#include "nsIX509CertDB.h" -#include "nsString.h" -#include "nsTHashtable.h" -#include "mozpkix/Input.h" - -#define NS_CERT_BLOCKLIST_CID \ - { \ - 0x11aefd53, 0x2fbb, 0x4c92, { \ - 0xa0, 0xc1, 0x05, 0x32, 0x12, 0xae, 0x42, 0xd0 \ - } \ - } - -enum CertBlocklistItemMechanism { - BlockByIssuerAndSerial, - BlockBySubjectAndPubKey -}; - -enum CertBlocklistItemState { CertNewFromBlocklist, CertOldFromLocalCache }; - -class CertBlocklistItem { - public: - CertBlocklistItem(const uint8_t* DNData, size_t DNLength, - const uint8_t* otherData, size_t otherLength, - CertBlocklistItemMechanism itemMechanism); - CertBlocklistItem(const CertBlocklistItem& aItem); - ~CertBlocklistItem(); - nsresult ToBase64(nsACString& b64IssuerOut, nsACString& b64SerialOut); - bool operator==(const CertBlocklistItem& aItem) const; - uint32_t Hash() const; - bool mIsCurrent; - CertBlocklistItemMechanism mItemMechanism; - - private: - size_t mDNLength; - uint8_t* mDNData; - size_t mOtherLength; - uint8_t* mOtherData; -}; - -typedef nsGenericHashKey BlocklistItemKey; -typedef nsTHashtable BlocklistTable; -typedef nsTHashtable BlocklistStringSet; -typedef nsClassHashtable IssuerTable; - -class CertBlocklist : public nsICertBlocklist { - public: - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_NSICERTBLOCKLIST - CertBlocklist(); - nsresult Init(); - - private: - BlocklistTable mBlocklist; - nsresult AddRevokedCertInternal(const nsACString& aEncodedDN, - const nsACString& aEncodedOther, - CertBlocklistItemMechanism aMechanism, - CertBlocklistItemState aItemState, - mozilla::MutexAutoLock& /*proofOfLock*/); - mozilla::Mutex mMutex; - bool mModified; - bool mBackingFileIsInitialized; - // call EnsureBackingFileInitialized before operations that read or - // modify CertBlocklist data - nsresult EnsureBackingFileInitialized(mozilla::MutexAutoLock& lock); - nsCOMPtr mBackingFile; - - protected: - static void PreferenceChanged(const char* aPref, CertBlocklist* aBlocklist); - static uint32_t sLastBlocklistUpdate; - static uint32_t sLastKintoUpdate; - static uint32_t sMaxStaleness; - static bool sUseAMO; - virtual ~CertBlocklist(); -}; - -#endif // CertBlocklist_h diff --git a/security/manager/ssl/ContentSignatureVerifier.h b/security/manager/ssl/ContentSignatureVerifier.h index 36de38370dfc..058df982bcff 100644 --- a/security/manager/ssl/ContentSignatureVerifier.h +++ b/security/manager/ssl/ContentSignatureVerifier.h @@ -9,8 +9,10 @@ #include "cert.h" #include "CSTrustDomain.h" +#include "nsDirectoryServiceUtils.h" #include "nsIContentSignatureVerifier.h" #include "nsIStreamListener.h" +#include "nsNetUtil.h" #include "nsString.h" #include "ScopedNSSTypes.h" diff --git a/security/manager/ssl/DataStorage.cpp b/security/manager/ssl/DataStorage.cpp index 28919af9d751..1296b5b0b4ab 100644 --- a/security/manager/ssl/DataStorage.cpp +++ b/security/manager/ssl/DataStorage.cpp @@ -18,6 +18,7 @@ #include "mozilla/Unused.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" +#include "nsIFileStreams.h" #include "nsIMemoryReporter.h" #include "nsIObserverService.h" #include "nsITimer.h" diff --git a/security/manager/ssl/NSSErrorsService.h b/security/manager/ssl/NSSErrorsService.h index d315a25d8a7b..9e765e8a214e 100644 --- a/security/manager/ssl/NSSErrorsService.h +++ b/security/manager/ssl/NSSErrorsService.h @@ -8,6 +8,8 @@ #include "nsINSSErrorsService.h" #include "mozilla/Attributes.h" #include "nsCOMPtr.h" +#include "nsILineInputStream.h" +#include "nsISafeOutputStream.h" #include "nsIStringBundle.h" #include "prerror.h" diff --git a/security/manager/ssl/cert_storage/Cargo.toml b/security/manager/ssl/cert_storage/Cargo.toml new file mode 100644 index 000000000000..415fc7415a9a --- /dev/null +++ b/security/manager/ssl/cert_storage/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cert_storage" +version = "0.0.1" +authors = ["Dana Keeler ", "Mark Goodwin mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_FAILED(rv)) { + return rv; + } + + mozilla::SyncRunnable::DispatchToThread( + mainThread, new mozilla::SyncRunnable( + NS_NewRunnableFunction("psm::Constructor", [&]() { + rv = cert_storage_constructor(outer, iid, result); + }))); + return rv; +} \ No newline at end of file diff --git a/security/manager/ssl/cert_storage/src/cert_storage.h b/security/manager/ssl/cert_storage/src/cert_storage.h new file mode 100644 index 000000000000..f46c09dadfef --- /dev/null +++ b/security/manager/ssl/cert_storage/src/cert_storage.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _cert_storage_h_ +#define _cert_storage_h_ + +#include "nsISupportsUtils.h" // for nsresult, etc. +#include "mozilla/SyncRunnable.h" + +// {16e5c837-f877-4e23-9c64-eddf905e30e6} +#define NS_CERT_STORAGE_CID \ + { \ + 0x16e5c837, 0xf877, 0x4e23, { \ + 0x9c, 0x64, 0xed, 0xdf, 0x90, 0x5e, 0x30, 0xe6 \ + } \ + } + +extern "C" { +nsresult cert_storage_constructor(nsISupports* outer, REFNSIID iid, + void** result); +}; + +nsresult construct_cert_storage(nsISupports* outer, REFNSIID iid, + void** result); + +#endif // _cert_storage_h_ \ No newline at end of file diff --git a/security/manager/ssl/cert_storage/src/lib.rs b/security/manager/ssl/cert_storage/src/lib.rs new file mode 100644 index 000000000000..4f459f51153f --- /dev/null +++ b/security/manager/ssl/cert_storage/src/lib.rs @@ -0,0 +1,766 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +extern crate base64; +extern crate nserror; +extern crate nsstring; +extern crate rkv; +extern crate sha2; +extern crate time; +#[macro_use] +extern crate xpcom; +extern crate style; + +use nsstring::{nsACString, nsAString, nsCStr, nsCString, nsString}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::fmt::Display; +use std::fs::{create_dir_all, remove_file, File}; +use std::io::{BufRead, BufReader}; +use std::os::raw::c_char; +use std::path::PathBuf; +use std::slice; +use std::str; +use std::sync::RwLock; +use std::time::{Duration, SystemTime}; +use style::gecko_bindings::structs::nsresult; +use xpcom::interfaces::{nsICertStorage, nsIFile, nsIObserver, nsIPrefBranch, nsISupports}; +use xpcom::{nsIID, GetterAddrefs, RefPtr, XpCom}; + +use rkv::{Rkv, StoreOptions, Value}; + +const PREFIX_REV_IS: &str = "is"; +const PREFIX_REV_SPK: &str = "spk"; +const PREFIX_CRLITE: &str = "crlite"; +const PREFIX_WL: &str = "wl"; + +#[allow(non_camel_case_types, non_snake_case)] + +/// `SecurityStateError` is a type to represent errors in accessing or +/// modifying security state. +#[derive(Debug)] +pub struct SecurityStateError { + message: String, +} + +impl From for SecurityStateError { + /// Creates a new instance of `SecurityStateError` from something that + /// implements the `Display` trait. + fn from(err: T) -> SecurityStateError { + SecurityStateError { + message: format!("{}", err), + } + } +} + +/// `SecurityState` +pub struct SecurityState { + path: PathBuf, + int_prefs: HashMap, +} + +impl SecurityState { + pub fn new(profile_path: PathBuf) -> Result { + let mut store_path = profile_path.clone(); + store_path.push("security_state"); + + create_dir_all(store_path.as_path())?; + let mut ss = SecurityState { + path: store_path, + int_prefs: HashMap::new(), + }; + + let mut revocations_path = profile_path; + revocations_path.push("revocations.txt"); + + // if the profile has a revocations.txt, migrate it and remove the file + if revocations_path.exists() { + ss.migrate(&revocations_path)?; + remove_file(revocations_path)?; + } + Ok(ss) + } + + fn migrate(&mut self, revocations_path: &PathBuf) -> Result<(), SecurityStateError> { + let f = File::open(revocations_path)?; + let file = BufReader::new(f); + + // Add the data from revocations.txt + let mut dn: Option = None; + let mut l; + for line in file.lines() { + l = match line.map_err(|_| SecurityStateError::from("io error reading line data")) { + Ok(data) => data.to_owned(), + Err(e) => return Err(e), + }; + let l_sans_prefix = match l.len() { + 0 => "".to_owned(), + _ => l[1..].to_owned(), + }; + // In future, we can maybe log migration failures. For now, ignore the error + // and attempt to continue. + let _ = match l.chars().next() { + Some('#') => Ok(()), + Some('\t') => match &dn { + Some(name) => self.set_revocation_by_subject_and_pub_key( + &name, + &l_sans_prefix, + nsICertStorage::STATE_ENFORCE as i16, + ), + None => Err(SecurityStateError::from("pubkey with no subject")), + }, + Some(' ') => match &dn { + Some(name) => self.set_revocation_by_issuer_and_serial( + &name, + &l_sans_prefix, + nsICertStorage::STATE_ENFORCE as i16, + ), + None => Err(SecurityStateError::from("serial with no issuer")), + }, + Some(_) => { + dn = Some(l); + Ok(()) + } + None => Ok(()), + }; + } + + Ok(()) + } + + fn write_entry(&mut self, key: &str, value: i16) -> Result<(), SecurityStateError> { + let p = self.path.as_path(); + let env = Rkv::new(p)?; + let store = env.open_single("cert_storage", StoreOptions::create())?; + + let mut writer = env.write()?; + store.put(&mut writer, key, &Value::I64(value as i64))?; + writer.commit()?; + Ok(()) + } + + fn read_entry(&self, key: &str) -> Result, SecurityStateError> { + // TODO: figure out a way to de-dupe getting the env / store + let p = self.path.as_path(); + let env = Rkv::new(p)?; + let store = env.open_single("cert_storage", StoreOptions::create())?; + + let reader = env.read()?; + match store.get(&reader, key) { + // There's no tidy way in rkv::Value to get an owned value. The + // only way I can see to get such functionality is to go via + // primitives. + Ok(Some(Value::I64(i))) + if i <= (std::i16::MAX as i64) && i >= (std::i16::MIN as i64) => + { + Ok(Some(i as i16)) + } + Ok(None) => Ok(None), + _ => Err(SecurityStateError::from( + "There was a problem getting the value", + )), + } + } + + pub fn set_revocation_by_issuer_and_serial( + &mut self, + issuer: &str, + serial: &str, + state: i16, + ) -> Result<(), SecurityStateError> { + self.write_entry(&format!("{}:{}:{}", PREFIX_REV_IS, issuer, serial), state) + } + + pub fn set_revocation_by_subject_and_pub_key( + &mut self, + subject: &str, + pub_key_hash: &str, + state: i16, + ) -> Result<(), SecurityStateError> { + self.write_entry( + &format!("{}:{}:{}", PREFIX_REV_SPK, subject, pub_key_hash), + state, + ) + } + + pub fn set_enrollment( + &mut self, + issuer: &str, + serial: &str, + state: i16, + ) -> Result<(), SecurityStateError> { + self.write_entry(&format!("{}:{}:{}", PREFIX_CRLITE, issuer, serial), state) + } + + pub fn set_whitelist( + &mut self, + issuer: &str, + serial: &str, + state: i16, + ) -> Result<(), SecurityStateError> { + self.write_entry(&format!("{}:{}:{}", PREFIX_WL, issuer, serial), state) + } + + pub fn get_revocation_state( + &self, + issuer: &str, + serial: &str, + subject: &str, + pub_key: &str, + ) -> Result { + let pub_key_hash = match base64::decode(pub_key) { + Ok(pk) => { + let mut digest = Sha256::default(); + digest.input(&pk); + let digest_result = digest.result(); + base64::encode(&digest_result) + } + Err(_) => { + return Err(SecurityStateError::from( + "problem base64 decoding public key", + )); + } + }; + + let subject_pubkey = format!("{}:{}:{}", PREFIX_REV_SPK, subject, pub_key_hash); + let issuer_serial = format!("{}:{}:{}", PREFIX_REV_IS, issuer, serial); + + let st: i16 = match self.read_entry(&issuer_serial) { + Ok(Some(value)) => value, + Ok(None) => nsICertStorage::STATE_UNSET as i16, + Err(_) => { + return Err(SecurityStateError::from( + "problem reading revocation state (from issuer / serial)", + )) + } + }; + + if st != nsICertStorage::STATE_UNSET as i16 { + return Ok(st); + } + + match self.read_entry(&subject_pubkey) { + Ok(Some(value)) => Ok(value), + Ok(None) => Ok(nsICertStorage::STATE_UNSET as i16), + Err(_) => { + return Err(SecurityStateError::from( + "problem reading revocation state (from subject / pubkey)", + )) + } + } + } + + pub fn get_enrollment_state( + &self, + issuer: &str, + serial: &str, + ) -> Result { + let issuer_serial = format!("{}:{}:{}", PREFIX_CRLITE, issuer, serial); + match self.read_entry(&issuer_serial) { + Ok(Some(value)) => Ok(value), + Ok(None) => Ok(nsICertStorage::STATE_UNSET as i16), + Err(_) => return Err(SecurityStateError::from("problem reading enrollment state")), + } + } + + pub fn get_whitelist_state( + &self, + issuer: &str, + serial: &str, + ) -> Result { + let issuer_serial = format!("{}:{}:{}", PREFIX_WL, issuer, serial); + match self.read_entry(&issuer_serial) { + Ok(Some(value)) => Ok(value), + Ok(None) => Ok(nsICertStorage::STATE_UNSET as i16), + Err(_) => Err(SecurityStateError::from("problem reading whitelist state")), + } + } + + pub fn is_data_fresh( + &self, + update_pref: &str, + allowed_staleness: &str, + ) -> Result { + let checked = match self.int_prefs.get(update_pref) { + Some(ch) => *ch, + None => 0, + }; + let staleness_seconds = match self.int_prefs.get(allowed_staleness) { + Some(st) => *st, + None => 0, + }; + + let update = SystemTime::UNIX_EPOCH + Duration::new(checked as u64, 0); + let staleness = Duration::new(staleness_seconds as u64, 0); + + Ok(match SystemTime::now().duration_since(update) { + Ok(duration) => duration <= staleness, + Err(_) => false, + }) + } + + pub fn is_blocklist_fresh(&self) -> Result { + self.is_data_fresh( + "services.blocklist.onecrl.checked", + "security.onecrl.maximum_staleness_in_seconds", + ) + } + + pub fn is_whitelist_fresh(&self) -> Result { + self.is_data_fresh( + "services.blocklist.intermediates.checked", + "security.onecrl.maximum_staleness_in_seconds", + ) + } + + pub fn is_enrollment_fresh(&self) -> Result { + self.is_data_fresh( + "services.blocklist.crlite.checked", + "security.onecrl.maximum_staleness_in_seconds", + ) + } + + pub fn pref_seen(&mut self, name: &str, value: i32) { + self.int_prefs.insert(name.to_owned(), value); + } +} + +fn get_path_from_directory_service(key: &str) -> Result { + let directory_service = match xpcom::services::get_DirectoryService() { + Some(ds) => ds, + _ => return Err(SecurityStateError::from("None")), + }; + + let cs_key = CString::new(key)?; + let mut requested_dir = GetterAddrefs::::new(); + + unsafe { + (*directory_service) + .Get( + (&cs_key).as_ptr(), + &nsIFile::IID as *const nsIID, + requested_dir.void_ptr(), + ) + .to_result() + .map_err(|res| SecurityStateError { + message: (*res.error_name()).as_str_unchecked().to_owned(), + }) + }?; + + let dir_path = match requested_dir.refptr() { + None => return Err(SecurityStateError::from("directory service failure")), + Some(refptr) => refptr, + }; + + let mut path = nsString::new(); + + unsafe { + (*dir_path) + .GetPath(&mut path as &mut nsAString) + // For reasons that aren't clear to me, NsresultExt does not + // implement std::error::Error (or Debug / Display). This map_err + // hack is a way to get an error with a useful message. + .to_result() + .map_err(|res| SecurityStateError { + message: (*res.error_name()).as_str_unchecked().to_owned(), + })?; + } + + Ok(PathBuf::from(format!("{}", path))) +} + +fn do_construct_cert_storage( + _outer: *const nsISupports, + iid: *const xpcom::nsIID, + result: *mut *mut xpcom::reexports::libc::c_void, +) -> Result<(), SecurityStateError> { + let path_buf = match get_path_from_directory_service("ProfD") { + Ok(path) => path, + Err(_) => match get_path_from_directory_service("TmpD") { + Ok(path) => path, + Err(e) => return Err(e), + }, + }; + + let cert_storage = CertStorage::allocate(InitCertStorage { + security_state: RwLock::new(SecurityState::new(path_buf)?), + }); + + unsafe { + cert_storage + .QueryInterface(iid, result) + // As above; greasy hack because NsresultExt + .to_result() + .map_err(|res| SecurityStateError { + message: (*res.error_name()).as_str_unchecked().to_owned(), + })?; + + return cert_storage.setup_prefs(); + }; +} + +fn read_int_pref(name: &str) -> Result { + let pref_service = match xpcom::services::get_PreferencesService() { + Some(ps) => ps, + _ => { + return Err(SecurityStateError::from( + "could not get preferences service", + )) + } + }; + + let prefs: RefPtr = match (*pref_service).query_interface() { + Some(pb) => pb, + _ => return Err(SecurityStateError::from("could not QI to nsIPrefBranch")), + }; + let pref_name = match CString::new(name) { + Ok(n) => n, + _ => return Err(SecurityStateError::from("could not build pref name string")), + }; + + let mut pref_value: i32 = -1; + + // We can't use GetIntPrefWithDefault because optional_argc is not + // supported. No matter, we can just check for failure and ignore + // any NS_ERROR_UNEXPECTED result. + let res = unsafe { (*prefs).GetIntPref((&pref_name).as_ptr(), (&mut pref_value) as *mut i32) }; + if !res.succeeded() { + match res.0 { + r if r == nsresult::NS_ERROR_UNEXPECTED as u32 => (), + _ => return Err(SecurityStateError::from("could not read pref")), // nserror::nsresult(err), + } + } + Ok(pref_value) +} + +#[no_mangle] +pub extern "C" fn cert_storage_constructor( + outer: *const nsISupports, + iid: *const xpcom::nsIID, + result: *mut *mut xpcom::reexports::libc::c_void, +) -> nserror::nsresult { + if !outer.is_null() { + return nserror::NS_ERROR_NO_AGGREGATION; + } + + match do_construct_cert_storage(outer, iid, result) { + Ok(_) => nserror::NS_OK, + Err(_) => { + // In future: log something so we know what went wrong? + nserror::NS_ERROR_FAILURE + } + } +} + +#[derive(xpcom)] +#[xpimplements(nsICertStorage, nsIObserver)] +#[refcnt = "atomic"] + +struct InitCertStorage { + security_state: RwLock, +} + +#[allow(non_snake_case)] +impl CertStorage { + unsafe fn setup_prefs(&self) -> Result<(), SecurityStateError> { + let int_prefs = [ + "services.blocklist.onecrl.checked", + "services.blocklist.intermediates.checked", + "services.blocklist.crlite.checked", + "security.onecrl.maximum_staleness_in_seconds", + ]; + + // Fetch add observers for relevant prefs + let pref_service = xpcom::services::get_PreferencesService().unwrap(); + let prefs: RefPtr = match (*pref_service).query_interface() { + Some(pb) => pb, + _ => return Err(SecurityStateError::from("could not QI to nsIPrefBranch")), + }; + + for pref in int_prefs.into_iter() { + let pref_nscstr = &nsCStr::from(pref.to_owned()) as &nsACString; + let rv = (*prefs).AddObserverImpl(pref_nscstr, self.coerce::(), false); + match read_int_pref(pref) { + Ok(up) => { + let mut ss = match self.security_state.write() { + Err(_) => return Err(SecurityStateError::from("could not get write lock")), + Ok(write_guard) => write_guard, + }; + ss.pref_seen(pref, up) + } + Err(_) => return Err(SecurityStateError::from("could not read pref")), + }; + assert!(rv.succeeded()); + } + + Ok(()) + } + + unsafe fn SetRevocationByIssuerAndSerial( + &self, + issuer: *const nsACString, + serial: *const nsACString, + state: i16, + ) -> nserror::nsresult { + if issuer.is_null() || serial.is_null() { + return nserror::NS_ERROR_FAILURE; + } + let mut ss = match self.security_state.write() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(write_guard) => write_guard, + }; + match ss.set_revocation_by_issuer_and_serial( + (*issuer).as_str_unchecked(), + (*serial).as_str_unchecked(), + state, + ) { + Ok(_) => nserror::NS_OK, + _ => nserror::NS_ERROR_FAILURE, + } + } + + unsafe fn SetRevocationBySubjectAndPubKey( + &self, + subject: *const nsACString, + pub_key_base64: *const nsACString, + state: i16, + ) -> nserror::nsresult { + if subject.is_null() || pub_key_base64.is_null() { + return nserror::NS_ERROR_FAILURE; + } + let mut ss = match self.security_state.write() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(write_guard) => write_guard, + }; + match ss.set_revocation_by_subject_and_pub_key( + (*subject).as_str_unchecked(), + (*pub_key_base64).as_str_unchecked(), + state, + ) { + Ok(_) => nserror::NS_OK, + _ => nserror::NS_ERROR_FAILURE, + } + } + + unsafe fn SetEnrollment( + &self, + issuer: *const nsACString, + serial: *const nsACString, + state: i16, + ) -> nserror::nsresult { + if issuer.is_null() || serial.is_null() { + return nserror::NS_ERROR_FAILURE; + } + let mut ss = match self.security_state.write() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(write_guard) => write_guard, + }; + match ss.set_enrollment( + (*issuer).as_str_unchecked(), + (*serial).as_str_unchecked(), + state, + ) { + Ok(_) => nserror::NS_OK, + _ => nserror::NS_ERROR_FAILURE, + } + } + + unsafe fn SetWhitelist( + &self, + issuer: *const nsACString, + serial: *const nsACString, + state: i16, + ) -> nserror::nsresult { + if issuer.is_null() || serial.is_null() { + return nserror::NS_ERROR_FAILURE; + } + let mut ss = match self.security_state.write() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(write_guard) => write_guard, + }; + match ss.set_whitelist( + (*issuer).as_str_unchecked(), + (*serial).as_str_unchecked(), + state, + ) { + Ok(_) => nserror::NS_OK, + _ => nserror::NS_ERROR_FAILURE, + } + } + + unsafe fn GetRevocationState( + &self, + issuer: *const nsACString, + serial: *const nsACString, + subject: *const nsACString, + pub_key_base64: *const nsACString, + state: *mut i16, + ) -> nserror::nsresult { + if issuer.is_null() || serial.is_null() || subject.is_null() || pub_key_base64.is_null() { + return nserror::NS_ERROR_FAILURE; + } + + let ss = match self.security_state.read() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(read_guard) => read_guard, + }; + + *state = nsICertStorage::STATE_UNSET as i16; + match ss.get_revocation_state( + (*issuer).as_str_unchecked(), + (*serial).as_str_unchecked(), + (*subject).as_str_unchecked(), + (*pub_key_base64).as_str_unchecked(), + ) { + Ok(st) => { + *state = st; + nserror::NS_OK + } + _ => nserror::NS_ERROR_FAILURE, + } + } + + unsafe fn GetEnrollmentState( + &self, + issuer: *const nsACString, + serial: *const nsACString, + state: *mut i16, + ) -> nserror::nsresult { + let ss = match self.security_state.read() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(read_guard) => read_guard, + }; + + *state = nsICertStorage::STATE_UNSET as i16; + + match ss.get_enrollment_state((*issuer).as_str_unchecked(), (*serial).as_str_unchecked()) { + Ok(st) => { + *state = st; + nserror::NS_OK + } + _ => nserror::NS_ERROR_FAILURE, + } + } + + unsafe fn GetWhitelistState( + &self, + issuer: *const nsACString, + serial: *const nsACString, + state: *mut i16, + ) -> nserror::nsresult { + let ss = match self.security_state.read() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(read_guard) => read_guard, + }; + + *state = nsICertStorage::STATE_UNSET as i16; + + match ss.get_whitelist_state((*issuer).as_str_unchecked(), (*serial).as_str_unchecked()) { + Ok(st) => { + *state = st; + nserror::NS_OK + } + _ => nserror::NS_ERROR_FAILURE, + } + } + + unsafe fn IsBlocklistFresh(&self, fresh: *mut bool) -> nserror::nsresult { + *fresh = false; + + let ss = match self.security_state.read() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(read_guard) => read_guard, + }; + + *fresh = match ss.is_blocklist_fresh() { + Ok(is_fresh) => is_fresh, + Err(_) => false, + }; + + nserror::NS_OK + } + + unsafe fn IsWhitelistFresh(&self, fresh: *mut bool) -> nserror::nsresult { + *fresh = false; + + let ss = match self.security_state.read() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(read_guard) => read_guard, + }; + + *fresh = match ss.is_whitelist_fresh() { + Ok(is_fresh) => is_fresh, + Err(_) => false, + }; + + nserror::NS_OK + } + + unsafe fn IsEnrollmentFresh(&self, fresh: *mut bool) -> nserror::nsresult { + *fresh = false; + + let ss = match self.security_state.read() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(read_guard) => read_guard, + }; + + *fresh = match ss.is_enrollment_fresh() { + Ok(is_fresh) => is_fresh, + Err(_) => false, + }; + + nserror::NS_OK + } + + unsafe fn Observe( + &self, + subject: *const nsISupports, + topic: *const c_char, + pref_name: *const i16, + ) -> nserror::nsresult { + match CStr::from_ptr(topic).to_str() { + Ok("nsPref:changed") => { + let mut pref_value: i32 = 0; + + let prefs: RefPtr = match (*subject).query_interface() { + Some(pb) => pb, + _ => return nserror::NS_ERROR_FAILURE, + }; + + // Convert our wstring pref_name to a cstring (via nsCString's + // utf16 to utf8 conversion) + let mut len: usize = 0; + while (*(pref_name.offset(len as isize))) != 0 { + len += 1; + } + let name_slice = slice::from_raw_parts(pref_name as *const u16, len); + let mut name_string = nsCString::new(); + name_string.assign_utf16_to_utf8(name_slice); + + let pref_name = match CString::new(name_string.as_str_unchecked()) { + Ok(n) => n, + _ => return nserror::NS_ERROR_FAILURE, + }; + + let res = prefs.GetIntPref( + (&pref_name).as_ptr(), + (&mut pref_value) as *mut i32, + ); + + if !res.succeeded() { + return res; + } + + let mut ss = match self.security_state.write() { + Err(_) => return nserror::NS_ERROR_FAILURE, + Ok(write_guard) => write_guard, + }; + ss.pref_seen(name_string.as_str_unchecked(), pref_value); + } + _ => (), + } + nserror::NS_OK + } +} diff --git a/security/manager/ssl/components.conf b/security/manager/ssl/components.conf index 6701e4512b80..35186a381bcd 100644 --- a/security/manager/ssl/components.conf +++ b/security/manager/ssl/components.conf @@ -157,10 +157,10 @@ Classes = [ 'legacy_constructor': 'mozilla::psm::NSSConstructor', }, { - 'cid': '{11aefd53-2fbb-4c92-a0c1-053212ae42d0}', - 'contract_ids': ['@mozilla.org/security/certblocklist;1'], - 'type': 'CertBlocklist', - 'legacy_constructor': 'mozilla::psm::NSSConstructor', + 'cid': '{16e5c837-f877-4e23-9c64-eddf905e30e6}', + 'contract_ids': ['@mozilla.org/security/certstorage;1'], + 'headers': ['/security/manager/ssl/cert_storage/src/cert_storage.h'], + 'legacy_constructor': 'construct_cert_storage', }, { 'cid': '{57972956-5718-42d2-8070-b3fc72212eaf}', diff --git a/security/manager/ssl/moz.build b/security/manager/ssl/moz.build index cba9b9c13a02..152183e552c2 100644 --- a/security/manager/ssl/moz.build +++ b/security/manager/ssl/moz.build @@ -11,9 +11,9 @@ XPIDL_SOURCES += [ 'nsIASN1PrintableItem.idl', 'nsIASN1Sequence.idl', 'nsIBadCertListener2.idl', - 'nsICertBlocklist.idl', 'nsICertificateDialogs.idl', 'nsICertOverrideService.idl', + 'nsICertStorage.idl', 'nsIClientAuthDialogs.idl', 'nsIContentSignatureVerifier.idl', 'nsICryptoHash.idl', @@ -99,7 +99,7 @@ EXPORTS.ipc += [ ] UNIFIED_SOURCES += [ - 'CertBlocklist.cpp', + 'cert_storage/src/cert_storage.cpp', 'ContentSignatureVerifier.cpp', 'CryptoTask.cpp', 'CSTrustDomain.cpp', diff --git a/security/manager/ssl/nsICertBlocklist.idl b/security/manager/ssl/nsICertBlocklist.idl deleted file mode 100644 index 9d8f4eb2a3dd..000000000000 --- a/security/manager/ssl/nsICertBlocklist.idl +++ /dev/null @@ -1,64 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIX509Cert; - -%{C++ -#define NS_CERTBLOCKLIST_CONTRACTID "@mozilla.org/security/certblocklist;1" -%} - -/** - * Represents a service to add certificates as explicitly blocked/distrusted. - */ -[scriptable, uuid(e0654480-f433-11e4-b939-0800200c9a66)] -interface nsICertBlocklist : nsISupports { - /** - * Add details of a revoked certificate : - * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER). - */ - [must_use] - void revokeCertByIssuerAndSerial(in ACString issuer, - in ACString serialNumber); - - /** - * Add details of a revoked certificate : - * subject name (base-64 encoded DER) and hash of public key (base-64 encoded - * sha-256 hash of the public key). - */ - [must_use] - void revokeCertBySubjectAndPubKey(in ACString subject, - in ACString pubKeyHash); - - /** - * Persist (fresh) blocklist entries to the profile (if a profile directory is - * available). Note: calling this will result in synchronous I/O. - */ - [must_use] - void saveEntries(); - - /** - * Check if a certificate is blocked. - * issuer - issuer name, DER, Base64 encoded - * serial - serial number, DER, BAse64 encoded - * subject - subject name, DER, Base64 encoded - * pubkey - public key, DER, Base64 encoded - */ - [must_use] - boolean isCertRevoked(in ACString issuer, - in ACString serial, - in ACString subject, - in ACString pubkey); - - /** - * Check that the blocklist data is current. Specifically, that the current - * time is no more than security.onecrl.maximum_staleness_in_seconds seconds - * after the last blocklist update (as stored in the - * app.update.lastUpdateTime.blocklist-background-update-timer pref) - */ - [must_use] - boolean isBlocklistFresh(); -}; diff --git a/security/manager/ssl/nsICertStorage.idl b/security/manager/ssl/nsICertStorage.idl new file mode 100644 index 000000000000..740c403c0a3b --- /dev/null +++ b/security/manager/ssl/nsICertStorage.idl @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#define NS_CERTSTORAGE_CONTRACTID "@mozilla.org/security/certstorage;1" +%} + +[scriptable, uuid(327100a7-3401-45ef-b160-bf880f1016fd)] +interface nsICertStorage : nsISupports { + const short STATE_UNSET = 0; + const short STATE_ENFORCE = 1; + + /** + * Set the revocation state of a certificate by issuer and serial number: + * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER). + */ + [must_use] + void setRevocationByIssuerAndSerial(in ACString issuer, + in ACString serialNumber, + in short state); + + /** + * Set the revocation state of a certificate by subject and public key hash: + * subject name (base-64 encoded DER) and hash of public key (base-64 encoded + * sha-256 hash of the public key). + * state (short) is STATE_ENFORCE for revoked certs, STATE_UNSET otherwise. + */ + [must_use] + void setRevocationBySubjectAndPubKey(in ACString subject, + in ACString pubKeyHash, + in short state); + + /** + * Set the whitelist state of an intermediate certificate by issuer and + * serial number: + * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER). + * state (short) is STATE_ENFORCE for whitelisted certs, STATE_UNSET otherwise. + */ + [must_use] + void setWhitelist(in ACString issuer, + in ACString serialNumber, + in short state); + + /** + * Set the CRLite enrollment state of a certificate by issuer and serial + * number: + * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER). + * state (short) is STATE_ENFORCE for enrolled certs, STATE_UNSET otherwise. + */ + [must_use] + void setEnrollment(in ACString issuer, + in ACString serialNumber, + in short state); + + /** + * Get the revocation state of a certificate. + * issuer - issuer name, DER, Base64 encoded + * serial - serial number, DER, Base64 encoded + * subject - subject name, DER, Base64 encoded + * pubkey - public key, DER, Base64 encoded + */ + [must_use] + short getRevocationState(in ACString issuer, + in ACString serial, + in ACString subject, + in ACString pubkey); + + /** + * Get the CRLite enrollment status of a certificate. + * issuer - issuer name, DER, Base64 encoded + * serial - serial number, DER, Base64 encoded + */ + [must_use] + short getEnrollmentState(in ACString issuer, + in ACString serial); + + /** + * Get the whitelist status of an intermediate certificate. + * issuer - issuer name, DER, Base64 encoded + * serial - serial number, DER, Base64 encoded + */ + [must_use] + short getWhitelistState(in ACString issuer, + in ACString serial); + + /** + * Check that the blocklist data is current. Specifically, that the current + * time is no more than security.onecrl.maximum_staleness_in_seconds seconds + * after the last blocklist update (as stored in the + * services.blocklist.onecrl.checked pref) + */ + [must_use] + boolean isBlocklistFresh(); + + /** + * Check that the whitelist data is current. Specifically, that the current + * time is no more than security.onecrl.maximum_staleness_in_seconds seconds + * after the last whitelist update (as stored in the + * services.blocklist.intermediates.checked pref) + */ + [must_use] + boolean isWhitelistFresh(); + + /** + * Check that the CRLite enrollment data is current. Specifically, that the current + * time is no more than security.onecrl.maximum_staleness_in_seconds seconds + * after the last crlite enrollment update (as stored in the + * services.blocklist.crlite.checked pref) + */ + [must_use] + boolean isEnrollmentFresh(); +}; diff --git a/security/manager/ssl/nsNSSModule.cpp b/security/manager/ssl/nsNSSModule.cpp index c7ee273c9809..0ed630de230e 100644 --- a/security/manager/ssl/nsNSSModule.cpp +++ b/security/manager/ssl/nsNSSModule.cpp @@ -6,9 +6,10 @@ #include "nsNSSModule.h" -#include "CertBlocklist.h" #include "ContentSignatureVerifier.h" #include "NSSErrorsService.h" +#include "OSKeyStore.h" +#include "OSReauthenticator.h" #include "PKCS11ModuleDB.h" #include "PSMContentListener.h" #include "SecretDecoderRing.h" @@ -33,8 +34,6 @@ #include "nsSecureBrowserUIImpl.h" #include "nsSiteSecurityService.h" #include "nsXULAppAPI.h" -#include "OSKeyStore.h" -#include "OSReauthenticator.h" #ifdef MOZ_XUL # include "nsCertTree.h" @@ -148,8 +147,6 @@ IMPL(nsRandomGenerator, nullptr, ProcessRestriction::AnyProcess) IMPL(TransportSecurityInfo, nullptr, ProcessRestriction::AnyProcess) IMPL(nsSiteSecurityService, &nsSiteSecurityService::Init, ProcessRestriction::AnyProcess, ThreadRestriction::MainThreadOnly) -IMPL(CertBlocklist, &CertBlocklist::Init, ProcessRestriction::ParentProcessOnly, - ThreadRestriction::MainThreadOnly) IMPL(OSKeyStore, nullptr, ProcessRestriction::ParentProcessOnly, ThreadRestriction::MainThreadOnly) IMPL(OSReauthenticator, nullptr, ProcessRestriction::ParentProcessOnly, diff --git a/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js b/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js index 3c151d4ac98e..04673992a02b 100644 --- a/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js +++ b/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js @@ -100,11 +100,12 @@ add_task(async function testRevoked() { // Note that there's currently no way to un-do this. This should only be a // problem if another test re-uses a certificate with this same key (perhaps // likely) and subject (less likely). - let certBlocklist = Cc["@mozilla.org/security/certblocklist;1"] - .getService(Ci.nsICertBlocklist); - certBlocklist.revokeCertBySubjectAndPubKey( + let certBlocklist = Cc["@mozilla.org/security/certstorage;1"] + .getService(Ci.nsICertStorage); + certBlocklist.setRevocationBySubjectAndPubKey( "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked - "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="); // hash of the shared key + "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=", // hash of the shared key + Ci.nsICertStorage.STATE_ENFORCE); // yes, we want this to be revoked let cert = await readCertificate("revoked.pem", ",,"); let win = await displayCertificate(cert); // As of bug 1312827, OneCRL only applies to TLS web server certificates, so diff --git a/security/manager/ssl/tests/unit/test_cert_blocklist.js b/security/manager/ssl/tests/unit/test_cert_storage.js similarity index 73% rename from security/manager/ssl/tests/unit/test_cert_blocklist.js rename to security/manager/ssl/tests/unit/test_cert_storage.js index 588ba7afe733..e9c0a90ea7b5 100644 --- a/security/manager/ssl/tests/unit/test_cert_blocklist.js +++ b/security/manager/ssl/tests/unit/test_cert_storage.js @@ -12,9 +12,9 @@ // * it does a sanity check to ensure other cert verifier behavior is // unmodified -const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); -const { RemoteSettings } = ChromeUtils.import("resource://services-settings/remote-settings.js"); -const BlocklistClients = ChromeUtils.import("resource://services-common/blocklist-clients.js", null); +const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm", {}); +const { RemoteSettings } = ChromeUtils.import("resource://services-settings/remote-settings.js", {}); +const BlocklistClients = ChromeUtils.import("resource://services-common/blocklist-clients.js", {}); // First, we need to setup appInfo for the blocklist service to work var id = "xpcshell@tests.mozilla.org"; @@ -187,8 +187,8 @@ function load_cert(cert, trust) { function test_is_revoked(certList, issuerString, serialString, subjectString, pubKeyString) { - return certList.isCertRevoked(btoa(issuerString), btoa(serialString), - btoa(subjectString), btoa(pubKeyString)); + return certList.getRevocationState(btoa(issuerString), btoa(serialString), + btoa(subjectString), btoa(pubKeyString)) == Ci.nsICertStorage.STATE_ENFORCE; } function fetch_blocklist() { @@ -202,89 +202,14 @@ function fetch_blocklist() { return RemoteSettings.pollChanges(); } -function* generate_revocations_txt_lines() { - let profile = do_get_profile(); - let revocations = profile.clone(); - revocations.append("revocations.txt"); - ok(revocations.exists(), "the revocations file should exist"); - let inputStream = Cc["@mozilla.org/network/file-input-stream;1"] - .createInstance(Ci.nsIFileInputStream); - inputStream.init(revocations, -1, -1, 0); - inputStream.QueryInterface(Ci.nsILineInputStream); - let hasmore = false; - do { - let line = {}; - hasmore = inputStream.readLine(line); - yield line.value; - } while (hasmore); -} - -// Check that revocations.txt contains, in any order, the lines -// ("top-level lines") that are the keys in |expected|, each followed -// immediately by the lines ("sublines") in expected[topLevelLine] -// (again, in any order). -function check_revocations_txt_contents(expected) { - let lineGenerator = generate_revocations_txt_lines(); - let firstLine = lineGenerator.next(); - equal(firstLine.done, false, - "first line of revocations.txt should be present"); - equal(firstLine.value, "# Auto generated contents. Do not edit.", - "first line of revocations.txt"); - let line = lineGenerator.next(); - let topLevelFound = {}; - while (true) { - if (line.done) { - break; - } - - ok(line.value in expected, - `${line.value} should be an expected top-level line in revocations.txt`); - ok(!(line.value in topLevelFound), - `should not have seen ${line.value} before in revocations.txt`); - topLevelFound[line.value] = true; - let topLevelLine = line.value; - - let sublines = expected[line.value]; - let subFound = {}; - while (true) { - line = lineGenerator.next(); - if (line.done || !(line.value in sublines)) { - break; - } - ok(!(line.value in subFound), - `should not have seen ${line.value} before in revocations.txt`); - subFound[line.value] = true; - } - for (let subline in sublines) { - ok(subFound[subline], - `should have found ${subline} below ${topLevelLine} in revocations.txt`); - } - } - for (let topLevelLine in expected) { - ok(topLevelFound[topLevelLine], - `should have found ${topLevelLine} in revocations.txt`); - } -} - function run_test() { // import the certificates we need load_cert("test-ca", "CTu,CTu,CTu"); load_cert("test-int", ",,"); load_cert("other-test-ca", "CTu,CTu,CTu"); - let certList = Cc["@mozilla.org/security/certblocklist;1"] - .getService(Ci.nsICertBlocklist); - - let expected = { "MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5": - { "\tVCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=": true }, - "MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E=": - { " Rym6o+VN9xgZXT/QLrvN/nv1ZN4=": true}, - "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=": - { " a0X7/7DlTaedpgrIJg25iBPOkIM=": true}, - "YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy": - { " YW5vdGhlciBzZXJpYWwu": true, - " c2VyaWFsMi4=": true }, - }; + let certList = Cc["@mozilla.org/security/certstorage;1"] + .getService(Ci.nsICertStorage); add_task(async function() { // check some existing items in revocations.txt are blocked. Since the @@ -348,10 +273,6 @@ function run_test() { "some imaginary subject", "some imaginary pubkey"), "issuer / serial pair should be blocked"); - // Check the blocklist entry has been persisted properly to the backing - // file - check_revocations_txt_contents(expected); - // Check the blocklisted intermediate now causes a failure let file = "test_onecrl/test-int-ee.pem"; await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE); @@ -374,22 +295,10 @@ function run_test() { // Check a bad cert is still bad (unknown issuer) file = "bad_certs/unknownissuer.pem"; await verify_cert(file, SEC_ERROR_UNKNOWN_ISSUER); - - // check that save with no further update is a no-op - let lastModified = gRevocations.lastModifiedTime; - // add an already existing entry - certList.revokeCertByIssuerAndSerial("YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy", - "c2VyaWFsMi4="); - certList.saveEntries(); - let newModified = gRevocations.lastModifiedTime; - equal(lastModified, newModified, - "saveEntries with no modifications should not update the backing file"); }); - add_test(function() { - // Check the blocklist entry has not changed - check_revocations_txt_contents(expected); - run_next_test(); + add_task(async function() { + ok(certList.isBlocklistFresh(), "Blocklist should be fresh."); }); run_next_test(); diff --git a/security/manager/ssl/tests/unit/xpcshell.ini b/security/manager/ssl/tests/unit/xpcshell.ini index 9f4cf04fd806..ebad1a912702 100644 --- a/security/manager/ssl/tests/unit/xpcshell.ini +++ b/security/manager/ssl/tests/unit/xpcshell.ini @@ -46,7 +46,7 @@ support-files = # exist on that platform. # FIPS still works on Linux/Windows, so this test doesn't make any sense there. skip-if = os != 'mac' -[test_cert_blocklist.js] +[test_cert_storage.js] tags = addons psm blocklist [test_cert_chains.js] run-sequentially = hardcoded ports diff --git a/services/common/blocklist-clients.js b/services/common/blocklist-clients.js index 0cd8a9c714e8..f0b20db27de8 100644 --- a/services/common/blocklist-clients.js +++ b/services/common/blocklist-clients.js @@ -39,17 +39,33 @@ const PREF_BLOCKLIST_GFX_SIGNER = "services.blocklist.gfx.signer"; * * @param {Object} data Current records in the local db. */ -async function updateCertBlocklist({ data: { current: records } }) { - const certList = Cc["@mozilla.org/security/certblocklist;1"] - .getService(Ci.nsICertBlocklist); - for (let item of records) { +async function updateCertBlocklist({ data: { created, updated, deleted } }) { + const certList = Cc["@mozilla.org/security/certstorage;1"] + .getService(Ci.nsICertStorage); + for (let item of deleted) { + if (item.issuerName && item.serialNumber) { + certList.setRevocationByIssuerAndSerial(item.issuerName, + item.serialNumber, + Ci.nsICertStorage.STATE_UNSET); + } else if (item.subject && item.pubKeyHash) { + certList.setRevocationBySubjectAndPubKey(item.subject, + item.pubKeyHash, + Ci.nsICertStorage.STATE_UNSET); + } + } + + const toAdd = created.concat(updated.map(u => u.new)); + + for (let item of toAdd) { try { if (item.issuerName && item.serialNumber) { - certList.revokeCertByIssuerAndSerial(item.issuerName, - item.serialNumber); + certList.setRevocationByIssuerAndSerial(item.issuerName, + item.serialNumber, + Ci.nsICertStorage.STATE_ENFORCE); } else if (item.subject && item.pubKeyHash) { - certList.revokeCertBySubjectAndPubKey(item.subject, - item.pubKeyHash); + certList.setRevocationBySubjectAndPubKey(item.subject, + item.pubKeyHash, + Ci.nsICertStorage.STATE_ENFORCE); } } catch (e) { // prevent errors relating to individual blocklist entries from @@ -58,7 +74,6 @@ async function updateCertBlocklist({ data: { current: records } }) { Cu.reportError(e); } } - certList.saveEntries(); } /** diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index 777e65bc107c..41e8f792e1c0 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -32,6 +32,7 @@ env_logger = {version = "0.5", default-features = false} # disable `regex` to re cose-c = { version = "0.1.5" } jsrust_shared = { path = "../../../../js/src/rust/shared", optional = true } arrayvec = "0.4" +cert_storage = { path = "../../../../security/manager/ssl/cert_storage" } [build-dependencies] rustc_version = "0.2" diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index 579a4af36f38..f5131e1bfad8 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -31,6 +31,7 @@ extern crate env_logger; extern crate u2fhid; extern crate gkrust_utils; extern crate log; +extern crate cert_storage; extern crate cosec; extern crate rsdparsa_capi; #[cfg(feature = "spidermonkey_rust")] diff --git a/xpcom/build/Services.py b/xpcom/build/Services.py index 81bdef7dc053..38ed022ad2b1 100644 --- a/xpcom/build/Services.py +++ b/xpcom/build/Services.py @@ -14,6 +14,8 @@ service('ToolkitChromeRegistryService', 'nsIToolkitChromeRegistry', "@mozilla.org/chrome/chrome-registry;1") service('XULChromeRegistryService', 'nsIXULChromeRegistry', "@mozilla.org/chrome/chrome-registry;1") +service('DirectoryService', 'nsIProperties', + "@mozilla.org/file/directory_service;1"), service('IOService', 'nsIIOService', "@mozilla.org/network/io-service;1") service('ObserverService', 'nsIObserverService', @@ -22,6 +24,8 @@ service('StringBundleService', 'nsIStringBundleService', "@mozilla.org/intl/stringbundle;1") service('PermissionManager', 'nsIPermissionManager', "@mozilla.org/permissionmanager;1") +service('PreferencesService', 'nsIPrefService', + "@mozilla.org/preferences-service;1") service('ServiceWorkerManager', 'nsIServiceWorkerManager', "@mozilla.org/serviceworkers/manager;1") service('AsyncShutdown', 'nsIAsyncShutdownService', @@ -67,6 +71,7 @@ CPP_INCLUDES = """ #include "IHistory.h" #include "nsIXPConnect.h" #include "nsIPermissionManager.h" +#include "nsIPrefService.h" #include "nsIServiceWorkerManager.h" #include "nsICacheStorageService.h" #include "nsIStreamTransportService.h"