using System.IO; using Java.Security; using Javax.Crypto; using Android.OS; using Bit.App.Abstractions; using System; using Android.Security; using Javax.Security.Auth.X500; using Java.Math; using Android.Security.Keystore; using Android.App; using Plugin.Settings.Abstractions; using System.Collections.Generic; using Java.Util; namespace Bit.Android.Services { public class KeyStoreBackedStorageService : ISecureStorageService { private const string AndroidKeyStore = "AndroidKeyStore"; private const string AndroidOpenSSL = "AndroidOpenSSL"; private const string KeyAlias = "bitwardenKey"; private const string SettingsFormat = "ksSecured:{0}"; private const string RsaMode = "RSA/ECB/PKCS1Padding"; 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 ISecureStorageService _oldKeyStorageService; public KeyStoreBackedStorageService(ISettings settings) { _oldKeyStorageService = new KeyStoreStorageService(new char[] { }); _settings = settings; _keyStore = KeyStore.GetInstance(AndroidKeyStore); _keyStore.Load(null); GenerateRsaKey(); GenerateAesKey(); } public bool Contains(string key) { return _settings.Contains(string.Format(SettingsFormat, key)) || _oldKeyStorageService.Contains(key); } public void Delete(string key) { CleanupOldKeyStore(key); _settings.Remove(string.Format(SettingsFormat, key)); } public byte[] Retrieve(string key) { var formattedKey = string.Format(SettingsFormat, key); if(!_settings.Contains(formattedKey)) { return TryGetAndMigrateFromOldKeyStore(key); } 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) { var formattedKey = string.Format(SettingsFormat, key); CleanupOldKeyStore(key); if(dataBytes == null) { _settings.Remove(formattedKey); return; } 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 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 gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore); var spec = new KeyPairGeneratorSpec.Builder(Application.Context) .SetAlias(KeyAlias) .SetSubject(subject) .SetSerialNumber(BigInteger.Ten) .SetStartDate(start.Time) .SetEndDate(end.Time) .Build(); gen.Initialize(spec); gen.GenerateKeyPair(); } else { var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore); var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt) .SetCertificateSubject(subject) .SetCertificateSerialNumber(BigInteger.Ten) .SetKeyValidityStart(start.Time) .SetKeyValidityEnd(end.Time) .Build(); gen.Init(spec); gen.GenerateKey(); } } private KeyStore.PrivateKeyEntry GetRsaKeyEntry() { return _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry; } private void GenerateAesKey() { if(_settings.Contains(AesKey)) { return; } var key = App.Utilities.Crypto.RandomBytes(512 / 8); var encKey = RsaEncrypt(key); _settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey)); } private App.Models.SymmetricCryptoKey GetAesKey() { try { var encKey = _settings.GetValueOrDefault(AesKey); if(encKey == null) { return null; } 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) { var entry = GetRsaKeyEntry(); var inputCipher = Cipher.GetInstance(RsaMode, AndroidOpenSSL); inputCipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey); var outputStream = new MemoryStream(); var cipherStream = new CipherOutputStream(outputStream, inputCipher); cipherStream.Write(input); cipherStream.Close(); var vals = outputStream.ToArray(); outputStream.Close(); return vals; } private byte[] RsaDecrypt(byte[] encInput) { var entry = GetRsaKeyEntry(); var outputCipher = Cipher.GetInstance(RsaMode, AndroidOpenSSL); outputCipher.Init(CipherMode.DecryptMode, entry.PrivateKey); var inputStream = new MemoryStream(encInput); var cipherStream = new CipherInputStream(inputStream, outputCipher); var values = new List(); int nextByte; while((nextByte = cipherStream.Read()) != -1) { values.Add((byte)nextByte); } inputStream.Close(); cipherStream.Close(); var bytes = new byte[values.Count]; for(var i = 0; i < bytes.Length; i++) { bytes[i] = values[i]; } return bytes; } private byte[] TryGetAndMigrateFromOldKeyStore(string key) { if(_oldKeyStorageService.Contains(key)) { var value = _oldKeyStorageService.Retrieve(key); Store(key, value); _oldKeyStorageService.Delete(key); return value; } return null; } private void CleanupOldKeyStore(string key) { if(_oldKeyStorageService.Contains(key)) { _oldKeyStorageService.Delete(key); } } } }