// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_4100_0006_0003 - SM Crypto CLI Integration - OSCCA Compliance Tests
using System;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.Cryptography.Plugin.SmSoft;
using Xunit;
namespace StellaOps.Cryptography.Plugin.SmSoft.Tests;
///
/// OSCCA GM/T 0003-2012 compliance tests for SM2 signature algorithm.
/// Test vectors from Appendix A of the standard.
///
public class Sm2ComplianceTests
{
private readonly SmSoftCryptoProvider _provider;
public Sm2ComplianceTests()
{
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug));
// Disable environment gate for testing
services.Configure(options =>
{
options.RequireEnvironmentGate = false;
});
services.AddSingleton();
var serviceProvider = services.BuildServiceProvider();
_provider = serviceProvider.GetRequiredService() as SmSoftCryptoProvider
?? throw new InvalidOperationException("Failed to resolve SmSoftCryptoProvider");
}
[Fact]
public void Provider_Name_IsCnSmSoft()
{
Assert.Equal("cn.sm.soft", _provider.Name);
}
[Theory]
[InlineData(CryptoCapability.Signing, "SM2", true)]
[InlineData(CryptoCapability.Verification, "SM2", true)]
[InlineData(CryptoCapability.ContentHashing, "SM3", true)]
[InlineData(CryptoCapability.Signing, "SM4", false)]
[InlineData(CryptoCapability.PasswordHashing, "SM2", false)]
public void Supports_ReturnsExpectedResults(CryptoCapability capability, string algorithmId, bool expected)
{
var result = _provider.Supports(capability, algorithmId);
Assert.Equal(expected, result);
}
[Fact]
public void GetPasswordHasher_ThrowsNotSupported()
{
Assert.Throws(() => _provider.GetPasswordHasher("PBKDF2"));
}
[Fact]
public void GetHasher_WithSm3_ReturnsSm3Hasher()
{
var hasher = _provider.GetHasher("SM3");
Assert.NotNull(hasher);
Assert.Equal("SM3", hasher.AlgorithmId);
}
[Fact]
public void GetHasher_WithInvalidAlgorithm_Throws()
{
Assert.Throws(() => _provider.GetHasher("SHA256"));
}
[Fact]
public void Sm3_ComputeHash_EmptyInput_ReturnsCorrectHash()
{
// OSCCA GM/T 0004-2012 test vector for empty string
// Expected: 1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b
var hasher = _provider.GetHasher("SM3");
var input = Array.Empty();
var hash = hasher.ComputeHashHex(input);
Assert.Equal("1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b", hash);
}
[Fact]
public void Sm3_ComputeHash_AbcInput_ReturnsCorrectHash()
{
// OSCCA GM/T 0004-2012 test vector for "abc"
// Expected: 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0
var hasher = _provider.GetHasher("SM3");
var input = Encoding.ASCII.GetBytes("abc");
var hash = hasher.ComputeHashHex(input);
Assert.Equal("66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0", hash);
}
[Fact]
public void Sm3_ComputeHash_LongInput_ReturnsCorrectHash()
{
// OSCCA GM/T 0004-2012 test vector for 64-byte string
// Input: "abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"
// Expected: debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732
var hasher = _provider.GetHasher("SM3");
var input = Encoding.ASCII.GetBytes("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd");
var hash = hasher.ComputeHashHex(input);
Assert.Equal("debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732", hash);
}
[Fact]
public async Task Sm2_SignAndVerify_WithTestKey_Succeeds()
{
// Note: This test uses the existing BouncyCastle SM2 implementation
// Full OSCCA test vector validation requires actual test key material
// which would be loaded from GM/T 0003-2012 Appendix A
// For now, we test that the sign/verify cycle works correctly
// with a test key (not from OSCCA vectors)
var testData = Encoding.UTF8.GetBytes("Test message for SM2 signature");
// Generate test key (in production, load from OSCCA test vectors)
var keyPair = GenerateTestSm2KeyPair();
var keyId = "test-sm2-key";
// Create signing key
var signingKey = new CryptoSigningKey(
new CryptoKeyReference(keyId),
"SM2",
SerializeSm2PrivateKey(keyPair),
DateTimeOffset.UtcNow
);
_provider.UpsertSigningKey(signingKey);
// Get signer
var signer = _provider.GetSigner("SM2", new CryptoKeyReference(keyId));
// Sign
var signature = await signer.SignAsync(testData);
Assert.NotNull(signature);
Assert.NotEmpty(signature);
// Verify
var isValid = await signer.VerifyAsync(testData, signature);
Assert.True(isValid);
// Verify with modified data fails
var modifiedData = Encoding.UTF8.GetBytes("Modified message");
var isInvalid = await signer.VerifyAsync(modifiedData, signature);
Assert.False(isInvalid);
}
[Fact]
public void Sm2_ExportPublicJsonWebKey_ReturnsValidJwk()
{
var keyPair = GenerateTestSm2KeyPair();
var keyId = "test-jwk-export";
var signingKey = new CryptoSigningKey(
new CryptoKeyReference(keyId),
"SM2",
SerializeSm2PrivateKey(keyPair),
DateTimeOffset.UtcNow
);
_provider.UpsertSigningKey(signingKey);
var signer = _provider.GetSigner("SM2", new CryptoKeyReference(keyId));
var jwk = signer.ExportPublicJsonWebKey();
Assert.NotNull(jwk);
Assert.Equal("EC", jwk.Kty);
Assert.Equal("SM2", jwk.Crv);
Assert.Equal("SM2", jwk.Alg);
Assert.Equal("sig", jwk.Use);
Assert.Equal(keyId, jwk.Kid);
Assert.NotNull(jwk.X);
Assert.NotNull(jwk.Y);
}
// Helper methods for test key generation
private static Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair GenerateTestSm2KeyPair()
{
var curve = Org.BouncyCastle.Asn1.GM.GMNamedCurves.GetByName("sm2p256v1");
var domainParams = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(
curve.Curve, curve.G, curve.N, curve.H, curve.GetSeed());
var generator = new Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator();
generator.Init(new Org.BouncyCastle.Crypto.KeyGenerationParameters(
new Org.BouncyCastle.Security.SecureRandom(), 256));
var keyParams = new Org.BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters(
domainParams, new Org.BouncyCastle.Security.SecureRandom());
generator.Init(keyParams);
return generator.GenerateKeyPair();
}
private static byte[] SerializeSm2PrivateKey(Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keyPair)
{
var privateKey = (Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters)keyPair.Private;
// Serialize to PKCS#8 DER format
var privateKeyInfo = Org.BouncyCastle.Pkcs.PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
return privateKeyInfo.GetEncoded();
}
}
///
/// SM2 algorithm constants.
///
public static class SignatureAlgorithms
{
public const string Sm2 = "SM2";
}
///
/// SM3 hash algorithm constants.
///
public static class HashAlgorithms
{
public const string Sm3 = "SM3";
}