bitwarden-android/src/Android/Services/AndroidKeyStoreStorageService.cs

367 lines
12 KiB
C#
Raw Normal View History

2017-05-27 08:05:12 +03:00
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;
2017-05-27 18:45:03 +03:00
using Java.Util;
using Javax.Crypto.Spec;
using Android.Preferences;
2017-05-27 08:05:12 +03:00
namespace Bit.Android.Services
{
public class AndroidKeyStoreStorageService : ISecureStorageService
2017-05-27 08:05:12 +03:00
{
private const string AndroidKeyStore = "AndroidKeyStore";
private const string AesMode = "AES/GCM/NoPadding";
private const string KeyAlias = "bitwardenKey2";
private const string KeyAliasV1 = "bitwardenKey";
private const string SettingsFormat = "ksSecured2:{0}";
private const string SettingsFormatV1 = "ksSecured:{0}";
private const string AesKey = "ksSecured2:aesKeyForService";
private const string AesKeyV1 = "ksSecured:aesKeyForService";
2017-05-27 08:05:12 +03:00
2017-06-06 05:25:59 +03:00
private readonly string _rsaMode;
private readonly bool _oldAndroid;
2017-05-27 08:05:12 +03:00
private readonly ISettings _settings;
private readonly KeyStore _keyStore;
private readonly ISecureStorageService _oldKeyStorageService;
2017-05-27 08:05:12 +03:00
public AndroidKeyStoreStorageService(ISettings settings)
2017-05-27 08:05:12 +03:00
{
_oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
_rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
_oldKeyStorageService = new KeyStoreStorageService(new char[] { });
_settings = settings;
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
_keyStore.Load(null);
GenerateStoreKey();
GenerateAesKey();
}
2017-05-27 08:05:12 +03:00
public bool Contains(string key)
{
return _settings.Contains(string.Format(SettingsFormat, key)) ||
_settings.Contains(string.Format(SettingsFormatV1, key)) ||
_oldKeyStorageService.Contains(key);
2017-05-27 08:05:12 +03:00
}
public void Delete(string key)
{
CleanupOld(key);
var formattedKey = string.Format(SettingsFormat, key);
if(_settings.Contains(formattedKey))
{
_settings.Remove(formattedKey);
}
2017-05-27 08:05:12 +03:00
}
public byte[] Retrieve(string key)
{
2017-05-27 19:23:35 +03:00
var formattedKey = string.Format(SettingsFormat, key);
if(!_settings.Contains(formattedKey))
2017-05-27 08:05:12 +03:00
{
return TryGetAndMigrate(key);
2017-05-27 08:05:12 +03:00
}
var cs = _settings.GetValueOrDefault<string>(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);
2017-06-22 16:39:02 +03:00
Utilities.SendCrashEmail(e);
return null;
}
2017-05-27 08:05:12 +03:00
}
public void Store(string key, byte[] dataBytes)
{
2017-05-27 19:23:35 +03:00
var formattedKey = string.Format(SettingsFormat, key);
CleanupOld(key);
2017-05-27 08:05:12 +03:00
if(dataBytes == null)
{
2017-05-27 19:23:35 +03:00
_settings.Remove(formattedKey);
2017-05-27 08:05:12 +03:00
return;
}
var aesKey = GetAesKey();
if(aesKey == null)
{
return;
}
2017-05-27 08:05:12 +03:00
try
{
var cipherString = App.Utilities.Crypto.AesCbcEncrypt(dataBytes, aesKey);
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
}
catch
{
Console.WriteLine("Failed to encrypt to secure storage.");
2017-06-22 16:39:02 +03:00
Utilities.SendCrashEmail(e);
}
2017-05-27 08:05:12 +03:00
}
private void GenerateStoreKey()
2017-05-27 08:05:12 +03:00
{
if(_keyStore.ContainsAlias(KeyAlias))
{
return;
}
ClearSettings();
var end = Calendar.Instance;
end.Add(CalendarField.Year, 99);
2017-05-27 08:05:12 +03:00
if(_oldAndroid)
{
var subject = new X500Principal($"CN={KeyAlias}");
2017-06-10 05:19:04 +03:00
var spec = new KeyPairGeneratorSpec.Builder(Application.Context)
.SetAlias(KeyAlias)
.SetSubject(subject)
.SetSerialNumber(BigInteger.Ten)
.SetStartDate(new Date(0))
.SetEndDate(end.Time)
2017-05-27 08:05:12 +03:00
.Build();
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
2017-05-27 08:05:12 +03:00
gen.Initialize(spec);
gen.GenerateKeyPair();
2017-05-27 08:05:12 +03:00
}
else
{
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
.SetBlockModes(KeyProperties.BlockModeGcm)
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
.SetKeyValidityStart(new Date(0))
.SetKeyValidityEnd(end.Time)
2017-05-27 08:05:12 +03:00
.Build();
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
gen.Init(spec);
gen.GenerateKey();
2017-05-27 08:05:12 +03:00
}
}
private KeyStore.PrivateKeyEntry GetRsaKeyEntry(string alias)
2017-05-27 08:05:12 +03:00
{
return _keyStore.GetEntry(alias, null) as KeyStore.PrivateKeyEntry;
}
2017-05-27 08:05:12 +03:00
private void GenerateAesKey()
{
2017-05-27 18:46:42 +03:00
if(_settings.Contains(AesKey))
2017-05-27 08:05:12 +03:00
{
return;
}
var key = App.Utilities.Crypto.RandomBytes(512 / 8);
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
_settings.AddOrUpdateValue(AesKey, encKey);
2017-05-27 08:05:12 +03:00
}
private App.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
2017-05-27 08:05:12 +03:00
{
try
2017-05-27 08:05:12 +03:00
{
2017-06-12 17:45:57 +03:00
var aesKey = v1 ? AesKeyV1 : AesKey;
if(!_settings.Contains(aesKey))
{
return null;
}
var encKey = _settings.GetValueOrDefault<string>(aesKey);
if(string.IsNullOrWhiteSpace(encKey))
{
return null;
}
if(_oldAndroid || v1)
{
var encKeyBytes = Convert.FromBase64String(encKey);
var key = RsaDecrypt(encKeyBytes, v1);
return new App.Models.SymmetricCryptoKey(key);
}
else
{
var parts = encKey.Split('|');
if(parts.Length < 2)
{
return null;
}
var ivBytes = Convert.FromBase64String(parts[0]);
var encKeyBytes = Convert.FromBase64String(parts[1]);
var key = AesDecrypt(ivBytes, encKeyBytes);
return new App.Models.SymmetricCryptoKey(key);
}
2017-05-27 08:05:12 +03:00
}
catch
2017-05-27 08:05:12 +03:00
{
Console.WriteLine("Cannot get AesKey.");
_keyStore.DeleteEntry(KeyAlias);
_settings.Remove(AesKey);
2017-06-12 17:45:57 +03:00
if(!v1)
{
2017-06-22 16:39:02 +03:00
Utilities.SendCrashEmail(e);
2017-06-12 17:45:57 +03:00
}
return null;
2017-05-27 08:05:12 +03:00
}
}
private string AesEncrypt(byte[] input)
2017-05-27 08:05:12 +03:00
{
using(var entry = _keyStore.GetKey(KeyAlias, null))
using(var cipher = Cipher.GetInstance(AesMode))
{
cipher.Init(CipherMode.EncryptMode, entry);
var encBytes = cipher.DoFinal(input);
var ivBytes = cipher.GetIV();
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
}
}
private byte[] AesDecrypt(byte[] iv, byte[] encData)
{
using(var entry = _keyStore.GetKey(KeyAlias, null))
using(var cipher = Cipher.GetInstance(AesMode))
{
var spec = new GCMParameterSpec(128, iv);
cipher.Init(CipherMode.DecryptMode, entry, spec);
var decBytes = cipher.DoFinal(encData);
return decBytes;
}
}
private string RsaEncrypt(byte[] data)
{
using(var entry = GetRsaKeyEntry(KeyAlias))
2017-06-06 05:25:59 +03:00
using(var cipher = Cipher.GetInstance(_rsaMode))
2017-05-27 08:05:12 +03:00
{
2017-06-06 05:25:59 +03:00
cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
var cipherText = cipher.DoFinal(data);
return Convert.ToBase64String(cipherText);
2017-05-27 08:05:12 +03:00
}
2017-06-06 05:25:59 +03:00
}
2017-05-27 08:05:12 +03:00
private byte[] RsaDecrypt(byte[] encData, bool v1)
2017-06-06 05:25:59 +03:00
{
using(var entry = GetRsaKeyEntry(v1 ? KeyAliasV1 : KeyAlias))
2017-06-07 06:52:52 +03:00
using(var cipher = Cipher.GetInstance(_rsaMode))
2017-05-27 08:05:12 +03:00
{
2017-06-08 04:44:53 +03:00
if(_oldAndroid)
{
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
}
else
{
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey, OAEPParameterSpec.Default);
}
2017-06-06 05:25:59 +03:00
var plainText = cipher.DoFinal(encData);
return plainText;
2017-05-27 08:05:12 +03:00
}
}
2017-05-27 18:42:22 +03:00
private byte[] TryGetAndMigrate(string key)
2017-05-27 18:42:22 +03:00
{
if(_oldKeyStorageService.Contains(key))
{
var value = _oldKeyStorageService.Retrieve(key);
Store(key, value);
return value;
}
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
if(_settings.Contains(formattedKeyV1))
{
var aesKeyV1 = GetAesKey(true);
if(aesKeyV1 != null)
{
2017-06-12 17:45:57 +03:00
try
{
var cs = _settings.GetValueOrDefault<string>(formattedKeyV1);
var value = App.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKeyV1);
Store(key, value);
return value;
}
catch
{
Console.WriteLine("Failed to decrypt v1 from secure storage.");
}
}
_settings.Remove(formattedKeyV1);
}
2017-05-27 18:42:22 +03:00
return null;
}
private void CleanupOld(string key)
2017-05-27 18:42:22 +03:00
{
if(_oldKeyStorageService.Contains(key))
{
_oldKeyStorageService.Delete(key);
}
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
if(_settings.Contains(formattedKeyV1))
{
_settings.Remove(formattedKeyV1);
}
2017-05-27 18:42:22 +03:00
}
2017-06-12 20:06:46 +03:00
private void ClearSettings(string format = SettingsFormat)
{
2017-06-12 20:06:46 +03:00
var prefix = string.Format(format, string.Empty);
2017-06-12 19:59:17 +03:00
using(var sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(Application.Context))
using(var sharedPreferencesEditor = sharedPreferences.Edit())
{
var removed = false;
foreach(var pref in sharedPreferences.All)
{
if(pref.Key.StartsWith(prefix))
{
removed = true;
sharedPreferencesEditor.Remove(pref.Key);
}
}
if(removed)
{
sharedPreferencesEditor.Commit();
}
}
}
2017-05-27 08:05:12 +03:00
}
}