diff --git a/src/App/Abstractions/Repositories/IAccountsApiRepository.cs b/src/App/Abstractions/Repositories/IAccountsApiRepository.cs index 980526eb2..ce40985fe 100644 --- a/src/App/Abstractions/Repositories/IAccountsApiRepository.cs +++ b/src/App/Abstractions/Repositories/IAccountsApiRepository.cs @@ -6,6 +6,7 @@ namespace Bit.App.Abstractions { public interface IAccountsApiRepository { + Task> PostPreloginAsync(PreloginRequest requestObj); Task PostRegisterAsync(RegisterRequest requestObj); Task PostPasswordHintAsync(PasswordHintRequest requestObj); Task> GetAccountRevisionDateAsync(); diff --git a/src/App/Abstractions/Services/IAuthService.cs b/src/App/Abstractions/Services/IAuthService.cs index 5f6e542a4..5ebdc6f5e 100644 --- a/src/App/Abstractions/Services/IAuthService.cs +++ b/src/App/Abstractions/Services/IAuthService.cs @@ -11,6 +11,8 @@ namespace Bit.App.Abstractions string PreviousUserId { get; } bool UserIdChanged { get; } string Email { get; set; } + KdfType Kdf { get; set; } + int KdfIterations { get; set; } string PIN { get; set; } bool BelongsToOrganization(string orgId); void LogOut(string logoutMessage = null); diff --git a/src/App/Abstractions/Services/ICryptoService.cs b/src/App/Abstractions/Services/ICryptoService.cs index 89ace6a32..636587c49 100644 --- a/src/App/Abstractions/Services/ICryptoService.cs +++ b/src/App/Abstractions/Services/ICryptoService.cs @@ -1,6 +1,6 @@ -using Bit.App.Models; +using Bit.App.Enums; +using Bit.App.Models; using Bit.App.Models.Api; -using System; using System.Collections.Generic; namespace Bit.App.Abstractions @@ -23,8 +23,7 @@ namespace Bit.App.Abstractions byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey); CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null); byte[] EncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key = null); - SymmetricCryptoKey MakeKeyFromPassword(string password, string salt); - string MakeKeyFromPasswordBase64(string password, string salt); + SymmetricCryptoKey MakeKeyFromPassword(string password, string salt, KdfType kdf, int kdfIterations); byte[] HashPassword(SymmetricCryptoKey key, string password); string HashPasswordBase64(SymmetricCryptoKey key, string password); CipherString MakeEncKey(SymmetricCryptoKey key); diff --git a/src/App/Enums/KdfType.cs b/src/App/Enums/KdfType.cs new file mode 100644 index 000000000..0ecec8cad --- /dev/null +++ b/src/App/Enums/KdfType.cs @@ -0,0 +1,7 @@ +namespace Bit.App.Enums +{ + public enum KdfType : short + { + PBKDF2 = 0 + } +} diff --git a/src/App/Models/Api/Request/PreloginRequest.cs b/src/App/Models/Api/Request/PreloginRequest.cs new file mode 100644 index 000000000..1cecc66bb --- /dev/null +++ b/src/App/Models/Api/Request/PreloginRequest.cs @@ -0,0 +1,7 @@ +namespace Bit.App.Models.Api +{ + public class PreloginRequest + { + public string Email { get; set; } + } +} diff --git a/src/App/Models/Api/Request/RegisterRequest.cs b/src/App/Models/Api/Request/RegisterRequest.cs index 618ec0bb0..5bf95d462 100644 --- a/src/App/Models/Api/Request/RegisterRequest.cs +++ b/src/App/Models/Api/Request/RegisterRequest.cs @@ -1,4 +1,6 @@ -namespace Bit.App.Models.Api +using Bit.App.Enums; + +namespace Bit.App.Models.Api { public class RegisterRequest { @@ -7,5 +9,7 @@ public string MasterPasswordHash { get; set; } public string MasterPasswordHint { get; set; } public string Key { get; set; } + public KdfType Kdf { get; set; } + public int KdfIterations { get; set; } } } diff --git a/src/App/Models/Api/Response/PreloginResponse.cs b/src/App/Models/Api/Response/PreloginResponse.cs new file mode 100644 index 000000000..c471d12a7 --- /dev/null +++ b/src/App/Models/Api/Response/PreloginResponse.cs @@ -0,0 +1,10 @@ +using Bit.App.Enums; + +namespace Bit.App.Models.Api +{ + public class PreloginResponse + { + public KdfType Kdf { get; set; } + public int KdfIterations { get; set; } + } +} diff --git a/src/App/Pages/Lock/LockPasswordPage.cs b/src/App/Pages/Lock/LockPasswordPage.cs index f993039e0..78aaf735a 100644 --- a/src/App/Pages/Lock/LockPasswordPage.cs +++ b/src/App/Pages/Lock/LockPasswordPage.cs @@ -151,7 +151,8 @@ namespace Bit.App.Pages return; } - var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, _authService.Email); + var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, _authService.Email, + _authService.Kdf, _authService.KdfIterations); if(key.Key.SequenceEqual(_cryptoService.Key.Key)) { _appSettingsService.Locked = false; diff --git a/src/App/Pages/RegisterPage.cs b/src/App/Pages/RegisterPage.cs index 183e3d49b..d715c47d4 100644 --- a/src/App/Pages/RegisterPage.cs +++ b/src/App/Pages/RegisterPage.cs @@ -192,8 +192,10 @@ namespace Bit.App.Pages return; } + var kdf = Enums.KdfType.PBKDF2; + var kdfIterations = 5000; var normalizedEmail = EmailCell.Entry.Text.ToLower(); - var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, normalizedEmail); + var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, normalizedEmail, kdf, kdfIterations); var encKey = _cryptoService.MakeEncKey(key); var request = new RegisterRequest { @@ -201,7 +203,9 @@ namespace Bit.App.Pages MasterPasswordHash = _cryptoService.HashPasswordBase64(key, PasswordCell.Entry.Text), MasterPasswordHint = !string.IsNullOrWhiteSpace(PasswordHintCell.Entry.Text) ? PasswordHintCell.Entry.Text : null, - Key = encKey.EncryptedString + Key = encKey.EncryptedString, + Kdf = kdf, + KdfIterations = kdfIterations }; await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount); diff --git a/src/App/Repositories/AccountsApiRepository.cs b/src/App/Repositories/AccountsApiRepository.cs index f81133a6e..e5ab2743d 100644 --- a/src/App/Repositories/AccountsApiRepository.cs +++ b/src/App/Repositories/AccountsApiRepository.cs @@ -20,6 +20,40 @@ namespace Bit.App.Repositories protected override string ApiRoute => "/accounts"; + public virtual async Task> PostPreloginAsync(PreloginRequest requestObj) + { + if(!Connectivity.IsConnected) + { + return HandledNotConnected(); + } + + using(var client = HttpService.ApiClient) + { + var requestMessage = new TokenHttpRequestMessage(requestObj) + { + Method = HttpMethod.Post, + RequestUri = new Uri(string.Concat(client.BaseAddress, ApiRoute, "/prelogin")), + }; + + try + { + var response = await client.SendAsync(requestMessage).ConfigureAwait(false); + if(!response.IsSuccessStatusCode) + { + return await HandleErrorAsync(response).ConfigureAwait(false); + } + + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var responseObj = JsonConvert.DeserializeObject(responseContent); + return ApiResult.Success(responseObj, response.StatusCode); + } + catch + { + return HandledWebException(); + } + } + } + public virtual async Task PostRegisterAsync(RegisterRequest requestObj) { if(!Connectivity.IsConnected) diff --git a/src/App/Services/AuthService.cs b/src/App/Services/AuthService.cs index da93e9b4e..6f5beefab 100644 --- a/src/App/Services/AuthService.cs +++ b/src/App/Services/AuthService.cs @@ -17,6 +17,8 @@ namespace Bit.App.Services public class AuthService : IAuthService { private const string EmailKey = "email"; + private const string KdfKey = "kdf"; + private const string KdfIterationsKey = "kdfIterations"; private const string UserIdKey = "userId"; private const string PreviousUserIdKey = "previousUserId"; private const string PinKey = "pin"; @@ -34,6 +36,8 @@ namespace Bit.App.Services private readonly IGoogleAnalyticsService _googleAnalyticsService; private string _email; + private KdfType? _kdf; + private int? _kdfIterations; private string _userId; private string _previousUserId; private string _pin; @@ -158,6 +162,40 @@ namespace Bit.App.Services } } + public KdfType Kdf + { + get + { + if(!_kdf.HasValue) + { + _kdf = (KdfType)_settings.GetValueOrDefault(KdfKey, (short)KdfType.PBKDF2); + } + return _kdf.Value; + } + set + { + _settings.AddOrUpdateValue(KdfKey, (short)value); + _kdf = value; + } + } + + public int KdfIterations + { + get + { + if(!_kdfIterations.HasValue) + { + _kdfIterations = _settings.GetValueOrDefault(KdfIterationsKey, 5000); + } + return _kdfIterations.Value; + } + set + { + _settings.AddOrUpdateValue(KdfIterationsKey, value); + _kdfIterations = value; + } + } + public bool IsAuthenticated { get @@ -231,10 +269,20 @@ namespace Bit.App.Services public async Task TokenPostAsync(string email, string masterPassword) { + Kdf = KdfType.PBKDF2; + KdfIterations = 5000; + var preloginResponse = await _accountsApiRepository.PostPreloginAsync( + new PreloginRequest { Email = email }); + if(preloginResponse.Succeeded) + { + Kdf = preloginResponse.Result.Kdf; + KdfIterations = preloginResponse.Result.KdfIterations; + } + var result = new FullLoginResult(); var normalizedEmail = email.Trim().ToLower(); - var key = _cryptoService.MakeKeyFromPassword(masterPassword, normalizedEmail); + var key = _cryptoService.MakeKeyFromPassword(masterPassword, normalizedEmail, Kdf, KdfIterations); var request = new TokenRequest { diff --git a/src/App/Services/CryptoService.cs b/src/App/Services/CryptoService.cs index 78d1b1fb7..20674db31 100644 --- a/src/App/Services/CryptoService.cs +++ b/src/App/Services/CryptoService.cs @@ -429,7 +429,7 @@ namespace Bit.App.Services return decryptedBytes; } - public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt) + public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt, KdfType kdf, int kdfIterations) { if(password == null) { @@ -444,16 +444,22 @@ namespace Bit.App.Services var passwordBytes = Encoding.UTF8.GetBytes(NormalizePassword(password)); var saltBytes = Encoding.UTF8.GetBytes(salt); - var keyBytes = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000); + byte[] keyBytes = null; + if(kdf == KdfType.PBKDF2) + { + if(kdfIterations < 5000) + { + throw new Exception("PBKDF2 iteration minimum is 5000."); + } + keyBytes = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, (uint)kdfIterations); + } + else + { + throw new Exception("Unknown Kdf."); + } return new SymmetricCryptoKey(keyBytes); } - public string MakeKeyFromPasswordBase64(string password, string salt) - { - var key = MakeKeyFromPassword(password, salt); - return Convert.ToBase64String(key.Key); - } - public byte[] HashPassword(SymmetricCryptoKey key, string password) { if(key == null)