ezk_stun_types/attributes/
integrity.rs

1use super::{Attribute, ATTRIBUTE_HEADER_LEN};
2use crate::builder::MessageBuilder;
3use crate::header::STUN_HEADER_LENGTH;
4use crate::parse::{AttrSpan, Message};
5use crate::Error;
6use hmac::digest::core_api::BlockSizeUser;
7use hmac::digest::{Digest, Update};
8use hmac::{Mac, SimpleHmac};
9use sha1::Sha1;
10use sha2::Sha256;
11use std::convert::TryFrom;
12
13pub fn long_term_password_md5(username: &str, realm: &str, password: &str) -> Vec<u8> {
14    md5::compute(format!("{}:{}:{}", username, realm, password).as_bytes()).to_vec()
15}
16
17pub fn long_term_password_sha256(username: &str, realm: &str, password: &str) -> Vec<u8> {
18    Sha256::digest(format!("{}:{}:{}", username, realm, password).as_bytes()).to_vec()
19}
20
21pub struct MessageIntegrityKey(SimpleHmac<Sha1>);
22
23impl MessageIntegrityKey {
24    pub fn new(key: impl AsRef<[u8]>) -> Self {
25        Self(SimpleHmac::new_from_slice(key.as_ref()).expect("any key length is valid"))
26    }
27}
28
29/// [RFC8489](https://datatracker.ietf.org/doc/html/rfc8489#section-14.5)
30pub struct MessageIntegrity;
31
32impl Attribute<'_> for MessageIntegrity {
33    type Context = MessageIntegrityKey;
34    const TYPE: u16 = 0x0008;
35
36    fn decode(ctx: Self::Context, msg: &mut Message, attr: AttrSpan) -> Result<Self, Error> {
37        message_integrity_decode(ctx.0, msg, attr)?;
38
39        Ok(Self)
40    }
41
42    fn encode(&self, ctx: Self::Context, builder: &mut MessageBuilder) {
43        message_integrity_encode(ctx.0, builder)
44    }
45
46    fn encode_len(&self) -> Result<u16, Error> {
47        Ok(u16::try_from(Sha1::output_size())?)
48    }
49}
50
51pub struct MessageIntegritySha256Key(SimpleHmac<Sha256>);
52
53impl MessageIntegritySha256Key {
54    pub fn new(key: impl AsRef<[u8]>) -> Self {
55        Self(SimpleHmac::new_from_slice(key.as_ref()).expect("any key length is valid"))
56    }
57}
58
59/// [RFC8489](https://datatracker.ietf.org/doc/html/rfc8489#section-14.6)
60pub struct MessageIntegritySha256;
61
62impl Attribute<'_> for MessageIntegritySha256 {
63    type Context = MessageIntegritySha256Key;
64    const TYPE: u16 = 0x001C;
65
66    fn decode(ctx: Self::Context, msg: &mut Message, attr: AttrSpan) -> Result<Self, Error> {
67        message_integrity_decode(ctx.0, msg, attr)?;
68
69        Ok(Self)
70    }
71
72    fn encode(&self, ctx: Self::Context, builder: &mut MessageBuilder) {
73        message_integrity_encode(ctx.0, builder)
74    }
75
76    fn encode_len(&self) -> Result<u16, Error> {
77        Ok(u16::try_from(dbg!(Sha256::output_size()))?)
78    }
79}
80
81fn message_integrity_decode<D>(
82    mut hmac: SimpleHmac<D>,
83    msg: &mut Message,
84    attr: AttrSpan,
85) -> Result<(), Error>
86where
87    D: Digest + BlockSizeUser,
88{
89    // The text used as input to HMAC is the STUN message, up to and
90    // including the attribute preceding the MESSAGE-INTEGRITY attribute.
91    // The Length field of the STUN message header is adjusted to point to
92    // the end of the MESSAGE-INTEGRITY attribute.
93
94    // The length of the message is temporarily set to the end of the previous attribute
95    msg.with_msg_len(
96        u16::try_from(attr.padding_end - STUN_HEADER_LENGTH)?,
97        |msg| {
98            // Get the digest from the received attribute
99            let received_digest = attr.get_value(msg.buffer());
100
101            // Get all bytes before the integrity attribute to calculate the hmac over
102            let message = &msg.buffer()[..attr.begin - ATTRIBUTE_HEADER_LEN];
103
104            // Calculate the expected digest,
105            Update::update(&mut hmac, message);
106            let calculated_digest = hmac.finalize().into_bytes();
107
108            // Compare the received and calculated digest
109            if calculated_digest.as_slice() != received_digest {
110                return Err(Error::InvalidData("failed to verify message integrity"));
111            }
112
113            Ok(())
114        },
115    )
116}
117
118fn message_integrity_encode<D>(mut hmac: SimpleHmac<D>, builder: &mut MessageBuilder)
119where
120    D: Digest + BlockSizeUser,
121{
122    // 4 bytes containing type and length is already written into the buffer
123    let message_length_with_integrity_attribute =
124        (builder.buffer().len() + <D as Digest>::output_size()) - STUN_HEADER_LENGTH;
125
126    builder.set_len(
127        message_length_with_integrity_attribute
128            .try_into()
129            .expect("stun messages must fit withing 65535 bytes"),
130    );
131
132    // Calculate the digest of the message up until the previous attribute
133    let data = builder.buffer();
134    let data = &data[..data.len() - ATTRIBUTE_HEADER_LEN];
135    Update::update(&mut hmac, data);
136    let digest = hmac.finalize().into_bytes();
137
138    builder.buffer().extend_from_slice(&digest);
139}
140
141#[cfg(test)]
142mod test {
143    use super::{
144        MessageIntegrity, MessageIntegrityKey, MessageIntegritySha256, MessageIntegritySha256Key,
145    };
146    use crate::attributes::Software;
147    use crate::builder::MessageBuilder;
148    use crate::header::{Class, Method};
149    use crate::parse::Message;
150    use crate::TransactionId;
151
152    #[test]
153    fn selftest_sha1() {
154        let password = "abc123";
155
156        let mut message =
157            MessageBuilder::new(Class::Request, Method::Binding, TransactionId::new([0; 12]));
158
159        message.add_attr(Software::new("ezk-stun"));
160        message.add_attr_with(MessageIntegrity, MessageIntegrityKey::new(password));
161
162        let bytes = message.finish();
163        let bytes = Vec::from(&bytes[..]);
164
165        let mut msg = Message::parse(bytes).unwrap();
166
167        msg.attribute_with::<MessageIntegrity>(MessageIntegrityKey::new(password))
168            .unwrap()
169            .unwrap();
170    }
171
172    #[test]
173    fn selftest_sha256() {
174        let password = "abc123";
175
176        let mut message =
177            MessageBuilder::new(Class::Request, Method::Binding, TransactionId::new([0; 12]));
178
179        message.add_attr(Software::new("ezk-stun"));
180        message.add_attr_with(
181            MessageIntegritySha256,
182            MessageIntegritySha256Key::new(password),
183        );
184
185        let bytes = message.finish();
186        let bytes = Vec::from(&bytes[..]);
187
188        let mut msg = Message::parse(bytes).unwrap();
189
190        msg.attribute_with::<MessageIntegritySha256>(MessageIntegritySha256Key::new(password))
191            .unwrap()
192            .unwrap();
193    }
194}