mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 23:25:45 +03:00
use aes key in keystore on "new" android. migrate.
This commit is contained in:
parent
6a88524f8e
commit
6aaa083157
2 changed files with 108 additions and 50 deletions
|
@ -207,23 +207,10 @@ namespace Bit.Android
|
||||||
container.RegisterSingleton(application.ApplicationContext);
|
container.RegisterSingleton(application.ApplicationContext);
|
||||||
container.RegisterSingleton<Application>(application);
|
container.RegisterSingleton<Application>(application);
|
||||||
|
|
||||||
// Secure Storage
|
|
||||||
ISecureStorageService secureStorage;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
secureStorage = new AndroidKeyStoreStorageService(CrossSettings.Current);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Some isolated devices are having a hard time generating RSA keys for the key store.
|
|
||||||
// Continue using the "old" keystore implementation for now.
|
|
||||||
secureStorage = new KeyStoreStorageService(new char[] { });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
container.RegisterSingleton<IDatabaseService, DatabaseService>();
|
container.RegisterSingleton<IDatabaseService, DatabaseService>();
|
||||||
container.RegisterSingleton<ISqlService, SqlService>();
|
container.RegisterSingleton<ISqlService, SqlService>();
|
||||||
container.RegisterSingleton(secureStorage);
|
container.RegisterSingleton<ISecureStorageService, AndroidKeyStoreStorageService>();
|
||||||
container.RegisterSingleton<ICryptoService, CryptoService>();
|
container.RegisterSingleton<ICryptoService, CryptoService>();
|
||||||
container.RegisterSingleton<IKeyDerivationService, BouncyCastleKeyDerivationService>();
|
container.RegisterSingleton<IKeyDerivationService, BouncyCastleKeyDerivationService>();
|
||||||
container.RegisterSingleton<IAuthService, AuthService>();
|
container.RegisterSingleton<IAuthService, AuthService>();
|
||||||
|
|
|
@ -18,9 +18,16 @@ namespace Bit.Android.Services
|
||||||
public class AndroidKeyStoreStorageService : ISecureStorageService
|
public class AndroidKeyStoreStorageService : ISecureStorageService
|
||||||
{
|
{
|
||||||
private const string AndroidKeyStore = "AndroidKeyStore";
|
private const string AndroidKeyStore = "AndroidKeyStore";
|
||||||
private const string KeyAlias = "bitwardenKey";
|
private const string AesMode = "AES/GCM/NoPadding";
|
||||||
private const string SettingsFormat = "ksSecured:{0}";
|
|
||||||
private const string AesKey = "ksSecured:aesKeyForService";
|
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";
|
||||||
|
|
||||||
private readonly string _rsaMode;
|
private readonly string _rsaMode;
|
||||||
private readonly bool _oldAndroid;
|
private readonly bool _oldAndroid;
|
||||||
|
@ -39,19 +46,26 @@ namespace Bit.Android.Services
|
||||||
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
|
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
|
||||||
_keyStore.Load(null);
|
_keyStore.Load(null);
|
||||||
|
|
||||||
GenerateRsaKey();
|
GenerateStoreKey();
|
||||||
GenerateAesKey();
|
GenerateAesKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains(string key)
|
public bool Contains(string key)
|
||||||
{
|
{
|
||||||
return _settings.Contains(string.Format(SettingsFormat, key)) || _oldKeyStorageService.Contains(key);
|
return _settings.Contains(string.Format(SettingsFormat, key)) ||
|
||||||
|
_settings.Contains(string.Format(SettingsFormatV1, key)) ||
|
||||||
|
_oldKeyStorageService.Contains(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete(string key)
|
public void Delete(string key)
|
||||||
{
|
{
|
||||||
CleanupOldKeyStore(key);
|
CleanupOld(key);
|
||||||
_settings.Remove(string.Format(SettingsFormat, key));
|
|
||||||
|
var formattedKey = string.Format(SettingsFormat, key);
|
||||||
|
if(_settings.Contains(formattedKey))
|
||||||
|
{
|
||||||
|
_settings.Remove(formattedKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] Retrieve(string key)
|
public byte[] Retrieve(string key)
|
||||||
|
@ -59,7 +73,7 @@ namespace Bit.Android.Services
|
||||||
var formattedKey = string.Format(SettingsFormat, key);
|
var formattedKey = string.Format(SettingsFormat, key);
|
||||||
if(!_settings.Contains(formattedKey))
|
if(!_settings.Contains(formattedKey))
|
||||||
{
|
{
|
||||||
return TryGetAndMigrateFromOldKeyStore(key);
|
return TryGetAndMigrate(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
var cs = _settings.GetValueOrDefault<string>(formattedKey);
|
var cs = _settings.GetValueOrDefault<string>(formattedKey);
|
||||||
|
@ -90,7 +104,7 @@ namespace Bit.Android.Services
|
||||||
public void Store(string key, byte[] dataBytes)
|
public void Store(string key, byte[] dataBytes)
|
||||||
{
|
{
|
||||||
var formattedKey = string.Format(SettingsFormat, key);
|
var formattedKey = string.Format(SettingsFormat, key);
|
||||||
CleanupOldKeyStore(key);
|
CleanupOld(key);
|
||||||
if(dataBytes == null)
|
if(dataBytes == null)
|
||||||
{
|
{
|
||||||
_settings.Remove(formattedKey);
|
_settings.Remove(formattedKey);
|
||||||
|
@ -115,18 +129,16 @@ namespace Bit.Android.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateRsaKey()
|
private void GenerateStoreKey()
|
||||||
{
|
{
|
||||||
if(_keyStore.ContainsAlias(KeyAlias))
|
if(_keyStore.ContainsAlias(KeyAlias))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
|
|
||||||
var subject = new X500Principal($"CN={KeyAlias}");
|
|
||||||
|
|
||||||
if(_oldAndroid)
|
if(_oldAndroid)
|
||||||
{
|
{
|
||||||
|
var subject = new X500Principal($"CN={KeyAlias}");
|
||||||
var start = Calendar.Instance;
|
var start = Calendar.Instance;
|
||||||
var end = Calendar.Instance;
|
var end = Calendar.Instance;
|
||||||
end.Add(CalendarField.Year, 30);
|
end.Add(CalendarField.Year, 30);
|
||||||
|
@ -139,25 +151,26 @@ namespace Bit.Android.Services
|
||||||
.SetEndDate(end.Time)
|
.SetEndDate(end.Time)
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
|
||||||
gen.Initialize(spec);
|
gen.Initialize(spec);
|
||||||
|
gen.GenerateKeyPair();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
||||||
.SetCertificateSubject(subject)
|
.SetBlockModes(KeyProperties.BlockModeGcm)
|
||||||
.SetDigests(KeyProperties.DigestSha1)
|
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
|
||||||
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingRsaOaep)
|
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
gen.Initialize(spec);
|
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
|
||||||
|
gen.Init(spec);
|
||||||
|
gen.GenerateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
gen.GenerateKeyPair();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStore.PrivateKeyEntry GetRsaKeyEntry()
|
private KeyStore.PrivateKeyEntry GetRsaKeyEntry(string alias)
|
||||||
{
|
{
|
||||||
return _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry;
|
return _keyStore.GetEntry(alias, null) as KeyStore.PrivateKeyEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GenerateAesKey()
|
private void GenerateAesKey()
|
||||||
|
@ -168,23 +181,39 @@ namespace Bit.Android.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = App.Utilities.Crypto.RandomBytes(512 / 8);
|
var key = App.Utilities.Crypto.RandomBytes(512 / 8);
|
||||||
var encKey = RsaEncrypt(key);
|
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
|
||||||
_settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey));
|
_settings.AddOrUpdateValue(AesKey, encKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private App.Models.SymmetricCryptoKey GetAesKey()
|
private App.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var encKey = _settings.GetValueOrDefault<string>(AesKey);
|
var encKey = _settings.GetValueOrDefault<string>(v1 ? AesKeyV1 : AesKey);
|
||||||
if(encKey == null)
|
if(encKey == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var encKeyBytes = Convert.FromBase64String(encKey);
|
if(_oldAndroid || v1)
|
||||||
var key = RsaDecrypt(encKeyBytes);
|
{
|
||||||
return new App.Models.SymmetricCryptoKey(key);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
|
@ -196,20 +225,44 @@ namespace Bit.Android.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] RsaEncrypt(byte[] data)
|
private string AesEncrypt(byte[] input)
|
||||||
{
|
{
|
||||||
using(var entry = GetRsaKeyEntry())
|
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))
|
||||||
using(var cipher = Cipher.GetInstance(_rsaMode))
|
using(var cipher = Cipher.GetInstance(_rsaMode))
|
||||||
{
|
{
|
||||||
cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
|
cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
|
||||||
var cipherText = cipher.DoFinal(data);
|
var cipherText = cipher.DoFinal(data);
|
||||||
return cipherText;
|
return Convert.ToBase64String(cipherText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] RsaDecrypt(byte[] encData)
|
private byte[] RsaDecrypt(byte[] encData, bool v1)
|
||||||
{
|
{
|
||||||
using(var entry = GetRsaKeyEntry())
|
using(var entry = GetRsaKeyEntry(v1 ? KeyAliasV1 : KeyAlias))
|
||||||
using(var cipher = Cipher.GetInstance(_rsaMode))
|
using(var cipher = Cipher.GetInstance(_rsaMode))
|
||||||
{
|
{
|
||||||
if(_oldAndroid)
|
if(_oldAndroid)
|
||||||
|
@ -226,25 +279,43 @@ namespace Bit.Android.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] TryGetAndMigrateFromOldKeyStore(string key)
|
private byte[] TryGetAndMigrate(string key)
|
||||||
{
|
{
|
||||||
if(_oldKeyStorageService.Contains(key))
|
if(_oldKeyStorageService.Contains(key))
|
||||||
{
|
{
|
||||||
var value = _oldKeyStorageService.Retrieve(key);
|
var value = _oldKeyStorageService.Retrieve(key);
|
||||||
Store(key, value);
|
Store(key, value);
|
||||||
_oldKeyStorageService.Delete(key);
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
|
||||||
|
if(_settings.Contains(formattedKeyV1))
|
||||||
|
{
|
||||||
|
var aesKeyV1 = GetAesKey(true);
|
||||||
|
if(aesKeyV1 != null)
|
||||||
|
{
|
||||||
|
var cs = _settings.GetValueOrDefault<string>(formattedKeyV1);
|
||||||
|
var value = App.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKeyV1);
|
||||||
|
Store(key, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CleanupOldKeyStore(string key)
|
private void CleanupOld(string key)
|
||||||
{
|
{
|
||||||
if(_oldKeyStorageService.Contains(key))
|
if(_oldKeyStorageService.Contains(key))
|
||||||
{
|
{
|
||||||
_oldKeyStorageService.Delete(key);
|
_oldKeyStorageService.Delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
|
||||||
|
if(_settings.Contains(formattedKeyV1))
|
||||||
|
{
|
||||||
|
_settings.Remove(formattedKeyV1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue