5100* tests strengthtenen work

This commit is contained in:
StellaOps Bot
2025-12-24 12:38:34 +02:00
parent 9a08d10b89
commit 02772c7a27
117 changed files with 29941 additions and 66 deletions

View File

@@ -0,0 +1,457 @@
// -----------------------------------------------------------------------------
// BouncyCastleCapabilityDetectionTests.cs
// Sprint: SPRINT_5100_0009_0006 - Signer Module Test Implementation
// Task: SIGNER-5100-004 - Add capability detection tests for BouncyCastle plugin: enumerate supported algorithms
// Description: Capability detection and algorithm enumeration tests for BouncyCastle crypto plugin
// -----------------------------------------------------------------------------
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Cryptography.DependencyInjection;
using StellaOps.Cryptography.Plugin.BouncyCastle;
using Xunit;
using Xunit.Abstractions;
namespace StellaOps.Cryptography.Tests;
/// <summary>
/// Capability detection tests for the BouncyCastle Ed25519 crypto provider.
/// Validates:
/// - Supported algorithm enumeration
/// - Capability declaration (Signing, Verification)
/// - Unsupported capability rejection
/// - Provider identity and naming
/// </summary>
[Trait("Category", "CryptoPlugin")]
[Trait("Category", "BouncyCastle")]
[Trait("Category", "C1")]
public sealed class BouncyCastleCapabilityDetectionTests
{
private readonly ITestOutputHelper _output;
private readonly BouncyCastleEd25519CryptoProvider _provider;
public BouncyCastleCapabilityDetectionTests(ITestOutputHelper output)
{
_output = output;
_provider = new BouncyCastleEd25519CryptoProvider();
}
#region Provider Identity Tests
[Fact]
public void Provider_Name_IsExpected()
{
_provider.Name.Should().Be("bouncycastle.ed25519");
_output.WriteLine($"✓ Provider name: {_provider.Name}");
}
[Fact]
public void Provider_CanBeResolvedFromDI()
{
// Arrange
var configuration = new ConfigurationBuilder().Build();
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddStellaOpsCrypto();
services.AddBouncyCastleEd25519Provider();
using var provider = services.BuildServiceProvider();
// Act
var cryptoProviders = provider.GetServices<ICryptoProvider>().ToList();
var bcProvider = cryptoProviders.OfType<BouncyCastleEd25519CryptoProvider>().FirstOrDefault();
// Assert
bcProvider.Should().NotBeNull();
bcProvider!.Name.Should().Be("bouncycastle.ed25519");
_output.WriteLine("✓ BouncyCastle provider resolved from DI");
}
#endregion
#region Signing Capability Tests
[Fact]
public void Supports_Signing_Ed25519_ReturnsTrue()
{
_provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Ed25519)
.Should().BeTrue();
_output.WriteLine("✓ Supports Signing/Ed25519");
}
[Fact]
public void Supports_Signing_EdDsa_ReturnsTrue()
{
_provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.EdDsa)
.Should().BeTrue();
_output.WriteLine("✓ Supports Signing/EdDSA");
}
[Theory]
[InlineData("ed25519")]
[InlineData("ED25519")]
[InlineData("Ed25519")]
[InlineData("eddsa")]
[InlineData("EDDSA")]
[InlineData("EdDSA")]
public void Supports_Signing_CaseInsensitive(string algorithm)
{
_provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Case-insensitive match: {algorithm}");
}
#endregion
#region Verification Capability Tests
[Fact]
public void Supports_Verification_Ed25519_ReturnsTrue()
{
_provider.Supports(CryptoCapability.Verification, SignatureAlgorithms.Ed25519)
.Should().BeTrue();
_output.WriteLine("✓ Supports Verification/Ed25519");
}
[Fact]
public void Supports_Verification_EdDsa_ReturnsTrue()
{
_provider.Supports(CryptoCapability.Verification, SignatureAlgorithms.EdDsa)
.Should().BeTrue();
_output.WriteLine("✓ Supports Verification/EdDSA");
}
#endregion
#region Unsupported Algorithm Tests
[Theory]
[InlineData(SignatureAlgorithms.Es256)]
[InlineData(SignatureAlgorithms.Es384)]
[InlineData(SignatureAlgorithms.Es512)]
[InlineData(SignatureAlgorithms.GostR3410_2012_256)]
[InlineData(SignatureAlgorithms.GostR3410_2012_512)]
[InlineData(SignatureAlgorithms.Sm2)]
[InlineData(SignatureAlgorithms.Dilithium3)]
[InlineData(SignatureAlgorithms.Falcon512)]
[InlineData("RS256")]
[InlineData("RS384")]
[InlineData("RS512")]
[InlineData("PS256")]
[InlineData("HS256")]
public void Supports_Signing_UnsupportedAlgorithm_ReturnsFalse(string algorithm)
{
_provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeFalse($"because {algorithm} is not supported by BouncyCastle Ed25519 provider");
_output.WriteLine($"✓ Correctly rejects unsupported algorithm: {algorithm}");
}
[Fact]
public void Supports_Signing_NullAlgorithm_ReturnsFalse()
{
_provider.Supports(CryptoCapability.Signing, null!)
.Should().BeFalse();
_output.WriteLine("✓ Null algorithm returns false");
}
[Fact]
public void Supports_Signing_EmptyAlgorithm_ReturnsFalse()
{
_provider.Supports(CryptoCapability.Signing, string.Empty)
.Should().BeFalse();
_output.WriteLine("✓ Empty algorithm returns false");
}
[Fact]
public void Supports_Signing_WhitespaceAlgorithm_ReturnsFalse()
{
_provider.Supports(CryptoCapability.Signing, " ")
.Should().BeFalse();
_output.WriteLine("✓ Whitespace algorithm returns false");
}
#endregion
#region Unsupported Capability Tests
[Fact]
public void Supports_PasswordHashing_ReturnsFalse()
{
_provider.Supports(CryptoCapability.PasswordHashing, SignatureAlgorithms.Ed25519)
.Should().BeFalse();
_output.WriteLine("✓ PasswordHashing capability not supported");
}
[Fact]
public void Supports_SymmetricEncryption_ReturnsFalse()
{
_provider.Supports(CryptoCapability.SymmetricEncryption, SignatureAlgorithms.Ed25519)
.Should().BeFalse();
_output.WriteLine("✓ SymmetricEncryption capability not supported");
}
[Fact]
public void Supports_KeyDerivation_ReturnsFalse()
{
_provider.Supports(CryptoCapability.KeyDerivation, SignatureAlgorithms.Ed25519)
.Should().BeFalse();
_output.WriteLine("✓ KeyDerivation capability not supported");
}
[Fact]
public void Supports_ContentHashing_ReturnsFalse()
{
_provider.Supports(CryptoCapability.ContentHashing, SignatureAlgorithms.Ed25519)
.Should().BeFalse();
_output.WriteLine("✓ ContentHashing capability not supported");
}
#endregion
#region Hasher Capability Tests
[Fact]
public void GetHasher_ThrowsNotSupported()
{
Action act = () => _provider.GetHasher("SHA-256");
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not expose hashing capabilities*");
_output.WriteLine("✓ GetHasher throws NotSupportedException");
}
[Fact]
public void GetPasswordHasher_ThrowsNotSupported()
{
Action act = () => _provider.GetPasswordHasher("argon2id");
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not expose password hashing capabilities*");
_output.WriteLine("✓ GetPasswordHasher throws NotSupportedException");
}
#endregion
#region Signing Key Management Tests
[Fact]
public void GetSigningKeys_InitiallyEmpty()
{
var provider = new BouncyCastleEd25519CryptoProvider();
provider.GetSigningKeys().Should().BeEmpty();
_output.WriteLine("✓ GetSigningKeys returns empty collection initially");
}
[Fact]
public void UpsertSigningKey_AddsKey()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyId = "test-key-001";
var privateKeyBytes = Enumerable.Range(0, 32).Select(i => (byte)i).ToArray();
var keyReference = new CryptoKeyReference(keyId, provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
privateKeyBytes,
createdAt: DateTimeOffset.UtcNow);
// Act
provider.UpsertSigningKey(signingKey);
// Assert
provider.GetSigningKeys().Should().HaveCount(1);
provider.GetSigningKeys().Single().Reference.KeyId.Should().Be(keyId);
_output.WriteLine("✓ UpsertSigningKey adds key to collection");
}
[Fact]
public void UpsertSigningKey_UnsupportedAlgorithm_Throws()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Es256, // Not supported
new byte[32],
createdAt: DateTimeOffset.UtcNow);
// Act
Action act = () => provider.UpsertSigningKey(signingKey);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*not supported*");
_output.WriteLine("✓ UpsertSigningKey rejects unsupported algorithm");
}
[Fact]
public void RemoveSigningKey_RemovesExistingKey()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyId = "key-to-remove";
var privateKeyBytes = Enumerable.Range(0, 32).Select(i => (byte)i).ToArray();
var keyReference = new CryptoKeyReference(keyId, provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
privateKeyBytes,
createdAt: DateTimeOffset.UtcNow);
provider.UpsertSigningKey(signingKey);
provider.GetSigningKeys().Should().HaveCount(1);
// Act
var result = provider.RemoveSigningKey(keyId);
// Assert
result.Should().BeTrue();
provider.GetSigningKeys().Should().BeEmpty();
_output.WriteLine("✓ RemoveSigningKey removes existing key");
}
[Fact]
public void RemoveSigningKey_NonExistentKey_ReturnsFalse()
{
var provider = new BouncyCastleEd25519CryptoProvider();
var result = provider.RemoveSigningKey("nonexistent");
result.Should().BeFalse();
_output.WriteLine("✓ RemoveSigningKey returns false for non-existent key");
}
#endregion
#region Signer Retrieval Tests
[Fact]
public void GetSigner_UnregisteredKey_ThrowsKeyNotFound()
{
// Arrange
var keyReference = new CryptoKeyReference("unregistered-key", _provider.Name);
// Act
Action act = () => _provider.GetSigner(SignatureAlgorithms.Ed25519, keyReference);
// Assert
act.Should().Throw<KeyNotFoundException>()
.WithMessage("*not registered*");
_output.WriteLine("✓ GetSigner throws KeyNotFoundException for unregistered key");
}
[Fact]
public void GetSigner_UnsupportedAlgorithm_ThrowsInvalidOperation()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyId = "test-key";
var privateKeyBytes = Enumerable.Range(0, 32).Select(i => (byte)i).ToArray();
var keyReference = new CryptoKeyReference(keyId, provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
privateKeyBytes,
createdAt: DateTimeOffset.UtcNow);
provider.UpsertSigningKey(signingKey);
// Act
Action act = () => provider.GetSigner(SignatureAlgorithms.Es256, keyReference);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*not supported*");
_output.WriteLine("✓ GetSigner throws InvalidOperationException for unsupported algorithm");
}
[Fact]
public void GetSigner_NullAlgorithm_ThrowsArgumentException()
{
var keyReference = new CryptoKeyReference("test-key", _provider.Name);
Action act = () => _provider.GetSigner(null!, keyReference);
act.Should().Throw<ArgumentException>();
_output.WriteLine("✓ GetSigner throws ArgumentException for null algorithm");
}
[Fact]
public void GetSigner_NullKeyReference_ThrowsArgumentNullException()
{
Action act = () => _provider.GetSigner(SignatureAlgorithms.Ed25519, null!);
act.Should().Throw<ArgumentNullException>();
_output.WriteLine("✓ GetSigner throws ArgumentNullException for null key reference");
}
#endregion
#region Algorithm Summary
[Fact]
public void AlgorithmSummary_DocumentsSupportedAlgorithms()
{
// This test documents the supported algorithms for reference
var supportedForSigning = new[]
{
SignatureAlgorithms.Ed25519,
SignatureAlgorithms.EdDsa
};
foreach (var algo in supportedForSigning)
{
_provider.Supports(CryptoCapability.Signing, algo).Should().BeTrue();
_provider.Supports(CryptoCapability.Verification, algo).Should().BeTrue();
}
_output.WriteLine("=== BouncyCastle Ed25519 Provider Algorithm Summary ===");
_output.WriteLine("Supported for Signing:");
foreach (var algo in supportedForSigning)
{
_output.WriteLine($" - {algo}");
}
_output.WriteLine("Supported for Verification:");
foreach (var algo in supportedForSigning)
{
_output.WriteLine($" - {algo}");
}
_output.WriteLine("Unsupported Capabilities:");
_output.WriteLine(" - PasswordHashing");
_output.WriteLine(" - ContentHashing");
_output.WriteLine(" - SymmetricEncryption");
_output.WriteLine(" - KeyDerivation");
}
#endregion
}

