deltachat/
pgp.rs

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