stun_rs/attributes/stun/
message_integrity.rs

1use crate::attributes::integrity_attr::message_integrity_attribute;
2use crate::attributes::integrity_attr::HmacSha;
3use crate::Decode;
4use hmac_sha1::hmac_sha1;
5use std::convert::TryInto;
6
7const MESSAGE_INTEGRITY: u16 = 0x0008;
8const MESSAGE_INTEGRITY_SIZE: usize = 20;
9
10impl HmacSha for MessageIntegrity {
11    fn hmac_sha(key: &[u8], message: &[u8]) -> Vec<u8> {
12        hmac_sha1(key, message).to_vec()
13    }
14}
15
16message_integrity_attribute!(
17    /// The [`MessageIntegrity`] attribute contains an `HMAC-SHA1`
18    /// [`RFC2104`](https://datatracker.ietf.org/doc/html/rfc2104)
19    /// of the STUN message. This attribute can be present in any STUN
20    /// message type.
21    ///
22    /// # Examples
23    ///```rust
24    /// # use stun_rs::attributes::stun::MessageIntegrity;
25    /// # use stun_rs::attributes::{AttributeType, StunAttributeType};
26    /// # use stun_rs::HMACKey;
27    /// # use std::error::Error;
28    /// #
29    /// # fn main() -> Result<(), Box<dyn Error>> {
30    /// // use short-term-credentials to generate the key
31    /// let key = HMACKey::new_short_term("foo bar")?;
32    /// let attr = MessageIntegrity::new(key);
33    /// assert_eq!(attr.attribute_type(), AttributeType::from(0x0008));
34    /// #
35    /// #  Ok(())
36    /// # }
37    ///```
38    MessageIntegrity,
39    MESSAGE_INTEGRITY,
40    MESSAGE_INTEGRITY_SIZE
41);
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::attributes::{EncodeAttributeValue, Verifiable};
47    use crate::context::AttributeEncoderContext;
48    use crate::{Algorithm, AlgorithmId, DecoderContextBuilder, HMACKey};
49    use crate::{StunAttribute, StunErrorType};
50
51    #[test]
52    fn encode_message_integrity_with_short_term() {
53        let mut input: [u8; 48] = [0xff; 48];
54        input.copy_from_slice(&stun_vectors::SAMPLE_IPV4_RESPONSE[..48]);
55        // Set message length to 28 bytes
56        input[3] = 0x1c;
57
58        let mut output: [u8; MESSAGE_INTEGRITY_SIZE] = [0xff; MESSAGE_INTEGRITY_SIZE];
59        let hmac_hash = [
60            0x2b, 0x91, 0xf5, 0x99, 0xfd, 0x9e, 0x90, 0xc3, 0x8c, 0x74, 0x89, 0xf9, 0x2a, 0xf9,
61            0xba, 0x53, 0xf0, 0x6b, 0xe7, 0xd7,
62        ];
63
64        let password = "VOkJxbRl1RmTxUk/WvJxBt";
65        let key = HMACKey::new_short_term(password).expect("Could not create HMACKey");
66        let attr = MessageIntegrity::new(key);
67        let ctx = AttributeEncoderContext::new(None, &input, &mut output);
68
69        let size = attr.encode(ctx).expect("Could not encode MessageIntegrty");
70        assert_eq!(size, MESSAGE_INTEGRITY_SIZE);
71        // Expect dummy value
72        output.iter().for_each(|x| assert_eq!(*x, 0x00));
73
74        // Set message length to 54 bytes
75        input[3] = 0x34;
76        let ctx = AttributeEncoderContext::new(None, &input, &mut output[..size]);
77
78        // Encode
79        attr.post_encode(ctx)
80            .expect("Could not encode MessageIntegrty");
81
82        assert_eq!(output, hmac_hash);
83
84        // Can not encode a decodable value
85        let ctx = AttributeEncoderContext::new(None, &input, &mut output);
86        let attr = MessageIntegrity::from(&hmac_hash);
87        let error = attr
88            .encode(ctx)
89            .expect_err("Could not encode Decodable attribute");
90        assert_eq!(error, StunErrorType::InvalidParam);
91
92        let ctx = AttributeEncoderContext::new(None, &input, &mut output);
93        let error = attr
94            .post_encode(ctx)
95            .expect_err("Could not encode Decodable attribute");
96        assert_eq!(error, StunErrorType::InvalidParam);
97    }
98
99    #[test]
100    fn encode_message_integrity_with_long_term() {
101        let mut input: [u8; 92] = [0xff; 92];
102        input.copy_from_slice(&stun_vectors::SAMPLE_REQUEST_LONG_TERM_AUTH[..92]);
103        // Set message length to 72 bytes
104        input[3] = 0x48;
105
106        let mut output: [u8; MESSAGE_INTEGRITY_SIZE] = [0xff; MESSAGE_INTEGRITY_SIZE];
107        let hmac_hash = [
108            0xF6, 0x70, 0x24, 0x65, 0x6D, 0xD6, 0x4A, 0x3E, 0x02, 0xB8, 0xE0, 0x71, 0x2E, 0x85,
109            0xC9, 0xA2, 0x8C, 0xA8, 0x96, 0x66,
110        ];
111
112        let username = "\u{30DE}\u{30C8}\u{30EA}\u{30C3}\u{30AF}\u{30B9}";
113        // Unicode `codepoint` {00AD} is disallowed in PRECIS, so we use the
114        // result of applying `SASLprep`
115        // let password = "The\u{00AD}M\u{00AA}tr\u{2168}";
116        let password = "TheMatrIX";
117        let realm = "example.org";
118
119        let algorithm = Algorithm::from(AlgorithmId::MD5);
120        let key = HMACKey::new_long_term(username, realm, password, algorithm)
121            .expect("Could not create HMACKey");
122        let attr = MessageIntegrity::new(key);
123        let ctx = AttributeEncoderContext::new(None, &input, &mut output);
124
125        let size = attr.encode(ctx).expect("Could not encode MessageIntegrty");
126        assert_eq!(size, MESSAGE_INTEGRITY_SIZE);
127        // Expect dummy value
128        output.iter().for_each(|x| assert_eq!(*x, 0x00));
129
130        // Set message length to 96 bytes
131        input[3] = 0x60;
132        let ctx = AttributeEncoderContext::new(None, &input, &mut output[..size]);
133
134        // Encode
135        attr.post_encode(ctx)
136            .expect("Could not encode MessageIntegrty");
137
138        assert_eq!(output, hmac_hash);
139    }
140
141    #[test]
142    fn validate_message_integrity_with_short_term() {
143        let input = crate::get_input_text::<MessageIntegrity>(&stun_vectors::SAMPLE_IPV4_RESPONSE)
144            .expect("Can not get input buffer");
145        let hmac_hash = [
146            0x2b, 0x91, 0xf5, 0x99, 0xfd, 0x9e, 0x90, 0xc3, 0x8c, 0x74, 0x89, 0xf9, 0x2a, 0xf9,
147            0xba, 0x53, 0xf0, 0x6b, 0xe7, 0xd7,
148        ];
149
150        let attr = MessageIntegrity::from(&hmac_hash);
151        let _val = format!("{:?}", attr);
152
153        let password = "VOkJxbRl1RmTxUk/WvJxBt";
154        let key = HMACKey::new_short_term(password).expect("Could not create HMACKey");
155
156        assert!(attr.validate(&input, &key));
157    }
158
159    #[test]
160    fn validation_error() {
161        let input = crate::get_input_text::<MessageIntegrity>(&stun_vectors::SAMPLE_IPV4_RESPONSE)
162            .expect("Can not get input buffer");
163        let hmac_hash = [
164            0x2b, 0x91, 0xf5, 0x99, 0xfd, 0x9e, 0x90, 0xc3, 0x8c, 0x74, 0x89, 0xf9, 0x2a, 0xf9,
165            0xba, 0x53, 0xf0, 0x6b, 0xe7, 0xd7,
166        ];
167
168        let attr = MessageIntegrity::from(&hmac_hash);
169        let _val = format!("{:?}", attr);
170
171        // Validation fail without key
172        let ctx = DecoderContextBuilder::default().build();
173        assert!(!attr.verify(&input, &ctx));
174
175        let input: [u8; 48] = [0xff; 48];
176        let password = "VOkJxbRl1RmTxUk/WvJxBt";
177        let key = HMACKey::new_short_term(password).expect("Could not create HMACKey");
178        let attr = MessageIntegrity::new(key.clone());
179        assert!(!attr.validate(&input, &key));
180    }
181
182    #[test]
183    fn validate_message_integrity_with_long_term() {
184        let input =
185            crate::get_input_text::<MessageIntegrity>(&stun_vectors::SAMPLE_REQUEST_LONG_TERM_AUTH)
186                .expect("Can not get input buffer");
187        let hmac_hash = [
188            0xF6, 0x70, 0x24, 0x65, 0x6D, 0xD6, 0x4A, 0x3E, 0x02, 0xB8, 0xE0, 0x71, 0x2E, 0x85,
189            0xC9, 0xA2, 0x8C, 0xA8, 0x96, 0x66,
190        ];
191
192        let attr = MessageIntegrity::from(hmac_hash);
193
194        let username = "\u{30DE}\u{30C8}\u{30EA}\u{30C3}\u{30AF}\u{30B9}";
195        // Unicode `codepoint` {00AD} is disallowed in PRECIS, so we use the
196        // result of applying `SASLprep`
197        // let password = "The\u{00AD}M\u{00AA}tr\u{2168}";
198        let password = "TheMatrIX";
199        let realm = "example.org";
200        let algorithm = Algorithm::from(AlgorithmId::MD5);
201        let key = HMACKey::new_long_term(username, realm, password, algorithm)
202            .expect("Could not create HMACKey");
203
204        assert!(attr.validate(&input, &key));
205    }
206
207    #[test]
208    fn message_integrity_stunt_attribute() {
209        let key = HMACKey::new_short_term("test").expect("Can not create short term credential");
210        let attr = StunAttribute::MessageIntegrity(MessageIntegrity::new(key));
211        assert!(attr.is_message_integrity());
212        assert!(attr.as_message_integrity().is_ok());
213        assert!(attr.as_error_code().is_err());
214
215        assert!(attr.attribute_type().is_comprehension_required());
216        assert!(!attr.attribute_type().is_comprehension_optional());
217
218        let dbg_fmt = format!("{:?}", attr);
219        assert_eq!("MessageIntegrity(Encodable(EncodableMessageIntegrity(HMACKey(HMACKeyPriv { mechanism: ShortTerm, key: [116, 101, 115, 116] }))))", dbg_fmt);
220    }
221}