Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / dom / webauthn / AndroidWebAuthnTokenManager.cpp
blobbdf21b723b3760fcbe80fa161c81174c75636f62
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"
19 namespace mozilla {
20 namespace jni {
22 template <>
23 dom::AndroidWebAuthnResult Java2Native(mozilla::jni::Object::Param aData,
24 JNIEnv* aEnv) {
25 // TODO:
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());
33 if (aData
34 .IsInstanceOf<java::WebAuthnTokenManager::MakeCredentialResponse>()) {
35 java::WebAuthnTokenManager::MakeCredentialResponse::LocalRef response(
36 aData);
37 return dom::AndroidWebAuthnResult(response);
40 MOZ_ASSERT(
41 aData.IsInstanceOf<java::WebAuthnTokenManager::GetAssertionResponse>());
42 java::WebAuthnTokenManager::GetAssertionResponse::LocalRef response(aData);
43 return dom::AndroidWebAuthnResult(response);
45 } // namespace jni
47 namespace dom {
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);
75 #ifdef DEBUG
76 bool current;
77 MOZ_ASSERT(
78 NS_SUCCEEDED(gAndroidPBackgroundThread->IsOnCurrentThread(&current)));
79 MOZ_ASSERT(current);
80 #endif
83 void AndroidWebAuthnTokenManager::Drop() {
84 AssertIsOnOwningThread();
86 ClearPromises();
87 gAndroidWebAuthnManager = nullptr;
88 gAndroidPBackgroundThread = nullptr;
91 RefPtr<U2FRegisterPromise> AndroidWebAuthnTokenManager::Register(
92 const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
93 AssertIsOnOwningThread();
95 ClearPromises();
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;
107 int ix = 0;
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())),
112 cred.id().Length());
114 idList->SetElement(ix, id);
115 transportBuf.AppendElement(cred.transports());
117 ix += 1;
120 jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
121 const_cast<void*>(
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())),
128 challBuf.Length());
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));
148 } else {
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.
159 if (StaticPrefs::
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));
178 } else if (
179 authenticatorAttachment.EqualsLiteral(
180 MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
181 GECKOBUNDLE_PUT(authSelBundle, "requireCrossPlatformAttachment",
182 java::sdk::Integer::ValueOf(1));
186 // Get extensions
187 for (const WebAuthnExtension& ext : aInfo.Extensions()) {
188 if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
189 GECKOBUNDLE_PUT(
190 extensionsBundle, "fidoAppId",
191 jni::StringParam(
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())),
223 uidBuf.Length());
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)
233 ->Then(
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));
241 }));
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
250 return;
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__);
261 }));
262 } else {
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,
269 aResult.mAttObj,
270 aResult.mKeyHandle, extensions);
271 self->mRegisterPromise.Resolve(std::move(result), __func__);
272 }));
276 RefPtr<U2FSignPromise> AndroidWebAuthnTokenManager::Sign(
277 const WebAuthnGetAssertionInfo& aInfo) {
278 AssertIsOnOwningThread();
280 ClearPromises();
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;
292 int ix = 0;
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())),
296 cred.id().Length());
298 idList->SetElement(ix, id);
299 transportBuf.AppendElement(cred.transports());
301 ix += 1;
304 jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
305 const_cast<void*>(
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())),
312 challBuf.Length());
314 // Get extensions
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) {
327 GECKOBUNDLE_PUT(
328 extensionsBundle, "fidoAppId",
329 jni::StringParam(
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,
346 extensionsBundle);
347 auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
348 MozPromise<AndroidWebAuthnResult, AndroidWebAuthnResult,
349 true>::FromGeckoResult(geckoResult)
350 ->Then(
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));
358 }));
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
367 return;
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__);
378 }));
379 } else {
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__);
391 }));
395 void AndroidWebAuthnTokenManager::Cancel() {
396 AssertIsOnOwningThread();
398 ClearPromises();
401 AndroidWebAuthnResult::AndroidWebAuthnResult(
402 const java::WebAuthnTokenManager::MakeCredentialResponse::LocalRef&
403 aResponse) {
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&
418 aResponse) {
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());
437 } // namespace dom
438 } // namespace mozilla