Skip to content

Política de Criptografia de PDF, FIPS e CBOM

O PDF Oxide abstrai cada operação de criptografia e assinatura por trás de um provedor criptográfico plugável, sobrepõe uma política de tempo de execução à prova de falhas (fail-closed) e registra um inventário por processo dos algoritmos que efetivamente exercitou. Esse inventário é exportado como uma Lista de Materiais Criptográficos (CBOM) CycloneDX 1.6 — o artefato legível por máquina que compradores regulados e auditores solicitam.

Esta página cobre a seleção de provedor, o modo FIPS 140-3, a gramática da política de tempo de execução, o inventário criptográfico e a exportação de CBOM.

Cobertura das bindings. Toda a superfície de governança criptográfica é global ao processo (não recebe handle de documento) e é exposta por Rust, Python, Go, Node.js / TypeScript e C#. Os nomes dos métodos diferem por binding (capitalização idiomática). A metade de seleção de provedor (active_provider / fips_available / use_fips) é a superfície FIPS da issue #236; a metade de política (set_policy / policy / inventory / cbom) é a superfície de governança em tempo de execução da issue #230. O próprio modo FIPS só faz algo quando a biblioteca nativa foi compilada com --features fips — caso contrário, use_fips retorna/levanta um erro e fips_available é falso.

Os dois provedores

Provedor Build Algoritmos Uso
rust-crypto (padrão) sempre presente todos os algoritmos que a especificação PDF referencia, incluindo o caminho legado MD5 + RC4 para documentos ISO 32000-1 R≤4 uso geral
aws-lc-rs opcional, --features fips validado pelo FIPS 140-3; recusa MD5, RC4 e assinatura SHA-1 conformidade com FIPS

O provedor padrão é instalado de forma preguiçosa (lazy) — a primeira operação criptográfica (abrir um documento criptografado, verificar uma assinatura) o fixa. É por isso que você deve mudar para FIPS antes que qualquer trabalho criptográfico aconteça no processo.

Como verifico o provedor criptográfico ativo?

active_provider é não inicializador: se nenhum provedor foi fixado ainda, ele retorna "rust-crypto (default, lazy)" sem travar o padrão, de modo que uma chamada posterior a use_fips ainda pode ter êxito. Uma vez que um provedor é fixado (explícita ou preguiçosamente), ele retorna o nome real ("rust-crypto" ou "aws-lc-rs").

import pdf_oxide

# Safe to call first — does not commit a provider.
print(pdf_oxide.crypto_active_provider())      # "rust-crypto (default, lazy)"
print(pdf_oxide.crypto_available_providers())  # ["rust-crypto"]  (+ "aws-lc-rs" on a FIPS wheel)
use pdf_oxide::crypto;

// Non-initializing read for display/audit.
let name = if crypto::is_set() {
    crypto::active().name().to_string()
} else {
    format!("{} (default, lazy)", crypto::RustCryptoProvider.name())
};
println!("active crypto provider: {name}");
package main

import (
    "fmt"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    fmt.Println(pdfoxide.ActiveCryptoProvider())   // "rust-crypto"
    fmt.Println(pdfoxide.IsFipsCryptoAvailable())  // false unless built with FIPS
}
const pdf_oxide = require("pdf-oxide");

console.log(pdf_oxide.getActiveCryptoProvider()); // "rust-crypto (default, lazy)"
console.log(pdf_oxide.isFipsCryptoAvailable());   // false unless the -fips package
using PdfOxide;

Console.WriteLine(PdfDocument.GetActiveCryptoProvider()); // "rust-crypto"
Console.WriteLine(PdfDocument.IsFipsCryptoAvailable());   // False unless FIPS lib

Assinaturas

Operação Rust Python Go Node.js C#
Provedor ativo crypto::active().name() -> &'static str crypto_active_provider() -> str ActiveCryptoProvider() -> string getActiveCryptoProvider(): string GetActiveCryptoProvider() -> string
FIPS disponível? cfg(feature = "fips") crypto_available_providers() -> list[str] IsFipsCryptoAvailable() -> bool isFipsCryptoAvailable(): boolean IsFipsCryptoAvailable() -> bool

A ABI C que elas encapsulam: char *pdf_oxide_crypto_active_provider(void) e int32_t pdf_oxide_crypto_fips_available(void) (retorna 1 se o provedor FIPS foi compilado, 0 caso contrário).

Como habilito o modo FIPS para um PDF?

Chame use_fips (ou, em Rust, set_provider) antes de qualquer operação criptográfica. É de definição única: uma segunda chamada — ou qualquer chamada após o provedor padrão ter sido instalado preguiçosamente — falha.

