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,201 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Sprint: SPRINT_4100_0006_0002 - eIDAS Crypto Plugin
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.EIDAS.Configuration;
|
||||
|
||||
namespace StellaOps.Cryptography.Plugin.EIDAS;
|
||||
|
||||
/// <summary>
|
||||
/// eIDAS-compliant crypto provider for European digital signatures.
|
||||
/// Supports QES (Qualified), AES (Advanced), and AdES (Standard) signature levels
|
||||
/// per Regulation (EU) No 910/2014.
|
||||
/// </summary>
|
||||
public class EidasCryptoProvider : ICryptoProvider
|
||||
{
|
||||
public string Name => "eidas";
|
||||
|
||||
private readonly ILogger<EidasCryptoProvider> _logger;
|
||||
private readonly EidasOptions _options;
|
||||
private readonly TrustServiceProviderClient _tspClient;
|
||||
private readonly LocalEidasProvider _localProvider;
|
||||
private readonly Dictionary<string, CryptoSigningKey> _signingKeys = new();
|
||||
|
||||
public EidasCryptoProvider(
|
||||
ILogger<EidasCryptoProvider> logger,
|
||||
IOptions<EidasOptions> options,
|
||||
TrustServiceProviderClient tspClient,
|
||||
LocalEidasProvider localProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_options = options.Value;
|
||||
_tspClient = tspClient;
|
||||
_localProvider = localProvider;
|
||||
}
|
||||
|
||||
public bool Supports(CryptoCapability capability, string algorithmId)
|
||||
{
|
||||
// eIDAS provider supports signing and verification only
|
||||
if (capability is not (CryptoCapability.Signing or CryptoCapability.Verification))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Supported algorithms: ECDSA-P256/384/521, RSA-PSS-2048/4096, EdDSA-Ed25519/448
|
||||
return algorithmId switch
|
||||
{
|
||||
"ECDSA-P256" or "ECDSA-P384" or "ECDSA-P521" => true,
|
||||
"RSA-PSS-2048" or "RSA-PSS-4096" => true,
|
||||
"EdDSA-Ed25519" or "EdDSA-Ed448" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
{
|
||||
throw new NotSupportedException("eIDAS plugin does not support password hashing");
|
||||
}
|
||||
|
||||
public ICryptoHasher GetHasher(string algorithmId)
|
||||
{
|
||||
throw new NotSupportedException("eIDAS plugin does not support content hashing - use BouncyCastle provider");
|
||||
}
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
||||
{
|
||||
// Return an eIDAS signer that routes to TSP or local provider
|
||||
return new EidasSigner(_logger, _options, _tspClient, _localProvider, algorithmId, keyReference);
|
||||
}
|
||||
|
||||
public void UpsertSigningKey(CryptoSigningKey signingKey)
|
||||
{
|
||||
_signingKeys[signingKey.Reference.KeyId] = signingKey;
|
||||
_logger.LogInformation("eIDAS signing key upserted: keyId={KeyId}", signingKey.Reference.KeyId);
|
||||
}
|
||||
|
||||
public bool RemoveSigningKey(string keyId)
|
||||
{
|
||||
var removed = _signingKeys.Remove(keyId);
|
||||
if (removed)
|
||||
{
|
||||
_logger.LogInformation("eIDAS signing key removed: keyId={KeyId}", keyId);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
|
||||
{
|
||||
return _signingKeys.Values.ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// eIDAS signer implementation that routes to TSP or local provider.
|
||||
/// </summary>
|
||||
internal class EidasSigner : ICryptoSigner
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly EidasOptions _options;
|
||||
private readonly TrustServiceProviderClient _tspClient;
|
||||
private readonly LocalEidasProvider _localProvider;
|
||||
private readonly string _algorithmId;
|
||||
private readonly CryptoKeyReference _keyReference;
|
||||
|
||||
public EidasSigner(
|
||||
ILogger logger,
|
||||
EidasOptions options,
|
||||
TrustServiceProviderClient tspClient,
|
||||
LocalEidasProvider localProvider,
|
||||
string algorithmId,
|
||||
CryptoKeyReference keyReference)
|
||||
{
|
||||
_logger = logger;
|
||||
_options = options;
|
||||
_tspClient = tspClient;
|
||||
_localProvider = localProvider;
|
||||
_algorithmId = algorithmId;
|
||||
_keyReference = keyReference;
|
||||
}
|
||||
|
||||
public string KeyId => _keyReference.KeyId;
|
||||
public string AlgorithmId => _algorithmId;
|
||||
|
||||
public async ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogDebug("eIDAS signing request: keyId={KeyId}, algorithm={Algorithm}",
|
||||
_keyReference.KeyId, _algorithmId);
|
||||
|
||||
// Resolve key configuration
|
||||
var keyConfig = _options.Keys.FirstOrDefault(k => k.KeyId == _keyReference.KeyId);
|
||||
if (keyConfig == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"eIDAS key '{_keyReference.KeyId}' not configured");
|
||||
}
|
||||
|
||||
// Route to appropriate signer based on key source
|
||||
byte[] signature = keyConfig.Source.ToLowerInvariant() switch
|
||||
{
|
||||
"tsp" => await _tspClient.RemoteSignAsync(data.ToArray(), _algorithmId, keyConfig, cancellationToken),
|
||||
"local" => await _localProvider.LocalSignAsync(data.ToArray(), _algorithmId, keyConfig, cancellationToken),
|
||||
_ => throw new InvalidOperationException($"Unsupported eIDAS key source: {keyConfig.Source}")
|
||||
};
|
||||
|
||||
_logger.LogInformation("eIDAS signature created: keyId={KeyId}, signatureLength={Length}, level={Level}",
|
||||
_keyReference.KeyId, signature.Length, _options.SignatureLevel);
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogDebug("eIDAS verification request: keyId={KeyId}, algorithm={Algorithm}",
|
||||
_keyReference.KeyId, _algorithmId);
|
||||
|
||||
// Resolve key configuration
|
||||
var keyConfig = _options.Keys.FirstOrDefault(k => k.KeyId == _keyReference.KeyId);
|
||||
if (keyConfig == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"eIDAS key '{_keyReference.KeyId}' not configured");
|
||||
}
|
||||
|
||||
// Route to appropriate verifier
|
||||
bool isValid = keyConfig.Source.ToLowerInvariant() switch
|
||||
{
|
||||
"tsp" => await _tspClient.RemoteVerifyAsync(data.ToArray(), signature.ToArray(), _algorithmId, keyConfig, cancellationToken),
|
||||
"local" => await _localProvider.LocalVerifyAsync(data.ToArray(), signature.ToArray(), _algorithmId, keyConfig, cancellationToken),
|
||||
_ => throw new InvalidOperationException($"Unsupported eIDAS key source: {keyConfig.Source}")
|
||||
};
|
||||
|
||||
_logger.LogInformation("eIDAS verification result: keyId={KeyId}, valid={Valid}",
|
||||
_keyReference.KeyId, isValid);
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public Microsoft.IdentityModel.Tokens.JsonWebKey ExportPublicJsonWebKey()
|
||||
{
|
||||
// For eIDAS, public key export requires certificate parsing
|
||||
// Stub implementation - in production, extract from certificate
|
||||
_logger.LogWarning("eIDAS ExportPublicJsonWebKey is not fully implemented - returning stub JWK");
|
||||
|
||||
var keyConfig = _options.Keys.FirstOrDefault(k => k.KeyId == _keyReference.KeyId);
|
||||
if (keyConfig?.Certificate != null)
|
||||
{
|
||||
// Production: Parse certificate and extract public key
|
||||
// var cert = X509Certificate2.CreateFromPem(keyConfig.Certificate);
|
||||
// var ecdsa = cert.GetECDsaPublicKey();
|
||||
// return JsonWebKeyConverter.ConvertFromECDsaSecurityKey(new ECDsaSecurityKey(ecdsa));
|
||||
}
|
||||
|
||||
return new Microsoft.IdentityModel.Tokens.JsonWebKey
|
||||
{
|
||||
Kty = "EC",
|
||||
Crv = "P-256",
|
||||
Use = "sig",
|
||||
Kid = _keyReference.KeyId,
|
||||
Alg = _algorithmId
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user