Skip to content

Криптополітика 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]*

  • modecompat | strict | fips-strict
  • clause = (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 % проходження; інвентар — це атомарний набір бітів без блокувань, а перевірки політики відбуваються лише на межах примусу криптографії.

Наступні кроки