1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-09-27 03:52:57 +02:00

PM-6468 Implemented copy TOTP if needed after using a Fido2 credential. Also added the Fido2MediatorService to have one point to interact with the authentication and also to add any new logic we need. (#3082)

This commit is contained in:
Federico Maccaroni 2024-03-14 18:12:50 -03:00 committed by GitHub
parent faa515b415
commit 970d3c2621
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 129 additions and 40 deletions

View File

@ -35,5 +35,6 @@ namespace Bit.Core.Abstractions
Task SoftDeleteWithServerAsync(string id);
Task RestoreWithServerAsync(string id);
Task<string> CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams);
Task CopyTotpCodeIfNeededAsync(CipherView cipher);
}
}

View File

@ -1,9 +0,0 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IFido2AuthenticationService
{
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams);
}
}

View File

@ -0,0 +1,14 @@
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions
{
public interface IFido2MediatorService
{
Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams);
Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams);
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface);
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface);
Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId);
}
}

View File

@ -34,6 +34,8 @@ namespace Bit.Core.Services
private readonly II18nService _i18nService;
private readonly Func<ISearchService> _searchService;
private readonly IConfigService _configService;
private readonly ITotpService _totpService;
private readonly IClipboardService _clipboardService;
private readonly string _clearCipherCacheKey;
private readonly string[] _allClearCipherCacheKeys;
private Dictionary<string, HashSet<string>> _domainMatchBlacklist = new Dictionary<string, HashSet<string>>
@ -53,6 +55,8 @@ namespace Bit.Core.Services
II18nService i18nService,
Func<ISearchService> searchService,
IConfigService configService,
ITotpService totpService,
IClipboardService clipboardService,
string clearCipherCacheKey,
string[] allClearCipherCacheKeys)
{
@ -65,6 +69,8 @@ namespace Bit.Core.Services
_i18nService = i18nService;
_searchService = searchService;
_configService = configService;
_totpService = totpService;
_clipboardService = clipboardService;
_clearCipherCacheKey = clearCipherCacheKey;
_allClearCipherCacheKeys = allClearCipherCacheKeys;
}
@ -1315,6 +1321,22 @@ namespace Bit.Core.Services
return encryptedCipher.Id;
}
public async Task CopyTotpCodeIfNeededAsync(CipherView cipher)
{
if (string.IsNullOrWhiteSpace(cipher?.Login?.Totp)
||
await _stateService.GetDisableAutoTotpCopyAsync() == true)
{
return;
}
if (cipher.OrganizationUseTotp || await _stateService.CanAccessPremiumAsync())
{
var totpCode = await _totpService.GetCodeAsync(cipher.Login.Totp);
await _clipboardService.CopyTextAsync(totpCode);
}
}
private class CipherLocaleComparer : IComparer<CipherView>
{
private readonly II18nService _i18nService;

View File

@ -1,18 +0,0 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Services
{
public class Fido2AuthenticationService : IFido2AuthenticationService
{
public Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams)
{
// TODO: IMPLEMENT this
return Task.FromResult(new Fido2AuthenticatorGetAssertionResult
{
AuthenticatorData = new byte[32],
Signature = new byte[8]
});
}
}
}

View File

@ -198,7 +198,8 @@ namespace Bit.Core.Services
SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential
{
Id = selectedCredentialId.GuidToRawFormat(),
UserHandle = selectedFido2Credential.UserHandleValue
UserHandle = selectedFido2Credential.UserHandleValue,
Cipher = selectedCipher
},
AuthenticatorData = authenticatorData,
Signature = signature

View File

@ -206,7 +206,8 @@ namespace Bit.Core.Services
Id = CoreHelpers.Base64UrlEncode(getAssertionResult.SelectedCredential.Id),
RawId = getAssertionResult.SelectedCredential.Id,
Signature = getAssertionResult.Signature,
UserHandle = getAssertionResult.SelectedCredential.UserHandle
UserHandle = getAssertionResult.SelectedCredential.UserHandle,
Cipher = getAssertionResult.SelectedCredential.Cipher
};
}
catch (InvalidStateError)

