mirror of
https://github.com/bitwarden/mobile
synced 2026-02-13 14:53:19 +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
@@ -10,6 +10,7 @@ using Bit.App.Abstractions;
|
||||
using Bit.App.Droid.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
using Bit.Core.Utilities.Fido2.Extensions;
|
||||
@@ -252,7 +253,7 @@ namespace Bit.App.Platforms.Android.Autofill
|
||||
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -262,7 +263,7 @@ namespace Bit.App.Platforms.Android.Autofill
|
||||
{
|
||||
if (callingAppInfo.Origin is null)
|
||||
{
|
||||
return await ValidateNativeAppAndGetOriginAsync(callingAppInfo, rpId);
|
||||
return await ValidateAssetLinksAndGetOriginAsync(callingAppInfo, rpId);
|
||||
}
|
||||
|
||||
var priviligedAllowedList = await LoadFido2PriviligedAllowedListAsync();
|
||||
@@ -285,10 +286,18 @@ namespace Bit.App.Platforms.Android.Autofill
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> ValidateNativeAppAndGetOriginAsync(CallingAppInfo callingAppInfo, string rpId)
|
||||
private static async Task<string> ValidateAssetLinksAndGetOriginAsync(CallingAppInfo callingAppInfo, string rpId)
|
||||
{
|
||||
// TODO: do asset links verification
|
||||
return callingAppInfo.GetAndroidOrigin();
|
||||
if (!ServiceContainer.TryResolve<IAssetLinksService>(out var assetLinksService))
|
||||
{
|
||||
throw new InvalidOperationException("Can't resolve IAssetLinksService");
|
||||
}
|
||||
|
||||
var normalizedFingerprint = callingAppInfo.GetLatestCertificationFingerprint();
|
||||
|
||||
var isValid = await assetLinksService.ValidateAssetLinksAsync(rpId, callingAppInfo.PackageName, normalizedFingerprint);
|
||||
|
||||
return isValid ? callingAppInfo.GetAndroidOrigin() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,19 @@ namespace Bit.App.Droid.Utilities
|
||||
var certHash = md.Digest(cert);
|
||||
return $"android:apk-key-hash:{CoreHelpers.Base64UrlEncode(certHash)}";
|
||||
}
|
||||
|
||||
public static string GetLatestCertificationFingerprint(this CallingAppInfo callingAppInfo)
|
||||
{
|
||||
if (callingAppInfo.SigningInfo.HasMultipleSigners)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var signature = callingAppInfo.SigningInfo.GetSigningCertificateHistory()[0].ToByteArray();
|
||||
var md = MessageDigest.GetInstance("SHA-256");
|
||||
var digestedSignature = md.Digest(signature);
|
||||
var normalizedFingerprint = string.Join(":", digestedSignature.Select(b => b.ToString("X2")));
|
||||
return normalizedFingerprint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Request;
|
||||
using Bit.Core.Models.Response;
|
||||
@@ -100,5 +95,6 @@ namespace Bit.Core.Abstractions
|
||||
Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
|
||||
Task<ConfigResponse> GetConfigsAsync();
|
||||
Task<string> GetFastmailAccountIdAsync(string apiKey);
|
||||
Task<List<Utilities.DigitalAssetLinks.Statement>> GetDigitalAssetLinksForRpAsync(string rpId);
|
||||
}
|
||||
}
|
||||
|
||||
7
src/Core/Abstractions/IAssetLinksService.cs
Normal file
7
src/Core/Abstractions/IAssetLinksService.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public interface IAssetLinksService
|
||||
{
|
||||
Task<bool> ValidateAssetLinksAsync(string rpId, string packageName, string normalizedFingerprint);
|
||||
}
|
||||
}
|
||||
@@ -839,6 +839,33 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Utilities.DigitalAssetLinks.Statement>> GetDigitalAssetLinksForRpAsync(string rpId)
|
||||
{
|
||||
using (var httpclient = new HttpClient())
|
||||
{
|
||||
HttpResponseMessage response;
|
||||
try
|
||||
{
|
||||
httpclient.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
response = await httpclient.GetAsync(new Uri($"https://{rpId}/.well-known/assetlinks.json"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApiException(HandleWebError(e));
|
||||
}
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new ApiException(new ErrorResponse
|
||||
{
|
||||
StatusCode = response.StatusCode,
|
||||
Message = $"Digital Asset links Rp error: {(int)response.StatusCode} {response.ReasonPhrase}."
|
||||
});
|
||||
}
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<List<Utilities.DigitalAssetLinks.Statement>>(json);
|
||||
}
|
||||
}
|
||||
|
||||
private ErrorResponse HandleWebError(Exception e)
|
||||
{
|
||||
return new ErrorResponse
|
||||
|
||||
35
src/Core/Services/AssetLinksService.cs
Normal file
35
src/Core/Services/AssetLinksService.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class AssetLinksService : IAssetLinksService
|
||||
{
|
||||
private readonly IApiService _apiService;
|
||||
|
||||
public AssetLinksService(IApiService apiService)
|
||||
{
|
||||
_apiService = apiService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the digital asset links file associated with the <paramref name="rpId"/> and
|
||||
/// validates that the <paramref name="packageName"/> and <paramref name="normalizedFingerprint"/> matches.
|
||||
/// </summary>
|
||||
/// <returns><c>True</c> if matches, <c>False</c> otherwise.</returns>
|
||||
public async Task<bool> ValidateAssetLinksAsync(string rpId, string packageName, string normalizedFingerprint)
|
||||
{
|
||||
var statementList = await _apiService.GetDigitalAssetLinksForRpAsync(rpId);
|
||||
|
||||
return statementList
|
||||
.Any(s => s.Target.Namespace == "android_app"
|
||||
&&
|
||||
s.Target.PackageName == packageName
|
||||
&&
|
||||
s.Relation.Contains("delegate_permission/common.get_login_creds")
|
||||
&&
|
||||
s.Relation.Contains("delegate_permission/common.handle_all_urls")
|
||||
&&
|
||||
s.Target.Sha256CertFingerprints.Contains(normalizedFingerprint));
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Core/Utilities/DigitalAssetLinks/Statement.cs
Normal file
8
src/Core/Utilities/DigitalAssetLinks/Statement.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Bit.Core.Utilities.DigitalAssetLinks
|
||||
{
|
||||
public class Statement
|
||||
{
|
||||
public IEnumerable<string> Relation { get; set; }
|
||||
public Target Target { get; set; }
|
||||
}
|
||||
}
|
||||
14
src/Core/Utilities/DigitalAssetLinks/Target.cs
Normal file
14
src/Core/Utilities/DigitalAssetLinks/Target.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Utilities.DigitalAssetLinks
|
||||
{
|
||||
public class Target
|
||||
{
|
||||
public string Namespace { get; set; }
|
||||
[JsonProperty("package_name")]
|
||||
public string PackageName { get; set; }
|
||||
[JsonProperty("sha256_cert_fingerprints")]
|
||||
public IEnumerable<string> Sha256CertFingerprints { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,9 @@ namespace Bit.Core.Utilities
|
||||
Register<IUsernameGenerationService>(usernameGenerationService);
|
||||
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
|
||||
Register<IPasswordResetEnrollmentService>(passwordResetEnrollmentService);
|
||||
#if ANDROID
|
||||
Register<IAssetLinksService>(new AssetLinksService(apiService));
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Register<T>(string serviceName, T obj)
|
||||
|
||||
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