Bug 1024809 - (OneCRL) Create a blocklist mechanism to revoke intermediate certs. r=keeler,Unfocused

This commit is contained in:
Mark Goodwin ext:(%2C%20Harsh%20Pathak%20%3Chpathak%40mozilla.com%3E) 2014-11-27 04:12:00 +01:00
parent d7fab6a6dd
commit e1eaa1f5df
19 changed files with 955 additions and 6 deletions

View File

@ -9,12 +9,13 @@
#include <stdint.h>
#include "ExtendedValidation.h"
#include "nsNSSCertificate.h"
#include "NSSErrorsService.h"
#include "OCSPRequestor.h"
#include "certdb.h"
#include "mozilla/Telemetry.h"
#include "nsNSSCertificate.h"
#include "nss.h"
#include "NSSErrorsService.h"
#include "nsServiceManagerUtils.h"
#include "pk11pub.h"
#include "pkix/pkix.h"
#include "pkix/pkixnss.h"
@ -68,6 +69,7 @@ NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
, mMinimumNonECCBits(forEV ? MINIMUM_NON_ECC_BITS_EV : MINIMUM_NON_ECC_BITS_DV)
, mHostname(hostname)
, mBuiltChain(builtChain)
, mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
{
}
@ -338,6 +340,27 @@ NSSCertDBTrustDomain::CheckRevocation(EndEntityOrCA endEntityOrCA,
maxOCSPLifetimeInDays = 365;
}
if (!mCertBlocklist) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
bool isCertRevoked;
nsresult nsrv = mCertBlocklist->IsCertRevoked(
certID.issuer.UnsafeGetData(),
certID.issuer.GetLength(),
certID.serialNumber.UnsafeGetData(),
certID.serialNumber.GetLength(),
&isCertRevoked);
if (NS_FAILED(nsrv)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
if (isCertRevoked) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: certificate is in blocklist"));
return Result::ERROR_REVOKED_CERTIFICATE;
}
// If we have a stapled OCSP response then the verification of that response
// determines the result unless the OCSP response is expired. We make an
// exception for expired responses because some servers, nginx in particular,

View File

@ -7,9 +7,10 @@
#ifndef mozilla_psm__NSSCertDBTrustDomain_h
#define mozilla_psm__NSSCertDBTrustDomain_h
#include "CertVerifier.h"
#include "nsICertBlocklist.h"
#include "pkix/pkixtypes.h"
#include "secmodt.h"
#include "CertVerifier.h"
namespace mozilla { namespace psm {
@ -110,6 +111,7 @@ private:
const unsigned int mMinimumNonECCBits;
const char* mHostname; // non-owning - only used for pinning checks
ScopedCERTCertList* mBuiltChain; // non-owning
nsCOMPtr<nsICertBlocklist> mCertBlocklist;
};
} } // namespace mozilla::psm

View File

@ -6,6 +6,7 @@
XPIDL_SOURCES += [
'nsIBufEntropyCollector.idl',
'nsICertBlocklist.idl',
'nsISecurityUITelemetry.idl',
'nsISecurityWarningDialogs.idl',
'nsISSLStatusProvider.idl',

View File

@ -0,0 +1,40 @@
/* -*- 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(44b0ee42-1af3-45e7-b601-7f17bd67c5cc)]
interface nsICertBlocklist : nsISupports {
/**
* Add details of a revoked certificate :
* issuer name (base-64 encoded DER) and serial number (base-64 encoded DER).
*/
void addRevokedCert(in string issuer, in string serialNumber);
/**
* Persist (fresh) blocklist entries to the profile (if a profile directory is
* available). Note: calling this will result in synchronous I/O.
*/
void saveEntries();
/**
* Check if a certificate is blocked.
* isser - issuer name, DER encoded
* serial - serial number, DER encoded
*/
boolean isCertRevoked([const, array, size_is(issuer_length)] in octet issuer,
in unsigned long issuer_length,
[const, array, size_is(serial_length)] in octet serial,
in unsigned long serial_length);
};

View File

