using System; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Services; using Bit.Core.Models.View; using Bit.Core.Enums; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; using System.Collections.Generic; using System.Linq; using System.Diagnostics.CodeAnalysis; using Bit.Core.Utilities; namespace Bit.Core.Test.Services { public class Fido2AuthenticatorSilentCredentialDiscoveryTests { [Theory] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] public async Task SilentCredentialDiscoveryAsync_ReturnsEmptyArray_NoCredentialsExist(SutProvider sutProvider) { sutProvider.GetDependency().GetAllDecryptedAsync().Returns(Task.FromResult(new List())); var result = await sutProvider.Sut.SilentCredentialDiscoveryAsync("bitwarden.com"); Assert.Empty(result); } [Theory] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] public async Task SilentCredentialDiscoveryAsync_ReturnsEmptyArray_OnlyNonDiscoverableCredentialsExist(SutProvider sutProvider) { sutProvider.GetDependency().GetAllDecryptedAsync().Returns(Task.FromResult(new List { CreateCipherView("bitwarden.com", false), CreateCipherView("bitwarden.com", false), CreateCipherView("bitwarden.com", false) })); var result = await sutProvider.Sut.SilentCredentialDiscoveryAsync("bitwarden.com"); Assert.Empty(result); } [Theory] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] public async Task SilentCredentialDiscoveryAsync_ReturnsEmptyArray_NoCredentialsWithMatchingRpIdExist(SutProvider sutProvider) { sutProvider.GetDependency().GetAllDecryptedAsync().Returns(Task.FromResult(new List { CreateCipherView("a.bitwarden.com", true), CreateCipherView("example.com", true) })); var result = await sutProvider.Sut.SilentCredentialDiscoveryAsync("bitwarden.com"); Assert.Empty(result); } [Theory] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] public async Task SilentCredentialDiscoveryAsync_ReturnsCredentials_DiscoverableCredentialsWithMatchingRpIdExist(SutProvider sutProvider) { var matchingCredentials = new List { CreateCipherView("bitwarden.com", true), CreateCipherView("bitwarden.com", true) }; var nonMatchingCredentials = new List { CreateCipherView("example.com", true) }; sutProvider.GetDependency().GetAllDecryptedAsync().Returns( matchingCredentials.Concat(nonMatchingCredentials).ToList() ); var result = await sutProvider.Sut.SilentCredentialDiscoveryAsync("bitwarden.com"); Assert.True( result.SequenceEqual(matchingCredentials.Select(c => new Fido2AuthenticatorDiscoverableCredentialMetadata { Type = Constants.DefaultFido2CredentialType, Id = c.Login.MainFido2Credential.CredentialId.GuidToRawFormat(), RpId = "bitwarden.com", UserHandle = c.Login.MainFido2Credential.UserHandleValue, UserName = c.Login.MainFido2Credential.UserName, CipherId = c.Id, }), new MetadataComparer()) ); } private byte[] RandomBytes(int length) { var bytes = new byte[length]; new Random().NextBytes(bytes); return bytes; } #nullable enable private CipherView CreateCipherView(string rpId, bool discoverable) { return new CipherView { Type = CipherType.Login, Id = Guid.NewGuid().ToString(), Reprompt = CipherRepromptType.None, Login = new LoginView { Fido2Credentials = new List { new Fido2CredentialView { CredentialId = Guid.NewGuid().ToString(), RpId = rpId ?? "null.com", DiscoverableValue = discoverable, UserHandleValue = RandomBytes(32), KeyValue = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgO4wC7AlY4eJP7uedRUJGYsAIJAd6gN1Vp7uJh6xXAp6hRANCAARGvr56F_t27DEG1Tzl-qJRhrTUtC7jOEbasAEEZcE3TiMqoWCan0sxKDPylhRYk-1qyrBC_feN1UtGWH57sROa" } } } }; } private class MetadataComparer : IEqualityComparer { public int GetHashCode([DisallowNull] Fido2AuthenticatorDiscoverableCredentialMetadata obj) => throw new NotImplementedException(); public bool Equals(Fido2AuthenticatorDiscoverableCredentialMetadata? a, Fido2AuthenticatorDiscoverableCredentialMetadata? b) => a != null && b != null && a.Type == b.Type && a.RpId == b.RpId && a.UserName == b.UserName && a.Id.SequenceEqual(b.Id) && a.UserHandle.SequenceEqual(b.UserHandle); } } }