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