stun_rs/attributes/stun/
nonce_cookie.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! The Nonce Cookie used for Long-Term Credential Mechanism

use crate::{attributes::stun::Nonce, StunError, StunErrorType};
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use byteorder::{BigEndian, ByteOrder};
use enumflags2::{bitflags, BitFlags};

const NONCE_COOKIE_HEADER: &str = "obMatJos2";

/// The STUN Security Feature flags
#[bitflags]
#[repr(u32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum StunSecurityFeatures {
    /// Password algorithms
    PasswordAlgorithms = 1 << 31,
    /// User name anonymity
    UserNameAnonymity = 1 << 30,
}

impl Nonce {
    /// Creates a [`Nonce`] attribute if the value provided
    /// is a valid sequence of `qdtext` or `quoted-pair`
    pub fn new_nonce_cookie<S>(
        value: S,
        flags: Option<BitFlags<StunSecurityFeatures>>,
    ) -> Result<Self, StunError>
    where
        S: AsRef<str>,
    {
        let features: u32 = match flags {
            Some(flags) => flags.bits(),
            None => 0,
        };

        let base64 = BASE64_STANDARD.encode(&features.to_be_bytes()[..3]);
        let value = format!("{}{}{}", NONCE_COOKIE_HEADER, base64, value.as_ref());
        Nonce::new(value)
    }

    /// Returns true if this is a nonce cookie
    pub fn is_nonce_cookie(&self) -> bool {
        self.as_str().starts_with(NONCE_COOKIE_HEADER)
            && self.as_str().len() >= NONCE_COOKIE_HEADER.len() + 4
    }

    /// Returns the security flags set in the Nonce Cookie.
    pub fn security_features(&self) -> Result<BitFlags<StunSecurityFeatures>, StunError> {
        self.is_nonce_cookie()
            .then_some(())
            .ok_or_else(|| StunError::new(StunErrorType::InvalidParam, "Not nonce cookie"))?;

        let flags = &self.as_str()[NONCE_COOKIE_HEADER.len()..NONCE_COOKIE_HEADER.len() + 4];
        let mut bytes = [0x00; 4];
        let size = BASE64_STANDARD
            .decode_slice(flags, &mut bytes)
            .map_err(|_e| {
                StunError::new(
                    StunErrorType::InvalidParam,
                    "Error decoding base64 security features",
                )
            })?;

        // (4-character) base64 STUN security features must be decoded in 24-bits (3 bytes)
        (size == 3).then_some(()).ok_or_else(|| {
            StunError::new(
                StunErrorType::InvalidParam,
                "Unexpected security features lenght",
            )
        })?;

        let val = BigEndian::read_u32(&bytes);

        let flags =
            BitFlags::<StunSecurityFeatures>::from_bits_truncate_c(val, BitFlags::CONST_TOKEN);
        Ok(flags)
    }
}

#[cfg(test)]
mod tests {
    use enumflags2::make_bitflags;

    use super::*;

    #[test]
    fn nonce_cookie() {
        let value = "f//499k954d6OL34oL9FSTvy64sA";
        let nonce = Nonce::new_nonce_cookie(value, None).expect("Can not create nonce cookie");
        assert!(nonce.is_nonce_cookie());
        let flags = nonce
            .security_features()
            .expect("Can not get feature flags");
        assert!(!flags.contains(StunSecurityFeatures::PasswordAlgorithms));
        assert!(!flags.contains(StunSecurityFeatures::UserNameAnonymity));

        let flags: BitFlags<StunSecurityFeatures> =
            make_bitflags!(StunSecurityFeatures::{PasswordAlgorithms});
        let nonce =
            Nonce::new_nonce_cookie(value, Some(flags)).expect("Can not create nonce cookie");
        assert!(nonce.is_nonce_cookie());
        let flags = nonce
            .security_features()
            .expect("Can not get feature flags");
        assert!(flags.contains(StunSecurityFeatures::PasswordAlgorithms));
        assert!(!flags.contains(StunSecurityFeatures::UserNameAnonymity));

        let flags: BitFlags<StunSecurityFeatures> =
            make_bitflags!(StunSecurityFeatures::{UserNameAnonymity});
        let nonce =
            Nonce::new_nonce_cookie(value, Some(flags)).expect("Can not create nonce cookie");
        assert!(nonce.is_nonce_cookie());
        let flags = nonce
            .security_features()
            .expect("Can not get feature flags");
        assert!(!flags.contains(StunSecurityFeatures::PasswordAlgorithms));
        assert!(flags.contains(StunSecurityFeatures::UserNameAnonymity));

        let flags: BitFlags<StunSecurityFeatures> =
            make_bitflags!(StunSecurityFeatures::{PasswordAlgorithms | UserNameAnonymity});
        let nonce =
            Nonce::new_nonce_cookie(value, Some(flags)).expect("Can not create nonce cookie");
        assert!(nonce.is_nonce_cookie());
        let flags = nonce
            .security_features()
            .expect("Can not get feature flags");
        assert!(flags.contains(StunSecurityFeatures::PasswordAlgorithms));
        assert!(flags.contains(StunSecurityFeatures::UserNameAnonymity));

        // Check empty nonce
        let flags: BitFlags<StunSecurityFeatures> =
            make_bitflags!(StunSecurityFeatures::{PasswordAlgorithms | UserNameAnonymity});
        let nonce = Nonce::new_nonce_cookie("", Some(flags)).expect("Can not create nonce cookie");
        assert!(nonce.is_nonce_cookie());
        let flags = nonce
            .security_features()
            .expect("Can not get feature flags");
        assert!(flags.contains(StunSecurityFeatures::PasswordAlgorithms));
        assert!(flags.contains(StunSecurityFeatures::UserNameAnonymity));
    }

    #[test]
    fn nonce_cookie_error() {
        let value = String::from("f//499k954d6OL34oL9FSTvy64sA");
        let nonce = Nonce::new(value).expect("Can not create a Nonce attribute");
        assert!(!nonce.is_nonce_cookie());
        let result = nonce.security_features();
        assert_eq!(
            result.expect_err("Error expected"),
            StunErrorType::InvalidParam
        );

        // Error decoding base64 security features
        let value = String::from("obMatJos2f//==8");
        let nonce = Nonce::new(value).expect("Can not create a Nonce attribute");
        assert!(nonce.is_nonce_cookie());
        let result = nonce.security_features();
        assert_eq!(
            result.expect_err("Error expected"),
            StunErrorType::InvalidParam
        );
    }
}