Skip to content

Firmas digitales

Añadido en v0.3.38. PDF Oxide lee las firmas de /AcroForm /Fields → /Sig, inspecciona el sobre CMS dentro de /Contents y ejecuta la comprobación de atributos del firmante según RFC 5652 §5.4 contra el certificado incrustado, junto con la verificación messageDigest del §11.2 contra los bytes del documento proporcionados por el llamador.

Alcance. Esta página cubre la lectura y verificación de firmas existentes: enumeración, comprobación criptográfica CMS, clasificación de nivel PAdES, inspección del DSS y timestamps RFC 3161. Para crear una firma — cargar credenciales PKCS#12/PEM y producir una firma CMS o PAdES B-B/B-T/B-LT — consulta Firmar un PDF: Firmas Digitales y PAdES (publicado en v0.3.50, issue #235).

Qué se verifica

  • RSA-PKCS#1 v1.5 over SHA-1 / SHA-256 / SHA-384 / SHA-512 — el relleno utilizado por prácticamente todos los PDF firmados — devuelve Valid / Invalid.
  • RSA-PSS y ECDSA aparecen como Unknown / UnsupportedFeatureException. Los llamadores que necesiten estos algoritmos pueden leer el certificado incrustado mediante Signature.GetCertificate() / .get_certificate() y realizar su propia comprobación.
  • La búsqueda del ancla de confianza, la ventana de validez y el DN del firmante se obtienen del certificado incrustado y se registran en el resultado de la verificación.

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 (CGo-only)

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());
}

C++

#include <pdf_oxide/pdf_oxide.hpp>
#include <fstream>

auto doc = pdf_oxide::Document::open("signed.pdf");

std::ifstream f("signed.pdf", std::ios::binary);
std::vector<std::uint8_t> pdf_bytes((std::istreambuf_iterator<char>(f)), {});

for (int i = 0; i < doc.get_signature_count(); ++i) {
    auto sig = doc.get_signature(i);
    std::cout << sig.signer_name() << " → " << sig.verify() << "\n";   // 1 valid / 0 invalid / -1 unknown
    std::cout << "detached ok = " << sig.verify_detached(pdf_bytes) << "\n";
}

Swift

import PdfOxide
import Foundation

let doc = try Document.open("signed.pdf")
let pdfBytes = [UInt8](try Data(contentsOf: URL(fileURLWithPath: "signed.pdf")))

for i in 0..<(try doc.signatureCount()) {
    guard let sig = try doc.signature(i) else { continue }
    print(try sig.signerName(), "→", try sig.verify())   // 1 valid / 0 invalid / -1 unknown
    print("detached ok =", try sig.verifyDetached(pdfBytes))
}

Dart

import 'dart:io';
import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('signed.pdf');
final pdfBytes = File('signed.pdf').readAsBytesSync();

for (var i = 0; i < doc.getSignatureCount(); i++) {
  final sig = doc.getSignature(i);
  print('${sig.signerName}${sig.verify()}');   // 1 valid / 0 invalid / -1 unknown
  print('detached ok = ${sig.verifyDetached(pdfBytes)}');
}

R

library(pdfoxide)

doc <- pdf_open("signed.pdf")
pdf_bytes <- readBin("signed.pdf", "raw", file.info("signed.pdf")$size)

for (i in seq_len(pdf_signature_count(doc)) - 1L) {
  sig <- pdf_get_signature(doc, i)
  cat(pdf_signature_signer_name(sig), "→", pdf_signature_verify(sig), "\n")  # 1 valid / 0 invalid / -1 unknown
  cat("detached ok =", pdf_signature_verify_detached(sig, pdf_bytes), "\n")
}

Julia

using PdfOxide

doc = open_document("signed.pdf")
pdf_bytes = read("signed.pdf")

for i in 0:(get_signature_count(doc) - 1)
    sig = get_signature(doc, i)
    println(signature_get_signer_name(sig), " → ", signature_verify(sig))   # 1 valid / 0 invalid / -1 unknown
    println("detached ok = ", signature_verify_detached(sig, pdf_bytes))
