From c3ad5f0580cdf0fcde02dd0f08279ebfef16c309 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 30 Jan 2023 17:34:50 +0100 Subject: [PATCH] [PS-2358] Add kdf configuration options (#2328) * Implement kdf configuration * Remove unused import * Move kdf parameters to kdfConfiguration struct * Remove unused state migration service keys * Revert newline changes in PCLCryptoFunctionService * Update KdfConfiguration.cs * Add checks for argon2, clean statemigration service * Update constants * Clean up code * Further cleanup * Change KdfType to non-nullable in SetKeyConnectorKeyRequest --------- Co-authored-by: Kyle Spearrin --- src/App/Pages/Accounts/LockPageViewModel.cs | 13 ++--- .../Pages/Accounts/RegisterPageViewModel.cs | 10 ++-- .../Accounts/SetPasswordPageViewModel.cs | 13 ++--- .../UpdateTempPasswordPageViewModel.cs | 5 +- .../SettingsPage/SettingsPageViewModel.cs | 7 +-- src/Core/Abstractions/ICryptoService.cs | 7 ++- src/Core/Abstractions/IStateService.cs | 5 +- src/Core/Constants.cs | 5 +- src/Core/Models/Domain/Account.cs | 4 ++ src/Core/Models/Domain/KdfConfiguration.cs | 27 ++++++++++ src/Core/Models/Request/RegisterRequest.cs | 2 + .../Request/SetKeyConnectorKeyRequest.cs | 11 ++-- src/Core/Models/Request/SetPasswordRequest.cs | 2 + .../Models/Response/IdentityTokenResponse.cs | 4 ++ src/Core/Models/Response/PreloginResponse.cs | 5 ++ src/Core/Services/AuthService.cs | 12 ++--- src/Core/Services/CryptoService.cs | 52 ++++++++++++------- src/Core/Services/StateService.cs | 30 ++--------- .../BaseLockPasswordViewController.cs | 11 ++-- .../Controllers/LockPasswordViewController.cs | 11 ++-- 20 files changed, 135 insertions(+), 101 deletions(-) create mode 100755 src/Core/Models/Domain/KdfConfiguration.cs diff --git a/src/App/Pages/Accounts/LockPageViewModel.cs b/src/App/Pages/Accounts/LockPageViewModel.cs index 5d2fccfd3..efa89edac 100644 --- a/src/App/Pages/Accounts/LockPageViewModel.cs +++ b/src/App/Pages/Accounts/LockPageViewModel.cs @@ -228,8 +228,7 @@ namespace Bit.App.Pages } ShowPassword = false; - var kdf = await _stateService.GetKdfTypeAsync(); - var kdfIterations = await _stateService.GetKdfIterationsAsync(); + var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); if (PinLock) { @@ -239,7 +238,7 @@ namespace Bit.App.Pages if (_isPinProtected) { var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), + kdfConfig, await _stateService.GetPinProtectedKeyAsync()); var encKey = await _cryptoService.GetEncKeyAsync(key); var protectedPin = await _stateService.GetProtectedPinAsync(); @@ -254,8 +253,7 @@ namespace Bit.App.Pages } else { - var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); + var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig); failed = false; Pin = string.Empty; await AppHelpers.ResetInvalidUnlockAttemptsAsync(); @@ -280,7 +278,7 @@ namespace Bit.App.Pages } else { - var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations); + var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig); var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var passwordValid = false; @@ -314,8 +312,7 @@ namespace Bit.App.Pages var protectedPin = await _stateService.GetProtectedPinAsync(); var encKey = await _cryptoService.GetEncKeyAsync(key); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); - var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); + var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig); await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey)); } MasterPassword = string.Empty; diff --git a/src/App/Pages/Accounts/RegisterPageViewModel.cs b/src/App/Pages/Accounts/RegisterPageViewModel.cs index f03fc8abe..46d54350d 100644 --- a/src/App/Pages/Accounts/RegisterPageViewModel.cs +++ b/src/App/Pages/Accounts/RegisterPageViewModel.cs @@ -175,8 +175,8 @@ namespace Bit.App.Pages Name = string.IsNullOrWhiteSpace(Name) ? null : Name; Email = Email.Trim().ToLower(); - var kdf = KdfType.PBKDF2_SHA256; - var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdf, Constants.KdfIterations); + var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); + var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig); var encKey = await _cryptoService.MakeEncKeyAsync(key); var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key); var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); @@ -187,8 +187,10 @@ namespace Bit.App.Pages MasterPasswordHash = hashedPassword, MasterPasswordHint = Hint, Key = encKey.Item2.EncryptedString, - Kdf = kdf, - KdfIterations = Constants.KdfIterations, + Kdf = kdfConfig.Type, + KdfIterations = kdfConfig.Iterations, + KdfMemory = kdfConfig.Memory, + KdfParallelism = kdfConfig.Parallelism, Keys = new KeysRequest { PublicKey = keys.Item1, diff --git a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs index 70b73f0ad..ecacbb265 100644 --- a/src/App/Pages/Accounts/SetPasswordPageViewModel.cs +++ b/src/App/Pages/Accounts/SetPasswordPageViewModel.cs @@ -163,9 +163,9 @@ namespace Bit.App.Pages return; } - var kdf = KdfType.PBKDF2_SHA256; + var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); var email = await _stateService.GetEmailAsync(); - var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, Constants.KdfIterations); + var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization); @@ -186,8 +186,10 @@ namespace Bit.App.Pages MasterPasswordHash = masterPasswordHash, Key = encKey.Item2.EncryptedString, MasterPasswordHint = Hint, - Kdf = kdf, - KdfIterations = Constants.KdfIterations, + Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256), + KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations), + KdfMemory = kdfConfig.Memory, + KdfParallelism = kdfConfig.Parallelism, OrgIdentifier = OrgIdentifier, Keys = new KeysRequest { @@ -201,8 +203,7 @@ namespace Bit.App.Pages await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount); // Set Password and relevant information await _apiService.SetPasswordAsync(request); - await _stateService.SetKdfTypeAsync(kdf); - await _stateService.SetKdfIterationsAsync(Constants.KdfIterations); + await _stateService.SetKdfConfigurationAsync(kdfConfig); await _cryptoService.SetKeyAsync(key); await _cryptoService.SetKeyHashAsync(localMasterPasswordHash); await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString); diff --git a/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs b/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs index 918e40fdc..5bac74d46 100644 --- a/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs +++ b/src/App/Pages/Accounts/UpdateTempPasswordPageViewModel.cs @@ -43,12 +43,11 @@ namespace Bit.App.Pages } // Retrieve details for key generation - var kdf = await _stateService.GetKdfTypeAsync(); - var kdfIterations = await _stateService.GetKdfIterationsAsync(); + var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var email = await _stateService.GetEmailAsync(); // Create new key and hash new password - var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations); + var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig); var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); // Create new encKey for the User diff --git a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs index bf00f5a3e..d6e348a64 100644 --- a/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs +++ b/src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs @@ -422,12 +422,9 @@ namespace Bit.App.Pages AppResources.Yes, AppResources.No); } - var kdf = await _stateService.GetKdfTypeAsync(); - var kdfIterations = await _stateService.GetKdfIterationsAsync(); + var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var email = await _stateService.GetEmailAsync(); - var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, - kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256), - kdfIterations.GetValueOrDefault(5000)); + var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig); var key = await _cryptoService.GetKeyAsync(); var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey); diff --git a/src/Core/Abstractions/ICryptoService.cs b/src/Core/Abstractions/ICryptoService.cs index b628f3ac1..60bbd3624 100644 --- a/src/Core/Abstractions/ICryptoService.cs +++ b/src/Core/Abstractions/ICryptoService.cs @@ -36,11 +36,10 @@ namespace Bit.Core.Abstractions Task HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization); Task HasKeyAsync(string userId = null); Task> MakeEncKeyAsync(SymmetricCryptoKey key); - Task MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations); - Task MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations, - EncString protectedKeyEs = null); + Task MakeKeyAsync(string password, string salt, KdfConfig config); + Task MakeKeyFromPinAsync(string pin, string salt, KdfConfig config, EncString protectedKeyEs = null); Task> MakeKeyPairAsync(SymmetricCryptoKey key = null); - Task MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations); + Task MakePinKeyAysnc(string pin, string salt, KdfConfig config); Task> MakeShareKeyAsync(); Task MakeSendKeyAsync(byte[] keyMaterial); Task RandomNumberAsync(int min, int max); diff --git a/src/Core/Abstractions/IStateService.cs b/src/Core/Abstractions/IStateService.cs index 86e8c404b..621a7cee3 100644 --- a/src/Core/Abstractions/IStateService.cs +++ b/src/Core/Abstractions/IStateService.cs @@ -37,10 +37,7 @@ namespace Bit.Core.Abstractions Task SetPinProtectedAsync(string value, string userId = null); Task GetPinProtectedKeyAsync(string userId = null); Task SetPinProtectedKeyAsync(EncString value, string userId = null); - Task GetKdfTypeAsync(string userId = null); - Task SetKdfTypeAsync(KdfType? value, string userId = null); - Task GetKdfIterationsAsync(string userId = null); - Task SetKdfIterationsAsync(int? value, string userId = null); + Task SetKdfConfigurationAsync(KdfConfig config, string userId = null); Task GetKeyEncryptedAsync(string userId = null); Task SetKeyEncryptedAsync(string value, string userId = null); Task GetKeyDecryptedAsync(string userId = null); diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index e48d32647..b520cf108 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -46,7 +46,10 @@ public const int SaveFileRequestCode = 44; public const int TotpDefaultTimer = 30; public const int PasswordlessNotificationTimeoutInMinutes = 15; - public const int KdfIterations = 600000; + public const int Pbkdf2Iterations = 600000; + public const int Argon2Iterations = 3; + public const int Argon2MemoryInMB = 64; + public const int Argon2Parallelism = 4; public const int MasterPasswordMinimumChars = 8; public static readonly string[] AndroidAllClearCipherCacheKeys = diff --git a/src/Core/Models/Domain/Account.cs b/src/Core/Models/Domain/Account.cs index ed0b4cc39..b110a9563 100644 --- a/src/Core/Models/Domain/Account.cs +++ b/src/Core/Models/Domain/Account.cs @@ -46,6 +46,8 @@ namespace Bit.Core.Models.Domain OrgIdentifier = copy.OrgIdentifier; KdfType = copy.KdfType; KdfIterations = copy.KdfIterations; + KdfMemory = copy.KdfMemory; + KdfParallelism = copy.KdfParallelism; EmailVerified = copy.EmailVerified; HasPremiumPersonally = copy.HasPremiumPersonally; AvatarColor = copy.AvatarColor; @@ -59,6 +61,8 @@ namespace Bit.Core.Models.Domain public string AvatarColor; public KdfType? KdfType; public int? KdfIterations; + public int? KdfMemory; + public int? KdfParallelism; public bool? EmailVerified; public bool? HasPremiumPersonally; } diff --git a/src/Core/Models/Domain/KdfConfiguration.cs b/src/Core/Models/Domain/KdfConfiguration.cs new file mode 100755 index 000000000..c185565be --- /dev/null +++ b/src/Core/Models/Domain/KdfConfiguration.cs @@ -0,0 +1,27 @@ +using Bit.Core; +using Bit.Core.Enums; +using Bit.Core.Models.Domain; +public struct KdfConfig +{ + public static KdfConfig Default = new KdfConfig(KdfType.PBKDF2_SHA256, 5000, null, null); + public KdfConfig(KdfType? type, int? iterations, int? memory, int? parallelism) + { + Type = type; + Iterations = iterations; + Memory = memory; + Parallelism = parallelism; + } + + public KdfConfig(Account.AccountProfile profile) + { + Type = profile.KdfType; + Iterations = profile.KdfIterations; + Memory = profile.KdfMemory; + Parallelism = profile.KdfParallelism; + } + + public KdfType? Type { get; set; } + public int? Iterations { get; set; } + public int? Memory { get; set; } + public int? Parallelism { get; set; } +} diff --git a/src/Core/Models/Request/RegisterRequest.cs b/src/Core/Models/Request/RegisterRequest.cs index ee38ff026..92b5d3c76 100644 --- a/src/Core/Models/Request/RegisterRequest.cs +++ b/src/Core/Models/Request/RegisterRequest.cs @@ -15,6 +15,8 @@ namespace Bit.Core.Models.Request public Guid? OrganizationUserId { get; set; } public KdfType? Kdf { get; set; } public int? KdfIterations { get; set; } + public int? KdfMemory { get; set; } + public int? KdfParallelism { get; set; } public string CaptchaResponse { get; set; } } } diff --git a/src/Core/Models/Request/SetKeyConnectorKeyRequest.cs b/src/Core/Models/Request/SetKeyConnectorKeyRequest.cs index fc053c160..3790a8b2c 100644 --- a/src/Core/Models/Request/SetKeyConnectorKeyRequest.cs +++ b/src/Core/Models/Request/SetKeyConnectorKeyRequest.cs @@ -9,15 +9,18 @@ namespace Bit.Core.Models.Request public KeysRequest Keys { get; set; } public KdfType Kdf { get; set; } public int? KdfIterations { get; set; } + public int? KdfMemory { get; set; } + public int? KdfParallelism { get; set; } public string OrgIdentifier { get; set; } - public SetKeyConnectorKeyRequest(string key, KeysRequest keys, - KdfType kdf, int? kdfIterations, string orgIdentifier) + public SetKeyConnectorKeyRequest(string key, KeysRequest keys, KdfConfig kdfConfig, string orgIdentifier) { this.Key = key; this.Keys = keys; - this.Kdf = kdf; - this.KdfIterations = kdfIterations; + this.Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256); + this.KdfIterations = kdfConfig.Iterations; + this.KdfMemory = kdfConfig.Memory; + this.KdfParallelism = kdfConfig.Parallelism; this.OrgIdentifier = orgIdentifier; } } diff --git a/src/Core/Models/Request/SetPasswordRequest.cs b/src/Core/Models/Request/SetPasswordRequest.cs index 28db2ab4e..d41eb2249 100644 --- a/src/Core/Models/Request/SetPasswordRequest.cs +++ b/src/Core/Models/Request/SetPasswordRequest.cs @@ -10,6 +10,8 @@ namespace Bit.Core.Models.Request public KeysRequest Keys { get; set; } public KdfType Kdf { get; set; } public int KdfIterations { get; set; } + public int? KdfMemory { get; set; } + public int? KdfParallelism { get; set; } public string OrgIdentifier { get; set; } } } diff --git a/src/Core/Models/Response/IdentityTokenResponse.cs b/src/Core/Models/Response/IdentityTokenResponse.cs index 0ef256a0e..eac480f42 100644 --- a/src/Core/Models/Response/IdentityTokenResponse.cs +++ b/src/Core/Models/Response/IdentityTokenResponse.cs @@ -20,7 +20,11 @@ namespace Bit.Core.Models.Response public string TwoFactorToken { get; set; } public KdfType Kdf { get; set; } public int? KdfIterations { get; set; } + public int? KdfMemory { get; set; } + public int? KdfParallelism { get; set; } public bool ForcePasswordReset { get; set; } public string KeyConnectorUrl { get; set; } + [JsonIgnore] + public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism); } } diff --git a/src/Core/Models/Response/PreloginResponse.cs b/src/Core/Models/Response/PreloginResponse.cs index 33cf5c385..74cfda0ce 100644 --- a/src/Core/Models/Response/PreloginResponse.cs +++ b/src/Core/Models/Response/PreloginResponse.cs @@ -1,4 +1,5 @@ using Bit.Core.Enums; +using Newtonsoft.Json; namespace Bit.Core.Models.Response { @@ -6,5 +7,9 @@ namespace Bit.Core.Models.Response { public KdfType Kdf { get; set; } public int KdfIterations { get; set; } + public int? KdfMemory { get; set; } + public int? KdfParallelism { get; set; } + [JsonIgnore] + public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism); } } diff --git a/src/Core/Services/AuthService.cs b/src/Core/Services/AuthService.cs index 4a6e82cb6..04525a188 100644 --- a/src/Core/Services/AuthService.cs +++ b/src/Core/Services/AuthService.cs @@ -276,15 +276,13 @@ namespace Bit.Core.Services private async Task MakePreloginKeyAsync(string masterPassword, string email) { email = email.Trim().ToLower(); - KdfType? kdf = null; - int? kdfIterations = null; + KdfConfig kdfConfig = KdfConfig.Default; try { var preloginResponse = await _apiService.PostPreloginAsync(new PreloginRequest { Email = email }); if (preloginResponse != null) { - kdf = preloginResponse.Kdf; - kdfIterations = preloginResponse.KdfIterations; + kdfConfig = preloginResponse.KdfConfig; } } catch (ApiException e) @@ -294,7 +292,7 @@ namespace Bit.Core.Services throw; } } - return await _cryptoService.MakeKeyAsync(masterPassword, email, kdf, kdfIterations); + return await _cryptoService.MakeKeyAsync(masterPassword, email, kdfConfig); } private async Task LogInHelperAsync(string email, string hashedPassword, string localHashedPassword, @@ -442,7 +440,7 @@ namespace Bit.Core.Services { // SSO Key Connector Onboarding var password = await _cryptoFunctionService.RandomBytesAsync(64); - var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.Kdf, tokenResponse.KdfIterations); + var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig); var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64); await _cryptoService.SetKeyAsync(k); @@ -465,7 +463,7 @@ namespace Bit.Core.Services EncryptedPrivateKey = keyPair.Item2.EncryptedString }; var setPasswordRequest = new SetKeyConnectorKeyRequest( - encKey.Item2.EncryptedString, keys, tokenResponse.Kdf, tokenResponse.KdfIterations, orgId + encKey.Item2.EncryptedString, keys, tokenResponse.KdfConfig, orgId ); await _apiService.PostSetKeyConnectorKey(setPasswordRequest); } diff --git a/src/Core/Services/CryptoService.cs b/src/Core/Services/CryptoService.cs index 433424ed8..b8aaadb7d 100644 --- a/src/Core/Services/CryptoService.cs +++ b/src/Core/Services/CryptoService.cs @@ -389,30 +389,46 @@ namespace Bit.Core.Services await SetKeyAsync(key); } - public async Task MakeKeyAsync(string password, string salt, - KdfType? kdf, int? kdfIterations) + public async Task MakeKeyAsync(string password, string salt, KdfConfig kdfConfig) { byte[] key = null; - if (kdf == null || kdf == KdfType.PBKDF2_SHA256) + if (kdfConfig.Type == null || kdfConfig.Type == KdfType.PBKDF2_SHA256) { - if (kdfIterations == null) - { - kdfIterations = 5000; - } - if (kdfIterations < 5000) + var iterations = kdfConfig.Iterations.GetValueOrDefault(5000); + if (iterations < 5000) { throw new Exception("PBKDF2 iteration minimum is 5000."); } key = await _cryptoFunctionService.Pbkdf2Async(password, salt, - CryptoHashAlgorithm.Sha256, kdfIterations.Value); + CryptoHashAlgorithm.Sha256, iterations); } - else if (kdf == KdfType.Argon2id) + else if (kdfConfig.Type == KdfType.Argon2id) { - var iterations = kdfIterations.Value; - const int parallelism = 1; - const int memory = 1024 * 16; // 16 MiB + var iterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Argon2Iterations); + var memory = kdfConfig.Memory.GetValueOrDefault(Constants.Argon2MemoryInMB) * 1024; + var parallelism = kdfConfig.Parallelism.GetValueOrDefault(Constants.Argon2Parallelism); - key = await _cryptoFunctionService.Argon2Async(password, salt, iterations, memory, parallelism); + if (kdfConfig.Iterations < 2) + { + throw new Exception("Argon2 iterations minimum is 2"); + } + + if (kdfConfig.Memory < 16) + { + throw new Exception("Argon2 memory minimum is 16 MB"); + } + else if (kdfConfig.Memory > 1024) + { + throw new Exception("Argon2 memory maximum is 1024 MB"); + } + + if (kdfConfig.Parallelism < 1) + { + throw new Exception("Argon2 parallelism minimum is 1"); + } + + var saltHash = await _cryptoFunctionService.HashAsync(salt, CryptoHashAlgorithm.Sha256); + key = await _cryptoFunctionService.Argon2Async(password, saltHash, iterations, memory, parallelism); } else { @@ -422,7 +438,7 @@ namespace Bit.Core.Services } public async Task MakeKeyFromPinAsync(string pin, string salt, - KdfType kdf, int kdfIterations, EncString protectedKeyCs = null) + KdfConfig config, EncString protectedKeyCs = null) { if (protectedKeyCs == null) { @@ -433,7 +449,7 @@ namespace Bit.Core.Services } protectedKeyCs = new EncString(pinProtectedKey); } - var pinKey = await MakePinKeyAysnc(pin, salt, kdf, kdfIterations); + var pinKey = await MakePinKeyAysnc(pin, salt, config); var decKey = await DecryptToBytesAsync(protectedKeyCs, pinKey); return new SymmetricCryptoKey(decKey); } @@ -454,9 +470,9 @@ namespace Bit.Core.Services return new Tuple(publicB64, privateEnc); } - public async Task MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations) + public async Task MakePinKeyAysnc(string pin, string salt, KdfConfig config) { - var pinKey = await MakeKeyAsync(pin, salt, kdf, kdfIterations); + var pinKey = await MakeKeyAsync(pin, salt, config); return await StretchKeyAsync(pinKey); } diff --git a/src/Core/Services/StateService.cs b/src/Core/Services/StateService.cs index 7cd1716d6..5c587d82f 100644 --- a/src/Core/Services/StateService.cs +++ b/src/Core/Services/StateService.cs @@ -339,35 +339,15 @@ namespace Bit.Core.Services await SaveAccountAsync(account, reconciledOptions); } - public async Task GetKdfTypeAsync(string userId = null) - { - return (await GetAccountAsync( - ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) - ))?.Profile?.KdfType; - } - - public async Task SetKdfTypeAsync(KdfType? value, string userId = null) + public async Task SetKdfConfigurationAsync(KdfConfig config, string userId = null) { var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()); var account = await GetAccountAsync(reconciledOptions); - account.Profile.KdfType = value; - await SaveAccountAsync(account, reconciledOptions); - } - - public async Task GetKdfIterationsAsync(string userId = null) - { - return (await GetAccountAsync( - ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync()) - ))?.Profile?.KdfIterations; - } - - public async Task SetKdfIterationsAsync(int? value, string userId = null) - { - var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, - await GetDefaultStorageOptionsAsync()); - var account = await GetAccountAsync(reconciledOptions); - account.Profile.KdfIterations = value; + account.Profile.KdfType = config.Type; + account.Profile.KdfIterations = config.Iterations; + account.Profile.KdfMemory = config.Memory; + account.Profile.KdfParallelism = config.Parallelism; await SaveAccountAsync(account, reconciledOptions); } diff --git a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs index 98db64ad6..3597cb76f 100644 --- a/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/BaseLockPasswordViewController.cs @@ -221,8 +221,7 @@ namespace Bit.iOS.Core.Controllers } var email = await _stateService.GetEmailAsync(); - var kdf = await _stateService.GetKdfTypeAsync(); - var kdfIterations = await _stateService.GetKdfIterationsAsync(); + var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var inputtedValue = MasterPasswordCell.TextField.Text; if (_pinLock) @@ -233,7 +232,7 @@ namespace Bit.iOS.Core.Controllers if (_isPinProtected) { var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), + kdfConfig, await _stateService.GetPinProtectedKeyAsync()); var encKey = await _cryptoService.GetEncKeyAsync(key); var protectedPin = await _stateService.GetProtectedPinAsync(); @@ -248,7 +247,7 @@ namespace Bit.iOS.Core.Controllers else { var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); + kdfConfig); failed = false; await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key2); @@ -265,7 +264,7 @@ namespace Bit.iOS.Core.Controllers } else { - var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations); + var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); var storedKeyHash = await _cryptoService.GetKeyHashAsync(); if (storedKeyHash == null) @@ -287,7 +286,7 @@ namespace Bit.iOS.Core.Controllers var encKey = await _cryptoService.GetEncKeyAsync(key2); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); + kdfConfig); await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey)); } await AppHelpers.ResetInvalidUnlockAttemptsAsync(); diff --git a/src/iOS.Core/Controllers/LockPasswordViewController.cs b/src/iOS.Core/Controllers/LockPasswordViewController.cs index 7a04759cf..2b73149eb 100644 --- a/src/iOS.Core/Controllers/LockPasswordViewController.cs +++ b/src/iOS.Core/Controllers/LockPasswordViewController.cs @@ -210,8 +210,7 @@ namespace Bit.iOS.Core.Controllers } var email = await _stateService.GetEmailAsync(); - var kdf = await _stateService.GetKdfTypeAsync(); - var kdfIterations = await _stateService.GetKdfIterationsAsync(); + var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var inputtedValue = MasterPasswordCell.TextField.Text; if (_pinLock) @@ -222,7 +221,7 @@ namespace Bit.iOS.Core.Controllers if (_isPinProtected) { var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000), + kdfConfig, await _stateService.GetPinProtectedKeyAsync()); var encKey = await _cryptoService.GetEncKeyAsync(key); var protectedPin = await _stateService.GetProtectedPinAsync(); @@ -237,7 +236,7 @@ namespace Bit.iOS.Core.Controllers else { var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); + kdfConfig); failed = false; await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await SetKeyAndContinueAsync(key2); @@ -260,7 +259,7 @@ namespace Bit.iOS.Core.Controllers } else { - var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdf, kdfIterations); + var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); var storedKeyHash = await _cryptoService.GetKeyHashAsync(); if (storedKeyHash == null) @@ -282,7 +281,7 @@ namespace Bit.iOS.Core.Controllers var encKey = await _cryptoService.GetEncKeyAsync(key2); var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey); var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email, - kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000)); + kdfConfig); await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey)); } await AppHelpers.ResetInvalidUnlockAttemptsAsync();