Skip to main content

krypton/
kdf.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::{Error, Result};
4
5/// Argon2id parameters.
6///
7/// Parameters are stored inside every encrypted container so that defaults can
8/// evolve in the future without breaking older files. When parsing parameters
9/// from untrusted input they are validated against the bounds documented on
10/// each field to prevent denial-of-service via absurd values.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub struct KdfParams {
13    /// Memory cost in KiB (Argon2 `m`). Default 65536 (64 MiB).
14    /// Accepted range when reading untrusted input: 8 MiB ..= 4 GiB.
15    pub m_cost_kib: u32,
16    /// Time cost — number of passes (Argon2 `t`). Default 4.
17    /// Accepted range: 1 ..= 64.
18    pub t_cost: u32,
19    /// Degree of parallelism (Argon2 `p`). Default 4.
20    /// Accepted range: 1 ..= 16.
21    pub p_cost: u32,
22}
23
24impl KdfParams {
25    /// Library defaults: Argon2id with 64 MiB memory, 4 passes, parallelism 4
26    /// (matches the krypton CLI ≥ 0.2).
27    pub const fn new() -> Self {
28        Self {
29            m_cost_kib: 65_536,
30            t_cost: 4,
31            p_cost: 4,
32        }
33    }
34
35    pub(crate) const SERIALIZED_LEN: usize = 12;
36
37    pub(crate) fn write_to(&self, buf: &mut [u8]) {
38        buf[0..4].copy_from_slice(&self.m_cost_kib.to_le_bytes());
39        buf[4..8].copy_from_slice(&self.t_cost.to_le_bytes());
40        buf[8..12].copy_from_slice(&self.p_cost.to_le_bytes());
41    }
42
43    /// Parses and validates parameters from a 12-byte little-endian encoding.
44    pub fn read_from(buf: &[u8]) -> Result<Self> {
45        if buf.len() < Self::SERIALIZED_LEN {
46            return Err(Error::InvalidHeader);
47        }
48        let params = Self {
49            m_cost_kib: u32::from_le_bytes(buf[0..4].try_into().map_err(|_| Error::InvalidHeader)?),
50            t_cost: u32::from_le_bytes(buf[4..8].try_into().map_err(|_| Error::InvalidHeader)?),
51            p_cost: u32::from_le_bytes(buf[8..12].try_into().map_err(|_| Error::InvalidHeader)?),
52        };
53        params.validate()?;
54        Ok(params)
55    }
56
57    /// Validates the parameters against safety bounds.
58    ///
59    /// The upper bounds exist so that a hostile config file cannot make the
60    /// process attempt an absurd allocation; the lower bound rejects settings
61    /// so weak that no meaningful protection is provided.
62    pub fn validate(&self) -> Result<()> {
63        const MIN_M_KIB: u32 = 8 * 1024;
64        const MAX_M_KIB: u32 = 4 * 1024 * 1024; // 4 GiB
65        const MIN_T: u32 = 1;
66        const MAX_T: u32 = 64;
67        const MIN_P: u32 = 1;
68        const MAX_P: u32 = 16;
69
70        if !(MIN_M_KIB..=MAX_M_KIB).contains(&self.m_cost_kib) {
71            return Err(Error::InvalidKdfParams);
72        }
73        if !(MIN_T..=MAX_T).contains(&self.t_cost) {
74            return Err(Error::InvalidKdfParams);
75        }
76        if !(MIN_P..=MAX_P).contains(&self.p_cost) {
77            return Err(Error::InvalidKdfParams);
78        }
79        Ok(())
80    }
81}
82
83impl Default for KdfParams {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn defaults_are_valid() {
95        assert!(KdfParams::new().validate().is_ok());
96    }
97
98    #[test]
99    fn roundtrip_serialization() {
100        let params = KdfParams {
101            m_cost_kib: 131_072,
102            t_cost: 3,
103            p_cost: 2,
104        };
105        let mut buf = [0u8; KdfParams::SERIALIZED_LEN];
106        params.write_to(&mut buf);
107        assert_eq!(KdfParams::read_from(&buf).unwrap(), params);
108    }
109
110    #[test]
111    fn rejects_out_of_bounds() {
112        assert!(KdfParams {
113            m_cost_kib: 1,
114            ..KdfParams::new()
115        }
116        .validate()
117        .is_err());
118        assert!(KdfParams {
119            m_cost_kib: u32::MAX,
120            ..KdfParams::new()
121        }
122        .validate()
123        .is_err());
124        assert!(KdfParams {
125            t_cost: 0,
126            ..KdfParams::new()
127        }
128        .validate()
129        .is_err());
130        assert!(KdfParams {
131            t_cost: 65,
132            ..KdfParams::new()
133        }
134        .validate()
135        .is_err());
136        assert!(KdfParams {
137            p_cost: 17,
138            ..KdfParams::new()
139        }
140        .validate()
141        .is_err());
142    }
143}