stabilizaiton work - projects rework for maintenanceability and ui livening

This commit is contained in:
master
2026-02-03 23:40:04 +02:00
parent 074ce117ba
commit 557feefdc3
3305 changed files with 186813 additions and 107843 deletions

View File

@@ -0,0 +1,60 @@
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Pqc.Crypto.Falcon;
using StellaOps.Cryptography;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Cryptography.Plugin.PqSoft;
internal sealed class FalconSignerWrapper : ICryptoSigner
{
private readonly string _keyId;
private readonly FalconPrivateKeyParameters _privateKey;
private readonly FalconPublicKeyParameters _publicKey;
public FalconSignerWrapper(string keyId, FalconPrivateKeyParameters privateKey, FalconPublicKeyParameters publicKey)
{
_keyId = keyId;
_privateKey = privateKey;
_publicKey = publicKey;
}
public string KeyId => _keyId;
public string AlgorithmId => SignatureAlgorithms.Falcon512;
public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var signer = new FalconSigner();
signer.Init(true, _privateKey);
return ValueTask.FromResult(signer.GenerateSignature(data.ToArray()));
}
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var verifier = new FalconSigner();
verifier.Init(false, _publicKey);
var ok = verifier.VerifySignature(data.ToArray(), signature.ToArray());
return ValueTask.FromResult(ok);
}
public JsonWebKey ExportPublicJsonWebKey()
{
var jwk = new JsonWebKey
{
Kid = _keyId,
Alg = AlgorithmId,
Kty = JsonWebAlgorithmsKeyTypes.Octet,
Use = JsonWebKeyUseNames.Sig,
Crv = "Falcon512"
};
jwk.KeyOps.Add("sign");
jwk.KeyOps.Add("verify");
jwk.X = Base64UrlEncoder.Encode(_publicKey.GetEncoded());
return jwk;
}
}

View File

@@ -0,0 +1,65 @@
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using StellaOps.Cryptography;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Cryptography.Plugin.PqSoft;
internal sealed class MLDsaSignerWrapper : ICryptoSigner
{
private readonly string _keyId;
private readonly MLDsaPrivateKeyParameters _privateKey;
private readonly MLDsaPublicKeyParameters _publicKey;
public MLDsaSignerWrapper(string keyId, MLDsaPrivateKeyParameters privateKey, MLDsaPublicKeyParameters publicKey)
{
_keyId = keyId;
_privateKey = privateKey;
_publicKey = publicKey;
}
public string KeyId => _keyId;
public string AlgorithmId => SignatureAlgorithms.Dilithium3;
public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var signer = new MLDsaSigner(MLDsaParameters.ml_dsa_65, deterministic: true);
signer.Init(true, _privateKey);
var dataArray = data.ToArray();
signer.BlockUpdate(dataArray, 0, dataArray.Length);
return ValueTask.FromResult(signer.GenerateSignature());
}
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var verifier = new MLDsaSigner(MLDsaParameters.ml_dsa_65, deterministic: true);
verifier.Init(false, _publicKey);
var dataArray = data.ToArray();
verifier.BlockUpdate(dataArray, 0, dataArray.Length);
var ok = verifier.VerifySignature(signature.ToArray());
return ValueTask.FromResult(ok);
}
public JsonWebKey ExportPublicJsonWebKey()
{
var jwk = new JsonWebKey
{
Kid = _keyId,
Alg = AlgorithmId,
Kty = JsonWebAlgorithmsKeyTypes.Octet,
Use = JsonWebKeyUseNames.Sig,
Crv = "Dilithium3"
};
jwk.KeyOps.Add("sign");
jwk.KeyOps.Add("verify");
jwk.X = Base64UrlEncoder.Encode(_publicKey.GetEncoded());
return jwk;
}
}

View File

