PM-7623 Fix proper implementation of IFido2GetAssertionUserInterface now that the Fido2ClientService is being used for passkey autofill (#3174)
This commit is contained in:
parent
bfa57ad888
commit
1bfe894181
|
@ -13,6 +13,7 @@ using Bit.Core.Resources.Localization;
|
||||||
using Bit.Core.Utilities.Fido2;
|
using Bit.Core.Utilities.Fido2;
|
||||||
using Java.Security;
|
using Java.Security;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
using Bit.App.Platforms.Android.Autofill;
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
|
@ -22,6 +23,7 @@ namespace Bit.Droid.Autofill
|
||||||
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
|
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
|
||||||
{
|
{
|
||||||
private LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
|
private LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
|
||||||
|
private LazyResolve<IFido2AndroidGetAssertionUserInterface> _fido2GetAssertionUserInterface = new LazyResolve<IFido2AndroidGetAssertionUserInterface>();
|
||||||
private LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>();
|
private LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>();
|
||||||
private LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
|
private LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
|
||||||
private LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
|
private LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
|
||||||
|
@ -69,7 +71,7 @@ namespace Bit.Droid.Autofill
|
||||||
var androidOrigin = AppInfoToOrigin(getRequest?.CallingAppInfo);
|
var androidOrigin = AppInfoToOrigin(getRequest?.CallingAppInfo);
|
||||||
var packageName = getRequest?.CallingAppInfo.PackageName;
|
var packageName = getRequest?.CallingAppInfo.PackageName;
|
||||||
var appInfoOrigin = getRequest?.CallingAppInfo.Origin;
|
var appInfoOrigin = getRequest?.CallingAppInfo.Origin;
|
||||||
|
|
||||||
if (appInfoOrigin is null)
|
if (appInfoOrigin is null)
|
||||||
{
|
{
|
||||||
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, AppResources.PasskeysNotSupportedForThisApp, AppResources.Ok);
|
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, AppResources.PasskeysNotSupportedForThisApp, AppResources.Ok);
|
||||||
|
@ -77,12 +79,12 @@ namespace Bit.Droid.Autofill
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var userInterface = new Fido2GetAssertionUserInterface(
|
_fido2GetAssertionUserInterface.Value.Init(
|
||||||
cipherId: cipherId,
|
cipherId,
|
||||||
userVerified: false,
|
false,
|
||||||
ensureUnlockedVaultCallback: EnsureUnlockedVaultAsync,
|
() => hasVaultBeenUnlockedInThisTransaction,
|
||||||
hasVaultBeenUnlockedInThisTransaction: () => hasVaultBeenUnlockedInThisTransaction,
|
RpId
|
||||||
verifyUserCallback: (cipherId, uvPreference) => VerifyUserAsync(cipherId, uvPreference, RpId, hasVaultBeenUnlockedInThisTransaction));
|
);
|
||||||
|
|
||||||
var clientAssertParams = new Fido2ClientAssertCredentialParams
|
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)
|
private string AppInfoToOrigin(CallingAppInfo info)
|
||||||
{
|
{
|
||||||
var cert = info.SigningInfo.GetApkContentsSigners()[0].ToByteArray();
|
var cert = info.SigningInfo.GetApkContentsSigners()[0].ToByteArray();
|
||||||
|
|
|
@ -1,21 +1,77 @@
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
|
||||||
namespace Bit.App.Platforms.Android.Autofill
|
namespace Bit.App.Platforms.Android.Autofill
|
||||||
{
|
{
|
||||||
//TODO: WIP: Temporary Dummy implementation
|
public interface IFido2AndroidGetAssertionUserInterface : IFido2GetAssertionUserInterface
|
||||||
public class Fido2GetAssertionUserInterface : 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];
|
Init(cipherId,
|
||||||
return Task.FromResult<(string CipherId, bool UserVerified)>((credential.CipherId, true));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,13 @@ namespace Bit.Droid
|
||||||
userVerificationMediatorService);
|
userVerificationMediatorService);
|
||||||
ServiceContainer.Register<IFido2AuthenticatorService>(fido2AuthenticatorService);
|
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(
|
var fido2MakeCredentialUserInterface = new Fido2MakeCredentialUserInterface(
|
||||||
ServiceContainer.Resolve<IStateService>(),
|
ServiceContainer.Resolve<IStateService>(),
|
||||||
ServiceContainer.Resolve<IVaultTimeoutService>(),
|
ServiceContainer.Resolve<IVaultTimeoutService>(),
|
||||||
|
@ -124,7 +131,7 @@ namespace Bit.Droid
|
||||||
ServiceContainer.Resolve<IEnvironmentService>(),
|
ServiceContainer.Resolve<IEnvironmentService>(),
|
||||||
ServiceContainer.Resolve<ICryptoFunctionService>(),
|
ServiceContainer.Resolve<ICryptoFunctionService>(),
|
||||||
ServiceContainer.Resolve<IFido2AuthenticatorService>(),
|
ServiceContainer.Resolve<IFido2AuthenticatorService>(),
|
||||||
new Fido2GetAssertionUserInterface(),
|
fido2GetAssertionUserInterface,
|
||||||
fido2MakeCredentialUserInterface);
|
fido2MakeCredentialUserInterface);
|
||||||
ServiceContainer.Register<IFido2ClientService>(fido2ClientService);
|
ServiceContainer.Register<IFido2ClientService>(fido2ClientService);
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,15 @@ namespace Bit.Core.Utilities.Fido2
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Fido2GetAssertionUserInterface : IFido2GetAssertionUserInterface
|
public class Fido2GetAssertionUserInterface : IFido2GetAssertionUserInterface
|
||||||
{
|
{
|
||||||
private readonly string _cipherId;
|
protected string _cipherId;
|
||||||
private readonly bool _userVerified = false;
|
protected bool _userVerified = false;
|
||||||
private readonly Func<Task> _ensureUnlockedVaultCallback;
|
protected Func<Task> _ensureUnlockedVaultCallback;
|
||||||
private readonly Func<bool> _hasVaultBeenUnlockedInThisTransaction;
|
protected Func<bool> _hasVaultBeenUnlockedInThisTransaction;
|
||||||
private readonly Func<string, Fido2UserVerificationPreference, Task<bool>> _verifyUserCallback;
|
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="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>
|
/// <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<Task> ensureUnlockedVaultCallback,
|
||||||
Func<bool> hasVaultBeenUnlockedInThisTransaction,
|
Func<bool> hasVaultBeenUnlockedInThisTransaction,
|
||||||
Func<string, Fido2UserVerificationPreference, Task<bool>> verifyUserCallback)
|
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;
|
_cipherId = cipherId;
|
||||||
_userVerified = userVerified;
|
_userVerified = userVerified;
|
||||||
|
|
Loading…
Reference in New Issue