diff --git a/src/Core/Services/Fido2AuthenticatorService.cs b/src/Core/Services/Fido2AuthenticatorService.cs index c2e9c4c88..3490084c7 100644 --- a/src/Core/Services/Fido2AuthenticatorService.cs +++ b/src/Core/Services/Fido2AuthenticatorService.cs @@ -22,6 +22,20 @@ namespace Bit.Core.Services _cryptoFunctionService = cryptoFunctionService; _userInterface = userInterface; } + + public Task MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams) + { + if (makeCredentialParams.CredTypesAndPubKeyAlgs.All((p) => p.Algorithm != (int) Fido2AlgorithmIdentifier.ES256)) + { + var requestedAlgorithms = string.Join(", ", makeCredentialParams.CredTypesAndPubKeyAlgs.Select((p) => p.Algorithm).ToArray()); + _logService.Warning( + $"[Fido2Authenticator] No compatible algorithms found, RP requested: {requestedAlgorithms}" + ); + throw new NotSupportedError(); + } + + throw new NotImplementedException(); + } public async Task GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams) { @@ -116,10 +130,6 @@ namespace Bit.Core.Services } } - public Task MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams) { - throw new NotImplementedException(); - } - private async Task> FindCredentialsById(PublicKeyCredentialDescriptor[] credentials, string rpId) { var ids = new List(); diff --git a/src/Core/Utilities/Fido2/Fido2AlgorithmIdentifier.cs b/src/Core/Utilities/Fido2/Fido2AlgorithmIdentifier.cs new file mode 100644 index 000000000..6d519f3a4 --- /dev/null +++ b/src/Core/Utilities/Fido2/Fido2AlgorithmIdentifier.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Utilities.Fido2 { + public enum Fido2AlgorithmIdentifier : int { + ES256 = -7, + RS256 = -257, + } +} diff --git a/src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs b/src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs index 361933ef4..5a8a75405 100644 --- a/src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs +++ b/src/Core/Utilities/Fido2/Fido2AuthenticatorException.cs @@ -14,6 +14,13 @@ namespace Bit.Core.Utilities.Fido2 } } + public class NotSupportedError : Fido2AuthenticatorException + { + public NotSupportedError() : base("NotSupportedError") + { + } + } + public class UnknownError : Fido2AuthenticatorException { public UnknownError() : base("UnknownError") diff --git a/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs index 9cc7e0191..2e0ab09f3 100644 --- a/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs +++ b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs @@ -20,7 +20,7 @@ namespace Bit.Core.Utilities.Fido2 /// /// A sequence of pairs of PublicKeyCredentialType and public key algorithms (COSEAlgorithmIdentifier) requested by the Relying Party. This sequence is ordered from most preferred to least preferred. The authenticator makes a best-effort to create the most preferred credential that it can. */ /// - public PublicKeyCredentialDescriptor[] CredTypesAndPubKeyAlgs { get; set; } + public PublicKeyCredentialAlgorithmDescriptor[] CredTypesAndPubKeyAlgs { get; set; } /// /// The effective resident key requirement for credential creation, a Boolean value determined by the client. Resident is synonymous with discoverable. */ diff --git a/src/Core/Utilities/Fido2/PublicKeyCredentialAlgorithmDescriptor.cs b/src/Core/Utilities/Fido2/PublicKeyCredentialAlgorithmDescriptor.cs new file mode 100644 index 000000000..47c0b60fc --- /dev/null +++ b/src/Core/Utilities/Fido2/PublicKeyCredentialAlgorithmDescriptor.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Utilities.Fido2 +{ + public class PublicKeyCredentialAlgorithmDescriptor { + public byte[] Id {get; set;} + public string[] Transports; + public string Type; + public int Algorithm; + } +} diff --git a/test/Core.Test/Services/Fido2AuthenticatorTests.cs b/test/Core.Test/Services/Fido2AuthenticatorGetAssertionTests.cs similarity index 99% rename from test/Core.Test/Services/Fido2AuthenticatorTests.cs rename to test/Core.Test/Services/Fido2AuthenticatorGetAssertionTests.cs index 0708803b7..20b3fb5cc 100644 --- a/test/Core.Test/Services/Fido2AuthenticatorTests.cs +++ b/test/Core.Test/Services/Fido2AuthenticatorGetAssertionTests.cs @@ -19,7 +19,7 @@ using System.Linq; namespace Bit.Core.Test.Services { - public class Fido2AuthenticatorTests + public class Fido2AuthenticatorGetAssertionTests { #region missing non-discoverable credential @@ -310,7 +310,7 @@ namespace Bit.Core.Test.Services sutProvider.GetDependency().SignAsync( Arg.Any(), Arg.Any(), - new CryptoSignEcdsaOptions { + new CryptoSignEcdsaOptions { Algorithm = CryptoSignEcdsaOptions.EcdsaAlgorithm.EcdsaP256Sha256, SignatureFormat = CryptoSignEcdsaOptions.DsaSignatureFormat.Rfc3279DerSequence } diff --git a/test/Core.Test/Services/Fido2AuthenticatorMakeCredentialTests.cs b/test/Core.Test/Services/Fido2AuthenticatorMakeCredentialTests.cs new file mode 100644 index 000000000..51d9c604d --- /dev/null +++ b/test/Core.Test/Services/Fido2AuthenticatorMakeCredentialTests.cs @@ -0,0 +1,89 @@ +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; +using Bit.Core.Enums; +using Bit.Core.Test.AutoFixture; +using Bit.Core.Utilities.Fido2; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using Xunit; +using Bit.Core.Utilities; +using System.Collections.Generic; +using System.Linq; + +namespace Bit.Core.Test.Services +{ + public class Fido2AuthenticatorMakeCredentialTests + { + #region missing non-discoverable credential + + // 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_ThrowsNotSupported_NoSupportedAlgorithm(SutProvider sutProvider, Fido2AuthenticatorMakeCredentialParams mParams) + { + mParams.CredTypesAndPubKeyAlgs = [ + new PublicKeyCredentialAlgorithmDescriptor { + Type = "public-key", + Algorithm = -257 // RS256 which we do not support + } + ]; + + await Assert.ThrowsAsync(() => sutProvider.Sut.MakeCredentialAsync(mParams)); + } + + // [Theory] + // [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + // public async Task GetAssertionAsync_Throws_CredentialExistsButRpIdDoesNotMatch(SutProvider sutProvider, Fido2AuthenticatorGetAssertionParams aParams) + // { + // 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)); + // } + + #endregion + + private byte[] RandomBytes(int length) + { + var bytes = new byte[length]; + new Random().NextBytes(bytes); + return bytes; + } + + #nullable enable + private CipherView CreateCipherView(string? credentialId, 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 = credentialId ?? Guid.NewGuid().ToString(), + RpId = rpId ?? "bitwarden.com", + Discoverable = discoverable.HasValue ? discoverable.ToString() : "true", + UserHandleValue = RandomBytes(32), + } + } + } + }; + } + } +}