summaryrefslogtreecommitdiffstats
path: root/dom/webauthn/AndroidWebAuthnTokenManager.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/webauthn/AndroidWebAuthnTokenManager.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/webauthn/AndroidWebAuthnTokenManager.cpp')
-rw-r--r--dom/webauthn/AndroidWebAuthnTokenManager.cpp438
1 files changed, 438 insertions, 0 deletions
diff --git a/dom/webauthn/AndroidWebAuthnTokenManager.cpp b/dom/webauthn/AndroidWebAuthnTokenManager.cpp
new file mode 100644
index 0000000000..bdf21b723b
--- /dev/null
+++ b/dom/webauthn/AndroidWebAuthnTokenManager.cpp
@@ -0,0 +1,438 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/jni/GeckoBundleUtils.h"
+#include "mozilla/StaticPtr.h"
+
+#include "AndroidWebAuthnTokenManager.h"
+#include "JavaBuiltins.h"
+#include "JavaExceptions.h"
+#include "mozilla/java/WebAuthnTokenManagerWrappers.h"
+#include "mozilla/jni/Conversions.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "WebAuthnEnumStrings.h"
+
+namespace mozilla {
+namespace jni {
+
+template <>
+dom::AndroidWebAuthnResult Java2Native(mozilla::jni::Object::Param aData,
+ JNIEnv* aEnv) {
+ // TODO:
+ // AndroidWebAuthnResult stores successful both result and failure result.
+ // We should split it into success and failure (Bug 1754157)
+ if (aData.IsInstanceOf<jni::Throwable>()) {
+ java::sdk::Throwable::LocalRef throwable(aData);
+ return dom::AndroidWebAuthnResult(throwable->GetMessage()->ToString());
+ }
+
+ if (aData
+ .IsInstanceOf<java::WebAuthnTokenManager::MakeCredentialResponse>()) {
+ java::WebAuthnTokenManager::MakeCredentialResponse::LocalRef response(
+ aData);
+ return dom::AndroidWebAuthnResult(response);
+ }
+
+ MOZ_ASSERT(
+ aData.IsInstanceOf<java::WebAuthnTokenManager::GetAssertionResponse>());
+ java::WebAuthnTokenManager::GetAssertionResponse::LocalRef response(aData);
+ return dom::AndroidWebAuthnResult(response);
+}
+} // namespace jni
+
+namespace dom {
+
+static nsIThread* gAndroidPBackgroundThread;
+
+StaticRefPtr<AndroidWebAuthnTokenManager> gAndroidWebAuthnManager;
+
+/* static */ AndroidWebAuthnTokenManager*
+AndroidWebAuthnTokenManager::GetInstance() {
+ if (!gAndroidWebAuthnManager) {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ gAndroidWebAuthnManager = new AndroidWebAuthnTokenManager();
+ }
+ return gAndroidWebAuthnManager;
+}
+
+AndroidWebAuthnTokenManager::AndroidWebAuthnTokenManager() {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!gAndroidWebAuthnManager);
+
+ gAndroidPBackgroundThread = NS_GetCurrentThread();
+ MOZ_ASSERT(gAndroidPBackgroundThread, "This should never be null!");
+ gAndroidWebAuthnManager = this;
+}
+
+void AndroidWebAuthnTokenManager::AssertIsOnOwningThread() const {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+ MOZ_ASSERT(gAndroidPBackgroundThread);
+#ifdef DEBUG
+ bool current;
+ MOZ_ASSERT(
+ NS_SUCCEEDED(gAndroidPBackgroundThread->IsOnCurrentThread(&current)));
+ MOZ_ASSERT(current);
+#endif
+}
+
+void AndroidWebAuthnTokenManager::Drop() {
+ AssertIsOnOwningThread();
+
+ ClearPromises();
+ gAndroidWebAuthnManager = nullptr;
+ gAndroidPBackgroundThread = nullptr;
+}
+
+RefPtr<U2FRegisterPromise> AndroidWebAuthnTokenManager::Register(
+ const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
+ AssertIsOnOwningThread();
+
+ ClearPromises();
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "java::WebAuthnTokenManager::WebAuthnMakeCredential",
+ [self = RefPtr{this}, aInfo, aForceNoneAttestation]() {
+ AssertIsOnMainThread();
+
+ // Produce the credential exclusion list
+ jni::ObjectArray::LocalRef idList =
+ jni::ObjectArray::New(aInfo.ExcludeList().Length());
+
+ nsTArray<uint8_t> transportBuf;
+ int ix = 0;
+
+ for (const WebAuthnScopedCredential& cred : aInfo.ExcludeList()) {
+ jni::ByteBuffer::LocalRef id = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(cred.id().Elements())),
+ cred.id().Length());
+
+ idList->SetElement(ix, id);
+ transportBuf.AppendElement(cred.transports());
+
+ ix += 1;
+ }
+
+ jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
+ const_cast<void*>(
+ static_cast<const void*>(transportBuf.Elements())),
+ transportBuf.Length());
+
+ const nsTArray<uint8_t>& challBuf = aInfo.Challenge();
+ jni::ByteBuffer::LocalRef challenge = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(challBuf.Elements())),
+ challBuf.Length());
+
+ nsTArray<uint8_t> uidBuf;
+
+ // Get authenticator selection criteria
+ GECKOBUNDLE_START(authSelBundle);
+ GECKOBUNDLE_START(extensionsBundle);
+ GECKOBUNDLE_START(credentialBundle);
+
+ const auto& rp = aInfo.Rp();
+ const auto& user = aInfo.User();
+
+ GECKOBUNDLE_PUT(credentialBundle, "isWebAuthn",
+ java::sdk::Integer::ValueOf(1));
+
+ // Get the attestation preference and override if the user asked
+ if (aForceNoneAttestation) {
+ // Add UI support to trigger this, bug 1550164
+ GECKOBUNDLE_PUT(authSelBundle, "attestationPreference",
+ jni::StringParam(u"none"_ns));
+ } else {
+ const nsString& attestation = aInfo.attestationConveyancePreference();
+ GECKOBUNDLE_PUT(authSelBundle, "attestationPreference",
+ jni::StringParam(attestation));
+ }
+
+ const WebAuthnAuthenticatorSelection& sel =
+ aInfo.AuthenticatorSelection();
+ // Unfortunately, GMS's FIDO2 API has no option for Passkey. If using
+ // residentKey, credential will be synced with Passkey via Google
+ // account or credential provider service. So this is experimental.
+ if (StaticPrefs::
+ security_webauthn_webauthn_enable_android_fido2_residentkey()) {
+ GECKOBUNDLE_PUT(authSelBundle, "residentKey",
+ jni::StringParam(sel.residentKey()));
+ }
+
+ if (sel.userVerificationRequirement().EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
+ GECKOBUNDLE_PUT(authSelBundle, "requireUserVerification",
+ java::sdk::Integer::ValueOf(1));
+ }
+
+ if (sel.authenticatorAttachment().isSome()) {
+ const nsString& authenticatorAttachment =
+ sel.authenticatorAttachment().value();
+ if (authenticatorAttachment.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM)) {
+ GECKOBUNDLE_PUT(authSelBundle, "requirePlatformAttachment",
+ java::sdk::Integer::ValueOf(1));
+ } else if (
+ authenticatorAttachment.EqualsLiteral(
+ MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
+ GECKOBUNDLE_PUT(authSelBundle, "requireCrossPlatformAttachment",
+ java::sdk::Integer::ValueOf(1));
+ }
+ }
+
+ // Get extensions
+ for (const WebAuthnExtension& ext : aInfo.Extensions()) {
+ if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
+ GECKOBUNDLE_PUT(
+ extensionsBundle, "fidoAppId",
+ jni::StringParam(
+ ext.get_WebAuthnExtensionAppId().appIdentifier()));
+ }
+ }
+
+ uidBuf.Assign(user.Id());
+
+ GECKOBUNDLE_PUT(credentialBundle, "rpName",
+ jni::StringParam(rp.Name()));
+ GECKOBUNDLE_PUT(credentialBundle, "rpIcon",
+ jni::StringParam(rp.Icon()));
+ GECKOBUNDLE_PUT(credentialBundle, "userName",
+ jni::StringParam(user.Name()));
+ GECKOBUNDLE_PUT(credentialBundle, "userIcon",
+ jni::StringParam(user.Icon()));
+ GECKOBUNDLE_PUT(credentialBundle, "userDisplayName",
+ jni::StringParam(user.DisplayName()));
+
+ GECKOBUNDLE_PUT(credentialBundle, "rpId",
+ jni::StringParam(aInfo.RpId()));
+ GECKOBUNDLE_PUT(credentialBundle, "origin",
+ jni::StringParam(aInfo.Origin()));
+ GECKOBUNDLE_PUT(credentialBundle, "timeoutMS",
+ java::sdk::Double::New(aInfo.TimeoutMS()));
+
+ GECKOBUNDLE_FINISH(authSelBundle);
+ GECKOBUNDLE_FINISH(extensionsBundle);
+ GECKOBUNDLE_FINISH(credentialBundle);
+
+ // For non-WebAuthn cases, uidBuf is empty (and unused)
+ jni::ByteBuffer::LocalRef uid = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(uidBuf.Elements())),
+ uidBuf.Length());
+
+ auto result = java::WebAuthnTokenManager::WebAuthnMakeCredential(
+ credentialBundle, uid, challenge, idList, transportList,
+ authSelBundle, extensionsBundle);
+ auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
+ // This is likely running on the main thread, so we'll always dispatch
+ // to the background for state updates.
+ MozPromise<AndroidWebAuthnResult, AndroidWebAuthnResult,
+ true>::FromGeckoResult(geckoResult)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = std::move(self)](AndroidWebAuthnResult&& aValue) {
+ self->HandleRegisterResult(std::move(aValue));
+ },
+ [self = std::move(self)](AndroidWebAuthnResult&& aValue) {
+ self->HandleRegisterResult(std::move(aValue));
+ });
+ }));
+
+ return mRegisterPromise.Ensure(__func__);
+}
+
+void AndroidWebAuthnTokenManager::HandleRegisterResult(
+ AndroidWebAuthnResult&& aResult) {
+ if (!gAndroidPBackgroundThread) {
+ // Promise is already rejected when shutting down background thread
+ return;
+ }
+ // This is likely running on the main thread, so we'll always dispatch to the
+ // background for state updates.
+ if (aResult.IsError()) {
+ nsresult aError = aResult.GetError();
+
+ gAndroidPBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "AndroidWebAuthnTokenManager::RegisterAbort",
+ [self = RefPtr<AndroidWebAuthnTokenManager>(this), aError]() {
+ self->mRegisterPromise.RejectIfExists(aError, __func__);
+ }));
+ } else {
+ gAndroidPBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "AndroidWebAuthnTokenManager::RegisterComplete",
+ [self = RefPtr<AndroidWebAuthnTokenManager>(this),
+ aResult = std::move(aResult)]() {
+ nsTArray<WebAuthnExtensionResult> extensions;
+ WebAuthnMakeCredentialResult result(aResult.mClientDataJSON,
+ aResult.mAttObj,
+ aResult.mKeyHandle, extensions);
+ self->mRegisterPromise.Resolve(std::move(result), __func__);
+ }));
+ }
+}
+
+RefPtr<U2FSignPromise> AndroidWebAuthnTokenManager::Sign(
+ const WebAuthnGetAssertionInfo& aInfo) {
+ AssertIsOnOwningThread();
+
+ ClearPromises();
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "java::WebAuthnTokenManager::WebAuthnGetAssertion",
+ [self = RefPtr{this}, aInfo]() {
+ AssertIsOnMainThread();
+
+ jni::ObjectArray::LocalRef idList =
+ jni::ObjectArray::New(aInfo.AllowList().Length());
+
+ nsTArray<uint8_t> transportBuf;
+
+ int ix = 0;
+ for (const WebAuthnScopedCredential& cred : aInfo.AllowList()) {
+ jni::ByteBuffer::LocalRef id = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(cred.id().Elements())),
+ cred.id().Length());
+
+ idList->SetElement(ix, id);
+ transportBuf.AppendElement(cred.transports());
+
+ ix += 1;
+ }
+
+ jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
+ const_cast<void*>(
+ static_cast<const void*>(transportBuf.Elements())),
+ transportBuf.Length());
+
+ const nsTArray<uint8_t>& challBuf = aInfo.Challenge();
+ jni::ByteBuffer::LocalRef challenge = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(challBuf.Elements())),
+ challBuf.Length());
+
+ // Get extensions
+ GECKOBUNDLE_START(assertionBundle);
+ GECKOBUNDLE_START(extensionsBundle);
+
+ GECKOBUNDLE_PUT(assertionBundle, "isWebAuthn",
+ java::sdk::Integer::ValueOf(1));
+
+ // User Verification Requirement is not currently used in the
+ // Android FIDO API. Adding it should look like
+ // AttestationConveyancePreference
+
+ for (const WebAuthnExtension& ext : aInfo.Extensions()) {
+ if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) {
+ GECKOBUNDLE_PUT(
+ extensionsBundle, "fidoAppId",
+ jni::StringParam(
+ ext.get_WebAuthnExtensionAppId().appIdentifier()));
+ }
+ }
+
+ GECKOBUNDLE_PUT(assertionBundle, "rpId",
+ jni::StringParam(aInfo.RpId()));
+ GECKOBUNDLE_PUT(assertionBundle, "origin",
+ jni::StringParam(aInfo.Origin()));
+ GECKOBUNDLE_PUT(assertionBundle, "timeoutMS",
+ java::sdk::Double::New(aInfo.TimeoutMS()));
+
+ GECKOBUNDLE_FINISH(assertionBundle);
+ GECKOBUNDLE_FINISH(extensionsBundle);
+
+ auto result = java::WebAuthnTokenManager::WebAuthnGetAssertion(
+ challenge, idList, transportList, assertionBundle,
+ extensionsBundle);
+ auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
+ MozPromise<AndroidWebAuthnResult, AndroidWebAuthnResult,
+ true>::FromGeckoResult(geckoResult)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = std::move(self)](AndroidWebAuthnResult&& aValue) {
+ self->HandleSignResult(std::move(aValue));
+ },
+ [self = std::move(self)](AndroidWebAuthnResult&& aValue) {
+ self->HandleSignResult(std::move(aValue));
+ });
+ }));
+
+ return mSignPromise.Ensure(__func__);
+}
+
+void AndroidWebAuthnTokenManager::HandleSignResult(
+ AndroidWebAuthnResult&& aResult) {
+ if (!gAndroidPBackgroundThread) {
+ // Promise is already rejected when shutting down background thread
+ return;
+ }
+ // This is likely running on the main thread, so we'll always dispatch to the
+ // background for state updates.
+ if (aResult.IsError()) {
+ nsresult aError = aResult.GetError();
+
+ gAndroidPBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "AndroidWebAuthnTokenManager::SignAbort",
+ [self = RefPtr<AndroidWebAuthnTokenManager>(this), aError]() {
+ self->mSignPromise.RejectIfExists(aError, __func__);
+ }));
+ } else {
+ gAndroidPBackgroundThread->Dispatch(NS_NewRunnableFunction(
+ "AndroidWebAuthnTokenManager::SignComplete",
+ [self = RefPtr<AndroidWebAuthnTokenManager>(this),
+ aResult = std::move(aResult)]() {
+ nsTArray<WebAuthnExtensionResult> emptyExtensions;
+ WebAuthnGetAssertionResult result(
+ aResult.mClientDataJSON, aResult.mKeyHandle, aResult.mSignature,
+ aResult.mAuthData, emptyExtensions, aResult.mUserHandle);
+ nsTArray<WebAuthnGetAssertionResultWrapper> results = {
+ {result, mozilla::Nothing()}};
+ self->mSignPromise.Resolve(std::move(results), __func__);
+ }));
+ }
+}
+
+void AndroidWebAuthnTokenManager::Cancel() {
+ AssertIsOnOwningThread();
+
+ ClearPromises();
+}
+
+AndroidWebAuthnResult::AndroidWebAuthnResult(
+ const java::WebAuthnTokenManager::MakeCredentialResponse::LocalRef&
+ aResponse) {
+ mClientDataJSON.Assign(
+ reinterpret_cast<const char*>(
+ aResponse->ClientDataJson()->GetElements().Elements()),
+ aResponse->ClientDataJson()->Length());
+ mKeyHandle.Assign(reinterpret_cast<uint8_t*>(
+ aResponse->KeyHandle()->GetElements().Elements()),
+ aResponse->KeyHandle()->Length());
+ mAttObj.Assign(reinterpret_cast<uint8_t*>(
+ aResponse->AttestationObject()->GetElements().Elements()),
+ aResponse->AttestationObject()->Length());
+}
+
+AndroidWebAuthnResult::AndroidWebAuthnResult(
+ const java::WebAuthnTokenManager::GetAssertionResponse::LocalRef&
+ aResponse) {
+ mClientDataJSON.Assign(
+ reinterpret_cast<const char*>(
+ aResponse->ClientDataJson()->GetElements().Elements()),
+ aResponse->ClientDataJson()->Length());
+ mKeyHandle.Assign(reinterpret_cast<uint8_t*>(
+ aResponse->KeyHandle()->GetElements().Elements()),
+ aResponse->KeyHandle()->Length());
+ mAuthData.Assign(reinterpret_cast<uint8_t*>(
+ aResponse->AuthData()->GetElements().Elements()),
+ aResponse->AuthData()->Length());
+ mSignature.Assign(reinterpret_cast<uint8_t*>(
+ aResponse->Signature()->GetElements().Elements()),
+ aResponse->Signature()->Length());
+ mUserHandle.Assign(reinterpret_cast<uint8_t*>(
+ aResponse->UserHandle()->GetElements().Elements()),
+ aResponse->UserHandle()->Length());
+}
+
+} // namespace dom
+} // namespace mozilla