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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/ipc/BackgroundParent.h"
8 #include "mozilla/jni/GeckoBundleUtils.h"
9 #include "mozilla/StaticPtr.h"
11 #include "AndroidWebAuthnTokenManager.h"
12 #include "JavaBuiltins.h"
13 #include "JavaExceptions.h"
14 #include "mozilla/java/WebAuthnTokenManagerWrappers.h"
15 #include "mozilla/jni/Conversions.h"
16 #include "mozilla/StaticPrefs_security.h"
17 #include "WebAuthnEnumStrings.h"
23 dom::AndroidWebAuthnResult
Java2Native(mozilla::jni::Object::Param aData
,
26 // AndroidWebAuthnResult stores successful both result and failure result.
27 // We should split it into success and failure (Bug 1754157)
28 if (aData
.IsInstanceOf
<jni::Throwable
>()) {
29 java::sdk::Throwable::LocalRef
throwable(aData
);
30 return dom::AndroidWebAuthnResult(throwable
->GetMessage()->ToString());
34 .IsInstanceOf
<java::WebAuthnTokenManager::MakeCredentialResponse
>()) {
35 java::WebAuthnTokenManager::MakeCredentialResponse::LocalRef
response(
37 return dom::AndroidWebAuthnResult(response
);
41 aData
.IsInstanceOf
<java::WebAuthnTokenManager::GetAssertionResponse
>());
42 java::WebAuthnTokenManager::GetAssertionResponse::LocalRef
response(aData
);
43 return dom::AndroidWebAuthnResult(response
);
49 static nsIThread
* gAndroidPBackgroundThread
;
51 StaticRefPtr
<AndroidWebAuthnTokenManager
> gAndroidWebAuthnManager
;
53 /* static */ AndroidWebAuthnTokenManager
*
54 AndroidWebAuthnTokenManager::GetInstance() {
55 if (!gAndroidWebAuthnManager
) {
56 mozilla::ipc::AssertIsOnBackgroundThread();
57 gAndroidWebAuthnManager
= new AndroidWebAuthnTokenManager();
59 return gAndroidWebAuthnManager
;
62 AndroidWebAuthnTokenManager::AndroidWebAuthnTokenManager() {
63 mozilla::ipc::AssertIsOnBackgroundThread();
64 MOZ_ASSERT(XRE_IsParentProcess());
65 MOZ_ASSERT(!gAndroidWebAuthnManager
);
67 gAndroidPBackgroundThread
= NS_GetCurrentThread();
68 MOZ_ASSERT(gAndroidPBackgroundThread
, "This should never be null!");
69 gAndroidWebAuthnManager
= this;
72 void AndroidWebAuthnTokenManager::AssertIsOnOwningThread() const {
73 mozilla::ipc::AssertIsOnBackgroundThread();
74 MOZ_ASSERT(gAndroidPBackgroundThread
);
78 NS_SUCCEEDED(gAndroidPBackgroundThread
->IsOnCurrentThread(¤t
)));
83 void AndroidWebAuthnTokenManager::Drop() {
84 AssertIsOnOwningThread();
87 gAndroidWebAuthnManager
= nullptr;
88 gAndroidPBackgroundThread
= nullptr;
91 RefPtr
<U2FRegisterPromise
> AndroidWebAuthnTokenManager::Register(
92 const WebAuthnMakeCredentialInfo
& aInfo
, bool aForceNoneAttestation
) {
93 AssertIsOnOwningThread();
97 GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
98 "java::WebAuthnTokenManager::WebAuthnMakeCredential",
99 [self
= RefPtr
{this}, aInfo
, aForceNoneAttestation
]() {
100 AssertIsOnMainThread();
102 // Produce the credential exclusion list
103 jni::ObjectArray::LocalRef idList
=
104 jni::ObjectArray::New(aInfo
.ExcludeList().Length());
106 nsTArray
<uint8_t> transportBuf
;
109 for (const WebAuthnScopedCredential
& cred
: aInfo
.ExcludeList()) {
110 jni::ByteBuffer::LocalRef id
= jni::ByteBuffer::New(
111 const_cast<void*>(static_cast<const void*>(cred
.id().Elements())),
114 idList
->SetElement(ix
, id
);
115 transportBuf
.AppendElement(cred
.transports());
120 jni::ByteBuffer::LocalRef transportList
= jni::ByteBuffer::New(
122 static_cast<const void*>(transportBuf
.Elements())),
123 transportBuf
.Length());
125 const nsTArray
<uint8_t>& challBuf
= aInfo
.Challenge();
126 jni::ByteBuffer::LocalRef challenge
= jni::ByteBuffer::New(
127 const_cast<void*>(static_cast<const void*>(challBuf
.Elements())),
130 nsTArray
<uint8_t> uidBuf
;
132 // Get authenticator selection criteria
133 GECKOBUNDLE_START(authSelBundle
);
134 GECKOBUNDLE_START(extensionsBundle
);
135 GECKOBUNDLE_START(credentialBundle
);
137 const auto& rp
= aInfo
.Rp();
138 const auto& user
= aInfo
.User();
140 GECKOBUNDLE_PUT(credentialBundle
, "isWebAuthn",
141 java::sdk::Integer::ValueOf(1));
143 // Get the attestation preference and override if the user asked
144 if (aForceNoneAttestation
) {
145 // Add UI support to trigger this, bug 1550164
146 GECKOBUNDLE_PUT(authSelBundle
, "attestationPreference",
147 jni::StringParam(u
"none"_ns
));
149 const nsString
& attestation
= aInfo
.attestationConveyancePreference();
150 GECKOBUNDLE_PUT(authSelBundle
, "attestationPreference",
151 jni::StringParam(attestation
));
154 const WebAuthnAuthenticatorSelection
& sel
=
155 aInfo
.AuthenticatorSelection();
156 // Unfortunately, GMS's FIDO2 API has no option for Passkey. If using
157 // residentKey, credential will be synced with Passkey via Google
158 // account or credential provider service. So this is experimental.
160 security_webauthn_webauthn_enable_android_fido2_residentkey()) {
161 GECKOBUNDLE_PUT(authSelBundle
, "residentKey",
162 jni::StringParam(sel
.residentKey()));
165 if (sel
.userVerificationRequirement().EqualsLiteral(
166 MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED
)) {
167 GECKOBUNDLE_PUT(authSelBundle
, "requireUserVerification",
168 java::sdk::Integer::ValueOf(1));
171 if (sel
.authenticatorAttachment().isSome()) {
172 const nsString
& authenticatorAttachment
=
173 sel
.authenticatorAttachment().value();
174 if (authenticatorAttachment
.EqualsLiteral(
175 MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM
)) {
176 GECKOBUNDLE_PUT(authSelBundle
, "requirePlatformAttachment",
177 java::sdk::Integer::ValueOf(1));
179 authenticatorAttachment
.EqualsLiteral(
180 MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM
)) {
181 GECKOBUNDLE_PUT(authSelBundle
, "requireCrossPlatformAttachment",
182 java::sdk::Integer::ValueOf(1));
187 for (const WebAuthnExtension
& ext
: aInfo
.Extensions()) {
188 if (ext
.type() == WebAuthnExtension::TWebAuthnExtensionAppId
) {
190 extensionsBundle
, "fidoAppId",
192 ext
.get_WebAuthnExtensionAppId().appIdentifier()));
196 uidBuf
.Assign(user
.Id());
198 GECKOBUNDLE_PUT(credentialBundle
, "rpName",
199 jni::StringParam(rp
.Name()));
200 GECKOBUNDLE_PUT(credentialBundle
, "rpIcon",
201 jni::StringParam(rp
.Icon()));
202 GECKOBUNDLE_PUT(credentialBundle
, "userName",
203 jni::StringParam(user
.Name()));
204 GECKOBUNDLE_PUT(credentialBundle
, "userIcon",
205 jni::StringParam(user
.Icon()));
206 GECKOBUNDLE_PUT(credentialBundle
, "userDisplayName",
207 jni::StringParam(user
.DisplayName()));
209 GECKOBUNDLE_PUT(credentialBundle
, "rpId",
210 jni::StringParam(aInfo
.RpId()));
211 GECKOBUNDLE_PUT(credentialBundle
, "origin",
212 jni::StringParam(aInfo
.Origin()));
213 GECKOBUNDLE_PUT(credentialBundle
, "timeoutMS",
214 java::sdk::Double::New(aInfo
.TimeoutMS()));
216 GECKOBUNDLE_FINISH(authSelBundle
);
217 GECKOBUNDLE_FINISH(extensionsBundle
);
218 GECKOBUNDLE_FINISH(credentialBundle
);
220 // For non-WebAuthn cases, uidBuf is empty (and unused)
221 jni::ByteBuffer::LocalRef uid
= jni::ByteBuffer::New(
222 const_cast<void*>(static_cast<const void*>(uidBuf
.Elements())),
225 auto result
= java::WebAuthnTokenManager::WebAuthnMakeCredential(
226 credentialBundle
, uid
, challenge
, idList
, transportList
,
227 authSelBundle
, extensionsBundle
);
228 auto geckoResult
= java::GeckoResult::LocalRef(std::move(result
));
229 // This is likely running on the main thread, so we'll always dispatch
230 // to the background for state updates.
231 MozPromise
<AndroidWebAuthnResult
, AndroidWebAuthnResult
,
232 true>::FromGeckoResult(geckoResult
)
234 GetMainThreadSerialEventTarget(), __func__
,
235 [self
= std::move(self
)](AndroidWebAuthnResult
&& aValue
) {
236 self
->HandleRegisterResult(std::move(aValue
));
238 [self
= std::move(self
)](AndroidWebAuthnResult
&& aValue
) {
239 self
->HandleRegisterResult(std::move(aValue
));
243 return mRegisterPromise
.Ensure(__func__
);
246 void AndroidWebAuthnTokenManager::HandleRegisterResult(
247 AndroidWebAuthnResult
&& aResult
) {
248 if (!gAndroidPBackgroundThread
) {
249 // Promise is already rejected when shutting down background thread
252 // This is likely running on the main thread, so we'll always dispatch to the
253 // background for state updates.
254 if (aResult
.IsError()) {
255 nsresult aError
= aResult
.GetError();
257 gAndroidPBackgroundThread
->Dispatch(NS_NewRunnableFunction(
258 "AndroidWebAuthnTokenManager::RegisterAbort",
259 [self
= RefPtr
<AndroidWebAuthnTokenManager
>(this), aError
]() {
260 self
->mRegisterPromise
.RejectIfExists(aError
, __func__
);
263 gAndroidPBackgroundThread
->Dispatch(NS_NewRunnableFunction(
264 "AndroidWebAuthnTokenManager::RegisterComplete",
265 [self
= RefPtr
<AndroidWebAuthnTokenManager
>(this),
266 aResult
= std::move(aResult
)]() {
267 nsTArray
<WebAuthnExtensionResult
> extensions
;
268 WebAuthnMakeCredentialResult
result(aResult
.mClientDataJSON
,
270 aResult
.mKeyHandle
, extensions
);
271 self
->mRegisterPromise
.Resolve(std::move(result
), __func__
);
276 RefPtr
<U2FSignPromise
> AndroidWebAuthnTokenManager::Sign(
277 const WebAuthnGetAssertionInfo
& aInfo
) {
278 AssertIsOnOwningThread();
282 GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
283 "java::WebAuthnTokenManager::WebAuthnGetAssertion",
284 [self
= RefPtr
{this}, aInfo
]() {
285 AssertIsOnMainThread();
287 jni::ObjectArray::LocalRef idList
=
288 jni::ObjectArray::New(aInfo
.AllowList().Length());
290 nsTArray
<uint8_t> transportBuf
;
293 for (const WebAuthnScopedCredential
& cred
: aInfo
.AllowList()) {
294 jni::ByteBuffer::LocalRef id
= jni::ByteBuffer::New(
295 const_cast<void*>(static_cast<const void*>(cred
.id().Elements())),
298 idList
->SetElement(ix
, id
);
299 transportBuf
.AppendElement(cred
.transports());
304 jni::ByteBuffer::LocalRef transportList
= jni::ByteBuffer::New(
306 static_cast<const void*>(transportBuf
.Elements())),
307 transportBuf
.Length());
309 const nsTArray
<uint8_t>& challBuf
= aInfo
.Challenge();
310 jni::ByteBuffer::LocalRef challenge
= jni::ByteBuffer::New(
311 const_cast<void*>(static_cast<const void*>(challBuf
.Elements())),
315 GECKOBUNDLE_START(assertionBundle
);
316 GECKOBUNDLE_START(extensionsBundle
);
318 GECKOBUNDLE_PUT(assertionBundle
, "isWebAuthn",
319 java::sdk::Integer::ValueOf(1));
321 // User Verification Requirement is not currently used in the
322 // Android FIDO API. Adding it should look like
323 // AttestationConveyancePreference
325 for (const WebAuthnExtension
& ext
: aInfo
.Extensions()) {
326 if (ext
.type() == WebAuthnExtension::TWebAuthnExtensionAppId
) {
328 extensionsBundle
, "fidoAppId",
330 ext
.get_WebAuthnExtensionAppId().appIdentifier()));
334 GECKOBUNDLE_PUT(assertionBundle
, "rpId",
335 jni::StringParam(aInfo
.RpId()));
336 GECKOBUNDLE_PUT(assertionBundle
, "origin",
337 jni::StringParam(aInfo
.Origin()));
338 GECKOBUNDLE_PUT(assertionBundle
, "timeoutMS",
339 java::sdk::Double::New(aInfo
.TimeoutMS()));
341 GECKOBUNDLE_FINISH(assertionBundle
);
342 GECKOBUNDLE_FINISH(extensionsBundle
);
344 auto result
= java::WebAuthnTokenManager::WebAuthnGetAssertion(
345 challenge
, idList
, transportList
, assertionBundle
,
347 auto geckoResult
= java::GeckoResult::LocalRef(std::move(result
));
348 MozPromise
<AndroidWebAuthnResult
, AndroidWebAuthnResult
,
349 true>::FromGeckoResult(geckoResult
)
351 GetMainThreadSerialEventTarget(), __func__
,
352 [self
= std::move(self
)](AndroidWebAuthnResult
&& aValue
) {
353 self
->HandleSignResult(std::move(aValue
));
355 [self
= std::move(self
)](AndroidWebAuthnResult
&& aValue
) {
356 self
->HandleSignResult(std::move(aValue
));
360 return mSignPromise
.Ensure(__func__
);
363 void AndroidWebAuthnTokenManager::HandleSignResult(
364 AndroidWebAuthnResult
&& aResult
) {
365 if (!gAndroidPBackgroundThread
) {
366 // Promise is already rejected when shutting down background thread
369 // This is likely running on the main thread, so we'll always dispatch to the
370 // background for state updates.
371 if (aResult
.IsError()) {
372 nsresult aError
= aResult
.GetError();
374 gAndroidPBackgroundThread
->Dispatch(NS_NewRunnableFunction(
375 "AndroidWebAuthnTokenManager::SignAbort",
376 [self
= RefPtr
<AndroidWebAuthnTokenManager
>(this), aError
]() {
377 self
->mSignPromise
.RejectIfExists(aError
, __func__
);
380 gAndroidPBackgroundThread
->Dispatch(NS_NewRunnableFunction(
381 "AndroidWebAuthnTokenManager::SignComplete",
382 [self
= RefPtr
<AndroidWebAuthnTokenManager
>(this),
383 aResult
= std::move(aResult
)]() {
384 nsTArray
<WebAuthnExtensionResult
> emptyExtensions
;
385 WebAuthnGetAssertionResult
result(
386 aResult
.mClientDataJSON
, aResult
.mKeyHandle
, aResult
.mSignature
,
387 aResult
.mAuthData
, emptyExtensions
, aResult
.mUserHandle
);
388 nsTArray
<WebAuthnGetAssertionResultWrapper
> results
= {
389 {result
, mozilla::Nothing()}};
390 self
->mSignPromise
.Resolve(std::move(results
), __func__
);
395 void AndroidWebAuthnTokenManager::Cancel() {
396 AssertIsOnOwningThread();
401 AndroidWebAuthnResult::AndroidWebAuthnResult(
402 const java::WebAuthnTokenManager::MakeCredentialResponse::LocalRef
&
404 mClientDataJSON
.Assign(
405 reinterpret_cast<const char*>(
406 aResponse
->ClientDataJson()->GetElements().Elements()),
407 aResponse
->ClientDataJson()->Length());
408 mKeyHandle
.Assign(reinterpret_cast<uint8_t*>(
409 aResponse
->KeyHandle()->GetElements().Elements()),
410 aResponse
->KeyHandle()->Length());
411 mAttObj
.Assign(reinterpret_cast<uint8_t*>(
412 aResponse
->AttestationObject()->GetElements().Elements()),
413 aResponse
->AttestationObject()->Length());
416 AndroidWebAuthnResult::AndroidWebAuthnResult(
417 const java::WebAuthnTokenManager::GetAssertionResponse::LocalRef
&
419 mClientDataJSON
.Assign(
420 reinterpret_cast<const char*>(
421 aResponse
->ClientDataJson()->GetElements().Elements()),
422 aResponse
->ClientDataJson()->Length());
423 mKeyHandle
.Assign(reinterpret_cast<uint8_t*>(
424 aResponse
->KeyHandle()->GetElements().Elements()),
425 aResponse
->KeyHandle()->Length());
426 mAuthData
.Assign(reinterpret_cast<uint8_t*>(
427 aResponse
->AuthData()->GetElements().Elements()),
428 aResponse
->AuthData()->Length());
429 mSignature
.Assign(reinterpret_cast<uint8_t*>(
430 aResponse
->Signature()->GetElements().Elements()),
431 aResponse
->Signature()->Length());
432 mUserHandle
.Assign(reinterpret_cast<uint8_t*>(
433 aResponse
->UserHandle()->GetElements().Elements()),
434 aResponse
->UserHandle()->Length());
438 } // namespace mozilla