gpgme: refactor Rust interface, add tests

- Refactor gpgme wrapper interface to use more robust reference counting
  for the gpgme context object
- Add SAFETY comments to unsafe {} blocks
- Add tests

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
pull/473/head
Manos Pitsidianakis 2 months ago
parent 87d2cec9d9
commit a7c73fc8cf
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -55,8 +55,8 @@ impl KeySelection {
allow_remote_lookup: ActionFlag,
context: &Context,
) -> Result<Self> {
use melib::gpgme::*;
let mut ctx = Context::new()?;
use melib::gpgme::{self, *};
let mut ctx = gpgme::Context::new()?;
if local {
ctx.set_auto_key_locate(LocateKey::LOCAL)?;
} else {
@ -145,7 +145,11 @@ impl Component for KeySelection {
}
} else {
*self = Self::Error {
err: Error::new(format!("No keys found for {}.", pattern)),
err: if pattern.is_empty() {
Error::new("No keys found.")
} else {
Error::new(format!("No keys found for {}.", pattern))
},
id,
}
}
@ -267,3 +271,178 @@ impl Default for GpgComposeState {
}
}
}
#[cfg(test)]
mod tests {
use std::{borrow::Cow, ffi::CString, thread::sleep, time::Duration};
use melib::gpgme::{EngineInfo, LocateKey, Protocol};
use sealed_test::prelude::*;
use super::*;
impl KeySelection {
fn new_mock(
secret: bool,
local: bool,
pattern: String,
allow_remote_lookup: ActionFlag,
context: &Context,
ctx: &mut melib::gpgme::Context,
) -> Result<Self> {
if local {
ctx.set_auto_key_locate(LocateKey::LOCAL)?;
} else {
ctx.set_auto_key_locate(LocateKey::WKD | LocateKey::LOCAL)?;
}
let job = ctx.keylist(secret, Some(pattern.clone()))?;
let handle = context.main_loop_handler.job_executor.spawn(
"gpg::keylist".into(),
job,
IsAsync::Blocking,
);
let mut progress_spinner = ProgressSpinner::new(8, context);
progress_spinner.start();
Ok(Self::LoadingKeys {
handle,
secret,
local,
pattern,
allow_remote_lookup,
progress_spinner,
})
}
}
const PUBKEY: &[u8]=b"-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: GnuPG v2.1.0-gitb3c71eb (GNU/Linux)\r\n\r\nmQGiBDo41NoRBADSfQazKGYf8nokq6zUKH/6INtV6MypSzSGmX2XErnARkIIPPYj\r\ncQRQ8zCbGV7ZU2ezVbzhFLUSJveE8PZUzzCrLp1O2NSyBTRcR5HVSXW95nJfY8eV\r\npOvZRAKul0BVLh81kYTsrfzaaCjh9VWNP26LoeN2r+PjZyktXe7gM3C4SwCgoTxK\r\nWUVi9HoT2HCLY7p7oig5hEcEALdCJal0UYomX3nJapIVLVZg3vkidr1RICYMb2vz\r\n58i17h8sxEtobD1vdIKNejulntaRAXs4n0tDYD9z7pRlwG1CLz1R9WxYzeOOqUDr\r\nfnVXdmU8L/oVWABat8v1V7QQhjMMf+41fuzVwDMMGqjVPLhu4X6wp3A8uyM3YDnQ\r\nVMN1A/4n2G5gHoOvjqxn8Ch5tBAdMGfO8gH4RjQOwzm2R1wPQss/yzUN1+tlMZGX\r\nK2dQ2FCWC/hDUSNaEQRlI15wxxBNZ2RQwlzE2A8v113DpvyzOtv0QO95gJ1teCXC\r\n7j/BN9asgHaBBc39JLO/TcpuI7Hf8PQ5VcP2F0UE3lczGhXbLLRESm9lIFJhbmRv\r\nbSBIYWNrZXIgKHRlc3Qga2V5IHdpdGggcGFzc3BocmFzZSAiYWJjIikgPGpvZUBl\r\neGFtcGxlLmNvbT6IYgQTEQIAIgUCTbdXqQIbIwYLCQgHAwIGFQgCCQoLBBYCAwEC\r\nHgECF4AACgkQr4IkT5zZ/VUcCACfQvSPi//9/gBv8SVrK6O4DiyD+jAAn3LEnfF1\r\n4j6MjwlqXTqol2VgQn1yuQENBDo41N0QBACedJb7Qhm50JSPe1V+rSZKLHT5nc3l\r\n2k1n7//wNsJkgDW2J7snIRjGtSzeNxMPh+hVzFidzAf3sbOlARQoBrMPPKpnJWtm\r\n6LEDf2lSwO36l0/bo6qDRmiFRJoHWytTJEjxVwRclVt4bXqHfNw9FKhZZbcKeAN2\r\noHgmBVSU6edHdwADBQP+OGAkEG4PcfSb8x191R+wkV/q2hA5Ay9z289Dx2rO28CO\r\n4M2fhhcjSmgr6x0DsrkfESCiG47UGJ169eu+QqJwk3HiF4crGN9rE5+VelBVFtrd\r\nMWkX2rPLGQWyw8iCZKbeH8g/ujmkaLovSmalzDcLe4v1xSLaP7Fnfzit0iIGZAGI\r\nRgQYEQIABgUCOjjU3QAKCRCvgiRPnNn9VVSaAJ9+rj1lIQnRl20i8Rom2Hwbe3re\r\n9QCfSYFnkZUw0yKF2DfCfqrDzdGAsbaIRgQYEQIABgUCOjjU3gAKCRCvgiRPnNn9\r\nVe4iAJ9FrGMlFR7s+GWf1scTeeyrthKrPQCfSpc/Yps72aFI7hPfyIa9MuerVZ4=\r\n=QRit\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n";
#[sealed_test]
fn test_gpg_verify_sig() {
let tempdir = tempfile::tempdir().unwrap();
{
#[allow(unused_unsafe)]
unsafe {
std::env::set_var("GNUPGHOME", tempdir.path());
}
#[allow(unused_unsafe)]
unsafe {
std::env::set_var("GPG_AGENT_INFO", "");
}
}
let mut ctx = Context::new_mock(&tempdir);
let mut gpgme_ctx = match melib::gpgme::Context::new() {
Ok(v) => v,
Err(err) if err.kind.is_not_found() => {
eprintln!("INFO: libgpgme could not be loaded, skipping this test.");
return;
}
err => err.unwrap(),
};
let current_engine_info = gpgme_ctx.engine_info().unwrap();
let prev_len = current_engine_info.len();
let Some(EngineInfo {
file_name: Some(engine_file_name),
..
}) = current_engine_info
.into_iter()
.find(|eng| eng.protocol == Protocol::OpenPGP)
else {
eprintln!("WARN: No openpg protocol engine returned from gpgme.");
return;
};
gpgme_ctx
.set_engine_info(
Protocol::OpenPGP,
Some(Cow::Owned(CString::new(engine_file_name).unwrap())),
Some(Cow::Owned(
CString::new(tempdir.path().display().to_string()).unwrap(),
)),
)
.unwrap();
let new_engine_info = gpgme_ctx.engine_info().unwrap();
assert_eq!(
new_engine_info.len(),
prev_len,
"new_engine_info was expected to have {} entry/ies but has {}: {:#?}",
prev_len,
new_engine_info.len(),
new_engine_info
);
assert_eq!(
new_engine_info[0].home_dir,
Some(tempdir.path().display().to_string()),
"new_engine_info was expected to have temp dir as home_dir but has: {:#?}",
new_engine_info[0].home_dir
);
let mut pubkey_data = Some(gpgme_ctx.new_data_mem(PUBKEY).unwrap());
for _ in 0..2 {
let mut key_sel = KeySelection::new_mock(
false,
true,
"".to_string(),
false.into(),
&ctx,
&mut gpgme_ctx,
)
.unwrap();
let component_id = key_sel.id();
for _ in 0..2 {
sleep(Duration::from_secs(2));
}
while let Ok(ev) = ctx.receiver.try_recv() {
// if !matches!(ev, ThreadEvent::UIEvent(UIEvent::Timer(_))) {
// dbg!(&ev);
// }
if let ThreadEvent::UIEvent(mut ev) = ev {
key_sel.process_event(&mut ev, &mut ctx);
} else if let ThreadEvent::JobFinished(job_id) = ev {
let mut ev = UIEvent::StatusEvent(StatusEvent::JobFinished(job_id));
key_sel.process_event(&mut ev, &mut ctx);
}
}
if let Some(pubkey_data) = pubkey_data.take() {
assert!(
matches!(
key_sel,
KeySelection::Error {
ref id,
ref err
} if *id == component_id && err.to_string() == melib::Error::new("No keys found.").to_string(),
),
"key_sel should have been an error but is: {:?}",
key_sel
);
gpgme_ctx.import_key(pubkey_data).unwrap();
} else {
let assert_key = |key: &melib::gpgme::Key| {
key.fingerprint() == "ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55"
&& key.primary_uid()
== Some(melib::Address::new(
Some("Joe Random Hacker".into()),
"joe@example.com".into(),
))
&& key.can_encrypt()
&& key.can_sign()
&& !key.secret()
&& !key.revoked()
&& !key.expired()
&& !key.invalid()
};
assert!(
matches!(
key_sel,
KeySelection::Loaded {
ref keys,
widget: _,
} if keys.len() == 1 && assert_key(&keys[0]),
),
"key_sel should have been an error but is: {:?}",
key_sel
);
}
}
}
}

