284 lines
11 KiB
C#
284 lines
11 KiB
C#
using FluentAssertions;
|
|
using StellaOps.Cryptography;
|
|
using StellaOps.Cryptography.Plugin.OfflineVerification;
|
|
using System.Security.Cryptography;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Cryptography.Plugin.OfflineVerification.Tests;
|
|
|
|
public class OfflineVerificationProviderTests
|
|
{
|
|
private readonly OfflineVerificationCryptoProvider _provider;
|
|
|
|
public OfflineVerificationProviderTests()
|
|
{
|
|
_provider = new OfflineVerificationCryptoProvider();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Name_ReturnsCorrectValue()
|
|
{
|
|
// Assert
|
|
_provider.Name.Should().Be("offline-verification");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData(CryptoCapability.Signing, "ES256", true)]
|
|
[InlineData(CryptoCapability.Signing, "ES384", true)]
|
|
[InlineData(CryptoCapability.Signing, "ES512", true)]
|
|
[InlineData(CryptoCapability.Signing, "RS256", true)]
|
|
[InlineData(CryptoCapability.Signing, "RS384", true)]
|
|
[InlineData(CryptoCapability.Signing, "RS512", true)]
|
|
[InlineData(CryptoCapability.Signing, "PS256", true)]
|
|
[InlineData(CryptoCapability.Signing, "PS384", true)]
|
|
[InlineData(CryptoCapability.Signing, "PS512", true)]
|
|
[InlineData(CryptoCapability.Verification, "ES256", true)]
|
|
[InlineData(CryptoCapability.Verification, "RS256", true)]
|
|
[InlineData(CryptoCapability.Verification, "PS256", true)]
|
|
[InlineData(CryptoCapability.ContentHashing, "SHA-256", true)]
|
|
[InlineData(CryptoCapability.ContentHashing, "SHA-384", true)]
|
|
[InlineData(CryptoCapability.ContentHashing, "SHA-512", true)]
|
|
[InlineData(CryptoCapability.ContentHashing, "SHA256", true)]
|
|
[InlineData(CryptoCapability.PasswordHashing, "PBKDF2", true)]
|
|
[InlineData(CryptoCapability.PasswordHashing, "Argon2id", true)]
|
|
[InlineData(CryptoCapability.Signing, "UNSUPPORTED", false)]
|
|
[InlineData(CryptoCapability.SymmetricEncryption, "AES-256", false)]
|
|
public void Supports_ReturnCorrectResult(CryptoCapability capability, string algorithmId, bool expected)
|
|
{
|
|
// Act
|
|
var result = _provider.Supports(capability, algorithmId);
|
|
|
|
// Assert
|
|
result.Should().Be(expected);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData("SHA-256", "hello world", "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")]
|
|
[InlineData("SHA-384", "hello world", "fdbd8e75a67f29f701a4e040385e2e23986303ea10239211af907fcbb83578b3e417cb71ce646efd0819dd8c088de1bd")]
|
|
[InlineData("SHA-512", "hello world", "309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f")]
|
|
[InlineData("SHA256", "hello world", "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9")] // Alternative form
|
|
public void GetHasher_ComputesCorrectHash(string algorithmId, string input, string expectedHex)
|
|
{
|
|
// Arrange
|
|
var hasher = _provider.GetHasher(algorithmId);
|
|
var inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
|
|
|
|
// Act
|
|
var hash = hasher.ComputeHash(inputBytes);
|
|
var actualHex = Convert.ToHexString(hash).ToLowerInvariant();
|
|
|
|
// Assert
|
|
actualHex.Should().Be(expectedHex);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void GetHasher_WithUnsupportedAlgorithm_ThrowsNotSupportedException()
|
|
{
|
|
// Act
|
|
var act = () => _provider.GetHasher("MD5");
|
|
|
|
// Assert
|
|
act.Should().Throw<NotSupportedException>()
|
|
.WithMessage("*MD5*");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void GetPasswordHasher_ThrowsNotSupportedException()
|
|
{
|
|
// Act
|
|
var act = () => _provider.GetPasswordHasher("PBKDF2");
|
|
|
|
// Assert
|
|
act.Should().Throw<NotSupportedException>()
|
|
.WithMessage("*Password hashing*");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData("ES256")]
|
|
[InlineData("ES384")]
|
|
[InlineData("ES512")]
|
|
public void CreateEphemeralVerifier_ForEcdsa_VerifiesSignatureCorrectly(string algorithmId)
|
|
{
|
|
// Arrange - Create a real ECDSA key, sign a message
|
|
using var ecdsa = ECDsa.Create();
|
|
var curve = algorithmId switch
|
|
{
|
|
"ES256" => ECCurve.NamedCurves.nistP256,
|
|
"ES384" => ECCurve.NamedCurves.nistP384,
|
|
"ES512" => ECCurve.NamedCurves.nistP521,
|
|
_ => throw new NotSupportedException()
|
|
};
|
|
ecdsa.GenerateKey(curve);
|
|
|
|
var hashAlgorithm = algorithmId switch
|
|
{
|
|
"ES256" => HashAlgorithmName.SHA256,
|
|
"ES384" => HashAlgorithmName.SHA384,
|
|
"ES512" => HashAlgorithmName.SHA512,
|
|
_ => throw new NotSupportedException()
|
|
};
|
|
|
|
var message = System.Text.Encoding.UTF8.GetBytes("ephemeral verifier test");
|
|
var signature = ecdsa.SignData(message, hashAlgorithm);
|
|
|
|
// Export public key in SubjectPublicKeyInfo format
|
|
var publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
|
|
|
|
// Act - Create ephemeral verifier from public key
|
|
var ephemeralVerifier = _provider.CreateEphemeralVerifier(algorithmId, publicKeyBytes);
|
|
|
|
// Assert - Verify signature using ephemeral verifier
|
|
var isValid = ephemeralVerifier.VerifyAsync(message, signature, default).GetAwaiter().GetResult();
|
|
isValid.Should().BeTrue("ephemeral verifier should verify signature from original key");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CreateEphemeralVerifier_ForRsaPkcs1_VerifiesSignatureCorrectly()
|
|
{
|
|
// Arrange - Create a real RSA key, sign a message
|
|
using var rsa = RSA.Create(2048);
|
|
var message = System.Text.Encoding.UTF8.GetBytes("ephemeral rsa verifier test");
|
|
var signature = rsa.SignData(message, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
|
|
// Export public key in SubjectPublicKeyInfo format
|
|
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
|
|
|
|
// Act - Create ephemeral verifier from public key
|
|
var ephemeralVerifier = _provider.CreateEphemeralVerifier("RS256", publicKeyBytes);
|
|
|
|
// Assert - Verify signature using ephemeral verifier
|
|
var isValid = ephemeralVerifier.VerifyAsync(message, signature, default).GetAwaiter().GetResult();
|
|
isValid.Should().BeTrue("ephemeral RSA verifier should verify PKCS1 signature from original key");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CreateEphemeralVerifier_ForRsaPss_VerifiesSignatureCorrectly()
|
|
{
|
|
// Arrange - Create a real RSA key, sign a message
|
|
using var rsa = RSA.Create(2048);
|
|
var message = System.Text.Encoding.UTF8.GetBytes("ephemeral rsa pss verifier test");
|
|
var signature = rsa.SignData(message, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
|
|
|
|
// Export public key in SubjectPublicKeyInfo format
|
|
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
|
|
|
|
// Act - Create ephemeral verifier from public key
|
|
var ephemeralVerifier = _provider.CreateEphemeralVerifier("PS256", publicKeyBytes);
|
|
|
|
// Assert - Verify signature using ephemeral verifier
|
|
var isValid = ephemeralVerifier.VerifyAsync(message, signature, default).GetAwaiter().GetResult();
|
|
isValid.Should().BeTrue("ephemeral RSA verifier should verify PSS signature from original key");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData("ES256")]
|
|
[InlineData("PS256")]
|
|
public void EphemeralVerifier_SignAsync_ThrowsNotSupportedException(string algorithmId)
|
|
{
|
|
// Arrange - Create a dummy public key
|
|
byte[] publicKeyBytes;
|
|
if (algorithmId.StartsWith("ES"))
|
|
{
|
|
using var ecdsa = ECDsa.Create();
|
|
publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
|
|
}
|
|
else
|
|
{
|
|
using var rsa = RSA.Create(2048);
|
|
publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
|
|
}
|
|
|
|
var ephemeralVerifier = _provider.CreateEphemeralVerifier(algorithmId, publicKeyBytes);
|
|
|
|
// Act
|
|
var message = System.Text.Encoding.UTF8.GetBytes("test");
|
|
var act = async () => await ephemeralVerifier.VerifyAsync(message, System.Text.Encoding.UTF8.GetBytes("invalid-signature"), default);
|
|
|
|
// Assert - should return false, not throw
|
|
var result = act().GetAwaiter().GetResult();
|
|
result.Should().BeFalse();
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData("ES256")]
|
|
[InlineData("PS256")]
|
|
public void EphemeralVerifier_WithTamperedMessage_FailsVerification(string algorithmId)
|
|
{
|
|
// Arrange - Create key and sign original message
|
|
byte[] publicKeyBytes;
|
|
byte[] signature;
|
|
var originalMessage = System.Text.Encoding.UTF8.GetBytes("original message");
|
|
var tamperedMessage = System.Text.Encoding.UTF8.GetBytes("tampered message");
|
|
|
|
if (algorithmId.StartsWith("ES"))
|
|
{
|
|
using var ecdsa = ECDsa.Create();
|
|
signature = ecdsa.SignData(originalMessage, HashAlgorithmName.SHA256);
|
|
publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
|
|
}
|
|
else
|
|
{
|
|
using var rsa = RSA.Create(2048);
|
|
var padding = algorithmId.StartsWith("PS") ? RSASignaturePadding.Pss : RSASignaturePadding.Pkcs1;
|
|
signature = rsa.SignData(originalMessage, HashAlgorithmName.SHA256, padding);
|
|
publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
|
|
}
|
|
|
|
var ephemeralVerifier = _provider.CreateEphemeralVerifier(algorithmId, publicKeyBytes);
|
|
|
|
// Act
|
|
var isValid = ephemeralVerifier.VerifyAsync(tamperedMessage, signature, default).GetAwaiter().GetResult();
|
|
|
|
// Assert
|
|
isValid.Should().BeFalse("ephemeral verifier should fail with tampered message");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CreateEphemeralVerifier_WithUnsupportedAlgorithm_ThrowsNotSupportedException()
|
|
{
|
|
// Arrange - Create a dummy public key
|
|
using var ecdsa = ECDsa.Create();
|
|
var publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
|
|
|
|
// Act
|
|
var act = () => _provider.CreateEphemeralVerifier("UNSUPPORTED", publicKeyBytes);
|
|
|
|
// Assert
|
|
act.Should().Throw<NotSupportedException>()
|
|
.WithMessage("*UNSUPPORTED*");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData("ES256")]
|
|
[InlineData("PS256")]
|
|
public void EphemeralVerifier_HasCorrectProperties(string algorithmId)
|
|
{
|
|
// Arrange - Create a dummy public key
|
|
byte[] publicKeyBytes;
|
|
using (var ecdsa = ECDsa.Create())
|
|
{
|
|
publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo();
|
|
using StellaOps.TestKit;
|
|
}
|
|
|
|
// Act
|
|
var ephemeralVerifier = _provider.CreateEphemeralVerifier(algorithmId, publicKeyBytes);
|
|
|
|
// Assert
|
|
ephemeralVerifier.KeyId.Should().Be("ephemeral");
|
|
ephemeralVerifier.AlgorithmId.Should().Be(algorithmId);
|
|
}
|
|
}
|