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 medianteSignature.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
- Firmar un PDF: Firmas Digitales y PAdES — crear firmas CMS y PAdES B-B/B-T/B-LT
- Metadatos y XMP — extracción de metadatos a nivel de documento
- Referencia de la API — interfaz
SignatureVerifieren el lado Rust