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