Line data Source code
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 "SharedCertVerifier.h"
11 : #include "cryptohi.h"
12 : #include "keyhi.h"
13 : #include "mozilla/Assertions.h"
14 : #include "mozilla/Base64.h"
15 : #include "mozilla/Casting.h"
16 : #include "mozilla/Unused.h"
17 : #include "nsCOMPtr.h"
18 : #include "nsContentUtils.h"
19 : #include "nsISupportsPriority.h"
20 : #include "nsIURI.h"
21 : #include "nsNSSComponent.h"
22 : #include "nsPromiseFlatString.h"
23 : #include "nsSecurityHeaderParser.h"
24 : #include "nsStreamUtils.h"
25 : #include "nsWhitespaceTokenizer.h"
26 : #include "pkix/pkix.h"
27 : #include "pkix/pkixtypes.h"
28 : #include "secerr.h"
29 :
30 0 : NS_IMPL_ISUPPORTS(ContentSignatureVerifier,
31 : nsIContentSignatureVerifier,
32 : nsIInterfaceRequestor,
33 : nsIStreamListener)
34 :
35 : using namespace mozilla;
36 : using namespace mozilla::pkix;
37 : using namespace mozilla::psm;
38 :
39 : static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
40 : #define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
41 :
42 : // Content-Signature prefix
43 3 : const nsLiteralCString kPREFIX = NS_LITERAL_CSTRING("Content-Signature:\x00");
44 :
45 0 : ContentSignatureVerifier::~ContentSignatureVerifier()
46 : {
47 0 : nsNSSShutDownPreventionLock locker;
48 0 : if (isAlreadyShutDown()) {
49 0 : return;
50 : }
51 0 : destructorSafeDestroyNSSReference();
52 0 : shutdown(ShutdownCalledFrom::Object);
53 0 : }
54 :
55 : NS_IMETHODIMP
56 0 : ContentSignatureVerifier::VerifyContentSignature(
57 : const nsACString& aData, const nsACString& aCSHeader,
58 : const nsACString& aCertChain, const nsACString& aName, bool* _retval)
59 : {
60 0 : NS_ENSURE_ARG(_retval);
61 0 : nsresult rv = CreateContext(aData, aCSHeader, aCertChain, aName);
62 0 : if (NS_FAILED(rv)) {
63 0 : *_retval = false;
64 0 : CSVerifier_LOG(("CSVerifier: Signature verification failed\n"));
65 0 : if (rv == NS_ERROR_INVALID_SIGNATURE) {
66 0 : return NS_OK;
67 : }
68 0 : return rv;
69 : }
70 :
71 0 : return End(_retval);
72 : }
73 :
74 : bool
75 0 : IsNewLine(char16_t c)
76 : {
77 0 : return c == '\n' || c == '\r';
78 : }
79 :
80 : nsresult
81 0 : ReadChainIntoCertList(const nsACString& aCertChain, CERTCertList* aCertList,
82 : const nsNSSShutDownPreventionLock& /*proofOfLock*/)
83 : {
84 0 : bool inBlock = false;
85 0 : bool certFound = false;
86 :
87 0 : const nsCString header = NS_LITERAL_CSTRING("-----BEGIN CERTIFICATE-----");
88 0 : const nsCString footer = NS_LITERAL_CSTRING("-----END CERTIFICATE-----");
89 :
90 0 : nsCWhitespaceTokenizerTemplate<IsNewLine> tokenizer(aCertChain);
91 :
92 0 : nsAutoCString blockData;
93 0 : while (tokenizer.hasMoreTokens()) {
94 0 : nsDependentCSubstring token = tokenizer.nextToken();
95 0 : if (token.IsEmpty()) {
96 0 : continue;
97 : }
98 0 : if (inBlock) {
99 0 : if (token.Equals(footer)) {
100 0 : inBlock = false;
101 0 : certFound = true;
102 : // base64 decode data, make certs, append to chain
103 0 : nsAutoCString derString;
104 0 : nsresult rv = Base64Decode(blockData, derString);
105 0 : if (NS_FAILED(rv)) {
106 0 : CSVerifier_LOG(("CSVerifier: decoding the signature failed\n"));
107 0 : return rv;
108 : }
109 : SECItem der = {
110 : siBuffer,
111 0 : BitwiseCast<unsigned char*, const char*>(derString.get()),
112 0 : derString.Length(),
113 0 : };
114 : UniqueCERTCertificate tmpCert(
115 : CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der, nullptr, false,
116 0 : true));
117 0 : if (!tmpCert) {
118 0 : return NS_ERROR_FAILURE;
119 : }
120 : // if adding tmpCert succeeds, tmpCert will now be owned by aCertList
121 0 : SECStatus res = CERT_AddCertToListTail(aCertList, tmpCert.get());
122 0 : if (res != SECSuccess) {
123 0 : return MapSECStatus(res);
124 : }
125 0 : Unused << tmpCert.release();
126 : } else {
127 0 : blockData.Append(token);
128 : }
129 0 : } else if (token.Equals(header)) {
130 0 : inBlock = true;
131 0 : blockData = "";
132 : }
133 : }
134 0 : if (inBlock || !certFound) {
135 : // the PEM data did not end; bad data.
136 0 : CSVerifier_LOG(("CSVerifier: supplied chain contains bad data\n"));
137 0 : return NS_ERROR_FAILURE;
138 : }
139 0 : return NS_OK;
140 : }
141 :
142 : nsresult
143 0 : ContentSignatureVerifier::CreateContextInternal(const nsACString& aData,
144 : const nsACString& aCertChain,
145 : const nsACString& aName)
146 : {
147 0 : MOZ_ASSERT(NS_IsMainThread());
148 0 : nsNSSShutDownPreventionLock locker;
149 0 : if (isAlreadyShutDown()) {
150 0 : CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
151 0 : return NS_ERROR_FAILURE;
152 : }
153 :
154 0 : UniqueCERTCertList certCertList(CERT_NewCertList());
155 0 : if (!certCertList) {
156 0 : return NS_ERROR_OUT_OF_MEMORY;
157 : }
158 :
159 0 : nsresult rv = ReadChainIntoCertList(aCertChain, certCertList.get(), locker);
160 0 : if (NS_FAILED(rv)) {
161 0 : return rv;
162 : }
163 :
164 0 : CERTCertListNode* node = CERT_LIST_HEAD(certCertList.get());
165 0 : if (!node || CERT_LIST_END(node, certCertList.get()) || !node->cert) {
166 0 : return NS_ERROR_FAILURE;
167 : }
168 :
169 0 : SECItem* certSecItem = &node->cert->derCert;
170 :
171 0 : Input certDER;
172 : mozilla::pkix::Result result =
173 0 : certDER.Init(BitwiseCast<uint8_t*, unsigned char*>(certSecItem->data),
174 0 : certSecItem->len);
175 0 : if (result != Success) {
176 0 : return NS_ERROR_FAILURE;
177 : }
178 :
179 :
180 : // Check the signerCert chain is good
181 0 : CSTrustDomain trustDomain(certCertList);
182 0 : result = BuildCertChain(trustDomain, certDER, Now(),
183 : EndEntityOrCA::MustBeEndEntity,
184 : KeyUsage::noParticularKeyUsageRequired,
185 : KeyPurposeId::id_kp_codeSigning,
186 : CertPolicyId::anyPolicy,
187 0 : nullptr/*stapledOCSPResponse*/);
188 0 : if (result != Success) {
189 : // if there was a library error, return an appropriate error
190 0 : if (IsFatalError(result)) {
191 0 : return NS_ERROR_FAILURE;
192 : }
193 : // otherwise, assume the signature was invalid
194 0 : CSVerifier_LOG(("CSVerifier: The supplied chain is bad\n"));
195 0 : return NS_ERROR_INVALID_SIGNATURE;
196 : }
197 :
198 : // Check the SAN
199 0 : Input hostnameInput;
200 :
201 0 : result = hostnameInput.Init(
202 : BitwiseCast<const uint8_t*, const char*>(aName.BeginReading()),
203 0 : aName.Length());
204 0 : if (result != Success) {
205 0 : return NS_ERROR_FAILURE;
206 : }
207 :
208 0 : BRNameMatchingPolicy nameMatchingPolicy(BRNameMatchingPolicy::Mode::Enforce);
209 0 : result = CheckCertHostname(certDER, hostnameInput, nameMatchingPolicy);
210 0 : if (result != Success) {
211 0 : return NS_ERROR_INVALID_SIGNATURE;
212 : }
213 :
214 0 : mKey.reset(CERT_ExtractPublicKey(node->cert));
215 :
216 : // in case we were not able to extract a key
217 0 : if (!mKey) {
218 0 : CSVerifier_LOG(("CSVerifier: unable to extract a key\n"));
219 0 : return NS_ERROR_INVALID_SIGNATURE;
220 : }
221 :
222 : // Base 64 decode the signature
223 0 : nsAutoCString rawSignature;
224 0 : rv = Base64Decode(mSignature, rawSignature);
225 0 : if (NS_FAILED(rv)) {
226 0 : CSVerifier_LOG(("CSVerifier: decoding the signature failed\n"));
227 0 : return rv;
228 : }
229 :
230 : // get signature object
231 0 : ScopedAutoSECItem signatureItem;
232 : SECItem rawSignatureItem = {
233 : siBuffer,
234 0 : BitwiseCast<unsigned char*, const char*>(rawSignature.get()),
235 0 : rawSignature.Length(),
236 0 : };
237 : // We have a raw ecdsa signature r||s so we have to DER-encode it first
238 : // Note that we have to check rawSignatureItem->len % 2 here as
239 : // DSAU_EncodeDerSigWithLen asserts this
240 0 : if (rawSignatureItem.len == 0 || rawSignatureItem.len % 2 != 0) {
241 0 : CSVerifier_LOG(("CSVerifier: signature length is bad\n"));
242 0 : return NS_ERROR_FAILURE;
243 : }
244 0 : if (DSAU_EncodeDerSigWithLen(&signatureItem, &rawSignatureItem,
245 : rawSignatureItem.len) != SECSuccess) {
246 0 : CSVerifier_LOG(("CSVerifier: encoding the signature failed\n"));
247 0 : return NS_ERROR_FAILURE;
248 : }
249 :
250 : // this is the only OID we support for now
251 0 : SECOidTag oid = SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE;
252 :
253 0 : mCx = UniqueVFYContext(
254 0 : VFY_CreateContext(mKey.get(), &signatureItem, oid, nullptr));
255 0 : if (!mCx) {
256 0 : return NS_ERROR_INVALID_SIGNATURE;
257 : }
258 :
259 0 : if (VFY_Begin(mCx.get()) != SECSuccess) {
260 0 : return NS_ERROR_INVALID_SIGNATURE;
261 : }
262 :
263 0 : rv = UpdateInternal(kPREFIX, locker);
264 0 : if (NS_FAILED(rv)) {
265 0 : return rv;
266 : }
267 : // add data if we got any
268 0 : return UpdateInternal(aData, locker);
269 : }
270 :
271 : nsresult
272 0 : ContentSignatureVerifier::DownloadCertChain()
273 : {
274 0 : MOZ_ASSERT(NS_IsMainThread());
275 :
276 0 : if (mCertChainURL.IsEmpty()) {
277 0 : return NS_ERROR_INVALID_SIGNATURE;
278 : }
279 :
280 0 : nsCOMPtr<nsIURI> certChainURI;
281 0 : nsresult rv = NS_NewURI(getter_AddRefs(certChainURI), mCertChainURL);
282 0 : if (NS_FAILED(rv) || !certChainURI) {
283 0 : return rv;
284 : }
285 :
286 : // If the address is not https, fail.
287 0 : bool isHttps = false;
288 0 : rv = certChainURI->SchemeIs("https", &isHttps);
289 0 : if (NS_FAILED(rv)) {
290 0 : return rv;
291 : }
292 0 : if (!isHttps) {
293 0 : return NS_ERROR_INVALID_SIGNATURE;
294 : }
295 :
296 0 : rv = NS_NewChannel(getter_AddRefs(mChannel), certChainURI,
297 : nsContentUtils::GetSystemPrincipal(),
298 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
299 0 : nsIContentPolicy::TYPE_OTHER);
300 0 : if (NS_FAILED(rv)) {
301 0 : return rv;
302 : }
303 :
304 : // we need this chain soon
305 0 : nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(mChannel);
306 0 : if (priorityChannel) {
307 0 : priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
308 : }
309 :
310 0 : rv = mChannel->AsyncOpen2(this);
311 0 : if (NS_FAILED(rv)) {
312 0 : return rv;
313 : }
314 :
315 0 : return NS_OK;
316 : }
317 :
318 : // Create a context for content signature verification using CreateContext below.
319 : // This function doesn't require a cert chain to be passed, but instead aCSHeader
320 : // must contain an x5u value that is then used to download the cert chain.
321 : NS_IMETHODIMP
322 0 : ContentSignatureVerifier::CreateContextWithoutCertChain(
323 : nsIContentSignatureReceiverCallback *aCallback, const nsACString& aCSHeader,
324 : const nsACString& aName)
325 : {
326 0 : MOZ_ASSERT(NS_IsMainThread());
327 0 : MOZ_ASSERT(aCallback);
328 0 : if (mInitialised) {
329 0 : return NS_ERROR_ALREADY_INITIALIZED;
330 : }
331 0 : mInitialised = true;
332 :
333 : // we get the raw content-signature header here, so first parse aCSHeader
334 0 : nsresult rv = ParseContentSignatureHeader(aCSHeader);
335 0 : if (NS_FAILED(rv)) {
336 0 : return rv;
337 : }
338 :
339 0 : mCallback = aCallback;
340 0 : mName.Assign(aName);
341 :
342 : // We must download the cert chain now.
343 : // This is async and blocks createContextInternal calls.
344 0 : return DownloadCertChain();
345 : }
346 :
347 : // Create a context for a content signature verification.
348 : // It sets signature, certificate chain and name that should be used to verify
349 : // the data. The data parameter is the first part of the data to verify (this
350 : // can be the empty string).
351 : NS_IMETHODIMP
352 0 : ContentSignatureVerifier::CreateContext(const nsACString& aData,
353 : const nsACString& aCSHeader,
354 : const nsACString& aCertChain,
355 : const nsACString& aName)
356 : {
357 0 : if (mInitialised) {
358 0 : return NS_ERROR_ALREADY_INITIALIZED;
359 : }
360 0 : mInitialised = true;
361 : // The cert chain is given in aCertChain so we don't have to download anything.
362 0 : mHasCertChain = true;
363 :
364 : // we get the raw content-signature header here, so first parse aCSHeader
365 0 : nsresult rv = ParseContentSignatureHeader(aCSHeader);
366 0 : if (NS_FAILED(rv)) {
367 0 : return rv;
368 : }
369 :
370 0 : return CreateContextInternal(aData, aCertChain, aName);
371 : }
372 :
373 : nsresult
374 0 : ContentSignatureVerifier::UpdateInternal(
375 : const nsACString& aData, const nsNSSShutDownPreventionLock& /*proofOfLock*/)
376 : {
377 0 : if (!aData.IsEmpty()) {
378 0 : if (VFY_Update(mCx.get(), (const unsigned char*)nsPromiseFlatCString(aData).get(),
379 : aData.Length()) != SECSuccess){
380 0 : return NS_ERROR_INVALID_SIGNATURE;
381 : }
382 : }
383 0 : return NS_OK;
384 : }
385 :
386 : /**
387 : * Add data to the context that shold be verified.
388 : */
389 : NS_IMETHODIMP
390 0 : ContentSignatureVerifier::Update(const nsACString& aData)
391 : {
392 0 : MOZ_ASSERT(NS_IsMainThread());
393 0 : nsNSSShutDownPreventionLock locker;
394 0 : if (isAlreadyShutDown()) {
395 0 : CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
396 0 : return NS_ERROR_FAILURE;
397 : }
398 :
399 : // If we didn't create the context yet, bail!
400 0 : if (!mHasCertChain) {
401 0 : MOZ_ASSERT_UNREACHABLE(
402 : "Someone called ContentSignatureVerifier::Update before "
403 : "downloading the cert chain.");
404 : return NS_ERROR_FAILURE;
405 : }
406 :
407 0 : return UpdateInternal(aData, locker);
408 : }
409 :
410 : /**
411 : * Finish signature verification and return the result in _retval.
412 : */
413 : NS_IMETHODIMP
414 0 : ContentSignatureVerifier::End(bool* _retval)
415 : {
416 0 : NS_ENSURE_ARG(_retval);
417 0 : MOZ_ASSERT(NS_IsMainThread());
418 0 : nsNSSShutDownPreventionLock locker;
419 0 : if (isAlreadyShutDown()) {
420 0 : CSVerifier_LOG(("CSVerifier: nss is already shutdown\n"));
421 0 : return NS_ERROR_FAILURE;
422 : }
423 :
424 : // If we didn't create the context yet, bail!
425 0 : if (!mHasCertChain) {
426 0 : MOZ_ASSERT_UNREACHABLE(
427 : "Someone called ContentSignatureVerifier::End before "
428 : "downloading the cert chain.");
429 : return NS_ERROR_FAILURE;
430 : }
431 :
432 0 : *_retval = (VFY_End(mCx.get()) == SECSuccess);
433 :
434 0 : return NS_OK;
435 : }
436 :
437 : nsresult
438 0 : ContentSignatureVerifier::ParseContentSignatureHeader(
439 : const nsACString& aContentSignatureHeader)
440 : {
441 0 : MOZ_ASSERT(NS_IsMainThread());
442 : // We only support p384 ecdsa according to spec
443 0 : NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
444 0 : NS_NAMED_LITERAL_CSTRING(certChainURL_var, "x5u");
445 :
446 0 : const nsCString& flatHeader = PromiseFlatCString(aContentSignatureHeader);
447 0 : nsSecurityHeaderParser parser(flatHeader);
448 0 : nsresult rv = parser.Parse();
449 0 : if (NS_FAILED(rv)) {
450 0 : CSVerifier_LOG(("CSVerifier: could not parse ContentSignature header\n"));
451 0 : return NS_ERROR_FAILURE;
452 : }
453 0 : LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
454 :
455 0 : for (nsSecurityHeaderDirective* directive = directives->getFirst();
456 0 : directive != nullptr; directive = directive->getNext()) {
457 0 : CSVerifier_LOG(("CSVerifier: found directive %s\n", directive->mName.get()));
458 0 : if (directive->mName.Length() == signature_var.Length() &&
459 0 : directive->mName.EqualsIgnoreCase(signature_var.get(),
460 0 : signature_var.Length())) {
461 0 : if (!mSignature.IsEmpty()) {
462 0 : CSVerifier_LOG(("CSVerifier: found two ContentSignatures\n"));
463 0 : return NS_ERROR_INVALID_SIGNATURE;
464 : }
465 :
466 0 : CSVerifier_LOG(("CSVerifier: found a ContentSignature directive\n"));
467 0 : mSignature = directive->mValue;
468 : }
469 0 : if (directive->mName.Length() == certChainURL_var.Length() &&
470 0 : directive->mName.EqualsIgnoreCase(certChainURL_var.get(),
471 0 : certChainURL_var.Length())) {
472 0 : if (!mCertChainURL.IsEmpty()) {
473 0 : CSVerifier_LOG(("CSVerifier: found two x5u values\n"));
474 0 : return NS_ERROR_INVALID_SIGNATURE;
475 : }
476 :
477 0 : CSVerifier_LOG(("CSVerifier: found an x5u directive\n"));
478 0 : mCertChainURL = directive->mValue;
479 : }
480 : }
481 :
482 : // we have to ensure that we found a signature at this point
483 0 : if (mSignature.IsEmpty()) {
484 0 : CSVerifier_LOG(("CSVerifier: got a Content-Signature header but didn't find a signature.\n"));
485 0 : return NS_ERROR_FAILURE;
486 : }
487 :
488 : // Bug 769521: We have to change b64 url to regular encoding as long as we
489 : // don't have a b64 url decoder. This should change soon, but in the meantime
490 : // we have to live with this.
491 0 : mSignature.ReplaceChar('-', '+');
492 0 : mSignature.ReplaceChar('_', '/');
493 :
494 0 : return NS_OK;
495 : }
496 :
497 : /* nsIStreamListener implementation */
498 :
499 : NS_IMETHODIMP
500 0 : ContentSignatureVerifier::OnStartRequest(nsIRequest* aRequest,
501 : nsISupports* aContext)
502 : {
503 0 : MOZ_ASSERT(NS_IsMainThread());
504 0 : return NS_OK;
505 : }
506 :
507 : NS_IMETHODIMP
508 0 : ContentSignatureVerifier::OnStopRequest(nsIRequest* aRequest,
509 : nsISupports* aContext, nsresult aStatus)
510 : {
511 0 : MOZ_ASSERT(NS_IsMainThread());
512 0 : nsCOMPtr<nsIContentSignatureReceiverCallback> callback;
513 0 : callback.swap(mCallback);
514 : nsresult rv;
515 :
516 : // Check HTTP status code and return if it's not 200.
517 0 : nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv);
518 : uint32_t httpResponseCode;
519 0 : if (NS_FAILED(rv) || NS_FAILED(http->GetResponseStatus(&httpResponseCode)) ||
520 0 : httpResponseCode != 200) {
521 0 : callback->ContextCreated(false);
522 0 : return NS_OK;
523 : }
524 :
525 0 : if (NS_FAILED(aStatus)) {
526 0 : callback->ContextCreated(false);
527 0 : return NS_OK;
528 : }
529 :
530 0 : nsAutoCString certChain;
531 0 : for (uint32_t i = 0; i < mCertChain.Length(); ++i) {
532 0 : certChain.Append(mCertChain[i]);
533 : }
534 :
535 : // We got the cert chain now. Let's create the context.
536 0 : rv = CreateContextInternal(NS_LITERAL_CSTRING(""), certChain, mName);
537 0 : if (NS_FAILED(rv)) {
538 0 : callback->ContextCreated(false);
539 0 : return NS_OK;
540 : }
541 :
542 0 : mHasCertChain = true;
543 0 : callback->ContextCreated(true);
544 0 : return NS_OK;
545 : }
546 :
547 : NS_IMETHODIMP
548 0 : ContentSignatureVerifier::OnDataAvailable(nsIRequest* aRequest,
549 : nsISupports* aContext,
550 : nsIInputStream* aInputStream,
551 : uint64_t aOffset, uint32_t aCount)
552 : {
553 0 : MOZ_ASSERT(NS_IsMainThread());
554 0 : nsAutoCString buffer;
555 :
556 0 : nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer);
557 0 : if (NS_FAILED(rv)) {
558 0 : return rv;
559 : }
560 :
561 0 : if (!mCertChain.AppendElement(buffer, fallible)) {
562 0 : mCertChain.TruncateLength(0);
563 0 : return NS_ERROR_OUT_OF_MEMORY;
564 : }
565 :
566 0 : return NS_OK;
567 : }
568 :
569 : NS_IMETHODIMP
570 0 : ContentSignatureVerifier::GetInterface(const nsIID& uuid, void** result)
571 : {
572 0 : return QueryInterface(uuid, result);
573 : }
|