stun_types/attribute/
integrity.rs

1// Copyright (C) 2020 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::convert::TryFrom;
10
11use crate::message::{StunParseError, StunWriteError};
12
13use super::{
14    Attribute, AttributeStaticType, AttributeType, AttributeWrite, AttributeWriteExt, RawAttribute,
15};
16
17use tracing::error;
18
19/// The MessageIntegrity [`Attribute`]
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct MessageIntegrity {
22    hmac: [u8; 20],
23}
24
25impl AttributeStaticType for MessageIntegrity {
26    const TYPE: AttributeType = AttributeType(0x0008);
27}
28impl Attribute for MessageIntegrity {
29    fn get_type(&self) -> AttributeType {
30        Self::TYPE
31    }
32
33    fn length(&self) -> u16 {
34        20
35    }
36}
37impl AttributeWrite for MessageIntegrity {
38    fn to_raw(&self) -> RawAttribute {
39        RawAttribute::new(MessageIntegrity::TYPE, &self.hmac)
40    }
41    fn write_into_unchecked(&self, dest: &mut [u8]) {
42        self.write_header_unchecked(dest);
43        dest[4..4 + self.hmac.len()].copy_from_slice(&self.hmac);
44    }
45}
46impl<'a> TryFrom<&RawAttribute<'a>> for MessageIntegrity {
47    type Error = StunParseError;
48
49    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
50        raw.check_type_and_len(Self::TYPE, 20..=20)?;
51        // sized checked earlier
52        let hmac: [u8; 20] = (&*raw.value).try_into().unwrap();
53        Ok(Self { hmac })
54    }
55}
56
57impl MessageIntegrity {
58    /// Create a new MessageIntegrity [`Attribute`]
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// # use stun_types::attribute::*;
64    /// let hmac = [0;20];
65    /// let integrity = MessageIntegrity::new(hmac);
66    /// assert_eq!(integrity.hmac(), &hmac);
67    /// ```
68    pub fn new(hmac: [u8; 20]) -> Self {
69        Self { hmac }
70    }
71
72    /// Retrieve the value of the hmac
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// # use stun_types::attribute::*;
78    /// let hmac = [0; 20];
79    /// let integrity = MessageIntegrity::new(hmac);
80    /// assert_eq!(integrity.hmac(), &hmac);
81    /// ```
82    pub fn hmac(&self) -> &[u8; 20] {
83        &self.hmac
84    }
85
86    /// Compute the Message Integrity value of a chunk of data using a key
87    ///
88    /// Note: use `MessageIntegrity::verify` for the actual verification to ensure constant time
89    /// checks of the values to defeat certain types of timing attacks.
90    ///
91    /// # Examples
92    /// ```
93    /// # use stun_types::attribute::*;
94    /// let key = [40; 10];
95    /// let data = [10; 30];
96    /// let expected = [209, 217, 210, 15, 124, 78, 87, 181, 211, 233, 165, 180, 44, 142, 81, 233, 138, 186, 184, 97];
97    /// let integrity = MessageIntegrity::compute(&data, &key).unwrap();
98    /// assert_eq!(integrity, expected);
99    /// ```
100    #[tracing::instrument(
101        name = "MessageIntegrity::compute",
102        level = "trace",
103        err,
104        ret,
105        skip(data, key)
106    )]
107    pub fn compute(data: &[u8], key: &[u8]) -> Result<[u8; 20], StunWriteError> {
108        use hmac::{Hmac, Mac};
109        let mut hmac =
110            Hmac::<sha1::Sha1>::new_from_slice(key).map_err(|_| StunWriteError::IntegrityFailed)?;
111        hmac.update(data);
112        Ok(hmac.finalize().into_bytes().into())
113    }
114
115    /// Compute the Message Integrity value of a chunk of data using a key
116    ///
117    /// # Examples
118    /// ```
119    /// # use stun_types::attribute::*;
120    /// let key = [40; 10];
121    /// let data = [10; 30];
122    /// let expected = [209, 217, 210, 15, 124, 78, 87, 181, 211, 233, 165, 180, 44, 142, 81, 233, 138, 186, 184, 97];
123    /// assert_eq!(MessageIntegrity::verify(&data, &key, &expected).unwrap(), ());
124    /// ```
125    #[tracing::instrument(
126        name = "MessageIntegrity::verify",
127        level = "debug",
128        skip(data, key, expected)
129    )]
130    pub fn verify(data: &[u8], key: &[u8], expected: &[u8; 20]) -> Result<(), StunParseError> {
131        use hmac::{Hmac, Mac};
132        let mut hmac = Hmac::<sha1::Sha1>::new_from_slice(key).map_err(|_| {
133            error!("failed to create hmac from key data");
134            StunParseError::InvalidAttributeData
135        })?;
136        hmac.update(data);
137        hmac.verify_slice(expected).map_err(|_| {
138            error!("integrity check failed");
139            StunParseError::IntegrityCheckFailed
140        })
141    }
142}
143
144impl std::fmt::Display for MessageIntegrity {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        write!(f, "{}: 0x", Self::TYPE)?;
147        for val in self.hmac.iter() {
148            write!(f, "{:02x}", val)?;
149        }
150        Ok(())
151    }
152}
153
154/// The MessageIntegritySha256 [`Attribute`]
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct MessageIntegritySha256 {
157    hmac: Vec<u8>,
158}
159
160impl AttributeStaticType for MessageIntegritySha256 {
161    const TYPE: AttributeType = AttributeType(0x001C);
162}
163impl Attribute for MessageIntegritySha256 {
164    fn get_type(&self) -> AttributeType {
165        Self::TYPE
166    }
167
168    fn length(&self) -> u16 {
169        self.hmac.len() as u16
170    }
171}
172impl AttributeWrite for MessageIntegritySha256 {
173    fn to_raw(&self) -> RawAttribute {
174        RawAttribute::new(MessageIntegritySha256::TYPE, &self.hmac)
175    }
176    fn write_into_unchecked(&self, dest: &mut [u8]) {
177        self.write_header_unchecked(dest);
178        dest[4..4 + self.hmac.len()].copy_from_slice(&self.hmac);
179    }
180}
181impl<'a> TryFrom<&RawAttribute<'a>> for MessageIntegritySha256 {
182    type Error = StunParseError;
183
184    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
185        raw.check_type_and_len(Self::TYPE, 16..=32)?;
186        if raw.value.len() % 4 != 0 {
187            return Err(StunParseError::InvalidAttributeData);
188        }
189        Ok(Self {
190            hmac: raw.value.to_vec(),
191        })
192    }
193}
194
195impl MessageIntegritySha256 {
196    /// Create a new MessageIntegritySha256 [`Attribute`]
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// # use stun_types::attribute::*;
202    /// let hmac = [0;20];
203    /// let integrity = MessageIntegritySha256::new(&hmac).unwrap();
204    /// assert_eq!(integrity.hmac(), &hmac);
205    /// ```
206    pub fn new(hmac: &[u8]) -> Result<Self, StunWriteError> {
207        if hmac.len() < 16 {
208            return Err(StunWriteError::TooSmall {
209                expected: 16,
210                actual: hmac.len(),
211            });
212        }
213        if hmac.len() > 32 {
214            return Err(StunWriteError::TooLarge {
215                expected: 32,
216                actual: hmac.len(),
217            });
218        }
219        if hmac.len() % 4 != 0 {
220            return Err(StunWriteError::IntegrityFailed);
221        }
222        Ok(Self {
223            hmac: hmac.to_vec(),
224        })
225    }
226
227    /// Retrieve the value of the hmac
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// # use stun_types::attribute::*;
233    /// let hmac = [0; 20];
234    /// let integrity = MessageIntegritySha256::new(&hmac).unwrap();
235    /// assert_eq!(integrity.hmac(), &hmac);
236    /// ```
237    pub fn hmac(&self) -> &[u8] {
238        &self.hmac
239    }
240
241    /// Compute the Message Integrity value of a chunk of data using a key
242    ///
243    /// Note: use `MessageIntegritySha256::verify` for the actual verification to ensure constant time
244    /// checks of the values to defeat certain types of timing attacks.
245    ///
246    /// # Examples
247    /// ```
248    /// # use stun_types::attribute::*;
249    /// let key = [40; 10];
250    /// let data = [10; 30];
251    /// let expected = [141, 112, 214, 41, 247, 110, 61, 95, 46, 245, 132, 79, 99, 16, 167, 95, 239, 168, 3, 63, 101, 78, 150, 24, 241, 139, 34, 229, 189, 37, 14, 113];
252    /// let integrity = MessageIntegritySha256::compute(&data, &key).unwrap();
253    /// assert_eq!(integrity, expected);
254    /// ```
255    #[tracing::instrument(
256        name = "MessageIntegritySha256::compute",
257        level = "trace",
258        err,
259        ret,
260        skip(data, key)
261    )]
262    pub fn compute(data: &[u8], key: &[u8]) -> Result<[u8; 32], StunWriteError> {
263        use hmac::{Hmac, Mac};
264        let mut hmac = Hmac::<sha2::Sha256>::new_from_slice(key)
265            .map_err(|_| StunWriteError::IntegrityFailed)?;
266        hmac.update(data);
267        let ret = hmac.finalize().into_bytes();
268        Ok(ret.into())
269    }
270
271    /// Compute the Message Integrity value of a chunk of data using a key
272    ///
273    /// # Examples
274    /// ```
275    /// # use stun_types::attribute::*;
276    /// let key = [40; 10];
277    /// let data = [10; 30];
278    /// let expected = [141, 112, 214, 41, 247, 110, 61, 95, 46, 245, 132, 79, 99, 16, 167, 95, 239, 168, 3, 63, 101, 78, 150, 24, 241, 139, 34, 229, 189, 37, 14, 113];
279    /// assert_eq!(MessageIntegritySha256::verify(&data, &key, &expected).unwrap(), ());
280    /// ```
281    #[tracing::instrument(
282        name = "MessageIntegrity::verify",
283        level = "debug",
284        skip(data, key, expected)
285    )]
286    pub fn verify(data: &[u8], key: &[u8], expected: &[u8]) -> Result<(), StunParseError> {
287        use hmac::{Hmac, Mac};
288        let mut hmac = Hmac::<sha2::Sha256>::new_from_slice(key).map_err(|_| {
289            error!("failed to create hmac from key data");
290            StunParseError::InvalidAttributeData
291        })?;
292        hmac.update(data);
293        hmac.verify_truncated_left(expected).map_err(|_| {
294            error!("integrity check failed");
295            StunParseError::IntegrityCheckFailed
296        })
297    }
298}
299
300impl std::fmt::Display for MessageIntegritySha256 {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        write!(f, "{}: 0x", Self::TYPE)?;
303        for val in self.hmac.iter() {
304            write!(f, "{:02x}", val)?;
305        }
306        Ok(())
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use byteorder::{BigEndian, ByteOrder};
314    use tracing::trace;
315
316    #[test]
317    fn message_integrity() {
318        let _log = crate::tests::test_init_log();
319        let val = [1; 20];
320        let attr = MessageIntegrity::new(val);
321        trace!("{attr}");
322        assert_eq!(attr.hmac(), &val);
323        assert_eq!(attr.length(), 20);
324        let raw = RawAttribute::from(&attr);
325        trace!("{raw}");
326        assert_eq!(raw.get_type(), MessageIntegrity::TYPE);
327        let mapped2 = MessageIntegrity::try_from(&raw).unwrap();
328        assert_eq!(mapped2.hmac(), &val);
329        // truncate by one byte
330        let mut data: Vec<_> = raw.clone().into();
331        let len = data.len();
332        BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
333        assert!(matches!(
334            MessageIntegrity::try_from(
335                &RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()
336            ),
337            Err(StunParseError::Truncated {
338                expected: 20,
339                actual: 19
340            })
341        ));
342        // provide incorrectly typed data
343        let mut data: Vec<_> = raw.into();
344        BigEndian::write_u16(&mut data[0..2], 0);
345        assert!(matches!(
346            MessageIntegrity::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
347            Err(StunParseError::WrongAttributeImplementation)
348        ));
349    }
350
351    #[test]
352    fn message_integrity_sha256() {
353        let _log = crate::tests::test_init_log();
354        let val = [1; 32];
355        let attr = MessageIntegritySha256::new(&val).unwrap();
356        trace!("{attr}");
357        assert_eq!(attr.hmac(), &val);
358        assert_eq!(attr.length(), 32);
359        let raw = RawAttribute::from(&attr);
360        trace!("{raw}");
361        assert_eq!(raw.get_type(), MessageIntegritySha256::TYPE);
362        let mapped2 = MessageIntegritySha256::try_from(&raw).unwrap();
363        assert_eq!(mapped2.hmac(), &val);
364        // truncate by one byte
365        let mut data: Vec<_> = raw.clone().into();
366        let len = data.len();
367        BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
368        assert!(matches!(
369            MessageIntegritySha256::try_from(
370                &RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()
371            ),
372            Err(StunParseError::InvalidAttributeData)
373        ));
374        // provide incorrectly typed data
375        let mut data: Vec<_> = raw.into();
376        BigEndian::write_u16(&mut data[0..2], 0);
377        assert!(matches!(
378            MessageIntegritySha256::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
379            Err(StunParseError::WrongAttributeImplementation)
380        ));
381    }
382
383    #[test]
384    fn message_integrity_sha256_new_too_large() {
385        let _log = crate::tests::test_init_log();
386        let val = [1; 33];
387        assert!(matches!(
388            MessageIntegritySha256::new(&val),
389            Err(StunWriteError::TooLarge {
390                expected: 32,
391                actual: 33
392            })
393        ));
394    }
395
396    #[test]
397    fn message_integrity_sha256_new_too_small() {
398        let _log = crate::tests::test_init_log();
399        let val = [1; 15];
400        assert!(matches!(
401            MessageIntegritySha256::new(&val),
402            Err(StunWriteError::TooSmall {
403                expected: 16,
404                actual: 15
405            })
406        ));
407    }
408
409    #[test]
410    fn message_integrity_sha256_new_not_multiple_of_4() {
411        let _log = crate::tests::test_init_log();
412        let val = [1; 19];
413        assert!(matches!(
414            MessageIntegritySha256::new(&val),
415            Err(StunWriteError::IntegrityFailed)
416        ));
417    }
418}