Files
git.stella-ops.org/src/__Libraries/StellaOps.Cryptography/EcdsaSigner.cs

103 lines
4.2 KiB
C#

using Microsoft.IdentityModel.Tokens;
using System;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography;
public 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 static ICryptoSigner CreateVerifierFromPublicKey(string algorithmId, ReadOnlySpan<byte> publicKeyBytes)
{
using var ecdsa = ECDsa.Create();
ecdsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
var publicParameters = ecdsa.ExportParameters(false);
var verifierKey = new CryptoSigningKey(
reference: new CryptoKeyReference("ephemeral-verifier"),
algorithmId: algorithmId,
publicParameters: publicParameters,
verificationOnly: true,
createdAt: DateTimeOffset.UtcNow,
expiresAt: null,
metadata: null);
return new EcdsaSigner(verifierKey);
}
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(_t("crypto.ecdsa.algorithm_unsupported", 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(_t("crypto.ecdsa.curve_unsupported", algorithmId))
};
}