Add PKCS#11 startup self-test health reporting
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
using EnvelopeGenerator.Application.Common.Configurations;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Net.Pkcs11Interop.Common;
|
||||
using Net.Pkcs11Interop.HighLevelAPI;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace EnvelopeGenerator.Infrastructure.Crypt.Services;
|
||||
|
||||
internal sealed class Pkcs11StartupSelfTestHostedService(
|
||||
IOptions<Pkcs11Options> options,
|
||||
Pkcs11SelfTestStatusStore statusStore,
|
||||
ILogger<Pkcs11StartupSelfTestHostedService> logger) : IHostedService
|
||||
{
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var pkcs11 = options.Value;
|
||||
|
||||
try
|
||||
{
|
||||
RunInteropProbe(pkcs11);
|
||||
statusStore.SetStatus(new Pkcs11SelfTestStatusDto(
|
||||
Completed: true,
|
||||
Success: true,
|
||||
Mode: "interop",
|
||||
Summary: "PKCS#11 startup self-test succeeded via interop.",
|
||||
CheckedAtUtc: DateTimeOffset.UtcNow,
|
||||
InteropError: null,
|
||||
ToolError: null));
|
||||
logger.LogInformation("PKCS#11 startup self-test succeeded (mode=interop, library={LibraryPath}, proxy={ProxySocket}).", pkcs11.ResolvedLibraryPath, pkcs11.ProxySocket);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception interopEx) when (Pkcs11ToolBridge.IsAvailable(pkcs11))
|
||||
{
|
||||
try
|
||||
{
|
||||
RunToolProbe(pkcs11);
|
||||
statusStore.SetStatus(new Pkcs11SelfTestStatusDto(
|
||||
Completed: true,
|
||||
Success: true,
|
||||
Mode: "pkcs11-tool",
|
||||
Summary: "PKCS#11 interop failed, tool fallback succeeded.",
|
||||
CheckedAtUtc: DateTimeOffset.UtcNow,
|
||||
InteropError: interopEx.Message,
|
||||
ToolError: null));
|
||||
logger.LogWarning(interopEx, "PKCS#11 interop self-test failed, but tool fallback is healthy (mode=pkcs11-tool, tool={ToolPath}).", pkcs11.ResolvedToolPath);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception toolEx)
|
||||
{
|
||||
statusStore.SetStatus(new Pkcs11SelfTestStatusDto(
|
||||
Completed: true,
|
||||
Success: false,
|
||||
Mode: "failed",
|
||||
Summary: "PKCS#11 startup self-test failed for both interop and tool fallback.",
|
||||
CheckedAtUtc: DateTimeOffset.UtcNow,
|
||||
InteropError: interopEx.Message,
|
||||
ToolError: toolEx.Message));
|
||||
logger.LogError(toolEx, "PKCS#11 startup self-test failed for both interop and tool fallback. library={LibraryPath}, proxy={ProxySocket}, tool={ToolPath}", pkcs11.ResolvedLibraryPath, pkcs11.ProxySocket, pkcs11.ResolvedToolPath);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
statusStore.SetStatus(new Pkcs11SelfTestStatusDto(
|
||||
Completed: true,
|
||||
Success: false,
|
||||
Mode: "failed",
|
||||
Summary: "PKCS#11 startup self-test failed via interop and no tool fallback is available.",
|
||||
CheckedAtUtc: DateTimeOffset.UtcNow,
|
||||
InteropError: ex.Message,
|
||||
ToolError: null));
|
||||
logger.LogError(ex, "PKCS#11 startup self-test failed (mode=interop, no fallback available). library={LibraryPath}, proxy={ProxySocket}", pkcs11.ResolvedLibraryPath, pkcs11.ProxySocket);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
private static void RunInteropProbe(Pkcs11Options options)
|
||||
{
|
||||
Pkcs11SessionRunner.Run(options, session =>
|
||||
{
|
||||
EnsureObjectExists(session, CKO.CKO_CERTIFICATE, options.CertificateLabel, "certificate");
|
||||
EnsureObjectExists(session, CKO.CKO_PRIVATE_KEY, options.PrivateKeyLabel, "private key");
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static void RunToolProbe(Pkcs11Options options)
|
||||
{
|
||||
_ = Pkcs11ToolBridge.ReadCertificateBytes(options);
|
||||
var testDigest = SHA256.HashData([1, 2, 3, 4]);
|
||||
var digestInfo = Pkcs11DigestInfoBuilder.Build(testDigest, HashAlgorithmName.SHA256);
|
||||
_ = Pkcs11ToolBridge.SignDigestInfo(options, digestInfo);
|
||||
}
|
||||
|
||||
private static void EnsureObjectExists(ISession session, CKO objectClass, string label, string objectType)
|
||||
{
|
||||
var attrs = new List<IObjectAttribute>
|
||||
{
|
||||
session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, objectClass),
|
||||
session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, label)
|
||||
};
|
||||
|
||||
var handles = session.FindAllObjects(attrs);
|
||||
if (handles.Count == 0)
|
||||
throw new InvalidOperationException($"PKCS#11 {objectType} '{label}' not found in token.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user