deltachat/
pgp.rs

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