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) if (oldTimeout != newTimeout)
{ {
await _cryptoService.RefreshKeysAsync();
await Device.InvokeOnMainThreadAsync(BuildList); await Device.InvokeOnMainThreadAsync(BuildList);
} }
} }

View File

@@ -19,6 +19,8 @@ namespace Bit.Core.Abstractions
Task<UserKey> MakeUserKeyAsync(); Task<UserKey> MakeUserKeyAsync();
Task ClearUserKeyAsync(string userId = null); Task ClearUserKeyAsync(string userId = null);
Task SetMasterKeyEncryptedUserKeyAsync(string value, 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 SetMasterKeyAsync(MasterKey masterKey, string userId = null);
Task<MasterKey> GetMasterKeyAsync(string userId = null); Task<MasterKey> GetMasterKeyAsync(string userId = null);
Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig); 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 SetMasterKeyAsync(MasterKey value, string userId = null);
Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null); Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null);
Task SetMasterKeyEncryptedUserKeyAsync(string value, 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> GetActiveUserIdAsync();
Task<string> GetActiveUserEmailAsync(); Task<string> GetActiveUserEmailAsync();
Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper); Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper);
@@ -190,6 +192,9 @@ namespace Bit.Core.Abstractions
Task SetEncKeyEncryptedAsync(string value, string userId = null); Task SetEncKeyEncryptedAsync(string value, string userId = null);
[Obsolete("Left for migration purposes")] [Obsolete("Left for migration purposes")]
Task SetKeyEncryptedAsync(string value, string userId = null); 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")] [Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null); 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 VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}";
public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}"; public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}";
public static string MasterKeyEncryptedUserKeyKey(string userId) => $"masterKeyEncryptedUserKey_{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 CiphersKey(string userId) => $"ciphers_{userId}";
public static string FoldersKey(string userId) => $"folders_{userId}"; public static string FoldersKey(string userId) => $"folders_{userId}";
public static string CollectionsKey(string userId) => $"collections_{userId}"; public static string CollectionsKey(string userId) => $"collections_{userId}";

View File

