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

252 lines
8.1 KiB
C#
Raw Normal View History

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);
2017-05-28 05:21:45 +03:00
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
.SetCertificateSubject(new X500Principal($"CN={KeyAlias}"))
.SetCertificateSerialNumber(BigInteger.Ten)
.SetKeyValidityStart(start.Time)
.SetKeyValidityEnd(end.Time)
2017-05-27 08:05:12 +03:00
.Build();
gen.Initialize(spec);
gen.GenerateKeyPair();
}
else
{
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
2017-05-28 05:21:45 +03:00
.SetBlockModes(KeyProperties.BlockModeGcm)
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
2017-05-27 08:05:12 +03:00
.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
{
2017-05-28 05:21:45 +03:00
return _keyStore.GetKey(KeyAlias, null);
2017-05-27 08:05:12 +03:00
}
}
2017-05-28 05:21:45 +03:00
private KeyStore.PrivateKeyEntry GetRsaKeyEntry()
{
return _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry;
}
2017-05-27 08:05:12 +03:00
private string AesEncrypt(byte[] input)
{
var cipher = Cipher.GetInstance(AesMode);
2017-05-27 19:23:35 +03:00
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)
{
2017-05-28 05:21:45 +03:00
var entry = GetRsaKeyEntry();
2017-05-27 08:05:12 +03:00
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)
{
2017-05-28 05:21:45 +03:00
var entry = GetRsaKeyEntry();
2017-05-27 08:05:12 +03:00
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
}
}