Skip to content
This repository was archived by the owner on May 15, 2024. It is now read-only.

Commit 4da4ea3

Browse files
GH-380 Use Internal Preferences for Secure Storage consistency. (#386)
* Use Internal Preferences for Secure Storage consistency. Save if we created key pre-M so we always use pre-M if device upgrades. * Fix logic for pre-m key check The logic was slightly off, I think this fixes it, but would be good to have another set of eyes... 1. We check to see if the device is pre-M (if it does _not_ have `M`, or if we already set the fact it's pre-M in the preference - aka from a previous install before an upgrade of the OS) 2. If we aren't pre-M, we can't use Symmetric Key from Keystore 3. If we make it down to using Asymmetric Key, we set the pre-M preference to `true` to persist the value for future invocations, which will make it 'stick' in the event of a pre-M to M+ OS upgrade. * Address feedback on key naming. * Added test for secure storage to simulate upgrade From API < 23 to API >= 23 after storing data with an asymmetric key and then moving to a platform supporting symmetric keys. * Ensure we always set flags when using specified keygen
1 parent 64718b8 commit 4da4ea3

File tree

2 files changed

+61
-59
lines changed

2 files changed

+61
-59
lines changed

DeviceTests/DeviceTests.Shared/SecureStorage_Tests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,30 @@ public async Task Remove_All_Keys(bool emulatePreApi23, string[] keys)
8585
foreach (var key in keys)
8686
Assert.Null(await SecureStorage.GetAsync(key));
8787
}
88+
89+
#if __ANDROID__
90+
[Fact]
91+
public async Task Asymmetric_to_Symmetric_API_Upgrade()
92+
{
93+
var key = "asym_to_sym_upgrade";
94+
var expected = "this is the value";
95+
96+
SecureStorage.RemoveAll();
97+
98+
// Emulate pre api 23
99+
SecureStorage.AlwaysUseAsymmetricKeyStorage = true;
100+
101+
await SecureStorage.SetAsync(key, expected);
102+
103+
// Simulate Upgrading to API23+
104+
SecureStorage.AlwaysUseAsymmetricKeyStorage = false;
105+
106+
var v = await SecureStorage.GetAsync(key);
107+
108+
SecureStorage.RemoveAll();
109+
110+
Assert.Equal(expected, v);
111+
}
112+
#endif
88113
}
89114
}

Xamarin.Essentials/SecureStorage/SecureStorage.android.cs

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ static Task<string> PlatformGetAsync(string key)
1818
{
1919
var context = Platform.AppContext;
2020

21-
string encStr;
22-
using (var prefs = context.GetSharedPreferences(Alias, FileCreationMode.Private))
23-
encStr = prefs.GetString(Utils.Md5Hash(key), null);
21+
string defaultEncStr = null;
22+
var encStr = Preferences.Get(Utils.Md5Hash(key), defaultEncStr, Alias);
2423

2524
string decryptedData = null;
2625
if (!string.IsNullOrEmpty(encStr))
@@ -40,13 +39,8 @@ static Task PlatformSetAsync(string key, string data)
4039
var ks = new AndroidKeyStore(context, Alias, AlwaysUseAsymmetricKeyStorage);
4140
var encryptedData = ks.Encrypt(data);
4241

43-
using (var prefs = context.GetSharedPreferences(Alias, FileCreationMode.Private))
44-
using (var prefsEditor = prefs.Edit())
45-
{
46-
var encStr = Convert.ToBase64String(encryptedData);
47-
prefsEditor.PutString(Utils.Md5Hash(key), encStr);
48-
prefsEditor.Commit();
49-
}
42+
var encStr = Convert.ToBase64String(encryptedData);
43+
Preferences.Set(Utils.Md5Hash(key), encStr, Alias);
5044

5145
return Task.CompletedTask;
5246
}
@@ -56,36 +50,13 @@ static bool PlatformRemove(string key)
5650
var context = Platform.AppContext;
5751

5852
key = Utils.Md5Hash(key);
59-
60-
using (var prefs = context.GetSharedPreferences(Alias, FileCreationMode.Private))
61-
{
62-
if (prefs.Contains(key))
63-
{
64-
using (var prefsEditor = prefs.Edit())
65-
{
66-
prefsEditor.Remove(key);
67-
prefsEditor.Commit();
68-
return true;
69-
}
70-
}
71-
}
53+
Preferences.Remove(key, Alias);
7254

7355
return false;
7456
}
7557

