mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 17:36:29 +03:00
fix: avoid blocking on expensive pgp operations
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -784,6 +784,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"smol",
|
||||||
"stop-token",
|
"stop-token",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ pretty_assertions = "0.6.1"
|
|||||||
pretty_env_logger = "0.3.0"
|
pretty_env_logger = "0.3.0"
|
||||||
proptest = "0.9.4"
|
proptest = "0.9.4"
|
||||||
async-std = { version = "1.6.0", features = ["unstable", "attributes"] }
|
async-std = { version = "1.6.0", features = ["unstable", "attributes"] }
|
||||||
|
smol = "0.1.10"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
|||||||
65
src/e2ee.rs
65
src/e2ee.rs
@@ -87,30 +87,30 @@ impl EncryptHelper {
|
|||||||
|
|
||||||
/// Tries to encrypt the passed in `mail`.
|
/// Tries to encrypt the passed in `mail`.
|
||||||
pub async fn encrypt(
|
pub async fn encrypt(
|
||||||
&mut self,
|
self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
min_verified: PeerstateVerifiedStatus,
|
min_verified: PeerstateVerifiedStatus,
|
||||||
mail_to_encrypt: lettre_email::PartBuilder,
|
mail_to_encrypt: lettre_email::PartBuilder,
|
||||||
peerstates: &[(Option<Peerstate<'_>>, &str)],
|
peerstates: Vec<(Option<Peerstate<'_>>, &str)>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let mut keyring = Keyring::default();
|
let mut keyring = Keyring::default();
|
||||||
|
|
||||||
for (peerstate, addr) in peerstates
|
for (peerstate, addr) in peerstates
|
||||||
.iter()
|
.into_iter()
|
||||||
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
|
.filter_map(|(state, addr)| state.map(|s| (s, addr)))
|
||||||
{
|
{
|
||||||
let key = peerstate.peek_key(min_verified).ok_or_else(|| {
|
let key = peerstate.take_key(min_verified).ok_or_else(|| {
|
||||||
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
|
format_err!("proper enc-key for {} missing, cannot encrypt", addr)
|
||||||
})?;
|
})?;
|
||||||
keyring.add_ref(key);
|
keyring.add(key);
|
||||||
}
|
}
|
||||||
let public_key = Key::from(self.public_key.clone());
|
let public_key = Key::from(self.public_key);
|
||||||
keyring.add_ref(&public_key);
|
keyring.add(public_key);
|
||||||
let sign_key = Key::from(SignedSecretKey::load_self(context).await?);
|
let sign_key = Key::from(SignedSecretKey::load_self(context).await?);
|
||||||
|
|
||||||
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
|
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
|
||||||
|
|
||||||
let ctext = pgp::pk_encrypt(&raw_message, &keyring, Some(&sign_key))?;
|
let ctext = pgp::pk_encrypt(&raw_message, keyring, Some(sign_key)).await?;
|
||||||
|
|
||||||
Ok(ctext)
|
Ok(ctext)
|
||||||
}
|
}
|
||||||
@@ -151,39 +151,38 @@ pub async fn try_decrypt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* possibly perform decryption */
|
/* possibly perform decryption */
|
||||||
let mut private_keyring = Keyring::default();
|
|
||||||
let mut public_keyring_for_validate = Keyring::default();
|
let mut public_keyring_for_validate = Keyring::default();
|
||||||
let mut out_mail = None;
|
let mut out_mail = None;
|
||||||
let mut signatures = HashSet::default();
|
let mut signatures = HashSet::default();
|
||||||
let self_addr = context.get_config(Config::ConfiguredAddr).await;
|
let self_addr = context.get_config(Config::ConfiguredAddr).await;
|
||||||
|
|
||||||
if let Some(self_addr) = self_addr {
|
if let Some(self_addr) = self_addr {
|
||||||
if private_keyring
|
if let Ok(private_keyring) =
|
||||||
.load_self_private_for_decrypting(context, self_addr, &context.sql)
|
Keyring::load_self_private_for_decrypting(context, self_addr).await
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||||
peerstate = Peerstate::from_addr(&context, &from).await;
|
peerstate = Peerstate::from_addr(&context, &from).await;
|
||||||
}
|
}
|
||||||
if let Some(ref peerstate) = peerstate {
|
if let Some(peerstate) = peerstate {
|
||||||
if peerstate.degrade_event.is_some() {
|
if peerstate.degrade_event.is_some() {
|
||||||
handle_degrade_event(context, &peerstate).await?;
|
handle_degrade_event(context, &peerstate).await?;
|
||||||
}
|
}
|
||||||
if let Some(ref key) = peerstate.gossip_key {
|
if let Some(key) = peerstate.gossip_key {
|
||||||
public_keyring_for_validate.add_ref(key);
|
public_keyring_for_validate.add(key);
|
||||||
}
|
}
|
||||||
if let Some(ref key) = peerstate.public_key {
|
if let Some(key) = peerstate.public_key {
|
||||||
public_keyring_for_validate.add_ref(key);
|
public_keyring_for_validate.add(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out_mail = decrypt_if_autocrypt_message(
|
out_mail = decrypt_if_autocrypt_message(
|
||||||
context,
|
context,
|
||||||
mail,
|
mail,
|
||||||
&private_keyring,
|
private_keyring,
|
||||||
&public_keyring_for_validate,
|
public_keyring_for_validate,
|
||||||
&mut signatures,
|
&mut signatures,
|
||||||
)?;
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((out_mail, signatures))
|
Ok((out_mail, signatures))
|
||||||
@@ -216,11 +215,11 @@ fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail
|
|||||||
Ok(&mail.subparts[1])
|
Ok(&mail.subparts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_if_autocrypt_message<'a>(
|
async fn decrypt_if_autocrypt_message<'a>(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
mail: &ParsedMail<'a>,
|
mail: &ParsedMail<'a>,
|
||||||
private_keyring: &Keyring,
|
private_keyring: Keyring,
|
||||||
public_keyring_for_validate: &Keyring,
|
public_keyring_for_validate: Keyring,
|
||||||
ret_valid_signatures: &mut HashSet<String>,
|
ret_valid_signatures: &mut HashSet<String>,
|
||||||
) -> 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
|
||||||
@@ -240,20 +239,19 @@ fn decrypt_if_autocrypt_message<'a>(
|
|||||||
info!(context, "Detected Autocrypt-mime message");
|
info!(context, "Detected Autocrypt-mime message");
|
||||||
|
|
||||||
decrypt_part(
|
decrypt_part(
|
||||||
context,
|
|
||||||
encrypted_data_part,
|
encrypted_data_part,
|
||||||
private_keyring,
|
private_keyring,
|
||||||
public_keyring_for_validate,
|
public_keyring_for_validate,
|
||||||
ret_valid_signatures,
|
ret_valid_signatures,
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns Ok(None) if nothing encrypted was found.
|
/// Returns Ok(None) if nothing encrypted was found.
|
||||||
fn decrypt_part(
|
async fn decrypt_part(
|
||||||
_context: &Context,
|
|
||||||
mail: &ParsedMail<'_>,
|
mail: &ParsedMail<'_>,
|
||||||
private_keyring: &Keyring,
|
private_keyring: Keyring,
|
||||||
public_keyring_for_validate: &Keyring,
|
public_keyring_for_validate: Keyring,
|
||||||
ret_valid_signatures: &mut HashSet<String>,
|
ret_valid_signatures: &mut HashSet<String>,
|
||||||
) -> Result<Option<Vec<u8>>> {
|
) -> Result<Option<Vec<u8>>> {
|
||||||
let data = mail.get_body_raw()?;
|
let data = mail.get_body_raw()?;
|
||||||
@@ -263,11 +261,12 @@ fn decrypt_part(
|
|||||||
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
|
||||||
|
|
||||||
let plain = pgp::pk_decrypt(
|
let plain = pgp::pk_decrypt(
|
||||||
&data,
|
data,
|
||||||
&private_keyring,
|
private_keyring,
|
||||||
&public_keyring_for_validate,
|
public_keyring_for_validate,
|
||||||
Some(ret_valid_signatures),
|
Some(ret_valid_signatures),
|
||||||
)?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
|
||||||
return Ok(Some(plain));
|
return Ok(Some(plain));
|
||||||
|
|||||||
24
src/imex.rs
24
src/imex.rs
@@ -187,7 +187,7 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<St
|
|||||||
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
|
||||||
};
|
};
|
||||||
let private_key_asc = private_key.to_asc(ac_headers);
|
let private_key_asc = private_key.to_asc(ac_headers);
|
||||||
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes())?;
|
let encr = pgp::symm_encrypt(&passphrase, private_key_asc.as_bytes()).await?;
|
||||||
|
|
||||||
let replacement = format!(
|
let replacement = format!(
|
||||||
concat!(
|
concat!(
|
||||||
@@ -274,7 +274,7 @@ pub async fn continue_key_transfer(
|
|||||||
if let Some(filename) = msg.get_file(context) {
|
if let Some(filename) = msg.get_file(context) {
|
||||||
let file = dc_open_file_std(context, filename)?;
|
let file = dc_open_file_std(context, filename)?;
|
||||||
let sc = normalize_setup_code(setup_code);
|
let sc = normalize_setup_code(setup_code);
|
||||||
let armored_key = decrypt_setup_file(context, &sc, file)?;
|
let armored_key = decrypt_setup_file(&sc, file).await?;
|
||||||
set_self_key(context, &armored_key, true, true).await?;
|
set_self_key(context, &armored_key, true, true).await?;
|
||||||
maybe_add_bcc_self_device_msg(context).await?;
|
maybe_add_bcc_self_device_msg(context).await?;
|
||||||
|
|
||||||
@@ -345,12 +345,11 @@ async fn set_self_key(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
async fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
|
||||||
_context: &Context,
|
|
||||||
passphrase: &str,
|
passphrase: &str,
|
||||||
file: T,
|
file: T,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let plain_bytes = pgp::symm_decrypt(passphrase, file)?;
|
let plain_bytes = pgp::symm_decrypt(passphrase, file).await?;
|
||||||
let plain_text = std::string::String::from_utf8(plain_bytes)?;
|
let plain_text = std::string::String::from_utf8(plain_bytes)?;
|
||||||
|
|
||||||
Ok(plain_text)
|
Ok(plain_text)
|
||||||
@@ -713,7 +712,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
|||||||
|
|
||||||
for (id, public_key, private_key, is_default) in keys {
|
for (id, public_key, private_key, is_default) in keys {
|
||||||
let id = Some(id).filter(|_| is_default != 0);
|
let id = Some(id).filter(|_| is_default != 0);
|
||||||
if let Some(key) = public_key {
|
if let Ok(key) = public_key {
|
||||||
if export_key_to_asc_file(context, &dir, id, &key)
|
if export_key_to_asc_file(context, &dir, id, &key)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
@@ -723,7 +722,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
|||||||
} else {
|
} else {
|
||||||
export_errors += 1;
|
export_errors += 1;
|
||||||
}
|
}
|
||||||
if let Some(key) = private_key {
|
if let Ok(key) = private_key {
|
||||||
if export_key_to_asc_file(context, &dir, id, &key)
|
if export_key_to_asc_file(context, &dir, id, &key)
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_err()
|
||||||
@@ -852,9 +851,6 @@ mod tests {
|
|||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_split_and_decrypt() {
|
async fn test_split_and_decrypt() {
|
||||||
let ctx = dummy_context().await;
|
|
||||||
let context = &ctx.ctx;
|
|
||||||
|
|
||||||
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
||||||
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
|
let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
|
||||||
assert_eq!(typ, BlockType::Message);
|
assert_eq!(typ, BlockType::Message);
|
||||||
@@ -864,11 +860,9 @@ mod tests {
|
|||||||
assert!(!base64.is_empty());
|
assert!(!base64.is_empty());
|
||||||
|
|
||||||
let setup_file = S_EM_SETUPFILE.to_string();
|
let setup_file = S_EM_SETUPFILE.to_string();
|
||||||
let decrypted = decrypt_setup_file(
|
let decrypted =
|
||||||
context,
|
decrypt_setup_file(S_EM_SETUPCODE, std::io::Cursor::new(setup_file.as_bytes()))
|
||||||
S_EM_SETUPCODE,
|
.await
|
||||||
std::io::Cursor::new(setup_file.as_bytes()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
|
let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
|
||||||
|
|||||||
23
src/key.rs
23
src/key.rs
@@ -38,6 +38,8 @@ pub enum Error {
|
|||||||
NoConfiguredAddr,
|
NoConfiguredAddr,
|
||||||
#[error("Configured address is invalid: {}", _0)]
|
#[error("Configured address is invalid: {}", _0)]
|
||||||
InvalidConfiguredAddr(#[from] InvalidEmailError),
|
InvalidConfiguredAddr(#[from] InvalidEmailError),
|
||||||
|
#[error("no data provided")]
|
||||||
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -260,22 +262,17 @@ impl Key {
|
|||||||
!self.is_public()
|
!self.is_public()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Option<Self> {
|
pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Result<Self> {
|
||||||
if bytes.is_empty() {
|
if bytes.is_empty() {
|
||||||
return None;
|
return Err(Error::Empty);
|
||||||
}
|
}
|
||||||
let res: std::result::Result<Key, _> = match key_type {
|
|
||||||
KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes)).map(Into::into),
|
let res = match key_type {
|
||||||
KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes)).map(Into::into),
|
KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes))?.into(),
|
||||||
|
KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes))?.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match res {
|
Ok(res)
|
||||||
Ok(key) => Some(key),
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Invalid key bytes: {:?}", err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_armored_string(
|
pub fn from_armored_string(
|
||||||
@@ -625,7 +622,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
|||||||
KeyType::Private
|
KeyType::Private
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert!(bad_key.is_none());
|
assert!(bad_key.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +1,47 @@
|
|||||||
use std::borrow::Cow;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::constants::KeyType;
|
use crate::constants::KeyType;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::key::Key;
|
use crate::key::Key;
|
||||||
use crate::sql::Sql;
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct Keyring<'a> {
|
pub struct Keyring {
|
||||||
keys: Vec<Cow<'a, Key>>,
|
keys: Vec<Key>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Keyring<'a> {
|
impl Keyring {
|
||||||
pub fn add_owned(&mut self, key: Key) {
|
pub fn add(&mut self, key: Key) {
|
||||||
self.add(Cow::Owned(key))
|
self.keys.push(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_ref(&mut self, key: &'a Key) {
|
pub fn len(&self) -> usize {
|
||||||
self.add(Cow::Borrowed(key))
|
self.keys.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(&mut self, key: Cow<'a, Key>) {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.keys.push(key);
|
self.keys.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keys(&self) -> &[Cow<'a, Key>] {
|
pub fn keys(&self) -> &[Key] {
|
||||||
&self.keys
|
&self.keys
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_self_private_for_decrypting(
|
pub async fn load_self_private_for_decrypting(
|
||||||
&mut self,
|
|
||||||
context: &Context,
|
context: &Context,
|
||||||
self_addr: impl AsRef<str>,
|
self_addr: impl AsRef<str>,
|
||||||
sql: &Sql,
|
) -> Result<Self> {
|
||||||
) -> bool {
|
let blob: Vec<u8> = context
|
||||||
sql.query_get_value(
|
.sql
|
||||||
context,
|
.query_get_value_result(
|
||||||
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
|
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
|
||||||
paramsv![self_addr.as_ref().to_string()],
|
paramsv![self_addr.as_ref().to_string()],
|
||||||
)
|
)
|
||||||
.await
|
.await?
|
||||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
|
.unwrap_or_default();
|
||||||
.map(|key| self.add_owned(key))
|
|
||||||
.is_some()
|
let key = async_std::task::spawn_blocking(move || Key::from_slice(&blob, KeyType::Private))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Self { keys: vec![key] })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
|||||||
let force_plaintext = self.should_force_plaintext();
|
let force_plaintext = self.should_force_plaintext();
|
||||||
let subject_str = self.subject_str().await;
|
let subject_str = self.subject_str().await;
|
||||||
let e2ee_guaranteed = self.is_e2ee_guaranteed();
|
let e2ee_guaranteed = self.is_e2ee_guaranteed();
|
||||||
let mut encrypt_helper = EncryptHelper::new(self.context).await?;
|
let encrypt_helper = EncryptHelper::new(self.context).await?;
|
||||||
|
|
||||||
let subject = encode_words(&subject_str);
|
let subject = encode_words(&subject_str);
|
||||||
|
|
||||||
@@ -560,7 +560,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let encrypted = encrypt_helper
|
let encrypted = encrypt_helper
|
||||||
.encrypt(self.context, min_verified, message, &peerstates)
|
.encrypt(self.context, min_verified, message, peerstates)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
outer_message = outer_message
|
outer_message = outer_message
|
||||||
|
|||||||
@@ -220,15 +220,15 @@ impl<'a> Peerstate<'a> {
|
|||||||
res.public_key = row
|
res.public_key = row
|
||||||
.get(4)
|
.get(4)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public).ok());
|
||||||
res.gossip_key = row
|
res.gossip_key = row
|
||||||
.get(6)
|
.get(6)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public).ok());
|
||||||
res.verified_key = row
|
res.verified_key = row
|
||||||
.get(9)
|
.get(9)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public));
|
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public).ok());
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
})
|
})
|
||||||
@@ -367,6 +367,15 @@ impl<'a> Peerstate<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn take_key(mut self, min_verified: PeerstateVerifiedStatus) -> Option<Key> {
|
||||||
|
match min_verified {
|
||||||
|
PeerstateVerifiedStatus::BidirectVerified => self.verified_key.take(),
|
||||||
|
PeerstateVerifiedStatus::Unverified => {
|
||||||
|
self.public_key.take().or_else(|| self.gossip_key.take())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Key> {
|
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Key> {
|
||||||
match min_verified {
|
match min_verified {
|
||||||
PeerstateVerifiedStatus::BidirectVerified => self.verified_key.as_ref(),
|
PeerstateVerifiedStatus::BidirectVerified => self.verified_key.as_ref(),
|
||||||
|
|||||||
190
src/pgp.rs
190
src/pgp.rs
@@ -238,28 +238,25 @@ fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<SignedPublicKeyOrSu
|
|||||||
|
|
||||||
/// Encrypts `plain` text using `public_keys_for_encryption`
|
/// Encrypts `plain` text using `public_keys_for_encryption`
|
||||||
/// and signs it using `private_key_for_signing`.
|
/// and signs it using `private_key_for_signing`.
|
||||||
pub fn pk_encrypt(
|
pub async fn pk_encrypt(
|
||||||
plain: &[u8],
|
plain: &[u8],
|
||||||
public_keys_for_encryption: &Keyring,
|
public_keys_for_encryption: Keyring,
|
||||||
private_key_for_signing: Option<&Key>,
|
private_key_for_signing: Option<Key>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let lit_msg = Message::new_literal_bytes("", plain);
|
let lit_msg = Message::new_literal_bytes("", plain);
|
||||||
|
|
||||||
|
async_std::task::spawn_blocking(move || {
|
||||||
let pkeys: Vec<SignedPublicKeyOrSubkey> = public_keys_for_encryption
|
let pkeys: Vec<SignedPublicKeyOrSubkey> = public_keys_for_encryption
|
||||||
.keys()
|
.keys()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|key| {
|
.filter_map(|key| key.try_into().ok().and_then(select_pk_for_encryption))
|
||||||
key.as_ref()
|
|
||||||
.try_into()
|
|
||||||
.ok()
|
|
||||||
.and_then(select_pk_for_encryption)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect();
|
let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect();
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
// TODO: measure time
|
// TODO: measure time
|
||||||
let encrypted_msg = if let Some(private_key) = private_key_for_signing {
|
let encrypted_msg = if let Some(ref private_key) = private_key_for_signing {
|
||||||
let skey: &SignedSecretKey = private_key
|
let skey: &SignedSecretKey = private_key
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| format_err!("Invalid private key"))?;
|
.map_err(|_| format_err!("Invalid private key"))?;
|
||||||
@@ -276,78 +273,95 @@ pub fn pk_encrypt(
|
|||||||
let encoded_msg = msg.to_armored_string(None)?;
|
let encoded_msg = msg.to_armored_string(None)?;
|
||||||
|
|
||||||
Ok(encoded_msg)
|
Ok(encoded_msg)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::implicit_hasher)]
|
#[allow(clippy::implicit_hasher)]
|
||||||
pub fn pk_decrypt(
|
pub async fn pk_decrypt(
|
||||||
ctext: &[u8],
|
ctext: Vec<u8>,
|
||||||
private_keys_for_decryption: &Keyring,
|
private_keys_for_decryption: Keyring,
|
||||||
public_keys_for_validation: &Keyring,
|
public_keys_for_validation: Keyring,
|
||||||
ret_signature_fingerprints: Option<&mut HashSet<String>>,
|
ret_signature_fingerprints: Option<&mut HashSet<String>>,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
let (msg, _) = Message::from_armor_single(Cursor::new(ctext))?;
|
let msgs = async_std::task::spawn_blocking(move || {
|
||||||
|
let cursor = Cursor::new(ctext);
|
||||||
|
let (msg, _) = Message::from_armor_single(cursor)?;
|
||||||
|
|
||||||
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
|
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
|
||||||
.keys()
|
.keys()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|key| {
|
.filter_map(|key| key.try_into().ok())
|
||||||
let k: &Key = &key;
|
|
||||||
k.try_into().ok()
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?;
|
let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?;
|
||||||
let msgs = decryptor.collect::<pgp::errors::Result<Vec<_>>>()?;
|
decryptor.collect::<pgp::errors::Result<Vec<_>>>()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
ensure!(!msgs.is_empty(), "No valid messages found");
|
ensure!(!msgs.is_empty(), "No valid messages found");
|
||||||
|
|
||||||
let dec_msg = &msgs[0];
|
let content = match msgs[0].get_content()? {
|
||||||
|
Some(content) => content,
|
||||||
|
None => bail!("Decrypted message is empty"),
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(ret_signature_fingerprints) = ret_signature_fingerprints {
|
if let Some(ret_signature_fingerprints) = ret_signature_fingerprints {
|
||||||
if !public_keys_for_validation.keys().is_empty() {
|
if !public_keys_for_validation.is_empty() {
|
||||||
let pkeys: Vec<&SignedPublicKey> = public_keys_for_validation
|
let fingerprints = async_std::task::spawn_blocking(move || {
|
||||||
|
let dec_msg = &msgs[0];
|
||||||
|
|
||||||
|
let pkeys = public_keys_for_validation
|
||||||
.keys()
|
.keys()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|key| {
|
.filter_map(|key| -> Option<&SignedPublicKey> { key.try_into().ok() });
|
||||||
let k: &Key = &key;
|
|
||||||
k.try_into().ok()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for pkey in &pkeys {
|
let mut fingerprints = Vec::new();
|
||||||
|
for pkey in pkeys {
|
||||||
if dec_msg.verify(&pkey.primary_key).is_ok() {
|
if dec_msg.verify(&pkey.primary_key).is_ok() {
|
||||||
let fp = hex::encode_upper(pkey.fingerprint());
|
let fp = hex::encode_upper(pkey.fingerprint());
|
||||||
ret_signature_fingerprints.insert(fp);
|
fingerprints.push(fp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fingerprints
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
ret_signature_fingerprints.extend(fingerprints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match dec_msg.get_content()? {
|
Ok(content)
|
||||||
Some(content) => Ok(content),
|
|
||||||
None => bail!("Decrypted message is empty"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Symmetric encryption.
|
/// Symmetric encryption.
|
||||||
pub fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String> {
|
pub async fn symm_encrypt(passphrase: &str, plain: &[u8]) -> Result<String> {
|
||||||
let mut rng = thread_rng();
|
|
||||||
let lit_msg = Message::new_literal_bytes("", plain);
|
let lit_msg = Message::new_literal_bytes("", plain);
|
||||||
|
let passphrase = passphrase.to_string();
|
||||||
|
|
||||||
|
async_std::task::spawn_blocking(move || {
|
||||||
|
let mut rng = thread_rng();
|
||||||
let s2k = StringToKey::new_default(&mut rng);
|
let s2k = StringToKey::new_default(&mut rng);
|
||||||
let msg =
|
let msg =
|
||||||
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase.into())?;
|
lit_msg.encrypt_with_password(&mut rng, s2k, Default::default(), || passphrase)?;
|
||||||
|
|
||||||
let encoded_msg = msg.to_armored_string(None)?;
|
let encoded_msg = msg.to_armored_string(None)?;
|
||||||
|
|
||||||
Ok(encoded_msg)
|
Ok(encoded_msg)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Symmetric decryption.
|
/// Symmetric decryption.
|
||||||
pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
pub async fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
||||||
passphrase: &str,
|
passphrase: &str,
|
||||||
ctext: T,
|
ctext: T,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
let (enc_msg, _) = Message::from_armor_single(ctext)?;
|
let (enc_msg, _) = Message::from_armor_single(ctext)?;
|
||||||
let decryptor = enc_msg.decrypt_with_password(|| passphrase.into())?;
|
|
||||||
|
let passphrase = passphrase.to_string();
|
||||||
|
async_std::task::spawn_blocking(move || {
|
||||||
|
let decryptor = enc_msg.decrypt_with_password(|| passphrase)?;
|
||||||
|
|
||||||
let msgs = decryptor.collect::<pgp::errors::Result<Vec<_>>>()?;
|
let msgs = decryptor.collect::<pgp::errors::Result<Vec<_>>>()?;
|
||||||
ensure!(!msgs.is_empty(), "No valid messages found");
|
ensure!(!msgs.is_empty(), "No valid messages found");
|
||||||
@@ -356,6 +370,8 @@ pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
|
|||||||
Some(content) => Ok(content),
|
Some(content) => Ok(content),
|
||||||
None => bail!("Decrypted message is empty"),
|
None => bail!("Decrypted message is empty"),
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -437,17 +453,17 @@ mod tests {
|
|||||||
/// A cyphertext encrypted to Alice & Bob, signed by Alice.
|
/// A cyphertext encrypted to Alice & Bob, signed by Alice.
|
||||||
static ref CTEXT_SIGNED: String = {
|
static ref CTEXT_SIGNED: String = {
|
||||||
let mut keyring = Keyring::default();
|
let mut keyring = Keyring::default();
|
||||||
keyring.add_owned(KEYS.alice_public.clone());
|
keyring.add(KEYS.alice_public.clone());
|
||||||
keyring.add_ref(&KEYS.bob_public);
|
keyring.add(KEYS.bob_public.clone());
|
||||||
pk_encrypt(CLEARTEXT, &keyring, Some(&KEYS.alice_secret)).unwrap()
|
smol::block_on(pk_encrypt(CLEARTEXT, keyring, Some(KEYS.alice_secret.clone()))).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A cyphertext encrypted to Alice & Bob, not signed.
|
/// A cyphertext encrypted to Alice & Bob, not signed.
|
||||||
static ref CTEXT_UNSIGNED: String = {
|
static ref CTEXT_UNSIGNED: String = {
|
||||||
let mut keyring = Keyring::default();
|
let mut keyring = Keyring::default();
|
||||||
keyring.add_owned(KEYS.alice_public.clone());
|
keyring.add(KEYS.alice_public.clone());
|
||||||
keyring.add_ref(&KEYS.bob_public);
|
keyring.add(KEYS.bob_public.clone());
|
||||||
pk_encrypt(CLEARTEXT, &keyring, None).unwrap()
|
smol::block_on(pk_encrypt(CLEARTEXT, keyring, None)).unwrap()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,20 +479,21 @@ mod tests {
|
|||||||
assert!(CTEXT_UNSIGNED.starts_with("-----BEGIN PGP MESSAGE-----"));
|
assert!(CTEXT_UNSIGNED.starts_with("-----BEGIN PGP MESSAGE-----"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[async_std::test]
|
||||||
fn test_decrypt_singed() {
|
async fn test_decrypt_singed() {
|
||||||
// Check decrypting as Alice
|
// Check decrypting as Alice
|
||||||
let mut decrypt_keyring = Keyring::default();
|
let mut decrypt_keyring = Keyring::default();
|
||||||
decrypt_keyring.add_ref(&KEYS.alice_secret);
|
decrypt_keyring.add(KEYS.alice_secret.clone());
|
||||||
let mut sig_check_keyring = Keyring::default();
|
let mut sig_check_keyring = Keyring::default();
|
||||||
sig_check_keyring.add_ref(&KEYS.alice_public);
|
sig_check_keyring.add(KEYS.alice_public.clone());
|
||||||
let mut valid_signatures: HashSet<String> = Default::default();
|
let mut valid_signatures: HashSet<String> = Default::default();
|
||||||
let plain = pk_decrypt(
|
let plain = pk_decrypt(
|
||||||
CTEXT_SIGNED.as_bytes(),
|
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||||
&decrypt_keyring,
|
decrypt_keyring,
|
||||||
&sig_check_keyring,
|
sig_check_keyring,
|
||||||
Some(&mut valid_signatures),
|
Some(&mut valid_signatures),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
.map_err(|err| println!("{:?}", err))
|
.map_err(|err| println!("{:?}", err))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(plain, CLEARTEXT);
|
assert_eq!(plain, CLEARTEXT);
|
||||||
@@ -484,89 +501,94 @@ mod tests {
|
|||||||
|
|
||||||
// Check decrypting as Bob
|
// Check decrypting as Bob
|
||||||
let mut decrypt_keyring = Keyring::default();
|
let mut decrypt_keyring = Keyring::default();
|
||||||
decrypt_keyring.add_ref(&KEYS.bob_secret);
|
decrypt_keyring.add(KEYS.bob_secret.clone());
|
||||||
let mut sig_check_keyring = Keyring::default();
|
let mut sig_check_keyring = Keyring::default();
|
||||||
sig_check_keyring.add_ref(&KEYS.alice_public);
|
sig_check_keyring.add(KEYS.alice_public.clone());
|
||||||
let mut valid_signatures: HashSet<String> = Default::default();
|
let mut valid_signatures: HashSet<String> = Default::default();
|
||||||
let plain = pk_decrypt(
|
let plain = pk_decrypt(
|
||||||
CTEXT_SIGNED.as_bytes(),
|
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||||
&decrypt_keyring,
|
decrypt_keyring,
|
||||||
&sig_check_keyring,
|
sig_check_keyring,
|
||||||
Some(&mut valid_signatures),
|
Some(&mut valid_signatures),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
.map_err(|err| println!("{:?}", err))
|
.map_err(|err| println!("{:?}", err))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(plain, CLEARTEXT);
|
assert_eq!(plain, CLEARTEXT);
|
||||||
assert_eq!(valid_signatures.len(), 1);
|
assert_eq!(valid_signatures.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[async_std::test]
|
||||||
fn test_decrypt_no_sig_check() {
|
async fn test_decrypt_no_sig_check() {
|
||||||
let mut keyring = Keyring::default();
|
let mut keyring = Keyring::default();
|
||||||
keyring.add_ref(&KEYS.alice_secret);
|
keyring.add(KEYS.alice_secret.clone());
|
||||||
let empty_keyring = Keyring::default();
|
let empty_keyring = Keyring::default();
|
||||||
let mut valid_signatures: HashSet<String> = Default::default();
|
let mut valid_signatures: HashSet<String> = Default::default();
|
||||||
let plain = pk_decrypt(
|
let plain = pk_decrypt(
|
||||||
CTEXT_SIGNED.as_bytes(),
|
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||||
&keyring,
|
keyring,
|
||||||
&empty_keyring,
|
empty_keyring,
|
||||||
Some(&mut valid_signatures),
|
Some(&mut valid_signatures),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(plain, CLEARTEXT);
|
assert_eq!(plain, CLEARTEXT);
|
||||||
assert_eq!(valid_signatures.len(), 0);
|
assert_eq!(valid_signatures.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[async_std::test]
|
||||||
fn test_decrypt_signed_no_key() {
|
async fn test_decrypt_signed_no_key() {
|
||||||
// The validation does not have the public key of the signer.
|
// The validation does not have the public key of the signer.
|
||||||
let mut decrypt_keyring = Keyring::default();
|
let mut decrypt_keyring = Keyring::default();
|
||||||
decrypt_keyring.add_ref(&KEYS.bob_secret);
|
decrypt_keyring.add(KEYS.bob_secret.clone());
|
||||||
let mut sig_check_keyring = Keyring::default();
|
let mut sig_check_keyring = Keyring::default();
|
||||||
sig_check_keyring.add_ref(&KEYS.bob_public);
|
sig_check_keyring.add(KEYS.bob_public.clone());
|
||||||
let mut valid_signatures: HashSet<String> = Default::default();
|
let mut valid_signatures: HashSet<String> = Default::default();
|
||||||
let plain = pk_decrypt(
|
let plain = pk_decrypt(
|
||||||
CTEXT_SIGNED.as_bytes(),
|
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||||
&decrypt_keyring,
|
decrypt_keyring,
|
||||||
&sig_check_keyring,
|
sig_check_keyring,
|
||||||
Some(&mut valid_signatures),
|
Some(&mut valid_signatures),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(plain, CLEARTEXT);
|
assert_eq!(plain, CLEARTEXT);
|
||||||
assert_eq!(valid_signatures.len(), 0);
|
assert_eq!(valid_signatures.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[async_std::test]
|
||||||
fn test_decrypt_unsigned() {
|
async fn test_decrypt_unsigned() {
|
||||||
let mut decrypt_keyring = Keyring::default();
|
let mut decrypt_keyring = Keyring::default();
|
||||||
decrypt_keyring.add_ref(&KEYS.bob_secret);
|
decrypt_keyring.add(KEYS.bob_secret.clone());
|
||||||
let sig_check_keyring = Keyring::default();
|
let sig_check_keyring = Keyring::default();
|
||||||
decrypt_keyring.add_ref(&KEYS.alice_public);
|
decrypt_keyring.add(KEYS.alice_public.clone());
|
||||||
let mut valid_signatures: HashSet<String> = Default::default();
|
let mut valid_signatures: HashSet<String> = Default::default();
|
||||||
let plain = pk_decrypt(
|
let plain = pk_decrypt(
|
||||||
CTEXT_UNSIGNED.as_bytes(),
|
CTEXT_UNSIGNED.as_bytes().to_vec(),
|
||||||
&decrypt_keyring,
|
decrypt_keyring,
|
||||||
&sig_check_keyring,
|
sig_check_keyring,
|
||||||
Some(&mut valid_signatures),
|
Some(&mut valid_signatures),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(plain, CLEARTEXT);
|
assert_eq!(plain, CLEARTEXT);
|
||||||
assert_eq!(valid_signatures.len(), 0);
|
assert_eq!(valid_signatures.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[async_std::test]
|
||||||
fn test_decrypt_signed_no_sigret() {
|
async fn test_decrypt_signed_no_sigret() {
|
||||||
// Check decrypting signed cyphertext without providing the HashSet for signatures.
|
// Check decrypting signed cyphertext without providing the HashSet for signatures.
|
||||||
let mut decrypt_keyring = Keyring::default();
|
let mut decrypt_keyring = Keyring::default();
|
||||||
decrypt_keyring.add_ref(&KEYS.bob_secret);
|
decrypt_keyring.add(KEYS.bob_secret.clone());
|
||||||
let mut sig_check_keyring = Keyring::default();
|
let mut sig_check_keyring = Keyring::default();
|
||||||
sig_check_keyring.add_ref(&KEYS.alice_public);
|
sig_check_keyring.add(KEYS.alice_public.clone());
|
||||||
let plain = pk_decrypt(
|
let plain = pk_decrypt(
|
||||||
CTEXT_SIGNED.as_bytes(),
|
CTEXT_SIGNED.as_bytes().to_vec(),
|
||||||
&decrypt_keyring,
|
decrypt_keyring,
|
||||||
&sig_check_keyring,
|
sig_check_keyring,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(plain, CLEARTEXT);
|
assert_eq!(plain, CLEARTEXT);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user