diff --git a/src/App/Abstractions/Services/IAuthService.cs b/src/App/Abstractions/Services/IAuthService.cs index 9a62db229..21f321831 100644 --- a/src/App/Abstractions/Services/IAuthService.cs +++ b/src/App/Abstractions/Services/IAuthService.cs @@ -14,6 +14,6 @@ namespace Bit.App.Abstractions void LogOut(); Task TokenPostAsync(string email, string masterPassword); - Task TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, byte[] key); + Task TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, CryptoKey key); } } diff --git a/src/App/Abstractions/Services/ICryptoService.cs b/src/App/Abstractions/Services/ICryptoService.cs index 566fea572..36b233afa 100644 --- a/src/App/Abstractions/Services/ICryptoService.cs +++ b/src/App/Abstractions/Services/ICryptoService.cs @@ -4,16 +4,15 @@ namespace Bit.App.Abstractions { public interface ICryptoService { - string Base64Key { get; } - byte[] Key { get; set; } - byte[] PreviousKey { get; } + CryptoKey Key { get; set; } + CryptoKey PreviousKey { get; } bool KeyChanged { get; } - string Decrypt(CipherString encyptedValue); - CipherString Encrypt(string plaintextValue); - byte[] MakeKeyFromPassword(string password, string salt); + string Decrypt(CipherString encyptedValue, CryptoKey key = null); + CipherString Encrypt(string plaintextValue, CryptoKey key = null); + CryptoKey MakeKeyFromPassword(string password, string salt); string MakeKeyFromPasswordBase64(string password, string salt); - byte[] HashPassword(byte[] key, string password); - string HashPasswordBase64(byte[] key, string password); + byte[] HashPassword(CryptoKey key, string password); + string HashPasswordBase64(CryptoKey key, string password); } } \ No newline at end of file diff --git a/src/App/App.csproj b/src/App/App.csproj index c880b6f26..79928db08 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -80,6 +80,7 @@ + @@ -108,6 +109,7 @@ + diff --git a/src/App/Enums/EncryptionType.cs b/src/App/Enums/EncryptionType.cs new file mode 100644 index 000000000..9a808d2da --- /dev/null +++ b/src/App/Enums/EncryptionType.cs @@ -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 + } +} diff --git a/src/App/Models/CipherString.cs b/src/App/Models/CipherString.cs index c770c33e9..41300652d 100644 --- a/src/App/Models/CipherString.cs +++ b/src/App/Models/CipherString.cs @@ -1,6 +1,7 @@ using System; using Bit.App.Abstractions; using XLabs.Ioc; +using Bit.App.Enums; namespace Bit.App.Models { @@ -15,10 +16,58 @@ namespace Bit.App.Models 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; } - public CipherString(string initializationVector, string cipherText, string mac = null) + public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText, string mac = null) { if(string.IsNullOrWhiteSpace(initializationVector)) { @@ -30,30 +79,24 @@ namespace Bit.App.Models 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)) { 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 InitializationVector => EncryptedString?.Split('|')[0] ?? null; - public string CipherText => EncryptedString?.Split('|')[1] ?? null; - public string Mac - { - get - { - var pieces = EncryptedString?.Split('|') ?? new string[0]; - if(pieces.Length > 2) - { - return pieces[2]; - } - - return null; - } - } + public string InitializationVector { get; private set; } + public string CipherText { get; private set; } + public string Mac { get; private set; } public byte[] InitializationVectorBytes => Convert.FromBase64String(InitializationVector); public byte[] CipherTextBytes => Convert.FromBase64String(CipherText); public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac); diff --git a/src/App/Models/CryptoKey.cs b/src/App/Models/CryptoKey.cs new file mode 100644 index 000000000..f2671d247 --- /dev/null +++ b/src/App/Models/CryptoKey.cs @@ -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; } + } +} diff --git a/src/App/Models/LoginResult.cs b/src/App/Models/LoginResult.cs index fa95621c5..18f2c7a44 100644 --- a/src/App/Models/LoginResult.cs +++ b/src/App/Models/LoginResult.cs @@ -9,7 +9,7 @@ public class FullLoginResult : LoginResult { public bool TwoFactorRequired { get; set; } - public byte[] Key { get; set; } + public CryptoKey Key { get; set; } public string MasterPasswordHash { get; set; } } } diff --git a/src/App/Pages/Lock/LockPasswordPage.cs b/src/App/Pages/Lock/LockPasswordPage.cs index 762710f24..d958ec60c 100644 --- a/src/App/Pages/Lock/LockPasswordPage.cs +++ b/src/App/Pages/Lock/LockPasswordPage.cs @@ -120,7 +120,7 @@ namespace Bit.App.Pages } 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); await Navigation.PopModalAsync(); diff --git a/src/App/Pages/LoginTwoFactorPage.cs b/src/App/Pages/LoginTwoFactorPage.cs index 5825935a6..6eeec4cde 100644 --- a/src/App/Pages/LoginTwoFactorPage.cs +++ b/src/App/Pages/LoginTwoFactorPage.cs @@ -7,6 +7,7 @@ using XLabs.Ioc; using Acr.UserDialogs; using System.Threading.Tasks; using PushNotification.Plugin.Abstractions; +using Bit.App.Models; namespace Bit.App.Pages { @@ -19,9 +20,9 @@ namespace Bit.App.Pages private IPushNotification _pushNotification; private readonly string _email; 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) { _email = email; diff --git a/src/App/Services/AuthService.cs b/src/App/Services/AuthService.cs index 414e3a8d7..495306767 100644 --- a/src/App/Services/AuthService.cs +++ b/src/App/Services/AuthService.cs @@ -6,8 +6,6 @@ using Bit.App.Models.Api; using Plugin.Settings.Abstractions; using Bit.App.Models; using System.Linq; -using Xamarin.Forms; -using PushNotification.Plugin.Abstractions; namespace Bit.App.Services { @@ -237,7 +235,7 @@ namespace Bit.App.Services } public async Task TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, - byte[] key) + CryptoKey key) { var result = new LoginResult(); @@ -263,7 +261,7 @@ namespace Bit.App.Services return result; } - private void ProcessLoginSuccess(byte[] key, TokenResponse response) + private void ProcessLoginSuccess(CryptoKey key, TokenResponse response) { _cryptoService.Key = key; _tokenService.Token = response.AccessToken; diff --git a/src/App/Services/CryptoService.cs b/src/App/Services/CryptoService.cs index 6c4a2d4b0..ae9441bb6 100644 --- a/src/App/Services/CryptoService.cs +++ b/src/App/Services/CryptoService.cs @@ -5,7 +5,6 @@ using Bit.App.Abstractions; using Bit.App.Models; using PCLCrypto; using System.Linq; -using Xamarin.Forms; namespace Bit.App.Services { @@ -17,8 +16,9 @@ namespace Bit.App.Services private readonly ISecureStorageService _secureStorage; private readonly IKeyDerivationService _keyDerivationService; - private byte[] _key; - private byte[] _previousKey; + private CryptoKey _key; + private CryptoKey _legacyEtmKey; + private CryptoKey _previousKey; public CryptoService( ISecureStorageService secureStorage, @@ -28,13 +28,13 @@ namespace Bit.App.Services _keyDerivationService = keyDerivationService; } - public byte[] Key + public CryptoKey Key { get { if(_key == null) { - _key = _secureStorage.Retrieve(KeyKey); + _key = new CryptoKey(_secureStorage.Retrieve(KeyKey)); } return _key; @@ -43,37 +43,25 @@ namespace Bit.App.Services { if(value != null) { - _secureStorage.Store(KeyKey, value); + _secureStorage.Store(KeyKey, value.Key); } else { PreviousKey = _key; _secureStorage.Delete(KeyKey); _key = null; + _legacyEtmKey = null; } } } - public string Base64Key - { - get - { - if(Key == null) - { - return null; - } - - return Convert.ToBase64String(Key); - } - } - - public byte[] PreviousKey + public CryptoKey PreviousKey { get { if(_previousKey == null) { - _previousKey = _secureStorage.Retrieve(PreviousKeyKey); + _previousKey = new CryptoKey(_secureStorage.Retrieve(PreviousKeyKey)); } return _previousKey; @@ -82,7 +70,7 @@ namespace Bit.App.Services { if(value != null) { - _secureStorage.Store(PreviousKeyKey, value); + _secureStorage.Store(PreviousKeyKey, value.Key); _previousKey = value; } } @@ -102,18 +90,20 @@ namespace Bit.App.Services return Key != null; } - return !PreviousKey.SequenceEqual(Key); + return !PreviousKey.Key.SequenceEqual(Key.Key); } } - public byte[] EncKey => Key?.Take(16).ToArray(); - public byte[] MacKey => Key?.Skip(16).Take(16).ToArray(); - - public CipherString Encrypt(string plaintextValue) + public CipherString Encrypt(string plaintextValue, CryptoKey key = null) { - if(Key == null) + if(key == null) { - throw new ArgumentNullException(nameof(Key)); + key = Key; + } + + if(key == null) + { + throw new ArgumentNullException(nameof(key)); } if(plaintextValue == null) @@ -124,23 +114,26 @@ namespace Bit.App.Services var plaintextBytes = Encoding.UTF8.GetBytes(plaintextValue); var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); - // TODO: Turn on whenever ready to support encrypt-then-mac - var cryptoKey = provider.CreateSymmetricKey(true ? EncKey : Key); + var cryptoKey = provider.CreateSymmetricKey(key.EncKey); var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength); var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plaintextBytes, iv); - // TODO: Turn on whenever ready to support encrypt-then-mac - var mac = true ? ComputeMac(encryptedBytes, iv) : null; + var mac = key.MacKey != null ? ComputeMac(encryptedBytes, iv, key.MacKey) : 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 { - if(Key == null) + if(key == null) { - throw new ArgumentNullException(nameof(Key)); + key = Key; + } + + if(key == null) + { + throw new ArgumentNullException(nameof(key)); } if(encyptedValue == null) @@ -148,9 +141,27 @@ namespace Bit.App.Services 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) { throw new InvalidOperationException("MAC failed."); @@ -158,7 +169,7 @@ namespace Bit.App.Services } 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, encyptedValue.InitializationVectorBytes); 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) @@ -188,13 +199,13 @@ namespace Bit.App.Services } var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256); - var hasher = algorithm.CreateHash(MacKey); + var hasher = algorithm.CreateHash(macKey); hasher.Append(ivBytes.Concat(ctBytes).ToArray()); var mac = hasher.GetValueAndReset(); return Convert.ToBase64String(mac); } - public byte[] MakeKeyFromPassword(string password, string salt) + public CryptoKey MakeKeyFromPassword(string password, string salt) { if(password == null) { @@ -209,17 +220,17 @@ namespace Bit.App.Services var passwordBytes = Encoding.UTF8.GetBytes(password); var saltBytes = Encoding.UTF8.GetBytes(salt); - var key = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000); - return key; + var keyBytes = _keyDerivationService.DeriveKey(passwordBytes, saltBytes, 5000); + return new CryptoKey(keyBytes); } public string MakeKeyFromPasswordBase64(string password, string 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) { @@ -232,11 +243,11 @@ namespace Bit.App.Services } var passwordBytes = Encoding.UTF8.GetBytes(password); - var hash = _keyDerivationService.DeriveKey(key, passwordBytes, 1); + var hash = _keyDerivationService.DeriveKey(key.Key, passwordBytes, 1); return hash; } - public string HashPasswordBase64(byte[] key, string password) + public string HashPasswordBase64(CryptoKey key, string password) { var hash = HashPassword(key, password); return Convert.ToBase64String(hash);