Skip to content

Commit db0d9d9

Browse files
committed
perf: Only load shared secrets if the message actually is symm-encrypted
This improves performance, though not a lot - 8% performance improvement if there is a huge number of shared secrets. NUM_BROADCAST_SECRETS=500 NUM_AUTH_TOKENS=5000 Decrypt/Decrypt and parse a symmetrically encrypted message time: [12.354 ms 12.364 ms 12.376 ms] change: [−0.5375% −0.3815% −0.2345%] (p = 0.00 < 0.05) Change within noise threshold. Found 7 outliers among 100 measurements (7.00%) 3 (3.00%) high mild 4 (4.00%) high severe Decrypt/Decrypt and parse a public-key encrypted message time: [12.269 ms 12.273 ms 12.278 ms] change: [−7.9163% −7.8439% −7.7724%] (p = 0.00 < 0.05) Performance has improved. Found 13 outliers among 100 measurements (13.00%) 8 (8.00%) high mild 5 (5.00%) high severe
1 parent 1b25853 commit db0d9d9

4 files changed

Lines changed: 105 additions & 91 deletions

File tree

src/decrypt.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,28 @@
1-
//! End-to-end decryption support.
1+
//! Helper functions for decryption.
2+
//! The actual decryption is done in the [`crate::pgp`] module.
23
34
use std::collections::HashSet;
5+
use std::io::Cursor;
46

7+
use ::pgp::composed::Message;
58
use anyhow::Result;
69
use mailparse::ParsedMail;
710

8-
use crate::key::{Fingerprint, SignedPublicKey, SignedSecretKey};
11+
use crate::key::{Fingerprint, SignedPublicKey};
912
use crate::pgp;
1013

11-
/// Tries to decrypt a message, but only if it is structured as an Autocrypt message.
12-
///
13-
/// If successful and the message was encrypted,
14-
/// returns the decrypted and decompressed message.
15-
pub fn try_decrypt<'a>(
16-
mail: &'a ParsedMail<'a>,
17-
private_keyring: &'a [SignedSecretKey],
18-
shared_secrets: &[String],
19-
) -> Result<Option<::pgp::composed::Message<'static>>> {
14+
pub fn get_encrypted_pgp_message<'a>(mail: &'a ParsedMail<'a>) -> Result<Option<Message<'static>>> {
2015
let Some(encrypted_data_part) = get_encrypted_mime(mail) else {
2116
return Ok(None);
2217
};
23-
2418
let data = encrypted_data_part.get_body_raw()?;
25-
let msg = pgp::decrypt(data, private_keyring, shared_secrets)?;
26-
19+
let cursor = Cursor::new(data);
20+
let (msg, _headers) = Message::from_armor(cursor)?;
2721
Ok(Some(msg))
2822
}
2923

3024
/// Returns a reference to the encrypted payload of a message.
31-
pub(crate) fn get_encrypted_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
25+
pub fn get_encrypted_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Option<&'a ParsedMail<'b>> {
3226
get_autocrypt_mime(mail)
3327
.or_else(|| get_mixed_up_mime(mail))
3428
.or_else(|| get_attachment_mime(mail))

src/internals_for_benches.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ pub fn create_dummy_keypair(addr: &str) -> Result<KeyPair> {
3535

3636
pub fn create_broadcast_secret() -> String {
3737
crate::tools::create_broadcast_secret()
38-
}
38+
}

src/mimeparser.rs

Lines changed: 77 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::chat::ChatId;
2020
use crate::config::Config;
2121
use crate::contact::ContactId;
2222
use crate::context::Context;
23-
use crate::decrypt::{try_decrypt, validate_detached_signature};
23+
use crate::decrypt::{get_encrypted_pgp_message, validate_detached_signature};
2424
use crate::dehtml::dehtml;
2525
use crate::download::PostMsgMetadata;
2626
use crate::events::EventType;
@@ -380,82 +380,93 @@ impl MimeMessage {
380380
PreMessageMode::None
381381
};
382382

383-
let mail_raw; // Memory location for a possible decrypted message.
384-
let decrypted_msg; // Decrypted signed OpenPGP message.
385-
386383
// TODO performance:
387384
// - maybe we should start sorting by timestamp
388-
// - we shouldn't do 3 SQL requests even if the message isn't symm-encrypted
389385
// - we're loading the whole bobstate, just to get the auth token
390-
let mut secrets: Vec<String> = context
391-
.sql
392-
.query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| {
393-
let secret: String = row.get(0)?;
394-
Ok(secret)
395-
})
396-
.await?;
397-
secrets.extend(token::lookup_all(context, token::Namespace::Auth).await?);
398-
context
399-
.sql
400-
.query_map(
401-
"SELECT id, invite FROM bobstate",
402-
(),
403-
|row| {
404-
let invite: crate::securejoin::QrInvite = row.get(1)?;
405-
Ok(invite)
406-
},
407-
|rows| {
408-
for row in rows {
409-
secrets.push(row?.authcode().to_string());
410-
}
411-
Ok(())
412-
},
413-
)
414-
.await?;
415386

