Bug 1861484 - move isUserVerifyingPlatformAuthenticatorAvailable to parent process. r=keeler

Differential Revision: https://phabricator.services.mozilla.com/D191998
This commit is contained in:
John Schanck 2023-11-08 21:20:15 +00:00
Родитель ced0683cec
Коммит e17a30add4
16 изменённых файлов: 242 добавлений и 69 удалений

Просмотреть файл

@ -124,6 +124,14 @@ void CredentialsContainer::EnsureWebAuthnManager() {
}
}
already_AddRefed<WebAuthnManager> CredentialsContainer::GetWebAuthnManager() {
MOZ_ASSERT(NS_IsMainThread());
EnsureWebAuthnManager();
RefPtr<WebAuthnManager> ref = mManager;
return ref.forget();
}
JSObject* CredentialsContainer::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return CredentialsContainer_Binding::Wrap(aCx, this, aGivenProto);

Просмотреть файл

@ -22,6 +22,8 @@ class CredentialsContainer final : public nsISupports, public nsWrapperCache {
nsPIDOMWindowInner* GetParentObject() const { return mParent; }
already_AddRefed<WebAuthnManager> GetWebAuthnManager();
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;

Просмотреть файл

@ -33,6 +33,11 @@ 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 browsingContextId,

Просмотреть файл

@ -141,6 +141,7 @@ async protocol PWebAuthnTransaction {
parent:
async RequestRegister(uint64_t aTransactionId, WebAuthnMakeCredentialInfo aTransactionInfo);
async RequestSign(uint64_t aTransactionId, WebAuthnGetAssertionInfo aTransactionInfo);
async RequestIsUVPAA() returns (bool available);
[Tainted] async RequestCancel(uint64_t aTransactionId);
async DestroyMe();

Просмотреть файл

@ -9,10 +9,13 @@
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/dom/AuthenticatorResponse.h"
#include "mozilla/dom/CredentialsContainer.h"
#include "mozilla/dom/ChromeUtils.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PublicKeyCredential.h"
#include "mozilla/dom/WebAuthenticationBinding.h"
#include "mozilla/dom/WebAuthnManager.h"
#include "nsCycleCollectionParticipant.h"
#ifdef XP_WIN
@ -117,46 +120,16 @@ void PublicKeyCredential::SetAssertionResponse(
already_AddRefed<Promise>
PublicKeyCredential::IsUserVerifyingPlatformAuthenticatorAvailable(
GlobalObject& aGlobal, ErrorResult& aError) {
RefPtr<Promise> promise =
Promise::Create(xpc::CurrentNativeGlobal(aGlobal.Context()), aError);
if (aError.Failed()) {
nsCOMPtr<nsPIDOMWindowInner> window =
do_QueryInterface(aGlobal.GetAsSupports());
if (!window) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
// https://w3c.github.io/webauthn/#isUserVerifyingPlatformAuthenticatorAvailable
//
// If on latest windows, call system APIs, otherwise return false, as we don't
// have other UVPAAs available at this time.
#ifdef XP_WIN
if (WinWebAuthnService::IsUserVerifyingPlatformAuthenticatorAvailable()) {
promise->MaybeResolve(true);
return promise.forget();
}
promise->MaybeResolve(false);
#elif defined(MOZ_WIDGET_ANDROID)
if (StaticPrefs::
security_webauthn_webauthn_enable_android_fido2_residentkey()) {
auto result = java::WebAuthnTokenManager::
WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable();
auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise](const MozPromise<bool, bool, false>::ResolveOrRejectValue&
aValue) {
if (aValue.IsResolve()) {
promise->MaybeResolve(aValue.ResolveValue());
}
});
} else {
promise->MaybeResolve(false);
}
#else
promise->MaybeResolve(false);
#endif
return promise.forget();
RefPtr<WebAuthnManager> manager =
window->Navigator()->Credentials()->GetWebAuthnManager();
return manager->IsUVPAA(aGlobal, aError);
}
/* static */

Просмотреть файл

@ -18,6 +18,7 @@
#include "mozilla/dom/PublicKeyCredential.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PWebAuthnTransaction.h"
#include "mozilla/dom/PWebAuthnTransactionChild.h"
#include "mozilla/dom/WebAuthnManager.h"
#include "mozilla/dom/WebAuthnTransactionChild.h"
#include "mozilla/dom/WebAuthnUtil.h"
@ -727,6 +728,32 @@ already_AddRefed<Promise> WebAuthnManager::Store(const Credential& aCredential,
return promise.forget();
}
already_AddRefed<Promise> WebAuthnManager::IsUVPAA(GlobalObject& aGlobal,
ErrorResult& aError) {
RefPtr<Promise> promise =
Promise::Create(xpc::CurrentNativeGlobal(aGlobal.Context()), aError);
if (aError.Failed()) {
return nullptr;
}
if (!MaybeCreateBackgroundActor()) {
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
return promise.forget();
}
mChild->SendRequestIsUVPAA()->Then(
GetCurrentSerialEventTarget(), __func__,
[promise](const PWebAuthnTransactionChild::RequestIsUVPAAPromise::
ResolveOrRejectValue& aValue) {
if (aValue.IsResolve()) {
promise->MaybeResolve(aValue.ResolveValue());
} else {
promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
}
});
return promise.forget();
}
void WebAuthnManager::FinishMakeCredential(
const uint64_t& aTransactionId,
const WebAuthnMakeCredentialResult& aResult) {

Просмотреть файл

@ -95,6 +95,8 @@ class WebAuthnManager final : public WebAuthnManagerBase, public AbortFollower {
already_AddRefed<Promise> Store(const Credential& aCredential,
ErrorResult& aError);
already_AddRefed<Promise> IsUVPAA(GlobalObject& aGlobal, ErrorResult& aError);
// WebAuthnManagerBase
void FinishMakeCredential(

Просмотреть файл

@ -40,6 +40,14 @@ WebAuthnService::GetAssertion(uint64_t aTransactionId,
aArgs, aPromise);
}
NS_IMETHODIMP
WebAuthnService::GetIsUVPAA(bool* aAvailable) {
if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {
return mTestService->GetIsUVPAA(aAvailable);
}
return mPlatformService->GetIsUVPAA(aAvailable);
}
NS_IMETHODIMP
WebAuthnService::Reset() {
if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {

Просмотреть файл

@ -280,6 +280,84 @@ mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestCancel(
return IPC_OK();
}
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvRequestIsUVPAA(
RequestIsUVPAAResolver&& aResolver) {
#ifdef MOZ_WIDGET_ANDROID
// Try the nsIWebAuthnService. If we're configured for tests we
// will get a result. Otherwise we expect NS_ERROR_NOT_IMPLEMENTED.
nsCOMPtr<nsIWebAuthnService> service(
do_GetService("@mozilla.org/webauthn/service;1"));
bool available;
nsresult rv = service->GetIsUVPAA(&available);
if (NS_SUCCEEDED(rv)) {
aResolver(available);
return IPC_OK();
}
// Don't consult the platform API if resident key support is disabled.
if (!StaticPrefs::
security_webauthn_webauthn_enable_android_fido2_residentkey()) {
aResolver(false);
return IPC_OK();
}
// The GeckoView implementation of
// isUserVerifiyingPlatformAuthenticatorAvailable does not block, but we must
// call it on the main thread. It returns a MozPromise which we can ->Then to
// call aResolver on the IPDL background thread.
//
// Bug 1550788: there is an unnecessary layer of dispatching here: ipdl ->
// main -> a background thread. Other platforms just do ipdl -> a background
// thread.
nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
__func__, [target, resolver = std::move(aResolver)]() {
auto result = java::WebAuthnTokenManager::
WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable();
auto geckoResult = java::GeckoResult::LocalRef(std::move(result));
MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
->Then(
target, __func__,
[resolver](
const MozPromise<bool, bool, false>::ResolveOrRejectValue&
aValue) {
if (aValue.IsResolve()) {
resolver(aValue.ResolveValue());
} else {
resolver(false);
}
});
}));
NS_DispatchToMainThread(runnable.forget());
return IPC_OK();
#else
nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
__func__, [target, resolver = std::move(aResolver)]() {
bool available;
nsCOMPtr<nsIWebAuthnService> service(
do_GetService("@mozilla.org/webauthn/service;1"));
nsresult rv = service->GetIsUVPAA(&available);
if (NS_FAILED(rv)) {
available = false;
}
BoolPromise::CreateAndResolve(available, __func__)
->Then(target, __func__,
[resolver](const BoolPromise::ResolveOrRejectValue& value) {
if (value.IsResolve()) {
resolver(value.ResolveValue());
} else {
resolver(false);
}
});
}));
NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK);
return IPC_OK();
#endif
}
mozilla::ipc::IPCResult WebAuthnTransactionParent::RecvDestroyMe() {
::mozilla::ipc::AssertIsOnBackgroundThread();

Просмотреть файл

@ -35,6 +35,9 @@ class WebAuthnTransactionParent final : public PWebAuthnTransactionParent {
mozilla::ipc::IPCResult RecvRequestCancel(
const Tainted<uint64_t>& aTransactionId);
mozilla::ipc::IPCResult RecvRequestIsUVPAA(
RequestIsUVPAAResolver&& aResolver);
mozilla::ipc::IPCResult RecvDestroyMe();
virtual void ActorDestroy(ActorDestroyReason aWhy) override;

Просмотреть файл

@ -132,18 +132,20 @@ bool WinWebAuthnService::AreWebAuthNApisAvailable() {
gWinWebauthnGetApiVersionNumber() >= kMinWinWebAuthNApiVersion;
}
// static
bool WinWebAuthnService::IsUserVerifyingPlatformAuthenticatorAvailable() {
NS_IMETHODIMP
WinWebAuthnService::GetIsUVPAA(bool* aAvailable) {
nsresult rv = EnsureWinWebAuthnModuleLoaded();
NS_ENSURE_SUCCESS(rv, false);
NS_ENSURE_SUCCESS(rv, rv);
if (WinWebAuthnService::AreWebAuthNApisAvailable()) {
BOOL isUVPAA = FALSE;
StaticAutoReadLock lock(gWinWebAuthnModuleLock);
return gWinWebAuthnModule && gWinWebauthnIsUVPAA(&isUVPAA) == S_OK &&
isUVPAA == TRUE;
*aAvailable = gWinWebAuthnModule && gWinWebauthnIsUVPAA(&isUVPAA) == S_OK &&
isUVPAA == TRUE;
} else {
*aAvailable = false;
}
return false;
return NS_OK;
}
NS_IMETHODIMP

Просмотреть файл

@ -614,6 +614,17 @@ impl AuthrsService {
.or(Err(NS_ERROR_FAILURE))
}
xpcom_method!(get_is_uvpaa => GetIsUVPAA() -> bool);
fn get_is_uvpaa(&self) -> Result<bool, nsresult> {
if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
Ok(false)
} else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
Ok(self.test_token_manager.has_platform_authenticator())
} else {
Err(NS_ERROR_NOT_AVAILABLE)
}
}
xpcom_method!(make_credential => MakeCredential(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsIWebAuthnRegisterArgs, aPromise: *const nsIWebAuthnRegisterPromise));
fn make_credential(
&self,

Просмотреть файл

@ -870,4 +870,19 @@ impl TestTokenManager {
.may_block(true)
.dispatch_background_task();
}
pub fn has_platform_authenticator(&self) -> bool {
if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
return false;
}
for token in self.state.lock().unwrap().values_mut() {
let _ = token.init();
if token.transport.as_str() == "internal" {
return true;
}
}
false
}
}

Просмотреть файл

@ -22,6 +22,9 @@ interface nsICredentialParameters : nsISupports
[scriptable, uuid(e236a9b4-a26f-11ed-b6cc-07a9834e19b1)]
interface nsIWebAuthnService : nsISupports
{
// IsUserVerifyingPlatformAuthenticatorAvailable
readonly attribute bool isUVPAA;
void makeCredential(
in uint64_t aTransactionId,
in uint64_t browsingContextId,

Просмотреть файл

@ -18,19 +18,30 @@
<script class="testbody" type="text/javascript">
"use strict";
add_task(async function test_is_platform_available() {
// This test ensures that isUserVerifyingPlatformAuthenticatorAvailable()
// is a callable method, but with the softtoken enabled, it's not useful to
// figure out what it actually returns, so we'll just make sure it runs.
await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.then(function(aResult) {
ok(true, "Resolved: " + aResult);
})
.catch(function(aProblem) {
ok(false, "Problem encountered: " + aProblem);
});
add_task(async function test_uvpaa_with_no_authenticator() {
let uvpaa = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
ok(uvpaa === false, "Platform authenticator is not available");
});
add_task(async () => {
await addVirtualAuthenticator("ctap2_1", "usb");
});
add_task(async function test_uvpaa_with_usb_authenticator() {
let uvpaa = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
ok(uvpaa === false, "Platform authenticator is not available");
});
add_task(async () => {
await addVirtualAuthenticator("ctap2_1", "internal");
});
add_task(async function test_uvpaa_with_usb_and_platform_authenticator() {
let uvpaa = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
ok(uvpaa === true, "Platform authenticator is available");
});
</script>
</body>

Просмотреть файл

@ -20,21 +20,45 @@ var { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
async function addVirtualAuthenticator() {
let id = await SpecialPowers.spawnChrome([], () => {
let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
Ci.nsIWebAuthnService
);
let id = webauthnService.addVirtualAuthenticator(
"ctap2_1",
"internal",
true,
true,
true,
true
);
return id;
});
async function addVirtualAuthenticator(
protocol = "ctap2_1",
transport = "internal",
hasResidentKey = true,
hasUserVerification = true,
isUserConsenting = true,
isUserVerified = true
) {
let id = await SpecialPowers.spawnChrome(
[
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified,
],
(
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified
) => {
let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
Ci.nsIWebAuthnService
);
let id = webauthnService.addVirtualAuthenticator(
protocol,
transport,
hasResidentKey,
hasUserVerification,
isUserConsenting,
isUserVerified
);
return id;
}
);
SimpleTest.registerCleanupFunction(async () => {
await SpecialPowers.spawnChrome([id], id => {