refactor: It's not actually necessary for Alice to remember how the message was encrypted

This commit is contained in:
Hocuri
2025-08-07 16:21:04 +02:00
parent 1377a77ea8
commit 40e3c34f59
5 changed files with 48 additions and 94 deletions

View File

@@ -71,7 +71,7 @@ fn criterion_benchmark(c: &mut Criterion) {
}); });
b.iter(|| { b.iter(|| {
let (mut msg, _) = let mut msg =
decrypt(encrypted.clone().into_bytes(), &[], black_box(&secrets)).unwrap(); decrypt(encrypted.clone().into_bytes(), &[], black_box(&secrets)).unwrap();
let decrypted = msg.as_data_vec().unwrap(); let decrypted = msg.as_data_vec().unwrap();
@@ -101,7 +101,7 @@ fn criterion_benchmark(c: &mut Criterion) {
}); });
b.iter(|| { b.iter(|| {
let (mut msg, _) = decrypt( let mut msg = decrypt(
encrypted.clone().into_bytes(), encrypted.clone().into_bytes(),
&[key_pair.secret.clone()], &[key_pair.secret.clone()],
black_box(&secrets), black_box(&secrets),

View File

@@ -19,7 +19,7 @@ pub fn try_decrypt<'a>(
mail: &'a ParsedMail<'a>, mail: &'a ParsedMail<'a>,
private_keyring: &'a [SignedSecretKey], private_keyring: &'a [SignedSecretKey],
shared_secrets: &[String], shared_secrets: &[String],
) -> Result<Option<(::pgp::composed::Message<'static>, Option<usize>)>> { ) -> 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);
}; };

View File

@@ -136,10 +136,6 @@ pub(crate) struct MimeMessage {
/// Sender timestamp in secs since epoch. Allowed to be in the future due to unsynchronized /// Sender timestamp in secs since epoch. Allowed to be in the future due to unsynchronized
/// clocks, but not too much. /// clocks, but not too much.
pub(crate) timestamp_sent: i64, pub(crate) timestamp_sent: i64,
/// How the message was encrypted (and now successfully decrypted):
/// The asymmetric key, an AUTH token, or a broadcast's shared secret.
pub(crate) was_encrypted_with: EncryptedWith,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -238,25 +234,6 @@ pub enum SystemMessage {
CallEnded = 67, CallEnded = 67,
} }
#[derive(Debug)]
pub(crate) enum EncryptedWith {
AsymmetricKey,
BroadcastSecret(String),
AuthToken(String),
None,
}
impl EncryptedWith {
pub(crate) fn auth_token(&self) -> Option<&str> {
match self {
EncryptedWith::AsymmetricKey => None,
EncryptedWith::BroadcastSecret(_) => None,
EncryptedWith::AuthToken(token) => Some(token),
EncryptedWith::None => None,
}
}
}
const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup"; const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup";
impl MimeMessage { impl MimeMessage {
@@ -389,64 +366,51 @@ impl MimeMessage {
}, },
) )
.await?; .await?;
let num_broadcast_secrets = secrets.len();
secrets.extend(token::lookup_all(context, token::Namespace::Auth).await?); secrets.extend(token::lookup_all(context, token::Namespace::Auth).await?);
let (mail, is_encrypted, decrypted_with) = match tokio::task::block_in_place(|| { let (mail, is_encrypted) =
try_decrypt(&mail, &private_keyring, &secrets) match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) {
}) { Ok(Some(mut msg)) => {
Ok(Some((mut msg, index_of_secret))) => { mail_raw = msg.as_data_vec().unwrap_or_default();
mail_raw = msg.as_data_vec().unwrap_or_default();
let decrypted_mail = mailparse::parse_mail(&mail_raw)?; let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() { if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
info!( info!(
context, context,
"decrypted message mime-body:\n{}", "decrypted message mime-body:\n{}",
String::from_utf8_lossy(&mail_raw), String::from_utf8_lossy(&mail_raw),
); );
}
decrypted_msg = Some(msg);
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);
}
let decrypted_with = if let Some(index_of_secret) = index_of_secret {
let used_secret = secrets.into_iter().nth(index_of_secret).unwrap_or_default();
if index_of_secret < num_broadcast_secrets {
EncryptedWith::BroadcastSecret(used_secret)
} else {
EncryptedWith::AuthToken(used_secret)
} }
} else {
EncryptedWith::AsymmetricKey
};
(Ok(decrypted_mail), true, decrypted_with) decrypted_msg = Some(msg);
}
Ok(None) => { timestamp_sent = Self::get_timestamp_sent(
mail_raw = Vec::new(); &decrypted_mail.headers,
decrypted_msg = None; timestamp_sent,
(Ok(mail), false, EncryptedWith::None) timestamp_rcvd,
} );
Err(err) => {
mail_raw = Vec::new(); if let Some(protected_aheader_value) = decrypted_mail
decrypted_msg = None; .headers
warn!(context, "decryption failed: {:#}", err); .get_header_value(HeaderDef::Autocrypt)
(Err(err), false, EncryptedWith::None) {
} aheader_value = Some(protected_aheader_value);
}; }
(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 { let autocrypt_header = if !incoming {
None None
@@ -656,7 +620,6 @@ impl MimeMessage {
is_bot: None, is_bot: None,
timestamp_rcvd, timestamp_rcvd,
timestamp_sent, timestamp_sent,
was_encrypted_with: decrypted_with,
}; };
match partial { match partial {

View File

@@ -251,7 +251,7 @@ pub fn decrypt(
ctext: Vec<u8>, ctext: Vec<u8>,
private_keys_for_decryption: &[SignedSecretKey], private_keys_for_decryption: &[SignedSecretKey],
shared_secrets: &[String], shared_secrets: &[String],
) -> Result<(pgp::composed::Message<'static>, Option<usize>)> { ) -> 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)?;
@@ -277,12 +277,7 @@ pub fn decrypt(
// remove one layer of compression // remove one layer of compression
let msg = msg.decompress()?; let msg = msg.decompress()?;
let decrypted_with_secret = ring_result Ok(msg)
.message_password
.iter()
.position(|&p| p == InnerRingResult::Ok);
Ok((msg, decrypted_with_secret))
} }
/// Returns fingerprints /// Returns fingerprints
@@ -418,7 +413,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);
@@ -622,14 +617,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 (mut decrypted, index_of_secret) = decrypt( let mut decrypted = decrypt(
ctext.into(), ctext.into(),
&bob_private_keyring, &bob_private_keyring,
&[shared_secret.to_string()], &[shared_secret.to_string()],
)?; )?;
assert_eq!(decrypted.as_data_vec()?, plain); assert_eq!(decrypted.as_data_vec()?, plain);
assert_eq!(index_of_secret, Some(0));
Ok(()) Ok(())
} }

View File

@@ -388,10 +388,7 @@ pub(crate) async fn handle_securejoin_handshake(
} }
// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code, // verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code,
// or that the message was encrypted with the secret written to the QR code. // or that the message was encrypted with the secret written to the QR code.
let auth = mime_message let Some(auth) = mime_message.get_header(HeaderDef::SecureJoinAuth) else {
.get_header(HeaderDef::SecureJoinAuth)
.or_else(|| mime_message.was_encrypted_with.auth_token());
let Some(auth) = auth else {
warn!( warn!(
context, context,
"Ignoring {step} message because of missing auth code." "Ignoring {step} message because of missing auth code."