summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/RootCertificateTelemetryUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--security/manager/ssl/RootCertificateTelemetryUtils.cpp139
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