デジタル署名
v0.3.38 で追加。PDF Oxide は /AcroForm /Fields → /Sig から署名を読み取り、/Contents 内の CMS エンベロープを検査し、埋め込み証明書に対する RFC 5652 §5.4 の署名者属性チェック、および呼び出し側が渡したドキュメントバイト列に対する §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 署名者属性チェック。Valid / Invalid / Unknown を返す。 |
.verify_detached(pdf_bytes) / .VerifyDetached(pdfBytes) |
呼び出し側が渡したドキュメントバイト列に対する RFC 5652 §11.2 messageDigest チェックを追加で実行。 |
.get_certificate() / .GetCertificate() |
詳細検査用の Certificate を返す。 |
Certificate
x509-parser を使って CMS /Contents BLOB から取り出した X.509 証明書。C FFI、C#、Node で利用可能です。
| プロパティ/メソッド | 説明 |
|---|---|
.subject |
証明書保持者の識別名(DN) |
.issuer |
発行 CA の DN |
.serial |
シリアル番号(ビッグエンディアンのバイト列または文字列) |
.not_before |
有効期間の開始(DateTimeOffset) |
.not_after |
有効期間の終了 |
.is_valid |
not_before ≤ now ≤ not_after なら真 |
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() |
TSTInfo 署名を埋め込み TSA 証明書に対して検証 |
TsaClient — RFC 3161 HTTP クライアント
HTTP 経由で Time Stamp Authority に新規タイムスタンプを要求します。tsa-client Cargo フィーチャーの背後にあり、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 は nonce 付きの RFC 3161 TimeStampReq を HTTP POST で送信し、username / password が設定されていれば HTTP Basic 認証も添付します。レスポンスは解包されて Timestamp::parse を通ります。
バインディング対応まとめ
| API | Rust | Python | Node | C# | Go | WASM |
|---|---|---|---|---|---|---|
Signature 列挙 + 検証 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Certificate 検査 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Timestamp 解析 + 検証 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
TsaClient HTTP 要求 |
✓ | ✓ | ✓ | ✓ | ✓ | — |
TsaClient × WASM の — は意図的かつ恒久的な判断です。ureq が wasm32 にコンパイルできないためです。サーバー側のバインディングから TsaClient を呼び出して生のタイムスタンプバイトを取得し、ブラウザ側の WASM で Timestamp.parse() に渡してください。
関連ページ
- メタデータ & XMP — ドキュメントレベルのメタデータ抽出
- API リファレンス — Rust 側の
SignatureVerifierAPI 面