Files
git.stella-ops.org/src/__Libraries/StellaOps.Cryptography.Plugin.EIDAS/EidasCryptoProvider.cs

202 lines
7.5 KiB
C#

// SPDX-License-Identifier: BUSL-1.1
// 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
};
}
}