Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crypto/core-test-framework/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod hash;
pub mod kdf;
pub mod kem;
pub mod mac;
pub mod serializable_state;
pub mod signature;

pub const DUMMY_SEED_512: &[u8; 512] = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
Expand Down
43 changes: 43 additions & 0 deletions crypto/core-test-framework/src/serializable_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use bouncycastle_core::errors::CoreError;
use bouncycastle_core::serializable_state::LIB_VERSION;
use bouncycastle_core::traits::SerializableState;

pub struct TestFrameworkSerializableState {}

impl TestFrameworkSerializableState {
pub fn new() -> Self {
Self {}
}

/// Test all the members of trait SerializableState.
///
/// Expects ta be handed an instance of the object that has some in-progress state to be serialized.
pub fn test<const SERIALIZED_STATE_LEN: usize, S: SerializableState<SERIALIZED_STATE_LEN>>(
&self,
instance: &S,
) {
// There's not a lot we can test here in the abstract, but we can test a few things to
// ensure that the SerializableState trait has been impl'd correctly.

// You can serialize and then deserialize the state.
let serialized_state = instance.serialize_state();
assert_eq!(serialized_state.len(), SERIALIZED_STATE_LEN);

let _deserialized_state = S::from_serialized_state(serialized_state).unwrap();

// The serialized state MUST include a prefix indicating the current version of the library.
assert_eq!(serialized_state[..3], LIB_VERSION);

// All implementations MUST reject a serialized state from lib ver 0.0.0
// This doesn't really serve any purpose except testing that all impl's have properly
// used the helper functions.
let mut busted_serialized_state = serialized_state.clone();
busted_serialized_state[..3].copy_from_slice(&[0, 0, 0]);
match S::from_serialized_state(busted_serialized_state) {
Err(CoreError::IncompatibleVersion) => { /* good */ }
_ => {
panic!("Expected IncompatibleVersion error")
}
}
}
}
6 changes: 6 additions & 0 deletions crypto/core/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#[derive(Debug)]
pub enum CoreError {
IncompatibleVersion,
InvalidData,
}

