Skip to main content

age_plugin_argon2/
params.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5/// Raw helper for validated deserialization of [`Argon2Params`].
6#[derive(Deserialize)]
7struct Argon2ParamsRaw {
8    m_cost: u32,
9    t_cost: u32,
10    p_cost: u32,
11}
12
13impl TryFrom<Argon2ParamsRaw> for Argon2Params {
14    type Error = InvalidParams;
15
16    fn try_from(raw: Argon2ParamsRaw) -> Result<Self, Self::Error> {
17        Self::new(raw.m_cost, raw.t_cost, raw.p_cost)
18    }
19}
20
21/// Argon2id parameters for key derivation.
22#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(try_from = "Argon2ParamsRaw")]
24pub struct Argon2Params {
25    /// Memory cost in KiB (minimum 8)
26    m_cost: u32,
27    /// Time cost / iterations (minimum 1)
28    t_cost: u32,
29    /// Parallelism (minimum 1)
30    p_cost: u32,
31}
32
33/// Parameter validation error.
34#[derive(Debug, thiserror::Error)]
35#[error("invalid Argon2 parameters: {reason}")]
36pub struct InvalidParams {
37    reason: String,
38}
39
40impl Argon2Params {
41    /// Create validated Argon2id parameters.
42    ///
43    /// Validates against argon2 library minimums:
44    /// - `m_cost` >= 8 KiB (argon2 crate minimum)
45    /// - `t_cost` >= 1
46    /// - `p_cost` >= 1
47    pub fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Result<Self, InvalidParams> {
48        // Validate by attempting to build argon2::Params — this catches all
49        // constraints enforced by the library (including m_cost >= 8*p_cost).
50        argon2::Params::new(m_cost, t_cost, p_cost, Some(32)).map_err(|e| InvalidParams {
51            reason: e.to_string(),
52        })?;
53
54        Ok(Self {
55            m_cost,
56            t_cost,
57            p_cost,
58        })
59    }
60
61    /// Memory cost in KiB.
62    pub fn m_cost(&self) -> u32 {
63        self.m_cost
64    }
65
66    /// Time cost (number of iterations).
67    pub fn t_cost(&self) -> u32 {
68        self.t_cost
69    }
70
71    /// Parallelism (number of lanes).
72    pub fn p_cost(&self) -> u32 {
73        self.p_cost
74    }
75}
76
77impl fmt::Debug for Argon2Params {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        f.debug_struct("Argon2Params")
80            .field("m_cost", &self.m_cost)
81            .field("t_cost", &self.t_cost)
82            .field("p_cost", &self.p_cost)
83            .finish()
84    }
85}
86
87/// Default: m=65536 (64 MiB), t=3, p=4 (matching KDBX4 settings)
88impl Default for Argon2Params {
89    fn default() -> Self {
90        // These are known-valid constants.
91        Self {
92            m_cost: 65536,
93            t_cost: 3,
94            p_cost: 4,
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_default_params_are_valid() {
105        let p = Argon2Params::default();
106        assert_eq!(p.m_cost(), 65536);
107        assert_eq!(p.t_cost(), 3);
108        assert_eq!(p.p_cost(), 4);
109    }
110
111    #[test]
112    fn test_valid_params() {
113        let p = Argon2Params::new(256, 1, 1).unwrap();
114        assert_eq!(p.m_cost(), 256);
115    }
116
117    #[test]
118    fn test_zero_t_cost_rejected() {
119        assert!(Argon2Params::new(256, 0, 1).is_err());
120    }
121
122    #[test]
123    fn test_zero_p_cost_rejected() {
124        assert!(Argon2Params::new(256, 1, 0).is_err());
125    }
126
127    #[test]
128    fn test_m_cost_too_low_rejected() {
129        // m_cost must be >= 8 * p_cost
130        assert!(Argon2Params::new(1, 1, 1).is_err());
131    }
132}