diff --git a/Cargo.toml b/Cargo.toml index 4ff511926..f62a952e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ opt-level = 1 # Make anyhow `backtrace` feature useful. # With `debug = 0` there are no line numbers in the backtrace # produced with RUST_BACKTRACE=1. -debug = 1 +debug = 'full' opt-level = 0 [profile.fuzz] diff --git a/src/decrypt.rs b/src/decrypt.rs index 8e242a57e..7a4475313 100644 --- a/src/decrypt.rs +++ b/src/decrypt.rs @@ -14,13 +14,14 @@ use crate::pgp; pub fn try_decrypt<'a>( mail: &'a ParsedMail<'a>, private_keyring: &'a [SignedSecretKey], + symmetric_secrets: &[&str], ) -> Result>> { let Some(encrypted_data_part) = get_encrypted_mime(mail) else { return Ok(None); }; let data = encrypted_data_part.get_body_raw()?; - let msg = pgp::pk_decrypt(data, private_keyring)?; + let msg = pgp::decrypt(data, private_keyring, symmetric_secrets)?; Ok(Some(msg)) } diff --git a/src/e2ee.rs b/src/e2ee.rs index 4ca0cd1a9..c1d1d77d2 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -73,8 +73,7 @@ impl EncryptHelper { let cursor = Cursor::new(&mut raw_message); mail_to_encrypt.clone().write_part(cursor).ok(); - let ctext = - pgp::encrypt_for_broadcast(raw_message, passphrase, Some(sign_key), compress).await?; + let ctext = pgp::encrypt_for_broadcast(raw_message, passphrase, sign_key, compress).await?; Ok(ctext) } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index bc6b38cd2..147ac9a09 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -333,50 +333,52 @@ impl MimeMessage { let mail_raw; // Memory location for a possible decrypted message. let decrypted_msg; // Decrypted signed OpenPGP message. + let symmetric_secrets = - let (mail, is_encrypted) = - match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring)) { - Ok(Some(mut msg)) => { - mail_raw = msg.as_data_vec().unwrap_or_default(); + let (mail, is_encrypted) = match tokio::task::block_in_place(|| { + try_decrypt(&mail, &private_keyring, symmetric_secrets) + }) { + Ok(Some(mut msg)) => { + mail_raw = msg.as_data_vec().unwrap_or_default(); - let decrypted_mail = mailparse::parse_mail(&mail_raw)?; - if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { - info!( - context, - "decrypted message mime-body:\n{}", - String::from_utf8_lossy(&mail_raw), - ); - } - - decrypted_msg = Some(msg); - - timestamp_sent = Self::get_timestamp_sent( - &decrypted_mail.headers, - timestamp_sent, - timestamp_rcvd, + let decrypted_mail = mailparse::parse_mail(&mail_raw)?; + if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { + info!( + context, + "decrypted message mime-body:\n{}", + String::from_utf8_lossy(&mail_raw), ); + } - if let Some(protected_aheader_value) = decrypted_mail - .headers - .get_header_value(HeaderDef::Autocrypt) - { - aheader_value = Some(protected_aheader_value); - } + decrypted_msg = Some(msg); - (Ok(decrypted_mail), true) + timestamp_sent = Self::get_timestamp_sent( + &decrypted_mail.headers, + timestamp_sent, + timestamp_rcvd, + ); + + if let Some(protected_aheader_value) = decrypted_mail + .headers + .get_header_value(HeaderDef::Autocrypt) + { + aheader_value = Some(protected_aheader_value); } - Ok(None) => { - mail_raw = Vec::new(); - decrypted_msg = None; - (Ok(mail), false) - } - Err(err) => { - mail_raw = Vec::new(); - decrypted_msg = None; - warn!(context, "decryption failed: {:#}", err); - (Err(err), false) - } - }; + + (Ok(decrypted_mail), true) + } + Ok(None) => { + mail_raw = Vec::new(); + decrypted_msg = None; + (Ok(mail), false) + } + Err(err) => { + mail_raw = Vec::new(); + decrypted_msg = None; + warn!(context, "decryption failed: {:#}", err); + (Err(err), false) + } + }; let autocrypt_header = if !incoming { None diff --git a/src/pgp.rs b/src/pgp.rs index 735b6a210..67cd812bc 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -236,9 +236,10 @@ pub fn pk_calc_signature( /// /// Receiver private keys are provided in /// `private_keys_for_decryption`. -pub fn pk_decrypt( +pub fn decrypt( ctext: Vec, private_keys_for_decryption: &[SignedSecretKey], + symmetric_secrets: &[&str], ) -> Result> { let cursor = Cursor::new(ctext); let (msg, _headers) = Message::from_armor(cursor)?; @@ -246,10 +247,17 @@ pub fn pk_decrypt( let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect(); let empty_pw = Password::empty(); + // TODO it may degrade performance that we always try out all passwords here + let message_password: Vec = symmetric_secrets + .iter() + .map(|p| Password::from(*p)) + .collect(); + let message_password: Vec<&Password> = message_password.iter().collect(); + let ring = TheRing { secret_keys: skeys, key_passwords: vec![&empty_pw], - message_password: vec![], + message_password, session_keys: vec![], allow_legacy: false, }; @@ -327,7 +335,7 @@ pub async fn symm_encrypt(passphrase: &str, plain: Vec) -> Result { pub async fn encrypt_for_broadcast( plain: Vec, passphrase: &str, - private_key_for_signing: Option, + private_key_for_signing: SignedSecretKey, compress: bool, ) -> Result { let passphrase = Password::from(passphrase.to_string()); @@ -344,11 +352,9 @@ pub async fn encrypt_for_broadcast( ); msg.encrypt_with_password(&mut rng, s2k, &passphrase)?; - if let Some(ref skey) = private_key_for_signing { - msg.sign(&**skey, Password::empty(), HASH_ALGORITHM); - if compress { - msg.compression(CompressionAlgorithm::ZLIB); - } + msg.sign(&*private_key_for_signing, Password::empty(), HASH_ALGORITHM); + if compress { + msg.compression(CompressionAlgorithm::ZLIB); } let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?; @@ -381,7 +387,10 @@ mod tests { use tokio::sync::OnceCell; use super::*; - use crate::test_utils::{alice_keypair, bob_keypair}; + use crate::{ + key::load_self_secret_key, + test_utils::{TestContext, TestContextManager, alice_keypair, bob_keypair}, + }; fn pk_decrypt_and_validate<'a>( ctext: &'a [u8], @@ -392,7 +401,7 @@ mod tests { HashSet, Vec, )> { - let mut msg = pk_decrypt(ctext.to_vec(), private_keys_for_decryption)?; + let mut msg = decrypt(ctext.to_vec(), private_keys_for_decryption)?; let content = msg.as_data_vec()?; let ret_signature_fingerprints = valid_signature_fingerprints(&msg, public_keys_for_validation)?; @@ -578,4 +587,26 @@ mod tests { assert_eq!(content, CLEARTEXT); assert_eq!(valid_signatures.len(), 0); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_encrypt_decrypt_broadcast() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + + let plain = Vec::from(b"this is the secret message"); + let shared_secret = "shared secret"; + let ctext = encrypt_for_broadcast( + plain, + shared_secret, + load_self_secret_key(alice).await?, + true, + ) + .await?; + + let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?; + let decrypted = decrypt(ctext.into(), &bob_private_keyring)?; + + Ok(()) + } } diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index 653d74d09..84a0646bf 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -1251,6 +1251,15 @@ CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint); .await?; } + inc_and_check(&mut migration_version, 133)?; + if dbversion < migration_version { + sql.execute_migration( + "CREATE TABLE symmetric_secrets( + chat_id INTEGER PRIMARY KEY NOT NULL, + symmetric_secret: ", + ) + } + let new_version = sql .get_raw_config_int(VERSION_CFG) .await?