Bug 1857841 - pt 3. Add a new page kind named "fresh" r=glandium
[gecko.git] / dom / webauthn / WebAuthnTransactionParent.cpp
blob697e8dc970f47b01162f171748be52251be5d6a9
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));
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);
164 if (NS_FAILED(rv)) {
165 promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
168 return IPC_OK();
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();
194 promise
195 ->Then(
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);
209 return;
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);
217 return;
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);
225 return;
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);
233 return;
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);
247 return;
249 authenticatorAttachment = Some(maybeAuthenticatorAttachment);
252 nsTArray<WebAuthnExtensionResult> extensions;
253 bool usedAppId;
254 rv = aValue->GetUsedAppId(&usedAppId);
255 if (rv != NS_ERROR_NOT_AVAILABLE) {
256 if (NS_FAILED(rv)) {
257 Unused << parent->SendAbort(aTransactionId,
258 NS_ERROR_DOM_NOT_ALLOWED_ERR);
259 return;
261 extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId));
264 WebAuthnGetAssertionResult result(
265 clientData, credentialId, signature, authenticatorData,
266 extensions, userHandle, authenticatorAttachment);
268 Unused << parent->SendConfirmSign(aTransactionId, result);
270 [this, parent,
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,
281 promiseHolder);
282 if (NS_FAILED(rv)) {
283 promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
286 return IPC_OK();
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)) {
295 return IPC_OK();
298 DisconnectTransaction();
299 return IPC_OK();
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"));
309 bool available;
310 nsresult rv = service->GetIsUVPAA(&available);
311 if (NS_SUCCEEDED(rv)) {
312 aResolver(available);
313 return IPC_OK();
316 // Don't consult the platform API if resident key support is disabled.
317 if (!StaticPrefs::
318 security_webauthn_webauthn_enable_android_fido2_residentkey()) {
319 aResolver(false);
320 return IPC_OK();
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
330 // thread.
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)
338 ->Then(
339 target, __func__,
340 [resolver](
341 const MozPromise<bool, bool, false>::ResolveOrRejectValue&
342 aValue) {
343 if (aValue.IsResolve()) {
344 resolver(aValue.ResolveValue());
345 } else {
346 resolver(false);
349 }));
350 NS_DispatchToMainThread(runnable.forget());
351 return IPC_OK();
353 #else
355 nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
356 nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
357 __func__, [target, resolver = std::move(aResolver)]() {
358 bool available;
359 nsCOMPtr<nsIWebAuthnService> service(
360 do_GetService("@mozilla.org/webauthn/service;1"));
361 nsresult rv = service->GetIsUVPAA(&available);
362 if (NS_FAILED(rv)) {
363 available = false;
365 BoolPromise::CreateAndResolve(available, __func__)
366 ->Then(target, __func__,
367 [resolver](const BoolPromise::ResolveOrRejectValue& value) {
368 if (value.IsResolve()) {
369 resolver(value.ResolveValue());
370 } else {
371 resolver(false);
374 }));
375 NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
376 return IPC_OK();
377 #endif
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);
395 return IPC_OK();
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