mirror of
https://github.com/bitwarden/mobile
synced 2025-12-17 00:33:20 +00:00
Compare commits
1 Commits
v2023.9.1
...
auth/pm-33
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dd6a3f294 |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.9.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.8.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using Bit.App.Utilities;
|
|||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@@ -73,12 +72,11 @@ namespace Bit.App.Pages
|
|||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
SubmitCommand = new Command(async () => await SubmitAsync());
|
SubmitCommand = new Command(async () => await SubmitAsync());
|
||||||
|
|
||||||
AccountSwitchingOverlayViewModel =
|
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
||||||
new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
|
{
|
||||||
{
|
AllowAddAccountRow = true,
|
||||||
AllowAddAccountRow = true,
|
AllowActiveAccountSelection = true
|
||||||
AllowActiveAccountSelection = true
|
};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MasterPassword
|
public string MasterPassword
|
||||||
@@ -157,12 +155,8 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public Command SubmitCommand { get; }
|
public Command SubmitCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
|
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
? AppResources.PasswordIsVisibleTapToHide
|
|
||||||
: AppResources.PasswordIsNotVisibleTapToShow;
|
|
||||||
|
|
||||||
public Action UnlockedAction { get; set; }
|
public Action UnlockedAction { get; set; }
|
||||||
public event Action<int?> FocusSecretEntry
|
public event Action<int?> FocusSecretEntry
|
||||||
{
|
{
|
||||||
@@ -184,9 +178,8 @@ namespace Bit.App.Pages
|
|||||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
||||||
?? await _stateService.GetPinProtectedKeyAsync();
|
?? await _stateService.GetPinProtectedKeyAsync();
|
||||||
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
||||||
_pinStatus == PinLockType.Persistent;
|
_pinStatus == PinLockType.Persistent;
|
||||||
|
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _biometricService.CanUseBiometricsUnlockAsync();
|
||||||
BiometricEnabled = await IsBiometricsEnabledAsync();
|
|
||||||
|
|
||||||
// Users without MP and without biometric or pin has no MP to unlock with
|
// Users without MP and without biometric or pin has no MP to unlock with
|
||||||
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
||||||
@@ -221,9 +214,7 @@ namespace Bit.App.Pages
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault;
|
PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault;
|
||||||
LockedVerifyText = _hasMasterPassword
|
LockedVerifyText = _hasMasterPassword ? AppResources.VaultLockedMasterPassword : AppResources.VaultLockedIdentity;
|
||||||
? AppResources.VaultLockedMasterPassword
|
|
||||||
: AppResources.VaultLockedIdentity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BiometricEnabled)
|
if (BiometricEnabled)
|
||||||
@@ -242,32 +233,11 @@ namespace Bit.App.Pages
|
|||||||
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
|
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
|
||||||
AppResources.UseFingerprintToUnlock;
|
AppResources.UseFingerprintToUnlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
{
|
|
||||||
ShowPassword = false;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
|
||||||
if (PinEnabled)
|
|
||||||
{
|
|
||||||
await UnlockWithPinAsync(kdfConfig);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await UnlockWithMasterPasswordAsync(kdfConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (LegacyUserException)
|
|
||||||
{
|
|
||||||
await HandleLegacyUserAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UnlockWithPinAsync(KdfConfig kdfConfig)
|
|
||||||
{
|
{
|
||||||
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
|
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
|
||||||
{
|
{
|
||||||
@@ -276,84 +246,6 @@ namespace Bit.App.Pages
|
|||||||
AppResources.Ok);
|
AppResources.Ok);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var failed = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
EncString userKeyPin;
|
|
||||||
EncString oldPinProtected;
|
|
||||||
switch (_pinStatus)
|
|
||||||
{
|
|
||||||
case PinLockType.Persistent:
|
|
||||||
{
|
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
|
||||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
|
||||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PinLockType.Transient:
|
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
|
||||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
|
||||||
break;
|
|
||||||
case PinLockType.Disabled:
|
|
||||||
default:
|
|
||||||
throw new Exception("Pin is disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
UserKey userKey;
|
|
||||||
if (oldPinProtected != null)
|
|
||||||
{
|
|
||||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
|
||||||
_pinStatus == PinLockType.Transient,
|
|
||||||
Pin,
|
|
||||||
_email,
|
|
||||||
kdfConfig,
|
|
||||||
oldPinProtected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
|
||||||
Pin,
|
|
||||||
_email,
|
|
||||||
kdfConfig,
|
|
||||||
userKeyPin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
|
||||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
|
||||||
failed = decryptedPin != Pin;
|
|
||||||
if (!failed)
|
|
||||||
{
|
|
||||||
Pin = string.Empty;
|
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
|
||||||
await SetUserKeyAndContinueAsync(userKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (LegacyUserException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
if (failed)
|
|
||||||
{
|
|
||||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
|
||||||
if (invalidUnlockAttempts >= 5)
|
|
||||||
{
|
|
||||||
_messagingService.Send("logout");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
|
|
||||||
AppResources.AnErrorHasOccurred);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UnlockWithMasterPasswordAsync(KdfConfig kdfConfig)
|
|
||||||
{
|
|
||||||
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
|
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
|
||||||
{
|
{
|
||||||
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
|
||||||
@@ -362,78 +254,142 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
ShowPassword = false;
|
||||||
if (await _cryptoService.IsLegacyUserAsync(masterKey))
|
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
|
||||||
{
|
|
||||||
throw new LegacyUserException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
|
if (PinEnabled)
|
||||||
var passwordValid = false;
|
|
||||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
|
||||||
|
|
||||||
if (storedKeyHash != null)
|
|
||||||
{
|
{
|
||||||
// Offline unlock possible
|
var failed = true;
|
||||||
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Online unlock required
|
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
|
||||||
var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
|
|
||||||
HashPurpose.ServerAuthorization);
|
|
||||||
var request = new PasswordVerificationRequest();
|
|
||||||
request.MasterPasswordHash = keyHash;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
EncString userKeyPin = null;
|
||||||
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
EncString oldPinProtected = null;
|
||||||
passwordValid = true;
|
if (_pinStatus == PinLockType.Persistent)
|
||||||
var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
|
{
|
||||||
HashPurpose.LocalAuthorization);
|
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||||
await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
|
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||||
|
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||||
|
}
|
||||||
|
else if (_pinStatus == PinLockType.Transient)
|
||||||
|
{
|
||||||
|
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||||
|
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
UserKey userKey;
|
||||||
|
if (oldPinProtected != null)
|
||||||
|
{
|
||||||
|
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||||
|
_pinStatus == PinLockType.Transient,
|
||||||
|
Pin,
|
||||||
|
_email,
|
||||||
|
kdfConfig,
|
||||||
|
oldPinProtected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||||
|
Pin,
|
||||||
|
_email,
|
||||||
|
kdfConfig,
|
||||||
|
userKeyPin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||||
|
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||||
|
failed = decryptedPin != Pin;
|
||||||
|
if (!failed)
|
||||||
|
{
|
||||||
|
Pin = string.Empty;
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
await SetUserKeyAndContinueAsync(userKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
|
failed = true;
|
||||||
}
|
}
|
||||||
await _deviceActionService.HideLoadingAsync();
|
if (failed)
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordValid)
|
|
||||||
{
|
|
||||||
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
|
||||||
{
|
{
|
||||||
// Save the ForcePasswordResetReason to force a password reset after unlock
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
await _stateService.SetForcePasswordResetReasonAsync(
|
if (invalidUnlockAttempts >= 5)
|
||||||
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
|
{
|
||||||
}
|
_messagingService.Send("logout");
|
||||||
|
return;
|
||||||
MasterPassword = string.Empty;
|
}
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
|
||||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
|
||||||
await SetUserKeyAndContinueAsync(userKey);
|
|
||||||
|
|
||||||
// Re-enable biometrics
|
|
||||||
if (BiometricEnabled & !BiometricIntegrityValid)
|
|
||||||
{
|
|
||||||
await _biometricService.SetupBiometricAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
||||||
if (invalidUnlockAttempts >= 5)
|
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||||
|
var passwordValid = false;
|
||||||
|
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||||
|
|
||||||
|
if (storedKeyHash != null)
|
||||||
{
|
{
|
||||||
_messagingService.Send("logout");
|
// Offline unlock possible
|
||||||
return;
|
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Online unlock required
|
||||||
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
|
||||||
|
var request = new PasswordVerificationRequest();
|
||||||
|
request.MasterPasswordHash = keyHash;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
|
||||||
|
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
|
||||||
|
passwordValid = true;
|
||||||
|
var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
|
||||||
|
await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
|
||||||
|
}
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
}
|
||||||
|
if (passwordValid)
|
||||||
|
{
|
||||||
|
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
|
||||||
|
{
|
||||||
|
// Save the ForcePasswordResetReason to force a password reset after unlock
|
||||||
|
await _stateService.SetForcePasswordResetReasonAsync(
|
||||||
|
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
|
||||||
|
}
|
||||||
|
|
||||||
|
MasterPassword = string.Empty;
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
|
||||||
|
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||||
|
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||||
|
await SetUserKeyAndContinueAsync(userKey);
|
||||||
|
|
||||||
|
// Re-enable biometrics
|
||||||
|
if (BiometricEnabled & !BiometricIntegrityValid)
|
||||||
|
{
|
||||||
|
await _biometricService.SetupBiometricAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
|
||||||
|
if (invalidUnlockAttempts >= 5)
|
||||||
|
{
|
||||||
|
_messagingService.Send("logout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
|
||||||
|
AppResources.AnErrorHasOccurred);
|
||||||
}
|
}
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
|
|
||||||
AppResources.AnErrorHasOccurred);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,34 +452,25 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
ShowPassword = !ShowPassword;
|
ShowPassword = !ShowPassword;
|
||||||
var secret = PinEnabled ? Pin : MasterPassword;
|
var secret = PinEnabled ? Pin : MasterPassword;
|
||||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length,
|
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
||||||
nameof(FocusSecretEntry));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
{
|
{
|
||||||
try
|
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||||
|
BiometricButtonVisible = BiometricIntegrityValid;
|
||||||
|
if (!BiometricEnabled || !BiometricIntegrityValid)
|
||||||
{
|
{
|
||||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
return;
|
||||||
BiometricButtonVisible = BiometricIntegrityValid;
|
|
||||||
if (!BiometricEnabled || !BiometricIntegrityValid)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
|
||||||
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
|
||||||
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
|
||||||
await _stateService.SetBiometricLockedAsync(!success);
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
|
||||||
await SetUserKeyAndContinueAsync(userKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (LegacyUserException)
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
|
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||||
|
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
|
||||||
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
|
if (success)
|
||||||
{
|
{
|
||||||
await HandleLegacyUserAsync();
|
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
||||||
|
await SetUserKeyAndContinueAsync(userKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,29 +493,5 @@ namespace Bit.App.Pages
|
|||||||
_messagingService.Send("unlocked");
|
_messagingService.Send("unlocked");
|
||||||
UnlockedAction?.Invoke();
|
UnlockedAction?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> IsBiometricsEnabledAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
|
||||||
await _biometricService.CanUseBiometricsUnlockAsync();
|
|
||||||
}
|
|
||||||
catch (LegacyUserException)
|
|
||||||
{
|
|
||||||
await HandleLegacyUserAsync();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleLegacyUserAsync()
|
|
||||||
{
|
|
||||||
// Legacy users must migrate on web vault.
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong,
|
|
||||||
AppResources.AnErrorHasOccurred,
|
|
||||||
AppResources.Ok);
|
|
||||||
await _vaultTimeoutService.LogOutAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,14 +248,6 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|
||||||
if (response.RequiresEncryptionKeyMigration)
|
|
||||||
{
|
|
||||||
// Legacy users must migrate on web vault.
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong, AppResources.AnErrorHasOccurred,
|
|
||||||
AppResources.Ok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.TwoFactor)
|
if (response.TwoFactor)
|
||||||
{
|
{
|
||||||
StartTwoFactorAction?.Invoke();
|
StartTwoFactorAction?.Invoke();
|
||||||
|
|||||||
@@ -49,6 +49,38 @@
|
|||||||
AutomationId="{Binding AutomationIdSettingStatus}" />
|
AutomationId="{Binding AutomationIdSettingStatus}" />
|
||||||
</controls:ExtendedStackLayout>
|
</controls:ExtendedStackLayout>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
<DataTemplate
|
||||||
|
x:Key="regularWithDescriptionTemplate"
|
||||||
|
x:DataType="pages:SettingsPageListItem">
|
||||||
|
<controls:ExtendedStackLayout Orientation="Horizontal"
|
||||||
|
StyleClass="list-row, list-row-platform">
|
||||||
|
<StackLayout
|
||||||
|
HorizontalOptions="StartAndExpand"
|
||||||
|
VerticalOptions="Center">
|
||||||
|
<controls:CustomLabel
|
||||||
|
Text="{Binding Name, Mode=OneWay}"
|
||||||
|
LineBreakMode="{Binding LineBreakMode}"
|
||||||
|
StyleClass="list-title"
|
||||||
|
TextColor="{Binding NameColor}"
|
||||||
|
AutomationId="{Binding AutomationIdSettingName}" />
|
||||||
|
<controls:CustomLabel
|
||||||
|
Text="{Binding Description, Mode=OneWay}"
|
||||||
|
LineBreakMode="{Binding LineBreakMode}"
|
||||||
|
TextColor="{DynamicResource MutedColor}"
|
||||||
|
FontSize="Micro"
|
||||||
|
StyleClass="list-sub"/>
|
||||||
|
</StackLayout>
|
||||||
|
<controls:CustomLabel Text="{Binding SubLabel, Mode=OneWay}"
|
||||||
|
IsVisible="{Binding ShowSubLabel}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
HorizontalTextAlignment="End"
|
||||||
|
VerticalOptions="CenterAndExpand"
|
||||||
|
VerticalTextAlignment="Start"
|
||||||
|
TextColor="{Binding SubLabelColor}"
|
||||||
|
StyleClass="list-sub"
|
||||||
|
AutomationId="{Binding AutomationIdSettingStatus}" />
|
||||||
|
</controls:ExtendedStackLayout>
|
||||||
|
</DataTemplate>
|
||||||
<DataTemplate
|
<DataTemplate
|
||||||
x:Key="timePickerTemplate"
|
x:Key="timePickerTemplate"
|
||||||
x:DataType="pages:SettingsPageListItem">
|
x:DataType="pages:SettingsPageListItem">
|
||||||
@@ -107,6 +139,7 @@
|
|||||||
x:Key="listItemDataTemplateSelector"
|
x:Key="listItemDataTemplateSelector"
|
||||||
HeaderTemplate="{StaticResource headerTemplate}"
|
HeaderTemplate="{StaticResource headerTemplate}"
|
||||||
RegularTemplate="{StaticResource regularTemplate}"
|
RegularTemplate="{StaticResource regularTemplate}"
|
||||||
|
RegularWithDescriptionTemplate="{StaticResource regularWithDescriptionTemplate}"
|
||||||
TimePickerTemplate="{StaticResource timePickerTemplate}" />
|
TimePickerTemplate="{StaticResource timePickerTemplate}" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace Bit.App.Pages
|
|||||||
private void RowSelected(object sender, SelectionChangedEventArgs e)
|
private void RowSelected(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
((ExtendedCollectionView)sender).SelectedItem = null;
|
((ExtendedCollectionView)sender).SelectedItem = null;
|
||||||
if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item)
|
if (e.CurrentSelection?.FirstOrDefault() is SettingsPageListItem item && item.Enabled)
|
||||||
{
|
{
|
||||||
_vm?.ExecuteSettingItemCommand.Execute(item);
|
_vm?.ExecuteSettingItemCommand.Execute(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ namespace Bit.App.Pages
|
|||||||
public string Icon { get; set; }
|
public string Icon { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string SubLabel { get; set; }
|
public string SubLabel { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
public TimeSpan? Time { get; set; }
|
public TimeSpan? Time { get; set; }
|
||||||
public bool UseFrame { get; set; }
|
public bool UseFrame { get; set; }
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
public Func<Task> ExecuteAsync { get; set; }
|
public Func<Task> ExecuteAsync { get; set; }
|
||||||
|
|
||||||
public bool SubLabelTextEnabled => SubLabel == AppResources.On;
|
public bool SubLabelTextEnabled => SubLabel == AppResources.On;
|
||||||
@@ -24,6 +26,9 @@ namespace Bit.App.Pages
|
|||||||
public Color SubLabelColor => SubLabelTextEnabled ?
|
public Color SubLabelColor => SubLabelTextEnabled ?
|
||||||
ThemeManager.GetResourceColor("SuccessColor") :
|
ThemeManager.GetResourceColor("SuccessColor") :
|
||||||
ThemeManager.GetResourceColor("MutedColor");
|
ThemeManager.GetResourceColor("MutedColor");
|
||||||
|
public Color NameColor => Enabled ?
|
||||||
|
ThemeManager.GetResourceColor("TextColor") :
|
||||||
|
ThemeManager.GetResourceColor("MutedColor");
|
||||||
|
|
||||||
public string AutomationIdSettingName
|
public string AutomationIdSettingName
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace Bit.App.Pages
|
|||||||
public DataTemplate HeaderTemplate { get; set; }
|
public DataTemplate HeaderTemplate { get; set; }
|
||||||
public DataTemplate RegularTemplate { get; set; }
|
public DataTemplate RegularTemplate { get; set; }
|
||||||
public DataTemplate TimePickerTemplate { get; set; }
|
public DataTemplate TimePickerTemplate { get; set; }
|
||||||
|
public DataTemplate RegularWithDescriptionTemplate { get; set; }
|
||||||
|
|
||||||
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
|
||||||
{
|
{
|
||||||
@@ -16,6 +17,10 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (item is SettingsPageListItem listItem)
|
if (item is SettingsPageListItem listItem)
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrEmpty(listItem.Description))
|
||||||
|
{
|
||||||
|
return RegularWithDescriptionTemplate;
|
||||||
|
}
|
||||||
return listItem.ShowTimeInput ? TimePickerTemplate : RegularTemplate;
|
return listItem.ShowTimeInput ? TimePickerTemplate : RegularTemplate;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -567,6 +567,8 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
Name = AppResources.VaultTimeoutAction,
|
Name = AppResources.VaultTimeoutAction,
|
||||||
SubLabel = _vaultTimeoutActionDisplayValue,
|
SubLabel = _vaultTimeoutActionDisplayValue,
|
||||||
|
Description = IsVaultTimeoutActionLockAllowed ? null : AppResources.SetUpAnUnlockMethodToChangeYourVaultTimeoutAction,
|
||||||
|
Enabled = IsVaultTimeoutActionLockAllowed,
|
||||||
ExecuteAsync = () => VaultTimeoutActionAsync()
|
ExecuteAsync = () => VaultTimeoutActionAsync()
|
||||||
},
|
},
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
@@ -582,16 +584,19 @@ namespace Bit.App.Pages
|
|||||||
ExecuteAsync = () => ApproveLoginRequestsAsync()
|
ExecuteAsync = () => ApproveLoginRequestsAsync()
|
||||||
},
|
},
|
||||||
new SettingsPageListItem
|
new SettingsPageListItem
|
||||||
{
|
|
||||||
Name = AppResources.LockNow,
|
|
||||||
ExecuteAsync = () => LockAsync()
|
|
||||||
},
|
|
||||||
new SettingsPageListItem
|
|
||||||
{
|
{
|
||||||
Name = AppResources.TwoStepLogin,
|
Name = AppResources.TwoStepLogin,
|
||||||
ExecuteAsync = () => TwoStepAsync()
|
ExecuteAsync = () => TwoStepAsync()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (IsVaultTimeoutActionLockAllowed)
|
||||||
|
{
|
||||||
|
securityItems.Insert(4, new SettingsPageListItem
|
||||||
|
{
|
||||||
|
Name = AppResources.LockNow,
|
||||||
|
ExecuteAsync = () => LockAsync()
|
||||||
|
});
|
||||||
|
}
|
||||||
if (_approvePasswordlessLoginRequests)
|
if (_approvePasswordlessLoginRequests)
|
||||||
{
|
{
|
||||||
manageItems.Add(new SettingsPageListItem
|
manageItems.Add(new SettingsPageListItem
|
||||||
|
|||||||
13780
src/App/Resources/AppResources.Designer.cs
generated
13780
src/App/Resources/AppResources.Designer.cs
generated
File diff suppressed because it is too large
Load Diff
@@ -954,9 +954,6 @@ Scanning will happen automatically.</value>
|
|||||||
<data name="UpdateKey" xml:space="preserve">
|
<data name="UpdateKey" xml:space="preserve">
|
||||||
<value>You cannot use this feature until you update your encryption key.</value>
|
<value>You cannot use this feature until you update your encryption key.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
|
|
||||||
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
|
|
||||||
</data>
|
|
||||||
<data name="LearnMore" xml:space="preserve">
|
<data name="LearnMore" xml:space="preserve">
|
||||||
<value>Learn more</value>
|
<value>Learn more</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2765,4 +2762,7 @@ Do you want to switch to this account?</value>
|
|||||||
<data name="LoggingInOn" xml:space="preserve">
|
<data name="LoggingInOn" xml:space="preserve">
|
||||||
<value>Logging in on</value>
|
<value>Logging in on</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SetUpAnUnlockMethodToChangeYourVaultTimeoutAction" xml:space="preserve">
|
||||||
|
<value>Set up an unlock method to change your vault timeout action.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ namespace Bit.Core.Abstractions
|
|||||||
Task RefreshKeysAsync();
|
Task RefreshKeysAsync();
|
||||||
Task SetUserKeyAsync(UserKey userKey, string userId = null);
|
Task SetUserKeyAsync(UserKey userKey, string userId = null);
|
||||||
Task<UserKey> GetUserKeyAsync(string userId = null);
|
Task<UserKey> GetUserKeyAsync(string userId = null);
|
||||||
Task<bool> IsLegacyUserAsync(MasterKey masterKey = null, string userId = null);
|
|
||||||
Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null);
|
Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null);
|
||||||
Task<bool> HasUserKeyAsync(string userId = null);
|
Task<bool> HasUserKeyAsync(string userId = null);
|
||||||
Task<bool> HasEncryptedUserKeyAsync(string userId = null);
|
Task<bool> HasEncryptedUserKeyAsync(string userId = null);
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
namespace Bit.Core.Exceptions
|
|
||||||
{
|
|
||||||
public class LegacyUserException : Exception
|
|
||||||
{
|
|
||||||
public LegacyUserException()
|
|
||||||
: base("Legacy users must migrate on web vault.")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ namespace Bit.Core.Models.Domain
|
|||||||
[Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")]
|
[Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")]
|
||||||
public bool ResetMasterPassword { get; set; }
|
public bool ResetMasterPassword { get; set; }
|
||||||
public bool ForcePasswordReset { get; set; }
|
public bool ForcePasswordReset { get; set; }
|
||||||
public bool RequiresEncryptionKeyMigration { get; set; }
|
|
||||||
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -477,17 +477,6 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tokenResponse = response.TokenResponse;
|
var tokenResponse = response.TokenResponse;
|
||||||
if (localHashedPassword != null && tokenResponse.Key == null)
|
|
||||||
{
|
|
||||||
// Only check for legacy if there is no key on token
|
|
||||||
if (await _cryptoService.IsLegacyUserAsync(masterKey))
|
|
||||||
{
|
|
||||||
// Legacy users must migrate on web vault;
|
|
||||||
result.RequiresEncryptionKeyMigration = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.ResetMasterPassword = tokenResponse.ResetMasterPassword;
|
result.ResetMasterPassword = tokenResponse.ResetMasterPassword;
|
||||||
result.ForcePasswordReset = tokenResponse.ForcePasswordReset;
|
result.ForcePasswordReset = tokenResponse.ForcePasswordReset;
|
||||||
_masterPasswordPolicy = tokenResponse.MasterPasswordPolicy;
|
_masterPasswordPolicy = tokenResponse.MasterPasswordPolicy;
|
||||||
|
|||||||
@@ -62,16 +62,6 @@ namespace Bit.Core.Services
|
|||||||
return _stateService.GetUserKeyAsync(userId);
|
return _stateService.GetUserKeyAsync(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> IsLegacyUserAsync(MasterKey masterKey = null, string userId = null)
|
|
||||||
{
|
|
||||||
masterKey ??= await GetMasterKeyAsync(userId);
|
|
||||||
if (masterKey == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return await ValidateUserKeyAsync(new UserKey(masterKey.Key));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null)
|
public async Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null)
|
||||||
{
|
{
|
||||||
var userKey = await GetUserKeyAsync(userId);
|
var userKey = await GetUserKeyAsync(userId);
|
||||||
@@ -1007,31 +997,6 @@ namespace Bit.Core.Services
|
|||||||
return keyCreator(key);
|
return keyCreator(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> ValidateUserKeyAsync(UserKey key, string userId = null)
|
|
||||||
{
|
|
||||||
if (key == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var encPrivateKey = await _stateService.GetPrivateKeyEncryptedAsync(userId);
|
|
||||||
if (encPrivateKey == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var privateKey = await DecryptToBytesAsync(new EncString(encPrivateKey), key);
|
|
||||||
await _cryptoFunctionService.RsaExtractPublicKeyAsync(privateKey);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class EncryptedObject
|
private class EncryptedObject
|
||||||
{
|
{
|
||||||
public byte[] Iv { get; set; }
|
public byte[] Iv { get; set; }
|
||||||
@@ -1054,10 +1019,6 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
// Decrypt
|
// Decrypt
|
||||||
var masterKey = new MasterKey(Convert.FromBase64String(oldKey));
|
var masterKey = new MasterKey(Convert.FromBase64String(oldKey));
|
||||||
if (await IsLegacyUserAsync(masterKey, userId))
|
|
||||||
{
|
|
||||||
throw new LegacyUserException();
|
|
||||||
}
|
|
||||||
var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId);
|
var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId);
|
||||||
if (encryptedUserKey == null)
|
if (encryptedUserKey == null)
|
||||||
{
|
{
|
||||||
@@ -1097,10 +1058,6 @@ namespace Bit.Core.Services
|
|||||||
kdfConfig,
|
kdfConfig,
|
||||||
oldPinKey
|
oldPinKey
|
||||||
);
|
);
|
||||||
if (await IsLegacyUserAsync(masterKey))
|
|
||||||
{
|
|
||||||
throw new LegacyUserException();
|
|
||||||
}
|
|
||||||
var encUserKey = await _stateService.GetEncKeyEncryptedAsync();
|
var encUserKey = await _stateService.GetEncKeyEncryptedAsync();
|
||||||
var userKey = await DecryptUserKeyWithMasterKeyAsync(
|
var userKey = await DecryptUserKeyWithMasterKeyAsync(
|
||||||
masterKey,
|
masterKey,
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Models.Domain;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@@ -69,23 +67,14 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
if (!await _cryptoService.HasUserKeyAsync(userId))
|
if (!await _cryptoService.HasUserKeyAsync(userId))
|
||||||
{
|
{
|
||||||
try
|
if (!await _cryptoService.HasAutoUnlockKeyAsync(userId))
|
||||||
{
|
{
|
||||||
if (!await _cryptoService.HasAutoUnlockKeyAsync(userId))
|
return true;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (userId != null && await _stateService.GetActiveUserIdAsync() != userId)
|
|
||||||
{
|
|
||||||
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId),
|
|
||||||
userId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (LegacyUserException)
|
if (userId != null && await _stateService.GetActiveUserIdAsync() != userId)
|
||||||
{
|
{
|
||||||
await LogOutAsync(false, userId);
|
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check again to verify auto key was set
|
// Check again to verify auto key was set
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.9.1</string>
|
<string>2023.8.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using Bit.App.Resources;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@@ -121,7 +120,8 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
||||||
_pinStatus == PinLockType.Persistent;
|
_pinStatus == PinLockType.Persistent;
|
||||||
|
|
||||||
_biometricEnabled = await IsBiometricsEnabledAsync();
|
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
|
||||||
|
&& await _biometricService.CanUseBiometricsUnlockAsync();
|
||||||
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
|
||||||
_biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled;
|
_biometricUnlockOnly = !_hasMasterPassword && _biometricEnabled && !_pinEnabled;
|
||||||
|
|
||||||
@@ -260,26 +260,99 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
&&
|
&&
|
||||||
kdfConfig.Memory > Constants.MaximumArgon2IdMemoryBeforeExtensionCrashing
|
kdfConfig.Memory > Constants.MaximumArgon2IdMemoryBeforeExtensionCrashing
|
||||||
&&
|
&&
|
||||||
!await _platformUtilsService.ShowDialogAsync(
|
!await _platformUtilsService.ShowDialogAsync(AppResources.UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve, AppResources.Warning, AppResources.Continue, AppResources.Cancel))
|
||||||
AppResources.UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve,
|
|
||||||
AppResources.Warning, AppResources.Continue, AppResources.Cancel))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pinEnabled)
|
if (_pinEnabled)
|
||||||
{
|
{
|
||||||
await UnlockWithPinAsync(inputtedValue, email, kdfConfig);
|
var failed = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EncString userKeyPin = null;
|
||||||
|
EncString oldPinProtected = null;
|
||||||
|
if (_pinStatus == PinLockType.Persistent)
|
||||||
|
{
|
||||||
|
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
||||||
|
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
||||||
|
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
||||||
|
}
|
||||||
|
else if (_pinStatus == PinLockType.Transient)
|
||||||
|
{
|
||||||
|
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
||||||
|
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
UserKey userKey;
|
||||||
|
if (oldPinProtected != null)
|
||||||
|
{
|
||||||
|
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
||||||
|
_pinStatus == PinLockType.Transient,
|
||||||
|
inputtedValue,
|
||||||
|
email,
|
||||||
|
kdfConfig,
|
||||||
|
oldPinProtected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
||||||
|
inputtedValue,
|
||||||
|
email,
|
||||||
|
kdfConfig,
|
||||||
|
userKeyPin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var protectedPin = await _stateService.GetProtectedPinAsync();
|
||||||
|
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
||||||
|
failed = decryptedPin != inputtedValue;
|
||||||
|
if (!failed)
|
||||||
|
{
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
await SetKeyAndContinueAsync(userKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
if (failed)
|
||||||
|
{
|
||||||
|
await HandleFailedCredentialsAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await UnlockWithMasterPasswordAsync(inputtedValue, email, kdfConfig);
|
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
|
||||||
|
|
||||||
|
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
|
||||||
|
if (storedPasswordHash == null)
|
||||||
|
{
|
||||||
|
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
||||||
|
if (masterKey.KeyB64 == oldKey)
|
||||||
|
{
|
||||||
|
var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
|
||||||
|
await _secureStorageService.RemoveAsync("oldKey");
|
||||||
|
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
|
||||||
|
if (passwordValid)
|
||||||
|
{
|
||||||
|
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
||||||
|
|
||||||
|
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
||||||
|
await _cryptoService.SetMasterKeyAsync(masterKey);
|
||||||
|
await SetKeyAndContinueAsync(userKey, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await HandleFailedCredentialsAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (LegacyUserException)
|
|
||||||
{
|
|
||||||
await HandleLegacyUserAsync();
|
|
||||||
}
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_checkingPassword = false;
|
_checkingPassword = false;
|
||||||
@@ -297,125 +370,20 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
InvalidValue();
|
InvalidValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UnlockWithPinAsync(string inputPin, string email, KdfConfig kdfConfig)
|
|
||||||
{
|
|
||||||
var failed = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
EncString userKeyPin = null;
|
|
||||||
EncString oldPinProtected = null;
|
|
||||||
if (_pinStatus == PinLockType.Persistent)
|
|
||||||
{
|
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
|
||||||
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
|
|
||||||
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
|
|
||||||
}
|
|
||||||
else if (_pinStatus == PinLockType.Transient)
|
|
||||||
{
|
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
|
|
||||||
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
UserKey userKey;
|
|
||||||
if (oldPinProtected != null)
|
|
||||||
{
|
|
||||||
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
|
|
||||||
_pinStatus == PinLockType.Transient,
|
|
||||||
inputPin,
|
|
||||||
email,
|
|
||||||
kdfConfig,
|
|
||||||
oldPinProtected
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
|
||||||
inputPin,
|
|
||||||
email,
|
|
||||||
kdfConfig,
|
|
||||||
userKeyPin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var protectedPin = await _stateService.GetProtectedPinAsync();
|
|
||||||
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
|
|
||||||
failed = decryptedPin != inputPin;
|
|
||||||
if (!failed)
|
|
||||||
{
|
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
|
||||||
await SetKeyAndContinueAsync(userKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed)
|
|
||||||
{
|
|
||||||
await HandleFailedCredentialsAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UnlockWithMasterPasswordAsync(string inputPassword, string email, KdfConfig kdfConfig)
|
|
||||||
{
|
|
||||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputPassword, email, kdfConfig);
|
|
||||||
if (await _cryptoService.IsLegacyUserAsync(masterKey))
|
|
||||||
{
|
|
||||||
throw new LegacyUserException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
|
|
||||||
if (storedPasswordHash == null)
|
|
||||||
{
|
|
||||||
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
|
||||||
if (masterKey.KeyB64 == oldKey)
|
|
||||||
{
|
|
||||||
var localPasswordHash =
|
|
||||||
await _cryptoService.HashMasterKeyAsync(inputPassword, masterKey,
|
|
||||||
HashPurpose.LocalAuthorization);
|
|
||||||
await _secureStorageService.RemoveAsync("oldKey");
|
|
||||||
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputPassword, masterKey);
|
|
||||||
if (passwordValid)
|
|
||||||
{
|
|
||||||
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
|
|
||||||
|
|
||||||
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
|
|
||||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
|
||||||
await SetKeyAndContinueAsync(userKey, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await HandleFailedCredentialsAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
{
|
{
|
||||||
try
|
if (!_biometricEnabled || !_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
if (!_biometricEnabled || !_biometricIntegrityValid)
|
return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
|
||||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
|
||||||
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
|
||||||
await _stateService.SetBiometricLockedAsync(!success);
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
|
||||||
await SetKeyAndContinueAsync(userKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (LegacyUserException)
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
|
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
||||||
|
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||||
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
|
if (success)
|
||||||
{
|
{
|
||||||
await HandleLegacyUserAsync();
|
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
||||||
|
await SetKeyAndContinueAsync(userKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,29 +436,6 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> IsBiometricsEnabledAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
|
||||||
await _biometricService.CanUseBiometricsUnlockAsync();
|
|
||||||
}
|
|
||||||
catch (LegacyUserException)
|
|
||||||
{
|
|
||||||
await HandleLegacyUserAsync();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleLegacyUserAsync()
|
|
||||||
{
|
|
||||||
// Legacy users must migrate on web vault.
|
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong,
|
|
||||||
AppResources.AnErrorHasOccurred,
|
|
||||||
AppResources.Ok);
|
|
||||||
await _vaultTimeoutService.LogOutAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InvalidValue()
|
private void InvalidValue()
|
||||||
{
|
{
|
||||||
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.9.1</string>
|
<string>2023.8.1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.9.1</string>
|
<string>2023.8.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden</string>
|
<string>com.8bit.bitwarden</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.9.1</string>
|
<string>2023.8.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user