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(
35 security_webauth_webauthn_testing_allow_direct_attestation());
38 nsCOMPtr
<nsIRunnable
> runnable(NS_NewRunnableFunction(
39 __func__
, [self
, aOrigin
, aTransactionId
, aBrowsingContextId
]() {
41 security_webauth_webauthn_testing_allow_direct_attestation()) {
42 self
->SetHasAttestationConsent(aTransactionId
, true);
45 nsCOMPtr
<nsIObserverService
> os
= services::GetObserverService();
49 const nsLiteralString jsonFmt
=
50 u
"{\"prompt\": {\"type\":\"attestation-consent\"},"_ns
51 u
"\"origin\": \"%S\","_ns
52 u
"\"tid\": %llu, \"browsingContextId\": %llu}"_ns
;
54 nsTextFormatter::ssprintf(json
, jsonFmt
.get(), aOrigin
.get(),
55 aTransactionId
, aBrowsingContextId
);
57 os
->NotifyObservers(nullptr, "webauthn-prompt", json
.get()));
60 NS_DispatchToMainThread(runnable
.forget());
64 WebAuthnService::MakeCredential(uint64_t aTransactionId
,
65 uint64_t aBrowsingContextId
,
66 nsIWebAuthnRegisterArgs
* aArgs
,
67 nsIWebAuthnRegisterPromise
* aPromise
) {
71 auto guard
= mTransactionState
.Lock();
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.
82 Unused
<< aArgs
->GetAttestationConveyancePreference(attestation
);
83 bool attestationRequested
= !attestation
.EqualsLiteral(
84 MOZ_WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE
);
87 Unused
<< aArgs
->GetOrigin(origin
);
89 RefPtr
<WebAuthnRegisterPromiseHolder
> promiseHolder
=
90 new WebAuthnRegisterPromiseHolder(GetCurrentSerialEventTarget());
92 RefPtr
<WebAuthnService
> self
= this;
93 RefPtr
<WebAuthnRegisterPromise
> promise
= promiseHolder
->Ensure();
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()) {
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());
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
,
131 guard
->ref().parentRegisterPromise
.ref()->Resolve(result
);
134 ->Track(guard
->ref().childRegisterRequest
);
136 nsresult rv
= guard
->ref().service
->MakeCredential(
137 aTransactionId
, aBrowsingContextId
, aArgs
, promiseHolder
);
139 promiseHolder
->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
145 WebAuthnService::GetAssertion(uint64_t aTransactionId
,
146 uint64_t aBrowsingContextId
,
147 nsIWebAuthnSignArgs
* aArgs
,
148 nsIWebAuthnSignPromise
* aPromise
) {
150 MOZ_ASSERT(aPromise
);
152 auto guard
= mTransactionState
.Lock();
154 *guard
= Some(TransactionState
{.service
= DefaultService(),
155 .transactionId
= aTransactionId
});
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.
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();
183 rv
= guard
->ref().service
->GetAssertion(aTransactionId
, aBrowsingContextId
,
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();
197 os
->NotifyObservers(nullptr, "webauthn:conditional-get-pending",
201 NS_DispatchToMainThread(runnable
.forget());
208 WebAuthnService::GetIsUVPAA(bool* aAvailable
) {
209 return DefaultService()->GetIsUVPAA(aAvailable
);
213 WebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId
,
214 const nsAString
& aOrigin
,
216 return SelectedService()->HasPendingConditionalGet(aBrowsingContextId
,
221 WebAuthnService::GetAutoFillEntries(
222 uint64_t aTransactionId
, nsTArray
<RefPtr
<nsIWebAuthnAutoFillEntry
>>& aRv
) {
223 return SelectedService()->GetAutoFillEntries(aTransactionId
, aRv
);
227 WebAuthnService::SelectAutoFillEntry(uint64_t aTransactionId
,
228 const nsTArray
<uint8_t>& aCredentialId
) {
229 return SelectedService()->SelectAutoFillEntry(aTransactionId
, aCredentialId
);
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();
250 WebAuthnService::Reset() {
251 auto guard
= mTransactionState
.Lock();
257 WebAuthnService::Cancel(uint64_t aTransactionId
) {
258 return SelectedService()->Cancel(aTransactionId
);
262 WebAuthnService::PinCallback(uint64_t aTransactionId
, const nsACString
& aPin
) {
263 return SelectedService()->PinCallback(aTransactionId
, aPin
);
267 WebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId
,
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.
276 MOZ_ASSERT(guard
->ref().parentRegisterPromise
.isSome());
277 MOZ_ASSERT(guard
->ref().registerResult
.isSome());
278 MOZ_ASSERT(!guard
->ref().childRegisterRequest
.Exists());
281 guard
->ref().registerResult
.ref()->Anonymize();
283 guard
->ref().parentRegisterPromise
.ref()->Resolve(
284 guard
->ref().registerResult
.ref());
291 WebAuthnService::SelectionCallback(uint64_t aTransactionId
, uint64_t aIndex
) {
292 return SelectedService()->SelectionCallback(aTransactionId
, aIndex
);
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
);
306 WebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId
) {
307 return SelectedService()->RemoveVirtualAuthenticator(authenticatorId
);
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
);
324 WebAuthnService::GetCredentials(
325 uint64_t authenticatorId
,
326 nsTArray
<RefPtr
<nsICredentialParameters
>>& retval
) {
327 return SelectedService()->GetCredentials(authenticatorId
, retval
);
331 WebAuthnService::RemoveCredential(uint64_t authenticatorId
,
332 const nsACString
& credentialId
) {
333 return SelectedService()->RemoveCredential(authenticatorId
, credentialId
);
337 WebAuthnService::RemoveAllCredentials(uint64_t authenticatorId
) {
338 return SelectedService()->RemoveAllCredentials(authenticatorId
);
342 WebAuthnService::SetUserVerified(uint64_t authenticatorId
,
343 bool isUserVerified
) {
344 return SelectedService()->SetUserVerified(authenticatorId
, isUserVerified
);
348 WebAuthnService::Listen() { return SelectedService()->Listen(); }
351 WebAuthnService::RunCommand(const nsACString
& cmd
) {
352 return SelectedService()->RunCommand(cmd
);
355 } // namespace mozilla::dom