@ -0,0 +1,469 @@
/* -*- 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 "CertBlocklist.h"
#include "mozilla/Base64.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCRTGlue.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIFileStreams.h"
#include "nsILineInputStream.h"
#include "nsIX509Cert.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsTHashtable.h"
#include "nsThreadUtils.h"
#include "pkix/Input.h"
#include "prlog.h"
NS_IMPL_ISUPPORTS(CertBlocklist, nsICertBlocklist)
static PRLogModuleInfo* gCertBlockPRLog;
CertBlocklistItem::CertBlocklistItem(mozilla::pkix::Input aIssuer,
mozilla::pkix::Input aSerial)
{
mIssuerData = new uint8_t[aIssuer.GetLength()];
memcpy(mIssuerData, aIssuer.UnsafeGetData(), aIssuer.GetLength());
mIssuer.Init(mIssuerData, aIssuer.GetLength());
mSerialData = new uint8_t[aSerial.GetLength()];
memcpy(mSerialData, aSerial.UnsafeGetData(), aSerial.GetLength());
mSerial.Init(mSerialData, aSerial.GetLength());
}
CertBlocklistItem::CertBlocklistItem(const CertBlocklistItem& aItem)
{
uint32_t issuerLength = aItem.mIssuer.GetLength();
mIssuerData = new uint8_t[issuerLength];
memcpy(mIssuerData, aItem.mIssuerData, issuerLength);
mIssuer.Init(mIssuerData, issuerLength);
uint32_t serialLength = aItem.mSerial.GetLength();
mSerialData = new uint8_t[serialLength];
memcpy(mSerialData, aItem.mSerialData, serialLength);
mSerial.Init(mSerialData, serialLength);
mIsCurrent = aItem.mIsCurrent;
}
CertBlocklistItem::~CertBlocklistItem()
{
delete[] mIssuerData;
delete[] mSerialData;
}
nsresult
CertBlocklistItem::ToBase64(nsACString& b64IssuerOut, nsACString& b64SerialOut)
{
nsDependentCSubstring issuerString(reinterpret_cast<char *>(mIssuerData),
mIssuer.GetLength());
nsDependentCSubstring serialString(reinterpret_cast<char *>(mSerialData),
mSerial.GetLength());
nsresult rv = mozilla::Base64Encode(issuerString, b64IssuerOut);
if (NS_FAILED(rv)) {
return rv;
}
rv = mozilla::Base64Encode(serialString, b64SerialOut);
return rv;
}
bool
CertBlocklistItem::operator==(const CertBlocklistItem& aItem) const
{
bool retval = InputsAreEqual(aItem.mIssuer, mIssuer) &&
InputsAreEqual(aItem.mSerial, mSerial);
return retval;
}
uint32_t
CertBlocklistItem::Hash() const
{
uint32_t hash;
uint32_t serialLength = mSerial.GetLength();
// there's no requirement for a serial to be as large as 32 bits; if it's
// smaller, fall back to the first octet (otherwise, the last four)
if (serialLength >= 4) {
hash = *(uint32_t *)(mSerialData + serialLength - 4);
} else {
hash = *mSerialData;
}
return hash;
}
CertBlocklist::CertBlocklist()
: mMutex("CertBlocklist::mMutex")
, mModified(false)
{
if (!gCertBlockPRLog) {
gCertBlockPRLog = PR_NewLogModule("CertBlock");
}
}
CertBlocklist::~CertBlocklist()
{
}
nsresult
CertBlocklist::Init()
{
mozilla::MutexAutoLock lock(mMutex);
PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG, ("CertBlocklist::Init"));
if (!NS_IsMainThread()) {
PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
("CertBlocklist::Init - called off main thread"));
return NS_ERROR_NOT_SAME_THREAD;
}
// Load the revocations file into the cert blocklist
PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
("CertBlocklist::Init - not initialized; initializing"));
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mBackingFile));
if (NS_FAILED(rv) || !mBackingFile) {
PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
("CertBlocklist::Init - couldn't get profile dir"));
return NS_OK;
}
rv = mBackingFile->Append(NS_LITERAL_STRING("revocations.txt"));
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString path;
rv = mBackingFile->GetNativePath(path);
if (NS_FAILED(rv)) {
return rv;
}
PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
("CertBlocklist::Init certList path: %s", path.get()));
bool exists = false;
rv = mBackingFile->Exists(&exists);
if (NS_FAILED(rv)) {
return rv;
}
if (!exists) {
PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
("CertBlocklist::Init no revocations file"));
return NS_OK;
}
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 issuer;
nsAutoCString serial;
// read in the revocations file. The file format is as follows: each line
// contains a comment, base64 encoded DER for an issuer or base64 encoded DER
// for a serial number. Comment lines start with '#', serial number lines, ' '
// (a space) and anything else is assumed to be an issuer.
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() != ' ') {
issuer = line;
continue;
}
serial = line;
serial.Trim(" ", true, false, false);
// serial numbers 'belong' to the last issuer line seen; if no issuer has
// been seen, the serial number is ignored
if (issuer.IsEmpty() || serial.IsEmpty()) {
continue;
}
PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
("CertBlocklist::Init adding: %s %s", issuer.get(), serial.get()));
rv = AddRevokedCertInternal(issuer.get(),
serial.get(),
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
PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
("CertBlocklist::Init adding revoked cert failed"));
}
} while (more);
return NS_OK;
}
// void addRevokedCert (in string issuer, in string serialNumber);
NS_IMETHODIMP
CertBlocklist::AddRevokedCert(const char* aIssuer,
const char* aSerialNumber)
{
PR_LOG(gCertBlockPRLog, PR_LOG_DEBUG,
("CertBlocklist::addRevokedCert - issuer is: %s and serial: %s",
aIssuer, aSerialNumber));
mozilla::MutexAutoLock lock(mMutex);
return AddRevokedCertInternal(aIssuer,
aSerialNumber,
CertNewFromBlocklist,
lock);
}
nsresult
CertBlocklist::AddRevokedCertInternal(const char* aIssuer,
const char* aSerialNumber,
CertBlocklistItemState aItemState,
mozilla::MutexAutoLock& /*proofOfLock*/)
{
nsCString decodedIssuer;
nsCString decodedSerial;
nsresult rv;
rv = mozilla::Base64Decode(nsDependentCString(aIssuer), decodedIssuer);
if (NS_FAILED(rv)) {
return rv;
}
rv = mozilla::Base64Decode(nsDependentCString(aSerialNumber), decodedSerial);
if (NS_FAILED(rv)) {
return rv;
}
mozilla::pkix::Input issuer;
mozilla::pkix::Input serial;
mozilla::pkix::Result pkrv;
pkrv = issuer.Init(reinterpret_cast<const uint8_t*>(decodedIssuer.get()),
decodedIssuer.Length());
if (pkrv != mozilla::pkix::Success) {
return NS_ERROR_FAILURE;
}
pkrv = serial.Init(reinterpret_cast<const uint8_t*>(decodedSerial.get()),
decodedSerial.Length());
if (pkrv != mozilla::pkix::Success) {
return NS_ERROR_FAILURE;
}
CertBlocklistItem item(issuer, serial);
if (aItemState == CertNewFromBlocklist) {
// we want SaveEntries to be a no-op if no new entries are added
if (!mBlocklist.Contains(item)) {
mModified = true;
}
// 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(item);
item.mIsCurrent = true;
}
mBlocklist.PutEntry(item);
return NS_OK;
}
// Data needed for writing blocklist items out to the revocations file
struct BlocklistSaveInfo
{
IssuerTable issuerTable;
BlocklistStringSet issuers;
nsCOMPtr<nsIOutputStream> outputStream;
bool success = true;
};
// 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;
}
// sort blocklist items into lists of serials for each issuer
PLDHashOperator
ProcessEntry(BlocklistItemKey* aHashKey, void* aUserArg)
{
BlocklistSaveInfo* saveInfo = reinterpret_cast<BlocklistSaveInfo*>(aUserArg);
CertBlocklistItem item = aHashKey->GetKey();
if (!item.mIsCurrent) {
return PL_DHASH_NEXT;
}
nsAutoCString encIssuer;
nsAutoCString encSerial;
nsresult rv = item.ToBase64(encIssuer, encSerial);
if (NS_FAILED(rv)) {
saveInfo->success = false;
return PL_DHASH_STOP;
}
saveInfo->issuers.PutEntry(encIssuer);
BlocklistStringSet* issuerSet = saveInfo->issuerTable.Get(encIssuer);
if (!issuerSet) {
issuerSet = new BlocklistStringSet();
saveInfo->issuerTable.Put(encIssuer, issuerSet);
}
issuerSet->PutEntry(encSerial);
return PL_DHASH_NEXT;
}
// write serial data to the output stream
PLDHashOperator
WriteSerial(nsCStringHashKey* aHashKey, void* aUserArg)
{
BlocklistSaveInfo* saveInfo = reinterpret_cast<BlocklistSaveInfo*>(aUserArg);
nsresult rv = WriteLine(saveInfo->outputStream,
NS_LITERAL_CSTRING(" ") + aHashKey->GetKey());
if (NS_FAILED(rv)) {
saveInfo->success = false;
return PL_DHASH_STOP;
}
return PL_DHASH_NEXT;
}
// Write issuer data to the output stream
PLDHashOperator
WriteIssuer(nsCStringHashKey* aHashKey, void* aUserArg)
{
BlocklistSaveInfo* saveInfo = reinterpret_cast<BlocklistSaveInfo *>(aUserArg);
nsAutoPtr<BlocklistStringSet> issuerSet;
saveInfo->issuerTable.RemoveAndForget(aHashKey->GetKey(), issuerSet);
nsresult rv = WriteLine(saveInfo->outputStream, aHashKey->GetKey());
if (!NS_SUCCEEDED(rv)) {
return PL_DHASH_STOP;
}
issuerSet->EnumerateEntries(WriteSerial, saveInfo);
if (!saveInfo->success) {
saveInfo->success = false;
return PL_DHASH_STOP;
}
return PL_DHASH_NEXT;
}
// 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()
{
mozilla::MutexAutoLock lock(mMutex);
if (!mModified) {
return NS_OK;
}
if (!mBackingFile) {
// We allow this to succeed with no profile directory for tests
PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
("CertBlocklist::SaveEntries no file in profile to write to"));
return NS_OK;
}
BlocklistSaveInfo saveInfo;
nsresult rv;
rv = NS_NewAtomicFileOutputStream(getter_AddRefs(saveInfo.outputStream),
mBackingFile, -1, -1, 0);
if (NS_FAILED(rv)) {
return rv;
}
mBlocklist.EnumerateEntries(ProcessEntry, &saveInfo);
if (!saveInfo.success) {
PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
("CertBlocklist::SaveEntries writing revocation data failed"));
return NS_ERROR_FAILURE;
}
rv = WriteLine(saveInfo.outputStream,
NS_LITERAL_CSTRING("# Auto generated contents. Do not edit."));
if (NS_FAILED(rv)) {
return rv;
}
saveInfo.issuers.EnumerateEntries(WriteIssuer, &saveInfo);
if (!saveInfo.success) {
PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
("CertBlocklist::SaveEntries writing revocation data failed"));
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsISafeOutputStream> safeStream =
do_QueryInterface(saveInfo.outputStream);
NS_ASSERTION(safeStream, "expected a safe output stream!");
if (!safeStream) {
return NS_ERROR_FAILURE;
}
rv = safeStream->Finish();
if (NS_FAILED(rv)) {
PR_LOG(gCertBlockPRLog, PR_LOG_WARN,
("CertBlocklist::SaveEntries saving revocation data failed"));
return rv;
}
mModified = false;
return NS_OK;
}
// boolean isCertRevoked([const, array, size_is(issuerLength)] in octet issuer,
// in unsigned long issuerLength,
// [const, array, size_is(serialLength)] in octet serial,
// in unsigned long serialLength);
NS_IMETHODIMP CertBlocklist::IsCertRevoked(const uint8_t* aIssuer, uint32_t aIssuerLength,
const uint8_t* aSerial, uint32_t aSerialLength,
bool* _retval)
{
mozilla::MutexAutoLock lock(mMutex);
mozilla::pkix::Input issuer;
mozilla::pkix::Input serial;
if (issuer.Init(aIssuer, aIssuerLength) != mozilla::pkix::Success) {
return NS_ERROR_FAILURE;
}
if (serial.Init(aSerial, aSerialLength) != mozilla::pkix::Success) {
return NS_ERROR_FAILURE;
}
CertBlocklistItem item(issuer, serial);
*_retval = mBlocklist.Contains(item);
return NS_OK;
}

