refactor for enc type header and cryptokey

This commit is contained in:
Kyle Spearrin 2017-04-19 23:16:09 -04:00
parent 0ebfe85d8e
commit e7f3b115a4
11 changed files with 211 additions and 85 deletions

View file

@ -14,6 +14,6 @@ namespace Bit.App.Abstractions
void LogOut(); void LogOut();
Task<FullLoginResult> TokenPostAsync(string email, string masterPassword); Task<FullLoginResult> TokenPostAsync(string email, string masterPassword);
Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, byte[] key); Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, CryptoKey key);
} }
} }

View file

@ -4,16 +4,15 @@ namespace Bit.App.Abstractions
{ {
public interface ICryptoService public interface ICryptoService
{ {
string Base64Key { get; } CryptoKey Key { get; set; }
byte[] Key { get; set; } CryptoKey PreviousKey { get; }
byte[] PreviousKey { get; }
bool KeyChanged { get; } bool KeyChanged { get; }
string Decrypt(CipherString encyptedValue); string Decrypt(CipherString encyptedValue, CryptoKey key = null);
CipherString Encrypt(string plaintextValue); CipherString Encrypt(string plaintextValue, CryptoKey key = null);
byte[] MakeKeyFromPassword(string password, string salt); CryptoKey MakeKeyFromPassword(string password, string salt);
string MakeKeyFromPasswordBase64(string password, string salt); string MakeKeyFromPasswordBase64(string password, string salt);
byte[] HashPassword(byte[] key, string password); byte[] HashPassword(CryptoKey key, string password);
string HashPasswordBase64(byte[] key, string password); string HashPasswordBase64(CryptoKey key, string password);
} }
} }

View file

@ -80,6 +80,7 @@
<Compile Include="Controls\FormEntryCell.cs" /> <Compile Include="Controls\FormEntryCell.cs" />
<Compile Include="Controls\PinControl.cs" /> <Compile Include="Controls\PinControl.cs" />
<Compile Include="Controls\VaultListViewCell.cs" /> <Compile Include="Controls\VaultListViewCell.cs" />
<Compile Include="Enums\EncryptionType.cs" />
<Compile Include="Enums\LockType.cs" /> <Compile Include="Enums\LockType.cs" />
<Compile Include="Enums\CipherType.cs" /> <Compile Include="Enums\CipherType.cs" />
<Compile Include="Enums\PushType.cs" /> <Compile Include="Enums\PushType.cs" />
@ -108,6 +109,7 @@
<Compile Include="Models\Api\LoginDataModel.cs" /> <Compile Include="Models\Api\LoginDataModel.cs" />
<Compile Include="Models\Cipher.cs" /> <Compile Include="Models\Cipher.cs" />
<Compile Include="Models\CipherString.cs" /> <Compile Include="Models\CipherString.cs" />
<Compile Include="Models\CryptoKey.cs" />
<Compile Include="Models\Data\SettingsData.cs" /> <Compile Include="Models\Data\SettingsData.cs" />
<Compile Include="Models\Data\FolderData.cs" /> <Compile Include="Models\Data\FolderData.cs" />
<Compile Include="Abstractions\IDataObject.cs" /> <Compile Include="Abstractions\IDataObject.cs" />

View file

@ -0,0 +1,10 @@
namespace Bit.App.Enums
{
public enum EncryptionType : byte
{
AesCbc256_B64 = 0,
AesCbc128_HmacSha256_B64 = 1,
AesCbc256_HmacSha256_B64 = 2,
RsaOaep_Sha256_B64 = 3
}
}

View file

