diff --git a/README.md b/README.md index 90dfcd5..c9e9143 100755 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ 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: @@ -64,6 +65,17 @@ 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 diff --git a/SecureStorage/Plugin.SecureStorage.iOSUnified/KeychainMigrator.cs b/SecureStorage/Plugin.SecureStorage.iOSUnified/KeychainMigrator.cs new file mode 100644 index 0000000..6960524 --- /dev/null +++ b/SecureStorage/Plugin.SecureStorage.iOSUnified/KeychainMigrator.cs @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////// +// Copyright (c) 2018 Ryan Rounkles // +// License: MIT License. // +//////////////////////////////////////////////////////// +using System; +using Plugin.SecureStorage.Abstractions; + +namespace Plugin.SecureStorage +{ + /// + /// Helper class to facilitate migrating a mac/iOS keychain entry from local-only + /// storage to icloud (synchronized) storage. + /// + public static class KeychainMigrator + { + /// + /// Migrates the given key to cloud storage. + /// + /// true, if key was successfully changed to be synchronized false otherwise. + /// Key. + 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); + } + + /// + /// Migrates all the given keys to be synchronized with users iCloud account. + /// + /// true, if all keys migrated to cloud storage successfully, false otherwise. + /// Keys that already exist in keychain + 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; + } + + /// + /// Migrate a given key, if it exists, to be synchronized. + /// + /// The migrate. + /// Source. + /// Destination. + /// Key. + 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); + } + } +} diff --git a/SecureStorage/Plugin.SecureStorage.iOSUnified/Plugin.SecureStorage.iOSUnified.csproj b/SecureStorage/Plugin.SecureStorage.iOSUnified/Plugin.SecureStorage.iOSUnified.csproj old mode 100755 new mode 100644 index 055d98f..9c241fd --- a/SecureStorage/Plugin.SecureStorage.iOSUnified/Plugin.SecureStorage.iOSUnified.csproj +++ b/SecureStorage/Plugin.SecureStorage.iOSUnified/Plugin.SecureStorage.iOSUnified.csproj @@ -42,6 +42,7 @@ CrossSecureStorage.cs + diff --git a/SecureStorage/Plugin.SecureStorage.iOSUnified/SecureStorageImplementation.cs b/SecureStorage/Plugin.SecureStorage.iOSUnified/SecureStorageImplementation.cs index d56fbf5..d58e6fa 100755 --- a/SecureStorage/Plugin.SecureStorage.iOSUnified/SecureStorageImplementation.cs +++ b/SecureStorage/Plugin.SecureStorage.iOSUnified/SecureStorageImplementation.cs @@ -23,6 +23,18 @@ public class SecureStorageImplementation : SecureStorageImplementationBase /// public static SecAccessible DefaultAccessible = SecAccessible.AfterFirstUnlock; + /// + /// Enabled will store keychain entries in iCloud (if enabled on the device). + /// Default value is false, change to true to synchronize keys + /// + /// + /// 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. + /// + public bool UseCloud { get; set; } = false; + #endregion #region ISecureStorage implementation /// @@ -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); @@ -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); }