GH-304: Added `Remove` and `RemoveAll` methods to SecureStorage (#330)
* [secure-storage] Added `Remove` and `RemoveAll` * [secure-storage] Added tests for Remove/RemoveAll * [secure-storage] Remove unnecessary method We no longer consider the `SecAccessible` value when _retrieving_ KeyChain records anymore, so it’s unnecessary to pass this value into the Get call, and unnecessary to have a platform specific overload with this parameter for getting a record. * [secure-storage] updated docs * [secure-storage] update sample with remove methods * [secure-storage] Fix GetAsync call for iOS We don’t use the overload with SecAccessible any longer. * Fix android remove, we must do a md5hash of the key as that is how it was stored :) * [secure-storage] Fix iOS PlatformGet
This commit is contained in:
Родитель
7e3e6a7154
Коммит
26c14c78f7
|
@ -19,7 +19,7 @@ namespace DeviceTests
|
|||
// Try the new platform specific api
|
||||
await SecureStorage.SetAsync(key, data, Security.SecAccessible.AfterFirstUnlock);
|
||||
|
||||
var b = await SecureStorage.GetAsync(key, Security.SecAccessible.AfterFirstUnlock);
|
||||
var b = await SecureStorage.GetAsync(key);
|
||||
|
||||
Assert.Equal(data, b);
|
||||
#endif
|
||||
|
@ -47,5 +47,43 @@ namespace DeviceTests
|
|||
|
||||
Assert.Null(v);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("KEY_TO_REMOVE1", true)]
|
||||
[InlineData("KEY_TO_REMOVE2", false)]
|
||||
public async Task Remove_Key(string key, bool emulatePreApi23)
|
||||
{
|
||||
#if __ANDROID__
|
||||
SecureStorage.AlwaysUseAsymmetricKeyStorage = emulatePreApi23;
|
||||
#endif
|
||||
await SecureStorage.SetAsync(key, "Irrelevant Data");
|
||||
|
||||
SecureStorage.Remove(key);
|
||||
|
||||
var v = await SecureStorage.GetAsync(key);
|
||||
|
||||
Assert.Null(v);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, new[] { "KEYS_TO_REMOVEA1", "KEYS_TO_REMOVEA2" })]
|
||||
[InlineData(false, new[] { "KEYS_TO_REMOVEB1", "KEYS_TO_REMOVEB2" })]
|
||||
public async Task Remove_All_Keys(bool emulatePreApi23, string[] keys)
|
||||
{
|
||||
#if __ANDROID__
|
||||
SecureStorage.AlwaysUseAsymmetricKeyStorage = emulatePreApi23;
|
||||
#endif
|
||||
|
||||
// Set a couple keys
|
||||
foreach (var key in keys)
|
||||
await SecureStorage.SetAsync(key, "Irrelevant Data");
|
||||
|
||||
// Remove them all
|
||||
SecureStorage.RemoveAll();
|
||||
|
||||
// Make sure they are all removed
|
||||
foreach (var key in keys)
|
||||
Assert.Null(await SecureStorage.GetAsync(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
|
||||
<Button Text="Load" Command="{Binding LoadCommand}" IsEnabled="{Binding IsNotBusy}" />
|
||||
<Button Text="Save" Command="{Binding SaveCommand}" IsEnabled="{Binding IsNotBusy}" />
|
||||
<Button Text="Remove" Command="{Binding RemoveCommand}" IsEnabled="{Binding IsNotBusy}" />
|
||||
|
||||
<Button Text="Remove All" Command="{Binding RemoveAllCommand}" IsEnabled="{Binding IsNotBusy}" Margin="0,10,0,0" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</StackLayout>
|
||||
|
|
|
@ -14,6 +14,8 @@ namespace Samples.ViewModel
|
|||
{
|
||||
LoadCommand = new Command(OnLoad);
|
||||
SaveCommand = new Command(OnSave);
|
||||
RemoveCommand = new Command(OnRemove);
|
||||
RemoveAllCommand = new Command(OnRemoveAll);
|
||||
}
|
||||
|
||||
public string Key
|
||||
|
@ -32,6 +34,10 @@ namespace Samples.ViewModel
|
|||
|
||||
public ICommand SaveCommand { get; }
|
||||
|
||||
public ICommand RemoveCommand { get; }
|
||||
|
||||
public ICommand RemoveAllCommand { get; }
|
||||
|
||||
async void OnLoad()
|
||||
{
|
||||
if (IsBusy)
|
||||
|
@ -66,5 +72,39 @@ namespace Samples.ViewModel
|
|||
}
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
async void OnRemove()
|
||||
{
|
||||
if (IsBusy)
|
||||
return;
|
||||
IsBusy = true;
|
||||
|
||||
try
|
||||
{
|
||||
SecureStorage.Remove(Key);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DisplayAlertAsync(ex.Message);
|
||||
}
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
async void OnRemoveAll()
|
||||
{
|
||||
if (IsBusy)
|
||||
return;
|
||||
IsBusy = true;
|
||||
|
||||
try
|
||||
{
|
||||
SecureStorage.RemoveAll();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await DisplayAlertAsync(ex.Message);
|
||||
}
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,42 @@ namespace Xamarin.Essentials
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
static bool PlatformRemove(string key)
|
||||
{
|
||||
var context = Platform.AppContext;
|
||||
|
||||
key = Utils.Md5Hash(key);
|
||||
|
||||
using (var prefs = context.GetSharedPreferences(Alias, FileCreationMode.Private))
|
||||
{
|
||||
if (prefs.Contains(key))
|
||||
{
|
||||
using (var prefsEditor = prefs.Edit())
|
||||
{
|
||||
prefsEditor.Remove(key);
|
||||
prefsEditor.Commit();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void PlatformRemoveAll()
|
||||
{
|
||||
var context = Platform.AppContext;
|
||||
|
||||
using (var prefs = context.GetSharedPreferences(Alias, FileCreationMode.Private))
|
||||
using (var prefsEditor = prefs.Edit())
|
||||
{
|
||||
foreach (var key in prefs.All.Keys)
|
||||
prefsEditor.Remove(key);
|
||||
|
||||
prefsEditor.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool AlwaysUseAsymmetricKeyStorage { get; set; } = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,16 +11,6 @@ namespace Xamarin.Essentials
|
|||
public static SecAccessible DefaultAccessible { get; set; } =
|
||||
SecAccessible.AfterFirstUnlock;
|
||||
|
||||
public static Task<string> GetAsync(string key, SecAccessible accessible)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
|
||||
var kc = new KeyChain(accessible);
|
||||
|
||||
return Task.FromResult(kc.ValueForKey(key, Alias));
|
||||
}
|
||||
|
||||
public static Task SetAsync(string key, string value, SecAccessible accessible)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
|
@ -35,11 +25,33 @@ namespace Xamarin.Essentials
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
static Task<string> PlatformGetAsync(string key) =>
|
||||
GetAsync(key, DefaultAccessible);
|
||||
static Task<string> PlatformGetAsync(string key)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
|
||||
var kc = new KeyChain(DefaultAccessible);
|
||||
var value = kc.ValueForKey(key, Alias);
|
||||
|
||||
return Task.FromResult(value);
|
||||
}
|
||||
|
||||
static Task PlatformSetAsync(string key, string data) =>
|
||||
SetAsync(key, data, DefaultAccessible);
|
||||
|
||||
static bool PlatformRemove(string key)
|
||||
{
|
||||
var kc = new KeyChain(DefaultAccessible);
|
||||
|
||||
return kc.Remove(key, Alias);
|
||||
}
|
||||
|
||||
static void PlatformRemoveAll()
|
||||
{
|
||||
var kc = new KeyChain(DefaultAccessible);
|
||||
|
||||
kc.RemoveAll(Alias);
|
||||
}
|
||||
}
|
||||
|
||||
class KeyChain
|
||||
|
@ -89,6 +101,30 @@ namespace Xamarin.Essentials
|
|||
throw new Exception($"Error adding record: {result}");
|
||||
}
|
||||
|
||||
internal bool Remove(string key, string service)
|
||||
{
|
||||
var record = ExistingRecordForKey(key, service);
|
||||
var match = SecKeyChain.QueryAsRecord(record, out var resultCode);
|
||||
|
||||
if (resultCode == SecStatusCode.Success)
|
||||
{
|
||||
RemoveRecord(record);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void RemoveAll(string service)
|
||||
{
|
||||
var query = new SecRecord(SecKind.GenericPassword)
|
||||
{
|
||||
Service = service
|
||||
};
|
||||
|
||||
SecKeyChain.Remove(query);
|
||||
}
|
||||
|
||||
SecRecord CreateRecordForNewKeyValue(string key, string value, string service)
|
||||
{
|
||||
return new SecRecord(SecKind.GenericPassword)
|
||||
|
|
|
@ -9,5 +9,11 @@ namespace Xamarin.Essentials
|
|||
|
||||
static Task PlatformSetAsync(string key, string data) =>
|
||||
throw new NotImplementedInReferenceAssemblyException();
|
||||
|
||||
static bool PlatformRemove(string key) =>
|
||||
throw new NotImplementedInReferenceAssemblyException();
|
||||
|
||||
static void PlatformRemoveAll() =>
|
||||
throw new NotImplementedInReferenceAssemblyException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,5 +25,11 @@ namespace Xamarin.Essentials
|
|||
|
||||
return PlatformSetAsync(key, value);
|
||||
}
|
||||
|
||||
public static bool Remove(string key)
|
||||
=> PlatformRemove(key);
|
||||
|
||||
public static void RemoveAll()
|
||||
=> PlatformRemoveAll();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,26 @@ namespace Xamarin.Essentials
|
|||
settings.Values[key] = encBytes;
|
||||
}
|
||||
|
||||
static bool PlatformRemove(string key)
|
||||
{
|
||||
var settings = GetSettings(Alias);
|
||||
|
||||
if (settings.Values.ContainsKey(key))
|
||||
{
|
||||
settings.Values.Remove(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void PlatformRemoveAll()
|
||||
{
|
||||
var settings = GetSettings(Alias);
|
||||
|
||||
settings.Values.Clear();
|
||||
}
|
||||
|
||||
static ApplicationDataContainer GetSettings(string name)
|
||||
{
|
||||
var localSettings = ApplicationData.Current.LocalSettings;
|
||||
|
|
|
@ -375,6 +375,8 @@
|
|||
</Type>
|
||||
<Type Name="Xamarin.Essentials.SecureStorage" Id="T:Xamarin.Essentials.SecureStorage">
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.GetAsync(System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.Remove(System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.RemoveAll" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.SetAsync(System.String,System.String)" />
|
||||
</Type>
|
||||
<Type Name="Xamarin.Essentials.SensorSpeed" Id="T:Xamarin.Essentials.SensorSpeed">
|
||||
|
|
|
@ -372,7 +372,8 @@
|
|||
</Type>
|
||||
<Type Name="Xamarin.Essentials.SecureStorage" Id="T:Xamarin.Essentials.SecureStorage">
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.GetAsync(System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.GetAsync(System.String,Security.SecAccessible)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.Remove(System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.RemoveAll" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.SetAsync(System.String,System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.SetAsync(System.String,System.String,Security.SecAccessible)" />
|
||||
<Member Id="P:Xamarin.Essentials.SecureStorage.DefaultAccessible" />
|
||||
|
|
|
@ -371,6 +371,8 @@
|
|||
</Type>
|
||||
<Type Name="Xamarin.Essentials.SecureStorage" Id="T:Xamarin.Essentials.SecureStorage">
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.GetAsync(System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.Remove(System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.RemoveAll" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.SetAsync(System.String,System.String)" />
|
||||
</Type>
|
||||
<Type Name="Xamarin.Essentials.SensorSpeed" Id="T:Xamarin.Essentials.SensorSpeed">
|
||||
|
|
|
@ -371,6 +371,8 @@
|
|||
</Type>
|
||||
<Type Name="Xamarin.Essentials.SecureStorage" Id="T:Xamarin.Essentials.SecureStorage">
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.GetAsync(System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.Remove(System.String)" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.RemoveAll" />
|
||||
<Member Id="M:Xamarin.Essentials.SecureStorage.SetAsync(System.String,System.String)" />
|
||||
</Type>
|
||||
<Type Name="Xamarin.Essentials.SensorSpeed" Id="T:Xamarin.Essentials.SensorSpeed">
|
||||
|
|
|
@ -71,30 +71,44 @@
|
|||
</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="GetAsync">
|
||||
<MemberSignature Language="C#" Value="public static System.Threading.Tasks.Task<string> GetAsync (string key, Security.SecAccessible accessible);" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public static hidebysig class System.Threading.Tasks.Task`1<string> GetAsync(string key, valuetype Security.SecAccessible accessible) cil managed" />
|
||||
<MemberSignature Language="DocId" Value="M:Xamarin.Essentials.SecureStorage.GetAsync(System.String,Security.SecAccessible)" />
|
||||
<Member MemberName="Remove">
|
||||
<MemberSignature Language="C#" Value="public static bool Remove (string key);" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public static hidebysig bool Remove(string key) cil managed" />
|
||||
<MemberSignature Language="DocId" Value="M:Xamarin.Essentials.SecureStorage.Remove(System.String)" />
|
||||
<MemberType>Method</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyName>Xamarin.Essentials</AssemblyName>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Threading.Tasks.Task<System.String></ReturnType>
|
||||
<ReturnType>System.Boolean</ReturnType>
|
||||
</ReturnValue>
|
||||
<Parameters>
|
||||
<Parameter Name="key" Type="System.String" />
|
||||
<Parameter Name="accessible" Type="Security.SecAccessible" />
|
||||
</Parameters>
|
||||
<Docs>
|
||||
<param name="key">Storage Key.</param>
|
||||
<param name="accessible">SecAccessible for iOS to use</param>
|
||||
<summary>Gets the decrypted value for a given Key. iOS override to specify SecAccessible for the KeyChain.</summary>
|
||||
<returns>
|
||||
<para></para>
|
||||
</returns>
|
||||
<remarks>iOS: Default SecAccessible to use for all Get/Set calls to KeyChain. Default value is AfterFirstUnlock.</remarks>
|
||||
<param name="key">The key to remove.</param>
|
||||
<summary>Removes the encrypted key/value pair for the given key.</summary>
|
||||
<returns>Returns true if the key value pair was removed.</returns>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="RemoveAll">
|
||||
<MemberSignature Language="C#" Value="public static void RemoveAll ();" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public static hidebysig void RemoveAll() cil managed" />
|
||||
<MemberSignature Language="DocId" Value="M:Xamarin.Essentials.SecureStorage.RemoveAll" />
|
||||
<MemberType>Method</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyName>Xamarin.Essentials</AssemblyName>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Void</ReturnType>
|
||||
</ReturnValue>
|
||||
<Parameters />
|
||||
<Docs>
|
||||
<summary>Removes all of the stored encrypted key/value pairs.</summary>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="SetAsync">
|
||||
|
@ -145,7 +159,7 @@
|
|||
<Docs>
|
||||
<param name="key">Storage Key.</param>
|
||||
<param name="value">The value to be encrypted.</param>
|
||||
<param name="accessible">To be added.</param>
|
||||
<param name="accessible">The KeyChain accessibility to create the encrypted record with.</param>
|
||||
<summary>Stores the value which is encrypted, for a given Key. iOS override to specify SecAccessible for the KeyChain.</summary>
|
||||
<returns>
|
||||
<para></para>
|
||||
|
|
Загрузка…
Ссылка в новой задаче