Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / webauthn / WebAuthnService.cpp
blob0c214ccd908b201f88da29c5b7da7c59e2ee6927
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "mozilla/Services.h"
6 #include "mozilla/StaticPrefs_security.h"
7 #include "nsIObserverService.h"
8 #include "nsTextFormatter.h"
9 #include "nsThreadUtils.h"
10 #include "WebAuthnEnumStrings.h"
11 #include "WebAuthnService.h"
12 #include "WebAuthnTransportIdentifiers.h"
14 namespace mozilla::dom {
16 already_AddRefed<nsIWebAuthnService> NewWebAuthnService() {
17 nsCOMPtr<nsIWebAuthnService> webauthnService(new WebAuthnService());
18 return webauthnService.forget();
21 NS_IMPL_ISUPPORTS(WebAuthnService, nsIWebAuthnService)
23 void WebAuthnService::ShowAttestationConsentPrompt(
24 const nsString& aOrigin, uint64_t aTransactionId,
25 uint64_t aBrowsingContextId) {
26 RefPtr<WebAuthnService> self = this;
27 #ifdef MOZ_WIDGET_ANDROID
28 // We don't have a way to prompt the user for consent on Android, so just
29 // assume consent not granted.
30 nsCOMPtr<nsIRunnable> runnable(
31 NS_NewRunnableFunction(__func__, [self, aTransactionId]() {
32 self->SetHasAttestationConsent(
33 aTransactionId,
34 StaticPrefs::
35 security_webauth_webauthn_testing_allow_direct_attestation());
36 }));
37 #else
38 nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
39 __func__, [self, aOrigin, aTransactionId, aBrowsingContextId]() {
40 if (StaticPrefs::
41 security_webauth_webauthn_testing_allow_direct_attestation()) {
42 self->SetHasAttestationConsent(aTransactionId, true);
43 return;
45 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
46 if (!os) {
47 return;
49 const nsLiteralString jsonFmt =
50 u"{\"prompt\": {\"type\":\"attestation-consent\"},"_ns
51 u"\"origin\": \"%S\","_ns
52 u"\"tid\": %llu, \"browsingContextId\": %llu}"_ns;
53 nsString json;
54 nsTextFormatter::ssprintf(json, jsonFmt.get(), aOrigin.get(),
55 aTransactionId, aBrowsingContextId);
56 MOZ_ALWAYS_SUCCEEDS(
57 os->NotifyObservers(nullptr, "webauthn-prompt", json.get()));
58 }));
59 #endif
60 NS_DispatchToMainThread(runnable.forget());
63 NS_IMETHODIMP
64 WebAuthnService::MakeCredential(uint64_t aTransactionId,
65 uint64_t aBrowsingContextId,
66 nsIWebAuthnRegisterArgs* aArgs,
67 nsIWebAuthnRegisterPromise* aPromise) {
68 MOZ_ASSERT(aArgs);
69 MOZ_ASSERT(aPromise);
71 auto guard = mTransactionState.Lock();
72 ResetLocked(guard);
73 *guard = Some(TransactionState{.service = DefaultService(),
74 .transactionId = aTransactionId,
75 .parentRegisterPromise = Some(aPromise)});
77 // We may need to show an attestation consent prompt before we return a
78 // credential to WebAuthnTransactionParent, so we insert a new promise that
79 // chains to `aPromise` here.
81 nsString attestation;
82 Unused << aArgs->GetAttestationConveyancePreference(attestation);
83 bool attestationRequested = !attestation.EqualsLiteral(
84 MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE);
86 nsString origin;
87 Unused << aArgs->GetOrigin(origin);
89 RefPtr<WebAuthnRegisterPromiseHolder> promiseHolder =
90 new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
92 RefPtr<WebAuthnService> self = this;
93 RefPtr<WebAuthnRegisterPromise> promise = promiseHolder->Ensure();
94 promise
95 ->Then(
96 GetCurrentSerialEventTarget(), __func__,
97 [self, origin, aTransactionId, aBrowsingContextId,
98 attestationRequested](
99 const WebAuthnRegisterPromise::ResolveOrRejectValue& aValue) {
100 auto guard = self->mTransactionState.Lock();
101 if (guard->isNothing()) {
102 return;
104 MOZ_ASSERT(guard->ref().parentRegisterPromise.isSome());
105 MOZ_ASSERT(guard->ref().registerResult.isNothing());
106 MOZ_ASSERT(guard->ref().childRegisterRequest.Exists());
108 guard->ref().childRegisterRequest.Complete();
110 if (aValue.IsReject()) {
111 guard->ref().parentRegisterPromise.ref()->Reject(
112 aValue.RejectValue());
113 guard->reset();
114 return;
117 nsIWebAuthnRegisterResult* result = aValue.ResolveValue();
118 // If the RP requested attestation, we need to show a consent prompt
119 // before returning any identifying information. The platform may
120 // have already done this for us, so we need to inspect the
121 // attestation object at this point.
122 bool resultIsIdentifying = true;
123 Unused << result->HasIdentifyingAttestation(&resultIsIdentifying);
124 if (attestationRequested && resultIsIdentifying) {
125 guard->ref().registerResult = Some(result);
126 self->ShowAttestationConsentPrompt(origin, aTransactionId,
127 aBrowsingContextId);
128 return;
130 result->Anonymize();
131 guard->ref().parentRegisterPromise.ref()->Resolve(result);
132 guard->reset();
134 ->Track(guard->ref().childRegisterRequest);
136 nsresult rv = guard->ref().service->MakeCredential(
137 aTransactionId, aBrowsingContextId, aArgs, promiseHolder);
138 if (NS_FAILED(rv)) {
139 promiseHolder->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
141 return NS_OK;
144 NS_IMETHODIMP
145 WebAuthnService::GetAssertion(uint64_t aTransactionId,
146 uint64_t aBrowsingContextId,
147 nsIWebAuthnSignArgs* aArgs,
148 nsIWebAuthnSignPromise* aPromise) {
149 MOZ_ASSERT(aArgs);
150 MOZ_ASSERT(aPromise);
152 auto guard = mTransactionState.Lock();
153 ResetLocked(guard);
154 *guard = Some(TransactionState{.service = DefaultService(),
155 .transactionId = aTransactionId});
156 nsresult rv;
158 #if defined(XP_MACOSX)
159 // The macOS security key API doesn't handle the AppID extension. So we'll
160 // use authenticator-rs if it's likely that the request requires AppID. We
161 // consider it likely if 1) the AppID extension is present, 2) the allow list
162 // is non-empty, and 3) none of the allowed credentials use the
163 // "internal" or "hybrid" transport.
164 nsString appId;
165 rv = aArgs->GetAppId(appId);
166 if (rv == NS_OK) { // AppID is set
167 uint8_t transportSet = 0;
168 nsTArray<uint8_t> allowListTransports;
169 Unused << aArgs->GetAllowListTransports(allowListTransports);
170 for (const uint8_t& transport : allowListTransports) {
171 transportSet |= transport;
173 uint8_t passkeyTransportMask =
174 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_INTERNAL |
175 MOZ_WEBAUTHN_AUTHENTICATOR_TRANSPORT_ID_HYBRID;
176 if (allowListTransports.Length() > 0 &&
177 (transportSet & passkeyTransportMask) == 0) {
178 guard->ref().service = AuthrsService();
181 #endif
183 rv = guard->ref().service->GetAssertion(aTransactionId, aBrowsingContextId,
184 aArgs, aPromise);
185 if (NS_FAILED(rv)) {
186 return rv;
189 // If this is a conditionally mediated request, notify observers that there
190 // is a pending transaction. This is mainly useful in tests.
191 bool conditionallyMediated;
192 Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
193 if (conditionallyMediated) {
194 nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(__func__, []() {
195 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
196 if (os) {
197 os->NotifyObservers(nullptr, "webauthn:conditional-get-pending",
198 nullptr);
200 }));
201 NS_DispatchToMainThread(runnable.forget());
204 return NS_OK;
207 NS_IMETHODIMP
208 WebAuthnService::GetIsUVPAA(bool* aAvailable) {
209 return DefaultService()->GetIsUVPAA(aAvailable);
212 NS_IMETHODIMP
213 WebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
214 const nsAString& aOrigin,
215 uint64_t* aRv) {
216 return SelectedService()->HasPendingConditionalGet(aBrowsingContextId,
217 aOrigin, aRv);
220 NS_IMETHODIMP
221 WebAuthnService::GetAutoFillEntries(
222 uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
223 return SelectedService()->GetAutoFillEntries(aTransactionId, aRv);
226 NS_IMETHODIMP
227 WebAuthnService::SelectAutoFillEntry(uint64_t aTransactionId,
228 const nsTArray<uint8_t>& aCredentialId) {
229 return SelectedService()->SelectAutoFillEntry(aTransactionId, aCredentialId);
232 NS_IMETHODIMP
233 WebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
234 return SelectedService()->ResumeConditionalGet(aTransactionId);
237 void WebAuthnService::ResetLocked(
238 const TransactionStateMutex::AutoLock& aGuard) {
239 if (aGuard->isSome()) {
240 aGuard->ref().childRegisterRequest.DisconnectIfExists();
241 if (aGuard->ref().parentRegisterPromise.isSome()) {
242 aGuard->ref().parentRegisterPromise.ref()->Reject(NS_ERROR_DOM_ABORT_ERR);
244 aGuard->ref().service->Reset();
246 aGuard->reset();
249 NS_IMETHODIMP
250 WebAuthnService::Reset() {
251 auto guard = mTransactionState.Lock();
252 ResetLocked(guard);
253 return NS_OK;
256 NS_IMETHODIMP
257 WebAuthnService::Cancel(uint64_t aTransactionId) {
258 return SelectedService()->Cancel(aTransactionId);
261 NS_IMETHODIMP
262 WebAuthnService::PinCallback(uint64_t aTransactionId, const nsACString& aPin) {
263 return SelectedService()->PinCallback(aTransactionId, aPin);
266 NS_IMETHODIMP
267 WebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
268 bool aHasConsent) {
269 auto guard = this->mTransactionState.Lock();
270 if (guard->isNothing() || guard->ref().transactionId != aTransactionId) {
271 // This could happen if the transaction was reset just when the prompt was
272 // receiving user input.
273 return NS_OK;
276 MOZ_ASSERT(guard->ref().parentRegisterPromise.isSome());
277 MOZ_ASSERT(guard->ref().registerResult.isSome());
278 MOZ_ASSERT(!guard->ref().childRegisterRequest.Exists());
280 if (!aHasConsent) {
281 guard->ref().registerResult.ref()->Anonymize();
283 guard->ref().parentRegisterPromise.ref()->Resolve(
284 guard->ref().registerResult.ref());
286 guard->reset();
287 return NS_OK;
290 NS_IMETHODIMP
291 WebAuthnService::SelectionCallback(uint64_t aTransactionId, uint64_t aIndex) {
292 return SelectedService()->SelectionCallback(aTransactionId, aIndex);
295 NS_IMETHODIMP
296 WebAuthnService::AddVirtualAuthenticator(
297 const nsACString& protocol, const nsACString& transport,
298 bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
299 bool isUserVerified, uint64_t* retval) {
300 return SelectedService()->AddVirtualAuthenticator(
301 protocol, transport, hasResidentKey, hasUserVerification,
302 isUserConsenting, isUserVerified, retval);
305 NS_IMETHODIMP
306 WebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
307 return SelectedService()->RemoveVirtualAuthenticator(authenticatorId);
310 NS_IMETHODIMP
311 WebAuthnService::AddCredential(uint64_t authenticatorId,
312 const nsACString& credentialId,
313 bool isResidentCredential,
314 const nsACString& rpId,
315 const nsACString& privateKey,
316 const nsACString& userHandle,
317 uint32_t signCount) {
318 return SelectedService()->AddCredential(authenticatorId, credentialId,
319 isResidentCredential, rpId,
320 privateKey, userHandle, signCount);
323 NS_IMETHODIMP
324 WebAuthnService::GetCredentials(
325 uint64_t authenticatorId,
326 nsTArray<RefPtr<nsICredentialParameters>>& retval) {
327 return SelectedService()->GetCredentials(authenticatorId, retval);
330 NS_IMETHODIMP
331 WebAuthnService::RemoveCredential(uint64_t authenticatorId,
332 const nsACString& credentialId) {
333 return SelectedService()->RemoveCredential(authenticatorId, credentialId);
336 NS_IMETHODIMP
337 WebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
338 return SelectedService()->RemoveAllCredentials(authenticatorId);
341 NS_IMETHODIMP
342 WebAuthnService::SetUserVerified(uint64_t authenticatorId,
343 bool isUserVerified) {
344 return SelectedService()->SetUserVerified(authenticatorId, isUserVerified);
347 NS_IMETHODIMP
348 WebAuthnService::Listen() { return SelectedService()->Listen(); }
350 NS_IMETHODIMP
351 WebAuthnService::RunCommand(const nsACString& cmd) {
352 return SelectedService()->RunCommand(cmd);
355 } // namespace mozilla::dom