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 mut raw_message = Vec::new();
87        let cursor = Cursor::new(&mut raw_message);
88        mail_to_encrypt.clone().write_part(cursor).ok();
89
90        let ctext =
91            pgp::symm_encrypt_message(raw_message, sign_key, shared_secret, compress).await?;
92
93        Ok(ctext)
94    }
95}
96
97/// Ensures a private key exists for the configured user.
98///
99/// Normally the private key is generated when the first message is
100/// sent but in a few locations there are no such guarantees,
101/// e.g. when exporting keys, and calling this function ensures a
102/// private key will be present.
103// TODO, remove this once deltachat::key::Key no longer exists.
104pub async fn ensure_secret_key_exists(context: &Context) -> Result<()> {
105    load_self_public_key(context).await?;
106    Ok(())
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use crate::chat;
113    use crate::chat::send_text_msg;
114    use crate::config::Config;
115    use crate::message::Message;
116    use crate::mimeparser::SystemMessage;
117    use crate::receive_imf::receive_imf;
118    use crate::test_utils::{TestContext, TestContextManager};
119
120    mod ensure_secret_key_exists {
121        use super::*;
122
123        #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
124        async fn test_prexisting() {
125            let t = TestContext::new_alice().await;
126            assert!(ensure_secret_key_exists(&t).await.is_ok());
127        }
128
129        #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
130        async fn test_not_configured() {
131            let t = TestContext::new().await;
132            assert!(ensure_secret_key_exists(&t).await.is_err());
133        }
134    }
135
136    #[test]
137    fn test_mailmime_parse() {
138        let plain = b"Chat-Disposition-Notification-To: hello@world.de
139Chat-Group-ID: CovhGgau8M-
140Chat-Group-Name: Delta Chat Dev
141Subject: =?utf-8?Q?Chat=3A?= Delta Chat =?utf-8?Q?Dev=3A?= sidenote for
142 =?utf-8?Q?all=3A?= rust core master ...
143Content-Type: text/plain; charset=\"utf-8\"; protected-headers=\"v1\"
144Content-Transfer-Encoding: quoted-printable
145
146sidenote for all: things are trick atm recomm=
147end not to try to run with desktop or ios unless you are ready to hunt bugs
148
149-- =20
150Sent with my Delta Chat Messenger: https://delta.chat";
151        let mail = mailparse::parse_mail(plain).expect("failed to parse valid message");
152
153        assert_eq!(mail.headers.len(), 6);
154        assert!(
155            mail.get_body().unwrap().starts_with(
156                "sidenote for all: things are trick atm recommend not to try to run with desktop or ios unless you are ready to hunt bugs")
157        );
158    }
159
160    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
161    async fn test_cannot_send_unencrypted_by_default() -> Result<()> {
162        let mut tcm = TestContextManager::new();
163        let alice = &tcm.alice().await;
164        let bob = &tcm.bob().await;
165        let chat = alice.create_email_chat(bob).await;
166
167        let mut msg = Message::new_text("Hello!".to_string());
168        assert!(chat::send_msg(alice, chat.id, &mut msg).await.is_err());
169        assert_eq!(
170            msg.error().unwrap(),
171            "\u{26a0}\u{fe0f} Your email provider example.org requires end-to-end encryption which is not setup yet."
172        );
173        let info_msg = alice.get_last_msg().await;
174        assert_eq!(
175            info_msg.get_info_type(),
176            SystemMessage::InvalidUnencryptedMail
177        );
178
179        Ok(())
180    }
181
182    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
183    async fn test_chatmail_can_send_unencrypted() -> Result<()> {
184        let mut tcm = TestContextManager::new();
185        let bob = &tcm.bob().await;
186        bob.set_config_bool(Config::IsChatmail, true).await?;
187        bob.allow_unencrypted().await?;
188        let bob_chat_id = receive_imf(
189            bob,
190            b"From: alice@example.org\n\
191            To: bob@example.net\n\
192            Message-ID: <2222@example.org>\n\
193            Date: Sun, 22 Mar 3000 22:37:58 +0000\n\
194            \n\
195            Hello\n",
196            false,
197        )
198        .await?
199        .unwrap()
200        .chat_id;
201        bob_chat_id.accept(bob).await?;
202        send_text_msg(bob, bob_chat_id, "hi".to_string()).await?;
203        let sent_msg = bob.pop_sent_msg().await;
204        let msg = Message::load_from_db(bob, sent_msg.sender_msg_id).await?;
205        assert!(!msg.get_showpadlock());
206        Ok(())
207    }
208}