Public/Set-VellumPdfSignature.ps1
|
function Set-VellumPdfSignature { <# .SYNOPSIS Stages a PAdES digital signature to be applied when the document is saved. .DESCRIPTION Builds a VellumPdf.Signing.PdfSignatureSettings from the supplied certificate and metadata and stages it on the document. The signature is applied by Save-VellumPdfDocument, which signs the document while writing the file (VellumPdf signs at serialization time via SigningExtensions.Sign; a signature cannot be added to an already-written document through this module). The signature is a PAdES baseline signature (SubFilter ETSI.CAdES.detached). PDF/A conformance and signing compose: a PDF/A-2b document can be signed. Encryption and signing cannot be combined - the library rejects the combination at save time, and this cmdlet (and Protect-VellumPdfDocument) fail fast with a clear error instead. Calling Set-VellumPdfSignature again before saving replaces the staged signature settings, consistent with Set-* semantics. CERTIFICATE: any [X509Certificate2] with a private key works - from Get-PfxCertificate, the cert: drive (Cert:\CurrentUser\My\<thumbprint>), or X509CertificateLoader/X509Certificate2 .NET APIs. Long-term validation (LTV: embedded OCSP/CRL) is not yet provided by the library and is out of scope here. .PARAMETER Document The live VellumPdf document flowing through the pipeline. The same instance is returned after the signature settings are staged, enabling chaining. .PARAMETER Certificate The signing certificate. Must include a private key (HasPrivateKey). Typical sources: Get-PfxCertificate -FilePath ./signer.pfx, or Get-Item Cert:\CurrentUser\My\<thumbprint>. .PARAMETER Reason Optional reason for signing, recorded in the signature dictionary (/Reason) and shown by PDF viewers (e.g. 'Approved', 'I am the author'). .PARAMETER Location Optional physical or logical location of signing, recorded as /Location. .PARAMETER ContactInfo Optional contact information for the signer (e.g. an email address), recorded as /ContactInfo. .PARAMETER SignerName Optional display name of the signer, recorded as /Name. When omitted, viewers typically fall back to the certificate subject. .PARAMETER SigningTime Optional claimed signing time recorded in the signature. When omitted the library uses the current time at save. .EXAMPLE $cert = Get-PfxCertificate -FilePath ./signer.pfx New-VellumPdfDocument | Add-VellumPdfParagraph -Text 'Signed content.' | Set-VellumPdfSignature -Certificate $cert -Reason 'Approved' | Save-VellumPdfDocument -Path ./signed.pdf .EXAMPLE # Sign a PDF/A-2b archival document with a certificate from the store $cert = Get-Item Cert:\CurrentUser\My\1234567890ABCDEF1234567890ABCDEF12345678 New-VellumPdfDocument -Conformance PdfA2b | Set-VellumPdfDocumentInfo -Title 'Contract' -Author 'Acme' | Add-VellumPdfParagraph -Text 'Terms.' -FontHandle $font | Set-VellumPdfSignature -Certificate $cert -Location 'Amsterdam' ` -ContactInfo 'legal@acme.example' | Save-VellumPdfDocument -Path ./contract.pdf .OUTPUTS VellumPdf.Layout.Document (the same instance, for chaining) #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Mutates an in-memory document object only; no external/system state change.')] [CmdletBinding()] [OutputType([VellumPdf.Layout.Document])] param( [Parameter(Mandatory, ValueFromPipeline)] [VellumPdf.Layout.Document]$Document, [Parameter(Mandatory, Position = 0)] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [string]$Reason, [string]$Location, [string]$ContactInfo, [string]$SignerName, [System.Nullable[System.DateTimeOffset]]$SigningTime ) process { Assert-VellumPdfDocumentOpen -Document $Document -CommandName 'Set-VellumPdfSignature' # The library rejects Encrypt() + Sign() at save time; fail fast here # with the same constraint so the error points at the right cmdlet. if ($Document.PSObject.Properties['PSVellumProtected']) { throw ('Set-VellumPdfSignature: encryption and digital signatures cannot be combined. ' + 'Remove the Protect-VellumPdfDocument call to sign this document.') } if (-not $Certificate.HasPrivateKey) { throw ('Set-VellumPdfSignature: the certificate does not include a private key. ' + 'Signing requires the private key; load the certificate from a PFX ' + '(Get-PfxCertificate) or a store entry that has the key.') } $settings = [VellumPdf.Signing.PdfSignatureSettings]::new() $settings.Certificate = $Certificate if ($PSBoundParameters.ContainsKey('Reason')) { $settings.Reason = $Reason } if ($PSBoundParameters.ContainsKey('Location')) { $settings.Location = $Location } if ($PSBoundParameters.ContainsKey('ContactInfo')) { $settings.ContactInfo = $ContactInfo } if ($PSBoundParameters.ContainsKey('SignerName')) { $settings.SignerName = $SignerName } if ($PSBoundParameters.ContainsKey('SigningTime')) { $settings.SigningTime = $SigningTime } # Stage for Save-VellumPdfDocument; Set-* semantics allow replacing a # previously staged signature. $existing = $Document.PSObject.Properties['PSVellumSignature'] if ($existing) { $existing.Value = $settings } else { $Document.PSObject.Properties.Add([psnoteproperty]::new('PSVellumSignature', $settings)) } $Document } } |