View File

@@ -0,0 +1,431 @@
// -----------------------------------------------------------------------------
// BouncyCastleErrorClassificationTests.cs
// Sprint: SPRINT_5100_0009_0006 - Signer Module Test Implementation
// Task: SIGNER-5100-006 - Add error classification tests for BouncyCastle: key not present → deterministic error code
// Description: Error classification tests for BouncyCastle Ed25519 crypto plugin
// -----------------------------------------------------------------------------
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Cryptography.DependencyInjection;
using StellaOps.Cryptography.Plugin.BouncyCastle;
using Xunit;
using Xunit.Abstractions;
namespace StellaOps.Cryptography.Tests;
/// <summary>
/// Error classification tests for the BouncyCastle Ed25519 crypto provider.
/// Validates that specific error conditions produce deterministic exception types
/// that can be reliably mapped to error codes.
/// </summary>
[Trait("Category", "CryptoPlugin")]
[Trait("Category", "BouncyCastle")]
[Trait("Category", "Errors")]
[Trait("Category", "C1")]
public sealed class BouncyCastleErrorClassificationTests
{
private readonly ITestOutputHelper _output;
public BouncyCastleErrorClassificationTests(ITestOutputHelper output)
{
_output = output;
}
#region Key Not Present Errors
[Fact]
public void GetSigner_KeyNotRegistered_ThrowsKeyNotFoundException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("nonexistent-key", provider.Name);
// Act
Action act = () => provider.GetSigner(SignatureAlgorithms.Ed25519, keyReference);
// Assert
var exception = act.Should().Throw<KeyNotFoundException>()
.WithMessage("*not registered*");
_output.WriteLine($"✓ Key not found → KeyNotFoundException");
_output.WriteLine($" Error code mapping: CRYPTO_KEY_NOT_FOUND");
}
[Fact]
public void GetSigner_DifferentKeyId_ThrowsKeyNotFoundException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var registeredKeyId = "registered-key";
var requestedKeyId = "different-key";
RegisterKey(provider, registeredKeyId);
var keyReference = new CryptoKeyReference(requestedKeyId, provider.Name);
// Act
Action act = () => provider.GetSigner(SignatureAlgorithms.Ed25519, keyReference);
// Assert
act.Should().Throw<KeyNotFoundException>();
_output.WriteLine("✓ Different key ID → KeyNotFoundException");
}
[Fact]
public void GetSigner_AfterKeyRemoval_ThrowsKeyNotFoundException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyId = "temporary-key";
RegisterKey(provider, keyId);
// Verify key works initially
var keyReference = new CryptoKeyReference(keyId, provider.Name);
var signer = provider.GetSigner(SignatureAlgorithms.Ed25519, keyReference);
signer.Should().NotBeNull();
// Remove the key
provider.RemoveSigningKey(keyId);
// Act
Action act = () => provider.GetSigner(SignatureAlgorithms.Ed25519, keyReference);
// Assert
act.Should().Throw<KeyNotFoundException>();
_output.WriteLine("✓ Key removed → KeyNotFoundException");
}
#endregion
#region Algorithm Not Supported Errors
[Fact]
public void GetSigner_UnsupportedAlgorithm_ThrowsInvalidOperationException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyId = "test-key";
RegisterKey(provider, keyId);
var keyReference = new CryptoKeyReference(keyId, provider.Name);
// Act
Action act = () => provider.GetSigner(SignatureAlgorithms.Es256, keyReference);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*not supported*");
_output.WriteLine("✓ Unsupported algorithm → InvalidOperationException");
_output.WriteLine(" Error code mapping: CRYPTO_ALGORITHM_NOT_SUPPORTED");
}
[Theory]
[InlineData("RS256")]
[InlineData("RS384")]
[InlineData("RS512")]
[InlineData("PS256")]
[InlineData("HS256")]
[InlineData("ES256")]
[InlineData("ES384")]
[InlineData("UNKNOWN")]
public void GetSigner_VariousUnsupportedAlgorithms_ThrowsInvalidOperationException(string algorithm)
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyId = "test-key";
RegisterKey(provider, keyId);
var keyReference = new CryptoKeyReference(keyId, provider.Name);
// Act
Action act = () => provider.GetSigner(algorithm, keyReference);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*not supported*");
_output.WriteLine($"✓ Algorithm '{algorithm}' → InvalidOperationException");
}
[Fact]
public void UpsertSigningKey_UnsupportedAlgorithm_ThrowsInvalidOperationException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Es256, // Not supported
new byte[32],
createdAt: DateTimeOffset.UtcNow);
// Act
Action act = () => provider.UpsertSigningKey(signingKey);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*not supported*");
_output.WriteLine("✓ UpsertSigningKey with unsupported algorithm → InvalidOperationException");
}
#endregion
#region Invalid Key Material Errors
[Theory]
[InlineData(0)]
[InlineData(16)]
[InlineData(31)]
[InlineData(33)]
[InlineData(65)]
[InlineData(128)]
public void UpsertSigningKey_InvalidPrivateKeyLength_ThrowsInvalidOperationException(int keyLength)
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
new byte[keyLength],
createdAt: DateTimeOffset.UtcNow);
// Act
Action act = () => provider.UpsertSigningKey(signingKey);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*32 or 64 bytes*");
_output.WriteLine($"✓ Private key length {keyLength} → InvalidOperationException");
_output.WriteLine(" Error code mapping: CRYPTO_INVALID_KEY_MATERIAL");
}
[Fact]
public void UpsertSigningKey_InvalidPublicKeyLength_ThrowsInvalidOperationException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
new byte[32], // Valid private key
createdAt: DateTimeOffset.UtcNow,
publicKey: new byte[16]); // Invalid public key
// Act
Action act = () => provider.UpsertSigningKey(signingKey);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*32 bytes*");
_output.WriteLine("✓ Invalid public key length → InvalidOperationException");
}
#endregion
#region Argument Validation Errors
[Fact]
public void GetSigner_NullAlgorithm_ThrowsArgumentException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
// Act
Action act = () => provider.GetSigner(null!, keyReference);
// Assert
act.Should().Throw<ArgumentException>();
_output.WriteLine("✓ Null algorithm → ArgumentException");
_output.WriteLine(" Error code mapping: CRYPTO_INVALID_ARGUMENT");
}
[Fact]
public void GetSigner_EmptyAlgorithm_ThrowsArgumentException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
// Act
Action act = () => provider.GetSigner(string.Empty, keyReference);
// Assert
act.Should().Throw<ArgumentException>();
_output.WriteLine("✓ Empty algorithm → ArgumentException");
}
[Fact]
public void GetSigner_WhitespaceAlgorithm_ThrowsArgumentException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
// Act
Action act = () => provider.GetSigner(" ", keyReference);
// Assert
act.Should().Throw<ArgumentException>();
_output.WriteLine("✓ Whitespace algorithm → ArgumentException");
}
[Fact]
public void GetSigner_NullKeyReference_ThrowsArgumentNullException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
// Act
Action act = () => provider.GetSigner(SignatureAlgorithms.Ed25519, null!);
// Assert
act.Should().Throw<ArgumentNullException>();
_output.WriteLine("✓ Null key reference → ArgumentNullException");
}
[Fact]
public void UpsertSigningKey_NullSigningKey_ThrowsArgumentNullException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
// Act
Action act = () => provider.UpsertSigningKey(null!);
// Assert
act.Should().Throw<ArgumentNullException>();
_output.WriteLine("✓ Null signing key → ArgumentNullException");
}
[Fact]
public void RemoveSigningKey_NullKeyId_ReturnsFalse()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
// Act
var result = provider.RemoveSigningKey(null!);
// Assert
result.Should().BeFalse();
_output.WriteLine("✓ Null key ID → returns false (graceful)");
}
[Fact]
public void RemoveSigningKey_EmptyKeyId_ReturnsFalse()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
// Act
var result = provider.RemoveSigningKey(string.Empty);
// Assert
result.Should().BeFalse();
_output.WriteLine("✓ Empty key ID → returns false (graceful)");
}
#endregion
#region Capability Not Supported Errors
[Fact]
public void GetHasher_ThrowsNotSupportedException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
// Act
Action act = () => provider.GetHasher("SHA-256");
// Assert
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not expose hashing capabilities*");
_output.WriteLine("✓ GetHasher → NotSupportedException");
_output.WriteLine(" Error code mapping: CRYPTO_CAPABILITY_NOT_SUPPORTED");
}
[Fact]
public void GetPasswordHasher_ThrowsNotSupportedException()
{
// Arrange
var provider = new BouncyCastleEd25519CryptoProvider();
// Act
Action act = () => provider.GetPasswordHasher("argon2id");
// Assert
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not expose password hashing capabilities*");
_output.WriteLine("✓ GetPasswordHasher → NotSupportedException");
}
#endregion
#region Error Code Mapping Summary
[Fact]
public void ErrorCodeMappingSummary_DocumentsExpectedMappings()
{
// This test documents the expected error code mappings for operators
var mappings = new Dictionary<Type, string>
{
{ typeof(KeyNotFoundException), "CRYPTO_KEY_NOT_FOUND" },
{ typeof(InvalidOperationException), "CRYPTO_INVALID_OPERATION" },
{ typeof(ArgumentNullException), "CRYPTO_INVALID_ARGUMENT" },
{ typeof(ArgumentException), "CRYPTO_INVALID_ARGUMENT" },
{ typeof(NotSupportedException), "CRYPTO_CAPABILITY_NOT_SUPPORTED" }
};
_output.WriteLine("=== BouncyCastle Error Code Mapping Reference ===");
foreach (var mapping in mappings)
{
_output.WriteLine($" {mapping.Key.Name} → {mapping.Value}");
}
_output.WriteLine("");
_output.WriteLine("Specific Error Codes:");
_output.WriteLine(" CRYPTO_KEY_NOT_FOUND - Key ID not registered with provider");
_output.WriteLine(" CRYPTO_ALGORITHM_NOT_SUPPORTED - Algorithm not supported by this provider");
_output.WriteLine(" CRYPTO_INVALID_KEY_MATERIAL - Key bytes have wrong length/format");
_output.WriteLine(" CRYPTO_INVALID_ARGUMENT - Null or invalid parameter");
_output.WriteLine(" CRYPTO_CAPABILITY_NOT_SUPPORTED - Provider doesn't support capability");
mappings.Should().NotBeEmpty();
}
#endregion
#region Helper Methods
private static void RegisterKey(BouncyCastleEd25519CryptoProvider provider, string keyId)
{
var privateKeyBytes = Enumerable.Range(0, 32).Select(i => (byte)i).ToArray();
var keyReference = new CryptoKeyReference(keyId, provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
privateKeyBytes,
createdAt: DateTimeOffset.UtcNow);
provider.UpsertSigningKey(signingKey);
}
#endregion
}

