PDFに署名する: デジタル署名とPAdES
v0.3.50(issue #235)で追加されました。PDF OxideはCMS/PKCS#7のデタッチ署名を/Contentsに埋め込み、/ByteRangeを埋めてPDFに署名します。PAdESの場合はさらに、増分更新によってRFC 3161タイムスタンプとDocument Security Store(DSS)を追加します。このページでは作成側を扱います。既存の署名を検査または検証するには、デジタル署名(抽出)を参照してください。
機能ゲート。 署名には
signaturesCargoフィーチャーが必要です。PAdES-B-TとB-LTには、RFC 3161タイムスタンプソースのために追加でtsa-clientフィーチャーが必要です。これらのフィーチャーなしでバインディングがビルドされている場合、署名呼び出しはそのバインディングの「未実装」エラーを発生させます。
バインディングの対応状況
署名は、C ABIのpdf_sign_bytes*およびpdf_certificate_load_*系をラップするバインディングで公開されています: Rust、Python、Go(CGo)、C#、Swift。NodeとWASMは読み取り/検証側のみを公開します — ページビルダーには未署名の/Sigプレースホルダーフィールドを提供しますが、CMS署名者は提供しません。署名済みバイト列を生成するには、サーバーサイドのバインディングを呼び出してください。
| 機能 | Rust | Python | Go | C# | Swift | Node | WASM |
|---|---|---|---|---|---|---|---|
| 資格情報の読み込み(PEM / PKCS#12) | ✓ | ✓ | ✓ | ✓ | ✓ | — | — |
sign_pdf_bytes(CMSデタッチ) |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
sign_pdf_bytes_pades(B-B / B-T / B-LT) |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
DSS(/DSS)カウントの読み取り |
✓ | ✓ | ✓ | ✓ | ✓ | — | — |
—は意図的なものです: Node/WASMにはCMS署名者が組み込まれていません。
署名用の資格情報はどのように読み込みますか?
署名者には証明書とそれに対応する秘密鍵が必要です。PKCS#12(.p12 / .pfx)ブロブから、または別々のPEM文字列から読み込めます。
SigningCredentials::from_pkcs12(data: &[u8], password: &str)/from_pem(cert_pem: &str, key_pem: &str)(Rust)Certificate.load_pkcs12(data: bytes, password: str)/Certificate.load_pem(cert_pem: str, key_pem: str)(Python)LoadCertificate(data []byte, password string)/LoadCertificateFromPem(certPem, keyPem string)(Go)Certificate.Load(byte[] data, string? password)/Certificate.LoadFromPem(string certPem, string keyPem)(C#)Certificate.loadFromBytes(_ bytes: [UInt8], password: String)/Certificate.loadFromPem(certPem:keyPem:)(Swift)
読み込むと、同じハンドルから埋め込まれた証明書のsubject、issuer、serial、validity、is_validにアクセスできます — 抽出ページの証明書テーブルを参照してください。
PDFにはどう署名しますか(CMS/PKCS#7)?
sign_pdf_bytesはレガシーなadbe.pkcs7.detached署名を生成します: ドキュメントのバイト範囲に対するCMSエンベロープで、オプションの/Reasonと/Locationエントリが署名辞書に書き込まれます。
Rust
use pdf_oxide::signatures::{sign_pdf_bytes, SignOptions, SigningCredentials};
let creds = SigningCredentials::from_pkcs12(&std::fs::read("id.p12")?, "testpass")?;
println!("signing as {}", creds.subject()?);
let pdf = std::fs::read("invoice.pdf")?;
let opts = SignOptions {
reason: Some("Approved".into()),
location: Some("HQ".into()),
..Default::default()
};
let signed = sign_pdf_bytes(&pdf, &creds, opts)?;
std::fs::write("invoice-signed.pdf", &signed)?;
assert!(signed.windows(10).any(|w| w == b"/ByteRange"));
Python
import pdf_oxide
with open("id.p12", "rb") as f:
cert = pdf_oxide.Certificate.load_pkcs12(f.read(), "testpass")
print("signing as", cert.subject)
with open("invoice.pdf", "rb") as f:
pdf_bytes = f.read()
signed = pdf_oxide.sign_pdf_bytes(pdf_bytes, cert, reason="Approved", location="HQ")
with open("invoice-signed.pdf", "wb") as f:
f.write(signed)
assert b"/ByteRange" in signed
Go(CGoのみ)
import (
"fmt"
"os"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
p12, _ := os.ReadFile("id.p12")
cert, err := pdfoxide.LoadCertificate(p12, "testpass")
if err != nil {
panic(err)
}
defer cert.Close()
pdf, _ := os.ReadFile("invoice.pdf")
signed, err := pdfoxide.SignPdfBytes(pdf, cert, "Approved", "HQ")
if err != nil {
panic(err)
}
os.WriteFile("invoice-signed.pdf", signed, 0o644)
fmt.Println("signed", len(signed), "bytes")
C#
using PdfOxide;
var cert = Certificate.Load(File.ReadAllBytes("id.p12"), "testpass");
Console.WriteLine($"signing as {cert.Subject}");
byte[] pdf = File.ReadAllBytes("invoice.pdf");
byte[] signed = cert.SignPdfBytes(pdf, reason: "Approved", location: "HQ");
File.WriteAllBytes("invoice-signed.pdf", signed);
Swift
import PdfOxide
let cert = try Certificate.loadFromBytes(Array(try Data(contentsOf: URL(fileURLWithPath: "id.p12"))), password: "testpass")
let pdf = Array(try Data(contentsOf: URL(fileURLWithPath: "invoice.pdf")))
let signed = try signBytes(pdf, certificate: cert, reason: "Approved", location: "HQ")
try Data(signed).write(to: URL(fileURLWithPath: "invoice-signed.pdf"))
Java
import fyi.oxide.pdf.PdfSigner;
import fyi.oxide.pdf.signature.SignOptions;
import fyi.oxide.pdf.signature.SignatureLevel;
import java.nio.file.*;
PdfSigner signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass");
byte[] pdf = Files.readAllBytes(Path.of("invoice.pdf"));
byte[] signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_B) // plain CMS / PKCS#7
.withReason("Approved")
.withLocation("HQ")
.build());
Files.write(Path.of("invoice-signed.pdf"), signed);
PHP
use PdfOxide\PdfSigner;
$signer = PdfSigner::fromPkcs12('id.p12', 'testpass');
$pdf = file_get_contents('invoice.pdf');
$signed = $signer->sign(
$pdf,
level: PdfSigner::LEVEL_B_B, // plain CMS / PKCS#7
reason: 'Approved',
location: 'HQ',
);
file_put_contents('invoice-signed.pdf', $signed);
$signer->close();
Ruby
require 'pdf_oxide'
# certificate_handle comes from your PKCS#12 / PEM credentials API
signer = PdfOxide::PdfSigner.new(certificate_handle)
pdf = File.binread('invoice.pdf')
signed = signer.sign(pdf, level: :b, reason: 'Approved', location: 'HQ')
File.binwrite('invoice-signed.pdf', signed)
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto cert = pdf_oxide::Certificate::load_from_bytes(read_bytes("id.p12"), "testpass");
auto pdf = read_bytes("invoice.pdf");
auto signed = pdf_oxide::sign_bytes(pdf, cert, "Approved", "HQ");
write_bytes("invoice-signed.pdf", signed);
Kotlin
import fyi.oxide.pdf.PdfSigner
import fyi.oxide.pdf.signature.SignOptions
import fyi.oxide.pdf.signature.SignatureLevel
import java.nio.file.*
val signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass")
val pdf = Files.readAllBytes(Path.of("invoice.pdf"))
val signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_B) // plain CMS / PKCS#7
.withReason("Approved")
.withLocation("HQ")
.build())
Files.write(Path.of("invoice-signed.pdf"), signed)
Dart
import 'dart:io';
import 'package:pdf_oxide/pdf_oxide.dart';
final cert = Certificate.loadFromBytes(File('id.p12').readAsBytesSync(), 'testpass');
final pdf = File('invoice.pdf').readAsBytesSync();
final signed = signBytes(pdf, cert, reason: 'Approved', location: 'HQ');
File('invoice-signed.pdf').writeAsBytesSync(signed);
R
library(pdfoxide)
cert <- pdf_certificate_load_from_bytes(readBin("id.p12", "raw", file.size("id.p12")), "testpass")
pdf <- readBin("invoice.pdf", "raw", file.size("invoice.pdf"))
signed <- pdf_sign_bytes(pdf, cert, reason = "Approved", location = "HQ")
writeBin(signed, "invoice-signed.pdf")
Julia
using PdfOxide
cert = certificate_load_from_bytes(read("id.p12"), "testpass")
pdf = read("invoice.pdf")
signed = sign_bytes(pdf, cert, "Approved", "HQ")
write("invoice-signed.pdf", signed)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
const cert = try pdf_oxide.Certificate.loadFromBytes(p12_bytes, "testpass");
const signed = try pdf_oxide.signBytes(a, pdf_bytes, cert, "Approved", "HQ");
defer a.free(signed);
try std.fs.cwd().writeFile(.{ .sub_path = "invoice-signed.pdf", .data = signed });
Scala
import fyi.oxide.pdf.PdfSigner
import fyi.oxide.pdf.signature.{SignOptions, SignatureLevel}
import java.nio.file.*
val signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass")
val pdf = Files.readAllBytes(Path.of("invoice.pdf"))
val signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_B) // plain CMS / PKCS#7
.withReason("Approved")
.withLocation("HQ")
.build())
Files.write(Path.of("invoice-signed.pdf"), signed)
Clojure
(import '[fyi.oxide.pdf PdfSigner]
'[fyi.oxide.pdf.signature SignOptions SignatureLevel])
(require '[clojure.java.io :as io])
(let [signer (PdfSigner/fromPkcs12 (java.nio.file.Path/of "id.p12" (make-array String 0)) "testpass")
pdf (java.nio.file.Files/readAllBytes (java.nio.file.Path/of "invoice.pdf" (make-array String 0)))
opts (-> (SignOptions/builder)
(.withLevel SignatureLevel/B_B) ; plain CMS / PKCS#7
(.withReason "Approved")
(.withLocation "HQ")
(.build))
signed (.sign signer pdf opts)]
(java.nio.file.Files/write (java.nio.file.Path/of "invoice-signed.pdf" (make-array String 0)) signed
(make-array java.nio.file.OpenOption 0)))
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXCertificate *cert = [POXCertificate loadFromBytes:p12Data password:@"testpass" error:&err];
NSData *pdf = [NSData dataWithContentsOfFile:@"invoice.pdf"];
NSData *signed = [POXSigning signBytes:pdf certificate:cert reason:@"Approved" location:@"HQ" error:&err];
[signed writeToFile:@"invoice-signed.pdf" atomically:YES];
Elixir
{:ok, cert} = PdfOxide.certificate_from_bytes(File.read!("id.p12"), "testpass")
pdf = File.read!("invoice.pdf")
{:ok, signed} = PdfOxide.sign_bytes(pdf, cert, "Approved", "HQ")
File.write!("invoice-signed.pdf", signed)
ドキュメントをビルドして1回のパスで署名することもできます — DocumentBuilder().….build()は未署名のバイト列を返し、それをそのままsign_pdf_bytesに渡せます。DocumentBuilderを参照してください。
PAdESベースラインレベルとは何ですか?
PAdES(PDF Advanced Electronic Signatures、ETSI EN 319 142)は、CMS署名の上に長期検証のレイヤーを重ねます。PDF OxideはETSI.CAdES.detached署名者を提供し、そのレベルマッピングはC ABIとすべてのバインディングで共有される固定されたものです:
| レベル | コード | 追加されるもの |
|---|---|---|
B-B |
0 | CAdESベースライン: ESS signing-certificate-v2(RFC 5035)を含む署名属性。 |
B-T |
1 | B-B + 署名値に対するRFC 3161のsignature-time-stamp未署名属性。 |
B-LT |
2 | B-T + Document Security Store(証明書 / CRL / OCSP + 署名ごとのVRI)。 |
B-LTA |
3 | B-LT + ドキュメントの/DocTimeStamp。署名用には予約済み — レベル3を署名者に渡すとUnsupportedが返されます。既存のB-LTAファイルは、下記のドキュメントタイムスタンプリーダーシグナルで検出します。 |
B-T、B-LT、B-LTAにはタイムスタンパーが必要です。B-TとB-LTではTSA URLを指定し、バインディングがRFC 3161トークンを取得します。タイムスタンパーなしの署名はUnsupportedでフェイルクローズします。
PAdES B-LT署名はどう作成しますか?
sign_pdf_bytes_padesは、レベル、オプションのTSA URL、そしてB-LTのDSSになる失効情報(DERエンコードされた証明書、CRL、OCSPレスポンス)を受け取ります。
Rust
use pdf_oxide::signatures::{
sign_pdf_bytes_pades, PadesLevel, RevocationMaterial, SignOptions, SigningCredentials,
};
use pdf_oxide::signatures::{TsaClient, TsaClientConfig};
let creds = SigningCredentials::from_pkcs12(&std::fs::read("id.p12")?, "testpass")?;
let pdf = std::fs::read("invoice.pdf")?;
// RFC 3161 token source for B-T / B-LT.
let tsa = TsaClient::new(TsaClientConfig::new("https://freetsa.org/tsr".into()));
let timestamper = |sig: &[u8]| tsa.request_timestamp(sig).map(|t| t.token_bytes().to_vec());
let material = RevocationMaterial {
certificates: vec![std::fs::read("ca.der")?],
..Default::default()
};
let signed = sign_pdf_bytes_pades(
&pdf,
&creds,
SignOptions { reason: Some("Approved".into()), ..Default::default() },
PadesLevel::BLt,
Some(×tamper),
&material,
)?;
std::fs::write("invoice-pades.pdf", &signed)?;
Python
import pdf_oxide
from pdf_oxide import PadesLevel, RevocationMaterial
with open("id.p12", "rb") as f:
cert = pdf_oxide.Certificate.load_pkcs12(f.read(), "testpass")
with open("invoice.pdf", "rb") as f:
pdf_bytes = f.read()
with open("ca.der", "rb") as f:
ca_der = f.read()
signed = pdf_oxide.sign_pdf_bytes_pades(
pdf_bytes,
cert,
PadesLevel.B_LT,
tsa_url="https://freetsa.org/tsr",
reason="Approved",
revocation=RevocationMaterial(certs=[ca_der]),
)
with open("invoice-pades.pdf", "wb") as f:
f.write(signed)
Go(CGoのみ)
ca, _ := os.ReadFile("ca.der")
opts := pdfoxide.PAdESOptions{
Level: pdfoxide.PAdESBLt,
TSAURL: "https://freetsa.org/tsr",
Reason: "Approved",
Revocation: &pdfoxide.RevocationMaterial{Certs: [][]byte{ca}},
}
signed, err := pdfoxide.SignPdfBytesPAdES(pdf, cert, opts)
if err != nil {
panic(err)
}
os.WriteFile("invoice-pades.pdf", signed, 0o644)
C#
var material = new RevocationMaterial();
material.Certificates.Add(File.ReadAllBytes("ca.der"));
byte[] signed = cert.SignPdfBytesPades(pdf, new PadesSignOptions
{
Level = PadesLevel.BLt,
TsaUrl = "https://freetsa.org/tsr",
Reason = "Approved",
Revocation = material,
});
File.WriteAllBytes("invoice-pades.pdf", signed);
Swift
let ca = Array(try Data(contentsOf: URL(fileURLWithPath: "ca.der")))
let signed = try signBytesPades(
pdf,
certificate: cert,
level: 2, // 0=B-B 1=B-T 2=B-LT
tsaUrl: "https://freetsa.org/tsr",
reason: "Approved",
certs: [ca]
)
try Data(signed).write(to: URL(fileURLWithPath: "invoice-pades.pdf"))
Java
import fyi.oxide.pdf.PdfSigner;
import fyi.oxide.pdf.signature.SignOptions;
import fyi.oxide.pdf.signature.SignatureLevel;
import java.nio.file.*;
PdfSigner signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass");
byte[] pdf = Files.readAllBytes(Path.of("invoice.pdf"));
byte[] signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_LT) // B-T / B-LT require a TSA
.withTsaUrl("https://freetsa.org/tsr")
.withReason("Approved")
.build());
Files.write(Path.of("invoice-pades.pdf"), signed);
PHP
use PdfOxide\PdfSigner;
$signer = PdfSigner::fromPkcs12('id.p12', 'testpass');
$pdf = file_get_contents('invoice.pdf');
$signed = $signer->sign(
$pdf,
level: PdfSigner::LEVEL_B_LT, // B-T / B-LT require a TSA
tsaUrl: 'https://freetsa.org/tsr',
reason: 'Approved',
);
file_put_contents('invoice-pades.pdf', $signed);
$signer->close();
Ruby
require 'pdf_oxide'
signer = PdfOxide::PdfSigner.new(certificate_handle)
pdf = File.binread('invoice.pdf')
signed = signer.sign(
pdf,
level: :lt, # B-T / B-LT require a TSA
tsa_url: 'https://freetsa.org/tsr',
reason: 'Approved',
)
File.binwrite('invoice-pades.pdf', signed)
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto cert = pdf_oxide::Certificate::load_from_bytes(read_bytes("id.p12"), "testpass");
auto pdf = read_bytes("invoice.pdf");
pdf_oxide::RevocationMaterial material;
material.certs.push_back(read_bytes("ca.der"));
auto signed = pdf_oxide::sign_bytes_pades(
pdf, cert,
/*level=*/2, // 0=B-B 1=B-T 2=B-LT
"https://freetsa.org/tsr",
"Approved", /*location=*/"",
material);
write_bytes("invoice-pades.pdf", signed);
Kotlin
import fyi.oxide.pdf.PdfSigner
import fyi.oxide.pdf.signature.SignOptions
import fyi.oxide.pdf.signature.SignatureLevel
import java.nio.file.*
val signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass")
val pdf = Files.readAllBytes(Path.of("invoice.pdf"))
val signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_LT) // B-T / B-LT require a TSA
.withTsaUrl("https://freetsa.org/tsr")
.withReason("Approved")
.build())
Files.write(Path.of("invoice-pades.pdf"), signed)
Dart
import 'dart:io';
import 'package:pdf_oxide/pdf_oxide.dart';
final cert = Certificate.loadFromBytes(File('id.p12').readAsBytesSync(), 'testpass');
final pdf = File('invoice.pdf').readAsBytesSync();
final ca = File('ca.der').readAsBytesSync();
final signed = signBytesPades(
pdf, cert,
2, // 0=B-B 1=B-T 2=B-LT
tsaUrl: 'https://freetsa.org/tsr',
reason: 'Approved',
certs: [ca],
);
File('invoice-pades.pdf').writeAsBytesSync(signed);
R
library(pdfoxide)
cert <- pdf_certificate_load_from_bytes(readBin("id.p12", "raw", file.size("id.p12")), "testpass")
pdf <- readBin("invoice.pdf", "raw", file.size("invoice.pdf"))
ca <- readBin("ca.der", "raw", file.size("ca.der"))
signed <- pdf_sign_bytes_pades(
pdf, cert,
level = 2L, # 0=B-B 1=B-T 2=B-LT
tsa_url = "https://freetsa.org/tsr",
reason = "Approved",
certs = list(ca)
)
writeBin(signed, "invoice-pades.pdf")
Julia
using PdfOxide
cert = certificate_load_from_bytes(read("id.p12"), "testpass")
pdf = read("invoice.pdf")
ca = read("ca.der")
signed = sign_bytes_pades(
pdf, cert,
2, # 0=B-B 1=B-T 2=B-LT
"https://freetsa.org/tsr",
"Approved", "";
certs = [ca],
)
write("invoice-pades.pdf", signed)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
const cert = try pdf_oxide.Certificate.loadFromBytes(p12_bytes, "testpass");
const signed = try pdf_oxide.signBytesPades(
a, pdf_bytes, cert,
2, // 0=B-B 1=B-T 2=B-LT
"https://freetsa.org/tsr",
"Approved", "",
&[_][]const u8{ca_der}, &.{}, &.{},
);
defer a.free(signed);
try std.fs.cwd().writeFile(.{ .sub_path = "invoice-pades.pdf", .data = signed });
Scala
import fyi.oxide.pdf.PdfSigner
import fyi.oxide.pdf.signature.{SignOptions, SignatureLevel}
import java.nio.file.*
val signer = PdfSigner.fromPkcs12(Path.of("id.p12"), "testpass")
val pdf = Files.readAllBytes(Path.of("invoice.pdf"))
val signed = signer.sign(pdf, SignOptions.builder()
.withLevel(SignatureLevel.B_LT) // B-T / B-LT require a TSA
.withTsaUrl("https://freetsa.org/tsr")
.withReason("Approved")
.build())
Files.write(Path.of("invoice-pades.pdf"), signed)
Clojure
(import '[fyi.oxide.pdf PdfSigner]
'[fyi.oxide.pdf.signature SignOptions SignatureLevel])
(let [signer (PdfSigner/fromPkcs12 (java.nio.file.Path/of "id.p12" (make-array String 0)) "testpass")
pdf (java.nio.file.Files/readAllBytes (java.nio.file.Path/of "invoice.pdf" (make-array String 0)))
opts (-> (SignOptions/builder)
(.withLevel SignatureLevel/B_LT) ; B-T / B-LT require a TSA
(.withTsaUrl "https://freetsa.org/tsr")
(.withReason "Approved")
(.build))
signed (.sign signer pdf opts)]
(java.nio.file.Files/write (java.nio.file.Path/of "invoice-pades.pdf" (make-array String 0)) signed
(make-array java.nio.file.OpenOption 0)))
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXCertificate *cert = [POXCertificate loadFromBytes:p12Data password:@"testpass" error:&err];
NSData *pdf = [NSData dataWithContentsOfFile:@"invoice.pdf"];
NSData *ca = [NSData dataWithContentsOfFile:@"ca.der"];
NSData *signed = [POXSigning signBytesPades:pdf
certificate:cert
level:2 // 0=B-B 1=B-T 2=B-LT
tsaUrl:@"https://freetsa.org/tsr"
reason:@"Approved"
location:nil
certs:@[ca]
crls:@[]
ocsps:@[]
error:&err];
[signed writeToFile:@"invoice-pades.pdf" atomically:YES];
Elixir
{:ok, cert} = PdfOxide.certificate_from_bytes(File.read!("id.p12"), "testpass")
pdf = File.read!("invoice.pdf")
ca = File.read!("ca.der")
{:ok, signed} =
PdfOxide.sign_bytes_pades(pdf, cert, 2, "https://freetsa.org/tsr", # 0=B-B 1=B-T 2=B-LT
reason: "Approved",
certs: [ca]
)
File.write!("invoice-pades.pdf", signed)
タイムスタンプなしの簡易なB-B署名を作成するには、レベル0 / PadesLevel.B_Bを渡し、TSA URLを省略してください。
生成したDocument Security Storeはどう検査しますか?
B-LT署名の後、PDFを再度開いてその/DSSを読み、検証情報が格納されたことを確認します。ドキュメントにDSSがない場合、dss()はNone/nullを返します — これはエラーではありません。
Rust
use pdf_oxide::PdfDocument;
use pdf_oxide::signatures::read_dss;
let doc = PdfDocument::open("invoice-pades.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("invoice-pades.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(CGoのみ)
doc, _ := pdfoxide.Open("invoice-pades.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("invoice-pades.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
let doc = try Document.open("invoice-pades.pdf")
if let dss = try doc.dss() {
print("certs=\(try dss.certCount()) crls=\(try dss.crlCount()) " +
"ocsps=\(try dss.ocspCount()) vri=\(try dss.vriCount())")
}
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("invoice-pades.pdf");
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";
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('invoice-pades.pdf');
final dss = doc.getDss(); // throws if the PDF has no /DSS
print('certs=${dss.certCount} crls=${dss.crlCount} '
'ocsps=${dss.ocspCount} vri=${dss.vriCount}');
R
library(pdfoxide)
doc <- pdf_open("invoice-pades.pdf")
dss <- pdf_get_dss(doc) # errors if the PDF has no /DSS
cat(sprintf("certs=%d crls=%d ocsps=%d vri=%d\n",
pdf_dss_cert_count(dss), pdf_dss_crl_count(dss),
pdf_dss_ocsp_count(dss), pdf_dss_vri_count(dss)))
Julia
using PdfOxide
doc = open_document("invoice-pades.pdf")
dss = document_get_dss(doc) # errors if the PDF has no /DSS
println("certs=$(dss_cert_count(dss)) crls=$(dss_crl_count(dss)) ",
"ocsps=$(dss_ocsp_count(dss)) vri=$(dss_vri_count(dss))")
Zig
const pdf_oxide = @import("pdf_oxide");
var doc = try pdf_oxide.Document.open("invoice-pades.pdf");
const dss = try doc.dss(); // errors if the PDF has no /DSS
std.debug.print("certs={d} crls={d} ocsps={d} vri={d}\n", .{
try dss.certCount(), try dss.crlCount(), try dss.ocspCount(), try dss.vriCount(),
});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"invoice-pades.pdf" error:&err];
POXDss *dss = [doc dssWithError:&err]; // nil + error 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
{:ok, doc} = PdfOxide.open("invoice-pades.pdf")
case PdfOxide.document_dss(doc) do # {:error, _} when the PDF has no /DSS
{: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
end
DSSアクセサ
| 機能(C ABI / Swift) | Python / Go / C# 相当 | 説明 |
|---|---|---|
dss.cert_count() |
len(dss.certs) / len(dss.Certs) / dss.Certificates.Count |
ドキュメントレベルのDER証明書(/Certs) |
dss.crl_count() |
len(dss.crls) / len(dss.CRLs) / dss.Crls.Count |
ドキュメントレベルのDER CRL(/CRLs) |
dss.ocsp_count() |
len(dss.ocsps) / len(dss.OCSPs) / dss.OcspResponses.Count |
ドキュメントレベルのDER OCSPレスポンス(/OCSPs) |
dss.vri_count() |
len(dss.vri) / dss.VRICount / dss.VriCount |
署名ごとの/VRIエントリ(/Contentsの大文字16進SHA-1) |
C ABI / Swiftはカウントメソッドに加えてインデックスごとのゲッター(pdf_dss_get_certなど)を公開します。PythonとGoはDERブロブをリストとして直接返します。
B-LTAアーカイブタイムスタンプはどう検出しますか?
Signature.pades_levelは署名スコープであり、設計上B-LTで上限に達します。B-LTAはドキュメントレベルの/DocTimeStampを追加します。これはドキュメント全体のシグナルなので、ドキュメントタイムスタンプチェックで読み取ります:
import pdf_oxide
with open("archive.pdf", "rb") as f:
is_lta = pdf_oxide.has_document_timestamp(f.read()) # -> bool
- Rust:
pdf_oxide::signatures::has_document_timestamp(pdf_data: &[u8]) -> bool - Go:
doc.HasDocumentTimestamp() (bool, error) - C#:
doc.HasDocumentTimestamp() -> bool - Swift:
try doc.hasTimestamp() -> Bool
パフォーマンス
PDF Oxideの抽出コアは、リファレンスコーパスにおいて**平均0.8 ms / p99 9 ms、パス率100%**で動作します(v0.3.8ベンチマーク)。署名コストはCMS/RSA演算が支配的で、B-T/B-LTの場合はそれに加えてTSAへのネットワークラウンドトリップが加わります — PDFバイト範囲のハッシュ計算と増分更新の書き込みが追加するオーバーヘッドはごくわずかです。
FAQ
署名するのにネットワーク接続は必要ですか?
TSA URLから取得するRFC 3161タイムスタンプを埋め込むB-TとB-LT(およびB-LTA)の場合のみ必要です。プレーンなsign_pdf_bytesとPAdES B-B署名は完全にオフラインです。
TSA URLなしでB-T/B-LTが失敗するのはなぜですか?
設計によるものです — これらのレベルはタイムスタンプトークンを必要とするため、署名者はレベルを暗黙的にダウングレードするのではなく、Unsupportedエラーでフェイルクローズします。
Nodeやブラウザ(WASM)から署名できますか?
まだできません。NodeとWASMは署名の読み取り/検証と未署名の/Sigプレースホルダーフィールドを公開しますが、CMS署名者はRust、Python、Go、C#、Swiftにのみ組み込まれています。サーバーサイドのバインディングで署名し、署名済みバイト列を配信してください。
署名者はどの署名アルゴリズムを使いますか? SHA-256上のRSA-PKCS#1 v1.5で、これは実質的にあらゆるPDFリーダーが受け入れるパディングです。設定されたポリシーがアルゴリズムを禁止している場合、FIPS暗号プロバイダーポリシーによって署名がフェイルクローズすることがあります。
関連ページ
- デジタル署名(抽出) — 既存の署名の読み取りと検証、RFC 3161タイムスタンプの解析、TSAタイムスタンプの要求
- DocumentBuilder Fluent API — 署名したいPDFをビルドする
- APIリファレンス — Rust側の
signaturesモジュールの機能