snow/params/
mod.rs

1//! All structures related to Noise parameter definitions (cryptographic primitive choices, protocol
2//! patterns/names)
3
4use crate::error::{Error, PatternProblem};
5use std::str::FromStr;
6mod patterns;
7
8pub use self::patterns::{
9    HandshakeChoice, HandshakeModifier, HandshakeModifierList, HandshakePattern,
10    SUPPORTED_HANDSHAKE_PATTERNS,
11};
12
13pub(crate) use self::patterns::{DhToken, HandshakeTokens, MessagePatterns, Token};
14
15/// I recommend you choose `Noise`.
16#[allow(missing_docs)]
17#[derive(PartialEq, Copy, Clone, Debug)]
18pub enum BaseChoice {
19    Noise,
20}
21
22impl FromStr for BaseChoice {
23    type Err = Error;
24
25    fn from_str(s: &str) -> Result<Self, Self::Err> {
26        use self::BaseChoice::*;
27        match s {
28            "Noise" => Ok(Noise),
29            _ => Err(PatternProblem::UnsupportedBaseType.into()),
30        }
31    }
32}
33
34/// One of `25519` or `448`, per the spec.
35#[allow(missing_docs)]
36#[derive(PartialEq, Copy, Clone, Debug)]
37pub enum DHChoice {
38    Curve25519,
39    Ed448,
40}
41
42impl FromStr for DHChoice {
43    type Err = Error;
44
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        use self::DHChoice::*;
47        match s {
48            "25519" => Ok(Curve25519),
49            "448" => Ok(Ed448),
50            _ => Err(PatternProblem::UnsupportedDhType.into()),
51        }
52    }
53}
54
55/// One of `ChaChaPoly` or `AESGCM`, per the spec.
56#[allow(missing_docs)]
57#[derive(PartialEq, Copy, Clone, Debug)]
58pub enum CipherChoice {
59    ChaChaPoly,
60    #[cfg(feature = "xchachapoly")]
61    XChaChaPoly,
62    AESGCM,
63}
64
65impl FromStr for CipherChoice {
66    type Err = Error;
67
68    fn from_str(s: &str) -> Result<Self, Self::Err> {
69        use self::CipherChoice::*;
70        match s {
71            "ChaChaPoly" => Ok(ChaChaPoly),
72            #[cfg(feature = "xchachapoly")]
73            "XChaChaPoly" => Ok(XChaChaPoly),
74            "AESGCM" => Ok(AESGCM),
75            _ => Err(PatternProblem::UnsupportedCipherType.into()),
76        }
77    }
78}
79
80/// One of the supported SHA-family or BLAKE-family hash choices, per the spec.
81#[allow(missing_docs)]
82#[derive(PartialEq, Copy, Clone, Debug)]
83pub enum HashChoice {
84    SHA256,
85    SHA512,
86    Blake2s,
87    Blake2b,
88}
89
90impl FromStr for HashChoice {
91    type Err = Error;
92
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        use self::HashChoice::*;
95        match s {
96            "SHA256" => Ok(SHA256),
97            "SHA512" => Ok(SHA512),
98            "BLAKE2s" => Ok(Blake2s),
99            "BLAKE2b" => Ok(Blake2b),
100            _ => Err(PatternProblem::UnsupportedHashType.into()),
101        }
102    }
103}
104
105/// One of the supported Kems provided for unstable HFS extension.
106#[cfg(feature = "hfs")]
107#[allow(missing_docs)]
108#[derive(PartialEq, Copy, Clone, Debug)]
109pub enum KemChoice {
110    Kyber1024,
111}
112
113#[cfg(feature = "hfs")]
114impl FromStr for KemChoice {
115    type Err = Error;
116
117    fn from_str(s: &str) -> Result<Self, Self::Err> {
118        use self::KemChoice::*;
119        match s {
120            "Kyber1024" => Ok(Kyber1024),
121            _ => Err(PatternProblem::UnsupportedKemType.into()),
122        }
123    }
124}
125
126/// The set of choices (as specified in the Noise spec) that constitute a full protocol definition.
127///
128/// See: [Chapter 11: Protocol Names](http://noiseprotocol.org/noise.html#protocol-names).
129///
130/// # Examples
131///
132/// From a string definition:
133///
134/// ```
135/// # use snow::params::*;
136///
137/// let params: NoiseParams = "Noise_XX_25519_AESGCM_SHA256".parse().unwrap();
138/// ```
139#[allow(missing_docs)]
140#[derive(PartialEq, Clone, Debug)]
141pub struct NoiseParams {
142    pub name:      String,
143    pub base:      BaseChoice,
144    pub handshake: HandshakeChoice,
145    pub dh:        DHChoice,
146    #[cfg(feature = "hfs")]
147    pub kem:       Option<KemChoice>,
148    pub cipher:    CipherChoice,
149    pub hash:      HashChoice,
150}
151
152impl NoiseParams {
153    #[cfg(not(feature = "hfs"))]
154    /// Construct a new NoiseParams via specifying enums directly.
155    pub fn new(
156        name: String,
157        base: BaseChoice,
158        handshake: HandshakeChoice,
159        dh: DHChoice,
160        cipher: CipherChoice,
161        hash: HashChoice,
162    ) -> Self {
163        NoiseParams { name, base, handshake, dh, cipher, hash }
164    }
165
166    #[cfg(feature = "hfs")]
167    /// Construct a new NoiseParams via specifying enums directly.
168    pub fn new(
169        name: String,
170        base: BaseChoice,
171        handshake: HandshakeChoice,
172        dh: DHChoice,
173        kem: Option<KemChoice>,
174        cipher: CipherChoice,
175        hash: HashChoice,
176    ) -> Self {
177        NoiseParams { name, base, handshake, dh, kem, cipher, hash }
178    }
179}
180
181impl FromStr for NoiseParams {
182    type Err = Error;
183
184    #[cfg(not(feature = "hfs"))]
185    fn from_str(s: &str) -> Result<Self, Self::Err> {
186        let mut split = s.split('_');
187        let params = NoiseParams::new(
188            s.to_owned(),
189            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
190            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
191            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
192            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
193            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
194        );
195        if split.next().is_some() {
196            return Err(PatternProblem::UnsupportedModifier.into());
197        }
198        Ok(params)
199    }
200
201    #[cfg(feature = "hfs")]
202    fn from_str(s: &str) -> Result<Self, Self::Err> {
203        let mut split = s.split('_').peekable();
204        let p = NoiseParams::new(
205            s.to_owned(),
206            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
207            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
208            split
209                .peek()
210                .ok_or(PatternProblem::TooFewParameters)?
211                .splitn(2, '+')
212                .nth(0)
213                .ok_or(PatternProblem::TooFewParameters)?
214                .parse()?,
215            split
216                .next()
217                .ok_or(PatternProblem::TooFewParameters)?
218                .splitn(2, '+')
219                .nth(1)
220                .map(|s| s.parse())
221                .transpose()?,
222            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
223            split.next().ok_or(PatternProblem::TooFewParameters)?.parse()?,
224        );
225        if split.next().is_some() {
226            return Err(PatternProblem::UnsupportedModifier.into());
227        }
228
229        // Validate that a KEM is specified iff the hfs modifier is present
230        if p.handshake.is_hfs() != p.kem.is_some() {
231            return Err(PatternProblem::TooFewParameters.into());
232        }
233        Ok(p)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use std::convert::TryFrom;
241
242    #[test]
243    fn test_simple_handshake() {
244        let _: HandshakePattern = "XX".parse().unwrap();
245    }
246
247    #[test]
248    fn test_basic() {
249        let p: NoiseParams = "Noise_XX_25519_AESGCM_SHA256".parse().unwrap();
250        assert!(p.handshake.modifiers.list.is_empty());
251    }
252
253    #[test]
254    fn test_basic_deferred() {
255        let p: NoiseParams = "Noise_X1X1_25519_AESGCM_SHA256".parse().unwrap();
256        assert!(p.handshake.modifiers.list.is_empty());
257    }
258
259    #[test]
260    fn test_fallback_mod() {
261        let p: NoiseParams = "Noise_XXfallback_25519_AESGCM_SHA256".parse().unwrap();
262        assert!(p.handshake.modifiers.list[0] == HandshakeModifier::Fallback);
263    }
264
265    #[test]
266    fn test_psk_fallback_mod() {
267        let p: NoiseParams = "Noise_XXfallback+psk0_25519_AESGCM_SHA256".parse().unwrap();
268        assert!(p.handshake.modifiers.list.len() == 2);
269    }
270
271    #[test]
272    fn test_single_psk_mod() {
273        let p: NoiseParams = "Noise_XXpsk0_25519_AESGCM_SHA256".parse().unwrap();
274        match p.handshake.modifiers.list[0] {
275            HandshakeModifier::Psk(0) => {},
276            _ => panic!("modifier isn't as expected!"),
277        }
278    }
279
280    #[test]
281    fn test_multi_psk_mod() {
282        use self::HandshakeModifier::*;
283
284        let p: NoiseParams = "Noise_XXpsk0+psk1+psk2_25519_AESGCM_SHA256".parse().unwrap();
285        let mods = p.handshake.modifiers.list;
286        match (mods[0], mods[1], mods[2]) {
287            (Psk(0), Psk(1), Psk(2)) => {},
288            _ => panic!("modifiers weren't as expected! actual: {:?}", mods),
289        }
290    }
291
292    #[test]
293    fn test_duplicate_psk_mod() {
294        assert!("Noise_XXfallback+psk1_25519_AESGCM_SHA256".parse::<NoiseParams>().is_ok());
295        assert_eq!(
296            Error::Pattern(PatternProblem::UnsupportedModifier),
297            "Noise_XXfallback+fallback_25519_AESGCM_SHA256".parse::<NoiseParams>().unwrap_err()
298        );
299        assert_eq!(
300            Error::Pattern(PatternProblem::UnsupportedModifier),
301            "Noise_XXpsk1+psk1_25519_AESGCM_SHA256".parse::<NoiseParams>().unwrap_err()
302        );
303    }
304
305    #[test]
306    fn test_modified_psk_handshake() {
307        let p: NoiseParams = "Noise_XXpsk0_25519_AESGCM_SHA256".parse().unwrap();
308        let tokens = HandshakeTokens::try_from(&p.handshake).unwrap();
309        match tokens.msg_patterns[0][0] {
310            Token::Psk(_) => {},
311            _ => panic!("missing token!"),
312        }
313    }
314
315    #[test]
316    fn test_modified_multi_psk_handshake() {
317        let p: NoiseParams = "Noise_XXpsk0+psk2_25519_AESGCM_SHA256".parse().unwrap();
318
319        let tokens = HandshakeTokens::try_from(&p.handshake).unwrap();
320
321        match tokens.msg_patterns[0][0] {
322            Token::Psk(_) => {},
323            _ => panic!("missing token!"),
324        }
325
326        let second = &tokens.msg_patterns[1];
327        match second[second.len() - 1] {
328            Token::Psk(_) => {},
329            _ => panic!("missing token!"),
330        }
331    }
332
333    #[test]
334    fn test_invalid_psk_handshake() {
335        let p: NoiseParams = "Noise_XXpsk9_25519_AESGCM_SHA256".parse().unwrap();
336
337        assert_eq!(
338            Error::Pattern(PatternProblem::InvalidPsk),
339            HandshakeTokens::try_from(&p.handshake).unwrap_err()
340        );
341    }
342
343    #[test]
344    fn test_extraneous_string_data() {
345        assert_eq!(
346            Error::Pattern(PatternProblem::UnsupportedModifier),
347            "Noise_XXpsk0_25519_AESGCM_SHA256_HackThePlanet".parse::<NoiseParams>().unwrap_err()
348        );
349    }
350}