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:
master
2025-12-23 14:06:48 +02:00
parent ef933db0d8
commit 84d97fd22c
51 changed files with 4353 additions and 747 deletions

View File

@@ -0,0 +1,276 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_4100_0006_0002 - eIDAS Crypto Plugin Tests
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.Cryptography.Plugin.EIDAS;
using StellaOps.Cryptography.Plugin.EIDAS.Configuration;
using StellaOps.Cryptography.Plugin.EIDAS.DependencyInjection;
using StellaOps.Cryptography.Plugin.EIDAS.Models;
using Xunit;
namespace StellaOps.Cryptography.Plugin.EIDAS.Tests;
public class EidasCryptoProviderTests
{
private readonly ServiceProvider _serviceProvider;
private readonly EidasCryptoProvider _provider;
public EidasCryptoProviderTests()
{
var services = new ServiceCollection();
// Configure eIDAS options
services.Configure<EidasOptions>(options =>
{
options.SignatureLevel = SignatureLevel.AdES;
options.SignatureFormat = SignatureFormat.CAdES;
options.DefaultAlgorithm = "ECDSA-P256";
options.DigestAlgorithm = "SHA256";
// Add test key configuration
options.Keys.Add(new EidasKeyConfig
{
KeyId = "test-key-local",
Source = "local"
});
options.Keys.Add(new EidasKeyConfig
{
KeyId = "test-key-tsp",
Source = "tsp"
});
// Configure local signing (stub)
options.Local = new LocalSigningOptions
{
Type = "PKCS12",
Path = "/tmp/test-keystore.p12",
Password = "test-password"
};
// Configure TSP (stub)
options.Tsp = new TspOptions
{
Endpoint = "https://tsp.example.com",
ApiKey = "test-api-key"
};
});
services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug));
services.AddHttpClient<TrustServiceProviderClient>();
services.AddSingleton<LocalEidasProvider>();
services.AddSingleton<ICryptoProvider, EidasCryptoProvider>();
_serviceProvider = services.BuildServiceProvider();
_provider = _serviceProvider.GetRequiredService<ICryptoProvider>() as EidasCryptoProvider
?? throw new InvalidOperationException("Failed to resolve EidasCryptoProvider");
}
[Fact]
public void Provider_Name_IsEidas()
{
Assert.Equal("eidas", _provider.Name);
}
[Theory]
[InlineData(CryptoCapability.Signing, "ECDSA-P256", true)]
[InlineData(CryptoCapability.Signing, "ECDSA-P384", true)]
[InlineData(CryptoCapability.Signing, "ECDSA-P521", true)]
[InlineData(CryptoCapability.Signing, "RSA-PSS-2048", true)]
[InlineData(CryptoCapability.Signing, "RSA-PSS-4096", true)]
[InlineData(CryptoCapability.Signing, "EdDSA-Ed25519", true)]
[InlineData(CryptoCapability.Signing, "EdDSA-Ed448", true)]
[InlineData(CryptoCapability.Verification, "ECDSA-P256", true)]
[InlineData(CryptoCapability.Signing, "UNKNOWN-ALGO", false)]
[InlineData(CryptoCapability.ContentHashing, "ECDSA-P256", false)]
[InlineData(CryptoCapability.PasswordHashing, "ECDSA-P256", 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_ThrowsNotSupported()
{
Assert.Throws<NotSupportedException>(() => _provider.GetHasher("SHA256"));
}
[Fact]
public void GetSigner_ReturnsEidasSigner()
{
var keyRef = new CryptoKeyReference("test-key-local");
var signer = _provider.GetSigner("ECDSA-P256", keyRef);
Assert.NotNull(signer);
Assert.Equal("test-key-local", signer.KeyId);
Assert.Equal("ECDSA-P256", signer.AlgorithmId);
}
[Fact]
public void UpsertSigningKey_AddsKey()
{
var keyRef = new CryptoKeyReference("test-upsert");
var signingKey = new CryptoSigningKey(
keyRef,
"ECDSA-P256",
new byte[] { 1, 2, 3, 4 },
DateTimeOffset.UtcNow
);
_provider.UpsertSigningKey(signingKey);
var keys = _provider.GetSigningKeys();
Assert.Contains(keys, k => k.Reference.KeyId == "test-upsert");
}
[Fact]
public void RemoveSigningKey_RemovesKey()
{
var keyRef = new CryptoKeyReference("test-remove");
var signingKey = new CryptoSigningKey(
keyRef,
"ECDSA-P256",
new byte[] { 1, 2, 3, 4 },
DateTimeOffset.UtcNow
);
_provider.UpsertSigningKey(signingKey);
Assert.Contains(_provider.GetSigningKeys(), k => k.Reference.KeyId == "test-remove");
var removed = _provider.RemoveSigningKey("test-remove");
Assert.True(removed);
Assert.DoesNotContain(_provider.GetSigningKeys(), k => k.Reference.KeyId == "test-remove");
}
[Fact]
public void RemoveSigningKey_ReturnsFalseForNonExistentKey()
{
var removed = _provider.RemoveSigningKey("non-existent-key");
Assert.False(removed);
}
[Fact]
public async Task SignAsync_WithLocalKey_ReturnsSignature()
{
// Note: This test will use the stub implementation
// In production, would require actual PKCS#12 keystore
var keyRef = new CryptoKeyReference("test-key-local");
var signer = _provider.GetSigner("ECDSA-P256", keyRef);
var data = "Test data for signing"u8.ToArray();
var signature = await signer.SignAsync(data);
Assert.NotNull(signature);
Assert.NotEmpty(signature);
}
[Fact]
public async Task VerifyAsync_WithLocalKey_ReturnsTrue()
{
// Note: This test will use the stub implementation
// In production, would require actual PKCS#12 keystore
var keyRef = new CryptoKeyReference("test-key-local");
var signer = _provider.GetSigner("ECDSA-P256", keyRef);
var data = "Test data for verification"u8.ToArray();
var signature = await signer.SignAsync(data);
var isValid = await signer.VerifyAsync(data, signature);
Assert.True(isValid);
}
[Fact]
public async Task SignAsync_WithTspKey_ReturnsSignature()
{
// Note: This test will use the stub TSP implementation
// In production, would call actual TSP API
var keyRef = new CryptoKeyReference("test-key-tsp");
var signer = _provider.GetSigner("ECDSA-P256", keyRef);
var data = "Test data for TSP signing"u8.ToArray();
var signature = await signer.SignAsync(data);
Assert.NotNull(signature);
Assert.NotEmpty(signature);
}
[Fact]
public void ExportPublicJsonWebKey_ReturnsStubJwk()
{
var keyRef = new CryptoKeyReference("test-key-local");
var signer = _provider.GetSigner("ECDSA-P256", keyRef);
var jwk = signer.ExportPublicJsonWebKey();
Assert.NotNull(jwk);
Assert.Equal("EC", jwk.Kty);
Assert.Equal("P-256", jwk.Crv);
Assert.Equal("sig", jwk.Use);
Assert.Equal("test-key-local", jwk.Kid);
}
}
public class EidasDependencyInjectionTests
{
[Fact]
public void AddEidasCryptoProviders_RegistersServices()
{
var services = new ServiceCollection();
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["StellaOps:Crypto:Profiles:eidas:SignatureLevel"] = "AdES",
["StellaOps:Crypto:Profiles:eidas:SignatureFormat"] = "CAdES",
["StellaOps:Crypto:Profiles:eidas:DefaultAlgorithm"] = "ECDSA-P256"
})
.Build();
services.AddLogging();
services.AddEidasCryptoProviders(configuration);
var serviceProvider = services.BuildServiceProvider();
var provider = serviceProvider.GetService<ICryptoProvider>();
Assert.NotNull(provider);
Assert.IsType<EidasCryptoProvider>(provider);
}
[Fact]
public void AddEidasCryptoProviders_WithAction_RegistersServices()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddEidasCryptoProviders(options =>
{
options.SignatureLevel = SignatureLevel.QES;
options.SignatureFormat = SignatureFormat.XAdES;
options.DefaultAlgorithm = "RSA-PSS-4096";
});
var serviceProvider = services.BuildServiceProvider();
var provider = serviceProvider.GetService<ICryptoProvider>();
Assert.NotNull(provider);
Assert.IsType<EidasCryptoProvider>(provider);
var eidasOptions = serviceProvider.GetRequiredService<IOptions<EidasOptions>>().Value;
Assert.Equal(SignatureLevel.QES, eidasOptions.SignatureLevel);
Assert.Equal(SignatureFormat.XAdES, eidasOptions.SignatureFormat);
Assert.Equal("RSA-PSS-4096", eidasOptions.DefaultAlgorithm);
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.EIDAS\StellaOps.Cryptography.Plugin.EIDAS.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>