Skip to content

Commit

Permalink
Move Keyring and fingerprint to DcKey trait
Browse files Browse the repository at this point in the history
This moves both the Keyring and the fingerprints to the DcKey trait,
unfortunately I was not able to disentangle these two changes.  The
Keyring now ensures only the right kind of key is added to it.

The keyring now uses the DcKey::load_self method rather than
re-implement the SQL to load keys from the database.  This vastly
simpliefies the use and fixes an error where a failed key load or
unconfigured would result in the message being treated as plain text
and benefits from the in-line key generation path.

For the fingerprint a new type representing it is introduced.  The aim
is to replace more fingerpring uses with this type as now there are
various string representations being passed around and converted
between.  The Display trait is used for the space-separated and
multiline format, which is perhaps not the most obvious but seems
right together with FromStr etc.
  • Loading branch information
flub committed May 28, 2020
1 parent 19a6a30 commit be385c2
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 176 deletions.
12 changes: 7 additions & 5 deletions src/contact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::error::{bail, ensure, format_err, Result};
use crate::events::Event;
use crate::key::{DcKey, Key, SignedPublicKey};
use crate::key::{DcKey, SignedPublicKey};
use crate::login_param::LoginParam;
use crate::message::{MessageState, MsgId};
use crate::mimeparser::AvatarAction;
Expand Down Expand Up @@ -691,18 +691,20 @@ impl Contact {
})
.await;
ret += &p;
let self_key = Key::from(SignedPublicKey::load_self(context).await?);
let p = context.stock_str(StockMessage::FingerPrints).await;
ret += &format!(" {}:", p);

let fingerprint_self = self_key.formatted_fingerprint();
let fingerprint_self = SignedPublicKey::load_self(context)
.await?
.fingerprint()
.to_string();
let fingerprint_other_verified = peerstate
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
.map(|k| k.formatted_fingerprint())
.map(|k| k.fingerprint().to_string())
.unwrap_or_default();
let fingerprint_other_unverified = peerstate
.peek_key(PeerstateVerifiedStatus::Unverified)
.map(|k| k.formatted_fingerprint())
.map(|k| k.fingerprint().to_string())
.unwrap_or_default();
if loginparam.addr < peerstate.addr {
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
Expand Down
4 changes: 2 additions & 2 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::dc_tools::duration_to_str;
use crate::error::*;
use crate::events::{Event, EventEmitter, Events};
use crate::job::{self, Action};
use crate::key::{DcKey, Key, SignedPublicKey};
use crate::key::{DcKey, SignedPublicKey};
use crate::login_param::LoginParam;
use crate::lot::Lot;
use crate::message::{self, Message, MessengerMessage, MsgId};
Expand Down Expand Up @@ -285,7 +285,7 @@ impl Context {
.query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![])
.await;
let fingerprint_str = match SignedPublicKey::load_self(self).await {
Ok(key) => Key::from(key).fingerprint(),
Ok(key) => key.fingerprint().hex(),
Err(err) => format!("<key failure: {}>", err),
};

Expand Down
66 changes: 29 additions & 37 deletions src/e2ee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl EncryptHelper {
mail_to_encrypt: lettre_email::PartBuilder,
peerstates: Vec<(Option<Peerstate<'_>>, &str)>,
) -> Result<String> {
let mut keyring = Keyring::default();
let mut keyring: Keyring<SignedPublicKey> = Keyring::new();

for (peerstate, addr) in peerstates
.into_iter()
Expand All @@ -104,8 +104,7 @@ impl EncryptHelper {
})?;
keyring.add(key);
}
let public_key = Key::from(self.public_key);
keyring.add(public_key);
keyring.add(self.public_key.clone());
let sign_key = Key::from(SignedSecretKey::load_self(context).await?);

let raw_message = mail_to_encrypt.build().as_string().into_bytes();
Expand Down Expand Up @@ -151,40 +150,33 @@ pub async fn try_decrypt(
}

/* possibly perform decryption */
let mut public_keyring_for_validate = Keyring::default();
let mut out_mail = None;
let private_keyring: Keyring<SignedSecretKey> = Keyring::new_self(context).await?;
let mut public_keyring_for_validate: Keyring<SignedPublicKey> = Keyring::new();
let mut signatures = HashSet::default();
let self_addr = context.get_config(Config::ConfiguredAddr).await;

if let Some(self_addr) = self_addr {
if let Ok(private_keyring) =
Keyring::load_self_private_for_decrypting(context, self_addr).await
{
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &from).await;
}
if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate).await?;
}
if let Some(key) = peerstate.gossip_key {
public_keyring_for_validate.add(key);
}
if let Some(key) = peerstate.public_key {
public_keyring_for_validate.add(key);
}
}

