use core::iter; use hex_literal::hex; use hkdf::{Hkdf, HkdfExtract, SimpleHkdfExtract}; use sha1::Sha1; use sha2::Sha256; const MAX_SHA256_LENGTH: usize = 255 * (256 / 8); // =8160 static COMPONENTS: &[&[u8]] = &[ b"09090909090909090909090909090909090909090909", b"8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a", b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0", b"4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4", b"1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d", ]; #[test] fn test_lengths() { let hkdf = Hkdf::::new(None, &[]); let mut longest = vec![0u8; MAX_SHA256_LENGTH]; assert!(hkdf.expand(&[], &mut longest).is_ok()); // Runtime is O(length), so exhaustively testing all legal lengths // would take too long (at least without --release). Only test a // subset: the first 500, the last 10, and every 100th in between. let range = 500..MAX_SHA256_LENGTH - 10; let lengths = (0..MAX_SHA256_LENGTH + 1).filter(|len| !range.contains(len) || *len % 100 == 0); for length in lengths { let mut okm = vec![0u8; length]; assert!(hkdf.expand(&[], &mut okm).is_ok()); assert_eq!(okm.len(), length); assert_eq!(okm[..], longest[..length]); } } #[test] fn test_max_length() { let hkdf = Hkdf::::new(Some(&[]), &[]); let mut okm = vec![0u8; MAX_SHA256_LENGTH]; assert!(hkdf.expand(&[], &mut okm).is_ok()); } #[test] fn test_max_length_exceeded() { let hkdf = Hkdf::::new(Some(&[]), &[]); let mut okm = vec![0u8; MAX_SHA256_LENGTH + 1]; assert!(hkdf.expand(&[], &mut okm).is_err()); } #[test] fn test_unsupported_length() { let hkdf = Hkdf::::new(Some(&[]), &[]); let mut okm = vec![0u8; 90000]; assert!(hkdf.expand(&[], &mut okm).is_err()); } #[test] fn test_prk_too_short() { use sha2::digest::Digest; let output_len = Sha256::output_size(); let prk = vec![0; output_len - 1]; assert!(Hkdf::::from_prk(&prk).is_err()); } #[test] #[rustfmt::skip] fn test_derive_sha1_with_none() { let ikm = hex!("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"); let salt = None; let info = hex!(""); let (prk, hkdf) = Hkdf::::extract(salt, &ikm[..]); let mut okm = [0u8; 42]; assert!(hkdf.expand(&info[..], &mut okm).is_ok()); assert_eq!( prk[..], hex!("2adccada18779e7c2077ad2eb19d3f3e731385dd")[..] ); assert_eq!( okm[..], hex!(" 2c91117204d745f3500d636a62f64f0a b3bae548aa53d423b0d1f27ebba6f5e5 673a081d70cce7acfc48 ")[..], ); } #[test] fn test_expand_multi_info() { let info_components = COMPONENTS; let (_, hkdf_ctx) = Hkdf::::extract(None, b"some ikm here"); // Compute HKDF-Expand on the concatenation of all the info components let mut oneshot_res = [0u8; 16]; hkdf_ctx .expand(&info_components.concat(), &mut oneshot_res) .unwrap(); // Now iteratively join the components of info_components until it's all 1 component. The value // of HKDF-Expand should be the same throughout let mut num_concatted = 0; let mut info_head = Vec::new(); while num_concatted < info_components.len() { info_head.extend(info_components[num_concatted]); // Build the new input to be the info head followed by the remaining components let input: Vec<&[u8]> = iter::once(info_head.as_slice()) .chain(info_components.iter().cloned().skip(num_concatted + 1)) .collect(); // Compute and compare to the one-shot answer let mut multipart_res = [0u8; 16]; hkdf_ctx .expand_multi_info(&input, &mut multipart_res) .unwrap(); assert_eq!(multipart_res, oneshot_res); num_concatted += 1; } } #[test] fn test_extract_streaming() { let ikm_components = COMPONENTS; let salt = b"mysalt"; // Compute HKDF-Extract on the concatenation of all the IKM components let (oneshot_res, _) = Hkdf::::extract(Some(&salt[..]), &ikm_components.concat()); // Now iteratively join the components of ikm_components until it's all 1 component. The value // of HKDF-Extract should be the same throughout let mut num_concatted = 0; let mut ikm_head = Vec::new(); while num_concatted < ikm_components.len() { ikm_head.extend(ikm_components[num_concatted]); // Make a new extraction context and build the new input to be the IKM head followed by the // remaining components let mut extract_ctx = HkdfExtract::::new(Some(&salt[..])); let input = iter::once(ikm_head.as_slice()) .chain(ikm_components.iter().cloned().skip(num_concatted + 1)); // Stream in the IKM input in the chunks specified for ikm in input { extract_ctx.input_ikm(ikm); } // Finalize and compare to the one-shot answer let (multipart_res, _) = extract_ctx.finalize(); assert_eq!(multipart_res, oneshot_res); num_concatted += 1; } let mut num_concatted = 0; let mut ikm_head = Vec::new(); while num_concatted < ikm_components.len() { ikm_head.extend(ikm_components[num_concatted]); // Make a new extraction context and build the new input to be the IKM head followed by the // remaining components let mut extract_ctx = SimpleHkdfExtract::::new(Some(&salt[..])); let input = iter::once(ikm_head.as_slice()) .chain(ikm_components.iter().cloned().skip(num_concatted + 1)); // Stream in the IKM input in the chunks specified for ikm in input { extract_ctx.input_ikm(ikm); } // Finalize and compare to the one-shot answer let (multipart_res, _) = extract_ctx.finalize(); assert_eq!(multipart_res, oneshot_res); num_concatted += 1; } } #[test] fn test_debug_impls() { fn needs_debug() {} needs_debug::>(); needs_debug::>(); }