Skip to main content

ferritls_core/
lib.rs

1//! # ferritls-core
2//!
3//! Pure-Rust cryptographic primitives, providing everything that
4//! [ferritls-rustls](https://docs.rs/ferritls-rustls) (the rustls
5//! `CryptoProvider` adapter) needs. No C, no assembly, no `unsafe`.
6//!
7//! ## FIPS 140-3 module boundary
8//!
9//! This crate is the **module boundary** intended for future CMVP
10//! validation: every algorithm is implemented in-boundary in pure Rust
11//! (`#![forbid(unsafe_code)]`), and the dependency whitelist is exactly
12//! [`subtle`], [`zeroize`] and [`getrandom`] (OS entropy input). New
13//! dependencies require updating the whitelist and audit rationale in
14//! `docs/FIPS.md` first (AGENTS.md hard rule 2).
15//!
16//! Note that the module is **not yet CMVP-validated**; the `fips` cargo
17//! feature selects the approved-algorithm mode, it is not a
18//! certification claim.
19//!
20//! ## Default feature `simd`
21//!
22//! The default `simd` feature uses the standard library's `core::simd`
23//! (portable_simd) for explicit vectorization of the AES and ChaCha20
24//! hot paths — `std` code, not a third-party dependency, so the
25//! whitelist is unchanged. On the **stable toolchain** it compiles via
26//! `RUSTC_BOOTSTRAP=1` (provided by this repository's
27//! `.cargo/config.toml`; downstream users set the env themselves or opt
28//! out with `default-features = false`, which keeps the scalar fallback
29//! path). Vector width follows the compile target (SSE2/NEON baseline,
30//! widening to AVX2/AVX-512 with the corresponding `target-feature`
31//! RUSTFLAGS); there is no runtime dispatch and no `unsafe`.
32//!
33//! ## Usage
34//!
35//! Hashing:
36//!
37//! ```
38//! use ferritls_core::sha2::Sha256;
39//!
40//! let digest = Sha256::one_shot(b"abc");
41//! assert_eq!(&digest[..4], &[0xba, 0x78, 0x16, 0xbf]); // FIPS 180-4 example
42//! ```
43//!
44//! Authenticated encryption (AES-128-GCM; the same shape exists for
45//! [`gcm::Aes256Gcm`], [`ccm`] and [`chacha20poly1305`]):
46//!
47//! ```
48//! use ferritls_core::gcm::Aes128Gcm;
49//!
50//! let cipher = Aes128Gcm::new(&[7u8; 16]);
51//! let nonce = [9u8; 12]; // unique per key — never repeat a (key, nonce) pair
52//!
53//! let ct_and_tag = cipher.seal(&nonce, b"aad", b"plaintext");
54//! let plaintext = cipher.open(&nonce, b"aad", &ct_and_tag).expect("authentic");
55//! assert_eq!(plaintext, b"plaintext");
56//!
57//! // Tampered ciphertexts fail verification before any plaintext is returned:
58//! let mut tampered = ct_and_tag.clone();
59//! tampered[0] ^= 0x01;
60//! assert!(cipher.open(&nonce, b"aad", &tampered).is_err());
61//! ```
62//!
63//! Key agreement, signatures, KEMs and the DRBG follow the same pattern:
64//! typed keys, explicit errors (no panics on attacker-controlled input),
65//! and zeroization of secret material on drop.
66//!
67//! ## Module map
68//!
69//! | Module | Contents | FIPS approval |
70//! |---|---|---|
71//! | [`sha2`] | SHA-256/384/512 | approved |
72//! | [`sha3`] | SHA3-224/256/384/512, SHAKE-128/256 (full FIPS 202 family) | approved |
73//! | [`hmac`] / [`hkdf`] | HMAC, HKDF | approved |
74//! | [`aes`] / [`gcm`] / [`ccm`] | AES, AES-GCM, AES-CCM | approved |
75//! | [`chacha20poly1305`] | ChaCha20-Poly1305 | not approved |
76//! | [`ecdh`] | X25519 (not approved), P-256/384 (approved) | mixed |
77//! | [`mlkem`] | ML-KEM-512/768/1024 (FIPS 203) | approved |
78//! | [`sign`] | ECDSA, RSA (approved), Ed25519 (not approved) | mixed |
79//! | [`drbg`] / [`entropy`] | SP 800-90A CTR-DRBG, OS entropy | approved |
80//! | [`selftest`] | Power-on self-tests (KATs) | required |
81//! | [`der`] | Minimal DER/PKCS#8/SEC1 parsing | — |
82//! | [`ct`] / [`policy`] / [`ops`] | Constant-time utilities, approval policy, backend dispatch | — |
83//!
84//! All modules are fully implemented and covered by official
85//! NIST/RFC/CAVP vectors, Wycheproof and the RFC 8448 key schedule (see
86//! `docs/VECTOR-PROVENANCE.md` for sources). AES-GCM and SHA-256 public
87//! types dispatch through [`ops`]: the default is a zero-overhead direct
88//! software path, and an optional out-of-boundary hardware backend crate
89//! can be installed to replace the execution core (see [`ops`]).
90
91#![forbid(unsafe_code)]
92// Default feature `simd`: explicit core::simd (portable_simd) paths — std
93// code, entirely safe. On stable toolchains the feature gate compiles via
94// RUSTC_BOOTSTRAP=1 (repo .cargo/config.toml [env] / CI env; downstream
95// users provide the env or opt out with default-features = false, which
96// keeps the scalar fallback). Constraints and lane-width selection rules:
97// AGENTS.md §5.5. Remove the bootstrap dependency once portable_simd
98// stabilizes.
99#![cfg_attr(feature = "simd", feature(portable_simd))]
100// Future hardware backends (AES-NI/SHA extensions) inevitably need unsafe;
101// they must not enter this crate — they live in separate out-of-boundary
102// backend crates hooked up through the ops module traits (see the ops
103// module docs and docs/ARCHITECTURE.md). Whether such a backend ever joins
104// the FIPS boundary is a phase-C decision.
105
106/// Compile canary for the `simd` feature: ensures portable_simd is truly
107/// available in the current configuration (the hot paths live in the
108/// [`aes`] and [`chacha20poly1305`] modules).
109#[cfg(feature = "simd")]
110#[allow(dead_code)]
111fn _portable_simd_canary(v: std::simd::u32x4) -> std::simd::u32x4 {
112    v + std::simd::u32x4::splat(1)
113}
114
115pub mod aes;
116pub mod ccm;
117pub mod chacha20poly1305;
118pub mod ct;
119pub mod der;
120pub mod drbg;
121pub mod ecdh;
122pub mod entropy;
123mod fields;
124pub mod gcm;
125pub mod hkdf;
126pub mod hmac;
127pub mod mlkem;
128pub mod ops;
129pub mod policy;
130mod rsabig;
131pub mod selftest;
132pub mod sha2;
133pub mod sha3;
134pub mod sign;
135
136pub use policy::Approval;
137
138/// The unified error type of the module boundary.
139///
140/// Rule: attacker-controlled input (network data, certificates, key
141/// files) must never panic — every failure is returned as this error
142/// (AGENTS.md hard rule 4). `non_exhaustive`: variants may be added as
143/// milestones land.
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145#[non_exhaustive]
146pub enum Error {
147    /// Input length or format does not satisfy the algorithm's requirements.
148    InvalidInput,
149    /// Verification of a key/IV/tag failed (AEAD open, signature
150    /// verification, etc.). Comparisons are constant-time; the error must
151    /// not distinguish failure reasons.
152    VerificationFailed,
153    /// The requested algorithm or curve is unavailable in this build
154    /// (approved mode).
155    Unsupported,
156    /// Entropy source failure (OS randomness unavailable).
157    EntropyFailed,
158    /// DRBG failure (health test failed, generation limit exceeded, etc.).
159    RngError,
160    /// Power-on self-test failed: the module is in an error state and
161    /// refuses service until re-initialized.
162    SelfTestFailed(&'static str),
163}
164
165impl core::fmt::Display for Error {
166    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
167        match self {
168            Error::InvalidInput => write!(f, "invalid input length or format"),
169            Error::VerificationFailed => write!(f, "verification failed"),
170            Error::Unsupported => write!(f, "algorithm unavailable in this build/mode"),
171            Error::EntropyFailed => write!(f, "OS entropy source failed"),
172            Error::RngError => write!(f, "DRBG failure"),
173            Error::SelfTestFailed(which) => {
174                write!(f, "power-up self-test failed: {which}")
175            }
176        }
177    }
178}
179
180impl std::error::Error for Error {}