Skip to main content

deltachat/
e2ee.rs

1//! End-to-end encryption support.
2
3use std::io::Cursor;
4
5use anyhow::Result;
6use mail_builder::mime::MimePart;
7
8use crate::aheader::{Aheader, EncryptPreference};
9use crate::context::Context;
10use crate::key::{SignedPublicKey, load_self_public_key, load_self_secret_key};
11use crate::pgp::{self, SeipdVersion};
12
13#[derive(Debug)]
14pub struct EncryptHelper {
15    pub addr: String,
16    pub public_key: SignedPublicKey,
17}
18
19impl EncryptHelper {
20    pub async fn new(context: &Context) -> Result<EncryptHelper> {
21        let addr = context.get_primary_self_addr().await?;
22        let public_key = load_self_public_key(context).await?;
23
24        Ok(EncryptHelper { addr, public_key })
25    }
26
27    pub fn get_aheader(&self) -> Aheader {
28        Aheader {
29            addr: self.addr.clone(),
30            public_key: self.public_key.clone(),
31            prefer_encrypt: EncryptPreference::Mutual,
32            verified: false,
33        }
34    }
35
36    /// Tries to encrypt the passed in `mail`.
37    pub async fn encrypt(
38        self,
39        context: &Context,
40        keyring: Vec<SignedPublicKey>,
41        mail_to_encrypt: MimePart<'static>,
42        compress: bool,
43        seipd_version: SeipdVersion,
44    ) -> Result<String> {
45        let mut raw_message = Vec::new();
46        let cursor = Cursor::new(&mut raw_message);
47        mail_to_encrypt.clone().write_part(cursor).ok();
48
49        let ctext = self
50            .encrypt_raw(context, keyring, raw_message, compress, seipd_version)
51            .await?;
52        Ok(ctext)
53    }
54
55    pub async fn encrypt_raw(
56        self,
57        context: &Context,
58        keyring: Vec<SignedPublicKey>,
59        raw_message: Vec<u8>,
60        compress: bool,
61        seipd_version: SeipdVersion,
62    ) -> Result<String> {
63        let sign_key = load_self_secret_key(context).await?;
64        let ctext =
65            pgp::pk_encrypt(raw_message, keyring, sign_key, compress, seipd_version).await?;
66
67        Ok(ctext)
68    }
69
70    /// Symmetrically encrypt the message. This is used for broadcast channels.
71    /// `shared secret` is the secret that will be used for symmetric encryption.
72    pub async fn encrypt_symmetrically(
73        self,
74        context: &Context,
75        shared_secret: &str,
76        mail_to_encrypt: MimePart<'static>,
77        compress: bool,
78        sign: bool,
79    ) -> Result<String> {
80        let sign_key = if sign {
81            Some(load_self_secret_key(context).await?)
82        } else {
83            None
84        };
85
86        let shared_secret = shared_secret.to_string();
87        let mut raw_message = Vec::new();
88        let cursor = Cursor::new(&mut raw_message);
89        mail_to_encrypt.clone().write_part(cursor).ok();
90
91        let ctext = tokio::task::spawn_blocking(move || {
92            pgp::symm_encrypt_message(raw_message, sign_key, shared_secret, compress)
93        })
94        .await??;
95
96        Ok(ctext)
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::chat;
104    use crate::chat::send_text_msg;
105    use crate::config::Config;
106    use crate::message::Message;
107    use crate::mimeparser::SystemMessage;
108    use crate::receive_imf::receive_imf;
109    use crate::test_utils::TestContextManager;
110
111    #[test]
112    fn test_mailmime_parse() {
113        let plain = b"Chat-Disposition-Notification-To: hello@world.de
114Chat-Group-ID: CovhGgau8M-
115Chat-Group-Name: Delta Chat Dev
116Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
117 =?utf-8?Q?all=3A?= rust core master ...
118Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
119Content-Transfer-Encoding: quoted-printable
120
121sidenote for all: things are trick atm recomm=
122end not to try to run with desktop or ios unless you are ready to hunt bugs
123
124-- =20
125Sent with my Delta Chat Messenger: https://delta.chat";
126        let mail = mailparse::parse_mail(plain).expect("failed to parse valid message");
127
128        assert_eq!(mail.headers.len(), 6);
129        assert!(
130            mail.get_body().unwrap().starts_with(
131                "sidenote for all: things are trick atm recommend not to try to run with desktop or ios unless you are ready to hunt bugs")
132        );
133    }
134
135    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
136    async fn test_cannot_send_unencrypted_by_default() -> Result<()> {
137        let mut tcm = TestContextManager::new();
138        let alice = &tcm.alice().await;
139        let bob = &tcm.bob().await;
140        let chat = alice.create_email_chat(bob).await;
141
142        let mut msg = Message::new_text("Hello!".to_string());
143        assert!(chat::send_msg(alice, chat.id, &mut msg).await.is_err());
144        assert_eq!(
145            msg.error().unwrap(),
146            "\u{26a0}\u{fe0f} Your email provider example.org requires end-to-end encryption which is not setup yet."
147        );
148        let info_msg = alice.get_last_msg().await;
149        assert_eq!(
150            info_msg.get_info_type(),
151            SystemMessage::InvalidUnencryptedMail
152        );
153
154        Ok(())
155    }
156
157    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
158    async fn test_chatmail_can_send_unencrypted() -> Result<()> {
159        let mut tcm = TestContextManager::new();
160        let bob = &tcm.bob().await;
161        bob.set_config_bool(Config::IsChatmail, true).await?;
162        bob.allow_unencrypted().await?;
163        let bob_chat_id = receive_imf(
164            bob,
165            b"From: alice@example.org\n\
166            To: bob@example.net\n\
167            Message-ID: <2222@example.org>\n\
168            Date: Sun, 22 Mar 3000 22:37:58 +0000\n\
169            \n\
170            Hello\n",
171            false,
172        )
173        .await?
174        .unwrap()
175        .chat_id;
176        bob_chat_id.accept(bob).await?;
177        send_text_msg(bob, bob_chat_id, "hi".to_string()).await?;
178        let sent_msg = bob.pop_sent_msg().await;
179        let msg = Message::load_from_db(bob, sent_msg.sender_msg_id).await?;
180        assert!(!msg.get_showpadlock());
181        Ok(())
182    }
183}