diff --git a/Directory.Build.props b/Directory.Build.props
index 379578e7f..ec47e8371 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,6 +1,6 @@
- 8.0.7-nightly.*
+ 8.0.7
Automatic:AppStore
iPhone Distribution
True
@@ -10,4 +10,4 @@
-
\ No newline at end of file
+
diff --git a/src/Core/Abstractions/IConditionedAwaiterManager.cs b/src/Core/Abstractions/IConditionedAwaiterManager.cs
index ea7dd951b..6eb4df5dc 100644
--- a/src/Core/Abstractions/IConditionedAwaiterManager.cs
+++ b/src/Core/Abstractions/IConditionedAwaiterManager.cs
@@ -5,7 +5,8 @@ namespace Bit.Core.Abstractions
{
public enum AwaiterPrecondition
{
- EnvironmentUrlsInited
+ EnvironmentUrlsInited,
+ AndroidWindowCreated
}
public interface IConditionedAwaiterManager
diff --git a/src/Core/Pages/Accounts/HomePage.xaml.cs b/src/Core/Pages/Accounts/HomePage.xaml.cs
index 9a418ac4d..a70526468 100644
--- a/src/Core/Pages/Accounts/HomePage.xaml.cs
+++ b/src/Core/Pages/Accounts/HomePage.xaml.cs
@@ -12,12 +12,14 @@ namespace Bit.App.Pages
private readonly HomeViewModel _vm;
private readonly AppOptions _appOptions;
private IBroadcasterService _broadcasterService;
+ private IConditionedAwaiterManager _conditionedAwaiterManager;
readonly LazyResolve _logger = new LazyResolve();
public HomePage(AppOptions appOptions = null)
{
_broadcasterService = ServiceContainer.Resolve();
+ _conditionedAwaiterManager = ServiceContainer.Resolve();
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as HomeViewModel;
@@ -56,6 +58,8 @@ namespace Bit.App.Pages
PerformNavigationOnAccountChangedOnLoad = false;
accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
}
+
+ _conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.AndroidWindowCreated);
#endif
}
diff --git a/src/Core/Pages/Accounts/LoginPasswordlessPage.xaml b/src/Core/Pages/Accounts/LoginPasswordlessPage.xaml
index b8ee30219..3a03e79dc 100644
--- a/src/Core/Pages/Accounts/LoginPasswordlessPage.xaml
+++ b/src/Core/Pages/Accounts/LoginPasswordlessPage.xaml
@@ -85,7 +85,6 @@
diff --git a/src/Core/Pages/AndroidNavigationRedirectPage.xaml.cs b/src/Core/Pages/AndroidNavigationRedirectPage.xaml.cs
index a96fa09ee..21fe1bd31 100644
--- a/src/Core/Pages/AndroidNavigationRedirectPage.xaml.cs
+++ b/src/Core/Pages/AndroidNavigationRedirectPage.xaml.cs
@@ -1,4 +1,5 @@
using Bit.App.Abstractions;
+using Bit.Core.Abstractions;
using Bit.Core.Utilities;
namespace Bit.Core.Pages;
@@ -6,15 +7,18 @@ namespace Bit.Core.Pages;
public partial class AndroidNavigationRedirectPage : ContentPage
{
private readonly IAccountsManager _accountsManager;
+ private readonly IConditionedAwaiterManager _conditionedAwaiterManager;
public AndroidNavigationRedirectPage()
{
_accountsManager = ServiceContainer.Resolve("accountsManager");
+ _conditionedAwaiterManager = ServiceContainer.Resolve();
InitializeComponent();
}
private void AndroidNavigationRedirectPage_OnLoaded(object sender, EventArgs e)
{
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
+ _conditionedAwaiterManager.SetAsCompleted(AwaiterPrecondition.AndroidWindowCreated);
}
}
diff --git a/src/Core/Pages/Send/SendAddEditPage.xaml b/src/Core/Pages/Send/SendAddEditPage.xaml
index c858234e2..c6d90cca3 100644
--- a/src/Core/Pages/Send/SendAddEditPage.xaml
+++ b/src/Core/Pages/Send/SendAddEditPage.xaml
@@ -266,6 +266,8 @@
AutomationId="SendShowHideOptionsButton" />
+
+
@@ -183,10 +187,6 @@
Value="Bold" />
-
-
@@ -223,8 +223,6 @@
Value="{DynamicResource ButtonTextColorOpacity}" />
-
diff --git a/src/Core/Services/ConditionedAwaiterManager.cs b/src/Core/Services/ConditionedAwaiterManager.cs
index f318996f5..30b744923 100644
--- a/src/Core/Services/ConditionedAwaiterManager.cs
+++ b/src/Core/Services/ConditionedAwaiterManager.cs
@@ -10,7 +10,8 @@ namespace Bit.Core.Services
{
private readonly ConcurrentDictionary> _preconditionsTasks = new ConcurrentDictionary>
{
- [AwaiterPrecondition.EnvironmentUrlsInited] = new TaskCompletionSource()
+ [AwaiterPrecondition.EnvironmentUrlsInited] = new TaskCompletionSource(),
+ [AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource()
};
public Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition)
diff --git a/src/Core/Services/LegacySecureStorage/AndroidKeyStore.cs b/src/Core/Services/LegacySecureStorage/AndroidKeyStore.cs
new file mode 100644
index 000000000..164d5db2f
--- /dev/null
+++ b/src/Core/Services/LegacySecureStorage/AndroidKeyStore.cs
@@ -0,0 +1,295 @@
+#if ANDROID
+
+using Android.Content;
+using Android.OS;
+using Android.Runtime;
+using Android.Security;
+using Android.Security.Keystore;
+using Java.Security;
+using Javax.Crypto;
+using Javax.Crypto.Spec;
+using System.Text;
+
+namespace Bit.Core.Services;
+
+class AndroidKeyStore
+{
+ const string androidKeyStore = "AndroidKeyStore"; // this is an Android const value
+ const string aesAlgorithm = "AES";
+ const string cipherTransformationAsymmetric = "RSA/ECB/PKCS1Padding";
+ const string cipherTransformationSymmetric = "AES/GCM/NoPadding";
+ const string prefsMasterKey = "SecureStorageKey";
+ const int initializationVectorLen = 12; // Android supports an IV of 12 for AES/GCM
+
+ internal AndroidKeyStore(Context context, string keystoreAlias, bool alwaysUseAsymmetricKeyStorage)
+ {
+ alwaysUseAsymmetricKey = alwaysUseAsymmetricKeyStorage;
+ appContext = context;
+ alias = keystoreAlias;
+
+ keyStore = KeyStore.GetInstance(androidKeyStore);
+ keyStore.Load(null);
+ }
+
+ readonly Context appContext;
+ readonly string alias;
+ readonly bool alwaysUseAsymmetricKey;
+ readonly string useSymmetricPreferenceKey = "essentials_use_symmetric";
+
+ KeyStore keyStore;
+ bool useSymmetric = false;
+
+ ISecretKey GetKey()
+ {
+ // check to see if we need to get our key from past-versions or newer versions.
+ // we want to use symmetric if we are >= 23 or we didn't set it previously.
+ var hasApiLevel = Build.VERSION.SdkInt >= BuildVersionCodes.M;
+
+ useSymmetric = Preferences.Get(useSymmetricPreferenceKey, hasApiLevel, alias);
+
+ // If >= API 23 we can use the KeyStore's symmetric key
+ if (useSymmetric && !alwaysUseAsymmetricKey)
+ return GetSymmetricKey();
+
+ // NOTE: KeyStore in < API 23 can only store asymmetric keys
+ // specifically, only RSA/ECB/PKCS1Padding
+ // So we will wrap our symmetric AES key we just generated
+ // with this and save the encrypted/wrapped key out to
+ // preferences for future use.
+ // ECB should be fine in this case as the AES key should be
+ // contained in one block.
+
+ // Get the asymmetric key pair
+ var keyPair = GetAsymmetricKeyPair();
+
+ var existingKeyStr = Preferences.Get(prefsMasterKey, null, alias);
+
+ if (!string.IsNullOrEmpty(existingKeyStr))
+ {
+ try
+ {
+ var wrappedKey = Convert.FromBase64String(existingKeyStr);
+
+ var unwrappedKey = UnwrapKey(wrappedKey, keyPair.Private);
+ var kp = unwrappedKey.JavaCast();
+
+ return kp;
+ }
+ catch (InvalidKeyException ikEx)
+ {
+ System.Diagnostics.Debug.WriteLine(
+ $"Unable to unwrap key: Invalid Key. This may be caused by system backup or upgrades. All secure storage items will now be removed. {ikEx.Message}");
+ }
+ catch (IllegalBlockSizeException ibsEx)
+ {
+ System.Diagnostics.Debug.WriteLine(
+ $"Unable to unwrap key: Illegal Block Size. This may be caused by system backup or upgrades. All secure storage items will now be removed. {ibsEx.Message}");
+ }
+ catch (BadPaddingException paddingEx)
+ {
+ System.Diagnostics.Debug.WriteLine(
+ $"Unable to unwrap key: Bad Padding. This may be caused by system backup or upgrades. All secure storage items will now be removed. {paddingEx.Message}");
+ }
+
+ LegacySecureStorage.RemoveAll();
+ }
+
+ var keyGenerator = KeyGenerator.GetInstance(aesAlgorithm);
+ var defSymmetricKey = keyGenerator.GenerateKey();
+
+ var newWrappedKey = WrapKey(defSymmetricKey, keyPair.Public);
+
+ Preferences.Set(prefsMasterKey, Convert.ToBase64String(newWrappedKey), alias);
+
+ return defSymmetricKey;
+ }
+
+ // API 23+ Only
+#pragma warning disable CA1416
+ ISecretKey GetSymmetricKey()
+ {
+ Preferences.Set(useSymmetricPreferenceKey, true, alias);
+
+ var existingKey = keyStore.GetKey(alias, null);
+
+ if (existingKey != null)
+ {
+ var existingSecretKey = existingKey.JavaCast();
+ return existingSecretKey;
+ }
+
+ var keyGenerator = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, androidKeyStore);
+ var builder = new KeyGenParameterSpec.Builder(alias, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
+ .SetBlockModes(KeyProperties.BlockModeGcm)
+ .SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
+ .SetRandomizedEncryptionRequired(false);
+
+ keyGenerator.Init(builder.Build());
+
+ return keyGenerator.GenerateKey();
+ }
+#pragma warning restore CA1416
+
+ KeyPair GetAsymmetricKeyPair()
+ {
+ // set that we generated keys on pre-m device.
+ Preferences.Set(useSymmetricPreferenceKey, false, alias);
+
+ var asymmetricAlias = $"{alias}.asymmetric";
+
+ var privateKey = keyStore.GetKey(asymmetricAlias, null)?.JavaCast();
+ var publicKey = keyStore.GetCertificate(asymmetricAlias)?.PublicKey;
+
+ // Return the existing key if found
+ if (privateKey != null && publicKey != null)
+ return new KeyPair(publicKey, privateKey);
+
+ var originalLocale = Java.Util.Locale.Default;
+ try
+ {
+ // Force to english for known bug in date parsing:
+ // https://issuetracker.google.com/issues/37095309
+ SetLocale(Java.Util.Locale.English);
+
+ // Otherwise we create a new key
+#pragma warning disable CA1416
+ var generator = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, androidKeyStore);
+#pragma warning restore CA1416
+
+ var end = DateTime.UtcNow.AddYears(20);
+ var startDate = new Java.Util.Date();
+#pragma warning disable CS0618 // Type or member is obsolete
+ var endDate = new Java.Util.Date(end.Year, end.Month, end.Day);
+#pragma warning restore CS0618 // Type or member is obsolete
+
+#pragma warning disable CS0618
+ var builder = new KeyPairGeneratorSpec.Builder(Platform.AppContext)
+ .SetAlias(asymmetricAlias)
+ .SetSerialNumber(Java.Math.BigInteger.One)
+ .SetSubject(new Javax.Security.Auth.X500.X500Principal($"CN={asymmetricAlias} CA Certificate"))
+ .SetStartDate(startDate)
+ .SetEndDate(endDate);
+
+ generator.Initialize(builder.Build());
+#pragma warning restore CS0618
+
+ return generator.GenerateKeyPair();
+ }
+ finally
+ {
+ SetLocale(originalLocale);
+ }
+ }
+
+ byte[] WrapKey(IKey keyToWrap, IKey withKey)
+ {
+ var cipher = Cipher.GetInstance(cipherTransformationAsymmetric);
+ cipher.Init(CipherMode.WrapMode, withKey);
+ return cipher.Wrap(keyToWrap);
+ }
+
+#pragma warning disable CA1416
+ IKey UnwrapKey(byte[] wrappedData, IKey withKey)
+ {
+ var cipher = Cipher.GetInstance(cipherTransformationAsymmetric);
+ cipher.Init(CipherMode.UnwrapMode, withKey);
+ var unwrapped = cipher.Unwrap(wrappedData, KeyProperties.KeyAlgorithmAes, KeyType.SecretKey);
+ return unwrapped;
+ }
+#pragma warning restore CA1416
+
+ internal byte[] Encrypt(string data)
+ {
+ var key = GetKey();
+
+ // Generate initialization vector
+ var iv = new byte[initializationVectorLen];
+
+ var sr = new SecureRandom();
+ sr.NextBytes(iv);
+
+ Cipher cipher;
+
+ // Attempt to use GCMParameterSpec by default
+ try
+ {
+ cipher = Cipher.GetInstance(cipherTransformationSymmetric);
+ cipher.Init(CipherMode.EncryptMode, key, new GCMParameterSpec(128, iv));
+ }
+ catch (InvalidAlgorithmParameterException)
+ {
+ // If we encounter this error, it's likely an old bouncycastle provider version
+ // is being used which does not recognize GCMParameterSpec, but should work
+ // with IvParameterSpec, however we only do this as a last effort since other
+ // implementations will error if you use IvParameterSpec when GCMParameterSpec
+ // is recognized and expected.
+ cipher = Cipher.GetInstance(cipherTransformationSymmetric);
+ cipher.Init(CipherMode.EncryptMode, key, new IvParameterSpec(iv));
+ }
+
+ var decryptedData = Encoding.UTF8.GetBytes(data);
+ var encryptedBytes = cipher.DoFinal(decryptedData);
+
+ // Combine the IV and the encrypted data into one array
+ var r = new byte[iv.Length + encryptedBytes.Length];
+ Buffer.BlockCopy(iv, 0, r, 0, iv.Length);
+ Buffer.BlockCopy(encryptedBytes, 0, r, iv.Length, encryptedBytes.Length);
+
+ return r;
+ }
+
+ internal string Decrypt(byte[] data)
+ {
+ if (data.Length < initializationVectorLen)
+ return null;
+
+ var key = GetKey();
+
+ // IV will be the first 16 bytes of the encrypted data
+ var iv = new byte[initializationVectorLen];
+ Buffer.BlockCopy(data, 0, iv, 0, initializationVectorLen);
+
+ Cipher cipher;
+
+ // Attempt to use GCMParameterSpec by default
+ try
+ {
+ cipher = Cipher.GetInstance(cipherTransformationSymmetric);
+ cipher.Init(CipherMode.DecryptMode, key, new GCMParameterSpec(128, iv));
+ }
+ catch (InvalidAlgorithmParameterException)
+ {
+ // If we encounter this error, it's likely an old bouncycastle provider version
+ // is being used which does not recognize GCMParameterSpec, but should work
+ // with IvParameterSpec, however we only do this as a last effort since other
+ // implementations will error if you use IvParameterSpec when GCMParameterSpec
+ // is recognized and expected.
+ cipher = Cipher.GetInstance(cipherTransformationSymmetric);
+ cipher.Init(CipherMode.DecryptMode, key, new IvParameterSpec(iv));
+ }
+
+ // Decrypt starting after the first 16 bytes from the IV
+ var decryptedData = cipher.DoFinal(data, initializationVectorLen, data.Length - initializationVectorLen);
+
+ return Encoding.UTF8.GetString(decryptedData);
+ }
+
+ internal void SetLocale(Java.Util.Locale locale)
+ {
+ Java.Util.Locale.Default = locale;
+ var resources = appContext.Resources;
+ var config = resources.Configuration;
+
+ if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
+ config.SetLocale(locale);
+ else
+#pragma warning disable CS0618 // Type or member is obsolete
+ config.Locale = locale;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ resources.UpdateConfiguration(config, resources.DisplayMetrics);
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+}
+#endif
diff --git a/src/Core/Services/LegacySecureStorage/KeyChain.cs b/src/Core/Services/LegacySecureStorage/KeyChain.cs
new file mode 100644
index 000000000..4d367f4f6
--- /dev/null
+++ b/src/Core/Services/LegacySecureStorage/KeyChain.cs
@@ -0,0 +1,128 @@
+#if IOS
+
+using System.Diagnostics;
+using Foundation;
+using Security;
+
+namespace Bit.Core.Services;
+
+internal class KeyChain
+{
+ SecAccessible accessible;
+
+ internal KeyChain(SecAccessible accessible) =>
+ this.accessible = accessible;
+
+ SecRecord ExistingRecordForKey(string key, string service)
+ {
+ return new SecRecord(SecKind.GenericPassword)
+ {
+ Account = key,
+ Service = service
+ };
+ }
+
+ internal string ValueForKey(string key, string service)
+ {
+ using (var record = ExistingRecordForKey(key, service))
+ using (var match = SecKeyChain.QueryAsRecord(record, out var resultCode))
+ {
+ if (resultCode == SecStatusCode.Success)
+ return NSString.FromData(match.ValueData, NSStringEncoding.UTF8);
+ else
+ return null;
+ }
+ }
+
+ internal void SetValueForKey(string value, string key, string service)
+ {
+ using (var record = ExistingRecordForKey(key, service))
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ if (!string.IsNullOrEmpty(ValueForKey(key, service)))
+ RemoveRecord(record);
+
+ return;
+ }
+
+ // if the key already exists, remove it
+ if (!string.IsNullOrEmpty(ValueForKey(key, service)))
+ RemoveRecord(record);
+ }
+
+ using (var newRecord = CreateRecordForNewKeyValue(key, value, service))
+ {
+ var result = SecKeyChain.Add(newRecord);
+
+ switch (result)
+ {
+ case SecStatusCode.DuplicateItem:
+ {
+ Debug.WriteLine("Duplicate item found. Attempting to remove and add again.");
+
+ // try to remove and add again
+ if (Remove(key, service))
+ {
+ result = SecKeyChain.Add(newRecord);
+ if (result != SecStatusCode.Success)
+ throw new Exception($"Error adding record: {result}");
+ }
+ else
+ {
+ Debug.WriteLine("Unable to remove key.");
+ }
+ }
+ break;
+ case SecStatusCode.Success:
+ return;
+ default:
+ throw new Exception($"Error adding record: {result}");
+ }
+ }
+ }
+
+ internal bool Remove(string key, string service)
+ {
+ using (var record = ExistingRecordForKey(key, service))
+ using (var match = SecKeyChain.QueryAsRecord(record, out var resultCode))
+ {
+ if (resultCode == SecStatusCode.Success)
+ {
+ RemoveRecord(record);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ internal void RemoveAll(string service)
+ {
+ using (var query = new SecRecord(SecKind.GenericPassword) { Service = service })
+ {
+ SecKeyChain.Remove(query);
+ }
+ }
+
+ SecRecord CreateRecordForNewKeyValue(string key, string value, string service)
+ {
+ return new SecRecord(SecKind.GenericPassword)
+ {
+ Account = key,
+ Service = service,
+ Label = key,
+ Accessible = accessible,
+ ValueData = NSData.FromString(value, NSStringEncoding.UTF8),
+ };
+ }
+
+ bool RemoveRecord(SecRecord record)
+ {
+ var result = SecKeyChain.Remove(record);
+ if (result != SecStatusCode.Success && result != SecStatusCode.ItemNotFound)
+ throw new Exception($"Error removing record: {result}");
+
+ return true;
+ }
+}
+#endif
diff --git a/src/Core/Services/LegacySecureStorage/LegacySecureStorage.cs b/src/Core/Services/LegacySecureStorage/LegacySecureStorage.cs
new file mode 100644
index 000000000..5820ed5b0
--- /dev/null
+++ b/src/Core/Services/LegacySecureStorage/LegacySecureStorage.cs
@@ -0,0 +1,108 @@
+#nullable enable
+
+#if IOS
+using Security;
+#endif
+
+#if ANDROID
+using Javax.Crypto;
+#endif
+
+namespace Bit.Core.Services;
+
+public class LegacySecureStorage
+{
+ internal static readonly string Alias = $"{AppInfo.PackageName}.xamarinessentials";
+
+#if IOS
+ private static SecAccessible DefaultAccessible { get; set; } = SecAccessible.AfterFirstUnlock;
+#endif
+
+
+ public static Task GetAsync(string key)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ throw new ArgumentNullException(nameof(key));
+
+#if ANDROID
+ return Task.Run(() =>
+ {
+ object locker = new object();
+ string? encVal = Preferences.Get(key, null, Alias);
+
+ if (!string.IsNullOrEmpty(encVal))
+ {
+ try
+ {
+ byte[] encData = Convert.FromBase64String(encVal);
+ lock (locker)
+ {
+ AndroidKeyStore keyStore = new AndroidKeyStore(Platform.AppContext, Alias, false);
+ return keyStore.Decrypt(encData);
+ }
+ }
+ catch (AEADBadTagException)
+ {
+ System.Diagnostics.Debug.WriteLine($"Unable to decrypt key, {key}, which is likely due to an app uninstall. Removing old key and returning null.");
+ Remove(key);
+ }
+ }
+
+ return null;
+ });
+#elif IOS
+ var keyChain = new KeyChain(DefaultAccessible);
+ return Task.FromResult(keyChain.ValueForKey(key, Alias));
+#else
+ return Task.FromResult((string?)null);
+#endif
+ }
+
+ public static Task SetAsync(string key, string value)
+ {
+#if ANDROID
+ return Task.Run(() =>
+ {
+ var context = Platform.AppContext;
+
+ byte[] encryptedData = null;
+ object locker = new object();
+ lock (locker)
+ {
+ AndroidKeyStore keyStore = new AndroidKeyStore(Platform.AppContext, Alias, false);
+ encryptedData = keyStore.Encrypt(value);
+ }
+
+ var encStr = Convert.ToBase64String(encryptedData);
+ Preferences.Set(key, encStr, Alias);
+ });
+#elif IOS
+ KeyChain keyChain = new KeyChain(DefaultAccessible);
+ keyChain.SetValueForKey(value, key, Alias);
+#endif
+ return Task.CompletedTask;
+ }
+
+ public static bool Remove(string key)
+ {
+#if ANDROID
+ Preferences.Remove(key, Alias);
+ return true;
+#elif IOS
+ var keyChain = new KeyChain(DefaultAccessible);
+ return keyChain.Remove(key, Alias);
+#else
+ return false;
+#endif
+ }
+
+ public static void RemoveAll()
+ {
+#if ANDROID
+ Preferences.Clear(Alias);
+#elif IOS
+ var keyChain = new KeyChain(DefaultAccessible);
+ keyChain.RemoveAll(Alias);
+#endif
+ }
+}
diff --git a/src/Core/Services/SecureStorageService.cs b/src/Core/Services/SecureStorageService.cs
index b519c21e7..76c561699 100644
--- a/src/Core/Services/SecureStorageService.cs
+++ b/src/Core/Services/SecureStorageService.cs
@@ -1,5 +1,5 @@
-using System.Threading.Tasks;
-using Bit.Core.Abstractions;
+using Bit.Core.Abstractions;
+using Bit.Core.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
@@ -16,7 +16,7 @@ namespace Bit.App.Services
public async Task GetAsync(string key)
{
var formattedKey = string.Format(_keyFormat, key);
- var val = await Microsoft.Maui.Storage.SecureStorage.GetAsync(formattedKey);
+ var val = await LegacySecureStorage.GetAsync(formattedKey);
if (typeof(T) == typeof(string))
{
return (T)(object)val;
@@ -37,11 +37,11 @@ namespace Bit.App.Services
var formattedKey = string.Format(_keyFormat, key);
if (typeof(T) == typeof(string))
{
- await Microsoft.Maui.Storage.SecureStorage.SetAsync(formattedKey, obj as string);
+ await LegacySecureStorage.SetAsync(formattedKey, obj as string);
}
else
{
- await Microsoft.Maui.Storage.SecureStorage.SetAsync(formattedKey,
+ await LegacySecureStorage.SetAsync(formattedKey,
JsonConvert.SerializeObject(obj, _jsonSettings));
}
}
@@ -49,7 +49,7 @@ namespace Bit.App.Services
public Task RemoveAsync(string key)
{
var formattedKey = string.Format(_keyFormat, key);
- Microsoft.Maui.Storage.SecureStorage.Remove(formattedKey);
+ LegacySecureStorage.Remove(formattedKey);
return Task.FromResult(0);
}
}
diff --git a/src/Core/Utilities/AccountManagement/AccountsManager.cs b/src/Core/Utilities/AccountManagement/AccountsManager.cs
index 34a4df975..724471d56 100644
--- a/src/Core/Utilities/AccountManagement/AccountsManager.cs
+++ b/src/Core/Utilities/AccountManagement/AccountsManager.cs
@@ -60,7 +60,9 @@ namespace Bit.App.Utilities.AccountManagement
public async Task StartDefaultNavigationFlowAsync(Action appOptionsAction)
{
await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
-
+#if ANDROID
+ await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.AndroidWindowCreated);
+#endif
appOptionsAction(Options);
await NavigateOnAccountChangeAsync();
@@ -69,6 +71,9 @@ namespace Bit.App.Utilities.AccountManagement
public async Task NavigateOnAccountChangeAsync(bool? isAuthed = null)
{
await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.EnvironmentUrlsInited);
+#if ANDROID
+ await _conditionedAwaiterManager.GetAwaiterForPrecondition(AwaiterPrecondition.AndroidWindowCreated);
+#endif
// TODO: this could be improved by doing chain of responsability pattern
// but for now it may be an overkill, if logic gets more complex consider refactoring it