1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-10 21:33:36 +00:00

[PM-6848] Improved User verification on passkeys creation (#3099)

* PM-6848 Updated cancellation flow on passkey user verification and improved UV enforcement on creation

* PM-6848 Added null checks to help diagnosing if NRE is presented
This commit is contained in:
Federico Maccaroni
2024-03-21 13:28:14 -03:00
committed by GitHub
parent 6bec0ede05
commit ebc068d820
15 changed files with 202 additions and 90 deletions

View File

@@ -26,7 +26,7 @@ namespace Bit.Core.Abstractions
bool SupportsDuo(); bool SupportsDuo();
Task<bool> SupportsBiometricAsync(); Task<bool> SupportsBiometricAsync();
Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null); Task<bool> IsBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false); Task<bool?> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false);
long GetActiveTime(); long GetActiveTime();
} }
} }

View File

@@ -1,14 +1,28 @@
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Abstractions namespace Bit.Core.Abstractions
{ {
public interface IUserVerificationMediatorService public interface IUserVerificationMediatorService
{ {
Task<bool> VerifyUserForFido2Async(Fido2UserVerificationOptions options); Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options);
Task<bool> CanPerformUserVerificationPreferredAsync(Fido2UserVerificationOptions options); Task<bool> CanPerformUserVerificationPreferredAsync(Fido2UserVerificationOptions options);
Task<bool> ShouldPerformMasterPasswordRepromptAsync(Fido2UserVerificationOptions options); Task<bool> ShouldPerformMasterPasswordRepromptAsync(Fido2UserVerificationOptions options);
Task<(bool CanPerfom, bool IsUnlocked)> PerformOSUnlockAsync(); Task<bool> ShouldEnforceFido2RequiredUserVerificationAsync(Fido2UserVerificationOptions options);
Task<(bool canPerformUnlockWithPin, bool pinVerified)> VerifyPinCodeAsync(); Task<CancellableResult<UVResult>> PerformOSUnlockAsync();
Task<(bool canPerformUnlockWithMasterPassword, bool mpVerified)> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt); Task<CancellableResult<UVResult>> VerifyPinCodeAsync();
Task<CancellableResult<UVResult>> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt);
public struct UVResult
{
public UVResult(bool canPerform, bool isVerified)
{
CanPerform = canPerform;
IsVerified = isVerified;
}
public bool CanPerform { get; set; }
public bool IsVerified { get; set; }
}
} }
} }

View File

@@ -515,7 +515,7 @@ namespace Bit.App.Pages
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinEnabled ? AppResources.PIN : AppResources.MasterPassword, PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)), () => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)),
!PinEnabled && !HasMasterPassword); !PinEnabled && !HasMasterPassword) ?? false;
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)

View File

@@ -370,7 +370,7 @@ namespace Bit.App.Pages
if (!_supportsBiometric if (!_supportsBiometric
|| ||
!await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null)) await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null) != true)
{ {
_canUnlockWithBiometrics = false; _canUnlockWithBiometrics = false;
MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics))); MainThread.BeginInvokeOnMainThread(() => TriggerPropertyChanged(nameof(CanUnlockWithBiometrics)));

View File