View File

@ -0,0 +1,71 @@
/* -*- 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 "nsClassHashtable.h"
#include "nsCOMPtr.h"
#include "nsICertBlocklist.h"
#include "nsIOutputStream.h"
#include "nsTHashtable.h"
#include "nsIX509CertDB.h"
#include "pkix/Input.h"
#define NS_CERT_BLOCKLIST_CID \
{0x11aefd53, 0x2fbb, 0x4c92, {0xa0, 0xc1, 0x05, 0x32, 0x12, 0xae, 0x42, 0xd0} }
enum CertBlocklistItemState {
CertNewFromBlocklist,
CertOldFromLocalCache
};
class CertBlocklistItem
{
public:
CertBlocklistItem(mozilla::pkix::Input aIssuer, mozilla::pkix::Input aSerial);
CertBlocklistItem(const CertBlocklistItem& aItem);
~CertBlocklistItem();
nsresult ToBase64(nsACString& b64IssuerOut, nsACString& b64SerialOut);
bool operator==(const CertBlocklistItem& aItem) const;
uint32_t Hash() const;
bool mIsCurrent = false;
private:
mozilla::pkix::Input mIssuer;
uint8_t* mIssuerData;
mozilla::pkix::Input mSerial;
uint8_t* mSerialData;
};
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 char* aIssuer,
const char* aSerial,
CertBlocklistItemState aItemState,
mozilla::MutexAutoLock& /*proofOfLock*/);
mozilla::Mutex mMutex;
bool mModified = false;
nsCOMPtr<nsIFile> mBackingFile;
protected:
virtual ~CertBlocklist();
};
#endif // CertBlocklist_h

