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();
}