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