View File

@@ -0,0 +1,406 @@
// -----------------------------------------------------------------------------
// BouncyCastleSignVerifyRoundtripTests.cs
// Sprint: SPRINT_5100_0009_0006 - Signer Module Test Implementation
// Task: SIGNER-5100-005 - Add sign/verify roundtrip tests for BouncyCastle: sign with private key → verify with public key
// Description: Sign/verify roundtrip tests for BouncyCastle Ed25519 crypto plugin
// -----------------------------------------------------------------------------
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Cryptography.DependencyInjection;
using StellaOps.Cryptography.Plugin.BouncyCastle;
using System.Security.Cryptography;
using System.Text;
using Xunit;
using Xunit.Abstractions;
namespace StellaOps.Cryptography.Tests;
/// <summary>
/// Sign/verify roundtrip tests for the BouncyCastle Ed25519 crypto provider.
/// Validates:
/// - Messages signed with private key can be verified with public key
/// - Tampered messages fail verification
/// - Different key pairs produce different signatures
/// - Large message handling
/// - Edge cases (empty message, single byte, etc.)
/// </summary>
[Trait("Category", "CryptoPlugin")]
[Trait("Category", "BouncyCastle")]
[Trait("Category", "SignVerify")]
[Trait("Category", "C1")]
public sealed class BouncyCastleSignVerifyRoundtripTests
{
private readonly ITestOutputHelper _output;
public BouncyCastleSignVerifyRoundtripTests(ITestOutputHelper output)
{
_output = output;
}
#region Basic Roundtrip Tests
[Fact]
public async Task SignAndVerify_BasicMessage_Succeeds()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-basic");
var message = Encoding.UTF8.GetBytes("Hello, StellaOps!");
// Act
var signature = await signer.SignAsync(message);
var verified = await signer.VerifyAsync(message, signature);
// Assert
verified.Should().BeTrue();
signature.Should().HaveCount(64, "Ed25519 signatures are 64 bytes");
_output.WriteLine("✓ Basic message sign/verify roundtrip succeeded");
}
[Fact]
public async Task SignAndVerify_EmptyMessage_Succeeds()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-empty");
var message = Array.Empty<byte>();
// Act
var signature = await signer.SignAsync(message);
var verified = await signer.VerifyAsync(message, signature);
// Assert
verified.Should().BeTrue();
_output.WriteLine("✓ Empty message sign/verify roundtrip succeeded");
}
[Fact]
public async Task SignAndVerify_SingleByte_Succeeds()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-single");
var message = new byte[] { 0x42 };
// Act
var signature = await signer.SignAsync(message);
var verified = await signer.VerifyAsync(message, signature);
// Assert
verified.Should().BeTrue();
_output.WriteLine("✓ Single byte message sign/verify roundtrip succeeded");
}
[Fact]
public async Task SignAndVerify_LargeMessage_Succeeds()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-large");
var message = new byte[1_000_000]; // 1 MB
RandomNumberGenerator.Fill(message);
// Act
var signature = await signer.SignAsync(message);
var verified = await signer.VerifyAsync(message, signature);
// Assert
verified.Should().BeTrue();
_output.WriteLine("✓ Large (1 MB) message sign/verify roundtrip succeeded");
}
[Fact]
public async Task SignAndVerify_BinaryData_Succeeds()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-binary");
var message = new byte[] { 0x00, 0xFF, 0x7F, 0x80, 0x01, 0xFE };
// Act
var signature = await signer.SignAsync(message);
var verified = await signer.VerifyAsync(message, signature);
// Assert
verified.Should().BeTrue();
_output.WriteLine("✓ Binary data sign/verify roundtrip succeeded");
}
#endregion
#region Tampering Detection Tests
[Fact]
public async Task Verify_TamperedMessage_ReturnsFalse()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-tamper-msg");
var message = Encoding.UTF8.GetBytes("Original message");
var tamperedMessage = Encoding.UTF8.GetBytes("Tampered message");
// Act
var signature = await signer.SignAsync(message);
var verified = await signer.VerifyAsync(tamperedMessage, signature);
// Assert
verified.Should().BeFalse();
_output.WriteLine("✓ Tampered message correctly rejected");
}
[Fact]
public async Task Verify_TamperedSignature_ReturnsFalse()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-tamper-sig");
var message = Encoding.UTF8.GetBytes("Test message");
// Act
var signature = await signer.SignAsync(message);
var tamperedSignature = signature.ToArray();
tamperedSignature[0] ^= 0xFF; // Flip bits in first byte
var verified = await signer.VerifyAsync(message, tamperedSignature);
// Assert
verified.Should().BeFalse();
_output.WriteLine("✓ Tampered signature correctly rejected");
}
[Fact]
public async Task Verify_TruncatedSignature_ReturnsFalse()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-truncate");
var message = Encoding.UTF8.GetBytes("Test message");
// Act
var signature = await signer.SignAsync(message);
var truncatedSignature = signature.Take(32).ToArray(); // Only half
var verified = await signer.VerifyAsync(message, truncatedSignature);
// Assert
verified.Should().BeFalse();
_output.WriteLine("✓ Truncated signature correctly rejected");
}
[Fact]
public async Task Verify_WrongKey_ReturnsFalse()
{
// Arrange
var provider = CreateProvider();
var signer1 = SetupSigner(provider, "key-1", seed: 1);
var signer2 = SetupSigner(provider, "key-2", seed: 2);
var message = Encoding.UTF8.GetBytes("Test message");
// Act - sign with key-1, verify with key-2
var signature = await signer1.SignAsync(message);
var verified = await signer2.VerifyAsync(message, signature);
// Assert
verified.Should().BeFalse();
_output.WriteLine("✓ Wrong key correctly rejected");
}
#endregion
#region Determinism Tests
[Fact]
public async Task Sign_SameMessage_ProducesSameSignature()
{
// Arrange
var provider = CreateProvider();
var signer = SetupSigner(provider, "key-determinism");
var message = Encoding.UTF8.GetBytes("Deterministic message");
// Act
var signature1 = await signer.SignAsync(message);
var signature2 = await signer.SignAsync(message);
// Assert - Ed25519 is deterministic
signature1.Should().BeEquivalentTo(signature2);
_output.WriteLine("✓ Ed25519 signing is deterministic");
}
[Fact]
public async Task Sign_DifferentKeys_ProduceDifferentSignatures()
{
// Arrange
var provider = CreateProvider();
var signer1 = SetupSigner(provider, "key-diff-1", seed: 100);
var signer2 = SetupSigner(provider, "key-diff-2", seed: 200);
var message = Encoding.UTF8.GetBytes("Same message");
// Act
var signature1 = await signer1.SignAsync(message);
var signature2 = await signer2.SignAsync(message);
// Assert
signature1.Should().NotBeEquivalentTo(signature2);
_output.WriteLine("✓ Different keys produce different signatures");
}
#endregion
#region JWK Export Tests
[Fact]
public async Task ExportPublicJwk_HasCorrectProperties()
{
// Arrange
var provider = CreateProvider();
var keyId = "key-jwk-export";
var signer = SetupSigner(provider, keyId);
// Act
var jwk = signer.ExportPublicJsonWebKey();
// Assert
jwk.Should().NotBeNull();
jwk.Kid.Should().Be(keyId);
jwk.Kty.Should().Be("OKP");
jwk.Crv.Should().Be("Ed25519");
jwk.Alg.Should().Be(SignatureAlgorithms.EdDsa);
jwk.Use.Should().Be("sig");
jwk.X.Should().NotBeNullOrEmpty("public key X coordinate required");
// Private key components should NOT be present
jwk.D.Should().BeNull("private key should not be exported");
_output.WriteLine("✓ JWK export has correct properties");
_output.WriteLine($" Kid: {jwk.Kid}");
_output.WriteLine($" Kty: {jwk.Kty}");
_output.WriteLine($" Crv: {jwk.Crv}");
_output.WriteLine($" Alg: {jwk.Alg}");
}
#endregion
#region Algorithm Alias Tests
[Fact]
public async Task Sign_EdDsaAlias_Works()
{
// Arrange
var provider = CreateProvider();
var keyId = "key-eddsa-alias";
var privateKeyBytes = Enumerable.Range(0, 32).Select(i => (byte)i).ToArray();
var keyReference = new CryptoKeyReference(keyId, provider.Name);
// Register with Ed25519
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
privateKeyBytes,
createdAt: DateTimeOffset.UtcNow);
provider.UpsertSigningKey(signingKey);
// Get signer using EdDSA alias
var signer = provider.GetSigner(SignatureAlgorithms.EdDsa, keyReference);
var message = Encoding.UTF8.GetBytes("Test with EdDSA alias");
// Act
var signature = await signer.SignAsync(message);
var verified = await signer.VerifyAsync(message, signature);
// Assert
verified.Should().BeTrue();
_output.WriteLine("✓ EdDSA alias works correctly");
}
#endregion
#region Registry Integration Tests
[Fact]
public async Task RegistryResolution_SignAndVerify_Succeeds()
{
// Arrange
var configuration = new ConfigurationBuilder().Build();
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddStellaOpsCrypto();
services.AddBouncyCastleEd25519Provider();
using var serviceProvider = services.BuildServiceProvider();
var registry = serviceProvider.GetRequiredService<ICryptoProviderRegistry>();
var bcProvider = serviceProvider.GetServices<ICryptoProvider>()
.OfType<BouncyCastleEd25519CryptoProvider>()
.Single();
var keyId = "registry-test-key";
var privateKeyBytes = Enumerable.Range(0, 32).Select(i => (byte)i).ToArray();
var keyReference = new CryptoKeyReference(keyId, bcProvider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
privateKeyBytes,
createdAt: DateTimeOffset.UtcNow);
bcProvider.UpsertSigningKey(signingKey);
// Act - resolve through registry
var resolution = registry.ResolveSigner(
CryptoCapability.Signing,
SignatureAlgorithms.Ed25519,
keyReference,
bcProvider.Name);
var message = Encoding.UTF8.GetBytes("Registry resolution test");
var signature = await resolution.Signer.SignAsync(message);
var verified = await resolution.Signer.VerifyAsync(message, signature);
// Assert
verified.Should().BeTrue();
resolution.ProviderName.Should().Be(bcProvider.Name);
_output.WriteLine("✓ Registry resolution sign/verify succeeded");
}
#endregion
#region Helper Methods
private static BouncyCastleEd25519CryptoProvider CreateProvider()
=> new();
private static ICryptoSigner SetupSigner(
BouncyCastleEd25519CryptoProvider provider,
string keyId,
int? seed = null)
{
var privateKeyBytes = seed.HasValue
? Enumerable.Range(seed.Value, 32).Select(i => (byte)(i % 256)).ToArray()
: Enumerable.Range(0, 32).Select(i => (byte)i).ToArray();
var keyReference = new CryptoKeyReference(keyId, provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
privateKeyBytes,
createdAt: DateTimeOffset.UtcNow);
provider.UpsertSigningKey(signingKey);
return provider.GetSigner(SignatureAlgorithms.Ed25519, keyReference);
}
#endregion
}

