Skip to content

Підпис PDF: цифрові підписи та PAdES

Додано у v0.3.50 (issue #235). PDF Oxide підписує PDF, вбудовуючи відокремлений підпис CMS/PKCS#7 у /Contents, заповнює /ByteRange і — для PAdES — додає мітку часу RFC 3161 та сховище безпеки документа (DSS) через інкрементальне оновлення. Ця сторінка охоплює бік створення. Щоб перевірити чи верифікувати наявний підпис, див. Цифрові підписи (вилучення).

Шлюз функцій. Підписання потребує Cargo-фічу signatures. Для PAdES-B-T та B-LT додатково потрібна фіча tsa-client як джерело мітки часу RFC 3161. Якщо привʼязку зібрано без цих фіч, виклики підписання спричиняють помилку «не реалізовано» відповідної привʼязки.

Підтримка у привʼязках

Підписання надається привʼязками, які обгортають родини pdf_sign_bytes* та pdf_certificate_load_* з C ABI: 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 вбудованого сертифіката — див. таблицю Certificate на сторінці вилучення.

Як підписати 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)

Ви також можете побудувати документ і підписати його за один прохід — 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 + непідписаний атрибут signature-time-stamp (RFC 3161) поверх значення підпису.
B-LT 2 B-T + сховище безпеки документа (сертифікати / 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; підписання без неї завершується з відмовою (fails closed) і повертає Unsupported.

Як створити підпис PAdES B-LT?

sign_pdf_bytes_pades приймає рівень, необовʼязковий TSA URL і матеріал відкликання (сертифікати, CRL та відповіді OCSP у кодуванні DER), який стає DSS рівня B-LT.

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.

Як перевірити створене мною сховище безпеки документа?

Після підпису B-LT знову відкрийте PDF і прочитайте його /DSS, щоб переконатися, що матеріал перевірки записано. dss() повертає None/null, коли в документі немає DSS — це не помилка.

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 для кожного підпису (SHA-1 від /Contents у hex верхнього регістру)

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 мс / p99 9 мс при 100% проходженні на еталонному корпусі (бенчмарк v0.3.8). Вартість підписання визначається переважно операцією CMS/RSA плюс — для B-T/B-LT — мережевим зверненням до TSA; гешування діапазону байтів PDF та запис інкрементального оновлення додають незначні накладні витрати поверх цього.

Часті запитання

Чи потрібне мережеве зʼєднання для підписання? Лише для B-T та B-LT (і B-LTA), які вбудовують мітку часу RFC 3161, отриману з TSA URL. Простий sign_pdf_bytes і підпис PAdES B-B повністю працюють офлайн.

Чому B-T/B-LT завершується помилкою без TSA URL? За задумом — ці рівні потребують токен мітки часу, тож підписувач завершується з відмовою і помилкою Unsupported, а не мовчки знижує рівень.

Чи можу я підписувати з Node або браузера (WASM)? Поки що ні. Node та WASM надають читання/перевірку підпису і непідписане поле-заповнювач /Sig, але CMS-підписувач підключено лише в Rust, Python, Go, C# та Swift. Підписуйте на серверній привʼязці й надсилайте підписані байти.

Який алгоритм підпису використовує підписувач? RSA-PKCS#1 v1.5 поверх SHA-256 — доповнення, яке приймає практично кожен PDF-рідер. Політика крипто-провайдера FIPS може спричинити відмову під час підписання, якщо налаштована політика забороняє алгоритм.

Повʼязані сторінки