Skip to main content

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