416-
let (mail, is_encrypted) =
417-
match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) {
418-
Ok(Some(mut msg)) => {
419-
mail_raw = msg.as_data_vec().unwrap_or_default();
387+
let encrypted_pgp_message = get_encrypted_pgp_message(&mail)?;
420388

421-
let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
422-
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
423-
info!(
424-
context,
425-
"decrypted message mime-body:\n{}",
426-
String::from_utf8_lossy(&mail_raw),
427-
);
428-
}
389+
let mut secrets: Vec<String>;
390+
if let Some(e) = &encrypted_pgp_message
391+
&& crate::pgp::check_symmetric_encryption(e).is_ok()
392+
{
393+
secrets = context
394+
.sql
395+
.query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| {
396+
let secret: String = row.get(0)?;
397+
Ok(secret)
398+
})
399+
.await?;
400+
secrets.extend(token::lookup_all(context, token::Namespace::Auth).await?);
401+
context
402+
.sql
403+
.query_map(
404+
"SELECT id, invite FROM bobstate",
405+
(),
406+
|row| {
407+
let invite: crate::securejoin::QrInvite = row.get(1)?;
408+
Ok(invite)
409+
},
410+
|rows| {
411+
for row in rows {
412+
secrets.push(row?.authcode().to_string());
413+
}
414+
Ok(())
415+
},
416+
)
417+
.await?;
418+
} else {
419+
// No need to load all the secrets if the message isn't symmetrically encrypted
420+
secrets = vec![];
421+
}
429422

430-
decrypted_msg = Some(msg);
423+
let mail_raw; // Memory location for a possible decrypted message.
424+
let decrypted_msg; // Decrypted signed OpenPGP message.
431425

432-
timestamp_sent = Self::get_timestamp_sent(
433-
&decrypted_mail.headers,
434-
timestamp_sent,
435-
timestamp_rcvd,
426+
let (mail, is_encrypted) = match tokio::task::block_in_place(|| {
427+
encrypted_pgp_message.map(|e| crate::pgp::decrypt(e, &private_keyring, &secrets))
428+
}) {
429+
Some(Ok(mut msg)) => {
430+
mail_raw = msg.as_data_vec().unwrap_or_default();
431+
432+
let decrypted_mail = mailparse::parse_mail(&mail_raw)?;
433+
if std::env::var(crate::DCC_MIME_DEBUG).is_ok() {
434+
info!(
435+
context,
436+
"decrypted message mime-body:\n{}",
437+
String::from_utf8_lossy(&mail_raw),
436438
);
439+
}
437440

438-
let protected_aheader_values = decrypted_mail
439-
.headers
440-
.get_all_values(HeaderDef::Autocrypt.into());
441-
if !protected_aheader_values.is_empty() {
442-
aheader_values = protected_aheader_values;
443-
}
441+
decrypted_msg = Some(msg);
444442

445-
(Ok(decrypted_mail), true)
446-
}
447-
Ok(None) => {
448-
mail_raw = Vec::new();
449-
decrypted_msg = None;
450-
(Ok(mail), false)
451-
}
452-
Err(err) => {
453-
mail_raw = Vec::new();
454-
decrypted_msg = None;
455-
warn!(context, "decryption failed: {:#}", err);
456-
(Err(err), false)
443+
timestamp_sent = Self::get_timestamp_sent(
444+
&decrypted_mail.headers,
445+
timestamp_sent,
446+
timestamp_rcvd,
447+
);
448+
449+
let protected_aheader_values = decrypted_mail
450+
.headers
451+
.get_all_values(HeaderDef::Autocrypt.into());
452+
if !protected_aheader_values.is_empty() {
453+
aheader_values = protected_aheader_values;
457454
}
458-
};
455+
456+
(Ok(decrypted_mail), true)
457+
}
458+
None => {
459+
mail_raw = Vec::new();
460+
decrypted_msg = None;
461+
(Ok(mail), false)
462+
}
463+
Some(Err(err)) => {
464+
mail_raw = Vec::new();
465+
decrypted_msg = None;
466+
warn!(context, "decryption failed: {:#}", err);
467+
(Err(err), false)
468+
}
469+
};
459470

