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;
}
}
}