Retire legacy implementations and flatten managed layout
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
using System.Text;
|
||||
|
||||
namespace VoiceCat.Crypto;
|
||||
|
||||
public enum TofuStatus { FirstConnect, Matched, Mismatch }
|
||||
|
||||
public sealed class TofuStore
|
||||
{
|
||||
private readonly string path;
|
||||
private readonly Dictionary<string, string> pins = new(StringComparer.Ordinal);
|
||||
|
||||
public TofuStore(string path)
|
||||
{
|
||||
this.path = Path.GetFullPath(path);
|
||||
if (!File.Exists(this.path)) return;
|
||||
foreach (string line in File.ReadLines(this.path))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line) || line.StartsWith('#')) continue;
|
||||
string[] parts = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2) throw new InvalidDataException("Malformed TOFU pin entry.");
|
||||
pins[parts[0]] = NormalizeFingerprint(parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
public TofuStatus Check(string host, ushort port, string fingerprint)
|
||||
{
|
||||
string key = Endpoint(host, port);
|
||||
string normalized = NormalizeFingerprint(fingerprint);
|
||||
return !pins.TryGetValue(key, out var pin) ? TofuStatus.FirstConnect :
|
||||
pin == normalized ? TofuStatus.Matched : TofuStatus.Mismatch;
|
||||
}
|
||||
|
||||
public void Pin(string host, ushort port, string fingerprint)
|
||||
{
|
||||
string key = Endpoint(host, port);
|
||||
string value = NormalizeFingerprint(fingerprint);
|
||||
var updated = new Dictionary<string, string>(pins, StringComparer.Ordinal) { [key] = value };
|
||||
Save(updated);
|
||||
pins[key] = value;
|
||||
}
|
||||
|
||||
public void Remove(string host, ushort port)
|
||||
{
|
||||
string key = Endpoint(host, port);
|
||||
var updated = new Dictionary<string, string>(pins, StringComparer.Ordinal);
|
||||
updated.Remove(key);
|
||||
Save(updated);
|
||||
pins.Remove(key);
|
||||
}
|
||||
|
||||
private void Save(Dictionary<string, string> updated)
|
||||
{
|
||||
string contents = string.Concat(updated.OrderBy(pair => pair.Key, StringComparer.Ordinal).Select(pair => $"{pair.Key} {pair.Value}\n"));
|
||||
PrivateFiles.Write(path, Encoding.UTF8.GetBytes(contents));
|
||||
}
|
||||
|
||||
private static string Endpoint(string host, ushort port)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(host);
|
||||
if (host.Any(char.IsWhiteSpace)) throw new ArgumentException("Host cannot contain whitespace.", nameof(host));
|
||||
ArgumentOutOfRangeException.ThrowIfZero(port);
|
||||
return $"{host}:{port}";
|
||||
}
|
||||
|
||||
private static string NormalizeFingerprint(string fingerprint)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(fingerprint);
|
||||
if (fingerprint.Length != 64 || !fingerprint.All(Uri.IsHexDigit))
|
||||
throw new InvalidDataException("TLS certificate fingerprints must contain 64 hexadecimal characters.");
|
||||
return fingerprint.ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user