up
This commit is contained in:
120
src/__Libraries/StellaOps.Cryptography.Kms/KmsCryptoProvider.cs
Normal file
120
src/__Libraries/StellaOps.Cryptography.Kms/KmsCryptoProvider.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.Kms;
|
||||
|
||||
/// <summary>
|
||||
/// Crypto provider that delegates signing operations to a KMS backend.
|
||||
/// </summary>
|
||||
public sealed class KmsCryptoProvider : ICryptoProvider
|
||||
{
|
||||
private readonly IKmsClient _kmsClient;
|
||||
private readonly ConcurrentDictionary<string, KmsSigningRegistration> _registrations = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public KmsCryptoProvider(IKmsClient kmsClient)
|
||||
=> _kmsClient = kmsClient ?? throw new ArgumentNullException(nameof(kmsClient));
|
||||
|
||||
public string Name => "kms";
|
||||
|
||||
public bool Supports(CryptoCapability capability, string algorithmId)
|
||||
{
|
||||
if (!string.Equals(algorithmId, KmsAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return capability is CryptoCapability.Signing or CryptoCapability.Verification;
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
=> throw new InvalidOperationException($"Provider '{Name}' does not support password hashing.");
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(keyReference);
|
||||
|
||||
if (!Supports(CryptoCapability.Signing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
}
|
||||
|
||||
if (!_registrations.TryGetValue(keyReference.KeyId, out var registration))
|
||||
{
|
||||
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
|
||||
}
|
||||
|
||||
return new KmsSigner(_kmsClient, registration);
|
||||
}
|
||||
|
||||
public void UpsertSigningKey(CryptoSigningKey signingKey)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(signingKey);
|
||||
|
||||
if (!string.Equals(signingKey.AlgorithmId, KmsAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"Provider '{Name}' only supports {KmsAlgorithms.Es256} signing keys.");
|
||||
}
|
||||
|
||||
if (signingKey.Metadata is null ||
|
||||
!signingKey.Metadata.TryGetValue(KmsMetadataKeys.Version, out var versionId) ||
|
||||
string.IsNullOrWhiteSpace(versionId))
|
||||
{
|
||||
throw new InvalidOperationException("KMS signing keys must include metadata entry 'kms.version'.");
|
||||
}
|
||||
|
||||
var registration = new KmsSigningRegistration(signingKey.Reference.KeyId, versionId!, signingKey.AlgorithmId);
|
||||
_registrations.AddOrUpdate(signingKey.Reference.KeyId, registration, (_, _) => registration);
|
||||
}
|
||||
|
||||
public bool RemoveSigningKey(string keyId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _registrations.TryRemove(keyId, out _);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
|
||||
{
|
||||
var list = new List<CryptoSigningKey>();
|
||||
foreach (var registration in _registrations.Values)
|
||||
{
|
||||
var material = _kmsClient.ExportAsync(registration.KeyId, registration.VersionId).GetAwaiter().GetResult();
|
||||
var parameters = new ECParameters
|
||||
{
|
||||
Curve = ECCurve.NamedCurves.nistP256,
|
||||
D = material.D,
|
||||
Q = new ECPoint
|
||||
{
|
||||
X = material.Qx,
|
||||
Y = material.Qy,
|
||||
},
|
||||
};
|
||||
|
||||
var metadata = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[KmsMetadataKeys.Version] = material.VersionId
|
||||
};
|
||||
|
||||
list.Add(new CryptoSigningKey(
|
||||
new CryptoKeyReference(material.KeyId, Name),
|
||||
material.Algorithm,
|
||||
in parameters,
|
||||
material.CreatedAt,
|
||||
metadata: metadata));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
internal static class KmsMetadataKeys
|
||||
{
|
||||
public const string Version = "kms.version";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record KmsSigningRegistration(string KeyId, string VersionId, string Algorithm);
|
||||
Reference in New Issue
Block a user