mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
[PM-2713] add auto unlock key to mobile
This commit is contained in:
@@ -323,6 +323,7 @@ namespace Bit.App.Pages
|
||||
}
|
||||
if (oldTimeout != newTimeout)
|
||||
{
|
||||
await _cryptoService.RefreshKeysAsync();
|
||||
await Device.InvokeOnMainThreadAsync(BuildList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace Bit.Core.Abstractions
|
||||
Task<UserKey> MakeUserKeyAsync();
|
||||
Task ClearUserKeyAsync(string userId = null);
|
||||
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
|
||||
Task<UserKey> GetAutoUnlockKeyAsync(string userId = null);
|
||||
Task<bool> HasAutoUnlockKeyAsync(string userId = null);
|
||||
Task SetMasterKeyAsync(MasterKey masterKey, string userId = null);
|
||||
Task<MasterKey> GetMasterKeyAsync(string userId = null);
|
||||
Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig);
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace Bit.Core.Abstractions
|
||||
Task SetMasterKeyAsync(MasterKey value, string userId = null);
|
||||
Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null);
|
||||
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
|
||||
Task<UserKey> GetUserKeyAutoUnlockAsync(string userId = null);
|
||||
Task SetUserKeyAutoUnlockAsync(string value, string userId = null);
|
||||
Task<string> GetActiveUserIdAsync();
|
||||
Task<string> GetActiveUserEmailAsync();
|
||||
Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper);
|
||||
@@ -190,6 +192,9 @@ namespace Bit.Core.Abstractions
|
||||
Task SetEncKeyEncryptedAsync(string value, string userId = null);
|
||||
[Obsolete("Left for migration purposes")]
|
||||
Task SetKeyEncryptedAsync(string value, string userId = null);
|
||||
|
||||
[Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")]
|
||||
Task<string> GetKeyEncryptedAsync(string userId = null);
|
||||
[Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
|
||||
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace Bit.Core
|
||||
public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}";
|
||||
public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}";
|
||||
public static string MasterKeyEncryptedUserKeyKey(string userId) => $"masterKeyEncryptedUserKey_{userId}";
|
||||
public static string UserKeyAutoUnlockKey(string userId) => $"autoUnlock_{userId}";
|
||||
public static string CiphersKey(string userId) => $"ciphers_{userId}";
|
||||
public static string FoldersKey(string userId) => $"folders_{userId}";
|
||||
public static string CollectionsKey(string userId) => $"collections_{userId}";
|
||||
|
||||
@@ -45,24 +45,15 @@ namespace Bit.Core.Services
|
||||
|
||||
public async Task RefreshKeysAsync()
|
||||
{
|
||||
// refresh or clear the pin key
|
||||
// Refresh or clear additional keys such as
|
||||
// pin and auto unlock keys
|
||||
await SetUserKeyAsync(await GetUserKeyAsync());
|
||||
}
|
||||
|
||||
public async Task SetUserKeyAsync(UserKey userKey, string userId = null)
|
||||
{
|
||||
await _stateService.SetUserKeyAsync(userKey, userId);
|
||||
|
||||
// Refresh the Pin Key if the user has a Pin set
|
||||
if (await _stateService.GetProtectedPinAsync(userId) != null)
|
||||
{
|
||||
await UpdateUserKeyPinAsync(userKey, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _stateService.SetPinKeyEncryptedUserKeyAsync(null, userId);
|
||||
await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(null, userId);
|
||||
}
|
||||
await StoreAdditionalKeysAsync(userKey, userId);
|
||||
}
|
||||
|
||||
public Task<UserKey> GetUserKeyAsync(string userId = null)
|
||||
@@ -108,6 +99,17 @@ namespace Bit.Core.Services
|
||||
return _stateService.SetMasterKeyEncryptedUserKeyAsync(value, userId);
|
||||
}
|
||||
|
||||
public async Task<UserKey> GetAutoUnlockKeyAsync(string userId = null)
|
||||
{
|
||||
await MigrateAutoUnlockKeyIfNeededAsync(userId);
|
||||
return await _stateService.GetUserKeyAutoUnlockAsync(userId);
|
||||
}
|
||||
|
||||
public async Task<bool> HasAutoUnlockKeyAsync(string userId = null)
|
||||
{
|
||||
return (await GetAutoUnlockKeyAsync(userId) != null);
|
||||
}
|
||||
|
||||
public Task SetMasterKeyAsync(MasterKey masterKey, string userId = null)
|
||||
{
|
||||
return _stateService.SetMasterKeyAsync(masterKey, userId);
|
||||
@@ -666,6 +668,30 @@ namespace Bit.Core.Services
|
||||
|
||||
// --HELPER METHODS--
|
||||
|
||||
private async Task StoreAdditionalKeysAsync(UserKey userKey, string userId = null)
|
||||
{
|
||||
// Refresh, set, or clear the pin key
|
||||
if (await _stateService.GetProtectedPinAsync(userId) != null)
|
||||
{
|
||||
await UpdateUserKeyPinAsync(userKey, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _stateService.SetPinKeyEncryptedUserKeyAsync(null, userId);
|
||||
await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(null, userId);
|
||||
}
|
||||
|
||||
// Refresh, set, or clear the auto key
|
||||
if (await _stateService.GetVaultTimeoutAsync(userId) == null)
|
||||
{
|
||||
await _stateService.SetUserKeyAutoUnlockAsync(userKey.KeyB64, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _stateService.SetUserKeyAutoUnlockAsync(null, userId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateUserKeyPinAsync(UserKey userKey, string userId = null)
|
||||
{
|
||||
var pin = await DecryptToUtf8Async(new EncString(await _stateService.GetProtectedPinAsync(userId)));
|
||||
@@ -930,6 +956,27 @@ namespace Bit.Core.Services
|
||||
// We previously used the master key for additional keys, but now we use the user key.
|
||||
// These methods support migrating the old keys to the new ones.
|
||||
|
||||
private async Task MigrateAutoUnlockKeyIfNeededAsync(string userId = null)
|
||||
{
|
||||
var oldAutoKey = await _stateService.GetKeyEncryptedAsync(userId);
|
||||
if (oldAutoKey == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Decrypt
|
||||
var masterKey = new MasterKey(Convert.FromBase64String(oldAutoKey));
|
||||
var encryptedUserKey = await _stateService.GetEncKeyEncryptedAsync(userId);
|
||||
var userKey = await DecryptUserKeyWithMasterKeyAsync(
|
||||
masterKey,
|
||||
new EncString(encryptedUserKey),
|
||||
userId);
|
||||
// Migrate
|
||||
await _stateService.SetUserKeyAutoUnlockAsync(userKey.KeyB64, userId);
|
||||
await _stateService.SetKeyEncryptedAsync(null, userId);
|
||||
// Set encrypted user key just in case the user locks without syncing
|
||||
await SetMasterKeyEncryptedUserKeyAsync(encryptedUserKey);
|
||||
}
|
||||
|
||||
public async Task<UserKey> DecryptAndMigrateOldPinKeyAsync(
|
||||
bool masterPasswordOnRestart,
|
||||
string pin,
|
||||
|
||||
@@ -336,12 +336,27 @@ namespace Bit.Core.Services
|
||||
|
||||
public async Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null)
|
||||
{
|
||||
return await _storageMediatorService.GetAsync<string>(Constants.MasterKeyEncryptedUserKeyKey(userId), false);
|
||||
return await _storageMediatorService.GetAsync<string>(
|
||||
await ComposeKeyAsync(Constants.MasterKeyEncryptedUserKeyKey, userId), false);
|
||||
}
|
||||
|
||||
public async Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(Constants.MasterKeyEncryptedUserKeyKey(userId), value, false);
|
||||
await _storageMediatorService.SaveAsync(
|
||||
await ComposeKeyAsync(Constants.MasterKeyEncryptedUserKeyKey, userId), value, false);
|
||||
}
|
||||
|
||||
public async Task<UserKey> GetUserKeyAutoUnlockAsync(string userId = null)
|
||||
{
|
||||
var keyB64 = await _storageMediatorService.GetAsync<string>(
|
||||
await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), true);
|
||||
return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64));
|
||||
}
|
||||
|
||||
public async Task SetUserKeyAutoUnlockAsync(string value, string userId = null)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(
|
||||
await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), value, true);
|
||||
}
|
||||
|
||||
public async Task<bool> CanAccessPremiumAsync(string userId = null)
|
||||
@@ -350,6 +365,7 @@ namespace Bit.Core.Services
|
||||
{
|
||||
userId = await GetActiveUserIdAsync();
|
||||
}
|
||||
|
||||
if (!await IsAuthenticatedAsync(userId))
|
||||
{
|
||||
return false;
|
||||
@@ -397,13 +413,15 @@ namespace Bit.Core.Services
|
||||
|
||||
public async Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null)
|
||||
{
|
||||
var key = await _storageMediatorService.GetAsync<string>(Constants.PinKeyEncryptedUserKeyKey(userId), false);
|
||||
var key = await _storageMediatorService.GetAsync<string>(
|
||||
await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), false);
|
||||
return key != null ? new EncString(key) : null;
|
||||
}
|
||||
|
||||
public async Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null)
|
||||
{
|
||||
await _storageMediatorService.SaveAsync(Constants.PinKeyEncryptedUserKeyKey(userId), value?.EncryptedString, false);
|
||||
await _storageMediatorService.SaveAsync(
|
||||
await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), value?.EncryptedString, false);
|
||||
}
|
||||
|
||||
public async Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null)
|
||||
@@ -1445,28 +1463,30 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
// Non-state storage
|
||||
await SetProtectedPinAsync(null, userId);
|
||||
await SetPinProtectedAsync(null, userId);
|
||||
await SetKeyEncryptedAsync(null, userId);
|
||||
await SetKeyHashAsync(null, userId);
|
||||
await SetEncKeyEncryptedAsync(null, userId);
|
||||
await SetOrgKeysEncryptedAsync(null, userId);
|
||||
await SetPrivateKeyEncryptedAsync(null, userId);
|
||||
await SetLastActiveTimeAsync(null, userId);
|
||||
await SetPreviousPageInfoAsync(null, userId);
|
||||
await SetInvalidUnlockAttemptsAsync(null, userId);
|
||||
await SetLocalDataAsync(null, userId);
|
||||
await SetEncryptedCiphersAsync(null, userId);
|
||||
await SetEncryptedCollectionsAsync(null, userId);
|
||||
await SetLastSyncAsync(null, userId);
|
||||
await SetEncryptedFoldersAsync(null, userId);
|
||||
await SetEncryptedPoliciesAsync(null, userId);
|
||||
await SetUsesKeyConnectorAsync(null, userId);
|
||||
await SetOrganizationsAsync(null, userId);
|
||||
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
||||
await SetEncryptedSendsAsync(null, userId);
|
||||
await SetSettingsAsync(null, userId);
|
||||
await SetApprovePasswordlessLoginsAsync(null, userId);
|
||||
await Task.WhenAll(
|
||||
SetUserKeyAutoUnlockAsync(null, userId),
|
||||
SetProtectedPinAsync(null, userId),
|
||||
SetKeyHashAsync(null, userId),
|
||||
SetOrgKeysEncryptedAsync(null, userId),
|
||||
SetPrivateKeyEncryptedAsync(null, userId),
|
||||
SetLastActiveTimeAsync(null, userId),
|
||||
SetPreviousPageInfoAsync(null, userId),
|
||||
SetInvalidUnlockAttemptsAsync(null, userId),
|
||||
SetLocalDataAsync(null, userId),
|
||||
SetEncryptedCiphersAsync(null, userId),
|
||||
SetEncryptedCollectionsAsync(null, userId),
|
||||
SetLastSyncAsync(null, userId),
|
||||
SetEncryptedFoldersAsync(null, userId),
|
||||
SetEncryptedPoliciesAsync(null, userId),
|
||||
SetUsesKeyConnectorAsync(null, userId),
|
||||
SetOrganizationsAsync(null, userId),
|
||||
SetEncryptedPasswordGenerationHistoryAsync(null, userId),
|
||||
SetEncryptedSendsAsync(null, userId),
|
||||
SetSettingsAsync(null, userId),
|
||||
SetApprovePasswordlessLoginsAsync(null, userId),
|
||||
SetEncKeyEncryptedAsync(null, userId),
|
||||
SetKeyEncryptedAsync(null, userId),
|
||||
SetPinProtectedAsync(null, userId));
|
||||
}
|
||||
|
||||
private async Task ScaffoldNewAccountAsync(Account account)
|
||||
@@ -1713,6 +1733,14 @@ namespace Bit.Core.Services
|
||||
await SetValueAsync(Constants.KeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")]
|
||||
public async Task<string> GetKeyEncryptedAsync(string userId = null)
|
||||
{
|
||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||
await GetDefaultSecureStorageOptionsAsync());
|
||||
return await GetValueAsync<string>(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
|
||||
public async Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null)
|
||||
{
|
||||
|
||||
@@ -59,15 +59,26 @@ namespace Bit.Core.Services
|
||||
|
||||
public async Task<bool> IsLockedAsync(string userId = null)
|
||||
{
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync(userId);
|
||||
if (hasKey)
|
||||
var biometricSet = await IsBiometricLockSetAsync(userId);
|
||||
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
|
||||
{
|
||||
var biometricSet = await IsBiometricLockSetAsync(userId);
|
||||
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!await _cryptoService.HasUserKeyAsync(userId))
|
||||
{
|
||||
if (await _cryptoService.HasAutoUnlockKeyAsync(userId))
|
||||
{
|
||||
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId));
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check again to verify auto key was set
|
||||
var hasKey = await _cryptoService.HasUserKeyAsync(userId);
|
||||
return !hasKey;
|
||||
}
|
||||
|
||||
@@ -196,6 +207,7 @@ namespace Bit.Core.Services
|
||||
await Task.WhenAll(
|
||||
_cryptoService.ClearUserKeyAsync(userId),
|
||||
_cryptoService.ClearMasterKeyAsync(userId),
|
||||
_stateService.SetUserKeyAutoUnlockAsync(null, userId),
|
||||
_cryptoService.ClearOrgKeysAsync(true, userId),
|
||||
_cryptoService.ClearKeyPairAsync(true, userId));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user