View File

@ -9,6 +9,7 @@ EXPORTS.mozilla += [
]
UNIFIED_SOURCES += [
'CertBlocklist.cpp',
'DataStorage.cpp',
'nsBOOTModule.cpp',
'nsEntropyCollector.cpp',

View File

@ -5,6 +5,7 @@
#include "mozilla/ModuleUtils.h"
#include "CertBlocklist.h"
#include "nsEntropyCollector.h"
#include "nsSecureBrowserUIImpl.h"
#include "nsSecurityWarningDialogs.h"
@ -12,6 +13,7 @@
NS_GENERIC_FACTORY_CONSTRUCTOR(nsEntropyCollector)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSecureBrowserUIImpl)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(CertBlocklist, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSecurityWarningDialogs, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSiteSecurityService, Init)
@ -19,12 +21,14 @@ NS_DEFINE_NAMED_CID(NS_ENTROPYCOLLECTOR_CID);
NS_DEFINE_NAMED_CID(NS_SECURITYWARNINGDIALOGS_CID);
NS_DEFINE_NAMED_CID(NS_SECURE_BROWSER_UI_CID);
NS_DEFINE_NAMED_CID(NS_SITE_SECURITY_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_CERT_BLOCKLIST_CID);
static const mozilla::Module::CIDEntry kBOOTCIDs[] = {
{ &kNS_ENTROPYCOLLECTOR_CID, false, nullptr, nsEntropyCollectorConstructor },
{ &kNS_SECURITYWARNINGDIALOGS_CID, false, nullptr, nsSecurityWarningDialogsConstructor },
{ &kNS_SECURE_BROWSER_UI_CID, false, nullptr, nsSecureBrowserUIImplConstructor },
{ &kNS_SITE_SECURITY_SERVICE_CID, false, nullptr, nsSiteSecurityServiceConstructor },
{ &kNS_CERT_BLOCKLIST_CID, false, nullptr, CertBlocklistConstructor},
{ nullptr }
};
@ -33,6 +37,7 @@ static const mozilla::Module::ContractIDEntry kBOOTContracts[] = {
{ NS_SECURITYWARNINGDIALOGS_CONTRACTID, &kNS_SECURITYWARNINGDIALOGS_CID },
{ NS_SECURE_BROWSER_UI_CONTRACTID, &kNS_SECURE_BROWSER_UI_CID },
{ NS_SSSERVICE_CONTRACTID, &kNS_SITE_SECURITY_SERVICE_CID },
{ NS_CERTBLOCKLIST_CONTRACTID, &kNS_CERT_BLOCKLIST_CID },
{ nullptr }
};