Códigos de erro da ABI C para int32_t pdf_oxide_crypto_use_fips(void):

Código Significado
0 sucesso
1 recurso FIPS não compilado
2 um provedor já está definido
import pdf_oxide

try:
    pdf_oxide.crypto_use_fips()   # raises RuntimeError if not compiled in / already set
    print("FIPS provider active:", pdf_oxide.crypto_active_provider())  # "aws-lc-rs"
except RuntimeError as e:
    print("FIPS unavailable:", e)

# Now every encrypted-PDF open / signature verify routes through aws-lc-rs.
doc = pdf_oxide.PdfDocument("encrypted-r6.pdf")
print(doc.page_count())
use std::sync::Arc;
use pdf_oxide::PdfDocument;
use pdf_oxide::crypto::{set_provider, AwsLcProvider};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Must run before any pdf_oxide operation that uses crypto.
    set_provider(Arc::new(AwsLcProvider::new()))
        .expect("crypto provider already installed");

    // Every PDF open / signature verify now routes through aws-lc-rs.
    let doc = PdfDocument::open("encrypted-r6.pdf")?;
    println!("pages: {}", doc.page_count());
    Ok(())
}
package main

import (
    "log"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    // Before any crypto operation.
    if err := pdfoxide.UseFipsCryptoProvider(); err != nil {
        log.Fatalf("FIPS unavailable: %v", err) // ErrFipsNotCompiled / ErrCryptoProviderAlreadySet
    }
    log.Println("active:", pdfoxide.ActiveCryptoProvider()) // "aws-lc-rs"
}
const pdf_oxide = require("pdf-oxide");

try {
  pdf_oxide.useFipsCryptoProvider();
  console.log("active:", pdf_oxide.getActiveCryptoProvider()); // "aws-lc-rs"
} catch (e) {
  console.error("FIPS unavailable:", e.message);
}
using PdfOxide;

try
{
    PdfDocument.UseFipsCryptoProvider();
    Console.WriteLine(PdfDocument.GetActiveCryptoProvider()); // "aws-lc-rs"
}
catch (InvalidOperationException e)
{
    Console.WriteLine($"FIPS unavailable: {e.Message}");
}

O que o FIPS rejeita?

O PDF Standard Security R≤4 (criptografia por senha do PDF 1.4–1.6, ISO 32000-1 §7.6.3) usa MD5 para derivação de chave e RC4/AES-128 como cifra. O FIPS 140-3 proíbe MD5 e RC4, então abrir tal documento sob aws-lc-rs falha com uma mensagem de correção:

active CryptoProvider 'aws-lc-rs' rejects PDF Standard Security R=4
(R≤4 requires MD5; FIPS 140-3 forbids MD5).
Re-encrypt the document at R=6 (AES-256) or build pdf_oxide
without the 'fips' feature so the default 'rust-crypto'
provider stays active.

A solução: recriptografar em R=6 (PDF 2.0 AES-256, ISO 32000-2 §7.6.4) em um ambiente sem restrições de FIPS. Documentos R=6 abrem sem problemas sob aws-lc-rs.

Instalando a variante FIPS. As distribuições -fips entregam binários de provedor único (auditores geralmente exigem isso). Use pip install pdf_oxide_fips, npm install pdf-oxide-fips, dotnet add package PdfOxide.Fips, go get github.com/yfedoseev/pdf_oxide/go-fips, ou compile Rust com cargo build --features fips. As variantes padrão e FIPS da mesma tag de release são idênticas byte a byte em todos os caminhos não criptográficos.

Como defino uma política de criptografia em tempo de execução?

Uma política restringe quais algoritmos são permitidos, independentemente de qual provedor está ativo. É de definição única (um downgrade em tempo de execução é um vetor de ataque) e à prova de falhas (uma especificação não analisável é rejeitada e nenhuma política é instalada). O padrão — nunca definido — é "compat", que é byte a byte idêntico ao comportamento anterior à política.

Gramática: mode[;clause]*

  • modecompat | strict | fips-strict
  • clause = (allow|deny):<alg>@<read|write> — por exemplo, "compat;deny:rc4@write;deny:md5@write" ou "fips-strict"

Códigos de erro da ABI C para int32_t pdf_oxide_crypto_set_policy(const char *spec):

Código Significado
0 sucesso
1 argumento inválido (especificação nula / não UTF-8)
2 erro de análise — especificação rejeitada, política não instalada
3 uma política já está definida
import pdf_oxide