76-
static void PlatformRemoveAll()
77-
{
78-
var context = Platform.AppContext;
79-
80-
using (var prefs = context.GetSharedPreferences(Alias, FileCreationMode.Private))
81-
using (var prefsEditor = prefs.Edit())
82-
{
83-
foreach (var key in prefs.All.Keys)
84-
prefsEditor.Remove(key);
85-
86-
prefsEditor.Commit();
87-
}
88-
}
58+
static void PlatformRemoveAll() =>
59+
Preferences.Clear(Alias);
8960

9061
internal static bool AlwaysUseAsymmetricKeyStorage { get; set; } = false;
9162
}
@@ -114,10 +85,18 @@ internal AndroidKeyStore(Context context, string keystoreAlias, bool alwaysUseAs
11485
KeyStore keyStore;
11586
bool alwaysUseAsymmetricKey;
11687

88+
bool useSymmetric = false;
89+
string useSymmetricPreferenceKey = "essentials_use_symmetric";
90+
11791
ISecretKey GetKey()
11892
{
93+
// check to see if we need to get our key from past-versions or newer versions.
94+
// we want to use symmetric if we are >= 23 or we didn't set it previously.
95+
96+
useSymmetric = Preferences.Get(useSymmetricPreferenceKey, Platform.HasApiLevel(BuildVersionCodes.M), SecureStorage.Alias);
97+
11998
// If >= API 23 we can use the KeyStore's symmetric key
120-
if (Platform.HasApiLevel(BuildVersionCodes.M) && !alwaysUseAsymmetricKey)
99+
if (useSymmetric && !alwaysUseAsymmetricKey)
121100
return GetSymmetricKey();
122101

123102
// NOTE: KeyStore in < API 23 can only store asymmetric keys
@@ -131,40 +110,35 @@ ISecretKey GetKey()
131110
// Get the asymmetric key pair
132111
var keyPair = GetAsymmetricKeyPair();
133112

134-
using (var prefs = appContext.GetSharedPreferences(alias, FileCreationMode.Private))
135-
{
136-
var existingKeyStr = prefs.GetString(prefsMasterKey, null);
113+
var existingKeyStr = Preferences.Get(prefsMasterKey, null, alias);
137114

138-
if (!string.IsNullOrEmpty(existingKeyStr))
139-
{
140-
var wrappedKey = Convert.FromBase64String(existingKeyStr);
115+
if (!string.IsNullOrEmpty(existingKeyStr))
116+
{
117+
var wrappedKey = Convert.FromBase64String(existingKeyStr);
141118

142-
var unwrappedKey = UnwrapKey(wrappedKey, keyPair.Private);
143-
var kp = unwrappedKey.JavaCast<ISecretKey>();
119+
var unwrappedKey = UnwrapKey(wrappedKey, keyPair.Private);
120+
var kp = unwrappedKey.JavaCast<ISecretKey>();
144121

145-
return kp;
146-
}
147-
else
148-
{
149-
var keyGenerator = KeyGenerator.GetInstance(aesAlgorithm);
150-
var defSymmetricKey = keyGenerator.GenerateKey();
122+
return kp;
123+
}
124+
else
125+
{
126+
var keyGenerator = KeyGenerator.GetInstance(aesAlgorithm);
127+
var defSymmetricKey = keyGenerator.GenerateKey();
151128

152-
var wrappedKey = WrapKey(defSymmetricKey, keyPair.Public);
129+
var wrappedKey = WrapKey(defSymmetricKey, keyPair.Public);
153130

154-
using (var prefsEditor = prefs.Edit())
155-
{
156-
prefsEditor.PutString(prefsMasterKey, Convert.ToBase64String(wrappedKey));
157-
prefsEditor.Commit();
158-
}
131+
Preferences.Set(prefsMasterKey, Convert.ToBase64String(wrappedKey), alias);
159132

160-
return defSymmetricKey;
161-
}
133+
return defSymmetricKey;
162134
}
163135
}
164136

165137
// API 23+ Only
166138
ISecretKey GetSymmetricKey()
167139
{
140+
Preferences.Set(useSymmetricPreferenceKey, true, SecureStorage.Alias);
141+
168142
var existingKey = keyStore.GetKey(alias, null);
169143

170144
if (existingKey != null)
@@ -186,6 +160,9 @@ ISecretKey GetSymmetricKey()
186160

187161
KeyPair GetAsymmetricKeyPair()
188162
{
163+
// set that we generated keys on pre-m device.
164+
Preferences.Set(useSymmetricPreferenceKey, false, SecureStorage.Alias);
165+
189166
var asymmetricAlias = $"{alias}.asymmetric";
190167

191168
var privateKey = keyStore.GetKey(asymmetricAlias, null)?.JavaCast<IPrivateKey>();

0 commit comments

Comments
 (0)