Firmas digitales
Añadido en la v0.3.38. PDF Oxide lee las firmas desde /AcroForm /Fields → /Sig, inspecciona el sobre CMS dentro de /Contents y ejecuta la comprobación de signer-attributes de RFC 5652 §5.4 contra el certificado embebido, más la verificación messageDigest de §11.2 contra los bytes del documento que proporciona la persona que llama.
Alcance. Esta release cubre la verificación. Firmar PDFs (a diferencia de verificar firmas existentes) se sigue en la otra mitad del issue #208 y aún no se ha publicado.
Qué se verifica
- RSA-PKCS#1 v1.5 sobre SHA-1 / SHA-256 / SHA-384 / SHA-512 — el padding que usa prácticamente cualquier PDF firmado en producción — devuelve
Valid/Invalid. - RSA-PSS y ECDSA se devuelven como
Unknown/UnsupportedFeatureException. Quien los necesite puede leer el certificado embebido conSignature.GetCertificate()/.get_certificate()y ejecutar su propia comprobación. - La raíz de confianza, la ventana de validez y el DN del firmante se marcan en el resultado a partir del certificado embebido.
Ejemplo rápido
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 (solo 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
Enumera e inspecciona firmas. Disponible en Python, WASM, C FFI, C#, Go y Node.
| Propiedad / método | Descripción |
|---|---|
.signer_name / .SignerName |
Common Name del certificado del firmante |
.reason / .Reason |
Motivo de firma (p. ej. “I approve this document”) |
.location / .Location |
Campo de ubicación |
.contact_info |
Información de contacto |
.signing_time / .SigningTime |
Hora de firma UTC del sobre CMS (DateTimeOffset? en C#) |
.verify() / .Verify() |
Comprobación RFC 5652 §5.4 de signer-attributes contra el certificado embebido. Devuelve Valid / Invalid / Unknown. |
.verify_detached(pdf_bytes) / .VerifyDetached(pdfBytes) |
Añade la comprobación RFC 5652 §11.2 messageDigest contra los bytes del documento aportados por la persona que llama. |
.get_certificate() / .GetCertificate() |
Devuelve un Certificate para inspección profunda. |
Certificate
Certificado X.509 extraído del blob CMS /Contents vía x509-parser. Disponible en C FFI, C# y Node.
| Propiedad / método | Descripción |
|---|---|
.subject |
Distinguished Name del titular del certificado |
.issuer |
DN de la CA emisora |
.serial |
Número de serie (como bytes big-endian o cadena) |
.not_before |
Inicio de la ventana de validez (DateTimeOffset) |
.not_after |
Fin de la ventana de validez |
.is_valid |
true si not_before ≤ now ≤ not_after |
Timestamp — RFC 3161 TSTInfo
Analiza el blob de timestamp del atributo TimeStampToken de una firma, o una respuesta RFC 3161 independiente. Disponible en Python, WASM, C FFI, C# y 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();
| Propiedad | Descripción |
|---|---|
.time |
Hora UTC aseverada por la TSA |
.serial |
Número de serie único del timestamp |
.policy_oid |
OID de política de la TSA |
.tsa_name |
Identificador de la TSA |
.hash_algorithm |
Algoritmo hash usado para message_imprint |
.message_imprint |
Hash del payload firmado |
.verify() |
Comprueba la firma del TSTInfo contra el cert. TSA embebido |
TsaClient — cliente HTTP RFC 3161
Solicita un timestamp fresco a una Time Stamp Authority por HTTP. Detrás de la feature tsa-client de Cargo. Disponible en Python, C FFI, C# y Go (no en WASM — ureq es incompatible con wasm).
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 envía una TimeStampReq RFC 3161 por HTTP POST con nonce y autenticación HTTP Basic (cuando se indican username / password). La respuesta se desempaqueta y se analiza con Timestamp::parse.
Resumen de cobertura por binding
| Superficie | Rust | Python | Node | C# | Go | WASM |
|---|---|---|---|---|---|---|
Signature enumerar + verificar |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Certificate inspeccionar |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Timestamp analizar + verificar |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
TsaClient petición HTTP |
✓ | ✓ | ✓ | ✓ | ✓ | — |
El — en TsaClient × WASM es intencional y permanente: ureq no compila a wasm32. Si necesitas inspeccionar la respuesta en el navegador, llama a TsaClient desde un binding del lado servidor y pasa los bytes crudos del timestamp a Timestamp.parse() en WASM.
Páginas relacionadas
- Metadatos y XMP — extracción de metadatos a nivel de documento
- Referencia de API — superficie
SignatureVerifierdel lado Rust