end

Zig

const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;

var doc = try pdf_oxide.Document.open("signed.pdf");
const pdf_bytes = try std.fs.cwd().readFileAlloc(a, "signed.pdf", 1 << 30);

var i: i32 = 0;
const count = try doc.signatureCount();
while (i < count) : (i += 1) {
    var sig = try doc.signature(i);
    const name = try sig.signerName(a);
    std.debug.print("{s} → {}\n", .{ name, try sig.verify() });   // 1 valid / 0 invalid / -1 unknown
    std.debug.print("detached ok = {}\n", .{try sig.verifyDetached(pdf_bytes)});
}

Objective-C

#import "POXPdfOxide.h"
NSError *err = nil;

POXDocument *doc = [POXDocument openPath:@"signed.pdf" error:&err];
NSData *pdfBytes = [NSData dataWithContentsOfFile:@"signed.pdf"];

int32_t count = [doc signatureCountWithError:&err];
for (int32_t i = 0; i < count; i++) {
    POXSignatureInfo *sig = [doc signatureAtIndex:i error:&err];
    NSLog(@"%@%d", [sig signerNameError:&err], [sig verifyError:&err]);   // 1 valid / 0 invalid / -1 unknown
    NSLog(@"detached ok = %d", [sig verifyDetached:pdfBytes error:&err]);
}

Elixir

{:ok, doc} = PdfOxide.open("signed.pdf")
pdf_bytes = File.read!("signed.pdf")

{:ok, count} = PdfOxide.signature_count(doc)
for i <- 0..(count - 1) do
  {:ok, sig} = PdfOxide.signature(doc, i)
  {:ok, name} = PdfOxide.signature_signer_name(sig)
  {:ok, verdict} = PdfOxide.signature_verify(sig)          # 1 valid / 0 invalid / -1 unknown
  {:ok, detached} = PdfOxide.signature_verify_detached(sig, pdf_bytes)
  IO.puts("#{name}#{verdict}")
  IO.puts("detached ok = #{detached}")
end

Signature

Enumerar e inspeccionar firmas. Disponible en todos los bindings.