@@ -74,7 +74,7 @@ namespace Bit.Core.Services
if (!userVerified if (!userVerified
&& &&
await ShouldEnforceRequiredUserVerificationAsync(new Fido2UserVerificationOptions( await _userVerificationMediatorService.ShouldEnforceFido2RequiredUserVerificationAsync(new Fido2UserVerificationOptions(
cipher.Reprompt != CipherRepromptType.None, cipher.Reprompt != CipherRepromptType.None,
makeCredentialParams.UserVerificationPreference, makeCredentialParams.UserVerificationPreference,
userInterface.HasVaultBeenUnlockedInThisTransaction))) userInterface.HasVaultBeenUnlockedInThisTransaction)))
@@ -158,7 +158,7 @@ namespace Bit.Core.Services
if (!userVerified if (!userVerified
&& &&
await ShouldEnforceRequiredUserVerificationAsync(new Fido2UserVerificationOptions( await _userVerificationMediatorService.ShouldEnforceFido2RequiredUserVerificationAsync(new Fido2UserVerificationOptions(
selectedCipher.Reprompt != CipherRepromptType.None, selectedCipher.Reprompt != CipherRepromptType.None,
assertionParams.UserVerificationPreference, assertionParams.UserVerificationPreference,
userInterface.HasVaultBeenUnlockedInThisTransaction))) userInterface.HasVaultBeenUnlockedInThisTransaction)))
@@ -458,19 +458,6 @@ namespace Bit.Core.Services
return dsa.SignData(sigBase, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence); return dsa.SignData(sigBase, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
} }
private async Task<bool> ShouldEnforceRequiredUserVerificationAsync(Fido2UserVerificationOptions options)
{
switch (options.UserVerificationPreference)
{
case Fido2UserVerificationPreference.Required:
return true;
case Fido2UserVerificationPreference.Discouraged:
return await _userVerificationMediatorService.ShouldPerformMasterPasswordRepromptAsync(options);
default:
return await _userVerificationMediatorService.CanPerformUserVerificationPreferredAsync(options);
}
}
private class PublicKey private class PublicKey
{ {
private readonly ECDsa _dsa; private readonly ECDsa _dsa;

View File

@@ -238,7 +238,7 @@ namespace Bit.App.Services
return await stateService.IsAccountBiometricIntegrityValidAsync(bioIntegritySrcKey); return await stateService.IsAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
} }
public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, public async Task<bool?> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false) Action fallback = null, bool logOutOnTooManyAttempts = false, bool allowAlternativeAuthentication = false)
{ {
try try
@@ -262,6 +262,10 @@ namespace Bit.App.Services
{ {
return true; return true;
} }
if (result.Status == FingerprintAuthenticationResultStatus.Canceled)
{
return null;
}
if (result.Status == FingerprintAuthenticationResultStatus.FallbackRequested) if (result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
{ {
fallback?.Invoke(); fallback?.Invoke();

View File

@@ -1,4 +1,5 @@
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Services.UserVerification namespace Bit.Core.Services.UserVerification
@@ -12,11 +13,11 @@ namespace Bit.Core.Services.UserVerification
_userVerificationMediatorService = userVerificationMediatorService; _userVerificationMediatorService = userVerificationMediatorService;
} }
public async Task<bool> VerifyUserForFido2Async(Fido2UserVerificationOptions options) public async Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options)
{ {
if (options.HasVaultBeenUnlockedInTransaction) if (options.HasVaultBeenUnlockedInTransaction)
{ {
return true; return new CancellableResult<bool>(true);
} }
if (options.OnNeedUITask != null) if (options.OnNeedUITask != null)
@@ -24,13 +25,17 @@ namespace Bit.Core.Services.UserVerification
await options.OnNeedUITask(); await options.OnNeedUITask();
} }
var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync(); var osUnlockVerification = await _userVerificationMediatorService.PerformOSUnlockAsync();
if (canPerformOSUnlock) if (osUnlockVerification.IsCancelled)
{ {
return isOSUnlocked; return new CancellableResult<bool>(false, true);
}
if (osUnlockVerification.Result.CanPerform)
{
return new CancellableResult<bool>(osUnlockVerification.Result.IsVerified);
} }
return false; return new CancellableResult<bool>(false);
} }
} }
} }

View File