@ -1,6 +1,7 @@
using System; using System;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using XLabs.Ioc; using XLabs.Ioc;
using Bit.App.Enums;
namespace Bit.App.Models namespace Bit.App.Models
{ {
@ -15,10 +16,58 @@ namespace Bit.App.Models
throw new ArgumentException(nameof(encryptedString)); throw new ArgumentException(nameof(encryptedString));
} }
var headerPieces = encryptedString.Split('.');
string[] encPieces;
if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out EncryptionType encType))
{
EncryptionType = encType;
encPieces = headerPieces[1].Split('|');
}
else if(headerPieces.Length == 1)
{
encPieces = headerPieces[0].Split('|');
EncryptionType = encPieces.Length == 3 ? EncryptionType.AesCbc128_HmacSha256_B64 : EncryptionType.AesCbc256_B64;
}
else
{
throw new ArgumentException("Malformed header.");
}
switch(EncryptionType)
{
case EncryptionType.AesCbc256_B64:
if(encPieces.Length != 2)
{
throw new ArgumentException("Malformed encPieces.");
}
InitializationVector = encPieces[0];
CipherText = encPieces[1];
break;
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if(encPieces.Length != 3)
{
throw new ArgumentException("Malformed encPieces.");
}
InitializationVector = encPieces[0];
CipherText = encPieces[1];
Mac = encPieces[2];
break;
case EncryptionType.RsaOaep_Sha256_B64:
if(encPieces.Length != 1)
{
throw new ArgumentException("Malformed encPieces.");
}
CipherText = encPieces[0];
break;
default:
throw new ArgumentException("Unknown encType.");
}
EncryptedString = encryptedString; EncryptedString = encryptedString;
} }
public CipherString(string initializationVector, string cipherText, string mac = null) public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText, string mac = null)
{ {
if(string.IsNullOrWhiteSpace(initializationVector)) if(string.IsNullOrWhiteSpace(initializationVector))
{ {
@ -30,30 +79,24 @@ namespace Bit.App.Models
throw new ArgumentNullException(nameof(cipherText)); throw new ArgumentNullException(nameof(cipherText));
} }
EncryptedString = string.Format("{0}|{1}", initializationVector, cipherText); EncryptionType = encryptionType;
EncryptedString = string.Format("{0}.{1}|{2}", (byte)encryptionType, initializationVector, cipherText);
if(!string.IsNullOrWhiteSpace(mac)) if(!string.IsNullOrWhiteSpace(mac))
{ {
EncryptedString = string.Format("{0}|{1}", EncryptedString, mac); EncryptedString = string.Format("{0}|{1}", EncryptedString, mac);
} }
CipherText = cipherText;
InitializationVector = initializationVector;
Mac = mac;
} }
public EncryptionType EncryptionType { get; private set; }
public string EncryptedString { get; private set; } public string EncryptedString { get; private set; }
public string InitializationVector => EncryptedString?.Split('|')[0] ?? null; public string InitializationVector { get; private set; }
public string CipherText => EncryptedString?.Split('|')[1] ?? null; public string CipherText { get; private set; }
public string Mac public string Mac { get; private set; }
{
get
{
var pieces = EncryptedString?.Split('|') ?? new string[0];
if(pieces.Length > 2)
{
return pieces[2];
}
return null;
}
}
public byte[] InitializationVectorBytes => Convert.FromBase64String(InitializationVector); public byte[] InitializationVectorBytes => Convert.FromBase64String(InitializationVector);
public byte[] CipherTextBytes => Convert.FromBase64String(CipherText); public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac); public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);

View file

