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