ic_core/module.rs
1//! The module's error state, where every crate can see it.
2//!
3//! A cryptographic module that finds itself wrong -- a known-answer test that
4//! fails, corruption detected by its host -- must stop producing output, and
5//! must not start again until it is restarted. `ic_fips` runs the tests and
6//! holds the rest of the module's state, but the primitive crates cannot
7//! depend on it: it depends on them. So the one fact they all need lives
8//! here, below them, as a flag that can be set and never cleared.
9//!
10//! Once [`enter_error_state`] has been called, every operation in this
11//! library that can report an error reports
12//! [`ErrorKind::ModuleErrorState`] and does nothing else: encryption and decryption, signing and verification,
13//! key generation and agreement, MACs, hashes and XOFs, key derivation and
14//! random generation. The operations that return `bool` return `false`.
15//!
16//! Hashes, XOFs and MACs refuse on output -- `finalize`, `digest`, `xof`, a
17//! reader's `read` or `fill`, `mac` -- so an object made before the failure
18//! produces nothing after it. Making one and feeding it input produce no
19//! output, and are not refused. Until 0.3.0 those interfaces had no error to
20//! return and could not refuse at all; they were the gate's stated limit.
21//!
22//! # What this does not gate
23//!
24//! The primitives call one another through `ic_core::traits::internal`, which
25//! does not refuse: a signature or a KDF refuses at its own entry, and does
26//! not check again for each hash on the way to its answer. That module is
27//! hidden and not API; a caller who names it has stepped inside the boundary.
28//!
29//! One thing that could refuse is left ungated on purpose: a block cipher
30//! object's `encrypt_block`, `decrypt_block` and `encrypt_blocks`. They are
31//! the inner loop of every mode, and every mode refuses at its own entry, as
32//! does `BlockCipher::new`; a check per block would be paid by all of them
33//! to stop only raw single-block use of a key made before the failure.
34//!
35//! Parsing and encoding, which use no key, are not gated either.
36//!
37//! # Before the self-tests
38//!
39//! In the default build nothing here requires the self-tests to have run: a
40//! primitive called before `ic_fips::initialize` works, and only a module that
41//! has failed refuses. `ic_fips::check` is the gate for that.
42//!
43//! A build with `--cfg ic_approved_only` closes it. There, every operation
44//! that consults [`operational`] also refuses, with
45//! [`ErrorKind::ModuleErrorState`], until `ic_fips::initialize` has run the
46//! known-answer tests and they have passed. FIPS 140-3 asks for the
47//! self-tests before a service is first used, and in that build it no longer
48//! depends on the caller asking for them.
49//!
50//! While the tests run, the primitives they call are let through, since the
51//! tests are made of them. A second thread that calls a primitive in that
52//! window is let through too: nothing below `ic_fips` can tell the two threads
53//! apart without `std`. A program that starts threads before `initialize`
54//! returns should not use the library from them until it does.
55//!
56//! This repository's own test suites are made of primitive calls that do not
57//! go through `ic_fips`, and the crates that hold them cannot depend on it, so
58//! `scripts/approved-only.sh` runs them with `--cfg ic_startup_gate_off` as
59//! well, which turns this gate, and only this gate, off. It is a test switch:
60//! a build with it does not run its self-tests before use. The same script
61//! then runs the tests that show the gate refusing, without it.
62//!
63//! # Cost
64//!
65//! One relaxed load of one byte per operation.
66
67use crate::{Error, ErrorKind, Result};
68use core::sync::atomic::{AtomicBool, AtomicU8, Ordering};
69
70static FAILED: AtomicBool = AtomicBool::new(false);
71
72const UNTESTED: u8 = 0;
73const TESTING: u8 = 1;
74const PASSED: u8 = 2;
75
76/// How far the known-answer tests have got. Consulted only in a build with
77/// `--cfg ic_approved_only`; see the module documentation.
78static SELF_TESTS: AtomicU8 = AtomicU8::new(UNTESTED);
79
80/// Whether this build refuses service until the self-tests have passed.
81const STARTUP_GATE: bool = cfg!(all(ic_approved_only, not(ic_startup_gate_off)));
82
83/// Record that the known-answer tests have started.
84///
85/// For `ic_fips::initialize`, which runs them. Until [`self_tests_passed`] is
86/// called the primitives the tests use are let through, so that the tests can
87/// run at all.
88pub fn self_tests_started() {
89 // Only forward: a module that has passed does not go back to testing
90 // when `initialize` is called again. A load and a store rather than a
91 // compare-exchange, which Cortex-M0 does not have; two racing callers can
92 // at worst leave the state at TESTING after a pass, which lets service
93 // through as PASSED does.
94 if SELF_TESTS.load(Ordering::SeqCst) == UNTESTED {
95 SELF_TESTS.store(TESTING, Ordering::SeqCst);
96 }
97}
98
99/// Record that every known-answer test passed.
100///
101/// For `ic_fips::initialize`. Calling it any other way skips the tests it
102/// stands for; the error state still overrides it.
103pub fn self_tests_passed() {
104 SELF_TESTS.store(PASSED, Ordering::SeqCst);
105}
106
107/// Whether the known-answer tests have passed in this process.
108#[must_use]
109pub fn self_tests_have_passed() -> bool {
110 SELF_TESTS.load(Ordering::SeqCst) == PASSED
111}
112
113/// Put the module into its error state.
114///
115/// There is no way back short of restarting the process, by design: nothing
116/// in this library clears the flag. Applications call
117/// `ic_fips::enter_error_state`, which calls this.
118pub fn enter_error_state() {
119 FAILED.store(true, Ordering::SeqCst);
120}
121
122/// Pass on the result of a conditional self-test, entering the error state
123/// if it failed.
124///
125/// A conditional self-test is one a module runs on its own work as it goes:
126/// the pairwise consistency test on a key pair it has just generated. Its
127/// input is the module's own output, never a caller's, so a failure is not
128/// something a peer can cause -- it means this module computed two things
129/// that should agree and do not, and nothing it computes afterwards can be
130/// vouched for.
131///
132/// Key generation wraps its test in this. The test functions themselves do
133/// not enter the state, so that they can be shown rejecting a mismatched
134/// pair without ending the process that shows it.
135pub fn conditional_self_test(result: Result<()>) -> Result<()> {
136 if result.is_err() {
137 enter_error_state();
138 }
139 result
140}
141
142/// Whether the module is in its error state.
143#[inline]
144#[must_use = "whether the module has failed; discarding it gates nothing"]
145pub fn in_error_state() -> bool {
146 FAILED.load(Ordering::Relaxed)
147}
148
149/// `Ok(())` unless the module is in its error state -- or, in a build with
150/// `--cfg ic_approved_only`, has not yet passed its self-tests.
151///
152/// The first line of every operation that can refuse:
153///
154/// ```
155/// fn service() -> ic_core::Result<()> {
156/// ic_core::module::operational()?;
157/// // ...
158/// Ok(())
159/// }
160/// # ic_core::module::self_tests_passed(); // what ic_fips::initialize records
161/// # service().unwrap();
162/// ```
163#[inline]
164pub fn operational() -> Result<()> {
165 if in_error_state() {
166 return Err(Error::new(
167 ErrorKind::ModuleErrorState,
168 "module in error state",
169 ));
170 }
171 if STARTUP_GATE && SELF_TESTS.load(Ordering::Relaxed) == UNTESTED {
172 return Err(Error::new(
173 ErrorKind::ModuleErrorState,
174 "self-tests have not run; call ic_fips::initialize() first",
175 ));
176 }
177 Ok(())
178}