Last active
February 19, 2018 16:23
-
-
Save Redth/7cb75bb1d275dffeeaa7b66cab33cb68 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.IO; | |
| using System.Collections.Generic; | |
| using System.Collections.ObjectModel; | |
| using System.ComponentModel; | |
| using System.Linq; | |
| using System.Threading.Tasks; | |
| #if __ANDROID__ | |
| using Java.Security; | |
| using Javax.Crypto; | |
| using Android.Content; | |
| #elif __IOS__ | |
| using Security; | |
| using Foundation; | |
| #endif | |
| namespace SecureStore | |
| { | |
| public class SecureStore | |
| { | |
| const string SSFILENAME = "XamarinSecureStorage"; | |
| public static string Load (string filename) | |
| { | |
| #if __ANDROID__ | |
| var keyStore = LoadKeyStore (); | |
| var entry = keyStore.Item1.GetEntry (filename, keyStore.Item2) as KeyStore.SecretKeyEntry; | |
| if (entry != null) { | |
| var bytes = entry.SecretKey.GetEncoded (); | |
| return System.Text.Encoding.UTF8.GetString (bytes); | |
| } | |
| #elif __IOS__ | |
| var kc = new KeyChain (); | |
| return kc.ValueForKey (filename); | |
| #else | |
| try { | |
| if (File.Exists (filename)) | |
| return File.ReadAllText (filename); | |
| } catch { } | |
| #endif | |
| return null; | |
| } | |
| public static void Save (string data, string filename) | |
| { | |
| #if __ANDROID__ | |
| var ks = LoadKeyStore (); | |
| ks.Item1.SetEntry (filename, new KeyStore.SecretKeyEntry (new SecretEntry (data)), ks.Item2); | |
| SaveKeyStore (ks.Item1); | |
| #elif __IOS__ | |
| var kc = new KeyChain (); | |
| kc.SetValueForKey (data, filename); | |
| #else | |
| File.WriteAllText (filename, data); | |
| #endif | |
| } | |
| #if __ANDROID__ | |
| static readonly object fileLock = new object (); | |
| static Tuple<KeyStore, KeyStore.PasswordProtection> LoadKeyStore () | |
| { | |
| var context = Xamarin.Forms.Forms.Context; | |
| var secureKey = GetSecureKey (context); | |
| var keyStore = KeyStore.GetInstance (KeyStore.DefaultType); | |
| var prot = new KeyStore.PasswordProtection (secureKey); | |
| try { | |
| lock (fileLock) { | |
| if (context.GetFileStreamPath (SSFILENAME)?.Exists () ?? false) { | |
| using (var s = context.OpenFileInput (SSFILENAME)) | |
| keyStore.Load (s, secureKey); | |
| } else { | |
| keyStore.Load (null, secureKey); | |
| } | |
| } | |
| } catch { | |
| keyStore.Load (null, secureKey); | |
| } | |
| return Tuple.Create (keyStore, prot); | |
| } | |
| static void SaveKeyStore (KeyStore keyStore) | |
| { | |
| var context = Xamarin.Forms.Forms.Context; | |
| lock (fileLock) { | |
| using (var s = context.OpenFileOutput (SSFILENAME, FileCreationMode.Private)) { | |
| keyStore.Store (s, GetSecureKey (context)); | |
| s.Flush (); | |
| s.Close (); | |
| } | |
| } | |
| } | |
| static char [] GetSecureKey (Context context) | |
| { | |
| const string CACHEKEY_KEY = "XamarinSecureStorageCacheKey"; | |
| var cacheKey = string.Empty; | |
| var prefs = context.GetSharedPreferences (SSFILENAME, FileCreationMode.Private); | |
| if (prefs.Contains (CACHEKEY_KEY)) { | |
| cacheKey = prefs.GetString (CACHEKEY_KEY, string.Empty); | |
| if (!string.IsNullOrEmpty (cacheKey)) | |
| return cacheKey.ToCharArray (); | |
| } | |
| // Generate a 256-bit key | |
| const int outputKeyLength = 256; | |
| var secureRandom = new SecureRandom (); | |
| // Do *not* seed secureRandom! Automatically seeded from system entropy. | |
| var keyGenerator = KeyGenerator.GetInstance ("AES"); | |
| keyGenerator.Init (outputKeyLength, secureRandom); | |
| var key = keyGenerator.GenerateKey (); | |
| cacheKey = Convert.ToBase64String (key.GetEncoded ()); | |
| prefs.Edit () | |
| .PutString (CACHEKEY_KEY, cacheKey) | |
| .Commit (); | |
| return cacheKey.ToCharArray (); | |
| } | |
| class SecretEntry : Java.Lang.Object, ISecretKey | |
| { | |
| byte [] bytes; | |
| public SecretEntry (string value) | |
| { | |
| bytes = System.Text.Encoding.UTF8.GetBytes (value); | |
| } | |
| public string Algorithm { get { return "RAW"; } } | |
| public string Format { get { return "RAW"; } } | |
| public byte [] GetEncoded () | |
| { | |
| return bytes; | |
| } | |
| } | |
| #endif | |
| #if __IOS__ | |
| public class KeyChain | |
| { | |
| public string ValueForKey(string key) | |
| { | |
| var record = ExistingRecordForKey (key); | |
| SecStatusCode resultCode; | |
| var match = SecKeyChain.QueryAsRecord(record, out resultCode); | |
| if (resultCode == SecStatusCode.Success) | |
| return NSString.FromData (match.ValueData, NSStringEncoding.UTF8); | |
| else | |
| return String.Empty; | |
| } | |
| public void SetValueForKey(string value, string key) | |
| { | |
| var record = ExistingRecordForKey (key); | |
| if (string.IsNullOrEmpty (value)) | |
| { | |
| if (!string.IsNullOrEmpty (ValueForKey(key))) | |
| RemoveRecord(record); | |
| return; | |
| } | |
| // if the key already exists, remove it | |
| if (!string.IsNullOrEmpty (ValueForKey(key))) | |
| RemoveRecord(record); | |
| var result = SecKeyChain.Add(CreateRecordForNewKeyValue(key, value)); | |
| if (result != SecStatusCode.Success) | |
| { | |
| throw new Exception(String.Format("Error adding record: {0}", result)); | |
| } | |
| } | |
| private SecRecord CreateRecordForNewKeyValue(string key, string value) | |
| { | |
| return new SecRecord(SecKind.GenericPassword) | |
| { | |
| Account = key, | |
| Service = SSFILENAME, | |
| Label = key, | |
| ValueData = NSData.FromString(value, NSStringEncoding.UTF8), | |
| }; | |
| } | |
| private SecRecord ExistingRecordForKey(string key) | |
| { | |
| return new SecRecord(SecKind.GenericPassword) | |
| { | |
| Account = key, | |
| Service = SSFILENAME, | |
| Label = key, | |
| }; | |
| } | |
| private bool RemoveRecord(SecRecord record) | |
| { | |
| var result = SecKeyChain.Remove(record); | |
| if (result != SecStatusCode.Success) | |
| { | |
| throw new Exception(String.Format("Error removing record: {0}", result)); | |
| } | |
| return true; | |
| } | |
| } | |
| #endif | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment