Skip to main content

vodozemac/olm/messages/
message.rs

1// Copyright 2021 Damir Jelić
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Debug;
16
17use prost::Message as ProstMessage;
18use serde::{Deserialize, Serialize};
19
20use crate::{
21    Curve25519PublicKey, DecodeError,
22    cipher::{Mac, MessageMac},
23    utilities::{base64_decode, base64_encode, encode_protobuf_message, extract_mac},
24};
25
26pub(crate) const MAC_TRUNCATED_VERSION: u8 = 3;
27pub(crate) const VERSION: u8 = 4;
28
29/// An encrypted Olm message.
30///
31/// Contains metadata that is required to find the correct ratchet state of a
32/// [`Session`] necessary to decrypt the message.
33///
34/// [`Session`]: crate::olm::Session
35#[derive(Clone, PartialEq, Eq)]
36pub struct Message {
37    pub(crate) version: u8,
38    pub(crate) ratchet_key: Curve25519PublicKey,
39    pub(crate) chain_index: u64,
40    pub(crate) ciphertext: Vec<u8>,
41    pub(crate) mac: MessageMac,
42}
43
44impl Message {
45    /// The public part of the ratchet key, that was used when the message was
46    /// encrypted.
47    pub const fn ratchet_key(&self) -> Curve25519PublicKey {
48        self.ratchet_key
49    }
50
51    /// The index of the chain that was used when the message was encrypted.
52    pub const fn chain_index(&self) -> u64 {
53        self.chain_index
54    }
55
56    /// The actual ciphertext of the message.
57    pub fn ciphertext(&self) -> &[u8] {
58        &self.ciphertext
59    }
60
61    /// The version of the Olm message.
62    pub const fn version(&self) -> u8 {
63        self.version
64    }
65
66    /// Try to decode the given byte slice as a Olm [`Message`].
67    ///
68    /// The expected format of the byte array is described in the
69    /// [`Message::to_bytes()`] method.
70    pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
71        Self::try_from(bytes)
72    }
73
74    /// Encode the `Message` as an array of bytes.
75    ///
76    /// Olm `Message`s consist of a one-byte version, followed by a variable
77    /// length payload and a fixed length message authentication code.
78    ///
79    /// ```text
80    /// +--------------+------------------------------------+-----------+
81    /// | Version Byte | Payload Bytes                      | MAC Bytes |
82    /// +--------------+------------------------------------+-----------+
83    /// ```
84    ///
85    /// The payload uses a format based on the Protocol Buffers encoding. It
86    /// consists of the following key-value pairs:
87    ///
88    /// **Name**   |**Tag**|**Type**|               **Meaning**
89    /// :---------:|:-----:|:------:|:-----------------------------------------:
90    /// Ratchet-Key|  0x0A | String |The public part of the ratchet key
91    /// Chain-Index|  0x10 | Integer|The chain index, of the message
92    /// Cipher-Text|  0x22 | String |The cipher-text of the message
93    pub fn to_bytes(&self) -> Vec<u8> {
94        let mut message = self.encode();
95        message.extend(self.mac.as_bytes());
96
97        message
98    }
99
100    /// Try to decode the given string as a Olm [`Message`].
101    ///
102    /// The string needs to be a base64 encoded byte array that follows the
103    /// format described in the [`Message::to_bytes()`] method.
104    pub fn from_base64(message: &str) -> Result<Self, DecodeError> {
105        Self::try_from(message)
106    }
107
108    /// Encode the [`Message`] as a string.
109    ///
110    /// This method first calls [`Message::to_bytes()`] and then encodes the
111    /// resulting byte array as a string using base64 encoding.
112    pub fn to_base64(&self) -> String {
113        base64_encode(self.to_bytes())
114    }
115
116    #[cfg(feature = "experimental-session-config")]
117    pub(crate) fn new(
118        ratchet_key: Curve25519PublicKey,
119        chain_index: u64,
120        ciphertext: Vec<u8>,
121    ) -> Self {
122        Self {
123            version: VERSION,
124            ratchet_key,
125            chain_index,
126            ciphertext,
127            mac: Mac([0u8; Mac::LENGTH]).into(),
128        }
129    }
130
131    pub(crate) fn new_truncated_mac(
132        ratchet_key: Curve25519PublicKey,
133        chain_index: u64,
134        ciphertext: Vec<u8>,
135    ) -> Self {
136        Self {
137            version: MAC_TRUNCATED_VERSION,
138            ratchet_key,
139            chain_index,
140            ciphertext,
141            mac: [0u8; Mac::TRUNCATED_LEN].into(),
142        }
143    }
144
145    fn encode(&self) -> Vec<u8> {
146        let message = ProtoBufMessage {
147            ratchet_key: self.ratchet_key.to_bytes().to_vec(),
148            chain_index: self.chain_index,
149            ciphertext: self.ciphertext.clone(),
150        };
151
152        encode_protobuf_message(message, self.version())
153    }
154
155    pub(crate) fn to_mac_bytes(&self) -> Vec<u8> {
156        self.encode()
157    }
158
159    pub(crate) fn set_mac(&mut self, mac: Mac) {
160        match self.mac {
161            MessageMac::Truncated(_) => self.mac = mac.truncate().into(),
162            MessageMac::Full(_) => self.mac = mac.into(),
163        }
164    }
165}
166
167impl Serialize for Message {
168    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
169    where
170        S: serde::Serializer,
171    {
172        let message = self.to_base64();
173        serializer.serialize_str(&message)
174    }
175}
176
177impl<'de> Deserialize<'de> for Message {
178    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
179        let ciphertext = String::deserialize(d)?;
180        Message::from_base64(&ciphertext).map_err(serde::de::Error::custom)
181    }
182}
183
184impl TryFrom<&str> for Message {
185    type Error = DecodeError;
186
187    fn try_from(value: &str) -> Result<Self, Self::Error> {
188        let decoded = base64_decode(value)?;
189
190        Self::try_from(decoded)
191    }
192}
193
194impl TryFrom<Vec<u8>> for Message {
195    type Error = DecodeError;
196
197    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
198        Self::try_from(value.as_slice())
199    }
200}
201
202impl TryFrom<&[u8]> for Message {
203    type Error = DecodeError;
204
205    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
206        let version = *value.first().ok_or(DecodeError::MissingVersion)?;
207
208        let mac_length = match version {
209            VERSION => Mac::LENGTH,
210            MAC_TRUNCATED_VERSION => Mac::TRUNCATED_LEN,
211            _ => return Err(DecodeError::InvalidVersion(VERSION, version)),
212        };
213
214        if value.len() < mac_length + 2 {
215            Err(DecodeError::MessageTooShort(value.len()))
216        } else {
217            let inner = ProtoBufMessage::decode(
218                value
219                    .get(1..value.len() - mac_length)
220                    .ok_or(DecodeError::MessageTooShort(value.len()))?,
221            )?;
222
223            let mac_slice = &value[value.len() - mac_length..];
224
225            if mac_slice.len() != mac_length {
226                Err(DecodeError::InvalidMacLength(mac_length, mac_slice.len()))
227            } else {
228                let mac = extract_mac(mac_slice, version == MAC_TRUNCATED_VERSION);
229
230                let chain_index = inner.chain_index;
231                let ciphertext = inner.ciphertext;
232                let ratchet_key = Curve25519PublicKey::from_slice(&inner.ratchet_key)?;
233
234                let message = Message { version, ratchet_key, chain_index, ciphertext, mac };
235
236                Ok(message)
237            }
238        }
239    }
240}
241
242impl Debug for Message {
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        let Self { version, ratchet_key, chain_index, ciphertext: _, mac: _ } = self;
245
246        f.debug_struct("Message")
247            .field("version", version)
248            .field("ratchet_key", ratchet_key)
249            .field("chain_index", chain_index)
250            .finish_non_exhaustive()
251    }
252}
253
254#[derive(ProstMessage, PartialEq, Eq)]
255struct ProtoBufMessage {
256    #[prost(bytes, tag = "1")]
257    ratchet_key: Vec<u8>,
258    #[prost(uint64, required, tag = "2")]
259    chain_index: u64,
260    #[prost(bytes, tag = "4")]
261    ciphertext: Vec<u8>,
262}
263
264#[cfg(test)]
265mod test {
266    use std::assert_matches;
267
268    use strass::assert_let;
269
270    use super::Message;
271    use crate::{
272        Curve25519PublicKey, DecodeError,
273        olm::messages::message::{MAC_TRUNCATED_VERSION, VERSION},
274    };
275
276    #[test]
277    fn encode() {
278        let message = b"\x03\n\x20ratchetkeyhereprettyplease123456\x10\x02\"\nciphertext";
279        let message_mac =
280            b"\x03\n\x20ratchetkeyhereprettyplease123456\x10\x02\"\nciphertextMACHEREE";
281
282        let ratchet_key = Curve25519PublicKey::from(*b"ratchetkeyhereprettyplease123456");
283        let ciphertext = b"ciphertext";
284        let chain_index = 2;
285
286        let mut encoded = Message::new_truncated_mac(ratchet_key, chain_index, ciphertext.to_vec());
287        encoded.mac = (*b"MACHEREE").into();
288
289        assert_eq!(encoded.to_mac_bytes(), message.as_slice());
290        assert_eq!(encoded.to_bytes(), message_mac.as_slice());
291        assert_eq!(encoded.ciphertext(), ciphertext.to_vec());
292        assert_eq!(encoded.chain_index(), chain_index);
293        assert_eq!(encoded.version(), MAC_TRUNCATED_VERSION);
294    }
295
296    #[test]
297    fn from_bytes_normal_message() {
298        let bytes = b"\x04\n\x20ratchetkeyhereprettyplease123456\x10\x02\"\nciphertextMAC_01234567890_01234567890_HERE";
299        let result = Message::try_from(bytes.as_slice());
300        assert_let!(Ok(message) = result);
301
302        let Message { version, ratchet_key, chain_index, ciphertext, mac } = message;
303
304        assert_eq!(version, VERSION);
305        assert_eq!(ratchet_key.as_bytes(), b"ratchetkeyhereprettyplease123456");
306        assert_eq!(chain_index, 2);
307        assert_eq!(ciphertext, b"ciphertext");
308        assert_eq!(mac.as_bytes(), b"MAC_01234567890_01234567890_HERE");
309    }
310
311    #[test]
312    fn from_bytes_too_short() {
313        let bytes = vec![MAC_TRUNCATED_VERSION, 0, 0, 0, 0, 0, 0, 0, 0];
314        let result = Message::try_from(bytes);
315        assert_matches!(result, Err(DecodeError::MessageTooShort(9)));
316    }
317
318    #[test]
319    fn from_bytes_decoding_error() {
320        let bytes = vec![MAC_TRUNCATED_VERSION, 0, 0, 0, 0, 0, 0, 0, 0, 0];
321        let result = Message::try_from(bytes);
322        assert_matches!(result, Err(DecodeError::ProtoBufError(_)));
323    }
324
325    #[test]
326    fn from_bytes_invalid_tag() {
327        let bytes = [
328            VERSION, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
329            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
330        ];
331        let result = Message::try_from(bytes.to_vec());
332        assert_matches!(result, Err(DecodeError::ProtoBufError(_)));
333    }
334}