1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-14 07:13:33 +00:00

[PM-5731] feat: add sameOriginWithAncestor and user id length checks

This commit is contained in:
Andreas Coroiu
2024-02-07 11:39:13 +01:00
parent 3223ceb9a8
commit ad8faec200
4 changed files with 128 additions and 1 deletions

View File

@@ -5,7 +5,25 @@ namespace Bit.Core.Services
{ {
public class Fido2ClientService : IFido2ClientService public class Fido2ClientService : IFido2ClientService
{ {
public Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams) => throw new NotImplementedException(); public Task<Fido2ClientCreateCredentialResult> CreateCredentialAsync(Fido2ClientCreateCredentialParams createCredentialParams)
{
if (!createCredentialParams.SameOriginWithAncestors)
{
throw new Fido2ClientException(
Fido2ClientException.ErrorCode.NotAllowedError,
"Credential creation is now allowed from embedded contexts with different origins.");
}
if (createCredentialParams.User.Id.Length < 1 || createCredentialParams.User.Id.Length > 64)
{
// TODO: Should we use ArgumentException here instead?
throw new Fido2ClientException(
Fido2ClientException.ErrorCode.TypeError,
"The length of user.id is not between 1 and 64 bytes (inclusive).");
}
throw new NotImplementedException();
}
public Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams) => throw new NotImplementedException(); public Task<Fido2ClientAssertCredentialResult> AssertCredentialAsync(Fido2ClientAssertCredentialParams assertCredentialParams) => throw new NotImplementedException();
} }

View File

@@ -12,6 +12,7 @@ namespace Bit.Core.Utilities.Fido2
/// </summary> /// </summary>
public required string Origin { get; set; } public required string Origin { get; set; }
// TODO: Check if we actually need this
/// <summary> /// <summary>
/// A value which is true if and only if the callers environment settings object is same-origin with its ancestors. /// A value which is true if and only if the callers environment settings object is same-origin with its ancestors.
/// It is false if caller is cross-origin. /// It is false if caller is cross-origin.

View File

@@ -0,0 +1,22 @@
namespace Bit.Core.Utilities.Fido2
{
public class Fido2ClientException : Exception
{
public enum ErrorCode
{
NotAllowedError,
TypeError,
SecurityError,
UnknownError
}
public readonly ErrorCode Code;
public readonly string Reason;
public Fido2ClientException(ErrorCode code, string reason) : base($"{code} ({reason})")
{
Code = code;
Reason = reason;
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Services;
using Bit.Core.Utilities.Fido2;
using Bit.Test.Common.AutoFixture;
using Xunit;
namespace Bit.Core.Test.Services
{
public class Fido2ClientCreateCredentialTests : IDisposable
{
private readonly SutProvider<Fido2ClientService> _sutProvider = new SutProvider<Fido2ClientService>().Create();
private Fido2ClientCreateCredentialParams _params;
public Fido2ClientCreateCredentialTests()
{
_params = new Fido2ClientCreateCredentialParams {
Origin = "https://bitwarden.com",
SameOriginWithAncestors = true,
Attestation = "none",
Challenge = RandomBytes(32),
PubKeyCredParams = [
new PublicKeyCredentialParameters {
Type = "public-key",
Alg = -7
}
],
Rp = new PublicKeyCredentialRpEntity {
Id = "bitwarden.com",
Name = "Bitwarden"
},
User = new PublicKeyCredentialUserEntity {
Id = RandomBytes(32),
Name = "user@bitwarden.com",
DisplayName = "User"
}
};
}
public void Dispose()
{
}
[Fact]
// Spec: If sameOriginWithAncestors is false, return a "NotAllowedError" DOMException.
public async Task CreateCredentialAsync_ThrowsNotAllowedError_SameOriginWithAncestorsIsFalse()
{
_params.SameOriginWithAncestors = false;
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
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()
{
_params.User.Id = RandomBytes(0);
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
Assert.Equal(Fido2ClientException.ErrorCode.TypeError, 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_UserIdIsTooLarge()
{
_params.User.Id = RandomBytes(65);
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
Assert.Equal(Fido2ClientException.ErrorCode.TypeError, exception.Code);
}
private byte[] RandomBytes(int length)
{
var bytes = new byte[length];
new Random().NextBytes(bytes);
return bytes;
}
}
}