Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / dom / webauthn / WebAuthnTransactionParent.cpp
bloba2c175402d9fbb513139d6bd476a36bcc520d8e1
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();
66 promise
67 ->Then(
68 GetCurrentSerialEventTarget(), __func__,
69 [this, parent, aTransactionId,
70 inputClientData = aTransactionInfo.ClientDataJSON()](
71 const WebAuthnRegisterPromise::ResolveValueType& aValue) {
72 CompleteTransaction();
74 nsCString clientData;
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);
81 return;
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);
89 return;
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);
97 return;
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);
105 return;
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);
116 return;
118 authenticatorAttachment = Some(maybeAuthenticatorAttachment);
121 nsTArray<WebAuthnExtensionResult> extensions;
122 bool credPropsRk;
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);
128 return;
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);
140 return;
142 extensions.AppendElement(
143 WebAuthnExtensionResultHmacSecret(hmacCreateSecret));
147 Maybe<bool> prfEnabledMaybe = Nothing();
148 Maybe<WebAuthnExtensionPrfValues> prfResults = Nothing();
150 bool prfEnabled;
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);
156 return;
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);
167 return;
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);
177 return;
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);
210 if (NS_FAILED(rv)) {
211 promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
214 return IPC_OK();
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();
240 promise
241 ->Then(
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);
255 return;
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);
263 return;
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);
271 return;
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);
279 return;
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);
293 return;
295 authenticatorAttachment = Some(maybeAuthenticatorAttachment);
298 nsTArray<WebAuthnExtensionResult> extensions;
299 bool usedAppId;
300 rv = aValue->GetUsedAppId(&usedAppId);
301 if (rv != NS_ERROR_NOT_AVAILABLE) {
302 if (NS_FAILED(rv)) {
303 Unused << parent->SendAbort(aTransactionId,
304 NS_ERROR_DOM_NOT_ALLOWED_ERR);
305 return;
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);
321 return;
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);
331 return;
333 prfResultsSecondMaybe = true;
336 prfResults = Some(WebAuthnExtensionPrfValues(
337 prfResultsFirst, prfResultsSecondMaybe,
338 prfResultsSecond));
339 } else {
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);
354 [this, parent,
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,
365 promiseHolder);
366 if (NS_FAILED(rv)) {
367 promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
370 return IPC_OK();
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)) {
379 return IPC_OK();
382 DisconnectTransaction();
383 return IPC_OK();
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"));
393 bool available;
394 nsresult rv = service->GetIsUVPAA(&available);
395 if (NS_SUCCEEDED(rv)) {
396 aResolver(available);
397 return IPC_OK();
400 // Don't consult the platform API if resident key support is disabled.
401 if (!StaticPrefs::
402 security_webauthn_webauthn_enable_android_fido2_residentkey()) {
403 aResolver(false);
404 return IPC_OK();
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
414 // thread.
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)
422 ->Then(
423 target, __func__,
424 [resolver](
425 const MozPromise<bool, bool, false>::ResolveOrRejectValue&
426 aValue) {
427 if (aValue.IsResolve()) {
428 resolver(aValue.ResolveValue());
429 } else {
430 resolver(false);
433 }));
434 NS_DispatchToMainThread(runnable.forget());
435 return IPC_OK();
437 #else
439 nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
440 nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
441 __func__, [target, resolver = std::move(aResolver)]() {
442 bool available;
443 nsCOMPtr<nsIWebAuthnService> service(
444 do_GetService("@mozilla.org/webauthn/service;1"));
445 nsresult rv = service->GetIsUVPAA(&available);
446 if (NS_FAILED(rv)) {
447 available = false;
449 BoolPromise::CreateAndResolve(available, __func__)
450 ->Then(target, __func__,
451 [resolver](const BoolPromise::ResolveOrRejectValue& value) {
452 if (value.IsResolve()) {
453 resolver(value.ResolveValue());
454 } else {
455 resolver(false);
458 }));
459 NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
460 return IPC_OK();
461 #endif
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);
479 return IPC_OK();
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