diff --git a/src/Android/Services/KeyStoreBackedStorageService.cs b/src/Android/Services/KeyStoreBackedStorageService.cs index d55d731ce..96e5d3d09 100644 --- a/src/Android/Services/KeyStoreBackedStorageService.cs +++ b/src/Android/Services/KeyStoreBackedStorageService.cs @@ -10,7 +10,6 @@ using Java.Math; using Android.Security.Keystore; using Android.App; using Plugin.Settings.Abstractions; -using Javax.Crypto.Spec; using System.Collections.Generic; using Java.Util; @@ -23,13 +22,12 @@ namespace Bit.Android.Services private const string KeyAlias = "bitwardenKey"; private const string SettingsFormat = "ksSecured:{0}"; private const string RsaMode = "RSA/ECB/PKCS1Padding"; - private const string AesMode = "AES/GCM/NoPadding"; private const string AesKey = "ksSecured:aesKeyForService"; private readonly ISettings _settings; private readonly KeyStore _keyStore; private readonly bool _oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M; - private readonly KeyStoreStorageService _oldKeyStorageService; + private readonly ISecureStorageService _oldKeyStorageService; public KeyStoreBackedStorageService(ISettings settings) { @@ -39,7 +37,7 @@ namespace Bit.Android.Services _keyStore = KeyStore.GetInstance(AndroidKeyStore); _keyStore.Load(null); - GenerateKeys(); + GenerateRsaKey(); GenerateAesKey(); } @@ -62,8 +60,28 @@ namespace Bit.Android.Services return TryGetAndMigrateFromOldKeyStore(key); } - var cipherString = _settings.GetValueOrDefault(formattedKey); - return AesDecrypt(cipherString); + var cs = _settings.GetValueOrDefault(formattedKey); + if(string.IsNullOrWhiteSpace(cs)) + { + return null; + } + + var aesKey = GetAesKey(); + if(aesKey == null) + { + return null; + } + + try + { + return App.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKey); + } + catch + { + Console.WriteLine("Failed to decrypt from secure storage."); + _settings.Remove(formattedKey); + return null; + } } public void Store(string key, byte[] dataBytes) @@ -76,35 +94,41 @@ namespace Bit.Android.Services return; } - var cipherString = AesEncrypt(dataBytes); - _settings.AddOrUpdateValue(formattedKey, cipherString); + var aesKey = GetAesKey(); + if(aesKey == null) + { + return; + } + + try + { + var cipherString = App.Utilities.Crypto.AesCbcEncrypt(dataBytes, aesKey); + _settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString); + } + catch + { + Console.WriteLine("Failed to encrypt to secure storage."); + } } - private byte[] RandomBytes(int length) - { - var key = new byte[length]; - var secureRandom = new SecureRandom(); - secureRandom.NextBytes(key); - return key; - } - - private void GenerateKeys() + private void GenerateRsaKey() { if(_keyStore.ContainsAlias(KeyAlias)) { return; } + var start = Calendar.Instance; + var end = Calendar.Instance; + end.Add(CalendarField.Year, 30); + var subject = new X500Principal($"CN={KeyAlias}"); + if(_oldAndroid) { - var start = Calendar.Instance; - var end = Calendar.Instance; - end.Add(CalendarField.Year, 30); - var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore); var spec = new KeyPairGeneratorSpec.Builder(Application.Context) .SetAlias(KeyAlias) - .SetSubject(new X500Principal($"CN={KeyAlias}")) + .SetSubject(subject) .SetSerialNumber(BigInteger.Ten) .SetStartDate(start.Time) .SetEndDate(end.Time) @@ -115,10 +139,12 @@ namespace Bit.Android.Services } else { - var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore); + var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore); var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt) - .SetBlockModes(KeyProperties.BlockModeGcm) - .SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone) + .SetCertificateSubject(subject) + .SetCertificateSerialNumber(BigInteger.Ten) + .SetKeyValidityStart(start.Time) + .SetKeyValidityEnd(end.Time) .Build(); gen.Init(spec); @@ -126,63 +152,44 @@ namespace Bit.Android.Services } } - private void GenerateAesKey() - { - if(!_oldAndroid) - { - return; - } - - if(_settings.Contains(AesKey)) - { - return; - } - - var key = RandomBytes(16); - var encKey = RsaEncrypt(key); - _settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey)); - } - - private IKey GetAesKey() - { - if(_oldAndroid) - { - var encKey = _settings.GetValueOrDefault(AesKey); - var encKeyBytes = Convert.FromBase64String(encKey); - var key = RsaDecrypt(encKeyBytes); - return new SecretKeySpec(key, "AES"); - } - else - { - return _keyStore.GetKey(KeyAlias, null); - } - } - private KeyStore.PrivateKeyEntry GetRsaKeyEntry() { return _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry; } - private string AesEncrypt(byte[] input) + private void GenerateAesKey() { - var cipher = Cipher.GetInstance(AesMode); - cipher.Init(CipherMode.EncryptMode, GetAesKey()); - var encBytes = cipher.DoFinal(input); - var ivBytes = cipher.GetIV(); - return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}"; + if(_settings.Contains(AesKey)) + { + return; + } + + var key = App.Utilities.Crypto.RandomBytes(512 / 8); + var encKey = RsaEncrypt(key); + _settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey)); } - private byte[] AesDecrypt(string cipherString) + private App.Models.SymmetricCryptoKey GetAesKey() { - var parts = cipherString.Split('|'); - var ivBytes = Convert.FromBase64String(parts[0]); - var encBytes = Convert.FromBase64String(parts[1]); + try + { + var encKey = _settings.GetValueOrDefault(AesKey); + if(encKey == null) + { + return null; + } - var cipher = Cipher.GetInstance(AesMode); - var spec = new GCMParameterSpec(128, ivBytes); - cipher.Init(CipherMode.DecryptMode, GetAesKey(), spec); - var decBytes = cipher.DoFinal(encBytes); - return decBytes; + var encKeyBytes = Convert.FromBase64String(encKey); + var key = RsaDecrypt(encKeyBytes); + return new App.Models.SymmetricCryptoKey(key); + } + catch + { + Console.WriteLine("Cannot get AesKey."); + _keyStore.DeleteEntry(KeyAlias); + _settings.Remove(AesKey); + return null; + } } private byte[] RsaEncrypt(byte[] input) diff --git a/src/App/App.csproj b/src/App/App.csproj index 7de6131e8..0051c31df 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -285,6 +285,7 @@ + diff --git a/src/App/Services/CryptoService.cs b/src/App/Services/CryptoService.cs index 4e5316b93..e853398ab 100644 --- a/src/App/Services/CryptoService.cs +++ b/src/App/Services/CryptoService.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using Newtonsoft.Json; using Plugin.Settings.Abstractions; using Bit.App.Models.Api; +using Bit.App.Utilities; namespace Bit.App.Services { @@ -27,7 +28,6 @@ namespace Bit.App.Services private SymmetricCryptoKey _key; private SymmetricCryptoKey _encKey; private SymmetricCryptoKey _legacyEtmKey; - private SymmetricCryptoKey _previousKey; private IDictionary _orgKeys; private byte[] _privateKey; @@ -257,14 +257,7 @@ namespace Bit.App.Services throw new ArgumentNullException(nameof(plainBytes)); } - var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); - var cryptoKey = provider.CreateSymmetricKey(key.EncKey); - var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength); - var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv); - var mac = key.MacKey != null ? ComputeMacBase64(encryptedBytes, iv, key.MacKey) : null; - - return new CipherString(key.EncryptionType, Convert.ToBase64String(iv), - Convert.ToBase64String(encryptedBytes), mac); + return Crypto.AesCbcEncrypt(plainBytes, key); } public string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null) @@ -298,8 +291,8 @@ namespace Bit.App.Services throw new ArgumentNullException(nameof(encyptedValue)); } - if(encyptedValue.EncryptionType == Enums.EncryptionType.AesCbc128_HmacSha256_B64 && - key.EncryptionType == Enums.EncryptionType.AesCbc256_B64) + if(encyptedValue.EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 && + key.EncryptionType == EncryptionType.AesCbc256_B64) { // Old encrypt-then-mac scheme, swap out the key if(_legacyEtmKey == null) @@ -315,21 +308,7 @@ namespace Bit.App.Services throw new ArgumentException("encType unavailable."); } - if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac)) - { - var computedMacBytes = ComputeMac(encyptedValue.CipherTextBytes, - encyptedValue.InitializationVectorBytes, key.MacKey); - if(!MacsEqual(key.MacKey, computedMacBytes, encyptedValue.MacBytes)) - { - throw new InvalidOperationException("MAC failed."); - } - } - - var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); - var cryptoKey = provider.CreateSymmetricKey(key.EncKey); - var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes, - encyptedValue.InitializationVectorBytes); - return decryptedBytes; + return Crypto.AesCbcDecrypt(encyptedValue, key); } public byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey) @@ -362,65 +341,6 @@ namespace Bit.App.Services return decryptedBytes; } - private string ComputeMacBase64(byte[] ctBytes, byte[] ivBytes, byte[] macKey) - { - var mac = ComputeMac(ctBytes, ivBytes, macKey); - return Convert.ToBase64String(mac); - } - - private byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey) - { - if(macKey == null) - { - throw new ArgumentNullException(nameof(macKey)); - } - - if(ctBytes == null) - { - throw new ArgumentNullException(nameof(ctBytes)); - } - - if(ivBytes == null) - { - throw new ArgumentNullException(nameof(ivBytes)); - } - - var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256); - var hasher = algorithm.CreateHash(macKey); - hasher.Append(ivBytes.Concat(ctBytes).ToArray()); - var mac = hasher.GetValueAndReset(); - return mac; - } - - // Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification). - // ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/ - private bool MacsEqual(byte[] macKey, byte[] mac1, byte[] mac2) - { - var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256); - var hasher = algorithm.CreateHash(macKey); - - hasher.Append(mac1); - mac1 = hasher.GetValueAndReset(); - - hasher.Append(mac2); - mac2 = hasher.GetValueAndReset(); - - if(mac1.Length != mac2.Length) - { - return false; - } - - for(int i = 0; i < mac2.Length; i++) - { - if(mac1[i] != mac2[i]) - { - return false; - } - } - - return true; - } - public SymmetricCryptoKey MakeKeyFromPassword(string password, string salt) { if(password == null) @@ -471,7 +391,7 @@ namespace Bit.App.Services public CipherString MakeEncKey(SymmetricCryptoKey key) { - var bytes = WinRTCrypto.CryptographicBuffer.GenerateRandom(512 / 8); + var bytes = Crypto.RandomBytes(512 / 8); return Encrypt(bytes, key); } } diff --git a/src/App/Utilities/Crypto.cs b/src/App/Utilities/Crypto.cs new file mode 100644 index 000000000..08ca50c73 --- /dev/null +++ b/src/App/Utilities/Crypto.cs @@ -0,0 +1,125 @@ +using Bit.App.Models; +using PCLCrypto; +using System; +using System.Linq; + +namespace Bit.App.Utilities +{ + public static class Crypto + { + public static CipherString AesCbcEncrypt(byte[] plainBytes, SymmetricCryptoKey key) + { + if(key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if(plainBytes == null) + { + throw new ArgumentNullException(nameof(plainBytes)); + } + + var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); + var cryptoKey = provider.CreateSymmetricKey(key.EncKey); + var iv = RandomBytes(provider.BlockLength); + var encryptedBytes = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv); + var mac = key.MacKey != null ? ComputeMacBase64(encryptedBytes, iv, key.MacKey) : null; + + return new CipherString(key.EncryptionType, Convert.ToBase64String(iv), + Convert.ToBase64String(encryptedBytes), mac); + } + + public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key) + { + if(key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if(encyptedValue == null) + { + throw new ArgumentNullException(nameof(encyptedValue)); + } + + if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac)) + { + var computedMacBytes = ComputeMac(encyptedValue.CipherTextBytes, + encyptedValue.InitializationVectorBytes, key.MacKey); + if(!MacsEqual(key.MacKey, computedMacBytes, encyptedValue.MacBytes)) + { + throw new InvalidOperationException("MAC failed."); + } + } + + var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); + var cryptoKey = provider.CreateSymmetricKey(key.EncKey); + var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes, + encyptedValue.InitializationVectorBytes); + return decryptedBytes; + } + + public static byte[] RandomBytes(int length) + { + return WinRTCrypto.CryptographicBuffer.GenerateRandom(length); + } + + private static string ComputeMacBase64(byte[] ctBytes, byte[] ivBytes, byte[] macKey) + { + var mac = ComputeMac(ctBytes, ivBytes, macKey); + return Convert.ToBase64String(mac); + } + + private static byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey) + { + if(macKey == null) + { + throw new ArgumentNullException(nameof(macKey)); + } + + if(ctBytes == null) + { + throw new ArgumentNullException(nameof(ctBytes)); + } + + if(ivBytes == null) + { + throw new ArgumentNullException(nameof(ivBytes)); + } + + var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256); + var hasher = algorithm.CreateHash(macKey); + hasher.Append(ivBytes.Concat(ctBytes).ToArray()); + var mac = hasher.GetValueAndReset(); + return mac; + } + + // Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification). + // ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/ + private static bool MacsEqual(byte[] macKey, byte[] mac1, byte[] mac2) + { + var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256); + var hasher = algorithm.CreateHash(macKey); + + hasher.Append(mac1); + mac1 = hasher.GetValueAndReset(); + + hasher.Append(mac2); + mac2 = hasher.GetValueAndReset(); + + if(mac1.Length != mac2.Length) + { + return false; + } + + for(int i = 0; i < mac2.Length; i++) + { + if(mac1[i] != mac2[i]) + { + return false; + } + } + + return true; + } + } +}