summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/RootCertificateTelemetryUtils.cpp
blob: d6bd54d741c657b6e05aeb1ef218de1db5e4dda0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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