diff --git a/dom/credentialmanagement/CredentialsContainer.cpp b/dom/credentialmanagement/CredentialsContainer.cpp index ab60b9e7ed5d..9fb879770deb 100644 --- a/dom/credentialmanagement/CredentialsContainer.cpp +++ b/dom/credentialmanagement/CredentialsContainer.cpp @@ -124,6 +124,14 @@ void CredentialsContainer::EnsureWebAuthnManager() { } } +already_AddRefed CredentialsContainer::GetWebAuthnManager() { + MOZ_ASSERT(NS_IsMainThread()); + + EnsureWebAuthnManager(); + RefPtr ref = mManager; + return ref.forget(); +} + JSObject* CredentialsContainer::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return CredentialsContainer_Binding::Wrap(aCx, this, aGivenProto); diff --git a/dom/credentialmanagement/CredentialsContainer.h b/dom/credentialmanagement/CredentialsContainer.h index 2347e8458241..1c4c53bd9d01 100644 --- a/dom/credentialmanagement/CredentialsContainer.h +++ b/dom/credentialmanagement/CredentialsContainer.h @@ -22,6 +22,8 @@ class CredentialsContainer final : public nsISupports, public nsWrapperCache { nsPIDOMWindowInner* GetParentObject() const { return mParent; } + already_AddRefed GetWebAuthnManager(); + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; diff --git a/dom/webauthn/AndroidWebAuthnService.cpp b/dom/webauthn/AndroidWebAuthnService.cpp index ad53f298579b..3862275e49ab 100644 --- a/dom/webauthn/AndroidWebAuthnService.cpp +++ b/dom/webauthn/AndroidWebAuthnService.cpp @@ -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, diff --git a/dom/webauthn/PWebAuthnTransaction.ipdl b/dom/webauthn/PWebAuthnTransaction.ipdl index e4b8e90a4ee2..f9d59209dbc1 100644 --- a/dom/webauthn/PWebAuthnTransaction.ipdl +++ b/dom/webauthn/PWebAuthnTransaction.ipdl @@ -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(); diff --git a/dom/webauthn/PublicKeyCredential.cpp b/dom/webauthn/PublicKeyCredential.cpp index 4b8109d7b80c..bfcbeccda864 100644 --- a/dom/webauthn/PublicKeyCredential.cpp +++ b/dom/webauthn/PublicKeyCredential.cpp @@ -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 PublicKeyCredential::IsUserVerifyingPlatformAuthenticatorAvailable( GlobalObject& aGlobal, ErrorResult& aError) { - RefPtr promise = - Promise::Create(xpc::CurrentNativeGlobal(aGlobal.Context()), aError); - if (aError.Failed()) { + nsCOMPtr 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::FromGeckoResult(geckoResult) - ->Then( - GetMainThreadSerialEventTarget(), __func__, - [promise](const MozPromise::ResolveOrRejectValue& - aValue) { - if (aValue.IsResolve()) { - promise->MaybeResolve(aValue.ResolveValue()); - } - }); - } else { - promise->MaybeResolve(false); - } -#else - promise->MaybeResolve(false); -#endif - return promise.forget(); + RefPtr manager = + window->Navigator()->Credentials()->GetWebAuthnManager(); + return manager->IsUVPAA(aGlobal, aError); } /* static */ diff --git a/dom/webauthn/WebAuthnManager.cpp b/dom/webauthn/WebAuthnManager.cpp index 0236f36024ee..e7282b8a5cbb 100644 --- a/dom/webauthn/WebAuthnManager.cpp +++ b/dom/webauthn/WebAuthnManager.cpp @@ -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 WebAuthnManager::Store(const Credential& aCredential, return promise.forget(); } +already_AddRefed WebAuthnManager::IsUVPAA(GlobalObject& aGlobal, + ErrorResult& aError) { + RefPtr 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) { diff --git a/dom/webauthn/WebAuthnManager.h b/dom/webauthn/WebAuthnManager.h index 05175c8087cf..b0ccd7ccd418 100644 --- a/dom/webauthn/WebAuthnManager.h +++ b/dom/webauthn/WebAuthnManager.h @@ -95,6 +95,8 @@ class WebAuthnManager final : public WebAuthnManagerBase, public AbortFollower { already_AddRefed Store(const Credential& aCredential, ErrorResult& aError); + already_AddRefed IsUVPAA(GlobalObject& aGlobal, ErrorResult& aError); + // WebAuthnManagerBase void FinishMakeCredential( diff --git a/dom/webauthn/WebAuthnService.cpp b/dom/webauthn/WebAuthnService.cpp index d8401698b7a7..70879f256cfa 100644 --- a/dom/webauthn/WebAuthnService.cpp +++ b/dom/webauthn/WebAuthnService.cpp @@ -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()) { diff --git a/dom/webauthn/WebAuthnTransactionParent.cpp b/dom/webauthn/WebAuthnTransactionParent.cpp index 8ddd408baa55..f0de1b40dcff 100644 --- a/dom/webauthn/WebAuthnTransactionParent.cpp +++ b/dom/webauthn/WebAuthnTransactionParent.cpp @@ -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 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 target = GetCurrentSerialEventTarget(); + nsCOMPtr runnable(NS_NewRunnableFunction( + __func__, [target, resolver = std::move(aResolver)]() { + auto result = java::WebAuthnTokenManager:: + WebAuthnIsUserVerifyingPlatformAuthenticatorAvailable(); + auto geckoResult = java::GeckoResult::LocalRef(std::move(result)); + MozPromise::FromGeckoResult(geckoResult) + ->Then( + target, __func__, + [resolver]( + const MozPromise::ResolveOrRejectValue& + aValue) { + if (aValue.IsResolve()) { + resolver(aValue.ResolveValue()); + } else { + resolver(false); + } + }); + })); + NS_DispatchToMainThread(runnable.forget()); + return IPC_OK(); + +#else + + nsCOMPtr target = GetCurrentSerialEventTarget(); + nsCOMPtr runnable(NS_NewRunnableFunction( + __func__, [target, resolver = std::move(aResolver)]() { + bool available; + nsCOMPtr 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(); diff --git a/dom/webauthn/WebAuthnTransactionParent.h b/dom/webauthn/WebAuthnTransactionParent.h index f117f1bbdb46..3a119c9d57a1 100644 --- a/dom/webauthn/WebAuthnTransactionParent.h +++ b/dom/webauthn/WebAuthnTransactionParent.h @@ -35,6 +35,9 @@ class WebAuthnTransactionParent final : public PWebAuthnTransactionParent { mozilla::ipc::IPCResult RecvRequestCancel( const Tainted& aTransactionId); + mozilla::ipc::IPCResult RecvRequestIsUVPAA( + RequestIsUVPAAResolver&& aResolver); + mozilla::ipc::IPCResult RecvDestroyMe(); virtual void ActorDestroy(ActorDestroyReason aWhy) override; diff --git a/dom/webauthn/WinWebAuthnService.cpp b/dom/webauthn/WinWebAuthnService.cpp index 9d6be22d1fb1..adb2c0e4b91a 100644 --- a/dom/webauthn/WinWebAuthnService.cpp +++ b/dom/webauthn/WinWebAuthnService.cpp @@ -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 diff --git a/dom/webauthn/authrs_bridge/src/lib.rs b/dom/webauthn/authrs_bridge/src/lib.rs index e5cb09fe8c5b..69045a00d742 100644 --- a/dom/webauthn/authrs_bridge/src/lib.rs +++ b/dom/webauthn/authrs_bridge/src/lib.rs @@ -614,6 +614,17 @@ impl AuthrsService { .or(Err(NS_ERROR_FAILURE)) } + xpcom_method!(get_is_uvpaa => GetIsUVPAA() -> bool); + fn get_is_uvpaa(&self) -> Result { + 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, diff --git a/dom/webauthn/authrs_bridge/src/test_token.rs b/dom/webauthn/authrs_bridge/src/test_token.rs index bd0386143e3d..d7dc44c5bd05 100644 --- a/dom/webauthn/authrs_bridge/src/test_token.rs +++ b/dom/webauthn/authrs_bridge/src/test_token.rs @@ -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 + } } diff --git a/dom/webauthn/nsIWebAuthnService.idl b/dom/webauthn/nsIWebAuthnService.idl index cd46b809921b..15bd414bb5f6 100644 --- a/dom/webauthn/nsIWebAuthnService.idl +++ b/dom/webauthn/nsIWebAuthnService.idl @@ -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, diff --git a/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html b/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html index e8ee1ba4536c..15b83659d512 100644 --- a/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html +++ b/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html @@ -18,19 +18,30 @@ diff --git a/dom/webauthn/tests/u2futil.js b/dom/webauthn/tests/u2futil.js index 178284d8eda7..e8026e8e5999 100644 --- a/dom/webauthn/tests/u2futil.js +++ b/dom/webauthn/tests/u2futil.js @@ -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 => {