diff --git a/src/App/Platforms/Android/Autofill/CredentialProviderSelectionActivity.cs b/src/App/Platforms/Android/Autofill/CredentialProviderSelectionActivity.cs index 690c36e7c..8c09b2e49 100644 --- a/src/App/Platforms/Android/Autofill/CredentialProviderSelectionActivity.cs +++ b/src/App/Platforms/Android/Autofill/CredentialProviderSelectionActivity.cs @@ -13,6 +13,7 @@ using Bit.Core.Resources.Localization; using Bit.Core.Utilities.Fido2; using Java.Security; using Bit.Core.Services; +using Bit.App.Platforms.Android.Autofill; namespace Bit.Droid.Autofill { @@ -22,6 +23,7 @@ namespace Bit.Droid.Autofill public class CredentialProviderSelectionActivity : MauiAppCompatActivity { private LazyResolve _fido2MediatorService = new LazyResolve(); + private LazyResolve _fido2GetAssertionUserInterface = new LazyResolve(); private LazyResolve _vaultTimeoutService = new LazyResolve(); private LazyResolve _stateService = new LazyResolve(); private LazyResolve _cipherService = new LazyResolve(); @@ -69,7 +71,7 @@ namespace Bit.Droid.Autofill var androidOrigin = AppInfoToOrigin(getRequest?.CallingAppInfo); var packageName = getRequest?.CallingAppInfo.PackageName; var appInfoOrigin = getRequest?.CallingAppInfo.Origin; - + if (appInfoOrigin is null) { await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, AppResources.PasskeysNotSupportedForThisApp, AppResources.Ok); @@ -77,12 +79,12 @@ namespace Bit.Droid.Autofill return; } - var userInterface = new Fido2GetAssertionUserInterface( - cipherId: cipherId, - userVerified: false, - ensureUnlockedVaultCallback: EnsureUnlockedVaultAsync, - hasVaultBeenUnlockedInThisTransaction: () => hasVaultBeenUnlockedInThisTransaction, - verifyUserCallback: (cipherId, uvPreference) => VerifyUserAsync(cipherId, uvPreference, RpId, hasVaultBeenUnlockedInThisTransaction)); + _fido2GetAssertionUserInterface.Value.Init( + cipherId, + false, + () => hasVaultBeenUnlockedInThisTransaction, + RpId + ); var clientAssertParams = new Fido2ClientAssertCredentialParams { @@ -142,38 +144,6 @@ namespace Bit.Droid.Autofill } } - private async Task EnsureUnlockedVaultAsync() - { - if (!await _stateService.Value.IsAuthenticatedAsync() || await _vaultTimeoutService.Value.IsLockedAsync()) - { - // this should never happen but just in case. - throw new InvalidOperationException("Not authed or vault locked"); - } - } - - internal async Task VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId, bool vaultUnlockedDuringThisTransaction) - { - try - { - var encrypted = await _cipherService.Value.GetAsync(selectedCipherId); - var cipher = await encrypted.DecryptAsync(); - - var userVerification = await _userVerificationMediatorService.Value.VerifyUserForFido2Async( - new Fido2UserVerificationOptions( - cipher?.Reprompt == Bit.Core.Enums.CipherRepromptType.Password, - userVerificationPreference, - vaultUnlockedDuringThisTransaction, - rpId) - ); - return !userVerification.IsCancelled && userVerification.Result; - } - catch (Exception ex) - { - LoggerHelper.LogEvenIfCantBeResolved(ex); - return false; - } - } - private string AppInfoToOrigin(CallingAppInfo info) { var cert = info.SigningInfo.GetApkContentsSigners()[0].ToByteArray(); diff --git a/src/App/Platforms/Android/Autofill/Fido2GetAssertionUserInterface.cs b/src/App/Platforms/Android/Autofill/Fido2GetAssertionUserInterface.cs index 4fa326f9f..9d1e503ec 100644 --- a/src/App/Platforms/Android/Autofill/Fido2GetAssertionUserInterface.cs +++ b/src/App/Platforms/Android/Autofill/Fido2GetAssertionUserInterface.cs @@ -1,21 +1,77 @@ using Bit.Core.Abstractions; +using Bit.Core.Services; +using Bit.Core.Utilities.Fido2; namespace Bit.App.Platforms.Android.Autofill { - //TODO: WIP: Temporary Dummy implementation - public class Fido2GetAssertionUserInterface : IFido2GetAssertionUserInterface + public interface IFido2AndroidGetAssertionUserInterface : IFido2GetAssertionUserInterface { - public bool HasVaultBeenUnlockedInThisTransaction => true; + void Init(string cipherId, + bool userVerified, + Func hasVaultBeenUnlockedInThisTransaction, + string rpId); + } - public Task EnsureUnlockedVaultAsync() + public class Fido2GetAssertionUserInterface : Core.Utilities.Fido2.Fido2GetAssertionUserInterface, IFido2AndroidGetAssertionUserInterface + { + private readonly IStateService _stateService; + private readonly IVaultTimeoutService _vaultTimeoutService; + private readonly ICipherService _cipherService; + private readonly IUserVerificationMediatorService _userVerificationMediatorService; + + public Fido2GetAssertionUserInterface(IStateService stateService, + IVaultTimeoutService vaultTimeoutService, + ICipherService cipherService, + IUserVerificationMediatorService userVerificationMediatorService) { - return Task.FromResult(true); + _stateService = stateService; + _vaultTimeoutService = vaultTimeoutService; + _cipherService = cipherService; + _userVerificationMediatorService = userVerificationMediatorService; } - public Task<(string CipherId, bool UserVerified)> PickCredentialAsync(Fido2GetAssertionUserInterfaceCredential[] credentials) + public void Init(string cipherId, + bool userVerified, + Func hasVaultBeenUnlockedInThisTransaction, + string rpId) { - var credential = credentials[0]; - return Task.FromResult<(string CipherId, bool UserVerified)>((credential.CipherId, true)); + Init(cipherId, + userVerified, + EnsureAuthenAndVaultUnlockedAsync, + hasVaultBeenUnlockedInThisTransaction, + (cipherId, userVerificationPreference) => VerifyUserAsync(cipherId, userVerificationPreference, rpId, hasVaultBeenUnlockedInThisTransaction())); + } + + public async Task EnsureAuthenAndVaultUnlockedAsync() + { + if (!await _stateService.IsAuthenticatedAsync() || await _vaultTimeoutService.IsLockedAsync()) + { + // this should never happen but just in case. + throw new InvalidOperationException("Not authed or vault locked"); + } + } + + private async Task VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId, bool vaultUnlockedDuringThisTransaction) + { + try + { + var encrypted = await _cipherService.GetAsync(selectedCipherId); + var cipher = await encrypted.DecryptAsync(); + + var userVerification = await _userVerificationMediatorService.VerifyUserForFido2Async( + new Fido2UserVerificationOptions( + cipher?.Reprompt == Core.Enums.CipherRepromptType.Password, + userVerificationPreference, + vaultUnlockedDuringThisTransaction, + rpId) + ); + return !userVerification.IsCancelled && userVerification.Result; + } + catch (Exception ex) + { + LoggerHelper.LogEvenIfCantBeResolved(ex); + return false; + } } } } diff --git a/src/App/Platforms/Android/MainApplication.cs b/src/App/Platforms/Android/MainApplication.cs index 6fed0e08a..69b4b01e6 100644 --- a/src/App/Platforms/Android/MainApplication.cs +++ b/src/App/Platforms/Android/MainApplication.cs @@ -110,6 +110,13 @@ namespace Bit.Droid userVerificationMediatorService); ServiceContainer.Register(fido2AuthenticatorService); + var fido2GetAssertionUserInterface = new Fido2GetAssertionUserInterface( + ServiceContainer.Resolve(), + ServiceContainer.Resolve(), + ServiceContainer.Resolve(), + ServiceContainer.Resolve()); + ServiceContainer.Register(fido2GetAssertionUserInterface); + var fido2MakeCredentialUserInterface = new Fido2MakeCredentialUserInterface( ServiceContainer.Resolve(), ServiceContainer.Resolve(), @@ -124,7 +131,7 @@ namespace Bit.Droid ServiceContainer.Resolve(), ServiceContainer.Resolve(), ServiceContainer.Resolve(), - new Fido2GetAssertionUserInterface(), + fido2GetAssertionUserInterface, fido2MakeCredentialUserInterface); ServiceContainer.Register(fido2ClientService); diff --git a/src/Core/Utilities/Fido2/Fido2GetAssertionUserInterface.cs b/src/Core/Utilities/Fido2/Fido2GetAssertionUserInterface.cs index e434b5482..07f946a18 100644 --- a/src/Core/Utilities/Fido2/Fido2GetAssertionUserInterface.cs +++ b/src/Core/Utilities/Fido2/Fido2GetAssertionUserInterface.cs @@ -12,11 +12,15 @@ namespace Bit.Core.Utilities.Fido2 /// public class Fido2GetAssertionUserInterface : IFido2GetAssertionUserInterface { - private readonly string _cipherId; - private readonly bool _userVerified = false; - private readonly Func _ensureUnlockedVaultCallback; - private readonly Func _hasVaultBeenUnlockedInThisTransaction; - private readonly Func> _verifyUserCallback; + protected string _cipherId; + protected bool _userVerified = false; + protected Func _ensureUnlockedVaultCallback; + protected Func _hasVaultBeenUnlockedInThisTransaction; + protected Func> _verifyUserCallback; + + public Fido2GetAssertionUserInterface() + { + } /// The cipherId for the credential that the user has already picker /// True if the user has already been verified by the operating system @@ -25,6 +29,15 @@ namespace Bit.Core.Utilities.Fido2 Func ensureUnlockedVaultCallback, Func hasVaultBeenUnlockedInThisTransaction, Func> verifyUserCallback) + { + Init(cipherId, userVerified, ensureUnlockedVaultCallback, hasVaultBeenUnlockedInThisTransaction, verifyUserCallback); + } + + protected void Init(string cipherId, + bool userVerified, + Func ensureUnlockedVaultCallback, + Func hasVaultBeenUnlockedInThisTransaction, + Func> verifyUserCallback) { _cipherId = cipherId; _userVerified = userVerified;