diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index b2124f941..1f1431edf 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -3069,6 +3069,45 @@ async fn test_leave_broadcast_multidevice() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_encrypt_decrypt_broadcast_integration() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + let bob_without_secret = &tcm.bob().await; + + let secret = "secret"; + + let alice_bob_contact_id = alice.add_or_lookup_contact_id(bob).await; + + tcm.section("Create a broadcast channel with Bob, and send a message"); + let alice_chat_id = create_broadcast(alice, "My Channel".to_string()).await?; + add_contact_to_chat(alice, alice_chat_id, alice_bob_contact_id).await?; + + let mut alice_chat = Chat::load_from_db(alice, alice_chat_id).await?; + alice_chat.param.set(Param::SymmetricKey, secret); + alice_chat.update_param(alice).await?; + + // TODO the chat_id 10 is magical here: + bob.sql + .execute( + "INSERT INTO broadcasts_shared_secrets (chat_id, secret) VALUES (10, ?)", + (secret,), + ) + .await?; + + let sent = alice + .send_text(alice_chat_id, "Symmetrically encrypted message") + .await; + let rcvd = bob.recv_msg(&sent).await; + assert_eq!(rcvd.text, "Symmetrically encrypted message"); + + tcm.section("If Bob doesn't know the secret, he can't decrypt the message"); + bob_without_secret.recv_msg_trash(&sent).await; + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_create_for_contact_with_blocked() -> Result<()> { let t = TestContext::new().await; diff --git a/src/decrypt.rs b/src/decrypt.rs index 7100ade68..3da5217d8 100644 --- a/src/decrypt.rs +++ b/src/decrypt.rs @@ -14,7 +14,7 @@ use crate::pgp; pub fn try_decrypt<'a>( mail: &'a ParsedMail<'a>, private_keyring: &'a [SignedSecretKey], - symmetric_secrets: &[&str], + symmetric_secrets: &[String], ) -> Result>> { let Some(encrypted_data_part) = get_encrypted_mime(mail) else { return Ok(None); diff --git a/src/mimefactory.rs b/src/mimefactory.rs index c5c595e6c..3d29583c0 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1158,6 +1158,7 @@ impl MimeFactory { }; let encrypted = if let Some(symmetric_key) = symmetric_key { + info!(context, "Symmetrically encrypting for broadcast channel."); encrypt_helper .encrypt_for_broadcast(context, symmetric_key, message, compress) .await? diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 25f267a71..a5feffa3d 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -354,10 +354,21 @@ impl MimeMessage { let mail_raw; // Memory location for a possible decrypted message. let decrypted_msg; // Decrypted signed OpenPGP message. - let symmetric_secrets = + let symmetric_secrets: Vec = context + .sql + .query_map( + "SELECT secret FROM broadcasts_shared_secrets", + (), + |row| row.get(0), + |rows| { + rows.collect::, _>>() + .map_err(Into::into) + }, + ) + .await?; let (mail, is_encrypted) = match tokio::task::block_in_place(|| { - try_decrypt(&mail, &private_keyring, symmetric_secrets) + try_decrypt(&mail, &private_keyring, &symmetric_secrets) }) { Ok(Some(mut msg)) => { mail_raw = msg.as_data_vec().unwrap_or_default(); diff --git a/src/param.rs b/src/param.rs index ca13bfba4..a8576e855 100644 --- a/src/param.rs +++ b/src/param.rs @@ -172,7 +172,7 @@ pub enum Param { /// post something to the mailing list. ListPost = b'p', - /// For Chats of type [`Chattype::OutBroadcast`] and [`Chattype::InBroadcast`]: + /// For Chats of type [`Chattype::OutBroadcast`] and [`Chattype::InBroadcast`]: // TODO (or just OutBroadcast) /// The symmetric key shared among all chat participants, /// used to encrypt and decrypt messages. SymmetricKey = b'z', diff --git a/src/pgp.rs b/src/pgp.rs index 8a80050fc..ff6991c93 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -239,7 +239,7 @@ pub fn pk_calc_signature( pub fn decrypt( ctext: Vec, private_keys_for_decryption: &[SignedSecretKey], - symmetric_secrets: &[&str], + symmetric_secrets: &[String], ) -> Result> { let cursor = Cursor::new(ctext); let (msg, _headers) = Message::from_armor(cursor)?; @@ -250,7 +250,7 @@ pub fn decrypt( // 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)) + .map(|p| Password::from(p.as_str())) .collect(); let message_password: Vec<&Password> = message_password.iter().collect(); @@ -320,7 +320,7 @@ pub async fn symm_encrypt(passphrase: &str, plain: Vec) -> Result { tokio::task::spawn_blocking(move || { let mut rng = thread_rng(); let s2k = StringToKey::new_default(&mut rng); - let builder = MessageBuilder::from_bytes("", plain); + let builder: MessageBuilder<'_> = MessageBuilder::from_bytes("", plain); let mut builder = builder.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM); builder.encrypt_with_password(s2k, &passphrase)?; @@ -389,7 +389,7 @@ mod tests { use super::*; use crate::{ key::load_self_secret_key, - test_utils::{TestContext, TestContextManager, alice_keypair, bob_keypair}, + test_utils::{TestContextManager, alice_keypair, bob_keypair}, }; fn pk_decrypt_and_validate<'a>( @@ -401,7 +401,7 @@ mod tests { HashSet, Vec, )> { - let mut msg = 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); @@ -597,7 +597,7 @@ mod tests { let plain = Vec::from(b"this is the secret message"); let shared_secret = "shared secret"; let ctext = encrypt_for_broadcast( - plain, + plain.clone(), shared_secret, load_self_secret_key(alice).await?, true, @@ -605,7 +605,13 @@ mod tests { .await?; let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?; - let decrypted = decrypt(ctext.into(), &bob_private_keyring)?; + let mut decrypted = decrypt( + ctext.into(), + &bob_private_keyring, + &[shared_secret.to_string()], + )?; + + assert_eq!(decrypted.as_data_vec()?, plain); Ok(()) } diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index e17989d3e..d794e2678 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -1264,10 +1264,13 @@ CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint); inc_and_check(&mut migration_version, 134)?; if dbversion < migration_version { sql.execute_migration( - "CREATE TABLE symmetric_secrets( - chat_id INTEGER PRIMARY KEY NOT NULL, - symmetric_secret: ", + "CREATE TABLE broadcasts_shared_secrets( + chat_id INTEGER PRIMARY KEY NOT NULL, -- TODO we don't actually need the chat_id + secret TEXT NOT NULL + ) STRICT", + migration_version, ) + .await?; } let new_version = sql