View File

@ -7,15 +7,16 @@
#include "nsNSSComponent.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "mozilla/Telemetry.h"
#include "nsCertVerificationThread.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCertVerificationThread.h"
#include "nsComponentManagerUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsICertBlocklist.h"
#include "nsICertOverrideService.h"
#include "mozilla/Preferences.h"
#include "NSSCertDBTrustDomain.h"
#include "nsThreadUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/PublicSSL.h"
#include "mozilla/StaticPtr.h"
@ -1071,6 +1072,12 @@ nsNSSComponent::InitializeNSS()
return NS_ERROR_FAILURE;
}
// ensure the CertBlocklist is initialised
nsCOMPtr<nsICertBlocklist> certList = do_GetService(NS_CERTBLOCKLIST_CONTRACTID);
if (!certList) {
return NS_ERROR_FAILURE;
}
// dynamic options from prefs
setValidationOptions(true, lock);

View File

@ -0,0 +1,264 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
// This test checks a number of things:
// * it ensures that data loaded from revocations.txt on startup is present
// * it ensures that certItems in blocklist.xml are persisted correctly
// * it ensures that items in the CertBlocklist are seen as revoked by the
// cert verifier
// * it does a sanity check to ensure other cert verifier behavior is
// unmodified
let { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
// First, we need to setup appInfo for the blocklist service to work
let id = "xpcshell@tests.mozilla.org";
let appName = "XPCShell";
let version = "1";
let platformVersion = "1.9.2";
let appInfo = {
// nsIXULAppInfo
vendor: "Mozilla",
name: appName,
ID: id,
version: version,
appBuildID: "2007010101",
platformVersion: platformVersion ? platformVersion : "1.0",
platformBuildID: "2007010101",
// nsIXULRuntime
inSafeMode: false,
logConsoleErrors: true,
OS: "XPCShell",
XPCOMABI: "noarch-spidermonkey",
invalidateCachesOnRestart: function invalidateCachesOnRestart() {
// Do nothing
},
// nsICrashReporter
annotations: {},
annotateCrashReport: function(key, data) {
this.annotations[key] = data;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo,
Ci.nsIXULRuntime,
Ci.nsICrashReporter,
Ci.nsISupports])
};
let XULAppInfoFactory = {
createInstance: function (outer, iid) {
appInfo.QueryInterface(iid);
if (outer != null) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return appInfo.QueryInterface(iid);
}
};
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
XULAPPINFO_CONTRACTID, XULAppInfoFactory);
// we need to ensure we setup revocation data before certDB, or we'll start with
// no revocation.txt in the profile
let profile = do_get_profile();
let revocations = profile.clone();
revocations.append("revocations.txt");
if (!revocations.exists()) {
let existing = do_get_file("test_onecrl/sample_revocations.txt", false);
existing.copyTo(profile,"revocations.txt");
}
let certDB = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
// set up a test server to serve the blocklist.xml
let testserver = new HttpServer();
let blocklist_contents =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
// test with some bad data ...
"<certItems><certItem issuerName='Some nonsense in issuer'>" +
"<serialNumber>AkHVNA==</serialNumber>" +
"</certItem><certItem issuerName='MA0xCzAJBgNVBAMMAmNh'>" +
"<serialNumber>some nonsense in serial</serialNumber>" +
"</certItem><certItem issuerName='some nonsense in both issuer'>" +
"<serialNumber>and serial</serialNumber></certItem>" +
// some mixed
// In this case, the issuer name and the valid serialNumber correspond
// to test-int.der in tlsserver/
"<certItem issuerName='MBIxEDAOBgNVBAMTB1Rlc3QgQ0E='>" +
"<serialNumber>oops! more nonsense.</serialNumber>" +
"<serialNumber>BA==</serialNumber></certItem>" +
// ... and some good
// This item corresponds to an entry in sample_revocations.txt where:
// isser name is "another imaginary issuer" base-64 encoded, and
// serialNumbers are:
// "serial2." base-64 encoded, and
// "another serial." base-64 encoded
// We need this to ensure that existing items are retained if they're
// also in the blocklist
"<certItem issuerName='YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy'>" +
"<serialNumber>c2VyaWFsMi4=</serialNumber>" +
"<serialNumber>YW5vdGhlciBzZXJpYWwu</serialNumber>" +
"</certItem></certItems></blocklist>";
testserver.registerPathHandler("/push_blocked_cert/",
function serveResponse(request, response) {
response.write(blocklist_contents);
});
// start the test server
testserver.start(-1);
let port = testserver.identity.primaryPort;
// Setup the addonManager
let addonManager = Cc["@mozilla.org/addons/integration;1"]
.getService(Ci.nsIObserver)
.QueryInterface(Ci.nsITimerCallback);
addonManager.observe(null, "addons-startup", null);
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
function verify_cert(file, expectedError) {
let cert_der = readFile(do_get_file(file));
let ee = certDB.constructX509(cert_der, cert_der.length);
equal(expectedError, certDB.verifyCertNow(ee, certificateUsageSSLServer,
NO_FLAGS, {}, {}));
}
function load_cert(cert, trust) {
let file = "tlsserver/" + cert + ".der";
addCertFromFile(certDB, file, trust);
}
function testIsRevoked(certList, issuerString, serialString) {
let issuer = converter.convertToByteArray(issuerString, {});
let serial = converter.convertToByteArray(serialString, {});
return certList.isCertRevoked(issuer,
issuerString.length,
serial,
serialString.length);
}
function run_test() {
// import the certificates we need
load_cert("test-ca", "CTu,CTu,CTu");
load_cert("test-int", ",,");
let certList = Cc["@mozilla.org/security/certblocklist;1"]
.getService(Ci.nsICertBlocklist);
// check some existing items in revocations.txt are blocked. Since the
// CertBlocklistItems don't know about the data they contain, we can use
// arbitrary data (not necessarily DER) to test if items are revoked or not.
// This test corresponds to:
// issuer: c29tZSBpbWFnaW5hcnkgaXNzdWVy
// serial: c2VyaWFsLg==
ok(testIsRevoked(certList, "some imaginary issuer","serial."),
"issuer / serial pair should be blocked");
// And this test corresponds to:
// issuer: YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy
// serial: c2VyaWFsMi4=
ok(testIsRevoked(certList, "another imaginary issuer","serial2."),
"issuer / serial pair should be blocked");
// Soon we'll load a blocklist which revokes test-int.der, which issued
// test-int-ee.der.
// Check the cert validates before we load the blocklist
let file = "tlsserver/test-int-ee.der";
verify_cert(file, Cr.NS_OK);
// blocklist load is async so we must use add_test from here
add_test(function() {
let certblockObserver = {
observe: function(aSubject, aTopic, aData) {
run_next_test();
Services.obs.removeObserver(this, "blocklist-updated");
}
}
Services.obs.addObserver(certblockObserver, "blocklist-updated", false);
Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" +
port + "/push_blocked_cert/");
let blocklist = Cc["@mozilla.org/extensions/blocklist;1"]
.getService(Ci.nsITimerCallback);
blocklist.notify(null);
});
add_test(function() {
// The blocklist will be loaded now. Let's check the data is sane.
// In particular, we should still have the revoked issuer / serial pair
// that was in both revocations.txt and the blocklist.xml
ok(testIsRevoked(certList, "another imaginary issuer", "serial2."),
"issuer / serial pair should be blocked");
// Check that both serials in the certItem with multiple serials were read
// properly
ok(testIsRevoked(certList, "another imaginary issuer", "serial2."),
"issuer / serial pair should be blocked");
ok(testIsRevoked(certList, "another imaginary issuer", "another serial."),
"issuer / serial pair should be blocked");
// Check the blocklist entry has been persisted properly to the backing
// file
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 contents = "";
let hasmore = false;
do {
var line = {};
hasmore = inputStream.readLine(line);
contents = contents + (contents.length == 0 ? "" : "\n") + line.value;
} while (hasmore);
let expected = "# Auto generated contents. Do not edit.\n" +
"MBIxEDAOBgNVBAMTB1Rlc3QgQ0E=\n" +
" BA==\n" +
"YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy\n" +
" YW5vdGhlciBzZXJpYWwu\n" +
" c2VyaWFsMi4=";
equal(contents, expected, "revocations.txt should be as expected");
// Check the blocklisted intermediate now causes a failure
let file = "tlsserver/test-int-ee.der";
verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
// Check a non-blocklisted chain still validates OK
file = "tlsserver/default-ee.der";
verify_cert(file, Cr.NS_OK);
// Check a bad cert is still bad (unknown issuer)
file = "tlsserver/unknown-issuer.der";
verify_cert(file, SEC_ERROR_UNKNOWN_ISSUER);
// check that save with no further update is a no-op
let lastModified = revocations.lastModifiedTime;
// add an already existing entry
certList.addRevokedCert("YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy","c2VyaWFsMi4=");
certList.saveEntries();
let newModified = revocations.lastModifiedTime;
equal(lastModified, newModified,
"saveEntries with no modifications should not update the backing file");
run_next_test();
});
// we need to start the async portions of the test
run_next_test();
}