@@ -1,5 +1,6 @@
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Services.UserVerification namespace Bit.Core.Services.UserVerification
@@ -16,11 +17,11 @@ namespace Bit.Core.Services.UserVerification
_platformUtilsService = platformUtilsService; _platformUtilsService = platformUtilsService;
} }
public async Task<bool> VerifyUserForFido2Async(Fido2UserVerificationOptions options) public async Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options)
{ {
if (options.HasVaultBeenUnlockedInTransaction) if (options.HasVaultBeenUnlockedInTransaction)
{ {
return true; return new CancellableResult<bool>(true);
} }
if (options.OnNeedUITask != null) if (options.OnNeedUITask != null)
@@ -28,22 +29,34 @@ namespace Bit.Core.Services.UserVerification
await options.OnNeedUITask(); await options.OnNeedUITask();
} }
var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync(); var osUnlockVerification = await _userVerificationMediatorService.PerformOSUnlockAsync();
if (canPerformOSUnlock) if (osUnlockVerification.IsCancelled)
{ {
return isOSUnlocked; return new CancellableResult<bool>(false, true);
}
if (osUnlockVerification.Result.CanPerform)
{
return new CancellableResult<bool>(osUnlockVerification.Result.IsVerified);
} }
var (canPerformUnlockWithPin, pinVerified) = await _userVerificationMediatorService.VerifyPinCodeAsync(); var pinVerification = await _userVerificationMediatorService.VerifyPinCodeAsync();
if (canPerformUnlockWithPin) if (pinVerification.IsCancelled)
{ {
return pinVerified; return new CancellableResult<bool>(false, true);
}
if (pinVerification.Result.CanPerform)
{
return new CancellableResult<bool>(pinVerification.Result.IsVerified);
} }
var (canPerformUnlockWithMasterPassword, mpVerified) = await _userVerificationMediatorService.VerifyMasterPasswordAsync(false); var mpVerification = await _userVerificationMediatorService.VerifyMasterPasswordAsync(false);
if (canPerformUnlockWithMasterPassword) if (mpVerification.IsCancelled)
{ {
return mpVerified; return new CancellableResult<bool>(false, true);
}
if (mpVerification.Result.CanPerform)
{
return new CancellableResult<bool>(mpVerification.Result.IsVerified);
} }
// TODO: Setup PIN code. For the sake of simplicity, we're not implementing this step now and just telling the user to do it in the main app. // TODO: Setup PIN code. For the sake of simplicity, we're not implementing this step now and just telling the user to do it in the main app.
@@ -52,7 +65,7 @@ namespace Bit.Core.Services.UserVerification
string.Format(AppResources.VerificationRequiredByX, options.RpId), string.Format(AppResources.VerificationRequiredByX, options.RpId),
AppResources.Ok); AppResources.Ok);
return false; return new CancellableResult<bool>(false);
} }
} }
} }

View File

@@ -1,9 +1,10 @@
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
namespace Bit.Core.Services.UserVerification namespace Bit.Core.Services.UserVerification
{ {
public interface IUserVerificationServiceStrategy public interface IUserVerificationServiceStrategy
{ {
Task<bool> VerifyUserForFido2Async(Fido2UserVerificationOptions options); Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options);
} }
} }

View File

