support for prelogin kdf params

This commit is contained in:
Kyle Spearrin 2018-08-14 16:46:31 -04:00
parent e70dbf8d8d
commit 7862005055
12 changed files with 140 additions and 17 deletions

View file

@ -6,6 +6,7 @@ namespace Bit.App.Abstractions
{ {
public interface IAccountsApiRepository public interface IAccountsApiRepository
{ {
Task<ApiResult<PreloginResponse>> PostPreloginAsync(PreloginRequest requestObj);
Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj); Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj);
Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj); Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj);
Task<ApiResult<DateTime?>> GetAccountRevisionDateAsync(); Task<ApiResult<DateTime?>> GetAccountRevisionDateAsync();

View file

@ -11,6 +11,8 @@ namespace Bit.App.Abstractions
string PreviousUserId { get; } string PreviousUserId { get; }
bool UserIdChanged { get; } bool UserIdChanged { get; }
string Email { get; set; } string Email { get; set; }
KdfType Kdf { get; set; }
int KdfIterations { get; set; }
string PIN { get; set; } string PIN { get; set; }
bool BelongsToOrganization(string orgId); bool BelongsToOrganization(string orgId);
void LogOut(string logoutMessage = null); void LogOut(string logoutMessage = null);

View file

@ -1,6 +1,6 @@
using Bit.App.Models; using Bit.App.Enums;
using Bit.App.Models;
using Bit.App.Models.Api; using Bit.App.Models.Api;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
@ -23,8 +23,7 @@ namespace Bit.App.Abstractions
byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey); byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey);
CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null); CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null);
byte[] EncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key = null); byte[] EncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key = null);
SymmetricCryptoKey MakeKeyFromPassword(string password, string salt); SymmetricCryptoKey MakeKeyFromPassword(string password, string salt, KdfType kdf, int kdfIterations);
string MakeKeyFromPasswordBase64(string password, string salt);
byte[] HashPassword(SymmetricCryptoKey key, string password); byte[] HashPassword(SymmetricCryptoKey key, string password);
string HashPasswordBase64(SymmetricCryptoKey key, string password); string HashPasswordBase64(SymmetricCryptoKey key, string password);
CipherString MakeEncKey(SymmetricCryptoKey key); CipherString MakeEncKey(SymmetricCryptoKey key);

7
src/App/Enums/KdfType.cs Normal file
View file

@ -0,0 +1,7 @@
namespace Bit.App.Enums
{
public enum KdfType : short
{
PBKDF2 = 0
}
}

View file

@ -0,0 +1,7 @@
namespace Bit.App.Models.Api
{
public class PreloginRequest
{
public string Email { get; set; }
}
}

View file

@ -1,4 +1,6 @@
namespace Bit.App.Models.Api using Bit.App.Enums;
namespace Bit.App.Models.Api
{ {
public class RegisterRequest public class RegisterRequest
{ {
@ -7,5 +9,7 @@
public string MasterPasswordHash { get; set; } public string MasterPasswordHash { get; set; }
public string MasterPasswordHint { get; set; } public string MasterPasswordHint { get; set; }
public string Key { get; set; } public string Key { get; set; }
public KdfType Kdf { get; set; }
public int KdfIterations { get; set; }
} }
} }

View file

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

View file