View File

@ -0,0 +1,60 @@
using Bit.Core.Abstractions;
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Services
{
public class Fido2MediatorService : IFido2MediatorService
{
private readonly IFido2AuthenticatorService _fido2AuthenticatorService;
private readonly IFido2ClientService _fido2ClientService;
private readonly ICipherService _cipherService;
public Fido2MediatorService(IFido2AuthenticatorService fido2AuthenticatorService,
IFido2ClientService fido2ClientService,
ICipherService cipherService)
{
_fido2AuthenticatorService = fido2AuthenticatorService;
_fido2ClientService = fido2ClientService;
_cipherService = cipherService;
}
public async Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams)
{
var result = await _fido2ClientService.AssertCredentialAsync(assertCredentialParams);
if (result?.Cipher != null)
{
await _cipherService.CopyTotpCodeIfNeededAsync(result.Cipher);
}
return result;
}
public Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams)
{
return _fido2ClientService.CreateCredentialAsync(createCredentialParams);
}
public async Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface)
{
var result = await _fido2AuthenticatorService.GetAssertionAsync(assertionParams, userInterface);
if (result?.SelectedCredential?.Cipher != null)
{
await _cipherService.CopyTotpCodeIfNeededAsync(result.SelectedCredential.Cipher);
}
return result;
}
public Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface)
{
return _fido2AuthenticatorService.MakeCredentialAsync(makeCredentialParams, userInterface);
}
public Task<Fido2AuthenticatorDiscoverableCredentialMetadata[]> SilentCredentialDiscoveryAsync(string rpId)
{
return _fido2AuthenticatorService.SilentCredentialDiscoveryAsync(rpId);
}
}
}

View File

@ -1,4 +1,6 @@
namespace Bit.Core.Utilities.Fido2
using Bit.Core.Models.View;
namespace Bit.Core.Utilities.Fido2
{
public class Fido2AuthenticatorGetAssertionResult
{
@ -14,6 +16,8 @@
#nullable enable
public byte[]? UserHandle { get; set; }
public CipherView? Cipher { get; set; }
}
}

View File

@ -1,3 +1,5 @@
using Bit.Core.Models.View;
namespace Bit.Core.Utilities.Fido2
{
/// <summary>
@ -38,5 +40,10 @@ namespace Bit.Core.Utilities.Fido2
/// return a user handle.
/// </summary>
public byte[]? UserHandle { get; set; }
/// <summary>
/// The selected cipher login item that has the credential
/// </summary>
public CipherView? Cipher { get; set; }
}
}

View File

@ -27,6 +27,7 @@ namespace Bit.Core.Utilities
var messagingService = Resolve<IMessagingService>("messagingService");
var cryptoFunctionService = Resolve<ICryptoFunctionService>("cryptoFunctionService");
var cryptoService = Resolve<ICryptoService>("cryptoService");
var clipboardService = Resolve<IClipboardService>();
var logger = Resolve<ILogger>();
SearchService searchService = null;
@ -43,8 +44,9 @@ namespace Bit.Core.Utilities
var settingsService = new SettingsService(stateService);
var fileUploadService = new FileUploadService(apiService);
var configService = new ConfigService(apiService, stateService, logger);
var totpService = new TotpService(cryptoFunctionService);
var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService,
fileUploadService, storageService, i18nService, () => searchService, configService, clearCipherCacheKey,
fileUploadService, storageService, i18nService, () => searchService, configService, totpService, clipboardService, clearCipherCacheKey,
allClearCipherCacheKeys);
var folderService = new FolderService(cryptoService, stateService, apiService, i18nService, cipherService);
var collectionService = new CollectionService(cryptoService, stateService, i18nService);
@ -76,7 +78,6 @@ namespace Bit.Core.Utilities
return Task.CompletedTask;
});
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
var totpService = new TotpService(cryptoFunctionService);
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
var passwordResetEnrollmentService = new PasswordResetEnrollmentService(apiService, cryptoService, organizationService, stateService);
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
@ -115,7 +116,6 @@ namespace Bit.Core.Utilities
Register<IUsernameGenerationService>(usernameGenerationService);
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
Register<IPasswordResetEnrollmentService>(passwordResetEnrollmentService);
Register<IFido2AuthenticationService>(new Fido2AuthenticationService());
}
public static void Register<T>(string serviceName, T obj)

