Sign a PDF: Digital Signatures & PAdES
Added in v0.3.50 (issue #235). PDF Oxide signs PDFs with a CMS/PKCS#7 detached signature embedded in /Contents, fills /ByteRange, and — for PAdES — appends an RFC 3161 timestamp and a Document Security Store (DSS) via incremental update. This page covers the create side. To inspect or verify an existing signature, see Digital Signatures (extraction).
Feature gate. Signing requires the
signaturesCargo feature. PAdES-B-T and B-LT additionally need thetsa-clientfeature for the RFC 3161 timestamp source. Where a binding is built without these features, the signing calls raise the binding’s “not implemented” error.
Binding coverage
Signing is exposed by the bindings that wrap the C ABI’s pdf_sign_bytes* and pdf_certificate_load_* families: Rust, Python, Go (CGo), C#, and Swift. Node and WASM expose the read/verify side only — they ship the unsigned /Sig placeholder field on the page builder but not the CMS signer. Call a server-side binding to produce the signed bytes.
| Surface | Rust | Python | Go | C# | Swift | Node | WASM |
|---|---|---|---|---|---|---|---|
| Load credentials (PEM / PKCS#12) | ✓ | ✓ | ✓ | ✓ | ✓ | — | — |
sign_pdf_bytes (CMS detached) |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
sign_pdf_bytes_pades (B-B / B-T / B-LT) |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
Read DSS (/DSS) counts |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
— is intentional: Node/WASM have no CMS signer wired.
How do I load signing credentials?
A signer needs a certificate and the matching private key. Load either from a PKCS#12 (.p12 / .pfx) blob or from separate PEM strings.
Rust
let creds = SigningCredentials::from_pkcs12(&data, "password")?;
// or from PEM strings:
let creds = SigningCredentials::from_pem(&cert_pem, &key_pem)?;
Python
cert = pdf_oxide.Certificate.load_pkcs12(data, "password")
# or from PEM strings:
cert = pdf_oxide.Certificate.load_pem(cert_pem, key_pem)
Go
cert, err := pdfoxide.LoadCertificate(data, "password")
// or from PEM strings:
cert, err := pdfoxide.LoadCertificateFromPem(certPem, keyPem)
C#
var cert = Certificate.Load(data, "password");
// or from PEM strings:
var cert = Certificate.LoadFromPem(certPem, keyPem);
Swift
let cert = try Certificate.loadFromBytes(bytes, password: "password")
// or from PEM strings:
let cert = try Certificate.loadFromPem(certPem: certPem, keyPem: keyPem)
Once loaded, the same handle exposes the embedded certificate’s subject, issuer, serial, validity, and is_valid — see the Certificate table on the extraction page.
How do I sign a PDF (CMS/PKCS#7)?
sign_pdf_bytes produces a legacy adbe.pkcs7.detached signature: a CMS envelope over the document byte-range, with the optional /Reason and /Location entries written into the signature dictionary.
Rust
use pdf_oxide::signatures::{sign_pdf_bytes, SignOptions, SigningCredentials};
let creds = SigningCredentials::from_pkcs12(&std::fs::read("id.p12")?, "testpass")?;
println!("signing as {}", creds.subject()?);
let pdf = std::fs::read("invoice.pdf")?;
let opts = SignOptions {
reason: Some("Approved".into()),
location: Some("HQ".into()),
..Default::default()
};
let signed = sign_pdf_bytes(&pdf, &creds, opts)?;
std::fs::write("invoice-signed.pdf", &signed)?;
assert!(signed.windows(10).any(|w| w == b"/ByteRange"));
Python
import pdf_oxide
with open("id.p12", "rb") as f:
cert = pdf_oxide.Certificate.load_pkcs12(f.read(), "testpass")
print("signing as", cert.subject)
with open("invoice.pdf", "rb") as f:
pdf_bytes = f.read()
signed = pdf_oxide.sign_pdf_bytes(pdf_bytes, cert, reason="Approved", location="HQ")
with open("invoice-signed.pdf", "wb") as f:
f.write(signed)
assert b"/ByteRange" in signed
Go (CGo-only)
import (
"fmt"
"os"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
p12, _ := os.ReadFile("id.p12")
cert, err := pdfoxide.LoadCertificate(p12, "testpass")
if err != nil {
panic(err)
}
defer cert.Close()
pdf, _ := os.ReadFile("invoice.pdf")
signed, err := pdfoxide.SignPdfBytes(pdf, cert, "Approved", "HQ")
if err != nil {
panic(err)
}
os.WriteFile("invoice-signed.pdf", signed, 0o644)
fmt.Println("signed", len(signed), "bytes")
C#
using PdfOxide;
var cert = Certificate.Load(File.ReadAllBytes("id.p12"), "testpass");
Console.WriteLine($"signing as {cert.Subject}");
byte[] pdf = File.ReadAllBytes("invoice.pdf");
byte[] signed = cert.SignPdfBytes(pdf, reason: "Approved", location: "HQ");
File.WriteAllBytes("invoice-signed.pdf", signed);
Swift
import PdfOxide
let cert = try Certificate.loadFromBytes(Array(try Data(contentsOf: URL(fileURLWithPath: "id.p12"))), password: "testpass")
let pdf = Array(try Data(contentsOf: URL(fileURLWithPath: "invoice.pdf")))
let signed = try signBytes(pdf, certificate: cert, reason: "Approved", location: "HQ")
try Data(signed).write(to: URL(fileURLWithPath: "invoice-signed.pdf"))
Java
import fyi.oxide.pdf.PdfSigner;
import fyi.oxide.pdf.signature.SignOptions;
import fyi.oxide.pdf.signature.SignatureLevel;
import java.nio.file.*;
PdfSigner signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass");
byte[] pdf = Files.readAllBytes(Path.of("invoice.pdf"));
byte[] signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_B) // plain CMS / PKCS#7
.withReason("Approved")
.withLocation("HQ")
.build());
Files.write(Path.of("invoice-signed.pdf"), signed);
PHP
use PdfOxide\PdfSigner;
$signer = PdfSigner::fromPkcs12('id.p12', 'testpass');
$pdf = file_get_contents('invoice.pdf');
$signed = $signer->sign(
$pdf,
level: PdfSigner::LEVEL_B_B, // plain CMS / PKCS#7
reason: 'Approved',
location: 'HQ',
);
file_put_contents('invoice-signed.pdf', $signed);
$signer->close();
Ruby
require 'pdf_oxide'
# certificate_handle comes from your PKCS#12 / PEM credentials API
signer = PdfOxide::PdfSigner.new(certificate_handle)
pdf = File.binread('invoice.pdf')
signed = signer.sign(pdf, level: :b, reason: 'Approved', location: 'HQ')
File.binwrite('invoice-signed.pdf', signed)
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto cert = pdf_oxide::Certificate::load_from_bytes(read_bytes("id.p12"), "testpass");
auto pdf = read_bytes("invoice.pdf");
auto signed = pdf_oxide::sign_bytes(pdf, cert, "Approved", "HQ");
write_bytes("invoice-signed.pdf", signed);
Kotlin
import fyi.oxide.pdf.PdfSigner
import fyi.oxide.pdf.signature.SignOptions
import fyi.oxide.pdf.signature.SignatureLevel
import java.nio.file.*
val signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass")
val pdf = Files.readAllBytes(Path.of("invoice.pdf"))
val signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_B) // plain CMS / PKCS#7
.withReason("Approved")
.withLocation("HQ")
.build())
Files.write(Path.of("invoice-signed.pdf"), signed)
Dart
import 'dart:io';
import 'package:pdf_oxide/pdf_oxide.dart';
final cert = Certificate.loadFromBytes(File('id.p12').readAsBytesSync(), 'testpass');
final pdf = File('invoice.pdf').readAsBytesSync();
final signed = signBytes(pdf, cert, reason: 'Approved', location: 'HQ');
File('invoice-signed.pdf').writeAsBytesSync(signed);
R
library(pdfoxide)
cert <- pdf_certificate_load_from_bytes(readBin("id.p12", "raw", file.size("id.p12")), "testpass")
pdf <- readBin("invoice.pdf", "raw", file.size("invoice.pdf"))
signed <- pdf_sign_bytes(pdf, cert, reason = "Approved", location = "HQ")
writeBin(signed, "invoice-signed.pdf")
Julia
using PdfOxide
cert = certificate_load_from_bytes(read("id.p12"), "testpass")
pdf = read("invoice.pdf")
signed = sign_bytes(pdf, cert, "Approved", "HQ")
write("invoice-signed.pdf", signed)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
const cert = try pdf_oxide.Certificate.loadFromBytes(p12_bytes, "testpass");
const signed = try pdf_oxide.signBytes(a, pdf_bytes, cert, "Approved", "HQ");
defer a.free(signed);
try std.fs.cwd().writeFile(.{ .sub_path = "invoice-signed.pdf", .data = signed });
Scala
import fyi.oxide.pdf.PdfSigner
import fyi.oxide.pdf.signature.{SignOptions, SignatureLevel}
import java.nio.file.*
val signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass")
val pdf = Files.readAllBytes(Path.of("invoice.pdf"))
val signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_B) // plain CMS / PKCS#7
.withReason("Approved")
.withLocation("HQ")
.build())
Files.write(Path.of("invoice-signed.pdf"), signed)
Clojure
(import '[fyi.oxide.pdf PdfSigner]
'[fyi.oxide.pdf.signature SignOptions SignatureLevel])
(require '[clojure.java.io :as io])
(let [signer (PdfSigner/fromPkcs12 (java.nio.file.Path/of "id.p12" (make-array String 0)) "testpass")
pdf (java.nio.file.Files/readAllBytes (java.nio.file.Path/of "invoice.pdf" (make-array String 0)))
opts (-> (SignOptions/builder)
(.withLevel SignatureLevel/B_B) ; plain CMS / PKCS#7
(.withReason "Approved")
(.withLocation "HQ")
(.build))
signed (.sign signer pdf opts)]
(java.nio.file.Files/write (java.nio.file.Path/of "invoice-signed.pdf" (make-array String 0)) signed
(make-array java.nio.file.OpenOption 0)))
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXCertificate *cert = [POXCertificate loadFromBytes:p12Data password:@"testpass" error:&err];
NSData *pdf = [NSData dataWithContentsOfFile:@"invoice.pdf"];
NSData *signed = [POXSigning signBytes:pdf certificate:cert reason:@"Approved" location:@"HQ" error:&err];
[signed writeToFile:@"invoice-signed.pdf" atomically:YES];
Elixir
{:ok, cert} = PdfOxide.certificate_from_bytes(File.read!("id.p12"), "testpass")
pdf = File.read!("invoice.pdf")
{:ok, signed} = PdfOxide.sign_bytes(pdf, cert, "Approved", "HQ")
File.write!("invoice-signed.pdf", signed)
You can also build the document and sign it in one pass — DocumentBuilder().….build() returns the unsigned bytes, which you feed straight into sign_pdf_bytes. See DocumentBuilder.
What are the PAdES baseline levels?
PAdES (PDF Advanced Electronic Signatures, ETSI EN 319 142) layers long-term validation on top of the CMS signature. PDF Oxide ships an ETSI.CAdES.detached signer with a frozen level mapping shared across the C ABI and every binding:
| Level | Code | What it adds |
|---|---|---|
B-B |
0 | CAdES baseline: signed attributes incl. the ESS signing-certificate-v2 (RFC 5035). |
B-T |
1 | B-B + an RFC 3161 signature-time-stamp unsigned attribute over the signature value. |
B-LT |
2 | B-T + a Document Security Store (certs / CRLs / OCSPs + per-signature VRI). |
B-LTA |
3 | B-LT + a document /DocTimeStamp. Reserved for signing — passing level 3 to the signer returns Unsupported. Detect an existing B-LTA file with the document-timestamp reader signal below. |
B-T, B-LT, and B-LTA require a timestamper. For B-T and B-LT you supply a TSA URL and the binding fetches the RFC 3161 token; signing without one fails closed with Unsupported.
How do I create a PAdES B-LT signature?
sign_pdf_bytes_pades takes the level, an optional TSA URL, and the revocation material (DER-encoded certificates, CRLs, and OCSP responses) that becomes the B-LT DSS.
Rust
use pdf_oxide::signatures::{
sign_pdf_bytes_pades, PadesLevel, RevocationMaterial, SignOptions, SigningCredentials,
};
use pdf_oxide::signatures::{TsaClient, TsaClientConfig};
let creds = SigningCredentials::from_pkcs12(&std::fs::read("id.p12")?, "testpass")?;
let pdf = std::fs::read("invoice.pdf")?;
// RFC 3161 token source for B-T / B-LT.
let tsa = TsaClient::new(TsaClientConfig::new("https://freetsa.org/tsr".into()));
let timestamper = |sig: &[u8]| tsa.request_timestamp(sig).map(|t| t.token_bytes().to_vec());
let material = RevocationMaterial {
certificates: vec![std::fs::read("ca.der")?],
..Default::default()
};
let signed = sign_pdf_bytes_pades(
&pdf,
&creds,
SignOptions { reason: Some("Approved".into()), ..Default::default() },
PadesLevel::BLt,
Some(×tamper),
&material,
)?;
std::fs::write("invoice-pades.pdf", &signed)?;
Python
import pdf_oxide
from pdf_oxide import PadesLevel, RevocationMaterial
with open("id.p12", "rb") as f:
cert = pdf_oxide.Certificate.load_pkcs12(f.read(), "testpass")
with open("invoice.pdf", "rb") as f:
pdf_bytes = f.read()
with open("ca.der", "rb") as f:
ca_der = f.read()
signed = pdf_oxide.sign_pdf_bytes_pades(
pdf_bytes,
cert,
PadesLevel.B_LT,
tsa_url="https://freetsa.org/tsr",
reason="Approved",
revocation=RevocationMaterial(certs=[ca_der]),
)
with open("invoice-pades.pdf", "wb") as f:
f.write(signed)
Go (CGo-only)
ca, _ := os.ReadFile("ca.der")
opts := pdfoxide.PAdESOptions{
Level: pdfoxide.PAdESBLt,
TSAURL: "https://freetsa.org/tsr",
Reason: "Approved",
Revocation: &pdfoxide.RevocationMaterial{Certs: [][]byte{ca}},
}
signed, err := pdfoxide.SignPdfBytesPAdES(pdf, cert, opts)
if err != nil {
panic(err)
}
os.WriteFile("invoice-pades.pdf", signed, 0o644)
C#
var material = new RevocationMaterial();
material.Certificates.Add(File.ReadAllBytes("ca.der"));
byte[] signed = cert.SignPdfBytesPades(pdf, new PadesSignOptions
{
Level = PadesLevel.BLt,
TsaUrl = "https://freetsa.org/tsr",
Reason = "Approved",
Revocation = material,
});
File.WriteAllBytes("invoice-pades.pdf", signed);
Swift
let ca = Array(try Data(contentsOf: URL(fileURLWithPath: "ca.der")))
let signed = try signBytesPades(
pdf,
certificate: cert,
level: 2, // 0=B-B 1=B-T 2=B-LT
tsaUrl: "https://freetsa.org/tsr",
reason: "Approved",
certs: [ca]
)
try Data(signed).write(to: URL(fileURLWithPath: "invoice-pades.pdf"))
Java
import fyi.oxide.pdf.PdfSigner;
import fyi.oxide.pdf.signature.SignOptions;
import fyi.oxide.pdf.signature.SignatureLevel;
import java.nio.file.*;
PdfSigner signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass");
byte[] pdf = Files.readAllBytes(Path.of("invoice.pdf"));
byte[] signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_LT) // B-T / B-LT require a TSA
.withTsaUrl("https://freetsa.org/tsr")
.withReason("Approved")
.build());
Files.write(Path.of("invoice-pades.pdf"), signed);
PHP
use PdfOxide\PdfSigner;
$signer = PdfSigner::fromPkcs12('id.p12', 'testpass');
$pdf = file_get_contents('invoice.pdf');
$signed = $signer->sign(
$pdf,
level: PdfSigner::LEVEL_B_LT, // B-T / B-LT require a TSA
tsaUrl: 'https://freetsa.org/tsr',
reason: 'Approved',
);
file_put_contents('invoice-pades.pdf', $signed);
$signer->close();
Ruby
require 'pdf_oxide'
signer = PdfOxide::PdfSigner.new(certificate_handle)
pdf = File.binread('invoice.pdf')
signed = signer.sign(
pdf,
level: :lt, # B-T / B-LT require a TSA
tsa_url: 'https://freetsa.org/tsr',
reason: 'Approved',
)
File.binwrite('invoice-pades.pdf', signed)
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto cert = pdf_oxide::Certificate::load_from_bytes(read_bytes("id.p12"), "testpass");
auto pdf = read_bytes("invoice.pdf");
pdf_oxide::RevocationMaterial material;
material.certs.push_back(read_bytes("ca.der"));
auto signed = pdf_oxide::sign_bytes_pades(
pdf, cert,
/*level=*/2, // 0=B-B 1=B-T 2=B-LT
"https://freetsa.org/tsr",
"Approved", /*location=*/"",
material);
write_bytes("invoice-pades.pdf", signed);
Kotlin
import fyi.oxide.pdf.PdfSigner
import fyi.oxide.pdf.signature.SignOptions
import fyi.oxide.pdf.signature.SignatureLevel
import java.nio.file.*
val signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass")
val pdf = Files.readAllBytes(Path.of("invoice.pdf"))
val signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_LT) // B-T / B-LT require a TSA
.withTsaUrl("https://freetsa.org/tsr")
.withReason("Approved")
.build())
Files.write(Path.of("invoice-pades.pdf"), signed)
Dart
import 'dart:io';
import 'package:pdf_oxide/pdf_oxide.dart';
final cert = Certificate.loadFromBytes(File('id.p12').readAsBytesSync(), 'testpass');
final pdf = File('invoice.pdf').readAsBytesSync();
final ca = File('ca.der').readAsBytesSync();
final signed = signBytesPades(
pdf, cert,
2, // 0=B-B 1=B-T 2=B-LT
tsaUrl: 'https://freetsa.org/tsr',
reason: 'Approved',
certs: [ca],
);
File('invoice-pades.pdf').writeAsBytesSync(signed);
R
library(pdfoxide)
cert <- pdf_certificate_load_from_bytes(readBin("id.p12", "raw", file.size("id.p12")), "testpass")
pdf <- readBin("invoice.pdf", "raw", file.size("invoice.pdf"))
ca <- readBin("ca.der", "raw", file.size("ca.der"))
signed <- pdf_sign_bytes_pades(
pdf, cert,
level = 2L, # 0=B-B 1=B-T 2=B-LT
tsa_url = "https://freetsa.org/tsr",
reason = "Approved",
certs = list(ca)
)
writeBin(signed, "invoice-pades.pdf")
Julia
using PdfOxide
cert = certificate_load_from_bytes(read("id.p12"), "testpass")
pdf = read("invoice.pdf")
ca = read("ca.der")
signed = sign_bytes_pades(
pdf, cert,
2, # 0=B-B 1=B-T 2=B-LT
"https://freetsa.org/tsr",
"Approved", "";
certs = [ca],
)
write("invoice-pades.pdf", signed)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
const cert = try pdf_oxide.Certificate.loadFromBytes(p12_bytes, "testpass");
const signed = try pdf_oxide.signBytesPades(
a, pdf_bytes, cert,
2, // 0=B-B 1=B-T 2=B-LT
"https://freetsa.org/tsr",
"Approved", "",
&[_][]const u8{ca_der}, &.{}, &.{},
);
defer a.free(signed);
try std.fs.cwd().writeFile(.{ .sub_path = "invoice-pades.pdf", .data = signed });
Scala
import fyi.oxide.pdf.PdfSigner
import fyi.oxide.pdf.signature.{SignOptions, SignatureLevel}
import java.nio.file.*
val signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass")
val pdf = Files.readAllBytes(Path.of("invoice.pdf"))
val signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_LT) // B-T / B-LT require a TSA
.withTsaUrl("https://freetsa.org/tsr")
.withReason("Approved")
.build())
Files.write(Path.of("invoice-pades.pdf"), signed)
Clojure
(import '[fyi.oxide.pdf PdfSigner]
'[fyi.oxide.pdf.signature SignOptions SignatureLevel])
(let [signer (PdfSigner/fromPkcs12 (java.nio.file.Path/of "id.p12" (make-array String 0)) "testpass")
pdf (java.nio.file.Files/readAllBytes (java.nio.file.Path/of "invoice.pdf" (make-array String 0)))
opts (-> (SignOptions/builder)
(.withLevel SignatureLevel/B_LT) ; B-T / B-LT require a TSA
(.withTsaUrl "https://freetsa.org/tsr")
(.withReason "Approved")
(.build))
signed (.sign signer pdf opts)]
(java.nio.file.Files/write (java.nio.file.Path/of "invoice-pades.pdf" (make-array String 0)) signed
(make-array java.nio.file.OpenOption 0)))
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXCertificate *cert = [POXCertificate loadFromBytes:p12Data password:@"testpass" error:&err];
NSData *pdf = [NSData dataWithContentsOfFile:@"invoice.pdf"];
NSData *ca = [NSData dataWithContentsOfFile:@"ca.der"];
NSData *signed = [POXSigning signBytesPades:pdf
certificate:cert
level:2 // 0=B-B 1=B-T 2=B-LT
tsaUrl:@"https://freetsa.org/tsr"
reason:@"Approved"
location:nil
certs:@[ca]
crls:@[]
ocsps:@[]
error:&err];
[signed writeToFile:@"invoice-pades.pdf" atomically:YES];
Elixir
{:ok, cert} = PdfOxide.certificate_from_bytes(File.read!("id.p12"), "testpass")
pdf = File.read!("invoice.pdf")
ca = File.read!("ca.der")
{:ok, signed} =
PdfOxide.sign_bytes_pades(pdf, cert, 2, "https://freetsa.org/tsr", # 0=B-B 1=B-T 2=B-LT
reason: "Approved",
certs: [ca]
)
File.write!("invoice-pades.pdf", signed)
For a quick B-B signature with no timestamp, pass level 0 / PadesLevel.B_B and omit the TSA URL.
How do I inspect the Document Security Store I produced?
After a B-LT sign, reopen the PDF and read its /DSS to confirm the validation material landed. dss() returns None/null when the document has no DSS — that is not an error.
Rust
use pdf_oxide::PdfDocument;
use pdf_oxide::signatures::read_dss;
let doc = PdfDocument::open("invoice-pades.pdf")?;
if let Some(dss) = read_dss(&doc)? {
println!("certs={} crls={} ocsps={} vri={}",
dss.certificates.len(), dss.crls.len(), dss.ocsp_responses.len(), dss.vri.len());
}
Python
import pdf_oxide
doc = pdf_oxide.PdfDocument("invoice-pades.pdf")
dss = doc.dss()
if dss is not None:
print("certs", len(dss.certs), "crls", len(dss.crls),
"ocsps", len(dss.ocsps), "vri", len(dss.vri))
Go (CGo-only)
doc, _ := pdfoxide.Open("invoice-pades.pdf")
defer doc.Close()
dss, _ := doc.DSS() // nil when the PDF has no /DSS
if dss != nil {
fmt.Printf("certs=%d crls=%d ocsps=%d vri=%d\n",
len(dss.Certs), len(dss.CRLs), len(dss.OCSPs), dss.VRICount)
}
C#
using var doc = PdfDocument.Open("invoice-pades.pdf");
var dss = doc.GetDss(); // null when the PDF has no /DSS
if (dss is not null)
{
Console.WriteLine($"certs={dss.Certificates.Count} crls={dss.Crls.Count} " +
$"ocsps={dss.OcspResponses.Count} vri={dss.VriCount}");
}
Swift
let doc = try Document.open("invoice-pades.pdf")
if let dss = try doc.dss() {
print("certs=\(try dss.certCount()) crls=\(try dss.crlCount()) " +
"ocsps=\(try dss.ocspCount()) vri=\(try dss.vriCount())")
}
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("invoice-pades.pdf");
auto dss = doc.get_dss(); // throws if the PDF has no /DSS
std::cout << "certs=" << dss.cert_count() << " crls=" << dss.crl_count()
<< " ocsps=" << dss.ocsp_count() << " vri=" << dss.vri_count() << "\n";
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('invoice-pades.pdf');
final dss = doc.getDss(); // throws if the PDF has no /DSS
print('certs=${dss.certCount} crls=${dss.crlCount} '
'ocsps=${dss.ocspCount} vri=${dss.vriCount}');
R
library(pdfoxide)
doc <- pdf_open("invoice-pades.pdf")
dss <- pdf_get_dss(doc) # errors if the PDF has no /DSS
cat(sprintf("certs=%d crls=%d ocsps=%d vri=%d\n",
pdf_dss_cert_count(dss), pdf_dss_crl_count(dss),
pdf_dss_ocsp_count(dss), pdf_dss_vri_count(dss)))
Julia
using PdfOxide
doc = open_document("invoice-pades.pdf")
dss = document_get_dss(doc) # errors if the PDF has no /DSS
println("certs=$(dss_cert_count(dss)) crls=$(dss_crl_count(dss)) ",
"ocsps=$(dss_ocsp_count(dss)) vri=$(dss_vri_count(dss))")
Zig
const pdf_oxide = @import("pdf_oxide");
var doc = try pdf_oxide.Document.open("invoice-pades.pdf");
const dss = try doc.dss(); // errors if the PDF has no /DSS
std.debug.print("certs={d} crls={d} ocsps={d} vri={d}\n", .{
try dss.certCount(), try dss.crlCount(), try dss.ocspCount(), try dss.vriCount(),
});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"invoice-pades.pdf" error:&err];
POXDss *dss = [doc dssWithError:&err]; // nil + error when the PDF has no /DSS
if (dss != nil) {
NSLog(@"certs=%d crls=%d ocsps=%d vri=%d",
[dss certCount], [dss crlCount], [dss ocspCount], [dss vriCount]);
}
Elixir
{:ok, doc} = PdfOxide.open("invoice-pades.pdf")
case PdfOxide.document_dss(doc) do # {:error, _} when the PDF has no /DSS
{:ok, dss} ->
IO.puts("certs=#{PdfOxide.dss_cert_count(dss)} crls=#{PdfOxide.dss_crl_count(dss)} " <>
"ocsps=#{PdfOxide.dss_ocsp_count(dss)} vri=#{PdfOxide.dss_vri_count(dss)}")
_ -> :no_dss
end
DSS accessors
| Surface (C ABI / Swift) | Python / Go / C# equivalent | Description |
|---|---|---|
dss.cert_count() |
len(dss.certs) / len(dss.Certs) / dss.Certificates.Count |
Document-level DER certificates (/Certs) |
dss.crl_count() |
len(dss.crls) / len(dss.CRLs) / dss.Crls.Count |
Document-level DER CRLs (/CRLs) |
dss.ocsp_count() |
len(dss.ocsps) / len(dss.OCSPs) / dss.OcspResponses.Count |
Document-level DER OCSP responses (/OCSPs) |
dss.vri_count() |
len(dss.vri) / dss.VRICount / dss.VriCount |
Per-signature /VRI entries (uppercase-hex SHA-1 of /Contents) |
The C ABI / Swift expose count methods plus per-index getters (pdf_dss_get_cert etc.); Python and Go return the DER blobs as lists directly.
How do I detect a B-LTA archival timestamp?
Signature.pades_level is signature-scoped and tops out at B-LT by design. B-LTA adds a document-level /DocTimeStamp, which is a document-wide signal — read it with the document-timestamp check:
Python
import pdf_oxide
with open("archive.pdf", "rb") as f:
is_lta = pdf_oxide.has_document_timestamp(f.read())
Rust
use pdf_oxide::signatures::has_document_timestamp;
let pdf = std::fs::read("archive.pdf")?;
let is_lta = has_document_timestamp(&pdf);
Go
doc, _ := pdfoxide.Open("archive.pdf")
defer doc.Close()
is_lta, _ := doc.HasDocumentTimestamp()
C#
using var doc = PdfDocument.Open("archive.pdf");
bool isLta = doc.HasDocumentTimestamp();
Swift
let doc = try Document.open("archive.pdf")
let isLta = try doc.hasTimestamp()
Performance
PDF Oxide’s extraction core runs at 0.8 ms mean / 9 ms p99 with a 100% pass rate on the reference corpus (v0.3.8 benchmark). Signing cost is dominated by the CMS/RSA operation plus, for B-T/B-LT, the network round-trip to the TSA — the PDF byte-range hashing and incremental-update write add negligible overhead on top.
FAQ
Do I need a network connection to sign?
Only for B-T and B-LT (and B-LTA), which embed an RFC 3161 timestamp fetched from a TSA URL. A plain sign_pdf_bytes and a PAdES B-B signature are fully offline.
Why does B-T/B-LT fail without a TSA URL?
By design — those levels require a timestamp token, so the signer fails closed with an Unsupported error rather than silently downgrading the level.
Can I sign from Node or the browser (WASM)?
Not yet. Node and WASM expose signature reading/verification and the unsigned /Sig placeholder field, but the CMS signer is wired only into Rust, Python, Go, C#, and Swift. Sign on a server-side binding and ship the signed bytes.
What signature algorithm does the signer use? RSA-PKCS#1 v1.5 over SHA-256, the padding accepted by essentially every PDF reader. A FIPS crypto-provider policy can make signing fail closed if the configured policy disallows an algorithm.
Related Pages
- Digital Signatures (extraction) — read and verify existing signatures, parse RFC 3161 timestamps, request a TSA timestamp
- DocumentBuilder Fluent API — build the PDF you want to sign
- API Reference — Rust-side
signaturesmodule surface