deltachat/
pgp.rs

1//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
2
3use std::collections::{BTreeMap, HashSet};
4use std::io::{BufRead, Cursor};
5
6use anyhow::{Context as _, Result};
7use chrono::SubsecRound;
8use deltachat_contact_tools::EmailAddress;
9use pgp::armor::BlockType;
10use pgp::composed::{
11    ArmorOptions, DecryptionOptions, Deserializable, DetachedSignature, KeyType as PgpKeyType,
12    Message, MessageBuilder, SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey,
13    SignedSecretKey, SubkeyParamsBuilder, TheRing,
14};
15use pgp::crypto::ecc_curve::ECCCurve;
16use pgp::crypto::hash::HashAlgorithm;
17use pgp::crypto::sym::SymmetricKeyAlgorithm;
18use pgp::packet::{SignatureConfig, SignatureType, Subpacket, SubpacketData};
19use pgp::types::{CompressionAlgorithm, KeyDetails, Password, PublicKeyTrait, StringToKey};
20use rand::thread_rng;
21use tokio::runtime::Handle;
22
23use crate::key::{DcKey, Fingerprint};
24
25#[cfg(test)]
26pub(crate) const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
27
28pub const HEADER_SETUPCODE: &str = "passphrase-begin";
29
30/// Preferred symmetric encryption algorithm.
31const SYMMETRIC_KEY_ALGORITHM: SymmetricKeyAlgorithm = SymmetricKeyAlgorithm::AES128;
32
33/// Preferred cryptographic hash.
34const HASH_ALGORITHM: HashAlgorithm = HashAlgorithm::Sha256;
35
36/// Split data from PGP Armored Data as defined in <https://tools.ietf.org/html/rfc4880#section-6.2>.
37///
38/// Returns (type, headers, base64 encoded body).
39pub fn split_armored_data(buf: &[u8]) -> Result<(BlockType, BTreeMap<String, String>, Vec<u8>)> {
40    use std::io::Read;
41
42    let cursor = Cursor::new(buf);
43    let mut dearmor = pgp::armor::Dearmor::new(cursor);
44
45    let mut bytes = Vec::with_capacity(buf.len());
46
47    dearmor.read_to_end(&mut bytes)?;
48    let typ = dearmor.typ.context("failed to parse type")?;
49
50    // normalize headers
51    let headers = dearmor
52        .headers
53        .into_iter()
54        .map(|(key, values)| {
55            (
56                key.trim().to_lowercase(),
57                values
58                    .last()
59                    .map_or_else(String::new, |s| s.trim().to_string()),
60            )
61        })
62        .collect();
63
64    Ok((typ, headers, bytes))
65}
66
67/// A PGP keypair.
68///
69/// This has it's own struct to be able to keep the public and secret
70/// keys together as they are one unit.
71#[derive(Debug, Clone, Eq, PartialEq)]
72pub struct KeyPair {
73    /// Public key.
74    pub public: SignedPublicKey,
75
76    /// Secret key.
77    pub secret: SignedSecretKey,
78}
79
80impl KeyPair {
81    /// Creates new keypair from a secret key.
82    ///
83    /// Public key is split off the secret key.
84    pub fn new(secret: SignedSecretKey) -> Result<Self> {
85        use crate::key::DcSecretKey;
86
87        let public = secret.split_public_key()?;
88        Ok(Self { public, secret })
89    }
90}
91
92/// Create a new key pair.
93///
94/// Both secret and public key consist of signing primary key and encryption subkey
95/// as [described in the Autocrypt standard](https://autocrypt.org/level1.html#openpgp-based-key-data).
96pub(crate) fn create_keypair(addr: EmailAddress) -> Result<KeyPair> {
97    let signing_key_type = PgpKeyType::Ed25519Legacy;
98    let encryption_key_type = PgpKeyType::ECDH(ECCCurve::Curve25519);
99
100    let user_id = format!("<{addr}>");
101    let key_params = SecretKeyParamsBuilder::default()
102        .key_type(signing_key_type)
103        .can_certify(true)
104        .can_sign(true)
105        .primary_user_id(user_id)
106        .passphrase(None)
107        .preferred_symmetric_algorithms(smallvec![
108            SymmetricKeyAlgorithm::AES256,
109            SymmetricKeyAlgorithm::AES192,
110            SymmetricKeyAlgorithm::AES128,
111        ])
112        .preferred_hash_algorithms(smallvec![
113            HashAlgorithm::Sha256,
114            HashAlgorithm::Sha384,
115            HashAlgorithm::Sha512,
116            HashAlgorithm::Sha224,
117        ])
118        .preferred_compression_algorithms(smallvec![
119            CompressionAlgorithm::ZLIB,
120            CompressionAlgorithm::ZIP,
121        ])
122        .subkey(
123            SubkeyParamsBuilder::default()
124                .key_type(encryption_key_type)
125                .can_encrypt(true)
126                .passphrase(None)
127                .build()
128                .context("failed to build subkey parameters")?,
129        )
130        .build()
131        .context("failed to build key parameters")?;
132
133    let mut rng = thread_rng();
134    let secret_key = key_params
135        .generate(&mut rng)
136        .context("failed to generate the key")?
137        .sign(&mut rng, &Password::empty())
138        .context("failed to sign secret key")?;
139    secret_key
140        .verify()
141        .context("invalid secret key generated")?;
142
143    let key_pair = KeyPair::new(secret_key)?;
144    key_pair
145        .public
146        .verify()
147        .context("invalid public key generated")?;
148    Ok(key_pair)
149}
150
151/// Selects a subkey of the public key to use for encryption.
152///
153/// Returns `None` if the public key cannot be used for encryption.
154///
155/// TODO: take key flags and expiration dates into account
156fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
157    key.public_subkeys
158        .iter()
159        .find(|subkey| subkey.is_encryption_key())
160}
161
162/// Encrypts `plain` text using `public_keys_for_encryption`
163/// and signs it using `private_key_for_signing`.
164pub async fn pk_encrypt(
165    plain: Vec<u8>,
166    public_keys_for_encryption: Vec<SignedPublicKey>,
167    private_key_for_signing: Option<SignedSecretKey>,
168    compress: bool,
169) -> Result<String> {
170    Handle::current()
171        .spawn_blocking(move || {
172            let mut rng = thread_rng();
173
174            let pkeys = public_keys_for_encryption
175                .iter()
176                .filter_map(select_pk_for_encryption);
177
178            let msg = MessageBuilder::from_bytes("", plain);
179            let mut msg = msg.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
180            for pkey in pkeys {
181                msg.encrypt_to_key_anonymous(&mut rng, &pkey)?;
182            }
183
184            if let Some(ref skey) = private_key_for_signing {
185                msg.sign(&**skey, Password::empty(), HASH_ALGORITHM);
186                if compress {
187                    msg.compression(CompressionAlgorithm::ZLIB);
188                }
189            }
190
191            let encoded_msg = msg.to_armored_string(&mut rng, Default::default())?;
192
193            Ok(encoded_msg)
194        })
195        .await?
196}
197
198/// Produces a detached signature for `plain` text using `private_key_for_signing`.
199pub fn pk_calc_signature(
200    plain: Vec<u8>,
201    private_key_for_signing: &SignedSecretKey,
202) -> Result<String> {
203    let rng = thread_rng();
204
205    let mut config = SignatureConfig::from_key(
206        rng,
207        &private_key_for_signing.primary_key,
208        SignatureType::Binary,
209    )?;
210
211    config.hashed_subpackets = vec![
212        Subpacket::regular(SubpacketData::IssuerFingerprint(
213            private_key_for_signing.fingerprint(),
214        ))?,
215        Subpacket::critical(SubpacketData::SignatureCreationTime(
216            chrono::Utc::now().trunc_subsecs(0),
217        ))?,
218    ];
219    config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(
220        private_key_for_signing.key_id(),
221    ))?];
222
223    let signature = config.sign(
224        &private_key_for_signing.primary_key,
225        &Password::empty(),
226        plain.as_slice(),
227    )?;
228
229    let sig = DetachedSignature::new(signature);
230
231    Ok(sig.to_armored_string(ArmorOptions::default())?)
232}
233
234/// Decrypts the message with keys from the private key keyring.
235///
236/// Receiver private keys are provided in
237/// `private_keys_for_decryption`.
238pub fn pk_decrypt(
239    ctext: Vec<u8>,
240    private_keys_for_decryption: &[SignedSecretKey],
241) -> Result<pgp::composed::Message<'static>> {
242    let cursor = Cursor::new(ctext);
243    let (msg, _headers) = Message::from_armor(cursor)?;
244
245    let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.iter().collect();
246    let empty_pw = Password::empty();
247
248    let decrypt_options = DecryptionOptions::new();
249    let ring = TheRing {
250        secret_keys: skeys,
251        key_passwords: vec![&empty_pw],
252        message_password: vec![],
253        session_keys: vec![],
254        decrypt_options,
255    };
256    let (msg, ring_result) = msg.decrypt_the_ring(ring, true)?;
257    anyhow::ensure!(
258        !ring_result.secret_keys.is_empty(),
259        "decryption failed, no matching secret keys"
260    );
261
262    // remove one layer of compression
263    let msg = msg.decompress()?;
264
265    Ok(msg)
266}
267
268/// Returns fingerprints
269/// of all keys from the `public_keys_for_validation` keyring that
270/// have valid signatures there.
271///
272/// If the message is wrongly signed, HashSet will be empty.
273pub fn valid_signature_fingerprints(
274    msg: &pgp::composed::Message,
275    public_keys_for_validation: &[SignedPublicKey],
276) -> HashSet<Fingerprint> {
277    let mut ret_signature_fingerprints: HashSet<Fingerprint> = Default::default();
278    if msg.is_signed() {
279        for pkey in public_keys_for_validation {
280            if msg.verify(&pkey.primary_key).is_ok() {
281                let fp = pkey.dc_fingerprint();
282                ret_signature_fingerprints.insert(fp);
283            }
284        }
285    }
286    ret_signature_fingerprints
287}
288
289/// Validates detached signature.
290pub fn pk_validate(
291    content: &[u8],
292    signature: &[u8],
293    public_keys_for_validation: &[SignedPublicKey],
294) -> Result<HashSet<Fingerprint>> {
295    let mut ret: HashSet<Fingerprint> = Default::default();
296
297    let detached_signature = DetachedSignature::from_armor_single(Cursor::new(signature))?.0;
298
299    for pkey in public_keys_for_validation {
300        if detached_signature.verify(pkey, content).is_ok() {
301            let fp = pkey.dc_fingerprint();
302            ret.insert(fp);
303        }
304    }
305    Ok(ret)
306}
307
308/// Symmetric encryption.
309pub async fn symm_encrypt(passphrase: &str, plain: Vec<u8>) -> Result<String> {
310    let passphrase = Password::from(passphrase.to_string());
311
312    tokio::task::spawn_blocking(move || {
313        let mut rng = thread_rng();
314        let s2k = StringToKey::new_default(&mut rng);
315        let builder = MessageBuilder::from_bytes("", plain);
316        let mut builder = builder.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM);
317        builder.encrypt_with_password(s2k, &passphrase)?;
318
319        let encoded_msg = builder.to_armored_string(&mut rng, Default::default())?;
320
321        Ok(encoded_msg)
322    })
323    .await?
324}
325
326/// Symmetric decryption.
327pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>(
328    passphrase: &str,
329    ctext: T,
330) -> Result<Vec<u8>> {
331    let passphrase = passphrase.to_string();
332    tokio::task::spawn_blocking(move || {
333        let (enc_msg, _) = Message::from_armor(ctext)?;
334        let password = Password::from(passphrase);
335
336        let msg = enc_msg.decrypt_with_password(&password)?;
337        let res = msg.decompress()?.as_data_vec()?;
338        Ok(res)
339    })
340    .await?
341}
342
343#[cfg(test)]
344mod tests {
345    use std::sync::LazyLock;
346    use tokio::sync::OnceCell;
347
348    use super::*;
349    use crate::test_utils::{alice_keypair, bob_keypair};
350    use pgp::composed::Esk;
351    use pgp::packet::PublicKeyEncryptedSessionKey;
352
353    fn pk_decrypt_and_validate<'a>(
354        ctext: &'a [u8],
355        private_keys_for_decryption: &'a [SignedSecretKey],
356        public_keys_for_validation: &[SignedPublicKey],
357    ) -> Result<(
358        pgp::composed::Message<'static>,
359        HashSet<Fingerprint>,
360        Vec<u8>,
361    )> {
362        let mut msg = pk_decrypt(ctext.to_vec(), private_keys_for_decryption)?;
363        let content = msg.as_data_vec()?;
364        let ret_signature_fingerprints =
365            valid_signature_fingerprints(&msg, public_keys_for_validation);
366
367        Ok((msg, ret_signature_fingerprints, content))
368    }
369
370    #[test]
371    fn test_split_armored_data_1() {
372        let (typ, _headers, base64) = split_armored_data(
373            b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE-----",
374        )
375        .unwrap();
376
377        assert_eq!(typ, BlockType::Message);
378        assert!(!base64.is_empty());
379        assert_eq!(
380            std::string::String::from_utf8(base64).unwrap(),
381            "hello world"
382        );
383    }
384
385    #[test]
386    fn test_split_armored_data_2() {
387        let (typ, headers, base64) = split_armored_data(
388            b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----"
389        )
390            .unwrap();
391
392        assert_eq!(typ, BlockType::PrivateKey);
393        assert!(!base64.is_empty());
394        assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
395    }
396
397    #[test]
398    fn test_create_keypair() {
399        let keypair0 = create_keypair(EmailAddress::new("foo@bar.de").unwrap()).unwrap();
400        let keypair1 = create_keypair(EmailAddress::new("two@zwo.de").unwrap()).unwrap();
401        assert_ne!(keypair0.public, keypair1.public);
402    }
403
404    /// [SignedSecretKey] and [SignedPublicKey] objects
405    /// to use in tests.
406    struct TestKeys {
407        alice_secret: SignedSecretKey,
408        alice_public: SignedPublicKey,
409        bob_secret: SignedSecretKey,
410        bob_public: SignedPublicKey,
411    }
412
413    impl TestKeys {
414        fn new() -> TestKeys {
415            let alice = alice_keypair();
416            let bob = bob_keypair();
417            TestKeys {
418                alice_secret: alice.secret.clone(),
419                alice_public: alice.public,
420                bob_secret: bob.secret.clone(),
421                bob_public: bob.public,
422            }
423        }
424    }
425
426    /// The original text of [CTEXT_SIGNED]
427    static CLEARTEXT: &[u8] = b"This is a test";
428
429    /// Initialised [TestKeys] for tests.
430    static KEYS: LazyLock<TestKeys> = LazyLock::new(TestKeys::new);
431
432    static CTEXT_SIGNED: OnceCell<String> = OnceCell::const_new();
433    static CTEXT_UNSIGNED: OnceCell<String> = OnceCell::const_new();
434
435    /// A ciphertext encrypted to Alice & Bob, signed by Alice.
436    async fn ctext_signed() -> &'static String {
437        CTEXT_SIGNED
438            .get_or_init(|| async {
439                let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
440                let compress = true;
441
442                pk_encrypt(
443                    CLEARTEXT.to_vec(),
444                    keyring,
445                    Some(KEYS.alice_secret.clone()),
446                    compress,
447                )
448                .await
449                .unwrap()
450            })
451            .await
452    }
453
454    /// A ciphertext encrypted to Alice & Bob, not signed.
455    async fn ctext_unsigned() -> &'static String {
456        CTEXT_UNSIGNED
457            .get_or_init(|| async {
458                let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
459                let compress = true;
460
461                pk_encrypt(CLEARTEXT.to_vec(), keyring, None, compress)
462                    .await
463                    .unwrap()
464            })
465            .await
466    }
467
468    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
469    async fn test_encrypt_signed() {
470        assert!(!ctext_signed().await.is_empty());
471        assert!(
472            ctext_signed()
473                .await
474                .starts_with("-----BEGIN PGP MESSAGE-----")
475        );
476    }
477
478    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
479    async fn test_encrypt_unsigned() {
480        assert!(!ctext_unsigned().await.is_empty());
481        assert!(
482            ctext_unsigned()
483                .await
484                .starts_with("-----BEGIN PGP MESSAGE-----")
485        );
486    }
487
488    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
489    async fn test_decrypt_singed() {
490        // Check decrypting as Alice
491        let decrypt_keyring = vec![KEYS.alice_secret.clone()];
492        let sig_check_keyring = vec![KEYS.alice_public.clone()];
493        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
494            ctext_signed().await.as_bytes(),
495            &decrypt_keyring,
496            &sig_check_keyring,
497        )
498        .unwrap();
499        assert_eq!(content, CLEARTEXT);
500        assert_eq!(valid_signatures.len(), 1);
501
502        // Check decrypting as Bob
503        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
504        let sig_check_keyring = vec![KEYS.alice_public.clone()];
505        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
506            ctext_signed().await.as_bytes(),
507            &decrypt_keyring,
508            &sig_check_keyring,
509        )
510        .unwrap();
511        assert_eq!(content, CLEARTEXT);
512        assert_eq!(valid_signatures.len(), 1);
513    }
514
515    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
516    async fn test_decrypt_no_sig_check() {
517        let keyring = vec![KEYS.alice_secret.clone()];
518        let (_msg, valid_signatures, content) =
519            pk_decrypt_and_validate(ctext_signed().await.as_bytes(), &keyring, &[]).unwrap();
520        assert_eq!(content, CLEARTEXT);
521        assert_eq!(valid_signatures.len(), 0);
522    }
523
524    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
525    async fn test_decrypt_signed_no_key() {
526        // The validation does not have the public key of the signer.
527        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
528        let sig_check_keyring = vec![KEYS.bob_public.clone()];
529        let (_msg, valid_signatures, content) = pk_decrypt_and_validate(
530            ctext_signed().await.as_bytes(),
531            &decrypt_keyring,
532            &sig_check_keyring,
533        )
534        .unwrap();
535        assert_eq!(content, CLEARTEXT);
536        assert_eq!(valid_signatures.len(), 0);
537    }
538
539    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
540    async fn test_decrypt_unsigned() {
541        let decrypt_keyring = vec![KEYS.bob_secret.clone()];
542        let (_msg, valid_signatures, content) =
543            pk_decrypt_and_validate(ctext_unsigned().await.as_bytes(), &decrypt_keyring, &[])
544                .unwrap();
545        assert_eq!(content, CLEARTEXT);
546        assert_eq!(valid_signatures.len(), 0);
547    }
548
549    /// Tests that recipient key IDs and fingerprints
550    /// are omitted or replaced with wildcards.
551    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
552    async fn test_anonymous_recipients() -> Result<()> {
553        let ctext = ctext_signed().await.as_bytes();
554        let cursor = Cursor::new(ctext);
555        let (msg, _headers) = Message::from_armor(cursor)?;
556
557        let Message::Encrypted { esk, .. } = msg else {
558            unreachable!();
559        };
560
561        for encrypted_session_key in esk {
562            let Esk::PublicKeyEncryptedSessionKey(pkesk) = encrypted_session_key else {
563                unreachable!()
564            };
565
566            match pkesk {
567                PublicKeyEncryptedSessionKey::V3 { id, .. } => {
568                    assert!(id.is_wildcard());
569                }
570                PublicKeyEncryptedSessionKey::V6 { fingerprint, .. } => {
571                    assert!(fingerprint.is_none());
572                }
573                PublicKeyEncryptedSessionKey::Other { .. } => unreachable!(),
574            }
575        }
576        Ok(())
577    }
578}