1use 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#[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 let hmac: [u8; 20] = (&*raw.value).try_into().unwrap();
53 Ok(Self { hmac })
54 }
55}
56
57impl MessageIntegrity {
58 pub fn new(hmac: [u8; 20]) -> Self {
69 Self { hmac }
70 }
71
72 pub fn hmac(&self) -> &[u8; 20] {
83 &self.hmac
84 }
85
86 #[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 #[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#[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 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 pub fn hmac(&self) -> &[u8] {
238 &self.hmac
239 }
240
241 #[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 #[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 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 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 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 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}