From 7766e2ea25d8fcf5c9782c04bc415b57870f29c9 Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 13 Oct 2026 14:44:06 +0200 Subject: [PATCH] Add PKCS#11 support for PDF signing and validation Introduced `Pkcs11Options` for configuring PKCS#11 signing workflows, including properties for library path, token selection, and signing metadata. Added `PdfSignOptionsDto` to represent signing options and `PdfSignatureCheckResultDto` with related DTOs for signature validation. Defined `PdfSignatureCertificationLevel` enum for certification levels. Added `IPdfDigitalSignatureService` interface to support signing and validating PDF signatures. These changes enable robust and extensible handling of digital signatures. --- .../Common/Configurations/Pkcs11Options.cs | 85 +++++++++++++++++++ .../Common/Dto/PdfSignOptionsDto.cs | 14 +++ .../Common/Dto/PdfSignatureCheckResultDto.cs | 52 ++++++++++++ .../Enums/PdfSignatureCertificationLevel.cs | 9 ++ .../Services/IPdfDigitalSignatureService.cs | 38 +++++++++ 5 files changed, 198 insertions(+) create mode 100644 EnvelopeGenerator.Application/Common/Configurations/Pkcs11Options.cs create mode 100644 EnvelopeGenerator.Application/Common/Dto/PdfSignOptionsDto.cs create mode 100644 EnvelopeGenerator.Application/Common/Dto/PdfSignatureCheckResultDto.cs create mode 100644 EnvelopeGenerator.Application/Common/Enums/PdfSignatureCertificationLevel.cs create mode 100644 EnvelopeGenerator.Application/Common/Interfaces/Services/IPdfDigitalSignatureService.cs diff --git a/EnvelopeGenerator.Application/Common/Configurations/Pkcs11Options.cs b/EnvelopeGenerator.Application/Common/Configurations/Pkcs11Options.cs new file mode 100644 index 00000000..f2a8f092 --- /dev/null +++ b/EnvelopeGenerator.Application/Common/Configurations/Pkcs11Options.cs @@ -0,0 +1,85 @@ +using EnvelopeGenerator.Application.Common.Enums; +using System.ComponentModel.DataAnnotations; + +namespace EnvelopeGenerator.Application.Common.Configurations; + +/// +/// Represents PKCS#11 signing configuration for PDF signing workflows. +/// This options model maps to the Crypt:Pkcs11 configuration section. +/// +public sealed class Pkcs11Options +{ + /// + /// Configuration section path used for binding PKCS#11 options. + /// + public const string SectionName = "Crypt:Pkcs11"; + + /// + /// Absolute or resolvable file path of the PKCS#11 provider library. + /// + [Required] + [MinLength(1)] + public required string LibraryPath { get; set; } + + /// + /// Optional numeric slot identifier used to directly select the target token slot. + /// + public ulong? SlotId { get; set; } + + /// + /// Optional token label used as a fallback token selector when is not provided. + /// + public string? TokenLabel { get; set; } + + /// + /// Optional PKCS#11 proxy endpoint (for example tcp://host:port). + /// + public string? ProxySocket { get; set; } + + /// + /// Optional full path to pkcs11-tool, used by diagnostics and fallback paths. + /// + public string? ToolPath { get; set; } + + /// + /// User PIN used to authenticate to the PKCS#11 token session. + /// + [Required] + [MinLength(1)] + public required string UserPin { get; set; } + + /// + /// Label of the private key object used to create digital signatures. + /// + [Required] + [MinLength(1)] + public required string PrivateKeyLabel { get; set; } + + /// + /// Label of the certificate object returned/embedded with the signature. + /// + [Required] + [MinLength(1)] + public required string CertificateLabel { get; set; } + + /// + /// Optional global default signing reason used when request-level options do not specify a value. + /// + public string? DefaultReason { get; set; } + + /// + /// Optional global default application name used when request-level options do not specify a value. + /// + public string? DefaultApplicationName { get; set; } + + /// + /// Controls how fallback signing time is created when request-level options do not specify a value. + /// true uses , false uses . + /// + public bool UseUtcSigningTime { get; set; } = true; + + /// + /// Optional global default certification level used when request-level options do not specify a value. + /// + public PdfSignatureCertificationLevel? DefaultCertificationLevel { get; set; } +} diff --git a/EnvelopeGenerator.Application/Common/Dto/PdfSignOptionsDto.cs b/EnvelopeGenerator.Application/Common/Dto/PdfSignOptionsDto.cs new file mode 100644 index 00000000..a6c0ed26 --- /dev/null +++ b/EnvelopeGenerator.Application/Common/Dto/PdfSignOptionsDto.cs @@ -0,0 +1,14 @@ +using EnvelopeGenerator.Application.Common.Enums; + +namespace EnvelopeGenerator.Application.Common.Dto; + +public sealed record PdfSignOptionsDto +{ + public string? Name { get; set; } + public string? Reason { get; set; } + public string? Location { get; set; } + public string? ContactInfo { get; set; } + public DateTimeOffset? SigningTime { get; set; } + public string? ApplicationName { get; set; } + public PdfSignatureCertificationLevel? CertificationLevel { get; set; } +} diff --git a/EnvelopeGenerator.Application/Common/Dto/PdfSignatureCheckResultDto.cs b/EnvelopeGenerator.Application/Common/Dto/PdfSignatureCheckResultDto.cs new file mode 100644 index 00000000..3029ed2c --- /dev/null +++ b/EnvelopeGenerator.Application/Common/Dto/PdfSignatureCheckResultDto.cs @@ -0,0 +1,52 @@ +namespace EnvelopeGenerator.Application.Common.Dto; + +public sealed record PdfSignaturePrincipalDto( + string? CommonName, + string? Organization, + string? Country, + string? Full); + +public sealed record PdfSignatureCertificateInfoDto( + PdfSignaturePrincipalDto IssuedTo, + PdfSignaturePrincipalDto IssuedBy, + string? SerialNumber, + DateTimeOffset? ValidFrom, + DateTimeOffset? ValidTo, + bool SelfSigned, + bool Expired, + bool NotYetValid, + bool IsSigner, + int? Version, + string? KeyAlgorithm, + string? SignatureAlgorithm, + string? Pem, + int? KeySizeBits); + +public sealed record PdfSignatureCheckItemDto( + bool CoversWholeDoc, + string? Reason, + string? Location, + string? DeclaredName, + string? ContactInfo, + string? DigestAlgorithm, + string? SignatureAlgorithm, + string? SignerName, + string? Issuer, + string? SerialNumber, + DateTimeOffset? ValidFrom, + DateTimeOffset? ValidTo, + string ExpiryCheckedAgainst, + IReadOnlyList Certificates, + bool Unchanged, + string? FieldName, + bool IsCryptographicallyValid, + string? SigningTime, + string? Filter, + string? SubFilter, + string? ApplicationName, + string? MessageDigestHex, + string? Error); + +public sealed record PdfSignatureCheckResultDto( + bool Signed, + IReadOnlyList Signatures); diff --git a/EnvelopeGenerator.Application/Common/Enums/PdfSignatureCertificationLevel.cs b/EnvelopeGenerator.Application/Common/Enums/PdfSignatureCertificationLevel.cs new file mode 100644 index 00000000..f629465e --- /dev/null +++ b/EnvelopeGenerator.Application/Common/Enums/PdfSignatureCertificationLevel.cs @@ -0,0 +1,9 @@ +namespace EnvelopeGenerator.Application.Common.Enums; + +public enum PdfSignatureCertificationLevel +{ + NoCertification = 0, + NoChangesAllowed = 1, + FormFillingAndSigning = 2, + FormFillingAndAnnotations = 3 +} diff --git a/EnvelopeGenerator.Application/Common/Interfaces/Services/IPdfDigitalSignatureService.cs b/EnvelopeGenerator.Application/Common/Interfaces/Services/IPdfDigitalSignatureService.cs new file mode 100644 index 00000000..84fe4b80 --- /dev/null +++ b/EnvelopeGenerator.Application/Common/Interfaces/Services/IPdfDigitalSignatureService.cs @@ -0,0 +1,38 @@ +using EnvelopeGenerator.Application.Common.Dto; + +namespace EnvelopeGenerator.Application.Common.Interfaces.Services; + +/// +/// Defines PDF digital-signing operations for the application boundary. +/// +public interface IPdfDigitalSignatureService +{ + /// + /// Creates a digital signature for the provided PDF bytes and returns + /// a new signed PDF document. + /// + /// Source PDF content as a byte array. + /// Optional signing options that map to PDF signature metadata fields. + /// Cancellation token for cooperative cancellation. + /// The signed PDF as a byte array. + Task SignPdfAsync(byte[] pdfBytes, PdfSignOptionsDto? options = null, CancellationToken cancellationToken = default); + + /// + /// Applies multiple signatures to the provided PDF in sequence and returns + /// the final signed PDF document. + /// + /// Source PDF content as a byte array. + /// Ordered signing options; one signature is applied per item. + /// Cancellation token for cooperative cancellation. + /// The signed PDF as a byte array. + Task SignPdfAsync(byte[] pdfBytes, IReadOnlyList optionsList, CancellationToken cancellationToken = default); + + /// + /// Checks whether the provided PDF contains signature field(s) and whether + /// those signatures are cryptographically valid. + /// + /// PDF content to inspect. + /// Cancellation token for cooperative cancellation. + /// A structured result containing signature presence and per-signature details. + Task CheckPdfSignatureAsync(byte[] pdfBytes, CancellationToken cancellationToken = default); +}