# Deny the legacy write paths while staying compatible for reads.
pdf_oxide.crypto_set_policy("compat;deny:rc4@write;deny:md5@write")
print(pdf_oxide.crypto_policy())  # canonical grammar string
use pdf_oxide::crypto::{self, SecurityPolicy};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Parse fail-closed, then install set-once.
    let policy: SecurityPolicy = "fips-strict".parse()?;
    crypto::set_policy(policy).expect("a crypto policy is already set");

    println!("active policy: {}", crypto::active_policy());
    Ok(())
}
package main

import (
    "log"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    // Treat any error as fatal — the policy is not installed on failure.
    if err := pdfoxide.SetCryptoPolicy("strict;deny:md5@write"); err != nil {
        log.Fatalf("policy rejected: %v", err)
    }
    log.Println("active policy:", pdfoxide.CryptoPolicy())
}
const pdf_oxide = require("pdf-oxide");

// Throws on an unparseable spec (fail-closed) or if already set.
pdf_oxide.setCryptoPolicy("fips-strict");
console.log(pdf_oxide.cryptoPolicy()); // "fips-strict"
using PdfOxide;

// Throws ArgumentException (rejected) or InvalidOperationException (already set).
PdfDocument.SetCryptoPolicy("compat;deny:rc4@write");
Console.WriteLine(PdfDocument.CryptoPolicy());

Assinaturas

Operação Rust Python Go Node.js C#
Definir política crypto::set_policy(SecurityPolicy) -> Result<(), SetPolicyError> crypto_set_policy(spec: str) -> None SetCryptoPolicy(spec string) -> error setCryptoPolicy(spec: string): void SetCryptoPolicy(string spec) -> void
Ler política crypto::active_policy() -> &'static SecurityPolicy crypto_policy() -> str CryptoPolicy() -> string cryptoPolicy(): string CryptoPolicy() -> string

Como leio o inventário criptográfico?

O inventário é o conjunto de tokens de algoritmo efetivamente exercitados até agora neste processo — um bitset atômico sem bloqueios registrado em cada fronteira de imposição. É o relatório mínimo de “qual criptografia esta execução usou?”. Os tokens são strings estáveis em minúsculas, como md5, aes256, rc4, sha256.

A ABI C retorna uma string unida por vírgulas (char *pdf_oxide_crypto_inventory(void), "" quando nada foi exercitado); as bindings de alto nível a dividem para você em uma lista/slice/array.

import pdf_oxide

doc = pdf_oxide.PdfDocument("signed.pdf")
doc.verify_signatures()                  # exercises some algorithms

print(pdf_oxide.crypto_inventory())      # e.g. ["sha256", "aes256"]  (list[str])
use pdf_oxide::crypto;

// `inventory()` returns the exercised algorithms in declaration order.
let used: Vec<&'static str> = crypto::inventory()
    .into_iter()
    .map(|a| a.token())
    .collect();
println!("exercised: {used:?}"); // e.g. ["md5", "aes256"]
package main

import (
    "fmt"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    inv := pdfoxide.CryptoInventory() // []string, empty when nothing exercised
    fmt.Println("exercised:", inv)
}
const pdf_oxide = require("pdf-oxide");

console.log(pdf_oxide.cryptoInventory()); // string[], e.g. ["sha256"]
using PdfOxide;

foreach (var token in PdfDocument.CryptoInventory()) // string[]
    Console.WriteLine(token);

Como exporto um CBOM (CycloneDX) para um PDF?

crypto_cbom serializa o inventário em uma Lista de Materiais cryptographic-asset no formato CycloneDX 1.6 como uma string JSON. O documento sempre tem bomFormat: "CycloneDX", specVersion: "1.6", um bloco metadata (timestamp RFC 3339 + o componente de ferramenta pdf_oxide) e um componente cryptographic-asset por algoritmo exercitado. Um inventário vazio produz um BOM válido sem componentes.

import json
import pdf_oxide

# Run your workload first so the inventory reflects what was used.
doc = pdf_oxide.PdfDocument("signed.pdf")
doc.verify_signatures()

cbom = pdf_oxide.crypto_cbom()           # JSON string
report = json.loads(cbom)
assert report["bomFormat"] == "CycloneDX"
assert report["specVersion"] == "1.6"

with open("pdf-cbom.json", "w") as f:
    f.write(cbom)
use std::fs;
use pdf_oxide::crypto;

fn main() -> std::io::Result<()> {
    // ... run the crypto workload first ...
    let cbom: String = crypto::cbom_json();   // CycloneDX 1.6 JSON
    fs::write("pdf-cbom.json", cbom)?;
    Ok(())
}
package main

