201 lines
7.1 KiB
C#
201 lines
7.1 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Validates eIDAS (EU qualified signatures) provider configuration.
|
|
/// </summary>
|
|
public sealed class EidasProviderCheck : IDoctorCheck
|
|
{
|
|
/// <inheritdoc />
|
|
public string CheckId => "check.crypto.eidas";
|
|
|
|
/// <inheritdoc />
|
|
public string Name => "eIDAS Provider";
|
|
|
|
/// <inheritdoc />
|
|
public string Description => "Validates eIDAS qualified signature provider configuration";
|
|
|
|
/// <inheritdoc />
|
|
public DoctorSeverity DefaultSeverity => DoctorSeverity.Info;
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyList<string> Tags => ["cryptography", "eidas", "qualified", "eu", "regional"];
|
|
|
|
/// <inheritdoc />
|
|
public TimeSpan EstimatedDuration => TimeSpan.FromMilliseconds(100);
|
|
|
|
/// <inheritdoc />
|
|
public bool CanRun(DoctorPluginContext context)
|
|
{
|
|
// Only run if eIDAS is configured or enabled
|
|
var eidasEnabled = context.Configuration.GetValue<bool?>("Cryptography:Eidas:Enabled")
|
|
?? context.Configuration.GetValue<bool?>("Cryptography:EnableEidas");
|
|
|
|
return eidasEnabled == true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<DoctorCheckResult> RunAsync(DoctorPluginContext context, CancellationToken ct)
|
|
{
|
|
var result = context.CreateResult(CheckId, "stellaops.doctor.cryptography", DoctorCategory.Cryptography.ToString());
|
|
|
|
var eidasProvider = context.Configuration.GetValue<string>("Cryptography:Eidas:Provider")
|
|
?? "pkcs11";
|
|
|
|
var trustListUrl = context.Configuration.GetValue<string>("Cryptography:Eidas:TrustListUrl")
|
|
?? "https://ec.europa.eu/tools/lotl/eu-lotl.xml";
|
|
|
|
var issues = new List<string>();
|
|
var providerInfo = new Dictionary<string, string>
|
|
{
|
|
["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<string> issues, Dictionary<string, string> providerInfo, IConfiguration config)
|
|
{
|
|
providerInfo["Provider"] = "PKCS#11 (Smart Card/HSM)";
|
|
|
|
var pkcs11Library = config.GetValue<string>("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<int?>("Cryptography:Eidas:SlotId");
|
|
providerInfo["SlotId"] = slotId?.ToString() ?? "(auto-detect)";
|
|
}
|
|
|
|
private static void CheckCertificateEidas(List<string> issues, Dictionary<string, string> providerInfo, IConfiguration config)
|
|
{
|
|
providerInfo["Provider"] = "Certificate Store";
|
|
|
|
var certThumbprint = config.GetValue<string>("Cryptography:Eidas:CertificateThumbprint");
|
|
var certPath = config.GetValue<string>("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<string> issues, Dictionary<string, string> providerInfo, IConfiguration config)
|
|
{
|
|
providerInfo["Provider"] = "Remote Signing Service";
|
|
|
|
var remoteEndpoint = config.GetValue<string>("Cryptography:Eidas:RemoteEndpoint");
|
|
if (string.IsNullOrWhiteSpace(remoteEndpoint))
|
|
{
|
|
issues.Add("Remote eIDAS signing endpoint not configured");
|
|
providerInfo["Endpoint"] = "(not set)";
|
|
}
|
|
else
|
|
{
|
|
providerInfo["Endpoint"] = remoteEndpoint;
|
|
}
|
|
}
|
|
}
|