diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index 46ce7a52e..5f75ac3d7 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -323,6 +323,7 @@ namespace Bit.App.Pages } if (oldTimeout != newTimeout) { + await _cryptoService.RefreshKeysAsync(); await Device.InvokeOnMainThreadAsync(BuildList); } } diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index 040068b24..b7d03df2d 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -19,6 +19,8 @@ namespace Bit.Core.Abstractions Task MakeUserKeyAsync(); Task ClearUserKeyAsync(string userId = null); Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null); + Task GetAutoUnlockKeyAsync(string userId = null); + Task HasAutoUnlockKeyAsync(string userId = null); Task SetMasterKeyAsync(MasterKey masterKey, string userId = null); Task GetMasterKeyAsync(string userId = null); Task MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig); diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index add556dcf..e98936863 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -19,6 +19,8 @@ namespace Bit.Core.Abstractions Task SetMasterKeyAsync(MasterKey value, string userId = null); Task GetMasterKeyEncryptedUserKeyAsync(string userId = null); Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null); + Task GetUserKeyAutoUnlockAsync(string userId = null); + Task SetUserKeyAutoUnlockAsync(string value, string userId = null); Task GetActiveUserIdAsync(); Task GetActiveUserEmailAsync(); Task GetActiveUserCustomDataAsync(Func 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 GetKeyEncryptedAsync(string userId = null); [Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")] Task GetKeyDecryptedAsync(string userId = null); } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index f4ddb10b1..608b1845c 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -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}"; diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 5786cb19c..2b61f9e67 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -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 GetUserKeyAsync(string userId = null) @@ -108,6 +99,17 @@ namespace Bit.Core.Services return _stateService.SetMasterKeyEncryptedUserKeyAsync(value, userId); } + public async Task GetAutoUnlockKeyAsync(string userId = null) + { + await MigrateAutoUnlockKeyIfNeededAsync(userId); + return await _stateService.GetUserKeyAutoUnlockAsync(userId); + } + + public async Task 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 DecryptAndMigrateOldPinKeyAsync( bool masterPasswordOnRestart, string pin, diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index dbe57cf4b..77d51833c 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -336,12 +336,27 @@ namespace Bit.Core.Services public async Task GetMasterKeyEncryptedUserKeyAsync(string userId = null) { - return await _storageMediatorService.GetAsync(Constants.MasterKeyEncryptedUserKeyKey(userId), false); + return await _storageMediatorService.GetAsync( + 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 GetUserKeyAutoUnlockAsync(string userId = null) + { + var keyB64 = await _storageMediatorService.GetAsync( + 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 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 GetPinKeyEncryptedUserKeyAsync(string userId = null) { - var key = await _storageMediatorService.GetAsync(Constants.PinKeyEncryptedUserKeyKey(userId), false); + var key = await _storageMediatorService.GetAsync( + 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 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 GetKeyEncryptedAsync(string userId = null) + { + var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, + await GetDefaultSecureStorageOptionsAsync()); + return await GetValueAsync(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions); + } + [Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")] public async Task GetKeyDecryptedAsync(string userId = null) { diff --git a/src/Core/Services/VaultTimeoutService.cs b/src/Core/Services/VaultTimeoutService.cs index 28639aeea..28b48c258 100644 --- a/src/Core/Services/VaultTimeoutService.cs +++ b/src/Core/Services/VaultTimeoutService.cs @@ -59,15 +59,26 @@ namespace Bit.Core.Services public async Task 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));