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/. */
8 #include "nsHTMLDocument.h"
9 #include "nsIURIMutator.h"
10 #include "nsThreadUtils.h"
11 #include "WebAuthnCoseIdentifiers.h"
12 #include "WebAuthnEnumStrings.h"
13 #include "WebAuthnTransportIdentifiers.h"
14 #include "mozilla/Base64.h"
15 #include "mozilla/BasePrincipal.h"
16 #include "mozilla/dom/AuthenticatorAssertionResponse.h"
17 #include "mozilla/dom/AuthenticatorAttestationResponse.h"
18 #include "mozilla/dom/PublicKeyCredential.h"
19 #include "mozilla/dom/Promise.h"
20 #include "mozilla/dom/PWebAuthnTransaction.h"
21 #include "mozilla/dom/WebAuthnManager.h"
22 #include "mozilla/dom/WebAuthnTransactionChild.h"
23 #include "mozilla/dom/WebAuthnUtil.h"
24 #include "mozilla/ipc/BackgroundChild.h"
25 #include "mozilla/ipc/PBackgroundChild.h"
28 # include "WinWebAuthnManager.h"
31 using namespace mozilla::ipc
;
33 namespace mozilla::dom
{
35 /***********************************************************************
37 **********************************************************************/
40 static mozilla::LazyLogModule
gWebAuthnManagerLog("webauthnmanager");
43 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(WebAuthnManager
,
46 NS_IMPL_CYCLE_COLLECTION_CLASS(WebAuthnManager
)
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebAuthnManager
,
50 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction
)
51 tmp
->mTransaction
.reset();
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebAuthnManager
,
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction
)
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
59 /***********************************************************************
61 **********************************************************************/
63 static nsresult
AssembleClientData(
64 const nsAString
& aOrigin
, const CryptoBuffer
& aChallenge
,
65 const nsAString
& aType
,
66 const AuthenticationExtensionsClientInputs
& aExtensions
,
67 /* out */ nsACString
& aJsonOut
) {
68 MOZ_ASSERT(NS_IsMainThread());
70 nsString challengeBase64
;
71 nsresult rv
= aChallenge
.ToJwkBase64(challengeBase64
);
72 if (NS_WARN_IF(NS_FAILED(rv
))) {
73 return NS_ERROR_FAILURE
;
76 CollectedClientData clientDataObject
;
77 clientDataObject
.mType
.Assign(aType
);
78 clientDataObject
.mChallenge
.Assign(challengeBase64
);
79 clientDataObject
.mOrigin
.Assign(aOrigin
);
82 if (NS_WARN_IF(!clientDataObject
.ToJSON(temp
))) {
83 return NS_ERROR_FAILURE
;
86 aJsonOut
.Assign(NS_ConvertUTF16toUTF8(temp
));
90 nsresult
GetOrigin(nsPIDOMWindowInner
* aParent
,
91 /*out*/ nsAString
& aOrigin
, /*out*/ nsACString
& aHost
) {
93 nsCOMPtr
<Document
> doc
= aParent
->GetDoc();
96 nsCOMPtr
<nsIPrincipal
> principal
= doc
->NodePrincipal();
98 nsContentUtils::GetWebExposedOriginSerialization(principal
, aOrigin
);
99 if (NS_WARN_IF(NS_FAILED(rv
)) || NS_WARN_IF(aOrigin
.IsEmpty())) {
100 return NS_ERROR_FAILURE
;
103 if (principal
->GetIsIpAddress()) {
104 return NS_ERROR_DOM_SECURITY_ERR
;
107 if (aOrigin
.EqualsLiteral("null")) {
108 // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
109 // DOMException whose name is "NotAllowedError", and terminate this
111 MOZ_LOG(gWebAuthnManagerLog
, LogLevel::Debug
,
112 ("Rejecting due to opaque origin"));
113 return NS_ERROR_DOM_NOT_ALLOWED_ERR
;
116 nsCOMPtr
<nsIURI
> originUri
;
117 auto* basePrin
= BasePrincipal::Cast(principal
);
118 if (NS_FAILED(basePrin
->GetURI(getter_AddRefs(originUri
)))) {
119 return NS_ERROR_FAILURE
;
121 if (NS_FAILED(originUri
->GetAsciiHost(aHost
))) {
122 return NS_ERROR_FAILURE
;
128 nsresult
RelaxSameOrigin(nsPIDOMWindowInner
* aParent
,
129 const nsAString
& aInputRpId
,
130 /* out */ nsACString
& aRelaxedRpId
) {
132 nsCOMPtr
<Document
> doc
= aParent
->GetDoc();
135 nsCOMPtr
<nsIPrincipal
> principal
= doc
->NodePrincipal();
136 auto* basePrin
= BasePrincipal::Cast(principal
);
137 nsCOMPtr
<nsIURI
> uri
;
139 if (NS_FAILED(basePrin
->GetURI(getter_AddRefs(uri
)))) {
140 return NS_ERROR_FAILURE
;
142 nsAutoCString originHost
;
143 if (NS_FAILED(uri
->GetAsciiHost(originHost
))) {
144 return NS_ERROR_FAILURE
;
146 nsCOMPtr
<Document
> document
= aParent
->GetDoc();
147 if (!document
|| !document
->IsHTMLDocument()) {
148 return NS_ERROR_FAILURE
;
150 nsHTMLDocument
* html
= document
->AsHTMLDocument();
151 // See if the given RP ID is a valid domain string.
152 // (We use the document's URI here as a template so we don't have to come up
153 // with our own scheme, etc. If we can successfully set the host as the given
154 // RP ID, then it should be a valid domain string.)
155 nsCOMPtr
<nsIURI
> inputRpIdURI
;
156 nsresult rv
= NS_MutateURI(uri
)
157 .SetHost(NS_ConvertUTF16toUTF8(aInputRpId
))
158 .Finalize(inputRpIdURI
);
160 return NS_ERROR_DOM_SECURITY_ERR
;
162 nsAutoCString inputRpId
;
163 if (NS_FAILED(inputRpIdURI
->GetAsciiHost(inputRpId
))) {
164 return NS_ERROR_FAILURE
;
166 if (!html
->IsRegistrableDomainSuffixOfOrEqualTo(
167 NS_ConvertUTF8toUTF16(inputRpId
), originHost
)) {
168 return NS_ERROR_DOM_SECURITY_ERR
;
171 aRelaxedRpId
.Assign(inputRpId
);
175 /***********************************************************************
176 * WebAuthnManager Implementation
177 **********************************************************************/
179 void WebAuthnManager::ClearTransaction() {
180 if (mTransaction
.isSome()) {
181 StopListeningForVisibilityEvents();
184 mTransaction
.reset();
188 void WebAuthnManager::CancelParent() {
189 if (!NS_WARN_IF(!mChild
|| mTransaction
.isNothing())) {
190 mChild
->SendRequestCancel(mTransaction
.ref().mId
);
194 void WebAuthnManager::HandleVisibilityChange() {
195 if (mTransaction
.isSome()) {
196 mTransaction
.ref().mVisibilityChanged
= true;
200 WebAuthnManager::~WebAuthnManager() {
201 MOZ_ASSERT(NS_IsMainThread());
203 if (mTransaction
.isSome()) {
208 RefPtr
<WebAuthnTransactionChild
> c
;
214 already_AddRefed
<Promise
> WebAuthnManager::MakeCredential(
215 const PublicKeyCredentialCreationOptions
& aOptions
,
216 const Optional
<OwningNonNull
<AbortSignal
>>& aSignal
, ErrorResult
& aError
) {
217 MOZ_ASSERT(NS_IsMainThread());
219 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
221 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
222 if (aError
.Failed()) {
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();
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_DOM_ABORT_ERR
);
242 nsresult rv
= GetOrigin(mParent
, origin
, rpId
);
243 if (NS_WARN_IF(NS_FAILED(rv
))) {
244 promise
->MaybeReject(rv
);
245 return promise
.forget();
248 // Enforce 5.4.3 User Account Parameters for Credential Generation
249 // When we add UX, we'll want to do more with this value, but for now
250 // we just have to verify its correctness.
253 userId
.Assign(aOptions
.mUser
.mId
);
254 if (userId
.Length() > 64) {
255 promise
->MaybeRejectWithTypeError("user.id is too long");
256 return promise
.forget();
259 // If timeoutSeconds was specified, check if its value lies within a
260 // reasonable range as defined by the platform and if not, correct it to the
261 // closest value lying within that range.
263 uint32_t adjustedTimeout
= 30000;
264 if (aOptions
.mTimeout
.WasPassed()) {
265 adjustedTimeout
= aOptions
.mTimeout
.Value();
266 adjustedTimeout
= std::max(15000u, adjustedTimeout
);
267 adjustedTimeout
= std::min(120000u, adjustedTimeout
);
270 if (aOptions
.mRp
.mId
.WasPassed()) {
271 // If rpId is specified, then invoke the procedure used for relaxing the
272 // same-origin restriction by setting the document.domain attribute, using
273 // rpId as the given value but without changing the current document’s
274 // domain. If no errors are thrown, set rpId to the value of host as
275 // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
276 // Otherwise, reject promise with a DOMException whose name is
277 // "SecurityError", and terminate this algorithm.
279 if (NS_FAILED(RelaxSameOrigin(mParent
, aOptions
.mRp
.mId
.Value(), rpId
))) {
280 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
281 return promise
.forget();
285 // <https://w3c.github.io/webauthn/#sctn-appid-extension>
286 if (aOptions
.mExtensions
.mAppid
.WasPassed()) {
287 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
288 return promise
.forget();
291 // Process each element of mPubKeyCredParams using the following steps, to
292 // produce a new sequence of coseAlgos.
293 nsTArray
<CoseAlg
> coseAlgos
;
294 // If pubKeyCredParams is empty, append ES256 and RS256
295 if (aOptions
.mPubKeyCredParams
.IsEmpty()) {
296 coseAlgos
.AppendElement(static_cast<long>(CoseAlgorithmIdentifier::ES256
));
297 coseAlgos
.AppendElement(static_cast<long>(CoseAlgorithmIdentifier::RS256
));
299 for (size_t a
= 0; a
< aOptions
.mPubKeyCredParams
.Length(); ++a
) {
300 // If current.type does not contain a PublicKeyCredentialType
301 // supported by this implementation, then stop processing current and move
302 // on to the next element in mPubKeyCredParams.
303 if (!aOptions
.mPubKeyCredParams
[a
].mType
.EqualsLiteral(
304 MOZ_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_TYPE_PUBLIC_KEY
)) {
308 coseAlgos
.AppendElement(aOptions
.mPubKeyCredParams
[a
].mAlg
);
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();
319 // If excludeList is undefined, set it to the empty list.
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.
327 // Currently no extensions are supported
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.
334 CryptoBuffer challenge
;
335 if (!challenge
.Assign(aOptions
.mChallenge
)) {
336 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
337 return promise
.forget();
340 nsAutoCString clientDataJSON
;
341 nsresult srv
= AssembleClientData(origin
, challenge
, u
"webauthn.create"_ns
,
342 aOptions
.mExtensions
, clientDataJSON
);
343 if (NS_WARN_IF(NS_FAILED(srv
))) {
344 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
345 return promise
.forget();
348 nsTArray
<WebAuthnScopedCredential
> excludeList
;
349 for (const auto& s
: aOptions
.mExcludeCredentials
) {
350 WebAuthnScopedCredential c
;
354 excludeList
.AppendElement(c
);
357 if (!MaybeCreateBackgroundActor()) {
358 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
359 return promise
.forget();
362 // TODO: Add extension list building
363 nsTArray
<WebAuthnExtension
> extensions
;
365 // <https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#sctn-hmac-secret-extension>
366 if (aOptions
.mExtensions
.mHmacCreateSecret
.WasPassed()) {
367 bool hmacCreateSecret
= aOptions
.mExtensions
.mHmacCreateSecret
.Value();
368 if (hmacCreateSecret
) {
369 extensions
.AppendElement(WebAuthnExtensionHmacSecret(hmacCreateSecret
));
373 if (aOptions
.mExtensions
.mCredProps
.WasPassed()) {
374 bool credProps
= aOptions
.mExtensions
.mCredProps
.Value();
376 extensions
.AppendElement(WebAuthnExtensionCredProps(credProps
));
380 if (aOptions
.mExtensions
.mMinPinLength
.WasPassed()) {
381 bool minPinLength
= aOptions
.mExtensions
.mMinPinLength
.Value();
383 extensions
.AppendElement(WebAuthnExtensionMinPinLength(minPinLength
));
387 const auto& selection
= aOptions
.mAuthenticatorSelection
;
388 const auto& attachment
= selection
.mAuthenticatorAttachment
;
389 const nsString
& attestation
= aOptions
.mAttestation
;
392 Maybe
<nsString
> authenticatorAttachment
;
393 if (attachment
.WasPassed()) {
394 authenticatorAttachment
.emplace(attachment
.Value());
397 // The residentKey field was added in WebAuthn level 2. It takes precedent
398 // over the requireResidentKey field if and only if it is present and it is a
399 // member of the ResidentKeyRequirement enum.
400 static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION
== 2);
401 bool useResidentKeyValue
=
402 selection
.mResidentKey
.WasPassed() &&
403 (selection
.mResidentKey
.Value().EqualsLiteral(
404 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED
) ||
405 selection
.mResidentKey
.Value().EqualsLiteral(
406 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED
) ||
407 selection
.mResidentKey
.Value().EqualsLiteral(
408 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED
));
410 nsString residentKey
;
411 if (useResidentKeyValue
) {
412 residentKey
= selection
.mResidentKey
.Value();
414 // "If no value is given then the effective value is required if
415 // requireResidentKey is true or discouraged if it is false or absent."
416 if (selection
.mRequireResidentKey
) {
417 residentKey
.AssignLiteral(MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED
);
419 residentKey
.AssignLiteral(
420 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED
);
424 // Create and forward authenticator selection criteria.
425 WebAuthnAuthenticatorSelection
authSelection(
426 residentKey
, selection
.mUserVerification
, authenticatorAttachment
);
428 WebAuthnMakeCredentialRpInfo
rpInfo(aOptions
.mRp
.mName
);
430 WebAuthnMakeCredentialUserInfo
userInfo(userId
, aOptions
.mUser
.mName
,
431 aOptions
.mUser
.mDisplayName
);
433 BrowsingContext
* context
= mParent
->GetBrowsingContext();
435 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
436 return promise
.forget();
439 // Abort the request if aborted flag is already set.
440 if (aSignal
.WasPassed() && aSignal
.Value().Aborted()) {
442 if (!jsapi
.Init(global
)) {
443 promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
444 return promise
.forget();
446 JSContext
* cx
= jsapi
.cx();
447 JS::Rooted
<JS::Value
> reason(cx
);
448 aSignal
.Value().GetReason(cx
, &reason
);
449 promise
->MaybeReject(reason
);
450 return promise
.forget();
453 WebAuthnMakeCredentialInfo
info(
454 origin
, NS_ConvertUTF8toUTF16(rpId
), challenge
, clientDataJSON
,
455 adjustedTimeout
, excludeList
, rpInfo
, userInfo
, coseAlgos
, extensions
,
456 authSelection
, attestation
, context
->Top()->Id());
458 // Set up the transaction state (including event listeners, etc). Fallible
459 // operations should not be performed below this line, as we must not leave
460 // the transaction state partially initialized. Once the transaction state is
461 // initialized the only valid ways to end the transaction are
462 // CancelTransaction, RejectTransaction, and FinishMakeCredential.
464 if (!WinWebAuthnManager::AreWebAuthNApisAvailable()) {
465 ListenForVisibilityEvents();
468 ListenForVisibilityEvents();
471 AbortSignal
* signal
= nullptr;
472 if (aSignal
.WasPassed()) {
473 signal
= &aSignal
.Value();
477 MOZ_ASSERT(mTransaction
.isNothing());
478 mTransaction
= Some(WebAuthnTransaction(promise
));
479 mChild
->SendRequestRegister(mTransaction
.ref().mId
, info
);
481 return promise
.forget();
484 const size_t MAX_ALLOWED_CREDENTIALS
= 20;
486 already_AddRefed
<Promise
> WebAuthnManager::GetAssertion(
487 const PublicKeyCredentialRequestOptions
& aOptions
,
488 const Optional
<OwningNonNull
<AbortSignal
>>& aSignal
, ErrorResult
& aError
) {
489 MOZ_ASSERT(NS_IsMainThread());
491 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
493 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
494 if (aError
.Failed()) {
498 if (mTransaction
.isSome()) {
499 // If there hasn't been a visibility change during the current
500 // transaction, then let's let that one complete rather than
501 // cancelling it on a subsequent call.
502 if (!mTransaction
.ref().mVisibilityChanged
) {
503 promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
504 return promise
.forget();
507 // Otherwise, the user may well have clicked away, so let's
508 // abort the old transaction and take over control from here.
509 CancelTransaction(NS_ERROR_DOM_ABORT_ERR
);
514 nsresult rv
= GetOrigin(mParent
, origin
, rpId
);
515 if (NS_WARN_IF(NS_FAILED(rv
))) {
516 promise
->MaybeReject(rv
);
517 return promise
.forget();
520 // If timeoutSeconds was specified, check if its value lies within a
521 // reasonable range as defined by the platform and if not, correct it to the
522 // closest value lying within that range.
524 uint32_t adjustedTimeout
= 30000;
525 if (aOptions
.mTimeout
.WasPassed()) {
526 adjustedTimeout
= aOptions
.mTimeout
.Value();
527 adjustedTimeout
= std::max(15000u, adjustedTimeout
);
528 adjustedTimeout
= std::min(120000u, adjustedTimeout
);
531 if (aOptions
.mRpId
.WasPassed()) {
532 // If rpId is specified, then invoke the procedure used for relaxing the
533 // same-origin restriction by setting the document.domain attribute, using
534 // rpId as the given value but without changing the current document’s
535 // domain. If no errors are thrown, set rpId to the value of host as
536 // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
537 // Otherwise, reject promise with a DOMException whose name is
538 // "SecurityError", and terminate this algorithm.
540 if (NS_FAILED(RelaxSameOrigin(mParent
, aOptions
.mRpId
.Value(), rpId
))) {
541 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
542 return promise
.forget();
546 // Abort the request if the allowCredentials set is too large
547 if (aOptions
.mAllowCredentials
.Length() > MAX_ALLOWED_CREDENTIALS
) {
548 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
549 return promise
.forget();
552 // Use assertionChallenge, callerOrigin and rpId, along with the token binding
553 // key associated with callerOrigin (if any), to create a ClientData structure
554 // representing this request. Choose a hash algorithm for hashAlg and compute
555 // the clientDataJSON and clientDataHash.
556 CryptoBuffer challenge
;
557 if (!challenge
.Assign(aOptions
.mChallenge
)) {
558 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
559 return promise
.forget();
562 nsAutoCString clientDataJSON
;
563 rv
= AssembleClientData(origin
, challenge
, u
"webauthn.get"_ns
,
564 aOptions
.mExtensions
, clientDataJSON
);
565 if (NS_WARN_IF(NS_FAILED(rv
))) {
566 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
567 return promise
.forget();
570 nsTArray
<WebAuthnScopedCredential
> allowList
;
571 for (const auto& s
: aOptions
.mAllowCredentials
) {
572 if (s
.mType
.EqualsLiteral(
573 MOZ_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_TYPE_PUBLIC_KEY
)) {
574 WebAuthnScopedCredential c
;
579 // Serialize transports.
580 if (s
.mTransports
.WasPassed()) {
581 uint8_t transports
= 0;
583 // We ignore unknown transports for forward-compatibility, but this
584 // needs to be reviewed if values are added to the
585 // AuthenticatorTransport enum.
586 static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION
== 2);
587 for (const nsAString
& str
: s
.mTransports
.Value()) {
588 if (str
.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_USB
)) {
589 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB
;
590 } else if (str
.EqualsLiteral(
591 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_NFC
)) {
592 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC
;
593 } else if (str
.EqualsLiteral(
594 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_BLE
)) {
595 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE
;
596 } else if (str
.EqualsLiteral(
597 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_INTERNAL
)) {
598 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL
;
601 c
.transports() = transports
;
604 allowList
.AppendElement(c
);
607 if (allowList
.Length() == 0 && aOptions
.mAllowCredentials
.Length() != 0) {
608 promise
->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
609 return promise
.forget();
612 if (!MaybeCreateBackgroundActor()) {
613 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
614 return promise
.forget();
617 // If extensions were specified, process any extensions supported by this
618 // client platform, to produce the extension data that needs to be sent to the
619 // authenticator. If an error is encountered while processing an extension,
620 // skip that extension and do not produce any extension data for it. Call the
621 // result of this processing clientExtensions.
622 nsTArray
<WebAuthnExtension
> extensions
;
624 // credProps is only supported in MakeCredentials
625 if (aOptions
.mExtensions
.mCredProps
.WasPassed()) {
626 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
627 return promise
.forget();
630 // minPinLength is only supported in MakeCredentials
631 if (aOptions
.mExtensions
.mMinPinLength
.WasPassed()) {
632 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
633 return promise
.forget();
636 // <https://w3c.github.io/webauthn/#sctn-appid-extension>
637 if (aOptions
.mExtensions
.mAppid
.WasPassed()) {
638 nsString
appId(aOptions
.mExtensions
.mAppid
.Value());
640 // Check that the appId value is allowed.
641 if (!EvaluateAppID(mParent
, origin
, appId
)) {
642 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
643 return promise
.forget();
646 // Append the hash and send it to the backend.
647 extensions
.AppendElement(WebAuthnExtensionAppId(appId
));
650 BrowsingContext
* context
= mParent
->GetBrowsingContext();
652 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
653 return promise
.forget();
656 // Abort the request if aborted flag is already set.
657 if (aSignal
.WasPassed() && aSignal
.Value().Aborted()) {
659 if (!jsapi
.Init(global
)) {
660 promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
661 return promise
.forget();
663 JSContext
* cx
= jsapi
.cx();
664 JS::Rooted
<JS::Value
> reason(cx
);
665 aSignal
.Value().GetReason(cx
, &reason
);
666 promise
->MaybeReject(reason
);
667 return promise
.forget();
670 WebAuthnGetAssertionInfo
info(origin
, NS_ConvertUTF8toUTF16(rpId
), challenge
,
671 clientDataJSON
, adjustedTimeout
, allowList
,
672 extensions
, aOptions
.mUserVerification
,
673 context
->Top()->Id());
675 // Set up the transaction state (including event listeners, etc). Fallible
676 // operations should not be performed below this line, as we must not leave
677 // the transaction state partially initialized. Once the transaction state is
678 // initialized the only valid ways to end the transaction are
679 // CancelTransaction, RejectTransaction, and FinishGetAssertion.
681 if (!WinWebAuthnManager::AreWebAuthNApisAvailable()) {
682 ListenForVisibilityEvents();
685 ListenForVisibilityEvents();
688 AbortSignal
* signal
= nullptr;
689 if (aSignal
.WasPassed()) {
690 signal
= &aSignal
.Value();
694 MOZ_ASSERT(mTransaction
.isNothing());
695 mTransaction
= Some(WebAuthnTransaction(promise
));
696 mChild
->SendRequestSign(mTransaction
.ref().mId
, info
);
698 return promise
.forget();
701 already_AddRefed
<Promise
> WebAuthnManager::Store(const Credential
& aCredential
,
702 ErrorResult
& aError
) {
703 MOZ_ASSERT(NS_IsMainThread());
705 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
707 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
708 if (aError
.Failed()) {
712 if (mTransaction
.isSome()) {
713 // If there hasn't been a visibility change during the current
714 // transaction, then let's let that one complete rather than
715 // cancelling it on a subsequent call.
716 if (!mTransaction
.ref().mVisibilityChanged
) {
717 promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
718 return promise
.forget();
721 // Otherwise, the user may well have clicked away, so let's
722 // abort the old transaction and take over control from here.
723 CancelTransaction(NS_ERROR_DOM_ABORT_ERR
);
726 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
727 return promise
.forget();
730 void WebAuthnManager::FinishMakeCredential(
731 const uint64_t& aTransactionId
,
732 const WebAuthnMakeCredentialResult
& aResult
) {
733 MOZ_ASSERT(NS_IsMainThread());
735 // Check for a valid transaction.
736 if (mTransaction
.isNothing() || mTransaction
.ref().mId
!= aTransactionId
) {
740 nsAutoCString keyHandleBase64Url
;
741 nsresult rv
= Base64URLEncode(
742 aResult
.KeyHandle().Length(), aResult
.KeyHandle().Elements(),
743 Base64URLEncodePaddingPolicy::Omit
, keyHandleBase64Url
);
744 if (NS_WARN_IF(NS_FAILED(rv
))) {
745 RejectTransaction(rv
);
749 // Create a new PublicKeyCredential object and populate its fields with the
750 // values returned from the authenticator as well as the clientDataJSON
752 RefPtr
<AuthenticatorAttestationResponse
> attestation
=
753 new AuthenticatorAttestationResponse(mParent
);
754 attestation
->SetClientDataJSON(aResult
.ClientDataJSON());
755 attestation
->SetAttestationObject(aResult
.AttestationObject());
756 attestation
->SetTransports(aResult
.Transports());
758 RefPtr
<PublicKeyCredential
> credential
= new PublicKeyCredential(mParent
);
759 credential
->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url
));
760 credential
->SetType(u
"public-key"_ns
);
761 credential
->SetRawId(aResult
.KeyHandle());
762 credential
->SetAttestationResponse(attestation
);
763 credential
->SetAuthenticatorAttachment(aResult
.AuthenticatorAttachment());
765 // Forward client extension results.
766 for (const auto& ext
: aResult
.Extensions()) {
768 WebAuthnExtensionResult::TWebAuthnExtensionResultCredProps
) {
769 bool credPropsRk
= ext
.get_WebAuthnExtensionResultCredProps().rk();
770 credential
->SetClientExtensionResultCredPropsRk(credPropsRk
);
773 WebAuthnExtensionResult::TWebAuthnExtensionResultHmacSecret
) {
774 bool hmacCreateSecret
=
775 ext
.get_WebAuthnExtensionResultHmacSecret().hmacCreateSecret();
776 credential
->SetClientExtensionResultHmacSecret(hmacCreateSecret
);
780 mTransaction
.ref().mPromise
->MaybeResolve(credential
);
784 void WebAuthnManager::FinishGetAssertion(
785 const uint64_t& aTransactionId
, const WebAuthnGetAssertionResult
& aResult
) {
786 MOZ_ASSERT(NS_IsMainThread());
788 // Check for a valid transaction.
789 if (mTransaction
.isNothing() || mTransaction
.ref().mId
!= aTransactionId
) {
793 nsAutoCString keyHandleBase64Url
;
794 nsresult rv
= Base64URLEncode(
795 aResult
.KeyHandle().Length(), aResult
.KeyHandle().Elements(),
796 Base64URLEncodePaddingPolicy::Omit
, keyHandleBase64Url
);
797 if (NS_WARN_IF(NS_FAILED(rv
))) {
798 RejectTransaction(rv
);
802 // Create a new PublicKeyCredential object named value and populate its fields
803 // with the values returned from the authenticator as well as the
804 // clientDataJSON computed earlier.
805 RefPtr
<AuthenticatorAssertionResponse
> assertion
=
806 new AuthenticatorAssertionResponse(mParent
);
807 assertion
->SetClientDataJSON(aResult
.ClientDataJSON());
808 assertion
->SetAuthenticatorData(aResult
.AuthenticatorData());
809 assertion
->SetSignature(aResult
.Signature());
810 assertion
->SetUserHandle(aResult
.UserHandle()); // may be empty
812 RefPtr
<PublicKeyCredential
> credential
= new PublicKeyCredential(mParent
);
813 credential
->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url
));
814 credential
->SetType(u
"public-key"_ns
);
815 credential
->SetRawId(aResult
.KeyHandle());
816 credential
->SetAssertionResponse(assertion
);
817 credential
->SetAuthenticatorAttachment(aResult
.AuthenticatorAttachment());
819 // Forward client extension results.
820 for (const auto& ext
: aResult
.Extensions()) {
821 if (ext
.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultAppId
) {
822 bool appid
= ext
.get_WebAuthnExtensionResultAppId().AppId();
823 credential
->SetClientExtensionResultAppId(appid
);
827 mTransaction
.ref().mPromise
->MaybeResolve(credential
);
831 void WebAuthnManager::RequestAborted(const uint64_t& aTransactionId
,
832 const nsresult
& aError
) {
833 MOZ_ASSERT(NS_IsMainThread());
835 if (mTransaction
.isSome() && mTransaction
.ref().mId
== aTransactionId
) {
836 RejectTransaction(aError
);
840 void WebAuthnManager::RunAbortAlgorithm() {
841 if (NS_WARN_IF(mTransaction
.isNothing())) {
845 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
848 if (!jsapi
.Init(global
)) {
849 CancelTransaction(NS_ERROR_DOM_ABORT_ERR
);
852 JSContext
* cx
= jsapi
.cx();
853 JS::Rooted
<JS::Value
> reason(cx
);
854 Signal()->GetReason(cx
, &reason
);
855 CancelTransaction(reason
);
858 } // namespace mozilla::dom