diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs index 7c14fd966..f382df089 100644 --- a/src/Android/MainApplication.cs +++ b/src/Android/MainApplication.cs @@ -43,10 +43,12 @@ namespace Bit.Droid private void RegisterLocalServices() { - var settingsShim = new App.Migration.SettingsShim(); - ServiceContainer.Register("settingsShim", settingsShim); - App.Utilities.AppHelpers.NeedsMigration = - settingsShim.GetValueOrDefault(Constants.OldLastActivityKey, DateTime.MinValue) > DateTime.MinValue; + ServiceContainer.Register("settingsShim", new App.Migration.SettingsShim()); + if(App.Migration.MigrationHelpers.NeedsMigration()) + { + ServiceContainer.Register( + "oldSecureStorageService", new Migration.AndroidKeyStoreStorageService()); + } Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init(); // Note: This might cause a race condition. Investigate more. diff --git a/src/Android/Migration/AndroidKeyStoreStorageService.cs b/src/Android/Migration/AndroidKeyStoreStorageService.cs index 95c3a223a..94b2d7411 100644 --- a/src/Android/Migration/AndroidKeyStoreStorageService.cs +++ b/src/Android/Migration/AndroidKeyStoreStorageService.cs @@ -13,10 +13,11 @@ 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 + public class AndroidKeyStoreStorageService : IOldSecureStorageService { private const string AndroidKeyStore = "AndroidKeyStore"; private const string AesMode = "AES/GCM/NoPadding"; diff --git a/src/App/App.xaml.cs b/src/App/App.xaml.cs index 19ae9eb92..8ec206f8e 100644 --- a/src/App/App.xaml.cs +++ b/src/App/App.xaml.cs @@ -35,6 +35,7 @@ namespace Bit.App private readonly IPlatformUtilsService _platformUtilsService; private readonly IAuthService _authService; private readonly IStorageService _storageService; + private readonly IStorageService _secureStorageService; private readonly IDeviceActionService _deviceActionService; private readonly AppOptions _appOptions; @@ -57,6 +58,7 @@ namespace Bit.App _authService = ServiceContainer.Resolve("authService"); _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); _storageService = ServiceContainer.Resolve("storageService"); + _secureStorageService = ServiceContainer.Resolve("secureStorageService"); _passwordGenerationService = ServiceContainer.Resolve( "passwordGenerationService"); _i18nService = ServiceContainer.Resolve("i18nService") as MobileI18nService; @@ -100,7 +102,8 @@ namespace Bit.App } else if(message.Command == "loggedOut") { - // TODO + // Clean up old migrated key if they ever log out. + await _secureStorageService.RemoveAsync("oldKey"); } else if(message.Command == "unlocked" || message.Command == "loggedIn") { diff --git a/src/App/Migration/Abstractions/IOldSecureStorageService.cs b/src/App/Migration/Abstractions/IOldSecureStorageService.cs new file mode 100644 index 000000000..cc3a53c85 --- /dev/null +++ b/src/App/Migration/Abstractions/IOldSecureStorageService.cs @@ -0,0 +1,10 @@ +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); + } +} diff --git a/src/App/Migration/MigrationHelpers.cs b/src/App/Migration/MigrationHelpers.cs new file mode 100644 index 000000000..e092e9c2f --- /dev/null +++ b/src/App/Migration/MigrationHelpers.cs @@ -0,0 +1,90 @@ +using Bit.Core; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Utilities; +using System; +using System.Text; +using System.Threading.Tasks; +using Xamarin.Forms; + +namespace Bit.App.Migration +{ + public static class MigrationHelpers + { + public static bool NeedsMigration() + { + return ServiceContainer.Resolve("settingsShim") + .GetValueOrDefault(Constants.OldLastActivityKey, DateTime.MinValue) > DateTime.MinValue; + } + + public static async Task PerformMigrationAsync() + { + if(!NeedsMigration()) + { + return false; + } + var settingsShim = ServiceContainer.Resolve("settingsShim"); + var oldSecureStorageService = ServiceContainer.Resolve( + "oldSecureStorageService"); + + var storageService = ServiceContainer.Resolve("storageService"); + var secureStorageService = ServiceContainer.Resolve("secureStorageService"); + var cryptoService = ServiceContainer.Resolve("cryptoService"); + var tokenService = ServiceContainer.Resolve("tokenService"); + var userService = ServiceContainer.Resolve("userService"); + + // 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) + { + 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); + + // Save settings + + + + // Save new authed data + + await tokenService.SetTwoFactorTokenAsync(oldTwoFactorToken, oldEmail); + await tokenService.SetTokensAsync(oldToken, oldRefreshToken); + await userService.SetInformationAsync(oldUserId, oldEmail, oldKdf, oldKdfIterations); + + 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); + + return true; + } + } +} diff --git a/src/App/Utilities/AppHelpers.cs b/src/App/Utilities/AppHelpers.cs index 0ee95537d..d66072e8e 100644 --- a/src/App/Utilities/AppHelpers.cs +++ b/src/App/Utilities/AppHelpers.cs @@ -13,8 +13,6 @@ namespace Bit.App.Utilities { public static class AppHelpers { - public static bool NeedsMigration = false; - public static async Task CipherListOptions(ContentPage page, CipherView cipher) { var platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); @@ -118,7 +116,7 @@ namespace Bit.App.Utilities { var currentBuild = deviceActionService.GetBuildNumber(); var lastBuild = await storageService.GetAsync(Constants.LastBuildKey); - if(!NeedsMigration) + if(!Migration.MigrationHelpers.NeedsMigration()) { if(lastBuild == null) {