зеркало из https://github.com/mozilla/gecko-dev.git
445 строки
17 KiB
C++
445 строки
17 KiB
C++
/* -*- 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"
|
|
|
|
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(¤t)));
|
|
MOZ_ASSERT(current);
|
|
#endif
|
|
}
|
|
|
|
void AndroidWebAuthnTokenManager::Drop() {
|
|
AssertIsOnOwningThread();
|
|
|
|
ClearPromises();
|
|
gAndroidWebAuthnManager = nullptr;
|
|
gAndroidPBackgroundThread = nullptr;
|
|
}
|
|
|
|
RefPtr<U2FRegisterPromise> AndroidWebAuthnTokenManager::Register(
|
|
const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation) {
|
|
AssertIsOnOwningThread();
|
|
|
|
ClearPromises();
|
|
|
|
GetMainThreadEventTarget()->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);
|
|
|
|
if (aInfo.Extra().isSome()) {
|
|
const auto& extra = aInfo.Extra().ref();
|
|
const auto& rp = extra.Rp();
|
|
const auto& user = extra.User();
|
|
|
|
// If we have extra data, then this is WebAuthn, not U2F
|
|
GECKOBUNDLE_PUT(credentialBundle, "isWebAuthn",
|
|
java::sdk::Integer::ValueOf(1));
|
|
|
|
// Get the attestation preference and override if the user asked
|
|
AttestationConveyancePreference attestation =
|
|
extra.attestationConveyancePreference();
|
|
|
|
if (aForceNoneAttestation) {
|
|
// Add UI support to trigger this, bug 1550164
|
|
attestation = AttestationConveyancePreference::None;
|
|
}
|
|
|
|
nsString attestPref;
|
|
attestPref.AssignASCII(
|
|
AttestationConveyancePreferenceValues::GetString(attestation));
|
|
GECKOBUNDLE_PUT(authSelBundle, "attestationPreference",
|
|
jni::StringParam(attestPref));
|
|
|
|
const WebAuthnAuthenticatorSelection& sel =
|
|
extra.AuthenticatorSelection();
|
|
if (sel.requireResidentKey()) {
|
|
GECKOBUNDLE_PUT(authSelBundle, "requireResidentKey",
|
|
java::sdk::Integer::ValueOf(1));
|
|
}
|
|
|
|
if (sel.userVerificationRequirement() ==
|
|
UserVerificationRequirement::Required) {
|
|
GECKOBUNDLE_PUT(authSelBundle, "requireUserVerification",
|
|
java::sdk::Integer::ValueOf(1));
|
|
}
|
|
|
|
if (sel.authenticatorAttachment().isSome()) {
|
|
const AuthenticatorAttachment authenticatorAttachment =
|
|
sel.authenticatorAttachment().value();
|
|
if (authenticatorAttachment == AuthenticatorAttachment::Platform) {
|
|
GECKOBUNDLE_PUT(authSelBundle, "requirePlatformAttachment",
|
|
java::sdk::Integer::ValueOf(1));
|
|
} else if (authenticatorAttachment ==
|
|
AuthenticatorAttachment::Cross_platform) {
|
|
GECKOBUNDLE_PUT(authSelBundle, "requireCrossPlatformAttachment",
|
|
java::sdk::Integer::ValueOf(1));
|
|
}
|
|
}
|
|
|
|
// Get extensions
|
|
for (const WebAuthnExtension& ext : extra.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)]() {
|
|
CryptoBuffer emptyBuffer;
|
|
nsTArray<WebAuthnExtensionResult> extensions;
|
|
WebAuthnMakeCredentialResult result(
|
|
aResult.mClientDataJSON, aResult.mAttObj, aResult.mKeyHandle,
|
|
emptyBuffer, extensions);
|
|
self->mRegisterPromise.Resolve(std::move(result), __func__);
|
|
}));
|
|
}
|
|
}
|
|
|
|
RefPtr<U2FSignPromise> AndroidWebAuthnTokenManager::Sign(
|
|
const WebAuthnGetAssertionInfo& aInfo) {
|
|
AssertIsOnOwningThread();
|
|
|
|
ClearPromises();
|
|
|
|
GetMainThreadEventTarget()->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);
|
|
if (aInfo.Extra().isSome()) {
|
|
const auto& extra = aInfo.Extra().ref();
|
|
|
|
// If we have extra data, then this is WebAuthn, not U2F
|
|
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 : extra.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)]() {
|
|
CryptoBuffer emptyBuffer;
|
|
|
|
nsTArray<WebAuthnExtensionResult> emptyExtensions;
|
|
WebAuthnGetAssertionResult result(
|
|
aResult.mClientDataJSON, aResult.mKeyHandle, aResult.mSignature,
|
|
aResult.mAuthData, emptyExtensions, emptyBuffer,
|
|
aResult.mUserHandle);
|
|
self->mSignPromise.Resolve(std::move(result), __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
|