1
0
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:
Jacob Fink
2023-08-03 10:49:55 -04:00
parent ee0dcd23f5
commit e076c9fe04
7 changed files with 138 additions and 42 deletions

View File

@@ -323,6 +323,7 @@ namespace Bit.App.Pages
}
if (oldTimeout != newTimeout)
{
await _cryptoService.RefreshKeysAsync();
await Device.InvokeOnMainThreadAsync(BuildList);
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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}";

View File

@@ -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,

View File

@@ -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)
{

View File

@@ -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));