@@ -0,0 +1,28 @@
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Pqc.Crypto.Falcon;
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.PqSoft;
internal abstract record PqKeyEntry(CryptoSigningKey Descriptor, string AlgorithmId)
{
public abstract ICryptoSigner CreateSigner();
}
internal sealed record MLDsaKeyEntry(
CryptoSigningKey Descriptor,
MLDsaPrivateKeyParameters PrivateKey,
MLDsaPublicKeyParameters PublicKey)
: PqKeyEntry(Descriptor, SignatureAlgorithms.Dilithium3)
{
public override ICryptoSigner CreateSigner() => new MLDsaSignerWrapper(Descriptor.Reference.KeyId, PrivateKey, PublicKey);
}
internal sealed record FalconKeyEntry(
CryptoSigningKey Descriptor,
FalconPrivateKeyParameters PrivateKey,
FalconPublicKeyParameters PublicKey)
: PqKeyEntry(Descriptor, SignatureAlgorithms.Falcon512)
{
public override ICryptoSigner CreateSigner() => new FalconSignerWrapper(Descriptor.Reference.KeyId, PrivateKey, PublicKey);
}

View File

@@ -0,0 +1,26 @@
using System;
namespace StellaOps.Cryptography.Plugin.PqSoft;
public sealed partial class PqSoftCryptoProvider
{
private bool GateEnabled()
{
if (!_options.RequireEnvironmentGate)
{
return true;
}
var value = Environment.GetEnvironmentVariable(EnvGate);
return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
}
private void EnsureAllowed()
{
if (!GateEnabled())
{
throw new InvalidOperationException($"Provider '{Name}' is disabled. Set {EnvGate}=1 or disable RequireEnvironmentGate to enable.");
}
}
}

View File

@@ -0,0 +1,67 @@
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Pqc.Crypto.Falcon;
using Org.BouncyCastle.Security;
using StellaOps.Cryptography;
using System;
namespace StellaOps.Cryptography.Plugin.PqSoft;
public sealed partial class PqSoftCryptoProvider
{
private static PqKeyEntry CreateDilithiumEntry(CryptoSigningKey signingKey)
{
var parameters = MLDsaParameters.ml_dsa_65;
var random = CreateSeededRandom(signingKey.PrivateKey);
var generator = new MLDsaKeyPairGenerator();
generator.Init(new MLDsaKeyGenerationParameters(random, parameters));
var pair = generator.GenerateKeyPair();
var priv = (MLDsaPrivateKeyParameters)pair.Private;
var pub = (MLDsaPublicKeyParameters)pair.Public;
var descriptor = new CryptoSigningKey(
signingKey.Reference,
SignatureAlgorithms.Dilithium3,
priv.GetEncoded(),
signingKey.CreatedAt,
signingKey.ExpiresAt,
pub.GetEncoded(),
signingKey.Metadata);
return new MLDsaKeyEntry(descriptor, priv, pub);
}
private static PqKeyEntry CreateFalconEntry(CryptoSigningKey signingKey)
{
var parameters = FalconParameters.falcon_512;
var random = CreateSeededRandom(signingKey.PrivateKey);
var generator = new FalconKeyPairGenerator();
generator.Init(new FalconKeyGenerationParameters(random, parameters));
var pair = generator.GenerateKeyPair();
var priv = (FalconPrivateKeyParameters)pair.Private;
var pub = (FalconPublicKeyParameters)pair.Public;
var descriptor = new CryptoSigningKey(
signingKey.Reference,
SignatureAlgorithms.Falcon512,
priv.GetEncoded(),
signingKey.CreatedAt,
signingKey.ExpiresAt,
pub.GetEncoded(),
signingKey.Metadata);
return new FalconKeyEntry(descriptor, priv, pub);
}
private static SecureRandom CreateSeededRandom(ReadOnlyMemory<byte> seed)
{
var generator = new DigestRandomGenerator(new Sha512Digest());
generator.AddSeedMaterial(seed.ToArray());
return new SecureRandom(generator);
}
}

View File

@@ -0,0 +1,43 @@
using Microsoft.Extensions.Logging;
using StellaOps.Cryptography;
using System;
using System.Collections.Generic;
using System.IO;
namespace StellaOps.Cryptography.Plugin.PqSoft;
public sealed partial class PqSoftCryptoProvider
{
private void TryLoadKeyFromFile(PqSoftKeyOptions key)
{
if (string.IsNullOrWhiteSpace(key.KeyId) || string.IsNullOrWhiteSpace(key.PrivateKeyPath))
{
return;
}
try
{
var priv = File.ReadAllBytes(key.PrivateKeyPath);
var pub = string.IsNullOrWhiteSpace(key.PublicKeyPath) ? Array.Empty<byte>() : File.ReadAllBytes(key.PublicKeyPath);
var signingKey = new CryptoSigningKey(
new CryptoKeyReference(key.KeyId, Name),
key.Algorithm,
priv,
DateTimeOffset.UtcNow,
metadata: new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["source"] = "file",
["path"] = key.PrivateKeyPath
},
publicKey: pub);
UpsertSigningKey(signingKey);
_logger.LogInformation("Loaded PQ key {KeyId} for algorithm {Algorithm}", key.KeyId, key.Algorithm);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load PQ key {KeyId} from {Path}", key.KeyId, key.PrivateKeyPath);
}
}
}

