deltachat/
pgp.rs

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