Compare commits
No commits in common. "7fc1a04ffd76db73a504671ad576daed3fdb85ca" and "e780bdb7bebd46687189962ad2e98f9664c12432" have entirely different histories.
7fc1a04ffd
...
e780bdb7be
|
@ -266,6 +266,6 @@
|
|||
<BundleResource Include="Platforms\iOS\PrivacyInfo.xcprivacy" LogicalName="PrivacyInfo.xcprivacy" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
|
||||
<MauiAsset Include="Resources\Raw\fido2_privileged_allow_list.json" LogicalName="fido2_privileged_allow_list.json" />
|
||||
<MauiAsset Include="Resources\Raw\fido2_priviliged_allow_list.json" LogicalName="fido2_priviliged_allow_list.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Nodes;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
|
@ -84,27 +83,23 @@ namespace Bit.App.Platforms.Android.Autofill
|
|||
|
||||
if (callingRequest is null)
|
||||
{
|
||||
await DisplayAlertAsync(AppResources.AnErrorHasOccurred, string.Empty);
|
||||
if (ServiceContainer.TryResolve<IDeviceActionService>(out var deviceActionService))
|
||||
{
|
||||
await deviceActionService.DisplayAlertAsync(AppResources.ErrorCreatingPasskey, string.Empty, AppResources.Ok);
|
||||
}
|
||||
FailAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
var credentialCreationOptions = GetPublicKeyCredentialCreationOptionsFromJson(callingRequest.RequestJson);
|
||||
string origin;
|
||||
try
|
||||
{
|
||||
origin = await ValidateCallingAppInfoAndGetOriginAsync(getRequest.CallingAppInfo, credentialCreationOptions.Rp.Id);
|
||||
}
|
||||
catch (Core.Exceptions.ValidationException valEx)
|
||||
{
|
||||
await DisplayAlertAsync(AppResources.AnErrorHasOccurred, valEx.Message);
|
||||
FailAndFinish();
|
||||
return;
|
||||
}
|
||||
var origin = await ValidateCallingAppInfoAndGetOriginAsync(getRequest.CallingAppInfo, credentialCreationOptions.Rp.Id);
|
||||
|
||||
if (origin is null)
|
||||
{
|
||||
await DisplayAlertAsync(AppResources.ErrorCreatingPasskey, AppResources.PasskeysNotSupportedForThisApp);
|
||||
if (ServiceContainer.TryResolve<IDeviceActionService>(out var deviceActionService))
|
||||
{
|
||||
await deviceActionService.DisplayAlertAsync(AppResources.ErrorCreatingPasskey, AppResources.PasskeysNotSupportedForThisApp, AppResources.Ok);
|
||||
}
|
||||
FailAndFinish();
|
||||
return;
|
||||
}
|
||||
|
@ -207,14 +202,6 @@ namespace Bit.App.Platforms.Android.Autofill
|
|||
activity.SetResult(Result.Ok, result);
|
||||
activity.Finish();
|
||||
|
||||
async Task DisplayAlertAsync(string title, string message)
|
||||
{
|
||||
if (ServiceContainer.TryResolve<IDeviceActionService>(out var deviceActionService))
|
||||
{
|
||||
await deviceActionService.DisplayAlertAsync(title, message, AppResources.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
void FailAndFinish()
|
||||
{
|
||||
var result = new Intent();
|
||||
|
@ -257,11 +244,11 @@ namespace Bit.App.Platforms.Android.Autofill
|
|||
return extensionsJson;
|
||||
}
|
||||
|
||||
public static async Task<string> LoadFido2PrivilegedAllowedListAsync()
|
||||
public static async Task<string> LoadFido2PriviligedAllowedListAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = await FileSystem.OpenAppPackageFileAsync("fido2_privileged_allow_list.json");
|
||||
using var stream = await FileSystem.OpenAppPackageFileAsync("fido2_priviliged_allow_list.json");
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
return reader.ReadToEnd();
|
||||
|
@ -279,24 +266,19 @@ namespace Bit.App.Platforms.Android.Autofill
|
|||
return await ValidateAssetLinksAndGetOriginAsync(callingAppInfo, rpId);
|
||||
}
|
||||
|
||||
var privilegedAllowedList = await LoadFido2PrivilegedAllowedListAsync();
|
||||
if (privilegedAllowedList is null)
|
||||
var priviligedAllowedList = await LoadFido2PriviligedAllowedListAsync();
|
||||
if (priviligedAllowedList is null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not load Fido2 privileged allowed list");
|
||||
}
|
||||
|
||||
if (!privilegedAllowedList.Contains($"\"package_name\": \"{callingAppInfo.PackageName}\""))
|
||||
{
|
||||
throw new Core.Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseBrowserIsNotPrivileged);
|
||||
throw new InvalidOperationException("Could not load Fido2 priviliged allowed list");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return callingAppInfo.GetOrigin(privilegedAllowedList);
|
||||
return callingAppInfo.GetOrigin(priviligedAllowedList);
|
||||
}
|
||||
catch (Java.Lang.IllegalStateException)
|
||||
{
|
||||
throw new Core.Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseBrowserSignatureDoesNotMatch);
|
||||
return null; // not priviliged
|
||||
}
|
||||
catch (Java.Lang.IllegalArgumentException)
|
||||
{
|
||||
|
|
|
@ -77,18 +77,7 @@ namespace Bit.Droid.Autofill
|
|||
|
||||
var packageName = getRequest.CallingAppInfo.PackageName;
|
||||
|
||||
string origin;
|
||||
try
|
||||
{
|
||||
origin = await CredentialHelpers.ValidateCallingAppInfoAndGetOriginAsync(getRequest.CallingAppInfo, RpId);
|
||||
}
|
||||
catch (Core.Exceptions.ValidationException valEx)
|
||||
{
|
||||
await _deviceActionService.Value.DisplayAlertAsync(AppResources.AnErrorHasOccurred, valEx.Message, AppResources.Ok);
|
||||
FailAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = await CredentialHelpers.ValidateCallingAppInfoAndGetOriginAsync(getRequest.CallingAppInfo, RpId);
|
||||
if (origin is null)
|
||||
{
|
||||
await _deviceActionService.Value.DisplayAlertAsync(AppResources.ErrorReadingPasskey, AppResources.PasskeysNotSupportedForThisApp, AppResources.Ok);
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
namespace Bit.Core.Exceptions
|
||||
{
|
||||
public class ValidationException : Exception
|
||||
{
|
||||
public ValidationException(string localizedMessage)
|
||||
: base(localizedMessage)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5263,51 +5263,6 @@ namespace Bit.Core.Resources.Localization {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passkey operation failed because app could not be verified.
|
||||
/// </summary>
|
||||
public static string PasskeyOperationFailedBecauseAppCouldNotBeVerified {
|
||||
get {
|
||||
return ResourceManager.GetString("PasskeyOperationFailedBecauseAppCouldNotBeVerified", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passkey operation failed because app not found in asset links.
|
||||
/// </summary>
|
||||
public static string PasskeyOperationFailedBecauseAppNotFoundInAssetLinks {
|
||||
get {
|
||||
return ResourceManager.GetString("PasskeyOperationFailedBecauseAppNotFoundInAssetLinks", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passkey operation failed because browser is not privileged.
|
||||
/// </summary>
|
||||
public static string PasskeyOperationFailedBecauseBrowserIsNotPrivileged {
|
||||
get {
|
||||
return ResourceManager.GetString("PasskeyOperationFailedBecauseBrowserIsNotPrivileged", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passkey operation failed because browser signature does not match.
|
||||
/// </summary>
|
||||
public static string PasskeyOperationFailedBecauseBrowserSignatureDoesNotMatch {
|
||||
get {
|
||||
return ResourceManager.GetString("PasskeyOperationFailedBecauseBrowserSignatureDoesNotMatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passkey operation failed because of missing asset links.
|
||||
/// </summary>
|
||||
public static string PasskeyOperationFailedBecauseOfMissingAssetLinks {
|
||||
get {
|
||||
return ResourceManager.GetString("PasskeyOperationFailedBecauseOfMissingAssetLinks", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Passkeys.
|
||||
/// </summary>
|
||||
|
|
|
@ -2990,19 +2990,4 @@ Do you want to switch to this account?</value>
|
|||
<data name="PasskeysNotSupportedForThisApp" xml:space="preserve">
|
||||
<value>Passkeys not supported for this app</value>
|
||||
</data>
|
||||
<data name="PasskeyOperationFailedBecauseBrowserIsNotPrivileged" xml:space="preserve">
|
||||
<value>Passkey operation failed because browser is not privileged</value>
|
||||
</data>
|
||||
<data name="PasskeyOperationFailedBecauseBrowserSignatureDoesNotMatch" xml:space="preserve">
|
||||
<value>Passkey operation failed because browser signature does not match</value>
|
||||
</data>
|
||||
<data name="PasskeyOperationFailedBecauseOfMissingAssetLinks" xml:space="preserve">
|
||||
<value>Passkey operation failed because of missing asset links</value>
|
||||
</data>
|
||||
<data name="PasskeyOperationFailedBecauseAppNotFoundInAssetLinks" xml:space="preserve">
|
||||
<value>Passkey operation failed because app not found in asset links</value>
|
||||
</data>
|
||||
<data name="PasskeyOperationFailedBecauseAppCouldNotBeVerified" xml:space="preserve">
|
||||
<value>Passkey operation failed because app could not be verified</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
|
@ -19,35 +18,18 @@ namespace Bit.Core.Services
|
|||
/// <returns><c>True</c> if matches, <c>False</c> otherwise.</returns>
|
||||
public async Task<bool> ValidateAssetLinksAsync(string rpId, string packageName, string normalizedFingerprint)
|
||||
{
|
||||
try
|
||||
{
|
||||
var statementList = await _apiService.GetDigitalAssetLinksForRpAsync(rpId);
|
||||
var statementList = await _apiService.GetDigitalAssetLinksForRpAsync(rpId);
|
||||
|
||||
var androidAppPackageStatements = statementList
|
||||
.Where(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"));
|
||||
|
||||
if (!androidAppPackageStatements.Any())
|
||||
{
|
||||
throw new Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseAppNotFoundInAssetLinks);
|
||||
}
|
||||
|
||||
if (!androidAppPackageStatements.Any(s => s.Target.Sha256CertFingerprints.Contains(normalizedFingerprint)))
|
||||
{
|
||||
throw new Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseAppCouldNotBeVerified);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exceptions.ApiException)
|
||||
{
|
||||
throw new Exceptions.ValidationException(AppResources.PasskeyOperationFailedBecauseOfMissingAssetLinks);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities.DigitalAssetLinks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
|
@ -72,7 +71,7 @@ namespace Bit.Core.Test.Services
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Throws_When_Data_Statement_Has_No_GetLoginCreds_Relation()
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Statement_Has_No_GetLoginCreds_Relation()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
|
@ -80,14 +79,14 @@ namespace Bit.Core.Test.Services
|
|||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementNoGetLoginCredsRelationJson())));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Exceptions.ValidationException>(() => _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint));
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AppResources.PasskeyOperationFailedBecauseAppNotFoundInAssetLinks, exception.Message);
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Throws_When_Data_Statement_Has_No_HandleAllUrls_Relation()
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Statement_Has_No_HandleAllUrls_Relation()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
|
@ -95,14 +94,14 @@ namespace Bit.Core.Test.Services
|
|||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementNoHandleAllUrlsRelationJson())));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Exceptions.ValidationException>(() => _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint));
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AppResources.PasskeyOperationFailedBecauseAppNotFoundInAssetLinks, exception.Message);
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Throws_When_Data_Statement_Has_Wrong_Namespace()
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Statement_Has_Wrong_Namespace()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
|
@ -110,14 +109,14 @@ namespace Bit.Core.Test.Services
|
|||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementWrongNamespaceJson())));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Exceptions.ValidationException>(() => _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint));
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AppResources.PasskeyOperationFailedBecauseAppNotFoundInAssetLinks, exception.Message);
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Throws_When_Data_Statement_Has_No_Fingerprints()
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_Statement_Has_No_Fingerprints()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
|
@ -125,30 +124,14 @@ namespace Bit.Core.Test.Services
|
|||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementNoFingerprintsJson())));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Exceptions.ValidationException>(() => _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint));
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AppResources.PasskeyOperationFailedBecauseAppCouldNotBeVerified, exception.Message);
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Throws_When_Data_PackageName_Doesnt_Match()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
.GetDigitalAssetLinksForRpAsync(_validRpId)
|
||||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementOneFingerprintJson())));
|
||||
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Exceptions.ValidationException>(() => _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, "com.foo.another", _validFingerprint));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AppResources.PasskeyOperationFailedBecauseAppNotFoundInAssetLinks, exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAssetLinksAsync_Throws_When_Data_Fingerprint_Doesnt_Match()
|
||||
public async Task ValidateAssetLinksAsync_Returns_False_When_Data_PackageName_Doesnt_Match()
|
||||
{
|
||||
// Arrange
|
||||
_sutProvider.GetDependency<IApiService>()
|
||||
|
@ -156,10 +139,25 @@ namespace Bit.Core.Test.Services
|
|||
.Returns(Task.FromResult(Deserialize(BasicAssetLinksTestData.OneStatementOneFingerprintJson())));
|
||||
|
||||
// Act
|
||||
var exception = await Assert.ThrowsAsync<Exceptions.ValidationException>(() => _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, _validPackageName, _validFingerprint.Replace("00", "33")));
|
||||
var isValid = await _sutProvider.Sut.ValidateAssetLinksAsync(_validRpId, "com.foo.another", _validFingerprint);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AppResources.PasskeyOperationFailedBecauseAppCouldNotBeVerified, exception.Message);
|
||||
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() {}
|
||||
|
|
Loading…
Reference in New Issue