1#[cfg(feature = "alloc")]
40use alloc::vec::Vec;
41use core::fmt;
42
43#[cfg(feature = "serde")]
44use serde::{Deserialize, Serialize};
45use zeroize::{Zeroize, ZeroizeOnDrop};
46
47use crate::classic::crypto_kdf::{crypto_kdf_derive_from_key, validate_subkey_length};
48use crate::constants::{CRYPTO_KDF_CONTEXTBYTES, CRYPTO_KDF_KEYBYTES};
49use crate::error::Error;
50use crate::types::*;
51
52pub type Key = StackByteArray<CRYPTO_KDF_KEYBYTES>;
54pub type Context = StackByteArray<CRYPTO_KDF_CONTEXTBYTES>;
56
57#[derive(Zeroize, Clone)]
58#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
59pub struct Kdf<
65 Key: ByteArray<CRYPTO_KDF_KEYBYTES> + Zeroize + ZeroizeOnDrop,
66 Context: ByteArray<CRYPTO_KDF_CONTEXTBYTES> + Zeroize,
67> {
68 main_key: Key,
69 context: Context,
70}
71
72impl<
73 Key: ByteArray<CRYPTO_KDF_KEYBYTES> + Zeroize + ZeroizeOnDrop,
74 Context: ByteArray<CRYPTO_KDF_CONTEXTBYTES> + Zeroize,
75> fmt::Debug for Kdf<Key, Context>
76{
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.debug_struct("Kdf")
79 .field("main_key", &"[REDACTED]")
80 .field("context", &self.context.as_slice())
81 .finish()
82 }
83}
84
85pub type StackKdf = Kdf<Key, Context>;
87
88#[cfg(any(
89 all(feature = "protected", any(unix, windows)),
90 all(doc, not(doctest), feature = "std")
91))]
92#[cfg_attr(all(feature = "nightly", doc), doc(cfg(feature = "protected")))]
93pub mod protected {
94 use super::*;
118 pub use crate::protected::*;
119
120 pub type Key = HeapByteArray<CRYPTO_KDF_KEYBYTES>;
123 pub type Context = HeapByteArray<CRYPTO_KDF_CONTEXTBYTES>;
126
127 pub type LockedKdf = Kdf<Locked<Key>, Locked<Context>>;
129}
130
131impl<
132 Key: NewByteArray<CRYPTO_KDF_KEYBYTES> + Zeroize + ZeroizeOnDrop,
133 Context: NewByteArray<CRYPTO_KDF_CONTEXTBYTES> + Zeroize,
134> Kdf<Key, Context>
135{
136 #[must_use]
138 pub fn generate() -> Self {
139 Self {
140 main_key: Key::generate(),
141 context: Context::generate(),
142 }
143 }
144}
145
146impl<
147 Key: ByteArray<CRYPTO_KDF_KEYBYTES> + Zeroize + ZeroizeOnDrop,
148 Context: ByteArray<CRYPTO_KDF_CONTEXTBYTES> + Zeroize,
149> Kdf<Key, Context>
150{
151 pub fn derive_subkey<const LENGTH: usize, Subkey: NewByteArray<LENGTH>>(
161 &self,
162 subkey_id: u64,
163 ) -> Result<Subkey, Error> {
164 validate_subkey_length(LENGTH)?;
165 let mut subkey = Subkey::new_byte_array();
166 crypto_kdf_derive_from_key(
167 subkey.as_mut_array(),
168 subkey_id,
169 self.context.as_array(),
170 self.main_key.as_array(),
171 )?;
172 Ok(subkey)
173 }
174
175 #[cfg(feature = "alloc")]
186 pub fn derive_subkey_to_vec(&self, subkey_id: u64, length: usize) -> Result<Vec<u8>, Error> {
187 validate_subkey_length(length)?;
188 let mut subkey = vec![0u8; length];
189 crypto_kdf_derive_from_key(
190 &mut subkey,
191 subkey_id,
192 self.context.as_array(),
193 self.main_key.as_array(),
194 )?;
195 Ok(subkey)
196 }
197
198 #[must_use]
200 pub fn from_parts(main_key: Key, context: Context) -> Self {
201 Self { main_key, context }
202 }
203
204 #[must_use]
207 pub fn into_parts(self) -> (Key, Context) {
208 (self.main_key, self.context)
209 }
210}
211
212#[cfg(all(test, feature = "alloc"))]
213mod tests {
214 use super::*;
215 use crate::constants::{
216 CRYPTO_KDF_BLAKE2B_BYTES_MAX as CRYPTO_KDF_BYTES_MAX,
217 CRYPTO_KDF_BLAKE2B_BYTES_MIN as CRYPTO_KDF_BYTES_MIN,
218 };
219
220 const KAT: [(u64, [&str; 3]); 2] = [
228 (
229 0,
230 [
231 "e9136a52b9690eb4df4e9665e819a6d3",
232 "c13fcc2e6cd0cd0f82d93b163a5696c5105378f8c629d36baf3ae0239de9c280",
233 concat!(
234 "a0c724404728c8bb95e5433eb6a9716171144d61efb23e74b873fcbeda51d807",
235 "1b5d70aae12066dfc94ce943f145aa176c055040c3dd73b0a15e36254d450614",
236 ),
237 ],
238 ),
239 (
240 u64::MAX,
241 [
242 "040f6b7312b53bce5d711bb9c589cdd4",
243 "500c3043b2b9177ec843ecbe9f98f92d8c11fbbd10a225ab844548de89c21d55",
244 concat!(
245 "6be4464350f6934d151c1bb8f555bc18e75028be95b892c6dca047101f2827a1",
246 "950b2b0fb35e996a2782db9a760e76c8b8da52e362f741bf5bcfefff0fc943fc",
247 ),
248 ],
249 ),
250 ];
251
252 fn kat_kdf() -> StackKdf {
253 let key: [u8; CRYPTO_KDF_KEYBYTES] = core::array::from_fn(|i| i as u8);
254 Kdf::from_parts(Key::from(key), Context::from(*b"KDF test"))
255 }
256
257 #[test]
258 fn derives_libsodium_known_answers_for_ids_zero_and_max() {
259 let kdf = kat_kdf();
260 for (subkey_id, expected) in KAT {
261 let expected16 = hex::decode(expected[0]).expect("hex");
262 let expected32 = hex::decode(expected[1]).expect("hex");
263 let expected64 = hex::decode(expected[2]).expect("hex");
264
265 let short: StackByteArray<16> = kdf.derive_subkey(subkey_id).expect("derive");
266 let medium: StackByteArray<32> = kdf.derive_subkey(subkey_id).expect("derive");
267 let long: StackByteArray<64> = kdf.derive_subkey(subkey_id).expect("derive");
268 assert_eq!(short.as_slice(), expected16.as_slice());
269 assert_eq!(medium.as_slice(), expected32.as_slice());
270 assert_eq!(long.as_slice(), expected64.as_slice());
271
272 assert_eq!(
273 kdf.derive_subkey_to_vec(subkey_id, 16).expect("derive"),
274 expected16
275 );
276 assert_eq!(
277 kdf.derive_subkey_to_vec(subkey_id, 32).expect("derive"),
278 expected32
279 );
280 assert_eq!(
281 kdf.derive_subkey_to_vec(subkey_id, 64).expect("derive"),
282 expected64
283 );
284
285 assert_ne!(&expected64[..32], expected32.as_slice());
288 assert_ne!(&expected32[..16], expected16.as_slice());
289 }
290 }
291
292 #[test]
293 fn matches_classic_derive_from_key_and_separates_key_context_and_id() {
294 let kdf = kat_kdf();
295 let (key, context) = kdf.clone().into_parts();
296 for subkey_id in [0, 1, 2, u64::from(u32::MAX), u64::MAX - 1, u64::MAX] {
297 for length in [
298 CRYPTO_KDF_BYTES_MIN,
299 17,
300 31,
301 32,
302 33,
303 63,
304 CRYPTO_KDF_BYTES_MAX,
305 ] {
306 let mut classic = vec![0u8; length];
307 crypto_kdf_derive_from_key(
308 &mut classic,
309 subkey_id,
310 context.as_array(),
311 key.as_array(),
312 )
313 .expect("classic derive");
314 assert_eq!(
315 kdf.derive_subkey_to_vec(subkey_id, length).expect("derive"),
316 classic
317 );
318 }
319 }
320
321 let baseline = kdf.derive_subkey_to_vec(7, 32).expect("derive");
322 assert_ne!(kdf.derive_subkey_to_vec(8, 32).expect("derive"), baseline);
323
324 let mut other_key = key.clone();
325 other_key[0] ^= 1;
326 assert_ne!(
327 Kdf::from_parts(other_key, context.clone())
328 .derive_subkey_to_vec(7, 32)
329 .expect("derive"),
330 baseline
331 );
332 let mut other_context = context.clone();
333 other_context[CRYPTO_KDF_CONTEXTBYTES - 1] ^= 1;
334 assert_ne!(
335 Kdf::from_parts(key, other_context)
336 .derive_subkey_to_vec(7, 32)
337 .expect("derive"),
338 baseline
339 );
340 }
341
342 #[test]
343 fn rejects_out_of_range_subkey_lengths_and_redacts_debug_output() {
344 let kdf = kat_kdf();
345 assert!(format!("{kdf:?}").contains("[REDACTED]"));
346 assert!(!format!("{kdf:?}").contains("KDF test"));
347
348 for length in [
349 0,
350 CRYPTO_KDF_BYTES_MIN - 1,
351 CRYPTO_KDF_BYTES_MAX + 1,
352 usize::MAX,
353 ] {
354 assert!(matches!(
355 kdf.derive_subkey_to_vec(0, length),
356 Err(Error::InvalidLength {
357 context: crate::ErrorContext::Subkey,
358 actual,
359 ..
360 }) if actual == length
361 ));
362 }
363 assert!(matches!(
364 kdf.derive_subkey::<15, StackByteArray<15>>(0),
365 Err(Error::InvalidLength {
366 context: crate::ErrorContext::Subkey,
367 actual: 15,
368 ..
369 })
370 ));
371 assert!(matches!(
372 kdf.derive_subkey::<65, StackByteArray<65>>(0),
373 Err(Error::InvalidLength {
374 context: crate::ErrorContext::Subkey,
375 actual: 65,
376 ..
377 })
378 ));
379 }
380
381 #[cfg(feature = "serde")]
382 #[test]
383 fn serde_round_trip_derives_the_same_subkeys() {
384 let kdf = kat_kdf();
385 let json = serde_json::to_string(&kdf).expect("serialize");
386 let decoded: StackKdf = serde_json::from_str(&json).expect("deserialize");
387 for (subkey_id, expected) in KAT {
388 assert_eq!(
389 decoded.derive_subkey_to_vec(subkey_id, 64).expect("derive"),
390 hex::decode(expected[2]).expect("hex")
391 );
392 }
393 let (key, context) = decoded.into_parts();
394 assert_eq!(context.as_slice(), b"KDF test");
395 assert_eq!(key, kat_kdf().into_parts().0);
396 }
397
398 #[cfg(all(feature = "protected", any(unix, windows)))]
399 #[test]
400 fn locked_kdf_derives_the_same_subkeys_as_the_stack_kdf() {
401 use crate::kdf::protected::*;
402
403 let (key, context) = kat_kdf().into_parts();
404 let locked: LockedKdf = Kdf::from_parts(
405 protected::Key::from_slice_into_locked(key.as_slice()).expect("lock key"),
406 protected::Context::from_slice_into_locked(context.as_slice()).expect("lock context"),
407 );
408 for (subkey_id, expected) in KAT {
409 let locked_subkey: Locked<HeapByteArray<64>> =
410 locked.derive_subkey(subkey_id).expect("derive");
411 assert_eq!(
412 locked_subkey.as_slice(),
413 hex::decode(expected[2]).expect("hex").as_slice()
414 );
415 assert_eq!(
416 locked.derive_subkey_to_vec(subkey_id, 16).expect("derive"),
417 hex::decode(expected[0]).expect("hex")
418 );
419 }
420 }
421
422 #[cfg(dryoc_native_tests)]
423 #[test]
424 fn matches_libsodium_derive_from_key() {
425 use crate::utils::test_util::XorShift64;
426
427 crate::native_test_util::init();
428
429 let mut rng = XorShift64::new(0x6b64_665f_7465_7374);
430 for _ in 0..8 {
431 let key = Key::from(rng.next_bytes32());
432 let context =
433 Context::try_from(&rng.next_bytes32()[..CRYPTO_KDF_CONTEXTBYTES]).expect("context");
434 let kdf = Kdf::from_parts(key.clone(), context.clone());
435 for subkey_id in [0, rng.next_u64(), u64::MAX] {
436 for length in [CRYPTO_KDF_BYTES_MIN, 32, CRYPTO_KDF_BYTES_MAX] {
437 let mut sodium = vec![0u8; length];
438 let rc = unsafe {
439 libsodium_sys::crypto_kdf_derive_from_key(
440 sodium.as_mut_ptr(),
441 length,
442 subkey_id,
443 context.as_ptr().cast(),
444 key.as_ptr(),
445 )
446 };
447 assert_eq!(rc, 0);
448 assert_eq!(
449 kdf.derive_subkey_to_vec(subkey_id, length).expect("derive"),
450 sodium
451 );
452 }
453 }
454 }
455 }
456}