Retire legacy implementations and flatten managed layout
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Foundation;
|
||||
using Security;
|
||||
|
||||
namespace VoiceCat.Mac;
|
||||
|
||||
internal sealed class MacKeychainPasswordStore
|
||||
{
|
||||
private const string Service = "net.iamtalon.voicecat";
|
||||
|
||||
internal string? Load(Guid profileId)
|
||||
{
|
||||
using var query = Query(profileId);
|
||||
using SecRecord? result = SecKeyChain.QueryAsRecord(query, out SecStatusCode status);
|
||||
if (status != SecStatusCode.Success || result?.ValueData is not { } data) return null;
|
||||
byte[] encoded = data.ToArray();
|
||||
try { return Encoding.UTF8.GetString(encoded); }
|
||||
finally { CryptographicOperations.ZeroMemory(encoded); }
|
||||
}
|
||||
|
||||
internal string? LoadLegacy(string? keychainTag)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keychainTag)) return null;
|
||||
using var query = new SecRecord(SecKind.GenericPassword) { Service = "cat.voice.VoiceCatMac", Account = keychainTag };
|
||||
using SecRecord? result = SecKeyChain.QueryAsRecord(query, out SecStatusCode status);
|
||||
if (status != SecStatusCode.Success || result?.ValueData is not { } data) return null;
|
||||
byte[] encoded = data.ToArray();
|
||||
try { return Encoding.UTF8.GetString(encoded); }
|
||||
finally { CryptographicOperations.ZeroMemory(encoded); }
|
||||
}
|
||||
|
||||
internal void Save(Guid profileId, string password)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(password);
|
||||
byte[] encoded = Encoding.UTF8.GetBytes(password);
|
||||
try
|
||||
{
|
||||
using var data = NSData.FromArray(encoded);
|
||||
using var query = Query(profileId);
|
||||
using var attributes = new SecRecord
|
||||
{
|
||||
Label = "VoiceCat server password",
|
||||
Accessible = SecAccessible.WhenUnlocked,
|
||||
ValueData = data
|
||||
};
|
||||
SecStatusCode status = SecKeyChain.Update(query, attributes);
|
||||
if (status == SecStatusCode.ItemNotFound)
|
||||
{
|
||||
using var record = Query(profileId);
|
||||
record.Label = attributes.Label; record.Accessible = attributes.Accessible; record.ValueData = data;
|
||||
status = SecKeyChain.Add(record);
|
||||
}
|
||||
if (status != SecStatusCode.Success) throw new InvalidOperationException($"Keychain save failed ({status}).");
|
||||
}
|
||||
finally { CryptographicOperations.ZeroMemory(encoded); }
|
||||
}
|
||||
|
||||
internal void Remove(Guid profileId)
|
||||
{
|
||||
using var query = Query(profileId);
|
||||
SecStatusCode status = SecKeyChain.Remove(query);
|
||||
if (status is not (SecStatusCode.Success or SecStatusCode.ItemNotFound))
|
||||
throw new InvalidOperationException($"Keychain removal failed ({status}).");
|
||||
}
|
||||
|
||||
private static SecRecord Query(Guid profileId) => new(SecKind.GenericPassword)
|
||||
{
|
||||
Service = Service,
|
||||
Account = profileId.ToString("D")
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user