@ -0,0 +1,62 @@
using Bit.App.Enums;
using System;
using System.Linq;
namespace Bit.App.Models
{
public class CryptoKey
{
public CryptoKey(byte[] rawBytes, EncryptionType? encType = null)
{
if(rawBytes == null || rawBytes.Length == 0)
{
throw new Exception("Must provide keyBytes.");
}
if(encType == null)
{
if(rawBytes.Length == 32)
{
encType = EncryptionType.AesCbc256_B64;
}
else if(rawBytes.Length == 64)
{
encType = EncryptionType.AesCbc256_HmacSha256_B64;
}
else
{
throw new Exception("Unable to determine encType.");
}
}
EncryptionType = encType.Value;
Key = rawBytes;
if(EncryptionType == EncryptionType.AesCbc256_B64 && Key.Length == 32)
{
EncKey = Key;
MacKey = null;
}
else if(EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
{
EncKey = Key.Take(16).ToArray();
MacKey = Key.Skip(16).Take(16).ToArray();
}
else if(EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
{
EncKey = Key.Take(32).ToArray();
MacKey = Key.Skip(32).Take(32).ToArray();
}
else
{
throw new Exception("Unsupported encType/key length.");
}
}
public byte[] Key { get; set; }
public string B64Key => Convert.ToBase64String(Key);
public byte[] EncKey { get; set; }
public byte[] MacKey { get; set; }
public EncryptionType EncryptionType { get; set; }
}
}

View file

@ -9,7 +9,7 @@
public class FullLoginResult : LoginResult public class FullLoginResult : LoginResult
{ {
public bool TwoFactorRequired { get; set; } public bool TwoFactorRequired { get; set; }
public byte[] Key { get; set; } public CryptoKey Key { get; set; }
public string MasterPasswordHash { get; set; } public string MasterPasswordHash { get; set; }
} }
} }

View file

