mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 23:25:45 +03:00
remove migration code
This commit is contained in:
parent
54fcabaea6
commit
93b59a75a4
14 changed files with 10 additions and 1221 deletions
|
@ -116,7 +116,6 @@
|
||||||
<Compile Include="Effects\FixedSizeEffect.cs" />
|
<Compile Include="Effects\FixedSizeEffect.cs" />
|
||||||
<Compile Include="Effects\SelectableLabelEffect.cs" />
|
<Compile Include="Effects\SelectableLabelEffect.cs" />
|
||||||
<Compile Include="Effects\TabBarEffect.cs" />
|
<Compile Include="Effects\TabBarEffect.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" />
|
||||||
|
|
|
@ -42,11 +42,6 @@ namespace Bit.Droid
|
||||||
RegisterLocalServices();
|
RegisterLocalServices();
|
||||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
|
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
|
||||||
if(App.Migration.MigrationHelpers.NeedsMigration())
|
|
||||||
{
|
|
||||||
var task = App.Migration.MigrationHelpers.PerformMigrationAsync();
|
|
||||||
Task.Delay(2000).Wait();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#if !FDROID
|
#if !FDROID
|
||||||
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||||
|
@ -74,12 +69,6 @@ namespace Bit.Droid
|
||||||
private void RegisterLocalServices()
|
private void RegisterLocalServices()
|
||||||
{
|
{
|
||||||
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
|
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
|
||||||
ServiceContainer.Register("settingsShim", new App.Migration.SettingsShim());
|
|
||||||
if(App.Migration.MigrationHelpers.NeedsMigration())
|
|
||||||
{
|
|
||||||
ServiceContainer.Register<App.Migration.Abstractions.IOldSecureStorageService>(
|
|
||||||
"oldSecureStorageService", new Migration.AndroidKeyStoreStorageService());
|
|
||||||
}
|
|
||||||
|
|
||||||
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
|
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
|
||||||
// Note: This might cause a race condition. Investigate more.
|
// Note: This might cause a race condition. Investigate more.
|
||||||
|
|
|
@ -1,373 +0,0 @@
|
||||||
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;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.App.Migration.Abstractions;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Migration
|
|
||||||
{
|
|
||||||
public class AndroidKeyStoreStorageService : IOldSecureStorageService
|
|
||||||
{
|
|
||||||
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 = ServiceContainer.Resolve<SettingsShim>("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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -97,10 +97,6 @@ namespace Bit.App
|
||||||
}
|
}
|
||||||
else if(message.Command == "logout")
|
else if(message.Command == "logout")
|
||||||
{
|
{
|
||||||
if(Migration.MigrationHelpers.Migrating)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(false));
|
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(false));
|
||||||
}
|
}
|
||||||
else if(message.Command == "loggedOut")
|
else if(message.Command == "loggedOut")
|
||||||
|
@ -355,10 +351,6 @@ namespace Bit.App
|
||||||
|
|
||||||
private void SyncIfNeeded()
|
private void SyncIfNeeded()
|
||||||
{
|
{
|
||||||
if(Migration.MigrationHelpers.Migrating)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
if(Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace Bit.App.Migration.Abstractions
|
|
||||||
{
|
|
||||||
public interface IOldSecureStorageService
|
|
||||||
{
|
|
||||||
bool Contains(string key);
|
|
||||||
void Delete(string key);
|
|
||||||
byte[] Retrieve(string key);
|
|
||||||
void Store(string key, byte[] dataBytes);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,199 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,201 +0,0 @@
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Bit.App.Migration
|
|
||||||
{
|
|
||||||
public static class MigrationHelpers
|
|
||||||
{
|
|
||||||
public static bool Migrating = false;
|
|
||||||
|
|
||||||
public static bool NeedsMigration()
|
|
||||||
{
|
|
||||||
return ServiceContainer.Resolve<SettingsShim>("settingsShim")
|
|
||||||
.GetValueOrDefault(Constants.OldUserIdKey, null) != null; ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<bool> PerformMigrationAsync()
|
|
||||||
{
|
|
||||||
if(!NeedsMigration() || Migrating)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Migrating = true;
|
|
||||||
var settingsShim = ServiceContainer.Resolve<SettingsShim>("settingsShim");
|
|
||||||
var oldSecureStorageService = ServiceContainer.Resolve<Abstractions.IOldSecureStorageService>(
|
|
||||||
"oldSecureStorageService");
|
|
||||||
|
|
||||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
|
||||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
||||||
var secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
|
|
||||||
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
|
|
||||||
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
|
|
||||||
var userService = ServiceContainer.Resolve<IUserService>("userService");
|
|
||||||
var environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
|
|
||||||
var passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
|
|
||||||
"passwordGenerationService");
|
|
||||||
var syncService = ServiceContainer.Resolve<ISyncService>("syncService");
|
|
||||||
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
|
||||||
|
|
||||||
// Get old data
|
|
||||||
|
|
||||||
var oldTokenBytes = oldSecureStorageService.Retrieve("accessToken");
|
|
||||||
var oldToken = oldTokenBytes == null ? null : Encoding.UTF8.GetString(
|
|
||||||
oldTokenBytes, 0, oldTokenBytes.Length);
|
|
||||||
var oldKeyBytes = oldSecureStorageService.Retrieve("key");
|
|
||||||
var oldKey = oldKeyBytes == null ? null : new Models.SymmetricCryptoKey(oldKeyBytes);
|
|
||||||
var oldUserId = settingsShim.GetValueOrDefault("userId", null);
|
|
||||||
|
|
||||||
var isAuthenticated = oldKey != null && !string.IsNullOrWhiteSpace(oldToken) &&
|
|
||||||
!string.IsNullOrWhiteSpace(oldUserId);
|
|
||||||
if(!isAuthenticated)
|
|
||||||
{
|
|
||||||
Migrating = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldRefreshTokenBytes = oldSecureStorageService.Retrieve("refreshToken");
|
|
||||||
var oldRefreshToken = oldRefreshTokenBytes == null ? null : Encoding.UTF8.GetString(
|
|
||||||
oldRefreshTokenBytes, 0, oldRefreshTokenBytes.Length);
|
|
||||||
var oldPinBytes = oldSecureStorageService.Retrieve("pin");
|
|
||||||
var oldPin = oldPinBytes == null ? null : Encoding.UTF8.GetString(
|
|
||||||
oldPinBytes, 0, oldPinBytes.Length);
|
|
||||||
|
|
||||||
var oldEncKey = settingsShim.GetValueOrDefault("encKey", null);
|
|
||||||
var oldEncPrivateKey = settingsShim.GetValueOrDefault("encPrivateKey", null);
|
|
||||||
var oldEmail = settingsShim.GetValueOrDefault("email", null);
|
|
||||||
var oldKdf = (KdfType)settingsShim.GetValueOrDefault("kdf", (int)KdfType.PBKDF2_SHA256);
|
|
||||||
var oldKdfIterations = settingsShim.GetValueOrDefault("kdfIterations", 5000);
|
|
||||||
|
|
||||||
var oldTwoFactorTokenBytes = oldSecureStorageService.Retrieve(
|
|
||||||
string.Format("twoFactorToken_{0}", Convert.ToBase64String(Encoding.UTF8.GetBytes(oldEmail))));
|
|
||||||
var oldTwoFactorToken = oldTwoFactorTokenBytes == null ? null : Encoding.UTF8.GetString(
|
|
||||||
oldTwoFactorTokenBytes, 0, oldTwoFactorTokenBytes.Length);
|
|
||||||
|
|
||||||
var oldAppIdBytes = oldSecureStorageService.Retrieve("appId");
|
|
||||||
var oldAppId = oldAppIdBytes == null ? null : new Guid(oldAppIdBytes).ToString();
|
|
||||||
var oldAnonAppIdBytes = oldSecureStorageService.Retrieve("anonymousAppId");
|
|
||||||
var oldAnonAppId = oldAnonAppIdBytes == null ? null : new Guid(oldAnonAppIdBytes).ToString();
|
|
||||||
var oldFingerprint = settingsShim.GetValueOrDefault("setting:fingerprintUnlockOn", false);
|
|
||||||
|
|
||||||
// Save settings
|
|
||||||
|
|
||||||
await storageService.SaveAsync(Constants.AccessibilityAutofillPersistNotificationKey,
|
|
||||||
settingsShim.GetValueOrDefault("setting:persistNotification", false));
|
|
||||||
await storageService.SaveAsync(Constants.AccessibilityAutofillPasswordFieldKey,
|
|
||||||
settingsShim.GetValueOrDefault("setting:autofillPasswordField", false));
|
|
||||||
await storageService.SaveAsync(Constants.DisableAutoTotpCopyKey,
|
|
||||||
settingsShim.GetValueOrDefault("setting:disableAutoCopyTotp", false));
|
|
||||||
await storageService.SaveAsync(Constants.DisableFaviconKey,
|
|
||||||
settingsShim.GetValueOrDefault("setting:disableWebsiteIcons", false));
|
|
||||||
await storageService.SaveAsync(Constants.AddSitePromptShownKey,
|
|
||||||
settingsShim.GetValueOrDefault("addedSiteAlert", false));
|
|
||||||
await storageService.SaveAsync(Constants.PushInitialPromptShownKey,
|
|
||||||
settingsShim.GetValueOrDefault("push:initialPromptShown", false));
|
|
||||||
await storageService.SaveAsync(Constants.PushCurrentTokenKey,
|
|
||||||
settingsShim.GetValueOrDefault("push:currentToken", null));
|
|
||||||
await storageService.SaveAsync(Constants.PushRegisteredTokenKey,
|
|
||||||
settingsShim.GetValueOrDefault("push:registeredToken", null));
|
|
||||||
// For some reason "push:lastRegistrationDate" isn't getting pulled from settingsShim correctly.
|
|
||||||
// We don't really need it anyways.
|
|
||||||
// var lastReg = settingsShim.GetValueOrDefault("push:lastRegistrationDate", DateTime.MinValue);
|
|
||||||
// await storageService.SaveAsync(Constants.PushLastRegistrationDateKey, lastReg);
|
|
||||||
await storageService.SaveAsync("rememberedEmail",
|
|
||||||
settingsShim.GetValueOrDefault("other:lastLoginEmail", null));
|
|
||||||
await storageService.SaveAsync("appExtensionStarted",
|
|
||||||
settingsShim.GetValueOrDefault("extension:started", false));
|
|
||||||
await storageService.SaveAsync("appExtensionActivated",
|
|
||||||
settingsShim.GetValueOrDefault("extension:activated", false));
|
|
||||||
|
|
||||||
await environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
|
|
||||||
{
|
|
||||||
Base = settingsShim.GetValueOrDefault("other:baseUrl", null),
|
|
||||||
Api = settingsShim.GetValueOrDefault("other:apiUrl", null),
|
|
||||||
WebVault = settingsShim.GetValueOrDefault("other:webVaultUrl", null),
|
|
||||||
Identity = settingsShim.GetValueOrDefault("other:identityUrl", null),
|
|
||||||
Icons = settingsShim.GetValueOrDefault("other:iconsUrl", null)
|
|
||||||
});
|
|
||||||
|
|
||||||
await passwordGenerationService.SaveOptionsAsync(new Core.Models.Domain.PasswordGenerationOptions
|
|
||||||
{
|
|
||||||
Ambiguous = settingsShim.GetValueOrDefault("pwGenerator:ambiguous", false),
|
|
||||||
Length = settingsShim.GetValueOrDefault("pwGenerator:length", 15),
|
|
||||||
Uppercase = settingsShim.GetValueOrDefault("pwGenerator:uppercase", true),
|
|
||||||
Lowercase = settingsShim.GetValueOrDefault("pwGenerator:lowercase", true),
|
|
||||||
Number = settingsShim.GetValueOrDefault("pwGenerator:numbers", true),
|
|
||||||
MinNumber = settingsShim.GetValueOrDefault("pwGenerator:minNumbers", 0),
|
|
||||||
Special = settingsShim.GetValueOrDefault("pwGenerator:special", true),
|
|
||||||
MinSpecial = settingsShim.GetValueOrDefault("pwGenerator:minSpecial", 0),
|
|
||||||
WordSeparator = "-",
|
|
||||||
NumWords = 3
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save lock options
|
|
||||||
|
|
||||||
int? lockOptionsSeconds = settingsShim.GetValueOrDefault("setting:lockSeconds", -10);
|
|
||||||
if(lockOptionsSeconds == -10)
|
|
||||||
{
|
|
||||||
lockOptionsSeconds = 60 * 15;
|
|
||||||
}
|
|
||||||
else if(lockOptionsSeconds == -1)
|
|
||||||
{
|
|
||||||
lockOptionsSeconds = null;
|
|
||||||
}
|
|
||||||
await storageService.SaveAsync(Constants.LockOptionKey,
|
|
||||||
lockOptionsSeconds == null ? (int?)null : lockOptionsSeconds.Value / 60);
|
|
||||||
|
|
||||||
// Save app ids
|
|
||||||
|
|
||||||
await storageService.SaveAsync("appId", oldAppId);
|
|
||||||
await storageService.SaveAsync("anonymousAppId", oldAnonAppId);
|
|
||||||
|
|
||||||
// Save new authed data
|
|
||||||
|
|
||||||
await tokenService.SetTwoFactorTokenAsync(oldTwoFactorToken, oldEmail);
|
|
||||||
await tokenService.SetTokensAsync(oldToken, oldRefreshToken);
|
|
||||||
await userService.SetInformationAsync(oldUserId, oldEmail, oldKdf, oldKdfIterations);
|
|
||||||
|
|
||||||
// Save fingerprint
|
|
||||||
if(oldFingerprint)
|
|
||||||
{
|
|
||||||
await storageService.SaveAsync(Constants.FingerprintUnlockKey, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newKey = new Core.Models.Domain.SymmetricCryptoKey(oldKey.Key);
|
|
||||||
await cryptoService.SetKeyAsync(newKey);
|
|
||||||
// Key hash is unavailable in old version, store old key until we can move it to key hash
|
|
||||||
await secureStorageService.SaveAsync("oldKey", newKey.KeyB64);
|
|
||||||
await cryptoService.SetEncKeyAsync(oldEncKey);
|
|
||||||
await cryptoService.SetEncPrivateKeyAsync(oldEncPrivateKey);
|
|
||||||
|
|
||||||
// Save pin
|
|
||||||
if(!oldFingerprint && !string.IsNullOrWhiteSpace(oldPin))
|
|
||||||
{
|
|
||||||
var pinKey = await cryptoService.MakePinKeyAysnc(oldPin, oldEmail, oldKdf, oldKdfIterations);
|
|
||||||
var pinProtectedKey = await cryptoService.EncryptAsync(oldKeyBytes, pinKey);
|
|
||||||
await storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post migration tasks
|
|
||||||
await cryptoService.ToggleKeyAsync();
|
|
||||||
await storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow.AddYears(-1));
|
|
||||||
await lockService.CheckLockAsync();
|
|
||||||
|
|
||||||
// Remove "needs migration" flag
|
|
||||||
settingsShim.Remove(Constants.OldUserIdKey);
|
|
||||||
await storageService.SaveAsync(Constants.MigratedFromV1, true);
|
|
||||||
Migrating = false;
|
|
||||||
messagingService.Send("migrated");
|
|
||||||
if(Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
|
|
||||||
{
|
|
||||||
var task = Task.Run(() => syncService.FullSyncAsync(true));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Bit.App.Migration
|
|
||||||
{
|
|
||||||
public class SettingsShim
|
|
||||||
{
|
|
||||||
private readonly string _sharedName;
|
|
||||||
|
|
||||||
public SettingsShim(string sharedName = null)
|
|
||||||
{
|
|
||||||
_sharedName = sharedName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(string key)
|
|
||||||
{
|
|
||||||
return _sharedName != null ? Xamarin.Essentials.Preferences.ContainsKey(key, _sharedName) :
|
|
||||||
Xamarin.Essentials.Preferences.ContainsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetValueOrDefault(string key, string defaultValue)
|
|
||||||
{
|
|
||||||
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
|
|
||||||
Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime GetValueOrDefault(string key, DateTime defaultValue)
|
|
||||||
{
|
|
||||||
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
|
|
||||||
Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool GetValueOrDefault(string key, bool defaultValue)
|
|
||||||
{
|
|
||||||
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
|
|
||||||
Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetValueOrDefault(string key, int defaultValue)
|
|
||||||
{
|
|
||||||
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
|
|
||||||
Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long GetValueOrDefault(string key, long defaultValue)
|
|
||||||
{
|
|
||||||
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
|
|
||||||
Xamarin.Essentials.Preferences.Get(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddOrUpdateValue(string key, string value)
|
|
||||||
{
|
|
||||||
if(_sharedName != null)
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddOrUpdateValue(string key, DateTime value)
|
|
||||||
{
|
|
||||||
if(_sharedName != null)
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddOrUpdateValue(string key, bool value)
|
|
||||||
{
|
|
||||||
if(_sharedName != null)
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddOrUpdateValue(string key, long value)
|
|
||||||
{
|
|
||||||
if(_sharedName != null)
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddOrUpdateValue(string key, int value)
|
|
||||||
{
|
|
||||||
if(_sharedName != null)
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Remove(string key)
|
|
||||||
{
|
|
||||||
if(_sharedName != null)
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Remove(key, _sharedName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Xamarin.Essentials.Preferences.Remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -124,8 +124,6 @@ namespace Bit.App.Utilities
|
||||||
{
|
{
|
||||||
var currentBuild = deviceActionService.GetBuildNumber();
|
var currentBuild = deviceActionService.GetBuildNumber();
|
||||||
var lastBuild = await storageService.GetAsync<string>(Constants.LastBuildKey);
|
var lastBuild = await storageService.GetAsync<string>(Constants.LastBuildKey);
|
||||||
if(!Migration.MigrationHelpers.NeedsMigration())
|
|
||||||
{
|
|
||||||
if(lastBuild == null)
|
if(lastBuild == null)
|
||||||
{
|
{
|
||||||
// Installed
|
// Installed
|
||||||
|
@ -140,7 +138,6 @@ namespace Bit.App.Utilities
|
||||||
// Updated
|
// Updated
|
||||||
var tasks = Task.Run(() => syncService.FullSyncAsync(true));
|
var tasks = Task.Run(() => syncService.FullSyncAsync(true));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if(lastBuild != currentBuild)
|
if(lastBuild != currentBuild)
|
||||||
{
|
{
|
||||||
await storageService.SaveAsync(Constants.LastBuildKey, currentBuild);
|
await storageService.SaveAsync(Constants.LastBuildKey, currentBuild);
|
||||||
|
|
|
@ -45,11 +45,6 @@ namespace Bit.iOS
|
||||||
{
|
{
|
||||||
Forms.Init();
|
Forms.Init();
|
||||||
InitApp();
|
InitApp();
|
||||||
if(App.Migration.MigrationHelpers.NeedsMigration())
|
|
||||||
{
|
|
||||||
var task = App.Migration.MigrationHelpers.PerformMigrationAsync();
|
|
||||||
Task.Delay(5000).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||||
|
@ -265,12 +260,6 @@ namespace Bit.iOS
|
||||||
|
|
||||||
// Migration services
|
// Migration services
|
||||||
ServiceContainer.Register<ILogService>("logService", new ConsoleLogService());
|
ServiceContainer.Register<ILogService>("logService", new ConsoleLogService());
|
||||||
ServiceContainer.Register("settingsShim", new App.Migration.SettingsShim(iOSCoreHelpers.AppGroupId));
|
|
||||||
if(App.Migration.MigrationHelpers.NeedsMigration())
|
|
||||||
{
|
|
||||||
ServiceContainer.Register<App.Migration.Abstractions.IOldSecureStorageService>(
|
|
||||||
"oldSecureStorageService", new Migration.KeyChainStorageService());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: This might cause a race condition. Investigate more.
|
// Note: This might cause a race condition. Investigate more.
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Bit.App.Migration.Abstractions;
|
|
||||||
using Foundation;
|
|
||||||
using Security;
|
|
||||||
|
|
||||||
namespace Bit.iOS.Migration
|
|
||||||
{
|
|
||||||
public class KeyChainStorageService : IOldSecureStorageService
|
|
||||||
{
|
|
||||||
public void Store(string key, byte[] dataBytes)
|
|
||||||
{
|
|
||||||
using(var data = NSData.FromArray(dataBytes))
|
|
||||||
using(var newRecord = GetKeyRecord(key, data))
|
|
||||||
{
|
|
||||||
Delete(key);
|
|
||||||
CheckError(SecKeyChain.Add(newRecord));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Retrieve(string key)
|
|
||||||
{
|
|
||||||
SecStatusCode resultCode;
|
|
||||||
|
|
||||||
using(var existingRecord = GetKeyRecord(key))
|
|
||||||
using(var record = SecKeyChain.QueryAsRecord(existingRecord, out resultCode))
|
|
||||||
{
|
|
||||||
if(resultCode == SecStatusCode.ItemNotFound)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckError(resultCode);
|
|
||||||
return record.Generic.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(string key)
|
|
||||||
{
|
|
||||||
using(var record = GetExistingRecord(key))
|
|
||||||
{
|
|
||||||
if(record != null)
|
|
||||||
{
|
|
||||||
CheckError(SecKeyChain.Remove(record));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(string key)
|
|
||||||
{
|
|
||||||
using(var existingRecord = GetExistingRecord(key))
|
|
||||||
{
|
|
||||||
return existingRecord != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CheckError(SecStatusCode resultCode, [CallerMemberName] string caller = null)
|
|
||||||
{
|
|
||||||
if(resultCode != SecStatusCode.Success)
|
|
||||||
{
|
|
||||||
throw new Exception(string.Format("Failed to execute {0}. Result code: {1}", caller, resultCode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SecRecord GetKeyRecord(string key, NSData data = null)
|
|
||||||
{
|
|
||||||
var record = new SecRecord(SecKind.GenericPassword)
|
|
||||||
{
|
|
||||||
Service = "com.8bit.bitwarden",
|
|
||||||
Account = key,
|
|
||||||
AccessGroup = "LTZ2PFU5D6.com.8bit.bitwarden"
|
|
||||||
};
|
|
||||||
|
|
||||||
if(data != null)
|
|
||||||
{
|
|
||||||
record.Generic = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SecRecord GetExistingRecord(string key)
|
|
||||||
{
|
|
||||||
var existingRecord = GetKeyRecord(key);
|
|
||||||
|
|
||||||
SecStatusCode resultCode;
|
|
||||||
SecKeyChain.QueryAsRecord(existingRecord, out resultCode);
|
|
||||||
|
|
||||||
return resultCode == SecStatusCode.Success ? existingRecord : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -134,7 +134,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Main.cs" />
|
<Compile Include="Main.cs" />
|
||||||
<Compile Include="AppDelegate.cs" />
|
<Compile Include="AppDelegate.cs" />
|
||||||
<Compile Include="Migration\KeyChainStorageService.cs" />
|
|
||||||
<Compile Include="NFCReaderDelegate.cs" />
|
<Compile Include="NFCReaderDelegate.cs" />
|
||||||
<Compile Include="Renderers\CustomButtonRenderer.cs" />
|
<Compile Include="Renderers\CustomButtonRenderer.cs" />
|
||||||
<Compile Include="Renderers\CustomSearchBarRenderer.cs" />
|
<Compile Include="Renderers\CustomSearchBarRenderer.cs" />
|
||||||
|
|
Loading…
Reference in a new issue