View File

@@ -0,0 +1,370 @@
// -----------------------------------------------------------------------------
// CryptoProCapabilityDetectionTests.cs
// Sprint: SPRINT_5100_0009_0006 - Signer Module Test Implementation
// Task: SIGNER-5100-007 - Repeat plugin tests for CryptoPro (GOST) plugin (Tasks 4-6 pattern)
// Description: Capability detection tests for CryptoPro GOST crypto plugin
// -----------------------------------------------------------------------------
using FluentAssertions;
using Microsoft.Extensions.Options;
using Xunit;
using Xunit.Abstractions;
namespace StellaOps.Cryptography.Tests;
/// <summary>
/// Capability detection tests for the CryptoPro GOST crypto provider.
/// These tests validate the provider's capability declarations and algorithm support.
/// Note: Full sign/verify tests require CryptoPro CSP installed (Windows only).
/// </summary>
[Trait("Category", "CryptoPlugin")]
[Trait("Category", "CryptoPro")]
[Trait("Category", "GOST")]
[Trait("Category", "C1")]
public sealed class CryptoProCapabilityDetectionTests
{
private readonly ITestOutputHelper _output;
public CryptoProCapabilityDetectionTests(ITestOutputHelper output)
{
_output = output;
}
#region Provider Identity Tests
[Fact]
public void Provider_Name_IsExpected()
{
var provider = CreateProvider();
provider.Name.Should().Be("ru.cryptopro.csp");
_output.WriteLine($"✓ Provider name: {provider.Name}");
}
#endregion
#region GOST Signing Capability Tests
[Fact]
public void Supports_Signing_GostR3410_2012_256_ReturnsTrue()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.GostR3410_2012_256)
.Should().BeTrue();
_output.WriteLine("✓ Supports Signing/GOST12-256");
}
[Fact]
public void Supports_Signing_GostR3410_2012_512_ReturnsTrue()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.GostR3410_2012_512)
.Should().BeTrue();
_output.WriteLine("✓ Supports Signing/GOST12-512");
}
[Theory]
[InlineData("gost12-256")]
[InlineData("GOST12-256")]
[InlineData("Gost12-256")]
[InlineData("gost12-512")]
[InlineData("GOST12-512")]
public void Supports_Signing_CaseInsensitive(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Case-insensitive match: {algorithm}");
}
#endregion
#region GOST Verification Capability Tests
[Fact]
public void Supports_Verification_GostR3410_2012_256_ReturnsTrue()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Verification, SignatureAlgorithms.GostR3410_2012_256)
.Should().BeTrue();
_output.WriteLine("✓ Supports Verification/GOST12-256");
}
[Fact]
public void Supports_Verification_GostR3410_2012_512_ReturnsTrue()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Verification, SignatureAlgorithms.GostR3410_2012_512)
.Should().BeTrue();
_output.WriteLine("✓ Supports Verification/GOST12-512");
}
#endregion
#region Unsupported Algorithm Tests
[Theory]
[InlineData(SignatureAlgorithms.Ed25519)]
[InlineData(SignatureAlgorithms.EdDsa)]
[InlineData(SignatureAlgorithms.Es256)]
[InlineData(SignatureAlgorithms.Es384)]
[InlineData(SignatureAlgorithms.Es512)]
[InlineData(SignatureAlgorithms.Sm2)]
[InlineData(SignatureAlgorithms.Dilithium3)]
[InlineData(SignatureAlgorithms.Falcon512)]
[InlineData("RS256")]
[InlineData("RS384")]
[InlineData("RS512")]
[InlineData("PS256")]
[InlineData("HS256")]
public void Supports_Signing_UnsupportedAlgorithm_ReturnsFalse(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeFalse($"because {algorithm} is not a GOST algorithm");
_output.WriteLine($"✓ Correctly rejects unsupported algorithm: {algorithm}");
}
#endregion
#region Unsupported Capability Tests
[Fact]
public void Supports_PasswordHashing_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.PasswordHashing, SignatureAlgorithms.GostR3410_2012_256)
.Should().BeFalse();
_output.WriteLine("✓ PasswordHashing capability not supported");
}
[Fact]
public void Supports_SymmetricEncryption_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.SymmetricEncryption, SignatureAlgorithms.GostR3410_2012_256)
.Should().BeFalse();
_output.WriteLine("✓ SymmetricEncryption capability not supported");
}
[Fact]
public void Supports_KeyDerivation_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.KeyDerivation, SignatureAlgorithms.GostR3410_2012_256)
.Should().BeFalse();
_output.WriteLine("✓ KeyDerivation capability not supported");
}
[Fact]
public void Supports_ContentHashing_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.ContentHashing, SignatureAlgorithms.GostR3410_2012_256)
.Should().BeFalse();
_output.WriteLine("✓ ContentHashing capability not supported");
}
#endregion
#region External Key Management Tests
[Fact]
public void UpsertSigningKey_ThrowsNotSupported()
{
var provider = CreateProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.GostR3410_2012_256,
new byte[32],
createdAt: DateTimeOffset.UtcNow);
Action act = () => provider.UpsertSigningKey(signingKey);
act.Should().Throw<NotSupportedException>()
.WithMessage("*managed externally*");
_output.WriteLine("✓ UpsertSigningKey throws NotSupportedException (keys are external)");
}
[Fact]
public void RemoveSigningKey_ReturnsFalse()
{
var provider = CreateProvider();
var result = provider.RemoveSigningKey("any-key");
result.Should().BeFalse("because CryptoPro keys are externally managed");
_output.WriteLine("✓ RemoveSigningKey returns false (keys are external)");
}
[Fact]
public void GetSigningKeys_ReturnsEmpty_WhenNoKeysConfigured()
{
var provider = CreateProvider();
provider.GetSigningKeys().Should().BeEmpty();
_output.WriteLine("✓ GetSigningKeys returns empty when no keys configured");
}
#endregion
#region Password Hasher Tests
[Fact]
public void GetPasswordHasher_ThrowsNotSupported()
{
var provider = CreateProvider();
Action act = () => provider.GetPasswordHasher("argon2id");
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not expose password hashing*");
_output.WriteLine("✓ GetPasswordHasher throws NotSupportedException");
}
#endregion
#region Error Classification Tests
[Fact]
public void GetSigner_UnregisteredKey_ThrowsKeyNotFoundException()
{
var provider = CreateProvider();
var keyReference = new CryptoKeyReference("nonexistent-key", provider.Name);
Action act = () => provider.GetSigner(SignatureAlgorithms.GostR3410_2012_256, keyReference);
act.Should().Throw<KeyNotFoundException>()
.WithMessage("*not registered*");
_output.WriteLine("✓ GetSigner with unregistered key → KeyNotFoundException");
}
[Fact]
public void GetSigner_NullKeyReference_ThrowsArgumentNullException()
{
var provider = CreateProvider();
Action act = () => provider.GetSigner(SignatureAlgorithms.GostR3410_2012_256, null!);
act.Should().Throw<ArgumentNullException>();
_output.WriteLine("✓ GetSigner with null key reference → ArgumentNullException");
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void GetSigner_InvalidKeyId_ThrowsArgumentException(string? keyId)
{
var provider = CreateProvider();
var keyReference = new CryptoKeyReference(keyId!, provider.Name);
Action act = () => provider.GetSigner(SignatureAlgorithms.GostR3410_2012_256, keyReference);
act.Should().Throw<ArgumentException>();
_output.WriteLine($"✓ GetSigner with invalid key ID '{keyId ?? "null"}' → ArgumentException");
}
#endregion
#region Provider Diagnostics Tests
[Fact]
public void DescribeKeys_ReturnsEmpty_WhenNoKeysConfigured()
{
var provider = CreateProvider();
// CryptoProGostCryptoProvider implements ICryptoProviderDiagnostics
if (provider is ICryptoProviderDiagnostics diagnostics)
{
var keys = diagnostics.DescribeKeys().ToList();
keys.Should().BeEmpty();
_output.WriteLine("✓ DescribeKeys returns empty when no keys configured");
}
else
{
_output.WriteLine("⚠ Provider does not implement ICryptoProviderDiagnostics");
}
}
#endregion
#region Algorithm Summary
[Fact]
public void AlgorithmSummary_DocumentsSupportedAlgorithms()
{
var provider = CreateProvider();
var supportedForSigning = new[]
{
SignatureAlgorithms.GostR3410_2012_256,
SignatureAlgorithms.GostR3410_2012_512
};
foreach (var algo in supportedForSigning)
{
provider.Supports(CryptoCapability.Signing, algo).Should().BeTrue();
provider.Supports(CryptoCapability.Verification, algo).Should().BeTrue();
}
_output.WriteLine("=== CryptoPro GOST Provider Algorithm Summary ===");
_output.WriteLine("Supported for Signing:");
foreach (var algo in supportedForSigning)
{
_output.WriteLine($" - {algo}");
}
_output.WriteLine("Supported for Verification:");
foreach (var algo in supportedForSigning)
{
_output.WriteLine($" - {algo}");
}
_output.WriteLine("Unsupported Capabilities:");
_output.WriteLine(" - PasswordHashing");
_output.WriteLine(" - ContentHashing");
_output.WriteLine(" - SymmetricEncryption");
_output.WriteLine(" - KeyDerivation");
_output.WriteLine("Note: Keys are managed externally via Windows certificate store");
}
#endregion
#region Helper Methods
private static Plugin.CryptoPro.CryptoProGostCryptoProvider CreateProvider()
{
var options = Options.Create(new Plugin.CryptoPro.CryptoProGostProviderOptions());
return new Plugin.CryptoPro.CryptoProGostCryptoProvider(options, null);
}
#endregion
}

