Use the Fingerprint type to handle fingerprints

This uses the Fingerprint type more consistenly when handling
fingerprits rather then have various string representations passed
around and sometimes converted back and forth with slight differences
in strictness.

It fixes an important bug in the existing, but until now unused,
parsing behaviour of Fingerprint.  It also adds a default length check
on the fingerprint as that was checked in some existing places.

Fially generating keys is no longer expensive, so let's not ignore
these tests.
This commit is contained in:
Floris Bruynooghe
2020-05-30 23:04:11 +02:00
parent 95cde55a7f
commit ca95f25639
8 changed files with 147 additions and 174 deletions

View File

@@ -11,7 +11,7 @@ use crate::context::Context;
use crate::error::*; use crate::error::*;
use crate::headerdef::HeaderDef; use crate::headerdef::HeaderDef;
use crate::headerdef::HeaderDefMap; use crate::headerdef::HeaderDefMap;
use crate::key::{DcKey, SignedPublicKey, SignedSecretKey}; use crate::key::{DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};
use crate::keyring::*; use crate::keyring::*;
use crate::peerstate::*; use crate::peerstate::*;
use crate::pgp; use crate::pgp;
@@ -119,7 +119,7 @@ pub async fn try_decrypt(
context: &Context, context: &Context,
mail: &ParsedMail<'_>, mail: &ParsedMail<'_>,
message_time: i64, message_time: i64,
) -> Result<(Option<Vec<u8>>, HashSet<String>)> { ) -> Result<(Option<Vec<u8>>, HashSet<Fingerprint>)> {
let from = mail let from = mail
.headers .headers
.get_header(HeaderDef::From_) .get_header(HeaderDef::From_)
@@ -212,7 +212,7 @@ async fn decrypt_if_autocrypt_message<'a>(
mail: &ParsedMail<'a>, mail: &ParsedMail<'a>,
private_keyring: Keyring<SignedSecretKey>, private_keyring: Keyring<SignedSecretKey>,
public_keyring_for_validate: Keyring<SignedPublicKey>, public_keyring_for_validate: Keyring<SignedPublicKey>,
ret_valid_signatures: &mut HashSet<String>, ret_valid_signatures: &mut HashSet<Fingerprint>,
) -> Result<Option<Vec<u8>>> { ) -> Result<Option<Vec<u8>>> {
// The returned bool is true if we detected an Autocrypt-encrypted // The returned bool is true if we detected an Autocrypt-encrypted
// message and successfully decrypted it. Decryption then modifies the // message and successfully decrypted it. Decryption then modifies the
@@ -244,7 +244,7 @@ async fn decrypt_part(
mail: &ParsedMail<'_>, mail: &ParsedMail<'_>,
private_keyring: Keyring<SignedSecretKey>, private_keyring: Keyring<SignedSecretKey>,
public_keyring_for_validate: Keyring<SignedPublicKey>, public_keyring_for_validate: Keyring<SignedPublicKey>,
ret_valid_signatures: &mut HashSet<String>, ret_valid_signatures: &mut HashSet<Fingerprint>,
) -> Result<Option<Vec<u8>>> { ) -> Result<Option<Vec<u8>>> {
let data = mail.get_body_raw()?; let data = mail.get_body_raw()?;

View File

@@ -9,6 +9,7 @@ use num_traits::FromPrimitive;
use pgp::composed::Deserializable; use pgp::composed::Deserializable;
use pgp::ser::Serialize; use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait}; use pgp::types::{KeyTrait, SecretKeyTrait};
use thiserror::Error;
use crate::config::Config; use crate::config::Config;
use crate::constants::*; use crate::constants::*;
@@ -106,7 +107,7 @@ pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
/// The fingerprint for the key. /// The fingerprint for the key.
fn fingerprint(&self) -> Fingerprint { fn fingerprint(&self) -> Fingerprint {
Fingerprint::new(KeyTrait::fingerprint(self)) Fingerprint::new(KeyTrait::fingerprint(self)).expect("Invalid fingerprint from rpgp")
} }
} }
@@ -354,12 +355,15 @@ pub async fn store_self_keypair(
} }
/// A key fingerprint /// A key fingerprint
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Fingerprint(Vec<u8>); pub struct Fingerprint(Vec<u8>);
impl Fingerprint { impl Fingerprint {
pub fn new(v: Vec<u8>) -> Fingerprint { pub fn new(v: Vec<u8>) -> std::result::Result<Fingerprint, FingerprintError> {
Fingerprint(v) match v.len() {
20 => Ok(Fingerprint(v)),
_ => Err(FingerprintError::WrongLength),
}
} }
/// Make a hex string from the fingerprint. /// Make a hex string from the fingerprint.
@@ -389,42 +393,26 @@ impl fmt::Display for Fingerprint {
/// Parse a human-readable or otherwise formatted fingerprint. /// Parse a human-readable or otherwise formatted fingerprint.
impl std::str::FromStr for Fingerprint { impl std::str::FromStr for Fingerprint {
type Err = hex::FromHexError; type Err = FingerprintError;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> { fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
let hex_repr: String = input let hex_repr: String = input
.to_uppercase()
.chars() .chars()
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F') .filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
.collect(); .collect();
let v: Vec<u8> = hex::decode(hex_repr)?; let v: Vec<u8> = hex::decode(hex_repr)?;
Ok(Fingerprint(v)) let fp = Fingerprint::new(v)?;
Ok(fp)
} }
} }
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format. #[derive(Debug, Error)]
pub fn dc_normalize_fingerprint(fp: &str) -> String { pub enum FingerprintError {
fp.to_uppercase() #[error("Invalid hex characters")]
.chars() NotHex(#[from] hex::FromHexError),
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F') #[error("Incorrect fingerprint lengths")]
.collect() WrongLength,
}
/// 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
let mut res = String::new();
for (i, c) in fingerprint.chars().enumerate() {
if i > 0 && i % 20 == 0 {
res += "\n";
} else if i > 0 && i % 4 == 0 {
res += " ";
}
res += &c.to_string();
}
res
} }
#[cfg(test)] #[cfg(test)]
@@ -432,6 +420,8 @@ mod tests {
use super::*; use super::*;
use crate::test_utils::*; use crate::test_utils::*;
use std::error::Error;
use async_std::sync::Arc; use async_std::sync::Arc;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@@ -439,23 +429,6 @@ mod tests {
static ref KEYPAIR: KeyPair = alice_keypair(); static ref KEYPAIR: KeyPair = alice_keypair();
} }
#[test]
fn test_normalize_fingerprint() {
let fingerprint = dc_normalize_fingerprint(" 1234 567890 \n AbcD abcdef ABCDEF ");
assert_eq!(fingerprint, "1234567890ABCDABCDEFABCDEF");
}
#[test]
fn test_format_fingerprint() {
let fingerprint = dc_format_fingerprint("1234567890ABCDABCDEFABCDEF1234567890ABCD");
assert_eq!(
fingerprint,
"1234 5678 90AB CDAB CDEF\nABCD EF12 3456 7890 ABCD"
);
}
#[test] #[test]
fn test_from_armored_string() { fn test_from_armored_string() {
let (private_key, _) = SignedSecretKey::from_asc( let (private_key, _) = SignedSecretKey::from_asc(
@@ -586,7 +559,6 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
} }
#[async_std::test] #[async_std::test]
#[ignore] // generating keys is expensive
async fn test_load_self_generate_public() { async fn test_load_self_generate_public() {
let t = dummy_context().await; let t = dummy_context().await;
t.ctx t.ctx
@@ -598,7 +570,6 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
} }
#[async_std::test] #[async_std::test]
#[ignore] // generating keys is expensive
async fn test_load_self_generate_secret() { async fn test_load_self_generate_secret() {
let t = dummy_context().await; let t = dummy_context().await;
t.ctx t.ctx
@@ -610,7 +581,6 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
} }
#[async_std::test] #[async_std::test]
#[ignore] // generating keys is expensive
async fn test_load_self_generate_concurrent() { async fn test_load_self_generate_concurrent() {
use std::thread; use std::thread;
@@ -685,32 +655,46 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
#[test] #[test]
fn test_fingerprint_from_str() { fn test_fingerprint_from_str() {
let res = Fingerprint::new(vec![1, 2, 4, 8, 16, 32, 64, 128, 255]); let res = Fingerprint::new(vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
])
.unwrap();
let fp: Fingerprint = "0102040810204080FF".parse().unwrap(); let fp: Fingerprint = "0102030405060708090A0B0c0d0e0F1011121314".parse().unwrap();
assert_eq!(fp, res); assert_eq!(fp, res);
let fp: Fingerprint = "zzzz 0102 0408\n1020 4080 FF zzz".parse().unwrap(); let fp: Fingerprint = "zzzz 0102 0304 0506\n0708090a0b0c0D0E0F1011121314 yyy"
.parse()
.unwrap();
assert_eq!(fp, res); assert_eq!(fp, res);
let err = "1".parse::<Fingerprint>().err().unwrap(); let err = "1".parse::<Fingerprint>().err().unwrap();
assert_eq!(err, hex::FromHexError::OddLength); match err {
FingerprintError::NotHex(_) => (),
_ => panic!("Wrong error"),
}
let src_err = err.source().unwrap().downcast_ref::<hex::FromHexError>();
assert_eq!(src_err, Some(&hex::FromHexError::OddLength));
} }
#[test] #[test]
fn test_fingerprint_hex() { fn test_fingerprint_hex() {
let fp = Fingerprint::new(vec![1, 2, 4, 8, 16, 32, 64, 128, 255]); let fp = Fingerprint::new(vec![
assert_eq!(fp.hex(), "0102040810204080FF"); 1, 2, 4, 8, 16, 32, 64, 128, 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
])
.unwrap();
assert_eq!(fp.hex(), "0102040810204080FF0A0B0C0D0E0F1011121314");
} }
#[test] #[test]
fn test_fingerprint_to_string() { fn test_fingerprint_to_string() {
let fp = Fingerprint::new(vec![ let fp = Fingerprint::new(vec![
1, 2, 4, 8, 16, 32, 64, 128, 255, 1, 2, 4, 8, 16, 32, 64, 128, 255, 1, 2, 4, 8, 16, 32, 64, 128, 255, 1, 2, 4, 8, 16, 32, 64, 128, 255, 19, 20,
]); ])
.unwrap();
assert_eq!( assert_eq!(
fp.to_string(), fp.to_string(),
"0102 0408 1020 4080 FF01\n0204 0810 2040 80FF" "0102 0408 1020 4080 FF01\n0204 0810 2040 80FF 1314"
); );
} }
} }

View File

@@ -1,5 +1,7 @@
use deltachat_derive::{FromSql, ToSql}; use deltachat_derive::{FromSql, ToSql};
use crate::key::Fingerprint;
/// An object containing a set of values. /// An object containing a set of values.
/// The meaning of the values is defined by the function returning the object. /// The meaning of the values is defined by the function returning the object.
/// Lot objects are created /// Lot objects are created
@@ -14,7 +16,7 @@ pub struct Lot {
pub(crate) timestamp: i64, pub(crate) timestamp: i64,
pub(crate) state: LotState, pub(crate) state: LotState,
pub(crate) id: u32, pub(crate) id: u32,
pub(crate) fingerprint: Option<String>, pub(crate) fingerprint: Option<Fingerprint>,
pub(crate) invitenumber: Option<String>, pub(crate) invitenumber: Option<String>,
pub(crate) auth: Option<String>, pub(crate) auth: Option<String>,
} }

View File

@@ -17,6 +17,7 @@ use crate::e2ee;
use crate::error::{bail, Result}; use crate::error::{bail, Result};
use crate::events::Event; use crate::events::Event;
use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::Fingerprint;
use crate::location; use crate::location;
use crate::message; use crate::message;
use crate::param::*; use crate::param::*;
@@ -44,7 +45,7 @@ pub struct MimeMessage {
pub from: Vec<SingleInfo>, pub from: Vec<SingleInfo>,
pub chat_disposition_notification_to: Option<SingleInfo>, pub chat_disposition_notification_to: Option<SingleInfo>,
pub decrypting_failed: bool, pub decrypting_failed: bool,
pub signatures: HashSet<String>, pub signatures: HashSet<Fingerprint>,
pub gossipped_addr: HashSet<String>, pub gossipped_addr: HashSet<String>,
pub is_forwarded: bool, pub is_forwarded: bool,
pub is_system_message: SystemMessage, pub is_system_message: SystemMessage,

View File

@@ -7,7 +7,7 @@ use num_traits::FromPrimitive;
use crate::aheader::*; use crate::aheader::*;
use crate::context::Context; use crate::context::Context;
use crate::key::{DcKey, SignedPublicKey}; use crate::key::{DcKey, Fingerprint, SignedPublicKey};
use crate::sql::Sql; use crate::sql::Sql;
#[derive(Debug)] #[derive(Debug)]
@@ -32,12 +32,12 @@ pub struct Peerstate<'a> {
pub last_seen_autocrypt: i64, pub last_seen_autocrypt: i64,
pub prefer_encrypt: EncryptPreference, pub prefer_encrypt: EncryptPreference,
pub public_key: Option<SignedPublicKey>, pub public_key: Option<SignedPublicKey>,
pub public_key_fingerprint: Option<String>, pub public_key_fingerprint: Option<Fingerprint>,
pub gossip_key: Option<SignedPublicKey>, pub gossip_key: Option<SignedPublicKey>,
pub gossip_timestamp: i64, pub gossip_timestamp: i64,
pub gossip_key_fingerprint: Option<String>, pub gossip_key_fingerprint: Option<Fingerprint>,
pub verified_key: Option<SignedPublicKey>, pub verified_key: Option<SignedPublicKey>,
pub verified_key_fingerprint: Option<String>, pub verified_key_fingerprint: Option<Fingerprint>,
pub to_save: Option<ToSave>, pub to_save: Option<ToSave>,
pub degrade_event: Option<DegradeEvent>, pub degrade_event: Option<DegradeEvent>,
} }
@@ -151,7 +151,7 @@ impl<'a> Peerstate<'a> {
pub async fn from_fingerprint( pub async fn from_fingerprint(
context: &'a Context, context: &'a Context,
_sql: &Sql, _sql: &Sql,
fingerprint: &str, fingerprint: &Fingerprint,
) -> Option<Peerstate<'a>> { ) -> Option<Peerstate<'a>> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \ let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \ gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
@@ -160,13 +160,8 @@ impl<'a> Peerstate<'a> {
WHERE public_key_fingerprint=? COLLATE NOCASE \ WHERE public_key_fingerprint=? COLLATE NOCASE \
OR gossip_key_fingerprint=? COLLATE NOCASE \ OR gossip_key_fingerprint=? COLLATE NOCASE \
ORDER BY public_key_fingerprint=? DESC;"; ORDER BY public_key_fingerprint=? DESC;";
let fp = fingerprint.hex();
Self::from_stmt( Self::from_stmt(context, query, paramsv![fp, fp, fp]).await
context,
query,
paramsv![fingerprint, fingerprint, fingerprint],
)
.await
} }
async fn from_stmt( async fn from_stmt(
@@ -189,33 +184,18 @@ impl<'a> Peerstate<'a> {
res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default(); res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default();
res.gossip_timestamp = row.get(5)?; res.gossip_timestamp = row.get(5)?;
res.public_key_fingerprint = row.get(7)?; res.public_key_fingerprint = row
if res .get::<_, Option<String>>(7)?
.public_key_fingerprint .map(|s| s.parse::<Fingerprint>())
.as_ref() .transpose()?;
.map(|s| s.is_empty()) res.gossip_key_fingerprint = row
.unwrap_or_default() .get::<_, Option<String>>(8)?
{ .map(|s| s.parse::<Fingerprint>())
res.public_key_fingerprint = None; .transpose()?;
} res.verified_key_fingerprint = row
res.gossip_key_fingerprint = row.get(8)?; .get::<_, Option<String>>(10)?
if res .map(|s| s.parse::<Fingerprint>())
.gossip_key_fingerprint .transpose()?;
.as_ref()
.map(|s| s.is_empty())
.unwrap_or_default()
{
res.gossip_key_fingerprint = None;
}
res.verified_key_fingerprint = row.get(10)?;
if res
.verified_key_fingerprint
.as_ref()
.map(|s| s.is_empty())
.unwrap_or_default()
{
res.verified_key_fingerprint = None;
}
res.public_key = row res.public_key = row
.get(4) .get(4)
.ok() .ok()
@@ -238,7 +218,7 @@ impl<'a> Peerstate<'a> {
pub fn recalc_fingerprint(&mut self) { pub fn recalc_fingerprint(&mut self) {
if let Some(ref public_key) = self.public_key { if let Some(ref public_key) = self.public_key {
let old_public_fingerprint = self.public_key_fingerprint.take(); let old_public_fingerprint = self.public_key_fingerprint.take();
self.public_key_fingerprint = Some(public_key.fingerprint().hex()); self.public_key_fingerprint = Some(public_key.fingerprint());
if old_public_fingerprint.is_none() if old_public_fingerprint.is_none()
|| self.public_key_fingerprint.is_none() || self.public_key_fingerprint.is_none()
@@ -253,7 +233,7 @@ impl<'a> Peerstate<'a> {
if let Some(ref gossip_key) = self.gossip_key { if let Some(ref gossip_key) = self.gossip_key {
let old_gossip_fingerprint = self.gossip_key_fingerprint.take(); let old_gossip_fingerprint = self.gossip_key_fingerprint.take();
self.gossip_key_fingerprint = Some(gossip_key.fingerprint().hex()); self.gossip_key_fingerprint = Some(gossip_key.fingerprint());
if old_gossip_fingerprint.is_none() if old_gossip_fingerprint.is_none()
|| self.gossip_key_fingerprint.is_none() || self.gossip_key_fingerprint.is_none()
@@ -387,7 +367,7 @@ impl<'a> Peerstate<'a> {
pub fn set_verified( pub fn set_verified(
&mut self, &mut self,
which_key: PeerstateKeyType, which_key: PeerstateKeyType,
fingerprint: &str, fingerprint: &Fingerprint,
verified: PeerstateVerifiedStatus, verified: PeerstateVerifiedStatus,
) -> bool { ) -> bool {
if verified == PeerstateVerifiedStatus::BidirectVerified { if verified == PeerstateVerifiedStatus::BidirectVerified {
@@ -445,10 +425,10 @@ impl<'a> Peerstate<'a> {
self.public_key.as_ref().map(|k| k.to_bytes()), self.public_key.as_ref().map(|k| k.to_bytes()),
self.gossip_timestamp, self.gossip_timestamp,
self.gossip_key.as_ref().map(|k| k.to_bytes()), self.gossip_key.as_ref().map(|k| k.to_bytes()),
self.public_key_fingerprint, self.public_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.gossip_key_fingerprint, self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.verified_key.as_ref().map(|k| k.to_bytes()), self.verified_key.as_ref().map(|k| k.to_bytes()),
self.verified_key_fingerprint, self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.addr, self.addr,
], ],
).await?; ).await?;
@@ -469,7 +449,7 @@ impl<'a> Peerstate<'a> {
Ok(()) Ok(())
} }
pub fn has_verified_key(&self, fingerprints: &HashSet<String>) -> bool { pub fn has_verified_key(&self, fingerprints: &HashSet<Fingerprint>) -> bool {
if self.verified_key.is_some() && self.verified_key_fingerprint.is_some() { if self.verified_key.is_some() && self.verified_key_fingerprint.is_some() {
let vkc = self.verified_key_fingerprint.as_ref().unwrap(); let vkc = self.verified_key_fingerprint.as_ref().unwrap();
if fingerprints.contains(vkc) { if fingerprints.contains(vkc) {
@@ -481,6 +461,12 @@ impl<'a> Peerstate<'a> {
} }
} }
impl From<crate::key::FingerprintError> for rusqlite::Error {
fn from(_source: crate::key::FingerprintError) -> Self {
Self::InvalidColumnType(0, "Invalid fingerprint".into(), rusqlite::types::Type::Text)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -502,12 +488,12 @@ mod tests {
last_seen_autocrypt: 11, last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual, prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()), public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint().hex()), public_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key: Some(pub_key.clone()), gossip_key: Some(pub_key.clone()),
gossip_timestamp: 12, gossip_timestamp: 12,
gossip_key_fingerprint: Some(pub_key.fingerprint().hex()), gossip_key_fingerprint: Some(pub_key.fingerprint()),
verified_key: Some(pub_key.clone()), verified_key: Some(pub_key.clone()),
verified_key_fingerprint: Some(pub_key.fingerprint().hex()), verified_key_fingerprint: Some(pub_key.fingerprint()),
to_save: Some(ToSave::All), to_save: Some(ToSave::All),
degrade_event: None, degrade_event: None,
}; };
@@ -525,7 +511,7 @@ mod tests {
peerstate.to_save = None; peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new); assert_eq!(peerstate, peerstate_new);
let peerstate_new2 = let peerstate_new2 =
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint().hex()) Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint())
.await .await
.expect("failed to load peerstate from db"); .expect("failed to load peerstate from db");
assert_eq!(peerstate, peerstate_new2); assert_eq!(peerstate, peerstate_new2);
@@ -544,7 +530,7 @@ mod tests {
last_seen_autocrypt: 11, last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual, prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()), public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint().hex()), public_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key: None, gossip_key: None,
gossip_timestamp: 12, gossip_timestamp: 12,
gossip_key_fingerprint: None, gossip_key_fingerprint: None,
@@ -578,7 +564,7 @@ mod tests {
last_seen_autocrypt: 11, last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual, prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()), public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint().hex()), public_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key: None, gossip_key: None,
gossip_timestamp: 12, gossip_timestamp: 12,
gossip_key_fingerprint: None, gossip_key_fingerprint: None,

View File

@@ -18,7 +18,7 @@ use rand::{thread_rng, CryptoRng, Rng};
use crate::constants::KeyGenType; use crate::constants::KeyGenType;
use crate::dc_tools::EmailAddress; use crate::dc_tools::EmailAddress;
use crate::error::{bail, ensure, format_err, Result}; use crate::error::{bail, ensure, format_err, Result};
use crate::key::DcKey; use crate::key::{DcKey, Fingerprint};
use crate::keyring::Keyring; use crate::keyring::Keyring;
pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt"; pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
@@ -277,7 +277,7 @@ pub async fn pk_decrypt(
ctext: Vec<u8>, ctext: Vec<u8>,
private_keys_for_decryption: Keyring<SignedSecretKey>, private_keys_for_decryption: Keyring<SignedSecretKey>,
public_keys_for_validation: Keyring<SignedPublicKey>, public_keys_for_validation: Keyring<SignedPublicKey>,
ret_signature_fingerprints: Option<&mut HashSet<String>>, ret_signature_fingerprints: Option<&mut HashSet<Fingerprint>>,
) -> Result<Vec<u8>> { ) -> Result<Vec<u8>> {
let msgs = async_std::task::spawn_blocking(move || { let msgs = async_std::task::spawn_blocking(move || {
let cursor = Cursor::new(ctext); let cursor = Cursor::new(ctext);
@@ -304,10 +304,10 @@ pub async fn pk_decrypt(
let pkeys = public_keys_for_validation.keys(); let pkeys = public_keys_for_validation.keys();
let mut fingerprints = Vec::new(); let mut fingerprints: Vec<Fingerprint> = Vec::new();
for pkey in pkeys { for pkey in pkeys {
if dec_msg.verify(&pkey.primary_key).is_ok() { if dec_msg.verify(&pkey.primary_key).is_ok() {
let fp = DcKey::fingerprint(pkey).hex(); let fp = DcKey::fingerprint(pkey);
fingerprints.push(fp); fingerprints.push(fp);
} }
} }
@@ -474,7 +474,7 @@ mod tests {
decrypt_keyring.add(KEYS.alice_secret.clone()); decrypt_keyring.add(KEYS.alice_secret.clone());
let mut sig_check_keyring: Keyring<SignedPublicKey> = Keyring::new(); let mut sig_check_keyring: Keyring<SignedPublicKey> = Keyring::new();
sig_check_keyring.add(KEYS.alice_public.clone()); sig_check_keyring.add(KEYS.alice_public.clone());
let mut valid_signatures: HashSet<String> = Default::default(); let mut valid_signatures: HashSet<Fingerprint> = Default::default();
let plain = pk_decrypt( let plain = pk_decrypt(
CTEXT_SIGNED.as_bytes().to_vec(), CTEXT_SIGNED.as_bytes().to_vec(),
decrypt_keyring, decrypt_keyring,
@@ -492,7 +492,7 @@ mod tests {
decrypt_keyring.add(KEYS.bob_secret.clone()); decrypt_keyring.add(KEYS.bob_secret.clone());
let mut sig_check_keyring = Keyring::new(); let mut sig_check_keyring = Keyring::new();
sig_check_keyring.add(KEYS.alice_public.clone()); sig_check_keyring.add(KEYS.alice_public.clone());
let mut valid_signatures: HashSet<String> = Default::default(); let mut valid_signatures: HashSet<Fingerprint> = Default::default();
let plain = pk_decrypt( let plain = pk_decrypt(
CTEXT_SIGNED.as_bytes().to_vec(), CTEXT_SIGNED.as_bytes().to_vec(),
decrypt_keyring, decrypt_keyring,
@@ -511,7 +511,7 @@ mod tests {
let mut keyring = Keyring::new(); let mut keyring = Keyring::new();
keyring.add(KEYS.alice_secret.clone()); keyring.add(KEYS.alice_secret.clone());
let empty_keyring = Keyring::new(); let empty_keyring = Keyring::new();
let mut valid_signatures: HashSet<String> = Default::default(); let mut valid_signatures: HashSet<Fingerprint> = Default::default();
let plain = pk_decrypt( let plain = pk_decrypt(
CTEXT_SIGNED.as_bytes().to_vec(), CTEXT_SIGNED.as_bytes().to_vec(),
keyring, keyring,
@@ -531,7 +531,7 @@ mod tests {
decrypt_keyring.add(KEYS.bob_secret.clone()); decrypt_keyring.add(KEYS.bob_secret.clone());
let mut sig_check_keyring = Keyring::new(); let mut sig_check_keyring = Keyring::new();
sig_check_keyring.add(KEYS.bob_public.clone()); sig_check_keyring.add(KEYS.bob_public.clone());
let mut valid_signatures: HashSet<String> = Default::default(); let mut valid_signatures: HashSet<Fingerprint> = Default::default();
let plain = pk_decrypt( let plain = pk_decrypt(
CTEXT_SIGNED.as_bytes().to_vec(), CTEXT_SIGNED.as_bytes().to_vec(),
decrypt_keyring, decrypt_keyring,
@@ -549,7 +549,7 @@ mod tests {
let mut decrypt_keyring = Keyring::new(); let mut decrypt_keyring = Keyring::new();
decrypt_keyring.add(KEYS.bob_secret.clone()); decrypt_keyring.add(KEYS.bob_secret.clone());
let sig_check_keyring = Keyring::new(); let sig_check_keyring = Keyring::new();
let mut valid_signatures: HashSet<String> = Default::default(); let mut valid_signatures: HashSet<Fingerprint> = Default::default();
let plain = pk_decrypt( let plain = pk_decrypt(
CTEXT_UNSIGNED.as_bytes().to_vec(), CTEXT_UNSIGNED.as_bytes().to_vec(),
decrypt_keyring, decrypt_keyring,

View File

@@ -10,8 +10,7 @@ use crate::constants::Blocked;
use crate::contact::*; use crate::contact::*;
use crate::context::Context; use crate::context::Context;
use crate::error::{bail, ensure, format_err, Error}; use crate::error::{bail, ensure, format_err, Error};
use crate::key::dc_format_fingerprint; use crate::key::Fingerprint;
use crate::key::dc_normalize_fingerprint;
use crate::lot::{Lot, LotState}; use crate::lot::{Lot, LotState};
use crate::param::*; use crate::param::*;
use crate::peerstate::*; use crate::peerstate::*;
@@ -80,6 +79,14 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
Some(pair) => pair, Some(pair) => pair,
None => (payload, ""), None => (payload, ""),
}; };
let fingerprint: Fingerprint = match fingerprint.parse() {
Ok(fp) => fp,
Err(err) => {
return Error::new(err)
.context("Failed to parse fingerprint in QR code")
.into()
}
};
// replace & with \n to match expected param format // replace & with \n to match expected param format
let fragment = fragment.replace('&', "\n"); let fragment = fragment.replace('&', "\n");
@@ -128,13 +135,6 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
None None
}; };
let fingerprint = dc_normalize_fingerprint(fingerprint);
// ensure valid fingerprint
if fingerprint.len() != 40 {
return format_err!("Bad fingerprint length in QR code").into();
}
let mut lot = Lot::new(); let mut lot = Lot::new();
// retrieve known state for this fingerprint // retrieve known state for this fingerprint
@@ -161,7 +161,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)).await; chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)).await;
} else { } else {
lot.state = LotState::QrFprWithoutAddr; lot.state = LotState::QrFprWithoutAddr;
lot.text1 = Some(dc_format_fingerprint(&fingerprint)); lot.text1 = Some(fingerprint.to_string());
} }
} else if let Some(addr) = addr { } else if let Some(addr) = addr {
if grpid.is_some() && grpname.is_some() { if grpid.is_some() && grpname.is_some() {

View File

@@ -14,7 +14,7 @@ use crate::e2ee::*;
use crate::error::{bail, Error}; use crate::error::{bail, Error};
use crate::events::Event; use crate::events::Event;
use crate::headerdef::HeaderDef; use crate::headerdef::HeaderDef;
use crate::key::{dc_normalize_fingerprint, DcKey, SignedPublicKey}; use crate::key::{DcKey, Fingerprint, SignedPublicKey};
use crate::lot::LotState; use crate::lot::LotState;
use crate::message::Message; use crate::message::Message;
use crate::mimeparser::*; use crate::mimeparser::*;
@@ -73,8 +73,6 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
==== Step 1 in "Setup verified contact" protocol ==== ==== Step 1 in "Setup verified contact" protocol ====
=======================================================*/ =======================================================*/
let fingerprint: String;
ensure_secret_key_exists(context).await.ok(); ensure_secret_key_exists(context).await.ok();
// invitenumber will be used to allow starting the handshake, // invitenumber will be used to allow starting the handshake,
@@ -95,7 +93,7 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
.await .await
.unwrap_or_default(); .unwrap_or_default();
fingerprint = match get_self_fingerprint(context).await { let fingerprint: Fingerprint = match get_self_fingerprint(context).await {
Some(fp) => fp, Some(fp) => fp,
None => { None => {
return None; return None;
@@ -140,9 +138,9 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
qr qr
} }
async fn get_self_fingerprint(context: &Context) -> Option<String> { async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
match SignedPublicKey::load_self(context).await { match SignedPublicKey::load_self(context).await {
Ok(key) => Some(key.fingerprint().hex()), Ok(key) => Some(key.fingerprint()),
Err(_) => { Err(_) => {
warn!(context, "get_self_fingerprint(): failed to load key"); warn!(context, "get_self_fingerprint(): failed to load key");
None None
@@ -249,7 +247,7 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId {
chat_id_2_contact_id(context, contact_chat_id).await, chat_id_2_contact_id(context, contact_chat_id).await,
400 400
); );
let own_fingerprint = get_self_fingerprint(context).await.unwrap_or_default(); let own_fingerprint = get_self_fingerprint(context).await;
// Bob -> Alice // Bob -> Alice
if let Err(err) = send_handshake_msg( if let Err(err) = send_handshake_msg(
@@ -261,7 +259,7 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId {
"vc-request-with-auth" "vc-request-with-auth"
}, },
get_qr_attr!(context, auth).to_string(), get_qr_attr!(context, auth).to_string(),
Some(own_fingerprint), own_fingerprint,
if join_vg { if join_vg {
get_qr_attr!(context, text2).to_string() get_qr_attr!(context, text2).to_string()
} else { } else {
@@ -311,7 +309,7 @@ async fn send_handshake_msg(
contact_chat_id: ChatId, contact_chat_id: ChatId,
step: &str, step: &str,
param2: impl AsRef<str>, param2: impl AsRef<str>,
fingerprint: Option<String>, fingerprint: Option<Fingerprint>,
grpid: impl AsRef<str>, grpid: impl AsRef<str>,
) -> Result<(), HandshakeError> { ) -> Result<(), HandshakeError> {
let mut msg = Message::default(); let mut msg = Message::default();
@@ -328,7 +326,7 @@ async fn send_handshake_msg(
msg.param.set(Param::Arg2, param2); msg.param.set(Param::Arg2, param2);
} }
if let Some(fp) = fingerprint { if let Some(fp) = fingerprint {
msg.param.set(Param::Arg3, fp); msg.param.set(Param::Arg3, fp.hex());
} }
if !grpid.as_ref().is_empty() { if !grpid.as_ref().is_empty() {
msg.param.set(Param::Arg4, grpid.as_ref()); msg.param.set(Param::Arg4, grpid.as_ref());
@@ -360,7 +358,7 @@ async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32
async fn fingerprint_equals_sender( async fn fingerprint_equals_sender(
context: &Context, context: &Context,
fingerprint: impl AsRef<str>, fingerprint: &Fingerprint,
contact_chat_id: ChatId, contact_chat_id: ChatId,
) -> bool { ) -> bool {
let contacts = chat::get_chat_contacts(context, contact_chat_id).await; let contacts = chat::get_chat_contacts(context, contact_chat_id).await;
@@ -368,9 +366,8 @@ async fn fingerprint_equals_sender(
if contacts.len() == 1 { if contacts.len() == 1 {
if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await { if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await {
if let Some(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await { if let Some(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await {
let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref());
if peerstate.public_key_fingerprint.is_some() if peerstate.public_key_fingerprint.is_some()
&& &fingerprint_normalized == peerstate.public_key_fingerprint.as_ref().unwrap() && fingerprint == peerstate.public_key_fingerprint.as_ref().unwrap()
{ {
return true; return true;
} }
@@ -397,6 +394,8 @@ pub(crate) enum HandshakeError {
NoSelfAddr, NoSelfAddr,
#[error("Failed to send message")] #[error("Failed to send message")]
MsgSendFailed(#[source] Error), MsgSendFailed(#[source] Error),
#[error("Failed to parse fingerprint")]
BadFingerprint(#[from] crate::key::FingerprintError),
} }
/// What to do with a Secure-Join handshake message after it was handled. /// What to do with a Secure-Join handshake message after it was handled.
@@ -516,10 +515,11 @@ pub(crate) async fn handle_securejoin_handshake(
// no error, just aborted somehow or a mail from another handshake // no error, just aborted somehow or a mail from another handshake
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string(); let scanned_fingerprint_of_alice: Fingerprint =
get_qr_attr!(context, fingerprint).clone();
let auth = get_qr_attr!(context, auth).to_string(); let auth = get_qr_attr!(context, auth).to_string();
if !encrypted_and_signed(context, mime_message, &scanned_fingerprint_of_alice) { if !encrypted_and_signed(context, mime_message, Some(&scanned_fingerprint_of_alice)) {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
@@ -576,8 +576,9 @@ pub(crate) async fn handle_securejoin_handshake(
==========================================================*/ ==========================================================*/
// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob // verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
let fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint) { let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint)
Some(fp) => fp, {
Some(fp) => fp.parse()?,
None => { None => {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
@@ -588,7 +589,7 @@ pub(crate) async fn handle_securejoin_handshake(
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
}; };
if !encrypted_and_signed(context, mime_message, &fingerprint) { if !encrypted_and_signed(context, mime_message, Some(&fingerprint)) {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
@@ -625,7 +626,7 @@ pub(crate) async fn handle_securejoin_handshake(
.await; .await;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
if mark_peer_as_verified(context, fingerprint).await.is_err() { if mark_peer_as_verified(context, &fingerprint).await.is_err() {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
@@ -673,7 +674,7 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id, contact_chat_id,
"vc-contact-confirm", "vc-contact-confirm",
"", "",
Some(fingerprint.clone()), Some(fingerprint),
"", "",
) )
.await?; .await?;
@@ -709,7 +710,8 @@ pub(crate) async fn handle_securejoin_handshake(
); );
return Ok(abort_retval); return Ok(abort_retval);
} }
let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string(); let scanned_fingerprint_of_alice: Fingerprint =
get_qr_attr!(context, fingerprint).clone();
let vg_expect_encrypted = if join_vg { let vg_expect_encrypted = if join_vg {
let group_id = get_qr_attr!(context, text2).to_string(); let group_id = get_qr_attr!(context, text2).to_string();
@@ -731,7 +733,7 @@ pub(crate) async fn handle_securejoin_handshake(
true true
}; };
if vg_expect_encrypted if vg_expect_encrypted
&& !encrypted_and_signed(context, mime_message, &scanned_fingerprint_of_alice) && !encrypted_and_signed(context, mime_message, Some(&scanned_fingerprint_of_alice))
{ {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
@@ -888,7 +890,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
if !encrypted_and_signed( if !encrypted_and_signed(
context, context,
mime_message, mime_message,
get_self_fingerprint(context).await.unwrap_or_default(), get_self_fingerprint(context).await.as_ref(),
) { ) {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
@@ -898,8 +900,9 @@ pub(crate) async fn observe_securejoin_on_other_device(
.await; .await;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
let fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint) { let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint)
Some(fp) => fp, {
Some(fp) => fp.parse()?,
None => { None => {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
@@ -910,7 +913,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
}; };
if mark_peer_as_verified(context, fingerprint).await.is_err() { if mark_peer_as_verified(context, &fingerprint).await.is_err() {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
@@ -967,16 +970,13 @@ async fn could_not_establish_secure_connection(
error!(context, "{} ({})", &msg, details); error!(context, "{} ({})", &msg, details);
} }
async fn mark_peer_as_verified( async fn mark_peer_as_verified(context: &Context, fingerprint: &Fingerprint) -> Result<(), Error> {
context: &Context,
fingerprint: impl AsRef<str>,
) -> Result<(), Error> {
if let Some(ref mut peerstate) = if let Some(ref mut peerstate) =
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref()).await Peerstate::from_fingerprint(context, &context.sql, fingerprint).await
{ {
if peerstate.set_verified( if peerstate.set_verified(
PeerstateKeyType::PublicKey, PeerstateKeyType::PublicKey,
fingerprint.as_ref(), fingerprint,
PeerstateVerifiedStatus::BidirectVerified, PeerstateVerifiedStatus::BidirectVerified,
) { ) {
peerstate.prefer_encrypt = EncryptPreference::Mutual; peerstate.prefer_encrypt = EncryptPreference::Mutual;
@@ -990,7 +990,7 @@ async fn mark_peer_as_verified(
} }
bail!( bail!(
"could not mark peer as verified for fingerprint {}", "could not mark peer as verified for fingerprint {}",
fingerprint.as_ref() fingerprint.hex()
); );
} }
@@ -1001,7 +1001,7 @@ async fn mark_peer_as_verified(
fn encrypted_and_signed( fn encrypted_and_signed(
context: &Context, context: &Context,
mimeparser: &MimeMessage, mimeparser: &MimeMessage,
expected_fingerprint: impl AsRef<str>, expected_fingerprint: Option<&Fingerprint>,
) -> bool { ) -> bool {
if !mimeparser.was_encrypted() { if !mimeparser.was_encrypted() {
warn!(context, "Message not encrypted.",); warn!(context, "Message not encrypted.",);
@@ -1009,17 +1009,17 @@ fn encrypted_and_signed(
} else if mimeparser.signatures.is_empty() { } else if mimeparser.signatures.is_empty() {
warn!(context, "Message not signed.",); warn!(context, "Message not signed.",);
false false
} else if expected_fingerprint.as_ref().is_empty() { } else if expected_fingerprint.is_none() {
warn!(context, "Fingerprint for comparison missing.",); warn!(context, "Fingerprint for comparison missing.");
false false
} else if !mimeparser } else if !mimeparser
.signatures .signatures
.contains(expected_fingerprint.as_ref()) .contains(expected_fingerprint.unwrap())
{ {
warn!( warn!(
context, context,
"Message does not match expected fingerprint {}.", "Message does not match expected fingerprint {}.",
expected_fingerprint.as_ref(), expected_fingerprint.unwrap(),
); );
false false
} else { } else {