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:
Jonathan Dick 2018-06-28 11:29:00 -04:00 коммит произвёл GitHub
Родитель 7e3e6a7154
Коммит 26c14c78f7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 234 добавлений и 28 удалений

Просмотреть файл

@ -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&lt;string&gt; GetAsync (string key, Security.SecAccessible accessible);" />
<MemberSignature Language="ILAsm" Value=".method public static hidebysig class System.Threading.Tasks.Task`1&lt;string&gt; 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&lt;System.String&gt;</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>