This commit is contained in:
master
2026-02-04 19:59:20 +02:00
parent 557feefdc3
commit 5548cf83bf
1479 changed files with 53557 additions and 40339 deletions

View File

@@ -0,0 +1,48 @@
using Org.BouncyCastle.Crypto.Parameters;
using StellaOps.Cryptography;
using System;
namespace StellaOps.Cryptography.Plugin.BouncyCastle;
public sealed partial class BouncyCastleEd25519CryptoProvider
{
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();
}
}

View File

@@ -0,0 +1,76 @@
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using StellaOps.Cryptography;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Cryptography.Plugin.BouncyCastle;
public sealed partial class BouncyCastleEd25519CryptoProvider
{
private sealed record KeyEntry(
CryptoSigningKey Descriptor,
Ed25519PrivateKeyParameters PrivateKey,
Ed25519PublicKeyParameters PublicKey);
private sealed class Ed25519SignerWrapper : ICryptoSigner
{
private readonly KeyEntry _entry;
public Ed25519SignerWrapper(KeyEntry entry)
{
_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;
}
}
}

View File

@@ -1,26 +1,25 @@
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using StellaOps.Cryptography;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace StellaOps.Cryptography.Plugin.BouncyCastle;
/// <summary>
/// Ed25519 signing provider backed by BouncyCastle primitives.
/// </summary>
public sealed class BouncyCastleEd25519CryptoProvider : ICryptoProvider
public sealed partial class BouncyCastleEd25519CryptoProvider : ICryptoProvider
{
private static readonly HashSet<string> SupportedAlgorithms = new(StringComparer.OrdinalIgnoreCase)
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);
private static readonly string[] _defaultKeyOps = { "sign", "verify" };
private readonly ConcurrentDictionary<string, KeyEntry> _signingKeys = new(StringComparer.Ordinal);
public string Name => "bouncycastle.ed25519";
@@ -33,7 +32,7 @@ public sealed class BouncyCastleEd25519CryptoProvider : ICryptoProvider
return capability switch
{
CryptoCapability.Signing or CryptoCapability.Verification => SupportedAlgorithms.Contains(algorithmId),
CryptoCapability.Signing or CryptoCapability.Verification => _supportedAlgorithms.Contains(algorithmId),
_ => false
};
}
@@ -49,7 +48,7 @@ public sealed class BouncyCastleEd25519CryptoProvider : ICryptoProvider
ArgumentException.ThrowIfNullOrWhiteSpace(algorithmId);
ArgumentNullException.ThrowIfNull(keyReference);
if (!signingKeys.TryGetValue(keyReference.KeyId, out var entry))
if (!_signingKeys.TryGetValue(keyReference.KeyId, out var entry))
{
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
}
@@ -90,7 +89,7 @@ public sealed class BouncyCastleEd25519CryptoProvider : ICryptoProvider
publicKey,
signingKey.Metadata);
signingKeys.AddOrUpdate(
_signingKeys.AddOrUpdate(
signingKey.Reference.KeyId,
_ => new KeyEntry(descriptor, privateKeyParameters, publicKeyParameters),
(_, _) => new KeyEntry(descriptor, privateKeyParameters, publicKeyParameters));
@@ -103,113 +102,9 @@ public sealed class BouncyCastleEd25519CryptoProvider : ICryptoProvider
return false;
}
return signingKeys.TryRemove(keyId, out _);
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;
}
}
=> _signingKeys.Values.Select(static entry => entry.Descriptor).ToArray();
}

View File

@@ -9,3 +9,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| AUDIT-0052-T | DONE | Revalidated 2026-01-08. |
| AUDIT-0052-A | TODO | Revalidated 2026-01-08 (open findings). |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
| REMED-05 | DONE | Private field naming fixed; file split into partials <= 100 lines; key normalization tests added; `dotnet test src/__Libraries/__Tests/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.csproj -p:BuildInParallel=false -p:UseSharedCompilation=false` passed (330 tests) 2026-02-04. |