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.
148 lines
4.4 KiB
C#
148 lines
4.4 KiB
C#
using EnvelopeGenerator.Application.Common.Configurations;
|
|
using System.Diagnostics;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace EnvelopeGenerator.Infrastructure.Crypt.Services;
|
|
|
|
internal static class Pkcs11ToolBridge
|
|
{
|
|
public static bool IsAvailable(Pkcs11Options options)
|
|
{
|
|
return !string.IsNullOrWhiteSpace(options.ToolPath) && File.Exists(options.ToolPath);
|
|
}
|
|
|
|
public static byte[] ReadCertificateBytes(Pkcs11Options options)
|
|
{
|
|
EnsureAvailable(options);
|
|
|
|
var outputFile = Path.Combine(Path.GetTempPath(), $"pkcs11-cert-{Guid.NewGuid():N}.der");
|
|
try
|
|
{
|
|
var args = CreateBaseArgs(options);
|
|
args.Add("--read-object");
|
|
args.Add("--type");
|
|
args.Add("cert");
|
|
args.Add("--label");
|
|
args.Add(options.CertificateLabel);
|
|
args.Add("--output-file");
|
|
args.Add(outputFile);
|
|
|
|
Execute(options, args);
|
|
return File.ReadAllBytes(outputFile);
|
|
}
|
|
finally
|
|
{
|
|
DeleteIfExists(outputFile);
|
|
}
|
|
}
|
|
|
|
public static byte[] SignDigestInfo(Pkcs11Options options, byte[] digestInfo)
|
|
{
|
|
EnsureAvailable(options);
|
|
|
|
var inputFile = Path.Combine(Path.GetTempPath(), $"pkcs11-sign-in-{Guid.NewGuid():N}.bin");
|
|
var outputFile = Path.Combine(Path.GetTempPath(), $"pkcs11-sign-out-{Guid.NewGuid():N}.bin");
|
|
|
|
try
|
|
{
|
|
File.WriteAllBytes(inputFile, digestInfo);
|
|
|
|
var args = CreateBaseArgs(options);
|
|
args.Add("--sign");
|
|
args.Add("--mechanism");
|
|
args.Add("RSA-PKCS");
|
|
args.Add("--type");
|
|
args.Add("privkey");
|
|
args.Add("--label");
|
|
args.Add(options.PrivateKeyLabel);
|
|
args.Add("--input-file");
|
|
args.Add(inputFile);
|
|
args.Add("--output-file");
|
|
args.Add(outputFile);
|
|
|
|
Execute(options, args);
|
|
return File.ReadAllBytes(outputFile);
|
|
}
|
|
finally
|
|
{
|
|
DeleteIfExists(inputFile);
|
|
DeleteIfExists(outputFile);
|
|
}
|
|
}
|
|
|
|
private static List<string> CreateBaseArgs(Pkcs11Options options)
|
|
{
|
|
var args = new List<string>
|
|
{
|
|
"--module",
|
|
options.LibraryPath,
|
|
"--login",
|
|
"--pin",
|
|
options.UserPin
|
|
};
|
|
|
|
if (options.SlotId.HasValue)
|
|
{
|
|
args.Add("--slot");
|
|
args.Add(options.SlotId.Value.ToString());
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(options.TokenLabel))
|
|
{
|
|
args.Add("--token-label");
|
|
args.Add(options.TokenLabel);
|
|
}
|
|
|
|
return args;
|
|
}
|
|
|
|
private static void Execute(Pkcs11Options options, IReadOnlyList<string> arguments)
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = options.ToolPath!,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
}
|
|
};
|
|
|
|
foreach (var argument in arguments)
|
|
process.StartInfo.ArgumentList.Add(argument);
|
|
|
|
if (!string.IsNullOrWhiteSpace(options.ProxySocket))
|
|
{
|
|
process.StartInfo.Environment["PKCS11_PROXY_SOCKET"] = options.ProxySocket;
|
|
process.StartInfo.Environment["PKCS11_DAEMON_SOCKET"] = options.ProxySocket;
|
|
}
|
|
|
|
process.Start();
|
|
|
|
string stdOut = process.StandardOutput.ReadToEnd();
|
|
string stdErr = process.StandardError.ReadToEnd();
|
|
|
|
if (!process.WaitForExit(30000))
|
|
{
|
|
try { process.Kill(entireProcessTree: true); } catch { }
|
|
throw new TimeoutException($"pkcs11-tool timed out. Args: {string.Join(' ', process.StartInfo.ArgumentList)}");
|
|
}
|
|
|
|
if (process.ExitCode != 0)
|
|
throw new InvalidOperationException($"pkcs11-tool failed with exit code {process.ExitCode}. StdOut: {stdOut} StdErr: {stdErr}");
|
|
}
|
|
|
|
private static void EnsureAvailable(Pkcs11Options options)
|
|
{
|
|
if (!IsAvailable(options))
|
|
throw new InvalidOperationException("pkcs11-tool is not available. Configure Crypt:Pkcs11:ToolPath.");
|
|
}
|
|
|
|
private static void DeleteIfExists(string path)
|
|
{
|
|
if (File.Exists(path))
|
|
File.Delete(path);
|
|
}
|
|
}
|