数字签名
v0.3.38 新增。PDF Oxide 从 /AcroForm /Fields → /Sig 读取签名,检查 /Contents 中的 CMS 信封,并针对嵌入的证书运行 RFC 5652 §5.4 signer-attributes 检查,以及针对调用方提供的文档字节运行 §11.2 messageDigest 检查。
范围。 本次发布覆盖的是验证。PDF 的签署(而非验证现有签名)属于 issue #208 剩下的一半,尚未随本版本提供。
验证能力
- RSA-PKCS#1 v1.5 搭配 SHA-1 / SHA-256 / SHA-384 / SHA-512(实际上几乎所有已签署 PDF 都使用这种填充方式)— 返回
Valid/Invalid。 - RSA-PSS 与 ECDSA 以
Unknown/UnsupportedFeatureException形式呈现。如有需要,调用方仍可通过Signature.GetCertificate()/.get_certificate()读取嵌入证书,自行执行验证。 - 信任根查找、有效期窗口与签名者 DN 会从嵌入证书中提取并附加到验证结果上。
快速示例
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)
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
枚举并检查签名。支持 Python、WASM、C FFI、C#、Go、Node。
| 属性 / 方法 | 描述 |
|---|---|
.signer_name / .SignerName |
从签名者证书中提取的 Common Name |
.reason / .Reason |
签名原因(例如 “I approve this document”) |
.location / .Location |
地点字段 |
.contact_info |
联系方式字段 |
.signing_time / .SigningTime |
从 CMS 信封提取的 UTC 签名时间(C# 中为 DateTimeOffset?) |
.verify() / .Verify() |
针对嵌入证书运行 RFC 5652 §5.4 signer-attributes 检查,返回 Valid / Invalid / Unknown。 |
.verify_detached(pdf_bytes) / .VerifyDetached(pdfBytes) |
追加 RFC 5652 §11.2 messageDigest 检查,用调用方提供的文档字节。 |
.get_certificate() / .GetCertificate() |
返回可供深入检查的 Certificate。 |
Certificate
通过 x509-parser 从 CMS /Contents 中提取的 X.509 证书。支持 C FFI、C#、Node。
| 属性 / 方法 | 描述 |
|---|---|
.subject |
证书持有者的 DN |
.issuer |
颁发 CA 的 DN |
.serial |
序列号(大端字节或字符串) |
.not_before |
有效期窗口起点(DateTimeOffset) |
.not_after |
有效期窗口终点 |
.is_valid |
若 not_before ≤ now ≤ not_after 则为 true |
Timestamp — RFC 3161 TSTInfo
从签名的 TimeStampToken 属性,或从独立的 RFC 3161 响应中解析时间戳 blob。支持 Python、WASM、C FFI、C#、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();
| 属性 | 描述 |
|---|---|
.time |
TSA 断言的 UTC 时间 |
.serial |
本次时间戳的唯一序列号 |
.policy_oid |
TSA 策略 OID |
.tsa_name |
TSA 标识 |
.hash_algorithm |
用于 message_imprint 的哈希算法 |
.message_imprint |
被签名负载的哈希 |
.verify() |
针对嵌入的 TSA 证书验证 TSTInfo 签名 |
TsaClient — RFC 3161 HTTP 客户端
通过 HTTP 向时间戳授权机构请求新的时间戳。在 tsa-client Cargo feature 后面。支持 Python、C FFI、C#、Go(不在 WASM 中 — ureq 与 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 通过 HTTP POST 发送 RFC 3161 TimeStampReq,包含 nonce 与(当设置 username / password 时的)HTTP Basic 认证。响应会被拆包并通过 Timestamp::parse 解析。
绑定覆盖总览
| 能力 | Rust | Python | Node | C# | Go | WASM |
|---|---|---|---|---|---|---|
Signature 枚举 + 验证 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Certificate 检查 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Timestamp 解析 + 验证 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
TsaClient HTTP 请求 |
✓ | ✓ | ✓ | ✓ | ✓ | — |
TsaClient × WASM 的 — 是刻意且永久的设计:ureq 无法编译到 wasm32。如果你需要在浏览器里查看响应,请从服务端绑定调用 TsaClient,再把原始时间戳字节交给 WASM 端的 Timestamp.parse()。