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, AttributeFromRaw, AttributeStaticType, AttributeType, AttributeWrite,
15    AttributeWriteExt, RawAttribute,
16};
17
18use tracing::error;
19
20/// The MessageIntegrity [`Attribute`]
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct MessageIntegrity {
23    hmac: [u8; 20],
24}
25
26impl AttributeStaticType for MessageIntegrity {
27    const TYPE: AttributeType = AttributeType(0x0008);
28}
29
30impl Attribute for MessageIntegrity {
31    fn get_type(&self) -> AttributeType {
32        Self::TYPE
33    }
34
35    fn length(&self) -> u16 {
36        20
37    }
38}
39
40impl AttributeWrite for MessageIntegrity {
41    fn to_raw(&self) -> RawAttribute {
42        RawAttribute::new(MessageIntegrity::TYPE, &self.hmac)
43    }
44    fn write_into_unchecked(&self, dest: &mut [u8]) {
45        self.write_header_unchecked(dest);
46        dest[4..4 + self.hmac.len()].copy_from_slice(&self.hmac);
47    }
48}
49
50impl AttributeFromRaw<'_> for MessageIntegrity {
51    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
52    where
53        Self: Sized,
54    {
55        Self::try_from(raw)
56    }
57}
58
59impl TryFrom<&RawAttribute<'_>> for MessageIntegrity {
60    type Error = StunParseError;
61
62    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
63        raw.check_type_and_len(Self::TYPE, 20..=20)?;
64        // sized checked earlier
65        let hmac: [u8; 20] = (&*raw.value).try_into().unwrap();
66        Ok(Self { hmac })
67    }
68}
69
70impl MessageIntegrity {
71    /// Create a new MessageIntegrity [`Attribute`]
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// # use stun_types::attribute::*;
77    /// let hmac = [0;20];
78    /// let integrity = MessageIntegrity::new(hmac);
79    /// assert_eq!(integrity.hmac(), &hmac);
80    /// ```
81    pub fn new(hmac: [u8; 20]) -> Self {
82        Self { hmac }
83    }
84
85    /// Retrieve the value of the hmac
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// # use stun_types::attribute::*;
91    /// let hmac = [0; 20];
92    /// let integrity = MessageIntegrity::new(hmac);
93    /// assert_eq!(integrity.hmac(), &hmac);
94    /// ```
95    pub fn hmac(&self) -> &[u8; 20] {
96        &self.hmac
97    }
98
99    /// Compute the Message Integrity value of a chunk of data using a key
100    ///
101    /// Note: use `MessageIntegrity::verify` for the actual verification to ensure constant time
102    /// checks of the values to defeat certain types of timing attacks.
103    ///
104    /// # Examples
105    /// ```
106    /// # use stun_types::attribute::*;
107    /// let key = [40; 10];
108    /// let data = [10; 30];
109    /// let expected = [209, 217, 210, 15, 124, 78, 87, 181, 211, 233, 165, 180, 44, 142, 81, 233, 138, 186, 184, 97];
110    /// let integrity = MessageIntegrity::compute(&data, &key).unwrap();
111    /// assert_eq!(integrity, expected);
112    /// ```
113    #[tracing::instrument(
114        name = "MessageIntegrity::compute",
115        level = "trace",
116        err,
117        ret,
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            error!("integrity check failed");
152            StunParseError::IntegrityCheckFailed
153        })
154    }
155}
156
157impl std::fmt::Display for MessageIntegrity {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        write!(f, "{}: 0x", Self::TYPE)?;
160        for val in self.hmac.iter() {
161            write!(f, "{:02x}", val)?;
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        ret,
285        skip(data, key)
286    )]
287    pub fn compute(data: &[u8], key: &[u8]) -> Result<[u8; 32], StunWriteError> {
288        use hmac::{Hmac, Mac};
289        let mut hmac = Hmac::<sha2::Sha256>::new_from_slice(key)
290            .map_err(|_| StunWriteError::IntegrityFailed)?;
291        hmac.update(data);
292        let ret = hmac.finalize().into_bytes();
293        Ok(ret.into())
294    }
295
296    /// Compute the Message Integrity value of a chunk of data using a key
297    ///
298    /// # Examples
299    /// ```
300    /// # use stun_types::attribute::*;
301    /// let key = [40; 10];
302    /// let data = [10; 30];
303    /// 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];
304    /// assert_eq!(MessageIntegritySha256::verify(&data, &key, &expected).unwrap(), ());
305    /// ```
306    #[tracing::instrument(
307        name = "MessageIntegrity::verify",
308        level = "debug",
309        skip(data, key, expected)
310    )]
311    pub fn verify(data: &[u8], key: &[u8], expected: &[u8]) -> Result<(), StunParseError> {
312        use hmac::{Hmac, Mac};
313        let mut hmac = Hmac::<sha2::Sha256>::new_from_slice(key).map_err(|_| {
314            error!("failed to create hmac from key data");
315            StunParseError::InvalidAttributeData
316        })?;
317        hmac.update(data);
318        hmac.verify_truncated_left(expected).map_err(|_| {
319            error!("integrity check failed");
320            StunParseError::IntegrityCheckFailed
321        })
322    }
323}
324
325impl std::fmt::Display for MessageIntegritySha256 {
326    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327        write!(f, "{}: 0x", Self::TYPE)?;
328        for val in self.hmac.iter() {
329            write!(f, "{:02x}", val)?;
330        }
331        Ok(())
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338    use byteorder::{BigEndian, ByteOrder};
339    use tracing::trace;
340
341    #[test]
342    fn message_integrity() {
343        let _log = crate::tests::test_init_log();
344        let val = [1; 20];
345        let attr = MessageIntegrity::new(val);
346        trace!("{attr}");
347        assert_eq!(attr.hmac(), &val);
348        assert_eq!(attr.length(), 20);
349        let raw = RawAttribute::from(&attr);
350        trace!("{raw}");
351        assert_eq!(raw.get_type(), MessageIntegrity::TYPE);
352        let mapped2 = MessageIntegrity::try_from(&raw).unwrap();
353        assert_eq!(mapped2.hmac(), &val);
354        // truncate by one byte
355        let mut data: Vec<_> = raw.clone().into();
356        let len = data.len();
357        BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
358        assert!(matches!(
359            MessageIntegrity::try_from(
360                &RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()
361            ),
362            Err(StunParseError::Truncated {
363                expected: 20,
364                actual: 19
365            })
366        ));
367        // provide incorrectly typed data
368        let mut data: Vec<_> = raw.into();
369        BigEndian::write_u16(&mut data[0..2], 0);
370        assert!(matches!(
371            MessageIntegrity::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
372            Err(StunParseError::WrongAttributeImplementation)
373        ));
374    }
375
376    #[test]
377    fn message_integrity_sha256() {
378        let _log = crate::tests::test_init_log();
379        let val = [1; 32];
380        let attr = MessageIntegritySha256::new(&val).unwrap();
381        trace!("{attr}");
382        assert_eq!(attr.hmac(), &val);
383        assert_eq!(attr.length(), 32);
384        let raw = RawAttribute::from(&attr);
385        trace!("{raw}");
386        assert_eq!(raw.get_type(), MessageIntegritySha256::TYPE);
387        let mapped2 = MessageIntegritySha256::try_from(&raw).unwrap();
388        assert_eq!(mapped2.hmac(), &val);
389        // truncate by one byte
390        let mut data: Vec<_> = raw.clone().into();
391        let len = data.len();
392        BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
393        assert!(matches!(
394            MessageIntegritySha256::try_from(
395                &RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()
396            ),
397            Err(StunParseError::InvalidAttributeData)
398        ));
399        // provide incorrectly typed data
400        let mut data: Vec<_> = raw.into();
401        BigEndian::write_u16(&mut data[0..2], 0);
402        assert!(matches!(
403            MessageIntegritySha256::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
404            Err(StunParseError::WrongAttributeImplementation)
405        ));
406    }
407
408    #[test]
409    fn message_integrity_sha256_new_too_large() {
410        let _log = crate::tests::test_init_log();
411        let val = [1; 33];
412        assert!(matches!(
413            MessageIntegritySha256::new(&val),
414            Err(StunWriteError::TooLarge {
415                expected: 32,
416                actual: 33
417            })
418        ));
419    }
420
421    #[test]
422    fn message_integrity_sha256_new_too_small() {
423        let _log = crate::tests::test_init_log();
424        let val = [1; 15];
425        assert!(matches!(
426            MessageIntegritySha256::new(&val),
427            Err(StunWriteError::TooSmall {
428                expected: 16,
429                actual: 15
430            })
431        ));
432    }
433
434    #[test]
435    fn message_integrity_sha256_new_not_multiple_of_4() {
436        let _log = crate::tests::test_init_log();
437        let val = [1; 19];
438        assert!(matches!(
439            MessageIntegritySha256::new(&val),
440            Err(StunWriteError::IntegrityFailed)
441        ));
442    }
443}