Skip to content

Assine um PDF: assinaturas digitais e PAdES

Adicionado na v0.3.50 (issue #235). O PDF Oxide assina PDFs com uma assinatura CMS/PKCS#7 destacada embutida em /Contents, preenche /ByteRange e — para PAdES — acrescenta um carimbo de tempo RFC 3161 e um Document Security Store (DSS) por meio de atualização incremental. Esta página cobre o lado da criação. Para inspecionar ou verificar uma assinatura existente, veja Assinaturas digitais (extração).

Feature gate. A assinatura requer a feature signatures do Cargo. PAdES-B-T e B-LT precisam adicionalmente da feature tsa-client para a fonte do carimbo de tempo RFC 3161. Quando uma binding é compilada sem essas features, as chamadas de assinatura lançam o erro de “não implementado” da própria binding.

Cobertura das bindings

A assinatura é exposta pelas bindings que envolvem as famílias pdf_sign_bytes* e pdf_certificate_load_* da C ABI: Rust, Python, Go (CGo), C# e Swift. Node e WASM expõem apenas o lado de leitura/verificação — eles fornecem o campo placeholder /Sig não assinado no construtor de páginas, mas não o assinador CMS. Chame uma binding do lado do servidor para produzir os bytes assinados.

Recurso Rust Python Go C# Swift Node WASM
Carregar credenciais (PEM / PKCS#12)
sign_pdf_bytes (CMS destacada)
sign_pdf_bytes_pades (B-B / B-T / B-LT)
Ler contagens do DSS (/DSS)

é intencional: Node/WASM não têm o assinador CMS conectado.

Como carrego as credenciais de assinatura?

Um assinador precisa de um certificado e da chave privada correspondente. Carregue a partir de um blob PKCS#12 (.p12 / .pfx) ou de strings PEM separadas.

  • 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)

Depois de carregado, o mesmo handle expõe os campos subject, issuer, serial, validity e is_valid do certificado embutido — veja a tabela Certificate na página de extração.

Como assino um PDF (CMS/PKCS#7)?

sign_pdf_bytes produz uma assinatura adbe.pkcs7.detached legada: um envelope CMS sobre o intervalo de bytes do documento, com as entradas opcionais /Reason e /Location gravadas no dicionário de assinatura.

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 (somente 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)

Você também pode construir o documento e assiná-lo de uma só vez — DocumentBuilder().…​.build() retorna os bytes não assinados, que você passa diretamente para sign_pdf_bytes. Veja DocumentBuilder.

Quais são os níveis baseline do PAdES?

PAdES (PDF Advanced Electronic Signatures, ETSI EN 319 142) adiciona validação de longo prazo sobre a assinatura CMS. O PDF Oxide fornece um assinador ETSI.CAdES.detached com um mapeamento de níveis fixo, compartilhado entre a C ABI e todas as bindings:

Nível Código O que adiciona
B-B 0 Baseline CAdES: atributos assinados incluindo o ESS signing-certificate-v2 (RFC 5035).
B-T 1 B-B + um atributo não assinado signature-time-stamp RFC 3161 sobre o valor da assinatura.
B-LT 2 B-T + um Document Security Store (certificados / CRLs / OCSPs + VRI por assinatura).
B-LTA 3 B-LT + um /DocTimeStamp no documento. Reservado para a assinatura — passar o nível 3 ao assinador retorna Unsupported. Detecte um arquivo B-LTA existente com o sinal de leitura do carimbo de tempo do documento abaixo.

B-T, B-LT e B-LTA exigem um servidor de carimbo de tempo. Para B-T e B-LT você fornece uma URL de TSA e a binding busca o token RFC 3161; assinar sem ele falha de forma segura (fails closed) com Unsupported.

Como crio uma assinatura PAdES B-LT?

sign_pdf_bytes_pades recebe o nível, uma URL de TSA opcional e o material de revogação (certificados, CRLs e respostas OCSP codificados em DER) que se torna o DSS do 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 (somente 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)

Para uma assinatura B-B rápida sem carimbo de tempo, passe o nível 0 / PadesLevel.B_B e omita a URL de TSA.

Como inspeciono o Document Security Store que produzi?

Após uma assinatura B-LT, reabra o PDF e leia seu /DSS para confirmar que o material de validação foi gravado. dss() retorna None/null quando o documento não tem DSS — isso não é um erro.

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 (somente 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

Acessores do DSS

Recurso (C ABI / Swift) Equivalente em Python / Go / C# Descrição
dss.cert_count() len(dss.certs) / len(dss.Certs) / dss.Certificates.Count Certificados DER no nível do documento (/Certs)
dss.crl_count() len(dss.crls) / len(dss.CRLs) / dss.Crls.Count CRLs DER no nível do documento (/CRLs)
dss.ocsp_count() len(dss.ocsps) / len(dss.OCSPs) / dss.OcspResponses.Count Respostas OCSP DER no nível do documento (/OCSPs)
dss.vri_count() len(dss.vri) / dss.VRICount / dss.VriCount Entradas /VRI por assinatura (SHA-1 em hex maiúsculo de /Contents)

A C ABI / Swift expõem métodos de contagem mais getters por índice (pdf_dss_get_cert etc.); Python e Go retornam os blobs DER diretamente como listas.

Como detecto um carimbo de tempo de arquivamento B-LTA?

Signature.pades_level tem escopo de assinatura e, por design, vai no máximo até B-LT. O B-LTA adiciona um /DocTimeStamp no nível do documento, que é um sinal de todo o documento — leia-o com a verificação de carimbo de tempo do documento:

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

Desempenho

O núcleo de extração do PDF Oxide roda a 0,8 ms de média / 9 ms p99 com taxa de aprovação de 100% no corpus de referência (benchmark da v0.3.8). O custo da assinatura é dominado pela operação CMS/RSA mais, para B-T/B-LT, o ida e volta na rede até a TSA — o hashing do intervalo de bytes do PDF e a gravação da atualização incremental adicionam sobrecarga desprezível por cima disso.

FAQ

Preciso de conexão de rede para assinar? Apenas para B-T e B-LT (e B-LTA), que embutem um carimbo de tempo RFC 3161 obtido de uma URL de TSA. Um sign_pdf_bytes simples e uma assinatura PAdES B-B são totalmente offline.

Por que B-T/B-LT falha sem uma URL de TSA? Por design — esses níveis exigem um token de carimbo de tempo, então o assinador falha de forma segura com um erro Unsupported em vez de rebaixar o nível silenciosamente.

Posso assinar a partir do Node ou do navegador (WASM)? Ainda não. Node e WASM expõem a leitura/verificação de assinatura e o campo placeholder /Sig não assinado, mas o assinador CMS está conectado apenas a Rust, Python, Go, C# e Swift. Assine em uma binding do lado do servidor e envie os bytes assinados.

Qual algoritmo de assinatura o assinador usa? RSA-PKCS#1 v1.5 sobre SHA-256, o preenchimento aceito por essencialmente todo leitor de PDF. Uma política de provedor de criptografia FIPS pode fazer a assinatura falhar de forma segura se a política configurada proibir um algoritmo.

Páginas relacionadas