dstu_core/crypto_kdf.rs
1//! `crypto_kdf` equivalent (`docs/dstu-crypto-project.md` "Mapping onto the libsodium API",
2//! `docs/TASKS.md` T-105, roadmap Step 3 item 2 - `docs/DECISIONS.md` D-66) - a thin libsodium-ergonomics
3//! wrapper over [`Kupyna256Kdf`],
4//! matching [`crate::crypto_auth`]'s reasoning exactly: only the 256-bit variant is exposed here
5//! (D-47's "delete the knob", same as `crypto_auth`'s choice among `Kupyna{256,384,512}Kmac`; the
6//! other two sizes stay available at `hazmat::kupyna_kdf`), and the master key is an opaque,
7//! `Zeroize`-on-drop [`MasterKey`] type rather than a raw `[u8; 32]`.
8//!
9//! Unlike `crypto_auth`, there is no error to foreclose either way: `hazmat::kupyna_kdf::
10//! Kupyna256Kdf::derive_subkey` is already infallible (fixed-length arrays in, fixed-length array
11//! out, no key-length check to fail). [`MasterKey`] adds `Zeroize`-on-drop and an OS-CSPRNG
12//! `generate()`, matching `crypto_secretbox`'s `SecretKey`/`crypto_auth`'s `Key` precedent, not a
13//! change in fallibility.
14//!
15//! Provenance is otherwise identical to the `hazmat` layer: no DSTU standard or reference
16//! implementation of "a KDF using Kupyna" exists anywhere, so - unlike every other module in this
17//! crate - there is no oracle vector for this construction, ever (D-45); verification is
18//! determinism/distinctness property tests only, inherited unchanged from `hazmat::kupyna_kdf`.
19//!
20//! # Example
21//!
22//! Derives many independent-looking subkeys from one master key, instead of generating and storing
23//! a fresh random key per purpose - useful when you want, say, a separate encryption key and MAC
24//! key derived from one secret rather than managing two unrelated secrets.
25//!
26//! ```rust
27//! use dstu_core::crypto_kdf::MasterKey;
28//!
29//! let master_key = MasterKey::generate().expect("OS CSPRNG should not fail");
30//!
31//! let encryption_subkey = master_key.derive_subkey(0, b"encrypt_");
32//! let mac_subkey = master_key.derive_subkey(1, b"mac_key_");
33//!
34//! // Different subkey_id (holding context fixed) gives a different, unrelated-looking subkey.
35//! assert_ne!(encryption_subkey, mac_subkey);
36//! // Deterministic: the same id/context always re-derives the same subkey.
37//! assert_eq!(encryption_subkey, master_key.derive_subkey(0, b"encrypt_"));
38//! ```
39
40use crate::hazmat::kupyna_kdf::Kupyna256Kdf;
41use zeroize::Zeroize;
42
43/// A `crypto_kdf` master key. Always exactly 32 bytes - [`Kupyna256Kdf`]'s fixed key length (see
44/// the module doc).
45pub struct MasterKey([u8; 32]);
46
47impl Drop for MasterKey {
48 fn drop(&mut self) {
49 self.0.zeroize();
50 }
51}
52
53impl MasterKey {
54 /// Generates a fresh master key from the OS CSPRNG - libsodium's `crypto_kdf_keygen`
55 /// equivalent.
56 ///
57 /// # Errors
58 ///
59 /// Returns [`crate::randombytes::RandomError`] if the OS CSPRNG fails.
60 #[cfg(any(feature = "std", feature = "getrandom"))]
61 pub fn generate() -> Result<Self, crate::randombytes::RandomError> {
62 let mut bytes = [0u8; 32];
63 crate::randombytes::randombytes_buf(&mut bytes)?;
64 Ok(MasterKey(bytes))
65 }
66
67 #[must_use]
68 pub fn from_bytes(bytes: [u8; 32]) -> Self {
69 MasterKey(bytes)
70 }
71
72 #[must_use]
73 pub fn as_bytes(&self) -> &[u8; 32] {
74 &self.0
75 }
76
77 /// Derives a subkey - see [`Kupyna256Kdf::derive_subkey`] for the construction itself.
78 /// Different `subkey_id`/`context` values (holding the others fixed) produce different
79 /// subkeys.
80 #[must_use]
81 pub fn derive_subkey(&self, subkey_id: u64, context: &[u8; 8]) -> [u8; 32] {
82 Kupyna256Kdf::derive_subkey(&self.0, subkey_id, context)
83 }
84}