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