Skip to content

PDFに署名する: デジタル署名とPAdES

v0.3.50(issue #235)で追加されました。PDF OxideはCMS/PKCS#7のデタッチ署名を/Contentsに埋め込み、/ByteRangeを埋めてPDFに署名します。PAdESの場合はさらに、増分更新によってRFC 3161タイムスタンプとDocument Security Store(DSS)を追加します。このページでは作成側を扱います。既存の署名を検査または検証するには、デジタル署名(抽出)を参照してください。

機能ゲート。 署名にはsignatures Cargoフィーチャーが必要です。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)

読み込むと、同じハンドルから埋め込まれた証明書のsubjectissuerserialvalidityis_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-TB-LTB-LTAにはタイムスタンパーが必要です。B-TB-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(&timestamper),
    &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暗号プロバイダーポリシーによって署名がフェイルクローズすることがあります。

関連ページ