WIP: Start with decryption, and a test for it. Next TODO: SQL table migartion.

This commit is contained in:
Hocuri
2025-07-07 17:41:31 +02:00
parent ec2056f5e2
commit 47bf4da1fe
6 changed files with 94 additions and 52 deletions

View File

@@ -15,7 +15,7 @@ opt-level = 1
# Make anyhow `backtrace` feature useful. # Make anyhow `backtrace` feature useful.
# With `debug = 0` there are no line numbers in the backtrace # With `debug = 0` there are no line numbers in the backtrace
# produced with RUST_BACKTRACE=1. # produced with RUST_BACKTRACE=1.
debug = 1 debug = 'full'
opt-level = 0 opt-level = 0
[profile.fuzz] [profile.fuzz]

View File

@@ -14,13 +14,14 @@ 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],
) -> 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);
}; };
let data = encrypted_data_part.get_body_raw()?; 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)) Ok(Some(msg))
} }

View File

@@ -73,8 +73,7 @@ impl EncryptHelper {
let cursor = Cursor::new(&mut raw_message); let cursor = Cursor::new(&mut raw_message);
mail_to_encrypt.clone().write_part(cursor).ok(); mail_to_encrypt.clone().write_part(cursor).ok();
let ctext = let ctext = pgp::encrypt_for_broadcast(raw_message, passphrase, sign_key, compress).await?;
pgp::encrypt_for_broadcast(raw_message, passphrase, Some(sign_key), compress).await?;
Ok(ctext) Ok(ctext)
} }

View File

@@ -354,9 +354,11 @@ 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 (mail, is_encrypted) = let (mail, is_encrypted) = match tokio::task::block_in_place(|| {
match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring)) { 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();

View File

@@ -236,9 +236,10 @@ pub fn pk_calc_signature(
/// ///
/// Receiver private keys are provided in /// Receiver private keys are provided in
/// `private_keys_for_decryption`. /// `private_keys_for_decryption`.
pub fn pk_decrypt( pub fn decrypt(
ctext: Vec<u8>, ctext: Vec<u8>,
private_keys_for_decryption: &[SignedSecretKey], private_keys_for_decryption: &[SignedSecretKey],
symmetric_secrets: &[&str],
) -> 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)?;
@@ -246,10 +247,17 @@ pub fn pk_decrypt(
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect(); let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect();
let empty_pw = Password::empty(); let empty_pw = Password::empty();
// TODO it may degrade performance that we always try out all passwords here
let message_password: Vec<Password> = symmetric_secrets
.iter()
.map(|p| Password::from(*p))
.collect();
let message_password: Vec<&Password> = message_password.iter().collect();
let ring = TheRing { let ring = TheRing {
secret_keys: skeys, secret_keys: skeys,
key_passwords: vec![&empty_pw], key_passwords: vec![&empty_pw],
message_password: vec![], message_password,
session_keys: vec![], session_keys: vec![],
allow_legacy: false, allow_legacy: false,
}; };
@@ -327,7 +335,7 @@ pub async fn symm_encrypt(passphrase: &str, plain: Vec<u8>) -> Result<String> {
pub async fn encrypt_for_broadcast( pub async fn encrypt_for_broadcast(
plain: Vec<u8>, plain: Vec<u8>,
passphrase: &str, passphrase: &str,
private_key_for_signing: Option<SignedSecretKey>, private_key_for_signing: SignedSecretKey,
compress: bool, compress: bool,
) -> Result<String> { ) -> Result<String> {
let passphrase = Password::from(passphrase.to_string()); let passphrase = Password::from(passphrase.to_string());
@@ -344,12 +352,10 @@ pub async fn encrypt_for_broadcast(
); );
msg.encrypt_with_password(&mut rng, s2k, &passphrase)?; msg.encrypt_with_password(&mut rng, s2k, &passphrase)?;
if let Some(ref skey) = private_key_for_signing { msg.sign(&*private_key_for_signing, Password::empty(), HASH_ALGORITHM);
msg.sign(&**skey, Password::empty(), HASH_ALGORITHM);
if compress { if compress {
msg.compression(CompressionAlgorithm::ZLIB); msg.compression(CompressionAlgorithm::ZLIB);
} }
}
let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?; let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
@@ -381,7 +387,10 @@ mod tests {
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use super::*; 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>( fn pk_decrypt_and_validate<'a>(
ctext: &'a [u8], ctext: &'a [u8],
@@ -392,7 +401,7 @@ mod tests {
HashSet<Fingerprint>, HashSet<Fingerprint>,
Vec<u8>, Vec<u8>,
)> { )> {
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 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);
@@ -578,4 +587,26 @@ mod tests {
assert_eq!(content, CLEARTEXT); assert_eq!(content, CLEARTEXT);
assert_eq!(valid_signatures.len(), 0); 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(())
}
} }

View File

@@ -1261,6 +1261,15 @@ CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
.await?; .await?;
} }
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: ",
)
}
let new_version = sql let new_version = sql
.get_raw_config_int(VERSION_CFG) .get_raw_config_int(VERSION_CFG)
.await? .await?