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.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user