Skip to main content

ic_core/
traits.rs

1//! The algorithm contracts every IronCrypto primitive implements.
2//!
3//! Two properties make these traits agent-friendly:
4//!
5//! 1. **Self-describing.** Every implementation carries an [`Algorithm::ID`]
6//!    that resolves to an entry in the ontology, so a value at runtime can be
7//!    traced back to its full machine-readable specification.
8//! 2. **Total.** Nothing panics on bad input; sizes are surfaced as associated
9//!    constants *and* as ontology metadata, so an agent can validate a call
10//!    before making it.
11
12use crate::Result;
13
14/// Links a concrete implementation to its ontology entry.
15///
16/// `ID` is the stable ontology identifier (for example `"sha2-256"`), and is
17/// the join key between runtime types and the machine-readable catalog exposed
18/// by `ic-ontology`.
19pub trait Algorithm {
20    /// Stable ontology identifier for this algorithm.
21    const ID: &'static str;
22    /// Human-facing display name (for example `"SHA-256"`).
23    const NAME: &'static str;
24}
25
26/// A cryptographic hash function with a fixed-length output.
27///
28/// Producing output can fail: [`Digest::finalize`] and [`Digest::digest`]
29/// refuse with [`ErrorKind::ModuleErrorState`](crate::ErrorKind) once the
30/// module is in its error state, and, in a build with
31/// `--cfg ic_approved_only`, before `ic_fips::initialize` has passed the
32/// self-tests. Making a hasher and absorbing input produce nothing, and do not
33/// refuse.
34///
35/// Implemented for every type that implements [`internal::Digest`], which is
36/// how the hash functions are written.
37pub trait Digest: Algorithm + Clone + Default {
38    /// The fixed-size output buffer type, e.g. `[u8; 32]`.
39    type Output: AsRef<[u8]> + AsMut<[u8]> + Copy;
40
41    /// Output length in bytes.
42    const OUTPUT_LEN: usize;
43    /// Internal block (rate) length in bytes — required by HMAC and KMAC.
44    const BLOCK_LEN: usize;
45
46    /// Create a fresh, empty hasher.
47    fn new() -> Self;
48
49    /// Absorb more input. May be called any number of times.
50    fn update(&mut self, data: &[u8]);
51
52    /// Consume the hasher and produce the digest.
53    fn finalize(self) -> Result<Self::Output>;
54
55    /// One-shot convenience: hash `data` in a single call.
56    fn digest(data: &[u8]) -> Result<Self::Output> {
57        let mut h = Self::new();
58        h.update(data);
59        h.finalize()
60    }
61}
62
63/// An extendable-output function (XOF) such as SHAKE128 / SHAKE256.
64///
65/// [`Xof::finalize_xof`] refuses as [`Digest::finalize`] does.
66pub trait Xof: Algorithm + Clone + Default {
67    /// Rate in bytes.
68    const BLOCK_LEN: usize;
69
70    /// Absorb more input.
71    fn update(&mut self, data: &[u8]);
72
73    /// Squeeze `out.len()` bytes of output, consuming the state.
74    fn finalize_xof(self, out: &mut [u8]) -> Result<()>;
75}
76
77impl<T: internal::Digest> Digest for T {
78    type Output = <T as internal::Digest>::Output;
79    const OUTPUT_LEN: usize = <T as internal::Digest>::OUTPUT_LEN;
80    const BLOCK_LEN: usize = <T as internal::Digest>::BLOCK_LEN;
81
82    fn new() -> Self {
83        <T as internal::Digest>::new()
84    }
85
86    fn update(&mut self, data: &[u8]) {
87        <T as internal::Digest>::update(self, data)
88    }
89
90    fn finalize(self) -> Result<Self::Output> {
91        crate::module::operational()?;
92        Ok(<T as internal::Digest>::finalize(self))
93    }
94}
95
96impl<T: internal::Xof> Xof for T {
97    const BLOCK_LEN: usize = <T as internal::Xof>::BLOCK_LEN;
98
99    fn update(&mut self, data: &[u8]) {
100        <T as internal::Xof>::update(self, data)
101    }
102
103    fn finalize_xof(self, out: &mut [u8]) -> Result<()> {
104        crate::module::operational()?;
105        <T as internal::Xof>::finalize_xof(self, out);
106        Ok(())
107    }
108}
109
110/// The hash functions and MACs as the rest of this library calls them,
111/// without the refusal on output. **Not API**, and not covered by semver.
112///
113/// A FIPS module has to refuse its services once it has failed; it does not
114/// have to refuse each step inside one. HMAC, HKDF, the DRBGs, ECDSA, RSA,
115/// ML-KEM, ML-DSA, SLH-DSA, LMS and the rest each refuse at their own entry,
116/// and then hash on the way to their answer through these traits, without a
117/// second check per block. Everything outside the module reaches a hash
118/// through [`Digest`] and [`Xof`], which refuse.
119///
120/// Nothing stops another crate from naming this module. A caller who does has
121/// stepped inside the boundary on purpose, and gets a hash that does not
122/// refuse.
123#[doc(hidden)]
124pub mod internal {
125    use super::Algorithm;
126
127    /// [`super::Digest`], without the refusal.
128    pub trait Digest: Algorithm + Clone + Default {
129        /// The fixed-size output buffer type.
130        type Output: AsRef<[u8]> + AsMut<[u8]> + Copy;
131        /// Output length in bytes.
132        const OUTPUT_LEN: usize;
133        /// Internal block (rate) length in bytes.
134        const BLOCK_LEN: usize;
135
136        /// Create a fresh, empty hasher.
137        fn new() -> Self {
138            Self::default()
139        }
140
141        /// Absorb more input.
142        fn update(&mut self, data: &[u8]);
143
144        /// Consume the hasher and produce the digest.
145        fn finalize(self) -> Self::Output;
146
147        /// Hash `data` in a single call.
148        fn digest(data: &[u8]) -> Self::Output {
149            let mut h = Self::new();
150            h.update(data);
151            h.finalize()
152        }
153    }
154
155    /// [`super::Mac`], without the refusal in `finalize`. `new` still
156    /// refuses, as every implementation's does.
157    pub trait Mac: Algorithm + Clone {
158        /// The fixed-size tag type.
159        type Tag: AsRef<[u8]> + AsMut<[u8]> + Copy;
160        /// Tag length in bytes.
161        const TAG_LEN: usize;
162
163        /// Create a MAC instance from a key.
164        fn new(key: &[u8]) -> crate::Result<Self>
165        where
166            Self: Sized;
167
168        /// Absorb more input.
169        fn update(&mut self, data: &[u8]);
170
171        /// Consume the instance and produce the tag.
172        fn finalize(self) -> Self::Tag;
173
174        /// One-shot authentication.
175        fn mac(key: &[u8], data: &[u8]) -> crate::Result<Self::Tag>
176        where
177            Self: Sized,
178        {
179            let mut m = Self::new(key)?;
180            m.update(data);
181            Ok(m.finalize())
182        }
183    }
184
185    /// [`super::Xof`], without the refusal.
186    pub trait Xof: Algorithm + Clone + Default {
187        /// Rate in bytes.
188        const BLOCK_LEN: usize;
189
190        /// Absorb more input.
191        fn update(&mut self, data: &[u8]);
192
193        /// Squeeze `out.len()` bytes of output, consuming the state.
194        fn finalize_xof(self, out: &mut [u8]);
195    }
196}
197
198/// A keyed message authentication code.
199///
200/// [`Mac::new`] and [`Mac::finalize`] refuse with
201/// [`ErrorKind::ModuleErrorState`](crate::ErrorKind) once the module is in its
202/// error state, so a MAC keyed before the failure produces no tag after it.
203///
204/// Implemented for every type that implements [`internal::Mac`].
205pub trait Mac: Algorithm + Clone {
206    /// The fixed-size tag type.
207    type Tag: AsRef<[u8]> + AsMut<[u8]> + Copy;
208
209    /// Tag length in bytes.
210    const TAG_LEN: usize;
211
212    /// Create a MAC instance from a key of any length the algorithm accepts.
213    fn new(key: &[u8]) -> Result<Self>
214    where
215        Self: Sized;
216
217    /// Absorb more input.
218    fn update(&mut self, data: &[u8]);
219
220    /// Consume the instance and produce the tag.
221    fn finalize(self) -> Result<Self::Tag>;
222
223    /// One-shot authentication.
224    fn mac(key: &[u8], data: &[u8]) -> Result<Self::Tag>
225    where
226        Self: Sized,
227    {
228        let mut m = Self::new(key)?;
229        m.update(data);
230        m.finalize()
231    }
232
233    /// Constant-time tag verification.
234    ///
235    /// Returns [`crate::ErrorKind::AuthenticationFailed`] on mismatch and
236    /// never reveals *where* the tags diverged.
237    fn verify(key: &[u8], data: &[u8], tag: &[u8]) -> Result<()>
238    where
239        Self: Sized,
240    {
241        let expected = Self::mac(key, data)?;
242        if crate::ct::verify(expected.as_ref(), tag) {
243            Ok(())
244        } else {
245            Err(crate::err!(AuthenticationFailed, "mac tag"))
246        }
247    }
248}
249
250impl<T: internal::Mac> Mac for T {
251    type Tag = <T as internal::Mac>::Tag;
252    const TAG_LEN: usize = <T as internal::Mac>::TAG_LEN;
253
254    fn new(key: &[u8]) -> Result<Self> {
255        <T as internal::Mac>::new(key)
256    }
257
258    fn update(&mut self, data: &[u8]) {
259        <T as internal::Mac>::update(self, data)
260    }
261
262    fn finalize(self) -> Result<Self::Tag> {
263        crate::module::operational()?;
264        Ok(<T as internal::Mac>::finalize(self))
265    }
266}
267
268/// A fixed-width block cipher primitive (raw ECB core, for use inside modes).
269pub trait BlockCipher: Algorithm {
270    /// Block size in bytes.
271    const BLOCK_LEN: usize;
272    /// Accepted key length in bytes.
273    const KEY_LEN: usize;
274
275    /// Expand a key into a round-key schedule.
276    fn new(key: &[u8]) -> Result<Self>
277    where
278        Self: Sized;
279
280    /// Encrypt a single block in place.
281    fn encrypt_block(&self, block: &mut [u8]) -> Result<()>;
282
283    /// Decrypt a single block in place.
284    fn decrypt_block(&self, block: &mut [u8]) -> Result<()>;
285
286    /// Encrypt a whole number of blocks in place.
287    ///
288    /// The default implementation loops over [`encrypt_block`][Self::encrypt_block].
289    /// Backends with instruction-level parallelism override it: AES-NI has a
290    /// pipelined round instruction, so encrypting eight independent blocks at
291    /// once is several times faster than eight sequential calls. Counter-based
292    /// modes route through here for exactly that reason.
293    ///
294    /// Returns [`crate::ErrorKind::InvalidLength`] if `data` is not a whole
295    /// number of blocks.
296    fn encrypt_blocks(&self, data: &mut [u8]) -> Result<()> {
297        if !data.len().is_multiple_of(Self::BLOCK_LEN) {
298            return Err(crate::err!(InvalidLength, "batch must be block-aligned"));
299        }
300        for block in data.chunks_mut(Self::BLOCK_LEN) {
301            self.encrypt_block(block)?;
302        }
303        Ok(())
304    }
305}
306
307/// An authenticated cipher with associated data.
308pub trait Aead: Algorithm {
309    /// Key length in bytes.
310    const KEY_LEN: usize;
311    /// Nonce length in bytes.
312    const NONCE_LEN: usize;
313    /// Authentication tag length in bytes.
314    const TAG_LEN: usize;
315
316    /// Bind a key to a cipher instance.
317    fn new(key: &[u8]) -> Result<Self>
318    where
319        Self: Sized;
320
321    /// Encrypt `in_out` in place and write the tag to `tag`.
322    fn seal_detached(
323        &self,
324        nonce: &[u8],
325        aad: &[u8],
326        in_out: &mut [u8],
327        tag: &mut [u8],
328    ) -> Result<()>;
329
330    /// Verify `tag` and decrypt `in_out` in place.
331    ///
332    /// On failure `in_out` is zeroized before returning, so a caller that
333    /// ignores the error cannot expose unauthenticated plaintext.
334    fn open_detached(&self, nonce: &[u8], aad: &[u8], in_out: &mut [u8], tag: &[u8]) -> Result<()>;
335}
336
337/// A key derivation function that expands keying material to a requested length.
338pub trait Kdf: Algorithm {
339    /// Derive `out.len()` bytes from the supplied inputs.
340    fn derive(secret: &[u8], salt: &[u8], info: &[u8], out: &mut [u8]) -> Result<()>;
341}
342
343/// A deterministic random bit generator (SP 800-90A).
344pub trait Drbg: Algorithm {
345    /// Instantiate from entropy input, a nonce, and an optional personalization
346    /// string.
347    fn instantiate(entropy: &[u8], nonce: &[u8], personalization: &[u8]) -> Result<Self>
348    where
349        Self: Sized;
350
351    /// Reseed with fresh entropy and optional additional input.
352    fn reseed(&mut self, entropy: &[u8], additional: &[u8]) -> Result<()>;
353
354    /// Fill `out` with generated bits, honouring the reseed interval.
355    fn generate(&mut self, additional: &[u8], out: &mut [u8]) -> Result<()>;
356}
357
358/// A source of random bytes suitable for key generation.
359pub trait RandomSource {
360    /// Fill `out` with random bytes.
361    fn fill(&mut self, out: &mut [u8]) -> Result<()>;
362}
363
364/// A Diffie-Hellman style key agreement scheme.
365pub trait KeyAgreement: Algorithm {
366    /// Private key (scalar) length in bytes.
367    const PRIVATE_KEY_LEN: usize;
368    /// Public key (encoded point) length in bytes.
369    const PUBLIC_KEY_LEN: usize;
370    /// Shared secret length in bytes.
371    const SHARED_SECRET_LEN: usize;
372
373    /// Compute the public key for a private key.
374    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()>;
375
376    /// Compute the shared secret from our private key and their public key.
377    fn agree(private_key: &[u8], peer_public_key: &[u8], out: &mut [u8]) -> Result<()>;
378}
379
380/// A digital signature scheme.
381pub trait SignatureScheme: Algorithm {
382    /// Seed / private key length in bytes.
383    const PRIVATE_KEY_LEN: usize;
384    /// Public key length in bytes.
385    const PUBLIC_KEY_LEN: usize;
386    /// Signature length in bytes.
387    const SIGNATURE_LEN: usize;
388
389    /// Derive the public key from a private key.
390    fn public_key(private_key: &[u8], out: &mut [u8]) -> Result<()>;
391
392    /// Sign `message`, writing exactly [`Self::SIGNATURE_LEN`] bytes.
393    fn sign(private_key: &[u8], message: &[u8], signature: &mut [u8]) -> Result<()>;
394
395    /// Verify a signature, returning [`crate::ErrorKind::AuthenticationFailed`]
396    /// when it does not check out.
397    fn verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<()>;
398}
399
400/// A known-answer test an implementation runs to satisfy FIPS 140-3 CAST
401/// requirements.
402///
403/// Implemented by every approved algorithm so `ic-fips` can enumerate and drive
404/// the full self-test suite without hard-coding a list.
405pub trait SelfTest {
406    /// Run the algorithm's known-answer test.
407    ///
408    /// Returns [`crate::ErrorKind::SelfTestFailed`] if the computed value does
409    /// not match the embedded vector.
410    fn self_test() -> Result<()>;
411}