From 4b8252e0019bd6725a84900acb30bedf566e6f61 Mon Sep 17 00:00:00 2001 From: Alexander Krotov Date: Sun, 17 Nov 2019 13:35:12 +0300 Subject: [PATCH] Implement public key selection First, try to use subkeys, because they are usually short-term encryption keys. If none of the subkeys are encryption keys, try to use the primary key. rPGP is updated to the master branch because the latest release does not have .is_encryption_key() yet. --- Cargo.lock | 9 ++--- Cargo.toml | 2 +- src/pgp.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 98 insertions(+), 16 deletions(-) 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?;