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