diff --git a/src/Core/Abstractions/ICipherService.cs b/src/Core/Abstractions/ICipherService.cs index 19bf0c441..e769c051a 100644 --- a/src/Core/Abstractions/ICipherService.cs +++ b/src/Core/Abstractions/ICipherService.cs @@ -35,5 +35,6 @@ namespace Bit.Core.Abstractions Task SoftDeleteWithServerAsync(string id); Task RestoreWithServerAsync(string id); Task CreateNewLoginForPasskeyAsync(Fido2ConfirmNewCredentialParams newPasskeyParams); + Task CopyTotpCodeIfNeededAsync(CipherView cipher); } } diff --git a/src/Core/Abstractions/IFido2AuthenticationService.cs b/src/Core/Abstractions/IFido2AuthenticationService.cs deleted file mode 100644 index cf86ff79d..000000000 --- a/src/Core/Abstractions/IFido2AuthenticationService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Bit.Core.Utilities.Fido2; - -namespace Bit.Core.Abstractions -{ - public interface IFido2AuthenticationService - { - Task GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams); - } -} diff --git a/src/Core/Abstractions/IFido2MediatorService.cs b/src/Core/Abstractions/IFido2MediatorService.cs new file mode 100644 index 000000000..46071b6f3 --- /dev/null +++ b/src/Core/Abstractions/IFido2MediatorService.cs @@ -0,0 +1,14 @@ +using Bit.Core.Utilities.Fido2; + +namespace Bit.Core.Abstractions +{ + public interface IFido2MediatorService + { + Task CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams); + Task AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams); + + Task MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface); + Task GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface); + Task SilentCredentialDiscoveryAsync(string rpId); + } +} diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index 07b3033af..65a021fbb 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -34,6 +34,8 @@ namespace Bit.Core.Services private readonly II18nService _i18nService; private readonly Func _searchService; private readonly IConfigService _configService; + private readonly ITotpService _totpService; + private readonly IClipboardService _clipboardService; private readonly string _clearCipherCacheKey; private readonly string[] _allClearCipherCacheKeys; private Dictionary> _domainMatchBlacklist = new Dictionary> @@ -53,6 +55,8 @@ namespace Bit.Core.Services II18nService i18nService, Func 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 { private readonly II18nService _i18nService; diff --git a/src/Core/Services/Fido2AuthenticationService.cs b/src/Core/Services/Fido2AuthenticationService.cs deleted file mode 100644 index 2714320aa..000000000 --- a/src/Core/Services/Fido2AuthenticationService.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Bit.Core.Abstractions; -using Bit.Core.Utilities.Fido2; - -namespace Bit.Core.Services -{ - public class Fido2AuthenticationService : IFido2AuthenticationService - { - public Task GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams) - { - // TODO: IMPLEMENT this - return Task.FromResult(new Fido2AuthenticatorGetAssertionResult - { - AuthenticatorData = new byte[32], - Signature = new byte[8] - }); - } - } -} diff --git a/src/Core/Services/Fido2AuthenticatorService.cs b/src/Core/Services/Fido2AuthenticatorService.cs index 3992911cc..00b42e8cc 100644 --- a/src/Core/Services/Fido2AuthenticatorService.cs +++ b/src/Core/Services/Fido2AuthenticatorService.cs @@ -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 diff --git a/src/Core/Services/Fido2ClientService.cs b/src/Core/Services/Fido2ClientService.cs index c3281e30f..bf454bc73 100644 --- a/src/Core/Services/Fido2ClientService.cs +++ b/src/Core/Services/Fido2ClientService.cs @@ -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) diff --git a/src/Core/Services/Fido2MediatorService.cs b/src/Core/Services/Fido2MediatorService.cs new file mode 100644 index 000000000..12917a8c9 --- /dev/null +++ b/src/Core/Services/Fido2MediatorService.cs @@ -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 AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams) + { + var result = await _fido2ClientService.AssertCredentialAsync(assertCredentialParams); + + if (result?.Cipher != null) + { + await _cipherService.CopyTotpCodeIfNeededAsync(result.Cipher); + } + + return result; + } + + public Task CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams) + { + return _fido2ClientService.CreateCredentialAsync(createCredentialParams); + } + + public async Task 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 MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams, IFido2MakeCredentialUserInterface userInterface) + { + return _fido2AuthenticatorService.MakeCredentialAsync(makeCredentialParams, userInterface); + } + + public Task SilentCredentialDiscoveryAsync(string rpId) + { + return _fido2AuthenticatorService.SilentCredentialDiscoveryAsync(rpId); + } + } +} diff --git a/src/Core/Utilities/Fido2/Fido2AuthenticatorGetAssertionResult.cs b/src/Core/Utilities/Fido2/Fido2AuthenticatorGetAssertionResult.cs index 70331029b..ff23d0668 100644 --- a/src/Core/Utilities/Fido2/Fido2AuthenticatorGetAssertionResult.cs +++ b/src/Core/Utilities/Fido2/Fido2AuthenticatorGetAssertionResult.cs @@ -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; } } } diff --git a/src/Core/Utilities/Fido2/Fido2ClientAssertCredentialResult.cs b/src/Core/Utilities/Fido2/Fido2ClientAssertCredentialResult.cs index a80a76d95..48afc9681 100644 --- a/src/Core/Utilities/Fido2/Fido2ClientAssertCredentialResult.cs +++ b/src/Core/Utilities/Fido2/Fido2ClientAssertCredentialResult.cs @@ -1,3 +1,5 @@ +using Bit.Core.Models.View; + namespace Bit.Core.Utilities.Fido2 { /// @@ -38,5 +40,10 @@ namespace Bit.Core.Utilities.Fido2 /// return a user handle. /// public byte[]? UserHandle { get; set; } + + /// + /// The selected cipher login item that has the credential + /// + public CipherView? Cipher { get; set; } } } diff --git a/src/Core/Utilities/ServiceContainer.cs b/src/Core/Utilities/ServiceContainer.cs index 01b20cebc..7c525416e 100644 --- a/src/Core/Utilities/ServiceContainer.cs +++ b/src/Core/Utilities/ServiceContainer.cs @@ -27,6 +27,7 @@ namespace Bit.Core.Utilities var messagingService = Resolve("messagingService"); var cryptoFunctionService = Resolve("cryptoFunctionService"); var cryptoService = Resolve("cryptoService"); + var clipboardService = Resolve(); var logger = Resolve(); 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(usernameGenerationService); Register(deviceTrustCryptoService); Register(passwordResetEnrollmentService); - Register(new Fido2AuthenticationService()); } public static void Register(string serviceName, T obj) diff --git a/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs b/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs index 1b8fdf090..ecbe0a50b 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs @@ -20,7 +20,7 @@ namespace Bit.iOS.Autofill { public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost { - private readonly LazyResolve _fido2AuthService = new LazyResolve(); + private readonly LazyResolve _fido2MediatorService = new LazyResolve(); private readonly LazyResolve _platformUtilsService = new LazyResolve(); private readonly LazyResolve _userVerificationMediatorService = new LazyResolve(); private readonly LazyResolve _cipherService = new LazyResolve(); @@ -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(), diff --git a/src/iOS.Autofill/LoginListViewController.cs b/src/iOS.Autofill/LoginListViewController.cs index fbda1d7fd..cb69902a0 100644 --- a/src/iOS.Autofill/LoginListViewController.cs +++ b/src/iOS.Autofill/LoginListViewController.cs @@ -46,7 +46,7 @@ namespace Bit.iOS.Autofill LazyResolve _platformUtilsService = new LazyResolve(); LazyResolve _logger = new LazyResolve(); LazyResolve _userVerificationMediatorService = new LazyResolve(); - LazyResolve _fido2AuthenticatorService = new LazyResolve(); + LazyResolve _fido2MediatorService = new LazyResolve(); 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(), diff --git a/src/iOS.Core/Utilities/iOSCoreHelpers.cs b/src/iOS.Core/Utilities/iOSCoreHelpers.cs index dd04a81ce..47ec9fc4a 100644 --- a/src/iOS.Core/Utilities/iOSCoreHelpers.cs +++ b/src/iOS.Core/Utilities/iOSCoreHelpers.cs @@ -203,11 +203,17 @@ namespace Bit.iOS.Core.Utilities ServiceContainer.Resolve()); ServiceContainer.Register(userVerificationMediatorService); - ServiceContainer.Register(new Fido2AuthenticatorService( + var fido2AuthenticatorService = new Fido2AuthenticatorService( ServiceContainer.Resolve(), ServiceContainer.Resolve(), ServiceContainer.Resolve(), - userVerificationMediatorService)); + userVerificationMediatorService); + ServiceContainer.Register(fido2AuthenticatorService); + + ServiceContainer.Register(new Fido2MediatorService( + fido2AuthenticatorService, + null, // iOS doesn't use IFido2ClientService so no need to have it in memory + ServiceContainer.Resolve())); ServiceContainer.Register(new WatchDeviceService(ServiceContainer.Resolve(), ServiceContainer.Resolve(),