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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/WebAuthnTransactionParent.h"
8 #include "mozilla/ipc/PBackgroundParent.h"
9 #include "mozilla/ipc/BackgroundParent.h"
10 #include "mozilla/StaticPrefs_security.h"
12 #include "nsThreadUtils.h"
13 #include "WebAuthnArgs.h"
15 namespace mozilla::dom
{
17 void WebAuthnTransactionParent::CompleteTransaction() {
18 if (mTransactionId
.isSome()) {
19 if (mRegisterPromiseRequest
.Exists()) {
20 mRegisterPromiseRequest
.Complete();
22 if (mSignPromiseRequest
.Exists()) {
23 mSignPromiseRequest
.Complete();
25 if (mWebAuthnService
) {
26 // We have to do this to work around Bug 1864526.
27 mWebAuthnService
->Cancel(mTransactionId
.ref());
29 mTransactionId
.reset();
33 void WebAuthnTransactionParent::DisconnectTransaction() {
34 mTransactionId
.reset();
35 mRegisterPromiseRequest
.DisconnectIfExists();
36 mSignPromiseRequest
.DisconnectIfExists();
37 if (mWebAuthnService
) {
38 mWebAuthnService
->Reset();
42 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvRequestRegister(
43 const uint64_t& aTransactionId
,
44 const WebAuthnMakeCredentialInfo
& aTransactionInfo
) {
45 ::mozilla::ipc::AssertIsOnBackgroundThread();
47 if (!mWebAuthnService
) {
48 mWebAuthnService
= do_GetService("@mozilla.org/webauthn/service;1");
49 if (!mWebAuthnService
) {
50 return IPC_FAIL_NO_REASON(this);
54 // If there's an ongoing transaction, abort it.
55 if (mTransactionId
.isSome()) {
56 DisconnectTransaction();
57 Unused
<< SendAbort(mTransactionId
.ref(), NS_ERROR_DOM_ABORT_ERR
);
59 mTransactionId
= Some(aTransactionId
);
61 RefPtr
<WebAuthnRegisterPromiseHolder
> promiseHolder
=
62 new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
64 PWebAuthnTransactionParent
* parent
= this;
65 RefPtr
<WebAuthnRegisterPromise
> promise
= promiseHolder
->Ensure();
68 GetCurrentSerialEventTarget(), __func__
,
69 [this, parent
, aTransactionId
,
70 inputClientData
= aTransactionInfo
.ClientDataJSON()](
71 const WebAuthnRegisterPromise::ResolveValueType
& aValue
) {
72 CompleteTransaction();
75 nsresult rv
= aValue
->GetClientDataJSON(clientData
);
76 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
77 clientData
= inputClientData
;
78 } else if (NS_FAILED(rv
)) {
79 Unused
<< parent
->SendAbort(aTransactionId
,
80 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
84 nsTArray
<uint8_t> attObj
;
85 rv
= aValue
->GetAttestationObject(attObj
);
86 if (NS_WARN_IF(NS_FAILED(rv
))) {
87 Unused
<< parent
->SendAbort(aTransactionId
,
88 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
92 nsTArray
<uint8_t> credentialId
;
93 rv
= aValue
->GetCredentialId(credentialId
);
94 if (NS_WARN_IF(NS_FAILED(rv
))) {
95 Unused
<< parent
->SendAbort(aTransactionId
,
96 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
100 nsTArray
<nsString
> transports
;
101 rv
= aValue
->GetTransports(transports
);
102 if (NS_WARN_IF(NS_FAILED(rv
))) {
103 Unused
<< parent
->SendAbort(aTransactionId
,
104 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
108 Maybe
<nsString
> authenticatorAttachment
;
109 nsString maybeAuthenticatorAttachment
;
110 rv
= aValue
->GetAuthenticatorAttachment(
111 maybeAuthenticatorAttachment
);
112 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
113 if (NS_WARN_IF(NS_FAILED(rv
))) {
114 Unused
<< parent
->SendAbort(aTransactionId
,
115 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
118 authenticatorAttachment
= Some(maybeAuthenticatorAttachment
);
121 nsTArray
<WebAuthnExtensionResult
> extensions
;
123 rv
= aValue
->GetCredPropsRk(&credPropsRk
);
124 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
125 if (NS_WARN_IF(NS_FAILED(rv
))) {
126 Unused
<< parent
->SendAbort(aTransactionId
,
127 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
130 extensions
.AppendElement(
131 WebAuthnExtensionResultCredProps(credPropsRk
));
134 bool hmacCreateSecret
;
135 rv
= aValue
->GetHmacCreateSecret(&hmacCreateSecret
);
136 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
137 if (NS_WARN_IF(NS_FAILED(rv
))) {
138 Unused
<< parent
->SendAbort(aTransactionId
,
139 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
142 extensions
.AppendElement(
143 WebAuthnExtensionResultHmacSecret(hmacCreateSecret
));
147 Maybe
<bool> prfEnabledMaybe
= Nothing();
148 Maybe
<WebAuthnExtensionPrfValues
> prfResults
= Nothing();
151 rv
= aValue
->GetPrfEnabled(&prfEnabled
);
152 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
153 if (NS_WARN_IF(NS_FAILED(rv
))) {
154 Unused
<< parent
->SendAbort(aTransactionId
,
155 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
158 prfEnabledMaybe
= Some(prfEnabled
);
161 nsTArray
<uint8_t> prfResultsFirst
;
162 rv
= aValue
->GetPrfResultsFirst(prfResultsFirst
);
163 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
164 if (NS_WARN_IF(NS_FAILED(rv
))) {
165 Unused
<< parent
->SendAbort(aTransactionId
,
166 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
170 bool prfResultsSecondMaybe
= false;
171 nsTArray
<uint8_t> prfResultsSecond
;
172 rv
= aValue
->GetPrfResultsSecond(prfResultsSecond
);
173 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
174 if (NS_WARN_IF(NS_FAILED(rv
))) {
175 Unused
<< parent
->SendAbort(aTransactionId
,
176 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
179 prfResultsSecondMaybe
= true;
182 prfResults
= Some(WebAuthnExtensionPrfValues(
183 prfResultsFirst
, prfResultsSecondMaybe
, prfResultsSecond
));
186 if (prfEnabledMaybe
.isSome() || prfResults
.isSome()) {
187 extensions
.AppendElement(
188 WebAuthnExtensionResultPrf(prfEnabledMaybe
, prfResults
));
192 WebAuthnMakeCredentialResult
result(
193 clientData
, attObj
, credentialId
, transports
, extensions
,
194 authenticatorAttachment
);
196 Unused
<< parent
->SendConfirmRegister(aTransactionId
, result
);
198 [this, parent
, aTransactionId
](
199 const WebAuthnRegisterPromise::RejectValueType aValue
) {
200 CompleteTransaction();
201 Unused
<< parent
->SendAbort(aTransactionId
, aValue
);
203 ->Track(mRegisterPromiseRequest
);
205 uint64_t browsingContextId
= aTransactionInfo
.BrowsingContextId();
206 RefPtr
<WebAuthnRegisterArgs
> args(new WebAuthnRegisterArgs(aTransactionInfo
));
208 nsresult rv
= mWebAuthnService
->MakeCredential(
209 aTransactionId
, browsingContextId
, args
, promiseHolder
);
211 promiseHolder
->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
217 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvRequestSign(
218 const uint64_t& aTransactionId
,
219 const WebAuthnGetAssertionInfo
& aTransactionInfo
) {
220 ::mozilla::ipc::AssertIsOnBackgroundThread();
222 if (!mWebAuthnService
) {
223 mWebAuthnService
= do_GetService("@mozilla.org/webauthn/service;1");
224 if (!mWebAuthnService
) {
225 return IPC_FAIL_NO_REASON(this);
229 if (mTransactionId
.isSome()) {
230 DisconnectTransaction();
231 Unused
<< SendAbort(mTransactionId
.ref(), NS_ERROR_DOM_ABORT_ERR
);
233 mTransactionId
= Some(aTransactionId
);
235 RefPtr
<WebAuthnSignPromiseHolder
> promiseHolder
=
236 new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget());
238 PWebAuthnTransactionParent
* parent
= this;
239 RefPtr
<WebAuthnSignPromise
> promise
= promiseHolder
->Ensure();
242 GetCurrentSerialEventTarget(), __func__
,
243 [this, parent
, aTransactionId
,
244 inputClientData
= aTransactionInfo
.ClientDataJSON()](
245 const WebAuthnSignPromise::ResolveValueType
& aValue
) {
246 CompleteTransaction();
248 nsCString clientData
;
249 nsresult rv
= aValue
->GetClientDataJSON(clientData
);
250 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
251 clientData
= inputClientData
;
252 } else if (NS_FAILED(rv
)) {
253 Unused
<< parent
->SendAbort(aTransactionId
,
254 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
258 nsTArray
<uint8_t> credentialId
;
259 rv
= aValue
->GetCredentialId(credentialId
);
260 if (NS_WARN_IF(NS_FAILED(rv
))) {
261 Unused
<< parent
->SendAbort(aTransactionId
,
262 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
266 nsTArray
<uint8_t> signature
;
267 rv
= aValue
->GetSignature(signature
);
268 if (NS_WARN_IF(NS_FAILED(rv
))) {
269 Unused
<< parent
->SendAbort(aTransactionId
,
270 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
274 nsTArray
<uint8_t> authenticatorData
;
275 rv
= aValue
->GetAuthenticatorData(authenticatorData
);
276 if (NS_WARN_IF(NS_FAILED(rv
))) {
277 Unused
<< parent
->SendAbort(aTransactionId
,
278 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
282 nsTArray
<uint8_t> userHandle
;
283 Unused
<< aValue
->GetUserHandle(userHandle
); // optional
285 Maybe
<nsString
> authenticatorAttachment
;
286 nsString maybeAuthenticatorAttachment
;
287 rv
= aValue
->GetAuthenticatorAttachment(
288 maybeAuthenticatorAttachment
);
289 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
290 if (NS_WARN_IF(NS_FAILED(rv
))) {
291 Unused
<< parent
->SendAbort(aTransactionId
,
292 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
295 authenticatorAttachment
= Some(maybeAuthenticatorAttachment
);
298 nsTArray
<WebAuthnExtensionResult
> extensions
;
300 rv
= aValue
->GetUsedAppId(&usedAppId
);
301 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
303 Unused
<< parent
->SendAbort(aTransactionId
,
304 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
307 extensions
.AppendElement(WebAuthnExtensionResultAppId(usedAppId
));
311 Maybe
<WebAuthnExtensionPrfValues
> prfResults
;
312 bool prfMaybe
= false;
313 rv
= aValue
->GetPrfMaybe(&prfMaybe
);
314 if (rv
== NS_OK
&& prfMaybe
) {
315 nsTArray
<uint8_t> prfResultsFirst
;
316 rv
= aValue
->GetPrfResultsFirst(prfResultsFirst
);
317 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
318 if (NS_WARN_IF(NS_FAILED(rv
))) {
319 Unused
<< parent
->SendAbort(aTransactionId
,
320 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
324 bool prfResultsSecondMaybe
= false;
325 nsTArray
<uint8_t> prfResultsSecond
;
326 rv
= aValue
->GetPrfResultsSecond(prfResultsSecond
);
327 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
328 if (NS_WARN_IF(NS_FAILED(rv
))) {
329 Unused
<< parent
->SendAbort(aTransactionId
,
330 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
333 prfResultsSecondMaybe
= true;
336 prfResults
= Some(WebAuthnExtensionPrfValues(
337 prfResultsFirst
, prfResultsSecondMaybe
,
340 prfResults
= Nothing();
343 extensions
.AppendElement(
344 WebAuthnExtensionResultPrf(Nothing(), prfResults
));
348 WebAuthnGetAssertionResult
result(
349 clientData
, credentialId
, signature
, authenticatorData
,
350 extensions
, userHandle
, authenticatorAttachment
);
352 Unused
<< parent
->SendConfirmSign(aTransactionId
, result
);
355 aTransactionId
](const WebAuthnSignPromise::RejectValueType aValue
) {
356 CompleteTransaction();
357 Unused
<< parent
->SendAbort(aTransactionId
, aValue
);
359 ->Track(mSignPromiseRequest
);
361 RefPtr
<WebAuthnSignArgs
> args(new WebAuthnSignArgs(aTransactionInfo
));
363 nsresult rv
= mWebAuthnService
->GetAssertion(
364 aTransactionId
, aTransactionInfo
.BrowsingContextId(), args
,
367 promiseHolder
->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
373 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvRequestCancel(
374 const Tainted
<uint64_t>& aTransactionId
) {
375 ::mozilla::ipc::AssertIsOnBackgroundThread();
377 if (mTransactionId
.isNothing() ||
378 !MOZ_IS_VALID(aTransactionId
, mTransactionId
.ref() == aTransactionId
)) {
382 DisconnectTransaction();
386 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvRequestIsUVPAA(
387 RequestIsUVPAAResolver
&& aResolver
) {
388 #ifdef MOZ_WIDGET_ANDROID
389 // Try the nsIWebAuthnService. If we're configured for tests we
390 // will get a result. Otherwise we expect NS_ERROR_NOT_IMPLEMENTED.
391 nsCOMPtr
<nsIWebAuthnService
> service(
392 do_GetService("@mozilla.org/webauthn/service;1"));
394 nsresult rv
= service
->GetIsUVPAA(&available
);
395 if (NS_SUCCEEDED(rv
)) {
396 aResolver(available
);
400 // Don't consult the platform API if resident key support is disabled.
402 security_webauthn_webauthn_enable_android_fido2_residentkey()) {
407 // The GeckoView implementation of
408 // isUserVerifiyingPlatformAuthenticatorAvailable does not block, but we must
409 // call it on the main thread. It returns a MozPromise which we can ->Then to
410 // call aResolver on the IPDL background thread.
412 // Bug 1550788: there is an unnecessary layer of dispatching here: ipdl ->
413 // main -> a background thread. Other platforms just do ipdl -> a background
415 nsCOMPtr
<nsISerialEventTarget
> target
= GetCurrentSerialEventTarget();
416 nsCOMPtr
<nsIRunnable
> runnable(NS_NewRunnableFunction(
417 __func__
, [target
, resolver
= std::move(aResolver
)]() {
418 auto result
= java::WebAuthnTokenManager::
419 WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable();
420 auto geckoResult
= java::GeckoResult::LocalRef(std::move(result
));
421 MozPromise
<bool, bool, false>::FromGeckoResult(geckoResult
)
425 const MozPromise
<bool, bool, false>::ResolveOrRejectValue
&
427 if (aValue
.IsResolve()) {
428 resolver(aValue
.ResolveValue());
434 NS_DispatchToMainThread(runnable
.forget());
439 nsCOMPtr
<nsISerialEventTarget
> target
= GetCurrentSerialEventTarget();
440 nsCOMPtr
<nsIRunnable
> runnable(NS_NewRunnableFunction(
441 __func__
, [target
, resolver
= std::move(aResolver
)]() {
443 nsCOMPtr
<nsIWebAuthnService
> service(
444 do_GetService("@mozilla.org/webauthn/service;1"));
445 nsresult rv
= service
->GetIsUVPAA(&available
);
449 BoolPromise::CreateAndResolve(available
, __func__
)
450 ->Then(target
, __func__
,
451 [resolver
](const BoolPromise::ResolveOrRejectValue
& value
) {
452 if (value
.IsResolve()) {
453 resolver(value
.ResolveValue());
459 NS_DispatchBackgroundTask(runnable
.forget(), NS_DISPATCH_EVENT_MAY_BLOCK
);
464 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvDestroyMe() {
465 ::mozilla::ipc::AssertIsOnBackgroundThread();
467 // The child was disconnected from the WebAuthnManager instance and will send
468 // no further messages. It is kept alive until we delete it explicitly.
470 // The child should have cancelled any active transaction. This means
471 // we expect no more messages to the child. We'll crash otherwise.
473 // The IPC roundtrip is complete. No more messages, hopefully.
474 IProtocol
* mgr
= Manager();
475 if (!Send__delete__(this)) {
476 return IPC_FAIL_NO_REASON(mgr
);
482 void WebAuthnTransactionParent::ActorDestroy(ActorDestroyReason aWhy
) {
483 ::mozilla::ipc::AssertIsOnBackgroundThread();
485 // Called either by Send__delete__() in RecvDestroyMe() above, or when
486 // the channel disconnects. Ensure the token manager forgets about us.
488 if (mTransactionId
.isSome()) {
489 DisconnectTransaction();
493 } // namespace mozilla::dom