460471
let mut autocrypt_header = None;
461472
if incoming {

src/pgp.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,10 @@ pub fn pk_calc_signature(
327327
///
328328
/// Returns the decrypted and decompressed message.
329329
pub fn decrypt(
330-
ctext: Vec<u8>,
330+
msg: Message<'static>,
331331
private_keys_for_decryption: &[SignedSecretKey],
332332
mut shared_secrets: &[String],
333333
) -> Result<pgp::composed::Message<'static>> {
334-
let cursor = Cursor::new(ctext);
335-
let (msg, _headers) = Message::from_armor(cursor)?;
336-
337334
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect();
338335
let empty_pw = Password::empty();
339336

@@ -389,7 +386,9 @@ pub fn decrypt(
389386
/// with all of the known shared secrets.
390387
/// In order to prevent this, we do not try to symmetrically decrypt messages
391388
/// that use a string2key algorithm other than 'Salted'.
392-
fn check_symmetric_encryption(msg: &Message<'_>) -> std::result::Result<(), &'static str> {
389+
pub(crate) fn check_symmetric_encryption(
390+
msg: &Message<'_>,
391+
) -> std::result::Result<(), &'static str> {
393392
let Message::Encrypted { esk, .. } = msg else {
394393
return Err("not encrypted");
395394
};
@@ -542,6 +541,7 @@ mod tests {
542541

543542
use super::*;
544543
use crate::{
544+
decrypt::get_encrypted_pgp_message,
545545
key::{load_self_public_key, load_self_secret_key},
546546
test_utils::{TestContextManager, alice_keypair, bob_keypair},
547547
};
@@ -558,7 +558,11 @@ mod tests {
558558
HashMap<Fingerprint, Vec<Fingerprint>>,
559559
Vec<u8>,
560560
)> {
561-
let mut msg = decrypt(ctext.to_vec(), private_keys_for_decryption, &[])?;
561+
let mut msg = decrypt(
562+
get_encrypted_pgp_message(ctext.to_vec()),
563+
private_keys_for_decryption,
564+
&[],
565+
)?;
562566
let content = msg.as_data_vec()?;
563567
let ret_signature_fingerprints =
564568
valid_signature_fingerprints(&msg, public_keys_for_validation);
@@ -747,7 +751,7 @@ mod tests {
747751

748752
let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
749753
let mut decrypted = decrypt(
750-
ctext.into(),
754+
get_encrypted_pgp_message(ctext.into()),
751755
&bob_private_keyring,
752756
&[shared_secret.to_string()],
753757
)?;
@@ -790,7 +794,7 @@ mod tests {
790794

791795
let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
792796
let error = decrypt(
793-
ctext.into(),
797+
get_encrypted_pgp_message(ctext.into()),
794798
&bob_private_keyring,
795799
&[shared_secret.to_string()],
796800
)
@@ -826,7 +830,12 @@ mod tests {
826830

827831
// Trying to decrypt it should fail with an OK error message:
828832
let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
829-
let error = decrypt(ctext.into(), &bob_private_keyring, &[]).unwrap_err();
833+
let error = decrypt(
834+
get_encrypted_pgp_message(ctext.into()),
835+
&bob_private_keyring,
836+
&[],
837+
)
838+
.unwrap_err();
830839

831840
assert_eq!(
832841
error.to_string(),

0 commit comments

Comments
 (0)