5100* tests strengthtenen work
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user