Skip to content

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 con Signature.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