Skip to main content

rtc_stun/
integrity.rs

1#[cfg(test)]
2mod integrity_test;
3
4use crate::attributes::*;
5use crate::message::*;
6use crypto::{CryptoError, HashAlgorithm, HmacAlgorithm, RTCCrypto, SecretVec};
7use shared::error::*;
8use std::fmt;
9
10// separator for credentials.
11pub(crate) const CREDENTIALS_SEP: &str = ":";
12
13// MessageIntegrity represents MESSAGE-INTEGRITY attribute.
14//
15// add_to and Check methods are using zero-allocation version of hmac, see
16// newHMAC function and internal/hmac/pool.go.
17//
18// RFC 5389 Section 15.4
19#[derive(Clone)]
20/// The `MESSAGE-INTEGRITY` key: an HMAC-SHA1 is computed over the message with it.
21///
22/// Built from a short-term password, or from a long-term username/realm/password triple.
23pub struct MessageIntegrity<'a> {
24    key: SecretVec,
25    crypto: &'a dyn RTCCrypto,
26}
27
28fn crypto_error(error: CryptoError) -> Error {
29    Error::Crypto(error.to_string())
30}
31
32impl<'a> fmt::Display for MessageIntegrity<'a> {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        write!(
35            f,
36            "MESSAGE-INTEGRITY key: [REDACTED; {} bytes]",
37            self.key.len()
38        )
39    }
40}
41
42impl<'a> Setter for MessageIntegrity<'a> {
43    // add_to adds MESSAGE-INTEGRITY attribute to message.
44    //
45    // CPU costly, see BenchmarkMessageIntegrity_AddTo.
46    fn add_to(&self, m: &mut Message) -> Result<()> {
47        for a in &m.attributes.0 {
48            // Message should not contain FINGERPRINT attribute
49            // before MESSAGE-INTEGRITY.
50            if a.typ == ATTR_FINGERPRINT {
51                return Err(Error::ErrFingerprintBeforeIntegrity);
52            }
53        }
54        // The text used as input to HMAC is the STUN message,
55        // including the header, up to and including the attribute preceding the
56        // MESSAGE-INTEGRITY attribute.
57        let length = m.length;
58        // Adjusting m.Length to contain MESSAGE-INTEGRITY TLV.
59        m.length += (MESSAGE_INTEGRITY_SIZE + ATTRIBUTE_HEADER_SIZE) as u32;
60        m.write_length(); // writing length to m.Raw
61        let mut value = [0_u8; MESSAGE_INTEGRITY_SIZE];
62        // A STUN message is authenticated once, so the MAC is keyed here rather than held. On a
63        // per-packet path the keyed object belongs in the surrounding state instead.
64        let result = self
65            .crypto
66            .new_hmac(HmacAlgorithm::Sha1, self.key.as_ref())
67            .and_then(|mut mac| mac.sign(&[&m.raw], &mut value));
68        m.length = length; // changing m.Length back
69        m.write_length();
70        result.map_err(crypto_error)?;
71
72        m.add(ATTR_MESSAGE_INTEGRITY, &value);
73
74        Ok(())
75    }
76}
77
78pub(crate) const MESSAGE_INTEGRITY_SIZE: usize = 20;
79
80impl<'a> MessageIntegrity<'a> {
81    /// Creates a raw-key integrity attribute with an explicit crypto provider.
82    #[must_use]
83    pub fn new_raw_integrity_with_provider(
84        key: impl Into<Vec<u8>>,
85        crypto: &'a dyn RTCCrypto,
86    ) -> Self {
87        Self {
88            key: SecretVec::new(key.into()),
89            crypto,
90        }
91    }
92
93    /// Creates a short-term integrity attribute with an explicit crypto provider.
94    #[must_use]
95    pub fn new_short_term_integrity_with_provider(
96        password: String,
97        crypto: &'a dyn RTCCrypto,
98    ) -> Self {
99        Self::new_raw_integrity_with_provider(password.into_bytes(), crypto)
100    }
101
102    /// Creates a long-term integrity attribute with an explicit crypto provider.
103    pub fn new_long_term_integrity_with_provider(
104        username: String,
105        realm: String,
106        password: String,
107        crypto: &'a dyn RTCCrypto,
108    ) -> Result<Self> {
109        let key = MessageIntegrity::long_term_integrity_key(username, realm, password, crypto)?;
110        Ok(Self::new_raw_integrity_with_provider(key, crypto))
111    }
112
113    /// Creates a long-term integrity key with an explicit crypto provider.
114    pub fn long_term_integrity_key(
115        username: String,
116        realm: String,
117        password: String,
118        crypto: &'a dyn RTCCrypto,
119    ) -> Result<Vec<u8>> {
120        let credentials = [username, realm, password].join(CREDENTIALS_SEP);
121        let key = crypto
122            .hash(HashAlgorithm::Md5, credentials.as_bytes())
123            .map_err(crypto_error)?;
124        if key.len() != 16 {
125            return Err(Error::Crypto(format!(
126                "provider returned an invalid MD5 digest length: {}",
127                key.len()
128            )));
129        }
130        Ok(key)
131    }
132
133    /// Check checks MESSAGE-INTEGRITY attribute.
134    ///
135    /// CPU costly, see BenchmarkMessageIntegrity_Check.
136    pub fn check(m: &mut Message, key: &[u8], crypto: &dyn RTCCrypto) -> Result<()> {
137        let v = m.get(ATTR_MESSAGE_INTEGRITY)?;
138
139        // Adjusting length in header to match m.Raw that was
140        // used when computing HMAC.
141
142        let length = m.length as usize;
143        let mut after_integrity = false;
144        let mut size_reduced = 0;
145
146        for a in &m.attributes.0 {
147            if after_integrity {
148                size_reduced += nearest_padded_value_length(a.length as usize);
149                size_reduced += ATTRIBUTE_HEADER_SIZE;
150            }
151            if a.typ == ATTR_MESSAGE_INTEGRITY {
152                after_integrity = true;
153            }
154        }
155        m.length -= size_reduced as u32;
156        m.write_length();
157        // start_of_hmac should be first byte of integrity attribute.
158        let start_of_hmac = MESSAGE_HEADER_SIZE + m.length as usize
159            - (ATTRIBUTE_HEADER_SIZE + MESSAGE_INTEGRITY_SIZE);
160        let b = &m.raw[..start_of_hmac]; // data before integrity attribute
161        let result = crypto
162            .new_hmac(HmacAlgorithm::Sha1, key)
163            .and_then(|mut mac| mac.verify(&[b], &v));
164        m.length = length as u32;
165        m.write_length(); // writing length back
166        match result {
167            Ok(()) => Ok(()),
168            Err(CryptoError::AuthenticationFailed | CryptoError::InvalidTagLength { .. }) => {
169                Err(Error::ErrIntegrityMismatch)
170            }
171            Err(error) => Err(crypto_error(error)),
172        }
173    }
174}