diff --git a/crypto/core-test-framework/src/lib.rs b/crypto/core-test-framework/src/lib.rs index 92f7215..d46b0cb 100644 --- a/crypto/core-test-framework/src/lib.rs +++ b/crypto/core-test-framework/src/lib.rs @@ -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"; diff --git a/crypto/core-test-framework/src/serializable_state.rs b/crypto/core-test-framework/src/serializable_state.rs new file mode 100644 index 0000000..f79c496 --- /dev/null +++ b/crypto/core-test-framework/src/serializable_state.rs @@ -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>( + &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") + } + } + } +} diff --git a/crypto/core/src/errors.rs b/crypto/core/src/errors.rs index b2eaf0c..111609c 100644 --- a/crypto/core/src/errors.rs +++ b/crypto/core/src/errors.rs @@ -1,3 +1,9 @@ +#[derive(Debug)] +pub enum CoreError { + IncompatibleVersion, + InvalidData, +} + #[derive(Debug)] pub enum HashError { GenericError(&'static str), diff --git a/crypto/core/src/lib.rs b/crypto/core/src/lib.rs index 379e54c..9516907 100644 --- a/crypto/core/src/lib.rs +++ b/crypto/core/src/lib.rs @@ -7,4 +7,5 @@ pub mod errors; pub mod key_material; +pub mod serializable_state; pub mod traits; diff --git a/crypto/core/src/serializable_state.rs b/crypto/core/src/serializable_state.rs new file mode 100644 index 0000000..3015e24 --- /dev/null +++ b/crypto/core/src/serializable_state.rs @@ -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(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( + 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..]) +} diff --git a/crypto/core/src/traits.rs b/crypto/core/src/traits.rs index dbf40ea..002833d 100644 --- a/crypto/core/src/traits.rs +++ b/crypto/core/src/traits.rs @@ -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; @@ -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. @@ -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: 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; +} + /// 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< diff --git a/crypto/sha2/Cargo.toml b/crypto/sha2/Cargo.toml index a620a6a..ae8a52d 100644 --- a/crypto/sha2/Cargo.toml +++ b/crypto/sha2/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] bouncycastle-core.workspace = true bouncycastle-utils.workspace = true +serde = { version = "1.0.228", features = ["derive"] } [dev-dependencies] criterion.workspace = true diff --git a/crypto/sha2/src/lib.rs b/crypto/sha2/src/lib.rs index b3582fc..d8a4544 100644 --- a/crypto/sha2/src/lib.rs +++ b/crypto/sha2/src/lib.rs @@ -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 ***/ @@ -53,8 +53,8 @@ pub const SHA512_NAME: &str = "SHA512"; /*** pub types ***/ pub type SHA224 = SHA256Internal; pub type SHA256 = SHA256Internal; -pub type SHA384 = Sha512Internal; -pub type SHA512 = Sha512Internal; +pub type SHA384 = SHA512Internal; +pub type SHA512 = SHA512Internal; /*** Param traits ***/ diff --git a/crypto/sha2/src/sha256.rs b/crypto/sha2/src/sha256.rs index 7be7f47..6995642 100644 --- a/crypto/sha2/src/sha256.rs +++ b/crypto/sha2/src/sha256.rs @@ -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: std::marker::PhantomData, + _params: core::marker::PhantomData, h: [u32; 8], } @@ -63,7 +62,7 @@ impl Sha256State { 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 Sha256State { } } -// todo -- cleanup -// #[derive(Clone, Copy)] #[derive(Clone)] pub struct SHA256Internal { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, state: Sha256State, byte_count: u64, x_buf: [u8; 64], @@ -166,7 +163,7 @@ impl Drop for SHA256Internal { impl SHA256Internal { pub fn new() -> Self { Self { - _params: std::marker::PhantomData, + _params: core::marker::PhantomData, state: Sha256State::::new(), byte_count: 0, x_buf: [0; 64], @@ -306,3 +303,67 @@ impl Hash for SHA256Internal { SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) } } + +impl SerializableState<108> for SHA256Internal { + fn serialize_state(&self) -> [u8; 108] { + let mut out_to_return = [0u8; 108]; + + // insert the version tag + let out: &mut [u8; 105] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + + // state.h: [u32; 8] + // 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 + 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 { + // 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, + }) + } +} diff --git a/crypto/sha2/src/sha512.rs b/crypto/sha2/src/sha512.rs index b4ae990..0c5ed13 100644 --- a/crypto/sha2/src/sha512.rs +++ b/crypto/sha2/src/sha512.rs @@ -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; @@ -160,7 +161,7 @@ impl Sha512State { // todo -- cleanup // #[derive(Clone, Copy)] #[derive(Clone)] -pub struct Sha512Internal { +pub struct SHA512Internal { _params: std::marker::PhantomData, state: Sha512State, byte_count: u64, // NOTE We only support 2^67 bits, not the full 2^128 @@ -168,13 +169,13 @@ pub struct Sha512Internal { x_buf_off: usize, } -impl Drop for Sha512Internal { +impl Drop for SHA512Internal { fn drop(&mut self) { self.x_buf.fill(0); } } -impl Sha512Internal { +impl SHA512Internal { pub fn new() -> Self { Self { _params: std::marker::PhantomData, @@ -186,13 +187,13 @@ impl Sha512Internal { } } -impl Default for Sha512Internal { +impl Default for SHA512Internal { fn default() -> Self { Self::new() } } -impl Hash for Sha512Internal { +impl Hash for SHA512Internal { /// As per FIPS 180-4 Figure 1 fn block_bitlen(&self) -> usize { 1024 @@ -317,3 +318,67 @@ impl Hash for Sha512Internal { SecurityStrength::from_bytes(PARAMS::OUTPUT_LEN / 2) } } + +impl SerializableState<204> for SHA512Internal { + fn serialize_state(&self) -> [u8; 204] { + let mut out_to_return = [0u8; 204]; + + // insert the version tag + let out: &mut [u8; 201] = add_lib_ver(&mut out_to_return).try_into().unwrap(); + + // state.h: [u64; 8] + // 8 * 8 = 64 + for i in 0..8 { + out[i * 8..(i * 8) + 8].copy_from_slice(&self.state.h[i].to_le_bytes()); + } + + // byte_count: u64 + out[64..72].copy_from_slice(&self.byte_count.to_le_bytes()); + + // x_buf: [u8; 128] + out[72..200].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 128 + debug_assert!(self.x_buf_off < 128); + out[200] = self.x_buf_off as u8; + + out_to_return + } + + fn from_serialized_state(serialized_state: [u8; 204]) -> Result { + // check the version tag + // At the moment, we have no not_before version to specify. + let input: &[u8; 201] = check_lib_ver(&serialized_state, None)?.try_into().unwrap(); + + // state.h: [u64; 8] + // 8 * 8 = 64 + let mut h = [0u64; 8]; + for i in 0..8 { + h[i] = u64::from_le_bytes(input[i * 8..(i * 8) + 8].try_into().unwrap()); + } + + // byte_count: u64 + let byte_count: u64 = u64::from_le_bytes(input[64..72].try_into().unwrap()); + + // x_buf: [u8; 128] + let x_buf: [u8; 128] = input[72..200].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 128 + let x_buf_off: usize = input[200] as usize; + if x_buf_off >= 128 { + return Err(CoreError::InvalidData); + } + + // Construct the object + let state = Sha512State { _params: core::marker::PhantomData, h }; + Ok(SHA512Internal { + _params: core::marker::PhantomData, + state, + byte_count, + x_buf, + x_buf_off, + }) + } +} diff --git a/crypto/sha2/tests/sha2_tests.rs b/crypto/sha2/tests/sha2_tests.rs index 7d3e239..442b94b 100644 --- a/crypto/sha2/tests/sha2_tests.rs +++ b/crypto/sha2/tests/sha2_tests.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod sha2_tests { + use bouncycastle_core::errors::CoreError; use bouncycastle_core::traits::{Algorithm, Hash, HashAlgParams, SecurityStrength}; use bouncycastle_core_test_framework::DUMMY_SEED_512; use bouncycastle_core_test_framework::hash::TestFrameworkHash; @@ -89,4 +90,66 @@ mod sha2_tests { assert_eq!(SHA384::default().max_security_strength(), SecurityStrength::_192bit); assert_eq!(SHA512::default().max_security_strength(), SecurityStrength::_256bit); } + + #[test] + fn test_serializable_state() { + use bouncycastle_core::traits::SerializableState; + use bouncycastle_core_test_framework::serializable_state::TestFrameworkSerializableState; + + let str = "Colorless green ideas sleep furiously"; + + // SHA256 + let mut sha256 = SHA256::new(); + sha256.do_update(str.as_bytes()); + + // do the default tests + let test_framework = TestFrameworkSerializableState::new(); + test_framework.test(&sha256); + + // now let's serialize the in-progress state + let serialized_state = sha256.serialize_state(); + + // finish the hash + let output = sha256.do_final(); + + // then load from state and finish the hash and make sure we get the same thing + let sha2_from_state = SHA256::from_serialized_state(serialized_state).unwrap(); + let output2 = sha2_from_state.do_final(); + assert_eq!(output, output2); + + // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested + let mut busted_state = serialized_state.clone(); + busted_state[3 + 104] = 65; + match SHA256::from_serialized_state(busted_state) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error"), + } + + // SHA512 + let mut sha512 = SHA512::new(); + sha512.do_update(str.as_bytes()); + + // do the default tests + let test_framework = TestFrameworkSerializableState::new(); + test_framework.test(&sha512); + + // now let's serialize the in-progress state + let serialized_state = sha512.serialize_state(); + + // finish the hash + let output = sha512.do_final(); + + // then load from state and finish the hash and make sure we get the same thing + let sha2_from_state = SHA512::from_serialized_state(serialized_state).unwrap(); + let output2 = sha2_from_state.do_final(); + assert_eq!(output, output2); + + // also, give it a busted x_buf_off, just to satisfy mutants that that's been tested + let mut busted_state = serialized_state.clone(); + busted_state[3 + 200] = 129; + match SHA512::from_serialized_state(busted_state) { + Err(CoreError::InvalidData) => { /* good */ } + _ => panic!("Expected an error"), + } + } }