diff --git a/src/Core/Services/Fido2ClientService.cs b/src/Core/Services/Fido2ClientService.cs
index bf454bc73..a36ba61a5 100644
--- a/src/Core/Services/Fido2ClientService.cs
+++ b/src/Core/Services/Fido2ClientService.cs
@@ -4,6 +4,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
+using Bit.Core.Utilities.Fido2.Extensions;
namespace Bit.Core.Services
{
@@ -124,6 +125,15 @@ namespace Bit.Core.Services
{
var makeCredentialResult = await _fido2AuthenticatorService.MakeCredentialAsync(makeCredentialParams, _makeCredentialUserInterface);
+ Fido2CredPropsResult credProps = null;
+ if (createCredentialParams.Extensions?.CredProps == true)
+ {
+ credProps = new Fido2CredPropsResult
+ {
+ Rk = makeCredentialParams.RequireResidentKey
+ };
+ }
+
return new Fido2ClientCreateCredentialResult
{
CredentialId = makeCredentialResult.CredentialId,
@@ -132,7 +142,11 @@ namespace Bit.Core.Services
ClientDataJSON = clientDataJSONBytes,
PublicKey = makeCredentialResult.PublicKey,
PublicKeyAlgorithm = makeCredentialResult.PublicKeyAlgorithm,
- Transports = createCredentialParams.Rp.Id == "google.com" ? new string[] { "internal", "usb" } : new string[] { "internal" } // workaround for a bug on Google's side
+ Transports = createCredentialParams.Rp.Id == "google.com" ? new string[] { "internal", "usb" } : new string[] { "internal" }, // workaround for a bug on Google's side
+ Extensions = new Fido2CreateCredentialExtensionsResult
+ {
+ CredProps = credProps
+ }
};
}
catch (InvalidStateError)
@@ -249,7 +263,8 @@ namespace Bit.Core.Services
Fido2ClientAssertCredentialParams assertCredentialParams,
byte[] cliendDataHash)
{
- return new Fido2AuthenticatorGetAssertionParams {
+ return new Fido2AuthenticatorGetAssertionParams
+ {
RpId = assertCredentialParams.RpId,
Challenge = assertCredentialParams.Challenge,
AllowCredentialDescriptorList = assertCredentialParams.AllowCredentials,
diff --git a/src/Core/Utilities/Fido2/Extensions/Fido2CreateCredentialExtensionsParams.cs b/src/Core/Utilities/Fido2/Extensions/Fido2CreateCredentialExtensionsParams.cs
new file mode 100644
index 000000000..c2311fdb6
--- /dev/null
+++ b/src/Core/Utilities/Fido2/Extensions/Fido2CreateCredentialExtensionsParams.cs
@@ -0,0 +1,9 @@
+namespace Bit.Core.Utilities.Fido2.Extensions
+{
+#nullable enable
+
+ public class Fido2CreateCredentialExtensionsParams
+ {
+ public bool CredProps { get; set; } = false;
+ }
+}
diff --git a/src/Core/Utilities/Fido2/Extensions/Fido2CreateCredentialExtensionsResult.cs b/src/Core/Utilities/Fido2/Extensions/Fido2CreateCredentialExtensionsResult.cs
new file mode 100644
index 000000000..410f14418
--- /dev/null
+++ b/src/Core/Utilities/Fido2/Extensions/Fido2CreateCredentialExtensionsResult.cs
@@ -0,0 +1,9 @@
+namespace Bit.Core.Utilities.Fido2.Extensions
+{
+#nullable enable
+
+ public class Fido2CreateCredentialExtensionsResult
+ {
+ public Fido2CredPropsResult? CredProps { get; set; }
+ }
+}
diff --git a/src/Core/Utilities/Fido2/Extensions/Fido2CredPropsResult.cs b/src/Core/Utilities/Fido2/Extensions/Fido2CredPropsResult.cs
new file mode 100644
index 000000000..b2fa1cce2
--- /dev/null
+++ b/src/Core/Utilities/Fido2/Extensions/Fido2CredPropsResult.cs
@@ -0,0 +1,9 @@
+namespace Bit.Core.Utilities.Fido2.Extensions
+{
+#nullable enable
+
+ public class Fido2CredPropsResult
+ {
+ public bool? Rk { get; set; } = false;
+ }
+}
diff --git a/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs
index 5aebb8ab0..13e210745 100644
--- a/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs
+++ b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs
@@ -36,7 +36,7 @@ namespace Bit.Core.Utilities.Fido2
/// The effective user verification preference for assertion, provided by the client.
///
public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
-
+
///
/// CTAP2 authenticators support setting this to false, but we only support the WebAuthn authenticator model which does not have that option.
///
diff --git a/src/Core/Utilities/Fido2/Fido2ClientCreateCredentialParams.cs b/src/Core/Utilities/Fido2/Fido2ClientCreateCredentialParams.cs
index 52adbfff0..d730ed025 100644
--- a/src/Core/Utilities/Fido2/Fido2ClientCreateCredentialParams.cs
+++ b/src/Core/Utilities/Fido2/Fido2ClientCreateCredentialParams.cs
@@ -1,6 +1,8 @@
+using Bit.Core.Utilities.Fido2.Extensions;
+
namespace Bit.Core.Utilities.Fido2
{
- #nullable enable
+#nullable enable
///
/// Parameters for creating a new credential.
@@ -42,9 +44,8 @@ namespace Bit.Core.Utilities.Fido2
///
/// This member contains additional parameters requesting additional processing by the client and authenticator.
- /// Not currently supported.
///
- public object? Extensions { get; set; }
+ public Fido2CreateCredentialExtensionsParams? Extensions { get; set; }
///
/// This member contains information about the desired properties of the credential to be created.
diff --git a/src/Core/Utilities/Fido2/Fido2ClientCreateCredentialResult.cs b/src/Core/Utilities/Fido2/Fido2ClientCreateCredentialResult.cs
index 21cd90357..65284c19e 100644
--- a/src/Core/Utilities/Fido2/Fido2ClientCreateCredentialResult.cs
+++ b/src/Core/Utilities/Fido2/Fido2ClientCreateCredentialResult.cs
@@ -1,3 +1,5 @@
+using Bit.Core.Utilities.Fido2.Extensions;
+
namespace Bit.Core.Utilities.Fido2
{
///
@@ -15,5 +17,6 @@ namespace Bit.Core.Utilities.Fido2
public byte[] PublicKey { get; set; }
public int PublicKeyAlgorithm { get; set; }
public string[] Transports { get; set; }
+ public Fido2CreateCredentialExtensionsResult Extensions { get; set; }
}
}
diff --git a/test/Core.Test/Services/Fido2ClientCreateCredentialTests.cs b/test/Core.Test/Services/Fido2ClientCreateCredentialTests.cs
index 0310494cc..70fcdb240 100644
--- a/test/Core.Test/Services/Fido2ClientCreateCredentialTests.cs
+++ b/test/Core.Test/Services/Fido2ClientCreateCredentialTests.cs
@@ -8,6 +8,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
+using Bit.Core.Utilities.Fido2.Extensions;
using Bit.Test.Common.AutoFixture;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
@@ -20,6 +21,7 @@ namespace Bit.Core.Test.Services
private readonly SutProvider _sutProvider = new SutProvider().Create();
private Fido2ClientCreateCredentialParams _params;
+ private Fido2AuthenticatorMakeCredentialResult _authenticatorResult;
public Fido2ClientCreateCredentialTests()
{
@@ -37,22 +39,33 @@ namespace Bit.Core.Test.Services
Alg = (int) Fido2AlgorithmIdentifier.ES256
}
},
- Rp = new PublicKeyCredentialRpEntity {
+ Rp = new PublicKeyCredentialRpEntity
+ {
Id = "bitwarden.com",
Name = "Bitwarden"
},
- User = new PublicKeyCredentialUserEntity {
+ User = new PublicKeyCredentialUserEntity
+ {
Id = RandomBytes(32),
Name = "user@bitwarden.com",
DisplayName = "User"
}
};
+ _authenticatorResult = new Fido2AuthenticatorMakeCredentialResult
+ {
+ CredentialId = RandomBytes(32),
+ AttestationObject = RandomBytes(32),
+ AuthData = RandomBytes(32),
+ PublicKey = RandomBytes(32),
+ PublicKeyAlgorithm = (int)Fido2AlgorithmIdentifier.ES256,
+ };
+
_sutProvider.GetDependency().GetAutofillBlacklistedUrisAsync().Returns(Task.FromResult(new List()));
_sutProvider.GetDependency().IsAuthenticatedAsync().Returns(true);
}
- public void Dispose()
+ public void Dispose()
{
}
@@ -69,7 +82,7 @@ namespace Bit.Core.Test.Services
// Assert
Assert.Equal(Fido2ClientException.ErrorCode.NotAllowedError, exception.Code);
}
-
+
[Fact]
// Spec: If the length of options.user.id is not between 1 and 64 bytes (inclusive) then return a TypeError.
public async Task CreateCredentialAsync_ThrowsTypeError_UserIdIsTooSmall()
@@ -198,16 +211,18 @@ namespace Bit.Core.Test.Services
public async Task CreateCredentialAsync_ReturnsNewCredential()
{
// Arrange
- _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria {
+ _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria
+ {
ResidentKey = "required",
UserVerification = "required"
};
- var authenticatorResult = new Fido2AuthenticatorMakeCredentialResult {
+ var authenticatorResult = new Fido2AuthenticatorMakeCredentialResult
+ {
CredentialId = RandomBytes(32),
AttestationObject = RandomBytes(32),
AuthData = RandomBytes(32),
PublicKey = RandomBytes(32),
- PublicKeyAlgorithm = (int) Fido2AlgorithmIdentifier.ES256,
+ PublicKeyAlgorithm = (int)Fido2AlgorithmIdentifier.ES256,
};
_sutProvider.GetDependency()
.MakeCredentialAsync(Arg.Any(), _sutProvider.GetDependency())
@@ -246,7 +261,8 @@ namespace Bit.Core.Test.Services
public async Task CreateCredentialAsync_ThrowsInvalidStateError_AuthenticatorThrowsInvalidStateError()
{
// Arrange
- _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria {
+ _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria
+ {
ResidentKey = "required",
UserVerification = "required"
};
@@ -304,6 +320,66 @@ namespace Bit.Core.Test.Services
Assert.Equal(Fido2ClientException.ErrorCode.NotAllowedError, exception.Code);
}
+ [Fact]
+ public async Task CreateCredentialAsync_ReturnsCredPropsRkTrue_WhenCreatingDiscoverableCredential()
+ {
+ // Arrange
+ _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria
+ {
+ ResidentKey = "required"
+ };
+ _params.Extensions = new Fido2CreateCredentialExtensionsParams { CredProps = true };
+ _sutProvider.GetDependency()
+ .MakeCredentialAsync(Arg.Any(), _sutProvider.GetDependency())
+ .Returns(_authenticatorResult);
+
+ // Act
+ var result = await _sutProvider.Sut.CreateCredentialAsync(_params);
+
+ // Assert
+ Assert.True(result.Extensions.CredProps?.Rk);
+ }
+
+ [Fact]
+ public async Task CreateCredentialAsync_ReturnsCredPropsRkFalse_WhenCreatingNonDiscoverableCredential()
+ {
+ // Arrange
+ _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria
+ {
+ ResidentKey = "discouraged"
+ };
+ _params.Extensions = new Fido2CreateCredentialExtensionsParams { CredProps = true };
+ _sutProvider.GetDependency()
+ .MakeCredentialAsync(Arg.Any(), _sutProvider.GetDependency())
+ .Returns(_authenticatorResult);
+
+ // Act
+ var result = await _sutProvider.Sut.CreateCredentialAsync(_params);
+
+ // Assert
+ Assert.False(result.Extensions.CredProps?.Rk);
+ }
+
+ [Fact]
+ public async Task CreateCredentialAsync_ReturnsCredPropsUndefined_WhenExtensionIsNotRequested()
+ {
+ // Arrange
+ _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria
+ {
+ ResidentKey = "required"
+ };
+ _params.Extensions = new Fido2CreateCredentialExtensionsParams();
+ _sutProvider.GetDependency()
+ .MakeCredentialAsync(Arg.Any(), _sutProvider.GetDependency())
+ .Returns(_authenticatorResult);
+
+ // Act
+ var result = await _sutProvider.Sut.CreateCredentialAsync(_params);
+
+ // Assert
+ Assert.Null(result.Extensions.CredProps);
+ }
+
private byte[] RandomBytes(int length)
{
var bytes = new byte[length];