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