Table of Contents

Sign and Verify PDF Digital Signatures

LM-Kit.NET ships a complete digital signature chain for PDF documents: PdfSigner produces PAdES (ETSI.CAdES.detached) and classic PKCS#7 signatures with optional certification, RFC 3161 timestamps, long-term validation (LTV) material, and visible appearances, while PdfSignatureValidator verifies any signed PDF against trust anchors you control. Everything runs 100% on-device and no AI model is required. Signing appends an incremental revision, so the original bytes survive verbatim and existing signatures keep verifying: signing an already-signed document simply adds the next signature.


Sign a Document

PdfSigningOptions.Certificate takes any X509Certificate2 whose private key the BCL can reach: a PFX file, the OS certificate store, or hardware-backed storage.

using LMKit.Document.Pdf;
using System.Security.Cryptography.X509Certificates;

X509Certificate2 certificate = /* PFX file, OS store, or hardware-backed key */;

var options = new PdfSigningOptions
{
    Certificate = certificate,
    Reason      = "Approved",
};

PdfSigner.Sign("contract.pdf", "contract-signed.pdf", options);

Byte-array and stream variants exist alongside the file overload, all available synchronously and asynchronously.


Choosing the Signature Format

Format SubFilter Notes
PadesDetached ETSI.CAdES.detached ETSI PAdES baseline. Default
Pkcs7Detached adbe.pkcs7.detached Classic Adobe PKCS#7 for legacy workflows
var options = new PdfSigningOptions
{
    Certificate = certificate,
    Format      = PdfSignatureFormat.Pkcs7Detached,
};

Certify a Document

A certification signature declares what may change after signing. Set PdfSigningOptions.Certification on the first signature:

Level Effect
None Ordinary approval signature. Default
NoChangesAllowed Any later change invalidates the certification
FormFillingAllowed Form filling and later signatures stay permitted
FormFillingAndAnnotationsAllowed Form filling, annotations, and later signatures stay permitted

Visible Appearances

By default the signature is invisible. To place a visible mark, supply bounds and an appearance. Text lines are auto-composed from the certificate in the culture given by AppearanceCulture (14 languages; English fallback otherwise), and FieldAppearance.FontFile embeds your own TTF for non-Latin scripts. An image can accompany or replace the text.

var options = new PdfSigningOptions
{
    Certificate = certificate,
    PageIndex   = 0,
    Bounds      = new PdfSigningOptions.FieldBounds(left: 350, bottom: 60, right: 550, top: 130),
    Appearance  = new PdfSigningOptions.FieldAppearance
    {
        Image = LMKit.Media.Image.ImageBuffer.Load("company-seal.png"),
    },
};

Timestamps and Long-Term Validation

Attach an RFC 3161 timestamp at signing time by setting TimestampAuthority, or add standalone document timestamps later. ExtendLtv embeds certificates and revocation material (CRLs, OCSP responses) into the document security store (DSS), so signatures keep verifying after certificates expire.

using LMKit.Document.Pdf;

// Standalone RFC 3161 document timestamp.
byte[] stamped = PdfSigner.AddDocumentTimestamp(
    File.ReadAllBytes("contract-signed.pdf"),
    new PdfTimestampOptions { TimestampAuthority = new Uri("https://tsa.example.com") });

// Embed validation material for archival.
byte[] archival = PdfSigner.ExtendLtv(stamped);

File.WriteAllBytes("contract-ltv.pdf", archival);

External Signing (HSM, KMS, Signing Services)

For private keys this process must never touch, BeginSign prepares the document and returns a session; your infrastructure produces the CMS container over the digest and Complete embeds it:

PdfSigningSession session = PdfSigner.BeginSign(File.ReadAllBytes("contract.pdf"), options);

byte[] digest = session.GetDigest();
byte[] cms    = await hsmClient.SignAsync(digest);   // your HSM / KMS / signing service

byte[] signed = session.Complete(cms);

Verify Signatures

Verification returns one result per signature with the overall verdict following the three-state model professional viewers use: Valid (bytes match and the signer chains to a trust anchor), Indeterminate (nothing proves it broken, but it could not be fully verified), or Invalid (proven broken: modified bytes, failed signature value, or a revoked certificate).

using LMKit.Document.Pdf;

var report = PdfSignatureValidator.Validate("contract-signed.pdf");

Console.WriteLine($"Overall: {report.OverallStatus}");

foreach (var result in report.Signatures)
{
    Console.WriteLine($"#{result.Signature.Index}: {result.Status}");
    Console.WriteLine($"  Integrity:  {result.Integrity}");
    Console.WriteLine($"  Identity:   {result.Identity}");
    Console.WriteLine($"  Revocation: {result.Revocation}");
    Console.WriteLine($"  Covers whole document: {result.Signature.CoversEntireDocument}");
}

Each result also carries the signer certificate, the built chain, the digest algorithm, the timestamp verdict, and the signing time. PdfSignatureReportJson serializes the whole report into machine-readable JSON for storage or audit trails.


Controlling Trust

By default the validator trusts the operating system's root store (TrustSystemRoots = true). Pin your own anchors for closed ecosystems:

var options = new PdfSignatureValidationOptions { TrustSystemRoots = false };
options.TrustedRoots.Add(corporateRootCa);

var report = PdfSignatureValidator.Validate("contract-signed.pdf", options);

Agent Tools

Five built-in tools expose the same operations to agents under ToolPermissionPolicy control: pdf_sign, pdf_signature_verify, pdf_signature_list, pdf_document_timestamp, and pdf_ltv_extend.


See Also

Share