mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 01:16:31 +03:00
WIP: Start with decryption, and a test for it. Next TODO: SQL table migartion.
This commit is contained in:
@@ -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]
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
47
src/pgp.rs
47
src/pgp.rs
@@ -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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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?
|
||||||
|
|||||||
Reference in New Issue
Block a user