View File

@ -20,7 +20,7 @@ namespace Bit.iOS.Autofill
{
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost
{
private readonly LazyResolve<IFido2AuthenticatorService> _fido2AuthService = new LazyResolve<IFido2AuthenticatorService>();
private readonly LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
private readonly LazyResolve<IPlatformUtilsService> _platformUtilsService = new LazyResolve<IPlatformUtilsService>();
private readonly LazyResolve<IUserVerificationMediatorService> _userVerificationMediatorService = new LazyResolve<IUserVerificationMediatorService>();
private readonly LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
@ -96,7 +96,7 @@ namespace Bit.iOS.Autofill
try
{
var result = await _fido2AuthService.Value.MakeCredentialAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorMakeCredentialParams
var result = await _fido2MediatorService.Value.MakeCredentialAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorMakeCredentialParams
{
Hash = passkeyRegistrationRequest.ClientDataHash.ToArray(),
CredTypesAndPubKeyAlgs = GetCredTypesAndPubKeyAlgs(passkeyRegistrationRequest.SupportedAlgorithms),
@ -202,7 +202,7 @@ namespace Bit.iOS.Autofill
try
{
var fido2AssertionResult = await _fido2AuthService.Value.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
var fido2AssertionResult = await _fido2MediatorService.Value.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
{
RpId = rpId,
Hash = _context.PasskeyCredentialRequest.ClientDataHash.ToArray(),

View File

@ -46,7 +46,7 @@ namespace Bit.iOS.Autofill
LazyResolve<IPlatformUtilsService> _platformUtilsService = new LazyResolve<IPlatformUtilsService>();
LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
LazyResolve<IUserVerificationMediatorService> _userVerificationMediatorService = new LazyResolve<IUserVerificationMediatorService>();
LazyResolve<IFido2AuthenticatorService> _fido2AuthenticatorService = new LazyResolve<IFido2AuthenticatorService>();
LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
bool _alreadyLoadItemsOnce = false;
bool _isLoading;
@ -177,7 +177,7 @@ namespace Bit.iOS.Autofill
try
{
var fido2AssertionResult = await _fido2AuthenticatorService.Value.GetAssertionAsync(new Fido2AuthenticatorGetAssertionParams
var fido2AssertionResult = await _fido2MediatorService.Value.GetAssertionAsync(new Fido2AuthenticatorGetAssertionParams
{
RpId = Context.PasskeyCredentialRequestParameters.RelyingPartyIdentifier,
Hash = Context.PasskeyCredentialRequestParameters.ClientDataHash.ToArray(),

View File

@ -203,11 +203,17 @@ namespace Bit.iOS.Core.Utilities
ServiceContainer.Resolve<IUserVerificationService>());
ServiceContainer.Register<IUserVerificationMediatorService>(userVerificationMediatorService);
ServiceContainer.Register<IFido2AuthenticatorService>(new Fido2AuthenticatorService(
var fido2AuthenticatorService = new Fido2AuthenticatorService(
ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<ISyncService>(),
ServiceContainer.Resolve<ICryptoFunctionService>(),
userVerificationMediatorService));
userVerificationMediatorService);
ServiceContainer.Register<IFido2AuthenticatorService>(fido2AuthenticatorService);
ServiceContainer.Register<IFido2MediatorService>(new Fido2MediatorService(
fido2AuthenticatorService,
null, // iOS doesn't use IFido2ClientService so no need to have it in memory
ServiceContainer.Resolve<ICipherService>()));
ServiceContainer.Register<IWatchDeviceService>(new WatchDeviceService(ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IEnvironmentService>(),