diff options
Diffstat (limited to 'security/manager/ssl/RootCertificateTelemetryUtils.cpp')
-rw-r--r-- | security/manager/ssl/RootCertificateTelemetryUtils.cpp | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/security/manager/ssl/RootCertificateTelemetryUtils.cpp b/security/manager/ssl/RootCertificateTelemetryUtils.cpp new file mode 100644 index 0000000000..d6bd54d741 --- /dev/null +++ b/security/manager/ssl/RootCertificateTelemetryUtils.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RootCertificateTelemetryUtils.h" + +#include "RootHashes.inc" // Note: Generated by genRootCAHashes.js +#include "ScopedNSSTypes.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Logging.h" +#include "nsINSSComponent.h" +#include "nsNSSCertHelper.h" +#include "nsServiceManagerUtils.h" +#include "pk11pub.h" + +namespace mozilla { +namespace psm { + +mozilla::LazyLogModule gPublicKeyPinningTelemetryLog( + "PublicKeyPinningTelemetryService"); + +// Used in the BinarySearch method, this does a memcmp between the pointer +// provided to its construtor and whatever the binary search is looking for. +// +// This implementation assumes everything to be of HASH_LEN, so it should not +// be used generically. +class BinaryHashSearchArrayComparator { + public: + explicit BinaryHashSearchArrayComparator(const uint8_t* aTarget, size_t len) + : mTarget(aTarget) { + MOZ_ASSERT(len == HASH_LEN, "Hashes should be of the same length."); + } + + int operator()(const CertAuthorityHash val) const { + return memcmp(mTarget, val.hash, HASH_LEN); + } + + private: + const uint8_t* mTarget; +}; + +// Perform a hash of the provided cert, then search in the RootHashes.inc data +// structure for a matching bin number. +// If no matching root is found, this may be a CA from the softoken (cert9.db), +// it may be a CA from an external PKCS#11 token, or it may be a CA from OS +// storage (Enterprise Root). +// See also the constants in RootCertificateTelemetryUtils.h. +int32_t RootCABinNumber(Span<const uint8_t> cert) { + nsTArray<uint8_t> digestArray; + + // Compute SHA256 hash of the certificate + nsresult rv = Digest::DigestBuf(SEC_OID_SHA256, cert, digestArray); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ROOT_CERTIFICATE_HASH_FAILURE; + } + + // Compare against list of stored hashes + size_t idx; + + MOZ_LOG(gPublicKeyPinningTelemetryLog, LogLevel::Debug, + ("pkpinTelem: First bytes %02x %02x %02x %02x\n", + digestArray.ElementAt(0), digestArray.ElementAt(1), + digestArray.ElementAt(2), digestArray.ElementAt(3))); + + if (mozilla::BinarySearchIf(ROOT_TABLE, 0, ArrayLength(ROOT_TABLE), + BinaryHashSearchArrayComparator( + digestArray.Elements(), digestArray.Length()), + &idx)) { + MOZ_LOG(gPublicKeyPinningTelemetryLog, LogLevel::Debug, + ("pkpinTelem: Telemetry index was %zu, bin is %d\n", idx, + ROOT_TABLE[idx].binNumber)); + return (int32_t)ROOT_TABLE[idx].binNumber; + } + + // Didn't find this certificate in the built-in list. It may be an enterprise + // root (gathered from the OS) or it may be from the softoken or an external + // PKCS#11 token. + nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID)); + if (!component) { + return ROOT_CERTIFICATE_UNKNOWN; + } + nsTArray<nsTArray<uint8_t>> enterpriseRoots; + rv = component->GetEnterpriseRoots(enterpriseRoots); + if (NS_FAILED(rv)) { + return ROOT_CERTIFICATE_UNKNOWN; + } + for (const auto& enterpriseRoot : enterpriseRoots) { + if (enterpriseRoot.Length() == cert.size() && + memcmp(enterpriseRoot.Elements(), cert.data(), + enterpriseRoot.Length()) == 0) { + return ROOT_CERTIFICATE_ENTERPRISE_ROOT; + } + } + + SECItem certItem = {siBuffer, const_cast<uint8_t*>(cert.data()), + static_cast<unsigned int>(cert.size())}; + UniquePK11SlotInfo softokenSlot(PK11_GetInternalKeySlot()); + if (!softokenSlot) { + return ROOT_CERTIFICATE_UNKNOWN; + } + CK_OBJECT_HANDLE softokenCertHandle = + PK11_FindEncodedCertInSlot(softokenSlot.get(), &certItem, nullptr); + if (softokenCertHandle != CK_INVALID_HANDLE) { + return ROOT_CERTIFICATE_SOFTOKEN; + } + // In theory this should never find the certificate in the root module, + // because then it should have already matched our built-in list. This is + // here as a backstop to catch situations where a built-in root was added but + // the built-in telemetry information was not updated. + UniqueSECMODModule rootsModule(SECMOD_FindModule(kRootModuleName)); + AutoSECMODListReadLock secmodLock; + if (!rootsModule || rootsModule->slotCount != 1) { + return ROOT_CERTIFICATE_UNKNOWN; + } + CK_OBJECT_HANDLE builtinCertHandle = + PK11_FindEncodedCertInSlot(rootsModule->slots[0], &certItem, nullptr); + if (builtinCertHandle == CK_INVALID_HANDLE) { + return ROOT_CERTIFICATE_EXTERNAL_TOKEN; + } + + // We have no idea what this is. + return ROOT_CERTIFICATE_UNKNOWN; +} + +// Attempt to increment the appropriate bin in the provided Telemetry probe ID. +// If there was a hash failure, we do nothing. +void AccumulateTelemetryForRootCA(mozilla::Telemetry::HistogramID probe, + const Span<const uint8_t> cert) { + int32_t binId = RootCABinNumber(cert); + + if (binId != ROOT_CERTIFICATE_HASH_FAILURE) { + Accumulate(probe, binId); + } +} + +} // namespace psm +} // namespace mozilla |