File: SecureStorage\SecureStorage.ios.tvos.watchos.macos.cs
Web Access
Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials)
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Foundation;
using Security;
 
namespace Microsoft.Maui.Storage
{
	partial class SecureStorageImplementation : ISecureStorage, IPlatformSecureStorage
	{
		public SecAccessible DefaultAccessible { get; set; } =
			SecAccessible.AfterFirstUnlock;
 
		public Task SetAsync(string key, string value, SecAccessible accessible)
		{
			if (string.IsNullOrWhiteSpace(key))
				throw new ArgumentNullException(nameof(key));
			if (value == null)
				throw new ArgumentNullException(nameof(value));
 
			var kc = new KeyChain(accessible);
			kc.SetValueForKey(value, key, Alias);
 
			return Task.CompletedTask;
		}
 
		Task<string> PlatformGetAsync(string key)
		{
			var kc = new KeyChain(DefaultAccessible);
			var value = kc.ValueForKey(key, Alias);
 
			return Task.FromResult(value);
		}
 
		Task PlatformSetAsync(string key, string data) =>
			SetAsync(key, data, DefaultAccessible);
 
		bool PlatformRemove(string key)
		{
			var kc = new KeyChain(DefaultAccessible);
			return kc.Remove(key, Alias);
		}
 
		void PlatformRemoveAll()
		{
			var kc = new KeyChain(DefaultAccessible);
			kc.RemoveAll(Alias);
		}
	}
 
	class KeyChain
	{
		SecAccessible accessible;
 
		internal KeyChain(SecAccessible accessible) =>
			this.accessible = accessible;
 
		SecRecord ExistingRecordForKey(string key, string service)
		{
			return new SecRecord(SecKind.GenericPassword)
			{
				Account = key,
				Service = service
			};
		}
 
		internal string ValueForKey(string key, string service)
		{
			using (var record = ExistingRecordForKey(key, service))
			using (var match = SecKeyChain.QueryAsRecord(record, out var resultCode))
			{
				if (resultCode == SecStatusCode.Success)
					return NSString.FromData(match.ValueData, NSStringEncoding.UTF8);
				else
					return null;
			}
		}
 
		internal void SetValueForKey(string value, string key, string service)
		{
			using (var record = ExistingRecordForKey(key, service))
			{
				if (string.IsNullOrEmpty(value))
				{
					if (!string.IsNullOrEmpty(ValueForKey(key, service)))
						RemoveRecord(record);
 
					return;
				}
 
				// if the key already exists, remove it
				if (!string.IsNullOrEmpty(ValueForKey(key, service)))
					RemoveRecord(record);
			}
 
			using (var newRecord = CreateRecordForNewKeyValue(key, value, service))
			{
				var result = SecKeyChain.Add(newRecord);
 
				switch (result)
				{
					case SecStatusCode.DuplicateItem:
						{
							// TODO: Use Logger here?
							Debug.WriteLine("Duplicate item found. Attempting to remove and add again.");
 
							// try to remove and add again
							if (Remove(key, service))
							{
								result = SecKeyChain.Add(newRecord);
								if (result != SecStatusCode.Success)
									throw new Exception($"Error adding record: {result}");
							}
							else
							{
								// TODO: Use Logger here?
								Debug.WriteLine("Unable to remove key.");
							}
						}
						break;
					case SecStatusCode.Success:
						return;
					default:
						throw new Exception($"Error adding record: {result}");
				}
			}
		}
 
		internal bool Remove(string key, string service)
		{
			using (var record = ExistingRecordForKey(key, service))
			using (var match = SecKeyChain.QueryAsRecord(record, out var resultCode))
			{
				if (resultCode == SecStatusCode.Success)
				{
					RemoveRecord(record);
					return true;
				}
			}
			return false;
		}
 
		internal void RemoveAll(string service)
		{
			using (var query = new SecRecord(SecKind.GenericPassword) { Service = service })
			{
				SecKeyChain.Remove(query);
			}
		}
 
		SecRecord CreateRecordForNewKeyValue(string key, string value, string service)
		{
			return new SecRecord(SecKind.GenericPassword)
			{
				Account = key,
				Service = service,
				Label = key,
				Accessible = accessible,
				ValueData = NSData.FromString(value, NSStringEncoding.UTF8),
			};
		}
 
		bool RemoveRecord(SecRecord record)
		{
			var result = SecKeyChain.Remove(record);
			if (result != SecStatusCode.Success && result != SecStatusCode.ItemNotFound)
				throw new Exception($"Error removing record: {result}");
 
			return true;
		}
	}
}