Skip to content

Commit 5dbaf37

Browse files
Android keychain
1 parent bc316b8 commit 5dbaf37

File tree

1 file changed

+138
-10
lines changed

1 file changed

+138
-10
lines changed

NomadCode.Azure/NomadCode.Azure/AzureClient/Keychain.cs

Lines changed: 138 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,20 @@
33
using System;
44

55
#if __IOS__
6+
67
using Foundation;
78
using Security;
9+
10+
#elif __ANDROID__
11+
12+
using System.Collections.Generic;
13+
14+
using Java.IO;
15+
using Java.Security;
16+
using Javax.Crypto;
17+
18+
using Android.Content;
19+
820
#endif
921

1022
namespace NomadCode.Azure
@@ -56,43 +68,159 @@ bool saveItemToKeychain (string service, string account, string privateKey)
5668

5769
var success = status == SecStatusCode.Success;
5870

59-
if (!success) Console.WriteLine ($"Error in Keychain: {status}");
71+
if (!success)
72+
{
73+
System.Diagnostics.Debug.WriteLine ($"Error in Keychain: {status}");
74+
System.Diagnostics.Debug.WriteLine ($"If you are seeing error code '-34018' got to Project Options -> iOS Bundle Signing -> make sure Entitlements.plist is populated for Custom Entitlements for iPhoneSimulator configs");
75+
}
6076

6177
return success;
6278
}
6379

6480

65-
void removeItemFromKeychain (string service)
81+
bool removeItemFromKeychain (string service)
6682
{
6783
var record = genericRecord (service);
6884

69-
SecKeyChain.Remove (record);
85+
var status = SecKeyChain.Remove (record);
86+
87+
var success = status == SecStatusCode.Success;
88+
89+
if (!success)
90+
{
91+
System.Diagnostics.Debug.WriteLine ($"Error in Keychain: {status}");
92+
System.Diagnostics.Debug.WriteLine ($"If you are seeing error code '-34018' got to Project Options -> iOS Bundle Signing -> make sure Entitlements.plist is populated for Custom Entitlements for iPhoneSimulator configs");
93+
}
94+
95+
return success;
7096
}
7197

7298
#else
7399

74-
// to implement, check out the links below:
75-
// https://github.com/sameerkapps/SecureStorage/blob/master/SecureStorage/Plugin.SecureStorage.Android/SecureStorageImplementation.cs
76-
// https://github.com/xamarin/Xamarin.Auth/blob/portable-bait-and-switch/source/Xamarin.Auth.XamarinAndroid/PlatformSpecific/AndroidAccountStore.cs
77-
// https://github.com/xamarin/Xamarin.Auth/blob/portable-bait-and-switch/source/Xamarin.Auth.XamarinAndroid/PlatformSpecific/AndroidAccountStore.Async.cs
100+
static Dictionary<string, KeyStore> keyStoresCache = new Dictionary<string, KeyStore> ();
101+
102+
103+
KeyStore getKeystore (string service)
104+
{
105+
var context = Android.App.Application.Context;
106+
107+
KeyStore keystore;
108+
109+
var serviceId = $"{context.PackageName}-{service}";
110+
111+
if (keyStoresCache.TryGetValue (serviceId, out keystore))
112+
{
113+
return keystore;
114+
}
115+
116+
var password = service.ToCharArray ();
117+
118+
keystore = KeyStore.GetInstance (KeyStore.DefaultType);
119+
120+
// var protection = new KeyStore.PasswordProtection (password);
121+
122+
try
123+
{
124+
// TODO: this isn't right, fix it
125+
using (var stream = context.OpenFileInput (serviceId))
126+
{
127+
keystore.Load (stream, password);
128+
}
129+
}
130+
catch (FileNotFoundException)
131+
{
132+
keystore.Load (null, password);
133+
}
134+
135+
keyStoresCache [serviceId] = keystore;
136+
137+
return keystore;
138+
}
78139

79140

80141
Tuple<string, string> getItemFromKeychain (string service)
81142
{
82-
throw new NotImplementedException ();
143+
var context = Android.App.Application.Context;
144+
145+
var password = service.ToCharArray ();
146+
147+
var protection = new KeyStore.PasswordProtection (password);
148+
149+
var keystore = getKeystore (service);
150+
151+
var aliases = keystore.Aliases ();
152+
153+
while (aliases.HasMoreElements)
154+
{
155+
var alias = aliases.NextElement ().ToString ();
156+
157+
var item = keystore.GetEntry (alias, protection) as KeyStore.SecretKeyEntry;
158+
159+
if (item != null)
160+
{
161+
var bytes = item.SecretKey.GetEncoded ();
162+
163+
var serialized = System.Text.Encoding.UTF8.GetString (bytes);
164+
165+
return new Tuple<string, string> (alias, serialized);
166+
}
167+
}
168+
169+
return null;
83170
}
84171

85172

86173
bool saveItemToKeychain (string service, string account, string privateKey)
87174
{
88-
throw new NotImplementedException ();
175+
var context = Android.App.Application.Context;
176+
177+
var password = service.ToCharArray ();
178+
179+
var serviceId = $"{context.PackageName}-{service}";
180+
181+
var keystore = getKeystore (service);
182+
183+
var item = new KeychainItem (privateKey);
184+
185+
var secretEntry = new KeyStore.SecretKeyEntry (item);
186+
187+
keystore.SetEntry (account, secretEntry, new KeyStore.PasswordProtection (password));
188+
189+
using (var stream = context.OpenFileOutput (serviceId, FileCreationMode.Private))
190+
{
191+
keystore.Store (stream, password);
192+
}
193+
194+
return true;
89195
}
90196

91197

92-
void removeItemFromKeychain (string service)
198+
bool removeItemFromKeychain (string service)
93199
{
94200
throw new NotImplementedException ();
95201
}
202+
203+
204+
class KeychainItem : Java.Lang.Object, ISecretKey
205+
{
206+
const string raw = "RAW";
207+
208+
byte [] bytes;
209+
210+
public KeychainItem (string data)
211+
{
212+
if (data == null) throw new ArgumentNullException ();
213+
214+
bytes = System.Text.Encoding.UTF8.GetBytes (data);
215+
}
216+
217+
public byte [] GetEncoded () => bytes;
218+
219+
public string Algorithm => raw;
220+
221+
public string Format => raw;
222+
}
223+
96224
#endif
97225
}
98226
}

0 commit comments

Comments
 (0)