Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,26 @@ Blog:
https://sameerkapps.wordpress.com/2016/02/01/secure-storage-plugin-for-xamarin/

# Breaking Changes in 2.5.0
* iOS - The KeyChain access level is now set at AfterFirstUnlock as default. This will let app set the values
* iOS
1. The KeyChain access level is now set at AfterFirstUnlock as default. This will let app set the values
in the background mode.
As a result, the keys stored using the earlier versions will not be accessible with this version. To maintain backward compatibility,
add a line to AppDelegate as follows:
```
SecureStorageImplementation.DefaultAccessible = Security.SecAccessible.Invalid;
````

2. Keychain entries can be synchronized between devices with the same iCloud user logged in (such as iPad, iPhone, and iPod). Keychain entries created before enabling this feature will not automatically synchronize with iCloud, so this feature is disabled by default (take a look at `KeychainMigrator` class for help migrating existing keys to be synchronized). This feature can be enabled by setting a property on the instance of secure storage:
```
((SecureStorageImplementation)CrossSecureStorage.Current).UseCloud = true

//or if using Dependency Injection, e.g., in an autofac module:

builder.RegisterInstance(new KeychainStorage() { UseCloud = true; })
.AsImplementedInterfaces()
.SingleInstance();
```

* Android - This version provides two storage types:
1. Android Key Store - This is now the default mechanism for storage. Android recommends using it as it prevents other apps from using the file.
ref: https://developer.android.com/training/articles/keystore#WhichShouldIUse
Expand Down
97 changes: 97 additions & 0 deletions SecureStorage/Plugin.SecureStorage.iOSUnified/KeychainMigrator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
////////////////////////////////////////////////////////
// Copyright (c) 2018 Ryan Rounkles //
// License: MIT License. //
////////////////////////////////////////////////////////
using System;
using Plugin.SecureStorage.Abstractions;

namespace Plugin.SecureStorage
{
/// <summary>
/// Helper class to facilitate migrating a mac/iOS keychain entry from local-only
/// storage to icloud (synchronized) storage.
/// </summary>
public static class KeychainMigrator
{
/// <summary>
/// Migrates the given key to cloud storage.
/// </summary>
/// <returns><c>true</c>, if key was successfully changed to be synchronized <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param>
public static bool MigrateToCloudStorage(string key)
{
if (null == key)
{
throw new ArgumentNullException(nameof(key));
}

var cloud = new SecureStorageImplementation
{
UseCloud = true
};
var local = new SecureStorageImplementation
{
UseCloud = false
};

return Migrate(local, cloud, key);
}

/// <summary>
/// Migrates all the given keys to be synchronized with users iCloud account.
/// </summary>
/// <returns><c>true</c>, if all keys migrated to cloud storage successfully, <c>false</c> otherwise.</returns>
/// <param name="keys">Keys that already exist in keychain</param>
public static bool MigrateAllToCloudStorage(params string[] keys)
{
if(null == keys)
{
throw new ArgumentNullException(nameof(keys));
}

var cloud = new SecureStorageImplementation
{
UseCloud = true
};
var local = new SecureStorageImplementation
{
UseCloud = false
};

var allGood = true;

foreach (var key in keys)
{
if(!Migrate(local, cloud, key))
{
allGood = false;
}
}

return allGood;
}

/// <summary>
/// Migrate a given key, if it exists, to be synchronized.
/// </summary>
/// <returns>The migrate.</returns>
/// <param name="source">Source.</param>
/// <param name="destination">Destination.</param>
/// <param name="key">Key.</param>
static bool Migrate(ISecureStorage source,
ISecureStorage destination,
string key)
{
if(!source.HasKey(key))
{
return false;
}
var value = source.GetValue(key);
if(!source.DeleteKey(key))
{
return false;
}
return destination.SetValue(key, value);
}
}
}
1 change: 1 addition & 0 deletions SecureStorage/Plugin.SecureStorage.iOSUnified/Plugin.SecureStorage.iOSUnified.csproj
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<Link>CrossSecureStorage.cs</Link>
</Compile>
<Compile Include="SecureStorageImplementation.cs" />
<Compile Include="KeychainMigrator.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Plugin.SecureStorage.Abstractions\Plugin.SecureStorage.Abstractions.csproj">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ public class SecureStorageImplementation : SecureStorageImplementationBase
/// </summary>
public static SecAccessible DefaultAccessible = SecAccessible.AfterFirstUnlock;

/// <summary>
/// Enabled will store keychain entries in iCloud (if enabled on the device).
/// Default value is false, change to true to synchronize keys
/// </summary>
/// <remarks>
/// Entries are not automatically migrated between local and cloud storage when
/// this is changed. So if you stored entries with this set to false, when
/// you change it to true the entries will not exist in the keychain, because
/// they will have been created as local only.
/// </remarks>
public bool UseCloud { get; set; } = false;

#endregion
#region ISecureStorage implementation
/// <summary>
Expand Down Expand Up @@ -119,6 +131,11 @@ private SecRecord GetRecord(string key, out SecStatusCode ssc)
if (DefaultAccessible != SecAccessible.Invalid)
{
sr.Accessible = DefaultAccessible;
}

if (UseCloud)
{
sr.Synchronizable = true;
}

return SecKeyChain.QueryAsRecord(sr, out ssc);
Expand All @@ -144,6 +161,11 @@ private SecStatusCode RemoveRecord(string key)
if (DefaultAccessible != SecAccessible.Invalid)
{
sr.Accessible = DefaultAccessible;
}

if (UseCloud)
{
sr.Synchronizable = true;
}
return SecKeyChain.Remove(sr);
}
Expand Down