View File

@ -0,0 +1,26 @@
# a sample revocations.txt for tests
# Lines starting with '#' are ignored - as are empty lines like this:
# otherwise:
# non-empty lines are treated as base-64 encoded DER issuer data
# ...unless the line starts with a ' ' (space) character, in which case it's
# assumed to be base-64 encoded DER serial data.
# First a serial with no issuer to ensure this doesn't cause parsing to fail
# (there should be an issuer first, but we need to test this won't fail)
dGVzdA==
# next, let's ensure data that isn't valid base64 doesn't cause breakage.
this serial isn't valid base64 (but then there's no issuer anyway)
Neither is this issuer, though the serial is fine
dGVzdA==
dGVzdA==
in this case, issuer is fine but not the serial
# Next two entries; we can add valid base-64 encoded data for some basic tests:
# issuer is "some imaginary issuer" base-64 encoded
# and serial "serial." base-64 encoded
c29tZSBpbWFnaW5hcnkgaXNzdWVy
c2VyaWFsLg==
# issuer is "another imaginary issuer" base-64 encoded
# serial is "serial2." base-64 encoded
YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy
c2VyaWFsMi4=

View File

@ -267,6 +267,7 @@ export_cert localhostAndExampleCom default-ee.der
make_EE ocspOtherEndEntity 'CN=Other Cert' testCA "localhost,*.example.com"
make_INT testINT 'CN=Test Intermediate' testCA
export_cert testINT test-int.der
make_EE ocspEEWithIntermediate 'CN=Test End-entity with Intermediate' testINT "localhost,*.example.com"
make_EE expired 'CN=Expired Test End-entity' testCA "expired.example.com" "-w -400"
export_cert expired expired-ee.der
@ -279,6 +280,7 @@ make_EE selfsigned 'CN=Self-signed Test End-entity' testCA "selfsigned.example.c
# get regenerated. Either way, deletedINT will then be removed again.
make_INT deletedINT 'CN=Test Intermediate to delete' testCA
make_EE unknownissuer 'CN=Test End-entity from unknown issuer' deletedINT "unknownissuer.example.com"
export_cert unknownissuer unknown-issuer.der
$RUN_MOZILLA $CERTUTIL -d $DB_ARGUMENT -D -n deletedINT
@ -320,4 +322,8 @@ make_V1 v1Cert 'CN=V1 Cert' testCA
export_cert v1Cert v1Cert.der
make_EE eeIssuedByV1Cert 'CN=EE Issued by V1 Cert' v1Cert "localhost,*.example.com"
# Make a valid EE using testINT to test OneCRL revocation of testINT
make_EE eeIssuedByIntermediate 'CN=EE issued by intermediate' testINT "localhost"
export_cert eeIssuedByIntermediate test-int-ee.der
cleanup

