Skip to content

Commit ad7dbc2

Browse files
New feature: Suspendable (serializable state) (#48)
* initial trait defn for SerializableState * sha2 impls SerializableState * rustfmt * Incorporating Nikola and David's feedback * An in-progress SHA3 object can now be serialized * Impl'd SerializableState for MLDSA, and some quality-of-life fixes for the impl for SHA2 and SHA3. * rustfmt * Impl'd SerializableState for MLDSA-lowmemory. * Impl'd SerializableKeyedState for HMAC. * Fixed a bug in HMAC that could cause panics. * Implemented Suspendable for HKDF and a bunch of other tidy-up. * rustfmt * HMAC now impl's Drop like it should. * Adjusted the Suspendable version checker to allow future versions from the same patch stream * HKDF now explicitly carries a lib_ver tag * Fixed Keccak corrupt-state rejection, Hashfactory honest Algorithm left as a TODO, left version number bumping as a TODO, passed #6 wycheproof randomized case, suspend and serialize sha2/sha3/shake regressions (#48) * rustfmt of all code changes in (#48) * tweaks --------- Co-authored-by: officialfrancismendoza <francissamuelmendoza7@gmail.com>
1 parent 9e645c1 commit ad7dbc2

34 files changed

Lines changed: 2535 additions & 449 deletions

QUALITY_AND_STYLE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ lib.rs for all crates needs to contain: `#![forbid(missing_docs)]`, `#![no_std]`
1212

1313
All primitives must be accompanied by a CLI in `/cli`.
1414

15+
All algorithms with a state that is exercised across multiple API calls -- typically through a `do_update()`,
16+
`do_final()` pattern -- should implement SerializableState so that the user can pause the execution of this algorithm to
17+
a cache and resume later.
18+
1519
# Quality
1620

1721
## Tests

alpha_0.1.2_release_notes.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,21 @@
3838

3939
# 0.1.2 Features / Changelog
4040

41+
## Major features
42+
4143
* New algorithms added to crypto/ :
4244
* mldsa (FIPS 204)
4345
* mldsa-lowmemory -- runs in about 1/10th of the usual memory (~ 30 kb of stack) with comparable performance impact.
4446
* mlkem (FIPS 203)
4547
* mlkem-lowmemory -- runs in about 1/4th of the usual memory (~ 12 kb of stack) with comparable performance impact.
48+
* New traits [Suspendable] and [SuspendableKeyed] allow algorithms with a streaming API (`do_update()` ->
49+
`do_final()`) to be suspended to a small byte array and then resumed later, potentially from a different host and
50+
potentially across versions of the library. The intended use case is if you are processing a large input that depends
51+
on one or more network round-trips and you wish to suspend to a cache and potentially transfer to a new host while
52+
waiting for network IO.
53+
54+
## Minor features / bug fixes
55+
4656
* All public `*_out(.., out: &mut [u8])` functions now begin by zeroizing the entire output buffer with `.fill(0)`,
4757
preventing exposure of stale data in oversized output buffers or on early error returns.
4858
* Reworked the way KeyMaterial hazardous operations work; instead of a stateful .allow_hazardous_operations() /

crypto/core-test-framework/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod kdf;
1414
pub mod kem;
1515
pub mod mac;
1616
pub mod signature;
17+
pub mod suspendable_state;
1718
pub mod symmetric_ciphers;
1819

1920
mod fixed_seed_rng;
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
use bouncycastle_core::errors::SuspendableError;
2+
use bouncycastle_core::suspendable_state::{LIB_VERSION, SemVer};
3+
use bouncycastle_core::traits::{Suspendable, SuspendableKeyed};
4+
5+
pub struct TestFrameworkSuspendableState {}
6+
7+
impl TestFrameworkSuspendableState {
8+
pub fn new() -> Self {
9+
Self {}
10+
}
11+
12+
/// Test all the members of trait SerializableState.
13+
///
14+
/// Expects ta be handed an instance of the object that has some in-progress state to be serialized.
15+
pub fn test<const SERIALIZED_STATE_LEN: usize, S: Suspendable<SERIALIZED_STATE_LEN> + Clone>(
16+
&self,
17+
instance: &S,
18+
) {
19+
// There's not a lot we can test here in the abstract, but we can test a few things to
20+
// ensure that the SerializableState trait has been impl'd correctly.
21+
22+
// we need to work on a clone because .serialize_state() moves self, which you can't do on a
23+
// borrowed instance.
24+
let instance_clone = instance.clone();
25+
26+
// You can serialize and then deserialize the state.
27+
let serialized_state = instance_clone.suspend();
28+
assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN);
29+
30+
let _deserialized_state = S::from_suspended(serialized_state).unwrap();
31+
32+
// The serialized state MUST include a prefix indicating the current version of the library.
33+
let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap();
34+
assert_eq!(SemVer::from(state_sized), LIB_VERSION);
35+
36+
// All implementations MUST reject a serialized state from lib ver 0.0.0
37+
// This doesn't really serve any purpose except testing that all impl's have properly
38+
// used the helper functions.
39+
let mut ver0_serialized_state = serialized_state.clone();
40+
ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]);
41+
match S::from_suspended(ver0_serialized_state) {
42+
Err(SuspendableError::IncompatibleVersion) => { /* good */ }
43+
_ => {
44+
panic!("Expected IncompatibleVersion error")
45+
}
46+
}
47+
48+
// All implementations MUST reject a serialized state from a future MAJOR or MINOR version.
49+
let mut future_ver = LIB_VERSION;
50+
future_ver.major += 1;
51+
let mut futurever_serialized_state = serialized_state.clone();
52+
futurever_serialized_state[..3]
53+
.copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]);
54+
match S::from_suspended(futurever_serialized_state) {
55+
Err(SuspendableError::IncompatibleVersion) => { /* good */ }
56+
_ => {
57+
panic!("Expected IncompatibleVersion error")
58+
}
59+
}
60+
61+
let mut future_ver = LIB_VERSION;
62+
future_ver.minor += 1;
63+
let mut futurever_serialized_state = serialized_state.clone();
64+
futurever_serialized_state[..3]
65+
.copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]);
66+
match S::from_suspended(futurever_serialized_state) {
67+
Err(SuspendableError::IncompatibleVersion) => { /* good */ }
68+
_ => {
69+
panic!("Expected IncompatibleVersion error")
70+
}
71+
}
72+
73+
// but should accept anything on the same patch stream.
74+
let mut future_ver = LIB_VERSION;
75+
future_ver.patch += 1;
76+
let mut futurever_serialized_state = serialized_state.clone();
77+
futurever_serialized_state[..3]
78+
.copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]);
79+
let _deserialized_state = S::from_suspended(futurever_serialized_state).unwrap();
80+
81+
// ... even up to patch 255
82+
let mut future_ver = LIB_VERSION;
83+
future_ver.patch = 255;
84+
let mut futurever_serialized_state = serialized_state.clone();
85+
futurever_serialized_state[..3]
86+
.copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]);
87+
let _deserialized_state = S::from_suspended(futurever_serialized_state).unwrap();
88+
}
89+
}
90+
91+
pub struct TestFrameworkSuspendableKeyedState {}
92+
93+
impl TestFrameworkSuspendableKeyedState {
94+
pub fn new() -> Self {
95+
Self {}
96+
}
97+
98+
/// Test all the members of trait SerializableState.
99+
///
100+
/// Expects ta be handed an instance of the object that has some in-progress state to be serialized.
101+
pub fn test<
102+
const SERIALIZED_STATE_LEN: usize,
103+
S: SuspendableKeyed<SERIALIZED_STATE_LEN> + Clone,
104+
>(
105+
&self,
106+
instance: &S,
107+
key: &S::Key,
108+
) {
109+
// There's not a lot we can test here in the abstract, but we can test a few things to
110+
// ensure that the SerializableState trait has been impl'd correctly.
111+
112+
// we need to work on a clone because .serialize_state() moves self, which you can't do on a
113+
// borrowed instance.
114+
let instance_clone = instance.clone();
115+
116+
// You can serialize and then deserialize the state.
117+
let serialized_state = instance_clone.suspend();
118+
assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN);
119+
120+
let _deserialized_state = S::from_suspended(serialized_state, key).unwrap();
121+
122+
// The serialized state MUST include a prefix indicating the current version of the library.
123+
let state_sized: [u8; 3] = serialized_state[..3].try_into().unwrap();
124+
assert_eq!(SemVer::from(state_sized), LIB_VERSION);
125+
126+
// All implementations MUST reject a serialized state from lib ver 0.0.0
127+
// This doesn't really serve any purpose except testing that all impl's have properly
128+
// used the helper functions.
129+
let mut ver0_serialized_state = serialized_state.clone();
130+
ver0_serialized_state[..3].copy_from_slice(&[0, 0, 0]);
131+
match S::from_suspended(ver0_serialized_state, key) {
132+
Err(SuspendableError::IncompatibleVersion) => { /* good */ }
133+
_ => {
134+
panic!("Expected IncompatibleVersion error")
135+
}
136+
}
137+
138+
// All implementations MUST reject a serialized state from a future MAJOR or MINOR version.
139+
let mut future_ver = LIB_VERSION;
140+
future_ver.major += 1;
141+
let mut futurever_serialized_state = serialized_state.clone();
142+
futurever_serialized_state[..3]
143+
.copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]);
144+
match S::from_suspended(futurever_serialized_state, key) {
145+
Err(SuspendableError::IncompatibleVersion) => { /* good */ }
146+
_ => {
147+
panic!("Expected IncompatibleVersion error")
148+
}
149+
}
150+
151+
let mut future_ver = LIB_VERSION;
152+
future_ver.minor += 1;
153+
let mut futurever_serialized_state = serialized_state.clone();
154+
futurever_serialized_state[..3]
155+
.copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]);
156+
match S::from_suspended(futurever_serialized_state, key) {
157+
Err(SuspendableError::IncompatibleVersion) => { /* good */ }
158+
_ => {
159+
panic!("Expected IncompatibleVersion error")
160+
}
161+
}
162+
163+
// but should accept anything on the same patch stream.
164+
let mut future_ver = LIB_VERSION;
165+
future_ver.patch += 1;
166+
let mut futurever_serialized_state = serialized_state.clone();
167+
futurever_serialized_state[..3]
168+
.copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]);
169+
let _deserialized_state = S::from_suspended(futurever_serialized_state, key).unwrap();
170+
171+
// ... even up to patch 255
172+
let mut future_ver = LIB_VERSION;
173+
future_ver.patch = 255;
174+
let mut futurever_serialized_state = serialized_state.clone();
175+
futurever_serialized_state[..3]
176+
.copy_from_slice(&[future_ver.major, future_ver.minor, future_ver.patch]);
177+
let _deserialized_state = S::from_suspended(futurever_serialized_state, key).unwrap();
178+
}
179+
}

