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