Skip to main content

vodozemac/ecies/
messages.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
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 thiserror::Error;
16
17#[cfg(doc)]
18use super::EstablishedEcies;
19use crate::{Curve25519PublicKey, KeyError, base64_decode, base64_encode};
20
21/// The error type for the ECIES message decoding failures.
22#[derive(Debug, Error)]
23pub enum MessageDecodeError {
24    /// The initial message could not have been decoded, it's missing the `|`
25    /// separator.
26    #[error("The initial message is missing the | separator")]
27    MissingSeparator,
28    /// The initial message could not have been decoded, the embedded Curve25519
29    /// key is malformed.
30    #[error("The embedded ephemeral Curve25519 key could not have been decoded: {0:?}")]
31    KeyError(#[from] KeyError),
32    /// The ciphertext is not valid base64.
33    #[error("The ciphertext could not have been decoded from a base64 string: {0:?}")]
34    Base64(#[from] base64::DecodeError),
35}
36
37/// The initial message, sent by the ECIES channel establisher.
38///
39/// This message embeds the public key of the message creator allowing the other
40/// side to establish a channel using this message.
41///
42/// This key is *unauthenticated* so authentication needs to happen out-of-band
43/// in order for the established channel to become secure.
44#[derive(Debug, PartialEq, Eq)]
45pub struct InitialMessage {
46    /// The ephemeral public key that was used to establish the ECIES channel.
47    pub public_key: Curve25519PublicKey,
48    /// The ciphertext of the initial message.
49    pub ciphertext: Vec<u8>,
50}
51
52impl InitialMessage {
53    /// Encode the message as a string.
54    ///
55    /// The string will contain the base64-encoded Curve25519 public key and the
56    /// ciphertext of the message separated by a `|`.
57    pub fn encode(&self) -> String {
58        let ciphertext = base64_encode(&self.ciphertext);
59        let key = self.public_key.to_base64();
60
61        format!("{ciphertext}|{key}")
62    }
63
64    /// Attempt do decode a string into a [`InitialMessage`].
65    pub fn decode(message: &str) -> Result<Self, MessageDecodeError> {
66        match message.split_once('|') {
67            Some((ciphertext, key)) => {
68                let public_key = Curve25519PublicKey::from_base64(key)?;
69                let ciphertext = base64_decode(ciphertext)?;
70
71                Ok(Self { ciphertext, public_key })
72            }
73            None => Err(MessageDecodeError::MissingSeparator),
74        }
75    }
76}
77
78/// An encrypted message a [`EstablishedEcies`] channel has sent.
79#[derive(Debug)]
80pub struct Message {
81    /// The ciphertext of the message.
82    pub ciphertext: Vec<u8>,
83}
84
85impl Message {
86    /// Encode the message as a string.
87    ///
88    /// The ciphertext bytes will be encoded using unpadded base64.
89    pub fn encode(&self) -> String {
90        base64_encode(&self.ciphertext)
91    }
92
93    /// Attempt do decode a base64 string into a [`Message`].
94    pub fn decode(message: &str) -> Result<Self, MessageDecodeError> {
95        Ok(Self { ciphertext: base64_decode(message)? })
96    }
97}
98
99#[cfg(test)]
100mod test {
101    use super::*;
102
103    const INITIAL_MESSAGE: &str = "3On7QFJyLQMAErua9K/yIOcJALvuMYax1AW0iWgf64AwtSMZXwAA012Q|9yA/CX8pJKF02Prd75ZyBQHg3fGTVVGDNl86q1z17Us";
104    const MESSAGE: &str = "ZmtSLdzMcyjC5eV6L8xBI6amsq7gDNbCjz1W5OjX4Z8W";
105    const PUBLIC_KEY: &str = "9yA/CX8pJKF02Prd75ZyBQHg3fGTVVGDNl86q1z17Us";
106
107    #[test]
108    fn initial_message() {
109        let message = InitialMessage::decode(INITIAL_MESSAGE)
110            .expect("We should be able to decode our known-valid initial message");
111
112        assert_eq!(
113            message.public_key.to_base64(),
114            PUBLIC_KEY,
115            "The decoded public key should match the expected one"
116        );
117
118        let encoded = message.encode();
119        assert_eq!(INITIAL_MESSAGE, encoded);
120    }
121
122    #[test]
123    fn message() {
124        let message = Message::decode(MESSAGE)
125            .expect("We should be able to decode our known-valid initial message");
126
127        let encoded = message.encode();
128        assert_eq!(MESSAGE, encoded);
129    }
130}