Skip to content

Commit b255dee

Browse files
authored
Merge pull request #2275 from initsecret/support-xof-squeeze
Expose EVP_DigestSqueeze from Hasher
2 parents 538a5cb + 95159d0 commit b255dee

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed

openssl/src/hash.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ unsafe impl Send for MessageDigest {}
194194
enum State {
195195
Reset,
196196
Updated,
197+
#[cfg(ossl330)]
198+
Squeeze,
197199
Finalized,
198200
}
199201

@@ -260,6 +262,8 @@ impl Hasher {
260262
Updated => {
261263
self.finish()?;
262264
}
265+
#[cfg(ossl330)]
266+
Squeeze => (),
263267
Finalized => (),
264268
}
265269
unsafe {
@@ -274,6 +278,19 @@ impl Hasher {
274278
if self.state == Finalized {
275279
self.init()?;
276280
}
281+
#[cfg(ossl330)]
282+
if self.state == Squeeze {
283+
// [`EVP_DigestUpdate`], depending on the implementation, may allow Updates after Squeezes.
284+
// But, [FIPS 202], as shown in Figure 7, has a distinguished absorbing phase followed by a squeezing phase.
285+
// Indeed, the [`sha3.c`] implmentation disallows Updates after Squeezes.
286+
// For consistency, we always return an error when Update is called after Squeeze.
287+
//
288+
// [`EVP_DigestUpdate`]: https://github.com/openssl/openssl/blob/b3bb214720f20f3b126ae4b9c330e9a48b835415/crypto/evp/digest.c#L385-L393
289+
// [FIPS 202]: https://dx.doi.org/10.6028/NIST.FIPS.202
290+
// [`sha3.c`]: https://github.com/openssl/openssl/blob/b3bb214720f20f3b126ae4b9c330e9a48b835415/crypto/sha/sha3.c#L52-L63
291+
let errors = ErrorStack::get();
292+
return Err(errors);
293+
}
277294
unsafe {
278295
cvt(ffi::EVP_DigestUpdate(
279296
self.ctx,
@@ -285,6 +302,21 @@ impl Hasher {
285302
Ok(())
286303
}
287304

305+
/// Squeezes buf out of the hasher. Can be called multiple times, unlike `finish_xof`.
306+
/// The output will be as long as the buf.
307+
#[cfg(ossl330)]
308+
pub fn squeeze_xof(&mut self, buf: &mut [u8]) -> Result<(), ErrorStack> {
309+
unsafe {
310+
cvt(ffi::EVP_DigestSqueeze(
311+
self.ctx,
312+
buf.as_mut_ptr(),
313+
buf.len(),
314+
))?;
315+
self.state = Squeeze;
316+
Ok(())
317+
}
318+
}
319+
288320
/// Returns the hash of the data written and resets the non-XOF hasher.
289321
pub fn finish(&mut self) -> Result<DigestBytes, ErrorStack> {
290322
if self.state == Finalized {
@@ -481,6 +513,21 @@ mod tests {
481513
assert_eq!(buf, expected);
482514
}
483515

516+
/// Squeezes the expected length by doing two squeezes.
517+
#[cfg(ossl330)]
518+
fn hash_xof_squeeze_test(hashtype: MessageDigest, hashtest: &(&str, &str)) {
519+
let data = Vec::from_hex(hashtest.0).unwrap();
520+
let mut h = Hasher::new(hashtype).unwrap();
521+
h.update(&data).unwrap();
522+
523+
let expected = Vec::from_hex(hashtest.1).unwrap();
524+
let mut buf = vec![0; expected.len()];
525+
assert!(expected.len() > 10);
526+
h.squeeze_xof(&mut buf[..10]).unwrap();
527+
h.squeeze_xof(&mut buf[10..]).unwrap();
528+
assert_eq!(buf, expected);
529+
}
530+
484531
fn hash_recycle_test(h: &mut Hasher, hashtest: &(&str, &str)) {
485532
h.write_all(&Vec::from_hex(hashtest.0).unwrap()).unwrap();
486533
let res = h.finish().unwrap();
@@ -537,6 +584,40 @@ mod tests {
537584
assert_eq!(&*res, &*null);
538585
}
539586

587+
#[cfg(ossl330)]
588+
#[test]
589+
fn test_finish_then_squeeze() {
590+
let digest = MessageDigest::shake_128();
591+
let mut h = Hasher::new(digest).unwrap();
592+
let mut buf = vec![0; digest.size()];
593+
h.finish_xof(&mut buf).unwrap();
594+
h.squeeze_xof(&mut buf)
595+
.expect_err("squeezing after finalize should fail");
596+
}
597+
598+
#[cfg(ossl330)]
599+
#[test]
600+
fn test_squeeze_then_update() {
601+
let digest = MessageDigest::shake_128();
602+
let data = Vec::from_hex(MD5_TESTS[6].0).unwrap();
603+
let mut h = Hasher::new(digest).unwrap();
604+
let mut buf = vec![0; digest.size()];
605+
h.squeeze_xof(&mut buf).unwrap();
606+
h.update(&data)
607+
.expect_err("updating after squeeze should fail");
608+
}
609+
610+
#[cfg(ossl330)]
611+
#[test]
612+
fn test_squeeze_then_finalize() {
613+
let digest = MessageDigest::shake_128();
614+
let mut h = Hasher::new(digest).unwrap();
615+
let mut buf = vec![0; digest.size()];
616+
h.squeeze_xof(&mut buf).unwrap();
617+
h.finish_xof(&mut buf)
618+
.expect_err("finalize after squeeze should fail");
619+
}
620+
540621
#[test]
541622
#[allow(clippy::redundant_clone)]
542623
fn test_clone() {
@@ -710,6 +791,8 @@ mod tests {
710791

711792
for test in tests.iter() {
712793
hash_xof_test(MessageDigest::shake_128(), test);
794+
#[cfg(ossl330)]
795+
hash_xof_squeeze_test(MessageDigest::shake_128(), test);
713796
}
714797

715798
assert_eq!(MessageDigest::shake_128().block_size(), 168);
@@ -730,6 +813,8 @@ mod tests {
730813

731814
for test in tests.iter() {
732815
hash_xof_test(MessageDigest::shake_256(), test);
816+
#[cfg(ossl330)]
817+
hash_xof_squeeze_test(MessageDigest::shake_256(), test);
733818
}
734819

735820
assert_eq!(MessageDigest::shake_256().block_size(), 136);

0 commit comments

Comments
 (0)