mirror of
https://github.com/bitwarden/mobile
synced 2026-02-23 16:12:57 +00:00
[PM-7576] Implemented digital asset links verification on Fido2 flows (#3191)
* PM-7553 Fix native apps passkeys autofill and creation * PM-7658 Implemented Fido2 priviliged apps verification * PM-7576 Implemented digital asset links verification on Fido2 flows for native apps. * PM-7576 Renamed to ValidateAssetLinksAndGetOriginAsync to go along with Google naming and also changed method to private given that public is not necessary * PM-7576 Moved digital asset links verification to a Core service AssetLinksService and added unit tests for it.
This commit is contained in:
committed by
GitHub
parent
ea098c92d3
commit
299899f952
165
test/Core.Test/Services/AssetLinksServiceTest.cs
Normal file
165
test/Core.Test/Services/AssetLinksServiceTest.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities.DigitalAssetLinks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Newtonsoft.Json;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services
|
||||
{
|
||||
public class AssetLinksServiceTest : IDisposable
|
||||
{
|
||||
private readonly SutProvider<AssetLinksService> _sutProvider = new SutProvider<AssetLinksService>().Create();
|
||||
|
||||
private readonly string _validRpId = "example.com";
|
||||
private readonly string _validPackageName = "com.example.app";
|
||||
private readonly string _validFingerprint = "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00";
|
||||
|
||||
private List<Statement> Deserialize(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<List<Statement>>(json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_True_When_Data_Has_One_Statement_And_One_Fingerprint()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementOneFingerprintJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.True(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_True_When_Data_Has_One_Statement_And_Multiple_Fingerprints()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementMultipleFingerprintsJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.True(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_True_When_Data_Has_Multiple_Statements()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.MultipleStatementsJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.True(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Statement_Has_No_GetLoginCreds_Relation()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementNoGetLoginCredsRelationJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Statement_Has_No_HandleAllUrls_Relation()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementNoHandleAllUrlsRelationJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Statement_Has_Wrong_Namespace()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementWrongNamespaceJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Statement_Has_No_Fingerprints()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementNoFingerprintsJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_PackageName_Doesnt_Match()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementOneFingerprintJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, "com.foo.another", _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Fingerprint_Doesnt_Match()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementOneFingerprintJson())));
|
||||
|
||||
// Act
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint.Replace("00", "33"));
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
public void Dispose() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
public static class BasicAssetLinksTestData
|
||||
{
|
||||
#region Valid statements
|
||||
|
||||
public static string OneStatementOneFingerprintJson()
|
||||
{
|
||||
return
|
||||
"""
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds",
|
||||
"delegate_permission/common.handle_all_urls"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.example.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
""";
|
||||
}
|
||||
|
||||
public static string OneStatementMultipleFingerprintsJson()
|
||||
{
|
||||
return
|
||||
"""
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds",
|
||||
"delegate_permission/common.handle_all_urls"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.example.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01",
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:02"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
""";
|
||||
}
|
||||
|
||||
public static string MultipleStatementsJson()
|
||||
{
|
||||
return
|
||||
"""
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds",
|
||||
"delegate_permission/common.handle_all_urls"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.example.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01",
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:02"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds",
|
||||
"delegate_permission/common.handle_all_urls"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.foo.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"10:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
|
||||
"10:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01",
|
||||
"10:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:02"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
""";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Invalid statements
|
||||
|
||||
public static string OneStatementNoGetLoginCredsRelationJson()
|
||||
{
|
||||
return
|
||||
"""
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.handle_all_urls"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.example.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
""";
|
||||
}
|
||||
|
||||
public static string OneStatementNoHandleAllUrlsRelationJson()
|
||||
{
|
||||
return
|
||||
"""
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.example.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
""";
|
||||
}
|
||||
|
||||
public static string OneStatementWrongNamespaceJson()
|
||||
{
|
||||
return
|
||||
"""
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds",
|
||||
"delegate_permission/common.handle_all_urls"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "NOT_android_app",
|
||||
"package_name": "com.example.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
""";
|
||||
}
|
||||
|
||||
public static string OneStatementNoFingerprintsJson()
|
||||
{
|
||||
return
|
||||
"""
|
||||
[
|
||||
{
|
||||
"relation": [
|
||||
"delegate_permission/common.get_login_creds",
|
||||
"delegate_permission/common.handle_all_urls"
|
||||
],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.example.app",
|
||||
"sha256_cert_fingerprints": []
|
||||
}
|
||||
}
|
||||
]
|
||||
""";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace Bit.Core.Test.Services
|
||||
_authenticatorResult = new Fido2AuthenticatorGetAssertionResult
|
||||
{
|
||||
AuthenticatorData = RandomBytes(32),
|
||||
SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential
|
||||
SelectedCredential = new Fido2SelectedCredential
|
||||
{
|
||||
Id = RandomBytes(16),
|
||||
UserHandle = RandomBytes(32)
|
||||
@@ -74,7 +74,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.Origin = "invalid-domain-name";
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.SecurityError, exception.Code);
|
||||
@@ -90,7 +90,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.RpId = "bitwarden.com";
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.SecurityError, exception.Code);
|
||||
@@ -105,7 +105,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.RpId = "bitwarden.com";
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.SecurityError, exception.Code);
|
||||
@@ -124,7 +124,7 @@ namespace Bit.Core.Test.Services
|
||||
}));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.UriBlockedError, exception.Code);
|
||||
@@ -139,7 +139,7 @@ namespace Bit.Core.Test.Services
|
||||
.Throws(new InvalidStateError());
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.InvalidStateError, exception.Code);
|
||||
@@ -155,7 +155,7 @@ namespace Bit.Core.Test.Services
|
||||
.Throws(new Exception("unknown error"));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.UnknownError, exception.Code);
|
||||
@@ -168,7 +168,7 @@ namespace Bit.Core.Test.Services
|
||||
_sutProvider.GetDependency<IStateService>().IsAuthenticatedAsync().Returns(false);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.InvalidStateError, exception.Code);
|
||||
@@ -182,7 +182,7 @@ namespace Bit.Core.Test.Services
|
||||
_sutProvider.GetDependency<IEnvironmentService>().GetWebVaultUrl().Returns("https://vault.bitwarden.com");
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.NotAllowedError, exception.Code);
|
||||
@@ -201,7 +201,7 @@ namespace Bit.Core.Test.Services
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
await _sutProvider.Sut.AssertCredentialAsync(_params);
|
||||
await _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams());
|
||||
|
||||
// Assert
|
||||
await _sutProvider.GetDependency<IFido2AuthenticatorService>().Received()
|
||||
@@ -216,12 +216,13 @@ namespace Bit.Core.Test.Services
|
||||
{
|
||||
// Arrange
|
||||
var mockHash = RandomBytes(32);
|
||||
var extraParams = new Fido2ExtraAssertCredentialParams(mockHash, null);
|
||||
_sutProvider.GetDependency<IFido2AuthenticatorService>()
|
||||
.GetAssertionAsync(Arg.Any<Fido2AuthenticatorGetAssertionParams>(), _sutProvider.GetDependency<IFido2GetAssertionUserInterface>())
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
await _sutProvider.Sut.AssertCredentialAsync(_params, mockHash);
|
||||
await _sutProvider.Sut.AssertCredentialAsync(_params, extraParams);
|
||||
|
||||
// Assert
|
||||
await _sutProvider.GetDependency<IFido2AuthenticatorService>().Received()
|
||||
@@ -241,7 +242,7 @@ namespace Bit.Core.Test.Services
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
var result = await _sutProvider.Sut.AssertCredentialAsync(_params);
|
||||
var result = await _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams());
|
||||
|
||||
// Assert
|
||||
await _sutProvider.GetDependency<IFido2AuthenticatorService>()
|
||||
@@ -268,6 +269,45 @@ namespace Bit.Core.Test.Services
|
||||
Assert.Equal(!_params.SameOriginWithAncestors, clientDataJSON["crossOrigin"].GetValue<bool>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssertCredentialAsync_ReturnsAssertionWithAndroidPackage()
|
||||
{
|
||||
// Arrange
|
||||
var packageName = "com.example.app";
|
||||
_params.UserVerification = "required";
|
||||
_sutProvider.GetDependency<IFido2AuthenticatorService>()
|
||||
.GetAssertionAsync(Arg.Any<Fido2AuthenticatorGetAssertionParams>(), _sutProvider.GetDependency<IFido2GetAssertionUserInterface>())
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
var result = await _sutProvider.Sut.AssertCredentialAsync(_params, new Fido2ExtraAssertCredentialParams(null, packageName));
|
||||
|
||||
// Assert
|
||||
await _sutProvider.GetDependency<IFido2AuthenticatorService>()
|
||||
.Received()
|
||||
.GetAssertionAsync(
|
||||
Arg.Is<Fido2AuthenticatorGetAssertionParams>(x =>
|
||||
x.RpId == _params.RpId &&
|
||||
x.UserVerificationPreference == Fido2UserVerificationPreference.Required &&
|
||||
x.AllowCredentialDescriptorList.Length == 1 &&
|
||||
x.AllowCredentialDescriptorList[0].Id == _params.AllowCredentials[0].Id
|
||||
),
|
||||
_sutProvider.GetDependency<IFido2GetAssertionUserInterface>()
|
||||
);
|
||||
|
||||
Assert.Equal(_authenticatorResult.SelectedCredential.Id, result.RawId);
|
||||
Assert.Equal(CoreHelpers.Base64UrlEncode(_authenticatorResult.SelectedCredential.Id), result.Id);
|
||||
Assert.Equal(_authenticatorResult.AuthenticatorData, result.AuthenticatorData);
|
||||
Assert.Equal(_authenticatorResult.Signature, result.Signature);
|
||||
|
||||
var clientDataJSON = JsonSerializer.Deserialize<JsonObject>(Encoding.UTF8.GetString(result.ClientDataJSON));
|
||||
Assert.Equal("webauthn.get", clientDataJSON["type"].GetValue<string>());
|
||||
Assert.Equal(CoreHelpers.Base64UrlEncode(_params.Challenge), clientDataJSON["challenge"].GetValue<string>());
|
||||
Assert.Equal(_params.Origin, clientDataJSON["origin"].GetValue<string>());
|
||||
Assert.Equal(!_params.SameOriginWithAncestors, clientDataJSON["crossOrigin"].GetValue<bool>());
|
||||
Assert.Equal(packageName, clientDataJSON["androidPackageName"].GetValue<string>());
|
||||
}
|
||||
|
||||
private byte[] RandomBytes(int length)
|
||||
{
|
||||
var bytes = new byte[length];
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.SameOriginWithAncestors = false;
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.NotAllowedError, exception.Code);
|
||||
@@ -92,7 +92,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.User.Id = RandomBytes(0);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.TypeError, exception.Code);
|
||||
@@ -106,7 +106,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.User.Id = RandomBytes(65);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.TypeError, exception.Code);
|
||||
@@ -125,7 +125,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.Origin = "invalid-domain-name";
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.SecurityError, exception.Code);
|
||||
@@ -141,7 +141,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.Rp.Id = "bitwarden.com";
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.SecurityError, exception.Code);
|
||||
@@ -156,7 +156,7 @@ namespace Bit.Core.Test.Services
|
||||
_params.Rp.Id = "bitwarden.com";
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.SecurityError, exception.Code);
|
||||
@@ -174,7 +174,7 @@ namespace Bit.Core.Test.Services
|
||||
}));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.UriBlockedError, exception.Code);
|
||||
@@ -198,7 +198,7 @@ namespace Bit.Core.Test.Services
|
||||
};
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.NotSupportedError, exception.Code);
|
||||
@@ -230,7 +230,7 @@ namespace Bit.Core.Test.Services
|
||||
.Returns(authenticatorResult);
|
||||
|
||||
// Act
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params);
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams());
|
||||
|
||||
// Assert
|
||||
await _sutProvider.GetDependency<IFido2AuthenticatorService>()
|
||||
@@ -258,6 +258,58 @@ namespace Bit.Core.Test.Services
|
||||
Assert.Equal(!_params.SameOriginWithAncestors, clientDataJSON["crossOrigin"].GetValue<bool>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateCredentialAsync_ReturnsNewCredential_WithAndroidPackageName()
|
||||
{
|
||||
// Arrange
|
||||
_params.AuthenticatorSelection = new AuthenticatorSelectionCriteria
|
||||
{
|
||||
ResidentKey = "required",
|
||||
UserVerification = "required"
|
||||
};
|
||||
var authenticatorResult = new Fido2AuthenticatorMakeCredentialResult
|
||||
{
|
||||
CredentialId = RandomBytes(32),
|
||||
AttestationObject = RandomBytes(32),
|
||||
AuthData = RandomBytes(32),
|
||||
PublicKey = RandomBytes(32),
|
||||
PublicKeyAlgorithm = (int)Fido2AlgorithmIdentifier.ES256,
|
||||
};
|
||||
_sutProvider.GetDependency<IFido2AuthenticatorService>()
|
||||
.MakeCredentialAsync(Arg.Any<Fido2AuthenticatorMakeCredentialParams>(), _sutProvider.GetDependency<IFido2MakeCredentialUserInterface>())
|
||||
.Returns(authenticatorResult);
|
||||
var packageName = "com.example.app";
|
||||
|
||||
// Act
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams(null, packageName));
|
||||
|
||||
// Assert
|
||||
await _sutProvider.GetDependency<IFido2AuthenticatorService>()
|
||||
.Received()
|
||||
.MakeCredentialAsync(
|
||||
Arg.Is<Fido2AuthenticatorMakeCredentialParams>(x =>
|
||||
x.RequireResidentKey == true &&
|
||||
x.UserVerificationPreference == Fido2UserVerificationPreference.Required &&
|
||||
x.RpEntity.Id == _params.Rp.Id &&
|
||||
x.UserEntity.DisplayName == _params.User.DisplayName
|
||||
),
|
||||
_sutProvider.GetDependency<IFido2MakeCredentialUserInterface>()
|
||||
);
|
||||
Assert.Equal(authenticatorResult.CredentialId, result.CredentialId);
|
||||
Assert.Equal(authenticatorResult.AttestationObject, result.AttestationObject);
|
||||
Assert.Equal(authenticatorResult.AuthData, result.AuthData);
|
||||
Assert.Equal(authenticatorResult.PublicKey, result.PublicKey);
|
||||
Assert.Equal(authenticatorResult.PublicKeyAlgorithm, result.PublicKeyAlgorithm);
|
||||
Assert.Equal(new string[] { "internal" }, result.Transports);
|
||||
|
||||
var clientDataJSON = JsonSerializer.Deserialize<JsonObject>(Encoding.UTF8.GetString(result.ClientDataJSON));
|
||||
Assert.Equal("webauthn.create", clientDataJSON["type"].GetValue<string>());
|
||||
Assert.Equal(CoreHelpers.Base64UrlEncode(_params.Challenge), clientDataJSON["challenge"].GetValue<string>());
|
||||
Assert.Equal(_params.Origin, clientDataJSON["origin"].GetValue<string>());
|
||||
Assert.Equal(!_params.SameOriginWithAncestors, clientDataJSON["crossOrigin"].GetValue<bool>());
|
||||
Assert.Equal(packageName, clientDataJSON["androidPackageName"].GetValue<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateCredentialAsync_ThrowsInvalidStateError_AuthenticatorThrowsInvalidStateError()
|
||||
{
|
||||
@@ -272,7 +324,7 @@ namespace Bit.Core.Test.Services
|
||||
.Throws(new InvalidStateError());
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.InvalidStateError, exception.Code);
|
||||
@@ -288,7 +340,7 @@ namespace Bit.Core.Test.Services
|
||||
.Throws(new Exception("unknown error"));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.UnknownError, exception.Code);
|
||||
@@ -301,7 +353,7 @@ namespace Bit.Core.Test.Services
|
||||
_sutProvider.GetDependency<IStateService>().IsAuthenticatedAsync().Returns(false);
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.InvalidStateError, exception.Code);
|
||||
@@ -315,14 +367,14 @@ namespace Bit.Core.Test.Services
|
||||
_sutProvider.GetDependency<IEnvironmentService>().GetWebVaultUrl().Returns("https://vault.bitwarden.com");
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params));
|
||||
var exception = await Assert.ThrowsAsync<Fido2ClientException>(() => _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams()));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Fido2ClientException.ErrorCode.NotAllowedError, exception.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssertCredentialAsync_ConstructsClientDataHash_WhenHashIsNotProvided()
|
||||
public async Task CreateCredentialAsync_ConstructsClientDataHash_WhenHashIsNotProvided()
|
||||
{
|
||||
// Arrange
|
||||
var mockHash = RandomBytes(32);
|
||||
@@ -334,18 +386,18 @@ namespace Bit.Core.Test.Services
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
await _sutProvider.Sut.CreateCredentialAsync(_params);
|
||||
await _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams());
|
||||
|
||||
// Assert
|
||||
await _sutProvider.GetDependency<IFido2AuthenticatorService>().Received()
|
||||
.GetAssertionAsync(
|
||||
Arg.Is((Fido2AuthenticatorGetAssertionParams x) => x.Hash == mockHash),
|
||||
Arg.Any<IFido2GetAssertionUserInterface>()
|
||||
.MakeCredentialAsync(
|
||||
Arg.Is((Fido2AuthenticatorMakeCredentialParams x) => x.Hash == mockHash),
|
||||
Arg.Any<IFido2MakeCredentialUserInterface>()
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssertCredentialAsync_UsesProvidedClientDataHash_WhenHashIsProvided()
|
||||
public async Task CreateCredentialAsync_UsesProvidedClientDataHash_WhenHashIsProvided()
|
||||
{
|
||||
// Arrange
|
||||
var mockHash = RandomBytes(32);
|
||||
@@ -354,13 +406,13 @@ namespace Bit.Core.Test.Services
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
await _sutProvider.Sut.CreateCredentialAsync(_params, mockHash);
|
||||
await _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams(mockHash));
|
||||
|
||||
// Assert
|
||||
await _sutProvider.GetDependency<IFido2AuthenticatorService>().Received()
|
||||
.GetAssertionAsync(
|
||||
Arg.Is((Fido2AuthenticatorGetAssertionParams x) => x.Hash == mockHash),
|
||||
Arg.Any<IFido2GetAssertionUserInterface>()
|
||||
.MakeCredentialAsync(
|
||||
Arg.Is((Fido2AuthenticatorMakeCredentialParams x) => x.Hash == mockHash),
|
||||
Arg.Any<IFido2MakeCredentialUserInterface>()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -378,7 +430,7 @@ namespace Bit.Core.Test.Services
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params);
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams());
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Extensions.CredProps?.Rk);
|
||||
@@ -398,7 +450,7 @@ namespace Bit.Core.Test.Services
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params);
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams());
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Extensions.CredProps?.Rk);
|
||||
@@ -418,7 +470,7 @@ namespace Bit.Core.Test.Services
|
||||
.Returns(_authenticatorResult);
|
||||
|
||||
// Act
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params);
|
||||
var result = await _sutProvider.Sut.CreateCredentialAsync(_params, new Fido2ExtraCreateCredentialParams());
|
||||
|
||||
// Assert
|
||||
Assert.Null(result.Extensions.CredProps);
|
||||
|
||||
Reference in New Issue
Block a user