feat(eidas): Implement eIDAS Crypto Plugin with dependency injection and signing capabilities
- 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.
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
// 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";
|
||||
}
|
||||
Reference in New Issue
Block a user