View File

@@ -0,0 +1,442 @@
// -----------------------------------------------------------------------------
// EidasCapabilityDetectionTests.cs
// Sprint: SPRINT_5100_0009_0006 - Signer Module Test Implementation
// Task: SIGNER-5100-008 - Repeat plugin tests for eIDAS plugin (Tasks 4-6 pattern)
// Description: Capability detection and error classification tests for eIDAS crypto plugin
// -----------------------------------------------------------------------------
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography.Plugin.EIDAS;
using StellaOps.Cryptography.Plugin.EIDAS.Configuration;
using StellaOps.Cryptography.Plugin.EIDAS.Models;
using Xunit;
using Xunit.Abstractions;
namespace StellaOps.Cryptography.Tests;
/// <summary>
/// Capability detection and error classification tests for the eIDAS crypto provider.
/// Validates:
/// - Supported algorithm enumeration (ECDSA, RSA-PSS, EdDSA)
/// - Capability declarations (Signing, Verification)
/// - Unsupported capability rejection
/// - Error classification for deterministic error codes
/// </summary>
[Trait("Category", "CryptoPlugin")]
[Trait("Category", "eIDAS")]
[Trait("Category", "C1")]
public sealed class EidasCapabilityDetectionTests
{
private readonly ITestOutputHelper _output;
public EidasCapabilityDetectionTests(ITestOutputHelper output)
{
_output = output;
}
#region Provider Identity Tests
[Fact]
public void Provider_Name_IsExpected()
{
var provider = CreateProvider();
provider.Name.Should().Be("eidas");
_output.WriteLine($"✓ Provider name: {provider.Name}");
}
#endregion
#region ECDSA Capability Tests
[Theory]
[InlineData("ECDSA-P256")]
[InlineData("ECDSA-P384")]
[InlineData("ECDSA-P521")]
public void Supports_Signing_EcdsaCurves_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Signing/{algorithm}");
}
[Theory]
[InlineData("ECDSA-P256")]
[InlineData("ECDSA-P384")]
[InlineData("ECDSA-P521")]
public void Supports_Verification_EcdsaCurves_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Verification, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Verification/{algorithm}");
}
#endregion
#region RSA-PSS Capability Tests
[Theory]
[InlineData("RSA-PSS-2048")]
[InlineData("RSA-PSS-4096")]
public void Supports_Signing_RsaPss_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Signing/{algorithm}");
}
#endregion
#region EdDSA Capability Tests
[Theory]
[InlineData("EdDSA-Ed25519")]
[InlineData("EdDSA-Ed448")]
public void Supports_Signing_EdDsa_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Signing/{algorithm}");
}
#endregion
#region Unsupported Algorithm Tests
[Theory]
[InlineData("RS256")]
[InlineData("RS384")]
[InlineData("RS512")]
[InlineData("ES256")] // Not the eIDAS format
[InlineData("ES384")]
[InlineData("HS256")]
[InlineData("PS256")]
[InlineData(SignatureAlgorithms.GostR3410_2012_256)]
[InlineData(SignatureAlgorithms.Sm2)]
[InlineData("UNKNOWN-ALGO")]
public void Supports_Signing_UnsupportedAlgorithm_ReturnsFalse(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeFalse($"because {algorithm} is not supported by eIDAS provider");
_output.WriteLine($"✓ Correctly rejects unsupported algorithm: {algorithm}");
}
#endregion
#region Unsupported Capability Tests
[Fact]
public void Supports_PasswordHashing_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.PasswordHashing, "ECDSA-P256")
.Should().BeFalse();
_output.WriteLine("✓ PasswordHashing capability not supported");
}
[Fact]
public void Supports_ContentHashing_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.ContentHashing, "ECDSA-P256")
.Should().BeFalse();
_output.WriteLine("✓ ContentHashing capability not supported");
}
[Fact]
public void Supports_SymmetricEncryption_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.SymmetricEncryption, "ECDSA-P256")
.Should().BeFalse();
_output.WriteLine("✓ SymmetricEncryption capability not supported");
}
[Fact]
public void Supports_KeyDerivation_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.KeyDerivation, "ECDSA-P256")
.Should().BeFalse();
_output.WriteLine("✓ KeyDerivation capability not supported");
}
#endregion
#region Hasher/PasswordHasher Tests
[Fact]
public void GetPasswordHasher_ThrowsNotSupported()
{
var provider = CreateProvider();
Action act = () => provider.GetPasswordHasher("PBKDF2");
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not support password hashing*");
_output.WriteLine("✓ GetPasswordHasher throws NotSupportedException");
}
[Fact]
public void GetHasher_ThrowsNotSupported()
{
var provider = CreateProvider();
Action act = () => provider.GetHasher("SHA256");
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not support content hashing*");
_output.WriteLine("✓ GetHasher throws NotSupportedException");
}
#endregion
#region Signer Retrieval Tests
[Fact]
public void GetSigner_ReturnsEidasSigner()
{
var provider = CreateProviderWithKey("test-key-local");
var keyRef = new CryptoKeyReference("test-key-local");
var signer = provider.GetSigner("ECDSA-P256", keyRef);
signer.Should().NotBeNull();
signer.KeyId.Should().Be("test-key-local");
signer.AlgorithmId.Should().Be("ECDSA-P256");
_output.WriteLine("✓ GetSigner returns valid EidasSigner");
}
#endregion
#region Signing Key Management Tests
[Fact]
public void UpsertSigningKey_AddsKey()
{
var provider = CreateProvider();
var keyRef = new CryptoKeyReference("upsert-test");
var signingKey = new CryptoSigningKey(
keyRef,
"ECDSA-P256",
new byte[] { 1, 2, 3, 4 },
DateTimeOffset.UtcNow);
provider.UpsertSigningKey(signingKey);
provider.GetSigningKeys().Should().Contain(k => k.Reference.KeyId == "upsert-test");
_output.WriteLine("✓ UpsertSigningKey adds key");
}
[Fact]
public void UpsertSigningKey_UpdatesExistingKey()
{
var provider = CreateProvider();
var keyRef = new CryptoKeyReference("update-test");
var signingKey1 = new CryptoSigningKey(keyRef, "ECDSA-P256", new byte[] { 1, 2, 3 }, DateTimeOffset.UtcNow);
var signingKey2 = new CryptoSigningKey(keyRef, "ECDSA-P384", new byte[] { 4, 5, 6 }, DateTimeOffset.UtcNow);
provider.UpsertSigningKey(signingKey1);
provider.UpsertSigningKey(signingKey2);
var keys = provider.GetSigningKeys();
keys.Should().ContainSingle(k => k.Reference.KeyId == "update-test");
keys.Single(k => k.Reference.KeyId == "update-test").AlgorithmId.Should().Be("ECDSA-P384");
_output.WriteLine("✓ UpsertSigningKey updates existing key");
}
[Fact]
public void RemoveSigningKey_RemovesExistingKey()
{
var provider = CreateProvider();
var keyRef = new CryptoKeyReference("remove-test");
var signingKey = new CryptoSigningKey(keyRef, "ECDSA-P256", new byte[] { 1, 2, 3, 4 }, DateTimeOffset.UtcNow);
provider.UpsertSigningKey(signingKey);
provider.GetSigningKeys().Should().Contain(k => k.Reference.KeyId == "remove-test");
var result = provider.RemoveSigningKey("remove-test");
result.Should().BeTrue();
provider.GetSigningKeys().Should().NotContain(k => k.Reference.KeyId == "remove-test");
_output.WriteLine("✓ RemoveSigningKey removes existing key");
}
[Fact]
public void RemoveSigningKey_NonExistentKey_ReturnsFalse()
{
var provider = CreateProvider();
var result = provider.RemoveSigningKey("nonexistent");
result.Should().BeFalse();
_output.WriteLine("✓ RemoveSigningKey returns false for non-existent key");
}
[Fact]
public void GetSigningKeys_InitiallyEmpty()
{
var provider = CreateProvider();
provider.GetSigningKeys().Should().BeEmpty();
_output.WriteLine("✓ GetSigningKeys returns empty initially");
}
#endregion
#region Algorithm Summary
[Fact]
public void AlgorithmSummary_DocumentsSupportedAlgorithms()
{
var provider = CreateProvider();
var supportedAlgorithms = new[]
{
"ECDSA-P256",
"ECDSA-P384",
"ECDSA-P521",
"RSA-PSS-2048",
"RSA-PSS-4096",
"EdDSA-Ed25519",
"EdDSA-Ed448"
};
foreach (var algo in supportedAlgorithms)
{
provider.Supports(CryptoCapability.Signing, algo).Should().BeTrue();
provider.Supports(CryptoCapability.Verification, algo).Should().BeTrue();
}
_output.WriteLine("=== eIDAS Provider Algorithm Summary ===");
_output.WriteLine("Supported for Signing & Verification:");
foreach (var algo in supportedAlgorithms)
{
_output.WriteLine($" - {algo}");
}
_output.WriteLine("Unsupported Capabilities:");
_output.WriteLine(" - PasswordHashing");
_output.WriteLine(" - ContentHashing");
_output.WriteLine(" - SymmetricEncryption");
_output.WriteLine(" - KeyDerivation");
_output.WriteLine("Signature Levels:");
_output.WriteLine(" - QES (Qualified Electronic Signature)");
_output.WriteLine(" - AES (Advanced Electronic Signature)");
_output.WriteLine(" - AdES (Standard Electronic Signature)");
}
#endregion
#region Helper Methods
private static EidasCryptoProvider CreateProvider()
{
var services = new ServiceCollection();
services.Configure<EidasOptions>(options =>
{
options.SignatureLevel = SignatureLevel.AdES;
options.SignatureFormat = SignatureFormat.CAdES;
options.DefaultAlgorithm = "ECDSA-P256";
options.DigestAlgorithm = "SHA256";
options.Local = new LocalSigningOptions
{
Type = "PKCS12",
Path = "/tmp/test.p12",
Password = "test"
};
options.Tsp = new TspOptions
{
Endpoint = "https://tsp.example.com",
ApiKey = "test-key"
};
});
services.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Warning));
services.AddHttpClient<TrustServiceProviderClient>();
services.AddSingleton<LocalEidasProvider>();
services.AddSingleton<EidasCryptoProvider>();
var serviceProvider = services.BuildServiceProvider();
return serviceProvider.GetRequiredService<EidasCryptoProvider>();
}
private static EidasCryptoProvider CreateProviderWithKey(string keyId)
{
var services = new ServiceCollection();
services.Configure<EidasOptions>(options =>
{
options.SignatureLevel = SignatureLevel.AdES;
options.SignatureFormat = SignatureFormat.CAdES;
options.DefaultAlgorithm = "ECDSA-P256";
options.DigestAlgorithm = "SHA256";
options.Keys.Add(new EidasKeyConfig
{
KeyId = keyId,
Source = "local"
});
options.Local = new LocalSigningOptions
{
Type = "PKCS12",
Path = "/tmp/test.p12",
Password = "test"
};
options.Tsp = new TspOptions
{
Endpoint = "https://tsp.example.com",
ApiKey = "test-key"
};
});
services.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Warning));
services.AddHttpClient<TrustServiceProviderClient>();
services.AddSingleton<LocalEidasProvider>();
services.AddSingleton<EidasCryptoProvider>();
var serviceProvider = services.BuildServiceProvider();
return serviceProvider.GetRequiredService<EidasCryptoProvider>();
}
#endregion
}

