bitwarden-mobile/src/iOS.Core/Services/KeyChainStorageService.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

132 lines
4.2 KiB
C#
Raw Normal View History

2019-04-09 03:23:16 +02:00
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;
2019-04-09 03:38:17 +02:00
namespace Bit.iOS.Core.Services
2019-04-09 03:23:16 +02:00
{
public class KeyChainStorageService : IStorageService
{
2019-07-04 02:04:23 +02:00
private readonly string _keyFormat = "bwKeyChainStorage:{0}:{1}";
2019-04-09 03:23:16 +02:00
private readonly string _service;
private readonly string _group;
2019-07-04 02:04:23 +02:00
private readonly Func<Task<string>> _getAppId;
2019-04-09 03:23:16 +02:00
private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
2019-07-04 02:04:23 +02:00
public KeyChainStorageService(string service, string group, Func<Task<string>> getAppId)
2019-04-09 03:23:16 +02:00
{
_service = service;
_group = group;
2019-07-04 02:04:23 +02:00
_getAppId = getAppId;
2019-04-09 03:23:16 +02:00
}
2019-07-04 02:04:23 +02:00
public async Task<T> GetAsync<T>(string key)
2019-04-09 03:23:16 +02:00
{
2019-07-04 02:04:23 +02:00
var appId = await _getAppId.Invoke();
var formattedKey = string.Format(_keyFormat, appId, key);
2019-04-09 03:23:16 +02:00
byte[] dataBytes = null;
using (var existingRecord = GetKeyRecord(formattedKey))
using (var record = SecKeyChain.QueryAsRecord(existingRecord, out var resultCode))
2019-04-09 03:23:16 +02:00
{
if (resultCode == SecStatusCode.ItemNotFound || resultCode == SecStatusCode.InteractionNotAllowed)
2019-04-09 03:23:16 +02:00
{
2019-07-04 02:04:23 +02:00
return (T)(object)null;
2019-04-09 03:23:16 +02:00
}
CheckError(resultCode);
dataBytes = record.Generic.ToArray();
}
var dataString = Encoding.UTF8.GetString(dataBytes);
if (typeof(T) == typeof(string))
2019-04-09 03:23:16 +02:00
{
2019-07-04 02:04:23 +02:00
return (T)(object)dataString;
2019-04-09 03:23:16 +02:00
}
else
{
2019-07-04 02:04:23 +02:00
return JsonConvert.DeserializeObject<T>(dataString, _jsonSettings);
2019-04-09 03:23:16 +02:00
}
}
public async Task SaveAsync<T>(string key, T obj)
{
if (obj == null)
2019-04-09 03:23:16 +02:00
{
await RemoveAsync(key);
return;
}
string dataString = null;
if (typeof(T) == typeof(string))
2019-04-09 03:23:16 +02:00
{
dataString = obj as string;
}
else
{
dataString = JsonConvert.SerializeObject(obj, _jsonSettings);
}
2019-07-04 02:04:23 +02:00
var appId = await _getAppId.Invoke();
var formattedKey = string.Format(_keyFormat, appId, key);
2019-04-09 03:23:16 +02:00
var dataBytes = Encoding.UTF8.GetBytes(dataString);
using (var data = NSData.FromArray(dataBytes))
using (var newRecord = GetKeyRecord(formattedKey, data))
2019-04-09 03:23:16 +02:00
{
2019-06-24 22:51:54 +02:00
await RemoveAsync(key);
2019-04-09 03:23:16 +02:00
CheckError(SecKeyChain.Add(newRecord));
}
}
2019-07-04 02:04:23 +02:00
public async Task RemoveAsync(string key)
2019-04-09 03:23:16 +02:00
{
2019-07-04 02:04:23 +02:00
var appId = await _getAppId.Invoke();
var formattedKey = string.Format(_keyFormat, appId, key);
using (var record = GetExistingRecord(formattedKey))
2019-04-09 03:23:16 +02:00
{
if (record != null)
2019-04-09 03:23:16 +02:00
{
CheckError(SecKeyChain.Remove(record));
}
}
}
private SecRecord GetKeyRecord(string key, NSData data = null)
{
var record = new SecRecord(SecKind.GenericPassword)
{
Service = _service,
Account = key,
AccessGroup = _group
};
if (data != null)
2019-04-09 03:23:16 +02:00
{
record.Generic = data;
}
return record;
}
private SecRecord GetExistingRecord(string key)
{
var existingRecord = GetKeyRecord(key);
2019-07-27 18:39:59 +02:00
SecKeyChain.QueryAsRecord(existingRecord, out var resultCode);
2019-04-09 03:23:16 +02:00
return resultCode == SecStatusCode.Success ? existingRecord : null;
}
private void CheckError(SecStatusCode resultCode, [CallerMemberName] string caller = null)
{
if (resultCode != SecStatusCode.Success)
2019-04-09 03:23:16 +02:00
{
throw new Exception(string.Format("Failed to execute {0}. Result code: {1}", caller, resultCode));
}
}
}
}