diff --git a/src/Core/Abstractions/IFido2UserInterface.cs b/src/Core/Abstractions/IFido2UserInterface.cs index f3476cff3..d5707c8c1 100644 --- a/src/Core/Abstractions/IFido2UserInterface.cs +++ b/src/Core/Abstractions/IFido2UserInterface.cs @@ -34,6 +34,37 @@ namespace Bit.Core.Abstractions public bool UserVerified { get; set; } } + public struct Fido2ConfirmNewCredentialParams + { + /// + /// The name of the credential. + /// + public string CredentialName { get; set; } + + /// + /// The name of the user. + /// + public string UserName { get; set; } + + /// + /// Whether or not the user must be verified before completing the operation. + /// + public bool UserVerification { get; set; } + } + + public struct Fido2ConfirmNewCredentialResult + { + /// + /// The name of the user. + /// + public string CipherId { get; set; } + + /// + /// Whether or not the user was verified. + /// + public bool UserVerified { get; set; } + } + public interface IFido2UserInterface { /// @@ -49,5 +80,12 @@ namespace Bit.Core.Abstractions /// The IDs of the excluded credentials. /// When user has confirmed the message Task InformExcludedCredential(string[] existingCipherIds); + + /// + /// Ask the user to confirm the creation of a new credential. + /// + /// The parameters to use when asking the user to confirm the creation of a new credential. + /// The ID of the cipher where the new credential should be saved. + Task ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams); } } diff --git a/src/Core/Services/Fido2AuthenticatorService.cs b/src/Core/Services/Fido2AuthenticatorService.cs index f2deb4376..84ba74aa4 100644 --- a/src/Core/Services/Fido2AuthenticatorService.cs +++ b/src/Core/Services/Fido2AuthenticatorService.cs @@ -48,7 +48,20 @@ namespace Bit.Core.Services throw new NotAllowedError(); } - throw new NotImplementedException(); + var response = await _userInterface.ConfirmNewCredentialAsync(new Fido2ConfirmNewCredentialParams { + CredentialName = makeCredentialParams.RpEntity.Name, + UserName = makeCredentialParams.UserEntity.Name, + UserVerification = makeCredentialParams.RequireUserVerification + }); + + return new Fido2AuthenticatorMakeCredentialResult + { + CredentialId = GuidToRawFormat(Guid.NewGuid().ToString()), + AttestationObject = Array.Empty(), + AuthData = Array.Empty(), + PublicKey = Array.Empty(), + PublicKeyAlgorithm = (int) Fido2AlgorithmIdentifier.ES256, + }; } public async Task GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams) diff --git a/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs index f2b1e5186..59b070905 100644 --- a/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs +++ b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs @@ -2,44 +2,44 @@ namespace Bit.Core.Utilities.Fido2 { public class Fido2AuthenticatorMakeCredentialParams { - /// - /// The caller’s RP ID, as determined by the user agent and the client. */ - /// - public string RpId { get; set; } + /// + /// The Relying Party's PublicKeyCredentialRpEntity. + /// + public PublicKeyCredentialRpEntity RpEntity { get; set; } - /// - /// The Relying Party's PublicKeyCredentialRpEntity. */ - /// + /// + /// The Relying Party's PublicKeyCredentialRpEntity. + /// public PublicKeyCredentialUserEntity UserEntity { get; set; } - /// - /// The hash of the serialized client data, provided by the client. */ - /// + /// + /// The hash of the serialized client data, provided by the client. + /// public byte[] Hash { get; set; } - /// - /// 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. */ - /// + /// + /// 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 PublicKeyCredentialAlgorithmDescriptor[] CredTypesAndPubKeyAlgs { get; set; } - /// + /// ///An OPTIONAL list of PublicKeyCredentialDescriptor objects provided by the Relying Party with the intention that, if any of these are known to the authenticator, it SHOULD NOT create a new credential. excludeCredentialDescriptorList contains a list of known credentials. - /// + /// public PublicKeyCredentialDescriptor[] ExcludeCredentialDescriptorList { get; set; } - /// + /// /// The effective resident key requirement for credential creation, a Boolean value determined by the client. Resident is synonymous with discoverable. */ - /// + /// public bool RequireResidentKey { get; set; } - /// - /// The effective user verification requirement for assertion, a Boolean value provided by the client. */ - /// + /// + /// The effective user verification requirement for assertion, a Boolean value provided by the client. + /// public bool RequireUserVerification { get; set; } - /// - /// CTAP2 authenticators support setting this to false, but we only support the WebAuthn authenticator model which does not have that option. */ - /// + /// + /// CTAP2 authenticators support setting this to false, but we only support the WebAuthn authenticator model which does not have that option. + /// // public bool RequireUserPresence { get; set; } // Always required /// diff --git a/src/Core/Utilities/Fido2/PublicKeyCredentialRpEntity.cs b/src/Core/Utilities/Fido2/PublicKeyCredentialRpEntity.cs new file mode 100644 index 000000000..abbf52cb0 --- /dev/null +++ b/src/Core/Utilities/Fido2/PublicKeyCredentialRpEntity.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Utilities.Fido2 +{ + public class PublicKeyCredentialRpEntity + { + public string Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/test/Core.Test/Services/Fido2AuthenticatorMakeCredentialTests.cs b/test/Core.Test/Services/Fido2AuthenticatorMakeCredentialTests.cs index 08d6d46f0..6170c4429 100644 --- a/test/Core.Test/Services/Fido2AuthenticatorMakeCredentialTests.cs +++ b/test/Core.Test/Services/Fido2AuthenticatorMakeCredentialTests.cs @@ -59,7 +59,7 @@ namespace Bit.Core.Test.Services Algorithm = -7 // ES256 } ]; - mParams.RpId = "bitwarden.com"; + mParams.RpEntity = new PublicKeyCredentialRpEntity { Id = "bitwarden.com" }; mParams.RequireUserVerification = false; mParams.ExcludeCredentialDescriptorList = [ new PublicKeyCredentialDescriptor { @@ -96,7 +96,7 @@ namespace Bit.Core.Test.Services Algorithm = -7 // ES256 } ]; - mParams.RpId = "bitwarden.com"; + mParams.RpEntity = new PublicKeyCredentialRpEntity { Id = "bitwarden.com" }; mParams.RequireUserVerification = false; mParams.ExcludeCredentialDescriptorList = [ new PublicKeyCredentialDescriptor { @@ -126,7 +126,7 @@ namespace Bit.Core.Test.Services Algorithm = -7 // ES256 } ]; - mParams.RpId = "bitwarden.com"; + mParams.RpEntity = new PublicKeyCredentialRpEntity { Id = "bitwarden.com" }; mParams.RequireUserVerification = false; mParams.ExcludeCredentialDescriptorList = [ new PublicKeyCredentialDescriptor { @@ -146,6 +146,74 @@ namespace Bit.Core.Test.Services #endregion + #region credential creation + + [Theory] + [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + public async Task MakeCredentialAsync_RequestsUserVerification_ParamsRequireUserVerification(SutProvider sutProvider, Fido2AuthenticatorMakeCredentialParams mParams) + { + // 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) + ]; + mParams.CredTypesAndPubKeyAlgs = [ + new PublicKeyCredentialAlgorithmDescriptor { + Type = "public-key", + Algorithm = -7 // ES256 + } + ]; + mParams.RpEntity = new PublicKeyCredentialRpEntity { Id = "bitwarden.com" }; + mParams.RequireUserVerification = false; + sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); + + // Arrange + mParams.RequireUserVerification = true; + + // Act + await sutProvider.Sut.MakeCredentialAsync(mParams); + + // Assert + await sutProvider.GetDependency().Received().ConfirmNewCredentialAsync(Arg.Is( + (p) => p.UserVerification == true + )); + } + + [Theory] + [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] + public async Task MakeCredentialAsync_DoesNotRequestUserVerification_ParamsDoNotRequireUserVerification(SutProvider sutProvider, Fido2AuthenticatorMakeCredentialParams mParams) + { + // 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) + ]; + mParams.CredTypesAndPubKeyAlgs = [ + new PublicKeyCredentialAlgorithmDescriptor { + Type = "public-key", + Algorithm = -7 // ES256 + } + ]; + mParams.RpEntity = new PublicKeyCredentialRpEntity { Id = "bitwarden.com" }; + mParams.RequireUserVerification = false; + sutProvider.GetDependency().GetAllDecryptedAsync().Returns(ciphers); + + // Arrange + mParams.RequireUserVerification = false; + + // Act + await sutProvider.Sut.MakeCredentialAsync(mParams); + + // Assert + await sutProvider.GetDependency().Received().ConfirmNewCredentialAsync(Arg.Is( + (p) => p.UserVerification == false + )); + } + + #endregion + private byte[] RandomBytes(int length) { var bytes = new byte[length];