Bug 1659786 - avoid CERTCertificate in CSTrustDomain and ContentSignatureVerifier r=rmf

Differential Revision: https://phabricator.services.mozilla.com/D87497
This commit is contained in:
Dana Keeler 2020-08-20 19:28:07 +00:00
Родитель c5077ba863
Коммит 3ac5dbc513
8 изменённых файлов: 80 добавлений и 105 удалений

Просмотреть файл

@ -9,15 +9,14 @@
#endif
#include "CSTrustDomain.h"
#include "mozilla/Base64.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "nsDirectoryServiceUtils.h"
#endif
#include "nsNSSCertificate.h"
#include "nsNSSComponent.h"
#include "NSSCertDBTrustDomain.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "mozpkix/pkixnss.h"
using namespace mozilla::pkix;
@ -28,8 +27,8 @@ namespace psm {
static LazyLogModule gTrustDomainPRLog("CSTrustDomain");
#define CSTrust_LOG(args) MOZ_LOG(gTrustDomainPRLog, LogLevel::Debug, args)
CSTrustDomain::CSTrustDomain(UniqueCERTCertList& certChain)
: mCertChain(certChain),
CSTrustDomain::CSTrustDomain(nsTArray<nsTArray<uint8_t>>& certList)
: mCertList(certList),
#ifdef MOZ_NEW_CERT_STORAGE
mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)) {
}
@ -47,13 +46,6 @@ Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
return Result::FATAL_ERROR_INVALID_ARGS;
}
SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER);
UniqueCERTCertificate candidateCert(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, true));
if (!candidateCert) {
return MapPRErrorCodeToResult(PR_GetError());
}
#ifdef MOZ_NEW_CERT_STORAGE
nsTArray<uint8_t> issuerBytes;
nsTArray<uint8_t> serialBytes;
@ -105,7 +97,9 @@ Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
if (!component) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
nsrv = component->IsCertContentSigningRoot(candidateCert.get(), &isRoot);
nsTArray<uint8_t> candidateCert(candidateCertDER.UnsafeGetData(),
candidateCertDER.GetLength());
nsrv = component->IsCertContentSigningRoot(candidateCert, &isRoot);
if (NS_FAILED(nsrv)) {
return Result::FATAL_ERROR_LIBRARY_FAILURE;
}
@ -123,28 +117,14 @@ Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
Result CSTrustDomain::FindIssuer(Input encodedIssuerName,
IssuerChecker& checker, Time time) {
// Loop over the chain, look for a matching subject
for (CERTCertListNode* n = CERT_LIST_HEAD(mCertChain);
!CERT_LIST_END(n, mCertChain); n = CERT_LIST_NEXT(n)) {
Input certDER;
Result rv = certDER.Init(n->cert->derCert.data, n->cert->derCert.len);
for (const auto& certBytes : mCertList) {
Input certInput;
Result rv = certInput.Init(certBytes.Elements(), certBytes.Length());
if (rv != Success) {
continue; // probably too big
}
// if the subject does not match, try the next certificate
Input subjectDER;
rv = subjectDER.Init(n->cert->derSubject.data, n->cert->derSubject.len);
if (rv != Success) {
continue; // just try the next one
}
if (!InputsAreEqual(subjectDER, encodedIssuerName)) {
CSTrust_LOG(("CSTrustDomain: subjects don't match\n"));
continue;
}
// If the subject does match, try the next step
bool keepGoing;
rv = checker.Check(certDER, nullptr /*additionalNameConstraints*/,
rv = checker.Check(certInput, nullptr /*additionalNameConstraints*/,
keepGoing);
if (rv != Success) {
return rv;

Просмотреть файл

@ -8,15 +8,12 @@
#define CSTrustDomain_h
#include "mozpkix/pkixtypes.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/UniquePtr.h"
#include "nsDebug.h"
#ifdef MOZ_NEW_CERT_STORAGE
# include "nsICertStorage.h"
#else
# include "nsICertBlocklist.h"
#endif
#include "ScopedNSSTypes.h"
#include "nsTArray.h"
namespace mozilla {
namespace psm {
@ -25,7 +22,7 @@ class CSTrustDomain final : public mozilla::pkix::TrustDomain {
public:
typedef mozilla::pkix::Result Result;
explicit CSTrustDomain(UniqueCERTCertList& certChain);
explicit CSTrustDomain(nsTArray<nsTArray<uint8_t>>& certList);
virtual Result GetCertTrust(
mozilla::pkix::EndEntityOrCA endEntityOrCA,
@ -76,7 +73,7 @@ class CSTrustDomain final : public mozilla::pkix::TrustDomain {
size_t digestBufLen) override;
private:
/*out*/ UniqueCERTCertList& mCertChain;
nsTArray<nsTArray<uint8_t>>& mCertList; // non-owning!
#ifdef MOZ_NEW_CERT_STORAGE
nsCOMPtr<nsICertStorage> mCertBlocklist;
#else

Просмотреть файл

@ -14,15 +14,14 @@
#include "cryptohi.h"
#include "keyhi.h"
#include "mozilla/Base64.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/Promise.h"
#include "nsCOMPtr.h"
#include "nsPromiseFlatString.h"
#include "nsProxyRelease.h"
#include "nsSecurityHeaderParser.h"
#include "nsWhitespaceTokenizer.h"
#include "mozpkix/pkix.h"
#include "mozpkix/pkixtypes.h"
#include "mozpkix/pkixutil.h"
#include "secerr.h"
#include "ssl.h"
@ -146,7 +145,7 @@ void VerifyContentSignatureTask::CallCallback(nsresult rv) {
bool IsNewLine(char16_t c) { return c == '\n' || c == '\r'; }
nsresult ReadChainIntoCertList(const nsACString& aCertChain,
CERTCertList* aCertList) {
nsTArray<nsTArray<uint8_t>>& aCertList) {
bool inBlock = false;
bool certFound = false;
@ -172,22 +171,8 @@ nsresult ReadChainIntoCertList(const nsACString& aCertChain,
CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
return rv;
}
SECItem der = {
siBuffer,
BitwiseCast<unsigned char*, const char*>(derString.get()),
derString.Length(),
};
UniqueCERTCertificate tmpCert(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &der, nullptr, false, true));
if (!tmpCert) {
return NS_ERROR_FAILURE;
}
// if adding tmpCert succeeds, tmpCert will now be owned by aCertList
SECStatus res = CERT_AddCertToListTail(aCertList, tmpCert.get());
if (res != SECSuccess) {
return MapSECStatus(res);
}
Unused << tmpCert.release();
nsTArray<uint8_t> derBytes(derString.Data(), derString.Length());
aCertList.AppendElement(std::move(derBytes));
} else {
blockData.Append(token);
}
@ -218,49 +203,44 @@ static nsresult VerifyContentSignatureInternal(
Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS& aErrorLabel,
/* out */ nsACString& aCertFingerprint,
/* out */ uint32_t& aErrorValue) {
UniqueCERTCertList certCertList(CERT_NewCertList());
if (!certCertList) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsresult rv = ReadChainIntoCertList(aCertChain, certCertList.get());
nsTArray<nsTArray<uint8_t>> certList;
nsresult rv = ReadChainIntoCertList(aCertChain, certList);
if (NS_FAILED(rv)) {
return rv;
}
CERTCertListNode* node = CERT_LIST_HEAD(certCertList.get());
if (!node || CERT_LIST_END(node, certCertList.get()) || !node->cert) {
if (certList.Length() < 1) {
return NS_ERROR_FAILURE;
}
SECItem* certSecItem = &node->cert->derCert;
Input certDER;
// The 0th element should be the end-entity that issued the content
// signature.
nsTArray<uint8_t>& certBytes(certList.ElementAt(0));
Input certInput;
mozilla::pkix::Result result =
certDER.Init(BitwiseCast<uint8_t*, unsigned char*>(certSecItem->data),
certSecItem->len);
certInput.Init(certBytes.Elements(), certBytes.Length());
if (result != Success) {
return NS_ERROR_FAILURE;
}
// Get EE certificate fingerprint for telemetry.
unsigned char fingerprint[SHA256_LENGTH] = {0};
SECStatus srv = PK11_HashBuf(SEC_OID_SHA256, fingerprint, certSecItem->data,
AssertedCast<int32_t>(certSecItem->len));
SECStatus srv =
PK11_HashBuf(SEC_OID_SHA256, fingerprint, certInput.UnsafeGetData(),
certInput.GetLength());
if (srv != SECSuccess) {
return NS_ERROR_FAILURE;
}
SECItem fingerprintItem = {siBuffer, fingerprint, SHA256_LENGTH};
UniquePORTString tmpFingerprintString(CERT_Hexify(&fingerprintItem, 0));
UniquePORTString tmpFingerprintString(
CERT_Hexify(&fingerprintItem, false /* don't use colon delimiters */));
if (!tmpFingerprintString) {
return NS_ERROR_OUT_OF_MEMORY;
}
aCertFingerprint.Assign(tmpFingerprintString.get());
// Check the signerCert chain is good
CSTrustDomain trustDomain(certCertList);
CSTrustDomain trustDomain(certList);
result = BuildCertChain(
trustDomain, certDER, Now(), EndEntityOrCA::MustBeEndEntity,
trustDomain, certInput, Now(), EndEntityOrCA::MustBeEndEntity,
KeyUsage::noParticularKeyUsageRequired, KeyPurposeId::id_kp_codeSigning,
CertPolicyId::anyPolicy, nullptr /*stapledOCSPResponse*/);
if (result != Success) {
@ -300,7 +280,7 @@ static nsresult VerifyContentSignatureInternal(
}
BRNameMatchingPolicy nameMatchingPolicy(BRNameMatchingPolicy::Mode::Enforce);
result = CheckCertHostname(certDER, hostnameInput, nameMatchingPolicy);
result = CheckCertHostname(certInput, hostnameInput, nameMatchingPolicy);
if (result != Success) {
// EE cert isnot valid for the given host name.
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err7;
@ -308,8 +288,28 @@ static nsresult VerifyContentSignatureInternal(
return NS_ERROR_INVALID_SIGNATURE;
}
mozilla::UniqueSECKEYPublicKey key(CERT_ExtractPublicKey(node->cert));
// in case we were not able to extract a key
pkix::BackCert backCert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
result = backCert.Init();
// This should never fail, because we've already built a verified certificate
// chain with this certificate.
if (result != Success) {
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
aErrorValue = 8;
CSVerifier_LOG(("CSVerifier: couldn't decode certificate to get spki"));
return NS_ERROR_INVALID_SIGNATURE;
}
Input spkiInput = backCert.GetSubjectPublicKeyInfo();
SECItem spkiItem = {siBuffer, const_cast<uint8_t*>(spkiInput.UnsafeGetData()),
spkiInput.GetLength()};
UniqueCERTSubjectPublicKeyInfo spki(
SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
if (!spki) {
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
aErrorValue = 8;
CSVerifier_LOG(("CSVerifier: couldn't decode spki"));
return NS_ERROR_INVALID_SIGNATURE;
}
mozilla::UniqueSECKEYPublicKey key(SECKEY_ExtractPublicKey(spki.get()));
if (!key) {
aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
aErrorValue = 8;

Просмотреть файл

@ -38,10 +38,10 @@ interface nsINSSComponent : nsISupports {
[noscript] bool isCertTestBuiltInRoot(in CERTCertificatePtr cert);
/**
* Used to determine if the given CERTCertificate is the content signing root
* certificate.
* Used to determine if the given certificate (represented as an array of
* bytes) is the content signing root certificate.
*/
[noscript] bool isCertContentSigningRoot(in CERTCertificatePtr cert);
[noscript] bool isCertContentSigningRoot(in Array<octet> cert);
/**
* If enabled by the preference "security.enterprise_roots.enabled", returns

Просмотреть файл

@ -568,7 +568,8 @@ nsNSSCertificate::GetIssuerName(nsAString& _issuerName) {
NS_IMETHODIMP
nsNSSCertificate::GetSerialNumber(nsAString& _serialNumber) {
_serialNumber.Truncate();
UniquePORTString tmpstr(CERT_Hexify(&mCert->serialNumber, 1));
UniquePORTString tmpstr(
CERT_Hexify(&mCert->serialNumber, true /* use colon delimiters */));
if (tmpstr) {
_serialNumber = NS_ConvertASCIItoUTF16(tmpstr.get());
return NS_OK;
@ -586,8 +587,8 @@ nsresult nsNSSCertificate::GetCertificateHash(nsAString& aFingerprint,
return rv;
}
// CERT_Hexify's second argument is an int that is interpreted as a boolean
UniquePORTString fpStr(CERT_Hexify(const_cast<SECItem*>(&digest.get()), 1));
UniquePORTString fpStr(CERT_Hexify(const_cast<SECItem*>(&digest.get()),
true /* use colon delimiters */));
if (!fpStr) {
return NS_ERROR_FAILURE;
}

Просмотреть файл

@ -2049,7 +2049,7 @@ nsresult nsNSSComponent::InitializeNSS() {
mTestBuiltInRootHash);
#endif
mContentSigningRootHash.Truncate();
Preferences::GetString("security.content.signature.root_hash",
Preferences::GetCString("security.content.signature.root_hash",
mContentSigningRootHash);
mMitmCanaryIssuer.Truncate();
@ -2442,7 +2442,7 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic,
} else if (prefName.EqualsLiteral("security.content.signature.root_hash")) {
MutexAutoLock lock(mMutex);
mContentSigningRootHash.Truncate();
Preferences::GetString("security.content.signature.root_hash",
Preferences::GetCString("security.content.signature.root_hash",
mContentSigningRootHash);
} else if (prefName.Equals(kEnterpriseRootModePref) ||
prefName.Equals(kFamilySafetyModePref)) {
@ -2566,30 +2566,28 @@ nsNSSComponent::IsCertTestBuiltInRoot(CERTCertificate* cert, bool* result) {
}
NS_IMETHODIMP
nsNSSComponent::IsCertContentSigningRoot(CERTCertificate* cert, bool* result) {
nsNSSComponent::IsCertContentSigningRoot(const nsTArray<uint8_t>& cert,
bool* result) {
NS_ENSURE_ARG_POINTER(result);
*result = false;
RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(cert);
if (!nsc) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("creating nsNSSCertificate failed"));
return NS_ERROR_FAILURE;
if (cert.Length() > std::numeric_limits<uint32_t>::max()) {
return NS_ERROR_INVALID_ARG;
}
nsAutoString certHash;
nsresult rv = nsc->GetSha256Fingerprint(certHash);
Digest digest;
nsresult rv =
digest.DigestBuf(SEC_OID_SHA256, cert.Elements(), cert.Length());
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("getting cert fingerprint failed"));
return rv;
}
UniquePORTString fingerprintCString(CERT_Hexify(
const_cast<SECItem*>(&digest.get()), true /* use colon delimiters */));
if (!fingerprintCString) {
return NS_ERROR_FAILURE;
}
MutexAutoLock lock(mMutex);
if (mContentSigningRootHash.IsEmpty()) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("mContentSigningRootHash is empty"));
return NS_ERROR_FAILURE;
}
*result = mContentSigningRootHash.Equals(certHash);
*result = mContentSigningRootHash.Equals(fingerprintCString.get());
return NS_OK;
}

Просмотреть файл

@ -123,7 +123,7 @@ class nsNSSComponent final : public nsINSSComponent, public nsIObserver {
#ifdef DEBUG
nsString mTestBuiltInRootHash;
#endif
nsString mContentSigningRootHash;
nsCString mContentSigningRootHash;
RefPtr<mozilla::psm::SharedCertVerifier> mDefaultCertVerifier;
nsString mMitmCanaryIssuer;
bool mMitmDetecionEnabled;

Просмотреть файл

@ -377,7 +377,6 @@ add_task(async function run_test() {
let badSections = [
// data that looks like PEM but isn't
"-----BEGIN CERTIFICATE-----\nBSsPRlYp5+gaFMRIczwUzaioRfteCjr94xyz0g==\n",
"-----BEGIN CERTIFICATE-----\nBSsPRlYp5+gaFMRIczwUzaioRfteCjr94xyz0g==\n-----END CERTIFICATE-----",
// data that will start to parse but won't base64decode
"-----BEGIN CERTIFICATE-----\nnon-base64-stuff\n-----END CERTIFICATE-----",
// data with garbage outside of PEM sections