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