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_fipsretorna/levanta um erro efips_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
-fipsentregam binários de provedor único (auditores geralmente exigem isso). Usepip 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 comcargo 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]*
mode∈compat|strict|fips-strictclause=(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
- Assinaturas Digitais — assine e verifique PDFs (o principal consumidor da política de criptografia)
- Validação PDF/A — conformidade de arquivamento (rejeita criptografia)
- Referência da API — API Rust completa