Криптополітика PDF, FIPS і CBOM
PDF Oxide абстрагує кожну операцію шифрування й підпису за підключуваним криптопровайдером, накладає поверх нього відмовостійку (fail-closed) політику часу виконання та записує попроцесний інвентар алгоритмів, які він фактично задіяв. Цей інвентар експортується як криптографічна специфікація матеріалів (CBOM) CycloneDX 1.6 — машинозчитуваний артефакт, який запитують регульовані покупці та аудитори.
Ця сторінка охоплює вибір провайдера, режим FIPS 140-3, граматику політики часу виконання, криптоінвентар та експорт CBOM.
Охоплення прив’язок. Уся поверхня керування криптографією є глобальною для процесу (не приймає дескриптор документа) і надається в Rust, Python, Go, Node.js / TypeScript і C#. Імена методів різняться залежно від прив’язки (ідіоматичний регістр). Половина з вибором провайдера (
active_provider/fips_available/use_fips) — це поверхня FIPS з issue #236; половина з політикою (set_policy/policy/inventory/cbom) — поверхня керування часом виконання з issue #230. Сам режим FIPS робить щось лише тоді, коли нативну бібліотеку скомпільовано з--features fips— інакшеuse_fipsповертає/викидає помилку, аfips_availableдорівнює false.
Два провайдери
| Провайдер | Збірка | Алгоритми | Застосування |
|---|---|---|---|
rust-crypto (за замовчуванням) |
завжди наявний | усі алгоритми, на які посилається специфікація PDF, включно із застарілим шляхом MD5 + RC4 для документів ISO 32000-1 R≤4 | загального призначення |
aws-lc-rs |
за вибором, --features fips |
пройшов валідацію FIPS 140-3; відхиляє MD5, RC4 та підпис SHA-1 | відповідність FIPS |
Провайдер за замовчуванням встановлюється лінькувато (lazy) — перша криптооперація (відкриття зашифрованого документа, перевірка підпису) фіксує його. Саме тому потрібно перемкнутися на FIPS до того, як у процесі відбудеться будь-яка крипторобота.
Як перевірити активний криптопровайдер?
active_provider не виконує ініціалізацію: якщо жоден провайдер ще не зафіксований, він повертає "rust-crypto (default, lazy)", не фіксуючи провайдер за замовчуванням, тож подальший виклик use_fips усе ще може завершитися успішно. Щойно провайдер зафіксовано (явно або лінькувато), повертається реальне ім’я ("rust-crypto" або "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
Сигнатури
| Операція | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| Активний провайдер | crypto::active().name() -> &'static str |
crypto_active_provider() -> str |
ActiveCryptoProvider() -> string |
getActiveCryptoProvider(): string |
GetActiveCryptoProvider() -> string |
| Чи доступний FIPS? | cfg(feature = "fips") |
crypto_available_providers() -> list[str] |
IsFipsCryptoAvailable() -> bool |
isFipsCryptoAvailable(): boolean |
IsFipsCryptoAvailable() -> bool |
C ABI, який вони обгортають: char *pdf_oxide_crypto_active_provider(void) та int32_t pdf_oxide_crypto_fips_available(void) (повертає 1, якщо провайдер FIPS було ввімкнено під час компіляції, інакше 0).
Як увімкнути режим FIPS для PDF?
Викличте use_fips (або, у Rust, set_provider) перед будь-якою криптооперацією. Це одноразове встановлення: другий виклик — або будь-який виклик після того, як провайдер за замовчуванням було лінькувато встановлено — завершується невдачею.
Коди помилок C ABI для int32_t pdf_oxide_crypto_use_fips(void):
| Код | Значення |
|---|---|
0 |
успіх |
1 |
функцію FIPS не ввімкнено під час компіляції |
2 |
провайдер уже встановлено |
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}");
}
Що відхиляє FIPS?
PDF Standard Security R≤4 (парольне шифрування PDF 1.4–1.6, ISO 32000-1 §7.6.3) використовує MD5 для виведення ключа та RC4/AES-128 як шифр. FIPS 140-3 забороняє MD5 і RC4, тому відкриття такого документа під aws-lc-rs завершується невдачею з повідомленням про усунення:
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.
Розв’язання: перешифрувати на рівні R=6 (PDF 2.0 AES-256, ISO 32000-2 §7.6.4) у середовищі без обмежень FIPS. Документи R=6 відкриваються під aws-lc-rs без проблем.
Встановлення варіанта FIPS. Дистрибутиви
-fipsпостачають бінарники з єдиним провайдером (аудитори зазвичай цього вимагають). Використовуйтеpip install pdf_oxide_fips,npm install pdf-oxide-fips,dotnet add package PdfOxide.Fips,go get github.com/yfedoseev/pdf_oxide/go-fipsабо зберіть Rust ізcargo build --features fips. Варіанти за замовчуванням і FIPS того самого тегу релізу побайтово ідентичні на всіх некриптографічних шляхах.
Як задати криптополітику часу виконання?
Політика звужує набір дозволених алгоритмів, незалежно від того, який провайдер активний. Це одноразове встановлення (зниження під час виконання — вектор атаки) і відмовостійка (специфікація, яку неможливо розібрати, відхиляється, і політика не встановлюється). Значення за замовчуванням — ніколи не задається — це "compat", побайтово ідентичне поведінці до запровадження політик.
Граматика: mode[;clause]*
mode∈compat|strict|fips-strictclause=(allow|deny):<alg>@<read|write>— наприклад,"compat;deny:rc4@write;deny:md5@write"або"fips-strict"
Коди помилок C ABI для int32_t pdf_oxide_crypto_set_policy(const char *spec):
| Код | Значення |
|---|---|
0 |
успіх |
1 |
недопустимий аргумент (null / специфікація не в UTF-8) |
2 |
помилка розбору — специфікацію відхилено, політику не встановлено |
3 |
політику вже встановлено |
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());
Сигнатури
| Операція | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| Задати політику | crypto::set_policy(SecurityPolicy) -> Result<(), SetPolicyError> |
crypto_set_policy(spec: str) -> None |
SetCryptoPolicy(spec string) -> error |
setCryptoPolicy(spec: string): void |
SetCryptoPolicy(string spec) -> void |
| Прочитати політику | crypto::active_policy() -> &'static SecurityPolicy |
crypto_policy() -> str |
CryptoPolicy() -> string |
cryptoPolicy(): string |
CryptoPolicy() -> string |
Як прочитати криптоінвентар?
Інвентар — це набір токенів алгоритмів, фактично задіяних дотепер у цьому процесі — атомарний набір бітів без блокувань, що записується на кожній межі примусу. Це мінімальний звіт «яку криптографію використав цей запуск?». Токени — це стабільні рядки в нижньому регістрі, такі як md5, aes256, rc4, sha256.
C ABI повертає рядок, з’єднаний комами (char *pdf_oxide_crypto_inventory(void), "", коли нічого не було задіяно); високорівневі прив’язки розбивають його для вас на список/зріз/масив.
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);
Як експортувати CBOM (CycloneDX) для PDF?
crypto_cbom серіалізує інвентар у специфікацію матеріалів cryptographic-asset формату CycloneDX 1.6 у вигляді рядка JSON. Документ завжди містить bomFormat: "CycloneDX", specVersion: "1.6", блок metadata (позначка часу RFC 3339 + компонент-інструмент pdf_oxide) та один компонент cryptographic-asset на кожен задіяний алгоритм. Порожній інвентар дає коректний BOM без компонентів.
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);
Структура CBOM
Кожен задіяний алгоритм стає одним компонентом:
{
"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"]
}
}
}
]
}
primitive відображається з виду алгоритму (hash, block-cipher, stream-cipher для RC4, signature, kdf, drbg); certificationLevel дорівнює ["fips140-3"] для алгоритмів, схвалених FIPS, та ["none"] в інших випадках.
Сигнатури
| Операція | Rust | Python | Go | Node.js | C# |
|---|---|---|---|---|---|
| Інвентар | 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 |
Рекомендована послідовність запуску
Оскільки і провайдери, і політики встановлюються одноразово, а провайдер за замовчуванням встановлюється лінькувато, налаштовуйте керування криптографією як найперше, що робить ваш процес:
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())
Поширені запитання
Чи можу я перемкнути криптопровайдер назад після його фіксації? Ні. І use_fips (провайдер), і set_policy (політика) за задумом встановлюються одноразово — заміна посеред процесу за наявності незавершеного криптостану (самотестування FIPS, сесія HSM) — це загроза коректності, а зниження політики під час виконання — вектор атаки. Тести, яким потрібен свіжий провайдер, мають виконуватися в окремих просторах імен процесів.
Чому crypto_active_provider() каже "rust-crypto (default, lazy)" до того, як я щось зробив? Це читання навмисно не виконує ініціалізацію: воно повідомляє значення за замовчуванням, не фіксуючи його, тож подальший crypto_use_fips() усе ще може завершитися успішно. Реальне ім’я з’являється після фіксації провайдера.
Чи доступний FIPS у кожному пакеті? Лише коли нативну бібліотеку зібрано з --features fips. Використовуйте паралельні дистрибутиви -fips (pdf_oxide_fips, pdf-oxide-fips, PdfOxide.Fips, go-fips), які постачають FIPS-бінарники з єдиним провайдером. У збірці без FIPS fips_available дорівнює false, а use_fips видає помилку.
Чи є CBOM стандартом? Так — це коректний документ CycloneDX 1.6 (bomFormat: "CycloneDX", specVersion: "1.6"), тож він без проблем вбудовується в інструментарій SBOM/CBOM та аудити ланцюга постачання.
Чи сповільнює встановлення політики обробку PDF? Ні. PDF Oxide залишається при своєму профілі видобування 0,8 мс у середньому / 100 % проходження; інвентар — це атомарний набір бітів без блокувань, а перевірки політики відбуваються лише на межах примусу криптографії.
Наступні кроки
- Цифрові підписи — підписання та перевірка PDF (основний споживач криптополітики)
- Валідація PDF/A — відповідність вимогам архівування (відхиляє шифрування)
- Довідник API — повний API Rust