1use 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 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 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}