112 lines
3.9 KiB
C#
112 lines
3.9 KiB
C#
using Org.BouncyCastle.Crypto.Parameters;
|
|
using StellaOps.Cryptography;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using static StellaOps.Localization.T;
|
|
|
|
namespace StellaOps.Cryptography.Plugin.BouncyCastle;
|
|
|
|
/// <summary>
|
|
/// Ed25519 signing provider backed by BouncyCastle primitives.
|
|
/// </summary>
|
|
public sealed partial class BouncyCastleEd25519CryptoProvider : ICryptoProvider
|
|
{
|
|
private static readonly HashSet<string> _supportedAlgorithms = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
SignatureAlgorithms.Ed25519,
|
|
SignatureAlgorithms.EdDsa
|
|
};
|
|
|
|
private static readonly string[] _defaultKeyOps = { "sign", "verify" };
|
|
private readonly ConcurrentDictionary<string, KeyEntry> _signingKeys = new(StringComparer.Ordinal);
|
|
|
|
public string Name => "bouncycastle.ed25519";
|
|
|
|
public bool Supports(CryptoCapability capability, string algorithmId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(algorithmId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return capability switch
|
|
{
|
|
CryptoCapability.Signing or CryptoCapability.Verification => _supportedAlgorithms.Contains(algorithmId),
|
|
_ => false
|
|
};
|
|
}
|
|
|
|
public ICryptoHasher GetHasher(string algorithmId)
|
|
=> throw new NotSupportedException(_t("crypto.ed25519.no_hashing"));
|
|
|
|
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
|
=> throw new NotSupportedException(_t("crypto.provider.no_password_hashing", Name));
|
|
|
|
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(algorithmId);
|
|
ArgumentNullException.ThrowIfNull(keyReference);
|
|
|
|
if (!_signingKeys.TryGetValue(keyReference.KeyId, out var entry))
|
|
{
|
|
throw new KeyNotFoundException(_t("crypto.provider.key_not_registered", keyReference.KeyId, Name));
|
|
}
|
|
|
|
EnsureAlgorithmSupported(algorithmId);
|
|
var normalized = NormalizeAlgorithm(algorithmId);
|
|
if (!string.Equals(entry.Descriptor.AlgorithmId, normalized, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new InvalidOperationException(
|
|
_t("crypto.provider.key_algorithm_mismatch", keyReference.KeyId, entry.Descriptor.AlgorithmId, algorithmId));
|
|
}
|
|
|
|
return new Ed25519SignerWrapper(entry);
|
|
}
|
|
|
|
public void UpsertSigningKey(CryptoSigningKey signingKey)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(signingKey);
|
|
EnsureAlgorithmSupported(signingKey.AlgorithmId);
|
|
|
|
if (signingKey.Kind != CryptoSigningKeyKind.Raw)
|
|
{
|
|
throw new InvalidOperationException(_t("crypto.ed25519.raw_key_required", Name));
|
|
}
|
|
|
|
var privateKey = NormalizePrivateKey(signingKey.PrivateKey);
|
|
var publicKey = NormalizePublicKey(signingKey.PublicKey, privateKey);
|
|
|
|
var privateKeyParameters = new Ed25519PrivateKeyParameters(privateKey, 0);
|
|
var publicKeyParameters = new Ed25519PublicKeyParameters(publicKey, 0);
|
|
|
|
var descriptor = new CryptoSigningKey(
|
|
signingKey.Reference,
|
|
NormalizeAlgorithm(signingKey.AlgorithmId),
|
|
privateKey,
|
|
signingKey.CreatedAt,
|
|
signingKey.ExpiresAt,
|
|
publicKey,
|
|
signingKey.Metadata);
|
|
|
|
_signingKeys.AddOrUpdate(
|
|
signingKey.Reference.KeyId,
|
|
_ => new KeyEntry(descriptor, privateKeyParameters, publicKeyParameters),
|
|
(_, _) => new KeyEntry(descriptor, privateKeyParameters, publicKeyParameters));
|
|
}
|
|
|
|
public bool RemoveSigningKey(string keyId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(keyId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return _signingKeys.TryRemove(keyId, out _);
|
|
}
|
|
|
|
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
|
|
=> _signingKeys.Values.Select(static entry => entry.Descriptor).ToArray();
|
|
}
|