diff options
Diffstat (limited to 'melib/src/gpgme/mod.rs')
-rw-r--r-- | melib/src/gpgme/mod.rs | 331 |
1 files changed, 305 insertions, 26 deletions
diff --git a/melib/src/gpgme/mod.rs b/melib/src/gpgme/mod.rs index e7d334d6..b0674619 100644 --- a/melib/src/gpgme/mod.rs +++ b/melib/src/gpgme/mod.rs @@ -25,11 +25,16 @@ use crate::email::{ }; use crate::error::{ErrorKind, IntoMeliError, MeliError, Result, ResultIntoMeliError}; use futures::FutureExt; +use serde::{ + de::{self, Deserialize}, + Deserializer, Serialize, Serializer, +}; use smol::Async; use std::borrow::Cow; use std::collections::HashMap; use std::ffi::{CStr, CString, OsStr}; use std::future::Future; +use std::io::Seek; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::Path; @@ -66,6 +71,7 @@ pub enum GpgmeFlag { ///"auto-key-retrieve" AutoKeyRetrieve, OfflineMode, + AsciiArmor, } bitflags! { @@ -91,6 +97,85 @@ bitflags! { } } +impl<'de> Deserialize<'de> for LocateKey { + fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + if let Ok(s) = <String>::deserialize(deserializer) { + LocateKey::from_string_de::<'de, D, String>(s) + } else { + Err(de::Error::custom("LocateKey value must be a string.")) + } + } +} + +impl Serialize for LocateKey { + fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl LocateKey { + pub fn from_string_de<'de, D, T: AsRef<str>>(s: T) -> std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + Ok(match s.as_ref().trim() { + s if s.eq_ignore_ascii_case("cert") => LocateKey::CERT, + s if s.eq_ignore_ascii_case("pka") => LocateKey::PKA, + s if s.eq_ignore_ascii_case("dane") => LocateKey::DANE, + s if s.eq_ignore_ascii_case("wkd") => LocateKey::WKD, + s if s.eq_ignore_ascii_case("ldap") => LocateKey::LDAP, + s if s.eq_ignore_ascii_case("keyserver") => LocateKey::KEYSERVER, + s if s.eq_ignore_ascii_case("keyserver-url") => LocateKey::KEYSERVER_URL, + s if s.eq_ignore_ascii_case("local") => LocateKey::LOCAL, + combination if combination.contains(",") => { + let mut ret = LocateKey::NODEFAULT; + for c in combination.trim().split(",") { + ret |= Self::from_string_de::<'de, D, &str>(c.trim())?; + } + ret + } + _ => { + return Err(de::Error::custom( + r#"Takes valid auto-key-locate GPG values: "cert", "pka", "dane", "wkd", "ldap", "keyserver", "keyserver-URL", "local", "nodefault""#, + )) + } + }) + } +} + +impl std::fmt::Display for LocateKey { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + if *self == LocateKey::NODEFAULT { + write!(fmt, "clear,nodefault") + } else { + let mut accum = String::new(); + macro_rules! is_set { + ($flag:expr, $string:literal) => {{ + if self.intersects($flag) { + accum.push_str($string); + accum.push_str(","); + } + }}; + } + is_set!(LocateKey::CERT, "cert"); + is_set!(LocateKey::PKA, "pka"); + is_set!(LocateKey::WKD, "wkd"); + is_set!(LocateKey::LDAP, "ldap"); + is_set!(LocateKey::KEYSERVER, "keyserver"); + is_set!(LocateKey::KEYSERVER_URL, "keyserver-url"); + is_set!(LocateKey::LOCAL, "local"); + accum.pop(); + write!(fmt, "{}", accum) + } + } +} + struct IoState { max_idx: usize, ops: HashMap<usize, GpgmeFd>, @@ -187,6 +272,7 @@ impl Context { }; ret.set_flag(GpgmeFlag::AutoKeyRetrieve, false)?; ret.set_flag(GpgmeFlag::OfflineMode, true)?; + ret.set_flag(GpgmeFlag::AsciiArmor, true)?; ret.set_auto_key_locate(LocateKey::LOCAL)?; Ok(ret) } @@ -221,12 +307,21 @@ impl Context { }; return Ok(()); } + GpgmeFlag::AsciiArmor => { + unsafe { + call!(&self.inner.lib, gpgme_set_armor)( + self.inner.inner.as_ptr(), + if value { 1 } else { 0 }, + ); + }; + return Ok(()); + } }; const VALUE_ON: &[u8; 2] = b"1\0"; const VALUE_OFF: &[u8; 2] = b"0\0"; let raw_flag = match flag { GpgmeFlag::AutoKeyRetrieve => c_string_literal!("auto-key-retrieve"), - GpgmeFlag::OfflineMode => unreachable!(), + GpgmeFlag::AsciiArmor | GpgmeFlag::OfflineMode => unreachable!(), }; self.set_flag_inner( raw_flag, @@ -249,6 +344,11 @@ impl Context { call!(&self.inner.lib, gpgme_get_offline)(self.inner.inner.as_ptr()) > 0 }); } + GpgmeFlag::AsciiArmor => { + return Ok(unsafe { + call!(&self.inner.lib, gpgme_get_armor)(self.inner.inner.as_ptr()) > 0 + }); + } }; let val = self.get_flag_inner(raw_flag); Ok(!val.is_null()) @@ -259,23 +359,7 @@ impl Context { if val == LocateKey::NODEFAULT { self.set_flag_inner(auto_key_locate, c_string_literal!("clear,nodefault")) } else { - let mut accum = String::new(); - macro_rules! is_set { - ($flag:expr, $string:literal) => {{ - if val.intersects($flag) { - accum.push_str($string); - accum.push_str(","); - } - }}; - } - is_set!(LocateKey::CERT, "cert"); - is_set!(LocateKey::PKA, "pka"); - is_set!(LocateKey::WKD, "wkd"); - is_set!(LocateKey::LDAP, "ldap"); - is_set!(LocateKey::KEYSERVER, "keyserver"); - is_set!(LocateKey::KEYSERVER_URL, "keyserver-url"); - is_set!(LocateKey::LOCAL, "local"); - accum.pop(); + let mut accum = val.to_string(); accum.push('\0'); self.set_flag_inner( auto_key_locate, @@ -592,11 +676,34 @@ impl Context { }) } - pub fn sign<'d>( + pub fn sign( &mut self, - text: &'d mut Data, - ) -> Result<impl Future<Output = Result<()>> + 'd> { - let sig = std::ptr::null_mut(); + sign_keys: Vec<Key>, + mut text: Data, + ) -> Result<impl Future<Output = Result<Vec<u8>>>> { + if sign_keys.is_empty() { + return Err( + MeliError::new("gpgme: Call to sign() with zero keys.").set_kind(ErrorKind::Bug) + ); + } + let mut sig: gpgme_data_t = std::ptr::null_mut(); + unsafe { + gpgme_error_try( + &self.inner.lib, + call!(&self.inner.lib, gpgme_data_new)(&mut sig), + )?; + call!(&self.inner.lib, gpgme_signers_clear)(self.inner.inner.as_ptr()); + for k in sign_keys { + gpgme_error_try( + &self.inner.lib, + call!(&self.inner.lib, gpgme_signers_add)( + self.inner.inner.as_ptr(), + k.inner.inner.as_ptr(), + ), + )?; + } + } + unsafe { gpgme_error_try( &self.inner.lib, @@ -608,6 +715,13 @@ impl Context { ), )?; } + let mut sig = Data { + lib: self.inner.lib.clone(), + kind: DataKind::Memory, + inner: core::ptr::NonNull::new(sig).ok_or_else(|| { + MeliError::new("internal libgpgme error").set_kind(ErrorKind::Bug) + })?, + }; let io_state = self.io_state.clone(); let io_state_lck = self.io_state.lock().unwrap(); @@ -672,13 +786,17 @@ impl Context { }; let _ = rcv.recv().await; let io_state_lck = io_state.lock().unwrap(); - let ret = io_state_lck + io_state_lck .done .lock() .unwrap() .take() - .unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error"))); - ret + .unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error")))?; + sig.seek(std::io::SeekFrom::Start(0)) + .chain_err_summary(|| { + "libgpgme error: could not perform seek on signature data object" + })?; + Ok(sig.into_bytes()?) }) } @@ -830,7 +948,6 @@ impl Context { recipient_iter = (*recipient_iter).next; } } - use std::io::Seek; /* Rewind cursor */ plain .seek(std::io::SeekFrom::Start(0)) @@ -846,6 +963,168 @@ impl Context { )) }) } + + pub fn encrypt( + &mut self, + sign_keys: Option<Vec<Key>>, + encrypt_keys: Vec<Key>, + mut plain: Data, + ) -> Result<impl Future<Output = Result<Vec<u8>>> + Send> { + if encrypt_keys.is_empty() { + return Err( + MeliError::new("gpgme: Call to encrypt() with zero keys.").set_kind(ErrorKind::Bug) + ); + } + unsafe { + call!(&self.inner.lib, gpgme_signers_clear)(self.inner.inner.as_ptr()); + } + + let also_sign: bool = if let Some(keys) = sign_keys { + if keys.is_empty() { + false + } else { + for k in keys { + unsafe { + gpgme_error_try( + &self.inner.lib, + call!(&self.inner.lib, gpgme_signers_add)( + self.inner.inner.as_ptr(), + k.inner.inner.as_ptr(), + ), + )?; + } + } + true + } + } else { + false + }; + let mut cipher: gpgme_data_t = std::ptr::null_mut(); + let mut raw_keys: Vec<gpgme_key_t> = Vec::with_capacity(encrypt_keys.len() + 1); + raw_keys.extend(encrypt_keys.iter().map(|k| k.inner.inner.as_ptr())); + raw_keys.push(std::ptr::null_mut()); + unsafe { + gpgme_error_try( + &self.inner.lib, + call!(&self.inner.lib, gpgme_data_new)(&mut cipher), + )?; + gpgme_error_try( + &self.inner.lib, + if also_sign { + call!(&self.inner.lib, gpgme_op_encrypt_sign_start)( + self.inner.inner.as_ptr(), + raw_keys.as_mut_ptr(), + gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_ENCRYPT_TO + | gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_COMPRESS, + plain.inner.as_mut(), + cipher, + ) + } else { + call!(&self.inner.lib, gpgme_op_encrypt_start)( + self.inner.inner.as_ptr(), + raw_keys.as_mut_ptr(), + gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_ENCRYPT_TO + | gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_COMPRESS, + plain.inner.as_mut(), + cipher, + ) + }, + )?; + } + let mut cipher = Data { + lib: self.inner.lib.clone(), + kind: DataKind::Memory, + inner: core::ptr::NonNull::new(cipher).ok_or_else(|| { + MeliError::new("internal libgpgme error").set_kind(ErrorKind::Bug) + })?, + }; + + let ctx = self.inner.clone(); + let io_state = self.io_state.clone(); + let io_state_lck = self.io_state.lock().unwrap(); + let done = io_state_lck.done.clone(); + let fut = io_state_lck + .ops + .values() + .map(|a| Async::new(a.clone()).unwrap()) + .collect::<Vec<Async<GpgmeFd>>>(); + drop(io_state_lck); + Ok(async move { + futures::future::join_all(fut.iter().map(|fut| { + let done = done.clone(); + if fut.get_ref().write { + futures::future::select( + fut.get_ref().receiver.recv().boxed(), + fut.write_with(move |_f| { + if done.lock().unwrap().is_some() { + return Ok(()); + } + unsafe { + (fut.get_ref().fnc.unwrap())( + fut.get_ref().fnc_data, + fut.get_ref().fd, + ) + }; + if done.lock().unwrap().is_none() { + return Err(std::io::ErrorKind::WouldBlock.into()); + } + Ok(()) + }) + .boxed(), + ) + .boxed() + } else { + futures::future::select( + fut.get_ref().receiver.recv().boxed(), + fut.read_with(move |_f| { + if done.lock().unwrap().is_some() { + return Ok(()); + } + unsafe { + (fut.get_ref().fnc.unwrap())( + fut.get_ref().fnc_data, + fut.get_ref().fd, + ) + }; + if done.lock().unwrap().is_none() { + return Err(std::io::ErrorKind::WouldBlock.into()); + } + Ok(()) + }) + .boxed(), + ) + .boxed() + } + })) + .await; + let rcv = { + let io_state_lck = io_state.lock().unwrap(); + io_state_lck.receiver.clone() + }; + let _ = rcv.recv().await; + let io_state_lck = io_state.lock().unwrap(); + io_state_lck + .done + .lock() + .unwrap() + .take() + .unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error")))?; + + let encrypt_result = + unsafe { call!(&ctx.lib, gpgme_op_encrypt_result)(ctx.inner.as_ptr()) }; + if encrypt_result.is_null() { + return Err(MeliError::new( + "Unspecified libgpgme error: gpgme_op_encrypt_result returned NULL.", + ) + .set_err_kind(ErrorKind::External)); + } + /* Rewind cursor */ + cipher + .seek(std::io::SeekFrom::Start(0)) + .chain_err_summary(|| "libgpgme error: could not perform seek on plain text")?; + Ok(cipher.into_bytes()?) + }) + } } fn gpgme_error_try(lib: &libloading::Library, error_code: GpgmeError) -> Result<()> { |