ferrocrypt 0.3.0-beta.3

Recipient-oriented file and directory encryption: passphrase (Argon2id) and X25519 public-key recipients, XChaCha20-Poly1305 STREAM payloads, HKDF-SHA3-256 / HMAC-SHA3-256 key derivation and authentication.
Documentation
//! Internal parser-surface re-exports for the in-repo fuzz targets.
//!
//! Gated behind the `fuzzing` Cargo feature so library consumers never
//! see these items. The only crate that enables the feature is
//! `ferrocrypt-lib/fuzz`, where each target drives a specific parser
//! at the lowest useful layer (encrypted-file header parse, private-
//! key header + body shape, KDF-parameter bounds, TLV grammar,
//! recipient-string Bech32 grammar) without paying the cost of
//! running a full Argon2id derivation.
//!
//! **Not a stable API.** Do not depend on this module from outside
//! the repository. Items here may be renamed, removed, or re-shaped
//! at any time without a semver bump.

#![allow(missing_docs)]

pub use crate::archive::format::{parse_fca_header, parse_manifest_bytes};
pub use crate::archive::model::{ArchiveEntry, ArchiveEntryKind, FcaHeader, Manifest};
pub use crate::archive::path::{ascii_case_collision_key, validate_fca_path};
pub use crate::crypto::kdf::{KDF_PARAMS_SIZE, KdfParams};
pub use crate::crypto::tlv::validate_tlv;
pub use crate::key::private::PrivateKeyHeader;
pub use crate::key::public::RECIPIENT_STRING_LEN_LOCAL_CAP_DEFAULT;
pub use crate::recipient::native::x25519::validate_private_key_shape;

// `HeaderReadLimits` is part of the stable public API; re-export the
// crate-internal `read_encrypted_header` here so fuzz targets can drive
// the parser without paying the cost of a full Argon2id derivation.
pub use crate::HeaderReadLimits;

pub fn read_encrypted_header<R: std::io::Read>(
    reader: &mut R,
    limits: HeaderReadLimits,
) -> Result<(), crate::CryptoError> {
    crate::container::read_encrypted_header(reader, limits).map(|_| ())
}

/// Wraps the crate-internal `validate_no_known_critical` so fuzz
/// targets can drive the TLV scanner over FCA `archive_ext` /
/// `entry_ext` regions without paying the cost of a full archive
/// extraction. Mirrors the policy production callers use: scan +
/// reject any critical-range tag.
pub fn validate_no_known_critical(
    bytes: &[u8],
    max_region_len: u32,
    max_value_len: u32,
) -> Result<(), crate::CryptoError> {
    crate::crypto::tlv::validate_no_known_critical(bytes, max_region_len, max_value_len)
}

/// Drives `decode_recipient_string` for fuzz targets without leaking
/// the parsed [`crate::key::public::DecodedRecipient`] type (which
/// carries a crate-internal `KeypairSuite` enum). Discards the result
/// so the fuzzer exercises the parser surface without touching
/// internal types.
pub fn decode_recipient_string(s: &str, local_max_chars: usize) -> Result<(), crate::CryptoError> {
    crate::key::public::decode_recipient_string(s, local_max_chars).map(|_| ())
}

/// Writer-side counterpart for the `fuzz_fca_manifest` round-trip
/// assert: serialises a parsed [`Manifest`] through the production
/// writer gate. Per the encrypt/decrypt symmetry rule, a manifest the
/// reader accepted must pass this gate and serialize byte-identically.
pub fn serialize_manifest(
    manifest: &Manifest,
    limits: crate::ArchiveLimits,
) -> Result<Vec<u8>, crate::CryptoError> {
    crate::archive::format::serialize_manifest(manifest, limits)
}

/// Drives the production FCA writer for corpus-seed generation
/// (`fuzz/examples/gen_seeds.rs`): archives `input_path` into plain
/// FCA bytes under default limits.
pub fn archive_for_fuzz(input_path: &std::path::Path) -> Result<Vec<u8>, crate::CryptoError> {
    crate::archive::archive(input_path, Vec::new(), crate::ArchiveLimits::default())
        .map(|(_root_name, bytes)| bytes)
}

/// Fixed key and nonce for the STREAM fuzz harness. Deterministic on
/// purpose: libfuzzer crash reproduction requires identical behavior
/// for identical input bytes, and corpus seeds built by
/// [`encrypt_stream_for_fuzz`] must stay decryptable across runs.
fn fuzz_payload_key() -> crate::crypto::keys::PayloadKey {
    crate::crypto::keys::PayloadKey::from_bytes_for_tests(
        [0x42; crate::crypto::keys::ENCRYPTION_KEY_SIZE],
    )
}

const FUZZ_STREAM_NONCE: [u8; crate::crypto::stream::STREAM_NONCE_SIZE] =
    [0x24; crate::crypto::stream::STREAM_NONCE_SIZE];

/// Drives the STREAM-BE32 `DecryptReader` state machine over arbitrary
/// ciphertext for the `fuzz_stream_decrypt` target: chunk refill, the
/// exact-chunk one-byte peek, truncation / tamper / trailing-data
/// classification, and the zeroize-and-park error path. The MAC-gated
/// end-to-end decrypt targets can never reach this layer with
/// attacker-shaped bytes; this helper feeds the cipher layer directly.
pub fn decrypt_stream_for_fuzz(ciphertext: &[u8]) -> Result<Vec<u8>, crate::CryptoError> {
    use std::io::Read as _;
    let mut reader = crate::crypto::stream::payload_decryptor(
        &fuzz_payload_key(),
        &FUZZ_STREAM_NONCE,
        ciphertext,
    );
    let mut plaintext = Vec::new();
    reader.read_to_end(&mut plaintext)?;
    Ok(plaintext)
}

/// Encrypt counterpart of [`decrypt_stream_for_fuzz`] under the same
/// fixed key and nonce. Used to generate valid corpus seeds and as the
/// round-trip oracle inside the target: STREAM is deterministic, so
/// any accepted ciphertext must re-encrypt byte-identically from its
/// recovered plaintext.
pub fn encrypt_stream_for_fuzz(plaintext: &[u8]) -> Result<Vec<u8>, crate::CryptoError> {
    use std::io::Write as _;
    let mut writer = crate::crypto::stream::payload_encryptor(
        &fuzz_payload_key(),
        &FUZZ_STREAM_NONCE,
        Vec::new(),
    );
    writer.write_all(plaintext)?;
    writer.finish()
}

pub use crate::archive::IncompleteOutputPolicy;

/// Drives the full FCA reader pipeline (`archive::unarchive`) on
/// arbitrary bytes for the `fuzz_fca_full_pipeline` target: header
/// parse, archive-ext TLV validation, manifest parse, tree
/// validation, content streaming, atomic promotion. Wraps the
/// generic-`R` underlying function so the fuzz target can call
/// through a concrete signature.
///
/// `output_dir` is the per-iteration tempdir the fuzz harness creates;
/// the policy is fixed to `DeleteOnError` so partial extractions are
/// cleaned up before the tempdir drops, keeping per-iteration disk
/// pressure bounded.
pub fn unarchive_for_fuzz(
    bytes: &[u8],
    output_dir: &std::path::Path,
    limits: crate::ArchiveLimits,
) -> Result<std::path::PathBuf, crate::CryptoError> {
    use std::io::Cursor;
    crate::archive::unarchive(
        Cursor::new(bytes),
        output_dir,
        limits,
        IncompleteOutputPolicy::DeleteOnError,
    )
}