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.
125 lines
4.3 KiB
C#
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.");
|
|
}
|
|
}
|