Skip to content

About

Enterprise digital signatures for .NET — PAdES · CAdES · XAdES. Zero external crypto deps. Native AOT. MIT.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

17 stars

Watchers

0 watching

Forks

Latest commit

 

History

344 Commits

Folders and files

Repository files navigation

SimpleSign

SimpleSign

Enterprise digital signatures for .NET
PAdES · CAdES · XAdES — sign, validate, inspect.

.NET 8 | 10 NuGet CI codecov MIT License Native AOT 1,750+ tests No third-party crypto dependencies


What is SimpleSign?

SimpleSign is a .NET library for creating and validating PAdES (ETSI EN 319 142), CAdES (ETSI EN 319 122), and XAdES (ETSI EN 319 132) digital signatures. All cryptography uses System.Security.Cryptography — no BouncyCastle, no third-party crypto dependencies. Native AOT compatible. 1,695+ tests. MIT licensed.

This README describes the current source tree. For released-package behavior, use its release-tagged documentation; the changelog records delivered changes and work under Unreleased.


Why SimpleSign?

  • Zero third-party crypto — all operations via BCL System.Security.Cryptography
  • Dependency Injection — 15+ interfaces, AddSimpleSign() wires everything
  • Native AOT compatible — no reflection, no dynamic, no Assembly.Load
  • MIT licensed — use anywhere, for anything, with no restrictions
  • 1,695+ tests — comprehensive coverage, 0 warnings, CI-enforced quality gates
  • Multi-target — .NET 8 and .NET 10 on Windows, macOS, and Linux

What's New in v0.9.2

Acrobat-safe structure preservation — dictionary scanners now treat literal strings, hexadecimal strings, and comments correctly, so content such as (merged >> streams) in a /Reason no longer confuses parsing. Key removal is token-aware, and document timestamps (B-LTA) preserve an inline /AcroForm dictionary just like regular signing.

Robust incremental signing on external PDFs — /Fields and /Annots entries are resolved without mistaking other keys' arrays (including compressed indirect arrays), signing into existing fields handles every /V form without duplicate keys, and empty /V () fields are recognised as unsigned.

Accurate metadata and positioning — hexadecimal and UTF-16 /M dates decode correctly, visible-widget counting no longer depends on a fixed window, and numeric parsing accepts only ASCII digits with complete objNum genNum R references.

See the full changelog.


What's New in v0.9.1

Adobe Acrobat compatibility fixes — the embedded LiberationSans /FontFile2 stream now carries the required /Length1 metadata, and signing a PDF whose Catalog contains an inline /AcroForm dictionary preserves that dictionary (including /DA and /DR) instead of replacing it, so Acrobat no longer invalidates earlier signatures.

Robust PDF parsing for external documents — compact /Kids page-tree arrays (/Kids[2 0 R 3 0 R]) are parsed with PDF token rules, and signature metadata such as /Reason now decodes hexadecimal and UTF-16 text strings used by external signing tools (e.g. Polish trusted-profile signatures).

Stronger timestamp and LTV verification — RFC 3161 tokens require an authenticated TSA signature before acceptance, validation timeouts bound AIA/OCSP/CRL network calls, and B-LT/B-LTA evidence must include signed revocation material before levels are reported.

See the full changelog.


What's New in v0.9.0

Verifiable AdES level fulfillment — a requested B-T/B-LT/B-LTA level now describes the artifact actually produced. Timestamp tokens are bound to their RFC 3161 request, LTV material is checked per certificate path, and result flags are derived from final-artifact inspection.

Standards-defined archival coverage — CAdES uses ETSI archiveTimestampV3 with ATSHashIndexV3 inside the archive timestamp token. XAdES uses the ETSI archive preimage and XMLDSig reference processing, including same-document distributed unsigned properties and preceding counter-signatures.

Resolved external signing — IExternalSigner.SignAsync(ExternalSigningRequest, …) receives coherent digest, scheme, operation ID and RSA-PSS parameters. RSA and ECDSA output is verified against the public certificate before packaging.

