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 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
97pub 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}