1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-20 02:03:49 +00:00

feat: add support for credProps.rk extension (#3132)

This commit is contained in:
Andreas Coroiu
2024-04-03 17:52:39 +02:00
committed by GitHub
parent 86368c57ef
commit ceca142c65
8 changed files with 136 additions and 14 deletions

View File

@@ -4,6 +4,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities.Fido2;
using Bit.Core.Utilities.Fido2.Extensions;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
@@ -124,6 +125,15 @@ namespace Bit.Core.Services
{ {
var makeCredentialResult = await _fido2AuthenticatorService.MakeCredentialAsync(makeCredentialParams, _makeCredentialUserInterface); var makeCredentialResult = await _fido2AuthenticatorService.MakeCredentialAsync(makeCredentialParams, _makeCredentialUserInterface);
Fido2CredPropsResult credProps = null;
if (createCredentialParams.Extensions?.CredProps == true)
{
credProps = new Fido2CredPropsResult
{
Rk = makeCredentialParams.RequireResidentKey
};
}
return new Fido2ClientCreateCredentialResult return new Fido2ClientCreateCredentialResult
{ {
CredentialId = makeCredentialResult.CredentialId, CredentialId = makeCredentialResult.CredentialId,
@@ -132,7 +142,11 @@ namespace Bit.Core.Services
ClientDataJSON = clientDataJSONBytes, ClientDataJSON = clientDataJSONBytes,
PublicKey = makeCredentialResult.PublicKey, PublicKey = makeCredentialResult.PublicKey,
PublicKeyAlgorithm = makeCredentialResult.PublicKeyAlgorithm, 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) catch (InvalidStateError)
@@ -249,7 +263,8 @@ namespace Bit.Core.Services
Fido2ClientAssertCredentialParams assertCredentialParams, Fido2ClientAssertCredentialParams assertCredentialParams,
byte[] cliendDataHash) byte[] cliendDataHash)
{ {
return new Fido2AuthenticatorGetAssertionParams { return new Fido2AuthenticatorGetAssertionParams
{
RpId = assertCredentialParams.RpId, RpId = assertCredentialParams.RpId,
Challenge = assertCredentialParams.Challenge, Challenge = assertCredentialParams.Challenge,
AllowCredentialDescriptorList = assertCredentialParams.AllowCredentials, AllowCredentialDescriptorList = assertCredentialParams.AllowCredentials,

View File

@@ -0,0 +1,9 @@
namespace Bit.Core.Utilities.Fido2.Extensions
{
#nullable enable
public class Fido2CreateCredentialExtensionsParams
{
public bool CredProps { get; set; } = false;
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.Core.Utilities.Fido2.Extensions
{
#nullable enable
public class Fido2CreateCredentialExtensionsResult
{
public Fido2CredPropsResult? CredProps { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace Bit.Core.Utilities.Fido2.Extensions
{
#nullable enable
public class Fido2CredPropsResult
{
public bool? Rk { get; set; } = false;
}
}

View File

@@ -36,7 +36,7 @@ namespace Bit.Core.Utilities.Fido2
/// The effective user verification preference for assertion, provided by the client. /// The effective user verification preference for assertion, provided by the client.
/// </summary> /// </summary>
public Fido2UserVerificationPreference UserVerificationPreference { get; set; } public Fido2UserVerificationPreference UserVerificationPreference { get; set; }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>

View File

@@ -1,6 +1,8 @@
using Bit.Core.Utilities.Fido2.Extensions;
namespace Bit.Core.Utilities.Fido2 namespace Bit.Core.Utilities.Fido2
{ {
#nullable enable #nullable enable
/// <summary> /// <summary>
/// Parameters for creating a new credential. /// Parameters for creating a new credential.
@@ -42,9 +44,8 @@ namespace Bit.Core.Utilities.Fido2
/// <summary> /// <summary>
/// This member contains additional parameters requesting additional processing by the client and authenticator. /// This member contains additional parameters requesting additional processing by the client and authenticator.
/// Not currently supported.
/// </summary> /// </summary>
public object? Extensions { get; set; } public Fido2CreateCredentialExtensionsParams? Extensions { get; set; }
/// <summary> /// <summary>
/// This member contains information about the desired properties of the credential to be created. /// This member contains information about the desired properties of the credential to be created.

View File

@@ -1,3 +1,5 @@
using Bit.Core.Utilities.Fido2.Extensions;
namespace Bit.Core.Utilities.Fido2 namespace Bit.Core.Utilities.Fido2
{ {
/// <summary> /// <summary>
@@ -15,5 +17,6 @@ namespace Bit.Core.Utilities.Fido2
public byte[] PublicKey { get; set; } public byte[] PublicKey { get; set; }
public int PublicKeyAlgorithm { get; set; } public int PublicKeyAlgorithm { get; set; }
public string[] Transports { get; set; } public string[] Transports { get; set; }
public Fido2CreateCredentialExtensionsResult Extensions { get; set; }
} }
} }

View File

@@ -8,6 +8,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities.Fido2;
using Bit.Core.Utilities.Fido2.Extensions;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using NSubstitute; using NSubstitute;
using NSubstitute.ExceptionExtensions; using NSubstitute.ExceptionExtensions;
@@ -20,6 +21,7 @@ namespace Bit.Core.Test.Services
private readonly SutProvider<Fido2ClientService> _sutProvider = new SutProvider<Fido2ClientService>().Create(); private readonly SutProvider<Fido2ClientService> _sutProvider = new SutProvider<Fido2ClientService>().Create();
private Fido2ClientCreateCredentialParams _params; private Fido2ClientCreateCredentialParams _params;
private Fido2AuthenticatorMakeCredentialResult _authenticatorResult;
public Fido2ClientCreateCredentialTests() public Fido2ClientCreateCredentialTests()
{ {
@@ -37,22 +39,33 @@ namespace Bit.Core.Test.Services
Alg = (int) Fido2AlgorithmIdentifier.ES256 Alg = (int) Fido2AlgorithmIdentifier.ES256
} }
}, },
Rp = new PublicKeyCredentialRpEntity { Rp = new PublicKeyCredentialRpEntity
{
Id = "bitwarden.com", Id = "bitwarden.com",
Name = "Bitwarden" Name = "Bitwarden"
}, },
User = new PublicKeyCredentialUserEntity { User = new PublicKeyCredentialUserEntity
{
Id = RandomBytes(32), Id = RandomBytes(32),
Name = "user@bitwarden.com", Name = "user@bitwarden.com",
DisplayName = "User" DisplayName = "User"
} }
}; };
_authenticatorResult = new Fido2AuthenticatorMakeCredentialResult
{
CredentialId = RandomBytes(32),
AttestationObject = RandomBytes(32),
AuthData = RandomBytes(32),
PublicKey = RandomBytes(32),
PublicKeyAlgorithm = (int)Fido2AlgorithmIdentifier.ES256,
};
_sutProvider.GetDependency<IStateService>().GetAutofillBlacklistedUrisAsync().Returns(Task.FromResult(new List<string>())); _sutProvider.GetDependency<IStateService>().GetAutofillBlacklistedUrisAsync().Returns(Task.FromResult(new List<string>()));
_sutProvider.GetDependency<IStateService>().IsAuthenticatedAsync().Returns(true); _sutProvider.GetDependency<IStateService>().IsAuthenticatedAsync().Returns(true);
} }
public void Dispose() public void Dispose()
{ {
} }
@@ -69,7 +82,7 @@ namespace Bit.Core.Test.Services
// Assert // Assert
Assert.Equal(Fido2ClientException.ErrorCode.NotAllowedError, exception.Code); Assert.Equal(Fido2ClientException.ErrorCode.NotAllowedError, exception.Code);
} }
[Fact] [Fact]
// Spec: If the length of options.user.id is not between 1 and 64 bytes (inclusive) then return a TypeError. // 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() public async Task CreateCredentialAsync_ThrowsTypeError_UserIdIsTooSmall()
@@ -198,16 +211,18 @@ namespace Bit.Core.Test.Services
public async Task CreateCredentialAsync_ReturnsNewCredential() public async Task CreateCredentialAsync_ReturnsNewCredential()
{ {
// Arrange // Arrange
_params.AuthenticatorSelection = new AuthenticatorSelectionCriteria { _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria
{
ResidentKey = "required", ResidentKey = "required",
UserVerification = "required" UserVerification = "required"
}; };
var authenticatorResult = new Fido2AuthenticatorMakeCredentialResult { var authenticatorResult = new Fido2AuthenticatorMakeCredentialResult
{
CredentialId = RandomBytes(32), CredentialId = RandomBytes(32),
AttestationObject = RandomBytes(32), AttestationObject = RandomBytes(32),
AuthData = RandomBytes(32), AuthData = RandomBytes(32),
PublicKey = RandomBytes(32), PublicKey = RandomBytes(32),
PublicKeyAlgorithm = (int) Fido2AlgorithmIdentifier.ES256, PublicKeyAlgorithm = (int)Fido2AlgorithmIdentifier.ES256,
}; };
_sutProvider.GetDependency<IFido2AuthenticatorService>() _sutProvider.GetDependency<IFido2AuthenticatorService>()
.MakeCredentialAsync(Arg.Any<Fido2AuthenticatorMakeCredentialParams>(), _sutProvider.GetDependency<IFido2MakeCredentialUserInterface>()) .MakeCredentialAsync(Arg.Any<Fido2AuthenticatorMakeCredentialParams>(), _sutProvider.GetDependency<IFido2MakeCredentialUserInterface>())
@@ -246,7 +261,8 @@ namespace Bit.Core.Test.Services
public async Task CreateCredentialAsync_ThrowsInvalidStateError_AuthenticatorThrowsInvalidStateError() public async Task CreateCredentialAsync_ThrowsInvalidStateError_AuthenticatorThrowsInvalidStateError()
{ {
// Arrange // Arrange
_params.AuthenticatorSelection = new AuthenticatorSelectionCriteria { _params.AuthenticatorSelection = new AuthenticatorSelectionCriteria
{
ResidentKey = "required", ResidentKey = "required",
UserVerification = "required" UserVerification = "required"
}; };
@@ -304,6 +320,66 @@ namespace Bit.Core.Test.Services
Assert.Equal(Fido2ClientException.ErrorCode.NotAllowedError, exception.Code); 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<IFido2AuthenticatorService>()
.MakeCredentialAsync(Arg.Any<Fido2AuthenticatorMakeCredentialParams>(), _sutProvider.GetDependency<IFido2MakeCredentialUserInterface>())
.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<IFido2AuthenticatorService>()
.MakeCredentialAsync(Arg.Any<Fido2AuthenticatorMakeCredentialParams>(), _sutProvider.GetDependency<IFido2MakeCredentialUserInterface>())
.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<IFido2AuthenticatorService>()
.MakeCredentialAsync(Arg.Any<Fido2AuthenticatorMakeCredentialParams>(), _sutProvider.GetDependency<IFido2MakeCredentialUserInterface>())
.Returns(_authenticatorResult);
// Act
var result = await _sutProvider.Sut.CreateCredentialAsync(_params);
// Assert
Assert.Null(result.Extensions.CredProps);
}
private byte[] RandomBytes(int length) private byte[] RandomBytes(int length)
{ {
var bytes = new byte[length]; var bytes = new byte[length];