Provider-based HTTP configuration — .WithHttpClientProvider(...) plus scoped providers in TimestampOptions, LongTermValidationOptions, and ArchiveTimestampOptions, with deterministic precedence.

CAdES-Enveloped (.p7m) — embed the original data inside the CMS payload via WithContentType(CadesContentType.Enveloped).

CLI improvements — JSON output for cades validate / xades validate (--json) and stdin/pipe input (path -) for all sign and validate commands.

Hardened signing invariants — certificates are checked against both NotBefore and NotAfter; PAdES inputs and configured binary/collection values are snapshotted; stream-backed PAdES builders are single-use and write transactionally.

See the full changelog and the v0.8 → v0.9 migration guide.


Installation

Packages are split by concern — install only what you need:

# Full PAdES stack (most common)
dotnet add package SimpleSign

# Brazilian PKI (ICP-Brasil + Gov.br)
dotnet add package SimpleSign.Brasil

# CAdES signatures (CMS/PKCS#7)
dotnet add package SimpleSign.CAdES

# XAdES signatures (XML-DSig)
dotnet add package SimpleSign.XAdES

# CLI tool
dotnet tool install -g SimpleSign.Cli

Package Map

  • SimpleSign (meta-package) — full PAdES + CAdES + XAdES stack
    • SimpleSign.PAdES — PDF signing & validation (PAdES B-B/T/LT/LTA)
      • SimpleSign.Pdf — PDF structure parser (xref, objects, fields)
      • SimpleSign.Core — Crypto primitives, CMS, TSA, revocation, HTTP
    • SimpleSign.CAdES — standalone CMS/PKCS#7 signing & validation (ETSI EN 319 122)
    • SimpleSign.XAdES — XML signature signing & validation (ETSI EN 319 132)
      • SimpleSign.Core (shared)
  • SimpleSign.Brasil — ICP-Brasil + Gov.br + Lei 14.063 (depends on PAdES)
  • SimpleSign.HtmlToPdf — Pure-.NET HTML→PDF (independent)
  • SimpleSign.Cli — CLI tool (install as dotnet tool)
  • SimpleSign.HostSigner — Windows tray app for local signing API

Quick Start

Sign a PDF (PAdES)

using SimpleSign.Core.Signing;
using SimpleSign.PAdES;

var pdfBytes = File.ReadAllBytes("contract.pdf");
var signedPdf = await PadesSigner
    .Document(pdfBytes)
    .WithCertificate(certificate)
    .WithLevel(AdesBaselineProfile.LongTerm(
        new TimestampOptions(new Uri("http://timestamp.digicert.com")),
        new LongTermValidationOptions()))
    .SignAsync();

File.WriteAllBytes("contract-signed.pdf", signedPdf);

Validate Signatures

using SimpleSign.Core.Validation;
using SimpleSign.PAdES.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($"  Timestamp={r.HasValidTimestamp}");
    Console.WriteLine($"  Signer: {r.SignerName} at {r.SigningTime}");
}

CAdES — Standalone CMS Signatures

