Renamed namespaces and projects from DocumentOperator to DocumentService, updating all references accordingly. Refactored PdfSignatureAppearanceDto and related enums to use properties with init accessors for improved clarity. Removed and re-added IPkcs11SelfTestStatusProvider with updated namespace. Simplified dependency injection by removing DocumentOperatorApplication registration. Updated appsettings.json with a specific AssetsRootPath. Incremented version numbers in project files for a new release. Introduced PdfSignatureClient and IPdfSignatureClient for PDF digital-signing operations. Updated project files to use newer Microsoft.Extensions package versions. Removed all DocumentOperator references, completing the transition to DocumentService. Added new DTOs and enums for PDF signature options and appearance.
113 lines
4.9 KiB
C#
113 lines
4.9 KiB
C#
using DocumentService.Application.Common.Configuration;
|
|
using DocumentService.Application.Common.DTOs;
|
|
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 DocumentService.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.");
|
|
}
|
|
}
|