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.
This commit is contained in:
Alexander Krotov
2019-11-17 13:35:12 +03:00
committed by holger krekel
parent f505ff03e4
commit 4b8252e001
3 changed files with 98 additions and 16 deletions

9
Cargo.lock generated
View File

@@ -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)" = "<none>"
"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"

View File

@@ -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"

View File

@@ -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<u8> {
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<R: Rng + CryptoRng>(
&self,
rng: &mut R,
plain: &[u8],
) -> pgp::errors::Result<Vec<Mpi>> {
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<str>) -> 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<SignedPublicKeyOrSubkey> {
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<String, Error> {
let lit_msg = Message::new_literal_bytes("", plain);
let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption
let pkeys: Vec<SignedPublicKeyOrSubkey> = 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?;