@@ -45,24 +45,15 @@ namespace Bit.Core.Services
public async Task RefreshKeysAsync() 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()); await SetUserKeyAsync(await GetUserKeyAsync());
} }
public async Task SetUserKeyAsync(UserKey userKey, string userId = null) public async Task SetUserKeyAsync(UserKey userKey, string userId = null)
{ {
await _stateService.SetUserKeyAsync(userKey, userId); await _stateService.SetUserKeyAsync(userKey, userId);
await StoreAdditionalKeysAsync(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);
}
} }
public Task<UserKey> GetUserKeyAsync(string userId = null) public Task<UserKey> GetUserKeyAsync(string userId = null)
@@ -108,6 +99,17 @@ namespace Bit.Core.Services
return _stateService.SetMasterKeyEncryptedUserKeyAsync(value, userId); 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) public Task SetMasterKeyAsync(MasterKey masterKey, string userId = null)
{ {
return _stateService.SetMasterKeyAsync(masterKey, userId); return _stateService.SetMasterKeyAsync(masterKey, userId);
@@ -666,6 +668,30 @@ namespace Bit.Core.Services
// --HELPER METHODS-- // --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) private async Task UpdateUserKeyPinAsync(UserKey userKey, string userId = null)
{ {
var pin = await DecryptToUtf8Async(new EncString(await _stateService.GetProtectedPinAsync(userId))); 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. // 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. // 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( public async Task<UserKey> DecryptAndMigrateOldPinKeyAsync(
bool masterPasswordOnRestart, bool masterPasswordOnRestart,
string pin, string pin,

View File

@@ -336,12 +336,27 @@ namespace Bit.Core.Services
public async Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null) 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) 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) public async Task<bool> CanAccessPremiumAsync(string userId = null)
@@ -350,6 +365,7 @@ namespace Bit.Core.Services
{ {
userId = await GetActiveUserIdAsync(); userId = await GetActiveUserIdAsync();
} }
if (!await IsAuthenticatedAsync(userId)) if (!await IsAuthenticatedAsync(userId))
{ {
return false; return false;
@@ -397,13 +413,15 @@ namespace Bit.Core.Services
public async Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null) 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; return key != null ? new EncString(key) : null;
} }
public async Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = 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) public async Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null)
@@ -1445,28 +1463,30 @@ namespace Bit.Core.Services
} }
// Non-state storage // Non-state storage
await SetProtectedPinAsync(null, userId); await Task.WhenAll(
await SetPinProtectedAsync(null, userId); SetUserKeyAutoUnlockAsync(null, userId),
await SetKeyEncryptedAsync(null, userId); SetProtectedPinAsync(null, userId),
await SetKeyHashAsync(null, userId); SetKeyHashAsync(null, userId),
await SetEncKeyEncryptedAsync(null, userId); SetOrgKeysEncryptedAsync(null, userId),
await SetOrgKeysEncryptedAsync(null, userId); SetPrivateKeyEncryptedAsync(null, userId),
await SetPrivateKeyEncryptedAsync(null, userId); SetLastActiveTimeAsync(null, userId),
await SetLastActiveTimeAsync(null, userId); SetPreviousPageInfoAsync(null, userId),
await SetPreviousPageInfoAsync(null, userId); SetInvalidUnlockAttemptsAsync(null, userId),
await SetInvalidUnlockAttemptsAsync(null, userId); SetLocalDataAsync(null, userId),
await SetLocalDataAsync(null, userId); SetEncryptedCiphersAsync(null, userId),
await SetEncryptedCiphersAsync(null, userId); SetEncryptedCollectionsAsync(null, userId),
await SetEncryptedCollectionsAsync(null, userId); SetLastSyncAsync(null, userId),
await SetLastSyncAsync(null, userId); SetEncryptedFoldersAsync(null, userId),
await SetEncryptedFoldersAsync(null, userId); SetEncryptedPoliciesAsync(null, userId),
await SetEncryptedPoliciesAsync(null, userId); SetUsesKeyConnectorAsync(null, userId),
await SetUsesKeyConnectorAsync(null, userId); SetOrganizationsAsync(null, userId),
await SetOrganizationsAsync(null, userId); SetEncryptedPasswordGenerationHistoryAsync(null, userId),
await SetEncryptedPasswordGenerationHistoryAsync(null, userId); SetEncryptedSendsAsync(null, userId),
await SetEncryptedSendsAsync(null, userId); SetSettingsAsync(null, userId),
await SetSettingsAsync(null, userId); SetApprovePasswordlessLoginsAsync(null, userId),
await SetApprovePasswordlessLoginsAsync(null, userId); SetEncKeyEncryptedAsync(null, userId),
SetKeyEncryptedAsync(null, userId),
SetPinProtectedAsync(null, userId));
} }
private async Task ScaffoldNewAccountAsync(Account account) private async Task ScaffoldNewAccountAsync(Account account)
@@ -1713,6 +1733,14 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.KeyKey(reconciledOptions.UserId), value, reconciledOptions); 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")] [Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
public async Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null) 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) public async Task<bool> IsLockedAsync(string userId = null)
{ {
var hasKey = await _cryptoService.HasUserKeyAsync(userId); var biometricSet = await IsBiometricLockSetAsync(userId);
if (hasKey) if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
{ {
var biometricSet = await IsBiometricLockSetAsync(userId); return true;
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId)) }
if (!await _cryptoService.HasUserKeyAsync(userId))
{
if (await _cryptoService.HasAutoUnlockKeyAsync(userId))
{
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId));
}
else
{ {
return true; return true;
} }
} }
// Check again to verify auto key was set
var hasKey = await _cryptoService.HasUserKeyAsync(userId);
return !hasKey; return !hasKey;
} }
@@ -196,6 +207,7 @@ namespace Bit.Core.Services
await Task.WhenAll( await Task.WhenAll(
_cryptoService.ClearUserKeyAsync(userId), _cryptoService.ClearUserKeyAsync(userId),
_cryptoService.ClearMasterKeyAsync(userId), _cryptoService.ClearMasterKeyAsync(userId),
_stateService.SetUserKeyAutoUnlockAsync(null, userId),
_cryptoService.ClearOrgKeysAsync(true, userId), _cryptoService.ClearOrgKeysAsync(true, userId),
_cryptoService.ClearKeyPairAsync(true, userId)); _cryptoService.ClearKeyPairAsync(true, userId));