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/PWebAuthnTransactionChild.h"
22 #include "mozilla/dom/WebAuthnManager.h"
23 #include "mozilla/dom/WebAuthnTransactionChild.h"
24 #include "mozilla/dom/WebAuthnUtil.h"
25 #include "mozilla/ipc/BackgroundChild.h"
26 #include "mozilla/ipc/PBackgroundChild.h"
29 # include "WinWebAuthnService.h"
32 using namespace mozilla::ipc
;
34 namespace mozilla::dom
{
36 /***********************************************************************
38 **********************************************************************/
41 static mozilla::LazyLogModule
gWebAuthnManagerLog("webauthnmanager");
44 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(WebAuthnManager
,
47 NS_IMPL_CYCLE_COLLECTION_CLASS(WebAuthnManager
)
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebAuthnManager
,
51 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransaction
)
52 tmp
->mTransaction
.reset();
53 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebAuthnManager
,
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction
)
58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
60 /***********************************************************************
62 **********************************************************************/
64 static nsresult
AssembleClientData(
65 const nsAString
& aOrigin
, const CryptoBuffer
& aChallenge
,
66 const nsAString
& aType
,
67 const AuthenticationExtensionsClientInputs
& aExtensions
,
68 /* out */ nsACString
& aJsonOut
) {
69 MOZ_ASSERT(NS_IsMainThread());
71 nsString challengeBase64
;
72 nsresult rv
= aChallenge
.ToJwkBase64(challengeBase64
);
73 if (NS_WARN_IF(NS_FAILED(rv
))) {
74 return NS_ERROR_FAILURE
;
77 CollectedClientData clientDataObject
;
78 clientDataObject
.mType
.Assign(aType
);
79 clientDataObject
.mChallenge
.Assign(challengeBase64
);
80 clientDataObject
.mOrigin
.Assign(aOrigin
);
83 if (NS_WARN_IF(!clientDataObject
.ToJSON(temp
))) {
84 return NS_ERROR_FAILURE
;
87 aJsonOut
.Assign(NS_ConvertUTF16toUTF8(temp
));
91 static uint8_t SerializeTransports(
92 const mozilla::dom::Sequence
<nsString
>& aTransports
) {
93 uint8_t transports
= 0;
95 // We ignore unknown transports for forward-compatibility, but this
96 // needs to be reviewed if values are added to the
97 // AuthenticatorTransport enum.
98 static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION
== 3);
99 for (const nsAString
& str
: aTransports
) {
100 if (str
.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_USB
)) {
101 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_USB
;
102 } else if (str
.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_NFC
)) {
103 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_NFC
;
104 } else if (str
.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_BLE
)) {
105 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_BLE
;
106 } else if (str
.EqualsLiteral(
107 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_INTERNAL
)) {
108 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL
;
109 } else if (str
.EqualsLiteral(MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_HYBRID
)) {
110 transports
|= MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID
;
116 nsresult
GetOrigin(nsPIDOMWindowInner
* aParent
,
117 /*out*/ nsAString
& aOrigin
, /*out*/ nsACString
& aHost
) {
119 nsCOMPtr
<Document
> doc
= aParent
->GetDoc();
122 nsCOMPtr
<nsIPrincipal
> principal
= doc
->NodePrincipal();
124 nsContentUtils::GetWebExposedOriginSerialization(principal
, aOrigin
);
125 if (NS_WARN_IF(NS_FAILED(rv
)) || NS_WARN_IF(aOrigin
.IsEmpty())) {
126 return NS_ERROR_FAILURE
;
129 if (principal
->GetIsIpAddress()) {
130 return NS_ERROR_DOM_SECURITY_ERR
;
133 if (aOrigin
.EqualsLiteral("null")) {
134 // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
135 // DOMException whose name is "NotAllowedError", and terminate this
137 MOZ_LOG(gWebAuthnManagerLog
, LogLevel::Debug
,
138 ("Rejecting due to opaque origin"));
139 return NS_ERROR_DOM_NOT_ALLOWED_ERR
;
142 nsCOMPtr
<nsIURI
> originUri
;
143 auto* basePrin
= BasePrincipal::Cast(principal
);
144 if (NS_FAILED(basePrin
->GetURI(getter_AddRefs(originUri
)))) {
145 return NS_ERROR_FAILURE
;
147 if (NS_FAILED(originUri
->GetAsciiHost(aHost
))) {
148 return NS_ERROR_FAILURE
;
154 nsresult
RelaxSameOrigin(nsPIDOMWindowInner
* aParent
,
155 const nsAString
& aInputRpId
,
156 /* out */ nsACString
& aRelaxedRpId
) {
158 nsCOMPtr
<Document
> doc
= aParent
->GetDoc();
161 nsCOMPtr
<nsIPrincipal
> principal
= doc
->NodePrincipal();
162 auto* basePrin
= BasePrincipal::Cast(principal
);
163 nsCOMPtr
<nsIURI
> uri
;
165 if (NS_FAILED(basePrin
->GetURI(getter_AddRefs(uri
)))) {
166 return NS_ERROR_FAILURE
;
168 nsAutoCString originHost
;
169 if (NS_FAILED(uri
->GetAsciiHost(originHost
))) {
170 return NS_ERROR_FAILURE
;
172 nsCOMPtr
<Document
> document
= aParent
->GetDoc();
173 if (!document
|| !document
->IsHTMLDocument()) {
174 return NS_ERROR_FAILURE
;
176 nsHTMLDocument
* html
= document
->AsHTMLDocument();
177 // See if the given RP ID is a valid domain string.
178 // (We use the document's URI here as a template so we don't have to come up
179 // with our own scheme, etc. If we can successfully set the host as the given
180 // RP ID, then it should be a valid domain string.)
181 nsCOMPtr
<nsIURI
> inputRpIdURI
;
182 nsresult rv
= NS_MutateURI(uri
)
183 .SetHost(NS_ConvertUTF16toUTF8(aInputRpId
))
184 .Finalize(inputRpIdURI
);
186 return NS_ERROR_DOM_SECURITY_ERR
;
188 nsAutoCString inputRpId
;
189 if (NS_FAILED(inputRpIdURI
->GetAsciiHost(inputRpId
))) {
190 return NS_ERROR_FAILURE
;
192 if (!html
->IsRegistrableDomainSuffixOfOrEqualTo(
193 NS_ConvertUTF8toUTF16(inputRpId
), originHost
)) {
194 return NS_ERROR_DOM_SECURITY_ERR
;
197 aRelaxedRpId
.Assign(inputRpId
);
201 /***********************************************************************
202 * WebAuthnManager Implementation
203 **********************************************************************/
205 void WebAuthnManager::ClearTransaction() {
206 mTransaction
.reset();
210 void WebAuthnManager::CancelParent() {
211 if (!NS_WARN_IF(!mChild
|| mTransaction
.isNothing())) {
212 mChild
->SendRequestCancel(mTransaction
.ref().mId
);
216 WebAuthnManager::~WebAuthnManager() {
217 MOZ_ASSERT(NS_IsMainThread());
219 if (mTransaction
.isSome()) {
224 RefPtr
<WebAuthnTransactionChild
> c
;
230 already_AddRefed
<Promise
> WebAuthnManager::MakeCredential(
231 const PublicKeyCredentialCreationOptions
& aOptions
,
232 const Optional
<OwningNonNull
<AbortSignal
>>& aSignal
, ErrorResult
& aError
) {
233 MOZ_ASSERT(NS_IsMainThread());
235 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
237 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
238 if (aError
.Failed()) {
242 if (mTransaction
.isSome()) {
243 // abort the old transaction and take over control from here.
244 CancelTransaction(NS_ERROR_DOM_ABORT_ERR
);
249 nsresult rv
= GetOrigin(mParent
, origin
, rpId
);
250 if (NS_WARN_IF(NS_FAILED(rv
))) {
251 promise
->MaybeReject(rv
);
252 return promise
.forget();
255 // Enforce 5.4.3 User Account Parameters for Credential Generation
256 // When we add UX, we'll want to do more with this value, but for now
257 // we just have to verify its correctness.
260 userId
.Assign(aOptions
.mUser
.mId
);
261 if (userId
.Length() > 64) {
262 promise
->MaybeRejectWithTypeError("user.id is too long");
263 return promise
.forget();
266 // If timeoutSeconds was specified, check if its value lies within a
267 // reasonable range as defined by the platform and if not, correct it to the
268 // closest value lying within that range.
270 uint32_t adjustedTimeout
= 30000;
271 if (aOptions
.mTimeout
.WasPassed()) {
272 adjustedTimeout
= aOptions
.mTimeout
.Value();
273 adjustedTimeout
= std::max(15000u, adjustedTimeout
);
274 adjustedTimeout
= std::min(120000u, adjustedTimeout
);
277 if (aOptions
.mRp
.mId
.WasPassed()) {
278 // If rpId is specified, then invoke the procedure used for relaxing the
279 // same-origin restriction by setting the document.domain attribute, using
280 // rpId as the given value but without changing the current document’s
281 // domain. If no errors are thrown, set rpId to the value of host as
282 // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
283 // Otherwise, reject promise with a DOMException whose name is
284 // "SecurityError", and terminate this algorithm.
286 if (NS_FAILED(RelaxSameOrigin(mParent
, aOptions
.mRp
.mId
.Value(), rpId
))) {
287 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
288 return promise
.forget();
292 // <https://w3c.github.io/webauthn/#sctn-appid-extension>
293 if (aOptions
.mExtensions
.mAppid
.WasPassed()) {
294 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
295 return promise
.forget();
298 // Process each element of mPubKeyCredParams using the following steps, to
299 // produce a new sequence of coseAlgos.
300 nsTArray
<CoseAlg
> coseAlgos
;
301 // If pubKeyCredParams is empty, append ES256 and RS256
302 if (aOptions
.mPubKeyCredParams
.IsEmpty()) {
303 coseAlgos
.AppendElement(static_cast<long>(CoseAlgorithmIdentifier::ES256
));
304 coseAlgos
.AppendElement(static_cast<long>(CoseAlgorithmIdentifier::RS256
));
306 for (size_t a
= 0; a
< aOptions
.mPubKeyCredParams
.Length(); ++a
) {
307 // If current.type does not contain a PublicKeyCredentialType
308 // supported by this implementation, then stop processing current and move
309 // on to the next element in mPubKeyCredParams.
310 if (!aOptions
.mPubKeyCredParams
[a
].mType
.EqualsLiteral(
311 MOZ_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_TYPE_PUBLIC_KEY
)) {
315 coseAlgos
.AppendElement(aOptions
.mPubKeyCredParams
[a
].mAlg
);
319 // If there are algorithms specified, but none are Public_key algorithms,
320 // reject the promise.
321 if (coseAlgos
.IsEmpty() && !aOptions
.mPubKeyCredParams
.IsEmpty()) {
322 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
323 return promise
.forget();
326 // If excludeList is undefined, set it to the empty list.
328 // If extensions was specified, process any extensions supported by this
329 // client platform, to produce the extension data that needs to be sent to the
330 // authenticator. If an error is encountered while processing an extension,
331 // skip that extension and do not produce any extension data for it. Call the
332 // result of this processing clientExtensions.
334 // Currently no extensions are supported
336 // Use attestationChallenge, callerOrigin and rpId, along with the token
337 // binding key associated with callerOrigin (if any), to create a ClientData
338 // structure representing this request. Choose a hash algorithm for hashAlg
339 // and compute the clientDataJSON and clientDataHash.
341 CryptoBuffer challenge
;
342 if (!challenge
.Assign(aOptions
.mChallenge
)) {
343 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
344 return promise
.forget();
347 nsAutoCString clientDataJSON
;
348 nsresult srv
= AssembleClientData(origin
, challenge
, u
"webauthn.create"_ns
,
349 aOptions
.mExtensions
, clientDataJSON
);
350 if (NS_WARN_IF(NS_FAILED(srv
))) {
351 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
352 return promise
.forget();
355 nsTArray
<WebAuthnScopedCredential
> excludeList
;
356 for (const auto& s
: aOptions
.mExcludeCredentials
) {
357 WebAuthnScopedCredential c
;
361 if (s
.mTransports
.WasPassed()) {
362 c
.transports() = SerializeTransports(s
.mTransports
.Value());
364 excludeList
.AppendElement(c
);
367 if (!MaybeCreateBackgroundActor()) {
368 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
369 return promise
.forget();
372 // TODO: Add extension list building
373 nsTArray
<WebAuthnExtension
> extensions
;
375 // <https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#sctn-hmac-secret-extension>
376 if (aOptions
.mExtensions
.mHmacCreateSecret
.WasPassed()) {
377 bool hmacCreateSecret
= aOptions
.mExtensions
.mHmacCreateSecret
.Value();
378 if (hmacCreateSecret
) {
379 extensions
.AppendElement(WebAuthnExtensionHmacSecret(hmacCreateSecret
));
383 if (aOptions
.mExtensions
.mCredProps
.WasPassed()) {
384 bool credProps
= aOptions
.mExtensions
.mCredProps
.Value();
386 extensions
.AppendElement(WebAuthnExtensionCredProps(credProps
));
390 if (aOptions
.mExtensions
.mMinPinLength
.WasPassed()) {
391 bool minPinLength
= aOptions
.mExtensions
.mMinPinLength
.Value();
393 extensions
.AppendElement(WebAuthnExtensionMinPinLength(minPinLength
));
397 const auto& selection
= aOptions
.mAuthenticatorSelection
;
398 const auto& attachment
= selection
.mAuthenticatorAttachment
;
399 const nsString
& attestation
= aOptions
.mAttestation
;
402 Maybe
<nsString
> authenticatorAttachment
;
403 if (attachment
.WasPassed()) {
404 authenticatorAttachment
.emplace(attachment
.Value());
407 // The residentKey field was added in WebAuthn level 2. It takes precedent
408 // over the requireResidentKey field if and only if it is present and it is a
409 // member of the ResidentKeyRequirement enum.
410 static_assert(MOZ_WEBAUTHN_ENUM_STRINGS_VERSION
== 3);
411 bool useResidentKeyValue
=
412 selection
.mResidentKey
.WasPassed() &&
413 (selection
.mResidentKey
.Value().EqualsLiteral(
414 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED
) ||
415 selection
.mResidentKey
.Value().EqualsLiteral(
416 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_PREFERRED
) ||
417 selection
.mResidentKey
.Value().EqualsLiteral(
418 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED
));
420 nsString residentKey
;
421 if (useResidentKeyValue
) {
422 residentKey
= selection
.mResidentKey
.Value();
424 // "If no value is given then the effective value is required if
425 // requireResidentKey is true or discouraged if it is false or absent."
426 if (selection
.mRequireResidentKey
) {
427 residentKey
.AssignLiteral(MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED
);
429 residentKey
.AssignLiteral(
430 MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_DISCOURAGED
);
434 // Create and forward authenticator selection criteria.
435 WebAuthnAuthenticatorSelection
authSelection(
436 residentKey
, selection
.mUserVerification
, authenticatorAttachment
);
438 WebAuthnMakeCredentialRpInfo
rpInfo(aOptions
.mRp
.mName
);
440 WebAuthnMakeCredentialUserInfo
userInfo(userId
, aOptions
.mUser
.mName
,
441 aOptions
.mUser
.mDisplayName
);
443 BrowsingContext
* context
= mParent
->GetBrowsingContext();
445 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
446 return promise
.forget();
449 // Abort the request if aborted flag is already set.
450 if (aSignal
.WasPassed() && aSignal
.Value().Aborted()) {
452 if (!jsapi
.Init(global
)) {
453 promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
454 return promise
.forget();
456 JSContext
* cx
= jsapi
.cx();
457 JS::Rooted
<JS::Value
> reason(cx
);
458 aSignal
.Value().GetReason(cx
, &reason
);
459 promise
->MaybeReject(reason
);
460 return promise
.forget();
463 WebAuthnMakeCredentialInfo
info(
464 origin
, NS_ConvertUTF8toUTF16(rpId
), challenge
, clientDataJSON
,
465 adjustedTimeout
, excludeList
, rpInfo
, userInfo
, coseAlgos
, extensions
,
466 authSelection
, attestation
, context
->Top()->Id());
468 // Set up the transaction state. Fallible operations should not be performed
469 // below this line, as we must not leave the transaction state partially
470 // initialized. Once the transaction state is initialized the only valid ways
471 // to end the transaction are CancelTransaction, RejectTransaction, and
472 // FinishMakeCredential.
473 AbortSignal
* signal
= nullptr;
474 if (aSignal
.WasPassed()) {
475 signal
= &aSignal
.Value();
479 MOZ_ASSERT(mTransaction
.isNothing());
480 mTransaction
= Some(WebAuthnTransaction(promise
));
481 mChild
->SendRequestRegister(mTransaction
.ref().mId
, info
);
483 return promise
.forget();
486 const size_t MAX_ALLOWED_CREDENTIALS
= 20;
488 already_AddRefed
<Promise
> WebAuthnManager::GetAssertion(
489 const PublicKeyCredentialRequestOptions
& aOptions
,
490 const bool aConditionallyMediated
,
491 const Optional
<OwningNonNull
<AbortSignal
>>& aSignal
, ErrorResult
& aError
) {
492 MOZ_ASSERT(NS_IsMainThread());
494 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
496 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
497 if (aError
.Failed()) {
501 if (mTransaction
.isSome()) {
502 // abort the old transaction and take over control from here.
503 CancelTransaction(NS_ERROR_DOM_ABORT_ERR
);
508 nsresult rv
= GetOrigin(mParent
, origin
, rpId
);
509 if (NS_WARN_IF(NS_FAILED(rv
))) {
510 promise
->MaybeReject(rv
);
511 return promise
.forget();
514 // If timeoutSeconds was specified, check if its value lies within a
515 // reasonable range as defined by the platform and if not, correct it to the
516 // closest value lying within that range.
518 uint32_t adjustedTimeout
= 30000;
519 if (aOptions
.mTimeout
.WasPassed()) {
520 adjustedTimeout
= aOptions
.mTimeout
.Value();
521 adjustedTimeout
= std::max(15000u, adjustedTimeout
);
522 adjustedTimeout
= std::min(120000u, adjustedTimeout
);
525 if (aOptions
.mRpId
.WasPassed()) {
526 // If rpId is specified, then invoke the procedure used for relaxing the
527 // same-origin restriction by setting the document.domain attribute, using
528 // rpId as the given value but without changing the current document’s
529 // domain. If no errors are thrown, set rpId to the value of host as
530 // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
531 // Otherwise, reject promise with a DOMException whose name is
532 // "SecurityError", and terminate this algorithm.
534 if (NS_FAILED(RelaxSameOrigin(mParent
, aOptions
.mRpId
.Value(), rpId
))) {
535 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
536 return promise
.forget();
540 // Abort the request if the allowCredentials set is too large
541 if (aOptions
.mAllowCredentials
.Length() > MAX_ALLOWED_CREDENTIALS
) {
542 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
543 return promise
.forget();
546 // Use assertionChallenge, callerOrigin and rpId, along with the token binding
547 // key associated with callerOrigin (if any), to create a ClientData structure
548 // representing this request. Choose a hash algorithm for hashAlg and compute
549 // the clientDataJSON and clientDataHash.
550 CryptoBuffer challenge
;
551 if (!challenge
.Assign(aOptions
.mChallenge
)) {
552 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
553 return promise
.forget();
556 nsAutoCString clientDataJSON
;
557 rv
= AssembleClientData(origin
, challenge
, u
"webauthn.get"_ns
,
558 aOptions
.mExtensions
, clientDataJSON
);
559 if (NS_WARN_IF(NS_FAILED(rv
))) {
560 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
561 return promise
.forget();
564 nsTArray
<WebAuthnScopedCredential
> allowList
;
565 for (const auto& s
: aOptions
.mAllowCredentials
) {
566 if (s
.mType
.EqualsLiteral(
567 MOZ_WEBAUTHN_PUBLIC_KEY_CREDENTIAL_TYPE_PUBLIC_KEY
)) {
568 WebAuthnScopedCredential c
;
572 if (s
.mTransports
.WasPassed()) {
573 c
.transports() = SerializeTransports(s
.mTransports
.Value());
575 allowList
.AppendElement(c
);
578 if (allowList
.Length() == 0 && aOptions
.mAllowCredentials
.Length() != 0) {
579 promise
->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
580 return promise
.forget();
583 if (!MaybeCreateBackgroundActor()) {
584 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
585 return promise
.forget();
588 // If extensions were specified, process any extensions supported by this
589 // client platform, to produce the extension data that needs to be sent to the
590 // authenticator. If an error is encountered while processing an extension,
591 // skip that extension and do not produce any extension data for it. Call the
592 // result of this processing clientExtensions.
593 nsTArray
<WebAuthnExtension
> extensions
;
595 // credProps is only supported in MakeCredentials
596 if (aOptions
.mExtensions
.mCredProps
.WasPassed()) {
597 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
598 return promise
.forget();
601 // minPinLength is only supported in MakeCredentials
602 if (aOptions
.mExtensions
.mMinPinLength
.WasPassed()) {
603 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
604 return promise
.forget();
607 // <https://w3c.github.io/webauthn/#sctn-appid-extension>
608 if (aOptions
.mExtensions
.mAppid
.WasPassed()) {
609 nsString
appId(aOptions
.mExtensions
.mAppid
.Value());
611 // Check that the appId value is allowed.
612 if (!EvaluateAppID(mParent
, origin
, appId
)) {
613 promise
->MaybeReject(NS_ERROR_DOM_SECURITY_ERR
);
614 return promise
.forget();
617 // Append the hash and send it to the backend.
618 extensions
.AppendElement(WebAuthnExtensionAppId(appId
));
621 BrowsingContext
* context
= mParent
->GetBrowsingContext();
623 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
624 return promise
.forget();
627 // Abort the request if aborted flag is already set.
628 if (aSignal
.WasPassed() && aSignal
.Value().Aborted()) {
630 if (!jsapi
.Init(global
)) {
631 promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
632 return promise
.forget();
634 JSContext
* cx
= jsapi
.cx();
635 JS::Rooted
<JS::Value
> reason(cx
);
636 aSignal
.Value().GetReason(cx
, &reason
);
637 promise
->MaybeReject(reason
);
638 return promise
.forget();
641 WebAuthnGetAssertionInfo
info(origin
, NS_ConvertUTF8toUTF16(rpId
), challenge
,
642 clientDataJSON
, adjustedTimeout
, allowList
,
643 extensions
, aOptions
.mUserVerification
,
644 aConditionallyMediated
, context
->Top()->Id());
646 // Set up the transaction state. Fallible operations should not be performed
647 // below this line, as we must not leave the transaction state partially
648 // initialized. Once the transaction state is initialized the only valid ways
649 // to end the transaction are CancelTransaction, RejectTransaction, and
650 // FinishGetAssertion.
651 AbortSignal
* signal
= nullptr;
652 if (aSignal
.WasPassed()) {
653 signal
= &aSignal
.Value();
657 MOZ_ASSERT(mTransaction
.isNothing());
658 mTransaction
= Some(WebAuthnTransaction(promise
));
659 mChild
->SendRequestSign(mTransaction
.ref().mId
, info
);
661 return promise
.forget();
664 already_AddRefed
<Promise
> WebAuthnManager::Store(const Credential
& aCredential
,
665 ErrorResult
& aError
) {
666 MOZ_ASSERT(NS_IsMainThread());
668 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
670 RefPtr
<Promise
> promise
= Promise::Create(global
, aError
);
671 if (aError
.Failed()) {
675 if (mTransaction
.isSome()) {
676 // abort the old transaction and take over control from here.
677 CancelTransaction(NS_ERROR_DOM_ABORT_ERR
);
680 promise
->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
681 return promise
.forget();
684 already_AddRefed
<Promise
> WebAuthnManager::IsUVPAA(GlobalObject
& aGlobal
,
685 ErrorResult
& aError
) {
686 RefPtr
<Promise
> promise
=
687 Promise::Create(xpc::CurrentNativeGlobal(aGlobal
.Context()), aError
);
688 if (aError
.Failed()) {
692 if (!MaybeCreateBackgroundActor()) {
693 promise
->MaybeReject(NS_ERROR_DOM_OPERATION_ERR
);
694 return promise
.forget();
697 mChild
->SendRequestIsUVPAA()->Then(
698 GetCurrentSerialEventTarget(), __func__
,
699 [promise
](const PWebAuthnTransactionChild::RequestIsUVPAAPromise::
700 ResolveOrRejectValue
& aValue
) {
701 if (aValue
.IsResolve()) {
702 promise
->MaybeResolve(aValue
.ResolveValue());
704 promise
->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
707 return promise
.forget();
710 void WebAuthnManager::FinishMakeCredential(
711 const uint64_t& aTransactionId
,
712 const WebAuthnMakeCredentialResult
& aResult
) {
713 MOZ_ASSERT(NS_IsMainThread());
715 // Check for a valid transaction.
716 if (mTransaction
.isNothing() || mTransaction
.ref().mId
!= aTransactionId
) {
720 nsAutoCString keyHandleBase64Url
;
721 nsresult rv
= Base64URLEncode(
722 aResult
.KeyHandle().Length(), aResult
.KeyHandle().Elements(),
723 Base64URLEncodePaddingPolicy::Omit
, keyHandleBase64Url
);
724 if (NS_WARN_IF(NS_FAILED(rv
))) {
725 RejectTransaction(rv
);
729 // Create a new PublicKeyCredential object and populate its fields with the
730 // values returned from the authenticator as well as the clientDataJSON
732 RefPtr
<AuthenticatorAttestationResponse
> attestation
=
733 new AuthenticatorAttestationResponse(mParent
);
734 attestation
->SetClientDataJSON(aResult
.ClientDataJSON());
735 attestation
->SetAttestationObject(aResult
.AttestationObject());
736 attestation
->SetTransports(aResult
.Transports());
738 RefPtr
<PublicKeyCredential
> credential
= new PublicKeyCredential(mParent
);
739 credential
->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url
));
740 credential
->SetType(u
"public-key"_ns
);
741 credential
->SetRawId(aResult
.KeyHandle());
742 credential
->SetAttestationResponse(attestation
);
743 credential
->SetAuthenticatorAttachment(aResult
.AuthenticatorAttachment());
745 // Forward client extension results.
746 for (const auto& ext
: aResult
.Extensions()) {
748 WebAuthnExtensionResult::TWebAuthnExtensionResultCredProps
) {
749 bool credPropsRk
= ext
.get_WebAuthnExtensionResultCredProps().rk();
750 credential
->SetClientExtensionResultCredPropsRk(credPropsRk
);
753 WebAuthnExtensionResult::TWebAuthnExtensionResultHmacSecret
) {
754 bool hmacCreateSecret
=
755 ext
.get_WebAuthnExtensionResultHmacSecret().hmacCreateSecret();
756 credential
->SetClientExtensionResultHmacSecret(hmacCreateSecret
);
760 mTransaction
.ref().mPromise
->MaybeResolve(credential
);
764 void WebAuthnManager::FinishGetAssertion(
765 const uint64_t& aTransactionId
, const WebAuthnGetAssertionResult
& aResult
) {
766 MOZ_ASSERT(NS_IsMainThread());
768 // Check for a valid transaction.
769 if (mTransaction
.isNothing() || mTransaction
.ref().mId
!= aTransactionId
) {
773 nsAutoCString keyHandleBase64Url
;
774 nsresult rv
= Base64URLEncode(
775 aResult
.KeyHandle().Length(), aResult
.KeyHandle().Elements(),
776 Base64URLEncodePaddingPolicy::Omit
, keyHandleBase64Url
);
777 if (NS_WARN_IF(NS_FAILED(rv
))) {
778 RejectTransaction(rv
);
782 // Create a new PublicKeyCredential object named value and populate its fields
783 // with the values returned from the authenticator as well as the
784 // clientDataJSON computed earlier.
785 RefPtr
<AuthenticatorAssertionResponse
> assertion
=
786 new AuthenticatorAssertionResponse(mParent
);
787 assertion
->SetClientDataJSON(aResult
.ClientDataJSON());
788 assertion
->SetAuthenticatorData(aResult
.AuthenticatorData());
789 assertion
->SetSignature(aResult
.Signature());
790 assertion
->SetUserHandle(aResult
.UserHandle()); // may be empty
792 RefPtr
<PublicKeyCredential
> credential
= new PublicKeyCredential(mParent
);
793 credential
->SetId(NS_ConvertASCIItoUTF16(keyHandleBase64Url
));
794 credential
->SetType(u
"public-key"_ns
);
795 credential
->SetRawId(aResult
.KeyHandle());
796 credential
->SetAssertionResponse(assertion
);
797 credential
->SetAuthenticatorAttachment(aResult
.AuthenticatorAttachment());
799 // Forward client extension results.
800 for (const auto& ext
: aResult
.Extensions()) {
801 if (ext
.type() == WebAuthnExtensionResult::TWebAuthnExtensionResultAppId
) {
802 bool appid
= ext
.get_WebAuthnExtensionResultAppId().AppId();
803 credential
->SetClientExtensionResultAppId(appid
);
807 mTransaction
.ref().mPromise
->MaybeResolve(credential
);
811 void WebAuthnManager::RequestAborted(const uint64_t& aTransactionId
,
812 const nsresult
& aError
) {
813 MOZ_ASSERT(NS_IsMainThread());
815 if (mTransaction
.isSome() && mTransaction
.ref().mId
== aTransactionId
) {
816 RejectTransaction(aError
);
820 void WebAuthnManager::RunAbortAlgorithm() {
821 if (NS_WARN_IF(mTransaction
.isNothing())) {
825 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(mParent
);
828 if (!jsapi
.Init(global
)) {
829 CancelTransaction(NS_ERROR_DOM_ABORT_ERR
);
832 JSContext
* cx
= jsapi
.cx();
833 JS::Rooted
<JS::Value
> reason(cx
);
834 Signal()->GetReason(cx
, &reason
);
835 CancelTransaction(reason
);
838 } // namespace mozilla::dom