deltachat/
pgp.rs

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