PDF signieren: digitale Signaturen & PAdES
Hinzugefügt in v0.3.50 (issue #235). PDF Oxide signiert PDFs mit einer abgetrennten CMS/PKCS#7-Signatur, die in /Contents eingebettet wird, füllt /ByteRange und hängt — bei PAdES — über ein inkrementelles Update einen RFC-3161-Zeitstempel sowie einen Document Security Store (DSS) an. Diese Seite behandelt die Erstellungs-Seite. Um eine vorhandene Signatur zu inspizieren oder zu verifizieren, siehe Digitale Signaturen (Extraktion).
Feature-Gate. Das Signieren erfordert das Cargo-Feature
signatures. PAdES-B-T und B-LT benötigen zusätzlich das Featuretsa-clientfür die RFC-3161-Zeitstempelquelle. Wird eine Binding ohne diese Features gebaut, lösen die Signieraufrufe den „nicht implementiert"-Fehler der jeweiligen Binding aus.
Binding-Abdeckung
Das Signieren wird von den Bindings bereitgestellt, die die Familien pdf_sign_bytes* und pdf_certificate_load_* der C-ABI kapseln: Rust, Python, Go (CGo), C# und Swift. Node und WASM stellen nur die Lese-/Verifizierungsseite bereit — sie liefern das unsignierte Platzhalterfeld /Sig im Page-Builder, aber nicht den CMS-Signierer. Rufen Sie eine serverseitige Binding auf, um die signierten Bytes zu erzeugen.
| Funktion | Rust | Python | Go | C# | Swift | Node | WASM |
|---|---|---|---|---|---|---|---|
| Anmeldedaten laden (PEM / PKCS#12) | ✓ | ✓ | ✓ | ✓ | ✓ | — | — |
sign_pdf_bytes (CMS abgetrennt) |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
sign_pdf_bytes_pades (B-B / B-T / B-LT) |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
DSS-Zähler lesen (/DSS) |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
— ist Absicht: In Node/WASM ist kein CMS-Signierer verdrahtet.
Wie lade ich Signier-Anmeldedaten?
Ein Signierer benötigt ein Zertifikat und den passenden privaten Schlüssel. Laden Sie beides entweder aus einem PKCS#12-Blob (.p12 / .pfx) oder aus getrennten PEM-Strings.
SigningCredentials::from_pkcs12(data: &[u8], password: &str)/from_pem(cert_pem: &str, key_pem: &str)(Rust)Certificate.load_pkcs12(data: bytes, password: str)/Certificate.load_pem(cert_pem: str, key_pem: str)(Python)LoadCertificate(data []byte, password string)/LoadCertificateFromPem(certPem, keyPem string)(Go)Certificate.Load(byte[] data, string? password)/Certificate.LoadFromPem(string certPem, string keyPem)(C#)Certificate.loadFromBytes(_ bytes: [UInt8], password: String)/Certificate.loadFromPem(certPem:keyPem:)(Swift)
Einmal geladen, stellt derselbe Handle die Felder subject, issuer, serial, validity und is_valid des eingebetteten Zertifikats bereit — siehe die Certificate-Tabelle auf der Extraktionsseite.
Wie signiere ich ein PDF (CMS/PKCS#7)?
sign_pdf_bytes erzeugt eine althergebrachte adbe.pkcs7.detached-Signatur: einen CMS-Umschlag über den Byte-Bereich des Dokuments, wobei die optionalen Einträge /Reason und /Location in das Signatur-Dictionary geschrieben werden.
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 (nur CGo)
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)
Sie können das Dokument auch in einem Durchgang erstellen und signieren — DocumentBuilder().….build() liefert die unsignierten Bytes zurück, die Sie direkt an sign_pdf_bytes übergeben. Siehe DocumentBuilder.
Was sind die PAdES-Baseline-Stufen?
PAdES (PDF Advanced Electronic Signatures, ETSI EN 319 142) schichtet eine Langzeitvalidierung über die CMS-Signatur. PDF Oxide liefert einen ETSI.CAdES.detached-Signierer mit einer fest verdrahteten Stufenzuordnung, die von der C-ABI und jeder Binding gemeinsam genutzt wird:
| Stufe | Code | Was sie hinzufügt |
|---|---|---|
B-B |
0 | CAdES-Baseline: signierte Attribute inkl. ESS signing-certificate-v2 (RFC 5035). |
B-T |
1 | B-B + ein unsigniertes RFC-3161-Attribut signature-time-stamp über dem Signaturwert. |
B-LT |
2 | B-T + ein Document Security Store (Zertifikate / CRLs / OCSPs + VRI pro Signatur). |
B-LTA |
3 | B-LT + ein dokumentweiter /DocTimeStamp. Für das Signieren reserviert — die Übergabe von Stufe 3 an den Signierer gibt Unsupported zurück. Eine vorhandene B-LTA-Datei erkennen Sie mit dem Dokument-Zeitstempel-Lesesignal weiter unten. |
B-T, B-LT und B-LTA erfordern einen Zeitstempeldienst. Für B-T und B-LT geben Sie eine TSA-URL an, und die Binding holt das RFC-3161-Token; Signieren ohne einen solchen schlägt sicher fehl (fails closed) mit Unsupported.
Wie erstelle ich eine PAdES-B-LT-Signatur?
sign_pdf_bytes_pades nimmt die Stufe, eine optionale TSA-URL und das Sperrmaterial (DER-codierte Zertifikate, CRLs und OCSP-Antworten) entgegen, das zum B-LT-DSS wird.
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 (nur CGo)
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)
Für eine schnelle B-B-Signatur ohne Zeitstempel übergeben Sie die Stufe 0 / PadesLevel.B_B und lassen die TSA-URL weg.
Wie inspiziere ich den von mir erzeugten Document Security Store?
Öffnen Sie das PDF nach einer B-LT-Signatur erneut und lesen Sie dessen /DSS, um zu bestätigen, dass das Validierungsmaterial angekommen ist. dss() gibt None/null zurück, wenn das Dokument keinen DSS hat — das ist kein Fehler.
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 (nur CGo)
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-Accessoren
| Funktion (C ABI / Swift) | Entsprechung in Python / Go / C# | Beschreibung |
|---|---|---|
dss.cert_count() |
len(dss.certs) / len(dss.Certs) / dss.Certificates.Count |
DER-Zertifikate auf Dokumentebene (/Certs) |
dss.crl_count() |
len(dss.crls) / len(dss.CRLs) / dss.Crls.Count |
DER-CRLs auf Dokumentebene (/CRLs) |
dss.ocsp_count() |
len(dss.ocsps) / len(dss.OCSPs) / dss.OcspResponses.Count |
DER-OCSP-Antworten auf Dokumentebene (/OCSPs) |
dss.vri_count() |
len(dss.vri) / dss.VRICount / dss.VriCount |
/VRI-Einträge pro Signatur (SHA-1 von /Contents in Großbuchstaben-Hex) |
Die C-ABI / Swift stellen Zählmethoden plus Getter pro Index bereit (pdf_dss_get_cert usw.); Python und Go geben die DER-Blobs direkt als Listen zurück.
Wie erkenne ich einen B-LTA-Archivzeitstempel?
Signature.pades_level ist signaturbezogen und endet per Design bei B-LT. B-LTA fügt einen dokumentweiten /DocTimeStamp hinzu, der ein Signal für das gesamte Dokument ist — lesen Sie ihn mit der Dokument-Zeitstempel-Prüfung:
import pdf_oxide
with open("archive.pdf", "rb") as f:
is_lta = pdf_oxide.has_document_timestamp(f.read()) # -> bool
- Rust:
pdf_oxide::signatures::has_document_timestamp(pdf_data: &[u8]) -> bool - Go:
doc.HasDocumentTimestamp() (bool, error) - C#:
doc.HasDocumentTimestamp() -> bool - Swift:
try doc.hasTimestamp() -> Bool
Performance
Der Extraktionskern von PDF Oxide läuft mit 0,8 ms Mittel / 9 ms p99 bei 100 % Erfolgsrate auf dem Referenzkorpus (Benchmark v0.3.8). Die Signierkosten werden von der CMS/RSA-Operation dominiert, bei B-T/B-LT zuzüglich der Netzwerk-Round-Trip-Zeit zur TSA — das Hashing des PDF-Byte-Bereichs und das Schreiben des inkrementellen Updates fügen darüber hinaus vernachlässigbaren Overhead hinzu.
FAQ
Brauche ich eine Netzwerkverbindung zum Signieren?
Nur für B-T und B-LT (sowie B-LTA), die einen von einer TSA-URL abgerufenen RFC-3161-Zeitstempel einbetten. Ein einfaches sign_pdf_bytes und eine PAdES-B-B-Signatur funktionieren vollständig offline.
Warum schlägt B-T/B-LT ohne TSA-URL fehl?
Das ist beabsichtigt — diese Stufen erfordern ein Zeitstempel-Token, daher schlägt der Signierer mit einem Unsupported-Fehler sicher fehl, anstatt die Stufe stillschweigend herabzustufen.
Kann ich aus Node oder dem Browser (WASM) signieren?
Noch nicht. Node und WASM stellen das Lesen/Verifizieren von Signaturen und das unsignierte Platzhalterfeld /Sig bereit, aber der CMS-Signierer ist nur in Rust, Python, Go, C# und Swift verdrahtet. Signieren Sie über eine serverseitige Binding und liefern Sie die signierten Bytes aus.
Welchen Signaturalgorithmus verwendet der Signierer? RSA-PKCS#1 v1.5 über SHA-256, das von praktisch jedem PDF-Reader akzeptierte Padding. Eine FIPS-Krypto-Provider-Richtlinie kann das Signieren sicher fehlschlagen lassen, wenn die konfigurierte Richtlinie einen Algorithmus verbietet.
Verwandte Seiten
- Digitale Signaturen (Extraktion) — vorhandene Signaturen lesen und verifizieren, RFC-3161-Zeitstempel parsen, einen TSA-Zeitstempel anfordern
- DocumentBuilder Fluent API — erstellen Sie das PDF, das Sie signieren möchten
- API-Referenz — Oberfläche des
signatures-Moduls auf der Rust-Seite