using System; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; namespace StellaOps.Cryptography; internal sealed class EcdsaSigner : ICryptoSigner { private static readonly string[] DefaultKeyOps = { "sign", "verify" }; private readonly CryptoSigningKey signingKey; private EcdsaSigner(CryptoSigningKey signingKey) => this.signingKey = signingKey ?? throw new ArgumentNullException(nameof(signingKey)); public string KeyId => signingKey.Reference.KeyId; public string AlgorithmId => signingKey.AlgorithmId; public static ICryptoSigner Create(CryptoSigningKey signingKey) => new EcdsaSigner(signingKey); public ValueTask SignAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); using var ecdsa = ECDsa.Create(signingKey.PrivateParameters); var hashAlgorithm = ResolveHashAlgorithm(signingKey.AlgorithmId); var signature = ecdsa.SignData(data.Span, hashAlgorithm); return ValueTask.FromResult(signature); } public ValueTask VerifyAsync(ReadOnlyMemory data, ReadOnlyMemory signature, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); using var ecdsa = ECDsa.Create(signingKey.PublicParameters); var hashAlgorithm = ResolveHashAlgorithm(signingKey.AlgorithmId); var verified = ecdsa.VerifyData(data.Span, signature.Span, hashAlgorithm); return ValueTask.FromResult(verified); } public JsonWebKey ExportPublicJsonWebKey() { var jwk = new JsonWebKey { Kid = signingKey.Reference.KeyId, Alg = signingKey.AlgorithmId, Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve, Use = JsonWebKeyUseNames.Sig, Crv = ResolveCurve(signingKey.AlgorithmId) }; foreach (var op in DefaultKeyOps) { jwk.KeyOps.Add(op); } jwk.X = Base64UrlEncoder.Encode(signingKey.PublicParameters.Q.X ?? Array.Empty()); jwk.Y = Base64UrlEncoder.Encode(signingKey.PublicParameters.Q.Y ?? Array.Empty()); return jwk; } private static HashAlgorithmName ResolveHashAlgorithm(string algorithmId) => algorithmId switch { { } alg when string.Equals(alg, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA256, { } alg when string.Equals(alg, SignatureAlgorithms.Es384, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA384, { } alg when string.Equals(alg, SignatureAlgorithms.Es512, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA512, _ => throw new InvalidOperationException($"Unsupported ECDSA signing algorithm '{algorithmId}'.") }; private static string ResolveCurve(string algorithmId) => algorithmId switch { { } alg when string.Equals(alg, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P256, { } alg when string.Equals(alg, SignatureAlgorithms.Es384, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P384, { } alg when string.Equals(alg, SignatureAlgorithms.Es512, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P521, _ => throw new InvalidOperationException($"Unsupported ECDSA curve mapping for algorithm '{algorithmId}'.") }; }