crypto/core/src/errors.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ pub enum RNGError {
7272
KeyMaterialError(KeyMaterialError),
7373
}
7474

75+
#[derive(Debug)]
76+
pub enum SuspendableError {
77+
/// The serialized state was produced by a library version incompatible with this one.
78+
IncompatibleVersion,
79+
/// The serialized state is malformed or corrupt.
80+
InvalidData,
81+
}
82+
7583
#[derive(Debug)]
7684
pub enum SignatureError {
7785
GenericError(&'static str),

crypto/core/src/key_material.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
//!
5151
//! See [do_hazardous_operations] for documentation and sample code.
5252
53-
use crate::errors::KeyMaterialError;
53+
use crate::errors::{KeyMaterialError, SuspendableError};
5454
use crate::traits::{RNG, Secret, SecurityStrength};
5555
use bouncycastle_utils::{ct, min};
5656

@@ -222,10 +222,15 @@ pub struct KeyMaterial<const KEY_LEN: usize> {
222222

223223
impl<const KEY_LEN: usize> Secret for KeyMaterial<KEY_LEN> {}
224224

225+
// The explicit `#[repr(u8)]` discriminants are the stable on-the-wire encoding used by
226+
// `SerializableState` implementations (see the `TryFrom<u8>` impl below). Pin each value to its
227+
// variant name: reordering variants is fine, but never reuse or renumber an existing discriminant,
228+
// or previously-serialized states will be misread.
225229
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
230+
#[repr(u8)]
226231
pub enum KeyType {
227232
/// The KeyMaterial is zeroized and MUST NOT be used for any cryptographic operation in this state.
228-
Zeroized,
233+
Zeroized = 0,
229234

230235
/// The KeyMaterial contains non-zero data of unknown key type.
231236
/// A KeyMaterial of key type Unknown will always have a [SecurityStrength] of [SecurityStrength::None].
@@ -235,19 +240,36 @@ pub enum KeyType {
235240
/// and must be done within a [do_hazardous_operations] closure.
236241
/// If you want to import key material directly into a known key type, use [KeyMaterial::from_bytes_as_type],
237242
/// which does not require a hazardous operations closure.
238-
Unknown,
243+
Unknown = 1,
239244

240245
/// The KeyMaterial contains data of full entropy and can be safely converted to any other key type.
241-
CryptographicRandom,
246+
CryptographicRandom = 2,
242247

243248
/// A seed for asymmetric private keys, RNGs, and other seed-based cryptographic objects.
244-
Seed,
249+
Seed = 3,
245250

246251
/// A MAC key.
247-
MACKey,
252+
MACKey = 4,
248253

249254
/// A key for a symmetric block or stream cipher.
250-
SymmetricCipherKey,
255+
SymmetricCipherKey = 5,
256+
}
257+
258+
impl TryFrom<u8> for KeyType {
259+
type Error = SuspendableError;
260+
261+
/// Inverse of `self as u8`; rejects unrecognized discriminants with [SuspendableError::InvalidData].
262+
fn try_from(value: u8) -> Result<Self, Self::Error> {
263+
Ok(match value {
264+
0 => Self::Zeroized,
265+
1 => Self::Unknown,
266+
2 => Self::CryptographicRandom,
267+
3 => Self::Seed,
268+
4 => Self::MACKey,
269+
5 => Self::SymmetricCipherKey,
270+
_ => return Err(SuspendableError::InvalidData),
271+
})
272+
}
251273
}
252274

253275
impl<const KEY_LEN: usize> Default for KeyMaterial<KEY_LEN> {

crypto/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77

88
pub mod errors;
99
pub mod key_material;
10+
pub mod suspendable_state;
1011
pub mod traits;

0 commit comments

Comments
 (0)