@@ -2,8 +2,10 @@
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Resources.Localization; using Bit.Core.Resources.Localization;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities.Fido2;
using Plugin.Fingerprint; using Plugin.Fingerprint;
using static Bit.Core.Abstractions.IUserVerificationMediatorService;
using FingerprintAvailability = Plugin.Fingerprint.Abstractions.FingerprintAvailability; using FingerprintAvailability = Plugin.Fingerprint.Abstractions.FingerprintAvailability;
namespace Bit.Core.Services.UserVerification namespace Bit.Core.Services.UserVerification
@@ -37,7 +39,7 @@ namespace Bit.Core.Services.UserVerification
_fido2UserVerificationStrategies.Add(Fido2UserVerificationPreference.Preferred, new Fido2UserVerificationPreferredServiceStrategy(this)); _fido2UserVerificationStrategies.Add(Fido2UserVerificationPreference.Preferred, new Fido2UserVerificationPreferredServiceStrategy(this));
} }
public async Task<bool> VerifyUserForFido2Async(Fido2UserVerificationOptions options) public async Task<CancellableResult<bool>> VerifyUserForFido2Async(Fido2UserVerificationOptions options)
{ {
if (await ShouldPerformMasterPasswordRepromptAsync(options)) if (await ShouldPerformMasterPasswordRepromptAsync(options))
{ {
@@ -46,13 +48,16 @@ namespace Bit.Core.Services.UserVerification
await options.OnNeedUITask(); await options.OnNeedUITask();
} }
var (canPerformMP, mpVerified) = await VerifyMasterPasswordAsync(true); var mpVerification = await VerifyMasterPasswordAsync(true);
return canPerformMP && mpVerified; return new CancellableResult<bool>(
!mpVerification.IsCancelled && mpVerification.Result.CanPerform && mpVerification.Result.IsVerified,
mpVerification.IsCancelled
);
} }
if (!_fido2UserVerificationStrategies.TryGetValue(options.UserVerificationPreference, out var userVerificationServiceStrategy)) if (!_fido2UserVerificationStrategies.TryGetValue(options.UserVerificationPreference, out var userVerificationServiceStrategy))
{ {
return false; return new CancellableResult<bool>(false, false);
} }
return await userVerificationServiceStrategy.VerifyUserForFido2Async(options); return await userVerificationServiceStrategy.VerifyUserForFido2Async(options);
@@ -77,32 +82,53 @@ namespace Bit.Core.Services.UserVerification
return options.ShouldCheckMasterPasswordReprompt && !await _passwordRepromptService.ShouldByPassMasterPasswordRepromptAsync(); return options.ShouldCheckMasterPasswordReprompt && !await _passwordRepromptService.ShouldByPassMasterPasswordRepromptAsync();
} }
public async Task<(bool CanPerfom, bool IsUnlocked)> PerformOSUnlockAsync() public async Task<bool> ShouldEnforceFido2RequiredUserVerificationAsync(Fido2UserVerificationOptions options)
{
switch (options.UserVerificationPreference)
{
case Fido2UserVerificationPreference.Required:
return true;
case Fido2UserVerificationPreference.Discouraged:
return await ShouldPerformMasterPasswordRepromptAsync(options);
default:
return await CanPerformUserVerificationPreferredAsync(options);
}
}
public async Task<CancellableResult<UVResult>> PerformOSUnlockAsync()
{ {
var availability = await CrossFingerprint.Current.GetAvailabilityAsync(); var availability = await CrossFingerprint.Current.GetAvailabilityAsync();
if (availability == FingerprintAvailability.Available) if (availability == FingerprintAvailability.Available)
{ {
var isValid = await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null); var isValid = await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null);
return (true, isValid); if (!isValid.HasValue)
{
return new UVResult(false, false).AsCancellable(true);
}
return new UVResult(true, isValid.Value).AsCancellable();
} }
var alternativeAuthAvailability = await CrossFingerprint.Current.GetAvailabilityAsync(true); var alternativeAuthAvailability = await CrossFingerprint.Current.GetAvailabilityAsync(true);
if (alternativeAuthAvailability == FingerprintAvailability.Available) if (alternativeAuthAvailability == FingerprintAvailability.Available)
{ {
var isNonBioValid = await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null, allowAlternativeAuthentication: true); var isNonBioValid = await _platformUtilsService.AuthenticateBiometricAsync(null, DeviceInfo.Platform == DevicePlatform.Android ? "." : null, allowAlternativeAuthentication: true);
return (true, isNonBioValid); if (!isNonBioValid.HasValue)
{
return new UVResult(false, false).AsCancellable(true);
}
return new UVResult(true, isNonBioValid.Value).AsCancellable();
} }
return (false, false); return new UVResult(false, false).AsCancellable();
} }
public async Task<(bool canPerformUnlockWithPin, bool pinVerified)> VerifyPinCodeAsync() public async Task<CancellableResult<UVResult>> VerifyPinCodeAsync()
{ {
return await VerifyWithAttemptsAsync(async () => return await VerifyWithAttemptsAsync(async () =>
{ {
if (!await _userPinService.IsPinLockEnabledAsync()) if (!await _userPinService.IsPinLockEnabledAsync())
{ {
return (false, false); return new UVResult(false, false).AsCancellable();
} }
var pin = await _deviceActionService.DisplayPromptAync(AppResources.EnterPIN, var pin = await _deviceActionService.DisplayPromptAync(AppResources.EnterPIN,
@@ -110,59 +136,76 @@ namespace Bit.Core.Services.UserVerification
if (pin is null) if (pin is null)
{ {
// cancelled by the user // cancelled by the user
return (true, false); return new UVResult(true, false).AsCancellable(true);
} }
try try
{ {
var isVerified = await _userPinService.VerifyPinAsync(pin); var isVerified = await _userPinService.VerifyPinAsync(pin);
return (true, isVerified); return new UVResult(true, isVerified).AsCancellable();
} }
catch (SymmetricCryptoKey.ArgumentKeyNullException) catch (SymmetricCryptoKey.ArgumentKeyNullException)
{ {
return (true, false); return new UVResult(true, false).AsCancellable();
} }
catch (SymmetricCryptoKey.InvalidKeyOperationException) catch (SymmetricCryptoKey.InvalidKeyOperationException)
{ {
return (true, false); return new UVResult(true, false).AsCancellable();
} }
}); });
} }
public async Task<(bool canPerformUnlockWithMasterPassword, bool mpVerified)> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt) public async Task<CancellableResult<UVResult>> VerifyMasterPasswordAsync(bool isMasterPasswordReprompt)
{ {
return await VerifyWithAttemptsAsync(async () => return await VerifyWithAttemptsAsync(async () =>
{ {
if (!await _userVerificationService.HasMasterPasswordAsync(true)) if (!await _userVerificationService.HasMasterPasswordAsync(true))
{ {
return (false, false); return new UVResult(false, false).AsCancellable();
} }
var title = isMasterPasswordReprompt ? AppResources.PasswordConfirmation : AppResources.MasterPassword; var title = isMasterPasswordReprompt ? AppResources.PasswordConfirmation : AppResources.MasterPassword;
var body = isMasterPasswordReprompt ? AppResources.PasswordConfirmationDesc : string.Empty; var body = isMasterPasswordReprompt ? AppResources.PasswordConfirmationDesc : string.Empty;
var (_, isValid) = await _platformUtilsService.ShowPasswordDialogAndGetItAsync(title, body, _userVerificationService.VerifyMasterPasswordAsync); var (password, isValid) = await _platformUtilsService.ShowPasswordDialogAndGetItAsync(title, body, _userVerificationService.VerifyMasterPasswordAsync);
return (true, isValid); if (password is null)
{
return new UVResult(true, false).AsCancellable(true);
}
return new UVResult(true, isValid).AsCancellable();
}); });
} }
private async Task<(bool canPerform, bool isVerified)> VerifyWithAttemptsAsync(Func<Task<(bool canPerform, bool isVerified)>> verifyAsync) private async Task<CancellableResult<UVResult>> VerifyWithAttemptsAsync(Func<Task<CancellableResult<UVResult>>> verifyAsync)
{ {
byte attempts = 0; byte attempts = 0;
do do
{ {
var (canPerform, verified) = await verifyAsync(); var verification = await verifyAsync();
if (!canPerform) if (verification.IsCancelled)
{ {
return (false, false); return new UVResult(false, false).AsCancellable(true);
} }
if (verified) if (!verification.Result.CanPerform)
{ {
return (true, true); return new UVResult(false, false).AsCancellable();
}
if (verification.Result.IsVerified)
{
return new UVResult(true, true).AsCancellable();
} }
} while (attempts++ < MAX_ATTEMPTS); } while (attempts++ < MAX_ATTEMPTS);
return (true, false); return new UVResult(true, false).AsCancellable();
}
}
public static class UVResultExtensions
{
public static CancellableResult<UVResult> AsCancellable(this UVResult result, bool isCancelled = false)
{
return new CancellableResult<UVResult>(result, isCancelled);
} }
} }
} }

