Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=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 file,
5
* You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "hasht.h"
8
#include "nsHTMLDocument.h"
9
#include "nsIURIMutator.h"
10
#include "nsThreadUtils.h"
11
#include "WebAuthnCoseIdentifiers.h"
12
#include "mozilla/dom/AuthenticatorAttestationResponse.h"
13
#include "mozilla/dom/Promise.h"
14
#include "mozilla/dom/PWebAuthnTransaction.h"
15
#include "mozilla/dom/WebAuthnManager.h"
16
#include "mozilla/dom/WebAuthnTransactionChild.h"
17
#include "mozilla/dom/WebAuthnUtil.h"
18
#include "mozilla/ipc/BackgroundChild.h"
19
#include "mozilla/ipc/PBackgroundChild.h"
20
21
#ifdef OS_WIN
22
# include "WinWebAuthnManager.h"
23
#endif
24
25
using namespace mozilla::ipc;
26
27
namespace mozilla {
28
namespace dom {
29
30
/***********************************************************************
31
* Statics
32
**********************************************************************/
33
34
namespace {
35
static mozilla::LazyLogModule gWebAuthnManagerLog("webauthnmanager");
36
}
37
38
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(WebAuthnManager,
39
WebAuthnManagerBase)
40
41
NS_IMPL_CYCLE_COLLECTION_CLASS(WebAuthnManager)
42
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebAuthnManager,
43
WebAuthnManagerBase)
44
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFollowingSignal)
45
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction)
46
tmp->mTransaction.reset();
47
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
48
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebAuthnManager,
49
WebAuthnManagerBase)
50
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFollowingSignal)
51
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
52
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
53
54
/***********************************************************************
55
* Utility Functions
56
**********************************************************************/
57
58
static nsresult AssembleClientData(
59
const nsAString& aOrigin, const CryptoBuffer& aChallenge,
60
const nsAString& aType,
61
const AuthenticationExtensionsClientInputs& aExtensions,
62
/* out */ nsACString& aJsonOut) {
63
MOZ_ASSERT(NS_IsMainThread());
64
65
nsString challengeBase64;
66
nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
67
if (NS_WARN_IF(NS_FAILED(rv))) {
68
return NS_ERROR_FAILURE;
69
}
70
71
CollectedClientData clientDataObject;
72
clientDataObject.mType.Assign(aType);
73
clientDataObject.mChallenge.Assign(challengeBase64);
74
clientDataObject.mOrigin.Assign(aOrigin);
75
clientDataObject.mHashAlgorithm.AssignLiteral(u"SHA-256");
76
clientDataObject.mClientExtensions = aExtensions;
77
78
nsAutoString temp;
79
if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
80
return NS_ERROR_FAILURE;
81
}
82
83
aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
84
return NS_OK;
85
}
86
87
nsresult GetOrigin(nsPIDOMWindowInner* aParent,
88
/*out*/ nsAString& aOrigin, /*out*/ nsACString& aHost) {
89
MOZ_ASSERT(aParent);
90
nsCOMPtr<Document> doc = aParent->GetDoc();
91
MOZ_ASSERT(doc);
92
93
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
94
nsresult rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
95
if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(aOrigin.IsEmpty())) {
96
return NS_ERROR_FAILURE;
97
}
98
99
if (aOrigin.EqualsLiteral("null")) {
100
// 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
101
// DOMException whose name is "NotAllowedError", and terminate this
102
// algorithm
103
MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug,
104
("Rejecting due to opaque origin"));
105
return NS_ERROR_DOM_NOT_ALLOWED_ERR;
106
}
107
108
nsCOMPtr<nsIURI> originUri;
109
if (NS_FAILED(principal->GetURI(getter_AddRefs(originUri)))) {
110
return NS_ERROR_FAILURE;
111
}
112
if (NS_FAILED(originUri->GetAsciiHost(aHost))) {
113
return NS_ERROR_FAILURE;
114
}
115
116
return NS_OK;
117
}
118
119
nsresult RelaxSameOrigin(nsPIDOMWindowInner* aParent,
120
const nsAString& aInputRpId,
121
/* out */ nsACString& aRelaxedRpId) {
122
MOZ_ASSERT(aParent);
123
nsCOMPtr<Document> doc = aParent->GetDoc();
124
MOZ_ASSERT(doc);
125
126
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
127
nsCOMPtr<nsIURI> uri;
128
if (NS_FAILED(principal->GetURI(getter_AddRefs(uri)))) {
129
return NS_ERROR_FAILURE;
130
}
131
nsAutoCString originHost;
132
if (NS_FAILED(uri->GetAsciiHost(originHost))) {
133
return NS_ERROR_FAILURE;
134
}
135
nsCOMPtr<Document> document = aParent->GetDoc();
136
if (!document || !document->IsHTMLDocument()) {
137
return NS_ERROR_FAILURE;
138
}
139
nsHTMLDocument* html = document->AsHTMLDocument();
140
// See if the given RP ID is a valid domain string.
141
// (We use the document's URI here as a template so we don't have to come up
142
// with our own scheme, etc. If we can successfully set the host as the given
143
// RP ID, then it should be a valid domain string.)
144
nsCOMPtr<nsIURI> inputRpIdURI;
145
nsresult rv = NS_MutateURI(uri)
146
.SetHost(NS_ConvertUTF16toUTF8(aInputRpId))
147
.Finalize(inputRpIdURI);
148
if (NS_FAILED(rv)) {
149
return NS_ERROR_DOM_SECURITY_ERR;
150
}
151
nsAutoCString inputRpId;
152
if (NS_FAILED(inputRpIdURI->GetAsciiHost(inputRpId))) {
153
return NS_ERROR_FAILURE;
154
}
155
if (!html->IsRegistrableDomainSuffixOfOrEqualTo(
156
NS_ConvertUTF8toUTF16(inputRpId), originHost)) {
157
return NS_ERROR_DOM_SECURITY_ERR;
158
}
159
160
aRelaxedRpId.Assign(inputRpId);
161
return NS_OK;
162
}
163
164
/***********************************************************************
165
* WebAuthnManager Implementation
166
**********************************************************************/
167
168
void WebAuthnManager::ClearTransaction() {
169
if (!mTransaction.isNothing()) {
170
StopListeningForVisibilityEvents();
171
}
172
173
mTransaction.reset();
174
Unfollow();
175
}
176
177
void WebAuthnManager::RejectTransaction(const nsresult& aError) {
178
if (!NS_WARN_IF(mTransaction.isNothing())) {
179
mTransaction.ref().mPromise->MaybeReject(aError);
180
}
181
182
ClearTransaction();
183
}
184
185
void WebAuthnManager::CancelTransaction(const nsresult& aError) {
186
if (!NS_WARN_IF(!mChild || mTransaction.isNothing())) {
187
mChild->SendRequestCancel(mTransaction.ref().mId);
188
}
189
190
RejectTransaction(aError);
191
}
192
193
void WebAuthnManager::HandleVisibilityChange() {
194
if (mTransaction.isSome()) {
195
mTransaction.ref().mVisibilityChanged = true;
196
}
197
}
198
199
WebAuthnManager::~WebAuthnManager() {
200
MOZ_ASSERT(NS_IsMainThread());
201
202
if (mTransaction.isSome()) {
203
ClearTransaction();
204
}
205
206
if (mChild) {
207
RefPtr<WebAuthnTransactionChild> c;
208
mChild.swap(c);
209
c->Disconnect();
210
}
211
}
212
213
already_AddRefed<Promise> WebAuthnManager::MakeCredential(
214
const PublicKeyCredentialCreationOptions& aOptions,
215
const Optional<OwningNonNull<AbortSignal>>& aSignal) {
216
MOZ_ASSERT(NS_IsMainThread());
217
218
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
219
220
ErrorResult rv;
221
RefPtr<Promise> promise = Promise::Create(global, rv);
222
if (rv.Failed()) {
223
return nullptr;
224
}
225
226
if (mTransaction.isSome()) {
227
// If there hasn't been a visibility change during the current
228
// transaction, then let's let that one complete rather than
229
// cancelling it on a subsequent call.
230
if (!mTransaction.ref().mVisibilityChanged) {
231
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
232
return promise.forget();
233
}
234
235
// Otherwise, the user may well have clicked away, so let's
236
// abort the old transaction and take over control from here.
237
CancelTransaction(NS_ERROR_ABORT);
238
}
239
240
// Abort the request if aborted flag is already set.
241
if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
242
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
243
return promise.forget();
244
}
245
246
nsString origin;
247
nsCString rpId;
248
rv = GetOrigin(mParent, origin, rpId);
249
if (NS_WARN_IF(rv.Failed())) {
250
promise->MaybeReject(rv);
251
return promise.forget();
252
}
253
254
// Enforce 5.4.3 User Account Parameters for Credential Generation
255
// When we add UX, we'll want to do more with this value, but for now
256
// we just have to verify its correctness.
257
258
CryptoBuffer userId;
259
userId.Assign(aOptions.mUser.mId);
260
if (userId.Length() > 64) {
261
promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
262
return promise.forget();
263
}
264
265
// If timeoutSeconds was specified, check if its value lies within a
266
// reasonable range as defined by the platform and if not, correct it to the
267
// closest value lying within that range.
268
269
uint32_t adjustedTimeout = 30000;
270
if (aOptions.mTimeout.WasPassed()) {
271
adjustedTimeout = aOptions.mTimeout.Value();
272
adjustedTimeout = std::max(15000u, adjustedTimeout);
273
adjustedTimeout = std::min(120000u, adjustedTimeout);
274
}
275
276
if (aOptions.mRp.mId.WasPassed()) {
277
// If rpId is specified, then invoke the procedure used for relaxing the
278
// same-origin restriction by setting the document.domain attribute, using
279
// rpId as the given value but without changing the current document’s
280
// domain. If no errors are thrown, set rpId to the value of host as
281
// computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
282
// Otherwise, reject promise with a DOMException whose name is
283
// "SecurityError", and terminate this algorithm.
284
285
if (NS_FAILED(RelaxSameOrigin(mParent, aOptions.mRp.mId.Value(), rpId))) {
286
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
287
return promise.forget();
288
}
289
}
290
292
if (aOptions.mExtensions.mAppid.WasPassed()) {
293
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
294
return promise.forget();
295
}
296
297
// Process each element of mPubKeyCredParams using the following steps, to
298
// produce a new sequence coseAlgos.
299
nsTArray<CoseAlg> coseAlgos;
300
for (size_t a = 0; a < aOptions.mPubKeyCredParams.Length(); ++a) {
301
// If current.type does not contain a PublicKeyCredentialType
302
// supported by this implementation, then stop processing current and move
303
// on to the next element in mPubKeyCredParams.
304
if (aOptions.mPubKeyCredParams[a].mType !=
305
PublicKeyCredentialType::Public_key) {
306
continue;
307
}
308
309
coseAlgos.AppendElement(aOptions.mPubKeyCredParams[a].mAlg);
310
}
311
312
// If there are algorithms specified, but none are Public_key algorithms,
313
// reject the promise.
314
if (coseAlgos.IsEmpty() && !aOptions.mPubKeyCredParams.IsEmpty()) {
315
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
316
return promise.forget();
317
}
318
319
// If excludeList is undefined, set it to the empty list.
320
//
321
// If extensions was specified, process any extensions supported by this
322
// client platform, to produce the extension data that needs to be sent to the
323
// authenticator. If an error is encountered while processing an extension,
324
// skip that extension and do not produce any extension data for it. Call the
325
// result of this processing clientExtensions.
326
//
327
// Currently no extensions are supported
328
//
329
// Use attestationChallenge, callerOrigin and rpId, along with the token
330
// binding key associated with callerOrigin (if any), to create a ClientData
331
// structure representing this request. Choose a hash algorithm for hashAlg
332
// and compute the clientDataJSON and clientDataHash.
333
334
CryptoBuffer challenge;
335
if (!challenge.Assign(aOptions.mChallenge)) {
336
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
337
return promise.forget();
338
}
339
340
nsAutoCString clientDataJSON;
341
nsresult srv = AssembleClientData(origin, challenge,
342
NS_LITERAL_STRING("webauthn.create"),
343
aOptions.mExtensions, clientDataJSON);
344
if (NS_WARN_IF(NS_FAILED(srv))) {
345
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
346
return promise.forget();
347
}
348
349
nsTArray<WebAuthnScopedCredential> excludeList;
350
for (const auto& s : aOptions.mExcludeCredentials) {
351
WebAuthnScopedCredential c;
352
CryptoBuffer cb;
353
cb.Assign(s.mId);
354
c.id() = cb;
355
excludeList.AppendElement(c);
356
}
357
358
if (!MaybeCreateBackgroundActor()) {
359
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
360
return promise.forget();
361
}
362
363
// TODO: Add extension list building
364
nsTArray<WebAuthnExtension> extensions;
365
367
if (aOptions.mExtensions.mHmacCreateSecret.WasPassed()) {
368
bool hmacCreateSecret = aOptions.mExtensions.mHmacCreateSecret.Value();
369
if (hmacCreateSecret) {
370
extensions.AppendElement(WebAuthnExtensionHmacSecret(hmacCreateSecret));
371
}
372
}
373
374
const auto& selection = aOptions.mAuthenticatorSelection;
375
const auto& attachment = selection.mAuthenticatorAttachment;
376
const AttestationConveyancePreference& attestation = aOptions.mAttestation;
377
378
// Attachment
379
Maybe<AuthenticatorAttachment> authenticatorAttachment;
380
if (attachment.WasPassed()) {
381
authenticatorAttachment.emplace(attachment.Value());
382
}
383
384
// Create and forward authenticator selection criteria.
385
WebAuthnAuthenticatorSelection authSelection(selection.mRequireResidentKey,
386
selection.mUserVerification,
387
authenticatorAttachment);
388
389
nsString rpIcon;
390
if (aOptions.mRp.mIcon.WasPassed()) {
391
rpIcon = aOptions.mRp.mIcon.Value();
392
}
393
394
nsString userIcon;
395
if (aOptions.mUser.mIcon.WasPassed()) {
396
userIcon = aOptions.mUser.mIcon.Value();
397
}
398
399
WebAuthnMakeCredentialRpInfo rpInfo(aOptions.mRp.mName, rpIcon);
400
401
WebAuthnMakeCredentialUserInfo userInfo(
402
userId, aOptions.mUser.mName, userIcon, aOptions.mUser.mDisplayName);
403
404
WebAuthnMakeCredentialExtraInfo extra(rpInfo, userInfo, coseAlgos, extensions,
405
authSelection, attestation);
406
407
WebAuthnMakeCredentialInfo info(origin, NS_ConvertUTF8toUTF16(rpId),
408
challenge, clientDataJSON, adjustedTimeout,
409
excludeList, Some(extra));
410
411
#ifdef OS_WIN
412
if (!WinWebAuthnManager::AreWebAuthNApisAvailable()) {
413
ListenForVisibilityEvents();
414
}
415
#else
416
ListenForVisibilityEvents();
417
#endif
418
419
AbortSignal* signal = nullptr;
420
if (aSignal.WasPassed()) {
421
signal = &aSignal.Value();
422
Follow(signal);
423
}
424
425
MOZ_ASSERT(mTransaction.isNothing());
426
mTransaction = Some(WebAuthnTransaction(promise));
427
mChild->SendRequestRegister(mTransaction.ref().mId, info);
428
429
return promise.forget();
430
}
431
432
already_AddRefed<Promise> WebAuthnManager::GetAssertion(
433
const PublicKeyCredentialRequestOptions& aOptions,
434
const Optional<OwningNonNull<AbortSignal>>& aSignal) {
435
MOZ_ASSERT(NS_IsMainThread());
436
437
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
438
439
ErrorResult rv;
440
RefPtr<Promise> promise = Promise::Create(global, rv);
441
if (rv.Failed()) {
442
return nullptr;
443
}
444
445
if (mTransaction.isSome()) {
446
// If there hasn't been a visibility change during the current
447
// transaction, then let's let that one complete rather than
448
// cancelling it on a subsequent call.
449
if (!mTransaction.ref().mVisibilityChanged) {
450
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
451
return promise.forget();
452
}
453
454
// Otherwise, the user may well have clicked away, so let's
455
// abort the old transaction and take over control from here.
456
CancelTransaction(NS_ERROR_ABORT);
457
}
458
459
// Abort the request if aborted flag is already set.
460
if (aSignal.WasPassed() && aSignal.Value().Aborted()) {
461
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
462
return promise.forget();
463
}
464
465
nsString origin;
466
nsCString rpId;
467
rv = GetOrigin(mParent, origin, rpId);
468
if (NS_WARN_IF(rv.Failed())) {
469
promise->MaybeReject(rv);
470
return promise.forget();
471
}
472
473
// If timeoutSeconds was specified, check if its value lies within a
474
// reasonable range as defined by the platform and if not, correct it to the
475
// closest value lying within that range.
476
477
uint32_t adjustedTimeout = 30000;
478
if (aOptions.mTimeout.WasPassed()) {
479
adjustedTimeout = aOptions.mTimeout.Value();
480
adjustedTimeout = std::max(15000u, adjustedTimeout);
481
adjustedTimeout = std::min(120000u, adjustedTimeout);
482
}
483
484
if (aOptions.mRpId.WasPassed()) {
485
// If rpId is specified, then invoke the procedure used for relaxing the
486
// same-origin restriction by setting the document.domain attribute, using
487
// rpId as the given value but without changing the current document’s
488
// domain. If no errors are thrown, set rpId to the value of host as
489
// computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
490
// Otherwise, reject promise with a DOMException whose name is
491
// "SecurityError", and terminate this algorithm.
492
493
if (NS_FAILED(RelaxSameOrigin(mParent, aOptions.mRpId.Value(), rpId))) {
494
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
495
return promise.forget();
496
}
497
}
498
499
CryptoBuffer rpIdHash;
500
if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
501
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
502
return promise.forget();
503
}
504
505
// Use assertionChallenge, callerOrigin and rpId, along with the token binding
506
// key associated with callerOrigin (if any), to create a ClientData structure
507
// representing this request. Choose a hash algorithm for hashAlg and compute
508
// the clientDataJSON and clientDataHash.
509
CryptoBuffer challenge;
510
if (!challenge.Assign(aOptions.mChallenge)) {
511
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
512
return promise.forget();
513
}
514
515
nsAutoCString clientDataJSON;
516
nsresult srv =
517
AssembleClientData(origin, challenge, NS_LITERAL_STRING("webauthn.get"),
518
aOptions.mExtensions, clientDataJSON);
519
if (NS_WARN_IF(NS_FAILED(srv))) {
520
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
521
return promise.forget();
522
}
523
524
nsTArray<WebAuthnScopedCredential> allowList;
525
for (const auto& s : aOptions.mAllowCredentials) {
526
if (s.mType == PublicKeyCredentialType::Public_key) {
527
WebAuthnScopedCredential c;
528
CryptoBuffer cb;
529
cb.Assign(s.mId);
530
c.id() = cb;
531
532
// Serialize transports.
533
if (s.mTransports.WasPassed()) {
534
uint8_t transports = 0;
535
536
// Transports is a string, but we match it to an enumeration so
537
// that we have forward-compatibility, ignoring unknown transports.
538
for (const nsAString& str : s.mTransports.Value()) {
539
NS_ConvertUTF16toUTF8 cStr(str);
540
int i = FindEnumStringIndexImpl(
541
cStr.get(), cStr.Length(), AuthenticatorTransportValues::strings);
542
if (i < 0) {
543
continue; // Unknown enum
544
}
545
AuthenticatorTransport t = static_cast<AuthenticatorTransport>(i);
546
547
if (t == AuthenticatorTransport::Usb) {
548
transports |= U2F_AUTHENTICATOR_TRANSPORT_USB;
549
}
550
if (t == AuthenticatorTransport::Nfc) {
551
transports |= U2F_AUTHENTICATOR_TRANSPORT_NFC;
552
}
553
if (t == AuthenticatorTransport::Ble) {
554
transports |= U2F_AUTHENTICATOR_TRANSPORT_BLE;
555
}
556
if (t == AuthenticatorTransport::Internal) {
557
transports |= CTAP_AUTHENTICATOR_TRANSPORT_INTERNAL;
558
}
559
}
560
c.transports() = transports;
561
}
562
563
allowList.AppendElement(c);
564
}
565
}
566
567
if (!MaybeCreateBackgroundActor()) {
568
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
569
return promise.forget();
570
}
571
572
// If extensions were specified, process any extensions supported by this
573
// client platform, to produce the extension data that needs to be sent to the
574
// authenticator. If an error is encountered while processing an extension,
575
// skip that extension and do not produce any extension data for it. Call the
576
// result of this processing clientExtensions.
577
nsTArray<WebAuthnExtension> extensions;
578
580
if (aOptions.mExtensions.mAppid.WasPassed()) {
581
nsString appId(aOptions.mExtensions.mAppid.Value());
582
583
// Check that the appId value is allowed.
584
if (!EvaluateAppID(mParent, origin, appId)) {
585
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
586
return promise.forget();
587
}
588
589
CryptoBuffer appIdHash;
590
if (!appIdHash.SetLength(SHA256_LENGTH, fallible)) {
591
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
592
return promise.forget();
593
}
594
595
// We need the SHA-256 hash of the appId.
596
srv = HashCString(NS_ConvertUTF16toUTF8(appId), appIdHash);
597
if (NS_WARN_IF(NS_FAILED(srv))) {
598
promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
599
return promise.forget();
600
}
601
602
// Append the hash and send it to the backend.
603
extensions.AppendElement(WebAuthnExtensionAppId(appIdHash, appId));
604
}
605
606
WebAuthnGetAssertionExtraInfo extra(extensions, aOptions.mUserVerification);
607
608
WebAuthnGetAssertionInfo info(origin, NS_ConvertUTF8toUTF16(rpId), challenge,
609
clientDataJSON, adjustedTimeout, allowList,
610
Some(extra));
611
612
#ifdef OS_WIN
613
if (!WinWebAuthnManager::AreWebAuthNApisAvailable()) {
614
ListenForVisibilityEvents();
615
}
616
#else
617
ListenForVisibilityEvents();
618
#endif
619
620
AbortSignal* signal = nullptr;
621
if (aSignal.WasPassed()) {
622
signal = &aSignal.Value();
623
Follow(signal);
624
}
625
626
MOZ_ASSERT(mTransaction.isNothing());
627
mTransaction = Some(WebAuthnTransaction(promise));
628
mChild->SendRequestSign(mTransaction.ref().mId, info);
629
630
return promise.forget();
631
}
632
633
already_AddRefed<Promise> WebAuthnManager::Store(
634
const Credential& aCredential) {
635
MOZ_ASSERT(NS_IsMainThread());
636
637
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
638
639
ErrorResult rv;
640
RefPtr<Promise> promise = Promise::Create(global, rv);
641
if (rv.Failed()) {
642
return nullptr;
643
}
644
645
if (mTransaction.isSome()) {
646
// If there hasn't been a visibility change during the current
647
// transaction, then let's let that one complete rather than
648
// cancelling it on a subsequent call.
649
if (!mTransaction.ref().mVisibilityChanged) {
650
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
651
return promise.forget();
652
}
653
654
// Otherwise, the user may well have clicked away, so let's
655
// abort the old transaction and take over control from here.
656
CancelTransaction(NS_ERROR_ABORT);
657
}
658
659
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
660
return promise.forget();
661
}
662
663
void WebAuthnManager::FinishMakeCredential(
664
const uint64_t& aTransactionId,
665
const WebAuthnMakeCredentialResult& aResult) {
666
MOZ_ASSERT(NS_IsMainThread());
667
668
// Check for a valid transaction.
669
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
670
return;
671
}
672
673
CryptoBuffer clientDataBuf;
674
if (NS_WARN_IF(!clientDataBuf.Assign(aResult.ClientDataJSON()))) {
675
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
676
return;
677
}
678
679
CryptoBuffer attObjBuf;
680
if (NS_WARN_IF(!attObjBuf.Assign(aResult.AttestationObject()))) {
681
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
682
return;
683
}
684
685
CryptoBuffer keyHandleBuf;
686
if (NS_WARN_IF(!keyHandleBuf.Assign(aResult.KeyHandle()))) {
687
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
688
return;
689
}
690
691
nsAutoString keyHandleBase64Url;
692
nsresult rv = keyHandleBuf.ToJwkBase64(keyHandleBase64Url);
693
if (NS_WARN_IF(NS_FAILED(rv))) {
694
RejectTransaction(rv);
695
return;
696
}
697
698
// Create a new PublicKeyCredential object and populate its fields with the
699
// values returned from the authenticator as well as the clientDataJSON
700
// computed earlier.
701
RefPtr<AuthenticatorAttestationResponse> attestation =
702
new AuthenticatorAttestationResponse(mParent);
703
attestation->SetClientDataJSON(clientDataBuf);
704
attestation->SetAttestationObject(attObjBuf);
705
706
RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mParent);
707
credential->SetId(keyHandleBase64Url);
708
credential->SetType(NS_LITERAL_STRING("public-key"));
709
credential->SetRawId(keyHandleBuf);
710
credential->SetResponse(attestation);
711
712
// Forward client extension results.
713
for (auto& ext : aResult.Extensions()) {
714
if (ext.type() ==
715
WebAuthnExtensionResult::TWebAuthnExtensionResultHmacSecret) {
716
bool hmacCreateSecret =
717
ext.get_WebAuthnExtensionResultHmacSecret().hmacCreateSecret();
718
credential->SetClientExtensionResultHmacSecret(hmacCreateSecret);
719
}
720
}
721
722
mTransaction.ref().mPromise->MaybeResolve(credential);
723
ClearTransaction();
724
}
725
726
void WebAuthnManager::FinishGetAssertion(
727
const uint64_t& aTransactionId, const WebAuthnGetAssertionResult& aResult) {
728
MOZ_ASSERT(NS_IsMainThread());
729
730
// Check for a valid transaction.
731
if (mTransaction.isNothing() || mTransaction.ref().mId != aTransactionId) {
732
return;
733
}
734
735
CryptoBuffer clientDataBuf;
736
if (!clientDataBuf.Assign(aResult.ClientDataJSON())) {
737
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
738
return;
739
}
740
741
CryptoBuffer credentialBuf;
742
if (!credentialBuf.Assign(aResult.KeyHandle())) {
743
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
744
return;
745
}
746
747
CryptoBuffer signatureBuf;
748
if (!signatureBuf.Assign(aResult.Signature())) {
749
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
750
return;
751
}
752
753
CryptoBuffer authenticatorDataBuf;
754
if (!authenticatorDataBuf.Assign(aResult.AuthenticatorData())) {
755
RejectTransaction(NS_ERROR_OUT_OF_MEMORY);
756
return;
757
}
758
759
nsAutoString credentialBase64Url;
760
nsresult rv = credentialBuf.ToJwkBase64(credentialBase64Url);
761
if (NS_WARN_IF(NS_FAILED(rv))) {
762
RejectTransaction(rv);
763
return;
764
}
765
766
CryptoBuffer userHandleBuf;
767
// U2FTokenManager don't return user handle.
768
// Best effort.
769
userHandleBuf.Assign(aResult.UserHandle());
770
771
// If any authenticator returns success:
772
773
// Create a new PublicKeyCredential object named value and populate its fields
774
// with the values returned from the authenticator as well as the
775
// clientDataJSON computed earlier.
776
RefPtr<AuthenticatorAssertionResponse> assertion =
777
new AuthenticatorAssertionResponse(mParent);
778
assertion->SetClientDataJSON(clientDataBuf);
779
assertion->SetAuthenticatorData(authenticatorDataBuf);
780
assertion->SetSignature(signatureBuf);
781
if (!userHandleBuf.IsEmpty()) {
782
assertion->SetUserHandle(userHandleBuf);
783
}
784
785
RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mParent);
786
credential->SetId(credentialBase64Url);
787
credential->SetType(NS_LITERAL_STRING("public-key"));
788
credential->SetRawId(credentialBuf);
789
credential->SetResponse(assertion);
790
791
// Forward client extension results.
792
for (auto& ext : aResult.Extensions()) {
793
if (ext.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultAppId) {
794
bool appid = ext.get_WebAuthnExtensionResultAppId().AppId();
795
credential->SetClientExtensionResultAppId(appid);
796
}
797
}
798
799
mTransaction.ref().mPromise->MaybeResolve(credential);
800
ClearTransaction();
801
}
802
803
void WebAuthnManager::RequestAborted(const uint64_t& aTransactionId,
804
const nsresult& aError) {
805
MOZ_ASSERT(NS_IsMainThread());
806
807
if (mTransaction.isSome() && mTransaction.ref().mId == aTransactionId) {
808
RejectTransaction(aError);
809
}
810
}
811
812
void WebAuthnManager::Abort() { CancelTransaction(NS_ERROR_DOM_ABORT_ERR); }
813
814
} // namespace dom
815
} // namespace mozilla