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
));
146 WebAuthnMakeCredentialResult
result(
147 clientData
, attObj
, credentialId
, transports
, extensions
,
148 authenticatorAttachment
);
150 Unused
<< parent
->SendConfirmRegister(aTransactionId
, result
);
152 [this, parent
, aTransactionId
](
153 const WebAuthnRegisterPromise::RejectValueType aValue
) {
154 CompleteTransaction();
155 Unused
<< parent
->SendAbort(aTransactionId
, aValue
);
157 ->Track(mRegisterPromiseRequest
);
159 uint64_t browsingContextId
= aTransactionInfo
.BrowsingContextId();
160 RefPtr
<WebAuthnRegisterArgs
> args(new WebAuthnRegisterArgs(aTransactionInfo
));
162 nsresult rv
= mWebAuthnService
->MakeCredential(
163 aTransactionId
, browsingContextId
, args
, promiseHolder
);
165 promiseHolder
->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
171 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvRequestSign(
172 const uint64_t& aTransactionId
,
173 const WebAuthnGetAssertionInfo
& aTransactionInfo
) {
174 ::mozilla::ipc::AssertIsOnBackgroundThread();
176 if (!mWebAuthnService
) {
177 mWebAuthnService
= do_GetService("@mozilla.org/webauthn/service;1");
178 if (!mWebAuthnService
) {
179 return IPC_FAIL_NO_REASON(this);
183 if (mTransactionId
.isSome()) {
184 DisconnectTransaction();
185 Unused
<< SendAbort(mTransactionId
.ref(), NS_ERROR_DOM_ABORT_ERR
);
187 mTransactionId
= Some(aTransactionId
);
189 RefPtr
<WebAuthnSignPromiseHolder
> promiseHolder
=
190 new WebAuthnSignPromiseHolder(GetCurrentSerialEventTarget());
192 PWebAuthnTransactionParent
* parent
= this;
193 RefPtr
<WebAuthnSignPromise
> promise
= promiseHolder
->Ensure();
196 GetCurrentSerialEventTarget(), __func__
,
197 [this, parent
, aTransactionId
,
198 inputClientData
= aTransactionInfo
.ClientDataJSON()](
199 const WebAuthnSignPromise::ResolveValueType
& aValue
) {
200 CompleteTransaction();
202 nsCString clientData
;
203 nsresult rv
= aValue
->GetClientDataJSON(clientData
);
204 if (rv
== NS_ERROR_NOT_AVAILABLE
) {
205 clientData
= inputClientData
;
206 } else if (NS_FAILED(rv
)) {
207 Unused
<< parent
->SendAbort(aTransactionId
,
208 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
212 nsTArray
<uint8_t> credentialId
;
213 rv
= aValue
->GetCredentialId(credentialId
);
214 if (NS_WARN_IF(NS_FAILED(rv
))) {
215 Unused
<< parent
->SendAbort(aTransactionId
,
216 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
220 nsTArray
<uint8_t> signature
;
221 rv
= aValue
->GetSignature(signature
);
222 if (NS_WARN_IF(NS_FAILED(rv
))) {
223 Unused
<< parent
->SendAbort(aTransactionId
,
224 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
228 nsTArray
<uint8_t> authenticatorData
;
229 rv
= aValue
->GetAuthenticatorData(authenticatorData
);
230 if (NS_WARN_IF(NS_FAILED(rv
))) {
231 Unused
<< parent
->SendAbort(aTransactionId
,
232 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
236 nsTArray
<uint8_t> userHandle
;
237 Unused
<< aValue
->GetUserHandle(userHandle
); // optional
239 Maybe
<nsString
> authenticatorAttachment
;
240 nsString maybeAuthenticatorAttachment
;
241 rv
= aValue
->GetAuthenticatorAttachment(
242 maybeAuthenticatorAttachment
);
243 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
244 if (NS_WARN_IF(NS_FAILED(rv
))) {
245 Unused
<< parent
->SendAbort(aTransactionId
,
246 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
249 authenticatorAttachment
= Some(maybeAuthenticatorAttachment
);
252 nsTArray
<WebAuthnExtensionResult
> extensions
;
254 rv
= aValue
->GetUsedAppId(&usedAppId
);
255 if (rv
!= NS_ERROR_NOT_AVAILABLE
) {
257 Unused
<< parent
->SendAbort(aTransactionId
,
258 NS_ERROR_DOM_NOT_ALLOWED_ERR
);
261 extensions
.AppendElement(WebAuthnExtensionResultAppId(usedAppId
));
264 WebAuthnGetAssertionResult
result(
265 clientData
, credentialId
, signature
, authenticatorData
,
266 extensions
, userHandle
, authenticatorAttachment
);
268 Unused
<< parent
->SendConfirmSign(aTransactionId
, result
);
271 aTransactionId
](const WebAuthnSignPromise::RejectValueType aValue
) {
272 CompleteTransaction();
273 Unused
<< parent
->SendAbort(aTransactionId
, aValue
);
275 ->Track(mSignPromiseRequest
);
277 RefPtr
<WebAuthnSignArgs
> args(new WebAuthnSignArgs(aTransactionInfo
));
279 nsresult rv
= mWebAuthnService
->GetAssertion(
280 aTransactionId
, aTransactionInfo
.BrowsingContextId(), args
,
283 promiseHolder
->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
289 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvRequestCancel(
290 const Tainted
<uint64_t>& aTransactionId
) {
291 ::mozilla::ipc::AssertIsOnBackgroundThread();
293 if (mTransactionId
.isNothing() ||
294 !MOZ_IS_VALID(aTransactionId
, mTransactionId
.ref() == aTransactionId
)) {
298 DisconnectTransaction();
302 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvRequestIsUVPAA(
303 RequestIsUVPAAResolver
&& aResolver
) {
304 #ifdef MOZ_WIDGET_ANDROID
305 // Try the nsIWebAuthnService. If we're configured for tests we
306 // will get a result. Otherwise we expect NS_ERROR_NOT_IMPLEMENTED.
307 nsCOMPtr
<nsIWebAuthnService
> service(
308 do_GetService("@mozilla.org/webauthn/service;1"));
310 nsresult rv
= service
->GetIsUVPAA(&available
);
311 if (NS_SUCCEEDED(rv
)) {
312 aResolver(available
);
316 // Don't consult the platform API if resident key support is disabled.
318 security_webauthn_webauthn_enable_android_fido2_residentkey()) {
323 // The GeckoView implementation of
324 // isUserVerifiyingPlatformAuthenticatorAvailable does not block, but we must
325 // call it on the main thread. It returns a MozPromise which we can ->Then to
326 // call aResolver on the IPDL background thread.
328 // Bug 1550788: there is an unnecessary layer of dispatching here: ipdl ->
329 // main -> a background thread. Other platforms just do ipdl -> a background
331 nsCOMPtr
<nsISerialEventTarget
> target
= GetCurrentSerialEventTarget();
332 nsCOMPtr
<nsIRunnable
> runnable(NS_NewRunnableFunction(
333 __func__
, [target
, resolver
= std::move(aResolver
)]() {
334 auto result
= java::WebAuthnTokenManager::
335 WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable();
336 auto geckoResult
= java::GeckoResult::LocalRef(std::move(result
));
337 MozPromise
<bool, bool, false>::FromGeckoResult(geckoResult
)
341 const MozPromise
<bool, bool, false>::ResolveOrRejectValue
&
343 if (aValue
.IsResolve()) {
344 resolver(aValue
.ResolveValue());
350 NS_DispatchToMainThread(runnable
.forget());
355 nsCOMPtr
<nsISerialEventTarget
> target
= GetCurrentSerialEventTarget();
356 nsCOMPtr
<nsIRunnable
> runnable(NS_NewRunnableFunction(
357 __func__
, [target
, resolver
= std::move(aResolver
)]() {
359 nsCOMPtr
<nsIWebAuthnService
> service(
360 do_GetService("@mozilla.org/webauthn/service;1"));
361 nsresult rv
= service
->GetIsUVPAA(&available
);
365 BoolPromise::CreateAndResolve(available
, __func__
)
366 ->Then(target
, __func__
,
367 [resolver
](const BoolPromise::ResolveOrRejectValue
& value
) {
368 if (value
.IsResolve()) {
369 resolver(value
.ResolveValue());
375 NS_DispatchBackgroundTask(runnable
.forget(), NS_DISPATCH_EVENT_MAY_BLOCK
);
380 mozilla::ipc::IPCResult
WebAuthnTransactionParent::RecvDestroyMe() {
381 ::mozilla::ipc::AssertIsOnBackgroundThread();
383 // The child was disconnected from the WebAuthnManager instance and will send
384 // no further messages. It is kept alive until we delete it explicitly.
386 // The child should have cancelled any active transaction. This means
387 // we expect no more messages to the child. We'll crash otherwise.
389 // The IPC roundtrip is complete. No more messages, hopefully.
390 IProtocol
* mgr
= Manager();
391 if (!Send__delete__(this)) {
392 return IPC_FAIL_NO_REASON(mgr
);
398 void WebAuthnTransactionParent::ActorDestroy(ActorDestroyReason aWhy
) {
399 ::mozilla::ipc::AssertIsOnBackgroundThread();
401 // Called either by Send__delete__() in RecvDestroyMe() above, or when
402 // the channel disconnects. Ensure the token manager forgets about us.
404 if (mTransactionId
.isSome()) {
405 DisconnectTransaction();
409 } // namespace mozilla::dom