2017-05-27 08:05:12 +03:00
|
|
|
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 Javax.Crypto.Spec;
|
|
|
|
using System.Collections.Generic;
|
2017-05-27 18:45:03 +03:00
|
|
|
using Java.Util;
|
2017-05-27 08:05:12 +03:00
|
|
|
|
|
|
|
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 AesMode = "AES/GCM/NoPadding";
|
2017-05-27 18:46:42 +03:00
|
|
|
private const string AesKey = "ksSecured:aesKeyForService";
|
2017-05-27 08:05:12 +03:00
|
|
|
|
|
|
|
private readonly ISettings _settings;
|
|
|
|
private readonly KeyStore _keyStore;
|
|
|
|
private readonly bool _oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
|
2017-05-27 18:42:22 +03:00
|
|
|
private readonly KeyStoreStorageService _oldKeyStorageService;
|
2017-05-27 08:05:12 +03:00
|
|
|
|
|
|
|
public KeyStoreBackedStorageService(ISettings settings)
|
|
|
|
{
|
2017-05-27 19:23:35 +03:00
|
|
|
_oldKeyStorageService = new KeyStoreStorageService(new char[] { });
|
2017-05-27 08:05:12 +03:00
|
|
|
_settings = settings;
|
|
|
|
|
|
|
|
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
|
|
|
|
_keyStore.Load(null);
|
|
|
|
|
|
|
|
GenerateKeys();
|
|
|
|
GenerateAesKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
public bool Contains(string key)
|
|
|
|
{
|
2017-05-27 19:23:35 +03:00
|
|
|
return _settings.Contains(string.Format(SettingsFormat, key)) || _oldKeyStorageService.Contains(key);
|
2017-05-27 08:05:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
public void Delete(string key)
|
|
|
|
{
|
2017-05-27 18:42:22 +03:00
|
|
|
CleanupOldKeyStore(key);
|
2017-05-27 08:05:12 +03:00
|
|
|
_settings.Remove(string.Format(SettingsFormat, key));
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2017-05-27 18:42:22 +03:00
|
|
|
return TryGetAndMigrateFromOldKeyStore(key);
|
2017-05-27 08:05:12 +03:00
|
|
|
}
|
|
|
|
|
2017-05-27 19:23:35 +03:00
|
|
|
var cipherString = _settings.GetValueOrDefault<string>(formattedKey);
|
2017-05-27 08:05:12 +03:00
|
|
|
return AesDecrypt(cipherString);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Store(string key, byte[] dataBytes)
|
|
|
|
{
|
2017-05-27 19:23:35 +03:00
|
|
|
var formattedKey = string.Format(SettingsFormat, key);
|
2017-05-27 18:42:22 +03:00
|
|
|
CleanupOldKeyStore(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 cipherString = AesEncrypt(dataBytes);
|
2017-05-27 19:23:35 +03:00
|
|
|
_settings.AddOrUpdateValue(formattedKey, cipherString);
|
2017-05-27 08:05:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private byte[] RandomBytes(int length)
|
|
|
|
{
|
|
|
|
var key = new byte[length];
|
|
|
|
var secureRandom = new SecureRandom();
|
|
|
|
secureRandom.NextBytes(key);
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void GenerateKeys()
|
|
|
|
{
|
|
|
|
if(_keyStore.ContainsAlias(KeyAlias))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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}"))
|
|
|
|
.SetSerialNumber(BigInteger.Ten)
|
|
|
|
.SetStartDate(start.Time)
|
|
|
|
.SetEndDate(end.Time)
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
gen.Initialize(spec);
|
|
|
|
gen.GenerateKeyPair();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
|
|
|
|
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
|
|
|
.SetBlockModes(KeyProperties.BlockModeGcm).SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
|
|
|
|
.Build();
|
|
|
|
|
|
|
|
gen.Init(spec);
|
|
|
|
gen.GenerateKey();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void GenerateAesKey()
|
|
|
|
{
|
|
|
|
if(!_oldAndroid)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-05-27 18:46:42 +03:00
|
|
|
if(_settings.Contains(AesKey))
|
2017-05-27 08:05:12 +03:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var key = RandomBytes(16);
|
|
|
|
var encKey = RsaEncrypt(key);
|
2017-05-27 18:46:42 +03:00
|
|
|
_settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey));
|
2017-05-27 08:05:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private IKey GetAesKey()
|
|
|
|
{
|
|
|
|
if(_oldAndroid)
|
|
|
|
{
|
2017-05-27 18:46:42 +03:00
|
|
|
var encKey = _settings.GetValueOrDefault<string>(AesKey);
|
2017-05-27 08:05:12 +03:00
|
|
|
var encKeyBytes = Convert.FromBase64String(encKey);
|
|
|
|
var key = RsaDecrypt(encKeyBytes);
|
|
|
|
return new SecretKeySpec(key, "AES");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
var entry = _keyStore.GetEntry(KeyAlias, null) as KeyStore.SecretKeyEntry;
|
|
|
|
return entry.SecretKey;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private string AesEncrypt(byte[] input)
|
|
|
|
{
|
|
|
|
var cipher = Cipher.GetInstance(AesMode);
|
2017-05-27 19:23:35 +03:00
|
|
|
//var ivBytes = RandomBytes(12);
|
|
|
|
//var spec = new GCMParameterSpec(128, ivBytes);
|
|
|
|
cipher.Init(CipherMode.EncryptMode, GetAesKey());
|
2017-05-27 08:05:12 +03:00
|
|
|
var encBytes = cipher.DoFinal(input);
|
2017-05-27 19:23:35 +03:00
|
|
|
var ivBytes = cipher.GetIV();
|
2017-05-27 08:05:12 +03:00
|
|
|
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
|
|
|
|
}
|
|
|
|
|
|
|
|
private byte[] AesDecrypt(string cipherString)
|
|
|
|
{
|
|
|
|
var parts = cipherString.Split('|');
|
|
|
|
var ivBytes = Convert.FromBase64String(parts[0]);
|
|
|
|
var encBytes = Convert.FromBase64String(parts[1]);
|
|
|
|
|
|
|
|
var cipher = Cipher.GetInstance(AesMode);
|
|
|
|
var spec = new GCMParameterSpec(128, ivBytes);
|
|
|
|
cipher.Init(CipherMode.DecryptMode, GetAesKey(), spec);
|
|
|
|
var decBytes = cipher.DoFinal(encBytes);
|
|
|
|
return decBytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
private byte[] RsaEncrypt(byte[] input)
|
|
|
|
{
|
|
|
|
var entry = _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry;
|
|
|
|
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 = _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry;
|
|
|
|
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<byte>();
|
|
|
|
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;
|
|
|
|
}
|
2017-05-27 18:42:22 +03:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2017-05-27 08:05:12 +03:00
|
|
|
}
|
|
|
|
}
|