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
This commit is contained in:
Mark Goodwin 2019-03-20 17:00:47 +00:00
parent 700d44723a
commit 50887394d6
27 changed files with 1041 additions and 920 deletions

15
Cargo.lock generated
View File

@ -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)",

View File

@ -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<nsISupports> sss = do_GetService(NS_SSSERVICE_CONTRACTID);
nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTBLOCKLIST_CONTRACTID);
nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTSTORAGE_CONTRACTID);
nsCOMPtr<nsISupports> cos = do_GetService(NS_CERTOVERRIDE_CONTRACTID);
}

View File

@ -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;

View File

@ -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<nsICertBlocklist> mCertBlocklist;
nsCOMPtr<nsICertStorage> mCertBlocklist;
CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
// Certificate Transparency data extracted during certificate verification
UniqueSECItem mSCTListFromCertificate;

View File

@ -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;
}

View File

@ -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<nsICertBlocklist> mCertBlocklist;
nsCOMPtr<nsICertStorage> mCertBlocklist;
};
} // namespace psm

View File

@ -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<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

@ -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<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

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -0,0 +1,14 @@
[package]
name = "cert_storage"
version = "0.0.1"
authors = ["Dana Keeler <dkeeler@mozilla.com>", "Mark Goodwin <mgoodwin@mozilla.com"]
[dependencies]
base64 = "0.10"
nserror = { path = "../../../../xpcom/rust/nserror" }
nsstring = { path = "../../../../xpcom/rust/nsstring" }
rkv = "0.9.2"
sha2 = "^0.7"
style = { path = "../../../../servo/components/style" }
time = "0.1"
xpcom = { path = "../../../../xpcom/rust/xpcom" }

View File

@ -0,0 +1,23 @@
/* -*- 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 "cert_storage.h"
nsresult construct_cert_storage(nsISupports* outer, REFNSIID iid,
void** result) {
// Forward to the main thread synchronously.
nsCOMPtr<nsIThread> 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;
}

View File

@ -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_

View File

@ -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<T: Display> From<T> 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<String, i32>,
}
impl SecurityState {
pub fn new(profile_path: PathBuf) -> Result<SecurityState, SecurityStateError> {
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<String> = 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<Option<i16>, 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<i16, SecurityStateError> {
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<i16, SecurityStateError> {
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<i16, SecurityStateError> {
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<bool, SecurityStateError> {
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<bool, SecurityStateError> {
self.is_data_fresh(
"services.blocklist.onecrl.checked",
"security.onecrl.maximum_staleness_in_seconds",
)
}
pub fn is_whitelist_fresh(&self) -> Result<bool, SecurityStateError> {
self.is_data_fresh(
"services.blocklist.intermediates.checked",
"security.onecrl.maximum_staleness_in_seconds",
)
}
pub fn is_enrollment_fresh(&self) -> Result<bool, SecurityStateError> {
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<PathBuf, SecurityStateError> {
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::<nsIFile>::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<i32, SecurityStateError> {
let pref_service = match xpcom::services::get_PreferencesService() {
Some(ps) => ps,
_ => {
return Err(SecurityStateError::from(
"could not get preferences service",
))
}
};
let prefs: RefPtr<nsIPrefBranch> = 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<SecurityState>,
}
#[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<nsIPrefBranch> = 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::<nsIObserver>(), 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<nsIPrefBranch> = 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
}
}

View File

@ -157,10 +157,10 @@ Classes = [
'legacy_constructor': 'mozilla::psm::NSSConstructor<nsSiteSecurityService>',
},
{
'cid': '{11aefd53-2fbb-4c92-a0c1-053212ae42d0}',
'contract_ids': ['@mozilla.org/security/certblocklist;1'],
'type': 'CertBlocklist',
'legacy_constructor': 'mozilla::psm::NSSConstructor<CertBlocklist>',
'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}',

View File

@ -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',

View File

@ -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();
};

View File

@ -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();
};

View File

@ -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,

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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();
}
/**

View File

@ -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"

View File

@ -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")]

View File

@ -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"