@ -151,7 +151,8 @@ namespace Bit.App.Pages
return; 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)) if(key.Key.SequenceEqual(_cryptoService.Key.Key))
{ {
_appSettingsService.Locked = false; _appSettingsService.Locked = false;

View file

@ -192,8 +192,10 @@ namespace Bit.App.Pages
return; return;
} }
var kdf = Enums.KdfType.PBKDF2;
var kdfIterations = 5000;
var normalizedEmail = EmailCell.Entry.Text.ToLower(); 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 encKey = _cryptoService.MakeEncKey(key);
var request = new RegisterRequest var request = new RegisterRequest
{ {
@ -201,7 +203,9 @@ namespace Bit.App.Pages
MasterPasswordHash = _cryptoService.HashPasswordBase64(key, PasswordCell.Entry.Text), MasterPasswordHash = _cryptoService.HashPasswordBase64(key, PasswordCell.Entry.Text),
MasterPasswordHint = !string.IsNullOrWhiteSpace(PasswordHintCell.Entry.Text) MasterPasswordHint = !string.IsNullOrWhiteSpace(PasswordHintCell.Entry.Text)
? PasswordHintCell.Entry.Text : null, ? PasswordHintCell.Entry.Text : null,
Key = encKey.EncryptedString Key = encKey.EncryptedString,
Kdf = kdf,
KdfIterations = kdfIterations
}; };
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount); await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);

View file

@ -20,6 +20,40 @@ namespace Bit.App.Repositories
protected override string ApiRoute => "/accounts"; protected override string ApiRoute => "/accounts";
public virtual async Task<ApiResult<PreloginResponse>> PostPreloginAsync(PreloginRequest requestObj)
{
if(!Connectivity.IsConnected)
{
return HandledNotConnected<PreloginResponse>();
}
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<PreloginResponse>(response).ConfigureAwait(false);
}
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var responseObj = JsonConvert.DeserializeObject<PreloginResponse>(responseContent);
return ApiResult<PreloginResponse>.Success(responseObj, response.StatusCode);
}
catch
{
return HandledWebException<PreloginResponse>();
}
}
}
public virtual async Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj) public virtual async Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj)
{ {
if(!Connectivity.IsConnected) if(!Connectivity.IsConnected)

View file

@ -17,6 +17,8 @@ namespace Bit.App.Services
public class AuthService : IAuthService public class AuthService : IAuthService
{ {
private const string EmailKey = "email"; private const string EmailKey = "email";
private const string KdfKey = "kdf";
private const string KdfIterationsKey = "kdfIterations";
private const string UserIdKey = "userId"; private const string UserIdKey = "userId";
private const string PreviousUserIdKey = "previousUserId"; private const string PreviousUserIdKey = "previousUserId";
private const string PinKey = "pin"; private const string PinKey = "pin";
@ -34,6 +36,8 @@ namespace Bit.App.Services
private readonly IGoogleAnalyticsService _googleAnalyticsService; private readonly IGoogleAnalyticsService _googleAnalyticsService;
private string _email; private string _email;
private KdfType? _kdf;
private int? _kdfIterations;
private string _userId; private string _userId;
private string _previousUserId; private string _previousUserId;
private string _pin; 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 public bool IsAuthenticated
{ {
get get
@ -231,10 +269,20 @@ namespace Bit.App.Services
public async Task<FullLoginResult> TokenPostAsync(string email, string masterPassword) public async Task<FullLoginResult> 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 result = new FullLoginResult();
var normalizedEmail = email.Trim().ToLower(); var normalizedEmail = email.Trim().ToLower();
var key = _cryptoService.MakeKeyFromPassword(masterPassword, normalizedEmail); var key = _cryptoService.MakeKeyFromPassword(masterPassword, normalizedEmail, Kdf, KdfIterations);
var request = new TokenRequest var request = new TokenRequest
{ {

View file

@ -429,7 +429,7 @@ namespace Bit.App.Services
return decryptedBytes; return decryptedBytes;
} }
public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt) public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt, KdfType kdf, int kdfIterations)
{ {
if(password == null) if(password == null)
{ {
@ -444,16 +444,22 @@ namespace Bit.App.Services
var passwordBytes = Encoding.UTF8.GetBytes(NormalizePassword(password)); var passwordBytes = Encoding.UTF8.GetBytes(NormalizePassword(password));
var saltBytes = Encoding.UTF8.GetBytes(salt); 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); 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) public byte[] HashPassword(SymmetricCryptoKey key, string password)
{ {
if(key == null) if(key == null)