Política criptográfica de PDF, FIPS y CBOM
PDF Oxide abstrae cada operación de cifrado y firma detrás de un proveedor criptográfico conectable, superpone una política de tiempo de ejecución a prueba de fallos (fail-closed) y registra un inventario por proceso de los algoritmos que realmente ejercitó. Ese inventario se exporta como una Lista de Materiales Criptográficos (CBOM) CycloneDX 1.6: el artefacto legible por máquina que solicitan los compradores regulados y los auditores.
Esta página cubre la selección del proveedor, el modo FIPS 140-3, la gramática de la política de tiempo de ejecución, el inventario criptográfico y la exportación de CBOM.
Cobertura de los bindings. Toda la superficie de gobernanza criptográfica es global al proceso (no recibe un handle de documento) y se expone en Rust, Python, Go, Node.js / TypeScript y C#. Los nombres de los métodos difieren según el binding (mayúsculas idiomáticas). La mitad de selección de proveedor (
active_provider/fips_available/use_fips) es la superficie FIPS de la issue #236; la mitad de política (set_policy/policy/inventory/cbom) es la superficie de gobernanza en tiempo de ejecución de la issue #230. El propio modo FIPS solo hace algo cuando la biblioteca nativa se compiló con--features fips; de lo contrario,use_fipsdevuelve/lanza un error yfips_availablees false.
Los dos proveedores
| Proveedor | Compilación | Algoritmos | Uso |
|---|---|---|---|
rust-crypto (predeterminado) |
siempre presente | todos los algoritmos que referencia la especificación PDF, incluida la ruta heredada MD5 + RC4 para documentos ISO 32000-1 R≤4 | uso general |
aws-lc-rs |
opcional, --features fips |
validado por FIPS 140-3; rechaza MD5, RC4 y la firma SHA-1 | conformidad con FIPS |
El proveedor predeterminado se instala de forma perezosa (lazy): la primera operación criptográfica (abrir un documento cifrado, verificar una firma) lo fija. Por eso debe cambiar a FIPS antes de que ocurra cualquier trabajo criptográfico en el proceso.
¿Cómo compruebo el proveedor criptográfico activo?
active_provider es no inicializador: si aún no se ha fijado ningún proveedor, devuelve "rust-crypto (default, lazy)" sin bloquear el predeterminado, de modo que una llamada posterior a use_fips aún puede tener éxito. Una vez que un proveedor se fija (explícita o perezosamente), devuelve el nombre real ("rust-crypto" o "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
Firmas
| Operación | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| Proveedor activo | crypto::active().name() -> &'static str |
crypto_active_provider() -> str |
ActiveCryptoProvider() -> string |
getActiveCryptoProvider(): string |
GetActiveCryptoProvider() -> string |
| ¿FIPS disponible? | cfg(feature = "fips") |
crypto_available_providers() -> list[str] |
IsFipsCryptoAvailable() -> bool |
isFipsCryptoAvailable(): boolean |
IsFipsCryptoAvailable() -> bool |
La ABI C que envuelven: char *pdf_oxide_crypto_active_provider(void) y int32_t pdf_oxide_crypto_fips_available(void) (devuelve 1 si el proveedor FIPS se compiló, 0 en caso contrario).
¿Cómo habilito el modo FIPS para un PDF?
Llame a use_fips (o, en Rust, set_provider) antes de cualquier operación criptográfica. Es de establecimiento único: una segunda llamada —o cualquier llamada después de que el proveedor predeterminado se haya instalado perezosamente— falla.
Códigos de error de la ABI C para int32_t pdf_oxide_crypto_use_fips(void):
| Código | Significado |
|---|---|
0 |
éxito |
1 |
característica FIPS no compilada |
2 |
ya hay un proveedor establecido |
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}");
}
¿Qué rechaza FIPS?
PDF Standard Security R≤4 (cifrado por contraseña de PDF 1.4–1.6, ISO 32000-1 §7.6.3) usa MD5 para la derivación de claves y RC4/AES-128 como cifrador. FIPS 140-3 prohíbe MD5 y RC4, por lo que abrir un documento de este tipo bajo aws-lc-rs falla con un mensaje de corrección:
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.
La solución: volver a cifrar en R=6 (PDF 2.0 AES-256, ISO 32000-2 §7.6.4) en un entorno sin restricciones de FIPS. Los documentos R=6 se abren sin problemas bajo aws-lc-rs.
Instalación de la variante FIPS. Las distribuciones
-fipsentregan binarios de un solo proveedor (los auditores suelen exigirlo). Usepip install pdf_oxide_fips,npm install pdf-oxide-fips,dotnet add package PdfOxide.Fips,go get github.com/yfedoseev/pdf_oxide/go-fips, o compile Rust concargo build --features fips. Las variantes predeterminada y FIPS de la misma etiqueta de versión son idénticas byte a byte en todas las rutas no criptográficas.
¿Cómo establezco una política criptográfica en tiempo de ejecución?
Una política restringe qué algoritmos se permiten, independientemente de qué proveedor esté activo. Es de establecimiento único (una degradación en tiempo de ejecución es un vector de ataque) y a prueba de fallos (una especificación no analizable se rechaza y no se instala ninguna política). El valor predeterminado —nunca establecido— es "compat", idéntico byte a byte al comportamiento anterior a las políticas.
Gramática: mode[;clause]*
mode∈compat|strict|fips-strictclause=(allow|deny):<alg>@<read|write>— por ejemplo,"compat;deny:rc4@write;deny:md5@write"o"fips-strict"
Códigos de error de la ABI C para int32_t pdf_oxide_crypto_set_policy(const char *spec):
| Código | Significado |
|---|---|
0 |
éxito |
1 |
argumento inválido (especificación nula / no UTF-8) |
2 |
error de análisis — especificación rechazada, política no instalada |
3 |
ya hay una política establecida |
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());
Firmas
| Operación | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| Establecer 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 |
| Leer política | crypto::active_policy() -> &'static SecurityPolicy |
crypto_policy() -> str |
CryptoPolicy() -> string |
cryptoPolicy(): string |
CryptoPolicy() -> string |
¿Cómo leo el inventario criptográfico?
El inventario es el conjunto de tokens de algoritmo realmente ejercitados hasta ahora en este proceso: un bitset atómico sin bloqueos registrado en cada frontera de imposición. Es el informe mínimo de “¿qué criptografía usó esta ejecución?”. Los tokens son cadenas estables en minúsculas como md5, aes256, rc4, sha256.
La ABI C devuelve una cadena unida por comas (char *pdf_oxide_crypto_inventory(void), "" cuando no se ejercitó nada); los bindings de alto nivel la dividen por usted en una 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);
¿Cómo exporto un CBOM (CycloneDX) para un PDF?
crypto_cbom serializa el inventario en una Lista de Materiales cryptographic-asset en formato CycloneDX 1.6 como una cadena JSON. El documento siempre tiene bomFormat: "CycloneDX", specVersion: "1.6", un bloque metadata (marca de tiempo RFC 3339 + el componente de herramienta pdf_oxide) y un componente cryptographic-asset por cada algoritmo ejercitado. Un inventario vacío produce un BOM válido sin 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);
Estructura del CBOM
Cada algoritmo ejercitado se convierte en un 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"]
}
}
}
]
}
El primitive se mapea a partir del tipo de algoritmo (hash, block-cipher, stream-cipher para RC4, signature, kdf, drbg); certificationLevel es ["fips140-3"] para los algoritmos aprobados por FIPS y ["none"] en caso contrario.
Firmas
| Operación | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| Inventario | 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 |
Secuencia de inicio recomendada
Dado que tanto los proveedores como las políticas son de establecimiento único y el proveedor predeterminado se instala perezosamente, configure la gobernanza criptográfica como lo primero que hace su proceso:
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())
Preguntas frecuentes
¿Puedo volver a cambiar el proveedor criptográfico después de fijarlo? No. Tanto use_fips (proveedor) como set_policy (política) son de establecimiento único por diseño: cambiarlo a mitad del proceso mientras existe estado criptográfico en curso (autoprueba FIPS, sesión HSM) es un riesgo de corrección, y una degradación de política en tiempo de ejecución es un vector de ataque. Las pruebas que necesitan un proveedor nuevo deben ejecutarse en espacios de nombres de proceso separados.
¿Por qué crypto_active_provider() dice "rust-crypto (default, lazy)" antes de que yo haga nada? Esa lectura es deliberadamente no inicializadora: informa el predeterminado sin fijarlo, de modo que un crypto_use_fips() posterior aún puede tener éxito. El nombre real aparece una vez que se fija un proveedor.
¿Está FIPS disponible en todos los paquetes? Solo cuando la biblioteca nativa se compiló con --features fips. Use las distribuciones paralelas -fips (pdf_oxide_fips, pdf-oxide-fips, PdfOxide.Fips, go-fips), que entregan binarios FIPS de un solo proveedor. En una compilación no FIPS, fips_available es false y use_fips da error.
¿Es estándar el CBOM? Sí: es un documento CycloneDX 1.6 válido (bomFormat: "CycloneDX", specVersion: "1.6"), por lo que se integra directamente en las herramientas de SBOM/CBOM y en las auditorías de la cadena de suministro.
¿Establecer una política ralentiza el procesamiento de PDF? No. PDF Oxide mantiene su perfil de extracción de 0,8 ms de media / 100 % de aprobación; el inventario es un bitset atómico sin bloqueos y las comprobaciones de política ocurren solo en las fronteras de imposición criptográfica.
Próximos pasos
- Firmas digitales — firme y verifique PDF (el principal consumidor de la política criptográfica)
- Validación de PDF/A — conformidad de archivado (rechaza el cifrado)
- Referencia de la API — API completa de Rust