out_mail = decrypt_if_autocrypt_message(
context,
mail,
private_keyring,
public_keyring_for_validate,
&mut signatures,
)
.await?;
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &from).await;
}
if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate).await?;
}
if let Some(key) = peerstate.gossip_key {
public_keyring_for_validate.add(key);
}
if let Some(key) = peerstate.public_key {
public_keyring_for_validate.add(key);
}
}

let out_mail = decrypt_if_autocrypt_message(
context,
mail,
private_keyring,
public_keyring_for_validate,
&mut signatures,
)
.await?;
Ok((out_mail, signatures))
}

Expand Down Expand Up @@ -218,8 +210,8 @@ fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail
async fn decrypt_if_autocrypt_message<'a>(
context: &Context,
mail: &ParsedMail<'a>,
private_keyring: Keyring,
public_keyring_for_validate: Keyring,
private_keyring: Keyring<SignedSecretKey>,
public_keyring_for_validate: Keyring<SignedPublicKey>,
ret_valid_signatures: &mut HashSet<String>,
) -> Result<Option<Vec<u8>>> {
// The returned bool is true if we detected an Autocrypt-encrypted
Expand Down Expand Up @@ -250,8 +242,8 @@ async fn decrypt_if_autocrypt_message<'a>(
/// Returns Ok(None) if nothing encrypted was found.
async fn decrypt_part(
mail: &ParsedMail<'_>,
private_keyring: Keyring,
public_keyring_for_validate: Keyring,
private_keyring: Keyring<SignedSecretKey>,
public_keyring_for_validate: Keyring<SignedPublicKey>,
ret_valid_signatures: &mut HashSet<String>,
) -> Result<Option<Vec<u8>>> {
let data = mail.get_body_raw()?;
Expand Down
147 changes: 107 additions & 40 deletions src/key.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Cryptographic key module

use std::collections::BTreeMap;
use std::fmt;
use std::io::Cursor;

use async_std::path::Path;
Expand Down Expand Up @@ -50,8 +51,8 @@ pub type Result<T> = std::result::Result<T, Error>;
/// [SignedSecretKey] types and makes working with them a little
/// easier in the deltachat world.
#[async_trait]
pub trait DcKey: Serialize + Deserializable {
type KeyType: Serialize + Deserializable;
pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
type KeyType: Serialize + Deserializable + KeyTrait + Clone;

/// Create a key from some bytes.
fn from_slice(bytes: &[u8]) -> Result<Self::KeyType> {
Expand All @@ -71,15 +72,25 @@ pub trait DcKey: Serialize + Deserializable {
/// Load the users' default key from the database.
async fn load_self(context: &Context) -> Result<Self::KeyType>;

/// Serialise the key to a base64 string.
fn to_base64(&self) -> String {
/// Serialise the key as bytes.
fn to_bytes(&self) -> Vec<u8> {
// Not using Serialize::to_bytes() to make clear *why* it is
// safe to ignore this error.
// Because we write to a Vec<u8> the io::Write impls never
// fail and we can hide this error.
let mut buf = Vec::new();
self.to_writer(&mut buf).unwrap();
base64::encode(&buf)
buf
}

/// Serialise the key to a base64 string.
fn to_base64(&self) -> String {
base64::encode(&DcKey::to_bytes(self))
}

/// The fingerprint for the key.
fn fingerprint(&self) -> Fingerprint {
Fingerprint::new(KeyTrait::fingerprint(self))
}
}

Expand Down Expand Up @@ -300,8 +311,8 @@ impl Key {

pub fn to_bytes(&self) -> Vec<u8> {
match self {
Key::Public(k) => k.to_bytes().unwrap_or_default(),
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
Key::Public(k) => Serialize::to_bytes(&k).unwrap_or_default(),
Key::Secret(k) => Serialize::to_bytes(&k).unwrap_or_default(),
}
}

Expand All @@ -312,11 +323,6 @@ impl Key {
}
}

pub fn to_base64(&self) -> String {
let buf = self.to_bytes();
base64::encode(&buf)
}

pub fn to_armored_string(
&self,
headers: Option<&BTreeMap<String, String>>,
Expand Down Expand Up @@ -353,18 +359,6 @@ impl Key {
res
}

pub fn fingerprint(&self) -> String {
match self {
Key::Public(k) => hex::encode_upper(k.fingerprint()),
Key::Secret(k) => hex::encode_upper(k.fingerprint()),
}
}

pub fn formatted_fingerprint(&self) -> String {
let rawhex = self.fingerprint();
dc_format_fingerprint(&rawhex)
}

pub fn split_key(&self) -> Option<Key> {
match self {
Key::Public(_) => None,
Expand Down Expand Up @@ -425,14 +419,8 @@ pub async fn store_self_keypair(
) -> std::result::Result<(), SaveKeyError> {
// Everything should really be one transaction, more refactoring
// is needed for that.
let public_key = keypair
.public
.to_bytes()
.map_err(|err| SaveKeyError::new("failed to serialise public key", err))?;
let secret_key = keypair
.secret
.to_bytes()
.map_err(|err| SaveKeyError::new("failed to serialise secret key", err))?;
let public_key = DcKey::to_bytes(&keypair.public);
let secret_key = DcKey::to_bytes(&keypair.secret);
context
.sql
.execute(
Expand Down Expand Up @@ -470,6 +458,62 @@ pub async fn store_self_keypair(
Ok(())
}

/// A key fingerprint
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Fingerprint(Vec<u8>);

impl Fingerprint {
pub fn new(v: Vec<u8>) -> Fingerprint {
Fingerprint(v)
}

/// Make a hex string from the fingerprint.
///
/// Use [std::fmt::Display] or [ToString::to_string] to get a
/// human-readable formatted string.
pub fn hex(&self) -> String {
hex::encode_upper(&self.0)
}
}

/// Make a human-readable fingerprint.
impl fmt::Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Split key into chunks of 4 with space and newline at 20 chars
for (i, c) in self.hex().chars().enumerate() {
if i > 0 && i % 20 == 0 {
writeln!(f)?;
} else if i > 0 && i % 4 == 0 {
write!(f, " ")?;
}
write!(f, "{}", c)?;
}
Ok(())
}
}

/// Parse a human-readable or otherwise formatted fingerprint.
impl std::str::FromStr for Fingerprint {
type Err = hex::FromHexError;

fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
let hex_repr: String = input
.chars()
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
.collect();
let v: Vec<u8> = hex::decode(hex_repr)?;
Ok(Fingerprint(v))
}
}

/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
pub fn dc_normalize_fingerprint(fp: &str) -> String {
fp.to_uppercase()
.chars()
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
.collect()
}

/// Make a fingerprint human-readable, in hex format.
pub fn dc_format_fingerprint(fingerprint: &str) -> String {
// split key into chunks of 4 with space, and 20 newline
Expand All @@ -488,14 +532,6 @@ pub fn dc_format_fingerprint(fingerprint: &str) -> String {
res
}

/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
pub fn dc_normalize_fingerprint(fp: &str) -> String {
fp.to_uppercase()
.chars()
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
.collect()
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -755,4 +791,35 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
// )
// .unwrap();
// }

#[test]
fn test_fingerprint_from_str() {
let res = Fingerprint::new(vec![1, 2, 4, 8, 16, 32, 64, 128, 255]);

let fp: Fingerprint = "0102040810204080FF".parse().unwrap();
assert_eq!(fp, res);

let fp: Fingerprint = "zzzz 0102 0408\n1020 4080 FF zzz".parse().unwrap();
assert_eq!(fp, res);

let err = "1".parse::<Fingerprint>().err().unwrap();
assert_eq!(err, hex::FromHexError::OddLength);
}

#[test]
fn test_fingerprint_hex() {
let fp = Fingerprint::new(vec![1, 2, 4, 8, 16, 32, 64, 128, 255]);
assert_eq!(fp.hex(), "0102040810204080FF");
}

#[test]
fn test_fingerprint_to_string() {
let fp = Fingerprint::new(vec![
1, 2, 4, 8, 16, 32, 64, 128, 255, 1, 2, 4, 8, 16, 32, 64, 128, 255,
]);
assert_eq!(
fp.to_string(),
"0102 0408 1020 4080 FF01\n0204 0810 2040 80FF"
);
}
}

0 comments on commit be385c2

Please sign in to comment.