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 CreateBaseArgs(Pkcs11Options options) { var args = new List { "--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 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); } }