//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp). use std::collections::{BTreeMap, HashSet}; use std::io; use std::io::Cursor; use anyhow::{bail, format_err, Context as _, Result}; use pgp::armor::BlockType; use pgp::composed::{ Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey, SignedSecretKey, StandaloneSignature, SubkeyParamsBuilder, }; use pgp::crypto::hash::HashAlgorithm; use pgp::crypto::sym::SymmetricKeyAlgorithm; use pgp::types::{ CompressionAlgorithm, KeyTrait, Mpi, PublicKeyTrait, SecretKeyTrait, StringToKey, }; use rand::{thread_rng, CryptoRng, Rng}; use tokio::runtime::Handle; use crate::constants::KeyGenType; use crate::key::{DcKey, Fingerprint}; use crate::keyring::Keyring; use crate::tools::EmailAddress; #[allow(missing_docs)] pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt"; #[allow(missing_docs)] pub const HEADER_SETUPCODE: &str = "passphrase-begin"; /// A wrapper for rPGP public key types #[derive(Debug)] enum SignedPublicKeyOrSubkey<'a> { Key(&'a SignedPublicKey), Subkey(&'a SignedPublicSubKey), } impl<'a> KeyTrait for SignedPublicKeyOrSubkey<'a> { fn fingerprint(&self) -> Vec { match self { Self::Key(k) => k.fingerprint(), Self::Subkey(k) => k.fingerprint(), } } fn key_id(&self) -> pgp::types::KeyId { match self { Self::Key(k) => k.key_id(), Self::Subkey(k) => k.key_id(), } } fn algorithm(&self) -> pgp::crypto::public_key::PublicKeyAlgorithm { match self { Self::Key(k) => k.algorithm(), Self::Subkey(k) => k.algorithm(), } } } impl<'a> PublicKeyTrait for SignedPublicKeyOrSubkey<'a> { fn verify_signature( &self, hash: HashAlgorithm, data: &[u8], sig: &[Mpi], ) -> pgp::errors::Result<()> { match self { Self::Key(k) => k.verify_signature(hash, data, sig), Self::Subkey(k) => k.verify_signature(hash, data, sig), } } fn encrypt( &self, rng: &mut R, plain: &[u8], ) -> pgp::errors::Result> { match self { Self::Key(k) => k.encrypt(rng, plain), Self::Subkey(k) => k.encrypt(rng, plain), } } fn to_writer_old(&self, writer: &mut impl io::Write) -> pgp::errors::Result<()> { match self { Self::Key(k) => k.to_writer_old(writer), Self::Subkey(k) => k.to_writer_old(writer), } } } /// Split data from PGP Armored Data as defined in . /// /// Returns (type, headers, base64 encoded body). pub fn split_armored_data(buf: &[u8]) -> Result<(BlockType, BTreeMap, Vec)> { use std::io::Read; let cursor = Cursor::new(buf); let mut dearmor = pgp::armor::Dearmor::new(cursor); let mut bytes = Vec::with_capacity(buf.len()); dearmor.read_to_end(&mut bytes)?; let typ = dearmor.typ.context("failed to parse type")?; // normalize headers let headers = dearmor .headers .into_iter() .map(|(key, value)| (key.trim().to_lowercase(), value.trim().to_string())) .collect(); Ok((typ, headers, bytes)) } /// A PGP keypair. /// /// This has it's own struct to be able to keep the public and secret /// keys together as they are one unit. #[derive(Debug, Clone, Eq, PartialEq)] pub struct KeyPair { /// Email address. pub addr: EmailAddress, /// Public key. pub public: SignedPublicKey, /// Secret key. pub secret: SignedSecretKey, } /// Create a new key pair. pub(crate) fn create_keypair(addr: EmailAddress, keygen_type: KeyGenType) -> Result { let (secret_key_type, public_key_type) = match keygen_type { KeyGenType::Rsa2048 => (PgpKeyType::Rsa(2048), PgpKeyType::Rsa(2048)), KeyGenType::Ed25519 | KeyGenType::Default => (PgpKeyType::EdDSA, PgpKeyType::ECDH), }; let user_id = format!("<{addr}>"); let key_params = SecretKeyParamsBuilder::default() .key_type(secret_key_type) .can_create_certificates(true) .can_sign(true) .primary_user_id(user_id) .passphrase(None) .preferred_symmetric_algorithms(smallvec![ SymmetricKeyAlgorithm::AES256, SymmetricKeyAlgorithm::AES192, SymmetricKeyAlgorithm::AES128, ]) .preferred_hash_algorithms(smallvec![ HashAlgorithm::SHA2_256, HashAlgorithm::SHA2_384, HashAlgorithm::SHA2_512, HashAlgorithm::SHA2_224, HashAlgorithm::SHA1, ]) .preferred_compression_algorithms(smallvec![ CompressionAlgorithm::ZLIB, CompressionAlgorithm::ZIP, ]) .subkey( SubkeyParamsBuilder::default() .key_type(public_key_type) .can_encrypt(true) .passphrase(None) .build() .map_err(|e| format_err!("{}", e)) .context("failed to build subkey parameters")?, ) .build() .map_err(|e| format_err!("{}", e)) .context("invalid key params")?; let key = key_params.generate().context("invalid params")?; let private_key = key .sign(|| "".into()) .map_err(|e| format_err!("{}", e)) .context("failed to sign secret key")?; let public_key = private_key.public_key(); let public_key = public_key .sign(&private_key, || "".into()) .map_err(|e| format_err!("{}", e)) .context("failed to sign public key")?; private_key .verify() .map_err(|e| format_err!("{}", e)) .context("invalid private key generated")?; public_key .verify() .map_err(|e| format_err!("{}", e)) .context("invalid public key generated")?; Ok(KeyPair { addr, public: public_key, secret: private_key, }) } /// Select public key or subkey to use for encryption. /// /// First, tries to use subkeys. If none of the subkeys are suitable /// for encryption, tries to use primary key. Returns `None` if the public /// key cannot be used for encryption. /// /// TODO: take key flags and expiration dates into account fn select_pk_for_encryption(key: &SignedPublicKey) -> Option { key.public_subkeys .iter() .find(|subkey| subkey.is_encryption_key()) .map_or_else( || { // No usable subkey found, try primary key if key.is_encryption_key() { Some(SignedPublicKeyOrSubkey::Key(key)) } else { None } }, |subkey| Some(SignedPublicKeyOrSubkey::Subkey(subkey)), ) } /// Encrypts `plain` text using `public_keys_for_encryption` /// and signs it using `private_key_for_signing`. pub async fn pk_encrypt( plain: &[u8], public_keys_for_encryption: Keyring, private_key_for_signing: Option, ) -> Result { let lit_msg = Message::new_literal_bytes("", plain); Handle::current() .spawn_blocking(move || { let pkeys: Vec = public_keys_for_encryption .keys() .iter() .filter_map(select_pk_for_encryption) .collect(); let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect(); let mut rng = thread_rng(); // TODO: measure time let encrypted_msg = if let Some(ref skey) = private_key_for_signing { lit_msg .sign(skey, || "".into(), Default::default()) .and_then(|msg| msg.compress(CompressionAlgorithm::ZLIB)) .and_then(|msg| msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs)) } else { lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs) }; let msg = encrypted_msg?; let encoded_msg = msg.to_armored_string(None)?; Ok(encoded_msg) }) .await? } /// Signs `plain` text using `private_key_for_signing`. pub fn pk_calc_signature( plain: &[u8], private_key_for_signing: &SignedSecretKey, ) -> Result { let msg = Message::new_literal_bytes("", plain).sign( private_key_for_signing, || "".into(), Default::default(), )?; let signature = msg.into_signature().to_armored_string(None)?; Ok(signature) } /// Decrypts the message with keys from the private key keyring. /// /// Receiver private keys are provided in /// `private_keys_for_decryption`. /// /// Returns decrypted message and fingerprints /// of all keys from the `public_keys_for_validation` keyring that /// have valid signatures there. #[allow(clippy::implicit_hasher)] pub fn pk_decrypt( ctext: Vec, private_keys_for_decryption: &Keyring, public_keys_for_validation: &Keyring, ) -> Result<(Vec, HashSet)> { let mut ret_signature_fingerprints: HashSet = Default::default(); let cursor = Cursor::new(ctext); let (msg, _) = Message::from_armor_single(cursor)?; let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.keys().iter().collect(); let (decryptor, _) = msg.decrypt(|| "".into(), &skeys[..])?; let msgs = decryptor.collect::>>()?; if let Some(msg) = msgs.into_iter().next() { // get_content() will decompress the message if needed, // but this avoids decompressing it again to check signatures let msg = msg.decompress()?; let content = match msg.get_content()? { Some(content) => content, None => bail!("The decrypted message is empty"), }; if !public_keys_for_validation.is_empty() { let pkeys = public_keys_for_validation.keys(); let mut fingerprints: Vec = Vec::new(); if let signed_msg @ pgp::composed::Message::Signed { .. } = msg { for pkey in pkeys { if signed_msg.verify(&pkey.primary_key).is_ok() { let fp = DcKey::fingerprint(pkey); fingerprints.push(fp); } } } ret_signature_fingerprints.extend(fingerprints); } Ok((content, ret_signature_fingerprints)) } else { bail!("No valid messages found"); } } /// Validates detached signature. pub fn pk_validate( content: &[u8], signature: &[u8], public_keys_for_validation: &Keyring, ) -> Result> { let mut ret: HashSet = Default::default(); let standalone_signature = StandaloneSignature::from_armor_single(Cursor::new(signature))?.0; let pkeys = public_keys_for_validation.keys(); // Remove trailing CRLF before the delimiter. // According to RFC 3156 it is considered to be part of the MIME delimiter for the purpose of // OpenPGP signature calculation. let content = content .get(..content.len().saturating_sub(2)) .context("index is out of range")?; for pkey in pkeys { if standalone_signature.verify(pkey, content).is_ok() { let fp = DcKey::fingerprint(pkey); ret.insert(fp); } } Ok(ret) } /// Symmetric encryption. pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result { let lit_msg = Message::new_literal_bytes("", plain); let passphrase = passphrase.to_string(); tokio::task::spawn_blocking(move || { let mut rng = thread_rng(); let s2k = StringToKey::new_default(&mut rng); let msg = lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase)?; let encoded_msg = msg.to_armored_string(None)?; Ok(encoded_msg) }) .await? } /// Symmetric decryption. pub async fn symm_decrypt( passphrase: &str, ctext: T, ) -> Result> { let (enc_msg, _) = Message::from_armor_single(ctext)?; let passphrase = passphrase.to_string(); tokio::task::spawn_blocking(move || { let decryptor = enc_msg.decrypt_with_password(|| passphrase)?; let msgs = decryptor.collect::>>()?; if let Some(msg) = msgs.first() { match msg.get_content()? { Some(content) => Ok(content), None => bail!("Decrypted message is empty"), } } else { bail!("No valid messages found") } }) .await? } #[cfg(test)] mod tests { use once_cell::sync::Lazy; use tokio::sync::OnceCell; use super::*; use crate::test_utils::{alice_keypair, bob_keypair}; #[test] fn test_split_armored_data_1() { let (typ, _headers, base64) = split_armored_data( b"-----BEGIN PGP MESSAGE-----\nNoVal:\n\naGVsbG8gd29ybGQ=\n-----END PGP MESSAGE----", ) .unwrap(); assert_eq!(typ, BlockType::Message); assert!(!base64.is_empty()); assert_eq!( std::string::String::from_utf8(base64).unwrap(), "hello world" ); } #[test] fn test_split_armored_data_2() { let (typ, headers, base64) = split_armored_data( b"-----BEGIN PGP PRIVATE KEY BLOCK-----\nAutocrypt-Prefer-Encrypt: mutual \n\naGVsbG8gd29ybGQ=\n-----END PGP PRIVATE KEY BLOCK-----" ) .unwrap(); assert_eq!(typ, BlockType::PrivateKey); assert!(!base64.is_empty()); assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string())); } #[test] fn test_create_keypair() { let keypair0 = create_keypair( EmailAddress::new("foo@bar.de").unwrap(), KeyGenType::Default, ) .unwrap(); let keypair1 = create_keypair( EmailAddress::new("two@zwo.de").unwrap(), KeyGenType::Default, ) .unwrap(); assert_ne!(keypair0.public, keypair1.public); } /// [Key] objects to use in tests. struct TestKeys { alice_secret: SignedSecretKey, alice_public: SignedPublicKey, bob_secret: SignedSecretKey, bob_public: SignedPublicKey, } impl TestKeys { fn new() -> TestKeys { let alice = alice_keypair(); let bob = bob_keypair(); TestKeys { alice_secret: alice.secret.clone(), alice_public: alice.public, bob_secret: bob.secret.clone(), bob_public: bob.public, } } } /// The original text of [CTEXT_SIGNED] static CLEARTEXT: &[u8] = b"This is a test"; /// Initialised [TestKeys] for tests. static KEYS: Lazy = Lazy::new(TestKeys::new); static CTEXT_SIGNED: OnceCell = OnceCell::const_new(); static CTEXT_UNSIGNED: OnceCell = OnceCell::const_new(); /// A ciphertext encrypted to Alice & Bob, signed by Alice. async fn ctext_signed() -> &'static String { CTEXT_SIGNED .get_or_init(|| async { let mut keyring = Keyring::new(); keyring.add(KEYS.alice_public.clone()); keyring.add(KEYS.bob_public.clone()); pk_encrypt(CLEARTEXT, keyring, Some(KEYS.alice_secret.clone())) .await .unwrap() }) .await } /// A ciphertext encrypted to Alice & Bob, not signed. async fn ctext_unsigned() -> &'static String { CTEXT_UNSIGNED .get_or_init(|| async { let mut keyring = Keyring::new(); keyring.add(KEYS.alice_public.clone()); keyring.add(KEYS.bob_public.clone()); pk_encrypt(CLEARTEXT, keyring, None).await.unwrap() }) .await } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_encrypt_signed() { assert!(!ctext_signed().await.is_empty()); assert!(ctext_signed() .await .starts_with("-----BEGIN PGP MESSAGE-----")); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_encrypt_unsigned() { assert!(!ctext_unsigned().await.is_empty()); assert!(ctext_unsigned() .await .starts_with("-----BEGIN PGP MESSAGE-----")); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decrypt_singed() { // Check decrypting as Alice let mut decrypt_keyring: Keyring = Keyring::new(); decrypt_keyring.add(KEYS.alice_secret.clone()); let mut sig_check_keyring: Keyring = Keyring::new(); sig_check_keyring.add(KEYS.alice_public.clone()); let (plain, valid_signatures) = pk_decrypt( ctext_signed().await.as_bytes().to_vec(), &decrypt_keyring, &sig_check_keyring, ) .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 1); // Check decrypting as Bob let mut decrypt_keyring = Keyring::new(); decrypt_keyring.add(KEYS.bob_secret.clone()); let mut sig_check_keyring = Keyring::new(); sig_check_keyring.add(KEYS.alice_public.clone()); let (plain, valid_signatures) = pk_decrypt( ctext_signed().await.as_bytes().to_vec(), &decrypt_keyring, &sig_check_keyring, ) .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 1); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decrypt_no_sig_check() { let mut keyring = Keyring::new(); keyring.add(KEYS.alice_secret.clone()); let empty_keyring = Keyring::new(); let (plain, valid_signatures) = pk_decrypt( ctext_signed().await.as_bytes().to_vec(), &keyring, &empty_keyring, ) .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 0); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decrypt_signed_no_key() { // The validation does not have the public key of the signer. let mut decrypt_keyring = Keyring::new(); decrypt_keyring.add(KEYS.bob_secret.clone()); let mut sig_check_keyring = Keyring::new(); sig_check_keyring.add(KEYS.bob_public.clone()); let (plain, valid_signatures) = pk_decrypt( ctext_signed().await.as_bytes().to_vec(), &decrypt_keyring, &sig_check_keyring, ) .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 0); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_decrypt_unsigned() { let mut decrypt_keyring = Keyring::new(); decrypt_keyring.add(KEYS.bob_secret.clone()); let sig_check_keyring = Keyring::new(); let (plain, valid_signatures) = pk_decrypt( ctext_unsigned().await.as_bytes().to_vec(), &decrypt_keyring, &sig_check_keyring, ) .unwrap(); assert_eq!(plain, CLEARTEXT); assert_eq!(valid_signatures.len(), 0); } }