83 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			83 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
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<byte[]> SignAsync(ReadOnlyMemory<byte> 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<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> 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<byte>());
 | 
						|
        jwk.Y = Base64UrlEncoder.Encode(signingKey.PublicParameters.Q.Y ?? Array.Empty<byte>());
 | 
						|
 | 
						|
        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}'.")
 | 
						|
        };
 | 
						|
}
 |