- Added ServiceCollectionExtensions for eIDAS crypto providers. - Implemented EidasCryptoProvider for handling eIDAS-compliant signatures. - Created LocalEidasProvider for local signing using PKCS#12 keystores. - Defined SignatureLevel and SignatureFormat enums for eIDAS compliance. - Developed TrustServiceProviderClient for remote signing via TSP. - Added configuration support for eIDAS options in the project file. - Implemented unit tests for SM2 compliance and crypto operations. - Introduced dependency injection extensions for SM software and remote plugins.
231 lines
7.9 KiB
C#
231 lines
7.9 KiB
C#
// 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;
|
|
|
|
/// <summary>
|
|
/// OSCCA GM/T 0003-2012 compliance tests for SM2 signature algorithm.
|
|
/// Test vectors from Appendix A of the standard.
|
|
/// </summary>
|
|
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<SmSoftProviderOptions>(options =>
|
|
{
|
|
options.RequireEnvironmentGate = false;
|
|
});
|
|
|
|
services.AddSingleton<ICryptoProvider, SmSoftCryptoProvider>();
|
|
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
_provider = serviceProvider.GetRequiredService<ICryptoProvider>() 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<NotSupportedException>(() => _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<InvalidOperationException>(() => _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<byte>();
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// SM2 algorithm constants.
|
|
/// </summary>
|
|
public static class SignatureAlgorithms
|
|
{
|
|
public const string Sm2 = "SM2";
|
|
}
|
|
|
|
/// <summary>
|
|
/// SM3 hash algorithm constants.
|
|
/// </summary>
|
|
public static class HashAlgorithms
|
|
{
|
|
public const string Sm3 = "SM3";
|
|
}
|