@ -120,7 +120,7 @@ namespace Bit.App.Pages
} }
var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, _authService.Email); var key = _cryptoService.MakeKeyFromPassword(PasswordCell.Entry.Text, _authService.Email);
if(key.SequenceEqual(_cryptoService.Key)) if(key.Key.SequenceEqual(_cryptoService.Key.Key))
{ {
_settings.AddOrUpdateValue(Constants.Locked, false); _settings.AddOrUpdateValue(Constants.Locked, false);
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();

View file

@ -7,6 +7,7 @@ using XLabs.Ioc;
using Acr.UserDialogs; using Acr.UserDialogs;
using System.Threading.Tasks; using System.Threading.Tasks;
using PushNotification.Plugin.Abstractions; using PushNotification.Plugin.Abstractions;
using Bit.App.Models;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -19,9 +20,9 @@ namespace Bit.App.Pages
private IPushNotification _pushNotification; private IPushNotification _pushNotification;
private readonly string _email; private readonly string _email;
private readonly string _masterPasswordHash; private readonly string _masterPasswordHash;
private readonly byte[] _key; private readonly CryptoKey _key;
public LoginTwoFactorPage(string email, string masterPasswordHash, byte[] key) public LoginTwoFactorPage(string email, string masterPasswordHash, CryptoKey key)
: base(updateActivity: false) : base(updateActivity: false)
{ {
_email = email; _email = email;

View file

@ -6,8 +6,6 @@ using Bit.App.Models.Api;
using Plugin.Settings.Abstractions; using Plugin.Settings.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using System.Linq; using System.Linq;
using Xamarin.Forms;
using PushNotification.Plugin.Abstractions;
namespace Bit.App.Services namespace Bit.App.Services
{ {
@ -237,7 +235,7 @@ namespace Bit.App.Services
} }
public async Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, public async Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash,
byte[] key) CryptoKey key)
{ {
var result = new LoginResult(); var result = new LoginResult();
@ -263,7 +261,7 @@ namespace Bit.App.Services
return result; return result;
} }
private void ProcessLoginSuccess(byte[] key, TokenResponse response) private void ProcessLoginSuccess(CryptoKey key, TokenResponse response)
{ {
_cryptoService.Key = key; _cryptoService.Key = key;
_tokenService.Token = response.AccessToken; _tokenService.Token = response.AccessToken;

View file

@ -5,7 +5,6 @@ using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using PCLCrypto; using PCLCrypto;
using System.Linq; using System.Linq;
using Xamarin.Forms;
namespace Bit.App.Services namespace Bit.App.Services
{ {
@ -17,8 +16,9 @@ namespace Bit.App.Services
private readonly ISecureStorageService _secureStorage; private readonly ISecureStorageService _secureStorage;
private readonly IKeyDerivationService _keyDerivationService; private readonly IKeyDerivationService _keyDerivationService;
private byte[] _key; private CryptoKey _key;
private byte[] _previousKey; private CryptoKey _legacyEtmKey;
private CryptoKey _previousKey;
public CryptoService( public CryptoService(
ISecureStorageService secureStorage, ISecureStorageService secureStorage,
@ -28,13 +28,13 @@ namespace Bit.App.Services
_keyDerivationService = keyDerivationService; _keyDerivationService = keyDerivationService;
} }
public byte[] Key public CryptoKey Key
{ {
get get
{ {
if(_key == null) if(_key == null)
{ {
_key = _secureStorage.Retrieve(KeyKey); _key = new CryptoKey(_secureStorage.Retrieve(KeyKey));
} }
return _key; return _key;
@ -43,37 +43,25 @@ namespace Bit.App.Services
{ {
if(value != null) if(value != null)
{ {
_secureStorage.Store(KeyKey, value); _secureStorage.Store(KeyKey, value.Key);
} }
else else
{ {
PreviousKey = _key; PreviousKey = _key;
_secureStorage.Delete(KeyKey); _secureStorage.Delete(KeyKey);
_key = null; _key = null;
_legacyEtmKey = null;
} }
} }
} }
public string Base64Key public CryptoKey PreviousKey
{
get
{
if(Key == null)
{
return null;
}
return Convert.ToBase64String(Key);
}
}
public byte[] PreviousKey
{ {
get get
{ {
if(_previousKey == null) if(_previousKey == null)
{ {
_previousKey = _secureStorage.Retrieve(PreviousKeyKey); _previousKey = new CryptoKey(_secureStorage.Retrieve(PreviousKeyKey));
} }
return _previousKey; return _previousKey;
@ -82,7 +70,7 @@ namespace Bit.App.Services
{ {
if(value != null) if(value != null)
{ {
_secureStorage.Store(PreviousKeyKey, value); _secureStorage.Store(PreviousKeyKey, value.Key);
_previousKey = value; _previousKey = value;
} }
} }
@ -102,18 +90,20 @@ namespace Bit.App.Services
return Key != null; return Key != null;
} }
return !PreviousKey.SequenceEqual(Key); return !PreviousKey.Key.SequenceEqual(Key.Key);
} }
} }
public byte[] EncKey => Key?.Take(16).ToArray(); public CipherString Encrypt(string plaintextValue, CryptoKey key = null)
public byte[] MacKey => Key?.Skip(16).Take(16).ToArray(); {
if(key == null)
{
key = Key;
}
public CipherString Encrypt(string plaintextValue) if(key == null)
{ {
if(Key == null) throw new ArgumentNullException(nameof(key));
{
throw new ArgumentNullException(nameof(Key));
} }
if(plaintextValue == null) if(plaintextValue == null)
@ -124,23 +114,26 @@ namespace Bit.App.Services
var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue); var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue);
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
// TODO: Turn on whenever ready to support encrypt-then-mac var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var cryptoKey = provider.CreateSymmetricKey(true ? EncKey : Key);
var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength); var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength);
var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plaintextBytes, iv); var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plaintextBytes, iv);
// TODO: Turn on whenever ready to support encrypt-then-mac var mac = key.MacKey != null ? ComputeMac(encryptedBytes, iv, key.MacKey) : null;
var mac = true ? ComputeMac(encryptedBytes, iv) : null;
return new CipherString(Convert.ToBase64String(iv), Convert.ToBase64String(encryptedBytes), mac); return new CipherString(key.EncryptionType, Convert.ToBase64String(iv), Convert.ToBase64String(encryptedBytes), mac);
} }
public string Decrypt(CipherString encyptedValue) public string Decrypt(CipherString encyptedValue, CryptoKey key = null)
{ {
try try
{ {
if(Key == null) if(key == null)
{ {
throw new ArgumentNullException(nameof(Key)); key = Key;
}
if(key == null)
{
throw new ArgumentNullException(nameof(key));
} }
if(encyptedValue == null) if(encyptedValue == null)
@ -148,9 +141,27 @@ namespace Bit.App.Services
throw new ArgumentNullException(nameof(encyptedValue)); throw new ArgumentNullException(nameof(encyptedValue));
} }
if(encyptedValue.Mac != null) if(encyptedValue.EncryptionType == Enums.EncryptionType.AesCbc128_HmacSha256_B64 &&
key.EncryptionType == Enums.EncryptionType.AesCbc256_B64)
{ {
var computedMac = ComputeMac(encyptedValue.CipherTextBytes, encyptedValue.InitializationVectorBytes); // Old encrypt-then-mac scheme, swap out the key
if(_legacyEtmKey == null)
{
_legacyEtmKey = new CryptoKey(key.Key, Enums.EncryptionType.AesCbc128_HmacSha256_B64);
}
key = _legacyEtmKey;
}
if(encyptedValue.EncryptionType != key.EncryptionType)
{
throw new ArgumentException("encType unavailable.");
}
if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac))
{
var computedMac = ComputeMac(encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes, key.MacKey);
if(computedMac != encyptedValue.Mac) if(computedMac != encyptedValue.Mac)
{ {
throw new InvalidOperationException("MAC failed."); throw new InvalidOperationException("MAC failed.");
@ -158,7 +169,7 @@ namespace Bit.App.Services
} }
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(encyptedValue.Mac != null ? EncKey : Key); var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes, var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes); encyptedValue.InitializationVectorBytes);
return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length).TrimEnd('\0'); return Encoding.UTF8.GetString(decryptedBytes, 0, decryptedBytes.Length).TrimEnd('\0');
@ -170,11 +181,11 @@ namespace Bit.App.Services
} }
} }
private string ComputeMac(byte[] ctBytes, byte[] ivBytes) private string ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{ {
if(MacKey == null) if(macKey == null)
{ {
throw new ArgumentNullException(nameof(MacKey)); throw new ArgumentNullException(nameof(macKey));
} }
if(ctBytes == null) if(ctBytes == null)
@ -188,13 +199,13 @@ namespace Bit.App.Services
} }
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256); var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(MacKey); var hasher = algorithm.CreateHash(macKey);
hasher.Append(ivBytes.Concat(ctBytes).ToArray()); hasher.Append(ivBytes.Concat(ctBytes).ToArray());
var mac = hasher.GetValueAndReset(); var mac = hasher.GetValueAndReset();
return Convert.ToBase64String(mac); return Convert.ToBase64String(mac);
} }
public byte[] MakeKeyFromPassword(string password, string salt) public CryptoKey MakeKeyFromPassword(string password, string salt)
{ {
if(password == null) if(password == null)
{ {
@ -209,17 +220,17 @@ namespace Bit.App.Services
var passwordBytes = Encoding.UTF8.GetBytes(password); var passwordBytes = Encoding.UTF8.GetBytes(password);
var saltBytes = Encoding.UTF8.GetBytes(salt); var saltBytes = Encoding.UTF8.GetBytes(salt);
var key = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000); var keyBytes = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000);
return key; return new CryptoKey(keyBytes);
} }
public string MakeKeyFromPasswordBase64(string password, string salt) public string MakeKeyFromPasswordBase64(string password, string salt)
{ {
var key = MakeKeyFromPassword(password, salt); var key = MakeKeyFromPassword(password, salt);
return Convert.ToBase64String(key); return Convert.ToBase64String(key.Key);
} }
public byte[] HashPassword(byte[] key, string password) public byte[] HashPassword(CryptoKey key, string password)
{ {
if(key == null) if(key == null)
{ {
@ -232,11 +243,11 @@ namespace Bit.App.Services
} }
var passwordBytes = Encoding.UTF8.GetBytes(password); var passwordBytes = Encoding.UTF8.GetBytes(password);
var hash = _keyDerivationService.DeriveKey(key, passwordBytes, 1); var hash = _keyDerivationService.DeriveKey(key.Key, passwordBytes, 1);
return hash; return hash;
} }
public string HashPasswordBase64(byte[] key, string password) public string HashPasswordBase64(CryptoKey key, string password)
{ {
var hash = HashPassword(key, password); var hash = HashPassword(key, password);
return Convert.ToBase64String(hash); return Convert.ToBase64String(hash);