using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Configuration; using StellaOps.Doctor.Models; using StellaOps.Doctor.Plugins; namespace StellaOps.Doctor.Plugins.Cryptography.Checks; /// /// Validates eIDAS (EU qualified signatures) provider configuration. /// public sealed class EidasProviderCheck : IDoctorCheck { /// public string CheckId => "check.crypto.eidas"; /// public string Name => "eIDAS Provider"; /// public string Description => "Validates eIDAS qualified signature provider configuration"; /// public DoctorSeverity DefaultSeverity => DoctorSeverity.Info; /// public IReadOnlyList Tags => ["cryptography", "eidas", "qualified", "eu", "regional"]; /// public TimeSpan EstimatedDuration => TimeSpan.FromMilliseconds(100); /// public bool CanRun(DoctorPluginContext context) { // Only run if eIDAS is configured or enabled var eidasEnabled = context.Configuration.GetValue("Cryptography:Eidas:Enabled") ?? context.Configuration.GetValue("Cryptography:EnableEidas"); return eidasEnabled == true; } /// public Task RunAsync(DoctorPluginContext context, CancellationToken ct) { var result = context.CreateResult(CheckId, "stellaops.doctor.cryptography", DoctorCategory.Cryptography.ToString()); var eidasProvider = context.Configuration.GetValue("Cryptography:Eidas:Provider") ?? "pkcs11"; var trustListUrl = context.Configuration.GetValue("Cryptography:Eidas:TrustListUrl") ?? "https://ec.europa.eu/tools/lotl/eu-lotl.xml"; var issues = new List(); var providerInfo = new Dictionary { ["ConfiguredProvider"] = eidasProvider, ["TrustListUrl"] = trustListUrl }; switch (eidasProvider.ToLowerInvariant()) { case "pkcs11": CheckPkcs11Eidas(issues, providerInfo, context.Configuration); break; case "certificate": CheckCertificateEidas(issues, providerInfo, context.Configuration); break; case "remote": CheckRemoteEidas(issues, providerInfo, context.Configuration); break; default: issues.Add($"Unknown eIDAS provider: {eidasProvider}"); break; } if (issues.Count > 0) { return Task.FromResult(result .Warn($"{issues.Count} eIDAS provider issue(s)") .WithEvidence("eIDAS configuration", e => { foreach (var kvp in providerInfo) { e.Add(kvp.Key, kvp.Value); } }) .WithCauses(issues.ToArray()) .WithRemediation(r => r .AddManualStep(1, "Configure provider", "Configure PKCS#11 library or certificate store for eIDAS") .AddManualStep(2, "Verify trust list", "Ensure EU Trust List is accessible")) .WithVerification("stella doctor --check check.crypto.eidas") .Build()); } return Task.FromResult(result .Pass("eIDAS provider is configured") .WithEvidence("eIDAS configuration", e => { foreach (var kvp in providerInfo) { e.Add(kvp.Key, kvp.Value); } }) .Build()); } private static void CheckPkcs11Eidas(List issues, Dictionary providerInfo, IConfiguration config) { providerInfo["Provider"] = "PKCS#11 (Smart Card/HSM)"; var pkcs11Library = config.GetValue("Cryptography:Eidas:Pkcs11Library"); if (string.IsNullOrWhiteSpace(pkcs11Library)) { issues.Add("PKCS#11 library path not configured for eIDAS"); providerInfo["Library"] = "(not set)"; } else if (!File.Exists(pkcs11Library)) { issues.Add($"PKCS#11 library not found: {pkcs11Library}"); providerInfo["Library"] = pkcs11Library; } else { providerInfo["Library"] = pkcs11Library; } var slotId = config.GetValue("Cryptography:Eidas:SlotId"); providerInfo["SlotId"] = slotId?.ToString() ?? "(auto-detect)"; } private static void CheckCertificateEidas(List issues, Dictionary providerInfo, IConfiguration config) { providerInfo["Provider"] = "Certificate Store"; var certThumbprint = config.GetValue("Cryptography:Eidas:CertificateThumbprint"); var certPath = config.GetValue("Cryptography:Eidas:CertificatePath"); if (!string.IsNullOrWhiteSpace(certThumbprint)) { providerInfo["CertificateThumbprint"] = certThumbprint; // Try to find certificate in store try { using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); var certs = store.Certificates.Find( X509FindType.FindByThumbprint, certThumbprint, validOnly: false); if (certs.Count == 0) { issues.Add($"Certificate with thumbprint {certThumbprint[..8]}... not found in store"); } else { var cert = certs[0]; providerInfo["CertificateSubject"] = cert.Subject; providerInfo["CertificateExpiry"] = cert.NotAfter.ToString("yyyy-MM-dd"); } } catch (Exception ex) { issues.Add($"Cannot access certificate store: {ex.Message}"); } } else if (!string.IsNullOrWhiteSpace(certPath)) { providerInfo["CertificatePath"] = certPath; if (!File.Exists(certPath)) { issues.Add($"Certificate file not found: {certPath}"); } } else { issues.Add("No certificate thumbprint or path configured for eIDAS"); } } private static void CheckRemoteEidas(List issues, Dictionary providerInfo, IConfiguration config) { providerInfo["Provider"] = "Remote Signing Service"; var remoteEndpoint = config.GetValue("Cryptography:Eidas:RemoteEndpoint"); if (string.IsNullOrWhiteSpace(remoteEndpoint)) { issues.Add("Remote eIDAS signing endpoint not configured"); providerInfo["Endpoint"] = "(not set)"; } else { providerInfo["Endpoint"] = remoteEndpoint; } } }