using System.Security.Cryptography; using System.Text; namespace Fleet.Identity; /// 点对点会话派生出的数据面凭据。SessionId 8 字节,Key 32 字节(HMAC-SHA256 的密钥)。 public sealed record SessionCredentials(byte[] SessionId, byte[] Key) { public long SessionIdValue => BitConverter.ToInt64(SessionId, 0); } /// 临时 ECDH 密钥:每个会话各生成一个,用完即弃,所以会话密钥具有前向保密。 public sealed class EphemeralKey : IDisposable { private readonly ECDiffieHellman _key = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); public byte[] PublicKeySpki => _key.ExportSubjectPublicKeyInfo(); /// 与对端的临时公钥做 ECDH,返回原始共享秘密;必须再经 KDF 才能当密钥用。 public byte[] AgreeRaw(ReadOnlySpan remotePublicKeySpki) { using ECDiffieHellman remote = ECDiffieHellman.Create(); remote.ImportSubjectPublicKeyInfo(remotePublicKeySpki, out _); return _key.DeriveRawSecretAgreement(remote.PublicKey); } /// /// 与对端的临时公钥协商,再经 HKDF-SHA256 派生会话凭据。 /// 盐是服务端的随机数,info 绑定双方的临时公钥,防止凭据被挪用到别的握手上。 /// public SessionCredentials Derive(ReadOnlySpan remotePublicKeySpki, ReadOnlySpan nonce, ReadOnlySpan clientPublic, ReadOnlySpan serverPublic) { using ECDiffieHellman remote = ECDiffieHellman.Create(); remote.ImportSubjectPublicKeyInfo(remotePublicKeySpki, out _); byte[] secret = _key.DeriveRawSecretAgreement(remote.PublicKey); byte[] info = [.. Encoding.UTF8.GetBytes("fleet|v1|session|"), .. SHA256.HashData(clientPublic), .. SHA256.HashData(serverPublic)]; byte[] material = HKDF.DeriveKey(HashAlgorithmName.SHA256, secret, 40, nonce.ToArray(), info); CryptographicOperations.ZeroMemory(secret); return new SessionCredentials(material[..8], material[8..]); } public void Dispose() => _key.Dispose(); } /// 数据面的消息认证:HMAC-SHA256 截断到 16 字节。 public static class DataPlaneMac { public const int Bytes = 16; public static void Compute(ReadOnlySpan key, ReadOnlySpan data, Span destination) { Span full = stackalloc byte[32]; HMACSHA256.HashData(key, data, full); full[..Bytes].CopyTo(destination); } public static bool Verify(ReadOnlySpan key, ReadOnlySpan data, ReadOnlySpan mac) { Span expected = stackalloc byte[Bytes]; Compute(key, data, expected); return CryptographicOperations.FixedTimeEquals(expected, mac); } }