Propiedad / Método Descripción
.signer_name / .SignerName Common Name del certificado del firmante. Accesor C ABI / Swift: signing_name / signerName().
.reason / .Reason Motivo de la firma (p. ej., “Apruebo este documento”). Accesor C ABI / Swift: signing_reason / signingReason().
.location / .Location Campo de ubicación. Accesor C ABI / Swift: signing_location / signingLocation().
.contact_info Campo de información de contacto
.signing_time / .SigningTime Hora UTC de firma del sobre CMS (DateTimeOffset? en C#)
.pades_level / padesLevel() Nivel de base PAdES clasificado solo desde atributos CMS (B_B/B_T). B_LT necesita además el /DSS del documento — combina con dss más abajo.
.has_timestamp() / hasTimestamp() True si esta firma lleva un timestamp RFC 3161 incrustado.
.add_timestamp(ts) / addTimestamp(_:) Adjunta un Timestamp ya analizado a la firma (C ABI / Swift).
.verify() / .Verify() Comprobación de atributos del firmante según RFC 5652 §5.4 contra el certificado incrustado. Devuelve Valid / Invalid / Unknown.
.verify_detached(pdf_bytes) / .VerifyDetached(pdfBytes) Añade la comprobación messageDigest del RFC 5652 §11.2 contra los bytes del documento proporcionados por el llamador.
.get_certificate() / .GetCertificate() Devuelve un Certificate para una inspección más profunda.

A nivel de documento, verify_all_signatures() / verifyAllSignatures() ejecuta la comprobación del firmante en cada firma y devuelve un único resultado consolidado: 1 = todas válidas, 0 = al menos una inválida, -1 = al menos una desconocida/no soportada.

use pdf_oxide::PdfDocument;

let doc = PdfDocument::open("signed.pdf")?;
for sig in doc.signatures() {
    println!("level={:?} timestamped={}", sig.pades_level(), sig.has_timestamp());
}

Certificate

Certificado X.509 extraído del blob /Contents mediante x509-parser. Disponible en todos los bindings desde v0.3.38 (los accesores de Python / Go / WASM llegaron justo después del lanzamiento inicial).

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

Dss — Document Security Store

El /DSS (ISO 32000-2 §12.8.4.3) contiene el material de validación a largo plazo para firmas PAdES-B-LT: certificados DER a nivel de documento, CRLs, respuestas OCSP y claves VRI por firma. Se lee del documento; None/null indica que el PDF no tiene DSS (no es un error). Disponible en Rust, Python, Go, C# y Swift.

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::signatures::read_dss;

let doc = PdfDocument::open("ltv.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("ltv.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

doc, _ := pdfoxide.Open("ltv.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("ltv.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

if let dss = try doc.dss() {
    print("certs=\(try dss.certCount()) crls=\(try dss.crlCount()) " +
          "ocsps=\(try dss.ocspCount()) vri=\(try dss.vriCount())")
}

C++

try {
    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";
} catch (const pdf_oxide::Error&) {
    // no DSS present
}

Dart

try {
  final dss = doc.getDss();             // throws if the PDF has no /DSS
  print('certs=${dss.certCount} crls=${dss.crlCount} '
        'ocsps=${dss.ocspCount} vri=${dss.vriCount}');
} on PdfOxideError {
  // no DSS present
}

R

dss <- pdf_get_dss(doc)                 # NULL when the PDF has no /DSS
if (!is.null(dss)) {
  cat("certs=", pdf_dss_cert_count(dss), "crls=", pdf_dss_crl_count(dss),
      "ocsps=", pdf_dss_ocsp_count(dss), "vri=", pdf_dss_vri_count(dss), "\n")
}

Julia

dss = document_get_dss(doc)             # nothing when the PDF has no /DSS
if dss !== nothing
    println("certs=", dss_cert_count(dss), " crls=", dss_crl_count(dss),
            " ocsps=", dss_ocsp_count(dss), " vri=", dss_vri_count(dss))
end

Zig

var dss = try doc.dss();                // error if the PDF has no /DSS
std.debug.print("certs={} crls={} ocsps={} vri={}\n", .{
    try dss.certCount(), try dss.crlCount(), try dss.ocspCount(), try dss.vriCount(),
});

Objective-C

POXDss *dss = [doc dssWithError:&err];  // nil 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

case PdfOxide.document_dss(doc) do
  {: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   # the PDF has no /DSS
end
Método C ABI / Swift Equivalente Python / Go / C# Descripción
cert_count() len(dss.certs) / len(dss.Certs) / dss.Certificates.Count Certificados DER a nivel de documento (/Certs)
crl_count() len(dss.crls) / len(dss.CRLs) / dss.Crls.Count CRLs DER a nivel de documento (/CRLs)
ocsp_count() len(dss.ocsps) / len(dss.OCSPs) / dss.OcspResponses.Count Respuestas OCSP DER a nivel de documento (/OCSPs)
vri_count() len(dss.vri) / dss.VRICount / dss.VriCount Entradas /VRI por firma (SHA-1 en hex mayúsculas de /Contents)

Para comprobar la presencia de un /DocTimeStamp de archivo B-LTA (señal a nivel de documento que pades_level no puede reportar), llama a has_document_timestamp(pdf_bytes) (Rust/Python), doc.HasDocumentTimestamp() (Go/C#) o doc.hasTimestamp() (Swift).

Timestamp — RFC 3161 TSTInfo

Analiza el blob de timestamp del atributo TimeStampToken de una firma, o de una respuesta RFC 3161 independiente. Disponible en todos los bindings (soporte para Node añadido tras el lanzamiento de v0.3.38).

Rust

use pdf_oxide::Timestamp;

let ts = Timestamp::parse(&tst_bytes)?;
println!("{} serial={} tsa={}", ts.time(), ts.serial(), ts.tsa_name());

Python

from pdf_oxide import Timestamp

ts = Timestamp.parse(tst_bytes)
print(ts.time, ts.serial, ts.policy_oid, ts.tsa_name, ts.hash_algorithm)

C#

var ts = Timestamp.Parse(tstBytes);
Console.WriteLine($"{ts.Time} serial={ts.Serial} tsa={ts.TsaName}");

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();

Go

ts, _ := pdfoxide.ParseTimestamp(tstBytes)
fmt.Println(ts.Time, ts.Serial, ts.PolicyOid, ts.TsaName, ts.HashAlgorithm)

WASM

import init, { Timestamp } from "pdf-oxide-wasm/web";
await init();

const ts = Timestamp.parse(tstBytes);
console.log(ts.time, ts.serial, ts.policyOid, ts.tsaName, ts.hashAlgorithm);

C++

auto ts = pdf_oxide::Timestamp::parse(tst_bytes);
std::cout << ts.time() << " serial=" << ts.serial() << " tsa=" << ts.tsa_name()
          << " policy=" << ts.policy_oid() << " hash=" << ts.hash_algorithm() << "\n";

Swift

let ts = try Timestamp.parse(tstBytes)
print(try ts.time(), "serial=\(try ts.serial())", "tsa=\(try ts.tsaName())",
      "policy=\(try ts.policyOid())", "hash=\(try ts.hashAlgorithm())")

Dart

final ts = Timestamp.parse(tstBytes);
print('${ts.time} serial=${ts.serial} tsa=${ts.tsaName} '
      'policy=${ts.policyOid} hash=${ts.hashAlgorithm}');

R

ts <- pdf_timestamp_parse(tst_bytes)
cat(pdf_timestamp_time(ts), "serial=", pdf_timestamp_serial(ts),
    "tsa=", pdf_timestamp_tsa_name(ts), "policy=", pdf_timestamp_policy_oid(ts),
    "hash=", pdf_timestamp_hash_algorithm(ts), "\n")

Julia

ts = timestamp_parse(tst_bytes)
println(timestamp_get_time(ts), " serial=", timestamp_get_serial(ts),
        " tsa=", timestamp_get_tsa_name(ts), " policy=", timestamp_get_policy_oid(ts),
        " hash=", timestamp_get_hash_algorithm(ts))

Zig

const a = std.heap.page_allocator;
var ts = try pdf_oxide.Timestamp.parse(tst_bytes);
const serial = try ts.serial(a);
const tsa = try ts.tsaName(a);
std.debug.print("{} serial={s} tsa={s} hash={}\n", .{
    try ts.time(), serial, tsa, try ts.hashAlgorithm(),
});

Objective-C

POXTimestamp *ts = [POXTimestamp parse:tstBytes error:&err];
NSLog(@"%lld serial=%@ tsa=%@ policy=%@ hash=%d",
      [ts timeError:&err], [ts serialError:&err], [ts tsaNameError:&err],
      [ts policyOidError:&err], [ts hashAlgorithmError:&err]);

Elixir

{:ok, ts} = PdfOxide.timestamp_parse(tst_bytes)
{:ok, time}   = PdfOxide.timestamp_time(ts)
{:ok, serial} = PdfOxide.timestamp_serial(ts)
{:ok, tsa}    = PdfOxide.timestamp_tsa_name(ts)
IO.puts("#{time} serial=#{serial} tsa=#{tsa}")
Propiedad Descripción
.time Hora UTC certificada por la TSA
.serial Número de serie único de este timestamp
.policy_oid OID de política de la TSA
.tsa_name Identificador de la TSA
.hash_algorithm Algoritmo de hash utilizado para message_imprint
.message_imprint Hash del contenido firmado
.verify() Verifica la firma TSTInfo contra el certificado TSA incrustado

TsaClient — Cliente HTTP RFC 3161

Solicita un nuevo timestamp a una Autoridad de Sellado de Tiempo mediante HTTP. Requiere la feature Cargo tsa-client. Disponible en todos los bindings excepto WASM (soporte para Node añadido tras v0.3.38; WASM excluido intencionalmente — ureq no compila a wasm32).

Rust

use pdf_oxide::TsaClient;

let client = TsaClient::new("https://freetsa.org/tsr")
    .with_timeout(std::time::Duration::from_secs(30))
    .with_hash_algorithm(pdf_oxide::HashAlgorithm::Sha256)
    .with_nonce(true);
let ts = client.request_timestamp(&pdf_bytes)?;
println!("{} serial={}", ts.time(), ts.serial());

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)

Node / TypeScript

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)

C++

auto client = pdf_oxide::TsaClient::create("https://freetsa.org/tsr");
auto ts = client.request_timestamp(pdf_bytes);
std::cout << ts.time() << " serial=" << ts.serial() << "\n";

Swift

let client = try TsaClient.create(url: "https://freetsa.org/tsr")
let ts = try client.requestTimestamp(pdfBytes)
print(try ts.time(), "serial=\(try ts.serial())")

Dart

final client = TsaClient.create('https://freetsa.org/tsr');
final ts = client.requestTimestamp(pdfBytes);
print('${ts.time} serial=${ts.serial}');

R

client <- pdf_tsa_client_create("https://freetsa.org/tsr")
ts <- pdf_tsa_request_timestamp(client, pdf_bytes)
cat(pdf_timestamp_time(ts), "serial=", pdf_timestamp_serial(ts), "\n")

Julia

client = tsa_client_create("https://freetsa.org/tsr")
ts = tsa_request_timestamp(client, pdf_bytes)
println(timestamp_get_time(ts), " serial=", timestamp_get_serial(ts))

Zig

const a = std.heap.page_allocator;
var client = try pdf_oxide.TsaClient.create("https://freetsa.org/tsr", "", "", 30, 0, true, true);
var ts = try client.requestTimestamp(pdf_bytes);
const serial = try ts.serial(a);
std.debug.print("{} serial={s}\n", .{ try ts.time(), serial });

Objective-C

POXTsaClient *client = [POXTsaClient createWithUrl:@"https://freetsa.org/tsr"
                                          username:nil password:nil
                                           timeout:30 hashAlgo:0
                                          useNonce:YES certReq:YES error:&err];
POXTimestamp *ts = [client requestTimestamp:pdfBytes error:&err];
NSLog(@"%lld serial=%@", [ts timeError:&err], [ts serialError:&err]);

Elixir

{:ok, client} = PdfOxide.tsa_client("https://freetsa.org/tsr")
{:ok, ts} = PdfOxide.tsa_request_timestamp(client, pdf_bytes)
{:ok, time}   = PdfOxide.timestamp_time(ts)
{:ok, serial} = PdfOxide.timestamp_serial(ts)
IO.puts("#{time} serial=#{serial}")

TsaClient envía un TimeStampReq RFC 3161 por HTTP POST con un nonce y autenticación HTTP Basic (cuando se proporcionan username / password). La respuesta se desempaqueta y se analiza mediante Timestamp::parse.

Resumen de cobertura por binding

Paridad completa desde v0.3.38: todas las funcionalidades de firma están disponibles en cada binding, con la única excepción intencionada de TsaClient en WASM (ureq es incompatible con wasm).

Funcionalidad Rust Python Node C# Go WASM
Signature enumerar y verificar
Certificate inspeccionar
Dss (/DSS) inspeccionar
Timestamp analizar y verificar
TsaClient petición HTTP
Firma (sign_pdf_bytes / PAdES)

El en TsaClient × WASM es intencional y permanente: ureq no compila a wasm32. Llama a TsaClient desde un binding del lado del servidor y pasa los bytes brutos del timestamp a Timestamp.parse() en WASM si necesitas inspeccionar la respuesta en el navegador.

Páginas relacionadas