diff --git a/test/Core.Test/Services/Fido2AuthenticatorGetAssertionTests.cs b/test/Core.Test/Services/Fido2AuthenticatorGetAssertionTests.cs index 2f6cde821..26ccdefc6 100644 --- a/test/Core.Test/Services/Fido2AuthenticatorGetAssertionTests.cs +++ b/test/Core.Test/Services/Fido2AuthenticatorGetAssertionTests.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using Bit.Core.Abstractions; -using Bit.Core.Exceptions; using Bit.Core.Services; using Bit.Core.Models.Domain; using Bit.Core.Models.View; @@ -19,213 +18,196 @@ using System.Security.Cryptography; namespace Bit.Core.Test.Services { - public class Fido2AuthenticatorGetAssertionTests + public class Fido2AuthenticatorGetAssertionTests : IDisposable { - #region missing non-discoverable credential + private readonly string _rpId = "bitwarden.com"; + private readonly SutProvider _sutProvider = new SutProvider().Create(); - // Spec: If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation. - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] - public async Task GetAssertionAsync_ThrowsNotAllowed_NoCredentialExists(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) + private List _credentialIds; + private List _ciphers; + private Fido2AuthenticatorGetAssertionParams _params; + private CipherView _selectedCipher; + + /// + /// Sets up a working environment for the tests. + /// + public Fido2AuthenticatorGetAssertionTests() { - await Assert.ThrowsAsync(() => sutProvider.Sut.GetAssertionAsync(aParams)); + _credentialIds = [ Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() ]; + _ciphers = [ + CreateCipherView(_credentialIds[0].ToString(), _rpId, false), + CreateCipherView(_credentialIds[1].ToString(), _rpId, true), + ]; + _selectedCipher = _ciphers[0]; + _params = CreateParams( + rpId: _rpId, + allowCredentialDescriptorList: [ + new PublicKeyCredentialDescriptor { + Id = _credentialIds[0].ToByteArray(), + Type = "public-key" + }, + new PublicKeyCredentialDescriptor { + Id = _credentialIds[1].ToByteArray(), + Type = "public-key" + }, + ], + requireUserVerification: false + ); + _sutProvider.GetDependency().GetAllDecryptedAsync().Returns(_ciphers); + _sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + CipherId = _ciphers[0].Id, + UserVerified = false + }); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] - public async Task GetAssertionAsync_ThrowsNotAllowed_CredentialExistsButRpIdDoesNotMatch(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) + public void Dispose() { - var credentialId = Guid.NewGuid(); - aParams.RpId = "bitwarden.com"; - aParams.AllowCredentialDescriptorList = [ - new PublicKeyCredentialDescriptor { - Id = credentialId.ToByteArray(), - Type = "public-key" - } - ]; - sutProvider.GetDependency().GetAllDecryptedAsync().Returns([ - CreateCipherView(credentialId.ToString(), "mismatch-rpid", false), - ]); + } - await Assert.ThrowsAsync(() => sutProvider.Sut.GetAssertionAsync(aParams)); + #region missing non-discoverable credential + + [Fact] + // Spec: If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation. + public async Task GetAssertionAsync_ThrowsNotAllowed_NoCredentialsExists() + { + // Arrange + _ciphers.Clear(); + + // Act & Assert + await Assert.ThrowsAsync(() => _sutProvider.Sut.GetAssertionAsync(_params)); + } + + [Fact] + public async Task GetAssertionAsync_ThrowsNotAllowed_CredentialExistsButRpIdDoesNotMatch() + { + // Arrange + _params.RpId = "mismatch-rpid"; + + // Act & Assert + await Assert.ThrowsAsync(() => _sutProvider.Sut.GetAssertionAsync(_params)); } #endregion #region vault contains credential - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] - public async Task GetAssertionAsync_AsksForAllCredentials_ParamsContainsAllowedCredentialsList(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) + [Fact] + public async Task GetAssertionAsync_AsksForAllCredentials_ParamsContainsAllowedCredentialsList() { - var credentialIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; - List ciphers = [ - CreateCipherView(credentialIds[0].ToString(), "bitwarden.com", false), - CreateCipherView(credentialIds[1].ToString(), "bitwarden.com", true) + // Arrange + _params.AllowCredentialDescriptorList = [ + new PublicKeyCredentialDescriptor { + Id = _credentialIds[0].ToByteArray(), + Type = "public-key" + }, + new PublicKeyCredentialDescriptor { + Id = _credentialIds[1].ToByteArray(), + Type = "public-key" + }, ]; - aParams.RpId = "bitwarden.com"; - aParams.RequireUserVerification = false; - aParams.AllowCredentialDescriptorList = credentialIds.Select((credentialId) => new PublicKeyCredentialDescriptor { - Id = credentialId.ToByteArray(), - Type = "public-key" - }).ToArray(); - sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); - sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { - CipherId = ciphers[0].Id, - UserVerified = false - }); - await sutProvider.Sut.GetAssertionAsync(aParams); + // Act + await _sutProvider.Sut.GetAssertionAsync(_params); - await sutProvider.GetDependency().Received().PickCredentialAsync(Arg.Is( - (pickCredentialParams) => pickCredentialParams.CipherIds.SequenceEqual(ciphers.Select((cipher) => cipher.Id)) + // Assert + await _sutProvider.GetDependency().Received().PickCredentialAsync(Arg.Is( + (pickCredentialParams) => pickCredentialParams.CipherIds.SequenceEqual(_ciphers.Select((cipher) => cipher.Id)) )); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] - public async Task GetAssertionAsync_AsksForDiscoverableCredentials_ParamsDoesNotContainAllowedCredentialsList(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) + [Fact] + public async Task GetAssertionAsync_AsksForDiscoverableCredentials_ParamsDoesNotContainAllowedCredentialsList() { - var credentialIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; - List ciphers = [ - CreateCipherView(credentialIds[0].ToString(), "bitwarden.com", false), - CreateCipherView(credentialIds[1].ToString(), "bitwarden.com", true) - ]; - var discoverableCiphers = ciphers.Where((cipher) => cipher.Login.MainFido2Credential.DiscoverableValue).ToList(); - aParams.RpId = "bitwarden.com"; - aParams.RequireUserVerification = false; - aParams.AllowCredentialDescriptorList = null; - sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); - sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + // Arrange + _params.AllowCredentialDescriptorList = null; + var discoverableCiphers = _ciphers.Where((cipher) => cipher.Login.MainFido2Credential.DiscoverableValue).ToList(); + _sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { CipherId = discoverableCiphers[0].Id, UserVerified = false }); - await sutProvider.Sut.GetAssertionAsync(aParams); + // Act + await _sutProvider.Sut.GetAssertionAsync(_params); - await sutProvider.GetDependency().Received().PickCredentialAsync(Arg.Is( + // Assert + await _sutProvider.GetDependency().Received().PickCredentialAsync(Arg.Is( (pickCredentialParams) => pickCredentialParams.CipherIds.SequenceEqual(discoverableCiphers.Select((cipher) => cipher.Id)) )); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + [Fact] // Spec: Prompt the user to select a public key credential source `selectedCredential` from `credentialOptions`. // If requireUserVerification is true, the authorization gesture MUST include user verification. - public async Task GetAssertionAsync_RequestsUserVerification_ParamsRequireUserVerification(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) { - var credentialIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; - List ciphers = [ - CreateCipherView(credentialIds[0].ToString(), "bitwarden.com", false), - CreateCipherView(credentialIds[1].ToString(), "bitwarden.com", true) - ]; - var discoverableCiphers = ciphers.Where((cipher) => cipher.Login.MainFido2Credential.DiscoverableValue).ToList(); - aParams.RpId = "bitwarden.com"; - aParams.AllowCredentialDescriptorList = null; - aParams.RequireUserVerification = true; - sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); - sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { - CipherId = discoverableCiphers[0].Id, - UserVerified = true - }); + public async Task GetAssertionAsync_RequestsUserVerification_ParamsRequireUserVerification() { + // Arrange + _params.RequireUserVerification = true; - await sutProvider.Sut.GetAssertionAsync(aParams); + // Act + await _sutProvider.Sut.GetAssertionAsync(_params); - await sutProvider.GetDependency().Received().PickCredentialAsync(Arg.Is( + // Assert + await _sutProvider.GetDependency().Received().PickCredentialAsync(Arg.Is( (pickCredentialParams) => pickCredentialParams.UserVerification == true )); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + [Fact] // Spec: Prompt the user to select a public key credential source `selectedCredential` from `credentialOptions`. // If `requireUserPresence` is true, the authorization gesture MUST include a test of user presence. - // Comment: User presence in implied by the UI returning a credential. - public async Task GetAssertionAsync_DoesNotRequestUserVerification_ParamsDoNotRequireUserVerification(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) { - var credentialIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; - List ciphers = [ - CreateCipherView(credentialIds[0].ToString(), "bitwarden.com", false), - CreateCipherView(credentialIds[1].ToString(), "bitwarden.com", true) - ]; - var discoverableCiphers = ciphers.Where((cipher) => cipher.Login.MainFido2Credential.DiscoverableValue).ToList(); - aParams.RpId = "bitwarden.com"; - aParams.AllowCredentialDescriptorList = null; - aParams.RequireUserVerification = false; - sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); - sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { - CipherId = discoverableCiphers[0].Id, - UserVerified = false - }); + // Comment: User presence is implied by the UI returning a credential. + public async Task GetAssertionAsync_DoesNotRequestUserVerification_ParamsDoNotRequireUserVerification() { + // Arrange + _params.RequireUserVerification = false; - await sutProvider.Sut.GetAssertionAsync(aParams); + // Act + await _sutProvider.Sut.GetAssertionAsync(_params); - await sutProvider.GetDependency().Received().PickCredentialAsync(Arg.Is( + // Assert + await _sutProvider.GetDependency().Received().PickCredentialAsync(Arg.Is( (pickCredentialParams) => pickCredentialParams.UserVerification == false )); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + [Fact] // Spec: If the user does not consent, return an error code equivalent to "NotAllowedError" and terminate the operation. - public async Task GetAssertionAsync_ThrowsNotAllowed_UserDoesNotConsent(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) { - var credentialIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; - List ciphers = [ - CreateCipherView(credentialIds[0].ToString(), "bitwarden.com", false), - CreateCipherView(credentialIds[1].ToString(), "bitwarden.com", true) - ]; - var discoverableCiphers = ciphers.Where((cipher) => cipher.Login.MainFido2Credential.DiscoverableValue).ToList(); - aParams.RpId = "bitwarden.com"; - aParams.AllowCredentialDescriptorList = null; - sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); - sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + public async Task GetAssertionAsync_ThrowsNotAllowed_UserDoesNotConsent() { + // Arrange + _sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { CipherId = null, UserVerified = false }); - await Assert.ThrowsAsync(() => sutProvider.Sut.GetAssertionAsync(aParams)); + // Act & Assert + await Assert.ThrowsAsync(() => _sutProvider.Sut.GetAssertionAsync(_params)); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + [Fact] // Spec: If the user does not consent, return an error code equivalent to "NotAllowedError" and terminate the operation. - public async Task GetAssertionAsync_ThrowsNotAllowed_NoUserVerificationWhenRequired(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) { - var credentialIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; - List ciphers = [ - CreateCipherView(credentialIds[0].ToString(), "bitwarden.com", false), - CreateCipherView(credentialIds[1].ToString(), "bitwarden.com", true) - ]; - var discoverableCiphers = ciphers.Where((cipher) => cipher.Login.MainFido2Credential.DiscoverableValue).ToList(); - aParams.RequireUserVerification = true; - aParams.RpId = "bitwarden.com"; - aParams.AllowCredentialDescriptorList = null; - sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); - sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { - CipherId = discoverableCiphers[0].Id, + public async Task GetAssertionAsync_ThrowsNotAllowed_NoUserVerificationWhenRequired() { + // Arrange + _params.RequireUserVerification = true; + _sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + CipherId = _selectedCipher.Id, UserVerified = false }); - await Assert.ThrowsAsync(() => sutProvider.Sut.GetAssertionAsync(aParams)); + // Act and assert + await Assert.ThrowsAsync(() => _sutProvider.Sut.GetAssertionAsync(_params)); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + [Fact] // Spec: If the user does not consent, return an error code equivalent to "NotAllowedError" and terminate the operation. - public async Task GetAssertionAsync_ThrowsNotAllowed_NoUserVerificationForCipherWithReprompt(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) { - var credentialIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; - List ciphers = [ - CreateCipherView(credentialIds[0].ToString(), "bitwarden.com", false), - CreateCipherView(credentialIds[1].ToString(), "bitwarden.com", true) - ]; - ciphers[0].Reprompt = CipherRepromptType.Password; - var discoverableCiphers = ciphers.Where((cipher) => cipher.Login.MainFido2Credential.DiscoverableValue).ToList(); - aParams.RpId = "bitwarden.com"; - aParams.AllowCredentialDescriptorList = null; - sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); - sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { - CipherId = discoverableCiphers[0].Id, + public async Task GetAssertionAsync_ThrowsNotAllowed_NoUserVerificationForCipherWithReprompt() { + // Arrange + _selectedCipher.Reprompt = CipherRepromptType.Password; + _params.RequireUserVerification = false; + _sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + CipherId = _selectedCipher.Id, UserVerified = false }); - await Assert.ThrowsAsync(() => sutProvider.Sut.GetAssertionAsync(aParams)); + // Act & Assert + await Assert.ThrowsAsync(() => _sutProvider.Sut.GetAssertionAsync(_params)); } #endregion @@ -235,27 +217,17 @@ namespace Bit.Core.Test.Services [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 - }); - + public async Task GetAssertionAsync_IncrementsCounter_CounterIsLargerThanZero(Cipher encryptedCipher) { // Arrange - cipherView.Login.MainFido2Credential.CounterValue = 9000; - sutProvider.GetDependency().EncryptAsync(cipherView).Returns(encryptedCipher); + _selectedCipher.Login.MainFido2Credential.CounterValue = 9000; + _sutProvider.GetDependency().EncryptAsync(_selectedCipher).Returns(encryptedCipher); // Act - await sutProvider.Sut.GetAssertionAsync(aParams); + await _sutProvider.Sut.GetAssertionAsync(_params); // Assert - await sutProvider.GetDependency().Received().SaveWithServerAsync(encryptedCipher); - await sutProvider.GetDependency().Received().EncryptAsync(Arg.Is( + await _sutProvider.GetDependency().Received().SaveWithServerAsync(encryptedCipher); + await _sutProvider.GetDependency().Received().EncryptAsync(Arg.Is( (cipher) => cipher.Login.MainFido2Credential.CounterValue == 9001 )); } @@ -263,54 +235,38 @@ namespace Bit.Core.Test.Services [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 - }); - + public async Task GetAssertionAsync_DoesNotIncrementsCounter_CounterIsZero(Cipher encryptedCipher) { // Arrange - cipherView.Login.MainFido2Credential.CounterValue = 0; - sutProvider.GetDependency().EncryptAsync(cipherView).Returns(encryptedCipher); + _selectedCipher.Login.MainFido2Credential.CounterValue = 0; + _sutProvider.GetDependency().EncryptAsync(_selectedCipher).Returns(encryptedCipher); // Act - await sutProvider.Sut.GetAssertionAsync(aParams); + await _sutProvider.Sut.GetAssertionAsync(_params); // Assert - await sutProvider.GetDependency().Received().SaveWithServerAsync(encryptedCipher); - await sutProvider.GetDependency().Received().EncryptAsync(Arg.Is( + await _sutProvider.GetDependency().Received().SaveWithServerAsync(encryptedCipher); + await _sutProvider.GetDependency().Received().EncryptAsync(Arg.Is( (cipher) => cipher.Login.MainFido2Credential.CounterValue == 0 )); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] - public async Task GetAssertionAsync_ReturnsAssertion(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) { - // 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 - }); - + [Fact] + public async Task GetAssertionAsync_ReturnsAssertion() { // Arrange var keyPair = GenerateKeyPair(); var rpIdHashMock = RandomBytes(32); - aParams.ClientDataHash = RandomBytes(32); - sutProvider.GetDependency().HashAsync(aParams.RpId, CryptoHashAlgorithm.Sha256).Returns(rpIdHashMock); - cipherView.Login.MainFido2Credential.CounterValue = 9000; - cipherView.Login.MainFido2Credential.KeyValue = CoreHelpers.Base64UrlEncode(keyPair.ExportPkcs8PrivateKey()); + _params.ClientDataHash = RandomBytes(32); + _params.RequireUserVerification = true; + _selectedCipher.Login.MainFido2Credential.CounterValue = 9000; + _selectedCipher.Login.MainFido2Credential.KeyValue = CoreHelpers.Base64UrlEncode(keyPair.ExportPkcs8PrivateKey()); + _sutProvider.GetDependency().HashAsync(_params.RpId, CryptoHashAlgorithm.Sha256).Returns(rpIdHashMock); + _sutProvider.GetDependency().PickCredentialAsync(Arg.Any()).Returns(new Fido2PickCredentialResult { + CipherId = _selectedCipher.Id, + UserVerified = true + }); // Act - var result = await sutProvider.Sut.GetAssertionAsync(aParams); + var result = await _sutProvider.Sut.GetAssertionAsync(_params); // Assert var authData = result.AuthenticatorData; @@ -318,74 +274,45 @@ namespace Bit.Core.Test.Services var flags = authData.Skip(32).Take(1); var counter = authData.Skip(33).Take(4); - Assert.Equal(Guid.Parse(cipherView.Login.MainFido2Credential.CredentialId).ToByteArray(), result.SelectedCredential.Id); - Assert.Equal(CoreHelpers.Base64UrlDecode(cipherView.Login.MainFido2Credential.UserHandle), result.SelectedCredential.UserHandle); + Assert.Equal(Guid.Parse(_selectedCipher.Login.MainFido2Credential.CredentialId).ToByteArray(), result.SelectedCredential.Id); + Assert.Equal(CoreHelpers.Base64UrlDecode(_selectedCipher.Login.MainFido2Credential.UserHandle), result.SelectedCredential.UserHandle); Assert.Equal(rpIdHashMock, rpIdHash); Assert.Equal(new byte[] { 0b00000101 }, flags); // UP = true, UV = true Assert.Equal(new byte[] { 0, 0, 0x23, 0x29 }, counter); // 9001 in binary big-endian format - Assert.True(keyPair.VerifyData(authData.Concat(aParams.ClientDataHash).ToArray(), result.Signature, HashAlgorithmName.SHA256), "Signature verification failed"); + Assert.True(keyPair.VerifyData(authData.Concat(_params.ClientDataHash).ToArray(), result.Signature, HashAlgorithmName.SHA256), "Signature verification failed"); } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] - public async Task GetAssertionAsync_DoesNotAskForConfirmation_ParamsContainsOneAllowedCredentialAndUserPresenceIsFalse(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) + [Fact] + public async Task GetAssertionAsync_DoesNotAskForConfirmation_ParamsContainsOneAllowedCredentialAndUserPresenceIsFalse() { - // Common arrange - var credentialIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; - List ciphers = [ - CreateCipherView(credentialIds[0].ToString(), "bitwarden.com", false), - CreateCipherView(credentialIds[1].ToString(), "bitwarden.com", true) - ]; - var cipherView = ciphers[0]; - aParams.RpId = "bitwarden.com"; - aParams.RequireUserVerification = false; - aParams.RequireUserPresence = false; - aParams.AllowCredentialDescriptorList = [ - new PublicKeyCredentialDescriptor { - Id = credentialIds[1].ToByteArray(), - Type = "public-key" - } - ]; - sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); - // Arrange - var keyPair = GenerateKeyPair(); var rpIdHashMock = RandomBytes(32); - aParams.ClientDataHash = RandomBytes(32); - sutProvider.GetDependency().HashAsync(aParams.RpId, CryptoHashAlgorithm.Sha256).Returns(rpIdHashMock); - cipherView.Login.MainFido2Credential.CounterValue = 9000; - cipherView.Login.MainFido2Credential.KeyValue = CoreHelpers.Base64UrlEncode(keyPair.ExportPkcs8PrivateKey()); + _params.RequireUserPresence = false; + _params.AllowCredentialDescriptorList = [ + new PublicKeyCredentialDescriptor { + Id = _credentialIds[0].ToByteArray(), + Type = "public-key" + }, + ]; + _sutProvider.GetDependency().HashAsync(_params.RpId, CryptoHashAlgorithm.Sha256).Returns(rpIdHashMock); // Act - var result = await sutProvider.Sut.GetAssertionAsync(aParams); + var result = await _sutProvider.Sut.GetAssertionAsync(_params); // Assert - await sutProvider.GetDependency().DidNotReceive().PickCredentialAsync(Arg.Any()); + await _sutProvider.GetDependency().DidNotReceive().PickCredentialAsync(Arg.Any()); var authData = result.AuthenticatorData; var flags = authData.Skip(32).Take(1); Assert.Equal(new byte[] { 0b00000000 }, flags); // UP = false, UV = false } - [Theory] - [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] - // Spec: Increment the credential associated signature counter - public async Task GetAssertionAsync_ThrowsUnknownError_SaveFails(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) { - // 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 - }); - + [Fact] + public async Task GetAssertionAsync_ThrowsUnknownError_SaveFails() { // Arrange - cipherView.Login.MainFido2Credential.CounterValue = 0; - sutProvider.GetDependency().SaveWithServerAsync(Arg.Any()).Throws(new Exception()); + _sutProvider.GetDependency().SaveWithServerAsync(Arg.Any()).Throws(new Exception()); // Act & Assert - await Assert.ThrowsAsync(() => sutProvider.Sut.GetAssertionAsync(aParams)); + await Assert.ThrowsAsync(() => _sutProvider.Sut.GetAssertionAsync(_params)); } #endregion @@ -425,5 +352,16 @@ namespace Bit.Core.Test.Services } }; } + + private Fido2AuthenticatorGetAssertionParams CreateParams(string? rpId = null, byte[]? clientDataHash = null, PublicKeyCredentialDescriptor[]? allowCredentialDescriptorList = null, bool? requireUserPresence = null, bool? requireUserVerification = null) + { + return new Fido2AuthenticatorGetAssertionParams { + RpId = rpId ?? "bitwarden.com", + ClientDataHash = clientDataHash ?? RandomBytes(32), + AllowCredentialDescriptorList = allowCredentialDescriptorList ?? null, + RequireUserPresence = requireUserPresence ?? true, + RequireUserVerification = requireUserPresence ?? false + }; + } } }