Bug 1547877 - enable configuration of new cert storage implementation r=keeler

Differential Revision: https://phabricator.services.mozilla.com/D29306

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Myk Melez 2019-05-02 23:02:13 +00:00
parent 926c25f9a4
commit bfe7c7e0b4
26 changed files with 1247 additions and 75 deletions

View File

@ -81,7 +81,11 @@
#include "nsHttpHandler.h"
#include "nsNSSComponent.h"
#include "nsIRedirectHistoryEntry.h"
#include "nsICertStorage.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "nsICertStorage.h"
#else
# include "nsICertBlocklist.h"
#endif
#include "nsICertOverrideService.h"
#include "nsQueryObject.h"
#include "mozIThirdPartyUtil.h"
@ -2565,7 +2569,11 @@ void net_EnsurePSMInit() {
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<nsISupports> sss = do_GetService(NS_SSSERVICE_CONTRACTID);
#ifdef MOZ_NEW_CERT_STORAGE
nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTSTORAGE_CONTRACTID);
#else
nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTBLOCKLIST_CONTRACTID);
#endif
nsCOMPtr<nsISupports> cos = do_GetService(NS_CERTOVERRIDE_CONTRACTID);
}

View File

@ -14,7 +14,9 @@
#include "PublicKeyPinningService.h"
#include "cert.h"
#include "certdb.h"
#include "cert_storage/src/cert_storage.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "cert_storage/src/cert_storage.h"
#endif
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "mozilla/Move.h"
@ -88,7 +90,11 @@ NSSCertDBTrustDomain::NSSCertDBTrustDomain(
mBuiltChain(builtChain),
mPinningTelemetryInfo(pinningTelemetryInfo),
mHostname(hostname),
#ifdef MOZ_NEW_CERT_STORAGE
mCertStorage(do_GetService(NS_CERT_STORAGE_CID)),
#else
mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)),
#endif
mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED),
mSCTListFromCertificate(),
mSCTListFromOCSPStapling() {}
@ -98,6 +104,7 @@ Result NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName,
Vector<Input> rootCandidates;
Vector<Input> intermediateCandidates;
#ifdef MOZ_NEW_CERT_STORAGE
if (!mCertStorage) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
@ -122,6 +129,7 @@ Result NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName,
return Result::FATAL_ERROR_NO_MEMORY;
}
}
#endif
SECItem encodedIssuerNameItem = UnsafeMapInputToSECItem(encodedIssuerName);
@ -216,13 +224,18 @@ Result NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
}
// Check the certificate against the OneCRL cert blocklist
#ifdef MOZ_NEW_CERT_STORAGE
if (!mCertStorage) {
#else
if (!mCertBlocklist) {
#endif
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
// The certificate blocklist currently only applies to TLS server
// certificates.
if (mCertDBTrustType == trustSSL) {
#ifdef MOZ_NEW_CERT_STORAGE
int16_t revocationState;
nsTArray<uint8_t> issuerBytes;
@ -232,18 +245,38 @@ Result NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
nsresult nsrv = BuildRevocationCheckArrays(
candidateCert, issuerBytes, serialBytes, subjectBytes, pubKeyBytes);
#else
bool isCertRevoked;
nsAutoCString encIssuer;
nsAutoCString encSerial;
nsAutoCString encSubject;
nsAutoCString encPubKey;
nsresult nsrv = BuildRevocationCheckStrings(
candidateCert.get(), encIssuer, encSerial, encSubject, encPubKey);
#endif
if (NS_FAILED(nsrv)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
#ifdef MOZ_NEW_CERT_STORAGE
nsrv = mCertStorage->GetRevocationState(
issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState);
#else
nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject,
encPubKey, &isCertRevoked);
#endif
if (NS_FAILED(nsrv)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
#ifdef MOZ_NEW_CERT_STORAGE
if (revocationState == nsICertStorage::STATE_ENFORCE) {
#else
if (isCertRevoked) {
#endif
MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
("NSSCertDBTrustDomain: certificate is in blocklist"));
return Result::ERROR_REVOKED_CERTIFICATE;
@ -497,7 +530,11 @@ Result NSSCertDBTrustDomain::CheckRevocation(
// If we have a fresh OneCRL Blocklist we can skip OCSP for CA certs
bool blocklistIsFresh;
#ifdef MOZ_NEW_CERT_STORAGE
nsresult nsrv = mCertStorage->IsBlocklistFresh(&blocklistIsFresh);
#else
nsresult nsrv = mCertBlocklist->IsBlocklistFresh(&blocklistIsFresh);
#endif
if (NS_FAILED(nsrv)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
@ -1273,6 +1310,7 @@ nsresult DefaultServerNicknameForCert(const CERTCertificate* cert,
return NS_ERROR_FAILURE;
}
#ifdef MOZ_NEW_CERT_STORAGE
nsresult BuildRevocationCheckArrays(const UniqueCERTCertificate& cert,
/*out*/ nsTArray<uint8_t>& issuerBytes,
/*out*/ nsTArray<uint8_t>& serialBytes,
@ -1304,6 +1342,45 @@ nsresult BuildRevocationCheckArrays(const UniqueCERTCertificate& cert,
}
return NS_OK;
}
#else
nsresult BuildRevocationCheckStrings(const CERTCertificate* cert,
/*out*/ nsCString& encIssuer,
/*out*/ nsCString& encSerial,
/*out*/ nsCString& encSubject,
/*out*/ nsCString& encPubKey) {
// Convert issuer, serial, subject and pubKey data to Base64 encoded DER
nsDependentCSubstring issuerString(
BitwiseCast<char*, uint8_t*>(cert->derIssuer.data), cert->derIssuer.len);
nsDependentCSubstring serialString(
BitwiseCast<char*, uint8_t*>(cert->serialNumber.data),
cert->serialNumber.len);
nsDependentCSubstring subjectString(
BitwiseCast<char*, uint8_t*>(cert->derSubject.data),
cert->derSubject.len);
nsDependentCSubstring pubKeyString(
BitwiseCast<char*, uint8_t*>(cert->derPublicKey.data),
cert->derPublicKey.len);
nsresult rv = Base64Encode(issuerString, encIssuer);
if (NS_FAILED(rv)) {
return rv;
}
rv = Base64Encode(serialString, encSerial);
if (NS_FAILED(rv)) {
return rv;
}
rv = Base64Encode(subjectString, encSubject);
if (NS_FAILED(rv)) {
return rv;
}
rv = Base64Encode(pubKeyString, encPubKey);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
#endif
/**
* Given a list of certificates representing a verified certificate path from an

View File

@ -11,7 +11,11 @@
#include "ScopedNSSTypes.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/TimeStamp.h"
#include "nsICertStorage.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "nsICertStorage.h"
#else
# include "nsICertBlocklist.h"
#endif
#include "nsString.h"
#include "mozpkix/pkixtypes.h"
#include "secmodt.h"
@ -59,6 +63,7 @@ void UnloadLoadableRoots();
nsresult DefaultServerNicknameForCert(const CERTCertificate* cert,
/*out*/ nsCString& nickname);
#ifdef MOZ_NEW_CERT_STORAGE
/**
* Build nsTArray<uint8_t>s out of the issuer, serial, subject and public key
* data from the supplied certificate for use in revocation checks.
@ -82,6 +87,31 @@ nsresult BuildRevocationCheckArrays(const UniqueCERTCertificate& cert,
/*out*/ nsTArray<uint8_t>& serialBytes,
/*out*/ nsTArray<uint8_t>& subjectBytes,
/*out*/ nsTArray<uint8_t>& pubKeyBytes);
#else
/**
* Build strings of base64 encoded issuer, serial, subject and public key data
* from the supplied certificate for use in revocation checks.
*
* @param cert
* The CERTCertificate* from which to extract the data.
* @param out encIssuer
* The string to populate with base64 encoded issuer data.
* @param out encSerial
* The string to populate with base64 encoded serial number data.
* @param out encSubject
* The string to populate with base64 encoded subject data.
* @param out encPubKey
* The string to populate with base64 encoded public key data.
* @return
* NS_OK, unless there's a Base64 encoding problem, in which case
* NS_ERROR_FAILURE.
*/
nsresult BuildRevocationCheckStrings(const CERTCertificate* cert,
/*out*/ nsCString& encIssuer,
/*out*/ nsCString& encSerial,
/*out*/ nsCString& encSubject,
/*out*/ nsCString& encPubKey);
#endif
void SaveIntermediateCerts(const UniqueCERTCertList& certList);
@ -232,7 +262,11 @@ class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain {
UniqueCERTCertList& mBuiltChain; // non-owning
PinningTelemetryInfo* mPinningTelemetryInfo;
const char* mHostname; // non-owning - only used for pinning checks
#ifdef MOZ_NEW_CERT_STORAGE
nsCOMPtr<nsICertStorage> mCertStorage;
#else
nsCOMPtr<nsICertBlocklist> mCertBlocklist;
#endif
CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
// Certificate Transparency data extracted during certificate verification
UniqueSECItem mSCTListFromCertificate;

View File

@ -4,11 +4,15 @@
* 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"
#ifdef MOZ_NEW_CERT_STORAGE
# include "cert_storage/src/cert_storage.h"
#endif
#include "CSTrustDomain.h"
#include "mozilla/Base64.h"
#include "mozilla/Preferences.h"
#include "nsDirectoryServiceUtils.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "nsDirectoryServiceUtils.h"
#endif
#include "nsNSSCertificate.h"
#include "nsNSSComponent.h"
#include "NSSCertDBTrustDomain.h"
@ -26,7 +30,11 @@ static LazyLogModule gTrustDomainPRLog("CSTrustDomain");
CSTrustDomain::CSTrustDomain(UniqueCERTCertList& certChain)
: mCertChain(certChain),
#ifdef MOZ_NEW_CERT_STORAGE
mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)) {}
#else
mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)) {}
#endif
Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
const CertPolicyId& policy,
@ -44,6 +52,7 @@ Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
return MapPRErrorCodeToResult(PR_GetError());
}
#ifdef MOZ_NEW_CERT_STORAGE
nsTArray<uint8_t> issuerBytes;
nsTArray<uint8_t> serialBytes;
nsTArray<uint8_t> subjectBytes;
@ -51,18 +60,37 @@ Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
nsresult nsrv = BuildRevocationCheckArrays(
candidateCert, issuerBytes, serialBytes, subjectBytes, pubKeyBytes);
#else
nsAutoCString encIssuer;
nsAutoCString encSerial;
nsAutoCString encSubject;
nsAutoCString encPubKey;
nsresult nsrv = BuildRevocationCheckStrings(candidateCert.get(), encIssuer,
encSerial, encSubject, encPubKey);
#endif
if (NS_FAILED(nsrv)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
#ifdef MOZ_NEW_CERT_STORAGE
int16_t revocationState;
nsrv = mCertBlocklist->GetRevocationState(
issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState);
#else
bool isCertRevoked;
nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject,
encPubKey, &isCertRevoked);
#endif
if (NS_FAILED(nsrv)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
#ifdef MOZ_NEW_CERT_STORAGE
if (revocationState == nsICertStorage::STATE_ENFORCE) {
#else
if (isCertRevoked) {
#endif
CSTrust_LOG(("CSTrustDomain: certificate is revoked\n"));
return Result::ERROR_REVOKED_CERTIFICATE;
}

View File

@ -11,7 +11,11 @@
#include "mozilla/StaticMutex.h"
#include "mozilla/UniquePtr.h"
#include "nsDebug.h"
#include "nsICertStorage.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "nsICertStorage.h"
#else
# include "nsICertBlocklist.h"
#endif
#include "nsIX509CertDB.h"
#include "ScopedNSSTypes.h"
@ -73,7 +77,11 @@ class CSTrustDomain final : public mozilla::pkix::TrustDomain {
private:
/*out*/ UniqueCERTCertList& mCertChain;
#ifdef MOZ_NEW_CERT_STORAGE
nsCOMPtr<nsICertStorage> mCertBlocklist;
#else
nsCOMPtr<nsICertBlocklist> mCertBlocklist;
#endif
};
} // namespace psm

View File

@ -0,0 +1,627 @@
/* -*- 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.settings.security.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<char*, uint8_t*>(mDNData),
mDNLength);
nsDependentCSubstring otherString(BitwiseCast<char*, uint8_t*>(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<nsIFileInputStream> 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<nsILineInputStream> 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<const uint8_t*, const char*>(decodedDN.get()),
decodedDN.Length(),
BitwiseCast<const uint8_t*, const char*>(decodedOther.get()),
decodedOther.Length(), aMechanism);
if (aItemState == CertNewFromBlocklist) {
// We want SaveEntries to be a no-op if no new entries are added.
nsGenericHashKey<CertBlocklistItem>* 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<nsIOutputStream> 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<BlocklistStringSet> 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<nsISafeOutputStream> 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<const uint8_t*, const char*>(decodedIssuer.get()),
decodedIssuer.Length(),
BitwiseCast<const uint8_t*, const char*>(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<nsICryptoHash> crypto;
crypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
rv = crypto->Init(nsICryptoHash::SHA256);
if (NS_FAILED(rv)) {
return rv;
}
rv = crypto->Update(
BitwiseCast<const uint8_t*, const char*>(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<const uint8_t*, const char*>(decodedSubject.get()),
decodedSubject.Length(),
BitwiseCast<const uint8_t*, const char*>(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));
}
}

View File

@ -0,0 +1,89 @@
/* -*- 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<CertBlocklistItem> BlocklistItemKey;
typedef nsTHashtable<BlocklistItemKey> BlocklistTable;
typedef nsTHashtable<nsCStringHashKey> BlocklistStringSet;
typedef nsClassHashtable<nsCStringHashKey, BlocklistStringSet> 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<nsIFile> 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

View File

@ -18,7 +18,9 @@
#include "mozilla/Unused.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIFileStreams.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "nsIFileStreams.h"
#endif
#include "nsIMemoryReporter.h"
#include "nsIObserverService.h"
#include "nsITimer.h"

View File

@ -8,8 +8,10 @@
#include "nsINSSErrorsService.h"
#include "mozilla/Attributes.h"
#include "nsCOMPtr.h"
#include "nsILineInputStream.h"
#include "nsISafeOutputStream.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "nsILineInputStream.h"
# include "nsISafeOutputStream.h"
#endif
#include "nsIStringBundle.h"
#include "prerror.h"

View File

@ -156,12 +156,6 @@ Classes = [
'type': 'nsSiteSecurityService',
'legacy_constructor': 'mozilla::psm::NSSConstructor<nsSiteSecurityService>',
},
{
'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}',
'contract_ids': ['@mozilla.org/security/oskeystore;1'],
@ -185,3 +179,22 @@ if defined('MOZ_XUL'):
'legacy_constructor': 'mozilla::psm::NSSConstructor<nsCertTree>',
},
]
if defined('MOZ_NEW_CERT_STORAGE'):
Classes += [
{
'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',
},
]
else:
Classes += [
{
'cid': '{11aefd53-2fbb-4c92-a0c1-053212ae42d0}',
'contract_ids': ['@mozilla.org/security/certblocklist;1'],
'type': 'CertBlocklist',
'legacy_constructor': 'mozilla::psm::NSSConstructor<CertBlocklist>',
},
]

View File

@ -13,7 +13,6 @@ XPIDL_SOURCES += [
'nsIBadCertListener2.idl',
'nsICertificateDialogs.idl',
'nsICertOverrideService.idl',
'nsICertStorage.idl',
'nsIClientAuthDialogs.idl',
'nsIContentSignatureVerifier.idl',
'nsICryptoHash.idl',
@ -94,7 +93,6 @@ EXPORTS.ipc += [
]
UNIFIED_SOURCES += [
'cert_storage/src/cert_storage.cpp',
'ContentSignatureVerifier.cpp',
'CryptoTask.cpp',
'CSTrustDomain.cpp',
@ -183,6 +181,21 @@ UNIFIED_SOURCES += [
'md4.c',
]
if CONFIG['MOZ_NEW_CERT_STORAGE']:
XPIDL_SOURCES += [
'nsICertStorage.idl',
]
UNIFIED_SOURCES += [
'cert_storage/src/cert_storage.cpp',
]
else:
XPIDL_SOURCES += [
'nsICertBlocklist.idl',
]
UNIFIED_SOURCES += [
'CertBlocklist.cpp',
]
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [

View File

@ -0,0 +1,64 @@
/* -*- 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();
};

View File

@ -6,6 +6,9 @@
#include "nsNSSModule.h"
#ifndef MOZ_NEW_CERT_STORAGE
# include "CertBlocklist.h"
#endif
#include "ContentSignatureVerifier.h"
#include "NSSErrorsService.h"
#include "OSKeyStore.h"
@ -147,6 +150,10 @@ IMPL(nsRandomGenerator, nullptr, ProcessRestriction::AnyProcess)
IMPL(TransportSecurityInfo, nullptr, ProcessRestriction::AnyProcess)
IMPL(nsSiteSecurityService, &nsSiteSecurityService::Init,
ProcessRestriction::AnyProcess, ThreadRestriction::MainThreadOnly)
#ifndef MOZ_NEW_CERT_STORAGE
IMPL(CertBlocklist, &CertBlocklist::Init, ProcessRestriction::ParentProcessOnly,
ThreadRestriction::MainThreadOnly)
#endif
IMPL(OSKeyStore, nullptr, ProcessRestriction::ParentProcessOnly,
ThreadRestriction::MainThreadOnly)
IMPL(OSReauthenticator, nullptr, ProcessRestriction::ParentProcessOnly,

View File

@ -8,6 +8,7 @@
// certificates are valid for or what errors prevented the certificates from
// being verified.
var { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
var { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
add_task(async function testCAandTitle() {
@ -100,16 +101,24 @@ 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/certstorage;1"]
.getService(Ci.nsICertStorage);
let result = await new Promise((resolve) =>
certBlocklist.setRevocations([{
QueryInterface: ChromeUtils.generateQI([Ci.nsISubjectAndPubKeyRevocationState]),
subject: "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
pubKey: "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=", // hash of the shared key
state: Ci.nsICertStorage.STATE_ENFORCE, // yes, we want this to be revoked
}], resolve));
Assert.equal(result, Cr.NS_OK, "setting revocation state should succeed");
if (AppConstants.MOZ_NEW_CERT_STORAGE) {
let certBlocklist = Cc["@mozilla.org/security/certstorage;1"]
.getService(Ci.nsICertStorage);
let result = await new Promise((resolve) =>
certBlocklist.setRevocations([{
QueryInterface: ChromeUtils.generateQI([Ci.nsISubjectAndPubKeyRevocationState]),
subject: "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
pubKey: "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=", // hash of the shared key
state: Ci.nsICertStorage.STATE_ENFORCE, // yes, we want this to be revoked
}], resolve));
Assert.equal(result, Cr.NS_OK, "setting revocation state should succeed");
} else {
let certBlocklist = Cc["@mozilla.org/security/certblocklist;1"]
.getService(Ci.nsICertBlocklist);
certBlocklist.revokeCertBySubjectAndPubKey(
"MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
"VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="); // hash of the shared key
}
let cert = await readCertificate("revoked.pem", ",,");
let win = await displayCertificate(cert);
// As of bug 1312827, OneCRL only applies to TLS web server certificates, so

View File

@ -195,14 +195,90 @@ 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/certstorage;1"]
.getService(Ci.nsICertStorage);
let certList = AppConstants.MOZ_NEW_CERT_STORAGE ?
Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage) :
Cc["@mozilla.org/security/certblocklist;1"].getService(Ci.nsICertBlocklist);
let expected = { "MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5":
{ "\tVCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=": true },
"MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E=":
{ " Rym6o+VN9xgZXT/QLrvN/nv1ZN4=": true},
"MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=":
{ " a0X7/7DlTaedpgrIJg25iBPOkIM=": true},
"MBwxGjAYBgNVBAMMEVRlc3QgSW50ZXJtZWRpYXRl":
{ " Tg==": true,
" Hw==": true },
};
add_task(async function() {
// check some existing items in revocations.txt are blocked.
@ -272,6 +348,12 @@ function run_test() {
file = "test_onecrl/ee-revoked-by-subject-and-pubkey.pem";
await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
if (!AppConstants.MOZ_NEW_CERT_STORAGE) {
// 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
file = "test_onecrl/test-int-ee.pem";
await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
@ -294,9 +376,31 @@ 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);
if (!AppConstants.MOZ_NEW_CERT_STORAGE) {
// check that save with no further update is a no-op
let lastModified = gRevocations.lastModifiedTime;
// add an already existing entry
certList.revokeCertByIssuerAndSerial("MBwxGjAYBgNVBAMMEVRlc3QgSW50ZXJtZWRpYXRl",
"Hw==");
certList.saveEntries();
let newModified = gRevocations.lastModifiedTime;
equal(lastModified, newModified,
"saveEntries with no modifications should not update the backing file");
}
});
add_task(async function() {
add_test({
skip_if: () => AppConstants.MOZ_NEW_CERT_STORAGE,
}, function() {
// Check the blocklist entry has not changed
check_revocations_txt_contents(expected);
run_next_test();
});
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function() {
ok(certList.isBlocklistFresh(), "Blocklist should be fresh.");
});

View File

@ -9,7 +9,9 @@
do_get_profile();
var certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
if (AppConstants.MOZ_NEW_CERT_STORAGE) {
this.certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
}
async function addCertBySubject(cert, subject) {
let result = await new Promise((resolve) => {
@ -46,7 +48,9 @@ function getLongString(uniquePart, length) {
return String(uniquePart).padStart(length, "0");
}
add_task(async function test_common_subject() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_common_subject() {
await addCertBySubject("some certificate bytes 1", "some common subject");
await addCertBySubject("some certificate bytes 2", "some common subject");
await addCertBySubject("some certificate bytes 3", "some common subject");
@ -68,7 +72,9 @@ add_task(async function test_common_subject() {
Assert.deepEqual(storedOtherCertsAsStrings, expectedOtherCerts, "should have other certificate");
});
add_task(async function test_many_entries() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_many_entries() {
const NUM_CERTS = 500;
const CERT_LENGTH = 3000;
const SUBJECT_LENGTH = 40;
@ -85,7 +91,9 @@ add_task(async function test_many_entries() {
}
});
add_task(async function test_removal() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_removal() {
// As long as cert_storage is given valid base64, attempting to delete some nonexistent
// certificate will "succeed" (it'll do nothing).
await removeCertByHash(btoa("thishashisthewrongsize"));

View File

@ -7,6 +7,10 @@
// Tests that cert_storage properly handles its preference values.
function run_test() {
if (!AppConstants.MOZ_NEW_CERT_STORAGE) {
return;
}
let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
// Since none of our prefs start with values, looking them up will fail. cert_storage should use
// safe fallbacks.

View File

@ -7,12 +7,16 @@
"use strict";
do_get_profile(); // must be called before getting nsIX509CertDB
const {RemoteSecuritySettings} = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
const {RemoteSettings} = ChromeUtils.import("resource://services-settings/remote-settings.js");
const {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm");
const {TelemetryTestUtils} = ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm");
let remoteSecSetting = new RemoteSecuritySettings();
let remoteSecSetting;
if (AppConstants.MOZ_NEW_CERT_STORAGE) {
const {RemoteSecuritySettings} = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
remoteSecSetting = new RemoteSecuritySettings();
}
let server;
let intermediate1Data;
@ -213,7 +217,9 @@ function setupKintoPreloadServer(certGenerator, options = {
});
}
add_task(async function test_preload_empty() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_preload_empty() {
Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
let countDownloadAttempts = 0;
@ -240,7 +246,9 @@ add_task(async function test_preload_empty() {
certificateUsageSSLServer);
});
add_task(async function test_preload_disabled() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_preload_disabled() {
Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, false);
let countDownloadAttempts = 0;
@ -254,7 +262,9 @@ add_task(async function test_preload_disabled() {
equal(countDownloadAttempts, 0, "There should have been no downloads");
});
add_task(async function test_preload_invalid_hash() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_preload_invalid_hash() {
Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
const invalidHash = "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d";
@ -294,7 +304,9 @@ add_task(async function test_preload_invalid_hash() {
certificateUsageSSLServer);
});
add_task(async function test_preload_invalid_length() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_preload_invalid_length() {
Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
let countDownloadAttempts = 0;
@ -333,7 +345,9 @@ add_task(async function test_preload_invalid_length() {
certificateUsageSSLServer);
});
add_task(async function test_preload_basic() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_preload_basic() {
Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
Services.prefs.setIntPref(INTERMEDIATES_DL_PER_POLL_PREF, 100);
@ -379,7 +393,9 @@ add_task(async function test_preload_basic() {
});
add_task(async function test_preload_200() {
add_task({
skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
}, async function test_preload_200() {
Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
Services.prefs.setIntPref(INTERMEDIATES_DL_PER_POLL_PREF, 100);

View File

@ -8,8 +8,8 @@ var EXPORTED_SYMBOLS = [
"initialize",
];
const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { RemoteSecuritySettings } = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(this, "RemoteSettings", "resource://services-settings/remote-settings.js");
@ -73,39 +73,60 @@ function setRevocations(certStorage, revocations) {
*
* @param {Object} data Current records in the local db.
*/
async function updateCertBlocklist({ data: { created, updated, deleted } }) {
const certList = Cc["@mozilla.org/security/certstorage;1"]
.getService(Ci.nsICertStorage);
let items = [];
const updateCertBlocklist = AppConstants.MOZ_NEW_CERT_STORAGE ?
async function({ data: { created, updated, deleted } }) {
const certList = Cc["@mozilla.org/security/certstorage;1"]
.getService(Ci.nsICertStorage);
let items = [];
for (let item of deleted) {
if (item.issuerName && item.serialNumber) {
items.push(new IssuerAndSerialRevocationState(item.issuerName,
item.serialNumber, Ci.nsICertStorage.STATE_UNSET));
} else if (item.subject && item.pubKeyHash) {
items.push(new SubjectAndPubKeyRevocationState(item.subject,
item.pubKeyHash, Ci.nsICertStorage.STATE_UNSET));
for (let item of deleted) {
if (item.issuerName && item.serialNumber) {
items.push(new IssuerAndSerialRevocationState(item.issuerName,
item.serialNumber, Ci.nsICertStorage.STATE_UNSET));
} else if (item.subject && item.pubKeyHash) {
items.push(new SubjectAndPubKeyRevocationState(item.subject,
item.pubKeyHash, Ci.nsICertStorage.STATE_UNSET));
}
}
}
const toAdd = created.concat(updated.map(u => u.new));
const toAdd = created.concat(updated.map(u => u.new));
for (let item of toAdd) {
if (item.issuerName && item.serialNumber) {
items.push(new IssuerAndSerialRevocationState(item.issuerName,
item.serialNumber, Ci.nsICertStorage.STATE_ENFORCE));
} else if (item.subject && item.pubKeyHash) {
items.push(new SubjectAndPubKeyRevocationState(item.subject,
item.pubKeyHash, Ci.nsICertStorage.STATE_ENFORCE));
for (let item of toAdd) {
if (item.issuerName && item.serialNumber) {
items.push(new IssuerAndSerialRevocationState(item.issuerName,
item.serialNumber, Ci.nsICertStorage.STATE_ENFORCE));
} else if (item.subject && item.pubKeyHash) {
items.push(new SubjectAndPubKeyRevocationState(item.subject,
item.pubKeyHash, Ci.nsICertStorage.STATE_ENFORCE));
}
}
}
try {
await setRevocations(certList, items);
} catch (e) {
Cu.reportError(e);
}
}
try {
await setRevocations(certList, items);
} catch (e) {
Cu.reportError(e);
}
} : async function({ data: { current: records } }) {
const certList = Cc["@mozilla.org/security/certblocklist;1"]
.getService(Ci.nsICertBlocklist);
for (let item of records) {
try {
if (item.issuerName && item.serialNumber) {
certList.revokeCertByIssuerAndSerial(item.issuerName,
item.serialNumber);
} else if (item.subject && item.pubKeyHash) {
certList.revokeCertBySubjectAndPubKey(item.subject,
item.pubKeyHash);
}
} catch (e) {
// prevent errors relating to individual blocklist entries from
// causing sync to fail. We will accumulate telemetry on these failures in
// bug 1254099.
Cu.reportError(e);
}
}
certList.saveEntries();
};
/**
* Modify the appropriate security pins based on records from the remote
@ -256,9 +277,22 @@ function initialize() {
});
PinningBlocklistClient.on("sync", updatePinningList);
// In Bug 1526018 this will move into its own service, as it's not quite like
// the others.
RemoteSecuritySettingsClient = new RemoteSecuritySettings();
if (AppConstants.MOZ_NEW_CERT_STORAGE) {
const { RemoteSecuritySettings } = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
// In Bug 1526018 this will move into its own service, as it's not quite like
// the others.
RemoteSecuritySettingsClient = new RemoteSecuritySettings();
return {
OneCRLBlocklistClient,
AddonBlocklistClient,
PluginBlocklistClient,
GfxBlocklistClient,
PinningBlocklistClient,
RemoteSecuritySettingsClient,
};
}
return {
OneCRLBlocklistClient,
@ -266,6 +300,5 @@ function initialize() {
PluginBlocklistClient,
GfxBlocklistClient,
PinningBlocklistClient,
RemoteSecuritySettingsClient,
};
}

View File

@ -24,6 +24,7 @@ gecko_profiler = ["gkrust-shared/gecko_profiler"]
gecko_profiler_parse_elf = ["gkrust-shared/gecko_profiler_parse_elf"]
bitsdownload = ["gkrust-shared/bitsdownload"]
new_xulstore = ["gkrust-shared/new_xulstore"]
new_cert_storage = ["gkrust-shared/new_cert_storage"]
[dependencies]
bench-collections-gtest = { path = "../../../../xpcom/rust/gtest/bench-collections" }

View File

@ -25,6 +25,7 @@ gecko_profiler = ["gkrust-shared/gecko_profiler"]
gecko_profiler_parse_elf = ["gkrust-shared/gecko_profiler_parse_elf"]
bitsdownload = ["gkrust-shared/bitsdownload"]
new_xulstore = ["gkrust-shared/new_xulstore"]
new_cert_storage = ["gkrust-shared/new_cert_storage"]
[dependencies]
gkrust-shared = { path = "shared" }

View File

@ -53,3 +53,6 @@ if CONFIG['MOZ_BITS_DOWNLOAD']:
if CONFIG['MOZ_NEW_XULSTORE']:
gkrust_features += ['new_xulstore']
if CONFIG['MOZ_NEW_CERT_STORAGE']:
gkrust_features += ['new_cert_storage']

View File

@ -34,7 +34,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" }
cert_storage = { path = "../../../../security/manager/ssl/cert_storage", optional = true }
bitsdownload = { path = "../../../components/bitsdownload", optional = true }
storage = { path = "../../../../storage/rust" }
bookmark_sync = { path = "../../../components/places/bookmark_sync", optional = true }
@ -62,6 +62,7 @@ cranelift_none = ["jsrust_shared/cranelift_none"]
gecko_profiler = ["profiler_helper"]
gecko_profiler_parse_elf = ["profiler_helper/parse_elf"]
new_xulstore = ["xulstore"]
new_cert_storage = ["cert_storage"]
[lib]
path = "lib.rs"

View File

@ -31,6 +31,7 @@ extern crate env_logger;
extern crate u2fhid;
extern crate gkrust_utils;
extern crate log;
#[cfg(feature = "new_cert_storage")]
extern crate cert_storage;
extern crate cosec;
extern crate rsdparsa_capi;

View File

@ -364,4 +364,11 @@ this.AppConstants = Object.freeze({
#else
false,
#endif
MOZ_NEW_CERT_STORAGE:
#ifdef MOZ_NEW_CERT_STORAGE
true,
#else
false,
#endif
});

View File

@ -1750,3 +1750,15 @@ def new_notification_store(milestone):
set_config('MOZ_NEW_NOTIFICATION_STORE', True, when=new_notification_store)
set_define('MOZ_NEW_NOTIFICATION_STORE', True, when=new_notification_store)
# new Cert Storage implementation
# ==============================================================
@depends(milestone)
def new_cert_storage(milestone):
if milestone.is_nightly:
return True
set_config('MOZ_NEW_CERT_STORAGE', True, when=new_cert_storage)
set_define('MOZ_NEW_CERT_STORAGE', True, when=new_cert_storage)