import (
    "os"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    cbom := pdfoxide.CryptoCBOM() // CycloneDX 1.6 JSON string
    _ = os.WriteFile("pdf-cbom.json", []byte(cbom), 0o644)
}
const fs = require("fs");
const pdf_oxide = require("pdf-oxide");

const cbom = pdf_oxide.cryptoCbom(); // CycloneDX 1.6 JSON string
fs.writeFileSync("pdf-cbom.json", cbom);
using System.IO;
using PdfOxide;

string cbom = PdfDocument.CryptoCbom(); // CycloneDX 1.6 JSON string
File.WriteAllText("pdf-cbom.json", cbom);

Formato do CBOM

Cada algoritmo exercitado se torna um componente:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "metadata": {
    "timestamp": "2026-06-22T12:00:00+00:00",
    "tools": { "components": [{ "type": "application", "name": "pdf_oxide", "version": "0.3.69" }] }
  },
  "components": [
    {
      "type": "cryptographic-asset",
      "name": "aes256",
      "bom-ref": "crypto/algorithm/aes256",
      "cryptoProperties": {
        "assetType": "algorithm",
        "algorithmProperties": {
          "primitive": "block-cipher",
          "classicalSecurityLevel": 256,
          "nistQuantumSecurityLevel": 0,
          "certificationLevel": ["fips140-3"]
        }
      }
    }
  ]
}

O primitive é mapeado a partir do tipo do algoritmo (hash, block-cipher, stream-cipher para RC4, signature, kdf, drbg); certificationLevel é ["fips140-3"] para algoritmos aprovados pelo FIPS e ["none"] caso contrário.

Assinaturas

Operação Rust Python Go Node.js C#
Inventário crypto::inventory() -> Vec<AlgorithmId> crypto_inventory() -> list[str] CryptoInventory() -> []string cryptoInventory(): string[] CryptoInventory() -> string[]
CBOM crypto::cbom_json() -> String crypto_cbom() -> str CryptoCBOM() -> string cryptoCbom(): string CryptoCbom() -> string

Sequência de inicialização recomendada

Como os provedores e as políticas são ambos de definição única e o provedor padrão é instalado preguiçosamente, configure a governança criptográfica como a primeira coisa que seu processo faz:

import pdf_oxide

# 1. (optional) switch to FIPS — must precede any crypto operation.
if "aws-lc-rs" in pdf_oxide.crypto_available_providers():
    pdf_oxide.crypto_use_fips()

# 2. install the governance policy (fail-closed, set-once).
pdf_oxide.crypto_set_policy("fips-strict")

# 3. ... run your PDF workload ...
doc = pdf_oxide.PdfDocument("encrypted-r6.pdf")
doc.verify_signatures()

# 4. emit governance artifacts.
print("policy:   ", pdf_oxide.crypto_policy())
print("inventory:", pdf_oxide.crypto_inventory())
with open("pdf-cbom.json", "w") as f:
    f.write(pdf_oxide.crypto_cbom())

Perguntas Frequentes

Posso voltar a trocar o provedor criptográfico depois de fixá-lo? Não. Tanto use_fips (provedor) quanto set_policy (política) são de definição única por design — trocar no meio do processo enquanto existe estado criptográfico em andamento (autoteste FIPS, sessão HSM) é um risco de integridade, e um downgrade de política em tempo de execução é um vetor de ataque. Testes que precisam de um provedor novo devem rodar em namespaces de processo separados.

Por que crypto_active_provider() diz "rust-crypto (default, lazy)" antes de eu fazer qualquer coisa? Essa leitura é deliberadamente não inicializadora: ela informa o padrão sem fixá-lo, de modo que um crypto_use_fips() posterior ainda pode ter êxito. O nome real aparece assim que um provedor é fixado.

O FIPS está disponível em todos os pacotes? Somente quando a biblioteca nativa foi compilada com --features fips. Use as distribuições paralelas -fips (pdf_oxide_fips, pdf-oxide-fips, PdfOxide.Fips, go-fips), que entregam binários FIPS de provedor único. Em um build não FIPS, fips_available é falso e use_fips gera erro.

O CBOM é padronizado? Sim — é um documento CycloneDX 1.6 válido (bomFormat: "CycloneDX", specVersion: "1.6"), então se encaixa diretamente em ferramentas de SBOM/CBOM e auditorias de cadeia de suprimentos.

Definir uma política deixa o processamento de PDF mais lento? Não. O PDF Oxide mantém seu perfil de extração de 0,8ms de média / 100% de aprovação; o inventário é um bitset atômico sem bloqueios e as verificações de política acontecem apenas nas fronteiras de imposição criptográfica.

Próximos Passos