@ -143,7 +143,7 @@ pub struct Context {
pub realized: IndexMap<ComponentId, Option<ComponentId>>,
pub unrealized: IndexSet<ComponentId>,
pub main_loop_handler: MainLoopHandler,
receiver: Receiver<ThreadEvent>,
pub receiver: Receiver<ThreadEvent>,
input_thread: InputHandler,
current_dir: PathBuf,
/// Children processes

@ -19,31 +19,62 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::io::{self, Read, Seek, Write};
use std::{
ffi::{c_int, c_void},
io::{self, Read, Seek, Write},
mem::ManuallyDrop,
ptr::NonNull,
};
use super::*;
#[derive(Debug)]
#[repr(C)]
struct TagData {
idx: usize,
fd: ::std::os::raw::c_int,
fd: c_int,
io_state: Arc<Mutex<IoState>>,
}
/// Wrapper type to automatically leak iostate Arc on drop.
#[repr(transparent)]
struct IoStateWrapper(ManuallyDrop<Arc<Mutex<IoState>>>);
impl IoStateWrapper {
// SAFETY: `ptr` must be the iostate reference that was leaked in
// `Context::new`.
unsafe fn from_raw(ptr: *mut c_void) -> Self {
let io_state: Arc<Mutex<IoState>> =
unsafe { Arc::from_raw(ptr.cast_const().cast::<Mutex<IoState>>()) };
Self(ManuallyDrop::new(io_state))
}
}
impl Drop for IoStateWrapper {
fn drop(&mut self) {
// SAFETY: struct unit value is ManuallyDrop, so no Drop impls are called on the
// uninit value.
let inner = unsafe { ManuallyDrop::take(&mut self.0) };
let _ = Arc::into_raw(inner);
}
}
///
/// # Safety
/// .
///
/// Must only be used if `add_priv` in `gpgme_io_cbs` is `Arc<Mutex<IoState>>`.
pub unsafe extern "C" fn gpgme_register_io_cb(
data: *mut ::std::os::raw::c_void,
fd: ::std::os::raw::c_int,
dir: ::std::os::raw::c_int,
data: *mut c_void,
fd: c_int,
dir: c_int,
fnc: gpgme_io_cb_t,
fnc_data: *mut ::std::os::raw::c_void,
tag: *mut *mut ::std::os::raw::c_void,
fnc_data: *mut c_void,
tag: *mut *mut c_void,
) -> gpgme_error_t {
let io_state: Arc<Mutex<IoState>> = unsafe { Arc::from_raw(data as *const _) };
let io_state_copy = io_state.clone();
let mut io_state_lck = io_state.lock().unwrap();
// SAFETY: This is the iostate reference that was leaked in `Context::new`.
let io_state: IoStateWrapper = unsafe { IoStateWrapper::from_raw(data) };
let io_state_copy = Arc::clone(&io_state.0);
if let Ok(mut io_state_lck) = io_state.0.lock() {
let idx = io_state_lck.max_idx;
io_state_lck.max_idx += 1;
let (sender, receiver) = smol::channel::unbounded();
@ -57,59 +88,95 @@ pub unsafe extern "C" fn gpgme_register_io_cb(
receiver,
io_state: io_state_copy.clone(),
};
{
let tag_data = Arc::into_raw(Arc::new(TagData {
idx,
fd,
io_state: io_state_copy,
}));
unsafe { std::ptr::write(tag, tag_data as *mut _) };
// SAFETY: tag_data is a valid pointer from the caller by contract, the tag_data
// allocation is leaked and will only be accessed when the cb is removed.
unsafe { std::ptr::write(tag, tag_data.cast_mut().cast::<c_void>()) };
}
io_state_lck.ops.insert(idx, gpgfd);
drop(io_state_lck);
let _ = Arc::into_raw(io_state);
}
0
}
///
/// # Safety
/// .
pub unsafe extern "C" fn gpgme_remove_io_cb(tag: *mut ::std::os::raw::c_void) {
let tag_data: Arc<TagData> = unsafe { Arc::from_raw(tag as *const _) };
let mut io_state_lck = tag_data.io_state.lock().unwrap();
let fd = io_state_lck.ops.remove(&tag_data.idx).unwrap();
fd.sender.try_send(()).unwrap();
drop(io_state_lck);
let _ = Arc::into_raw(tag_data);
///
/// The callback must have been registered with a `TagData` value, therefore
/// `tag` can only hold a valid `Arc<TagData>` allocation. We assume that gpgme
/// will only call the remove cb with this tag data once.
pub unsafe extern "C" fn gpgme_remove_io_cb(tag: *mut c_void) {
let tag_data: Arc<TagData> = unsafe { Arc::from_raw(tag.cast_const().cast::<TagData>()) };
let gpgfd: GpgmeFd = {
let Ok(mut io_state_lck) = tag_data.io_state.lock() else {
// mutex is poisoned, bail out.
return;
};
io_state_lck.ops.remove(&tag_data.idx).unwrap_or_else(|| {
panic!(
"gpgme_remove_io_cb called with tag_data {:?}, but idx {} is not included in \
io_state ops. This is a bug.",
tag_data, tag_data.idx
)
})
};
_ = gpgfd.sender.try_send(());
}
///
/// # Safety
/// .
///
/// Must only be used if `add_priv` in `gpgme_io_cbs` is `Arc<Mutex<IoState>>`.
pub unsafe extern "C" fn gpgme_event_io_cb(
data: *mut ::std::os::raw::c_void,
type_: gpgme_event_io_t,
type_data: *mut ::std::os::raw::c_void,
data: *mut c_void,
r#type: gpgme_event_io_t,
type_data: *mut c_void,
) {
if type_ == gpgme_event_io_t_GPGME_EVENT_DONE {
let err = type_data as gpgme_io_event_done_data_t;
let io_state: Arc<Mutex<IoState>> = unsafe { Arc::from_raw(data as *const _) };
let io_state_lck = io_state.lock().unwrap();
io_state_lck.sender.try_send(()).unwrap();
*io_state_lck.done.lock().unwrap() =
Some(gpgme_error_try(&io_state_lck.lib, unsafe { (*err).err }));
drop(io_state_lck);
let _ = Arc::into_raw(io_state);
} else if type_ == gpgme_event_io_t_GPGME_EVENT_NEXT_KEY {
if let Some(inner) = std::ptr::NonNull::new(type_data as gpgme_key_t) {
let io_state: Arc<Mutex<IoState>> = unsafe { Arc::from_raw(data as *const _) };
let io_state_lck = io_state.lock().unwrap();
io_state_lck
.key_sender
.try_send(KeyInner { inner })
.unwrap();
drop(io_state_lck);
let _ = Arc::into_raw(io_state);
if r#type == gpgme_event_io_t_GPGME_EVENT_START {
return;
}
// SAFETY: This is the iostate reference that was leaked in `Context::new`.
let io_state: IoStateWrapper = unsafe { IoStateWrapper::from_raw(data) };
if r#type == gpgme_event_io_t_GPGME_EVENT_DONE {
let Some(status) = NonNull::new(type_data.cast::<gpgme_io_event_done_data>()) else {
log::error!("gpgme_event_io_cb DONE event with NULL type_data. This is a gpgme bug.",);
return;
};
// SAFETY: since type is DONE and type_data is not NULL, status is a valid
// gpgme_io_event_done_data pointer.
let err = unsafe { status.as_ref().err };
if let Ok(io_state_lck) = io_state.0.lock() {
_ = io_state_lck.sender.try_send(());
if let Ok(mut done) = io_state_lck.done.lock() {
*done = Some(gpgme_error_try(&io_state_lck.lib, err));
}
}
return;
}
if r#type == gpgme_event_io_t_GPGME_EVENT_NEXT_KEY {
let Some(ptr) = NonNull::new(type_data.cast::<_gpgme_key>()) else {
log::error!(
"gpgme_event_io_cb NEXT_KEY event with NULL type_data. This is a gpgme bug.",
);
return;
};
if let Ok(io_state_lck) = io_state.0.lock() {
_ = io_state_lck.key_sender.try_send(KeyInner { ptr });
}
return;
}
log::error!(
"gpgme_event_io_cb called with unexpected event type: {}",
r#type
);
}
impl Read for Data {

@ -0,0 +1,158 @@
/*
* melib - gpgme module
*
* Copyright 2020 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
// [ref:DEBT] segfaults on libgpgme code can crash the entire app; it should be
// handled.
use std::{borrow::Cow, ffi::CStr, ptr::NonNull, sync::Arc};
use super::bindings::*;
use crate::email::Address;
#[derive(Clone)]
pub struct KeyInner {
pub ptr: NonNull<_gpgme_key>,
}
unsafe impl Send for KeyInner {}
unsafe impl Sync for KeyInner {}
pub struct Key {
pub inner: KeyInner,
pub lib: Arc<libloading::Library>,
}
unsafe impl Send for Key {}
unsafe impl Sync for Key {}
impl Clone for Key {
fn clone(&self) -> Self {
let lib = self.lib.clone();
unsafe {
call!(&self.lib, gpgme_key_ref)(self.inner.ptr.as_ptr());
}
Self {
inner: self.inner.clone(),
lib,
}
}
}
impl Key {
#[inline]
pub fn new(inner: KeyInner, lib: Arc<libloading::Library>) -> Self {
Self { inner, lib }
}
pub fn primary_uid(&self) -> Option<Address> {
unsafe {
if (self.inner.ptr.as_ref()).uids.is_null() {
return None;
}
let uid = self.inner.ptr.as_ref().uids;
if (*uid).name.is_null() && (*uid).email.is_null() {
None
} else if (*uid).name.is_null() {
Some(Address::new(
None,
CStr::from_ptr((*uid).email).to_string_lossy().to_string(),
))
} else if (*uid).email.is_null() {
Some(Address::new(
None,
CStr::from_ptr((*uid).name).to_string_lossy().to_string(),
))
} else {
Some(Address::new(
Some(CStr::from_ptr((*uid).name).to_string_lossy().to_string()),
CStr::from_ptr((*uid).email).to_string_lossy().to_string(),
))
}
}
}
pub fn revoked(&self) -> bool {
unsafe { self.inner.ptr.as_ref().revoked() > 0 }
}
pub fn expired(&self) -> bool {
unsafe { self.inner.ptr.as_ref().expired() > 0 }
}
pub fn disabled(&self) -> bool {
unsafe { self.inner.ptr.as_ref().disabled() > 0 }
}
pub fn invalid(&self) -> bool {
unsafe { self.inner.ptr.as_ref().invalid() > 0 }
}
pub fn can_encrypt(&self) -> bool {
unsafe { self.inner.ptr.as_ref().can_encrypt() > 0 }
}
pub fn can_sign(&self) -> bool {
unsafe { self.inner.ptr.as_ref().can_sign() > 0 }
}
pub fn secret(&self) -> bool {
unsafe { self.inner.ptr.as_ref().secret() > 0 }
}
pub fn fingerprint(&self) -> Cow<'_, str> {
// SAFETY: self.inner.ptr is valid.
let fpr = unsafe { self.inner.ptr.as_ref().fpr };
let Some(fpr_pr) = NonNull::new(fpr) else {
return Cow::Borrowed("");
};
unsafe { CStr::from_ptr(fpr_pr.as_ptr()) }.to_string_lossy()
}
}
impl std::fmt::Debug for Key {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct(crate::identify!(Key))
.field("fingerprint", &self.fingerprint())
.field("uid", &self.primary_uid())
.field("can_encrypt", &self.can_encrypt())
.field("can_sign", &self.can_sign())
.field("secret", &self.secret())
.field("revoked", &self.revoked())
.field("expired", &self.expired())
.field("invalid", &self.invalid())
.finish_non_exhaustive()
}
}
impl PartialEq for Key {
fn eq(&self, other: &Self) -> bool {
self.fingerprint() == other.fingerprint()
}
}
impl Eq for Key {}
impl Drop for Key {
fn drop(&mut self) {
unsafe {
call!(&self.lib, gpgme_key_unref)(self.inner.ptr.as_ptr());
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,153 @@
//
// meli
//
// Copyright 2024 Emmanouil Pitsidianakis <manos@pitsidianak.is>
//
// This file is part of meli.
//
// meli is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// meli is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with meli. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: EUPL-1.2 OR GPL-3.0-or-later
use std::{borrow::Cow, ffi::CString, future::Future};
use sealed_test::prelude::*;
use crate::{
gpgme::{Context, EngineInfo, Key, LocateKey, Protocol},
Address, Result,
};
const PUBKEY: &[u8]=b"-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: GnuPG v2.1.0-gitb3c71eb (GNU/Linux)\r\n\r\nmQGiBDo41NoRBADSfQazKGYf8nokq6zUKH/6INtV6MypSzSGmX2XErnARkIIPPYj\r\ncQRQ8zCbGV7ZU2ezVbzhFLUSJveE8PZUzzCrLp1O2NSyBTRcR5HVSXW95nJfY8eV\r\npOvZRAKul0BVLh81kYTsrfzaaCjh9VWNP26LoeN2r+PjZyktXe7gM3C4SwCgoTxK\r\nWUVi9HoT2HCLY7p7oig5hEcEALdCJal0UYomX3nJapIVLVZg3vkidr1RICYMb2vz\r\n58i17h8sxEtobD1vdIKNejulntaRAXs4n0tDYD9z7pRlwG1CLz1R9WxYzeOOqUDr\r\nfnVXdmU8L/oVWABat8v1V7QQhjMMf+41fuzVwDMMGqjVPLhu4X6wp3A8uyM3YDnQ\r\nVMN1A/4n2G5gHoOvjqxn8Ch5tBAdMGfO8gH4RjQOwzm2R1wPQss/yzUN1+tlMZGX\r\nK2dQ2FCWC/hDUSNaEQRlI15wxxBNZ2RQwlzE2A8v113DpvyzOtv0QO95gJ1teCXC\r\n7j/BN9asgHaBBc39JLO/TcpuI7Hf8PQ5VcP2F0UE3lczGhXbLLRESm9lIFJhbmRv\r\nbSBIYWNrZXIgKHRlc3Qga2V5IHdpdGggcGFzc3BocmFzZSAiYWJjIikgPGpvZUBl\r\neGFtcGxlLmNvbT6IYgQTEQIAIgUCTbdXqQIbIwYLCQgHAwIGFQgCCQoLBBYCAwEC\r\nHgECF4AACgkQr4IkT5zZ/VUcCACfQvSPi//9/gBv8SVrK6O4DiyD+jAAn3LEnfF1\r\n4j6MjwlqXTqol2VgQn1yuQENBDo41N0QBACedJb7Qhm50JSPe1V+rSZKLHT5nc3l\r\n2k1n7//wNsJkgDW2J7snIRjGtSzeNxMPh+hVzFidzAf3sbOlARQoBrMPPKpnJWtm\r\n6LEDf2lSwO36l0/bo6qDRmiFRJoHWytTJEjxVwRclVt4bXqHfNw9FKhZZbcKeAN2\r\noHgmBVSU6edHdwADBQP+OGAkEG4PcfSb8x191R+wkV/q2hA5Ay9z289Dx2rO28CO\r\n4M2fhhcjSmgr6x0DsrkfESCiG47UGJ169eu+QqJwk3HiF4crGN9rE5+VelBVFtrd\r\nMWkX2rPLGQWyw8iCZKbeH8g/ujmkaLovSmalzDcLe4v1xSLaP7Fnfzit0iIGZAGI\r\nRgQYEQIABgUCOjjU3QAKCRCvgiRPnNn9VVSaAJ9+rj1lIQnRl20i8Rom2Hwbe3re\r\n9QCfSYFnkZUw0yKF2DfCfqrDzdGAsbaIRgQYEQIABgUCOjjU3gAKCRCvgiRPnNn9\r\nVe4iAJ9FrGMlFR7s+GWf1scTeeyrthKrPQCfSpc/Yps72aFI7hPfyIa9MuerVZ4=\r\n=QRit\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n";
const SECKEY: &[u8] = b"-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: GnuPG v2.1.0-gitb3c71eb (GNU/Linux)\r\n\r\nlQHpBDo41NoRBADSfQazKGYf8nokq6zUKH/6INtV6MypSzSGmX2XErnARkIIPPYj\r\ncQRQ8zCbGV7ZU2ezVbzhFLUSJveE8PZUzzCrLp1O2NSyBTRcR5HVSXW95nJfY8eV\r\npOvZRAKul0BVLh81kYTsrfzaaCjh9VWNP26LoeN2r+PjZyktXe7gM3C4SwCgoTxK\r\nWUVi9HoT2HCLY7p7oig5hEcEALdCJal0UYomX3nJapIVLVZg3vkidr1RICYMb2vz\r\n58i17h8sxEtobD1vdIKNejulntaRAXs4n0tDYD9z7pRlwG1CLz1R9WxYzeOOqUDr\r\nfnVXdmU8L/oVWABat8v1V7QQhjMMf+41fuzVwDMMGqjVPLhu4X6wp3A8uyM3YDnQ\r\nVMN1A/4n2G5gHoOvjqxn8Ch5tBAdMGfO8gH4RjQOwzm2R1wPQss/yzUN1+tlMZGX\r\nK2dQ2FCWC/hDUSNaEQRlI15wxxBNZ2RQwlzE2A8v113DpvyzOtv0QO95gJ1teCXC\r\n7j/BN9asgHaBBc39JLO/TcpuI7Hf8PQ5VcP2F0UE3lczGhXbLP4HAwL0A7A1a/jY\r\n6s5JxysLUpKA31U2SrKxePmkmzYSuAiValUVdfkmLRrLSwmNJSy5NcrBHGimja1O\r\nfUUmPTg465j1+vD/tERKb2UgUmFuZG9tIEhhY2tlciAodGVzdCBrZXkgd2l0aCBw\r\nYXNzcGhyYXNlICJhYmMiKSA8am9lQGV4YW1wbGUuY29tPohiBBMRAgAiBQJNt1ep\r\nAhsjBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCvgiRPnNn9VRwIAJ9C9I+L\r\n//3+AG/xJWsro7gOLIP6MACfcsSd8XXiPoyPCWpdOqiXZWBCfXKdAWAEOjjU3RAE\r\nAJ50lvtCGbnQlI97VX6tJkosdPmdzeXaTWfv//A2wmSANbYnuychGMa1LN43Ew+H\r\n6FXMWJ3MB/exs6UBFCgGsw88qmcla2bosQN/aVLA7fqXT9ujqoNGaIVEmgdbK1Mk\r\nSPFXBFyVW3hteod83D0UqFlltwp4A3ageCYFVJTp50d3AAMFA/44YCQQbg9x9Jvz\r\nHX3VH7CRX+raEDkDL3Pbz0PHas7bwI7gzZ+GFyNKaCvrHQOyuR8RIKIbjtQYnXr1\r\n675ConCTceIXhysY32sTn5V6UFUW2t0xaRfas8sZBbLDyIJkpt4fyD+6OaRoui9K\r\nZqXMNwt7i/XFIto/sWd/OK3SIgZkAf4HAwIoimqPHVJZM85dNw6JtvLKFvvmkm3X\r\nuoCUG5nU6cgk6vetUYiykuKpU4zG3mDtdZdIZf76hJJ6lZTSHH9frLy7bRYPfu/k\r\nU1AFd1T1OxENiEYEGBECAAYFAjo41N0ACgkQr4IkT5zZ/VVUmgCffq49ZSEJ0Zdt\r\nIvEaJth8G3t63vUAn0mBZ5GVMNMihdg3wn6qw83RgLG2iEYEGBECAAYFAjo41N4A\r\nCgkQr4IkT5zZ/VXuIgCfRaxjJRUe7Phln9bHE3nsq7YSqz0An0qXP2KbO9mhSO4T\r\n38iGvTLnq1We\r\n=m0YJ\r\n-----END PGP PRIVATE KEY BLOCK-----\r\n";
#[sealed_test]
fn test_gpgme_verify_sig() {
fn make_fut(
secret: bool,
local: bool,
pattern: String,
ctx: &mut Context,
) -> Result<impl Future<Output = Result<Vec<Key>>>> {
if local {
ctx.set_auto_key_locate(LocateKey::LOCAL)?;
} else {
ctx.set_auto_key_locate(LocateKey::WKD | LocateKey::LOCAL)?;
}
ctx.keylist(secret, Some(pattern))
}
let tempdir = tempfile::tempdir().unwrap();
unsafe {
std::env::set_var("GNUPGHOME", tempdir.path());
}
unsafe {
std::env::set_var("GPG_AGENT_INFO", "");
}
let mut gpgme_ctx = match Context::new() {
Ok(v) => v,
Err(err) if err.kind.is_not_found() => {
eprintln!("INFO: libgpgme could not be loaded, skipping this test.");
return;
}
err => err.unwrap(),
};
gpgme_ctx.set_protocol(Protocol::OpenPGP).unwrap();
let current_engine_info = gpgme_ctx.engine_info().unwrap();
let prev_len = current_engine_info.len();
let Some(EngineInfo {
file_name: Some(engine_file_name),
..
}) = current_engine_info
.into_iter()
.find(|eng| eng.protocol == Protocol::OpenPGP)
else {
eprintln!("WARN: No openpg protocol engine returned from gpgme.");
return;
};
gpgme_ctx
.set_engine_info(
Protocol::OpenPGP,
Some(Cow::Owned(CString::new(engine_file_name).unwrap())),
Some(Cow::Owned(
CString::new(tempdir.path().display().to_string()).unwrap(),
)),
)
.unwrap();
let new_engine_info = gpgme_ctx.engine_info().unwrap();
assert_eq!(
new_engine_info.len(),
prev_len,
"new_engine_info was expected to have {} entry/ies but has {}: {:#?}",
prev_len,
new_engine_info.len(),
new_engine_info
);
assert_eq!(
new_engine_info[0].home_dir,
Some(tempdir.path().display().to_string()),
"new_engine_info was expected to have temp dir as home_dir but has: {:#?}",
new_engine_info[0].home_dir
);
let mut pubkey_data = Some(gpgme_ctx.new_data_mem(PUBKEY).unwrap());
for _ in 0..2 {
let keylist: Vec<Key> = smol::block_on(async {
make_fut(false, true, "".to_string(), &mut gpgme_ctx)
.unwrap()
.await
})
.unwrap();
if let Some(pubkey_data) = pubkey_data.take() {
assert_eq!(
&keylist,
&[],
"keylist should have been empty but is: {:?}",
keylist
);
gpgme_ctx.import_key(pubkey_data).unwrap();
} else {
let assert_key = |key: &Key| {
key.fingerprint() == "ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55"
&& key.primary_uid()
== Some(Address::new(
Some("Joe Random Hacker".into()),
"joe@example.com".into(),
))
&& key.can_encrypt()
&& key.can_sign()
&& !key.secret()
&& !key.revoked()
&& !key.expired()
&& !key.invalid()
};
assert_eq!(keylist.len(), 1);
assert!(
assert_key(&keylist[0]),
"keylist expected to have {:?} but got {:?}",
"ADAB7FCC1F4DE2616ECFA402AF82244F9CD9FD55",
keylist[0]
);
}
}
gpgme_ctx
.import_key(gpgme_ctx.new_data_mem(SECKEY).unwrap())
.unwrap();
gpgme_ctx
.import_key(gpgme_ctx.new_data_mem(SECKEY).unwrap())
.unwrap_err();
}
Loading…
Cancel
Save