Bug 1935611 - Fix libyuv/libpng link failed for loongarch64. r=glandium,tnikkel,ng
[gecko.git] / security / manager / ssl / ContentSignatureVerifier.cpp
blobbfb004726702d5926477c09bda5e6253b9665f6d
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ContentSignatureVerifier.h"
9 #include "AppTrustDomain.h"
10 #include "CryptoTask.h"
11 #include "ScopedNSSTypes.h"
12 #include "SharedCertVerifier.h"
13 #include "cryptohi.h"
14 #include "keyhi.h"
15 #include "mozilla/Base64.h"
16 #include "mozilla/Logging.h"
17 #include "mozilla/dom/Promise.h"
18 #include "nsCOMPtr.h"
19 #include "nsPromiseFlatString.h"
20 #include "nsSecurityHeaderParser.h"
21 #include "nsWhitespaceTokenizer.h"
22 #include "mozpkix/pkix.h"
23 #include "mozpkix/pkixtypes.h"
24 #include "mozpkix/pkixutil.h"
25 #include "secerr.h"
26 #include "ssl.h"
28 NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
30 using namespace mozilla;
31 using namespace mozilla::pkix;
32 using namespace mozilla::psm;
33 using dom::Promise;
35 static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
36 #define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
38 // Content-Signature prefix
39 const unsigned char kPREFIX[] = {'C', 'o', 'n', 't', 'e', 'n', 't',
40 '-', 'S', 'i', 'g', 'n', 'a', 't',
41 'u', 'r', 'e', ':', 0};
43 class VerifyContentSignatureTask : public CryptoTask {
44 public:
45 VerifyContentSignatureTask(const nsACString& aData,
46 const nsACString& aCSHeader,
47 const nsACString& aCertChain,
48 const nsACString& aHostname,
49 AppTrustedRoot aTrustedRoot,
50 RefPtr<Promise>& aPromise)
51 : mData(aData),
52 mCSHeader(aCSHeader),
53 mCertChain(aCertChain),
54 mHostname(aHostname),
55 mTrustedRoot(aTrustedRoot),
56 mSignatureVerified(false),
57 mPromise(new nsMainThreadPtrHolder<Promise>(
58 "VerifyContentSignatureTask::mPromise", aPromise)) {}
60 private:
61 virtual nsresult CalculateResult() override;
62 virtual void CallCallback(nsresult rv) override;
64 nsCString mData;
65 nsCString mCSHeader;
66 nsCString mCertChain;
67 nsCString mHostname;
68 AppTrustedRoot mTrustedRoot;
69 bool mSignatureVerified;
70 nsMainThreadPtrHandle<Promise> mPromise;
73 NS_IMETHODIMP
74 ContentSignatureVerifier::AsyncVerifyContentSignature(
75 const nsACString& aData, const nsACString& aCSHeader,
76 const nsACString& aCertChain, const nsACString& aHostname,
77 AppTrustedRoot aTrustedRoot, JSContext* aCx, Promise** aPromise) {
78 NS_ENSURE_ARG_POINTER(aCx);
80 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
81 if (NS_WARN_IF(!globalObject)) {
82 return NS_ERROR_UNEXPECTED;
85 ErrorResult result;
86 RefPtr<Promise> promise = Promise::Create(globalObject, result);
87 if (NS_WARN_IF(result.Failed())) {
88 return result.StealNSResult();
91 RefPtr<VerifyContentSignatureTask> task(new VerifyContentSignatureTask(
92 aData, aCSHeader, aCertChain, aHostname, aTrustedRoot, promise));
93 nsresult rv = task->Dispatch();
94 if (NS_FAILED(rv)) {
95 return rv;
98 promise.forget(aPromise);
99 return NS_OK;
102 static nsresult VerifyContentSignatureInternal(
103 const nsACString& aData, const nsACString& aCSHeader,
104 const nsACString& aCertChain, const nsACString& aHostname,
105 AppTrustedRoot aTrustedRoot,
106 /* out */
107 mozilla::Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS&
108 aErrorLabel,
109 /* out */ nsACString& aCertFingerprint, /* out */ uint32_t& aErrorValue);
110 static nsresult ParseContentSignatureHeader(
111 const nsACString& aContentSignatureHeader,
112 /* out */ nsCString& aSignature);
114 nsresult VerifyContentSignatureTask::CalculateResult() {
115 // 3 is the default, non-specific, "something failed" error.
116 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS errorLabel =
117 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err3;
118 nsAutoCString certFingerprint;
119 uint32_t errorValue = 3;
120 nsresult rv = VerifyContentSignatureInternal(
121 mData, mCSHeader, mCertChain, mHostname, mTrustedRoot, errorLabel,
122 certFingerprint, errorValue);
123 if (NS_FAILED(rv)) {
124 CSVerifier_LOG(("CSVerifier: Signature verification failed"));
125 if (certFingerprint.Length() > 0) {
126 Telemetry::AccumulateCategoricalKeyed(certFingerprint, errorLabel);
128 Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, errorValue);
129 if (rv == NS_ERROR_INVALID_SIGNATURE) {
130 return NS_OK;
132 return rv;
135 mSignatureVerified = true;
136 Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 0);
138 return NS_OK;
141 void VerifyContentSignatureTask::CallCallback(nsresult rv) {
142 if (NS_FAILED(rv)) {
143 mPromise->MaybeReject(rv);
144 } else {
145 mPromise->MaybeResolve(mSignatureVerified);
149 bool IsNewLine(char16_t c) { return c == '\n' || c == '\r'; }
151 nsresult ReadChainIntoCertList(const nsACString& aCertChain,
152 nsTArray<nsTArray<uint8_t>>& aCertList) {
153 bool inBlock = false;
154 bool certFound = false;
156 const nsCString header = "-----BEGIN CERTIFICATE-----"_ns;
157 const nsCString footer = "-----END CERTIFICATE-----"_ns;
159 nsCWhitespaceTokenizerTemplate<IsNewLine> tokenizer(aCertChain);
161 nsAutoCString blockData;
162 while (tokenizer.hasMoreTokens()) {
163 nsDependentCSubstring token = tokenizer.nextToken();
164 if (token.IsEmpty()) {
165 continue;
167 if (inBlock) {
168 if (token.Equals(footer)) {
169 inBlock = false;
170 certFound = true;
171 // base64 decode data, make certs, append to chain
172 nsAutoCString derString;
173 nsresult rv = Base64Decode(blockData, derString);
174 if (NS_FAILED(rv)) {
175 CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
176 return rv;
178 nsTArray<uint8_t> derBytes(derString.Data(), derString.Length());
179 aCertList.AppendElement(std::move(derBytes));
180 } else {
181 blockData.Append(token);
183 } else if (token.Equals(header)) {
184 inBlock = true;
185 blockData = "";
188 if (inBlock || !certFound) {
189 // the PEM data did not end; bad data.
190 CSVerifier_LOG(("CSVerifier: supplied chain contains bad data"));
191 return NS_ERROR_FAILURE;
193 return NS_OK;
196 // Given data to verify, a content signature header value, a string representing
197 // a list of PEM-encoded certificates, and a hostname to validate the
198 // certificates against, this function attempts to validate the certificate
199 // chain, extract the signature from the header, and verify the data using the
200 // key in the end-entity certificate from the chain. Returns NS_OK if everything
201 // is satisfactory and a failing nsresult otherwise. The output parameters are
202 // filled with telemetry data to report in the case of failures.
203 static nsresult VerifyContentSignatureInternal(
204 const nsACString& aData, const nsACString& aCSHeader,
205 const nsACString& aCertChain, const nsACString& aHostname,
206 AppTrustedRoot aTrustedRoot,
207 /* out */
208 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS& aErrorLabel,
209 /* out */ nsACString& aCertFingerprint,
210 /* out */ uint32_t& aErrorValue) {
211 nsTArray<nsTArray<uint8_t>> certList;
212 nsresult rv = ReadChainIntoCertList(aCertChain, certList);
213 if (NS_FAILED(rv)) {
214 return rv;
216 if (certList.Length() < 1) {
217 return NS_ERROR_FAILURE;
219 // The 0th element should be the end-entity that issued the content
220 // signature.
221 nsTArray<uint8_t>& certBytes(certList.ElementAt(0));
222 Input certInput;
223 mozilla::pkix::Result result =
224 certInput.Init(certBytes.Elements(), certBytes.Length());
225 if (result != Success) {
226 return NS_ERROR_FAILURE;
229 // Get EE certificate fingerprint for telemetry.
230 unsigned char fingerprint[SHA256_LENGTH] = {0};
231 SECStatus srv =
232 PK11_HashBuf(SEC_OID_SHA256, fingerprint, certInput.UnsafeGetData(),
233 certInput.GetLength());
234 if (srv != SECSuccess) {
235 return NS_ERROR_FAILURE;
237 SECItem fingerprintItem = {siBuffer, fingerprint, SHA256_LENGTH};
238 UniquePORTString tmpFingerprintString(
239 CERT_Hexify(&fingerprintItem, false /* don't use colon delimiters */));
240 if (!tmpFingerprintString) {
241 return NS_ERROR_OUT_OF_MEMORY;
243 aCertFingerprint.Assign(tmpFingerprintString.get());
245 nsTArray<Span<const uint8_t>> certSpans;
246 // Collect just the CAs.
247 for (size_t i = 1; i < certList.Length(); i++) {
248 Span<const uint8_t> certSpan(certList.ElementAt(i).Elements(),
249 certList.ElementAt(i).Length());
250 certSpans.AppendElement(std::move(certSpan));
252 AppTrustDomain trustDomain(std::move(certSpans));
253 rv = trustDomain.SetTrustedRoot(aTrustedRoot);
254 if (NS_FAILED(rv)) {
255 return rv;
257 // Check the signerCert chain is good
258 result = BuildCertChain(
259 trustDomain, certInput, Now(), EndEntityOrCA::MustBeEndEntity,
260 KeyUsage::noParticularKeyUsageRequired, KeyPurposeId::id_kp_codeSigning,
261 CertPolicyId::anyPolicy, nullptr /*stapledOCSPResponse*/);
262 if (result != Success) {
263 // if there was a library error, return an appropriate error
264 if (IsFatalError(result)) {
265 return NS_ERROR_FAILURE;
267 // otherwise, assume the signature was invalid
268 if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) {
269 aErrorLabel =
270 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err4;
271 aErrorValue = 4;
272 } else if (result ==
273 mozilla::pkix::Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
274 aErrorLabel =
275 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err5;
276 aErrorValue = 5;
277 } else {
278 // Building cert chain failed for some other reason.
279 aErrorLabel =
280 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err6;
281 aErrorValue = 6;
283 CSVerifier_LOG(("CSVerifier: The supplied chain is bad (%s)",
284 MapResultToName(result)));
285 return NS_ERROR_INVALID_SIGNATURE;
288 // Check the SAN
289 Input hostnameInput;
291 result = hostnameInput.Init(
292 BitwiseCast<const uint8_t*, const char*>(aHostname.BeginReading()),
293 aHostname.Length());
294 if (result != Success) {
295 return NS_ERROR_FAILURE;
298 result = CheckCertHostname(certInput, hostnameInput);
299 if (result != Success) {
300 // EE cert isnot valid for the given host name.
301 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err7;
302 aErrorValue = 7;
303 return NS_ERROR_INVALID_SIGNATURE;
306 pkix::BackCert backCert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
307 result = backCert.Init();
308 // This should never fail, because we've already built a verified certificate
309 // chain with this certificate.
310 if (result != Success) {
311 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
312 aErrorValue = 8;
313 CSVerifier_LOG(("CSVerifier: couldn't decode certificate to get spki"));
314 return NS_ERROR_INVALID_SIGNATURE;
316 Input spkiInput = backCert.GetSubjectPublicKeyInfo();
317 SECItem spkiItem = {siBuffer, const_cast<uint8_t*>(spkiInput.UnsafeGetData()),
318 spkiInput.GetLength()};
319 UniqueCERTSubjectPublicKeyInfo spki(
320 SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
321 if (!spki) {
322 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
323 aErrorValue = 8;
324 CSVerifier_LOG(("CSVerifier: couldn't decode spki"));
325 return NS_ERROR_INVALID_SIGNATURE;
327 mozilla::UniqueSECKEYPublicKey key(SECKEY_ExtractPublicKey(spki.get()));
328 if (!key) {
329 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
330 aErrorValue = 8;
331 CSVerifier_LOG(("CSVerifier: unable to extract a key"));
332 return NS_ERROR_INVALID_SIGNATURE;
335 nsAutoCString signature;
336 rv = ParseContentSignatureHeader(aCSHeader, signature);
337 if (NS_FAILED(rv)) {
338 return rv;
341 // Base 64 decode the signature
342 nsAutoCString rawSignature;
343 rv = Base64Decode(signature, rawSignature);
344 if (NS_FAILED(rv)) {
345 CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
346 return rv;
349 // get signature object
350 ScopedAutoSECItem signatureItem;
351 SECItem rawSignatureItem = {
352 siBuffer,
353 BitwiseCast<unsigned char*, const char*>(rawSignature.get()),
354 uint32_t(rawSignature.Length()),
356 // We have a raw ecdsa signature r||s so we have to DER-encode it first
357 // Note that we have to check rawSignatureItem->len % 2 here as
358 // DSAU_EncodeDerSigWithLen asserts this
359 if (rawSignatureItem.len == 0 || rawSignatureItem.len % 2 != 0) {
360 CSVerifier_LOG(("CSVerifier: signature length is bad"));
361 return NS_ERROR_FAILURE;
363 if (DSAU_EncodeDerSigWithLen(&signatureItem, &rawSignatureItem,
364 rawSignatureItem.len) != SECSuccess) {
365 CSVerifier_LOG(("CSVerifier: encoding the signature failed"));
366 return NS_ERROR_FAILURE;
369 // this is the only OID we support for now
370 SECOidTag oid = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
371 mozilla::UniqueVFYContext cx(
372 VFY_CreateContext(key.get(), &signatureItem, oid, nullptr));
373 if (!cx) {
374 // Creating context failed.
375 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err9;
376 aErrorValue = 9;
377 return NS_ERROR_INVALID_SIGNATURE;
380 if (VFY_Begin(cx.get()) != SECSuccess) {
381 // Creating context failed.
382 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err9;
383 aErrorValue = 9;
384 return NS_ERROR_INVALID_SIGNATURE;
386 if (VFY_Update(cx.get(), kPREFIX, sizeof(kPREFIX)) != SECSuccess) {
387 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
388 aErrorValue = 1;
389 return NS_ERROR_INVALID_SIGNATURE;
391 if (VFY_Update(cx.get(),
392 reinterpret_cast<const unsigned char*>(aData.BeginReading()),
393 aData.Length()) != SECSuccess) {
394 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
395 aErrorValue = 1;
396 return NS_ERROR_INVALID_SIGNATURE;
398 if (VFY_End(cx.get()) != SECSuccess) {
399 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
400 aErrorValue = 1;
401 return NS_ERROR_INVALID_SIGNATURE;
404 return NS_OK;
407 static nsresult ParseContentSignatureHeader(
408 const nsACString& aContentSignatureHeader,
409 /* out */ nsCString& aSignature) {
410 // We only support p384 ecdsa.
411 constexpr auto signature_var = "p384ecdsa"_ns;
413 aSignature.Truncate();
415 const nsCString& flatHeader = PromiseFlatCString(aContentSignatureHeader);
416 nsSecurityHeaderParser parser(flatHeader);
417 nsresult rv = parser.Parse();
418 if (NS_FAILED(rv)) {
419 CSVerifier_LOG(("CSVerifier: could not parse ContentSignature header"));
420 return NS_ERROR_FAILURE;
422 LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
424 for (nsSecurityHeaderDirective* directive = directives->getFirst();
425 directive != nullptr; directive = directive->getNext()) {
426 CSVerifier_LOG(
427 ("CSVerifier: found directive '%s'", directive->mName.get()));
428 if (directive->mName.EqualsIgnoreCase(signature_var)) {
429 if (!aSignature.IsEmpty()) {
430 CSVerifier_LOG(("CSVerifier: found two ContentSignatures"));
431 return NS_ERROR_INVALID_SIGNATURE;
433 if (directive->mValue.isNothing()) {
434 CSVerifier_LOG(("CSVerifier: found empty ContentSignature directive"));
435 return NS_ERROR_INVALID_SIGNATURE;
438 CSVerifier_LOG(("CSVerifier: found a ContentSignature directive"));
439 aSignature.Assign(*(directive->mValue));
443 // we have to ensure that we found a signature at this point
444 if (aSignature.IsEmpty()) {
445 CSVerifier_LOG(
446 ("CSVerifier: got a Content-Signature header but didn't find a "
447 "signature."));
448 return NS_ERROR_FAILURE;
451 // Bug 769521: We have to change b64 url to regular encoding as long as we
452 // don't have a b64 url decoder. This should change soon, but in the meantime
453 // we have to live with this.
454 aSignature.ReplaceChar('-', '+');
455 aSignature.ReplaceChar('_', '/');
457 return NS_OK;