Binary file not shown.

View File

@ -18,6 +18,7 @@ support-files =
test_ocsp_fetch_method/**
test_keysize/**
test_pinning_dynamic/**
test_onecrl/**
[test_datasignatureverifier.js]
[test_hash_algorithms.js]
@ -46,6 +47,7 @@ skip-if = buildapp == "b2g" && processor == "arm"
run-sequentially = hardcoded ports
# Bug 1009158: this test times out on Android
skip-if = os == "android"
[test_cert_blocklist.js]
[test_ocsp_stapling_expired.js]
run-sequentially = hardcoded ports
# Bug 1009158: this test times out on Android

View File

@ -74,6 +74,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker",
"@mozilla.org/xpcom/version-comparator;1",
"nsIVersionComparator");
XPCOMUtils.defineLazyServiceGetter(this, "gCertBlocklistService",
"@mozilla.org/security/certblocklist;1",
"nsICertBlocklist");
XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() {
return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
QueryInterface(Ci.nsIPrefBranch);
@ -725,6 +729,13 @@ Blocklist.prototype = {
# <match name="description" exp="1[.]2[.]3"/>
# </pluginItem>
# </pluginItems>
# <certItems>
# <!-- issuerName is the DER issuer name data base64 encoded... -->
# <certItem issuerName="MA0xCzAJBgNVBAMMAmNh">
# <!-- ... as is the serial number DER data -->
# <serialNumber>AkHVNA==</serialNumber>
# </certItem>
# </certItems>
# </blocklist>
*/
@ -862,12 +873,17 @@ Blocklist.prototype = {
this._pluginEntries = this._processItemNodes(element.childNodes, "plugin",
this._handlePluginItemNode);
break;
case "certItems":
this._processItemNodes(element.childNodes, "cert",
this._handleCertItemNode.bind(this));
break;
default:
Services.obs.notifyObservers(element,
"blocklist-data-" + element.localName,
null);
}
}
gCertBlocklistService.saveEntries();
}
catch (e) {
LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
@ -889,6 +905,22 @@ Blocklist.prototype = {
return result;
},
_handleCertItemNode: function Blocklist_handleCertItemNode(blocklistElement,
result) {
let issuer = blocklistElement.getAttribute("issuerName");
for (let snElement of blocklistElement.children) {
try {
if (issuer) {
gCertBlocklistService.addRevokedCert(issuer, snElement.textContent);
}
} catch (e) {
// we want to keep trying other elements since missing all items
// is worse than missing one
LOG("Blocklist::_handleCertItemNode: Error adding revoked cert " + e);
}
}
},
_handleEmItemNode: function Blocklist_handleEmItemNode(blocklistElement, result) {
if (!matchesOSABI(blocklistElement))
return;