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/. */
6
7 #include "ContentSignatureVerifier.h"
8
9 #include "BRNameMatchingPolicy.h"
10 #include "CryptoTask.h"
11 #include "CSTrustDomain.h"
12 #include "ScopedNSSTypes.h"
13 #include "SharedCertVerifier.h"
14 #include "cryptohi.h"
15 #include "keyhi.h"
16 #include "mozilla/Base64.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"
27
28 NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
29
30 using namespace mozilla;
31 using namespace mozilla::pkix;
32 using namespace mozilla::psm;
33 using dom::Promise;
34
35 static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
36 #define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
37
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};
42
43 class VerifyContentSignatureTask : public CryptoTask {
44 public:
VerifyContentSignatureTask(const nsACString & aData,const nsACString & aCSHeader,const nsACString & aCertChain,const nsACString & aHostname,RefPtr<Promise> & aPromise)45 VerifyContentSignatureTask(const nsACString& aData,
46 const nsACString& aCSHeader,
47 const nsACString& aCertChain,
48 const nsACString& aHostname,
49 RefPtr<Promise>& aPromise)
50 : mData(aData),
51 mCSHeader(aCSHeader),
52 mCertChain(aCertChain),
53 mHostname(aHostname),
54 mSignatureVerified(false),
55 mPromise(new nsMainThreadPtrHolder<Promise>(
56 "VerifyContentSignatureTask::mPromise", aPromise)) {}
57
58 private:
59 virtual nsresult CalculateResult() override;
60 virtual void CallCallback(nsresult rv) override;
61
62 nsCString mData;
63 nsCString mCSHeader;
64 nsCString mCertChain;
65 nsCString mHostname;
66 bool mSignatureVerified;
67 nsMainThreadPtrHandle<Promise> mPromise;
68 };
69
70 NS_IMETHODIMP
AsyncVerifyContentSignature(const nsACString & aData,const nsACString & aCSHeader,const nsACString & aCertChain,const nsACString & aHostname,JSContext * aCx,Promise ** aPromise)71 ContentSignatureVerifier::AsyncVerifyContentSignature(
72 const nsACString& aData, const nsACString& aCSHeader,
73 const nsACString& aCertChain, const nsACString& aHostname, JSContext* aCx,
74 Promise** aPromise) {
75 NS_ENSURE_ARG_POINTER(aCx);
76
77 nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
78 if (NS_WARN_IF(!globalObject)) {
79 return NS_ERROR_UNEXPECTED;
80 }
81
82 ErrorResult result;
83 RefPtr<Promise> promise = Promise::Create(globalObject, result);
84 if (NS_WARN_IF(result.Failed())) {
85 return result.StealNSResult();
86 }
87
88 RefPtr<VerifyContentSignatureTask> task(new VerifyContentSignatureTask(
89 aData, aCSHeader, aCertChain, aHostname, promise));
90 nsresult rv = task->Dispatch();
91 if (NS_FAILED(rv)) {
92 return rv;
93 }
94
95 promise.forget(aPromise);
96 return NS_OK;
97 }
98
99 static nsresult VerifyContentSignatureInternal(
100 const nsACString& aData, const nsACString& aCSHeader,
101 const nsACString& aCertChain, const nsACString& aHostname,
102 /* out */
103 mozilla::Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS&
104 aErrorLabel,
105 /* out */ nsACString& aCertFingerprint, /* out */ uint32_t& aErrorValue);
106 static nsresult ParseContentSignatureHeader(
107 const nsACString& aContentSignatureHeader,
108 /* out */ nsCString& aSignature);
109
CalculateResult()110 nsresult VerifyContentSignatureTask::CalculateResult() {
111 // 3 is the default, non-specific, "something failed" error.
112 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS errorLabel =
113 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err3;
114 nsAutoCString certFingerprint;
115 uint32_t errorValue = 3;
116 nsresult rv =
117 VerifyContentSignatureInternal(mData, mCSHeader, mCertChain, mHostname,
118 errorLabel, certFingerprint, errorValue);
119 if (NS_FAILED(rv)) {
120 CSVerifier_LOG(("CSVerifier: Signature verification failed"));
121 if (certFingerprint.Length() > 0) {
122 Telemetry::AccumulateCategoricalKeyed(certFingerprint, errorLabel);
123 }
124 Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, errorValue);
125 if (rv == NS_ERROR_INVALID_SIGNATURE) {
126 return NS_OK;
127 }
128 return rv;
129 }
130
131 mSignatureVerified = true;
132 Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 0);
133
134 return NS_OK;
135 }
136
CallCallback(nsresult rv)137 void VerifyContentSignatureTask::CallCallback(nsresult rv) {
138 if (NS_FAILED(rv)) {
139 mPromise->MaybeReject(rv);
140 } else {
141 mPromise->MaybeResolve(mSignatureVerified);
142 }
143 }
144
IsNewLine(char16_t c)145 bool IsNewLine(char16_t c) { return c == '\n' || c == '\r'; }
146
ReadChainIntoCertList(const nsACString & aCertChain,nsTArray<nsTArray<uint8_t>> & aCertList)147 nsresult ReadChainIntoCertList(const nsACString& aCertChain,
148 nsTArray<nsTArray<uint8_t>>& aCertList) {
149 bool inBlock = false;
150 bool certFound = false;
151
152 const nsCString header = "-----BEGIN CERTIFICATE-----"_ns;
153 const nsCString footer = "-----END CERTIFICATE-----"_ns;
154
155 nsCWhitespaceTokenizerTemplate<IsNewLine> tokenizer(aCertChain);
156
157 nsAutoCString blockData;
158 while (tokenizer.hasMoreTokens()) {
159 nsDependentCSubstring token = tokenizer.nextToken();
160 if (token.IsEmpty()) {
161 continue;
162 }
163 if (inBlock) {
164 if (token.Equals(footer)) {
165 inBlock = false;
166 certFound = true;
167 // base64 decode data, make certs, append to chain
168 nsAutoCString derString;
169 nsresult rv = Base64Decode(blockData, derString);
170 if (NS_FAILED(rv)) {
171 CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
172 return rv;
173 }
174 nsTArray<uint8_t> derBytes(derString.Data(), derString.Length());
175 aCertList.AppendElement(std::move(derBytes));
176 } else {
177 blockData.Append(token);
178 }
179 } else if (token.Equals(header)) {
180 inBlock = true;
181 blockData = "";
182 }
183 }
184 if (inBlock || !certFound) {
185 // the PEM data did not end; bad data.
186 CSVerifier_LOG(("CSVerifier: supplied chain contains bad data"));
187 return NS_ERROR_FAILURE;
188 }
189 return NS_OK;
190 }
191
192 // Given data to verify, a content signature header value, a string representing
193 // a list of PEM-encoded certificates, and a hostname to validate the
194 // certificates against, this function attempts to validate the certificate
195 // chain, extract the signature from the header, and verify the data using the
196 // key in the end-entity certificate from the chain. Returns NS_OK if everything
197 // is satisfactory and a failing nsresult otherwise. The output parameters are
198 // filled with telemetry data to report in the case of failures.
VerifyContentSignatureInternal(const nsACString & aData,const nsACString & aCSHeader,const nsACString & aCertChain,const nsACString & aHostname,Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS & aErrorLabel,nsACString & aCertFingerprint,uint32_t & aErrorValue)199 static nsresult VerifyContentSignatureInternal(
200 const nsACString& aData, const nsACString& aCSHeader,
201 const nsACString& aCertChain, const nsACString& aHostname,
202 /* out */
203 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS& aErrorLabel,
204 /* out */ nsACString& aCertFingerprint,
205 /* out */ uint32_t& aErrorValue) {
206 nsTArray<nsTArray<uint8_t>> certList;
207 nsresult rv = ReadChainIntoCertList(aCertChain, certList);
208 if (NS_FAILED(rv)) {
209 return rv;
210 }
211 if (certList.Length() < 1) {
212 return NS_ERROR_FAILURE;
213 }
214 // The 0th element should be the end-entity that issued the content
215 // signature.
216 nsTArray<uint8_t>& certBytes(certList.ElementAt(0));
217 Input certInput;
218 mozilla::pkix::Result result =
219 certInput.Init(certBytes.Elements(), certBytes.Length());
220 if (result != Success) {
221 return NS_ERROR_FAILURE;
222 }
223
224 // Get EE certificate fingerprint for telemetry.
225 unsigned char fingerprint[SHA256_LENGTH] = {0};
226 SECStatus srv =
227 PK11_HashBuf(SEC_OID_SHA256, fingerprint, certInput.UnsafeGetData(),
228 certInput.GetLength());
229 if (srv != SECSuccess) {
230 return NS_ERROR_FAILURE;
231 }
232 SECItem fingerprintItem = {siBuffer, fingerprint, SHA256_LENGTH};
233 UniquePORTString tmpFingerprintString(
234 CERT_Hexify(&fingerprintItem, false /* don't use colon delimiters */));
235 if (!tmpFingerprintString) {
236 return NS_ERROR_OUT_OF_MEMORY;
237 }
238 aCertFingerprint.Assign(tmpFingerprintString.get());
239
240 // Check the signerCert chain is good
241 CSTrustDomain trustDomain(certList);
242 result = BuildCertChain(
243 trustDomain, certInput, Now(), EndEntityOrCA::MustBeEndEntity,
244 KeyUsage::noParticularKeyUsageRequired, KeyPurposeId::id_kp_codeSigning,
245 CertPolicyId::anyPolicy, nullptr /*stapledOCSPResponse*/);
246 if (result != Success) {
247 // if there was a library error, return an appropriate error
248 if (IsFatalError(result)) {
249 return NS_ERROR_FAILURE;
250 }
251 // otherwise, assume the signature was invalid
252 if (result == mozilla::pkix::Result::ERROR_EXPIRED_CERTIFICATE) {
253 aErrorLabel =
254 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err4;
255 aErrorValue = 4;
256 } else if (result ==
257 mozilla::pkix::Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
258 aErrorLabel =
259 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err5;
260 aErrorValue = 5;
261 } else {
262 // Building cert chain failed for some other reason.
263 aErrorLabel =
264 Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err6;
265 aErrorValue = 6;
266 }
267 CSVerifier_LOG(("CSVerifier: The supplied chain is bad (%s)",
268 MapResultToName(result)));
269 return NS_ERROR_INVALID_SIGNATURE;
270 }
271
272 // Check the SAN
273 Input hostnameInput;
274
275 result = hostnameInput.Init(
276 BitwiseCast<const uint8_t*, const char*>(aHostname.BeginReading()),
277 aHostname.Length());
278 if (result != Success) {
279 return NS_ERROR_FAILURE;
280 }
281
282 BRNameMatchingPolicy nameMatchingPolicy(BRNameMatchingPolicy::Mode::Enforce);
283 result = CheckCertHostname(certInput, hostnameInput, nameMatchingPolicy);
284 if (result != Success) {
285 // EE cert isnot valid for the given host name.
286 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err7;
287 aErrorValue = 7;
288 return NS_ERROR_INVALID_SIGNATURE;
289 }
290
291 pkix::BackCert backCert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
292 result = backCert.Init();
293 // This should never fail, because we've already built a verified certificate
294 // chain with this certificate.
295 if (result != Success) {
296 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
297 aErrorValue = 8;
298 CSVerifier_LOG(("CSVerifier: couldn't decode certificate to get spki"));
299 return NS_ERROR_INVALID_SIGNATURE;
300 }
301 Input spkiInput = backCert.GetSubjectPublicKeyInfo();
302 SECItem spkiItem = {siBuffer, const_cast<uint8_t*>(spkiInput.UnsafeGetData()),
303 spkiInput.GetLength()};
304 UniqueCERTSubjectPublicKeyInfo spki(
305 SECKEY_DecodeDERSubjectPublicKeyInfo(&spkiItem));
306 if (!spki) {
307 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
308 aErrorValue = 8;
309 CSVerifier_LOG(("CSVerifier: couldn't decode spki"));
310 return NS_ERROR_INVALID_SIGNATURE;
311 }
312 mozilla::UniqueSECKEYPublicKey key(SECKEY_ExtractPublicKey(spki.get()));
313 if (!key) {
314 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err8;
315 aErrorValue = 8;
316 CSVerifier_LOG(("CSVerifier: unable to extract a key"));
317 return NS_ERROR_INVALID_SIGNATURE;
318 }
319
320 nsAutoCString signature;
321 rv = ParseContentSignatureHeader(aCSHeader, signature);
322 if (NS_FAILED(rv)) {
323 return rv;
324 }
325
326 // Base 64 decode the signature
327 nsAutoCString rawSignature;
328 rv = Base64Decode(signature, rawSignature);
329 if (NS_FAILED(rv)) {
330 CSVerifier_LOG(("CSVerifier: decoding the signature failed"));
331 return rv;
332 }
333
334 // get signature object
335 ScopedAutoSECItem signatureItem;
336 SECItem rawSignatureItem = {
337 siBuffer,
338 BitwiseCast<unsigned char*, const char*>(rawSignature.get()),
339 uint32_t(rawSignature.Length()),
340 };
341 // We have a raw ecdsa signature r||s so we have to DER-encode it first
342 // Note that we have to check rawSignatureItem->len % 2 here as
343 // DSAU_EncodeDerSigWithLen asserts this
344 if (rawSignatureItem.len == 0 || rawSignatureItem.len % 2 != 0) {
345 CSVerifier_LOG(("CSVerifier: signature length is bad"));
346 return NS_ERROR_FAILURE;
347 }
348 if (DSAU_EncodeDerSigWithLen(&signatureItem, &rawSignatureItem,
349 rawSignatureItem.len) != SECSuccess) {
350 CSVerifier_LOG(("CSVerifier: encoding the signature failed"));
351 return NS_ERROR_FAILURE;
352 }
353
354 // this is the only OID we support for now
355 SECOidTag oid = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
356 mozilla::UniqueVFYContext cx(
357 VFY_CreateContext(key.get(), &signatureItem, oid, nullptr));
358 if (!cx) {
359 // Creating context failed.
360 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err9;
361 aErrorValue = 9;
362 return NS_ERROR_INVALID_SIGNATURE;
363 }
364
365 if (VFY_Begin(cx.get()) != SECSuccess) {
366 // Creating context failed.
367 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err9;
368 aErrorValue = 9;
369 return NS_ERROR_INVALID_SIGNATURE;
370 }
371 if (VFY_Update(cx.get(), kPREFIX, sizeof(kPREFIX)) != SECSuccess) {
372 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
373 aErrorValue = 1;
374 return NS_ERROR_INVALID_SIGNATURE;
375 }
376 if (VFY_Update(cx.get(),
377 reinterpret_cast<const unsigned char*>(aData.BeginReading()),
378 aData.Length()) != SECSuccess) {
379 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
380 aErrorValue = 1;
381 return NS_ERROR_INVALID_SIGNATURE;
382 }
383 if (VFY_End(cx.get()) != SECSuccess) {
384 aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
385 aErrorValue = 1;
386 return NS_ERROR_INVALID_SIGNATURE;
387 }
388
389 return NS_OK;
390 }
391
ParseContentSignatureHeader(const nsACString & aContentSignatureHeader,nsCString & aSignature)392 static nsresult ParseContentSignatureHeader(
393 const nsACString& aContentSignatureHeader,
394 /* out */ nsCString& aSignature) {
395 // We only support p384 ecdsa.
396 constexpr auto signature_var = "p384ecdsa"_ns;
397
398 aSignature.Truncate();
399
400 const nsCString& flatHeader = PromiseFlatCString(aContentSignatureHeader);
401 nsSecurityHeaderParser parser(flatHeader);
402 nsresult rv = parser.Parse();
403 if (NS_FAILED(rv)) {
404 CSVerifier_LOG(("CSVerifier: could not parse ContentSignature header"));
405 return NS_ERROR_FAILURE;
406 }
407 LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
408
409 for (nsSecurityHeaderDirective* directive = directives->getFirst();
410 directive != nullptr; directive = directive->getNext()) {
411 CSVerifier_LOG(
412 ("CSVerifier: found directive '%s'", directive->mName.get()));
413 if (directive->mName.Length() == signature_var.Length() &&
414 directive->mName.EqualsIgnoreCase(signature_var.get(),
415 signature_var.Length())) {
416 if (!aSignature.IsEmpty()) {
417 CSVerifier_LOG(("CSVerifier: found two ContentSignatures"));
418 return NS_ERROR_INVALID_SIGNATURE;
419 }
420
421 CSVerifier_LOG(("CSVerifier: found a ContentSignature directive"));
422 aSignature.Assign(directive->mValue);
423 }
424 }
425
426 // we have to ensure that we found a signature at this point
427 if (aSignature.IsEmpty()) {
428 CSVerifier_LOG(
429 ("CSVerifier: got a Content-Signature header but didn't find a "
430 "signature."));
431 return NS_ERROR_FAILURE;
432 }
433
434 // Bug 769521: We have to change b64 url to regular encoding as long as we
435 // don't have a b64 url decoder. This should change soon, but in the meantime
436 // we have to live with this.
437 aSignature.ReplaceChar('-', '+');
438 aSignature.ReplaceChar('_', '/');
439
440 return NS_OK;
441 }
442