move some items needed for migration
This commit is contained in:
parent
a66489cd8c
commit
c7b62c8551
|
@ -110,6 +110,7 @@
|
||||||
<Compile Include="Autofill\FilledItem.cs" />
|
<Compile Include="Autofill\FilledItem.cs" />
|
||||||
<Compile Include="Autofill\Parser.cs" />
|
<Compile Include="Autofill\Parser.cs" />
|
||||||
<Compile Include="Autofill\SavedItem.cs" />
|
<Compile Include="Autofill\SavedItem.cs" />
|
||||||
|
<Compile Include="Migration\AndroidKeyStoreStorageService.cs" />
|
||||||
<Compile Include="Push\FirebaseInstanceIdService.cs" />
|
<Compile Include="Push\FirebaseInstanceIdService.cs" />
|
||||||
<Compile Include="Push\FirebaseMessagingService.cs" />
|
<Compile Include="Push\FirebaseMessagingService.cs" />
|
||||||
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
|
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
|
||||||
|
|
|
@ -0,0 +1,371 @@
|
||||||
|
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 Java.Util;
|
||||||
|
using Javax.Crypto.Spec;
|
||||||
|
using Android.Preferences;
|
||||||
|
using Bit.App.Migration;
|
||||||
|
|
||||||
|
namespace Bit.Droid.Migration
|
||||||
|
{
|
||||||
|
public class AndroidKeyStoreStorageService
|
||||||
|
{
|
||||||
|
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";
|
||||||
|
|
||||||
|
private readonly string _rsaMode;
|
||||||
|
private readonly bool _oldAndroid;
|
||||||
|
private readonly SettingsShim _settings;
|
||||||
|
private readonly KeyStore _keyStore;
|
||||||
|
|
||||||
|
public AndroidKeyStoreStorageService()
|
||||||
|
{
|
||||||
|
_oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
|
||||||
|
_rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
|
||||||
|
|
||||||
|
_settings = new SettingsShim();
|
||||||
|
|
||||||
|
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
|
||||||
|
_keyStore.Load(null);
|
||||||
|
|
||||||
|
/*
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GenerateStoreKey(true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
GenerateStoreKey(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
GenerateAesKey();
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(string key)
|
||||||
|
{
|
||||||
|
return _settings.Contains(string.Format(SettingsFormat, key)) ||
|
||||||
|
_settings.Contains(string.Format(SettingsFormatV1, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(string key)
|
||||||
|
{
|
||||||
|
CleanupOld(key);
|
||||||
|
|
||||||
|
var formattedKey = string.Format(SettingsFormat, key);
|
||||||
|
if(_settings.Contains(formattedKey))
|
||||||
|
{
|
||||||
|
_settings.Remove(formattedKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Retrieve(string key)
|
||||||
|
{
|
||||||
|
var formattedKey = string.Format(SettingsFormat, key);
|
||||||
|
if(!_settings.Contains(formattedKey))
|
||||||
|
{
|
||||||
|
return TryGetAndMigrate(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cs = _settings.GetValueOrDefault(formattedKey, null);
|
||||||
|
if(string.IsNullOrWhiteSpace(cs))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aesKey = GetAesKey();
|
||||||
|
if(aesKey == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKey);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to decrypt from secure storage.");
|
||||||
|
_settings.Remove(formattedKey);
|
||||||
|
//Utilities.SendCrashEmail(e);
|
||||||
|
//Utilities.SaveCrashFile(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Store(string key, byte[] dataBytes)
|
||||||
|
{
|
||||||
|
var formattedKey = string.Format(SettingsFormat, key);
|
||||||
|
CleanupOld(key);
|
||||||
|
if(dataBytes == null)
|
||||||
|
{
|
||||||
|
_settings.Remove(formattedKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var aesKey = GetAesKey();
|
||||||
|
if(aesKey == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cipherString = App.Migration.Crypto.AesCbcEncrypt(dataBytes, aesKey);
|
||||||
|
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to encrypt to secure storage.");
|
||||||
|
//Utilities.SendCrashEmail(e);
|
||||||
|
//Utilities.SaveCrashFile(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateStoreKey(bool withDate)
|
||||||
|
{
|
||||||
|
if(_keyStore.ContainsAlias(KeyAlias))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearSettings();
|
||||||
|
|
||||||
|
var end = Calendar.Instance;
|
||||||
|
end.Add(CalendarField.Year, 99);
|
||||||
|
|
||||||
|
if(_oldAndroid)
|
||||||
|
{
|
||||||
|
var subject = new X500Principal($"CN={KeyAlias}");
|
||||||
|
|
||||||
|
var builder = new KeyPairGeneratorSpec.Builder(Application.Context)
|
||||||
|
.SetAlias(KeyAlias)
|
||||||
|
.SetSubject(subject)
|
||||||
|
.SetSerialNumber(BigInteger.Ten);
|
||||||
|
|
||||||
|
if(withDate)
|
||||||
|
{
|
||||||
|
builder.SetStartDate(new Date(0)).SetEndDate(end.Time);
|
||||||
|
}
|
||||||
|
|
||||||
|
var spec = builder.Build();
|
||||||
|
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
|
||||||
|
gen.Initialize(spec);
|
||||||
|
gen.GenerateKeyPair();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var builder = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
||||||
|
.SetBlockModes(KeyProperties.BlockModeGcm)
|
||||||
|
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone);
|
||||||
|
|
||||||
|
if(withDate)
|
||||||
|
{
|
||||||
|
builder.SetKeyValidityStart(new Date(0)).SetKeyValidityEnd(end.Time);
|
||||||
|
}
|
||||||
|
|
||||||
|
var spec = builder.Build();
|
||||||
|
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
|
||||||
|
gen.Init(spec);
|
||||||
|
gen.GenerateKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore.PrivateKeyEntry GetRsaKeyEntry(string alias)
|
||||||
|
{
|
||||||
|
return _keyStore.GetEntry(alias, null) as KeyStore.PrivateKeyEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateAesKey()
|
||||||
|
{
|
||||||
|
if(_settings.Contains(AesKey))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = App.Migration.Crypto.RandomBytes(512 / 8);
|
||||||
|
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
|
||||||
|
_settings.AddOrUpdateValue(AesKey, encKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private App.Migration.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var aesKey = v1 ? AesKeyV1 : AesKey;
|
||||||
|
if(!_settings.Contains(aesKey))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var encKey = _settings.GetValueOrDefault(aesKey, null);
|
||||||
|
if(string.IsNullOrWhiteSpace(encKey))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_oldAndroid || v1)
|
||||||
|
{
|
||||||
|
var encKeyBytes = Convert.FromBase64String(encKey);
|
||||||
|
var key = RsaDecrypt(encKeyBytes, v1);
|
||||||
|
return new App.Migration.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.Migration.Models.SymmetricCryptoKey(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.WriteLine("Cannot get AesKey.");
|
||||||
|
_keyStore.DeleteEntry(KeyAlias);
|
||||||
|
_settings.Remove(AesKey);
|
||||||
|
if(!v1)
|
||||||
|
{
|
||||||
|
//Utilities.SendCrashEmail(e);
|
||||||
|
//Utilities.SaveCrashFile(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AesEncrypt(byte[] input)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
|
||||||
|
var cipherText = cipher.DoFinal(data);
|
||||||
|
return Convert.ToBase64String(cipherText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] RsaDecrypt(byte[] encData, bool v1)
|
||||||
|
{
|
||||||
|
using(var entry = GetRsaKeyEntry(v1 ? KeyAliasV1 : KeyAlias))
|
||||||
|
using(var cipher = Cipher.GetInstance(_rsaMode))
|
||||||
|
{
|
||||||
|
if(_oldAndroid)
|
||||||
|
{
|
||||||
|
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey, OAEPParameterSpec.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
var plainText = cipher.DoFinal(encData);
|
||||||
|
return plainText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] TryGetAndMigrate(string key)
|
||||||
|
{
|
||||||
|
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
|
||||||
|
if(_settings.Contains(formattedKeyV1))
|
||||||
|
{
|
||||||
|
var aesKeyV1 = GetAesKey(true);
|
||||||
|
if(aesKeyV1 != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cs = _settings.GetValueOrDefault(formattedKeyV1, null);
|
||||||
|
var value = App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKeyV1);
|
||||||
|
Store(key, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to decrypt v1 from secure storage.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_settings.Remove(formattedKeyV1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupOld(string key)
|
||||||
|
{
|
||||||
|
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
|
||||||
|
if(_settings.Contains(formattedKeyV1))
|
||||||
|
{
|
||||||
|
_settings.Remove(formattedKeyV1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearSettings(string format = SettingsFormat)
|
||||||
|
{
|
||||||
|
var prefix = string.Format(format, string.Empty);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
using Bit.App.Migration.Models;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using PCLCrypto;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Bit.App.Migration
|
||||||
|
{
|
||||||
|
public static class Crypto
|
||||||
|
{
|
||||||
|
public static CipherString AesCbcEncrypt(byte[] plainBytes, SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
var parts = AesCbcEncryptToParts(plainBytes, key);
|
||||||
|
return new CipherString(parts.Item1, Convert.ToBase64String(parts.Item2),
|
||||||
|
Convert.ToBase64String(parts.Item4), parts.Item3 != null ? Convert.ToBase64String(parts.Item3) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] AesCbcEncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
var parts = AesCbcEncryptToParts(plainBytes, key);
|
||||||
|
var macLength = parts.Item3?.Length ?? 0;
|
||||||
|
|
||||||
|
var encBytes = new byte[1 + parts.Item2.Length + macLength + parts.Item4.Length];
|
||||||
|
encBytes[0] = (byte)parts.Item1;
|
||||||
|
parts.Item2.CopyTo(encBytes, 1);
|
||||||
|
if(parts.Item3 != null)
|
||||||
|
{
|
||||||
|
parts.Item3.CopyTo(encBytes, 1 + parts.Item2.Length);
|
||||||
|
}
|
||||||
|
parts.Item4.CopyTo(encBytes, 1 + parts.Item2.Length + macLength);
|
||||||
|
return encBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Tuple<EncryptionType, byte[], byte[], byte[]> AesCbcEncryptToParts(byte[] plainBytes,
|
||||||
|
SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
if(key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(plainBytes == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(plainBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
|
||||||
|
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
|
||||||
|
var iv = RandomBytes(provider.BlockLength);
|
||||||
|
var ct = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv);
|
||||||
|
var mac = key.MacKey != null ? ComputeMac(ct, iv, key.MacKey) : null;
|
||||||
|
|
||||||
|
return new Tuple<EncryptionType, byte[], byte[], byte[]>(key.EncryptionType, iv, mac, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
if(encyptedValue == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(encyptedValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
return AesCbcDecrypt(encyptedValue.EncryptionType, encyptedValue.CipherTextBytes,
|
||||||
|
encyptedValue.InitializationVectorBytes, encyptedValue.MacBytes, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] AesCbcDecrypt(EncryptionType type, byte[] ct, byte[] iv, byte[] mac,
|
||||||
|
SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
if(key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ct == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(ct));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(iv == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(iv));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key.MacKey != null && mac == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(mac));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key.EncryptionType != type)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(nameof(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key.MacKey != null && mac != null)
|
||||||
|
{
|
||||||
|
var computedMacBytes = ComputeMac(ct, iv, key.MacKey);
|
||||||
|
if(!MacsEqual(computedMacBytes, mac))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("MAC failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
|
||||||
|
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
|
||||||
|
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, ct, iv);
|
||||||
|
return decryptedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] RandomBytes(int length)
|
||||||
|
{
|
||||||
|
return WinRTCrypto.CryptographicBuffer.GenerateRandom(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
|
||||||
|
{
|
||||||
|
if(ctBytes == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(ctBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ivBytes == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(ivBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ComputeMac(ivBytes.Concat(ctBytes), macKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] ComputeMac(IEnumerable<byte> dataBytes, byte[] macKey)
|
||||||
|
{
|
||||||
|
if(macKey == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(macKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dataBytes == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(dataBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
|
||||||
|
var hasher = algorithm.CreateHash(macKey);
|
||||||
|
hasher.Append(dataBytes.ToArray());
|
||||||
|
var mac = hasher.GetValueAndReset();
|
||||||
|
return mac;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
||||||
|
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||||
|
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||||
|
public static bool MacsEqual(byte[] mac1, byte[] mac2)
|
||||||
|
{
|
||||||
|
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
|
||||||
|
var hasher = algorithm.CreateHash(RandomBytes(32));
|
||||||
|
|
||||||
|
hasher.Append(mac1);
|
||||||
|
mac1 = hasher.GetValueAndReset();
|
||||||
|
|
||||||
|
hasher.Append(mac2);
|
||||||
|
mac2 = hasher.GetValueAndReset();
|
||||||
|
|
||||||
|
if(mac1.Length != mac2.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < mac2.Length; i++)
|
||||||
|
{
|
||||||
|
if(mac1[i] != mac2[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ref: https://tools.ietf.org/html/rfc5869
|
||||||
|
public static byte[] HkdfExpand(byte[] prk, byte[] info, int size)
|
||||||
|
{
|
||||||
|
var hashLen = 32; // sha256
|
||||||
|
var okm = new byte[size];
|
||||||
|
var previousT = new byte[0];
|
||||||
|
var n = (int)Math.Ceiling((double)size / hashLen);
|
||||||
|
for(int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
var t = new byte[previousT.Length + info.Length + 1];
|
||||||
|
previousT.CopyTo(t, 0);
|
||||||
|
info.CopyTo(t, previousT.Length);
|
||||||
|
t[t.Length - 1] = (byte)(i + 1);
|
||||||
|
previousT = ComputeMac(t, prk);
|
||||||
|
previousT.CopyTo(okm, i * hashLen);
|
||||||
|
}
|
||||||
|
return okm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
using System;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.App.Migration.Models
|
||||||
|
{
|
||||||
|
public class CipherString
|
||||||
|
{
|
||||||
|
private string _decryptedValue;
|
||||||
|
|
||||||
|
public CipherString(string encryptedString)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(encryptedString))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(nameof(encryptedString));
|
||||||
|
}
|
||||||
|
|
||||||
|
var headerPieces = encryptedString.Split('.');
|
||||||
|
string[] encPieces;
|
||||||
|
|
||||||
|
EncryptionType encType;
|
||||||
|
if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out encType))
|
||||||
|
{
|
||||||
|
EncryptionType = encType;
|
||||||
|
encPieces = headerPieces[1].Split('|');
|
||||||
|
}
|
||||||
|
else if(headerPieces.Length == 1)
|
||||||
|
{
|
||||||
|
encPieces = headerPieces[0].Split('|');
|
||||||
|
EncryptionType = encPieces.Length == 3 ? EncryptionType.AesCbc128_HmacSha256_B64 :
|
||||||
|
EncryptionType.AesCbc256_B64;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Malformed header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(EncryptionType)
|
||||||
|
{
|
||||||
|
case EncryptionType.AesCbc256_B64:
|
||||||
|
if(encPieces.Length != 2)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Malformed encPieces.");
|
||||||
|
}
|
||||||
|
InitializationVector = encPieces[0];
|
||||||
|
CipherText = encPieces[1];
|
||||||
|
break;
|
||||||
|
case EncryptionType.AesCbc128_HmacSha256_B64:
|
||||||
|
case EncryptionType.AesCbc256_HmacSha256_B64:
|
||||||
|
if(encPieces.Length != 3)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Malformed encPieces.");
|
||||||
|
}
|
||||||
|
InitializationVector = encPieces[0];
|
||||||
|
CipherText = encPieces[1];
|
||||||
|
Mac = encPieces[2];
|
||||||
|
break;
|
||||||
|
case EncryptionType.Rsa2048_OaepSha256_B64:
|
||||||
|
case EncryptionType.Rsa2048_OaepSha1_B64:
|
||||||
|
if(encPieces.Length != 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Malformed encPieces.");
|
||||||
|
}
|
||||||
|
CipherText = encPieces[0];
|
||||||
|
break;
|
||||||
|
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
||||||
|
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
|
||||||
|
if(encPieces.Length != 2)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Malformed encPieces.");
|
||||||
|
}
|
||||||
|
CipherText = encPieces[0];
|
||||||
|
Mac = encPieces[1];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Unknown encType.");
|
||||||
|
}
|
||||||
|
|
||||||
|
EncryptedString = encryptedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText,
|
||||||
|
string mac = null)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(initializationVector))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(initializationVector));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cipherText))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(cipherText));
|
||||||
|
}
|
||||||
|
|
||||||
|
EncryptionType = encryptionType;
|
||||||
|
EncryptedString = string.Format("{0}.{1}|{2}", (byte)EncryptionType, initializationVector, cipherText);
|
||||||
|
|
||||||
|
if(!string.IsNullOrWhiteSpace(mac))
|
||||||
|
{
|
||||||
|
EncryptedString = string.Format("{0}|{1}", EncryptedString, mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
CipherText = cipherText;
|
||||||
|
InitializationVector = initializationVector;
|
||||||
|
Mac = mac;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EncryptionType EncryptionType { get; private set; }
|
||||||
|
public string EncryptedString { get; private set; }
|
||||||
|
public string InitializationVector { get; private set; }
|
||||||
|
public string CipherText { get; private set; }
|
||||||
|
public string Mac { get; private set; }
|
||||||
|
public byte[] InitializationVectorBytes => string.IsNullOrWhiteSpace(InitializationVector) ?
|
||||||
|
null : Convert.FromBase64String(InitializationVector);
|
||||||
|
public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
|
||||||
|
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Bit.App.Migration.Models
|
||||||
|
{
|
||||||
|
public class SymmetricCryptoKey
|
||||||
|
{
|
||||||
|
public SymmetricCryptoKey(byte[] rawBytes, EncryptionType? encType = null)
|
||||||
|
{
|
||||||
|
if(rawBytes == null || rawBytes.Length == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("Must provide keyBytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(encType == null)
|
||||||
|
{
|
||||||
|
if(rawBytes.Length == 32)
|
||||||
|
{
|
||||||
|
encType = EncryptionType.AesCbc256_B64;
|
||||||
|
}
|
||||||
|
else if(rawBytes.Length == 64)
|
||||||
|
{
|
||||||
|
encType = EncryptionType.AesCbc256_HmacSha256_B64;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Unable to determine encType.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EncryptionType = encType.Value;
|
||||||
|
Key = rawBytes;
|
||||||
|
|
||||||
|
if(EncryptionType == EncryptionType.AesCbc256_B64 && Key.Length == 32)
|
||||||
|
{
|
||||||
|
EncKey = Key;
|
||||||
|
MacKey = null;
|
||||||
|
}
|
||||||
|
else if(EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
|
||||||
|
{
|
||||||
|
EncKey = Key.Take(16).ToArray();
|
||||||
|
MacKey = Key.Skip(16).Take(16).ToArray();
|
||||||
|
}
|
||||||
|
else if(EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
|
||||||
|
{
|
||||||
|
EncKey = Key.Take(32).ToArray();
|
||||||
|
MacKey = Key.Skip(32).Take(32).ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Unsupported encType/key length.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Key { get; set; }
|
||||||
|
public string B64Key => Convert.ToBase64String(Key);
|
||||||
|
public byte[] EncKey { get; set; }
|
||||||
|
public byte[] MacKey { get; set; }
|
||||||
|
public EncryptionType EncryptionType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.App.Migration
|
||||||
|
{
|
||||||
|
public class SettingsShim
|
||||||
|
{
|
||||||
|
public bool Contains(string key)
|
||||||
|
{
|
||||||
|
return Xamarin.Essentials.Preferences.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetValueOrDefault(string key, string defaultValue)
|
||||||
|
{
|
||||||
|
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime GetValueOrDefault(string key, DateTime defaultValue)
|
||||||
|
{
|
||||||
|
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetValueOrDefault(string key, bool defaultValue)
|
||||||
|
{
|
||||||
|
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetValueOrDefault(string key, int defaultValue)
|
||||||
|
{
|
||||||
|
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetValueOrDefault(string key, long defaultValue)
|
||||||
|
{
|
||||||
|
return Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateValue(string key, string value)
|
||||||
|
{
|
||||||
|
Xamarin.Essentials.Preferences.Set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateValue(string key, DateTime value)
|
||||||
|
{
|
||||||
|
Xamarin.Essentials.Preferences.Set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateValue(string key, bool value)
|
||||||
|
{
|
||||||
|
Xamarin.Essentials.Preferences.Set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateValue(string key, long value)
|
||||||
|
{
|
||||||
|
Xamarin.Essentials.Preferences.Set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddOrUpdateValue(string key, int value)
|
||||||
|
{
|
||||||
|
Xamarin.Essentials.Preferences.Set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(string key)
|
||||||
|
{
|
||||||
|
Xamarin.Essentials.Preferences.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue