зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1918279 - implement a signature cache r=jschanck
Differential Revision: https://phabricator.services.mozilla.com/D221904
This commit is contained in:
Родитель
c787aab229
Коммит
4e10a310f1
|
@ -2358,6 +2358,7 @@ dependencies = [
|
|||
"rure",
|
||||
"rusqlite",
|
||||
"rust_minidump_writer_linux",
|
||||
"signature_cache",
|
||||
"static_prefs",
|
||||
"storage",
|
||||
"unic-langid",
|
||||
|
@ -5463,6 +5464,13 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "signature_cache"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hashlink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.10"
|
||||
|
|
|
@ -15544,6 +15544,24 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
# The number of entries in the certificate signature cache.
|
||||
# A higher number increases memory usage, but should increase the cache hit rate.
|
||||
# Each entry is approximately 64 bytes, and the maximum number of entries is 65535.
|
||||
- name: security.pki.cert_signature_cache_size
|
||||
type: RelaxedAtomicUint32
|
||||
value: 128
|
||||
mirror: always
|
||||
|
||||
# The number of entries in the SCT signature cache.
|
||||
# A higher number increases memory usage, but should increase the cache hit rate.
|
||||
# Each entry is approximately 64 bytes, and the maximum number of entries is 65535.
|
||||
# There will be 2-3 SCTs per certificate, so this probably needs to be 2-3x
|
||||
# security.pki.cert_signature_cache_size to achieve similar hit rates.
|
||||
- name: security.pki.sct_signature_cache_size
|
||||
type: RelaxedAtomicUint32
|
||||
value: 256
|
||||
mirror: always
|
||||
|
||||
- name: security.tls.version.min
|
||||
type: RelaxedAtomicUint32
|
||||
value: 3
|
||||
|
|
|
@ -20,17 +20,18 @@
|
|||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "nsNSSComponent.h"
|
||||
#include "mozilla/StaticPrefs_security.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "nsPromiseFlatString.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "pk11pub.h"
|
||||
#include "mozpkix/pkix.h"
|
||||
#include "mozpkix/pkixcheck.h"
|
||||
#include "mozpkix/pkixnss.h"
|
||||
#include "mozpkix/pkixutil.h"
|
||||
#include "secmod.h"
|
||||
#include "nsNSSComponent.h"
|
||||
#include "nsNetCID.h"
|
||||
#include "nsPromiseFlatString.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "pk11pub.h"
|
||||
#include "secmod.h"
|
||||
|
||||
using namespace mozilla::ct;
|
||||
using namespace mozilla::pkix;
|
||||
|
@ -68,7 +69,11 @@ CertVerifier::CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
|
|||
mCertShortLifetimeInDays(certShortLifetimeInDays),
|
||||
mNetscapeStepUpPolicy(netscapeStepUpPolicy),
|
||||
mCTMode(ctMode),
|
||||
mCRLiteMode(crliteMode) {
|
||||
mCRLiteMode(crliteMode),
|
||||
mSignatureCache(
|
||||
signature_cache_new(
|
||||
StaticPrefs::security_pki_cert_signature_cache_size()),
|
||||
signature_cache_free) {
|
||||
LoadKnownCTLogs();
|
||||
mThirdPartyCerts = thirdPartyCerts.Clone();
|
||||
for (const auto& root : mThirdPartyCerts) {
|
||||
|
@ -459,12 +464,12 @@ Result CertVerifier::VerifyCert(
|
|||
// XXX: We don't really have a trust bit for SSL client authentication so
|
||||
// just use trustEmail as it is the closest alternative.
|
||||
NSSCertDBTrustDomain trustDomain(
|
||||
trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
|
||||
mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK,
|
||||
ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch,
|
||||
mCRLiteMode, originAttributes, mThirdPartyRootInputs,
|
||||
mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr,
|
||||
nullptr);
|
||||
trustEmail, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
|
||||
pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays,
|
||||
MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff,
|
||||
NetscapeStepUpPolicy::NeverMatch, mCRLiteMode, originAttributes,
|
||||
mThirdPartyRootInputs, mThirdPartyIntermediateInputs,
|
||||
extraCertificates, builtChain, nullptr, nullptr);
|
||||
rv = BuildCertChain(
|
||||
trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::digitalSignature, KeyPurposeId::id_kp_clientAuth,
|
||||
|
@ -492,12 +497,12 @@ Result CertVerifier::VerifyCert(
|
|||
rv = Result::ERROR_UNKNOWN_ERROR;
|
||||
for (const auto& evPolicy : evPolicies) {
|
||||
NSSCertDBTrustDomain trustDomain(
|
||||
trustSSL, evOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
|
||||
mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS,
|
||||
ValidityCheckingMode::CheckForEV, mNetscapeStepUpPolicy,
|
||||
mCRLiteMode, originAttributes, mThirdPartyRootInputs,
|
||||
mThirdPartyIntermediateInputs, extraCertificates, builtChain,
|
||||
pinningTelemetryInfo, hostname);
|
||||
trustSSL, evOCSPFetching, mOCSPCache, mSignatureCache.get(), pinArg,
|
||||
mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays,
|
||||
MIN_RSA_BITS, ValidityCheckingMode::CheckForEV,
|
||||
mNetscapeStepUpPolicy, mCRLiteMode, originAttributes,
|
||||
mThirdPartyRootInputs, mThirdPartyIntermediateInputs,
|
||||
extraCertificates, builtChain, pinningTelemetryInfo, hostname);
|
||||
rv = BuildCertChainForOneKeyUsage(
|
||||
trustDomain, certDER, time,
|
||||
KeyUsage::digitalSignature, // (EC)DHE
|
||||
|
@ -554,8 +559,9 @@ Result CertVerifier::VerifyCert(
|
|||
}
|
||||
|
||||
NSSCertDBTrustDomain trustDomain(
|
||||
trustSSL, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
|
||||
mOCSPTimeoutHard, mCertShortLifetimeInDays, keySizeOptions[i],
|
||||
trustSSL, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
|
||||
pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard,
|
||||
mCertShortLifetimeInDays, keySizeOptions[i],
|
||||
ValidityCheckingMode::CheckingOff, mNetscapeStepUpPolicy,
|
||||
mCRLiteMode, originAttributes, mThirdPartyRootInputs,
|
||||
mThirdPartyIntermediateInputs, extraCertificates, builtChain,
|
||||
|
@ -608,12 +614,12 @@ Result CertVerifier::VerifyCert(
|
|||
|
||||
case certificateUsageSSLCA: {
|
||||
NSSCertDBTrustDomain trustDomain(
|
||||
trustSSL, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
|
||||
mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK,
|
||||
ValidityCheckingMode::CheckingOff, mNetscapeStepUpPolicy, mCRLiteMode,
|
||||
originAttributes, mThirdPartyRootInputs,
|
||||
mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr,
|
||||
nullptr);
|
||||
trustSSL, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
|
||||
pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays,
|
||||
MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff,
|
||||
mNetscapeStepUpPolicy, mCRLiteMode, originAttributes,
|
||||
mThirdPartyRootInputs, mThirdPartyIntermediateInputs,
|
||||
extraCertificates, builtChain, nullptr, nullptr);
|
||||
rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeCA,
|
||||
KeyUsage::keyCertSign, KeyPurposeId::id_kp_serverAuth,
|
||||
CertPolicyId::anyPolicy, stapledOCSPResponse);
|
||||
|
@ -626,12 +632,12 @@ Result CertVerifier::VerifyCert(
|
|||
|
||||
case certificateUsageEmailSigner: {
|
||||
NSSCertDBTrustDomain trustDomain(
|
||||
trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
|
||||
mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK,
|
||||
ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch,
|
||||
mCRLiteMode, originAttributes, mThirdPartyRootInputs,
|
||||
mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr,
|
||||
nullptr);
|
||||
trustEmail, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
|
||||
pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays,
|
||||
MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff,
|
||||
NetscapeStepUpPolicy::NeverMatch, mCRLiteMode, originAttributes,
|
||||
mThirdPartyRootInputs, mThirdPartyIntermediateInputs,
|
||||
extraCertificates, builtChain, nullptr, nullptr);
|
||||
rv = BuildCertChain(
|
||||
trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::digitalSignature, KeyPurposeId::id_kp_emailProtection,
|
||||
|
@ -654,12 +660,12 @@ Result CertVerifier::VerifyCert(
|
|||
// usage it is trying to verify for, and base its algorithm choices
|
||||
// based on the result of the verification(s).
|
||||
NSSCertDBTrustDomain trustDomain(
|
||||
trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
|
||||
mOCSPTimeoutHard, mCertShortLifetimeInDays, MIN_RSA_BITS_WEAK,
|
||||
ValidityCheckingMode::CheckingOff, NetscapeStepUpPolicy::NeverMatch,
|
||||
mCRLiteMode, originAttributes, mThirdPartyRootInputs,
|
||||
mThirdPartyIntermediateInputs, extraCertificates, builtChain, nullptr,
|
||||
nullptr);
|
||||
trustEmail, defaultOCSPFetching, mOCSPCache, mSignatureCache.get(),
|
||||
pinArg, mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays,
|
||||
MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff,
|
||||
NetscapeStepUpPolicy::NeverMatch, mCRLiteMode, originAttributes,
|
||||
mThirdPartyRootInputs, mThirdPartyIntermediateInputs,
|
||||
extraCertificates, builtChain, nullptr, nullptr);
|
||||
rv = BuildCertChain(trustDomain, certDER, time,
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::keyEncipherment, // RSA
|
||||
|
@ -867,5 +873,110 @@ Result CertVerifier::VerifySSLServerCert(
|
|||
return Success;
|
||||
}
|
||||
|
||||
// Take the (data, signature, subjectPublicKeyInfo, publicKeyAlgorithm,
|
||||
// digestAlgorithm) tuple that defines a signature and derive a hash that
|
||||
// uniquely identifies it. This is done by prefixing each variable-length
|
||||
// component (data, signature, and subjectPublicKeyInfo) with
|
||||
// sizeof(pkix::Input::size_type) bytes (currently 2) indicating the length of
|
||||
// that component and concatenating them together, followed by one byte for the
|
||||
// digestAlgorithm. The concatenation is then hashed with sha512.
|
||||
// It should be computationally infeasible to find two distinct sets of inputs
|
||||
// that have the same sha512 hash (and if it were possible, then it would be
|
||||
// possible to break the signature scheme itself).
|
||||
void HashSignatureParams(pkix::Input data, pkix::Input signature,
|
||||
pkix::Input subjectPublicKeyInfo,
|
||||
pkix::der::PublicKeyAlgorithm publicKeyAlgorithm,
|
||||
pkix::DigestAlgorithm digestAlgorithm,
|
||||
/*out*/ Maybe<nsTArray<uint8_t>>& sha512Hash) {
|
||||
sha512Hash.reset();
|
||||
Digest digest;
|
||||
if (NS_FAILED(digest.Begin(SEC_OID_SHA512))) {
|
||||
return;
|
||||
}
|
||||
pkix::Input::size_type dataLength = data.GetLength();
|
||||
if (NS_FAILED(digest.Update(reinterpret_cast<const uint8_t*>(&dataLength),
|
||||
sizeof(dataLength)))) {
|
||||
return;
|
||||
}
|
||||
if (NS_FAILED(digest.Update(data.UnsafeGetData(), dataLength))) {
|
||||
return;
|
||||
}
|
||||
pkix::Input::size_type signatureLength = signature.GetLength();
|
||||
if (NS_FAILED(
|
||||
digest.Update(reinterpret_cast<const uint8_t*>(&signatureLength),
|
||||
sizeof(signatureLength)))) {
|
||||
return;
|
||||
}
|
||||
if (NS_FAILED(digest.Update(signature.UnsafeGetData(), signatureLength))) {
|
||||
return;
|
||||
}
|
||||
pkix::Input::size_type spkiLength = subjectPublicKeyInfo.GetLength();
|
||||
if (NS_FAILED(digest.Update(reinterpret_cast<const uint8_t*>(&spkiLength),
|
||||
sizeof(spkiLength)))) {
|
||||
return;
|
||||
}
|
||||
if (NS_FAILED(
|
||||
digest.Update(subjectPublicKeyInfo.UnsafeGetData(), spkiLength))) {
|
||||
return;
|
||||
}
|
||||
if (NS_FAILED(
|
||||
digest.Update(reinterpret_cast<const uint8_t*>(&publicKeyAlgorithm),
|
||||
sizeof(publicKeyAlgorithm)))) {
|
||||
return;
|
||||
}
|
||||
if (NS_FAILED(
|
||||
digest.Update(reinterpret_cast<const uint8_t*>(&digestAlgorithm),
|
||||
sizeof(digestAlgorithm)))) {
|
||||
return;
|
||||
}
|
||||
nsTArray<uint8_t> result;
|
||||
if (NS_FAILED(digest.End(result))) {
|
||||
return;
|
||||
}
|
||||
sha512Hash.emplace(std::move(result));
|
||||
}
|
||||
|
||||
Result VerifySignedDataWithCache(
|
||||
der::PublicKeyAlgorithm publicKeyAlg,
|
||||
mozilla::glean::impl::DenominatorMetric telemetryDenominator,
|
||||
mozilla::glean::impl::NumeratorMetric telemetryNumerator, Input data,
|
||||
DigestAlgorithm digestAlgorithm, Input signature,
|
||||
Input subjectPublicKeyInfo, SignatureCache* signatureCache, void* pinArg) {
|
||||
telemetryDenominator.Add(1);
|
||||
Maybe<nsTArray<uint8_t>> sha512Hash;
|
||||
HashSignatureParams(data, signature, subjectPublicKeyInfo, publicKeyAlg,
|
||||
digestAlgorithm, sha512Hash);
|
||||
// If hashing the signature parameters succeeded, see if this signature is in
|
||||
// the signature cache.
|
||||
if (sha512Hash.isSome() &&
|
||||
signature_cache_get(signatureCache, sha512Hash.ref().Elements())) {
|
||||
telemetryNumerator.AddToNumerator(1);
|
||||
return Success;
|
||||
}
|
||||
Result result;
|
||||
switch (publicKeyAlg) {
|
||||
case der::PublicKeyAlgorithm::ECDSA:
|
||||
result = VerifyECDSASignedDataNSS(data, digestAlgorithm, signature,
|
||||
subjectPublicKeyInfo, pinArg);
|
||||
break;
|
||||
case der::PublicKeyAlgorithm::RSA_PKCS1:
|
||||
result = VerifyRSAPKCS1SignedDataNSS(data, digestAlgorithm, signature,
|
||||
subjectPublicKeyInfo, pinArg);
|
||||
break;
|
||||
case der::PublicKeyAlgorithm::RSA_PSS:
|
||||
result = VerifyRSAPSSSignedDataNSS(data, digestAlgorithm, signature,
|
||||
subjectPublicKeyInfo, pinArg);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("unhandled public key algorithm");
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
// Add this signature to the signature cache.
|
||||
if (sha512Hash.isSome() && result == Success) {
|
||||
signature_cache_insert(signatureCache, sha512Hash.ref().Elements());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace psm
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -17,8 +17,11 @@
|
|||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "nsString.h"
|
||||
#include "mozilla/glean/GleanMetrics.h"
|
||||
#include "mozpkix/pkixder.h"
|
||||
#include "mozpkix/pkixtypes.h"
|
||||
#include "nsString.h"
|
||||
#include "signature_cache_ffi.h"
|
||||
#include "sslt.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
@ -264,6 +267,13 @@ class CertVerifier {
|
|||
// so we must allocate dynamically.
|
||||
UniquePtr<mozilla::ct::MultiLogCTVerifier> mCTVerifier;
|
||||
|
||||
// If many connections are made to a site using a particular certificate,
|
||||
// this cache will speed up verifications after the first one by saving the
|
||||
// results of signature verification.
|
||||
// This will also be beneficial in situations where different sites use
|
||||
// different certificates that happen to be issued by the same intermediate.
|
||||
UniquePtr<SignatureCache, decltype(&signature_cache_free)> mSignatureCache;
|
||||
|
||||
void LoadKnownCTLogs();
|
||||
mozilla::pkix::Result VerifyCertificateTransparencyPolicy(
|
||||
NSSCertDBTrustDomain& trustDomain,
|
||||
|
@ -277,6 +287,18 @@ mozilla::pkix::Result CertListContainsExpectedKeys(const CERTCertList* certList,
|
|||
const char* hostname,
|
||||
mozilla::pkix::Time time);
|
||||
|
||||
// Verify signed data, making use of the given SignatureCache. That is, if the
|
||||
// (data, digestAlgorithm, signature, subjectPublicKeyInfo) tuple has already
|
||||
// been verified and is in the cache, this skips the work of verifying the
|
||||
// signature (which is slow) and returns the already-known result.
|
||||
mozilla::pkix::Result VerifySignedDataWithCache(
|
||||
mozilla::pkix::der::PublicKeyAlgorithm publicKeyAlg,
|
||||
mozilla::glean::impl::DenominatorMetric telemetryDenominator,
|
||||
mozilla::glean::impl::NumeratorMetric telemetryNumerator,
|
||||
mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm,
|
||||
mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo,
|
||||
SignatureCache* signatureCache, void* pinArg);
|
||||
|
||||
} // namespace psm
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace psm {
|
|||
|
||||
NSSCertDBTrustDomain::NSSCertDBTrustDomain(
|
||||
SECTrustType certDBTrustType, OCSPFetching ocspFetching,
|
||||
OCSPCache& ocspCache,
|
||||
OCSPCache& ocspCache, SignatureCache* signatureCache,
|
||||
/*optional but shouldn't be*/ void* pinArg, TimeDuration ocspTimeoutSoft,
|
||||
TimeDuration ocspTimeoutHard, uint32_t certShortLifetimeInDays,
|
||||
unsigned int minRSABits, ValidityCheckingMode validityCheckingMode,
|
||||
|
@ -83,6 +83,7 @@ NSSCertDBTrustDomain::NSSCertDBTrustDomain(
|
|||
: mCertDBTrustType(certDBTrustType),
|
||||
mOCSPFetching(ocspFetching),
|
||||
mOCSPCache(ocspCache),
|
||||
mSignatureCache(signatureCache),
|
||||
mPinArg(pinArg),
|
||||
mOCSPTimeoutSoft(ocspTimeoutSoft),
|
||||
mOCSPTimeoutHard(ocspTimeoutHard),
|
||||
|
@ -1470,15 +1471,21 @@ Result NSSCertDBTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
|
|||
Result NSSCertDBTrustDomain::VerifyRSAPKCS1SignedData(
|
||||
Input data, DigestAlgorithm digestAlgorithm, Input signature,
|
||||
Input subjectPublicKeyInfo) {
|
||||
return VerifyRSAPKCS1SignedDataNSS(data, digestAlgorithm, signature,
|
||||
subjectPublicKeyInfo, mPinArg);
|
||||
return VerifySignedDataWithCache(
|
||||
der::PublicKeyAlgorithm::RSA_PKCS1,
|
||||
mozilla::glean::cert_signature_cache::total,
|
||||
mozilla::glean::cert_signature_cache::hits, data, digestAlgorithm,
|
||||
signature, subjectPublicKeyInfo, mSignatureCache, mPinArg);
|
||||
}
|
||||
|
||||
Result NSSCertDBTrustDomain::VerifyRSAPSSSignedData(
|
||||
Input data, DigestAlgorithm digestAlgorithm, Input signature,
|
||||
Input subjectPublicKeyInfo) {
|
||||
return VerifyRSAPSSSignedDataNSS(data, digestAlgorithm, signature,
|
||||
subjectPublicKeyInfo, mPinArg);
|
||||
return VerifySignedDataWithCache(
|
||||
der::PublicKeyAlgorithm::RSA_PSS,
|
||||
mozilla::glean::cert_signature_cache::total,
|
||||
mozilla::glean::cert_signature_cache::hits, data, digestAlgorithm,
|
||||
signature, subjectPublicKeyInfo, mSignatureCache, mPinArg);
|
||||
}
|
||||
|
||||
Result NSSCertDBTrustDomain::CheckECDSACurveIsAcceptable(
|
||||
|
@ -1496,8 +1503,11 @@ Result NSSCertDBTrustDomain::CheckECDSACurveIsAcceptable(
|
|||
Result NSSCertDBTrustDomain::VerifyECDSASignedData(
|
||||
Input data, DigestAlgorithm digestAlgorithm, Input signature,
|
||||
Input subjectPublicKeyInfo) {
|
||||
return VerifyECDSASignedDataNSS(data, digestAlgorithm, signature,
|
||||
subjectPublicKeyInfo, mPinArg);
|
||||
return VerifySignedDataWithCache(
|
||||
der::PublicKeyAlgorithm::ECDSA,
|
||||
mozilla::glean::cert_signature_cache::total,
|
||||
mozilla::glean::cert_signature_cache::hits, data, digestAlgorithm,
|
||||
signature, subjectPublicKeyInfo, mSignatureCache, mPinArg);
|
||||
}
|
||||
|
||||
Result NSSCertDBTrustDomain::CheckValidityIsAcceptable(
|
||||
|
|
|
@ -145,7 +145,8 @@ class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain {
|
|||
|
||||
NSSCertDBTrustDomain(
|
||||
SECTrustType certDBTrustType, OCSPFetching ocspFetching,
|
||||
OCSPCache& ocspCache, void* pinArg, mozilla::TimeDuration ocspTimeoutSoft,
|
||||
OCSPCache& ocspCache, SignatureCache* signatureCache, void* pinArg,
|
||||
mozilla::TimeDuration ocspTimeoutSoft,
|
||||
mozilla::TimeDuration ocspTimeoutHard, uint32_t certShortLifetimeInDays,
|
||||
unsigned int minRSABits, ValidityCheckingMode validityCheckingMode,
|
||||
NetscapeStepUpPolicy netscapeStepUpPolicy, CRLiteMode crliteMode,
|
||||
|
@ -299,8 +300,9 @@ class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain {
|
|||
|
||||
const SECTrustType mCertDBTrustType;
|
||||
const OCSPFetching mOCSPFetching;
|
||||
OCSPCache& mOCSPCache; // non-owning!
|
||||
void* mPinArg; // non-owning!
|
||||
OCSPCache& mOCSPCache; // non-owning!
|
||||
SignatureCache* mSignatureCache; // non-owning!
|
||||
void* mPinArg; // non-owning!
|
||||
const mozilla::TimeDuration mOCSPTimeoutSoft;
|
||||
const mozilla::TimeDuration mOCSPTimeoutHard;
|
||||
const uint32_t mCertShortLifetimeInDays;
|
||||
|
|
|
@ -98,3 +98,28 @@ cert_verifier:
|
|||
- jschanck@mozilla.com
|
||||
expires: 144
|
||||
unit: trust objects
|
||||
|
||||
cert_signature_cache:
|
||||
hits:
|
||||
type: rate
|
||||
description: >
|
||||
How often a certificate signature to be verified is in the cache already.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1918279
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1918279
|
||||
notification_emails:
|
||||
- dkeeler@mozilla.com
|
||||
expires: never
|
||||
denominator_metric: cert_signature_cache.total
|
||||
total:
|
||||
type: counter
|
||||
description: >
|
||||
How many certificate signature verifications are performed.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1918279
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1918279
|
||||
notification_emails:
|
||||
- dkeeler@mozilla.com
|
||||
expires: never
|
||||
|
|
|
@ -30,6 +30,7 @@ LOCAL_INCLUDES += [
|
|||
|
||||
DIRS += [
|
||||
"../ct",
|
||||
"signature_cache",
|
||||
]
|
||||
|
||||
TEST_DIRS += [
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "signature_cache"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
hashlink = "0.9"
|
|
@ -0,0 +1,18 @@
|
|||
header = """/* 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/. */
|
||||
"""
|
||||
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
|
||||
|
||||
include_guard = "signature_cache_ffi_h"
|
||||
include_version = true
|
||||
braces = "SameLine"
|
||||
line_length = 100
|
||||
tab_width = 2
|
||||
language = "C++"
|
||||
namespaces = []
|
||||
includes = []
|
||||
|
||||
[export]
|
||||
exclude = []
|
||||
item_types = ["opaque", "functions"]
|
|
@ -0,0 +1,16 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
||||
|
||||
if CONFIG["COMPILE_ENVIRONMENT"]:
|
||||
CbindgenHeader(
|
||||
"signature_cache_ffi.h", inputs=["/security/certverifier/signature_cache"]
|
||||
)
|
||||
|
||||
EXPORTS += [
|
||||
"!signature_cache_ffi.h",
|
||||
]
|
|
@ -0,0 +1,113 @@
|
|||
/* 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 hashlink;
|
||||
|
||||
use hashlink::LruCache;
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
const SHA512_LENGTH_IN_BYTES: usize = 64;
|
||||
|
||||
/// SignatureCache is a simple least-recently-used cache. The input is a sha512
|
||||
/// hash representing the parameters defining a signature. A hit in the cache
|
||||
/// indicates that the signature previously verified successfully.
|
||||
pub struct SignatureCache {
|
||||
cache: Mutex<LruCache<[u8; SHA512_LENGTH_IN_BYTES], ()>>,
|
||||
}
|
||||
|
||||
impl SignatureCache {
|
||||
/// Make a new SignatureCache with the specified number of slots.
|
||||
fn new(capacity: u16) -> SignatureCache {
|
||||
SignatureCache {
|
||||
cache: Mutex::new(LruCache::new(capacity as usize)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up a signature hash in the cache. Returns true if the signature
|
||||
/// previously verified correctly, and false otherwise.
|
||||
fn get(&mut self, sha512_hash: &[u8; SHA512_LENGTH_IN_BYTES]) -> bool {
|
||||
let Ok(mut cache) = self.cache.lock() else {
|
||||
return false;
|
||||
};
|
||||
cache.get(sha512_hash).is_some()
|
||||
}
|
||||
|
||||
/// Insert a signature hash into the cache.
|
||||
fn insert(&self, sha512_hash: [u8; SHA512_LENGTH_IN_BYTES]) {
|
||||
let Ok(mut cache) = self.cache.lock() else {
|
||||
return;
|
||||
};
|
||||
let _ = cache.insert(sha512_hash, ());
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new SignatureCache.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn signature_cache_new(capacity: u16) -> *mut SignatureCache {
|
||||
// This pattern returns a SignatureCache that will be owned by the caller.
|
||||
// That is, the SignatureCache will live until `signature_cache_free` is
|
||||
// called on it.
|
||||
Box::into_raw(Box::new(SignatureCache::new(capacity)))
|
||||
}
|
||||
|
||||
/// Free a SignatureCache.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must only be called with a null pointer or a pointer returned from
|
||||
/// `signature_cache_new`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn signature_cache_free(signature_cache: *mut SignatureCache) {
|
||||
if signature_cache.is_null() {
|
||||
return;
|
||||
}
|
||||
// This takes a SignatureCache that was created by calling
|
||||
// `signature_cache_new` and ensures its resources are destroyed.
|
||||
let _ = Box::from_raw(signature_cache);
|
||||
}
|
||||
|
||||
/// Look up a signature parameter hash in a SignatureCache.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must only be called with a pointer returned from
|
||||
/// `signature_cache_new` and a pointer to `SHA512_LENGTH_IN_BYTES` bytes.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn signature_cache_get(
|
||||
signature_cache: *mut SignatureCache,
|
||||
sha512_hash: *const u8,
|
||||
) -> bool {
|
||||
if signature_cache.is_null() || sha512_hash.is_null() {
|
||||
return false;
|
||||
}
|
||||
let sha512_hash = std::slice::from_raw_parts(sha512_hash, SHA512_LENGTH_IN_BYTES);
|
||||
let Ok(sha512_hash) = sha512_hash.try_into() else {
|
||||
return false;
|
||||
};
|
||||
let signature_cache = &mut *signature_cache;
|
||||
signature_cache.get(&sha512_hash)
|
||||
}
|
||||
|
||||
/// Add a signature parameter hash to a SignatureCache.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function must only be called with a pointer returned from
|
||||
/// `signature_cache_new` and a pointer to `SHA512_LENGTH_IN_BYTES` bytes.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn signature_cache_insert(
|
||||
signature_cache: *mut SignatureCache,
|
||||
sha512_hash: *const u8,
|
||||
) {
|
||||
if signature_cache.is_null() || sha512_hash.is_null() {
|
||||
return;
|
||||
}
|
||||
let sha512_hash = std::slice::from_raw_parts(sha512_hash, SHA512_LENGTH_IN_BYTES);
|
||||
let Ok(sha512_hash) = sha512_hash.try_into() else {
|
||||
return;
|
||||
};
|
||||
let signature_cache = &mut *signature_cache;
|
||||
signature_cache.insert(sha512_hash);
|
||||
}
|
|
@ -9,15 +9,17 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "CTSerialization.h"
|
||||
#include "CertVerifier.h"
|
||||
#include "hasht.h"
|
||||
#include "mozpkix/Result.h"
|
||||
#include "mozpkix/pkixnss.h"
|
||||
#include "mozpkix/pkixutil.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
// A TrustDomain used to extract the SCT log signature parameters
|
||||
// given its subjectPublicKeyInfo.
|
||||
// Only RSASSA-PKCS1v15 with SHA-256 and ECDSA (using the NIST P-256 curve)
|
||||
|
@ -29,75 +31,80 @@ class SignatureParamsTrustDomain final : public TrustDomain {
|
|||
SignatureParamsTrustDomain()
|
||||
: mSignatureAlgorithm(DigitallySigned::SignatureAlgorithm::Anonymous) {}
|
||||
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
|
||||
TrustLevel&) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
|
||||
TrustLevel&) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker&, Time) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result FindIssuer(Input, IssuerChecker&, Time) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
const Input*, const Input*, const Input*) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
const Input*, const Input*,
|
||||
const Input*) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result IsChainValid(const DERArray&, Time,
|
||||
const CertPolicyId&) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input, DigestAlgorithm, uint8_t*, size_t) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result DigestBuf(Input, DigestAlgorithm, uint8_t*, size_t) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA,
|
||||
Time) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA,
|
||||
Time) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve curve) override {
|
||||
pkix::Result CheckECDSACurveIsAcceptable(EndEntityOrCA,
|
||||
NamedCurve curve) override {
|
||||
assert(mSignatureAlgorithm ==
|
||||
DigitallySigned::SignatureAlgorithm::Anonymous);
|
||||
if (curve != NamedCurve::secp256r1) {
|
||||
return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
|
||||
return pkix::Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
|
||||
}
|
||||
mSignatureAlgorithm = DigitallySigned::SignatureAlgorithm::ECDSA;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedData(Input, DigestAlgorithm, Input, Input) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result VerifyECDSASignedData(Input, DigestAlgorithm, Input,
|
||||
Input) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(
|
||||
pkix::Result CheckRSAPublicKeyModulusSizeInBits(
|
||||
EndEntityOrCA, unsigned int modulusSizeInBits) override {
|
||||
assert(mSignatureAlgorithm ==
|
||||
DigitallySigned::SignatureAlgorithm::Anonymous);
|
||||
// Require RSA keys of at least 2048 bits. See RFC 6962, Section 2.1.4.
|
||||
if (modulusSizeInBits < 2048) {
|
||||
return Result::ERROR_INADEQUATE_KEY_SIZE;
|
||||
return pkix::Result::ERROR_INADEQUATE_KEY_SIZE;
|
||||
}
|
||||
mSignatureAlgorithm = DigitallySigned::SignatureAlgorithm::RSA;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedData(Input, DigestAlgorithm, Input,
|
||||
Input) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result VerifyRSAPKCS1SignedData(Input, DigestAlgorithm, Input,
|
||||
Input) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result VerifyRSAPSSSignedData(Input, DigestAlgorithm, Input, Input) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result VerifyRSAPSSSignedData(Input, DigestAlgorithm, Input,
|
||||
Input) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA,
|
||||
KeyPurposeId) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA,
|
||||
KeyPurposeId) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result NetscapeStepUpMatchesServerAuth(Time, bool&) override {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
pkix::Result NetscapeStepUpMatchesServerAuth(Time, bool&) override {
|
||||
return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override {}
|
||||
|
@ -112,10 +119,10 @@ CTLogVerifier::CTLogVerifier(CTLogOperatorId operatorId, CTLogState state,
|
|||
mState(state),
|
||||
mTimestamp(timestamp) {}
|
||||
|
||||
Result CTLogVerifier::Init(Input subjectPublicKeyInfo) {
|
||||
pkix::Result CTLogVerifier::Init(Input subjectPublicKeyInfo) {
|
||||
SignatureParamsTrustDomain trustDomain;
|
||||
Result rv = CheckSubjectPublicKeyInfo(subjectPublicKeyInfo, trustDomain,
|
||||
EndEntityOrCA::MustBeEndEntity);
|
||||
pkix::Result rv = CheckSubjectPublicKeyInfo(subjectPublicKeyInfo, trustDomain,
|
||||
EndEntityOrCA::MustBeEndEntity);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -159,17 +166,18 @@ Result CTLogVerifier::Init(Input subjectPublicKeyInfo) {
|
|||
return Success;
|
||||
}
|
||||
|
||||
Result CTLogVerifier::Verify(const LogEntry& entry,
|
||||
const SignedCertificateTimestamp& sct) {
|
||||
if (mKeyId.empty() || sct.logId != mKeyId) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
pkix::Result CTLogVerifier::Verify(const LogEntry& entry,
|
||||
const SignedCertificateTimestamp& sct,
|
||||
SignatureCache* signatureCache) {
|
||||
if (mKeyId.empty() || sct.logId != mKeyId || !signatureCache) {
|
||||
return pkix::Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
if (!SignatureParametersMatch(sct.signature)) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
return pkix::Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
Buffer serializedLogEntry;
|
||||
Result rv = EncodeLogEntry(entry, serializedLogEntry);
|
||||
pkix::Result rv = EncodeLogEntry(entry, serializedLogEntry);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -196,7 +204,8 @@ Result CTLogVerifier::Verify(const LogEntry& entry,
|
|||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return VerifySignature(serializedData, sct.signature.signatureData);
|
||||
return VerifySignature(serializedData, sct.signature.signatureData,
|
||||
signatureCache);
|
||||
}
|
||||
|
||||
bool CTLogVerifier::SignatureParametersMatch(const DigitallySigned& signature) {
|
||||
|
@ -204,50 +213,28 @@ bool CTLogVerifier::SignatureParametersMatch(const DigitallySigned& signature) {
|
|||
DigitallySigned::HashAlgorithm::SHA256, mSignatureAlgorithm);
|
||||
}
|
||||
|
||||
static Result FasterVerifyECDSASignedDataNSS(Input data, Input signature,
|
||||
UniqueSECKEYPublicKey& pubkey) {
|
||||
assert(pubkey);
|
||||
if (!pubkey) {
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
// The signature is encoded as a DER SEQUENCE of two INTEGERs. PK11_Verify
|
||||
// expects the signature as only the two integers r and s (so no encoding -
|
||||
// just two series of bytes each half as long as SECKEY_SignatureLen(pubkey)).
|
||||
// DSAU_DecodeDerSigToLen converts from the former format to the latter.
|
||||
SECItem derSignatureSECItem(UnsafeMapInputToSECItem(signature));
|
||||
size_t signatureLen = SECKEY_SignatureLen(pubkey.get());
|
||||
if (signatureLen == 0) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
UniqueSECItem signatureSECItem(
|
||||
DSAU_DecodeDerSigToLen(&derSignatureSECItem, signatureLen));
|
||||
if (!signatureSECItem) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
SECItem dataSECItem(UnsafeMapInputToSECItem(data));
|
||||
SECStatus srv =
|
||||
PK11_VerifyWithMechanism(pubkey.get(), CKM_ECDSA_SHA256, nullptr,
|
||||
signatureSECItem.get(), &dataSECItem, nullptr);
|
||||
if (srv != SECSuccess) {
|
||||
return MapPRErrorCodeToResult(PR_GetError());
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CTLogVerifier::VerifySignature(Input data, Input signature) {
|
||||
pkix::Result CTLogVerifier::VerifySignature(Input data, Input signature,
|
||||
SignatureCache* signatureCache) {
|
||||
Input spki;
|
||||
Result rv = BufferToInput(mSubjectPublicKeyInfo, spki);
|
||||
pkix::Result rv = BufferToInput(mSubjectPublicKeyInfo, spki);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
switch (mSignatureAlgorithm) {
|
||||
case DigitallySigned::SignatureAlgorithm::RSA:
|
||||
rv = VerifyRSAPKCS1SignedDataNSS(data, DigestAlgorithm::sha256, signature,
|
||||
spki, nullptr);
|
||||
rv = psm::VerifySignedDataWithCache(
|
||||
der::PublicKeyAlgorithm::RSA_PKCS1,
|
||||
mozilla::glean::sct_signature_cache::total,
|
||||
mozilla::glean::sct_signature_cache::hits, data,
|
||||
DigestAlgorithm::sha256, signature, spki, signatureCache, nullptr);
|
||||
break;
|
||||
case DigitallySigned::SignatureAlgorithm::ECDSA:
|
||||
rv = FasterVerifyECDSASignedDataNSS(data, signature, mPublicECKey);
|
||||
rv = psm::VerifySignedDataWithCache(
|
||||
der::PublicKeyAlgorithm::ECDSA,
|
||||
mozilla::glean::sct_signature_cache::total,
|
||||
mozilla::glean::sct_signature_cache::hits, data,
|
||||
DigestAlgorithm::sha256, signature, spki, signatureCache, nullptr);
|
||||
break;
|
||||
// We do not expect new values added to this enum any time soon,
|
||||
// so just listing all the available ones seems to be the easiest way
|
||||
|
@ -257,22 +244,23 @@ Result CTLogVerifier::VerifySignature(Input data, Input signature) {
|
|||
case DigitallySigned::SignatureAlgorithm::DSA:
|
||||
default:
|
||||
assert(false);
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
return pkix::Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
if (rv != Success) {
|
||||
if (IsFatalError(rv)) {
|
||||
return rv;
|
||||
}
|
||||
// If the error is non-fatal, we assume the signature was invalid.
|
||||
return Result::ERROR_BAD_SIGNATURE;
|
||||
return pkix::Result::ERROR_BAD_SIGNATURE;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result CTLogVerifier::VerifySignature(const Buffer& data,
|
||||
const Buffer& signature) {
|
||||
pkix::Result CTLogVerifier::VerifySignature(const Buffer& data,
|
||||
const Buffer& signature,
|
||||
SignatureCache* signatureCache) {
|
||||
Input dataInput;
|
||||
Result rv = BufferToInput(data, dataInput);
|
||||
pkix::Result rv = BufferToInput(data, dataInput);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -281,7 +269,7 @@ Result CTLogVerifier::VerifySignature(const Buffer& data,
|
|||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return VerifySignature(dataInput, signatureInput);
|
||||
return VerifySignature(dataInput, signatureInput, signatureCache);
|
||||
}
|
||||
|
||||
} // namespace ct
|
||||
|
|
|
@ -13,9 +13,11 @@
|
|||
#include "CTLog.h"
|
||||
#include "CTUtils.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
#include "mozilla/glean/GleanMetrics.h"
|
||||
#include "mozpkix/Input.h"
|
||||
#include "mozpkix/Result.h"
|
||||
#include "mozpkix/pkix.h"
|
||||
#include "signature_cache_ffi.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
@ -52,7 +54,8 @@ class CTLogVerifier {
|
|||
// Verifies that |sct| contains a valid signature for |entry|.
|
||||
// |sct| must be signed by the verifier's log.
|
||||
pkix::Result Verify(const LogEntry& entry,
|
||||
const SignedCertificateTimestamp& sct);
|
||||
const SignedCertificateTimestamp& sct,
|
||||
SignatureCache* signatureCache);
|
||||
|
||||
// Returns true if the signature and hash algorithms in |signature|
|
||||
// match those of the log.
|
||||
|
@ -64,8 +67,10 @@ class CTLogVerifier {
|
|||
// DigitallySigned struct encoding).
|
||||
// Returns Success if passed verification, ERROR_BAD_SIGNATURE if failed
|
||||
// verification, or other result on error.
|
||||
pkix::Result VerifySignature(pkix::Input data, pkix::Input signature);
|
||||
pkix::Result VerifySignature(const Buffer& data, const Buffer& signature);
|
||||
pkix::Result VerifySignature(pkix::Input data, pkix::Input signature,
|
||||
SignatureCache* signatureCache);
|
||||
pkix::Result VerifySignature(const Buffer& data, const Buffer& signature,
|
||||
SignatureCache* signatureCache);
|
||||
|
||||
// mPublicECKey works around an architectural deficiency in NSS. In the case
|
||||
// of EC, if we don't create, import, and cache this key, NSS will import and
|
||||
|
|
|
@ -8,25 +8,32 @@
|
|||
|
||||
#include "CTObjectsExtractor.h"
|
||||
#include "CTSerialization.h"
|
||||
#include "mozilla/StaticPrefs_security.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
MultiLogCTVerifier::MultiLogCTVerifier()
|
||||
: mSignatureCache(signature_cache_new(
|
||||
StaticPrefs::security_pki_sct_signature_cache_size()),
|
||||
signature_cache_free) {}
|
||||
|
||||
void MultiLogCTVerifier::AddLog(CTLogVerifier&& log) {
|
||||
mLogs.push_back(std::move(log));
|
||||
}
|
||||
|
||||
Result MultiLogCTVerifier::Verify(Input cert, Input issuerSubjectPublicKeyInfo,
|
||||
Input sctListFromCert,
|
||||
Input sctListFromOCSPResponse,
|
||||
Input sctListFromTLSExtension, Time time,
|
||||
CTVerifyResult& result) {
|
||||
pkix::Result MultiLogCTVerifier::Verify(Input cert,
|
||||
Input issuerSubjectPublicKeyInfo,
|
||||
Input sctListFromCert,
|
||||
Input sctListFromOCSPResponse,
|
||||
Input sctListFromTLSExtension,
|
||||
Time time, CTVerifyResult& result) {
|
||||
assert(cert.GetLength() > 0);
|
||||
result.Reset();
|
||||
|
||||
Result rv;
|
||||
pkix::Result rv;
|
||||
|
||||
// Verify embedded SCTs
|
||||
if (issuerSubjectPublicKeyInfo.GetLength() > 0 &&
|
||||
|
@ -72,7 +79,7 @@ void DecodeSCTs(Input encodedSctList,
|
|||
decodedSCTs.clear();
|
||||
|
||||
Reader listReader;
|
||||
Result rv = DecodeSCTList(encodedSctList, listReader);
|
||||
pkix::Result rv = DecodeSCTList(encodedSctList, listReader);
|
||||
if (rv != Success) {
|
||||
decodingErrors++;
|
||||
return;
|
||||
|
@ -97,14 +104,14 @@ void DecodeSCTs(Input encodedSctList,
|
|||
}
|
||||
}
|
||||
|
||||
Result MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
|
||||
const LogEntry& expectedEntry,
|
||||
SCTOrigin origin, Time time,
|
||||
CTVerifyResult& result) {
|
||||
pkix::Result MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
|
||||
const LogEntry& expectedEntry,
|
||||
SCTOrigin origin, Time time,
|
||||
CTVerifyResult& result) {
|
||||
std::vector<SignedCertificateTimestamp> decodedSCTs;
|
||||
DecodeSCTs(encodedSctList, decodedSCTs, result.decodingErrors);
|
||||
for (auto sct : decodedSCTs) {
|
||||
Result rv =
|
||||
pkix::Result rv =
|
||||
VerifySingleSCT(std::move(sct), expectedEntry, origin, time, result);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
|
@ -113,10 +120,9 @@ Result MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
|
|||
return Success;
|
||||
}
|
||||
|
||||
Result MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
|
||||
const LogEntry& expectedEntry,
|
||||
SCTOrigin origin, Time time,
|
||||
CTVerifyResult& result) {
|
||||
pkix::Result MultiLogCTVerifier::VerifySingleSCT(
|
||||
SignedCertificateTimestamp&& sct, const LogEntry& expectedEntry,
|
||||
SCTOrigin origin, Time time, CTVerifyResult& result) {
|
||||
switch (origin) {
|
||||
case SCTOrigin::Embedded:
|
||||
result.embeddedSCTs++;
|
||||
|
@ -149,9 +155,10 @@ Result MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
|
|||
return Success;
|
||||
}
|
||||
|
||||
Result rv = matchingLog->Verify(expectedEntry, sct);
|
||||
pkix::Result rv =
|
||||
matchingLog->Verify(expectedEntry, sct, mSignatureCache.get());
|
||||
if (rv != Success) {
|
||||
if (rv == Result::ERROR_BAD_SIGNATURE) {
|
||||
if (rv == pkix::Result::ERROR_BAD_SIGNATURE) {
|
||||
result.sctsWithInvalidSignatures++;
|
||||
return Success;
|
||||
}
|
||||
|
|
|
@ -11,10 +11,11 @@
|
|||
|
||||
#include "CTLogVerifier.h"
|
||||
#include "CTVerifyResult.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
#include "mozpkix/Input.h"
|
||||
#include "mozpkix/Result.h"
|
||||
#include "mozpkix/Time.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
#include "signature_cache_ffi.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
@ -27,6 +28,8 @@ void DecodeSCTs(pkix::Input encodedSctList,
|
|||
// Timestamps from multiple logs.
|
||||
class MultiLogCTVerifier {
|
||||
public:
|
||||
MultiLogCTVerifier();
|
||||
|
||||
// Adds a new log to the list of known logs to verify against.
|
||||
void AddLog(CTLogVerifier&& log);
|
||||
|
||||
|
@ -80,6 +83,11 @@ class MultiLogCTVerifier {
|
|||
|
||||
// The list of known logs.
|
||||
std::vector<CTLogVerifier> mLogs;
|
||||
|
||||
// If many connections are made to a site using a particular certificate,
|
||||
// this cache will speed up verifications after the first one by saving the
|
||||
// results of verifying the signatures on the SCTs for that certificate.
|
||||
UniquePtr<SignatureCache, decltype(&signature_cache_free)> mSignatureCache;
|
||||
};
|
||||
|
||||
} // namespace ct
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# 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/.
|
||||
# Adding a new metric? We have docs for that!
|
||||
# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
|
||||
|
||||
---
|
||||
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
|
||||
$tags:
|
||||
- 'Core :: Security: PSM'
|
||||
|
||||
|
||||
sct_signature_cache:
|
||||
hits:
|
||||
type: rate
|
||||
description: >
|
||||
How often an SCT signature to be verified is in the cache already.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1918279
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1918279
|
||||
notification_emails:
|
||||
- dkeeler@mozilla.com
|
||||
expires: never
|
||||
denominator_metric: sct_signature_cache.total
|
||||
total:
|
||||
type: counter
|
||||
description: >
|
||||
How many SCT signature verifications are performed.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1918279
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1918279
|
||||
notification_emails:
|
||||
- dkeeler@mozilla.com
|
||||
expires: never
|
|
@ -7,6 +7,7 @@
|
|||
#include "CTLogVerifier.h"
|
||||
#include "CTTestUtils.h"
|
||||
#include "nss.h"
|
||||
#include "signature_cache_ffi.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
|
@ -23,12 +24,18 @@ class CTLogVerifierTest : public ::testing::Test {
|
|||
abort();
|
||||
}
|
||||
|
||||
mSignatureCache = signature_cache_new(1);
|
||||
|
||||
ASSERT_EQ(Success, mLog.Init(InputForBuffer(GetTestPublicKey())));
|
||||
ASSERT_EQ(GetTestPublicKeyId(), mLog.keyId());
|
||||
}
|
||||
|
||||
void TearDown() override { signature_cache_free(mSignatureCache); }
|
||||
|
||||
protected:
|
||||
CTLogVerifier mLog = CTLogVerifier(-1, CTLogState::Admissible, 0);
|
||||
// For some reason, the templating makes it impossible to use UniquePtr here.
|
||||
SignatureCache* mSignatureCache;
|
||||
};
|
||||
|
||||
TEST_F(CTLogVerifierTest, VerifiesCertSCT) {
|
||||
|
@ -38,7 +45,7 @@ TEST_F(CTLogVerifierTest, VerifiesCertSCT) {
|
|||
SignedCertificateTimestamp certSct;
|
||||
GetX509CertSCT(certSct);
|
||||
|
||||
EXPECT_EQ(Success, mLog.Verify(certEntry, certSct));
|
||||
EXPECT_EQ(Success, mLog.Verify(certEntry, certSct, mSignatureCache));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, VerifiesPrecertSCT) {
|
||||
|
@ -48,7 +55,7 @@ TEST_F(CTLogVerifierTest, VerifiesPrecertSCT) {
|
|||
SignedCertificateTimestamp precertSct;
|
||||
GetPrecertSCT(precertSct);
|
||||
|
||||
EXPECT_EQ(Success, mLog.Verify(precertEntry, precertSct));
|
||||
EXPECT_EQ(Success, mLog.Verify(precertEntry, precertSct, mSignatureCache));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, FailsInvalidTimestamp) {
|
||||
|
@ -61,7 +68,8 @@ TEST_F(CTLogVerifierTest, FailsInvalidTimestamp) {
|
|||
// Mangle the timestamp, so that it should fail signature validation.
|
||||
certSct.timestamp = 0;
|
||||
|
||||
EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
|
||||
EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE,
|
||||
mLog.Verify(certEntry, certSct, mSignatureCache));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, FailsInvalidSignature) {
|
||||
|
@ -73,7 +81,8 @@ TEST_F(CTLogVerifierTest, FailsInvalidSignature) {
|
|||
SignedCertificateTimestamp certSct;
|
||||
GetX509CertSCT(certSct);
|
||||
certSct.signature.signatureData[20] ^= '\xFF';
|
||||
EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
|
||||
EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE,
|
||||
mLog.Verify(certEntry, certSct, mSignatureCache));
|
||||
|
||||
// Mangle the encoding of the signature, making the underlying implementation
|
||||
// return ERROR_BAD_DER. We still expect the verifier to return
|
||||
|
@ -82,7 +91,7 @@ TEST_F(CTLogVerifierTest, FailsInvalidSignature) {
|
|||
GetX509CertSCT(certSct2);
|
||||
certSct2.signature.signatureData[0] ^= '\xFF';
|
||||
EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE,
|
||||
mLog.Verify(certEntry, certSct2));
|
||||
mLog.Verify(certEntry, certSct2, mSignatureCache));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, FailsInvalidLogID) {
|
||||
|
@ -97,7 +106,7 @@ TEST_F(CTLogVerifierTest, FailsInvalidLogID) {
|
|||
certSct.logId.push_back('\x0');
|
||||
|
||||
EXPECT_EQ(pkix::Result::FATAL_ERROR_INVALID_ARGS,
|
||||
mLog.Verify(certEntry, certSct));
|
||||
mLog.Verify(certEntry, certSct, mSignatureCache));
|
||||
}
|
||||
|
||||
// Test that excess data after the public key is rejected.
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "CTTestUtils.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "nss.h"
|
||||
#include "signature_cache_ffi.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace ct {
|
||||
|
@ -73,7 +74,9 @@ TEST_F(CTObjectsExtractorTest, ComplementarySCTVerifies) {
|
|||
|
||||
LogEntry entry;
|
||||
GetX509LogEntry(InputForBuffer(mTestCert), entry);
|
||||
EXPECT_EQ(Success, mLog.Verify(entry, sct));
|
||||
SignatureCache* signatureCache(signature_cache_new(1));
|
||||
EXPECT_EQ(Success, mLog.Verify(entry, sct, signatureCache));
|
||||
signature_cache_free(signatureCache);
|
||||
}
|
||||
|
||||
} // namespace ct
|
||||
|
|
|
@ -42,6 +42,7 @@ gecko_metrics = [
|
|||
"parser/html/metrics.yaml",
|
||||
"parser/htmlparser/metrics.yaml",
|
||||
"security/certverifier/metrics.yaml",
|
||||
"security/ct/metrics.yaml",
|
||||
"security/manager/ssl/metrics.yaml",
|
||||
"services/common/metrics.yaml",
|
||||
"toolkit/components/antitracking/bouncetrackingprotection/metrics.yaml",
|
||||
|
|
|
@ -37,6 +37,7 @@ log = {version = "0.4", features = ["release_max_level_info"]}
|
|||
cose-c = { version = "0.1.5" }
|
||||
jsrust_shared = { path = "../../../../js/src/rust/shared" }
|
||||
cascade_bloom_filter = { path = "../../../components/cascade_bloom_filter" }
|
||||
signature_cache = { path = "../../../../security/certverifier/signature_cache" }
|
||||
cert_storage = { path = "../../../../security/manager/ssl/cert_storage" }
|
||||
crypto_hash = { path = "../../../../security/manager/ssl/crypto_hash" }
|
||||
data_storage = { path = "../../../../security/manager/ssl/data_storage" }
|
||||
|
|
|
@ -46,6 +46,7 @@ extern crate processtools;
|
|||
#[cfg(feature = "gecko_profiler")]
|
||||
extern crate profiler_helper;
|
||||
extern crate rsdparsa_capi;
|
||||
extern crate signature_cache;
|
||||
extern crate static_prefs;
|
||||
extern crate storage;
|
||||
extern crate webrender_bindings;
|
||||
|
|
Загрузка…
Ссылка в новой задаче