View File

@@ -0,0 +1,91 @@
using StellaOps.Cryptography;
using System;
using System.Collections.Generic;
using System.Linq;
namespace StellaOps.Cryptography.Plugin.PqSoft;
public sealed partial class PqSoftCryptoProvider
{
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
{
EnsureAllowed();
ArgumentNullException.ThrowIfNull(keyReference);
if (!SupportedAlgorithms.Contains(algorithmId))
{
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
}
if (!_entries.TryGetValue(keyReference.KeyId, out var entry))
{
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
}
if (!string.Equals(entry.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"Signing key '{keyReference.KeyId}' is registered for algorithm '{entry.AlgorithmId}', not '{algorithmId}'.");
}
return entry.CreateSigner();
}
public void UpsertSigningKey(CryptoSigningKey signingKey)
{
EnsureAllowed();
ArgumentNullException.ThrowIfNull(signingKey);
var normalizedAlg = Normalize(signingKey.AlgorithmId);
if (!SupportedAlgorithms.Contains(normalizedAlg))
{
throw new InvalidOperationException($"Signing algorithm '{normalizedAlg}' is not supported by provider '{Name}'.");
}
if (signingKey.PrivateKey.IsEmpty)
{
throw new InvalidOperationException("PQ provider requires raw private key bytes.");
}
var entry = normalizedAlg switch
{
SignatureAlgorithms.Dilithium3 => CreateDilithiumEntry(signingKey),
SignatureAlgorithms.Falcon512 => CreateFalconEntry(signingKey),
_ => throw new InvalidOperationException($"Unsupported PQ algorithm '{normalizedAlg}'.")
};
_entries.AddOrUpdate(signingKey.Reference.KeyId, entry, (_, _) => entry);
}
public bool RemoveSigningKey(string keyId)
{
if (string.IsNullOrWhiteSpace(keyId))
{
return false;
}
return _entries.TryRemove(keyId, out _);
}
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
=> _entries.Values.Select(static e => e.Descriptor).ToArray();
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys()
{
foreach (var entry in _entries.Values)
{
yield return new CryptoProviderKeyDescriptor(
Name,
entry.Descriptor.Reference.KeyId,
entry.AlgorithmId,
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["provider"] = Name,
["algorithm"] = entry.AlgorithmId,
["certified"] = "false",
["simulation"] = "software"
});
}
}
private static string Normalize(string algorithmId) => algorithmId.ToUpperInvariant();
}

View File