#[derive(Debug)]
pub enum HashError {
GenericError(&'static str),
Expand Down
1 change: 1 addition & 0 deletions crypto/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

pub mod errors;
pub mod key_material;
pub mod serializable_state;
pub mod traits;
83 changes: 83 additions & 0 deletions crypto/core/src/serializable_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! Helper functions for standardizing serialization and deserialization of stateful objects.

use crate::errors::CoreError;

/// The current library version.
// There is almost certainly a more elegant way to do this.
pub const LIB_VERSION: [u8; 3] = [0, 1, 2];

/// Compare two library semantic versions in the standard C format:
/// * if a < b => -1
///* if a == b => 0
///* if a > b => 1
pub fn cmp_lib_ver(a: &[u8; 3], b: &[u8; 3]) -> i8 {
if a[0] < b[0] {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let a_ver = a[2] << 16 | a[1] << 8 | a[0]
let b_ver = b[2] << 16 | b[1] << 8 | b[0]

if a_ver < b_ver
   return -1;
else if ...

Also, give it a type such as SemVer([u8; 3]), and then impl Ord would be a much rusty way. Or even better,

struct SemVer(u32);
impl SemVer
   fn new(major: u8, minor: u8, patch: u8) -> Self {
     Self(major << 16 | ...)
   }

-1
} else if a[0] > b[0] {
1
}
// first component is equal
else if a[1] < b[1] {
-1
} else if a[1] > b[1] {
1
}
// first two components are equal
else if a[2] < b[2] {
-1
} else if a[2] > b[2] {
1
}
// all three components are equal
else {
0
}
}

#[test]
fn test_cmp_lib_ver() {
assert_eq!(cmp_lib_ver(&[0, 2, 1], &[1, 1, 1]), -1);
assert_eq!(cmp_lib_ver(&[2, 1, 1], &[1, 1, 1]), 1);
assert_eq!(cmp_lib_ver(&[1, 0, 2], &[1, 1, 1]), -1);
assert_eq!(cmp_lib_ver(&[1, 2, 0], &[1, 1, 1]), 1);
assert_eq!(cmp_lib_ver(&[1, 1, 0], &[1, 1, 1]), -1);
assert_eq!(cmp_lib_ver(&[1, 1, 2], &[1, 1, 1]), 1);
assert_eq!(cmp_lib_ver(&[1, 1, 1], &[1, 1, 1]), 0);
}

/// Puts the library version into the first three bytes of the state array.
///
/// Hands back a slice to the same array, starting after the version tag.
pub fn add_lib_ver<const SERIALIZED_LEN: usize>(state: &mut [u8; SERIALIZED_LEN]) -> &mut [u8] {
state[..3].copy_from_slice(&LIB_VERSION);
&mut state[3..]
}

/// A helper for deserializing an object's state
///
/// The state_out array must have length at least SERIALIZED_LEN - 3.
///
/// Returns the number of bytes written to state_out, or a [CoreError::IncompatibleVersion] if the
/// serialized state contains a version header earlier than the specified `not_before` version.
///
/// Note that for testability, this will always reject if the serialized state contains a version tag
/// of `[0,0,0]`.
///
/// Hands back a slice to the same array, starting after the version tag.
pub fn check_lib_ver<const SERIALIZED_LEN: usize>(
state: &[u8; SERIALIZED_LEN],
not_before: Option<[u8; 3]>,
) -> Result<&[u8], CoreError> {
let ver: [u8; 3] = state[..3].try_into().unwrap();

let not_before = not_before.unwrap_or([0, 0, 0]);

if cmp_lib_ver(&ver, &not_before) < 0 {
return Err(CoreError::IncompatibleVersion);
};
if ver == [0, 0, 0] {
return Err(CoreError::IncompatibleVersion);
};

Ok(&state[3..])
}
28 changes: 26 additions & 2 deletions crypto/core/src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Provides simplified abstracted APIs over classes of cryptigraphic primitives, such as Hash, KDF, etc.

use crate::errors::{HashError, KDFError, KEMError, MACError, RNGError, SignatureError};
use crate::errors::{CoreError, HashError, KDFError, KEMError, MACError, RNGError, SignatureError};
use crate::key_material::KeyMaterialTrait;
use core::fmt::{Debug, Display};
use core::marker::Sized;
Expand Down Expand Up @@ -37,7 +37,6 @@ pub trait Hash: Default {
/// Provide a chunk of data to be absorbed into the hashes.
/// `data` can be of any length, including zero bytes.
/// do_update() is intended to be used as part of a streaming interface, and so may by called multiple times.
// fn do_update(&mut self, data: &[u8]) -> Result<(), HashError>;
fn do_update(&mut self, data: &[u8]);

/// Finish absorbing input and produce the hashes output.
Expand Down Expand Up @@ -457,6 +456,31 @@ pub trait RNG: Default {
#[allow(drop_bounds)]
pub trait Secret: Drop + Debug + Display {}

/// Allows a stateful object to serialize its state so that it can be paused and resumed later,
/// potentially from a different host.
///
/// This is intended for situations where an object is being used through its streaming API
/// (do_update, do_final) and the operation wants to be paused to a cache, for example while waiting
/// for network IO.
///
/// This is not intended as a mechanism to clone the state of an object since in most cases `.clone()`
/// will be more straightforward.
pub trait SerializableState<const SERIALIZED_STATE_LEN: usize>: Sized {
/// Serialize the state of the object.
///
/// The serialized state MUST include a prefix indicating the version of the library that serialized it.
fn serialize_state(&self) -> [u8; SERIALIZED_STATE_LEN];

/// Create a new object from a serialized state.
///
/// Deserializers SHOULD check the version and reject serialized states from incompatible versions.
/// For example, if a given object made a breaking change to its serialization in version 1.2.3, then its
/// deserializer should reject serialized states from that version or older.
fn from_serialized_state(
serialized_state: [u8; SERIALIZED_STATE_LEN],
) -> Result<Self, CoreError>;
}

/// Pre-Hashed Signer is an extension to [Signer] that adds functionality specific to signature
/// primatives that can operate on a pre-hashed message instead of the full message.
pub trait PHSigner<
Expand Down
1 change: 1 addition & 0 deletions crypto/sha2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition.workspace = true
[dependencies]
bouncycastle-core.workspace = true
bouncycastle-utils.workspace = true
serde = { version = "1.0.228", features = ["derive"] }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it used?


[dev-dependencies]
criterion.workspace = true
Expand Down
6 changes: 3 additions & 3 deletions crypto/sha2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ mod sha256;
mod sha512;

pub use self::sha256::SHA256Internal;
pub use self::sha512::Sha512Internal;
pub use self::sha512::SHA512Internal;
use bouncycastle_core::traits::{Algorithm, HashAlgParams, SecurityStrength};

/*** String constants ***/
Expand All @@ -53,8 +53,8 @@ pub const SHA512_NAME: &str = "SHA512";
/*** pub types ***/
pub type SHA224 = SHA256Internal<SHA224Params>;
pub type SHA256 = SHA256Internal<SHA256Params>;
pub type SHA384 = Sha512Internal<SHA384Params>;
pub type SHA512 = Sha512Internal<SHA512Params>;
pub type SHA384 = SHA512Internal<SHA384Params>;
pub type SHA512 = SHA512Internal<SHA512Params>;

/*** Param traits ***/

Expand Down
81 changes: 71 additions & 10 deletions crypto/sha2/src/sha256.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::SHA2Params;
use bouncycastle_core::errors::HashError;
use bouncycastle_core::traits::{Hash, SecurityStrength};
use bouncycastle_core::errors::{CoreError, HashError};
use bouncycastle_core::serializable_state::{add_lib_ver, check_lib_ver};
use bouncycastle_core::traits::{Hash, SecurityStrength, SerializableState};
use bouncycastle_utils::min;
use core::slice;

Expand Down Expand Up @@ -45,11 +46,9 @@ fn theta1(x: u32) -> u32 {
x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10)
}

// todo -- cleanup
// #[derive(Clone, Copy)]
#[derive(Clone)]
pub(crate) struct Sha256State<PARAMS: SHA2Params> {
_params: std::marker::PhantomData<PARAMS>,
_params: core::marker::PhantomData<PARAMS>,
h: [u32; 8],
}

Expand All @@ -63,7 +62,7 @@ impl<PARAMS: SHA2Params> Sha256State<PARAMS> {
pub(crate) fn new() -> Self {
match PARAMS::OUTPUT_LEN * 8 {
224 => Self {
_params: std::marker::PhantomData,
_params: core::marker::PhantomData,
h: [
0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, 0xFFC00B31, 0x68581511,
0x64F98FA7, 0xBEFA4FA4,
Expand Down Expand Up @@ -145,11 +144,9 @@ impl<PARAMS: SHA2Params> Sha256State<PARAMS> {
}
}

// todo -- cleanup
// #[derive(Clone, Copy)]
#[derive(Clone)]
pub struct SHA256Internal<PARAMS: SHA2Params> {
_params: std::marker::PhantomData<PARAMS>,
_params: core::marker::PhantomData<PARAMS>,
state: Sha256State<PARAMS>,
byte_count: u64,
x_buf: [u8; 64],
Expand All @@ -166,7 +163,7 @@ impl<PARAMS: SHA2Params> Drop for SHA256Internal<PARAMS> {
impl<PARAMS: SHA2Params> SHA256Internal<PARAMS> {
pub fn new() -> Self {
Self {
_params: std::marker::PhantomData,
_params: core::marker::PhantomData,
state: Sha256State::<PARAMS>::new(),
byte_count: 0,
x_buf: [0; 64],
Expand Down Expand Up @@ -306,3 +303,67 @@ impl<PARAMS: SHA2Params> Hash for SHA256Internal<PARAMS> {
SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2)
}
}

impl<PARAMS: SHA2Params> SerializableState<108> for SHA256Internal<PARAMS> {
fn serialize_state(&self) -> [u8; 108] {
let mut out_to_return = [0u8; 108];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where 108 and other sizes coming from?


// insert the version tag
let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap();

// state.h: [u32; 8]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is state.h?

// 4 * 8 = 32
for i in 0..8 {
out[i * 4..(i * 4) + 4].copy_from_slice(&self.state.h[i].to_le_bytes());
}

// byte_count: u64
out[32..40].copy_from_slice(&self.byte_count.to_le_bytes());

// x_buf: [u8; 64]
out[40..104].copy_from_slice(&self.x_buf);

// x_buf_off: usize
// in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, usize can be u32 on 32-bit platforms.

debug_assert!(self.x_buf_off < 64);
out[104] = self.x_buf_off as u8;

out_to_return
}

fn from_serialized_state(serialized_state: [u8; 108]) -> Result<Self, CoreError> {
// check the version tag
// At the moment, we have no not_before version to specify.
let input: &[u8; 105] = check_lib_ver(&serialized_state, None)?.try_into().unwrap();

// state.h: [u32; 8]
// 4 * 8 = 32
let mut h = [0u32; 8];
for i in 0..8 {
h[i] = u32::from_le_bytes(input[i * 4..(i * 4) + 4].try_into().unwrap());
}

// byte_count: u64
let byte_count: u64 = u64::from_le_bytes(input[32..40].try_into().unwrap());

// x_buf: [u8; 64]
let x_buf: [u8; 64] = input[40..104].try_into().unwrap();

// x_buf_off: usize
// in general, a usize should be serialized into a u64, but in this case, it can't ever be larger than 64
let x_buf_off: usize = input[104] as usize;
if x_buf_off >= 64 {
return Err(CoreError::InvalidData);
}

// Construct the object
let state = Sha256State { _params: core::marker::PhantomData, h };
Ok(SHA256Internal {
_params: core::marker::PhantomData,
state,
byte_count,
x_buf,
x_buf_off,
})
}
}
Loading
Loading