/* 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 "AndroidWebAuthnService.h"
#include "JavaBuiltins.h"
#include "JavaExceptions.h"
#include "WebAuthnEnumStrings.h"
#include "WebAuthnPromiseHolder.h"
#include "WebAuthnResult.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/java/WebAuthnTokenManagerWrappers.h"
#include "mozilla/jni/Conversions.h"
#include "mozilla/jni/GeckoBundleUtils.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) {
MOZ_ASSERT(NS_IsMainThread());
GECKOBUNDLE_START(credentialBundle);
GECKOBUNDLE_PUT(credentialBundle,
"isWebAuthn",
java::sdk::Integer::ValueOf(
1));
{
GECKOBUNDLE_START(rpBundle);
nsString rpId;
(
void)aArgs->GetRpId(rpId);
GECKOBUNDLE_PUT(rpBundle,
"id", jni::StringParam(rpId));
nsString rpName;
(
void)aArgs->GetRpName(rpName);
GECKOBUNDLE_PUT(rpBundle,
"name", jni::StringParam(rpName));
GECKOBUNDLE_FINISH(rpBundle);
GECKOBUNDLE_PUT(credentialBundle,
"rp", rpBundle);
}
{
GECKOBUNDLE_START(userBundle);
nsString userName;
(
void)aArgs->GetUserName(userName);
GECKOBUNDLE_PUT(userBundle,
"name", jni::StringParam(userName));
nsString userDisplayName;
(
void)aArgs->GetUserDisplayName(userDisplayName);
GECKOBUNDLE_PUT(userBundle,
"displayName",
jni::StringParam(userDisplayName));
GECKOBUNDLE_FINISH(userBundle);
GECKOBUNDLE_PUT(credentialBundle,
"user", userBundle);
}
nsString origin;
(
void)aArgs->GetOrigin(origin);
GECKOBUNDLE_PUT(credentialBundle,
"origin", jni::StringParam(origin));
uint32_t timeout;
(
void)aArgs->GetTimeoutMS(&timeout);
GECKOBUNDLE_PUT(credentialBundle,
"timeout", java::sdk::
Double::
New(timeout));
// Add UI support to consent to attestation, bug 1550164
GECKOBUNDLE_PUT(credentialBundle,
"attestation",
jni::StringParam(u
"none"_ns));
GECKOBUNDLE_FINISH(credentialBundle);
nsTArray<uint8_t> userId;
(
void)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;
(
void)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;
(
void)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;
(
void)aArgs->GetExcludeListTransports(transportBuf);
jni::ByteBuffer::LocalRef transportList = jni::ByteBuffer::
New(
const_cast<
void*>(static_cast<
const void*>(transportBuf.Elements())),
transportBuf.Length());
nsTArray<uint8_t> clientDataHash;
(
void)aArgs->GetClientDataHash(clientDataHash);
jni::ByteBuffer::LocalRef hash = jni::ByteBuffer::
New(
const_cast<
void*>(static_cast<
const void*>(clientDataHash.Elements())),
clientDataHash.Length());
nsTArray<int32_t> coseAlgs;
(
void)aArgs->GetCoseAlgs(coseAlgs);
jni::IntArray::LocalRef algs =
jni::IntArray::
New(coseAlgs.Elements(), coseAlgs.Length());
GECKOBUNDLE_START(authSelBundle);
nsString residentKey;
(
void)aArgs->GetResidentKey(residentKey);
// Get extensions
bool requestedCredProps;
(
void)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.
if (requestedCredProps &&
StaticPrefs::
security_webauthn_webauthn_enable_android_fido2_residentkey()) {
GECKOBUNDLE_PUT(authSelBundle,
"residentKey",
jni::StringParam(residentKey));
}
nsString userVerification;
(
void)aArgs->GetUserVerification(userVerification);
if (userVerification.EqualsLiteral(
MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED) ||
userVerification.EqualsLiteral(
MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED) ||
userVerification.EqualsLiteral(
MOZ_WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED)) {
GECKOBUNDLE_PUT(authSelBundle,
"userVerification",
jni::StringParam(userVerification));
}
nsString authenticatorAttachment;
nsresult rv = aArgs->GetAuthenticatorAttachment(authenticatorAttachment);
if (rv != NS_ERROR_NOT_AVAILABLE) {
if (NS_FAILED(rv)) {
aPromise->Reject(rv);
return NS_OK;
}
if (authenticatorAttachment.EqualsLiteral(
MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM) ||
authenticatorAttachment.EqualsLiteral(
MOZ_WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM)) {
GECKOBUNDLE_PUT(authSelBundle,
"authenticatorAttachment",
jni::StringParam(authenticatorAttachment));
}
}
GECKOBUNDLE_FINISH(authSelBundle);
GECKOBUNDLE_START(extensionsBundle);
GECKOBUNDLE_PUT(extensionsBundle,
"credProps",
requestedCredProps ? java::sdk::Boolean::TRUE()
: java::sdk::Boolean::
FALSE());
GECKOBUNDLE_FINISH(extensionsBundle);
nsString json;
(
void)aArgs->GetJson(json);
auto result = java::WebAuthnTokenManager::WebAuthnMakeCredential(
credentialBundle, uid, challenge, idList, transportList, authSelBundle,
extensionsBundle, algs, hash, json);
auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
MozPromise<RefPtr<WebAuthnRegisterResult>, AndroidWebAuthnError,
true>::FromGeckoResult(geckoResult)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aPromise =
RefPtr{aPromise}](RefPtr<WebAuthnRegisterResult>&& aValue) {
aPromise->Resolve(aValue);
},
[aPromise = RefPtr{aPromise}](AndroidWebAuthnError&& aValue) {
aPromise->Reject(aValue.GetError());
});
return NS_OK;
}
NS_IMETHODIMP
AndroidWebAuthnService::GetAssertion(uint64_t aTransactionId,
uint64_t aBrowsingContextId,
nsIWebAuthnSignArgs* aArgs,
nsIWebAuthnSignPromise* aPromise) {
MOZ_ASSERT(NS_IsMainThread());
nsTArray<uint8_t> challBuf;
(
void)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;
(
void)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> clientDataHash;
(
void)aArgs->GetClientDataHash(clientDataHash);
jni::ByteBuffer::LocalRef hash = jni::ByteBuffer::
New(
const_cast<
void*>(static_cast<
const void*>(clientDataHash.Elements())),
clientDataHash.Length());
nsTArray<uint8_t> transportBuf;
(
void)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;
(
void)aArgs->GetRpId(rpId);
GECKOBUNDLE_PUT(assertionBundle,
"rpId", jni::StringParam(rpId));
nsString origin;
(
void)aArgs->GetOrigin(origin);
GECKOBUNDLE_PUT(assertionBundle,
"origin", jni::StringParam(origin));
uint32_t timeout;
(
void)aArgs->GetTimeoutMS(&timeout);
GECKOBUNDLE_PUT(assertionBundle,
"timeout", java::sdk::
Double::
New(timeout));
nsString userVerification;
(
void)aArgs->GetUserVerification(userVerification);
GECKOBUNDLE_PUT(assertionBundle,
"userVerification",
jni::StringParam(userVerification));
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 NS_OK;
}
GECKOBUNDLE_PUT(extensionsBundle,
"fidoAppId", jni::StringParam(appId));
}
GECKOBUNDLE_FINISH(extensionsBundle);
nsString json;
(
void)aArgs->GetJson(json);
auto result = java::WebAuthnTokenManager::WebAuthnGetAssertion(
challenge, idList, transportList, assertionBundle, extensionsBundle, hash,
json);
auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
MozPromise<RefPtr<WebAuthnSignResult>, AndroidWebAuthnError,
true>::FromGeckoResult(geckoResult)
->Then(
GetCurrentSerialEventTarget(), __func__,
[aPromise = RefPtr{aPromise}](RefPtr<WebAuthnSignResult>&& aValue) {
aPromise->Resolve(aValue);
},
[aPromise = RefPtr{aPromise}](AndroidWebAuthnError&& aValue) {
aPromise->Reject(aValue.GetError());
});
return NS_OK;
}
NS_IMETHODIMP
AndroidWebAuthnService::Reset() {
MOZ_ASSERT(NS_IsMainThread());
return NS_OK;
}
NS_IMETHODIMP
AndroidWebAuthnService::Cancel(uint64_t aTransactionId) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::HasPendingConditionalGet(uint64_t aBrowsingContextId,
const nsAString& aOrigin,
uint64_t* aRv) {
MOZ_ASSERT(NS_IsMainThread());
// 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, nsIWebAuthnAutoFillEntriesCallback* aCallback) {
MOZ_ASSERT(NS_IsMainThread());
aCallback->Reject(NS_ERROR_NOT_IMPLEMENTED);
return NS_OK;
}
NS_IMETHODIMP
AndroidWebAuthnService::GetAutoFillEntriesForRpId(
const nsAString& aRpId,
const nsTArray<nsTArray<uint8_t>>& aAllowList,
nsIWebAuthnAutoFillEntriesCallback* aCallback) {
MOZ_ASSERT(NS_IsMainThread());
aCallback->Reject(NS_ERROR_NOT_IMPLEMENTED);
return NS_OK;
}
NS_IMETHODIMP
AndroidWebAuthnService::SelectAutoFillEntry(
uint64_t aTransactionId,
const nsTArray<uint8_t>& aCredentialId) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::ResumeConditionalGet(uint64_t aTransactionId) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::PinCallback(uint64_t aTransactionId,
const nsACString& aPin) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::SetHasAttestationConsent(uint64_t aTransactionId,
bool aHasConsent) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::SelectionCallback(uint64_t aTransactionId,
uint64_t aIndex) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::AddVirtualAuthenticator(
const nsACString& aProtocol,
const nsACString& aTransport,
bool aHasResidentKey,
bool aHasUserVerification,
bool aIsUserConsenting,
bool aIsUserVerified, nsACString& aRetval) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::RemoveVirtualAuthenticator(
const nsACString& aAuthenticatorId) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::AddCredential(
const nsACString& aAuthenticatorId,
const nsACString& aCredentialId,
bool aIsResidentCredential,
const nsACString& aRpId,
const nsACString& aPrivateKey,
const nsACString& aUserHandle,
uint32_t aSignCount) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::GetCredentials(
const nsACString& aAuthenticatorId,
nsTArray<RefPtr<nsICredentialParameters>>& _aRetval) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::RemoveCredential(
const nsACString& aAuthenticatorId,
const nsACString& aCredentialId) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::RemoveAllCredentials(
const nsACString& aAuthenticatorId) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::SetUserVerified(
const nsACString& aAuthenticatorId,
bool aIsUserVerified) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::Listen() {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::RunCommand(
const nsACString& aCmd) {
MOZ_ASSERT(NS_IsMainThread());
return NS_ERROR_NOT_IMPLEMENTED;
}
}
// namespace dom
}
// namespace mozilla