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.