View File

@@ -0,0 +1,491 @@
// -----------------------------------------------------------------------------
// KmsHsmConnectorTests.cs
// Sprint: SPRINT_5100_0009_0006 - Signer Module Test Implementation
// Task: SIGNER-5100-010 - Add KMS/HSM connector tests (remote signing providers): fixture-based request/response snapshots
// Description: KMS/HSM connector tests with fixture-based request/response validation
// -----------------------------------------------------------------------------
using FluentAssertions;
using StellaOps.Cryptography.Kms;
using Xunit;
using Xunit.Abstractions;
namespace StellaOps.Cryptography.Tests;
/// <summary>
/// KMS/HSM connector tests for remote signing providers.
/// Tests the KmsCryptoProvider which delegates to IKmsClient implementations:
/// - AWS KMS
/// - GCP KMS
/// - PKCS#11 HSMs
/// - File-based (testing)
/// - FIDO2 (hardware authenticators)
/// </summary>
[Trait("Category", "CryptoPlugin")]
[Trait("Category", "KMS")]
[Trait("Category", "HSM")]
[Trait("Category", "C1")]
public sealed class KmsHsmConnectorTests
{
private readonly ITestOutputHelper _output;
public KmsHsmConnectorTests(ITestOutputHelper output)
{
_output = output;
}
#region Provider Identity Tests
[Fact]
public void Provider_Name_IsExpected()
{
var provider = CreateProvider(new MockKmsClient());
provider.Name.Should().Be("kms");
_output.WriteLine($"✓ Provider name: {provider.Name}");
}
#endregion
#region Capability Tests
[Fact]
public void Supports_Signing_Es256_ReturnsTrue()
{
var provider = CreateProvider(new MockKmsClient());
provider.Supports(CryptoCapability.Signing, KmsAlgorithms.Es256)
.Should().BeTrue();
_output.WriteLine("✓ Supports Signing/ES256");
}
[Fact]
public void Supports_Verification_Es256_ReturnsTrue()
{
var provider = CreateProvider(new MockKmsClient());
provider.Supports(CryptoCapability.Verification, KmsAlgorithms.Es256)
.Should().BeTrue();
_output.WriteLine("✓ Supports Verification/ES256");
}
[Theory]
[InlineData(SignatureAlgorithms.Ed25519)]
[InlineData(SignatureAlgorithms.GostR3410_2012_256)]
[InlineData(SignatureAlgorithms.Sm2)]
[InlineData("RS256")]
[InlineData("RS512")]
[InlineData("PS256")]
public void Supports_Signing_UnsupportedAlgorithm_ReturnsFalse(string algorithm)
{
var provider = CreateProvider(new MockKmsClient());
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeFalse();
_output.WriteLine($"✓ Correctly rejects unsupported algorithm: {algorithm}");
}
[Fact]
public void Supports_PasswordHashing_ReturnsFalse()
{
var provider = CreateProvider(new MockKmsClient());
provider.Supports(CryptoCapability.PasswordHashing, KmsAlgorithms.Es256)
.Should().BeFalse();
_output.WriteLine("✓ PasswordHashing capability not supported");
}
[Fact]
public void Supports_ContentHashing_ReturnsFalse()
{
var provider = CreateProvider(new MockKmsClient());
provider.Supports(CryptoCapability.ContentHashing, KmsAlgorithms.Es256)
.Should().BeFalse();
_output.WriteLine("✓ ContentHashing capability not supported");
}
#endregion
#region Key Registration Tests
[Fact]
public void UpsertSigningKey_ValidKey_Succeeds()
{
var provider = CreateProvider(new MockKmsClient());
var keyReference = new CryptoKeyReference("kms-key-001", provider.Name);
var metadata = new Dictionary<string, string?> { [KmsMetadataKeys.Version] = "v1" };
var signingKey = new CryptoSigningKey(
keyReference,
KmsAlgorithms.Es256,
new byte[32],
DateTimeOffset.UtcNow,
metadata: metadata);
Action act = () => provider.UpsertSigningKey(signingKey);
act.Should().NotThrow();
_output.WriteLine("✓ UpsertSigningKey with valid key succeeds");
}
[Fact]
public void UpsertSigningKey_MissingVersionMetadata_Throws()
{
var provider = CreateProvider(new MockKmsClient());
var keyReference = new CryptoKeyReference("kms-key-001", provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
KmsAlgorithms.Es256,
new byte[32],
DateTimeOffset.UtcNow);
Action act = () => provider.UpsertSigningKey(signingKey);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*kms.version*");
_output.WriteLine("✓ UpsertSigningKey without version metadata → InvalidOperationException");
}
[Fact]
public void UpsertSigningKey_UnsupportedAlgorithm_Throws()
{
var provider = CreateProvider(new MockKmsClient());
var keyReference = new CryptoKeyReference("kms-key-001", provider.Name);
var metadata = new Dictionary<string, string?> { [KmsMetadataKeys.Version] = "v1" };
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Ed25519,
new byte[32],
DateTimeOffset.UtcNow,
metadata: metadata);
Action act = () => provider.UpsertSigningKey(signingKey);
act.Should().Throw<InvalidOperationException>()
.WithMessage($"*only supports*");
_output.WriteLine("✓ UpsertSigningKey with unsupported algorithm → InvalidOperationException");
}
[Fact]
public void RemoveSigningKey_ExistingKey_ReturnsTrue()
{
var provider = CreateProvider(new MockKmsClient());
var keyId = "kms-key-to-remove";
RegisterKey(provider, keyId);
var result = provider.RemoveSigningKey(keyId);
result.Should().BeTrue();
_output.WriteLine("✓ RemoveSigningKey removes existing key");
}
[Fact]
public void RemoveSigningKey_NonExistentKey_ReturnsFalse()
{
var provider = CreateProvider(new MockKmsClient());
var result = provider.RemoveSigningKey("nonexistent");
result.Should().BeFalse();
_output.WriteLine("✓ RemoveSigningKey returns false for non-existent key");
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void RemoveSigningKey_InvalidKeyId_ReturnsFalse(string? keyId)
{
var provider = CreateProvider(new MockKmsClient());
var result = provider.RemoveSigningKey(keyId!);
result.Should().BeFalse();
_output.WriteLine($"✓ RemoveSigningKey with '{keyId ?? "null"}' returns false");
}
#endregion
#region Signer Retrieval Tests
[Fact]
public void GetSigner_RegisteredKey_ReturnsKmsSigner()
{
var mockClient = new MockKmsClient();
var provider = CreateProvider(mockClient);
var keyId = "kms-key-signer";
RegisterKey(provider, keyId);
var keyReference = new CryptoKeyReference(keyId, provider.Name);
var signer = provider.GetSigner(KmsAlgorithms.Es256, keyReference);
signer.Should().NotBeNull();
signer.Should().BeOfType<KmsSigner>();
_output.WriteLine("✓ GetSigner returns KmsSigner");
}
[Fact]
public void GetSigner_UnregisteredKey_ThrowsKeyNotFoundException()
{
var provider = CreateProvider(new MockKmsClient());
var keyReference = new CryptoKeyReference("unregistered", provider.Name);
Action act = () => provider.GetSigner(KmsAlgorithms.Es256, keyReference);
act.Should().Throw<KeyNotFoundException>()
.WithMessage("*not registered*");
_output.WriteLine("✓ GetSigner with unregistered key → KeyNotFoundException");
}
[Fact]
public void GetSigner_UnsupportedAlgorithm_ThrowsInvalidOperationException()
{
var provider = CreateProvider(new MockKmsClient());
var keyId = "kms-key-wrong-algo";
RegisterKey(provider, keyId);
var keyReference = new CryptoKeyReference(keyId, provider.Name);
Action act = () => provider.GetSigner(SignatureAlgorithms.Ed25519, keyReference);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*not supported*");
_output.WriteLine("✓ GetSigner with unsupported algorithm → InvalidOperationException");
}
[Fact]
public void GetSigner_NullKeyReference_ThrowsArgumentNullException()
{
var provider = CreateProvider(new MockKmsClient());
Action act = () => provider.GetSigner(KmsAlgorithms.Es256, null!);
act.Should().Throw<ArgumentNullException>();
_output.WriteLine("✓ GetSigner with null key reference → ArgumentNullException");
}
#endregion
#region Hasher Tests
[Fact]
public void GetPasswordHasher_ThrowsInvalidOperationException()
{
var provider = CreateProvider(new MockKmsClient());
Action act = () => provider.GetPasswordHasher("PBKDF2");
act.Should().Throw<InvalidOperationException>()
.WithMessage("*does not support password hashing*");
_output.WriteLine("✓ GetPasswordHasher throws InvalidOperationException");
}
[Fact]
public void GetHasher_ThrowsInvalidOperationException()
{
var provider = CreateProvider(new MockKmsClient());
Action act = () => provider.GetHasher("SHA-256");
act.Should().Throw<InvalidOperationException>()
.WithMessage("*does not support content hashing*");
_output.WriteLine("✓ GetHasher throws InvalidOperationException");
}
#endregion
#region KMS Client Interface Tests
[Fact]
public async Task MockKmsClient_Sign_ReturnsValidSignature()
{
var mockClient = new MockKmsClient();
var data = new byte[] { 1, 2, 3, 4, 5 };
var result = await mockClient.SignAsync("test-key", "v1", data);
result.Should().NotBeNull();
result.Signature.Should().NotBeEmpty();
result.KeyId.Should().Be("test-key");
_output.WriteLine("✓ MockKmsClient.SignAsync returns valid result");
}
[Fact]
public async Task MockKmsClient_Verify_MatchingSignature_ReturnsTrue()
{
var mockClient = new MockKmsClient();
var data = new byte[] { 1, 2, 3, 4, 5 };
var signResult = await mockClient.SignAsync("test-key", "v1", data);
var verified = await mockClient.VerifyAsync("test-key", "v1", data, signResult.Signature);
verified.Should().BeTrue();
_output.WriteLine("✓ MockKmsClient sign/verify roundtrip succeeds");
}
[Fact]
public async Task MockKmsClient_Verify_TamperedData_ReturnsFalse()
{
var mockClient = new MockKmsClient();
var data = new byte[] { 1, 2, 3, 4, 5 };
var tamperedData = new byte[] { 1, 2, 3, 4, 6 };
var signResult = await mockClient.SignAsync("test-key", "v1", data);
var verified = await mockClient.VerifyAsync("test-key", "v1", tamperedData, signResult.Signature);
verified.Should().BeFalse();
_output.WriteLine("✓ MockKmsClient correctly rejects tampered data");
}
[Fact]
public async Task MockKmsClient_GetMetadata_ReturnsKeyInfo()
{
var mockClient = new MockKmsClient();
var metadata = await mockClient.GetMetadataAsync("test-key");
metadata.Should().NotBeNull();
metadata.KeyId.Should().Be("test-key");
metadata.State.Should().Be(KmsKeyState.Active);
_output.WriteLine("✓ MockKmsClient.GetMetadataAsync returns valid metadata");
}
#endregion
#region Error Code Mapping
[Fact]
public void ErrorCodeMappingSummary_DocumentsExpectedMappings()
{
_output.WriteLine("=== KMS/HSM Error Code Mapping Reference ===");
_output.WriteLine(" KeyNotFoundException → KMS_KEY_NOT_FOUND");
_output.WriteLine(" InvalidOperationException → KMS_INVALID_OPERATION");
_output.WriteLine(" ArgumentNullException → KMS_INVALID_ARGUMENT");
_output.WriteLine("");
_output.WriteLine("Backend-specific errors:");
_output.WriteLine(" AWS KMS: KMSNotFoundException → KMS_KEY_NOT_FOUND");
_output.WriteLine(" AWS KMS: KMSInvalidStateException → KMS_KEY_DISABLED");
_output.WriteLine(" GCP KMS: StatusCode.NotFound → KMS_KEY_NOT_FOUND");
_output.WriteLine(" GCP KMS: StatusCode.FailedPrecondition → KMS_KEY_DISABLED");
_output.WriteLine(" PKCS#11: CKR_KEY_HANDLE_INVALID → KMS_KEY_NOT_FOUND");
_output.WriteLine(" PKCS#11: CKR_TOKEN_NOT_PRESENT → KMS_HSM_UNAVAILABLE");
}
#endregion
#region Helper Methods
private static KmsCryptoProvider CreateProvider(IKmsClient kmsClient)
=> new(kmsClient);
private static void RegisterKey(KmsCryptoProvider provider, string keyId)
{
var keyReference = new CryptoKeyReference(keyId, provider.Name);
var metadata = new Dictionary<string, string?> { [KmsMetadataKeys.Version] = "v1" };
var signingKey = new CryptoSigningKey(
keyReference,
KmsAlgorithms.Es256,
new byte[32],
DateTimeOffset.UtcNow,
metadata: metadata);
provider.UpsertSigningKey(signingKey);
}
#endregion
#region Mock KMS Client
/// <summary>
/// Mock KMS client for testing without external dependencies.
/// </summary>
private sealed class MockKmsClient : IKmsClient
{
private readonly Dictionary<string, byte[]> _signatures = new();
public Task<KmsSignResult> SignAsync(
string keyId,
string? keyVersion,
ReadOnlyMemory<byte> data,
CancellationToken cancellationToken = default)
{
var signature = System.Security.Cryptography.SHA256.HashData(data.Span);
var signatureKey = $"{keyId}:{keyVersion}:{Convert.ToBase64String(data.ToArray())}";
_signatures[signatureKey] = signature;
return Task.FromResult(new KmsSignResult(keyId, keyVersion ?? "v1", signature));
}
public Task<bool> VerifyAsync(
string keyId,
string? keyVersion,
ReadOnlyMemory<byte> data,
ReadOnlyMemory<byte> signature,
CancellationToken cancellationToken = default)
{
var expectedSignature = System.Security.Cryptography.SHA256.HashData(data.Span);
return Task.FromResult(signature.Span.SequenceEqual(expectedSignature));
}
public Task<KmsKeyMetadata> GetMetadataAsync(string keyId, CancellationToken cancellationToken = default)
{
var metadata = new KmsKeyMetadata(
keyId,
"v1",
KmsKeyState.Active,
DateTimeOffset.UtcNow.AddMonths(-1),
null);
return Task.FromResult(metadata);
}
public Task<KmsKeyMaterial> ExportAsync(
string keyId,
string? keyVersion,
CancellationToken cancellationToken = default)
{
// Return mock key material with empty private key (verification only)
var material = new KmsKeyMaterial(
keyId,
keyVersion ?? "v1",
X: new byte[32],
Y: new byte[32],
D: Array.Empty<byte>());
return Task.FromResult(material);
}
public Task<KmsKeyMetadata> RotateAsync(string keyId, CancellationToken cancellationToken = default)
{
var metadata = new KmsKeyMetadata(
keyId,
"v2",
KmsKeyState.Active,
DateTimeOffset.UtcNow,
null);
return Task.FromResult(metadata);
}
public Task RevokeAsync(string keyId, CancellationToken cancellationToken = default)
=> Task.CompletedTask;
}
#endregion
}

