using System.Security.Cryptography; using Fleet.Identity; using Fleet.Server; namespace MoShiro.Backend.Application; /// /// 服务器的身份密钥(P-256):操作员用 passkey 把公钥签进操作员集合,之后节点只认带这把密钥签名的服务器, /// 并在这条链路上用应用层加密(TLS 在 Cloudflare 或被冒充的证书那里终止时,中间人拿不出这把密钥)。 /// 私钥存在 Backend 的设置里:它保护的是「面板被冒充」,不是「面板被攻破」;面板被攻破时它也会泄露,那时要由 passkey 签一个换掉它的新集合版本。 /// public sealed class ServerIdentityService : IServerIdentity, IDisposable { private const string Key = "server.identity_key"; private readonly ISettingsStore _settings; private readonly object _gate = new(); private ECDsa? _key; private byte[]? _spki; public ServerIdentityService(ISettingsStore settings) { _settings = settings; } public byte[] PublicKeySpki { get { EnsureLoaded(); return _spki!; } } /// 公钥的指纹(SHA-256,小写十六进制):页面上给人核对。 public string Fingerprint => Convert.ToHexStringLower(SHA256.HashData(PublicKeySpki)); public byte[] Sign(ReadOnlySpan payload) { EnsureLoaded(); return _key!.SignData(payload, HashAlgorithmName.SHA256, DSASignatureFormat.IeeeP1363FixedFieldConcatenation); } /// 启动时调用:读出或生成密钥。节点可能在启动完成之前就连上来,所以 也会按需加载。 public ValueTask EnsureAsync(CancellationToken ct) { EnsureLoaded(ct); return ValueTask.CompletedTask; } private void EnsureLoaded(CancellationToken ct = default) { if (_key is not null) { return; } lock (_gate) { if (_key is not null) { return; } string? stored = _settings.GetAsync(Key, ct).AsTask().GetAwaiter().GetResult(); var key = ECDsa.Create(ECCurve.NamedCurves.nistP256); if (stored is not null) { key.ImportPkcs8PrivateKey(Convert.FromBase64String(stored), out _); } else { _settings.SetAsync(Key, Convert.ToBase64String(key.ExportPkcs8PrivateKey()), ct).AsTask().GetAwaiter().GetResult(); } _spki = key.ExportSubjectPublicKeyInfo(); _key = key; } } public void Dispose() => _key?.Dispose(); }