View File

@@ -0,0 +1,15 @@
namespace Bit.Core.Utilities
{
public readonly struct CancellableResult<T>
{
public CancellableResult(T result, bool isCancelled = false)
{
Result = result;
IsCancelled = isCancelled;
}
public T Result { get; }
public bool IsCancelled { get; }
}
}

View File

@@ -264,7 +264,7 @@ namespace Bit.iOS.Autofill
var encrypted = await _cipherService.Value.GetAsync(selectedCipherId); var encrypted = await _cipherService.Value.GetAsync(selectedCipherId);
var cipher = await encrypted.DecryptAsync(); var cipher = await encrypted.DecryptAsync();
return await _userVerificationMediatorService.Value.VerifyUserForFido2Async( var cResult = await _userVerificationMediatorService.Value.VerifyUserForFido2Async(
new Fido2UserVerificationOptions( new Fido2UserVerificationOptions(
cipher?.Reprompt == Bit.Core.Enums.CipherRepromptType.Password, cipher?.Reprompt == Bit.Core.Enums.CipherRepromptType.Password,
userVerificationPreference, userVerificationPreference,
@@ -285,8 +285,10 @@ namespace Bit.iOS.Autofill
_platformUtilsService.Value.ShowToast(null, null, AppResources.VerifyingIdentityEllipsis); _platformUtilsService.Value.ShowToast(null, null, AppResources.VerifyingIdentityEllipsis);
await _conditionedAwaiterManager.Value.GetAwaiterForPrecondition(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear); await _conditionedAwaiterManager.Value.GetAwaiterForPrecondition(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear);
}) }
); )
);
return !cResult.IsCancelled && cResult.Result;
} }
catch (InvalidOperationNeedsUIException) catch (InvalidOperationNeedsUIException)
{ {

View File

@@ -87,7 +87,12 @@ namespace Bit.iOS.Autofill
if (Context?.PasskeyCreationParams?.UserVerificationPreference != Fido2UserVerificationPreference.Discouraged) if (Context?.PasskeyCreationParams?.UserVerificationPreference != Fido2UserVerificationPreference.Discouraged)
{ {
_isUserVerified = await VerifyUserAsync(); var verification = await VerifyUserAsync();
if (verification.IsCancelled)
{
return;
}
_isUserVerified = verification.Result;
} }
var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving); var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving);
@@ -109,13 +114,13 @@ namespace Bit.iOS.Autofill
} }
} }
private async Task<bool> VerifyUserAsync() private async Task<CancellableResult<bool>> VerifyUserAsync()
{ {
try try
{ {
if (Context?.PasskeyCreationParams is null) if (Context?.PasskeyCreationParams is null)
{ {
return false; return new CancellableResult<bool>(false);
} }
return await _userVerificationMediatorService.Value.VerifyUserForFido2Async( return await _userVerificationMediatorService.Value.VerifyUserForFido2Async(
@@ -123,13 +128,14 @@ namespace Bit.iOS.Autofill
false, false,
Context.PasskeyCreationParams.Value.UserVerificationPreference, Context.PasskeyCreationParams.Value.UserVerificationPreference,
Context.VaultUnlockedDuringThisSession, Context.VaultUnlockedDuringThisSession,
Context.PasskeyCredentialIdentity?.RelyingPartyIdentifier) Context.PasskeyCredentialIdentity?.RelyingPartyIdentifier
); )
);
} }
catch (Exception ex) catch (Exception ex)
{ {
LoggerHelper.LogEvenIfCantBeResolved(ex); LoggerHelper.LogEvenIfCantBeResolved(ex);
return false; return new CancellableResult<bool>(false);
} }
} }

