key chain storage service

This commit is contained in:
Kyle Spearrin 2019-04-08 21:23:16 -04:00
parent 8055de4f25
commit 8c6823c463
4 changed files with 132 additions and 7 deletions

View file

@ -8,8 +8,7 @@ namespace Bit.Core.Services
{ {
public class PreferencesStorageService : IStorageService public class PreferencesStorageService : IStorageService
{ {
private string _keyFormat = "bwPreferencesStorage:{0}"; private readonly string _keyFormat = "bwPreferencesStorage:{0}";
private readonly string _sharedName; private readonly string _sharedName;
private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{ {

View file

@ -7,7 +7,7 @@ namespace Bit.Core.Services
{ {
public class SecureStorageService : IStorageService public class SecureStorageService : IStorageService
{ {
private string _keyFormat = "bwSecureStorage:{0}"; private readonly string _keyFormat = "bwSecureStorage:{0}";
private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{ {
ContractResolver = new CamelCasePropertyNamesContractResolver() ContractResolver = new CamelCasePropertyNamesContractResolver()
@ -17,8 +17,7 @@ namespace Bit.Core.Services
{ {
var formattedKey = string.Format(_keyFormat, key); var formattedKey = string.Format(_keyFormat, key);
var val = await Xamarin.Essentials.SecureStorage.GetAsync(formattedKey); var val = await Xamarin.Essentials.SecureStorage.GetAsync(formattedKey);
var objType = typeof(T); if(typeof(T) == typeof(string))
if(objType == typeof(string))
{ {
return (T)(object)val; return (T)(object)val;
} }
@ -36,8 +35,7 @@ namespace Bit.Core.Services
return; return;
} }
var formattedKey = string.Format(_keyFormat, key); var formattedKey = string.Format(_keyFormat, key);
var objType = typeof(T); if(typeof(T) == typeof(string))
if(objType == typeof(string))
{ {
await Xamarin.Essentials.SecureStorage.SetAsync(formattedKey, obj as string); await Xamarin.Essentials.SecureStorage.SetAsync(formattedKey, obj as string);
} }

View file

@ -0,0 +1,127 @@
using System;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Foundation;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Security;
namespace Bit.iOS.Services
{
public class KeyChainStorageService : IStorageService
{
private readonly string _keyFormat = "bwKeyChainStorage:{0}";
private readonly string _service;
private readonly string _group;
private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
public KeyChainStorageService(string service, string group)
{
_service = service;
_group = group;
}
public Task<T> GetAsync<T>(string key)
{
var formattedKey = string.Format(_keyFormat, key);
byte[] dataBytes = null;
using(var existingRecord = GetKeyRecord(formattedKey))
using(var record = SecKeyChain.QueryAsRecord(existingRecord, out SecStatusCode resultCode))
{
if(resultCode == SecStatusCode.ItemNotFound)
{
return Task.FromResult((T)(object)null);
}
CheckError(resultCode);
dataBytes = record.Generic.ToArray();
}
var dataString = Encoding.UTF8.GetString(dataBytes);
if(typeof(T) == typeof(string))
{
return Task.FromResult((T)(object)dataString);
}
else
{
return Task.FromResult(JsonConvert.DeserializeObject<T>(dataString, _jsonSettings));
}
}
public async Task SaveAsync<T>(string key, T obj)
{
if(obj == null)
{
await RemoveAsync(key);
return;
}
string dataString = null;
if(typeof(T) == typeof(string))
{
dataString = obj as string;
}
else
{
dataString = JsonConvert.SerializeObject(obj, _jsonSettings);
}
var formattedKey = string.Format(_keyFormat, key);
var dataBytes = Encoding.UTF8.GetBytes(dataString);
using(var data = NSData.FromArray(dataBytes))
using(var newRecord = GetKeyRecord(formattedKey, data))
{
await RemoveAsync(formattedKey);
CheckError(SecKeyChain.Add(newRecord));
}
}
public Task RemoveAsync(string key)
{
var formattedKey = string.Format(_keyFormat, key);
using(var record = GetExistingRecord(formattedKey))
{
if(record != null)
{
CheckError(SecKeyChain.Remove(record));
}
}
return Task.FromResult(0);
}
private SecRecord GetKeyRecord(string key, NSData data = null)
{
var record = new SecRecord(SecKind.GenericPassword)
{
Service = _service,
Account = key,
AccessGroup = _group
};
if(data != null)
{
record.Generic = data;
}
return record;
}
private SecRecord GetExistingRecord(string key)
{
var existingRecord = GetKeyRecord(key);
SecKeyChain.QueryAsRecord(existingRecord, out SecStatusCode resultCode);
return resultCode == SecStatusCode.Success ? existingRecord : null;
}
private void CheckError(SecStatusCode resultCode, [CallerMemberName] string caller = null)
{
if(resultCode != SecStatusCode.Success)
{
throw new Exception(string.Format("Failed to execute {0}. Result code: {1}", caller, resultCode));
}
}
}
}

View file

@ -93,6 +93,7 @@
<Compile Include="Main.cs" /> <Compile Include="Main.cs" />
<Compile Include="AppDelegate.cs" /> <Compile Include="AppDelegate.cs" />
<Compile Include="Services\CryptoPrimitiveService.cs" /> <Compile Include="Services\CryptoPrimitiveService.cs" />
<Compile Include="Services\KeyChainStorageService.cs" />
<None Include="Entitlements.plist" /> <None Include="Entitlements.plist" />
<None Include="Info.plist" /> <None Include="Info.plist" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />