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

Провайдер по умолчанию устанавливается лениво — первая криптооперация (открытие зашифрованного документа, проверка подписи) фиксирует его. Именно поэтому необходимо переключиться на 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 % прохождения; инвентарь — это атомарный набор битов без блокировок, а проверки политики происходят только на границах принуждения криптографии.

Дальнейшие шаги