PM-7623 Fix proper implementation of IFido2GetAssertionUserInterface now that the Fido2ClientService is being used for passkey autofill (#3174)

This commit is contained in:
Federico Maccaroni 2024-04-22 13:36:18 -03:00 committed by GitHub
parent bfa57ad888
commit 1bfe894181
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 53 deletions

View File

@ -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<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
private LazyResolve<IFido2AndroidGetAssertionUserInterface> _fido2GetAssertionUserInterface = new LazyResolve<IFido2AndroidGetAssertionUserInterface>();
private LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>();
private LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
private LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
@ -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<bool> 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();

View File

@ -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<bool> 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<bool> 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<bool> 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;
}
}
}
}

View File

@ -110,6 +110,13 @@ namespace Bit.Droid
userVerificationMediatorService);
ServiceContainer.Register<IFido2AuthenticatorService>(fido2AuthenticatorService);
var fido2GetAssertionUserInterface = new Fido2GetAssertionUserInterface(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>(),
ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IUserVerificationMediatorService>());
ServiceContainer.Register<IFido2AndroidGetAssertionUserInterface>(fido2GetAssertionUserInterface);
var fido2MakeCredentialUserInterface = new Fido2MakeCredentialUserInterface(
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>(),
@ -124,7 +131,7 @@ namespace Bit.Droid
ServiceContainer.Resolve<IEnvironmentService>(),
ServiceContainer.Resolve<ICryptoFunctionService>(),
ServiceContainer.Resolve<IFido2AuthenticatorService>(),
new Fido2GetAssertionUserInterface(),
fido2GetAssertionUserInterface,
fido2MakeCredentialUserInterface);
ServiceContainer.Register<IFido2ClientService>(fido2ClientService);

View File

@ -12,11 +12,15 @@ namespace Bit.Core.Utilities.Fido2
/// </summary>
public class Fido2GetAssertionUserInterface : IFido2GetAssertionUserInterface
{
private readonly string _cipherId;
private readonly bool _userVerified = false;
private readonly Func<Task> _ensureUnlockedVaultCallback;
private readonly Func<bool> _hasVaultBeenUnlockedInThisTransaction;
private readonly Func<string, Fido2UserVerificationPreference, Task<bool>> _verifyUserCallback;
protected string _cipherId;
protected bool _userVerified = false;
protected Func<Task> _ensureUnlockedVaultCallback;
protected Func<bool> _hasVaultBeenUnlockedInThisTransaction;
protected Func<string, Fido2UserVerificationPreference, Task<bool>> _verifyUserCallback;
public Fido2GetAssertionUserInterface()
{
}
/// <param name="cipherId">The cipherId for the credential that the user has already picker</param>
/// <param name="userVerified">True if the user has already been verified by the operating system</param>
@ -25,6 +29,15 @@ namespace Bit.Core.Utilities.Fido2
Func<Task> ensureUnlockedVaultCallback,
Func<bool> hasVaultBeenUnlockedInThisTransaction,
Func<string, Fido2UserVerificationPreference, Task<bool>> verifyUserCallback)
{
Init(cipherId, userVerified, ensureUnlockedVaultCallback, hasVaultBeenUnlockedInThisTransaction, verifyUserCallback);
}
protected void Init(string cipherId,
bool userVerified,
Func<Task> ensureUnlockedVaultCallback,
Func<bool> hasVaultBeenUnlockedInThisTransaction,
Func<string, Fido2UserVerificationPreference, Task<bool>> verifyUserCallback)
{
_cipherId = cipherId;
_userVerified = userVerified;