Files
EnvelopeGenerator/EnvelopeGenerator.Infrastructure.Crypt/Services/Pkcs11SessionRunner.cs
TekH f361898ba4 Add PKCS#11-based PDF signing infrastructure
Introduced a cryptographic infrastructure for PDF signing and validation using PKCS#11-based hardware security modules (HSMs) or software tokens. Key changes include:

- Added `PdfDigitalSignatureService` for signing PDFs, multi-signature support, and signature validation.
- Implemented `Pkcs11ExternalSigner` and `Pkcs11PdfSigningRsa` for PKCS#11-based signing with DevExpress PDF infrastructure.
- Added utilities like `Pkcs11SessionRunner`, `Pkcs11InteropBootstrap`, and `Pkcs11ToolBridge` for session management, library initialization, and fallback mechanisms.
- Introduced `Pkcs11Options` for managing PKCS#11 configurations.
- Updated project dependencies to include `DevExpress.Document.Processor`, `System.Security.Cryptography.Pkcs`, and `Pkcs11Interop`.
- Enhanced error handling and fallback support for PKCS#11 operations.

These changes enable robust and extensible PDF signing capabilities with detailed signature metadata extraction and validation.
2026-10-13 14:45:47 +02:00

125 lines
4.3 KiB
C#

using EnvelopeGenerator.Application.Common.Configurations;
using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
namespace EnvelopeGenerator.Infrastructure.Crypt.Services;
internal static class Pkcs11SessionRunner
{
public static T Run<T>(Pkcs11Options options, Func<ISession, T> action)
{
Pkcs11InteropBootstrap.EnsureLoaded(options);
var factories = new Pkcs11InteropFactories();
using var pkcs11 = LoadLibraryWithFallback(factories, options.LibraryPath, options.ProxySocket);
var slot = ResolveSlot(pkcs11.GetSlotList(SlotsType.WithTokenPresent), options);
using var session = slot.OpenSession(SessionType.ReadOnly);
session.Login(CKU.CKU_USER, options.UserPin);
try
{
return action(session);
}
finally
{
session.Logout();
}
}
private static IPkcs11Library LoadLibraryWithFallback(Pkcs11InteropFactories factories, string libraryPath, string? proxySocket)
{
List<Exception> errors = [];
foreach (string candidate in BuildLibraryCandidates(libraryPath))
{
try
{
return factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, candidate, AppType.MultiThreaded);
}
catch (Exception ex)
{
errors.Add(new InvalidOperationException($"{candidate} | MultiThreaded+WithFunctionList", ex));
}
try
{
return factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, candidate, AppType.SingleThreaded);
}
catch (Exception ex)
{
errors.Add(new InvalidOperationException($"{candidate} | SingleThreaded+WithFunctionList", ex));
}
try
{
return factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, candidate, AppType.MultiThreaded, InitType.WithoutFunctionList);
}
catch (Exception ex)
{
errors.Add(new InvalidOperationException($"{candidate} | MultiThreaded+WithoutFunctionList", ex));
}
try
{
return factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, candidate, AppType.SingleThreaded, InitType.WithoutFunctionList);
}
catch (Exception ex)
{
errors.Add(new InvalidOperationException($"{candidate} | SingleThreaded+WithoutFunctionList", ex));
}
}
var socketInfo = string.IsNullOrWhiteSpace(proxySocket) ? "<empty>" : proxySocket;
throw new AggregateException($"Unable to initialize PKCS#11 library '{libraryPath}' with any supported initialization mode. PKCS11_PROXY_SOCKET={socketInfo}", errors);
}
private static IEnumerable<string> BuildLibraryCandidates(string configuredPath)
{
var unique = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
configuredPath
};
var directory = Path.GetDirectoryName(configuredPath);
if (!string.IsNullOrWhiteSpace(directory))
{
string[] alternates =
[
"client.dll",
"pkcs11-proxy.dll",
"libpkcs11-proxy.dll"
];
foreach (var fileName in alternates)
{
var candidate = Path.Combine(directory, fileName);
if (File.Exists(candidate))
unique.Add(candidate);
}
}
return unique;
}
private static ISlot ResolveSlot(IEnumerable<ISlot> slots, Pkcs11Options options)
{
if (options.SlotId.HasValue)
{
var bySlotId = slots.FirstOrDefault(s => s.SlotId == options.SlotId.Value);
if (bySlotId is not null)
return bySlotId;
}
if (!string.IsNullOrWhiteSpace(options.TokenLabel))
{
var byTokenLabel = slots.FirstOrDefault(s =>
string.Equals(s.GetTokenInfo().Label?.Trim(), options.TokenLabel.Trim(), StringComparison.Ordinal));
if (byTokenLabel is not null)
return byTokenLabel;
}
throw new InvalidOperationException("Unable to resolve PKCS#11 slot. Check Crypt:Pkcs11:SlotId / TokenLabel.");
}
}