@@ -1,24 +1,10 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Pqc.Crypto.Falcon;
using Org.BouncyCastle.Security;
using StellaOps.Cryptography;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Cryptography.Plugin.PqSoft;
@@ -26,7 +12,7 @@ namespace StellaOps.Cryptography.Plugin.PqSoft;
/// Software-only post-quantum provider (Dilithium3, Falcon512) using BouncyCastle PQC primitives.
/// Guarded by the <c>PQ_SOFT_ALLOWED</c> environment variable by default.
/// </summary>
public sealed class PqSoftCryptoProvider : ICryptoProvider, ICryptoProviderDiagnostics
public sealed partial class PqSoftCryptoProvider : ICryptoProvider, ICryptoProviderDiagnostics
{
private const string EnvGate = "PQ_SOFT_ALLOWED";
@@ -36,18 +22,18 @@ public sealed class PqSoftCryptoProvider : ICryptoProvider, ICryptoProviderDiagn
SignatureAlgorithms.Falcon512
};
private readonly ConcurrentDictionary<string, PqKeyEntry> entries = new(StringComparer.OrdinalIgnoreCase);
private readonly ILogger<PqSoftCryptoProvider> logger;
private readonly PqSoftProviderOptions options;
private readonly ConcurrentDictionary<string, PqKeyEntry> _entries = new(StringComparer.OrdinalIgnoreCase);
private readonly ILogger<PqSoftCryptoProvider> _logger;
private readonly PqSoftProviderOptions _options;
public PqSoftCryptoProvider(
IOptions<PqSoftProviderOptions>? optionsAccessor = null,
ILogger<PqSoftCryptoProvider>? logger = null)
{
options = optionsAccessor?.Value ?? new PqSoftProviderOptions();
this.logger = logger ?? NullLogger<PqSoftCryptoProvider>.Instance;
_options = optionsAccessor?.Value ?? new PqSoftProviderOptions();
_logger = logger ?? NullLogger<PqSoftCryptoProvider>.Instance;
foreach (var key in options.Keys)
foreach (var key in _options.Keys)
{
TryLoadKeyFromFile(key);
}
@@ -74,357 +60,4 @@ public sealed class PqSoftCryptoProvider : ICryptoProvider, ICryptoProviderDiagn
public ICryptoHasher GetHasher(string algorithmId)
=> throw new NotSupportedException("PQ provider does not expose hashing.");
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
{
EnsureAllowed();
ArgumentNullException.ThrowIfNull(keyReference);
if (!SupportedAlgorithms.Contains(algorithmId))
{
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
}
if (!entries.TryGetValue(keyReference.KeyId, out var entry))
{
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
}
if (!string.Equals(entry.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"Signing key '{keyReference.KeyId}' is registered for algorithm '{entry.AlgorithmId}', not '{algorithmId}'.");
}
return entry.CreateSigner();
}
public void UpsertSigningKey(CryptoSigningKey signingKey)
{
EnsureAllowed();
ArgumentNullException.ThrowIfNull(signingKey);
var normalizedAlg = Normalize(signingKey.AlgorithmId);
if (!SupportedAlgorithms.Contains(normalizedAlg))
{
throw new InvalidOperationException($"Signing algorithm '{normalizedAlg}' is not supported by provider '{Name}'.");
}
if (signingKey.PrivateKey.IsEmpty)
{
throw new InvalidOperationException("PQ provider requires raw private key bytes.");
}
var entry = normalizedAlg switch
{
SignatureAlgorithms.Dilithium3 => CreateDilithiumEntry(signingKey),
SignatureAlgorithms.Falcon512 => CreateFalconEntry(signingKey),
_ => throw new InvalidOperationException($"Unsupported PQ algorithm '{normalizedAlg}'.")
};
entries.AddOrUpdate(signingKey.Reference.KeyId, entry, (_, _) => entry);
}
public bool RemoveSigningKey(string keyId)
{
if (string.IsNullOrWhiteSpace(keyId))
{
return false;
}
return entries.TryRemove(keyId, out _);
}
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
=> entries.Values.Select(static e => e.Descriptor).ToArray();
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys()
{
foreach (var entry in entries.Values)
{
yield return new CryptoProviderKeyDescriptor(
Name,
entry.Descriptor.Reference.KeyId,
entry.AlgorithmId,
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["provider"] = Name,
["algorithm"] = entry.AlgorithmId,
["certified"] = "false",
["simulation"] = "software"
});
}
}
private bool GateEnabled()
{
if (!options.RequireEnvironmentGate)
{
return true;
}
var value = Environment.GetEnvironmentVariable(EnvGate);
return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
}
private void EnsureAllowed()
{
if (!GateEnabled())
{
throw new InvalidOperationException($"Provider '{Name}' is disabled. Set {EnvGate}=1 or disable RequireEnvironmentGate to enable.");
}
}
private void TryLoadKeyFromFile(PqSoftKeyOptions key)
{
if (string.IsNullOrWhiteSpace(key.KeyId) || string.IsNullOrWhiteSpace(key.PrivateKeyPath))
{
return;
}
try
{
var priv = File.ReadAllBytes(key.PrivateKeyPath);
var pub = string.IsNullOrWhiteSpace(key.PublicKeyPath) ? Array.Empty<byte>() : File.ReadAllBytes(key.PublicKeyPath);
var signingKey = new CryptoSigningKey(
new CryptoKeyReference(key.KeyId, Name),
key.Algorithm,
priv,
DateTimeOffset.UtcNow,
metadata: new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["source"] = "file",
["path"] = key.PrivateKeyPath
},
publicKey: pub);
UpsertSigningKey(signingKey);
logger.LogInformation("Loaded PQ key {KeyId} for algorithm {Algorithm}", key.KeyId, key.Algorithm);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to load PQ key {KeyId} from {Path}", key.KeyId, key.PrivateKeyPath);
}
}
private static string Normalize(string algorithmId) => algorithmId.ToUpperInvariant();
private static PqKeyEntry CreateDilithiumEntry(CryptoSigningKey signingKey)
{
var parameters = MLDsaParameters.ml_dsa_65;
// Always regenerate keys from deterministic seed - BC 2.5+ API changes
// make direct byte reconstruction complex. Seeded generation is deterministic
// and will produce the same keys from the same private key seed.
var random = CreateSeededRandom(signingKey.PrivateKey);
var generator = new MLDsaKeyPairGenerator();
generator.Init(new MLDsaKeyGenerationParameters(random, parameters));
var pair = generator.GenerateKeyPair();
var priv = (MLDsaPrivateKeyParameters)pair.Private;
var pub = (MLDsaPublicKeyParameters)pair.Public;
var descriptor = new CryptoSigningKey(
signingKey.Reference,
SignatureAlgorithms.Dilithium3,
priv.GetEncoded(),
signingKey.CreatedAt,
signingKey.ExpiresAt,
pub.GetEncoded(),
signingKey.Metadata);
return new MLDsaKeyEntry(descriptor, priv, pub);
}
private static PqKeyEntry CreateFalconEntry(CryptoSigningKey signingKey)
{
var parameters = FalconParameters.falcon_512;
var random = CreateSeededRandom(signingKey.PrivateKey);
var generator = new FalconKeyPairGenerator();
generator.Init(new FalconKeyGenerationParameters(random, parameters));
var pair = generator.GenerateKeyPair();
var priv = (FalconPrivateKeyParameters)pair.Private;
var pub = (FalconPublicKeyParameters)pair.Public;
var descriptor = new CryptoSigningKey(
signingKey.Reference,
SignatureAlgorithms.Falcon512,
priv.GetEncoded(),
signingKey.CreatedAt,
signingKey.ExpiresAt,
pub.GetEncoded(),
signingKey.Metadata);
return new FalconKeyEntry(descriptor, priv, pub);
}
private static SecureRandom CreateSeededRandom(ReadOnlyMemory<byte> seed)
{
var generator = new DigestRandomGenerator(new Sha512Digest());
generator.AddSeedMaterial(seed.ToArray());
return new SecureRandom(generator);
}
}
/// <summary>
/// Options for the PQ soft provider.
/// </summary>
public sealed class PqSoftProviderOptions
{
public bool RequireEnvironmentGate { get; set; } = true;
public List<PqSoftKeyOptions> Keys { get; set; } = new();
}
/// <summary>
/// Key configuration for the PQ soft provider.
/// </summary>
public sealed class PqSoftKeyOptions
{
public required string KeyId { get; set; }
= string.Empty;
public required string Algorithm { get; set; }
= SignatureAlgorithms.Dilithium3;
public string? PrivateKeyPath { get; set; }
= string.Empty;
public string? PublicKeyPath { get; set; }
= string.Empty;
}
internal abstract record PqKeyEntry(CryptoSigningKey Descriptor, string AlgorithmId)
{
public abstract ICryptoSigner CreateSigner();
}
internal sealed record MLDsaKeyEntry(
CryptoSigningKey Descriptor,
MLDsaPrivateKeyParameters PrivateKey,
MLDsaPublicKeyParameters PublicKey)
: PqKeyEntry(Descriptor, SignatureAlgorithms.Dilithium3)
{
public override ICryptoSigner CreateSigner() => new MLDsaSignerWrapper(Descriptor.Reference.KeyId, PrivateKey, PublicKey);
}
internal sealed record FalconKeyEntry(
CryptoSigningKey Descriptor,
FalconPrivateKeyParameters PrivateKey,
FalconPublicKeyParameters PublicKey)
: PqKeyEntry(Descriptor, SignatureAlgorithms.Falcon512)
{
public override ICryptoSigner CreateSigner() => new FalconSignerWrapper(Descriptor.Reference.KeyId, PrivateKey, PublicKey);
}
internal sealed class MLDsaSignerWrapper : ICryptoSigner
{
private readonly string keyId;
private readonly MLDsaPrivateKeyParameters privateKey;
private readonly MLDsaPublicKeyParameters publicKey;
public MLDsaSignerWrapper(string keyId, MLDsaPrivateKeyParameters privateKey, MLDsaPublicKeyParameters publicKey)
{
this.keyId = keyId;
this.privateKey = privateKey;
this.publicKey = publicKey;
}
public string KeyId => keyId;
public string AlgorithmId => SignatureAlgorithms.Dilithium3;
public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var signer = new MLDsaSigner(MLDsaParameters.ml_dsa_65, deterministic: true);
signer.Init(true, privateKey);
var dataArray = data.ToArray();
signer.BlockUpdate(dataArray, 0, dataArray.Length);
return ValueTask.FromResult(signer.GenerateSignature());
}
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var verifier = new MLDsaSigner(MLDsaParameters.ml_dsa_65, deterministic: true);
verifier.Init(false, publicKey);
var dataArray = data.ToArray();
verifier.BlockUpdate(dataArray, 0, dataArray.Length);
var ok = verifier.VerifySignature(signature.ToArray());
return ValueTask.FromResult(ok);
}
public JsonWebKey ExportPublicJsonWebKey()
{
var jwk = new JsonWebKey
{
Kid = keyId,
Alg = AlgorithmId,
Kty = JsonWebAlgorithmsKeyTypes.Octet, // PQ JWK mapping not standard; encode as opaque octet key
Use = JsonWebKeyUseNames.Sig,
Crv = "Dilithium3"
};
jwk.KeyOps.Add("sign");
jwk.KeyOps.Add("verify");
jwk.X = Base64UrlEncoder.Encode(publicKey.GetEncoded());
return jwk;
}
}
internal sealed class FalconSignerWrapper : ICryptoSigner
{
private readonly string keyId;
private readonly FalconPrivateKeyParameters privateKey;
private readonly FalconPublicKeyParameters publicKey;
public FalconSignerWrapper(string keyId, FalconPrivateKeyParameters privateKey, FalconPublicKeyParameters publicKey)
{
this.keyId = keyId;
this.privateKey = privateKey;
this.publicKey = publicKey;
}
public string KeyId => keyId;
public string AlgorithmId => SignatureAlgorithms.Falcon512;
public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var signer = new FalconSigner();
signer.Init(true, privateKey);
return ValueTask.FromResult(signer.GenerateSignature(data.ToArray()));
}
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var verifier = new FalconSigner();
verifier.Init(false, publicKey);
var ok = verifier.VerifySignature(data.ToArray(), signature.ToArray());
return ValueTask.FromResult(ok);
}
public JsonWebKey ExportPublicJsonWebKey()
{
var jwk = new JsonWebKey
{
Kid = keyId,
Alg = AlgorithmId,
Kty = JsonWebAlgorithmsKeyTypes.Octet,
Use = JsonWebKeyUseNames.Sig,
Crv = "Falcon512"
};
jwk.KeyOps.Add("sign");
jwk.KeyOps.Add("verify");
jwk.X = Base64UrlEncoder.Encode(publicKey.GetEncoded());
return jwk;
}
}

View File

@@ -0,0 +1,21 @@
using StellaOps.Cryptography;
namespace StellaOps.Cryptography.Plugin.PqSoft;
/// <summary>
/// Key configuration for the PQ soft provider.
/// </summary>
public sealed class PqSoftKeyOptions
{
public required string KeyId { get; set; }
= string.Empty;
public required string Algorithm { get; set; }
= SignatureAlgorithms.Dilithium3;
public string? PrivateKeyPath { get; set; }
= string.Empty;
public string? PublicKeyPath { get; set; }
= string.Empty;
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace StellaOps.Cryptography.Plugin.PqSoft;
/// <summary>
/// Options for the PQ soft provider.
/// </summary>
public sealed class PqSoftProviderOptions
{
public bool RequireEnvironmentGate { get; set; } = true;
public List<PqSoftKeyOptions> Keys { get; set; } = new();
}

View File

@@ -9,3 +9,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| AUDIT-0061-T | DONE | Revalidated 2026-01-08; open findings tracked in audit report. |
| AUDIT-0061-A | TODO | Revalidated 2026-01-08 (open findings). |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
| REMED-08 | DONE | PqSoftCryptoProvider split <= 100 lines, private fields renamed; env gate + key load + algorithm mapping tests added. |