deltachat/
pgp.rs

1//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
2
3use std::collections::{BTreeMap, HashMap, HashSet};
4use std::io::{BufRead, Cursor};
5
6use anyhow::{Context as _, Result};
7use deltachat_contact_tools::EmailAddress;
8use pgp::armor::BlockType;
9use pgp::composed::{
10    ArmorOptions, Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType,
11    Message, MessageBuilder, SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey,
12    SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig,
13};
14use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
15use pgp::crypto::ecc_curve::ECCCurve;
16use pgp::crypto::hash::HashAlgorithm;
17use pgp::crypto::sym::SymmetricKeyAlgorithm;
18use pgp::packet::{SignatureConfig, SignatureType, Subpacket, SubpacketData};
19use pgp::types::{
20    CompressionAlgorithm, KeyDetails, KeyVersion, Password, SigningKey as _, StringToKey,
21};
22use rand_old::{Rng as _, thread_rng};
23use tokio::runtime::Handle;
24
25use crate::key::{DcKey, Fingerprint};
26
27#[cfg(test)]
28pub(crate) const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
29
30pub(crate) const HEADER_SETUPCODE: &str = "passphrase-begin";
31
32/// Preferred symmetric encryption algorithm.
33const SYMMETRIC_KEY_ALGORITHM: SymmetricKeyAlgorithm = SymmetricKeyAlgorithm::AES128;
34
35/// Split data from PGP Armored Data as defined in <https://tools.ietf.org/html/rfc4880#section-6.2>.
36///
37/// Returns (type, headers, base64 encoded body).
38pub fn split_armored_data(buf: &[u8]) -> Result<(BlockType, BTreeMap<String, String>, Vec<u8>)> {
39    use std::io::Read;
40
41    let cursor = Cursor::new(buf);
42    let mut dearmor = pgp::armor::Dearmor::new(cursor);
43
44    let mut bytes = Vec::with_capacity(buf.len());
45
46    dearmor.read_to_end(&mut bytes)?;
47    let typ = dearmor.typ.context("failed to parse type")?;
48
49    // normalize headers
50    let headers = dearmor
51        .headers
52        .into_iter()
53        .map(|(key, values)| {
54            (
55                key.trim().to_lowercase(),
56                values
57                    .last()
58                    .map_or_else(String::new, |s| s.trim().to_string()),
59            )
60        })
61        .collect();
62
63    Ok((typ, headers, bytes))
64}
65
66/// Create a new key pair.
67///
68/// Both secret and public key consist of signing primary key and encryption subkey
69/// as [described in the Autocrypt standard](https://autocrypt.org/level1.html#openpgp-based-key-data).
70pub(crate) fn create_keypair(addr: EmailAddress) -> Result<SignedSecretKey> {
71    let signing_key_type = PgpKeyType::Ed25519Legacy;
72    let encryption_key_type = PgpKeyType::ECDH(ECCCurve::Curve25519);
73
74    let user_id = format!("<{addr}>");
75    let key_params = SecretKeyParamsBuilder::default()
76        .key_type(signing_key_type)
77        .can_certify(true)
78        .can_sign(true)
79        .feature_seipd_v2(true)
80        .primary_user_id(user_id)
81        .passphrase(None)
82        .preferred_symmetric_algorithms(smallvec![
83            SymmetricKeyAlgorithm::AES256,
84            SymmetricKeyAlgorithm::AES192,
85            SymmetricKeyAlgorithm::AES128,
86        ])
87        .preferred_hash_algorithms(smallvec![
88            HashAlgorithm::Sha256,
89            HashAlgorithm::Sha384,
90            HashAlgorithm::Sha512,
91            HashAlgorithm::Sha224,
92        ])
93        .preferred_compression_algorithms(smallvec![
94            CompressionAlgorithm::ZLIB,
95            CompressionAlgorithm::ZIP,
96        ])
97        .subkey(
98            SubkeyParamsBuilder::default()
99                .key_type(encryption_key_type)
100                .can_encrypt(EncryptionCaps::All)
101                .passphrase(None)
102                .build()
103                .context("failed to build subkey parameters")?,
104        )
105        .build()
106        .context("failed to build key parameters")?;
107
108    let mut rng = thread_rng();
109    let secret_key = key_params
110        .generate(&mut rng)
111        .context("Failed to generate the key")?;
112    secret_key
113        .verify_bindings()
114        .context("Invalid secret key generated")?;
115
116    Ok(secret_key)
117}
118
119/// Selects a subkey of the public key to use for encryption.
120///
121/// Returns `None` if the public key cannot be used for encryption.
122///
123/// TODO: take key flags and expiration dates into account
124fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
125    key.public_subkeys
126        .iter()
127        .find(|subkey| subkey.algorithm().can_encrypt())
128}
129
130/// Version of SEIPD packet to use.
131///
132/// See
133/// <https://www.rfc-editor.org/rfc/rfc9580#name-avoiding-ciphertext-malleab>
134/// for the discussion on when v2 SEIPD should be used.
135#[derive(Debug)]
136pub enum SeipdVersion {
137    /// Use v1 SEIPD, for compatibility.
138    V1,
139
140    /// Use v2 SEIPD when we know that v2 SEIPD is supported.
141    V2,
142}
143
144/// Encrypts `plain` text using `public_keys_for_encryption`
145/// and signs it using `private_key_for_signing`.
146#[expect(clippy::arithmetic_side_effects)]
147pub async fn pk_encrypt(
148    plain: Vec<u8>,
149    public_keys_for_encryption: Vec<SignedPublicKey>,
150    private_key_for_signing: SignedSecretKey,
151    compress: bool,
152    anonymous_recipients: bool,
153    seipd_version: SeipdVersion,
154) -> Result<String> {
155    Handle::current()
156        .spawn_blocking(move || {
157            let mut rng = thread_rng();
158
159            let pkeys = public_keys_for_encryption
160                .iter()
161                .filter_map(select_pk_for_encryption);
162            let subpkts = {
163                let mut hashed = Vec::with_capacity(1 + public_keys_for_encryption.len() + 1);
164                hashed.push(Subpacket::critical(SubpacketData::SignatureCreationTime(
165                    pgp::types::Timestamp::now(),
166                ))?);
167                // Test "elena" uses old Delta Chat.
168                let skip = private_key_for_signing.dc_fingerprint().hex()
169                    == "B86586B6DEF437D674BFAFC02A6B2EBC633B9E82";
170                for key in &public_keys_for_encryption {
171                    if skip {
172                        break;
173                    }
174                    let data = SubpacketData::IntendedRecipientFingerprint(key.fingerprint());
175                    let subpkt = match private_key_for_signing.version() < KeyVersion::V6 {
176                        true => Subpacket::regular(data)?,
177                        false => Subpacket::critical(data)?,
178                    };
179                    hashed.push(subpkt);
180                }
181                hashed.push(Subpacket::regular(SubpacketData::IssuerFingerprint(
182                    private_key_for_signing.fingerprint(),
183                ))?);
184                let mut unhashed = vec![];
185                if private_key_for_signing.version() <= KeyVersion::V4 {
186                    unhashed.push(Subpacket::regular(SubpacketData::IssuerKeyId(
187                        private_key_for_signing.legacy_key_id(),
188                    ))?);
189                }
190                SubpacketConfig::UserDefined { hashed, unhashed }
191            };
192
193            let msg = MessageBuilder::from_bytes("", plain);
194            let encoded_msg = match seipd_version {
195                SeipdVersion::V1 => {
196                    let mut msg = msg.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
197
198                    for pkey in pkeys {
199                        if anonymous_recipients {
200                            msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
201                        } else {
202                            msg.encrypt_to_key(&mut rng, &pkey)?;
203                        }
204                    }
205
206                    let hash_algorithm = private_key_for_signing.hash_alg();
207                    msg.sign_with_subpackets(
208                        &*private_key_for_signing,
209                        Password::empty(),
210                        hash_algorithm,
211                        subpkts,
212                    );
213                    if compress {
214                        msg.compression(CompressionAlgorithm::ZLIB);
215                    }
216
217                    msg.to_armored_string(&mut rng, Default::default())?
218                }
219                SeipdVersion::V2 => {
220                    let mut msg = msg.seipd_v2(
221                        &mut rng,
222                        SYMMETRIC_KEY_ALGORITHM,
223                        AeadAlgorithm::Ocb,
224                        ChunkSize::C8KiB,
225                    );
226
227                    for pkey in pkeys {
228                        if anonymous_recipients {
229                            msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
230                        } else {
231                            msg.encrypt_to_key(&mut rng, &pkey)?;
232                        }
233                    }
234
235                    let hash_algorithm = private_key_for_signing.hash_alg();
236                    msg.sign_with_subpackets(
237                        &*private_key_for_signing,
238                        Password::empty(),
239                        hash_algorithm,
240                        subpkts,
241                    );
242                    if compress {
243                        msg.compression(CompressionAlgorithm::ZLIB);
244                    }
245
246                    msg.to_armored_string(&mut rng, Default::default())?
247                }
248            };
249
250            Ok(encoded_msg)
251        })
252        .await?
253}
254
255/// Produces a detached signature for `plain` text using `private_key_for_signing`.
256pub fn pk_calc_signature(
257    plain: Vec<u8>,
258    private_key_for_signing: &SignedSecretKey,
259) -> Result<String> {
260    let rng = thread_rng();
261
262    let mut config = SignatureConfig::from_key(
263        rng,
264        &private_key_for_signing.primary_key,
265        SignatureType::Binary,
266    )?;
267
268    config.hashed_subpackets = vec![
269        Subpacket::regular(SubpacketData::IssuerFingerprint(
270            private_key_for_signing.fingerprint(),
271        ))?,
272        Subpacket::critical(SubpacketData::SignatureCreationTime(
273            pgp::types::Timestamp::now(),
274        ))?,
275    ];
276    config.unhashed_subpackets = vec![];
277    if private_key_for_signing.version() <= KeyVersion::V4 {
278        config
279            .unhashed_subpackets
280            .push(Subpacket::regular(SubpacketData::IssuerKeyId(
281                private_key_for_signing.legacy_key_id(),
282            ))?);
283    }
284
285    let signature = config.sign(
286        &private_key_for_signing.primary_key,
287        &Password::empty(),
288        plain.as_slice(),
289    )?;
290
291    let sig = DetachedSignature::new(signature);
292
293    Ok(sig.to_armored_string(ArmorOptions::default())?)
294}
295
296/// Returns fingerprints
297/// of all keys from the `public_keys_for_validation` keyring that
298/// have valid signatures in `msg` and corresponding intended recipient fingerprints
299/// (<https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr>) if any.
300///
301/// If the message is wrongly signed, returns an empty map.
302pub fn valid_signature_fingerprints(
303    msg: &pgp::composed::Message,
304    public_keys_for_validation: &[SignedPublicKey],
305) -> HashMap<Fingerprint, Vec<Fingerprint>> {
306    let mut ret_signature_fingerprints = HashMap::new();
307    if msg.is_signed() {
308        for pkey in public_keys_for_validation {
309            if let Ok(signature) = msg.verify(&pkey.primary_key) {
310                let fp = pkey.dc_fingerprint();
311                let mut recipient_fps = Vec::new();
312                if let Some(cfg) = signature.config() {
313                    for subpkt in &cfg.hashed_subpackets {
314                        if let SubpacketData::IntendedRecipientFingerprint(fp) = &subpkt.data {
315                            recipient_fps.push(fp.clone().into());
316                        }
317                    }
318                }
319                ret_signature_fingerprints.insert(fp, recipient_fps);
320            }
321        }
322    }
323    ret_signature_fingerprints
324}
325
326/// Validates detached signature.
327pub fn pk_validate(
328    content: &[u8],
329    signature: &[u8],
330    public_keys_for_validation: &[SignedPublicKey],
331) -> Result<HashSet<Fingerprint>> {
332    let mut ret: HashSet<Fingerprint> = Default::default();
333
334    let detached_signature = DetachedSignature::from_armor_single(Cursor::new(signature))?.0;
335
336    for pkey in public_keys_for_validation {
337        if detached_signature.verify(pkey, content).is_ok() {
338            let fp = pkey.dc_fingerprint();
339            ret.insert(fp);
340        }
341    }
342    Ok(ret)
343}
344
345/// Symmetric encryption for the autocrypt setup message (ASM).
346pub async fn symm_encrypt_autocrypt_setup(passphrase: &str, plain: Vec<u8>) -> Result<String> {
347    let passphrase = Password::from(passphrase.to_string());
348
349    tokio::task::spawn_blocking(move || {
350        let mut rng = thread_rng();
351        let s2k = StringToKey::new_default(&mut rng);
352        let builder = MessageBuilder::from_bytes("", plain);
353        let mut builder = builder.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
354        builder.encrypt_with_password(s2k, &passphrase)?;
355
356        let encoded_msg = builder.to_armored_string(&mut rng, Default::default())?;
357
358        Ok(encoded_msg)
359    })
360    .await?
361}
362
363/// Symmetrically encrypt the message.
364/// This is used for broadcast channels and for version 2 of the Securejoin protocol.
365/// `shared secret` is the secret that will be used for symmetric encryption.
366pub async fn symm_encrypt_message(
367    plain: Vec<u8>,
368    private_key_for_signing: Option<SignedSecretKey>,
369    shared_secret: &str,
370    compress: bool,
371) -> Result<String> {
372    let shared_secret = Password::from(shared_secret.to_string());
373
374    tokio::task::spawn_blocking(move || {
375        let msg = MessageBuilder::from_bytes("", plain);
376        let mut rng = thread_rng();
377        let mut salt = [0u8; 8];
378        rng.fill(&mut salt[..]);
379        let s2k = StringToKey::Salted {
380            hash_alg: HashAlgorithm::default(),
381            salt,
382        };
383        let mut msg = msg.seipd_v2(
384            &mut rng,
385            SYMMETRIC_KEY_ALGORITHM,
386            AeadAlgorithm::Ocb,
387            ChunkSize::C8KiB,
388        );
389        msg.encrypt_with_password(&mut rng, s2k, &shared_secret)?;
390
391        if let Some(private_key_for_signing) = private_key_for_signing.as_deref() {
392            let hash_algorithm = private_key_for_signing.hash_alg();
393            msg.sign(private_key_for_signing, Password::empty(), hash_algorithm);
394        }
395        if compress {
396            msg.compression(CompressionAlgorithm::ZLIB);
397        }
398
399        let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
400
401        Ok(encoded_msg)
402    })
403    .await?
404}
405
406/// Symmetric decryption.
407pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>(
408    passphrase: &str,
409    ctext: T,
410) -> Result<Vec<u8>> {
411    let passphrase = passphrase.to_string();
412    tokio::task::spawn_blocking(move || {
413        let (enc_msg, _) = Message::from_armor(ctext)?;
414        let password = Password::from(passphrase);
415
416        let msg = enc_msg.decrypt_with_password(&password)?;
417        let res = msg.decompress()?.as_data_vec()?;
418        Ok(res)
419    })
420    .await?
421}
422
423#[cfg(test)]
424mod tests {
425    use std::sync::LazyLock;
426    use tokio::sync::OnceCell;
427
428    use super::*;
429    use crate::{
430        decrypt,
431        key::{load_self_public_key, load_self_secret_key, store_self_keypair},
432        mimefactory::{render_outer_message, wrap_encrypted_part},
433        test_utils::{TestContext, TestContextManager, alice_keypair, bob_keypair},
434        token,
435    };
436    use pgp::composed::Esk;
437    use pgp::packet::PublicKeyEncryptedSessionKey;
438
439    async fn decrypt_bytes(
440        bytes: Vec<u8>,
441        private_keys_for_decryption: &[SignedSecretKey],
442        shared_secrets: &[String],
443    ) -> Result<pgp::composed::Message<'static>> {
444        let t = &TestContext::new().await;
445
446        for secret in shared_secrets {
447            token::save(t, token::Namespace::Auth, None, secret, 0).await?;
448        }
449        let [secret_key] = private_keys_for_decryption else {
450            panic!("Only one private key is allowed anymore");
451        };
452        store_self_keypair(t, secret_key).await?;
453
454        let mime_message = wrap_encrypted_part(bytes.try_into().unwrap());
455        let rendered = render_outer_message(vec![], mime_message);
456        let parsed = mailparse::parse_mail(rendered.as_bytes())?;
457        let (decrypted, _fp) = decrypt::decrypt(t, &parsed).await?.unwrap();
458        Ok(decrypted)
459    }
460
461    async fn pk_decrypt_and_validate<'a>(
462        ctext: &'a [u8],
463        private_keys_for_decryption: &'a [SignedSecretKey],
464        public_keys_for_validation: &[SignedPublicKey],
465    ) -> Result<(
466        pgp::composed::Message<'static>,
467        HashMap<Fingerprint, Vec<Fingerprint>>,
468        Vec<u8>,
469    )> {
470        let mut msg = decrypt_bytes(ctext.to_vec(), private_keys_for_decryption, &[]).await?;
471        let content = msg.as_data_vec()?;
472        let ret_signature_fingerprints =
473            valid_signature_fingerprints(&msg, public_keys_for_validation);
474
475        Ok((msg, ret_signature_fingerprints, content))
476    }
477
478    #[test]
479    fn test_split_armored_data_1() {
480        let (typ, _headers, base64) = split_armored_data(
481            b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE-----",
482        )
483        .unwrap();
484
485        assert_eq!(typ, BlockType::Message);
486        assert!(!base64.is_empty());
487        assert_eq!(
488            std::string::String::from_utf8(base64).unwrap(),
489            "hello world"
490        );
491    }
492
493    #[test]
494    fn test_split_armored_data_2() {
495        let (typ, headers, base64) = split_armored_data(
496            b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
497        )
498            .unwrap();
499
500        assert_eq!(typ, BlockType::PrivateKey);
501        assert!(!base64.is_empty());
502        assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
503    }
504
505    #[test]
506    fn test_create_keypair() {
507        let keypair0 = create_keypair(EmailAddress::new("foo@bar.de").unwrap()).unwrap();
508        let keypair1 = create_keypair(EmailAddress::new("two@zwo.de").unwrap()).unwrap();
509        assert_ne!(keypair0.public_key(), keypair1.public_key());
510    }
511
512    /// [SignedSecretKey] and [SignedPublicKey] objects
513    /// to use in tests.
514    struct TestKeys {
515        alice_secret: SignedSecretKey,
516        alice_public: SignedPublicKey,
517        bob_secret: SignedSecretKey,
518        bob_public: SignedPublicKey,
519    }
520
521    impl TestKeys {
522        fn new() -> TestKeys {
523            let alice = alice_keypair();
524            let bob = bob_keypair();
525            TestKeys {
526                alice_secret: alice.clone(),
527                alice_public: alice.to_public_key(),
528                bob_secret: bob.clone(),
529                bob_public: bob.to_public_key(),
530            }
531        }
532    }
533
534    /// The original text of [CTEXT_SIGNED]
535    static CLEARTEXT: &[u8] = b"This is a test";
536
537    /// Initialised [TestKeys] for tests.
538    static KEYS: LazyLock<TestKeys> = LazyLock::new(TestKeys::new);
539
540    static CTEXT_SIGNED: OnceCell<String> = OnceCell::const_new();
541
542    /// A ciphertext encrypted to Alice & Bob, signed by Alice.
543    async fn ctext_signed() -> &'static String {
544        let anonymous_recipients = true;
545        CTEXT_SIGNED
546            .get_or_init(|| async {
547                let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
548                let compress = true;
549
550                pk_encrypt(
551                    CLEARTEXT.to_vec(),
552                    keyring,
553                    KEYS.alice_secret.clone(),
554                    compress,
555                    anonymous_recipients,
556                    SeipdVersion::V2,
557                )
558                .await
559                .unwrap()
560            })
561            .await
562    }
563
564    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
565    async fn test_encrypt_signed() {
566        assert!(!ctext_signed().await.is_empty());
567        assert!(
568            ctext_signed()
569                .await
570                .starts_with("-----BEGIN PGP MESSAGE-----")
571        );
572    }
573
574    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
575    async fn test_decrypt_signed() {
576        // Check decrypting as Alice
577        let decrypt_keyring = vec![KEYS.alice_secret.clone()];
578        let sig_check_keyring = vec![KEYS.alice_public.clone()];
579        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
580            ctext_signed().await.as_bytes(),
581            &decrypt_keyring,
582            &sig_check_keyring,
583        )
584        .await
585        .unwrap();
586        assert_eq!(content, CLEARTEXT);
587        assert_eq!(valid_signatures.len(), 1);
588        for recipient_fps in valid_signatures.values() {
589            assert_eq!(recipient_fps.len(), 2);
590        }
591
592        // Check decrypting as Bob
593        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
594        let sig_check_keyring = vec![KEYS.alice_public.clone()];
595        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
596            ctext_signed().await.as_bytes(),
597            &decrypt_keyring,
598            &sig_check_keyring,
599        )
600        .await
601        .unwrap();
602        assert_eq!(content, CLEARTEXT);
603        assert_eq!(valid_signatures.len(), 1);
604        for recipient_fps in valid_signatures.values() {
605            assert_eq!(recipient_fps.len(), 2);
606        }
607    }
608
609    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
610    async fn test_decrypt_no_sig_check() {
611        let keyring = vec![KEYS.alice_secret.clone()];
612        let (_msg, valid_signatures, content) =
613            pk_decrypt_and_validate(ctext_signed().await.as_bytes(), &keyring, &[])
614                .await
615                .unwrap();
616        assert_eq!(content, CLEARTEXT);
617        assert_eq!(valid_signatures.len(), 0);
618    }
619
620    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
621    async fn test_decrypt_signed_no_key() {
622        // The validation does not have the public key of the signer.
623        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
624        let sig_check_keyring = vec![KEYS.bob_public.clone()];
625        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
626            ctext_signed().await.as_bytes(),
627            &decrypt_keyring,
628            &sig_check_keyring,
629        )
630        .await
631        .unwrap();
632        assert_eq!(content, CLEARTEXT);
633        assert_eq!(valid_signatures.len(), 0);
634    }
635
636    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
637    async fn test_decrypt_unsigned() {
638        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
639        let ctext_unsigned = include_bytes!("../test-data/message/ctext_unsigned.asc");
640        let (_msg, valid_signatures, content) =
641            pk_decrypt_and_validate(ctext_unsigned, &decrypt_keyring, &[])
642                .await
643                .unwrap();
644        assert_eq!(content, CLEARTEXT);
645        assert_eq!(valid_signatures.len(), 0);
646    }
647
648    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
649    async fn test_encrypt_decrypt_broadcast() -> Result<()> {
650        let mut tcm = TestContextManager::new();
651        let alice = &tcm.alice().await;
652        let bob = &tcm.bob().await;
653
654        let plain = Vec::from(b"this is the secret message");
655        let shared_secret = "shared secret";
656        let ctext = symm_encrypt_message(
657            plain.clone(),
658            Some(load_self_secret_key(alice).await?),
659            shared_secret,
660            true,
661        )
662        .await?;
663
664        let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
665        let mut decrypted = decrypt_bytes(
666            ctext.into(),
667            &bob_private_keyring,
668            &[shared_secret.to_string()],
669        )
670        .await
671        .unwrap();
672
673        assert_eq!(decrypted.as_data_vec()?, plain);
674
675        Ok(())
676    }
677
678    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
679    async fn test_dont_decrypt_expensive_message_happy_path() -> Result<()> {
680        let s2k = StringToKey::Salted {
681            hash_alg: HashAlgorithm::default(),
682            salt: [1; 8],
683        };
684
685        test_dont_decrypt_expensive_message_ex(s2k, false, None).await
686    }
687
688    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
689    async fn test_dont_decrypt_expensive_message_bad_s2k() -> Result<()> {
690        let s2k = StringToKey::new_default(&mut thread_rng()); // Default is IteratedAndSalted
691
692        test_dont_decrypt_expensive_message_ex(s2k, false, Some("unsupported string2key algorithm"))
693            .await
694    }
695
696    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
697    async fn test_dont_decrypt_expensive_message_multiple_secrets() -> Result<()> {
698        let s2k = StringToKey::Salted {
699            hash_alg: HashAlgorithm::default(),
700            salt: [1; 8],
701        };
702
703        // This error message is actually not great,
704        // but grepping for it will lead to the correct code
705        test_dont_decrypt_expensive_message_ex(s2k, true, Some("decrypt_with_keys: missing key"))
706            .await
707    }
708
709    /// Test that we don't try to decrypt a message
710    /// that is symmetrically encrypted
711    /// with an expensive string2key algorithm
712    /// or multiple shared secrets.
713    /// This is to prevent possible DOS attacks on the app.
714    async fn test_dont_decrypt_expensive_message_ex(
715        s2k: StringToKey,
716        encrypt_twice: bool,
717        expected_error_msg: Option<&str>,
718    ) -> Result<()> {
719        let mut tcm = TestContextManager::new();
720        let bob = &tcm.bob().await;
721
722        let plain = Vec::from(b"this is the secret message");
723        let shared_secret = "shared secret";
724
725        let shared_secret_pw = Password::from(shared_secret.to_string());
726        let msg = MessageBuilder::from_bytes("", plain);
727        let mut rng = thread_rng();
728
729        let mut msg = msg.seipd_v2(
730            &mut rng,
731            SymmetricKeyAlgorithm::AES128,
732            AeadAlgorithm::Ocb,
733            ChunkSize::C8KiB,
734        );
735        msg.encrypt_with_password(&mut rng, s2k.clone(), &shared_secret_pw)?;
736        if encrypt_twice {
737            msg.encrypt_with_password(&mut rng, s2k, &shared_secret_pw)?;
738        }
739
740        let ctext = msg.to_armored_string(&mut rng, Default::default())?;
741
742        // Trying to decrypt it should fail with a helpful error message:
743
744        let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
745        let res = decrypt_bytes(
746            ctext.into(),
747            &bob_private_keyring,
748            &[shared_secret.to_string()],
749        )
750        .await;
751
752        if let Some(expected_error_msg) = expected_error_msg {
753            assert_eq!(format!("{:#}", res.unwrap_err()), expected_error_msg);
754        } else {
755            res.unwrap();
756        }
757
758        Ok(())
759    }
760
761    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
762    async fn test_decryption_error_msg() -> Result<()> {
763        let mut tcm = TestContextManager::new();
764        let alice = &tcm.alice().await;
765        let bob = &tcm.bob().await;
766
767        let plain = Vec::from(b"this is the secret message");
768        let pk_for_encryption = load_self_public_key(alice).await?;
769
770        // Encrypt a message, but only to self, not to Bob:
771        let ctext = pk_encrypt(
772            plain,
773            vec![pk_for_encryption],
774            KEYS.alice_secret.clone(),
775            true,
776            true,
777            SeipdVersion::V2,
778        )
779        .await?;
780
781        // Trying to decrypt it should fail with an OK error message:
782        let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
783        let error = decrypt_bytes(ctext.into(), &bob_private_keyring, &[])
784            .await
785            .unwrap_err();
786
787        assert_eq!(format!("{error:#}"), "decrypt_with_keys: missing key");
788
789        Ok(())
790    }
791
792    /// Tests that recipient key IDs and fingerprints
793    /// are omitted or replaced with wildcards.
794    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
795    async fn test_anonymous_recipients() -> Result<()> {
796        let ctext = ctext_signed().await.as_bytes();
797        let cursor = Cursor::new(ctext);
798        let (msg, _headers) = Message::from_armor(cursor)?;
799
800        let Message::Encrypted { esk, .. } = msg else {
801            unreachable!();
802        };
803
804        for encrypted_session_key in esk {
805            let Esk::PublicKeyEncryptedSessionKey(pkesk) = encrypted_session_key else {
806                unreachable!()
807            };
808
809            match pkesk {
810                PublicKeyEncryptedSessionKey::V3 { id, .. } => {
811                    assert!(id.is_wildcard());
812                }
813                PublicKeyEncryptedSessionKey::V6 { fingerprint, .. } => {
814                    assert!(fingerprint.is_none());
815                }
816                PublicKeyEncryptedSessionKey::Other { .. } => unreachable!(),
817            }
818        }
819        Ok(())
820    }
821}