Assinaturas digitais
Adicionado na v0.3.38. O PDF Oxide lê as assinaturas de /AcroForm /Fields → /Sig, inspeciona o envelope CMS dentro de /Contents e executa o check RFC 5652 §5.4 de atributos do signatário contra o certificado embutido, mais o check §11.2 de messageDigest contra os bytes do documento fornecidos pelo chamador.
Escopo. Esta release cobre verificação. A assinatura de PDFs (em oposição à verificação de assinaturas existentes) é rastreada como a outra metade da issue #208 e ainda não foi entregue.
O que é verificado
- RSA-PKCS#1 v1.5 sobre SHA-1 / SHA-256 / SHA-384 / SHA-512 — o padding usado por praticamente todos os PDFs assinados em produção — retorna
Valid/Invalid. - RSA-PSS e ECDSA aparecem como
Unknown/UnsupportedFeatureException. Quem precisar desses casos ainda pode ler o certificado embutido viaSignature.GetCertificate()/.get_certificate()e rodar a própria verificação. - A busca de raiz de confiança, a janela de validade e o DN do signatário são carimbados no resultado da verificação a partir do certificado embutido.
Exemplo 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 (somente 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
Enumere e inspecione assinaturas. Disponível em Python, WASM, C FFI, C#, Go e Node.
| Propriedade / método | Descrição |
|---|---|
.signer_name / .SignerName |
Common Name do certificado do signatário |
.reason / .Reason |
Motivo da assinatura (ex.: “Eu aprovo este documento”) |
.location / .Location |
Campo de localização |
.contact_info |
Campo de informações de contato |
.signing_time / .SigningTime |
Hora UTC da assinatura a partir do envelope CMS (DateTimeOffset? em C#) |
.verify() / .Verify() |
Check RFC 5652 §5.4 de atributos do signatário contra o certificado embutido. Retorna Valid / Invalid / Unknown. |
.verify_detached(pdf_bytes) / .VerifyDetached(pdfBytes) |
Acrescenta o check RFC 5652 §11.2 de messageDigest contra os bytes do documento fornecidos pelo chamador. |
.get_certificate() / .GetCertificate() |
Retorna um Certificate para inspeção aprofundada. |
Certificate
Certificado X.509 extraído do blob CMS em /Contents via x509-parser. Disponível em C FFI, C# e Node.
| Propriedade / método | Descrição |
|---|---|
.subject |
Distinguished Name do titular do certificado |
.issuer |
DN da CA emissora |
.serial |
Número serial (como bytes big-endian ou string) |
.not_before |
Início da janela de validade (DateTimeOffset) |
.not_after |
Fim da janela de validade |
.is_valid |
true se not_before ≤ now ≤ not_after |
Timestamp — RFC 3161 TSTInfo
Faça parse do blob de timestamp a partir do atributo TimeStampToken da assinatura, ou de uma resposta RFC 3161 autônoma. Disponível em Python, WASM, C FFI, C# e 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();
| Propriedade | Descrição |
|---|---|
.time |
Hora UTC atestada pela TSA |
.serial |
Serial único deste timestamp |
.policy_oid |
OID da política da TSA |
.tsa_name |
Identificador da TSA |
.hash_algorithm |
Algoritmo de hash usado em message_imprint |
.message_imprint |
Hash do payload assinado |
.verify() |
Verifica a assinatura do TSTInfo contra o certificado embutido da TSA |
TsaClient — cliente HTTP RFC 3161
Solicite um timestamp novo a uma Time Stamp Authority via HTTP. Fica atrás da feature Cargo tsa-client. Disponível em Python, C FFI, C# e Go (não WASM — ureq é incompatível com 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)
O TsaClient envia um TimeStampReq RFC 3161 via HTTP POST com nonce e HTTP Basic auth (quando username / password estão definidos). A resposta é desembrulhada e analisada por Timestamp::parse.
Resumo da cobertura entre bindings
| Superfície | Rust | Python | Node | C# | Go | WASM |
|---|---|---|---|---|---|---|
Signature enumerar + verificar |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Inspeção de Certificate |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Timestamp parse + verificar |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Requisição HTTP via TsaClient |
✓ | ✓ | ✓ | ✓ | ✓ | — |
O — em TsaClient × WASM é intencional e permanente: ureq não compila para wasm32. Se precisar inspecionar a resposta no navegador, chame o TsaClient a partir de um binding server-side e passe os bytes brutos do timestamp para Timestamp.parse() no WASM.
Páginas relacionadas
- Metadados e XMP — extração de metadados no nível do documento
- Referência da API — superfície
SignatureVerifierem Rust