summaryrefslogtreecommitdiffstats
path: root/dom/webauthn/AndroidWebAuthnService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webauthn/AndroidWebAuthnService.cpp')
-rw-r--r--dom/webauthn/AndroidWebAuthnService.cpp428
1 files changed, 428 insertions, 0 deletions
diff --git a/dom/webauthn/AndroidWebAuthnService.cpp b/dom/webauthn/AndroidWebAuthnService.cpp
new file mode 100644
index 0000000000..162cc52033
--- /dev/null
+++ b/dom/webauthn/AndroidWebAuthnService.cpp
@@ -0,0 +1,428 @@
+/* -*- 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/StaticPtr.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/jni/GeckoBundleUtils.h"
+
+#include "AndroidWebAuthnService.h"
+#include "JavaBuiltins.h"
+#include "JavaExceptions.h"
+#include "WebAuthnPromiseHolder.h"
+#include "WebAuthnEnumStrings.h"
+#include "WebAuthnResult.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/java/WebAuthnTokenManagerWrappers.h"
+#include "mozilla/jni/Conversions.h"
+
+namespace mozilla {
+namespace jni {
+template <>
+dom::AndroidWebAuthnError Java2Native(mozilla::jni::Object::Param aData,
+ JNIEnv* aEnv) {
+ MOZ_ASSERT(aData.IsInstanceOf<jni::Throwable>());
+ java::sdk::Throwable::LocalRef throwable(aData);
+ return dom::AndroidWebAuthnError(throwable->GetMessage()->ToString());
+}
+} // namespace jni
+
+namespace dom {
+
+NS_IMPL_ISUPPORTS(AndroidWebAuthnService, nsIWebAuthnService)
+
+NS_IMETHODIMP
+AndroidWebAuthnService::GetIsUVPAA(bool* aAvailable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::MakeCredential(uint64_t aTransactionId,
+ uint64_t aBrowsingContextId,
+ nsIWebAuthnRegisterArgs* aArgs,
+ nsIWebAuthnRegisterPromise* aPromise) {
+ Reset();
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "java::WebAuthnTokenManager::WebAuthnMakeCredential",
+ [aArgs = RefPtr{aArgs}, aPromise = RefPtr{aPromise}]() {
+ AssertIsOnMainThread();
+
+ GECKOBUNDLE_START(credentialBundle);
+ GECKOBUNDLE_PUT(credentialBundle, "isWebAuthn",
+ java::sdk::Integer::ValueOf(1));
+
+ nsString rpId;
+ Unused << aArgs->GetRpId(rpId);
+ GECKOBUNDLE_PUT(credentialBundle, "rpId", jni::StringParam(rpId));
+
+ nsString rpName;
+ Unused << aArgs->GetRpName(rpName);
+ GECKOBUNDLE_PUT(credentialBundle, "rpName", jni::StringParam(rpName));
+
+ nsString userName;
+ Unused << aArgs->GetUserName(userName);
+ GECKOBUNDLE_PUT(credentialBundle, "userName",
+ jni::StringParam(userName));
+
+ nsString userDisplayName;
+ Unused << aArgs->GetUserDisplayName(userDisplayName);
+ GECKOBUNDLE_PUT(credentialBundle, "userDisplayName",
+ jni::StringParam(userDisplayName));
+
+ nsString origin;
+ Unused << aArgs->GetOrigin(origin);
+ GECKOBUNDLE_PUT(credentialBundle, "origin", jni::StringParam(origin));
+
+ uint32_t timeout;
+ Unused << aArgs->GetTimeoutMS(&timeout);
+ GECKOBUNDLE_PUT(credentialBundle, "timeoutMS",
+ java::sdk::Double::New(timeout));
+ GECKOBUNDLE_FINISH(credentialBundle);
+
+ nsTArray<uint8_t> userId;
+ Unused << aArgs->GetUserId(userId);
+ jni::ByteBuffer::LocalRef uid = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(userId.Elements())),
+ userId.Length());
+
+ nsTArray<uint8_t> challBuf;
+ Unused << aArgs->GetChallenge(challBuf);
+ jni::ByteBuffer::LocalRef challenge = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(challBuf.Elements())),
+ challBuf.Length());
+
+ nsTArray<nsTArray<uint8_t>> excludeList;
+ Unused << aArgs->GetExcludeList(excludeList);
+ jni::ObjectArray::LocalRef idList =
+ jni::ObjectArray::New(excludeList.Length());
+ int ix = 0;
+ for (const nsTArray<uint8_t>& credId : excludeList) {
+ jni::ByteBuffer::LocalRef id = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(credId.Elements())),
+ credId.Length());
+
+ idList->SetElement(ix, id);
+
+ ix += 1;
+ }
+
+ nsTArray<uint8_t> transportBuf;
+ Unused << aArgs->GetExcludeListTransports(transportBuf);
+ jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
+ const_cast<void*>(
+ static_cast<const void*>(transportBuf.Elements())),
+ transportBuf.Length());
+
+ GECKOBUNDLE_START(authSelBundle);
+ // Add UI support to consent to attestation, bug 1550164
+ GECKOBUNDLE_PUT(authSelBundle, "attestationPreference",
+ jni::StringParam(u"none"_ns));
+
+ nsString residentKey;
+ Unused << aArgs->GetResidentKey(residentKey);
+
+ // Get extensions
+ bool requestedCredProps;
+ Unused << aArgs->GetCredProps(&requestedCredProps);
+
+ // 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.
+ Maybe<bool> credPropsResponse;
+ if (requestedCredProps &&
+ StaticPrefs::
+ security_webauthn_webauthn_enable_android_fido2_residentkey()) {
+ GECKOBUNDLE_PUT(authSelBundle, "residentKey",
+ jni::StringParam(residentKey));
+ bool residentKeyRequired = residentKey.EqualsLiteral(
+ MOZ_WEBAUTHN_RESIDENT_KEY_REQUIREMENT_REQUIRED);
+ credPropsResponse = Some(residentKeyRequired);
+ }
+
+ nsString userVerification;
+ Unused << aArgs->GetUserVerification(userVerification);
+ if (userVerification.EqualsLiteral(
+ MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED)) {
+ GECKOBUNDLE_PUT(authSelBundle, "requireUserVerification",
+ java::sdk::Integer::ValueOf(1));
+ }
+
+ nsString authenticatorAttachment;
+ nsresult rv =
+ aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(rv);
+ return;
+ }
+ 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));
+ }
+ }
+ GECKOBUNDLE_FINISH(authSelBundle);
+
+ GECKOBUNDLE_START(extensionsBundle);
+ GECKOBUNDLE_FINISH(extensionsBundle);
+
+ auto result = java::WebAuthnTokenManager::WebAuthnMakeCredential(
+ credentialBundle, uid, challenge, idList, transportList,
+ authSelBundle, extensionsBundle);
+
+ auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
+
+ MozPromise<RefPtr<WebAuthnRegisterResult>, AndroidWebAuthnError,
+ true>::FromGeckoResult(geckoResult)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aPromise, credPropsResponse = std::move(credPropsResponse)](
+ RefPtr<WebAuthnRegisterResult>&& aValue) {
+ // We don't have a way for the user to consent to attestation
+ // on Android, so always anonymize the result.
+ nsresult rv = aValue->Anonymize();
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ }
+ if (credPropsResponse.isSome()) {
+ Unused << aValue->SetCredPropsRk(credPropsResponse.ref());
+ }
+ aPromise->Resolve(aValue);
+ },
+ [aPromise](AndroidWebAuthnError&& aValue) {
+ aPromise->Reject(aValue.GetError());
+ });
+ }));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::GetAssertion(uint64_t aTransactionId,
+ uint64_t aBrowsingContextId,
+ nsIWebAuthnSignArgs* aArgs,
+ nsIWebAuthnSignPromise* aPromise) {
+ Reset();
+
+ bool conditionallyMediated;
+ Unused << aArgs->GetConditionallyMediated(&conditionallyMediated);
+ if (conditionallyMediated) {
+ aPromise->Reject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return NS_OK;
+ }
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "java::WebAuthnTokenManager::WebAuthnGetAssertion",
+ [self = RefPtr{this}, aArgs = RefPtr{aArgs},
+ aPromise = RefPtr{aPromise}]() {
+ AssertIsOnMainThread();
+
+ nsTArray<uint8_t> challBuf;
+ Unused << aArgs->GetChallenge(challBuf);
+ jni::ByteBuffer::LocalRef challenge = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(challBuf.Elements())),
+ challBuf.Length());
+
+ nsTArray<nsTArray<uint8_t>> allowList;
+ Unused << aArgs->GetAllowList(allowList);
+ jni::ObjectArray::LocalRef idList =
+ jni::ObjectArray::New(allowList.Length());
+ int ix = 0;
+ for (const nsTArray<uint8_t>& credId : allowList) {
+ jni::ByteBuffer::LocalRef id = jni::ByteBuffer::New(
+ const_cast<void*>(static_cast<const void*>(credId.Elements())),
+ credId.Length());
+
+ idList->SetElement(ix, id);
+
+ ix += 1;
+ }
+
+ nsTArray<uint8_t> transportBuf;
+ Unused << aArgs->GetAllowListTransports(transportBuf);
+ jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::New(
+ const_cast<void*>(
+ static_cast<const void*>(transportBuf.Elements())),
+ transportBuf.Length());
+
+ GECKOBUNDLE_START(assertionBundle);
+
+ GECKOBUNDLE_PUT(assertionBundle, "isWebAuthn",
+ java::sdk::Integer::ValueOf(1));
+
+ nsString rpId;
+ Unused << aArgs->GetRpId(rpId);
+ GECKOBUNDLE_PUT(assertionBundle, "rpId", jni::StringParam(rpId));
+
+ nsString origin;
+ Unused << aArgs->GetOrigin(origin);
+ GECKOBUNDLE_PUT(assertionBundle, "origin", jni::StringParam(origin));
+
+ uint32_t timeout;
+ Unused << aArgs->GetTimeoutMS(&timeout);
+ GECKOBUNDLE_PUT(assertionBundle, "timeoutMS",
+ java::sdk::Double::New(timeout));
+
+ // User Verification Requirement is not currently used in the
+ // Android FIDO API.
+
+ GECKOBUNDLE_FINISH(assertionBundle);
+
+ GECKOBUNDLE_START(extensionsBundle);
+
+ nsString appId;
+ nsresult rv = aArgs->GetAppId(appId);
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ if (NS_FAILED(rv)) {
+ aPromise->Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ GECKOBUNDLE_PUT(extensionsBundle, "fidoAppId",
+ jni::StringParam(appId));
+ }
+
+ GECKOBUNDLE_FINISH(extensionsBundle);
+
+ auto result = java::WebAuthnTokenManager::WebAuthnGetAssertion(
+ challenge, idList, transportList, assertionBundle,
+ extensionsBundle);
+ auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
+ MozPromise<RefPtr<WebAuthnSignResult>, AndroidWebAuthnError,
+ true>::FromGeckoResult(geckoResult)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aPromise](RefPtr<WebAuthnSignResult>&& aValue) {
+ aPromise->Resolve(aValue);
+ },
+ [aPromise](AndroidWebAuthnError&& aValue) {
+ aPromise->Reject(aValue.GetError());
+ });
+ }));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::Reset() {
+ mRegisterCredPropsRk.reset();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::Cancel(uint64_t aTransactionId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
+ const nsAString& aOrigin,
+ uint64_t* aRv) {
+ // Signal that there is no pending conditional get request, so the caller
+ // will not attempt to call GetAutoFillEntries, SelectAutoFillEntry, or
+ // ResumeConditionalGet (as these are not implemented).
+ *aRv = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::GetAutoFillEntries(
+ uint64_t aTransactionId, nsTArray<RefPtr<nsIWebAuthnAutoFillEntry>>& aRv) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::SelectAutoFillEntry(
+ uint64_t aTransactionId, const nsTArray<uint8_t>& aCredentialId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::PinCallback(uint64_t aTransactionId,
+ const nsACString& aPin) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::ResumeMakeCredential(uint64_t aTransactionId,
+ bool aForceNoneAttestation) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::SelectionCallback(uint64_t aTransactionId,
+ uint64_t aIndex) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::AddVirtualAuthenticator(
+ const nsACString& protocol, const nsACString& transport,
+ bool hasResidentKey, bool hasUserVerification, bool isUserConsenting,
+ bool isUserVerified, uint64_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::RemoveVirtualAuthenticator(uint64_t authenticatorId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::AddCredential(uint64_t authenticatorId,
+ const nsACString& credentialId,
+ bool isResidentCredential,
+ const nsACString& rpId,
+ const nsACString& privateKey,
+ const nsACString& userHandle,
+ uint32_t signCount) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::GetCredentials(
+ uint64_t authenticatorId,
+ nsTArray<RefPtr<nsICredentialParameters>>& _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::RemoveCredential(uint64_t authenticatorId,
+ const nsACString& credentialId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::RemoveAllCredentials(uint64_t authenticatorId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::SetUserVerified(uint64_t authenticatorId,
+ bool isUserVerified) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AndroidWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+AndroidWebAuthnService::RunCommand(const nsACString& cmd) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace dom
+} // namespace mozilla