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.
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
using EnvelopeGenerator.Application.Common.Enums;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Common.Configurations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents PKCS#11 signing configuration for PDF signing workflows.
|
||||||
|
/// This options model maps to the <c>Crypt:Pkcs11</c> configuration section.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class Pkcs11Options
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configuration section path used for binding PKCS#11 options.
|
||||||
|
/// </summary>
|
||||||
|
public const string SectionName = "Crypt:Pkcs11";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Absolute or resolvable file path of the PKCS#11 provider library.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[MinLength(1)]
|
||||||
|
public required string LibraryPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional numeric slot identifier used to directly select the target token slot.
|
||||||
|
/// </summary>
|
||||||
|
public ulong? SlotId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional token label used as a fallback token selector when <see cref="SlotId"/> is not provided.
|
||||||
|
/// </summary>
|
||||||
|
public string? TokenLabel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional PKCS#11 proxy endpoint (for example <c>tcp://host:port</c>).
|
||||||
|
/// </summary>
|
||||||
|
public string? ProxySocket { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional full path to <c>pkcs11-tool</c>, used by diagnostics and fallback paths.
|
||||||
|
/// </summary>
|
||||||
|
public string? ToolPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User PIN used to authenticate to the PKCS#11 token session.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[MinLength(1)]
|
||||||
|
public required string UserPin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Label of the private key object used to create digital signatures.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[MinLength(1)]
|
||||||
|
public required string PrivateKeyLabel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Label of the certificate object returned/embedded with the signature.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
[MinLength(1)]
|
||||||
|
public required string CertificateLabel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional global default signing reason used when request-level options do not specify a value.
|
||||||
|
/// </summary>
|
||||||
|
public string? DefaultReason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional global default application name used when request-level options do not specify a value.
|
||||||
|
/// </summary>
|
||||||
|
public string? DefaultApplicationName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls how fallback signing time is created when request-level options do not specify a value.
|
||||||
|
/// <c>true</c> uses <see cref="DateTimeOffset.UtcNow"/>, <c>false</c> uses <see cref="DateTimeOffset.Now"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseUtcSigningTime { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional global default certification level used when request-level options do not specify a value.
|
||||||
|
/// </summary>
|
||||||
|
public PdfSignatureCertificationLevel? DefaultCertificationLevel { get; set; }
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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<PdfSignatureCertificateInfoDto> 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<PdfSignatureCheckItemDto> Signatures);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace EnvelopeGenerator.Application.Common.Enums;
|
||||||
|
|
||||||
|
public enum PdfSignatureCertificationLevel
|
||||||
|
{
|
||||||
|
NoCertification = 0,
|
||||||
|
NoChangesAllowed = 1,
|
||||||
|
FormFillingAndSigning = 2,
|
||||||
|
FormFillingAndAnnotations = 3
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using EnvelopeGenerator.Application.Common.Dto;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines PDF digital-signing operations for the application boundary.
|
||||||
|
/// </summary>
|
||||||
|
public interface IPdfDigitalSignatureService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a digital signature for the provided PDF bytes and returns
|
||||||
|
/// a new signed PDF document.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pdfBytes">Source PDF content as a byte array.</param>
|
||||||
|
/// <param name="options">Optional signing options that map to PDF signature metadata fields.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||||
|
/// <returns>The signed PDF as a byte array.</returns>
|
||||||
|
Task<byte[]> SignPdfAsync(byte[] pdfBytes, PdfSignOptionsDto? options = null, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies multiple signatures to the provided PDF in sequence and returns
|
||||||
|
/// the final signed PDF document.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pdfBytes">Source PDF content as a byte array.</param>
|
||||||
|
/// <param name="optionsList">Ordered signing options; one signature is applied per item.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||||
|
/// <returns>The signed PDF as a byte array.</returns>
|
||||||
|
Task<byte[]> SignPdfAsync(byte[] pdfBytes, IReadOnlyList<PdfSignOptionsDto> optionsList, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the provided PDF contains signature field(s) and whether
|
||||||
|
/// those signatures are cryptographically valid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pdfBytes">PDF content to inspect.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||||
|
/// <returns>A structured result containing signature presence and per-signature details.</returns>
|
||||||
|
Task<PdfSignatureCheckResultDto> CheckPdfSignatureAsync(byte[] pdfBytes, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user