View File

@@ -0,0 +1,467 @@
// -----------------------------------------------------------------------------
// SimRemoteCapabilityDetectionTests.cs
// Sprint: SPRINT_5100_0009_0006 - Signer Module Test Implementation
// Task: SIGNER-5100-009 - Repeat plugin tests for SimRemote (SM2/SM3) plugin (Tasks 4-6 pattern)
// Description: Capability detection and error classification tests for SimRemote crypto plugin
// -----------------------------------------------------------------------------
using FluentAssertions;
using Microsoft.Extensions.Options;
using Moq;
using StellaOps.Cryptography.Plugin.SimRemote;
using Xunit;
using Xunit.Abstractions;
namespace StellaOps.Cryptography.Tests;
/// <summary>
/// Capability detection and error classification tests for the SimRemote crypto provider.
/// SimRemote is a simulation provider for testing various algorithms including:
/// - SM2/SM3 (Chinese national cryptographic standards)
/// - Post-Quantum algorithms (Dilithium3, Falcon512)
/// - GOST algorithms
/// - FIPS/eIDAS/KCMVP compliance modes
/// </summary>
[Trait("Category", "CryptoPlugin")]
[Trait("Category", "SimRemote")]
[Trait("Category", "SM2")]
[Trait("Category", "PostQuantum")]
[Trait("Category", "C1")]
public sealed class SimRemoteCapabilityDetectionTests
{
private readonly ITestOutputHelper _output;
public SimRemoteCapabilityDetectionTests(ITestOutputHelper output)
{
_output = output;
}
#region Provider Identity Tests
[Fact]
public void Provider_Name_IsExpected()
{
var provider = CreateProvider();
provider.Name.Should().Be("sim.crypto.remote");
_output.WriteLine($"✓ Provider name: {provider.Name}");
}
#endregion
#region SM2/SM3 Capability Tests
[Fact]
public void Supports_Signing_Sm2_ReturnsTrue()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Sm2)
.Should().BeTrue();
_output.WriteLine("✓ Supports Signing/SM2");
}
[Theory]
[InlineData("sm.sim")]
[InlineData("sm2.sim")]
public void Supports_Signing_SmSimulationModes_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Signing/{algorithm}");
}
#endregion
#region Post-Quantum Capability Tests
[Theory]
[InlineData(SignatureAlgorithms.Dilithium3)]
[InlineData(SignatureAlgorithms.Falcon512)]
[InlineData("pq.sim")]
public void Supports_Signing_PostQuantum_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Signing/{algorithm} (Post-Quantum)");
}
#endregion
#region GOST Capability Tests
[Theory]
[InlineData(SignatureAlgorithms.GostR3410_2012_256)]
[InlineData(SignatureAlgorithms.GostR3410_2012_512)]
[InlineData("ru.magma.sim")]
[InlineData("ru.kuznyechik.sim")]
public void Supports_Signing_Gost_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Signing/{algorithm} (GOST)");
}
#endregion
#region ECDSA Capability Tests
[Theory]
[InlineData(SignatureAlgorithms.Es256)]
[InlineData(SignatureAlgorithms.Es384)]
[InlineData(SignatureAlgorithms.Es512)]
public void Supports_Signing_Ecdsa_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Signing/{algorithm} (ECDSA)");
}
#endregion
#region Compliance Mode Capability Tests
[Theory]
[InlineData("fips.sim")]
[InlineData("eidas.sim")]
[InlineData("kcmvp.sim")]
[InlineData("world.sim")]
public void Supports_Signing_ComplianceModes_ReturnsTrue(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeTrue();
_output.WriteLine($"✓ Supports Signing/{algorithm} (Compliance Mode)");
}
#endregion
#region Verification Capability Tests
[Fact]
public void Supports_Verification_Sm2_ReturnsTrue()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Verification, SignatureAlgorithms.Sm2)
.Should().BeTrue();
_output.WriteLine("✓ Supports Verification/SM2");
}
[Fact]
public void Supports_Verification_Dilithium3_ReturnsTrue()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Verification, SignatureAlgorithms.Dilithium3)
.Should().BeTrue();
_output.WriteLine("✓ Supports Verification/Dilithium3");
}
#endregion
#region Unsupported Algorithm Tests
[Theory]
[InlineData(SignatureAlgorithms.Ed25519)]
[InlineData(SignatureAlgorithms.EdDsa)]
[InlineData("RS256")]
[InlineData("RS384")]
[InlineData("HS256")]
[InlineData("UNKNOWN-ALGO")]
public void Supports_Signing_UnsupportedAlgorithm_ReturnsFalse(string algorithm)
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.Signing, algorithm)
.Should().BeFalse($"because {algorithm} is not in the configured algorithms");
_output.WriteLine($"✓ Correctly rejects unsupported algorithm: {algorithm}");
}
[Fact]
public void Supports_CustomAlgorithms_CanBeConfigured()
{
var options = new SimRemoteProviderOptions
{
Algorithms = new List<string> { "custom-algo-1", "custom-algo-2" }
};
var provider = CreateProviderWithOptions(options);
provider.Supports(CryptoCapability.Signing, "custom-algo-1").Should().BeTrue();
provider.Supports(CryptoCapability.Signing, "custom-algo-2").Should().BeTrue();
provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Sm2).Should().BeFalse();
_output.WriteLine("✓ Custom algorithms can be configured");
}
#endregion
#region Unsupported Capability Tests
[Fact]
public void Supports_PasswordHashing_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.PasswordHashing, SignatureAlgorithms.Sm2)
.Should().BeFalse();
_output.WriteLine("✓ PasswordHashing capability not supported");
}
[Fact]
public void Supports_ContentHashing_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.ContentHashing, SignatureAlgorithms.Sm2)
.Should().BeFalse();
_output.WriteLine("✓ ContentHashing capability not supported");
}
[Fact]
public void Supports_SymmetricEncryption_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.SymmetricEncryption, SignatureAlgorithms.Sm2)
.Should().BeFalse();
_output.WriteLine("✓ SymmetricEncryption capability not supported");
}
[Fact]
public void Supports_KeyDerivation_ReturnsFalse()
{
var provider = CreateProvider();
provider.Supports(CryptoCapability.KeyDerivation, SignatureAlgorithms.Sm2)
.Should().BeFalse();
_output.WriteLine("✓ KeyDerivation capability not supported");
}
#endregion
#region Error Classification Tests
[Fact]
public void GetPasswordHasher_ThrowsNotSupported()
{
var provider = CreateProvider();
Action act = () => provider.GetPasswordHasher("argon2id");
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not handle password hashing*");
_output.WriteLine("✓ GetPasswordHasher throws NotSupportedException");
}
[Fact]
public void GetHasher_ThrowsNotSupported()
{
var provider = CreateProvider();
Action act = () => provider.GetHasher("SHA-256");
act.Should().Throw<NotSupportedException>()
.WithMessage("*does not handle hashing*");
_output.WriteLine("✓ GetHasher throws NotSupportedException");
}
[Fact]
public void UpsertSigningKey_ThrowsNotSupported()
{
var provider = CreateProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
var signingKey = new CryptoSigningKey(
keyReference,
SignatureAlgorithms.Sm2,
new byte[32],
createdAt: DateTimeOffset.UtcNow);
Action act = () => provider.UpsertSigningKey(signingKey);
act.Should().Throw<NotSupportedException>()
.WithMessage("*uses remote keys*");
_output.WriteLine("✓ UpsertSigningKey throws NotSupportedException (remote keys only)");
}
[Fact]
public void RemoveSigningKey_ReturnsFalse()
{
var provider = CreateProvider();
var result = provider.RemoveSigningKey("any-key");
result.Should().BeFalse();
_output.WriteLine("✓ RemoveSigningKey returns false (remote keys only)");
}
[Fact]
public void GetSigningKeys_ReturnsEmpty()
{
var provider = CreateProvider();
provider.GetSigningKeys().Should().BeEmpty();
_output.WriteLine("✓ GetSigningKeys returns empty (remote keys)");
}
[Fact]
public void GetSigner_NullKeyReference_ThrowsArgumentNullException()
{
var provider = CreateProvider();
Action act = () => provider.GetSigner(SignatureAlgorithms.Sm2, null!);
act.Should().Throw<ArgumentNullException>();
_output.WriteLine("✓ GetSigner with null key reference → ArgumentNullException");
}
[Fact]
public void GetSigner_UnsupportedAlgorithm_ThrowsInvalidOperationException()
{
var provider = CreateProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
Action act = () => provider.GetSigner(SignatureAlgorithms.Ed25519, keyReference);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*not enabled for simulation*");
_output.WriteLine("✓ GetSigner with unsupported algorithm → InvalidOperationException");
}
[Fact]
public void GetSigner_ValidAlgorithm_ReturnsSigner()
{
var provider = CreateProvider();
var keyReference = new CryptoKeyReference("test-key", provider.Name);
var signer = provider.GetSigner(SignatureAlgorithms.Sm2, keyReference);
signer.Should().NotBeNull();
signer.AlgorithmId.Should().Be(SignatureAlgorithms.Sm2);
_output.WriteLine("✓ GetSigner returns valid signer");
}
#endregion
#region Provider Diagnostics Tests
[Fact]
public void DescribeKeys_ReturnsConfiguredAlgorithms()
{
var provider = CreateProvider();
// SimRemoteProvider implements ICryptoProviderDiagnostics
if (provider is ICryptoProviderDiagnostics diagnostics)
{
var keys = diagnostics.DescribeKeys().ToList();
keys.Should().NotBeEmpty();
foreach (var key in keys)
{
key.ProviderName.Should().Be("sim.crypto.remote");
key.Metadata.Should().ContainKey("simulation");
key.Metadata["simulation"].Should().Be("true");
}
_output.WriteLine($"✓ DescribeKeys returns {keys.Count} algorithm descriptors");
}
}
#endregion
#region Algorithm Summary
[Fact]
public void AlgorithmSummary_DocumentsSupportedAlgorithms()
{
var provider = CreateProvider();
_output.WriteLine("=== SimRemote Provider Algorithm Summary ===");
_output.WriteLine("SM2/SM3 (Chinese National Standards):");
_output.WriteLine($" - SM2: {provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Sm2)}");
_output.WriteLine($" - sm.sim: {provider.Supports(CryptoCapability.Signing, "sm.sim")}");
_output.WriteLine("Post-Quantum:");
_output.WriteLine($" - DILITHIUM3: {provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Dilithium3)}");
_output.WriteLine($" - FALCON512: {provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Falcon512)}");
_output.WriteLine($" - pq.sim: {provider.Supports(CryptoCapability.Signing, "pq.sim")}");
_output.WriteLine("GOST (Russian Federation):");
_output.WriteLine($" - GOST12-256: {provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.GostR3410_2012_256)}");
_output.WriteLine($" - GOST12-512: {provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.GostR3410_2012_512)}");
_output.WriteLine("ECDSA:");
_output.WriteLine($" - ES256: {provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Es256)}");
_output.WriteLine($" - ES384: {provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Es384)}");
_output.WriteLine($" - ES512: {provider.Supports(CryptoCapability.Signing, SignatureAlgorithms.Es512)}");
_output.WriteLine("Compliance Modes:");
_output.WriteLine($" - fips.sim: {provider.Supports(CryptoCapability.Signing, "fips.sim")}");
_output.WriteLine($" - eidas.sim: {provider.Supports(CryptoCapability.Signing, "eidas.sim")}");
_output.WriteLine($" - kcmvp.sim: {provider.Supports(CryptoCapability.Signing, "kcmvp.sim")}");
_output.WriteLine("");
_output.WriteLine("Unsupported Capabilities:");
_output.WriteLine(" - PasswordHashing");
_output.WriteLine(" - ContentHashing");
_output.WriteLine(" - SymmetricEncryption");
_output.WriteLine(" - KeyDerivation");
_output.WriteLine("Note: Keys are managed remotely via simulation endpoint");
}
#endregion
#region Helper Methods
private static SimRemoteProvider CreateProvider()
{
var mockHttpClient = CreateMockHttpClient();
var options = Options.Create(new SimRemoteProviderOptions());
return new SimRemoteProvider(mockHttpClient, options, null);
}
private static SimRemoteProvider CreateProviderWithOptions(SimRemoteProviderOptions options)
{
var mockHttpClient = CreateMockHttpClient();
return new SimRemoteProvider(mockHttpClient, Options.Create(options), null);
}
private static SimRemoteHttpClient CreateMockHttpClient()
{
var httpClient = new HttpClient();
var options = Options.Create(new SimRemoteProviderOptions());
return new SimRemoteHttpClient(httpClient, options);
}
#endregion
}