Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
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 file,
5 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "hasht.h"
8 : #include "nsICryptoHash.h"
9 : #include "nsNetCID.h"
10 : #include "nsThreadUtils.h"
11 : #include "mozilla/ClearOnShutdown.h"
12 : #include "mozilla/dom/AuthenticatorAttestationResponse.h"
13 : #include "mozilla/dom/Promise.h"
14 : #include "mozilla/dom/WebAuthnManager.h"
15 : #include "mozilla/dom/WebAuthnUtil.h"
16 : #include "mozilla/dom/PWebAuthnTransaction.h"
17 : #include "mozilla/dom/WebAuthnTransactionChild.h"
18 : #include "mozilla/dom/WebCryptoCommon.h"
19 : #include "mozilla/ipc/PBackgroundChild.h"
20 : #include "mozilla/ipc/BackgroundChild.h"
21 :
22 : using namespace mozilla::ipc;
23 :
24 : namespace mozilla {
25 : namespace dom {
26 :
27 : /***********************************************************************
28 : * Statics
29 : **********************************************************************/
30 :
31 : namespace {
32 3 : StaticRefPtr<WebAuthnManager> gWebAuthnManager;
33 : static mozilla::LazyLogModule gWebAuthnManagerLog("webauthnmanager");
34 : }
35 :
36 0 : NS_IMPL_ISUPPORTS(WebAuthnManager, nsIIPCBackgroundChildCreateCallback);
37 :
38 : /***********************************************************************
39 : * Utility Functions
40 : **********************************************************************/
41 :
42 : template<class OOS>
43 : static nsresult
44 0 : GetAlgorithmName(const OOS& aAlgorithm,
45 : /* out */ nsString& aName)
46 : {
47 0 : MOZ_ASSERT(aAlgorithm.IsString()); // TODO: remove assertion when we coerce.
48 :
49 0 : if (aAlgorithm.IsString()) {
50 : // If string, then treat as algorithm name
51 0 : aName.Assign(aAlgorithm.GetAsString());
52 : } else {
53 : // TODO: Coerce to string and extract name. See WebCryptoTask.cpp
54 : }
55 :
56 0 : if (!NormalizeToken(aName, aName)) {
57 0 : return NS_ERROR_DOM_SYNTAX_ERR;
58 : }
59 :
60 0 : return NS_OK;
61 : }
62 :
63 : static nsresult
64 0 : HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
65 : /* out */ CryptoBuffer& aOut)
66 : {
67 0 : MOZ_ASSERT(aHashService);
68 :
69 0 : nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
70 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
71 0 : return rv;
72 : }
73 :
74 0 : rv = aHashService->Update(
75 0 : reinterpret_cast<const uint8_t*>(aIn.BeginReading()),aIn.Length());
76 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
77 0 : return rv;
78 : }
79 :
80 0 : nsAutoCString fullHash;
81 : // Passing false below means we will get a binary result rather than a
82 : // base64-encoded string.
83 0 : rv = aHashService->Finish(false, fullHash);
84 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
85 0 : return rv;
86 : }
87 :
88 0 : aOut.Assign(fullHash);
89 0 : return rv;
90 : }
91 :
92 : static nsresult
93 0 : AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
94 : /* out */ nsACString& aJsonOut)
95 : {
96 0 : MOZ_ASSERT(NS_IsMainThread());
97 :
98 0 : nsString challengeBase64;
99 0 : nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
100 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
101 0 : return NS_ERROR_FAILURE;
102 : }
103 :
104 0 : CollectedClientData clientDataObject;
105 0 : clientDataObject.mChallenge.Assign(challengeBase64);
106 0 : clientDataObject.mOrigin.Assign(aOrigin);
107 0 : clientDataObject.mHashAlg.Assign(NS_LITERAL_STRING("S256"));
108 :
109 0 : nsAutoString temp;
110 0 : if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
111 0 : return NS_ERROR_FAILURE;
112 : }
113 :
114 0 : aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
115 0 : return NS_OK;
116 : }
117 :
118 : nsresult
119 0 : GetOrigin(nsPIDOMWindowInner* aParent,
120 : /*out*/ nsAString& aOrigin, /*out*/ nsACString& aHost)
121 : {
122 0 : nsCOMPtr<nsIDocument> doc = aParent->GetDoc();
123 0 : MOZ_ASSERT(doc);
124 :
125 0 : nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
126 0 : nsresult rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
127 0 : if (NS_WARN_IF(NS_FAILED(rv)) ||
128 0 : NS_WARN_IF(aOrigin.IsEmpty())) {
129 0 : return NS_ERROR_FAILURE;
130 : }
131 :
132 0 : if (aOrigin.EqualsLiteral("null")) {
133 : // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
134 : // DOMException whose name is "NotAllowedError", and terminate this
135 : // algorithm
136 0 : MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug, ("Rejecting due to opaque origin"));
137 0 : return NS_ERROR_DOM_NOT_ALLOWED_ERR;
138 : }
139 :
140 0 : nsCOMPtr<nsIURI> originUri;
141 0 : if (NS_FAILED(principal->GetURI(getter_AddRefs(originUri)))) {
142 0 : return NS_ERROR_FAILURE;
143 : }
144 0 : if (NS_FAILED(originUri->GetAsciiHost(aHost))) {
145 0 : return NS_ERROR_FAILURE;
146 : }
147 :
148 0 : return NS_OK;
149 : }
150 :
151 : nsresult
152 0 : RelaxSameOrigin(nsPIDOMWindowInner* aParent,
153 : const nsAString& aInputRpId,
154 : /* out */ nsACString& aRelaxedRpId)
155 : {
156 0 : MOZ_ASSERT(aParent);
157 0 : nsCOMPtr<nsIDocument> doc = aParent->GetDoc();
158 0 : MOZ_ASSERT(doc);
159 0 : nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
160 0 : nsCOMPtr<nsIURI> uri;
161 0 : if (NS_FAILED(principal->GetURI(getter_AddRefs(uri)))) {
162 0 : return NS_ERROR_FAILURE;
163 : }
164 0 : nsAutoCString originHost;
165 0 : if (NS_FAILED(uri->GetAsciiHost(originHost))) {
166 0 : return NS_ERROR_FAILURE;
167 : }
168 0 : nsCOMPtr<nsIDocument> document = aParent->GetDoc();
169 0 : if (!document || !document->IsHTMLDocument()) {
170 0 : return NS_ERROR_FAILURE;
171 : }
172 0 : nsHTMLDocument* html = document->AsHTMLDocument();
173 0 : if (NS_WARN_IF(!html)) {
174 0 : return NS_ERROR_FAILURE;
175 : }
176 :
177 0 : if (!html->IsRegistrableDomainSuffixOfOrEqualTo(aInputRpId, originHost)) {
178 0 : return NS_ERROR_DOM_SECURITY_ERR;
179 : }
180 :
181 0 : aRelaxedRpId.Assign(NS_ConvertUTF16toUTF8(aInputRpId));
182 0 : return NS_OK;
183 : }
184 :
185 : /***********************************************************************
186 : * WebAuthnManager Implementation
187 : **********************************************************************/
188 :
189 0 : WebAuthnManager::WebAuthnManager()
190 : {
191 0 : MOZ_ASSERT(NS_IsMainThread());
192 0 : }
193 :
194 : void
195 0 : WebAuthnManager::MaybeClearTransaction()
196 : {
197 0 : mClientData.reset();
198 0 : mInfo.reset();
199 0 : mTransactionPromise = nullptr;
200 0 : if (mChild) {
201 0 : RefPtr<WebAuthnTransactionChild> c;
202 0 : mChild.swap(c);
203 0 : c->Send__delete__(c);
204 : }
205 0 : }
206 :
207 0 : WebAuthnManager::~WebAuthnManager()
208 : {
209 0 : MaybeClearTransaction();
210 0 : }
211 :
212 : already_AddRefed<MozPromise<nsresult, nsresult, false>>
213 0 : WebAuthnManager::GetOrCreateBackgroundActor()
214 : {
215 0 : bool ok = BackgroundChild::GetOrCreateForCurrentThread(this);
216 0 : if (NS_WARN_IF(!ok)) {
217 0 : ActorFailed();
218 : }
219 0 : return mPBackgroundCreationPromise.Ensure(__func__);
220 : }
221 :
222 : //static
223 : WebAuthnManager*
224 0 : WebAuthnManager::GetOrCreate()
225 : {
226 0 : MOZ_ASSERT(NS_IsMainThread());
227 0 : if (gWebAuthnManager) {
228 0 : return gWebAuthnManager;
229 : }
230 :
231 0 : gWebAuthnManager = new WebAuthnManager();
232 0 : ClearOnShutdown(&gWebAuthnManager);
233 0 : return gWebAuthnManager;
234 : }
235 :
236 : //static
237 : WebAuthnManager*
238 0 : WebAuthnManager::Get()
239 : {
240 0 : MOZ_ASSERT(NS_IsMainThread());
241 0 : return gWebAuthnManager;
242 : }
243 :
244 : already_AddRefed<Promise>
245 0 : WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
246 : const MakeCredentialOptions& aOptions)
247 : {
248 0 : MOZ_ASSERT(aParent);
249 :
250 0 : MaybeClearTransaction();
251 :
252 0 : nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
253 :
254 0 : ErrorResult rv;
255 0 : RefPtr<Promise> promise = Promise::Create(global, rv);
256 0 : if(rv.Failed()) {
257 0 : return nullptr;
258 : }
259 :
260 0 : nsString origin;
261 0 : nsCString rpId;
262 0 : rv = GetOrigin(aParent, origin, rpId);
263 0 : if (NS_WARN_IF(rv.Failed())) {
264 0 : promise->MaybeReject(rv);
265 0 : return promise.forget();
266 : }
267 :
268 : // If timeoutSeconds was specified, check if its value lies within a
269 : // reasonable range as defined by the platform and if not, correct it to the
270 : // closest value lying within that range.
271 :
272 0 : double adjustedTimeout = 30.0;
273 0 : if (aOptions.mTimeout.WasPassed()) {
274 0 : adjustedTimeout = aOptions.mTimeout.Value();
275 0 : adjustedTimeout = std::max(15.0, adjustedTimeout);
276 0 : adjustedTimeout = std::min(120.0, adjustedTimeout);
277 : }
278 :
279 0 : if (aOptions.mRp.mId.WasPassed()) {
280 : // If rpId is specified, then invoke the procedure used for relaxing the
281 : // same-origin restriction by setting the document.domain attribute, using
282 : // rpId as the given value but without changing the current document’s
283 : // domain. If no errors are thrown, set rpId to the value of host as
284 : // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
285 : // Otherwise, reject promise with a DOMException whose name is
286 : // "SecurityError", and terminate this algorithm.
287 :
288 0 : if (NS_FAILED(RelaxSameOrigin(aParent, aOptions.mRp.mId.Value(), rpId))) {
289 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
290 0 : return promise.forget();
291 : }
292 : }
293 :
294 0 : CryptoBuffer rpIdHash;
295 0 : if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
296 0 : promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
297 0 : return promise.forget();
298 : }
299 :
300 : nsresult srv;
301 : nsCOMPtr<nsICryptoHash> hashService =
302 0 : do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
303 0 : if (NS_WARN_IF(NS_FAILED(srv))) {
304 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
305 0 : return promise.forget();
306 : }
307 :
308 0 : srv = HashCString(hashService, rpId, rpIdHash);
309 0 : if (NS_WARN_IF(NS_FAILED(srv))) {
310 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
311 0 : return promise.forget();
312 : }
313 :
314 : // Process each element of cryptoParameters using the following steps, to
315 : // produce a new sequence normalizedParameters.
316 0 : nsTArray<PublicKeyCredentialParameters> normalizedParams;
317 0 : for (size_t a = 0; a < aOptions.mParameters.Length(); ++a) {
318 : // Let current be the currently selected element of
319 : // cryptoParameters.
320 :
321 : // If current.type does not contain a PublicKeyCredentialType
322 : // supported by this implementation, then stop processing current and move
323 : // on to the next element in cryptoParameters.
324 0 : if (aOptions.mParameters[a].mType != PublicKeyCredentialType::Public_key) {
325 0 : continue;
326 : }
327 :
328 : // Let normalizedAlgorithm be the result of normalizing an algorithm using
329 : // the procedure defined in [WebCryptoAPI], with alg set to
330 : // current.algorithm and op set to 'generateKey'. If an error occurs during
331 : // this procedure, then stop processing current and move on to the next
332 : // element in cryptoParameters.
333 :
334 0 : nsString algName;
335 0 : if (NS_FAILED(GetAlgorithmName(aOptions.mParameters[a].mAlgorithm,
336 : algName))) {
337 0 : continue;
338 : }
339 :
340 : // Add a new object of type PublicKeyCredentialParameters to
341 : // normalizedParameters, with type set to current.type and algorithm set to
342 : // normalizedAlgorithm.
343 0 : PublicKeyCredentialParameters normalizedObj;
344 0 : normalizedObj.mType = aOptions.mParameters[a].mType;
345 0 : normalizedObj.mAlgorithm.SetAsString().Assign(algName);
346 :
347 0 : if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){
348 0 : promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
349 0 : return promise.forget();
350 : }
351 : }
352 :
353 : // If normalizedAlgorithm is empty and cryptoParameters was not empty, cancel
354 : // the timer started in step 2, reject promise with a DOMException whose name
355 : // is "NotSupportedError", and terminate this algorithm.
356 0 : if (normalizedParams.IsEmpty() && !aOptions.mParameters.IsEmpty()) {
357 0 : promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
358 0 : return promise.forget();
359 : }
360 :
361 : // TODO: The following check should not be here. This is checking for
362 : // parameters specific to the soft key, and should be put in the soft key
363 : // manager in the parent process. Still need to serialize
364 : // PublicKeyCredentialParameters first.
365 :
366 : // Check if at least one of the specified combinations of
367 : // PublicKeyCredentialParameters and cryptographic parameters is supported. If
368 : // not, return an error code equivalent to NotSupportedError and terminate the
369 : // operation.
370 :
371 0 : bool isValidCombination = false;
372 :
373 0 : for (size_t a = 0; a < normalizedParams.Length(); ++a) {
374 0 : if (normalizedParams[a].mType == PublicKeyCredentialType::Public_key &&
375 0 : normalizedParams[a].mAlgorithm.IsString() &&
376 0 : normalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
377 : WEBCRYPTO_NAMED_CURVE_P256)) {
378 0 : isValidCombination = true;
379 0 : break;
380 : }
381 : }
382 0 : if (!isValidCombination) {
383 0 : promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
384 0 : return promise.forget();
385 : }
386 :
387 : // If excludeList is undefined, set it to the empty list.
388 : //
389 : // If extensions was specified, process any extensions supported by this
390 : // client platform, to produce the extension data that needs to be sent to the
391 : // authenticator. If an error is encountered while processing an extension,
392 : // skip that extension and do not produce any extension data for it. Call the
393 : // result of this processing clientExtensions.
394 : //
395 : // Currently no extensions are supported
396 : //
397 : // Use attestationChallenge, callerOrigin and rpId, along with the token
398 : // binding key associated with callerOrigin (if any), to create a ClientData
399 : // structure representing this request. Choose a hash algorithm for hashAlg
400 : // and compute the clientDataJSON and clientDataHash.
401 :
402 0 : CryptoBuffer challenge;
403 0 : if (!challenge.Assign(aOptions.mChallenge)) {
404 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
405 0 : return promise.forget();
406 : }
407 :
408 0 : nsAutoCString clientDataJSON;
409 0 : srv = AssembleClientData(origin, challenge, clientDataJSON);
410 0 : if (NS_WARN_IF(NS_FAILED(srv))) {
411 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
412 0 : return promise.forget();
413 : }
414 :
415 0 : CryptoBuffer clientDataHash;
416 0 : if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
417 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
418 0 : return promise.forget();
419 : }
420 :
421 0 : srv = HashCString(hashService, clientDataJSON, clientDataHash);
422 0 : if (NS_WARN_IF(NS_FAILED(srv))) {
423 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
424 0 : return promise.forget();
425 : }
426 :
427 0 : nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
428 0 : if (aOptions.mExcludeList.WasPassed()) {
429 0 : for (const auto& s: aOptions.mExcludeList.Value()) {
430 0 : WebAuthnScopedCredentialDescriptor c;
431 0 : CryptoBuffer cb;
432 0 : cb.Assign(s.mId);
433 0 : c.id() = cb;
434 0 : excludeList.AppendElement(c);
435 : }
436 : }
437 :
438 : // TODO: Add extension list building
439 0 : nsTArray<WebAuthnExtension> extensions;
440 :
441 : WebAuthnTransactionInfo info(rpIdHash,
442 : clientDataHash,
443 : adjustedTimeout,
444 : excludeList,
445 0 : extensions);
446 0 : RefPtr<MozPromise<nsresult, nsresult, false>> p = GetOrCreateBackgroundActor();
447 : p->Then(GetMainThreadSerialEventTarget(), __func__,
448 0 : []() {
449 0 : WebAuthnManager* mgr = WebAuthnManager::Get();
450 0 : if (!mgr) {
451 0 : return;
452 : }
453 0 : mgr->StartRegister();
454 : },
455 0 : []() {
456 : // This case can't actually happen, we'll have crashed if the child
457 : // failed to create.
458 0 : });
459 0 : mTransactionPromise = promise;
460 0 : mClientData = Some(clientDataJSON);
461 0 : mCurrentParent = aParent;
462 0 : mInfo = Some(info);
463 0 : return promise.forget();
464 : }
465 :
466 : void
467 0 : WebAuthnManager::StartRegister() {
468 0 : if (mChild) {
469 0 : mChild->SendRequestRegister(mInfo.ref());
470 : }
471 0 : }
472 :
473 : void
474 0 : WebAuthnManager::StartSign() {
475 0 : if (mChild) {
476 0 : mChild->SendRequestSign(mInfo.ref());
477 : }
478 0 : }
479 :
480 : already_AddRefed<Promise>
481 0 : WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
482 : const PublicKeyCredentialRequestOptions& aOptions)
483 : {
484 0 : MOZ_ASSERT(aParent);
485 :
486 0 : MaybeClearTransaction();
487 :
488 0 : nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
489 :
490 0 : ErrorResult rv;
491 0 : RefPtr<Promise> promise = Promise::Create(global, rv);
492 0 : if(rv.Failed()) {
493 0 : return nullptr;
494 : }
495 :
496 0 : nsString origin;
497 0 : nsCString rpId;
498 0 : rv = GetOrigin(aParent, origin, rpId);
499 0 : if (NS_WARN_IF(rv.Failed())) {
500 0 : promise->MaybeReject(rv);
501 0 : return promise.forget();
502 : }
503 :
504 : // If timeoutSeconds was specified, check if its value lies within a
505 : // reasonable range as defined by the platform and if not, correct it to the
506 : // closest value lying within that range.
507 :
508 0 : uint32_t adjustedTimeout = 30000;
509 0 : if (aOptions.mTimeout.WasPassed()) {
510 0 : adjustedTimeout = aOptions.mTimeout.Value();
511 0 : adjustedTimeout = std::max(15000u, adjustedTimeout);
512 0 : adjustedTimeout = std::min(120000u, adjustedTimeout);
513 : }
514 :
515 0 : if (aOptions.mRpId.WasPassed()) {
516 : // If rpId is specified, then invoke the procedure used for relaxing the
517 : // same-origin restriction by setting the document.domain attribute, using
518 : // rpId as the given value but without changing the current document’s
519 : // domain. If no errors are thrown, set rpId to the value of host as
520 : // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
521 : // Otherwise, reject promise with a DOMException whose name is
522 : // "SecurityError", and terminate this algorithm.
523 :
524 0 : if (NS_FAILED(RelaxSameOrigin(aParent, aOptions.mRpId.Value(), rpId))) {
525 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
526 0 : return promise.forget();
527 : }
528 : }
529 :
530 0 : CryptoBuffer rpIdHash;
531 0 : if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
532 0 : promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
533 0 : return promise.forget();
534 : }
535 :
536 : nsresult srv;
537 : nsCOMPtr<nsICryptoHash> hashService =
538 0 : do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
539 0 : if (NS_WARN_IF(NS_FAILED(srv))) {
540 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
541 0 : return promise.forget();
542 : }
543 :
544 0 : srv = HashCString(hashService, rpId, rpIdHash);
545 0 : if (NS_WARN_IF(NS_FAILED(srv))) {
546 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
547 0 : return promise.forget();
548 : }
549 :
550 : // Use assertionChallenge, callerOrigin and rpId, along with the token binding
551 : // key associated with callerOrigin (if any), to create a ClientData structure
552 : // representing this request. Choose a hash algorithm for hashAlg and compute
553 : // the clientDataJSON and clientDataHash.
554 0 : CryptoBuffer challenge;
555 0 : if (!challenge.Assign(aOptions.mChallenge)) {
556 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
557 0 : return promise.forget();
558 : }
559 :
560 0 : nsAutoCString clientDataJSON;
561 0 : srv = AssembleClientData(origin, challenge, clientDataJSON);
562 0 : if (NS_WARN_IF(NS_FAILED(srv))) {
563 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
564 0 : return promise.forget();
565 : }
566 :
567 0 : CryptoBuffer clientDataHash;
568 0 : if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
569 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
570 0 : return promise.forget();
571 : }
572 :
573 0 : srv = HashCString(hashService, clientDataJSON, clientDataHash);
574 0 : if (NS_WARN_IF(NS_FAILED(srv))) {
575 0 : promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
576 0 : return promise.forget();
577 : }
578 :
579 : // Note: we only support U2F-style authentication for now, so we effectively
580 : // require an AllowList.
581 0 : if (aOptions.mAllowList.Length() < 1) {
582 0 : promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
583 0 : return promise.forget();
584 : }
585 :
586 0 : nsTArray<WebAuthnScopedCredentialDescriptor> allowList;
587 0 : for (const auto& s: aOptions.mAllowList) {
588 0 : WebAuthnScopedCredentialDescriptor c;
589 0 : CryptoBuffer cb;
590 0 : cb.Assign(s.mId);
591 0 : c.id() = cb;
592 0 : allowList.AppendElement(c);
593 : }
594 :
595 : // TODO: Add extension list building
596 : // If extensions was specified, process any extensions supported by this
597 : // client platform, to produce the extension data that needs to be sent to the
598 : // authenticator. If an error is encountered while processing an extension,
599 : // skip that extension and do not produce any extension data for it. Call the
600 : // result of this processing clientExtensions.
601 0 : nsTArray<WebAuthnExtension> extensions;
602 :
603 : WebAuthnTransactionInfo info(rpIdHash,
604 : clientDataHash,
605 : adjustedTimeout,
606 : allowList,
607 0 : extensions);
608 0 : RefPtr<MozPromise<nsresult, nsresult, false>> p = GetOrCreateBackgroundActor();
609 : p->Then(GetMainThreadSerialEventTarget(), __func__,
610 0 : []() {
611 0 : WebAuthnManager* mgr = WebAuthnManager::Get();
612 0 : if (!mgr) {
613 0 : return;
614 : }
615 0 : mgr->StartSign();
616 : },
617 0 : []() {
618 : // This case can't actually happen, we'll have crashed if the child
619 : // failed to create.
620 0 : });
621 :
622 : // Only store off the promise if we've succeeded in sending the IPC event.
623 0 : mTransactionPromise = promise;
624 0 : mClientData = Some(clientDataJSON);
625 0 : mCurrentParent = aParent;
626 0 : mInfo = Some(info);
627 0 : return promise.forget();
628 : }
629 :
630 : void
631 0 : WebAuthnManager::FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer)
632 : {
633 0 : MOZ_ASSERT(mTransactionPromise);
634 0 : MOZ_ASSERT(mInfo.isSome());
635 :
636 0 : CryptoBuffer regData;
637 0 : if (NS_WARN_IF(!regData.Assign(aRegBuffer.Elements(), aRegBuffer.Length()))) {
638 0 : mTransactionPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
639 0 : return;
640 : }
641 :
642 : // Decompose the U2F registration packet
643 0 : CryptoBuffer pubKeyBuf;
644 0 : CryptoBuffer keyHandleBuf;
645 0 : CryptoBuffer attestationCertBuf;
646 0 : CryptoBuffer signatureBuf;
647 :
648 0 : nsresult rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
649 0 : attestationCertBuf, signatureBuf);
650 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
651 0 : Cancel(rv);
652 0 : return;
653 : }
654 :
655 0 : CryptoBuffer clientDataBuf;
656 0 : if (!clientDataBuf.Assign(mClientData.ref())) {
657 0 : Cancel(NS_ERROR_OUT_OF_MEMORY);
658 0 : return;
659 : }
660 :
661 0 : CryptoBuffer rpIdHashBuf;
662 0 : if (!rpIdHashBuf.Assign(mInfo.ref().RpIdHash())) {
663 0 : Cancel(NS_ERROR_OUT_OF_MEMORY);
664 0 : return;
665 : }
666 :
667 0 : CryptoBuffer authenticatorDataBuf;
668 0 : rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, rpIdHashBuf,
669 : signatureBuf);
670 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
671 0 : Cancel(NS_ERROR_OUT_OF_MEMORY);
672 0 : return;
673 : }
674 :
675 : // Create a new PublicKeyCredential object and populate its fields with the
676 : // values returned from the authenticator as well as the clientDataJSON
677 : // computed earlier.
678 : RefPtr<AuthenticatorAttestationResponse> attestation =
679 0 : new AuthenticatorAttestationResponse(mCurrentParent);
680 0 : attestation->SetClientDataJSON(clientDataBuf);
681 0 : attestation->SetAttestationObject(regData);
682 :
683 0 : RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mCurrentParent);
684 0 : credential->SetRawId(keyHandleBuf);
685 0 : credential->SetResponse(attestation);
686 :
687 0 : mTransactionPromise->MaybeResolve(credential);
688 0 : MaybeClearTransaction();
689 : }
690 :
691 : void
692 0 : WebAuthnManager::FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
693 : nsTArray<uint8_t>& aSigBuffer)
694 : {
695 0 : MOZ_ASSERT(mTransactionPromise);
696 0 : MOZ_ASSERT(mInfo.isSome());
697 :
698 0 : CryptoBuffer signatureData;
699 0 : if (NS_WARN_IF(!signatureData.Assign(aSigBuffer.Elements(), aSigBuffer.Length()))) {
700 0 : Cancel(NS_ERROR_OUT_OF_MEMORY);
701 0 : return;
702 : }
703 :
704 0 : CryptoBuffer clientDataBuf;
705 0 : if (!clientDataBuf.Assign(mClientData.ref())) {
706 0 : Cancel(NS_ERROR_OUT_OF_MEMORY);
707 0 : return;
708 : }
709 :
710 0 : CryptoBuffer rpIdHashBuf;
711 0 : if (!rpIdHashBuf.Assign(mInfo.ref().RpIdHash())) {
712 0 : Cancel(NS_ERROR_OUT_OF_MEMORY);
713 0 : return;
714 : }
715 :
716 0 : CryptoBuffer authenticatorDataBuf;
717 0 : nsresult rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, rpIdHashBuf,
718 0 : signatureData);
719 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
720 0 : Cancel(rv);
721 0 : return;
722 : }
723 :
724 0 : CryptoBuffer credentialBuf;
725 0 : if (!credentialBuf.Assign(aCredentialId)) {
726 0 : Cancel(rv);
727 0 : return;
728 : }
729 :
730 : // If any authenticator returns success:
731 :
732 : // Create a new PublicKeyCredential object named value and populate its fields
733 : // with the values returned from the authenticator as well as the
734 : // clientDataJSON computed earlier.
735 : RefPtr<AuthenticatorAssertionResponse> assertion =
736 0 : new AuthenticatorAssertionResponse(mCurrentParent);
737 0 : assertion->SetClientDataJSON(clientDataBuf);
738 0 : assertion->SetAuthenticatorData(authenticatorDataBuf);
739 0 : assertion->SetSignature(signatureData);
740 :
741 : RefPtr<PublicKeyCredential> credential =
742 0 : new PublicKeyCredential(mCurrentParent);
743 0 : credential->SetRawId(credentialBuf);
744 0 : credential->SetResponse(assertion);
745 :
746 0 : mTransactionPromise->MaybeResolve(credential);
747 0 : MaybeClearTransaction();
748 : }
749 :
750 : void
751 0 : WebAuthnManager::Cancel(const nsresult& aError)
752 : {
753 0 : if (mTransactionPromise) {
754 0 : mTransactionPromise->MaybeReject(aError);
755 : }
756 0 : MaybeClearTransaction();
757 0 : }
758 :
759 : void
760 0 : WebAuthnManager::ActorCreated(PBackgroundChild* aActor)
761 : {
762 0 : MOZ_ASSERT(aActor);
763 :
764 0 : RefPtr<WebAuthnTransactionChild> mgr(new WebAuthnTransactionChild());
765 : PWebAuthnTransactionChild* constructedMgr =
766 0 : aActor->SendPWebAuthnTransactionConstructor(mgr);
767 :
768 0 : if (NS_WARN_IF(!constructedMgr)) {
769 0 : ActorFailed();
770 0 : return;
771 : }
772 0 : MOZ_ASSERT(constructedMgr == mgr);
773 0 : mChild = mgr.forget();
774 0 : mPBackgroundCreationPromise.Resolve(NS_OK, __func__);
775 : }
776 :
777 : void
778 0 : WebAuthnManager::ActorDestroyed()
779 : {
780 0 : mChild = nullptr;
781 0 : }
782 :
783 : void
784 0 : WebAuthnManager::ActorFailed()
785 : {
786 0 : MOZ_CRASH("We shouldn't be here!");
787 : }
788 :
789 : }
790 : }
|