deltachat/
pgp.rs

1//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
2
3use std::collections::{HashMap, HashSet};
4use std::io::Cursor;
5
6use anyhow::{Context as _, Result, ensure};
7use deltachat_contact_tools::{EmailAddress, may_be_valid_addr};
8use pgp::composed::{
9    ArmorOptions, Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType,
10    MessageBuilder, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, SignedPublicSubKey,
11    SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig,
12};
13use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
14use pgp::crypto::ecc_curve::ECCCurve;
15use pgp::crypto::hash::HashAlgorithm;
16use pgp::crypto::sym::SymmetricKeyAlgorithm;
17use pgp::packet::{Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData};
18use pgp::types::{
19    CompressionAlgorithm, Imprint, KeyDetails, KeyVersion, Password, SignedUser, SigningKey as _,
20    StringToKey,
21};
22use rand_old::{Rng as _, thread_rng};
23use sha2::Sha256;
24use tokio::runtime::Handle;
25
26use crate::key::{DcKey, Fingerprint};
27
28/// Preferred symmetric encryption algorithm.
29const SYMMETRIC_KEY_ALGORITHM: SymmetricKeyAlgorithm = SymmetricKeyAlgorithm::AES128;
30
31/// Create a new key pair.
32///
33/// Both secret and public key consist of signing primary key and encryption subkey
34/// as [described in the Autocrypt standard](https://autocrypt.org/level1.html#openpgp-based-key-data).
35pub(crate) fn create_keypair(addr: EmailAddress) -> Result<SignedSecretKey> {
36    let signing_key_type = PgpKeyType::Ed25519Legacy;
37    let encryption_key_type = PgpKeyType::ECDH(ECCCurve::Curve25519);
38
39    let user_id = format!("<{addr}>");
40    let key_params = SecretKeyParamsBuilder::default()
41        .key_type(signing_key_type)
42        .can_certify(true)
43        .can_sign(true)
44        .feature_seipd_v2(true)
45        .primary_user_id(user_id)
46        .passphrase(None)
47        .preferred_symmetric_algorithms(smallvec![
48            SymmetricKeyAlgorithm::AES256,
49            SymmetricKeyAlgorithm::AES192,
50            SymmetricKeyAlgorithm::AES128,
51        ])
52        .preferred_hash_algorithms(smallvec![
53            HashAlgorithm::Sha256,
54            HashAlgorithm::Sha384,
55            HashAlgorithm::Sha512,
56            HashAlgorithm::Sha224,
57        ])
58        .preferred_compression_algorithms(smallvec![
59            CompressionAlgorithm::ZLIB,
60            CompressionAlgorithm::ZIP,
61        ])
62        .subkey(
63            SubkeyParamsBuilder::default()
64                .key_type(encryption_key_type)
65                .can_encrypt(EncryptionCaps::All)
66                .passphrase(None)
67                .build()
68                .context("failed to build subkey parameters")?,
69        )
70        .build()
71        .context("failed to build key parameters")?;
72
73    let mut rng = thread_rng();
74    let secret_key = key_params
75        .generate(&mut rng)
76        .context("Failed to generate the key")?;
77    secret_key
78        .verify_bindings()
79        .context("Invalid secret key generated")?;
80
81    Ok(secret_key)
82}
83
84/// Selects a subkey of the public key to use for encryption.
85///
86/// Returns `None` if the public key cannot be used for encryption.
87///
88/// TODO: take key flags and expiration dates into account
89fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
90    key.public_subkeys
91        .iter()
92        .find(|subkey| subkey.algorithm().can_encrypt())
93}
94
95/// Version of SEIPD packet to use.
96///
97/// See
98/// <https://www.rfc-editor.org/rfc/rfc9580#name-avoiding-ciphertext-malleab>
99/// for the discussion on when v2 SEIPD should be used.
100#[derive(Debug)]
101pub enum SeipdVersion {
102    /// Use v1 SEIPD, for compatibility.
103    V1,
104
105    /// Use v2 SEIPD when we know that v2 SEIPD is supported.
106    V2,
107}
108
109/// Encrypts `plain` text using `public_keys_for_encryption`
110/// and signs it using `private_key_for_signing`.
111#[expect(clippy::arithmetic_side_effects)]
112pub async fn pk_encrypt(
113    plain: Vec<u8>,
114    public_keys_for_encryption: Vec<SignedPublicKey>,
115    private_key_for_signing: SignedSecretKey,
116    compress: bool,
117    seipd_version: SeipdVersion,
118) -> Result<String> {
119    Handle::current()
120        .spawn_blocking(move || {
121            let mut rng = thread_rng();
122
123            let pkeys = public_keys_for_encryption
124                .iter()
125                .filter_map(select_pk_for_encryption);
126            let subpkts = {
127                let mut hashed = Vec::with_capacity(1 + public_keys_for_encryption.len() + 1);
128                hashed.push(Subpacket::critical(SubpacketData::SignatureCreationTime(
129                    pgp::types::Timestamp::now(),
130                ))?);
131                // Test "elena" uses old Delta Chat.
132                let skip = private_key_for_signing.dc_fingerprint().hex()
133                    == "B86586B6DEF437D674BFAFC02A6B2EBC633B9E82";
134                for key in &public_keys_for_encryption {
135                    if skip {
136                        break;
137                    }
138                    let data = SubpacketData::IntendedRecipientFingerprint(key.fingerprint());
139                    let subpkt = match private_key_for_signing.version() < KeyVersion::V6 {
140                        true => Subpacket::regular(data)?,
141                        false => Subpacket::critical(data)?,
142                    };
143                    hashed.push(subpkt);
144                }
145                hashed.push(Subpacket::regular(SubpacketData::IssuerFingerprint(
146                    private_key_for_signing.fingerprint(),
147                ))?);
148                let mut unhashed = vec![];
149                if private_key_for_signing.version() <= KeyVersion::V4 {
150                    unhashed.push(Subpacket::regular(SubpacketData::IssuerKeyId(
151                        private_key_for_signing.legacy_key_id(),
152                    ))?);
153                }
154                SubpacketConfig::UserDefined { hashed, unhashed }
155            };
156
157            let msg = MessageBuilder::from_bytes("", plain);
158            let encoded_msg = match seipd_version {
159                SeipdVersion::V1 => {
160                    let mut msg = msg.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
161
162                    for pkey in pkeys {
163                        msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
164                    }
165
166                    let hash_algorithm = private_key_for_signing.hash_alg();
167                    msg.sign_with_subpackets(
168                        &*private_key_for_signing,
169                        Password::empty(),
170                        hash_algorithm,
171                        subpkts,
172                    );
173                    if compress {
174                        msg.compression(CompressionAlgorithm::ZLIB);
175                    }
176
177                    msg.to_armored_string(&mut rng, Default::default())?
178                }
179                SeipdVersion::V2 => {
180                    let mut msg = msg.seipd_v2(
181                        &mut rng,
182                        SYMMETRIC_KEY_ALGORITHM,
183                        AeadAlgorithm::Ocb,
184                        ChunkSize::C8KiB,
185                    );
186
187                    for pkey in pkeys {
188                        msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
189                    }
190
191                    let hash_algorithm = private_key_for_signing.hash_alg();
192                    msg.sign_with_subpackets(
193                        &*private_key_for_signing,
194                        Password::empty(),
195                        hash_algorithm,
196                        subpkts,
197                    );
198                    if compress {
199                        msg.compression(CompressionAlgorithm::ZLIB);
200                    }
201
202                    msg.to_armored_string(&mut rng, Default::default())?
203                }
204            };
205
206            Ok(encoded_msg)
207        })
208        .await?
209}
210
211/// Produces a detached signature for `plain` text using `private_key_for_signing`.
212pub fn pk_calc_signature(
213    plain: Vec<u8>,
214    private_key_for_signing: &SignedSecretKey,
215) -> Result<String> {
216    let rng = thread_rng();
217
218    let mut config = SignatureConfig::from_key(
219        rng,
220        &private_key_for_signing.primary_key,
221        SignatureType::Binary,
222    )?;
223
224    config.hashed_subpackets = vec![
225        Subpacket::regular(SubpacketData::IssuerFingerprint(
226            private_key_for_signing.fingerprint(),
227        ))?,
228        Subpacket::critical(SubpacketData::SignatureCreationTime(
229            pgp::types::Timestamp::now(),
230        ))?,
231    ];
232    config.unhashed_subpackets = vec![];
233    if private_key_for_signing.version() <= KeyVersion::V4 {
234        config
235            .unhashed_subpackets
236            .push(Subpacket::regular(SubpacketData::IssuerKeyId(
237                private_key_for_signing.legacy_key_id(),
238            ))?);
239    }
240
241    let signature = config.sign(
242        &private_key_for_signing.primary_key,
243        &Password::empty(),
244        plain.as_slice(),
245    )?;
246
247    let sig = DetachedSignature::new(signature);
248
249    Ok(sig.to_armored_string(ArmorOptions::default())?)
250}
251
252/// Returns fingerprints
253/// of all keys from the `public_keys_for_validation` keyring that
254/// have valid signatures in `msg` and corresponding intended recipient fingerprints
255/// (<https://www.rfc-editor.org/rfc/rfc9580.html#name-intended-recipient-fingerpr>) if any.
256///
257/// If the message is wrongly signed, returns an empty map.
258pub fn valid_signature_fingerprints(
259    msg: &pgp::composed::Message,
260    public_keys_for_validation: &[SignedPublicKey],
261) -> HashMap<Fingerprint, Vec<Fingerprint>> {
262    let mut ret_signature_fingerprints = HashMap::new();
263    if msg.is_signed() {
264        for pkey in public_keys_for_validation {
265            if let Ok(signature) = msg.verify(&pkey.primary_key) {
266                let fp = pkey.dc_fingerprint();
267                let mut recipient_fps = Vec::new();
268                if let Some(cfg) = signature.config() {
269                    for subpkt in &cfg.hashed_subpackets {
270                        if let SubpacketData::IntendedRecipientFingerprint(fp) = &subpkt.data {
271                            recipient_fps.push(fp.clone().into());
272                        }
273                    }
274                }
275                ret_signature_fingerprints.insert(fp, recipient_fps);
276            }
277        }
278    }
279    ret_signature_fingerprints
280}
281
282/// Validates detached signature.
283pub fn pk_validate(
284    content: &[u8],
285    signature: &[u8],
286    public_keys_for_validation: &[SignedPublicKey],
287) -> Result<HashSet<Fingerprint>> {
288    let mut ret: HashSet<Fingerprint> = Default::default();
289
290    let detached_signature = DetachedSignature::from_armor_single(Cursor::new(signature))?.0;
291
292    for pkey in public_keys_for_validation {
293        if detached_signature.verify(pkey, content).is_ok() {
294            let fp = pkey.dc_fingerprint();
295            ret.insert(fp);
296        }
297    }
298    Ok(ret)
299}
300
301/// Symmetrically encrypt the message.
302/// This is used for broadcast channels and for version 2 of the Securejoin protocol.
303/// `shared secret` is the secret that will be used for symmetric encryption.
304pub async fn symm_encrypt_message(
305    plain: Vec<u8>,
306    private_key_for_signing: Option<SignedSecretKey>,
307    shared_secret: &str,
308    compress: bool,
309) -> Result<String> {
310    let shared_secret = Password::from(shared_secret.to_string());
311
312    tokio::task::spawn_blocking(move || {
313        let msg = MessageBuilder::from_bytes("", plain);
314        let mut rng = thread_rng();
315        let mut salt = [0u8; 8];
316        rng.fill(&mut salt[..]);
317        let s2k = StringToKey::Salted {
318            hash_alg: HashAlgorithm::default(),
319            salt,
320        };
321        let mut msg = msg.seipd_v2(
322            &mut rng,
323            SYMMETRIC_KEY_ALGORITHM,
324            AeadAlgorithm::Ocb,
325            ChunkSize::C8KiB,
326        );
327        msg.encrypt_with_password(&mut rng, s2k, &shared_secret)?;
328
329        if let Some(private_key_for_signing) = private_key_for_signing.as_deref() {
330            let hash_algorithm = private_key_for_signing.hash_alg();
331            msg.sign(private_key_for_signing, Password::empty(), hash_algorithm);
332        }
333        if compress {
334            msg.compression(CompressionAlgorithm::ZLIB);
335        }
336
337        let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
338
339        Ok(encoded_msg)
340    })
341    .await?
342}
343
344/// Merges and minimizes OpenPGP certificates.
345///
346/// Keeps at most one direct key signature and
347/// at most one User ID with exactly one signature.
348///
349/// See <https://openpgp.dev/book/adv/certificates.html#merging>
350/// and <https://openpgp.dev/book/adv/certificates.html#certificate-minimization>.
351///
352/// `new_certificate` does not necessarily contain newer data.
353/// It may come not directly from the key owner,
354/// e.g. via protected Autocrypt header or protected attachment
355/// in a signed message, but from Autocrypt-Gossip header or a vCard.
356/// Gossiped key may be older than the one we have
357/// or even have some packets maliciously dropped
358/// (for example, all encryption subkeys dropped)
359/// or restored from some older version of the certificate.
360pub fn merge_openpgp_certificates(
361    old_certificate: SignedPublicKey,
362    new_certificate: SignedPublicKey,
363) -> Result<SignedPublicKey> {
364    old_certificate
365        .verify_bindings()
366        .context("First key cannot be verified")?;
367    new_certificate
368        .verify_bindings()
369        .context("Second key cannot be verified")?;
370
371    // Decompose certificates.
372    let SignedPublicKey {
373        primary_key: old_primary_key,
374        details: old_details,
375        public_subkeys: old_public_subkeys,
376    } = old_certificate;
377    let SignedPublicKey {
378        primary_key: new_primary_key,
379        details: new_details,
380        public_subkeys: _new_public_subkeys,
381    } = new_certificate;
382
383    // Public keys may be serialized differently, e.g. using old and new packet type,
384    // so we compare imprints instead of comparing the keys
385    // directly with `old_primary_key == new_primary_key`.
386    // Imprints, like fingerprints, are calculated over normalized packets.
387    // On error we print fingerprints as this is what is used in the database
388    // and what most tools show.
389    let old_imprint = old_primary_key.imprint::<Sha256>()?;
390    let new_imprint = new_primary_key.imprint::<Sha256>()?;
391    ensure!(
392        old_imprint == new_imprint,
393        "Cannot merge certificates with different primary keys {} and {}",
394        old_primary_key.fingerprint(),
395        new_primary_key.fingerprint()
396    );
397
398    // Decompose old and the new key details.
399    //
400    // Revocation signatures are currently ignored so we do not store them.
401    //
402    // User attributes are thrown away on purpose,
403    // the only defined in RFC 9580 attribute is the Image Attribute
404    // (<https://www.rfc-editor.org/rfc/rfc9580.html#section-5.12.1>
405    // which we do not use and do not want to gossip.
406    let SignedKeyDetails {
407        revocation_signatures: _old_revocation_signatures,
408        direct_signatures: old_direct_signatures,
409        users: old_users,
410        user_attributes: _old_user_attributes,
411    } = old_details;
412    let SignedKeyDetails {
413        revocation_signatures: _new_revocation_signatures,
414        direct_signatures: new_direct_signatures,
415        users: new_users,
416        user_attributes: _new_user_attributes,
417    } = new_details;
418
419    // Select at most one direct key signature, the newest one.
420    let best_direct_key_signature: Option<Signature> = old_direct_signatures
421        .into_iter()
422        .chain(new_direct_signatures)
423        .filter(|x: &Signature| x.verify_key(&old_primary_key).is_ok())
424        .max_by_key(|x: &Signature|
425            // Converting to seconds because `Ord` is not derived for `Timestamp`:
426            // <https://github.com/rpgp/rpgp/issues/737>
427            x.created().map_or(0, |ts| ts.as_secs()));
428    let direct_signatures: Vec<Signature> = best_direct_key_signature.into_iter().collect();
429
430    // Select at most one User ID.
431    //
432    // We prefer User IDs marked as primary,
433    // but will select non-primary otherwise
434    // because sometimes keys have no primary User ID,
435    // such as Alice's key in `test-data/key/alice-secret.asc`.
436    let best_user: Option<SignedUser> = old_users
437        .into_iter()
438        .chain(new_users.clone())
439        .filter_map(|SignedUser { id, signatures }| {
440            // Select the best signature for each User ID.
441            // If User ID has no valid signatures, it is filtered out.
442            let best_user_signature: Option<Signature> = signatures
443                .into_iter()
444                .filter(|signature: &Signature| {
445                    signature
446                        .verify_certification(&old_primary_key, pgp::types::Tag::UserId, &id)
447                        .is_ok()
448                })
449                .max_by_key(|signature: &Signature| {
450                    signature.created().map_or(0, |ts| ts.as_secs())
451                });
452            best_user_signature.map(|signature| (id, signature))
453        })
454        .max_by_key(|(_id, signature)| signature.created().map_or(0, |ts| ts.as_secs()))
455        .map(|(id, signature)| SignedUser {
456            id,
457            signatures: vec![signature],
458        });
459    let users: Vec<SignedUser> = best_user.into_iter().collect();
460
461    let public_subkeys = old_public_subkeys;
462
463    Ok(SignedPublicKey {
464        primary_key: old_primary_key,
465        details: SignedKeyDetails {
466            revocation_signatures: vec![],
467            direct_signatures,
468            users,
469            user_attributes: vec![],
470        },
471        public_subkeys,
472    })
473}
474
475/// Returns relays addresses from the public key signature.
476///
477/// Not more than 3 relays are returned for each key.
478pub(crate) fn addresses_from_public_key(public_key: &SignedPublicKey) -> Option<Vec<String>> {
479    for signature in &public_key.details.direct_signatures {
480        // The signature should be verified already when importing the key,
481        // but we double-check here.
482        let signature_is_valid = signature.verify_key(&public_key.primary_key).is_ok();
483        debug_assert!(signature_is_valid);
484        if signature_is_valid {
485            for notation in signature.notations() {
486                if notation.name == "relays@chatmail.at"
487                    && let Ok(value) = str::from_utf8(&notation.value)
488                {
489                    return Some(
490                        value
491                            .split(",")
492                            .map(|s| s.to_string())
493                            .filter(|s| may_be_valid_addr(s))
494                            .take(3)
495                            .collect(),
496                    );
497                }
498            }
499        }
500    }
501    None
502}
503
504#[cfg(test)]
505mod tests {
506    use std::sync::LazyLock;
507    use tokio::sync::OnceCell;
508
509    use super::*;
510    use crate::{
511        config::Config,
512        decrypt,
513        key::{load_self_public_key, self_fingerprint, store_self_keypair},
514        mimefactory::{render_outer_message, wrap_encrypted_part},
515        test_utils::{TestContext, TestContextManager, alice_keypair, bob_keypair},
516        token,
517    };
518    use pgp::composed::{Esk, Message};
519    use pgp::packet::PublicKeyEncryptedSessionKey;
520
521    async fn decrypt_bytes(
522        bytes: Vec<u8>,
523        private_keys_for_decryption: &[SignedSecretKey],
524        auth_tokens_for_decryption: &[String],
525    ) -> Result<pgp::composed::Message<'static>> {
526        let t = &TestContext::new().await;
527        t.set_config(Config::ConfiguredAddr, Some("alice@example.org"))
528            .await
529            .expect("Failed to configure address");
530
531        for secret in auth_tokens_for_decryption {
532            token::save(t, token::Namespace::Auth, None, secret, 0).await?;
533        }
534        let [secret_key] = private_keys_for_decryption else {
535            panic!("Only one private key is allowed anymore");
536        };
537        store_self_keypair(t, secret_key).await?;
538
539        let mime_message = wrap_encrypted_part(bytes.try_into().unwrap());
540        let rendered = render_outer_message(vec![], mime_message);
541        let parsed = mailparse::parse_mail(rendered.as_bytes())?;
542        let (decrypted, _fp) = decrypt::decrypt(t, &parsed).await?.unwrap();
543        Ok(decrypted)
544    }
545
546    async fn pk_decrypt_and_validate<'a>(
547        ctext: &'a [u8],
548        private_keys_for_decryption: &'a [SignedSecretKey],
549        public_keys_for_validation: &[SignedPublicKey],
550    ) -> Result<(
551        pgp::composed::Message<'static>,
552        HashMap<Fingerprint, Vec<Fingerprint>>,
553        Vec<u8>,
554    )> {
555        let mut msg = decrypt_bytes(ctext.to_vec(), private_keys_for_decryption, &[]).await?;
556        let content = msg.as_data_vec()?;
557        let ret_signature_fingerprints =
558            valid_signature_fingerprints(&msg, public_keys_for_validation);
559
560        Ok((msg, ret_signature_fingerprints, content))
561    }
562
563    #[test]
564    fn test_create_keypair() {
565        let keypair0 = create_keypair(EmailAddress::new("foo@bar.de").unwrap()).unwrap();
566        let keypair1 = create_keypair(EmailAddress::new("two@zwo.de").unwrap()).unwrap();
567        assert_ne!(keypair0.public_key(), keypair1.public_key());
568    }
569
570    /// [SignedSecretKey] and [SignedPublicKey] objects
571    /// to use in tests.
572    struct TestKeys {
573        alice_secret: SignedSecretKey,
574        alice_public: SignedPublicKey,
575        bob_secret: SignedSecretKey,
576        bob_public: SignedPublicKey,
577    }
578
579    impl TestKeys {
580        fn new() -> TestKeys {
581            let alice = alice_keypair();
582            let bob = bob_keypair();
583            TestKeys {
584                alice_secret: alice.clone(),
585                alice_public: alice.to_public_key(),
586                bob_secret: bob.clone(),
587                bob_public: bob.to_public_key(),
588            }
589        }
590    }
591
592    /// The original text of [CTEXT_SIGNED]
593    static CLEARTEXT: &[u8] = b"This is a test";
594
595    /// Initialised [TestKeys] for tests.
596    static KEYS: LazyLock<TestKeys> = LazyLock::new(TestKeys::new);
597
598    static CTEXT_SIGNED: OnceCell<String> = OnceCell::const_new();
599
600    /// A ciphertext encrypted to Alice & Bob, signed by Alice.
601    async fn ctext_signed() -> &'static String {
602        CTEXT_SIGNED
603            .get_or_init(|| async {
604                let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
605                let compress = true;
606
607                pk_encrypt(
608                    CLEARTEXT.to_vec(),
609                    keyring,
610                    KEYS.alice_secret.clone(),
611                    compress,
612                    SeipdVersion::V2,
613                )
614                .await
615                .unwrap()
616            })
617            .await
618    }
619
620    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
621    async fn test_encrypt_signed() {
622        assert!(!ctext_signed().await.is_empty());
623        assert!(
624            ctext_signed()
625                .await
626                .starts_with("-----BEGIN PGP MESSAGE-----")
627        );
628    }
629
630    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
631    async fn test_decrypt_signed() {
632        // Check decrypting as Alice
633        let decrypt_keyring = vec![KEYS.alice_secret.clone()];
634        let sig_check_keyring = vec![KEYS.alice_public.clone()];
635        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
636            ctext_signed().await.as_bytes(),
637            &decrypt_keyring,
638            &sig_check_keyring,
639        )
640        .await
641        .unwrap();
642        assert_eq!(content, CLEARTEXT);
643        assert_eq!(valid_signatures.len(), 1);
644        for recipient_fps in valid_signatures.values() {
645            assert_eq!(recipient_fps.len(), 2);
646        }
647
648        // Check decrypting as Bob
649        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
650        let sig_check_keyring = vec![KEYS.alice_public.clone()];
651        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
652            ctext_signed().await.as_bytes(),
653            &decrypt_keyring,
654            &sig_check_keyring,
655        )
656        .await
657        .unwrap();
658        assert_eq!(content, CLEARTEXT);
659        assert_eq!(valid_signatures.len(), 1);
660        for recipient_fps in valid_signatures.values() {
661            assert_eq!(recipient_fps.len(), 2);
662        }
663    }
664
665    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
666    async fn test_decrypt_no_sig_check() {
667        let keyring = vec![KEYS.alice_secret.clone()];
668        let (_msg, valid_signatures, content) =
669            pk_decrypt_and_validate(ctext_signed().await.as_bytes(), &keyring, &[])
670                .await
671                .unwrap();
672        assert_eq!(content, CLEARTEXT);
673        assert_eq!(valid_signatures.len(), 0);
674    }
675
676    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
677    async fn test_decrypt_signed_no_key() {
678        // The validation does not have the public key of the signer.
679        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
680        let sig_check_keyring = vec![KEYS.bob_public.clone()];
681        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
682            ctext_signed().await.as_bytes(),
683            &decrypt_keyring,
684            &sig_check_keyring,
685        )
686        .await
687        .unwrap();
688        assert_eq!(content, CLEARTEXT);
689        assert_eq!(valid_signatures.len(), 0);
690    }
691
692    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
693    async fn test_decrypt_unsigned() {
694        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
695        let ctext_unsigned = include_bytes!("../test-data/message/ctext_unsigned.asc");
696        let (_msg, valid_signatures, content) =
697            pk_decrypt_and_validate(ctext_unsigned, &decrypt_keyring, &[])
698                .await
699                .unwrap();
700        assert_eq!(content, CLEARTEXT);
701        assert_eq!(valid_signatures.len(), 0);
702    }
703
704    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
705    async fn test_dont_decrypt_expensive_message_happy_path() -> Result<()> {
706        let s2k = StringToKey::Salted {
707            hash_alg: HashAlgorithm::default(),
708            salt: [1; 8],
709        };
710
711        test_dont_decrypt_expensive_message_ex(s2k, false, None).await
712    }
713
714    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
715    async fn test_dont_decrypt_expensive_message_bad_s2k() -> Result<()> {
716        let s2k = StringToKey::new_default(&mut thread_rng()); // Default is IteratedAndSalted
717
718        test_dont_decrypt_expensive_message_ex(s2k, false, Some("unsupported string2key algorithm"))
719            .await
720    }
721
722    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
723    async fn test_dont_decrypt_expensive_message_multiple_secrets() -> Result<()> {
724        let s2k = StringToKey::Salted {
725            hash_alg: HashAlgorithm::default(),
726            salt: [1; 8],
727        };
728
729        // This error message is actually not great,
730        // but grepping for it will lead to the correct code
731        test_dont_decrypt_expensive_message_ex(s2k, true, Some("decrypt_with_keys: missing key"))
732            .await
733    }
734
735    /// Test that we don't try to decrypt a message
736    /// that is symmetrically encrypted
737    /// with an expensive string2key algorithm
738    /// or multiple shared secrets.
739    /// This is to prevent possible DOS attacks on the app.
740    async fn test_dont_decrypt_expensive_message_ex(
741        s2k: StringToKey,
742        encrypt_twice: bool,
743        expected_error_msg: Option<&str>,
744    ) -> Result<()> {
745        let mut tcm = TestContextManager::new();
746        let bob = &tcm.bob().await;
747
748        let plain = Vec::from(b"this is the secret message");
749        let shared_secret = "shared secret";
750        let bob_fp = self_fingerprint(bob).await?;
751
752        let shared_secret_pw = Password::from(format!("securejoin/{bob_fp}/{shared_secret}"));
753        let msg = MessageBuilder::from_bytes("", plain);
754        let mut rng = thread_rng();
755
756        let mut msg = msg.seipd_v2(
757            &mut rng,
758            SymmetricKeyAlgorithm::AES128,
759            AeadAlgorithm::Ocb,
760            ChunkSize::C8KiB,
761        );
762        msg.encrypt_with_password(&mut rng, s2k.clone(), &shared_secret_pw)?;
763        if encrypt_twice {
764            msg.encrypt_with_password(&mut rng, s2k, &shared_secret_pw)?;
765        }
766
767        let ctext = msg.to_armored_string(&mut rng, Default::default())?;
768
769        // Trying to decrypt it should fail with a helpful error message:
770
771        let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
772        let res = decrypt_bytes(
773            ctext.into(),
774            &bob_private_keyring,
775            &[shared_secret.to_string()],
776        )
777        .await;
778
779        if let Some(expected_error_msg) = expected_error_msg {
780            assert_eq!(format!("{:#}", res.unwrap_err()), expected_error_msg);
781        } else {
782            res.unwrap();
783        }
784
785        Ok(())
786    }
787
788    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
789    async fn test_decryption_error_msg() -> Result<()> {
790        let mut tcm = TestContextManager::new();
791        let alice = &tcm.alice().await;
792        let bob = &tcm.bob().await;
793
794        let plain = Vec::from(b"this is the secret message");
795        let pk_for_encryption = load_self_public_key(alice).await?;
796
797        // Encrypt a message, but only to self, not to Bob:
798        let compress = true;
799        let ctext = pk_encrypt(
800            plain,
801            vec![pk_for_encryption],
802            KEYS.alice_secret.clone(),
803            compress,
804            SeipdVersion::V2,
805        )
806        .await?;
807
808        // Trying to decrypt it should fail with an OK error message:
809        let bob_private_keyring = crate::key::load_self_secret_keyring(bob).await?;
810        let error = decrypt_bytes(ctext.into(), &bob_private_keyring, &[])
811            .await
812            .unwrap_err();
813
814        assert_eq!(format!("{error:#}"), "decrypt_with_keys: missing key");
815
816        Ok(())
817    }
818
819    /// Tests that recipient key IDs and fingerprints
820    /// are omitted or replaced with wildcards.
821    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
822    async fn test_anonymous_recipients() -> Result<()> {
823        let ctext = ctext_signed().await.as_bytes();
824        let cursor = Cursor::new(ctext);
825        let (msg, _headers) = Message::from_armor(cursor)?;
826
827        let Message::Encrypted { esk, .. } = msg else {
828            unreachable!();
829        };
830
831        for encrypted_session_key in esk {
832            let Esk::PublicKeyEncryptedSessionKey(pkesk) = encrypted_session_key else {
833                unreachable!()
834            };
835
836            match pkesk {
837                PublicKeyEncryptedSessionKey::V3 { id, .. } => {
838                    assert!(id.is_wildcard());
839                }
840                PublicKeyEncryptedSessionKey::V6 { fingerprint, .. } => {
841                    assert!(fingerprint.is_none());
842                }
843                PublicKeyEncryptedSessionKey::Other { .. } => unreachable!(),
844            }
845        }
846        Ok(())
847    }
848
849    #[test]
850    fn test_merge_openpgp_certificates() {
851        let alice = alice_keypair().to_public_key();
852        let bob = bob_keypair().to_public_key();
853
854        // Merging certificate with itself does not change it.
855        assert_eq!(
856            merge_openpgp_certificates(alice.clone(), alice.clone()).unwrap(),
857            alice
858        );
859        assert_eq!(
860            merge_openpgp_certificates(bob.clone(), bob.clone()).unwrap(),
861            bob
862        );
863
864        // Cannot merge certificates with different primary key.
865        assert!(merge_openpgp_certificates(alice.clone(), bob.clone()).is_err());
866        assert!(merge_openpgp_certificates(bob.clone(), alice.clone()).is_err());
867    }
868}