we (web engine): Experimental web browser project to understand the limits of Claude
at js-bytecode 295 lines 10 kB view raw
1//! HKDF: HMAC-based Extract-and-Expand Key Derivation Function (RFC 5869). 2 3use crate::hmac::{HashFunction, Hmac}; 4 5/// HKDF-Extract: derive a pseudorandom key (PRK) from input keying material. 6/// 7/// `salt` is an optional non-secret random value; if empty, a string of 8/// `H::OUTPUT_SIZE` zeros is used (per RFC 5869 §2.2). 9pub fn hkdf_extract<H: HashFunction>(salt: &[u8], ikm: &[u8]) -> Vec<u8> { 10 let effective_salt: Vec<u8>; 11 let salt = if salt.is_empty() { 12 effective_salt = vec![0u8; H::OUTPUT_SIZE]; 13 &effective_salt 14 } else { 15 salt 16 }; 17 let mut hmac = Hmac::<H>::new(salt); 18 hmac.update(ikm); 19 hmac.finalize() 20} 21 22/// HKDF-Expand: expand a PRK into output keying material of length `len`. 23/// 24/// `len` must be <= 255 * `H::OUTPUT_SIZE`. 25/// Returns `None` if `len` exceeds the maximum. 26pub fn hkdf_expand<H: HashFunction>(prk: &[u8], info: &[u8], len: usize) -> Option<Vec<u8>> { 27 let hash_len = H::OUTPUT_SIZE; 28 if len > 255 * hash_len { 29 return None; 30 } 31 32 let n = len.div_ceil(hash_len); 33 let mut okm = Vec::with_capacity(n * hash_len); 34 let mut t_prev: Vec<u8> = Vec::new(); 35 36 for i in 1..=n { 37 let mut hmac = Hmac::<H>::new(prk); 38 hmac.update(&t_prev); 39 hmac.update(info); 40 hmac.update(&[i as u8]); 41 t_prev = hmac.finalize(); 42 okm.extend_from_slice(&t_prev); 43 } 44 45 okm.truncate(len); 46 Some(okm) 47} 48 49/// Combined HKDF: extract then expand in one call. 50/// 51/// Returns `None` if `len` exceeds 255 * `H::OUTPUT_SIZE`. 52pub fn hkdf<H: HashFunction>(salt: &[u8], ikm: &[u8], info: &[u8], len: usize) -> Option<Vec<u8>> { 53 let prk = hkdf_extract::<H>(salt, ikm); 54 hkdf_expand::<H>(&prk, info, len) 55} 56 57// --------------------------------------------------------------------------- 58// Tests — RFC 5869 test vectors 59// --------------------------------------------------------------------------- 60 61#[cfg(test)] 62mod tests { 63 use super::*; 64 use crate::sha2::Sha256; 65 66 fn hex(bytes: &[u8]) -> String { 67 bytes.iter().map(|b| format!("{b:02x}")).collect() 68 } 69 70 fn from_hex(s: &str) -> Vec<u8> { 71 (0..s.len()) 72 .step_by(2) 73 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) 74 .collect() 75 } 76 77 // ----------------------------------------------------------------------- 78 // Test Case 1: Basic test case with SHA-256 79 // ----------------------------------------------------------------------- 80 81 #[test] 82 fn rfc5869_case1_extract() { 83 let ikm = from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); 84 let salt = from_hex("000102030405060708090a0b0c"); 85 let prk = hkdf_extract::<Sha256>(&salt, &ikm); 86 assert_eq!( 87 hex(&prk), 88 "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" 89 ); 90 } 91 92 #[test] 93 fn rfc5869_case1_expand() { 94 let prk = from_hex("077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"); 95 let info = from_hex("f0f1f2f3f4f5f6f7f8f9"); 96 let okm = hkdf_expand::<Sha256>(&prk, &info, 42).unwrap(); 97 assert_eq!( 98 hex(&okm), 99 "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865" 100 ); 101 } 102 103 #[test] 104 fn rfc5869_case1_combined() { 105 let ikm = from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); 106 let salt = from_hex("000102030405060708090a0b0c"); 107 let info = from_hex("f0f1f2f3f4f5f6f7f8f9"); 108 let okm = hkdf::<Sha256>(&salt, &ikm, &info, 42).unwrap(); 109 assert_eq!( 110 hex(&okm), 111 "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865" 112 ); 113 } 114 115 // ----------------------------------------------------------------------- 116 // Test Case 2: Longer inputs/outputs with SHA-256 117 // ----------------------------------------------------------------------- 118 119 #[test] 120 fn rfc5869_case2_extract() { 121 let ikm = from_hex( 122 "000102030405060708090a0b0c0d0e0f\ 123 101112131415161718191a1b1c1d1e1f\ 124 202122232425262728292a2b2c2d2e2f\ 125 303132333435363738393a3b3c3d3e3f\ 126 404142434445464748494a4b4c4d4e4f", 127 ); 128 let salt = from_hex( 129 "606162636465666768696a6b6c6d6e6f\ 130 707172737475767778797a7b7c7d7e7f\ 131 808182838485868788898a8b8c8d8e8f\ 132 909192939495969798999a9b9c9d9e9f\ 133 a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", 134 ); 135 let prk = hkdf_extract::<Sha256>(&salt, &ikm); 136 assert_eq!( 137 hex(&prk), 138 "06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244" 139 ); 140 } 141 142 #[test] 143 fn rfc5869_case2_expand() { 144 let prk = from_hex("06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244"); 145 let info = from_hex( 146 "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf\ 147 c0c1c2c3c4c5c6c7c8c9cacbcccdcecf\ 148 d0d1d2d3d4d5d6d7d8d9dadbdcdddedf\ 149 e0e1e2e3e4e5e6e7e8e9eaebecedeeef\ 150 f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", 151 ); 152 let okm = hkdf_expand::<Sha256>(&prk, &info, 82).unwrap(); 153 assert_eq!( 154 hex(&okm), 155 "b11e398dc80327a1c8e7f78c596a4934\ 156 4f012eda2d4efad8a050cc4c19afa97c\ 157 59045a99cac7827271cb41c65e590e09\ 158 da3275600c2f09b8367793a9aca3db71\ 159 cc30c58179ec3e87c14c01d5c1f3434f\ 160 1d87" 161 ); 162 } 163 164 #[test] 165 fn rfc5869_case2_combined() { 166 let ikm = from_hex( 167 "000102030405060708090a0b0c0d0e0f\ 168 101112131415161718191a1b1c1d1e1f\ 169 202122232425262728292a2b2c2d2e2f\ 170 303132333435363738393a3b3c3d3e3f\ 171 404142434445464748494a4b4c4d4e4f", 172 ); 173 let salt = from_hex( 174 "606162636465666768696a6b6c6d6e6f\ 175 707172737475767778797a7b7c7d7e7f\ 176 808182838485868788898a8b8c8d8e8f\ 177 909192939495969798999a9b9c9d9e9f\ 178 a0a1a2a3a4a5a6a7a8a9aaabacadaeaf", 179 ); 180 let info = from_hex( 181 "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf\ 182 c0c1c2c3c4c5c6c7c8c9cacbcccdcecf\ 183 d0d1d2d3d4d5d6d7d8d9dadbdcdddedf\ 184 e0e1e2e3e4e5e6e7e8e9eaebecedeeef\ 185 f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", 186 ); 187 let okm = hkdf::<Sha256>(&salt, &ikm, &info, 82).unwrap(); 188 assert_eq!( 189 hex(&okm), 190 "b11e398dc80327a1c8e7f78c596a4934\ 191 4f012eda2d4efad8a050cc4c19afa97c\ 192 59045a99cac7827271cb41c65e590e09\ 193 da3275600c2f09b8367793a9aca3db71\ 194 cc30c58179ec3e87c14c01d5c1f3434f\ 195 1d87" 196 ); 197 } 198 199 // ----------------------------------------------------------------------- 200 // Test Case 3: SHA-256, zero-length salt and info 201 // ----------------------------------------------------------------------- 202 203 #[test] 204 fn rfc5869_case3_extract() { 205 let ikm = from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); 206 let prk = hkdf_extract::<Sha256>(&[], &ikm); 207 assert_eq!( 208 hex(&prk), 209 "19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04" 210 ); 211 } 212 213 #[test] 214 fn rfc5869_case3_expand() { 215 let prk = from_hex("19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04"); 216 let okm = hkdf_expand::<Sha256>(&prk, &[], 42).unwrap(); 217 assert_eq!( 218 hex(&okm), 219 "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8" 220 ); 221 } 222 223 #[test] 224 fn rfc5869_case3_combined() { 225 let ikm = from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); 226 let okm = hkdf::<Sha256>(&[], &ikm, &[], 42).unwrap(); 227 assert_eq!( 228 hex(&okm), 229 "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8" 230 ); 231 } 232 233 // ----------------------------------------------------------------------- 234 // Output length validation 235 // ----------------------------------------------------------------------- 236 237 #[test] 238 fn expand_rejects_oversized_output() { 239 let prk = [0x42u8; 32]; 240 // 255 * 32 = 8160 is the max for SHA-256 241 assert!(hkdf_expand::<Sha256>(&prk, &[], 8160).is_some()); 242 assert!(hkdf_expand::<Sha256>(&prk, &[], 8161).is_none()); 243 } 244 245 #[test] 246 fn hkdf_rejects_oversized_output() { 247 let ikm = [0x0bu8; 22]; 248 assert!(hkdf::<Sha256>(&[], &ikm, &[], 8161).is_none()); 249 } 250 251 // ----------------------------------------------------------------------- 252 // Edge cases 253 // ----------------------------------------------------------------------- 254 255 #[test] 256 fn expand_zero_length_output() { 257 let prk = [0x42u8; 32]; 258 let okm = hkdf_expand::<Sha256>(&prk, &[], 0).unwrap(); 259 assert!(okm.is_empty()); 260 } 261 262 #[test] 263 fn expand_exact_hash_length() { 264 let prk = [0x42u8; 32]; 265 let okm = hkdf_expand::<Sha256>(&prk, b"info", 32).unwrap(); 266 assert_eq!(okm.len(), 32); 267 } 268 269 #[test] 270 fn extract_expand_with_sha512() { 271 use crate::sha2::Sha512; 272 273 // Use Test Case 1 inputs but with SHA-512 — verify it produces 274 // the correct output length and doesn't panic. 275 let ikm = from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); 276 let salt = from_hex("000102030405060708090a0b0c"); 277 let prk = hkdf_extract::<Sha512>(&salt, &ikm); 278 assert_eq!(prk.len(), 64); 279 280 let okm = hkdf_expand::<Sha512>(&prk, b"test info", 100).unwrap(); 281 assert_eq!(okm.len(), 100); 282 } 283 284 #[test] 285 fn extract_expand_with_sha384() { 286 use crate::sha2::Sha384; 287 288 let ikm = [0xaau8; 80]; 289 let prk = hkdf_extract::<Sha384>(&[], &ikm); 290 assert_eq!(prk.len(), 48); 291 292 let okm = hkdf_expand::<Sha384>(&prk, b"context", 60).unwrap(); 293 assert_eq!(okm.len(), 60); 294 } 295}