Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / webauthn / WebAuthnManager.cpp
blobe4d18ebc45a1868744f68b4aaf5f8ee4fd4a9eac
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/. */
7 #include "hasht.h"
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"
28 #ifdef XP_WIN
29 # include "WinWebAuthnService.h"
30 #endif
32 using namespace mozilla::ipc;
34 namespace mozilla::dom {
36 /***********************************************************************
37 * Statics
38 **********************************************************************/
40 namespace {
41 static mozilla::LazyLogModule gWebAuthnManagerLog("webauthnmanager");
44 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(WebAuthnManager,
45 WebAuthnManagerBase)
47 NS_IMPL_CYCLE_COLLECTION_CLASS(WebAuthnManager)
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebAuthnManager,
50 WebAuthnManagerBase)
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,
56 WebAuthnManagerBase)
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
60 /***********************************************************************
61 * Utility Functions
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);
82 nsAutoString temp;
83 if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
84 return NS_ERROR_FAILURE;
87 aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
88 return NS_OK;
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;
113 return transports;
116 nsresult GetOrigin(nsPIDOMWindowInner* aParent,
117 /*out*/ nsAString& aOrigin, /*out*/ nsACString& aHost) {
118 MOZ_ASSERT(aParent);
119 nsCOMPtr<Document> doc = aParent->GetDoc();
120 MOZ_ASSERT(doc);
122 nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
123 nsresult rv =
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
136 // algorithm
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;
151 return NS_OK;
154 nsresult RelaxSameOrigin(nsPIDOMWindowInner* aParent,
155 const nsAString& aInputRpId,
156 /* out */ nsACString& aRelaxedRpId) {
157 MOZ_ASSERT(aParent);
158 nsCOMPtr<Document> doc = aParent->GetDoc();
159 MOZ_ASSERT(doc);
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);
185 if (NS_FAILED(rv)) {
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);
198 return NS_OK;
201 /***********************************************************************
202 * WebAuthnManager Implementation
203 **********************************************************************/
205 void WebAuthnManager::ClearTransaction() {
206 mTransaction.reset();
207 Unfollow();
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()) {
220 ClearTransaction();
223 if (mChild) {
224 RefPtr<WebAuthnTransactionChild> c;
225 mChild.swap(c);
226 c->Disconnect();
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()) {
239 return nullptr;
242 if (mTransaction.isSome()) {
243 // abort the old transaction and take over control from here.
244 CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
247 nsString origin;
248 nsCString rpId;
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.
259 CryptoBuffer userId;
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));
305 } else {
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)) {
312 continue;
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;
358 CryptoBuffer cb;
359 cb.Assign(s.mId);
360 c.id() = cb;
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();
385 if (credProps) {
386 extensions.AppendElement(WebAuthnExtensionCredProps(credProps));
390 if (aOptions.mExtensions.mMinPinLength.WasPassed()) {
391 bool minPinLength = aOptions.mExtensions.mMinPinLength.Value();
392 if (minPinLength) {
393 extensions.AppendElement(WebAuthnExtensionMinPinLength(minPinLength));
397 const auto& selection = aOptions.mAuthenticatorSelection;
398 const auto& attachment = selection.mAuthenticatorAttachment;
399 const nsString& attestation = aOptions.mAttestation;
401 // Attachment
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();
423 } else {
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);
428 } else {
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();
444 if (!context) {
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()) {
451 AutoJSAPI jsapi;
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();
476 Follow(signal);
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()) {
498 return nullptr;
501 if (mTransaction.isSome()) {
502 // abort the old transaction and take over control from here.
503 CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
506 nsString origin;
507 nsCString rpId;
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;
569 CryptoBuffer cb;
570 cb.Assign(s.mId);
571 c.id() = cb;
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();
622 if (!context) {
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()) {
629 AutoJSAPI jsapi;
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();
654 Follow(signal);
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()) {
672 return nullptr;
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()) {
689 return nullptr;
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());
703 } else {
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) {
717 return;
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);
726 return;
729 // Create a new PublicKeyCredential object and populate its fields with the
730 // values returned from the authenticator as well as the clientDataJSON
731 // computed earlier.
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()) {
747 if (ext.type() ==
748 WebAuthnExtensionResult::TWebAuthnExtensionResultCredProps) {
749 bool credPropsRk = ext.get_WebAuthnExtensionResultCredProps().rk();
750 credential->SetClientExtensionResultCredPropsRk(credPropsRk);
752 if (ext.type() ==
753 WebAuthnExtensionResult::TWebAuthnExtensionResultHmacSecret) {
754 bool hmacCreateSecret =
755 ext.get_WebAuthnExtensionResultHmacSecret().hmacCreateSecret();
756 credential->SetClientExtensionResultHmacSecret(hmacCreateSecret);
760 mTransaction.ref().mPromise->MaybeResolve(credential);
761 ClearTransaction();
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) {
770 return;
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);
779 return;
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);
808 ClearTransaction();
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())) {
822 return;
825 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
827 AutoJSAPI jsapi;
828 if (!jsapi.Init(global)) {
829 CancelTransaction(NS_ERROR_DOM_ABORT_ERR);
830 return;
832 JSContext* cx = jsapi.cx();
833 JS::Rooted<JS::Value> reason(cx);
834 Signal()->GetReason(cx, &reason);
835 CancelTransaction(reason);
838 } // namespace mozilla::dom