Add post-quantum cryptography support with PqSoftCryptoProvider
Some checks failed
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
Docs CI / lint-and-preview (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
wine-csp-build / Build Wine CSP Image (push) Has been cancelled
Some checks failed
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
Docs CI / lint-and-preview (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
wine-csp-build / Build Wine CSP Image (push) Has been cancelled
- Implemented PqSoftCryptoProvider for software-only post-quantum algorithms (Dilithium3, Falcon512) using BouncyCastle. - Added PqSoftProviderOptions and PqSoftKeyOptions for configuration. - Created unit tests for Dilithium3 and Falcon512 signing and verification. - Introduced EcdsaPolicyCryptoProvider for compliance profiles (FIPS/eIDAS) with explicit allow-lists. - Added KcmvpHashOnlyProvider for KCMVP baseline compliance. - Updated project files and dependencies for new libraries and testing frameworks.
This commit is contained in:
@@ -11,6 +11,8 @@ using StellaOps.Cryptography.Plugin.CryptoPro;
|
||||
using StellaOps.Cryptography.Plugin.Pkcs11Gost;
|
||||
using StellaOps.Cryptography.Plugin.OpenSslGost;
|
||||
using StellaOps.Cryptography.Plugin.SmSoft;
|
||||
using StellaOps.Cryptography.Plugin.PqSoft;
|
||||
using StellaOps.Cryptography.Plugin.WineCsp;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
@@ -68,6 +70,10 @@ public static class CryptoServiceCollectionExtensions
|
||||
services.TryAddSingleton<ICryptoHash, DefaultCryptoHash>();
|
||||
services.TryAddSingleton<ICryptoHmac, DefaultCryptoHmac>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, SmSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, PqSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, FipsSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, EidasSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, KcmvpHashOnlyProvider>());
|
||||
|
||||
services.TryAddSingleton<ICryptoProviderRegistry>(sp =>
|
||||
{
|
||||
@@ -152,10 +158,12 @@ public static class CryptoServiceCollectionExtensions
|
||||
#endif
|
||||
services.Configure<Pkcs11GostProviderOptions>(baseSection.GetSection("Pkcs11"));
|
||||
services.Configure<OpenSslGostProviderOptions>(baseSection.GetSection("OpenSsl"));
|
||||
services.Configure<WineCspProviderOptions>(baseSection.GetSection("WineCsp"));
|
||||
|
||||
services.AddStellaOpsCrypto(configureRegistry);
|
||||
services.AddOpenSslGostProvider();
|
||||
services.AddPkcs11GostProvider();
|
||||
services.AddWineCspProvider();
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
@@ -178,6 +186,7 @@ public static class CryptoServiceCollectionExtensions
|
||||
{
|
||||
InsertIfMissing(providers, "ru.pkcs11");
|
||||
InsertIfMissing(providers, "ru.openssl.gost");
|
||||
InsertIfMissing(providers, "ru.winecsp.http");
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.Pkcs11Gost\StellaOps.Cryptography.Plugin.Pkcs11Gost.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.OpenSslGost\StellaOps.Cryptography.Plugin.OpenSslGost.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.SmSoft\StellaOps.Cryptography.Plugin.SmSoft.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.PqSoft\StellaOps.Cryptography.Plugin.PqSoft.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.WineCsp\StellaOps.Cryptography.Plugin.WineCsp.csproj" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium;
|
||||
using Org.BouncyCastle.Pqc.Crypto.Falcon;
|
||||
using Org.BouncyCastle.Crypto.Prng;
|
||||
using Org.BouncyCastle.Crypto.Digests;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.Plugin.PqSoft;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
private const string EnvGate = "PQ_SOFT_ALLOWED";
|
||||
|
||||
private static readonly HashSet<string> SupportedAlgorithms = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
SignatureAlgorithms.Dilithium3,
|
||||
SignatureAlgorithms.Falcon512
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
foreach (var key in options.Keys)
|
||||
{
|
||||
TryLoadKeyFromFile(key);
|
||||
}
|
||||
}
|
||||
|
||||
public string Name => "pq.soft";
|
||||
|
||||
public bool Supports(CryptoCapability capability, string algorithmId)
|
||||
{
|
||||
if (!GateEnabled() || string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return capability switch
|
||||
{
|
||||
CryptoCapability.Signing or CryptoCapability.Verification => SupportedAlgorithms.Contains(algorithmId),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
=> throw new NotSupportedException("PQ provider does not expose password hashing.");
|
||||
|
||||
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 = DilithiumParameters.Dilithium3;
|
||||
if (!signingKey.PublicKey.IsEmpty)
|
||||
{
|
||||
var pubFromBytes = new DilithiumPublicKeyParameters(parameters, signingKey.PublicKey.ToArray());
|
||||
var privFromBytes = new DilithiumPrivateKeyParameters(parameters, signingKey.PrivateKey.ToArray(), pubFromBytes);
|
||||
|
||||
var descriptorFromBytes = new CryptoSigningKey(
|
||||
signingKey.Reference,
|
||||
SignatureAlgorithms.Dilithium3,
|
||||
privFromBytes.GetEncoded(),
|
||||
signingKey.CreatedAt,
|
||||
signingKey.ExpiresAt,
|
||||
pubFromBytes.GetEncoded(),
|
||||
signingKey.Metadata);
|
||||
|
||||
return new DilithiumKeyEntry(descriptorFromBytes, privFromBytes, pubFromBytes);
|
||||
}
|
||||
|
||||
var random = CreateSeededRandom(signingKey.PrivateKey);
|
||||
var generator = new DilithiumKeyPairGenerator();
|
||||
generator.Init(new DilithiumKeyGenerationParameters(random, parameters));
|
||||
var pair = generator.GenerateKeyPair();
|
||||
|
||||
var priv = (DilithiumPrivateKeyParameters)pair.Private;
|
||||
var pub = (DilithiumPublicKeyParameters)pair.Public;
|
||||
|
||||
var descriptor = new CryptoSigningKey(
|
||||
signingKey.Reference,
|
||||
SignatureAlgorithms.Dilithium3,
|
||||
priv.GetEncoded(),
|
||||
signingKey.CreatedAt,
|
||||
signingKey.ExpiresAt,
|
||||
pub.GetEncoded(),
|
||||
signingKey.Metadata);
|
||||
|
||||
return new DilithiumKeyEntry(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 DilithiumKeyEntry(
|
||||
CryptoSigningKey Descriptor,
|
||||
DilithiumPrivateKeyParameters PrivateKey,
|
||||
DilithiumPublicKeyParameters PublicKey)
|
||||
: PqKeyEntry(Descriptor, SignatureAlgorithms.Dilithium3)
|
||||
{
|
||||
public override ICryptoSigner CreateSigner() => new DilithiumSignerWrapper(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 DilithiumSignerWrapper : ICryptoSigner
|
||||
{
|
||||
private readonly string keyId;
|
||||
private readonly DilithiumPrivateKeyParameters privateKey;
|
||||
private readonly DilithiumPublicKeyParameters publicKey;
|
||||
|
||||
public DilithiumSignerWrapper(string keyId, DilithiumPrivateKeyParameters privateKey, DilithiumPublicKeyParameters 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 DilithiumSigner();
|
||||
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 DilithiumSigner();
|
||||
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, // 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -15,12 +15,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -124,13 +124,13 @@ public sealed class WineCspHttpProvider : ICryptoProvider, ICryptoProviderDiagno
|
||||
ArgumentNullException.ThrowIfNull(signingKey);
|
||||
|
||||
var entry = new WineCspKeyEntry(
|
||||
signingKey.KeyId,
|
||||
signingKey.Algorithm,
|
||||
signingKey.KeyId,
|
||||
signingKey.Reference.KeyId,
|
||||
signingKey.AlgorithmId,
|
||||
signingKey.Reference.KeyId,
|
||||
null);
|
||||
|
||||
entries[signingKey.KeyId] = entry;
|
||||
logger?.LogDebug("Registered Wine CSP key reference: {KeyId}", signingKey.KeyId);
|
||||
entries[signingKey.Reference.KeyId] = entry;
|
||||
logger?.LogDebug("Registered Wine CSP key reference: {KeyId}", signingKey.Reference.KeyId);
|
||||
}
|
||||
|
||||
public bool RemoveSigningKey(string keyId)
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class PolicyProvidersTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task FipsSoft_Signs_And_Verifies_Es256()
|
||||
{
|
||||
Environment.SetEnvironmentVariable("FIPS_SOFT_ALLOWED", "1");
|
||||
|
||||
var provider = new FipsSoftCryptoProvider();
|
||||
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
||||
var key = new CryptoSigningKey(
|
||||
new CryptoKeyReference("fips-es256"),
|
||||
SignatureAlgorithms.Es256,
|
||||
ecdsa.ExportParameters(true),
|
||||
DateTimeOffset.UtcNow);
|
||||
|
||||
provider.UpsertSigningKey(key);
|
||||
|
||||
var signer = provider.GetSigner(SignatureAlgorithms.Es256, new CryptoKeyReference("fips-es256"));
|
||||
var data = Encoding.UTF8.GetBytes("fips-soft-provider");
|
||||
var signature = await signer.SignAsync(data);
|
||||
|
||||
(await signer.VerifyAsync(data, signature)).Should().BeTrue();
|
||||
provider.GetHasher(HashAlgorithms.Sha256).ComputeHash(data).Length.Should().Be(32);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EidasSoft_Signs_And_Verifies_Es384()
|
||||
{
|
||||
Environment.SetEnvironmentVariable("EIDAS_SOFT_ALLOWED", "1");
|
||||
|
||||
var provider = new EidasSoftCryptoProvider();
|
||||
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP384);
|
||||
var key = new CryptoSigningKey(
|
||||
new CryptoKeyReference("eidas-es384"),
|
||||
SignatureAlgorithms.Es384,
|
||||
ecdsa.ExportParameters(true),
|
||||
DateTimeOffset.UtcNow);
|
||||
|
||||
provider.UpsertSigningKey(key);
|
||||
|
||||
var signer = provider.GetSigner(SignatureAlgorithms.Es384, new CryptoKeyReference("eidas-es384"));
|
||||
var data = Encoding.UTF8.GetBytes("eidas-soft-provider");
|
||||
var signature = await signer.SignAsync(data);
|
||||
|
||||
(await signer.VerifyAsync(data, signature)).Should().BeTrue();
|
||||
provider.GetHasher(HashAlgorithms.Sha384).ComputeHash(data).Length.Should().Be(48);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KcmvpHashOnly_Computes_Hash()
|
||||
{
|
||||
Environment.SetEnvironmentVariable("KCMVP_HASH_ALLOWED", "1");
|
||||
|
||||
var provider = new KcmvpHashOnlyProvider();
|
||||
var data = Encoding.UTF8.GetBytes("kcmvp-hash-only");
|
||||
|
||||
provider.Supports(CryptoCapability.ContentHashing, HashAlgorithms.Sha256).Should().BeTrue();
|
||||
var digest = provider.GetHasher(HashAlgorithms.Sha256).ComputeHash(data);
|
||||
digest.Length.Should().Be(32);
|
||||
|
||||
provider.Invoking(p => p.GetSigner(SignatureAlgorithms.Es256, new CryptoKeyReference("none")))
|
||||
.Should().Throw<NotSupportedException>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium;
|
||||
using Org.BouncyCastle.Pqc.Crypto.Falcon;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.PqSoft;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class PqSoftCryptoProviderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Dilithium3_Signs_And_Verifies()
|
||||
{
|
||||
var provider = CreateProvider();
|
||||
|
||||
var generator = new DilithiumKeyPairGenerator();
|
||||
generator.Init(new DilithiumKeyGenerationParameters(new SecureRandom(), DilithiumParameters.Dilithium3));
|
||||
var keyPair = generator.GenerateKeyPair();
|
||||
|
||||
var priv = ((DilithiumPrivateKeyParameters)keyPair.Private).GetEncoded();
|
||||
var pub = ((DilithiumPublicKeyParameters)keyPair.Public).GetEncoded();
|
||||
|
||||
provider.UpsertSigningKey(new CryptoSigningKey(
|
||||
new CryptoKeyReference("pq-dil3"),
|
||||
SignatureAlgorithms.Dilithium3,
|
||||
priv,
|
||||
DateTimeOffset.UtcNow,
|
||||
publicKey: pub));
|
||||
|
||||
var signer = provider.GetSigner(SignatureAlgorithms.Dilithium3, new CryptoKeyReference("pq-dil3"));
|
||||
var data = Encoding.UTF8.GetBytes("dilithium-soft");
|
||||
|
||||
var signature = await signer.SignAsync(data);
|
||||
(await signer.VerifyAsync(data, signature)).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Falcon512_Signs_And_Verifies()
|
||||
{
|
||||
var provider = CreateProvider();
|
||||
|
||||
var generator = new FalconKeyPairGenerator();
|
||||
generator.Init(new FalconKeyGenerationParameters(new SecureRandom(), FalconParameters.falcon_512));
|
||||
var keyPair = generator.GenerateKeyPair();
|
||||
|
||||
var priv = ((FalconPrivateKeyParameters)keyPair.Private).GetEncoded();
|
||||
var pub = ((FalconPublicKeyParameters)keyPair.Public).GetEncoded();
|
||||
|
||||
provider.UpsertSigningKey(new CryptoSigningKey(
|
||||
new CryptoKeyReference("pq-falcon"),
|
||||
SignatureAlgorithms.Falcon512,
|
||||
priv,
|
||||
DateTimeOffset.UtcNow,
|
||||
publicKey: pub));
|
||||
|
||||
var signer = provider.GetSigner(SignatureAlgorithms.Falcon512, new CryptoKeyReference("pq-falcon"));
|
||||
var data = Encoding.UTF8.GetBytes("falcon-soft");
|
||||
|
||||
var signature = await signer.SignAsync(data);
|
||||
(await signer.VerifyAsync(data, signature)).Should().BeTrue();
|
||||
}
|
||||
|
||||
private static PqSoftCryptoProvider CreateProvider()
|
||||
{
|
||||
var options = Options.Create(new PqSoftProviderOptions
|
||||
{
|
||||
RequireEnvironmentGate = false
|
||||
});
|
||||
|
||||
return new PqSoftCryptoProvider(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.PqSoft\StellaOps.Cryptography.Plugin.PqSoft.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// EC signing provider with an explicit allow-list for compliance profiles (FIPS/eIDAS).
|
||||
/// </summary>
|
||||
public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnostics
|
||||
{
|
||||
private readonly string name;
|
||||
private readonly HashSet<string> signingAlgorithms;
|
||||
private readonly HashSet<string> hashAlgorithms;
|
||||
private readonly string? gateEnv;
|
||||
|
||||
private readonly ConcurrentDictionary<string, CryptoSigningKey> signingKeys = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public EcdsaPolicyCryptoProvider(
|
||||
string name,
|
||||
IEnumerable<string> signingAlgorithms,
|
||||
IEnumerable<string> hashAlgorithms,
|
||||
string? gateEnv = null)
|
||||
{
|
||||
this.name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
this.signingAlgorithms = new HashSet<string>(signingAlgorithms ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
this.hashAlgorithms = new HashSet<string>(hashAlgorithms ?? Array.Empty<string>(), StringComparer.OrdinalIgnoreCase);
|
||||
this.gateEnv = string.IsNullOrWhiteSpace(gateEnv) ? null : gateEnv;
|
||||
|
||||
if (this.signingAlgorithms.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one signing algorithm must be supplied.", nameof(signingAlgorithms));
|
||||
}
|
||||
|
||||
if (this.hashAlgorithms.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one hash algorithm must be supplied.", nameof(hashAlgorithms));
|
||||
}
|
||||
}
|
||||
|
||||
public string Name => name;
|
||||
|
||||
public bool Supports(CryptoCapability capability, string algorithmId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(algorithmId) || !GateEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return capability switch
|
||||
{
|
||||
CryptoCapability.Signing or CryptoCapability.Verification => signingAlgorithms.Contains(algorithmId),
|
||||
CryptoCapability.ContentHashing => hashAlgorithms.Contains(algorithmId),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
=> throw new NotSupportedException($"Provider '{Name}' does not expose password hashing.");
|
||||
|
||||
public ICryptoHasher GetHasher(string algorithmId)
|
||||
{
|
||||
EnsureHashSupported(algorithmId);
|
||||
return new DefaultCryptoHasher(NormalizeHash(algorithmId));
|
||||
}
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
||||
{
|
||||
EnsureSigningSupported(algorithmId);
|
||||
ArgumentNullException.ThrowIfNull(keyReference);
|
||||
|
||||
if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey))
|
||||
{
|
||||
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
|
||||
}
|
||||
|
||||
if (!string.Equals(signingKey.AlgorithmId, NormalizeAlg(algorithmId), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'.");
|
||||
}
|
||||
|
||||
return EcdsaSigner.Create(signingKey);
|
||||
}
|
||||
|
||||
public void UpsertSigningKey(CryptoSigningKey signingKey)
|
||||
{
|
||||
EnsureSigningSupported(signingKey?.AlgorithmId ?? string.Empty);
|
||||
ArgumentNullException.ThrowIfNull(signingKey);
|
||||
|
||||
if (signingKey.Kind != CryptoSigningKeyKind.Ec)
|
||||
{
|
||||
throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys.");
|
||||
}
|
||||
|
||||
ValidateCurve(signingKey.AlgorithmId, signingKey.PrivateParameters);
|
||||
signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey);
|
||||
}
|
||||
|
||||
public bool RemoveSigningKey(string keyId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(keyId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return signingKeys.TryRemove(keyId, out _);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
|
||||
=> signingKeys.Values.ToArray();
|
||||
|
||||
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys()
|
||||
{
|
||||
foreach (var key in signingKeys.Values)
|
||||
{
|
||||
yield return new CryptoProviderKeyDescriptor(
|
||||
Name,
|
||||
key.Reference.KeyId,
|
||||
key.AlgorithmId,
|
||||
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["curve"] = ResolveCurve(key.AlgorithmId),
|
||||
["profile"] = Name,
|
||||
["certified"] = "false"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bool GateEnabled()
|
||||
{
|
||||
if (gateEnv is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var value = Environment.GetEnvironmentVariable(gateEnv);
|
||||
return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void EnsureSigningSupported(string algorithmId)
|
||||
{
|
||||
if (!Supports(CryptoCapability.Signing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureHashSupported(string algorithmId)
|
||||
{
|
||||
if (!Supports(CryptoCapability.ContentHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Hash algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeAlg(string algorithmId) => algorithmId.ToUpperInvariant();
|
||||
|
||||
private static string NormalizeHash(string algorithmId) => algorithmId.ToUpperInvariant();
|
||||
|
||||
private static void ValidateCurve(string algorithmId, ECParameters parameters)
|
||||
{
|
||||
var expectedCurve = ResolveCurve(algorithmId);
|
||||
var oid = parameters.Curve.Oid?.Value ?? string.Empty;
|
||||
|
||||
var matches = expectedCurve switch
|
||||
{
|
||||
JsonWebKeyECTypes.P256 => string.Equals(oid, ECCurve.NamedCurves.nistP256.Oid.Value, StringComparison.Ordinal),
|
||||
JsonWebKeyECTypes.P384 => string.Equals(oid, ECCurve.NamedCurves.nistP384.Oid.Value, StringComparison.Ordinal),
|
||||
JsonWebKeyECTypes.P521 => string.Equals(oid, ECCurve.NamedCurves.nistP521.Oid.Value, StringComparison.Ordinal),
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (!matches)
|
||||
{
|
||||
throw new InvalidOperationException($"Signing key curve mismatch. Expected curve '{expectedCurve}' for algorithm '{algorithmId}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveCurve(string algorithmId)
|
||||
=> algorithmId.ToUpperInvariant() switch
|
||||
{
|
||||
SignatureAlgorithms.Es256 => JsonWebKeyECTypes.P256,
|
||||
SignatureAlgorithms.Es384 => JsonWebKeyECTypes.P384,
|
||||
SignatureAlgorithms.Es512 => JsonWebKeyECTypes.P521,
|
||||
_ => throw new InvalidOperationException($"Unsupported ECDSA curve mapping for algorithm '{algorithmId}'.")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FIPS-compatible ECDSA provider (software-only, non-certified).
|
||||
/// </summary>
|
||||
public sealed class FipsSoftCryptoProvider : EcdsaPolicyCryptoProvider
|
||||
{
|
||||
public FipsSoftCryptoProvider()
|
||||
: base(
|
||||
name: "fips.ecdsa.soft",
|
||||
signingAlgorithms: new[] { SignatureAlgorithms.Es256, SignatureAlgorithms.Es384, SignatureAlgorithms.Es512 },
|
||||
hashAlgorithms: new[] { HashAlgorithms.Sha256, HashAlgorithms.Sha384, HashAlgorithms.Sha512 },
|
||||
gateEnv: "FIPS_SOFT_ALLOWED")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// eIDAS-compatible ECDSA provider (software-only, non-certified, QSCD not enforced).
|
||||
/// </summary>
|
||||
public sealed class EidasSoftCryptoProvider : EcdsaPolicyCryptoProvider
|
||||
{
|
||||
public EidasSoftCryptoProvider()
|
||||
: base(
|
||||
name: "eu.eidas.soft",
|
||||
signingAlgorithms: new[] { SignatureAlgorithms.Es256, SignatureAlgorithms.Es384 },
|
||||
hashAlgorithms: new[] { HashAlgorithms.Sha256, HashAlgorithms.Sha384 },
|
||||
gateEnv: "EIDAS_SOFT_ALLOWED")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash-only provider for KCMVP baseline (software-only, non-certified).
|
||||
/// </summary>
|
||||
public sealed class KcmvpHashOnlyProvider : ICryptoProvider
|
||||
{
|
||||
private const string GateEnv = "KCMVP_HASH_ALLOWED";
|
||||
|
||||
public string Name => "kr.kcmvp.hash";
|
||||
|
||||
public bool Supports(CryptoCapability capability, string algorithmId)
|
||||
{
|
||||
if (!GateEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return capability == CryptoCapability.ContentHashing &&
|
||||
string.Equals(algorithmId, HashAlgorithms.Sha256, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
=> throw new NotSupportedException("KCMVP hash provider does not expose password hashing.");
|
||||
|
||||
public ICryptoHasher GetHasher(string algorithmId)
|
||||
{
|
||||
if (!Supports(CryptoCapability.ContentHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Hash algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
}
|
||||
|
||||
return new DefaultCryptoHasher(HashAlgorithms.Sha256);
|
||||
}
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
||||
=> throw new NotSupportedException("KCMVP hash-only provider does not expose signing.");
|
||||
|
||||
public void UpsertSigningKey(CryptoSigningKey signingKey)
|
||||
=> throw new NotSupportedException("KCMVP hash-only provider does not manage signing keys.");
|
||||
|
||||
public bool RemoveSigningKey(string keyId) => false;
|
||||
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys() => Array.Empty<CryptoSigningKey>();
|
||||
|
||||
private static bool GateEnabled()
|
||||
{
|
||||
var value = Environment.GetEnvironmentVariable(GateEnv);
|
||||
return string.IsNullOrEmpty(value) ||
|
||||
string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,6 @@ public static class SignatureAlgorithms
|
||||
public const string GostR3410_2012_256 = "GOST12-256";
|
||||
public const string GostR3410_2012_512 = "GOST12-512";
|
||||
public const string Sm2 = "SM2";
|
||||
public const string Dilithium3 = "DILITHIUM3";
|
||||
public const string Falcon512 = "FALCON512";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user