Digital signatures for .NET — PAdES, CAdES & XAdES.
Sign, validate, and inspect PDF, CMS, and XML documents with a clean, modern API.
SimpleSign is a .NET library for creating and validating digitally signed documents according to European (ETSI) and Brazilian (ICP-Brasil) standards. It covers the three pillars of AdES digital signatures:
| Standard | Format | Use Case |
|---|---|---|
| PAdES (ETSI EN 319 142) | Contracts, invoices, legal documents | |
| CAdES (ETSI EN 319 122) | Binary / CMS | Any file or data stream |
| XAdES (ETSI EN 319 132) | XML | NF-e, CT-e, government XML schemas |
All cryptography is handled by System.Security.Cryptography — no third-party crypto libraries are used.
using SimpleSign.Signing;
var signedPdf = await SimpleSigner
.Document("contract.pdf")
.WithCertificate(certificate)
.WithTimestamp("http://timestamp.digicert.com")
.WithLtv()
.SignAsync();
File.WriteAllBytes("contract-signed.pdf", signedPdf);using SimpleSign.CAdES.Signing;
var signature = await CadesSigner.Data(documentBytes)
.WithCertificate(certificate)
.WithTimestamp("http://timestamp.digicert.com")
.SignAsync();
await signature.SaveAsync("document.p7s");using SimpleSign.XAdES.Signing;
var signedXml = await XadesSigner.Xml(invoiceXml)
.WithCertificate(certificate)
.AsEnveloped()
.WithTimestamp("http://timestamp.digicert.com")
.SignAsync();using SimpleSign.Validation;
var validator = new PdfSignatureValidator(new ValidationOptions
{
CheckRevocation = true,
TrustSystemRoots = true
});
var results = await validator.ValidateAsync(File.OpenRead("signed.pdf"));
foreach (var r in results)
{
Console.WriteLine($"{r.FieldName}: Valid={r.IsValid}");
Console.WriteLine($" Integrity={r.IsIntegrityValid}");
Console.WriteLine($" Chain={r.IsCertificateChainValid}");
Console.WriteLine($" Signer: {r.SignerName} at {r.SigningTime}");
}Packages are split by concern — install only what you need:
# Full PAdES + CAdES stack (most common)
dotnet add package SimpleSign
# Only XML signatures
dotnet add package SimpleSign.XAdES
# Brazilian PKI (ICP-Brasil + Gov.br)
dotnet add package SimpleSign.Brasil
# CLI tool
dotnet tool install -g SimpleSign.CliSimpleSign (meta-package)
├── SimpleSign.PAdES PDF signing & validation (PAdES B-B/T/LT/LTA)
│ ├── SimpleSign.CAdES CMS signatures (CAdES-BES/T/EPES)
│ ├── SimpleSign.Pdf PDF structure parser (xref, objects, fields)
│ └── SimpleSign.Core Crypto primitives, TSA, revocation, HTTP
│
SimpleSign.XAdES XML signatures (XAdES-BES/T/EPES) → depends on Core
SimpleSign.Brasil ICP-Brasil + Gov.br + Lei 14.063 → depends on PAdES, CAdES
SimpleSign.HtmlToPdf Pure-.NET HTML→PDF (independent)
Sign PDFs with full European standard compliance, from basic signatures to long-term archival:
var signed = await SimpleSigner
.Document(pdfBytes)
.WithCertificate(cert)
.WithMetadata(signerName: "Jane Doe", reason: "Approval", location: "New York")
.WithTimestamp("http://timestamp.digicert.com")
.WithLtv() // Embed CRL/OCSP for offline validation
.WithArchivalTimestamp() // PAdES B-LTA — valid for decades
.WithHashAlgorithm(HashAlgorithmName.SHA512)
.SignAsync();| Capability | API |
|---|---|
| Basic signature (B-B) | .WithCertificate(cert).SignAsync() |
| Timestamp (B-T) | .WithTimestamp(tsaUrl) |
| Long-term validation (B-LT) | .WithLtv() |
| Archival (B-LTA) | .WithArchivalTimestamp() |
| Document certification (DocMDP) | .AsCertification(level) |
| PDF/A preservation | .WithPdfAPreservation() |
| Visible signature with QR code | .WithAppearance(appearance) |
| External signer (HSM, KMS) | .WithExternalSigner(cert, signerFunc) |
| Existing field | .WithExistingField("SignHere") |
| Deferred (2-phase) | DeferredSigner.PrepareAsync() → CompleteAsync() |
| Batch (parallel) | BatchSigner.Create(cert).Build() |
var appearance = new SignatureAppearance
{
Page = 1,
X = 50, Y = 50,
ShowDate = true,
ShowReason = true,
BackgroundImagePng = logoBytes,
VerificationUrl = "https://verify.example.com/abc123", // Renders a QR code
ExtraLines = ["Department: Legal", "Ref: DOC-2025-001"]
};
await SimpleSigner
.Document(pdfBytes)
.WithCertificate(cert)
.WithAppearance(appearance)
.SignAsync(output);Sign any binary data — files, streams, messages — using CMS/PKCS#7:
// Detached (signature separate from data)
var sig = await CadesSigner.Data(data)
.WithCertificate(cert)
.SignAsync();
// Enveloped (data embedded in signature)
var sig = await CadesSigner.Data(data)
.WithCertificate(cert)
.AsEnveloped()
.SignAsync();
// With policy (CAdES-EPES)
var sig = await CadesSigner.Data(data)
.WithCertificate(cert)
.WithSignaturePolicy("2.16.76.1.7.1.1.1", policyUrl)
.SignAsync();Validate:
var validator = new CadesValidator();
var result = await validator.ValidateDetachedAsync(p7sBytes, originalData);
Console.WriteLine($"Valid: {result.IsValid}, Level: {result.Level}");Inspect:
var info = new CadesInspector().Inspect(p7sBytes);
Console.WriteLine($"Signer: {info.Signer?.Subject}, Timestamp: {info.Timestamp?.GenerationTime}");Sign XML documents following ETSI standards — enveloped, enveloping, or detached:
// Enveloped (signature inside the XML document)
var signed = await XadesSigner.Xml(xmlBytes)
.WithCertificate(cert)
.AsEnveloped()
.SignAsync();
// Enveloping (XML wrapped inside the signature)
var signed = await XadesSigner.Xml(xmlBytes)
.WithCertificate(cert)
.AsEnveloping()
.SignAsync();
// Detached (separate signature referencing external data)
var signed = await XadesSigner.Data(data)
.WithCertificate(cert)
.AsDetached()
.SignAsync();All three formats offer validation with the same consistent result model:
// PAdES
var pdfResults = await new PdfSignatureValidator(options).ValidateAsync(stream);
// CAdES
var cadesResult = await new CadesValidator().ValidateDetachedAsync(sig, data);
// XAdES
var xadesResult = await new XadesValidator().ValidateAsync(xmlBytes);Each result includes:
IsIntegrityValid— byte-range hash matches (no tampering)IsSignatureValid— cryptographic signature verifies against public keyIsCertificateChainValid— chain builds to a trusted rootIsTimestampValid— RFC 3161 token is validIsValid— all checks passSignerName,SigningTime,DigestAlgorithmOid,SubFilter,Warnings
Extract metadata without full validation (fast, non-cryptographic):
// PDF signatures
var inspector = new PdfSignatureInspector();
var sigs = await inspector.InspectAsync(stream);
foreach (var s in sigs)
Console.WriteLine($"{s.FieldName}: {s.SignerName}, {s.SigningTime}, {s.DigestAlgorithm}");
// CAdES
var info = new CadesInspector().Inspect(p7sBytes);
// XAdES
var info = new XadesInspector().Inspect(xmlBytes);Extract the embedded CMS signature from a signed PDF:
var extractor = new PadesExtractor();
byte[] cmsBytes = await extractor.ExtractFirstSignatureAsync(pdfStream);
// Now validate with CadesValidator or inspect with CadesInspectorSign multiple documents in parallel with shared resources:
var batch = BatchSigner.Create(cert)
.WithTimestamp("http://timestamp.digicert.com")
.WithLtv()
.Build();
var results = await batch.SignAsync(documents);
// results.Succeeded, results.Failed, results.ElapsedMsFor web applications where the signing key is on a client device:
// Server: prepare the hash
var prepared = await DeferredSigner.PrepareAsync(pdfBytes, cert);
byte[] hashToSign = prepared.Hash;
// Client: sign the hash with the private key
byte[] signature = SignWithClientKey(hashToSign);
// Server: embed the signature
byte[] signedPdf = await prepared.CompleteAsync(signature);Resilient timestamp authority connections with pooling and retry:
var pool = new TsaPool([
"http://timestamp.digicert.com",
"http://tsa.starfieldtech.com",
"http://timestamp.sectigo.com"
]);
await SimpleSigner.Document(pdf)
.WithCertificate(cert)
.WithTimestampPool(pool)
.SignAsync();126 source-generated [LoggerMessage] definitions with semantic fields:
services.AddLogging(b => b.AddConsole());
var validator = new PdfSignatureValidator(options, logger: loggerFactory.CreateLogger<PdfSignatureValidator>());All operations emit structured log events with correlation IDs, timing, and counts. See docs/observability.md.
Full support for Brazilian digital signature standards:
services.AddSimpleSignBrasil(); // registers ICP-Brasil trust anchors (v4–v13)
var validator = new IcpBrasilChainValidator();
var result = await validator.ValidateAsync(signedPdf);
// result.Level: AD_RB, AD_RT, AD_RV, AD_RC, AD_RAvar govValidator = new GovBrChainValidator();
var level = await govValidator.GetAssuranceLevelAsync(certificate);
// Bronze, Silver, Goldvar info = AdvancedSignatureInfo.FromCertificate(cert);
Console.WriteLine($"Type: {info.SignatureType}, Level: {info.AssuranceLevel}");var options = new ValidationOptions
{
TrustSystemRoots = false, // don't use OS store
CustomTrustAnchors = IcpBrasilRoots.All // use ICP-Brasil roots only
};# Sign a PDF
simplesign sign contract.pdf --cert mycert.pfx --password secret --timestamp
# Validate
simplesign validate signed.pdf
# Inspect
simplesign inspect signed.pdf
# Batch sign
simplesign batch-sign ./documents/ --cert mycert.pfx --parallel 8
# Extract CMS from signed PDF
simplesign extract signed.pdf --output signature.p7s┌─────────────────────────────────────────────────────────┐
│ PDF Signature Validation Report │
├──────────────┬──────────────────────────────────────────┤
│ File │ contract-signed.pdf │
│ Signatures │ 1 │
├──────────────┼──────────────────────────────────────────┤
│ Field │ Signature1 │
│ Signer │ CN=Jane Doe, O=Acme Corp │
│ Time │ 2025-04-28T14:30:00Z │
│ Integrity │ ✅ Valid │
│ Signature │ ✅ Valid │
│ Chain │ ✅ Valid │
│ Timestamp │ ✅ Valid (DigiCert) │
│ Overall │ ✅ VALID │
└──────────────┴──────────────────────────────────────────┘
A desktop agent for signing with PKCS#11 hardware tokens, smart cards, or system certificate stores. Built with Photino.NET for cross-platform native UI.
simplesign-agent --listen 8234
# Exposes a local HTTP API for browser-based signing workflowsSimpleSign is extensively tested against industry-standard tools and real-world corpora:
| Tool | What We Test | Status |
|---|---|---|
| EU DSS (Digital Signature Service) | SimpleSign output → EU DSS validator | ✅ Passing |
| iText 7 v9 | SimpleSign-signed PDFs → iText validation | ✅ Passing |
| Apache PDFBox | SimpleSign-signed PDFs → PDFBox verification | ✅ Passing |
| OpenSSL | CMS detached/enveloped signatures → OpenSSL verify | ✅ Passing |
| xmlsec1 | XAdES signatures → xmlsec1 validation | ✅ Passing |
33 tests against real-world signed PDFs from the EU DSS interoperability corpus:
- PAdES-LT/LTA multi-revision documents (DSS updates + archive timestamps)
- Belgian eID signed documents
- Spanish (doc-firmado) signed documents
- Hungarian (HU_MIC) plugtest documents
- Known-bad fixtures (DSS-1683 SHA-1 regression)
External tools sign → SimpleSign validates:
| Tool | Format | Result |
|---|---|---|
| OpenSSL CMS (SHA-256/384/512) | CAdES detached + enveloped | ✅ Validates |
| xmlsec1 | XAdES enveloped | ✅ Validates |
| EU DSS | PAdES B-B/T/LT/LTA | ✅ Validates |
All interop tests run in Docker containers in CI (EU DSS, iText validator, OpenSSL, xmlsec1) — see interop/ for infrastructure details.
Benchmarks run on Apple M2 Pro, .NET 10, using BenchmarkDotNet.
| Operation | Time | Memory |
|---|---|---|
| PAdES sign 1 KB PDF | 71 ms | 1.8 MB |
| PAdES sign 100 KB PDF | 44 ms | 2.5 MB |
| PAdES sign 1 MB PDF | 47 ms | 7.4 MB |
| PAdES sign 10 MB PDF | 82 ms | 61 MB |
| ECDSA-P384 / SHA-384 | 51 ms | 1.8 MB |
| Operation | Time | Memory |
|---|---|---|
| PAdES validate (1 signature) | 15 ms | 338 KB |
| PAdES validate (5 signatures) | 22 ms | 1.6 MB |
| PAdES validate (chain: Root→Intermediate→End) | 17 ms | 338 KB |
| CAdES validate (detached) | 6 ms | 14 KB |
| XAdES validate (enveloped) | 10 ms | 246 KB |
| Workload | Time | vs Sequential |
|---|---|---|
| Sequential (32 signs) | 476 ms | 1.00× |
| 8 concurrent tasks (32 signs) | 450 ms | 0.94× |
| 16 concurrent tasks (32 signs) | 466 ms | 0.98× |
| 32 concurrent tasks (32 signs) | 440 ms | 0.92× |
| Library | Time | Relative | Memory |
|---|---|---|---|
| SimpleSign | 23 ms | 1.0× | 13 KB |
| BouncyCastle | 58 ms | 2.5× | 46 KB |
| iText7 v9 | 92 ms | 2.5× | 742 KB |
Note: Benchmarks are single-iteration cold starts (Dry run). Use for relative comparison, not absolute throughput measurement.
| Extension | Interface / Pattern |
|---|---|
| Custom trust anchors | ITrustAnchorProvider |
| Custom hash algorithm | HashAlgorithmName parameter |
| External signer (HSM/KMS) | Func<byte[], Task<byte[]>> callback |
| Custom revocation | IRevocationChecker |
| Custom timestamp | ITimestampClient |
| Custom HTTP | IHttpClientFactory / HttpClient injection |
| Custom logging | ILogger<T> injection |
| Custom PDF rendering | ISignatureAppearanceRenderer |
| Standard | Levels | Status | Notes |
|---|---|---|---|
| PAdES | B-B (Basic) | ✅ | PKCS#7 embedded in PDF |
| B-T (Timestamp) | ✅ | RFC 3161 timestamp token | |
| B-LT (Long-Term) | ✅ | DSS dictionary with CRL/OCSP | |
| B-LTA (Archive) | ✅ | Document timestamp for decade-long validity | |
| DocMDP (Certification) | ✅ | Three permission levels | |
| PDF/A preservation | ✅ | Detects and preserves 1a/1b/2a/2b/3a/3b | |
| CAdES | BES (Baseline) | ✅ | Detached + enveloped |
| T (Timestamp) | ✅ | RFC 3161 counter-signature | |
| EPES (Policy) | ✅ | Policy OID + URI | |
| XAdES | BES (Baseline) | ✅ | Enveloped, enveloping, detached |
| T (Timestamp) | ✅ | RFC 3161 timestamp | |
| EPES (Policy) | ✅ | Policy identifier |
| Category | Algorithms |
|---|---|
| Hash | SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-384, SHA3-512 |
| Signature | RSA PKCS#1 v1.5, RSA-PSS, ECDSA (P-256/P-384/P-521), EdDSA (Ed25519/Ed448)¹ |
| Revocation | CRL, OCSP, embedded DSS |
| Timestamps | RFC 3161 |
| PDF/A | 1a, 1b, 2a, 2b, 3a, 3b (detection + preservation) |
¹ EdDSA via external signer pipeline; verification depends on runtime support.
SimpleSign.Core Crypto primitives, CMS, TSA, revocation, HTTP
├── Signing/ CmsSignatureBuilder, TimestampClient, TsaPool
├── Validation/ IntegrityVerifier, CryptoVerifier, ChainBuilder
├── Revocation/ CrlClient, OcspClient, RevocationChecker
└── Inspection/ CmsParser, DssExtractor
SimpleSign.Pdf PDF structure parsing (no crypto)
├── PdfStructureReader Xref, objects, signature fields
└── PdfStructureParser Low-level token/stream parser
SimpleSign.CAdES CMS Advanced Electronic Signatures
├── CadesSigner BES, T, EPES (detached + enveloped)
├── CadesValidator Detached/enveloped/auto-detect
├── CadesInspector Metadata extraction
└── CadesBatchSigner Parallel signing with metrics
SimpleSign.XAdES XML Advanced Electronic Signatures
├── XadesSigner BES, T, EPES (enveloped, enveloping, detached)
├── XadesValidator Signature verification
├── XadesInspector Metadata extraction
└── XadesBatchSigner Parallel signing with metrics
SimpleSign.PAdES PDF Advanced Electronic Signatures
├── SimpleSigner Fluent API entry point
├── SignerBuilder Immutable builder (16 options)
├── PdfSignatureWriter Incremental PDF update (append-only)
├── BatchSigner Parallel signing with metrics
├── DeferredSigner Two-phase signing for web apps
├── LtvEmbedder DSS dictionary (CRL/OCSP/VRI)
├── DocTimeStampWriter PAdES B-LTA archival timestamp
├── PdfSignatureValidator Full validation pipeline
├── PdfSignatureInspector Metadata extraction
└── PadesExtractor CMS extraction from signed PDFs
SimpleSign.Brasil Brazilian PKI
├── IcpBrasilChainValidator ICP-Brasil chain validation (AD-RB..AD-RA)
├── GovBrChainValidator Gov.br assurance levels
├── AdvancedSignatureInfo AEA Lei 14.063 metadata
└── BrasilExtension Registration entry point
SimpleSign.Cli 12 commands via Spectre.Console
SimpleSign.Agent Desktop agent for PKCS#11 tokens
| Principle | Implementation |
|---|---|
| Immutable builders | Every .WithX() returns a new instance — safe to share across threads |
| Async-first | All signing/validation methods return Task<T> — no blocking calls anywhere |
| Zero-allocation hot paths | Span<byte> and ReadOnlySpan<byte> for PDF parsing and hash computation |
| Pooled memory | RecyclableMemoryStream for buffer reuse |
| Structured logging | 126 [LoggerMessage] definitions — zero-cost when disabled |
| Native AOT | No reflection in hot paths, trimmer-friendly |
| Nullable enabled | All public APIs are fully annotated |
| Metric | Value |
|---|---|
| Tests | 1,544 (unit, integration, interop, fuzz, corpus) |
| Test categories | Unit (algorithm coverage), Integration (sign→validate round-trip), Interop (EU DSS, iText, OpenSSL, xmlsec1), ETSI Corpus (33 real-world PDFs), Fuzz (SharpFuzz harnesses) |
| Source lines | ~33,500 |
| Warnings | 0 (all warnings treated as errors) |
| Code analysis | Full Roslyn analyzer suite enabled |
| CI | Build + test, CodeQL SAST, weekly benchmarks, weekly fuzz |
| AOT | Native AOT smoke-tested in CI |
| Target frameworks | net8.0 + net10.0 |
The /samples directory contains runnable examples:
| Sample | Description |
|---|---|
| PAdES.QuickStart | Sign a PDF and validate the result |
| PAdES.Validation | Validate a signed PDF and display detailed results |
| PAdES.DeferredSigning | Two-phase hash-and-sign workflow for web apps |
| PAdES.ExternalSigner | Sign using an external key (HSM/KMS pattern) |
| PAdES.Inspection | Inspect PDF signature metadata without validation |
| PAdES.MultiSign | Sign a PDF with multiple signers sequentially |
| PAdES.VisibleSignature | Visible signature with custom appearance and QR code |
| CAdES.DetachedSign | Create and verify a detached CMS signature |
| XAdES.EnvelopedSign | Sign XML with an enveloped XAdES signature |
| XAdES.EnvelopingSig | XAdES enveloping signature |
| Brasil.IcpBrasil | Set up ICP-Brasil trust anchors via DI |
| BatchSigning | Sign 10 PDFs in parallel |
| Document | Description |
|---|---|
| API Reference | Full API documentation with all builder methods and types |
| Troubleshooting | Common errors with solutions |
| Test Strategy | Test pyramid, categories, how to run and add tests |
| Observability | Structured logging, correlation IDs, metrics |
| Web Signing Guide | Deferred signing in ASP.NET Core apps |
| Contributing | How to contribute, coding standards, PR process |
| Security | Vulnerability reporting and threat model |
| Changelog | Release history |
- .NET 8 or .NET 10 (multi-target: net8.0 + net10.0)
- No native or COM dependencies
- No third-party cryptography libraries
- Runs on Windows, macOS, and Linux
- PKCS#11 HSM integration (Thales, SafeNet, nCipher)
- Cloud KMS signing (Azure Key Vault, AWS CloudHSM, Google Cloud KMS)
- PDF/A-3 attachment embedding
- CAdES-B-LT / B-LTA (long-term CMS archival)
- XAdES-B-LT / B-LTA (long-term XML archival)
- API reference site (Docfx on GitHub Pages)
- Performance dashboard (published benchmark trends)
- IHttpClientFactory best practices guide
MIT — use it anywhere, for anything, forever.
Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.
Built for developers who believe document signing should be simple.