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