Skip to content

eupassarin/SimpleSign

SimpleSign

SimpleSign

Digital signatures for .NET — PAdES, CAdES & XAdES.
Sign, validate, and inspect PDF, CMS, and XML documents with a clean, modern API.

.NET 8 | 10 MIT License Native AOT 1,544 tests Zero crypto dependencies


What is SimpleSign?

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) PDF 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.Cryptographyno third-party crypto libraries are used.


Quick Start

Sign a PDF (PAdES)

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);

Sign Binary Data (CAdES)

using SimpleSign.CAdES.Signing;

var signature = await CadesSigner.Data(documentBytes)
    .WithCertificate(certificate)
    .WithTimestamp("http://timestamp.digicert.com")
    .SignAsync();

await signature.SaveAsync("document.p7s");

Sign XML (XAdES)

using SimpleSign.XAdES.Signing;

var signedXml = await XadesSigner.Xml(invoiceXml)
    .WithCertificate(certificate)
    .AsEnveloped()
    .WithTimestamp("http://timestamp.digicert.com")
    .SignAsync();

Validate Signatures

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}");
}

Installation

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.Cli

Package Map

SimpleSign (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)

Features

PAdES — PDF Signatures

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()

Signature Appearance

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);

CAdES — CMS Signatures

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}");

XAdES — XML Signatures

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();

Validation

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 key
  • IsCertificateChainValid — chain builds to a trusted root
  • IsTimestampValid — RFC 3161 token is valid
  • IsValid — all checks pass
  • SignerName, SigningTime, DigestAlgorithmOid, SubFilter, Warnings

Inspection

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);

PAdES → CAdES Extraction

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 CadesInspector

Enterprise Features

Batch Signing

Sign 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.ElapsedMs

Deferred Signing (Two-Phase)

For 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);

TSA Connection Pool

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();

Structured Logging

126 source-generated [LoggerMessage] definitions with semantic fields:

services.AddLogging(b => b.AddConsole());
var validator = new PdfSignatureValidator(options, logger: loggerFactory.CreateLogger<PdfSignatureValidator>());

Observability

All operations emit structured log events with correlation IDs, timing, and counts. See docs/observability.md.


🇧🇷 Brazilian PKI (ICP-Brasil)

Full support for Brazilian digital signature standards:

ICP-Brasil Chain Validation

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_RA

Gov.br Validation

var govValidator = new GovBrChainValidator();
var level = await govValidator.GetAssuranceLevelAsync(certificate);
// Bronze, Silver, Gold

AEA — Advanced Electronic Signature (Lei 14.063/2020)

var info = AdvancedSignatureInfo.FromCertificate(cert);
Console.WriteLine($"Type: {info.SignatureType}, Level: {info.AssuranceLevel}");

Trust Anchors for Validation

var options = new ValidationOptions
{
    TrustSystemRoots = false, // don't use OS store
    CustomTrustAnchors = IcpBrasilRoots.All // use ICP-Brasil roots only
};

CLI Tool

# 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

Validation Output

┌─────────────────────────────────────────────────────────┐
│ 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                                 │
└──────────────┴──────────────────────────────────────────┘

Desktop Agent

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 workflows

Interoperability & Conformance

SimpleSign is extensively tested against industry-standard tools and real-world corpora:

Cross-Validation Matrix

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

ETSI Corpus Tests

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)

Reverse Interop

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

Docker-Based CI Tests

All interop tests run in Docker containers in CI (EU DSS, iText validator, OpenSSL, xmlsec1) — see interop/ for infrastructure details.


Performance

Benchmarks run on Apple M2 Pro, .NET 10, using BenchmarkDotNet.

Signing Performance

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

Validation Performance

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

Concurrency Scaling

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×

vs Competitors (PAdES signing)

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 Points

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

Conformance Matrix

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

Supported Algorithms

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.


Architecture

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

Design Principles

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

Quality

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

Samples

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

Documentation

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

Requirements

  • .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

Future

  • 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

License

MIT — use it anywhere, for anything, forever.

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.


Built for developers who believe document signing should be simple.

About

Modern .NET 8/10 digital signature library — PAdES, CAdES, XAdES. Zero dependencies. Async-first. AOT compatible.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors