-
Notifications
You must be signed in to change notification settings - Fork 15
New feature: SerializableState #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/0.1.2alpha
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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") | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,4 +7,5 @@ | |
|
|
||
| pub mod errors; | ||
| pub mod key_material; | ||
| pub mod serializable_state; | ||
| pub mod traits; | ||
| 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] { | ||
| -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, ¬_before) < 0 { | ||
| return Err(CoreError::IncompatibleVersion); | ||
| }; | ||
| if ver == [0, 0, 0] { | ||
| return Err(CoreError::IncompatibleVersion); | ||
| }; | ||
|
|
||
| Ok(&state[3..]) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ edition.workspace = true | |
| [dependencies] | ||
| bouncycastle-core.workspace = true | ||
| bouncycastle-utils.workspace = true | ||
| serde = { version = "1.0.228", features = ["derive"] } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it used? |
||
|
|
||
| [dev-dependencies] | ||
| criterion.workspace = true | ||
|
|
||
| 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; | ||
|
|
||
|
|
@@ -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], | ||
| } | ||
|
|
||
|
|
@@ -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, | ||
|
|
@@ -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], | ||
|
|
@@ -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], | ||
|
|
@@ -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]; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
| }) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, give it a type such as SemVer([u8; 3]), and then
impl Ordwould be a much rusty way. Or even better,