up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 00:20:26 +02:00
parent e1f1bef4c1
commit 564df71bfb
2376 changed files with 334389 additions and 328032 deletions

View File

@@ -1,21 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.BouncyCastle;
/// <summary>
/// Dependency injection helpers for registering the BouncyCastle Ed25519 crypto provider.
/// </summary>
public static class BouncyCastleCryptoServiceCollectionExtensions
{
public static IServiceCollection AddBouncyCastleEd25519Provider(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.TryAddSingleton<BouncyCastleEd25519CryptoProvider>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, BouncyCastleEd25519CryptoProvider>());
return services;
}
}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.BouncyCastle;
/// <summary>
/// Dependency injection helpers for registering the BouncyCastle Ed25519 crypto provider.
/// </summary>
public static class BouncyCastleCryptoServiceCollectionExtensions
{
public static IServiceCollection AddBouncyCastleEd25519Provider(this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
services.TryAddSingleton<BouncyCastleEd25519CryptoProvider>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, BouncyCastleEd25519CryptoProvider>());
return services;
}
}

View File

@@ -1,214 +1,214 @@
using System.Collections.Concurrent;
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.BouncyCastle;
/// <summary>
/// Ed25519 signing provider backed by BouncyCastle primitives.
/// </summary>
public sealed 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("BouncyCastle Ed25519 provider does not expose hashing capabilities.");
public IPasswordHasher GetPasswordHasher(string algorithmId)
=> throw new NotSupportedException("BouncyCastle provider does not expose password hashing capabilities.");
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
{
ArgumentException.ThrowIfNullOrWhiteSpace(algorithmId);
ArgumentNullException.ThrowIfNull(keyReference);
if (!signingKeys.TryGetValue(keyReference.KeyId, out var entry))
{
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
}
EnsureAlgorithmSupported(algorithmId);
var normalized = NormalizeAlgorithm(algorithmId);
if (!string.Equals(entry.Descriptor.AlgorithmId, normalized, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
$"Signing key '{keyReference.KeyId}' is registered for algorithm '{entry.Descriptor.AlgorithmId}', not '{algorithmId}'.");
}
return new Ed25519SignerWrapper(entry);
}
public void UpsertSigningKey(CryptoSigningKey signingKey)
{
ArgumentNullException.ThrowIfNull(signingKey);
EnsureAlgorithmSupported(signingKey.AlgorithmId);
if (signingKey.Kind != CryptoSigningKeyKind.Raw)
{
throw new InvalidOperationException($"Provider '{Name}' requires raw Ed25519 private key material.");
}
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();
private static void EnsureAlgorithmSupported(string algorithmId)
{
if (!SupportedAlgorithms.Contains(algorithmId))
{
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'bouncycastle.ed25519'.");
}
}
private static string NormalizeAlgorithm(string algorithmId)
=> string.Equals(algorithmId, SignatureAlgorithms.EdDsa, StringComparison.OrdinalIgnoreCase)
? SignatureAlgorithms.Ed25519
: SignatureAlgorithms.Ed25519;
private static byte[] NormalizePrivateKey(ReadOnlyMemory<byte> privateKey)
{
var span = privateKey.Span;
return span.Length switch
{
32 => span.ToArray(),
64 => span[..32].ToArray(),
_ => throw new InvalidOperationException("Ed25519 private key must be 32 or 64 bytes.")
};
}
private static byte[] NormalizePublicKey(ReadOnlyMemory<byte> publicKey, byte[] privateKey)
{
if (publicKey.IsEmpty)
{
var privateParams = new Ed25519PrivateKeyParameters(privateKey, 0);
return privateParams.GeneratePublicKey().GetEncoded();
}
if (publicKey.Span.Length != 32)
{
throw new InvalidOperationException("Ed25519 public key must be 32 bytes.");
}
return publicKey.ToArray();
}
private sealed record KeyEntry(
CryptoSigningKey Descriptor,
Ed25519PrivateKeyParameters PrivateKey,
Ed25519PublicKeyParameters PublicKey);
private sealed class Ed25519SignerWrapper : ICryptoSigner
{
private readonly KeyEntry entry;
public Ed25519SignerWrapper(KeyEntry entry)
{
this.entry = entry ?? throw new ArgumentNullException(nameof(entry));
}
public string KeyId => entry.Descriptor.Reference.KeyId;
public string AlgorithmId => entry.Descriptor.AlgorithmId;
public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var signer = new Ed25519Signer();
var buffer = data.ToArray();
signer.Init(true, entry.PrivateKey);
signer.BlockUpdate(buffer, 0, buffer.Length);
var signature = signer.GenerateSignature();
return ValueTask.FromResult(signature);
}
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var verifier = new Ed25519Signer();
var buffer = data.ToArray();
verifier.Init(false, entry.PublicKey);
verifier.BlockUpdate(buffer, 0, buffer.Length);
var verified = verifier.VerifySignature(signature.ToArray());
return ValueTask.FromResult(verified);
}
public JsonWebKey ExportPublicJsonWebKey()
{
var jwk = new JsonWebKey
{
Kid = entry.Descriptor.Reference.KeyId,
Alg = SignatureAlgorithms.EdDsa,
Kty = "OKP",
Use = JsonWebKeyUseNames.Sig,
Crv = "Ed25519"
};
foreach (var op in DefaultKeyOps)
{
jwk.KeyOps.Add(op);
}
jwk.X = Base64UrlEncoder.Encode(entry.PublicKey.GetEncoded());
return jwk;
}
}
}
using System.Collections.Concurrent;
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.BouncyCastle;
/// <summary>
/// Ed25519 signing provider backed by BouncyCastle primitives.
/// </summary>
public sealed 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("BouncyCastle Ed25519 provider does not expose hashing capabilities.");
public IPasswordHasher GetPasswordHasher(string algorithmId)
=> throw new NotSupportedException("BouncyCastle provider does not expose password hashing capabilities.");
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
{
ArgumentException.ThrowIfNullOrWhiteSpace(algorithmId);
ArgumentNullException.ThrowIfNull(keyReference);
if (!signingKeys.TryGetValue(keyReference.KeyId, out var entry))
{
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
}
EnsureAlgorithmSupported(algorithmId);
var normalized = NormalizeAlgorithm(algorithmId);
if (!string.Equals(entry.Descriptor.AlgorithmId, normalized, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
$"Signing key '{keyReference.KeyId}' is registered for algorithm '{entry.Descriptor.AlgorithmId}', not '{algorithmId}'.");
}
return new Ed25519SignerWrapper(entry);
}
public void UpsertSigningKey(CryptoSigningKey signingKey)
{
ArgumentNullException.ThrowIfNull(signingKey);
EnsureAlgorithmSupported(signingKey.AlgorithmId);
if (signingKey.Kind != CryptoSigningKeyKind.Raw)
{
throw new InvalidOperationException($"Provider '{Name}' requires raw Ed25519 private key material.");
}
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();
private static void EnsureAlgorithmSupported(string algorithmId)
{
if (!SupportedAlgorithms.Contains(algorithmId))
{
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'bouncycastle.ed25519'.");
}
}
private static string NormalizeAlgorithm(string algorithmId)
=> string.Equals(algorithmId, SignatureAlgorithms.EdDsa, StringComparison.OrdinalIgnoreCase)
? SignatureAlgorithms.Ed25519
: SignatureAlgorithms.Ed25519;
private static byte[] NormalizePrivateKey(ReadOnlyMemory<byte> privateKey)
{
var span = privateKey.Span;
return span.Length switch
{
32 => span.ToArray(),
64 => span[..32].ToArray(),
_ => throw new InvalidOperationException("Ed25519 private key must be 32 or 64 bytes.")
};
}
private static byte[] NormalizePublicKey(ReadOnlyMemory<byte> publicKey, byte[] privateKey)
{
if (publicKey.IsEmpty)
{
var privateParams = new Ed25519PrivateKeyParameters(privateKey, 0);
return privateParams.GeneratePublicKey().GetEncoded();
}
if (publicKey.Span.Length != 32)
{
throw new InvalidOperationException("Ed25519 public key must be 32 bytes.");
}
return publicKey.ToArray();
}
private sealed record KeyEntry(
CryptoSigningKey Descriptor,
Ed25519PrivateKeyParameters PrivateKey,
Ed25519PublicKeyParameters PublicKey);
private sealed class Ed25519SignerWrapper : ICryptoSigner
{
private readonly KeyEntry entry;
public Ed25519SignerWrapper(KeyEntry entry)
{
this.entry = entry ?? throw new ArgumentNullException(nameof(entry));
}
public string KeyId => entry.Descriptor.Reference.KeyId;
public string AlgorithmId => entry.Descriptor.AlgorithmId;
public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var signer = new Ed25519Signer();
var buffer = data.ToArray();
signer.Init(true, entry.PrivateKey);
signer.BlockUpdate(buffer, 0, buffer.Length);
var signature = signer.GenerateSignature();
return ValueTask.FromResult(signature);
}
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var verifier = new Ed25519Signer();
var buffer = data.ToArray();
verifier.Init(false, entry.PublicKey);
verifier.BlockUpdate(buffer, 0, buffer.Length);
var verified = verifier.VerifySignature(signature.ToArray());
return ValueTask.FromResult(verified);
}
public JsonWebKey ExportPublicJsonWebKey()
{
var jwk = new JsonWebKey
{
Kid = entry.Descriptor.Reference.KeyId,
Alg = SignatureAlgorithms.EdDsa,
Kty = "OKP",
Use = JsonWebKeyUseNames.Sig,
Crv = "Ed25519"
};
foreach (var op in DefaultKeyOps)
{
jwk.KeyOps.Add(op);
}
jwk.X = Base64UrlEncoder.Encode(entry.PublicKey.GetEncoded());
return jwk;
}
}
}