mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
feat: Save the secret to encrypt and decrypt messages. Next: Send it in a 'member added' message.
This commit is contained in:
@@ -3036,6 +3036,45 @@ async fn test_leave_broadcast_multidevice() -> Result<()> {
|
|||||||
Ok(())
|
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)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_create_for_contact_with_blocked() -> Result<()> {
|
async fn test_create_for_contact_with_blocked() -> Result<()> {
|
||||||
let t = TestContext::new().await;
|
let t = TestContext::new().await;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::pgp;
|
|||||||
pub fn try_decrypt<'a>(
|
pub fn try_decrypt<'a>(
|
||||||
mail: &'a ParsedMail<'a>,
|
mail: &'a ParsedMail<'a>,
|
||||||
private_keyring: &'a [SignedSecretKey],
|
private_keyring: &'a [SignedSecretKey],
|
||||||
symmetric_secrets: &[&str],
|
symmetric_secrets: &[String],
|
||||||
) -> Result<Option<::pgp::composed::Message<'static>>> {
|
) -> Result<Option<::pgp::composed::Message<'static>>> {
|
||||||
let Some(encrypted_data_part) = get_encrypted_mime(mail) else {
|
let Some(encrypted_data_part) = get_encrypted_mime(mail) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
|||||||
@@ -1132,6 +1132,7 @@ impl MimeFactory {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let encrypted = if let Some(symmetric_key) = symmetric_key {
|
let encrypted = if let Some(symmetric_key) = symmetric_key {
|
||||||
|
info!(context, "Symmetrically encrypting for broadcast channel.");
|
||||||
encrypt_helper
|
encrypt_helper
|
||||||
.encrypt_for_broadcast(context, symmetric_key, message, compress)
|
.encrypt_for_broadcast(context, symmetric_key, message, compress)
|
||||||
.await?
|
.await?
|
||||||
|
|||||||
@@ -333,10 +333,21 @@ impl MimeMessage {
|
|||||||
|
|
||||||
let mail_raw; // Memory location for a possible decrypted message.
|
let mail_raw; // Memory location for a possible decrypted message.
|
||||||
let decrypted_msg; // Decrypted signed OpenPGP message.
|
let decrypted_msg; // Decrypted signed OpenPGP message.
|
||||||
let symmetric_secrets =
|
let symmetric_secrets: Vec<String> = context
|
||||||
|
.sql
|
||||||
|
.query_map(
|
||||||
|
"SELECT secret FROM broadcasts_shared_secrets",
|
||||||
|
(),
|
||||||
|
|row| row.get(0),
|
||||||
|
|rows| {
|
||||||
|
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let (mail, is_encrypted) = match tokio::task::block_in_place(|| {
|
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)) => {
|
Ok(Some(mut msg)) => {
|
||||||
mail_raw = msg.as_data_vec().unwrap_or_default();
|
mail_raw = msg.as_data_vec().unwrap_or_default();
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ pub enum Param {
|
|||||||
/// post something to the mailing list.
|
/// post something to the mailing list.
|
||||||
ListPost = b'p',
|
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,
|
/// The symmetric key shared among all chat participants,
|
||||||
/// used to encrypt and decrypt messages.
|
/// used to encrypt and decrypt messages.
|
||||||
SymmetricKey = b'z',
|
SymmetricKey = b'z',
|
||||||
|
|||||||
20
src/pgp.rs
20
src/pgp.rs
@@ -239,7 +239,7 @@ pub fn pk_calc_signature(
|
|||||||
pub fn decrypt(
|
pub fn decrypt(
|
||||||
ctext: Vec<u8>,
|
ctext: Vec<u8>,
|
||||||
private_keys_for_decryption: &[SignedSecretKey],
|
private_keys_for_decryption: &[SignedSecretKey],
|
||||||
symmetric_secrets: &[&str],
|
symmetric_secrets: &[String],
|
||||||
) -> Result<pgp::composed::Message<'static>> {
|
) -> Result<pgp::composed::Message<'static>> {
|
||||||
let cursor = Cursor::new(ctext);
|
let cursor = Cursor::new(ctext);
|
||||||
let (msg, _headers) = Message::from_armor(cursor)?;
|
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
|
// TODO it may degrade performance that we always try out all passwords here
|
||||||
let message_password: Vec<Password> = symmetric_secrets
|
let message_password: Vec<Password> = symmetric_secrets
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| Password::from(*p))
|
.map(|p| Password::from(p.as_str()))
|
||||||
.collect();
|
.collect();
|
||||||
let message_password: Vec<&Password> = message_password.iter().collect();
|
let message_password: Vec<&Password> = message_password.iter().collect();
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ pub async fn symm_encrypt(passphrase: &str, plain: Vec<u8>) -> Result<String> {
|
|||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let s2k = StringToKey::new_default(&mut 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);
|
let mut builder = builder.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
|
||||||
builder.encrypt_with_password(s2k, &passphrase)?;
|
builder.encrypt_with_password(s2k, &passphrase)?;
|
||||||
|
|
||||||
@@ -389,7 +389,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
key::load_self_secret_key,
|
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>(
|
fn pk_decrypt_and_validate<'a>(
|
||||||
@@ -401,7 +401,7 @@ mod tests {
|
|||||||
HashSet<Fingerprint>,
|
HashSet<Fingerprint>,
|
||||||
Vec<u8>,
|
Vec<u8>,
|
||||||
)> {
|
)> {
|
||||||
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 content = msg.as_data_vec()?;
|
||||||
let ret_signature_fingerprints =
|
let ret_signature_fingerprints =
|
||||||
valid_signature_fingerprints(&msg, public_keys_for_validation)?;
|
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 plain = Vec::from(b"this is the secret message");
|
||||||
let shared_secret = "shared secret";
|
let shared_secret = "shared secret";
|
||||||
let ctext = encrypt_for_broadcast(
|
let ctext = encrypt_for_broadcast(
|
||||||
plain,
|
plain.clone(),
|
||||||
shared_secret,
|
shared_secret,
|
||||||
load_self_secret_key(alice).await?,
|
load_self_secret_key(alice).await?,
|
||||||
true,
|
true,
|
||||||
@@ -605,7 +605,13 @@ mod tests {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let bob_private_keyring = crate::key::load_self_secret_keyring(bob).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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1254,10 +1254,13 @@ CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
|
|||||||
inc_and_check(&mut migration_version, 133)?;
|
inc_and_check(&mut migration_version, 133)?;
|
||||||
if dbversion < migration_version {
|
if dbversion < migration_version {
|
||||||
sql.execute_migration(
|
sql.execute_migration(
|
||||||
"CREATE TABLE symmetric_secrets(
|
"CREATE TABLE broadcasts_shared_secrets(
|
||||||
chat_id INTEGER PRIMARY KEY NOT NULL,
|
chat_id INTEGER PRIMARY KEY NOT NULL, -- TODO we don't actually need the chat_id
|
||||||
symmetric_secret: ",
|
secret TEXT NOT NULL
|
||||||
|
) STRICT",
|
||||||
|
migration_version,
|
||||||
)
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_version = sql
|
let new_version = sql
|
||||||
|
|||||||
Reference in New Issue
Block a user