Bug 1935611 - Fix libyuv/libpng link failed for loongarch64. r=glandium,tnikkel,ng
[gecko.git] / security / manager / ssl / PublicKeyPinningService.cpp
blobbb1fd8ac496f9d2befbe4c5942ef4c53c39e8dc5
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "PublicKeyPinningService.h"
7 #include "RootCertificateTelemetryUtils.h"
8 #include "mozilla/ArrayUtils.h"
9 #include "mozilla/Base64.h"
10 #include "mozilla/BinarySearch.h"
11 #include "mozilla/Casting.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/Span.h"
14 #include "mozilla/StaticPrefs_security.h"
15 #include "mozilla/Telemetry.h"
16 #include "nsDependentString.h"
17 #include "nsServiceManagerUtils.h"
18 #include "nsSiteSecurityService.h"
19 #include "mozpkix/pkixtypes.h"
20 #include "mozpkix/pkixutil.h"
21 #include "seccomon.h"
22 #include "sechash.h"
24 #include "StaticHPKPins.h" // autogenerated by genHPKPStaticpins.js
26 using namespace mozilla;
27 using namespace mozilla::pkix;
28 using namespace mozilla::psm;
30 LazyLogModule gPublicKeyPinningLog("PublicKeyPinningService");
32 NS_IMPL_ISUPPORTS(PublicKeyPinningService, nsIPublicKeyPinningService)
34 enum class PinningMode : uint32_t {
35 Disabled = 0,
36 AllowUserCAMITM = 1,
37 Strict = 2,
38 EnforceTestMode = 3
41 PinningMode GetPinningMode() {
42 PinningMode pinningMode = static_cast<PinningMode>(
43 StaticPrefs::security_cert_pinning_enforcement_level_DoNotUseDirectly());
44 switch (pinningMode) {
45 case PinningMode::Disabled:
46 return PinningMode::Disabled;
47 case PinningMode::AllowUserCAMITM:
48 return PinningMode::AllowUserCAMITM;
49 case PinningMode::Strict:
50 return PinningMode::Strict;
51 case PinningMode::EnforceTestMode:
52 return PinningMode::EnforceTestMode;
53 default:
54 return PinningMode::Disabled;
58 /**
59 Computes in the location specified by base64Out the SHA256 digest
60 of the DER Encoded subject Public Key Info for the given cert
62 static nsresult GetBase64HashSPKI(const BackCert& cert,
63 nsACString& hashSPKIDigest) {
64 Input derPublicKey = cert.GetSubjectPublicKeyInfo();
66 hashSPKIDigest.Truncate();
67 nsTArray<uint8_t> digestArray;
68 nsresult nsrv =
69 Digest::DigestBuf(SEC_OID_SHA256, derPublicKey.UnsafeGetData(),
70 derPublicKey.GetLength(), digestArray);
71 if (NS_FAILED(nsrv)) {
72 return nsrv;
74 return Base64Encode(nsDependentCSubstring(
75 BitwiseCast<char*, uint8_t*>(digestArray.Elements()),
76 digestArray.Length()),
77 hashSPKIDigest);
81 * Sets certMatchesPinset to true if a given cert matches any fingerprints from
82 * the given pinset and false otherwise.
84 static nsresult EvalCert(const BackCert& cert,
85 const StaticFingerprints* fingerprints,
86 /*out*/ bool& certMatchesPinset) {
87 certMatchesPinset = false;
88 if (!fingerprints) {
89 MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
90 ("pkpin: No hashes found\n"));
91 return NS_ERROR_INVALID_ARG;
94 nsAutoCString base64Out;
95 nsresult rv = GetBase64HashSPKI(cert, base64Out);
96 if (NS_FAILED(rv)) {
97 MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
98 ("pkpin: GetBase64HashSPKI failed!\n"));
99 return rv;
102 if (fingerprints) {
103 for (size_t i = 0; i < fingerprints->size; i++) {
104 if (base64Out.Equals(fingerprints->data[i])) {
105 MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
106 ("pkpin: found pin base_64 ='%s'\n", base64Out.get()));
107 certMatchesPinset = true;
108 return NS_OK;
112 return NS_OK;
116 * Sets certListIntersectsPinset to true if a given chain matches any
117 * fingerprints from the given static fingerprints and false otherwise.
119 static nsresult EvalChain(const nsTArray<Span<const uint8_t>>& derCertList,
120 const StaticFingerprints* fingerprints,
121 /*out*/ bool& certListIntersectsPinset) {
122 certListIntersectsPinset = false;
123 if (!fingerprints) {
124 MOZ_ASSERT(false, "Must pass in at least one type of pinset");
125 return NS_ERROR_FAILURE;
128 EndEntityOrCA endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
129 for (const auto& cert : derCertList) {
130 Input certInput;
131 mozilla::pkix::Result rv = certInput.Init(cert.data(), cert.size());
132 if (rv != mozilla::pkix::Result::Success) {
133 return NS_ERROR_INVALID_ARG;
135 BackCert backCert(certInput, endEntityOrCA, nullptr);
136 rv = backCert.Init();
137 if (rv != mozilla::pkix::Result::Success) {
138 return NS_ERROR_INVALID_ARG;
141 nsresult nsrv = EvalCert(backCert, fingerprints, certListIntersectsPinset);
142 if (NS_FAILED(nsrv)) {
143 return nsrv;
145 if (certListIntersectsPinset) {
146 break;
148 endEntityOrCA = EndEntityOrCA::MustBeCA;
151 if (!certListIntersectsPinset) {
152 MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
153 ("pkpin: no matches found\n"));
155 return NS_OK;
158 class TransportSecurityPreloadBinarySearchComparator {
159 public:
160 explicit TransportSecurityPreloadBinarySearchComparator(
161 const char* aTargetHost)
162 : mTargetHost(aTargetHost) {}
164 int operator()(const TransportSecurityPreload& val) const {
165 return strcmp(mTargetHost, val.mHost);
168 private:
169 const char* mTargetHost; // non-owning
172 #ifdef DEBUG
173 static Atomic<bool> sValidatedPinningPreloadList(false);
175 static void ValidatePinningPreloadList() {
176 if (sValidatedPinningPreloadList) {
177 return;
179 for (const auto& entry : kPublicKeyPinningPreloadList) {
180 // If and only if a static entry is a Mozilla entry, it has a telemetry ID.
181 MOZ_ASSERT((entry.mIsMoz && entry.mId != kUnknownId) ||
182 (!entry.mIsMoz && entry.mId == kUnknownId));
184 sValidatedPinningPreloadList = true;
186 #endif // DEBUG
188 // Returns via one of the output parameters the most relevant pinning
189 // information that is valid for the given host at the given time.
190 static nsresult FindPinningInformation(
191 const char* hostname, mozilla::pkix::Time time,
192 /*out*/ const TransportSecurityPreload*& staticFingerprints) {
193 #ifdef DEBUG
194 ValidatePinningPreloadList();
195 #endif
196 if (!hostname || hostname[0] == 0) {
197 return NS_ERROR_INVALID_ARG;
199 staticFingerprints = nullptr;
200 const TransportSecurityPreload* foundEntry = nullptr;
201 const char* evalHost = hostname;
202 const char* evalPart;
203 // Notice how the (xx = strchr) prevents pins for unqualified domain names.
204 while (!foundEntry && (evalPart = strchr(evalHost, '.'))) {
205 MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
206 ("pkpin: Querying pinsets for host: '%s'\n", evalHost));
207 size_t foundEntryIndex;
208 if (BinarySearchIf(kPublicKeyPinningPreloadList, 0,
209 std::size(kPublicKeyPinningPreloadList),
210 TransportSecurityPreloadBinarySearchComparator(evalHost),
211 &foundEntryIndex)) {
212 foundEntry = &kPublicKeyPinningPreloadList[foundEntryIndex];
213 MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
214 ("pkpin: Found pinset for host: '%s'\n", evalHost));
215 if (evalHost != hostname) {
216 if (!foundEntry->mIncludeSubdomains) {
217 // Does not apply to this host, continue iterating
218 foundEntry = nullptr;
221 } else {
222 MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
223 ("pkpin: Didn't find pinset for host: '%s'\n", evalHost));
225 // Add one for '.'
226 evalHost = evalPart + 1;
229 if (foundEntry && foundEntry->pinset) {
230 if (time > TimeFromEpochInSeconds(kPreloadPKPinsExpirationTime /
231 PR_USEC_PER_SEC)) {
232 return NS_OK;
234 staticFingerprints = foundEntry;
236 return NS_OK;
239 // Returns true via the output parameter if the given certificate list meets
240 // pinning requirements for the given host at the given time. It must be the
241 // case that either there is an intersection between the set of hashes of
242 // subject public key info data in the list and the most relevant non-expired
243 // pinset for the host or there is no pinning information for the host.
244 static nsresult CheckPinsForHostname(
245 const nsTArray<Span<const uint8_t>>& certList, const char* hostname,
246 bool enforceTestMode, mozilla::pkix::Time time,
247 /*out*/ bool& chainHasValidPins,
248 /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo) {
249 chainHasValidPins = false;
250 if (certList.IsEmpty()) {
251 return NS_ERROR_INVALID_ARG;
253 if (!hostname || hostname[0] == 0) {
254 return NS_ERROR_INVALID_ARG;
257 const TransportSecurityPreload* staticFingerprints = nullptr;
258 nsresult rv = FindPinningInformation(hostname, time, staticFingerprints);
259 if (NS_FAILED(rv)) {
260 return rv;
262 // If we have no pinning information, the certificate chain trivially
263 // validates with respect to pinning.
264 if (!staticFingerprints) {
265 chainHasValidPins = true;
266 return NS_OK;
268 if (staticFingerprints) {
269 bool enforceTestModeResult;
270 rv = EvalChain(certList, staticFingerprints->pinset, enforceTestModeResult);
271 if (NS_FAILED(rv)) {
272 return rv;
274 chainHasValidPins = enforceTestModeResult;
275 if (staticFingerprints->mTestMode && !enforceTestMode) {
276 chainHasValidPins = true;
279 if (pinningTelemetryInfo) {
280 // If and only if a static entry is a Mozilla entry, it has a telemetry
281 // ID.
282 if ((staticFingerprints->mIsMoz &&
283 staticFingerprints->mId == kUnknownId) ||
284 (!staticFingerprints->mIsMoz &&
285 staticFingerprints->mId != kUnknownId)) {
286 return NS_ERROR_FAILURE;
289 Telemetry::HistogramID histogram;
290 int32_t bucket;
291 // We can collect per-host pinning violations for this host because it is
292 // operationally critical to Firefox.
293 if (staticFingerprints->mIsMoz) {
294 histogram = staticFingerprints->mTestMode
295 ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS_BY_HOST
296 : Telemetry::CERT_PINNING_MOZ_RESULTS_BY_HOST;
297 bucket = staticFingerprints->mId * 2 + (enforceTestModeResult ? 1 : 0);
298 } else {
299 histogram = staticFingerprints->mTestMode
300 ? Telemetry::CERT_PINNING_TEST_RESULTS
301 : Telemetry::CERT_PINNING_RESULTS;
302 bucket = enforceTestModeResult ? 1 : 0;
304 pinningTelemetryInfo->accumulateResult = true;
305 pinningTelemetryInfo->certPinningResultHistogram = Some(histogram);
306 pinningTelemetryInfo->certPinningResultBucket = bucket;
308 // We only collect per-CA pinning statistics upon failures.
309 if (!enforceTestModeResult) {
310 int32_t binNumber = RootCABinNumber(certList.LastElement());
311 if (binNumber != ROOT_CERTIFICATE_UNKNOWN) {
312 pinningTelemetryInfo->accumulateForRoot = true;
313 pinningTelemetryInfo->rootBucket = binNumber;
318 MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
319 ("pkpin: Pin check %s for %s host '%s' (mode=%s)\n",
320 enforceTestModeResult ? "passed" : "failed",
321 staticFingerprints->mIsMoz ? "mozilla" : "non-mozilla", hostname,
322 staticFingerprints->mTestMode ? "test" : "production"));
325 return NS_OK;
328 nsresult PublicKeyPinningService::ChainHasValidPins(
329 const nsTArray<Span<const uint8_t>>& certList, const char* hostname,
330 mozilla::pkix::Time time, bool isBuiltInRoot,
331 /*out*/ bool& chainHasValidPins,
332 /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo) {
333 PinningMode pinningMode(GetPinningMode());
334 if (pinningMode == PinningMode::Disabled ||
335 (!isBuiltInRoot && pinningMode == PinningMode::AllowUserCAMITM)) {
336 chainHasValidPins = true;
337 return NS_OK;
340 chainHasValidPins = false;
341 if (certList.IsEmpty()) {
342 return NS_ERROR_INVALID_ARG;
344 if (!hostname || hostname[0] == 0) {
345 return NS_ERROR_INVALID_ARG;
347 nsAutoCString canonicalizedHostname(CanonicalizeHostname(hostname));
348 bool enforceTestMode = pinningMode == PinningMode::EnforceTestMode;
349 return CheckPinsForHostname(certList, canonicalizedHostname.get(),
350 enforceTestMode, time, chainHasValidPins,
351 pinningTelemetryInfo);
354 NS_IMETHODIMP
355 PublicKeyPinningService::HostHasPins(nsIURI* aURI, bool* hostHasPins) {
356 NS_ENSURE_ARG(aURI);
357 NS_ENSURE_ARG(hostHasPins);
358 *hostHasPins = false;
359 PinningMode pinningMode(GetPinningMode());
360 if (pinningMode == PinningMode::Disabled) {
361 return NS_OK;
363 nsAutoCString hostname;
364 nsresult rv = nsSiteSecurityService::GetHost(aURI, hostname);
365 if (NS_FAILED(rv)) {
366 return rv;
368 if (nsSiteSecurityService::HostIsIPAddress(hostname)) {
369 return NS_OK;
372 const TransportSecurityPreload* staticFingerprints = nullptr;
373 rv = FindPinningInformation(hostname.get(), Now(), staticFingerprints);
374 if (NS_FAILED(rv)) {
375 return rv;
377 if (staticFingerprints) {
378 *hostHasPins = !staticFingerprints->mTestMode ||
379 pinningMode == PinningMode::EnforceTestMode;
381 return NS_OK;
384 nsAutoCString PublicKeyPinningService::CanonicalizeHostname(
385 const char* hostname) {
386 nsAutoCString canonicalizedHostname(hostname);
387 ToLowerCase(canonicalizedHostname);
388 while (canonicalizedHostname.Length() > 0 &&
389 canonicalizedHostname.Last() == '.') {
390 canonicalizedHostname.Truncate(canonicalizedHostname.Length() - 1);
392 return canonicalizedHostname;