Digitale Signaturen
Neu in v0.3.38. PDF Oxide liest Signaturen aus /AcroForm /Fields → /Sig, inspiziert den CMS-Envelope in /Contents und führt die Signer-Attribute-Prüfung nach RFC 5652 §5.4 gegen das eingebettete Zertifikat sowie die messageDigest-Prüfung nach §11.2 gegen die vom Aufrufer übergebenen Dokumentbytes aus.
Umfang. Dieses Release deckt die Verifizierung ab. Das Signieren von PDFs (im Gegensatz zum Prüfen bestehender Signaturen) ist der noch offene Teil von Issue #208 und wird noch nicht ausgeliefert.
Was geprüft wird
- RSA-PKCS#1 v1.5 über SHA-1 / SHA-256 / SHA-384 / SHA-512 — das Padding, das in der Praxis von so gut wie jedem signierten PDF verwendet wird — liefert
Valid/Invalid. - RSA-PSS und ECDSA erscheinen als
Unknown/UnsupportedFeatureException. Wer sie braucht, kann das eingebettete Zertifikat überSignature.GetCertificate()/.get_certificate()lesen und eine eigene Prüfung ausführen. - Trust-Root-Zuordnung, Gültigkeitsfenster und Signer-DN werden aus dem eingebetteten Zertifikat auf das Prüfergebnis gestempelt.
Schnellbeispiel
Rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("signed.pdf")?;
for sig in doc.signatures() {
println!("{} → {:?}", sig.signer_name(), sig.verify()?);
// End-to-end with document bytes
let pdf_bytes = std::fs::read("signed.pdf")?;
println!("detached ok = {:?}", sig.verify_detached(&pdf_bytes)?);
// Cert inspection + trust-root / expiry / signer DN
let result = pdf_oxide::SignatureVerifier::verify(&sig, &pdf_bytes)?;
println!("signer DN = {}", result.signer_dn);
}
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("signed.pdf")
for sig in doc.signatures():
print(sig.signer_name, "→", sig.verify())
# End-to-end with document bytes
with open("signed.pdf", "rb") as f:
pdf_bytes = f.read()
for sig in doc.signatures():
print("detached ok =", sig.verify_detached(pdf_bytes))
Node / TypeScript
import { PdfDocument } from "pdf-oxide";
import { readFileSync } from "fs";
const doc = PdfDocument.open("signed.pdf");
for (const sig of doc.signatures()) {
console.log(sig.signerName, "→", sig.verify());
console.log("detached ok =", sig.verifyDetached(readFileSync("signed.pdf")));
}
C#
using PdfOxide;
using var doc = PdfDocument.Open("signed.pdf");
foreach (var sig in doc.Signatures)
{
Console.WriteLine($"{sig.SignerName} → {sig.Verify()}");
var cert = sig.GetCertificate();
Console.WriteLine($"subject={cert.Subject} issuer={cert.Issuer} valid={cert.IsValid}");
}
Go (nur unter CGo)
import (
"fmt"
"os"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
doc, _ := pdfoxide.Open("signed.pdf")
defer doc.Close()
pdfBytes, _ := os.ReadFile("signed.pdf")
sigs, _ := doc.Signatures()
for _, sig := range sigs {
ok, _ := sig.Verify()
fmt.Printf("%s → %v\n", sig.SignerName, ok)
// End-to-end with document bytes
detachedOk, _ := sig.VerifyDetached(pdfBytes)
fmt.Println("detached ok =", detachedOk)
}
WASM
import init, { PdfDocument } from "pdf-oxide-wasm/web";
await init();
const doc = new PdfDocument(bytes);
for (const sig of doc.signatures()) {
console.log(sig.signerName, "→", sig.verify());
}
Signature
Signaturen aufzählen und einsehen. Verfügbar in Python, WASM, C-FFI, C#, Go, Node.
| Eigenschaft / Methode | Beschreibung |
|---|---|
.signer_name / .SignerName |
Common Name aus dem Zertifikat des Unterzeichners |
.reason / .Reason |
Signaturgrund (z. B. „Ich bestätige dieses Dokument") |
.location / .Location |
Feld „Location" |
.contact_info |
Feld „Contact info" |
.signing_time / .SigningTime |
UTC-Signaturzeit aus dem CMS-Envelope (DateTimeOffset? in C#) |
.verify() / .Verify() |
Signer-Attribute-Prüfung nach RFC 5652 §5.4 gegen das eingebettete Zertifikat. Liefert Valid / Invalid / Unknown. |
.verify_detached(pdf_bytes) / .VerifyDetached(pdfBytes) |
Ergänzt die messageDigest-Prüfung nach RFC 5652 §11.2 gegen die vom Aufrufer bereitgestellten Dokumentbytes. |
.get_certificate() / .GetCertificate() |
Liefert ein Certificate für tiefergehende Inspektion. |
Certificate
X.509-Zertifikat, das über x509-parser aus dem CMS-Blob in /Contents gelesen wird. Verfügbar in C-FFI, C#, Node.
| Eigenschaft / Methode | Beschreibung |
|---|---|
.subject |
Distinguished Name des Zertifikatsinhabers |
.issuer |
DN der ausstellenden CA |
.serial |
Seriennummer (als Big-Endian-Bytes oder String) |
.not_before |
Beginn des Gültigkeitsfensters (DateTimeOffset) |
.not_after |
Ende des Gültigkeitsfensters |
.is_valid |
Wahr, wenn not_before ≤ now ≤ not_after |
Timestamp — RFC 3161 TSTInfo
Parst den Zeitstempel-Blob aus dem TimeStampToken-Attribut einer Signatur oder aus einer eigenständigen RFC-3161-Antwort. Verfügbar in Python, WASM, C-FFI, C#, Go.
from pdf_oxide import Timestamp
ts = Timestamp.parse(tst_bytes)
print(ts.time, ts.serial, ts.policy_oid, ts.tsa_name, ts.hash_algorithm)
Node / TypeScript
import { Timestamp } from "pdf-oxide";
const ts = Timestamp.parse(tstBytes);
console.log(ts.time, ts.serial, ts.policyOid, ts.tsaName, ts.hashAlgorithm);
ts.close();
| Eigenschaft | Beschreibung |
|---|---|
.time |
Von der TSA behauptete UTC-Zeit |
.serial |
Eindeutige Seriennummer dieses Zeitstempels |
.policy_oid |
TSA-Policy-OID |
.tsa_name |
TSA-Kennung |
.hash_algorithm |
Hash-Algorithmus für message_imprint |
.message_imprint |
Hash der signierten Nutzlast |
.verify() |
Prüft die TSTInfo-Signatur gegen das eingebettete TSA-Zertifikat |
TsaClient — RFC-3161-HTTP-Client
Fordert einen frischen Zeitstempel von einer Time Stamp Authority über HTTP an. Hinter dem Cargo-Feature tsa-client. Verfügbar in Python, C-FFI, C#, Go (nicht in WASM — ureq ist nicht wasm-kompatibel).
Python
from pdf_oxide import TsaClient
client = TsaClient(
url="https://freetsa.org/tsr",
username=None,
password=None,
timeout_seconds=30,
hash_algorithm=2, # 2 = SHA-256
use_nonce=True,
cert_req=True,
)
ts = client.request_timestamp(pdf_bytes)
print(ts.time, ts.serial)
C#
import { TsaClient } from "pdf-oxide";
const client = new TsaClient({
url: "https://freetsa.org/tsr",
timeoutSeconds: 30,
hashAlgorithm: 2, // 2 = SHA-256
useNonce: true,
certReq: true,
});
const ts = client.requestTimestamp(pdfBytes);
console.log(ts.time, ts.serial);
ts.close();
client.close();
C#
var client = new TsaClient("https://freetsa.org/tsr");
var ts = client.RequestTimestamp(pdfBytes);
Console.WriteLine($"{ts.Time} serial={ts.Serial}");
Go
client := pdfoxide.NewTsaClient("https://freetsa.org/tsr")
ts, err := client.RequestTimestamp(pdfBytes)
if err != nil { log.Fatal(err) }
fmt.Println(ts.Time, ts.Serial)
TsaClient sendet einen RFC-3161-TimeStampReq per HTTP-POST mit Nonce und HTTP-Basic-Auth (wenn username / password gesetzt sind). Die Antwort wird ausgepackt und durch Timestamp::parse geleitet.
Übersicht der Binding-Abdeckung
| Oberfläche | Rust | Python | Node | C# | Go | WASM |
|---|---|---|---|---|---|---|
Signature aufzählen + prüfen |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Certificate einsehen |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Timestamp parsen + prüfen |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
TsaClient HTTP-Anfrage |
✓ | ✓ | ✓ | ✓ | ✓ | — |
Das — bei TsaClient × WASM ist beabsichtigt und dauerhaft: ureq kompiliert nicht zu wasm32. Wenn Sie die Antwort im Browser inspizieren müssen, rufen Sie TsaClient aus einem serverseitigen Binding auf und übergeben die rohen Timestamp-Bytes auf WASM-Seite an Timestamp.parse().
Verwandte Seiten
- Metadaten & XMP — Extraktion auf Dokumentebene
- API-Referenz — Rust-seitige
SignatureVerifier-Oberfläche