Skip to content

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 über Signature.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