View File

@@ -332,7 +332,18 @@ namespace Bit.iOS.Autofill
bool? isUserVerified = null; bool? isUserVerified = null;
if (Context?.PasskeyCreationParams?.UserVerificationPreference != Fido2UserVerificationPreference.Discouraged) if (Context?.PasskeyCreationParams?.UserVerificationPreference != Fido2UserVerificationPreference.Discouraged)
{ {
isUserVerified = await VerifyUserAsync(); var verification = await VerifyUserAsync();
if (verification.IsCancelled)
{
return;
}
isUserVerified = verification.Result;
if (!isUserVerified.Value && await _userVerificationMediatorService.Value.ShouldEnforceFido2RequiredUserVerificationAsync(Fido2UserVerificationOptions))
{
await _platformUtilsService.Value.ShowDialogAsync(AppResources.ErrorCreatingPasskey, AppResources.SavePasskey);
return;
}
} }
var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving); var loadingAlert = Dialogs.CreateLoadingAlert(AppResources.Saving);
@@ -350,27 +361,38 @@ namespace Bit.iOS.Autofill
} }
} }
private async Task<bool> VerifyUserAsync() private async Task<CancellableResult<bool>> VerifyUserAsync()
{ {
try try
{ {
if (Context?.PasskeyCreationParams is null) if (Context?.PasskeyCreationParams is null)
{ {
return false; return new CancellableResult<bool>(false);
} }
return await _userVerificationMediatorService.Value.VerifyUserForFido2Async( return await _userVerificationMediatorService.Value.VerifyUserForFido2Async(Fido2UserVerificationOptions);
new Fido2UserVerificationOptions(
false,
Context.PasskeyCreationParams.Value.UserVerificationPreference,
Context.VaultUnlockedDuringThisSession,
Context.PasskeyCredentialIdentity?.RelyingPartyIdentifier)
);
} }
catch (Exception ex) catch (Exception ex)
{ {
LoggerHelper.LogEvenIfCantBeResolved(ex); LoggerHelper.LogEvenIfCantBeResolved(ex);
return false; return new CancellableResult<bool>(false);
}
}
private Fido2UserVerificationOptions Fido2UserVerificationOptions
{
get
{
ArgumentNullException.ThrowIfNull(Context);
ArgumentNullException.ThrowIfNull(Context.PasskeyCreationParams);
return new Fido2UserVerificationOptions
(
false,
Context.PasskeyCreationParams.Value.UserVerificationPreference,
Context.VaultUnlockedDuringThisSession,
Context.PasskeyCredentialIdentity?.RelyingPartyIdentifier
);
} }
} }

View File

@@ -424,7 +424,7 @@ namespace Bit.iOS.Core.Controllers
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword, _pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder(), () => MasterPasswordCell.TextField.BecomeFirstResponder(),
!_pinEnabled && !_hasMasterPassword); !_pinEnabled && !_hasMasterPassword) ?? false;
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)