From d0e0f0ecdbc2e068d51dab0311065f3ce606de5a Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 22 Jan 2024 13:26:19 +0100 Subject: [PATCH] [PM-5731] feat: add support for counter --- src/Core/Models/View/Fido2CredentialView.cs | 5 ++ .../Services/Fido2AuthenticatorService.cs | 30 ++++--- .../Fido2/Fido2AuthenticatorException.cs | 7 ++ .../Services/Fido2AuthenticatorTests.cs | 82 +++++++++++++++++++ 4 files changed, 114 insertions(+), 10 deletions(-) diff --git a/src/Core/Models/View/Fido2CredentialView.cs b/src/Core/Models/View/Fido2CredentialView.cs index 5086c72d3..acb5dec4e 100644 --- a/src/Core/Models/View/Fido2CredentialView.cs +++ b/src/Core/Models/View/Fido2CredentialView.cs @@ -27,6 +27,11 @@ namespace Bit.Core.Models.View public string Counter { get; set; } public DateTime CreationDate { get; set; } + public int CounterValue { + get => int.TryParse(Counter, out var counter) ? counter : 0; + set => Counter = value.ToString(); + } + public override string SubTitle => UserName; public override List> LinkedFieldOptions => new List>(); public bool IsDiscoverable => bool.TryParse(Discoverable, out var isDiscoverable) && isDiscoverable; diff --git a/src/Core/Services/Fido2AuthenticatorService.cs b/src/Core/Services/Fido2AuthenticatorService.cs index 74700ad4d..685bc176c 100644 --- a/src/Core/Services/Fido2AuthenticatorService.cs +++ b/src/Core/Services/Fido2AuthenticatorService.cs @@ -68,17 +68,27 @@ namespace Bit.Core.Services throw new NotAllowedError(); } - - // if ( - // !userVerified && - // (params.requireUserVerification || selectedCipher.reprompt !== CipherRepromptType.None) - // ) { - // this.logService?.warning( - // `[Fido2Authenticator] Aborting because user verification was unsuccessful.`, - // ); - // throw new Fido2AuthenticatorError(Fido2AuthenticatorErrorCode.NotAllowed); - // } + try { + var selectedFido2Credential = selectedCipher.Login.MainFido2Credential; + var selectedCredentialId = selectedFido2Credential.CredentialId; + + if (selectedFido2Credential.CounterValue != 0) { + ++selectedFido2Credential.CounterValue; + } + + var encrypted = await _cipherService.EncryptAsync(selectedCipher); + await _cipherService.SaveWithServerAsync(encrypted); + + + } catch { + _logService.Info( + "[Fido2Authenticator] Aborting because no matching credentials were found in the vault." + ); + + throw new UnknownError(); + } + // TODO: IMPLEMENT this return new Fido2AuthenticatorGetAssertionResult { diff --git a/src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs b/src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs index f47625f3d..361933ef4 100644 --- a/src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs +++ b/src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs @@ -13,4 +13,11 @@ namespace Bit.Core.Utilities.Fido2 { } } + + public class UnknownError : Fido2AuthenticatorException + { + public UnknownError() : base("UnknownError") + { + } + } } diff --git a/test/Core.Test/Services/Fido2AuthenticatorTests.cs b/test/Core.Test/Services/Fido2AuthenticatorTests.cs index 1525aa7f3..1afafbf16 100644 --- a/test/Core.Test/Services/Fido2AuthenticatorTests.cs +++ b/test/Core.Test/Services/Fido2AuthenticatorTests.cs @@ -230,6 +230,88 @@ namespace Bit.Core.Test.Services #endregion + #region assertion of credential + + [Theory] + [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + // Spec: Increment the credential associated signature counter + public async Task GetAssertionAsync_IncrementsCounter_CounterIsLargerThanZero(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams, Cipher encryptedCipher) { + // Common Arrange + var cipherView = CreateCipherView(null, "bitwarden.com", true); + aParams.RpId = cipherView.Login.MainFido2Credential.RpId; + aParams.AllowCredentialDescriptorList = null; + sutProvider.GetDependency().GetAllDecryptedAsync().Returns(new List { cipherView }); + sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + CipherId = cipherView.Id, + UserVerified = true + }); + + // Arrange + cipherView.Login.MainFido2Credential.CounterValue = 9000; + sutProvider.GetDependency().EncryptAsync(cipherView).Returns(encryptedCipher); + + // Act + await sutProvider.Sut.GetAssertionAsync(aParams); + + // Assert + await sutProvider.GetDependency().Received().SaveWithServerAsync(encryptedCipher); + await sutProvider.GetDependency().Received().EncryptAsync(Arg.Is( + (cipher) => cipher.Login.MainFido2Credential.CounterValue == 9001 + )); + } + + [Theory] + [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + // Spec: Increment the credential associated signature counter + public async Task GetAssertionAsync_DoesNotIncrementsCounter_CounterIsZero(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams, Cipher encryptedCipher) { + // Common Arrange + var cipherView = CreateCipherView(null, "bitwarden.com", true); + aParams.RpId = cipherView.Login.MainFido2Credential.RpId; + aParams.AllowCredentialDescriptorList = null; + sutProvider.GetDependency().GetAllDecryptedAsync().Returns(new List { cipherView }); + sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + CipherId = cipherView.Id, + UserVerified = true + }); + + // Arrange + cipherView.Login.MainFido2Credential.CounterValue = 0; + sutProvider.GetDependency().EncryptAsync(cipherView).Returns(encryptedCipher); + + // Act + await sutProvider.Sut.GetAssertionAsync(aParams); + + // Assert + await sutProvider.GetDependency().Received().SaveWithServerAsync(encryptedCipher); + await sutProvider.GetDependency().Received().EncryptAsync(Arg.Is( + (cipher) => cipher.Login.MainFido2Credential.CounterValue == 0 + )); + } + + [Theory] + [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + // Spec: Increment the credential associated signature counter + public async Task GetAssertionAsync_ThrowsUnknownError_SaveFails(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams, Cipher encryptedCipher) { + // Common Arrange + var cipherView = CreateCipherView(null, "bitwarden.com", true); + aParams.RpId = cipherView.Login.MainFido2Credential.RpId; + aParams.AllowCredentialDescriptorList = null; + sutProvider.GetDependency().GetAllDecryptedAsync().Returns(new List { cipherView }); + sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + CipherId = cipherView.Id, + UserVerified = true + }); + + // Arrange + cipherView.Login.MainFido2Credential.CounterValue = 0; + sutProvider.GetDependency().SaveWithServerAsync(Arg.Any()).Throws(new Exception()); + + // Act & Assert + await Assert.ThrowsAsync(() => sutProvider.Sut.GetAssertionAsync(aParams)); + } + + #endregion + private byte[] RandomBytes(int length) { var bytes = new byte[length];