Create and validate CAdES signatures (CMS/PKCS#7 SignedData) for any binary data using the fluent builder API. Supports both detached (.p7s) and enveloped (.p7m) content types. B-LT/B-LTA certificates and CRLs are stored in the root CMS SignedData sets, with OCSP responses encoded as RFC 5940 revocation choices. Validation also reads legacy CAdES-XL certificate and revocation unsigned attributes.

Choose the baseline profile and content type when signing:

using SimpleSign.CAdES;
using SimpleSign.Core.Validation;
using SimpleSign.Core.Signing;

var data = File.ReadAllBytes("document.pdf");
var tsa = new Uri("http://timestamp.digicert.com");

// CAdES-B-B (basic)
var cms = await CadesSigner
    .Document(data)
    .WithCertificate(certificate)
    .SignAsync();

// CAdES-B-T (with timestamp)
var cmsBt = await CadesSigner
    .Document(data)
    .WithCertificate(certificate)
    .WithLevel(AdesBaselineProfile.Timestamped(new TimestampOptions(tsa)))
    .SignAsync();

// CAdES-B-LT (long-term with LTV data)
var cmsBlt = await CadesSigner
    .Document(data)
    .WithCertificate(certificate, chain)
    .WithLevel(AdesBaselineProfile.LongTerm(
        new TimestampOptions(tsa), new LongTermValidationOptions()))
    .SignAsync();

// CAdES-B-LTA (archival timestamp)
var cmsBlta = await CadesSigner
    .Document(data)
    .WithCertificate(certificate)
    .WithLevel(AdesBaselineProfile.Archive(
        new TimestampOptions(tsa), new LongTermValidationOptions()))
    .SignAsync();

File.WriteAllBytes("document.pdf.p7s", cms);

// Enveloped mode — data embedded inside the CMS (.p7m)
var cmsEnveloped = await CadesSigner
    .Document(data)
    .WithCertificate(certificate)
    .WithContentType(CadesContentType.Enveloped)
    .SignAsync();
File.WriteAllBytes("document.pdf.p7m", cmsEnveloped);

Validate CAdES Signatures

using SimpleSign.CAdES;

var validator = new CadesSignatureValidator(
    new ValidationOptions { CheckRevocation = false });

var result = validator.Validate(cmsBytes, originalData, trustAnchors);

Console.WriteLine($"Signer: {result.SignerCertificate?.Subject}");
Console.WriteLine($"Integrity: {result.IsIntegrityValid}");
Console.WriteLine($"Signature: {result.IsSignatureValid}");
Console.WriteLine($"Chain: {result.IsCertificateChainValid}");
Console.WriteLine($"Timestamp: {result.HasValidTimestamp}");
Console.WriteLine($"LTV: {result.IsLtvDataValid}");
Console.WriteLine($"Archive TS: {result.HasValidArchiveTimestamp}");
Console.WriteLine($"Valid: {result.IsValid}");

XAdES — XML Signatures

Sign and validate XML documents with XAdES (ETSI EN 319 132) using the same fluent builder API:

using SimpleSign.Core.Signing;
using SimpleSign.XAdES;

var xml = File.ReadAllBytes("invoice.xml");
var tsa = new Uri("http://timestamp.digicert.com");

// XAdES-B-B (basic)
var signed = await XadesSigner
    .Document(xml)
    .WithCertificate(certificate)
    .SignAsync();

// XAdES-B-T (with timestamp)
var signedBt = await XadesSigner
    .Document(xml)
    .WithCertificate(certificate)
    .WithLevel(AdesBaselineProfile.Timestamped(new TimestampOptions(tsa)))
    .SignAsync();

// XAdES-B-LT (long-term with LTV data)
var signedBlt = await XadesSigner
    .Document(xml)
    .WithCertificate(certificate, chain)
    .WithLevel(AdesBaselineProfile.LongTerm(
        new TimestampOptions(tsa), new LongTermValidationOptions()))
    .SignAsync();

// XAdES-B-LTA (archival timestamp)
var signedBlta = await XadesSigner
    .Document(xml)
    .WithCertificate(certificate)
    .WithLevel(AdesBaselineProfile.Archive(
        new TimestampOptions(tsa), new LongTermValidationOptions()))
    .SignAsync();

File.WriteAllBytes("invoice-signed.xml", signed);

// Detached form (signature in separate file, data referenced by URI)
var detached = await XadesSigner
    .Document(xml)
    .WithCertificate(certificate)
    .WithForm(XadesForm.Detached)
    .WithDataUri("invoice.xml")
    .WithLevel(AdesBaselineProfile.Basic())
    .SignAsync();
File.WriteAllBytes("invoice-signed.xml", detached);

// Enveloping form (data wrapped inside the signature)
var enveloping = await XadesSigner
    .Document(xml)
    .WithCertificate(certificate)
    .WithForm(XadesForm.Enveloping)
    .WithLevel(AdesBaselineProfile.Basic())
    .SignAsync();
File.WriteAllBytes("invoice-signed.xml", enveloping);

Validate XAdES Signatures

using SimpleSign.XAdES;

var validator = new XadesSignatureValidator();
var result = validator.Validate(signedXml, trustAnchors: anchors);

Console.WriteLine($"Signer: {result.SignerCertificate?.Subject}");
Console.WriteLine($"Signature: {result.IsSignatureValid}");
Console.WriteLine($"Integrity: {result.IsIntegrityValid}");
Console.WriteLine($"Chain: {result.IsCertificateChainValid}");
Console.WriteLine($"Observed level: {result.DetectedLevel}");
Console.WriteLine($"Timestamp valid: {result.HasValidSignatureTimeStamp}");
Console.WriteLine($"LTV evidence valid: {result.IsLtvDataValid}");
Console.WriteLine($"Archive valid: {result.HasValidArchiveTimeStamp}");
Console.WriteLine($"TSA trusted: {result.IsTsaTrusted}");
Console.WriteLine($"Valid: {result.IsValid}");

DetectedLevel reports observed XML elements, including properties that fail validation. A malformed timestamp can report Timestamped while HasValidSignatureTimeStamp is false. Check the timestamp, LTV, and archive outcomes for the required baseline level; IsValid combines fundamental signature, integrity, and signer-chain checks. IsTsaTrusted reports the separate signature-TSA trust outcome.


Features

PAdES — PDF Signatures

Sign PDFs with full European standard compliance, from basic signatures to long-term archival:

using SimpleSign.Core.Extensions;
using SimpleSign.Core.Signing;
using SimpleSign.PAdES;

var signed = await PadesSigner
    .Document(pdfBytes)
    .WithCertificate(cert)
    .WithMetadata(new SignatureMetadata
    {
        SignerName = "Jane Doe",
        Reason = "Approval",
        Location = "New York"
    })
    .WithLevel(AdesBaselineProfile.Archive(      // PAdES B-LTA — valid for decades
        new TimestampOptions(new Uri("http://timestamp.digicert.com")),
        new LongTermValidationOptions()))         // Embeds CRL/OCSP for offline validation
    .WithHashAlgorithm(HashAlgorithmName.SHA512)
    .SignAsync();
Capability API
Basic signature (B-B) .WithCertificate(cert).SignAsync()
Timestamp (B-T) .WithLevel(AdesBaselineProfile.Timestamped(new TimestampOptions(new Uri(tsaUrl))))
Long-term validation (B-LT) .WithLevel(AdesBaselineProfile.LongTerm(new TimestampOptions(new Uri(tsaUrl)), new LongTermValidationOptions()))
Archival (B-LTA) .WithLevel(AdesBaselineProfile.Archive(new TimestampOptions(new Uri(tsaUrl)), new LongTermValidationOptions()))
Document certification (DocMDP) .AsCertification(level)
PDF/A preservation .WithPdfAPreservation()
Visible signature with QR code .WithAppearance(appearance)
External signer (HSM, KMS) .WithExternalSigner(cert, signer) (IExternalSigner)
Custom signing time .WithSigningTime(dateTimeOffset) — drives the signature dictionary /M entry
Custom HTTP client .WithHttpClientProvider(provider) / scoped providers in TimestampOptions
Existing field .WithExistingField("SignHere")
Deferred (2-phase) Document(pdf).WithCertificate(cert).WithSessionIntegrityKey(key).PrepareAsync() → Resume(session, key).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 PadesSigner
    .Document(pdfBytes)
    .WithCertificate(cert)
    .WithAppearance(appearance)
    .SignAsync(output);

Validation

Signing revalidates the completed base signature and content integrity before returning a result. SignWithDetailsAsync reports RequestedLevel, AchievedLevel, and checked timestamp/LTV/archive facts. Strict success fulfills the requested level; an explicit ReturnLowerLevel profile permits enrichment failures to return a lower level with structured warnings. Base-signature failures and cancellation still fail, and byte-only SignAsync accepts strict profiles only.

Embedded-evidence checks authenticate OCSP/CRL signatures, certificate association, responder authorization, and usable validity bounds. Signer-chain trust and TSA trust depend on validation anchors and policy. Evaluate them with detailed results:

var pdfResults = await new PdfSignatureValidator(options).ValidateAsync(stream);

Each result includes:

  • IsIntegrityValid — byte-range hash matches (no tampering)
  • IsSignatureValid — cryptographic signature verifies against public key
  • IsCertificateChainValid — chain builds to a trusted root
  • HasValidTimestamp — RFC 3161 token signature, signed content, and imprint verify (bool?)
  • IsTsaTrusted — TSA certificate passes the evaluated chain and timestamping-purpose policy (bool?; null when the token is absent, invalid, or unchecked)
  • IsValid — integrity, signature, configured chain trust, and revocation policy pass
  • SignerName, SigningTime, DigestAlgorithmOid, SubFilter, Warnings

Inspection

Extract metadata without full validation (fast, non-cryptographic):

using SimpleSign.PAdES.Inspection;

var result = await PdfSignatureInspector.InspectAsync(stream);
foreach (var s in result.Signatures)
    Console.WriteLine($"{s.FieldName}: {s.SignerName}, {s.SigningTime}, {s.DigestAlgorithm}");

Batch Signing

Sign multiple documents in parallel with shared resources:

var batch = BatchSigner.Create(cert)
    .WithFieldOptions(new SignatureFieldOptions
    {
        SignerName = "Jane Doe",
        Reason = "Batch approval"
    })
    .WithLevel(AdesBaselineProfile.Basic())
    .WithMaxConcurrency(4)
    .Build();

// Sign a single document
byte[] signed = await batch.SignAsync(pdfBytes);

// Or stream results as they complete
await foreach (var result in batch.SignAllAsync(inputs))
{
    if (result.SignedPdf is not null)
        Console.WriteLine($"{result.Id}: signed");
}

// Results are ordered by completion, not source order. Breaking the enumeration
// cancels pending batch work and the cancellation-aware input source.

// Access aggregate stats after signing
Console.WriteLine($"Success: {batch.SuccessCount}, Fail: {batch.FailureCount}, Avg: {batch.AverageElapsedMs:F0}ms");

Deferred Signing (Two-Phase)

For web applications where the signing key is on a client device:

var builder = DeferredSigner.Document(pdfBytes)
    .WithCertificate(cert, chain)
    .WithSessionIntegrityKey(sessionIntegrityKey)
    .WithLevel(AdesBaselineProfile.Timestamped(
        new TimestampOptions(new Uri("https://tsa.example"))))
    .WithHttpClientProvider(httpClientProvider);

// Server: prepare the signed attributes
var prepared = await builder.PrepareAsync();
byte[] hashToSign = prepared.HashToSign;

// Client: sign the hash with the private key (RSA PKCS#1 v1.5, ECDSA, etc.)
byte[] signature = SignWithClientKey(hashToSign);

// Server: load the persisted session, then validate and embed the raw signature.
byte[] signedPdf = await DeferredSigner.Resume(prepared.SessionData, sessionIntegrityKey)
    .WithHttpClientProvider(httpClientProvider)
    .CompleteAsync(signature);

TSA Connection Pool

Resilient timestamp authority connections with pooling and retry:

using SimpleSign.Core.Crypto;

var pool = new TsaPool([
    "http://timestamp.digicert.com",
    "http://tsa.starfieldtech.com",
    "http://timestamp.sectigo.com"
]);

// Send timestamp request with auto-failover across servers
byte[] tsaResponse = await pool.GetTimestampAsync(
    hash, HashAlgorithmName.SHA256, httpClient, cancellationToken);

Structured Logging

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

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

🇧🇷 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(certificate);
// result.DetectedPolicy: AdRb, AdRt, AdRv, AdRc, AdRa

CPF / CNPJ Extraction

// CPF and CNPJ are automatically extracted from the certificate SAN
Console.WriteLine(result.CpfFormatted);   // "123.456.789-09"
Console.WriteLine(result.CnpjFormatted);  // "12.345.678/0001-90"

Health Professional Data (e-Prescriptions)

// CRM/CRO registration extracted from SAN (DOC-ICP-04)
if (result.HealthProfessional is { } hp)
{
    Console.WriteLine($"Council: {hp.Council}");           // Crm / Cro
    Console.WriteLine($"State:   {hp.StateCode}");         // "SP"
    Console.WriteLine($"Number:  {hp.RegistrationNumber}"); // "SP123456"
}

VALIDAR ITI Portal Link

// Generate a direct VALIDAR link for QR code embedding in a signed document
string url = ValidarItiUrlBuilder.ForDocument("https://storage.example.com/doc.pdf");
// → "https://validar.iti.gov.br/?document=https%3A%2F%2Fstorage..."

Gov.br Validation

var level = GovBrChainValidator.DetectAssuranceLevel(certificate);
// Bronze, Silver, Gold

// Or full chain validation
var govValidator = new GovBrChainValidator();
var result = await govValidator.ValidateAsync(certificate);
Console.WriteLine($"Level: {result.AssuranceLevel}, Valid: {result.IsValid}");

AEA — Advanced Electronic Signature (Lei 14.063/2020)

var info = new AdvancedSignatureInfo
{
    SignerName = "Nome do Signatário",
    Cpf = "12345678901",
    AuthMethod = AuthenticationMethod.GovBr,
    CommitmentType = CommitmentType.ProofOfApproval,
    InstitutionName = "TCE-ES",
    InstitutionCnpj = "12345678000190"
};
// Embed via PadesSignerBuilder.WithAdvancedSignature()

Trust Anchors for Validation

// Use AddSimpleSignBrasil() to register ICP-Brasil trust anchors automatically.
// For manual configuration, supply trusted roots via the TrustedRoots property:
var options = new ValidationOptions
{
    TrustSystemRoots = false, // don't use OS store
    TrustedRoots = icpBrasilCertificates // IReadOnlyList<X509Certificate2>
};

CLI Tool

PDF Signatures

# Sign a PDF
simplesign sign contract.pdf --cert mycert.pfx --password secret --timestamp

# Validate
simplesign validate signed.pdf

# Validate a directory of signed PDFs
simplesign validate-dir ./documents/

# Inspect
simplesign inspect signed.pdf

# Extract CMS from signed PDF
simplesign extract signed.pdf --output signature.p7s

# Convert HTML to PDF
simplesign html2pdf page.html --output page.pdf

# Explain certificate details
simplesign explain --cert mycert.pfx --password secret

# Show version
simplesign version

CAdES Signatures

# CAdES-B-B (basic)
simplesign cades sign document.pdf --cert mycert.pfx

# CAdES-B-T (with timestamp)
simplesign cades sign document.pdf --cert mycert.pfx \
    --tsa http://timestamp.digicert.com --level timestamped

# CAdES-B-LT (long-term with LTV)
simplesign cades sign document.pdf --cert mycert.pfx \
    --tsa http://timestamp.digicert.com --level longterm --chain chain.pem

# CAdES-B-LTA (with archival timestamp)
simplesign cades sign document.pdf --cert mycert.pfx \
    --tsa http://timestamp.digicert.com --level archive

# Enveloped mode — data embedded inside the CMS (.p7m)
simplesign cades sign document.pdf --cert mycert.pfx --content-type enveloped

# Validate a CAdES signature
simplesign cades validate document.pdf.p7s --data document.pdf

# Validate with custom trust anchors
simplesign cades validate document.pdf.p7s --data document.pdf --trust root-ca.pem

XAdES Signatures

# XAdES-B-B enveloped (default form)
simplesign xades sign invoice.xml --cert mycert.pfx

# XAdES-B-T detached
simplesign xades sign data.xml --cert mycert.pfx \
    --tsa http://timestamp.digicert.com --level timestamped \
    --form detached --data-uri data.xml

# XAdES-B-LT enveloping
simplesign xades sign data.xml --cert mycert.pfx \
    --tsa http://timestamp.digicert.com --level longterm \
    --form enveloping --chain chain.pem

# Validate a XAdES signature
simplesign xades validate signed.xml

# Validate detached with original data
simplesign xades validate signed.xml --data data.xml

# Validate with custom trust anchors
simplesign xades validate signed.xml --trust root-ca.pem

Validation Output

contract-signed.pdf  1/1 valid
├── Document
│   ├── Signatures: 1 user + 0 timestamps
│   ├── Encrypted:  No
│   ├── DocMDP:     Not locked
│   ├── PDF/A:      None
│   └── ✓ DSS (embedded)
└── Signature1  ✓ VALID
    ├── Signer:       CN=Jane Doe, O=Acme Corp
    ├── SubFilter:    ETSI.CAdES.detached
    ├── PAdES:        B-T (Timestamp)
    ├── Certificate
    │   ├── Subject:        CN=Jane Doe, O=Acme Corp
    │   ├── Issuer:         DigiCert SHA2 Assured ID CA
    │   ├── Serial:         0A:1B:2C:3D
    │   ├── Key:            RSA 2048-bit
    │   ├── Valid:          2024-01-01 – 2026-01-01
    │   └── NonRepudiation: ✓
    ├── ESS CertV2:   ✓
    ├── Validation
    │   ├── Integrity:  ✓ Valid
    │   ├── Signature:  ✓ Valid
    │   ├── Chain:      ✓ Valid
    │   ├── Revoked:    ✓ Not revoked (OCSP)
    │   └── Timestamp:  ✓ 2025-04-28 14:30:00 UTC
    ├── Timestamp
    │   ├── Time:       2025-04-28 14:30:00 UTC
    │   ├── TSA:        CN=DigiCert Timestamp 2023
    │   └── Token Size: 4.2 KB
    ├── Algorithm:    SHA-256
    ├── Byte Range:   [0, 1234, 5678, 9012]  ✓
    └── Signed at:    2025-04-28 14:30:00 UTC

Extension Points

Extension Interface / Pattern
Custom trust anchors ITrustAnchorProvider
Custom hash algorithm HashAlgorithmName parameter
External signer (HSM/KMS) IExternalSigner implementation
Custom HTTP IHttpClientProvider / SingleClientProvider / HttpClient injection
Custom logging ILogger<T> injection
Country extensions ICountryExtension
Chain validation IChainValidationProvider
Certificate caching ICertificateCache
Certificate store ICertificateStore

Documentation

Document Description
API Reference Full API documentation (Docfx)
Documentation Home Docfx documentation entry point
Getting Started Installation, first signature, validation
Deferred Signing Two-phase signing for web apps
Inspection & Validation Metadata extraction and cryptographic verification
ICP-Brasil Brazilian PKI integration
Interoperability PDF generators tested, cross-validation matrix, ETSI corpus
Conformance ISO 32000, PAdES ETSI EN 319 142, RFC 5652 compliance
Benchmark Results Comprehensive benchmark report — 69 benchmarks across 15 suites
HostSigner Local signing tray app — API docs & install
Web Signing Sample Browser-based PDF signing demo
Web Inspect Sample Browser-based PDF inspector & validator
Contributing How to contribute, coding standards, PR process
Security Vulnerability reporting
Changelog Release history

Requirements

  • .NET 8 or .NET 10 (multi-target: net8.0 + net10.0)
  • No native or COM dependencies
  • No third-party cryptography — all crypto via System.Security.Cryptography (BCL)
  • Runs on Windows, macOS, and Linux

License

MIT — use it anywhere, for anything, forever.

Contributing

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


Sponsoring

If SimpleSign is useful to you or your organisation, consider sponsoring the project on GitHub. Your support helps keep the library maintained, secure, and free for everyone.


Built for developers who believe document signing should be simple.

About

Enterprise digital signatures for .NET — PAdES · CAdES · XAdES. Zero external crypto deps. Native AOT. MIT.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

17 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages