stun_rs/attributes/stun/
nonce_cookie.rs

1//! The Nonce Cookie used for Long-Term Credential Mechanism
2
3use crate::{attributes::stun::Nonce, StunError, StunErrorType};
4use base64::prelude::BASE64_STANDARD;
5use base64::Engine;
6use byteorder::{BigEndian, ByteOrder};
7use enumflags2::{bitflags, BitFlags};
8
9const NONCE_COOKIE_HEADER: &str = "obMatJos2";
10
11/// The STUN Security Feature flags
12#[bitflags]
13#[repr(u32)]
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
15pub enum StunSecurityFeatures {
16    /// Password algorithms
17    PasswordAlgorithms = 1 << 31,
18    /// User name anonymity
19    UserNameAnonymity = 1 << 30,
20}
21
22impl Nonce {
23    /// Creates a [`Nonce`] attribute if the value provided
24    /// is a valid sequence of `qdtext` or `quoted-pair`
25    pub fn new_nonce_cookie<S>(
26        value: S,
27        flags: Option<BitFlags<StunSecurityFeatures>>,
28    ) -> Result<Self, StunError>
29    where
30        S: AsRef<str>,
31    {
32        let features: u32 = match flags {
33            Some(flags) => flags.bits(),
34            None => 0,
35        };
36
37        let base64 = BASE64_STANDARD.encode(&features.to_be_bytes()[..3]);
38        let value = format!("{}{}{}", NONCE_COOKIE_HEADER, base64, value.as_ref());
39        Nonce::new(value)
40    }
41
42    /// Returns true if this is a nonce cookie
43    pub fn is_nonce_cookie(&self) -> bool {
44        self.as_str().starts_with(NONCE_COOKIE_HEADER)
45            && self.as_str().len() >= NONCE_COOKIE_HEADER.len() + 4
46    }
47
48    /// Returns the security flags set in the Nonce Cookie.
49    pub fn security_features(&self) -> Result<BitFlags<StunSecurityFeatures>, StunError> {
50        self.is_nonce_cookie()
51            .then_some(())
52            .ok_or_else(|| StunError::new(StunErrorType::InvalidParam, "Not nonce cookie"))?;
53
54        let flags = &self.as_str()[NONCE_COOKIE_HEADER.len()..NONCE_COOKIE_HEADER.len() + 4];
55        let mut bytes = [0x00; 4];
56        let size = BASE64_STANDARD
57            .decode_slice(flags, &mut bytes)
58            .map_err(|_e| {
59                StunError::new(
60                    StunErrorType::InvalidParam,
61                    "Error decoding base64 security features",
62                )
63            })?;
64
65        // (4-character) base64 STUN security features must be decoded in 24-bits (3 bytes)
66        (size == 3).then_some(()).ok_or_else(|| {
67            StunError::new(
68                StunErrorType::InvalidParam,
69                "Unexpected security features lenght",
70            )
71        })?;
72
73        let val = BigEndian::read_u32(&bytes);
74
75        let flags =
76            BitFlags::<StunSecurityFeatures>::from_bits_truncate_c(val, BitFlags::CONST_TOKEN);
77        Ok(flags)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use enumflags2::make_bitflags;
84
85    use super::*;
86
87    #[test]
88    fn nonce_cookie() {
89        let value = "f//499k954d6OL34oL9FSTvy64sA";
90        let nonce = Nonce::new_nonce_cookie(value, None).expect("Can not create nonce cookie");
91        assert!(nonce.is_nonce_cookie());
92        let flags = nonce
93            .security_features()
94            .expect("Can not get feature flags");
95        assert!(!flags.contains(StunSecurityFeatures::PasswordAlgorithms));
96        assert!(!flags.contains(StunSecurityFeatures::UserNameAnonymity));
97
98        let flags: BitFlags<StunSecurityFeatures> =
99            make_bitflags!(StunSecurityFeatures::{PasswordAlgorithms});
100        let nonce =
101            Nonce::new_nonce_cookie(value, Some(flags)).expect("Can not create nonce cookie");
102        assert!(nonce.is_nonce_cookie());
103        let flags = nonce
104            .security_features()
105            .expect("Can not get feature flags");
106        assert!(flags.contains(StunSecurityFeatures::PasswordAlgorithms));
107        assert!(!flags.contains(StunSecurityFeatures::UserNameAnonymity));
108
109        let flags: BitFlags<StunSecurityFeatures> =
110            make_bitflags!(StunSecurityFeatures::{UserNameAnonymity});
111        let nonce =
112            Nonce::new_nonce_cookie(value, Some(flags)).expect("Can not create nonce cookie");
113        assert!(nonce.is_nonce_cookie());
114        let flags = nonce
115            .security_features()
116            .expect("Can not get feature flags");
117        assert!(!flags.contains(StunSecurityFeatures::PasswordAlgorithms));
118        assert!(flags.contains(StunSecurityFeatures::UserNameAnonymity));
119
120        let flags: BitFlags<StunSecurityFeatures> =
121            make_bitflags!(StunSecurityFeatures::{PasswordAlgorithms | UserNameAnonymity});
122        let nonce =
123            Nonce::new_nonce_cookie(value, Some(flags)).expect("Can not create nonce cookie");
124        assert!(nonce.is_nonce_cookie());
125        let flags = nonce
126            .security_features()
127            .expect("Can not get feature flags");
128        assert!(flags.contains(StunSecurityFeatures::PasswordAlgorithms));
129        assert!(flags.contains(StunSecurityFeatures::UserNameAnonymity));
130
131        // Check empty nonce
132        let flags: BitFlags<StunSecurityFeatures> =
133            make_bitflags!(StunSecurityFeatures::{PasswordAlgorithms | UserNameAnonymity});
134        let nonce = Nonce::new_nonce_cookie("", Some(flags)).expect("Can not create nonce cookie");
135        assert!(nonce.is_nonce_cookie());
136        let flags = nonce
137            .security_features()
138            .expect("Can not get feature flags");
139        assert!(flags.contains(StunSecurityFeatures::PasswordAlgorithms));
140        assert!(flags.contains(StunSecurityFeatures::UserNameAnonymity));
141    }
142
143    #[test]
144    fn nonce_cookie_error() {
145        let value = String::from("f//499k954d6OL34oL9FSTvy64sA");
146        let nonce = Nonce::new(value).expect("Can not create a Nonce attribute");
147        assert!(!nonce.is_nonce_cookie());
148        let result = nonce.security_features();
149        assert_eq!(
150            result.expect_err("Error expected"),
151            StunErrorType::InvalidParam
152        );
153
154        // Error decoding base64 security features
155        let value = String::from("obMatJos2f//==8");
156        let nonce = Nonce::new(value).expect("Can not create a Nonce attribute");
157        assert!(nonce.is_nonce_cookie());
158        let result = nonce.security_features();
159        assert_eq!(
160            result.expect_err("Error expected"),
161            StunErrorType::InvalidParam
162        );
163    }
164}