From 1915d6258d598bb88bb6f1412c99586a96aebbfc Mon Sep 17 00:00:00 2001 From: sanketh Date: Sat, 27 Jul 2024 21:28:37 -0400 Subject: [PATCH 1/9] Add support for XOF squeeze --- openssl/src/hash.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index f2f2698f3..641192cdf 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -199,6 +199,7 @@ unsafe impl Send for MessageDigest {} enum State { Reset, Updated, + Squeeze, Finalized, } @@ -265,6 +266,7 @@ impl Hasher { Updated => { self.finish()?; } + Squeeze => (), Finalized => (), } unsafe { @@ -290,6 +292,21 @@ impl Hasher { Ok(()) } + /// Squeezes buf out of the hasher. + /// The output will be as long as the buf. + #[cfg(ossl330)] + pub fn squeeze_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::EVP_DigestSqueeze( + self.ctx, + buf.as_mut_ptr(), + buf.len(), + ))?; + self.state = Squeeze; + Ok(()) + } + } + /// Returns the hash of the data written and resets the non-XOF hasher. pub fn finish(&mut self) -> Result { if self.state == Finalized { @@ -486,6 +503,21 @@ mod tests { assert_eq!(buf, expected); } + /// Squeezes the expected length by doing two squeezes. + #[cfg(ossl330)] + fn hash_xof_squeeze_test(hashtype: MessageDigest, hashtest: &(&str, &str)) { + let data = Vec::from_hex(hashtest.0).unwrap(); + let mut h = Hasher::new(hashtype).unwrap(); + h.update(&data).unwrap(); + + let expected = Vec::from_hex(hashtest.1).unwrap(); + let mut buf = vec![0; expected.len()]; + assert!(expected.len() > 10); + h.squeeze_xof(&mut buf[..10]).unwrap(); + h.squeeze_xof(&mut buf[10..]).unwrap(); + assert_eq!(buf, expected); + } + fn hash_recycle_test(h: &mut Hasher, hashtest: &(&str, &str)) { h.write_all(&Vec::from_hex(hashtest.0).unwrap()).unwrap(); let res = h.finish().unwrap(); @@ -715,6 +747,8 @@ mod tests { for test in tests.iter() { hash_xof_test(MessageDigest::shake_128(), test); + #[cfg(ossl330)] + hash_xof_squeeze_test(MessageDigest::shake_128(), test); } assert_eq!(MessageDigest::shake_128().block_size(), 168); @@ -735,6 +769,8 @@ mod tests { for test in tests.iter() { hash_xof_test(MessageDigest::shake_256(), test); + #[cfg(ossl330)] + hash_xof_squeeze_test(MessageDigest::shake_256(), test); } assert_eq!(MessageDigest::shake_256().block_size(), 136); From dc7db607fd85fc29e12ed6dce80603a6e446856f Mon Sep 17 00:00:00 2001 From: sanketh Date: Wed, 7 Aug 2024 22:28:55 -0400 Subject: [PATCH 2/9] update docstring --- openssl/src/hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index 641192cdf..5eb4a4d3c 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -292,7 +292,7 @@ impl Hasher { Ok(()) } - /// Squeezes buf out of the hasher. + /// Squeezes buf out of the hasher. Can be called multiple times, unlike `finish_xof`. /// The output will be as long as the buf. #[cfg(ossl330)] pub fn squeeze_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> { From 003e6f2cc93a2e04cc173684c00637d00f951821 Mon Sep 17 00:00:00 2001 From: sanketh Date: Wed, 7 Aug 2024 22:35:58 -0400 Subject: [PATCH 3/9] address clippy warning --- openssl/src/hash.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index 5eb4a4d3c..a917eeb30 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -199,6 +199,7 @@ unsafe impl Send for MessageDigest {} enum State { Reset, Updated, + #[cfg(ossl330)] Squeeze, Finalized, } @@ -266,6 +267,7 @@ impl Hasher { Updated => { self.finish()?; } + #[cfg(ossl330)] Squeeze => (), Finalized => (), } From 70496c9f7dfe3e7ab3d8bf4faf6e1465aaad066f Mon Sep 17 00:00:00 2001 From: sanketh Date: Wed, 7 Aug 2024 23:13:21 -0400 Subject: [PATCH 4/9] address review comments --- openssl/src/hash.rs | 50 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index a917eeb30..6053658ea 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -280,8 +280,11 @@ impl Hasher { /// Feeds data into the hasher. pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> { - if self.state == Finalized { - self.init()?; + match self.state { + #[cfg(ossl330)] + Squeeze => self.init()?, + Finalized => self.init()?, + _ => {} } unsafe { cvt(ffi::EVP_DigestUpdate( @@ -298,6 +301,9 @@ impl Hasher { /// The output will be as long as the buf. #[cfg(ossl330)] pub fn squeeze_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> { + if self.state == Finalized { + self.init()?; + } unsafe { cvt(ffi::EVP_DigestSqueeze( self.ctx, @@ -311,8 +317,11 @@ impl Hasher { /// Returns the hash of the data written and resets the non-XOF hasher. pub fn finish(&mut self) -> Result { - if self.state == Finalized { - self.init()?; + match self.state { + #[cfg(ossl330)] + Squeeze => self.init()?, + Finalized => self.init()?, + _ => {} } unsafe { #[cfg(not(boringssl))] @@ -337,8 +346,11 @@ impl Hasher { /// The hash will be as long as the buf. #[cfg(ossl111)] pub fn finish_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> { - if self.state == Finalized { - self.init()?; + match self.state { + #[cfg(ossl330)] + Squeeze => self.init()?, + Finalized => self.init()?, + _ => {} } unsafe { cvt(ffi::EVP_DigestFinalXOF( @@ -576,6 +588,32 @@ mod tests { assert_eq!(&*res, &*null); } + #[cfg(ossl330)] + #[test] + fn test_finish_then_squeeze() { + let digest = MessageDigest::shake_128(); + let mut h = Hasher::new(digest).unwrap(); + let mut buf = vec![0; digest.size()]; + h.finish_xof(&mut buf).unwrap(); + h.squeeze_xof(&mut buf).unwrap(); + let null = hash(digest, &[]).unwrap(); + assert_eq!(&*buf, &*null); + } + + #[cfg(ossl330)] + #[test] + fn test_squeeze_then_update() { + let digest = MessageDigest::shake_128(); + let data = Vec::from_hex(MD5_TESTS[6].0).unwrap(); + let mut h = Hasher::new(digest).unwrap(); + let mut buf = vec![0; digest.size()]; + h.squeeze_xof(&mut buf).unwrap(); + h.update(&data).unwrap(); + h.squeeze_xof(&mut buf).unwrap(); + let null = hash(digest, &data).unwrap(); + assert_eq!(&*buf, &*null); + } + #[test] #[allow(clippy::redundant_clone)] fn test_clone() { From 4ad92493646f11a9e689e2bb6f4231e04a15e001 Mon Sep 17 00:00:00 2001 From: sanketh Date: Mon, 2 Sep 2024 14:48:08 -0400 Subject: [PATCH 5/9] do not silently reinit after squeeze --- openssl/src/hash.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index 6053658ea..c74b594d3 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -281,8 +281,6 @@ impl Hasher { /// Feeds data into the hasher. pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> { match self.state { - #[cfg(ossl330)] - Squeeze => self.init()?, Finalized => self.init()?, _ => {} } @@ -301,9 +299,6 @@ impl Hasher { /// The output will be as long as the buf. #[cfg(ossl330)] pub fn squeeze_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> { - if self.state == Finalized { - self.init()?; - } unsafe { cvt(ffi::EVP_DigestSqueeze( self.ctx, @@ -318,8 +313,6 @@ impl Hasher { /// Returns the hash of the data written and resets the non-XOF hasher. pub fn finish(&mut self) -> Result { match self.state { - #[cfg(ossl330)] - Squeeze => self.init()?, Finalized => self.init()?, _ => {} } @@ -347,8 +340,6 @@ impl Hasher { #[cfg(ossl111)] pub fn finish_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> { match self.state { - #[cfg(ossl330)] - Squeeze => self.init()?, Finalized => self.init()?, _ => {} } @@ -595,9 +586,7 @@ mod tests { let mut h = Hasher::new(digest).unwrap(); let mut buf = vec![0; digest.size()]; h.finish_xof(&mut buf).unwrap(); - h.squeeze_xof(&mut buf).unwrap(); - let null = hash(digest, &[]).unwrap(); - assert_eq!(&*buf, &*null); + h.squeeze_xof(&mut buf).expect_err("squeezing after finalize should fail"); } #[cfg(ossl330)] @@ -608,10 +597,18 @@ mod tests { let mut h = Hasher::new(digest).unwrap(); let mut buf = vec![0; digest.size()]; h.squeeze_xof(&mut buf).unwrap(); - h.update(&data).unwrap(); + h.update(&data).expect_err("updating after squeeze should fail"); + } + + #[cfg(ossl330)] + #[test] + fn test_squeeze_then_finalize() { + let digest = MessageDigest::shake_128(); + let data = Vec::from_hex(MD5_TESTS[6].0).unwrap(); + let mut h = Hasher::new(digest).unwrap(); + let mut buf = vec![0; digest.size()]; h.squeeze_xof(&mut buf).unwrap(); - let null = hash(digest, &data).unwrap(); - assert_eq!(&*buf, &*null); + h.finish_xof(&mut buf).expect_err("finalize after squeeze should fail"); } #[test] From e35ec91dbce906011be9415a3949aee2b7103344 Mon Sep 17 00:00:00 2001 From: sanketh Date: Mon, 2 Sep 2024 15:13:06 -0400 Subject: [PATCH 6/9] minimize diffs --- openssl/src/hash.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index c74b594d3..fb039817b 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -280,9 +280,8 @@ impl Hasher { /// Feeds data into the hasher. pub fn update(&mut self, data: &[u8]) -> Result<(), ErrorStack> { - match self.state { - Finalized => self.init()?, - _ => {} + if self.state == Finalized { + self.init()?; } unsafe { cvt(ffi::EVP_DigestUpdate( @@ -312,9 +311,8 @@ impl Hasher { /// Returns the hash of the data written and resets the non-XOF hasher. pub fn finish(&mut self) -> Result { - match self.state { - Finalized => self.init()?, - _ => {} + if self.state == Finalized { + self.init()?; } unsafe { #[cfg(not(boringssl))] @@ -339,9 +337,8 @@ impl Hasher { /// The hash will be as long as the buf. #[cfg(ossl111)] pub fn finish_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> { - match self.state { - Finalized => self.init()?, - _ => {} + if self.state == Finalized { + self.init()?; } unsafe { cvt(ffi::EVP_DigestFinalXOF( @@ -604,7 +601,6 @@ mod tests { #[test] fn test_squeeze_then_finalize() { let digest = MessageDigest::shake_128(); - let data = Vec::from_hex(MD5_TESTS[6].0).unwrap(); let mut h = Hasher::new(digest).unwrap(); let mut buf = vec![0; digest.size()]; h.squeeze_xof(&mut buf).unwrap(); From 680136daef7a90ebc7af5256d32352d328ab38ad Mon Sep 17 00:00:00 2001 From: sanketh Date: Mon, 2 Sep 2024 15:14:36 -0400 Subject: [PATCH 7/9] run cargo fmt --- openssl/src/hash.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index fb039817b..5d0203294 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -583,7 +583,8 @@ mod tests { let mut h = Hasher::new(digest).unwrap(); let mut buf = vec![0; digest.size()]; h.finish_xof(&mut buf).unwrap(); - h.squeeze_xof(&mut buf).expect_err("squeezing after finalize should fail"); + h.squeeze_xof(&mut buf) + .expect_err("squeezing after finalize should fail"); } #[cfg(ossl330)] @@ -594,7 +595,8 @@ mod tests { let mut h = Hasher::new(digest).unwrap(); let mut buf = vec![0; digest.size()]; h.squeeze_xof(&mut buf).unwrap(); - h.update(&data).expect_err("updating after squeeze should fail"); + h.update(&data) + .expect_err("updating after squeeze should fail"); } #[cfg(ossl330)] @@ -604,7 +606,8 @@ mod tests { let mut h = Hasher::new(digest).unwrap(); let mut buf = vec![0; digest.size()]; h.squeeze_xof(&mut buf).unwrap(); - h.finish_xof(&mut buf).expect_err("finalize after squeeze should fail"); + h.finish_xof(&mut buf) + .expect_err("finalize after squeeze should fail"); } #[test] From 769f0b22d29044814926ea8516d035e79e8d48cd Mon Sep 17 00:00:00 2001 From: sanketh Date: Fri, 20 Dec 2024 19:21:53 -0500 Subject: [PATCH 8/9] disallow updates after squeezes --- openssl/src/hash.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index 5d0203294..117bb2fb0 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -283,6 +283,18 @@ impl Hasher { if self.state == Finalized { self.init()?; } + if self.state == Squeeze { + // [`EVP_DigestUpdate`], depending on the implementation, may allow Updates after Squeezes. + // But, [FIPS 202], as shown in Figure 7, has a distinguished absorbing phase followed by a squeezing phase. + // Indeed, the [`sha3.c`] implmentation disallows Updates after Squeezes. + // For consistency, we always return an error when Update is called after Squeeze. + // + // [`EVP_DigestUpdate`]: https://github.com/openssl/openssl/blob/b3bb214720f20f3b126ae4b9c330e9a48b835415/crypto/evp/digest.c#L385-L393 + // [FIPS 202]: https://dx.doi.org/10.6028/NIST.FIPS.202 + // [`sha3.c`]: https://github.com/openssl/openssl/blob/b3bb214720f20f3b126ae4b9c330e9a48b835415/crypto/sha/sha3.c#L52-L63 + let errors = ErrorStack::get(); + return Err(errors); + } unsafe { cvt(ffi::EVP_DigestUpdate( self.ctx, From 95159d0d7ccb47eb38cf12554f938d29beab25ce Mon Sep 17 00:00:00 2001 From: sanketh Date: Fri, 20 Dec 2024 19:38:15 -0500 Subject: [PATCH 9/9] oops forgot compiler directive --- openssl/src/hash.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/openssl/src/hash.rs b/openssl/src/hash.rs index 8efb4239f..f66c5ce01 100644 --- a/openssl/src/hash.rs +++ b/openssl/src/hash.rs @@ -278,6 +278,7 @@ impl Hasher { if self.state == Finalized { self.init()?; } + #[cfg(ossl330)] if self.state == Squeeze { // [`EVP_DigestUpdate`], depending on the implementation, may allow Updates after Squeezes. // But, [FIPS 202], as shown in Figure 7, has a distinguished absorbing phase followed by a squeezing phase.