diff --git a/Cargo.lock b/Cargo.lock index 80eb67a55..1960ebbe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -506,7 +506,7 @@ dependencies = [ "num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pgp 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "pgp 0.3.1 (git+https://github.com/rpgp/rpgp)", "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1505,8 +1505,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pgp" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.3.1" +source = "git+https://github.com/rpgp/rpgp#6977b14479ffa079d5857861ca89e69e93c8bd55" dependencies = [ "aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1520,6 +1520,7 @@ dependencies = [ "cfb-mode 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "circular 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clear_on_drop 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "crc24 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "des 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2969,7 +2970,7 @@ dependencies = [ "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pgp 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a14471cfc3855455f5bbb639e367cedd69fbce71b32bfb83aff20c3deafce36e" +"checksum pgp 0.3.1 (git+https://github.com/rpgp/rpgp)" = "" "checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" diff --git a/Cargo.toml b/Cargo.toml index 1f9231109..a619aaba6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ deltachat_derive = { path = "./deltachat_derive" } mmime = { version = "0.1.2", path = "./mmime" } libc = "0.2.51" -pgp = { version = "0.2.3", default-features = false } +pgp = { git = "https://github.com/rpgp/rpgp", branch = "master", default-features = false } hex = "0.3.2" sha2 = "0.8.0" rand = "0.6.5" diff --git a/src/pgp.rs b/src/pgp.rs index a7cf4add3..31c40e2f7 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashSet}; use std::convert::TryInto; +use std::io; use std::io::Cursor; use pgp::armor::BlockType; @@ -10,8 +11,10 @@ use pgp::composed::{ SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder, }; use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm}; -use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey}; -use rand::thread_rng; +use pgp::types::{ + CompressionAlgorithm, KeyTrait, Mpi, PublicKeyTrait, SecretKeyTrait, StringToKey, +}; +use rand::{thread_rng, CryptoRng, Rng}; use crate::error::Error; use crate::key::*; @@ -20,6 +23,68 @@ use crate::keyring::*; pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt"; 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::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 https://tools.ietf.org/html/rfc4880#section-6.2. /// /// Returns (type, headers, base64 encoded body). @@ -99,13 +164,28 @@ pub fn create_keypair(addr: impl AsRef) -> Option<(Key, Key)> { Some((Key::Public(public_key), Key::Secret(private_key))) } -/// Select subkey of the public key to use for encryption. +/// Select public key or subkey to use for encryption. /// -/// Currently the first subkey is selected. -fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> { - key.public_subkeys.iter().find(|_k| - // TODO: check if it is an encryption subkey - true) +/// 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` @@ -116,7 +196,7 @@ pub fn pk_encrypt( private_key_for_signing: Option<&Key>, ) -> Result { let lit_msg = Message::new_literal_bytes("", plain); - let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption + let pkeys: Vec = public_keys_for_encryption .keys() .iter() .filter_map(|key| { @@ -126,6 +206,7 @@ pub fn pk_encrypt( .and_then(select_pk_for_encryption) }) .collect(); + let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect(); let mut rng = thread_rng(); @@ -138,9 +219,9 @@ pub fn pk_encrypt( 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)) + .and_then(|msg| msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs)) } else { - lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys) + lit_msg.encrypt_to_keys(&mut rng, Default::default(), &pkeys_refs) }; let msg = encrypted_msg?;