From 98b3151c5fe9304432d3ef7d6893ec33f911e5ad Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Fri, 24 Jan 2020 00:08:11 +0100 Subject: [PATCH] Refactor keypair handling and expose storing keypairs on ffi The user-visible change here is that it allows the FFI API to save keys in the database for a context. This is primarily intended for testing purposes as it allows you to get a key without having to generate it. Internally the most important change is to start using the SignedPublicKey and SignedPrivateKey types from rpgp instead of wrapping them into a single Key object. This allows APIs to be specific about which they want instead of having to do runtime checks like .is_public() or so. This means some of the functionality of the Key impl now needs to be a trait. A thid API change is to introduce the KeyPair struct, which binds together the email address, public and private key for a keypair. All these changes result in a bunch of cleanups, though more more should be done to completely replace the Key type with the SignedPublicKye/SignedPrivateKey + traits. But this change is large enough already. Testing-wise this adds two new keys which can be loaded from disk and and avoids a few more key-generating tests. The encrypt/decrypt tests are moved from the stress tests into the pgp tests and split up. --- deltachat-ffi/deltachat.h | 21 +++ deltachat-ffi/src/lib.rs | 76 ++++++++++ python/src/deltachat/account.py | 12 ++ python/tests/test_account.py | 18 +++ src/aheader.rs | 47 +++---- src/chat.rs | 2 +- src/context.rs | 2 +- src/dc_tools.rs | 10 +- src/e2ee.rs | 51 +++---- src/error.rs | 15 ++ src/imex.rs | 44 +++--- src/key.rs | 206 +++++++++++++++++++++++---- src/keyring.rs | 4 +- src/peerstate.rs | 43 +++--- src/pgp.rs | 238 ++++++++++++++++++++++++++++++-- src/qr.rs | 2 +- src/securejoin.rs | 2 +- src/test_utils.rs | 81 +++++++---- test-data/key/alice-public.asc | 1 + test-data/key/alice-secret.asc | 1 + test-data/key/bob-public.asc | 1 + test-data/key/bob-secret.asc | 1 + test-data/key/private.asc | 1 - test-data/key/public.asc | 1 - tests/stress.rs | 113 --------------- 25 files changed, 699 insertions(+), 294 deletions(-) create mode 100644 test-data/key/alice-public.asc create mode 100644 test-data/key/alice-secret.asc create mode 100644 test-data/key/bob-public.asc create mode 100644 test-data/key/bob-secret.asc delete mode 100644 test-data/key/private.asc delete mode 100644 test-data/key/public.asc diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 2e50f4cce..9ee3dc012 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -852,6 +852,27 @@ void dc_interrupt_smtp_idle (dc_context_t* context); void dc_maybe_network (dc_context_t* context); +/** + * Save a keypair as the default keys for the user. + * + * This API is only for testing purposes and should not be used as part of a + * normal application, use the import-export APIs instead. + * + * This saves a public/private keypair as the default keypair in the context. + * It allows avoiding having to generate a secret key for unittests which need + * one. + * + * @memberof dc_context_t + * @param context The context as created by dc_context_new(). + * @param addr The email address of the user. This must match the + * configured_addr setting of the context as well as the UID of the key. + * @param public_data The public key as base64. + * @param secret_data The secret key as base64. + * @return 1 on success, 0 on failure. + */ +int _dc_save_self_keypair (dc_context_t* context, const char *addr, const char *public_data, const char *secret_data); + + // handle chatlists #define DC_GCL_ARCHIVED_ONLY 0x01 diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index db00f064f..93f5b1e36 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -28,6 +28,7 @@ use deltachat::chat::ChatId; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::Contact; use deltachat::context::Context; +use deltachat::key::DcKey; use deltachat::message::MsgId; use deltachat::stock::StockMessage; use deltachat::*; @@ -94,6 +95,14 @@ impl ContextWrapper { self.translate_cb(Event::Error(msg.to_string())); } + /// Log a warning on the FFI context. + /// + /// Like [error] but logs as a warning which only goes to the + /// logfile rather than being shown directly to the user. + unsafe fn warning(&self, msg: &str) { + self.translate_cb(Event::Warning(msg.to_string())); + } + /// Unlock the context and execute a closure with it. /// /// This unlocks the context and gets a read lock. The Rust @@ -123,6 +132,23 @@ impl ContextWrapper { } } + /// Unlock the context and execute a closure with it. + /// + /// This is like [ContextWrapper::with_inner] but uses + /// [failure::Error] as error type. This allows you to write a + /// closure which could produce many errors, use the `?` operator + /// to return them and handle them all as the return of this call. + fn try_inner(&self, ctxfn: F) -> Result + where + F: FnOnce(&Context) -> Result, + { + let guard = self.inner.read().unwrap(); + match guard.as_ref() { + Some(ref ctx) => ctxfn(ctx), + None => Err(failure::err_msg("context not open")), + } + } + /// Translates the callback from the rust style to the C-style version. unsafe fn translate_cb(&self, event: Event) { if let Some(ffi_cb) = self.cb { @@ -665,6 +691,35 @@ pub unsafe extern "C" fn dc_maybe_network(context: *mut dc_context_t) { .unwrap_or(()) } +#[no_mangle] +pub unsafe extern "C" fn _dc_save_self_keypair( + context: *mut dc_context_t, + addr: *const libc::c_char, + public_data: *const libc::c_char, + secret_data: *const libc::c_char, +) -> i32 { + if context.is_null() { + eprintln!("ignoring careless call to dc_save_keypair()"); + return 0; + } + let ffi_context = &*context; + ffi_context + .try_inner(|ctx| { + let addr = dc_tools::EmailAddress::new(&to_string_lossy(addr))?; + let public = key::SignedPublicKey::from_base64(&to_string_lossy(public_data))?; + let secret = key::SignedSecretKey::from_base64(&to_string_lossy(secret_data))?; + let keypair = key::KeyPair { + addr, + public, + secret, + }; + key::save_self_keypair(ctx, &keypair, key::KeyPairUse::Default)?; + Ok(1) + }) + .log_warn(ffi_context, "Failed to save keypair") + .unwrap_or(0) +} + #[no_mangle] pub unsafe extern "C" fn dc_get_chatlist( context: *mut dc_context_t, @@ -3161,6 +3216,18 @@ pub unsafe extern "C" fn dc_str_unref(s: *mut libc::c_char) { pub trait ResultExt { fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T; fn log_err(self, context: &context::Context, message: &str) -> Result; + + /// Log a warning to a [ContextWrapper] for an [Err] result. + /// + /// Does nothing for an [Ok]. This is usually preferable over + /// [ResultExt::log_err] because warnings go to the logfile and + /// errors are displayed directly to the user. Usually problems + /// on the FFI layer are coding errors and not errors which need + /// to be displayed to the user. + /// + /// You can do this as soon as the wrapper exists, it does not + /// have to be open (which is required for teh `warn!()` macro). + fn log_warn(self, wrapper: &ContextWrapper, message: &str) -> Result; } impl ResultExt for Result { @@ -3180,6 +3247,15 @@ impl ResultExt for Result { err }) } + + fn log_warn(self, wrapper: &ContextWrapper, message: &str) -> Result { + self.map_err(|err| { + unsafe { + wrapper.warning(&format!("{}: {}", message, err)); + } + err + }) + } } unsafe fn strdup_opt(s: Option>) -> *mut libc::c_char { diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 77e1d53fd..bf1e5afb4 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -118,6 +118,18 @@ class Account(object): assert res != ffi.NULL, "config value not found for: {!r}".format(name) return from_dc_charpointer(res) + def _save_self_keypair(self, addr, public, secret): + """See _dc_save_self_keypair() in deltachat.h. + + In other words, you don't need this. + """ + res = lib._dc_save_self_keypair(self._dc_context, + as_dc_charpointer(addr), + as_dc_charpointer(public), + as_dc_charpointer(secret)) + if res == 0: + raise Exception("Failed to set key") + def configure(self, **kwargs): """ set config values and configure this account. diff --git a/python/tests/test_account.py b/python/tests/test_account.py index a3362a028..9f446cfcd 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1,4 +1,5 @@ from __future__ import print_function +import py import pytest import os import queue @@ -9,6 +10,17 @@ from datetime import datetime, timedelta from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection, wait_securejoin_inviter_progress +@pytest.fixture +def datadir(): + """The py.path.local object of the test-data/ directory.""" + for path in reversed(py.path.local(__file__).parts()): + datadir = path.join('test-data') + if datadir.isdir(): + return datadir + else: + pytest.skip('test-data directory not found') + + class TestOfflineAccountBasic: def test_wrong_db(self, tmpdir): p = tmpdir.join("hello.db") @@ -24,6 +36,12 @@ class TestOfflineAccountBasic: ac1 = Account(p.strpath, os_name="solarpunk") ac1.get_info() + def test_save_self_keypair(self, acfactory, datadir): + ac = acfactory.get_unconfigured_account() + ac._save_self_keypair("alice@example.com", + datadir.join('key/alice-public.asc').read(), + datadir.join('key/alice-secret.asc').read()) + def test_getinfo(self, acfactory): ac1 = acfactory.get_unconfigured_account() d = ac1.get_info() diff --git a/src/aheader.rs b/src/aheader.rs index c748ef6f1..326c1a21e 100644 --- a/src/aheader.rs +++ b/src/aheader.rs @@ -6,10 +6,9 @@ use std::collections::BTreeMap; use std::str::FromStr; use std::{fmt, str}; -use crate::constants::*; use crate::contact::*; use crate::context::Context; -use crate::key::*; +use crate::key::{DcKey, SignedPublicKey}; /// Possible values for encryption preference #[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive)] @@ -52,13 +51,17 @@ impl str::FromStr for EncryptPreference { #[derive(Debug)] pub struct Aheader { pub addr: String, - pub public_key: Key, + pub public_key: SignedPublicKey, pub prefer_encrypt: EncryptPreference, } impl Aheader { /// Creates new autocrypt header - pub fn new(addr: String, public_key: Key, prefer_encrypt: EncryptPreference) -> Self { + pub fn new( + addr: String, + public_key: SignedPublicKey, + prefer_encrypt: EncryptPreference, + ) -> Self { Aheader { addr, public_key, @@ -103,16 +106,19 @@ impl fmt::Display for Aheader { // adds a whitespace every 78 characters, this allows // email crate to wrap the lines according to RFC 5322 // (which may insert a linebreak before every whitespace) - let keydata = self.public_key.to_base64().chars().enumerate().fold( - String::new(), - |mut res, (i, c)| { + let keydata = self + .public_key + .to_base64() + .unwrap_or_default() + .chars() + .enumerate() + .fold(String::new(), |mut res, (i, c)| { if i % 78 == 78 - "keydata=".len() { res.push(' ') } res.push(c); res - }, - ); + }); write!(fmt, " keydata={}", keydata) } } @@ -142,22 +148,11 @@ impl str::FromStr for Aheader { return Err(()); } }; - - let public_key = match attributes + let public_key: SignedPublicKey = attributes .remove("keydata") - .and_then(|raw| Key::from_base64(&raw, KeyType::Public)) - { - Some(key) => { - if key.verify() { - key - } else { - return Err(()); - } - } - None => { - return Err(()); - } - }; + .ok_or(()) + .and_then(|raw| SignedPublicKey::from_base64(&raw).or(Err(()))) + .and_then(|key| key.verify().and(Ok(key)).or(Err(())))?; let prefer_encrypt = attributes .remove("prefer-encrypt") @@ -292,7 +287,7 @@ mod tests { "{}", Aheader::new( "test@example.com".to_string(), - Key::from_base64(RAWKEY, KeyType::Public).unwrap(), + SignedPublicKey::from_base64(RAWKEY).unwrap(), EncryptPreference::Mutual ) ) @@ -305,7 +300,7 @@ mod tests { "{}", Aheader::new( "test@example.com".to_string(), - Key::from_base64(RAWKEY, KeyType::Public).unwrap(), + SignedPublicKey::from_base64(RAWKEY).unwrap(), EncryptPreference::NoPreference ) ) diff --git a/src/chat.rs b/src/chat.rs index c24220ba9..dc485afc9 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -45,7 +45,7 @@ impl ChatId { /// An unset ChatId /// - /// Like [is_error], from which it is indistinguishable, this is + /// Like [ChatId::is_error], from which it is indistinguishable, this is /// transitional and should not be used in new code. pub fn is_unset(self) -> bool { self.0 == 0 diff --git a/src/context.rs b/src/context.rs index 4db0a65dc..c7ae9b63d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -14,7 +14,7 @@ use crate::events::Event; use crate::imap::*; use crate::job::*; use crate::job_thread::JobThread; -use crate::key::*; +use crate::key::Key; use crate::login_param::LoginParam; use crate::lot::Lot; use crate::message::{self, Message, MessengerMessage, MsgId}; diff --git a/src/dc_tools.rs b/src/dc_tools.rs index 3d90f6343..865f4f60b 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -460,7 +460,7 @@ pub(crate) fn time() -> i64 { /// /// ``` /// use deltachat::dc_tools::EmailAddress; -/// let email = match "someone@example.com".parse::() { +/// let email = match EmailAddress::new("someone@example.com") { /// Ok(addr) => addr, /// Err(e) => panic!("Error parsing address, error was {}", e), /// }; @@ -468,12 +468,18 @@ pub(crate) fn time() -> i64 { /// assert_eq!(&email.domain, "example.com"); /// assert_eq!(email.to_string(), "someone@example.com"); /// ``` -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct EmailAddress { pub local: String, pub domain: String, } +impl EmailAddress { + pub fn new(input: &str) -> Result { + input.parse::() + } +} + impl fmt::Display for EmailAddress { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}@{}", self.local, self.domain) diff --git a/src/e2ee.rs b/src/e2ee.rs index 43f9be3a4..fec676c14 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -1,6 +1,7 @@ //! End-to-end encryption support. use std::collections::HashSet; +use std::convert::TryFrom; use mailparse::{MailHeaderMap, ParsedMail}; use num_traits::FromPrimitive; @@ -8,8 +9,9 @@ use num_traits::FromPrimitive; use crate::aheader::*; use crate::config::Config; use crate::context::Context; +use crate::dc_tools::EmailAddress; use crate::error::*; -use crate::key::*; +use crate::key::{self, Key, KeyPairUse, SignedPublicKey}; use crate::keyring::*; use crate::peerstate::*; use crate::pgp; @@ -19,7 +21,7 @@ use crate::securejoin::handle_degrade_event; pub struct EncryptHelper { pub prefer_encrypt: EncryptPreference, pub addr: String, - pub public_key: Key, + pub public_key: SignedPublicKey, } impl EncryptHelper { @@ -102,8 +104,8 @@ impl EncryptHelper { })?; keyring.add_ref(key); } - - keyring.add_ref(&self.public_key); + let public_key = Key::from(self.public_key.clone()); + keyring.add_ref(&public_key); let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql) .ok_or_else(|| format_err!("missing own private key"))?; @@ -191,15 +193,20 @@ pub fn try_decrypt( /// storing a new one when one doesn't exist yet. Care is taken to /// only generate one key per context even when multiple threads call /// this function concurrently. -fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef) -> Result { +fn load_or_generate_self_public_key( + context: &Context, + self_addr: impl AsRef, +) -> Result { if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) { - return Ok(key); + return Ok(SignedPublicKey::try_from(key) + .map_err(|_| Error::Message("Not a public key".into()))?); } let _guard = context.generating_key_mutex.lock().unwrap(); // Check again in case the key was generated while we were waiting for the lock. if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) { - return Ok(key); + return Ok(SignedPublicKey::try_from(key) + .map_err(|_| Error::Message("Not a public key".into()))?); } let start = std::time::Instant::now(); @@ -207,28 +214,14 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef { - if dc_key_save_self_keypair( - context, - &public_key, - &private_key, - &self_addr, - true, - &context.sql, - ) { - info!( - context, - "Keypair generated in {:.3}s.", - start.elapsed().as_secs() - ); - Ok(public_key) - } else { - Err(format_err!("Failed to save keypair")) - } - } - None => Err(format_err!("Failed to generate keypair")), - } + let keypair = pgp::create_keypair(EmailAddress::new(self_addr.as_ref())?)?; + key::save_self_keypair(context, &keypair, KeyPairUse::Default)?; + info!( + context, + "Keypair generated in {:.3}s.", + start.elapsed().as_secs() + ); + Ok(keypair.public) } /// Returns a reference to the encrypted payload and validates the autocrypt structure. diff --git a/src/error.rs b/src/error.rs index 179ff4998..7fec11193 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,6 +16,9 @@ pub enum Error { #[fail(display = "{:?}", _0)] Message(String), + #[fail(display = "{:?}", _0)] + MessageChain(String, #[cause] failure::Error, failure::Backtrace), + #[fail(display = "{:?}", _0)] Image(image_meta::ImageError), @@ -118,6 +121,18 @@ impl From for Error { } } +impl From for Error { + fn from(err: crate::key::SaveKeyError) -> Error { + Error::MessageChain(format!("{}", err), err.into(), failure::Backtrace::new()) + } +} + +impl From for Error { + fn from(err: crate::pgp::PgpKeygenError) -> Error { + Error::MessageChain(format!("{}", err), err.into(), failure::Backtrace::new()) + } +} + impl From for Error { fn from(err: mailparse::MailParseError) -> Error { Error::MailParseError(err) diff --git a/src/imex.rs b/src/imex.rs index 800d1376a..27e54039d 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -18,7 +18,7 @@ use crate::e2ee; use crate::error::*; use crate::events::Event; use crate::job::*; -use crate::key::*; +use crate::key::{self, Key}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; use crate::param::*; @@ -289,7 +289,6 @@ fn set_self_key( .and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h))); ensure!(keys.is_some(), "Not a valid private key"); - let (private_key, public_key, header) = keys.unwrap(); let preferencrypt = header.get("Autocrypt-Prefer-Encrypt"); match preferencrypt.map(|s| s.as_str()) { @@ -314,6 +313,7 @@ fn set_self_key( let self_addr = context.get_config(Config::ConfiguredAddr); ensure!(self_addr.is_some(), "Missing self addr"); + let addr = EmailAddress::new(&self_addr.unwrap_or_default())?; // XXX maybe better make dc_key_save_self_keypair delete things sql::execute( @@ -322,26 +322,24 @@ fn set_self_key( "DELETE FROM keypairs WHERE public_key=? OR private_key=?;", params![public_key.to_bytes(), private_key.to_bytes()], )?; - - if set_default { - sql::execute( - context, - &context.sql, - "UPDATE keypairs SET is_default=0;", - params![], - )?; - } - - if !dc_key_save_self_keypair( + let (public, secret) = match (public_key, private_key) { + (Key::Public(p), Key::Secret(s)) => (p, s), + _ => bail!("wrong keys unpacked"), + }; + let keypair = pgp::KeyPair { + addr, + public, + secret, + }; + key::save_self_keypair( context, - &public_key, - &private_key, - self_addr.unwrap_or_default(), - set_default, - &context.sql, - ) { - bail!("Cannot save keypair, internal key-state possibly corrupted now!"); - } + &keypair, + if set_default { + key::KeyPairUse::Default + } else { + key::KeyPairUse::ReadOnly + }, + )?; Ok(()) } @@ -746,7 +744,6 @@ fn export_key_to_asc_file( #[cfg(test)] mod tests { use super::*; - use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE}; use crate::test_utils::*; use ::pgp::armor::BlockType; @@ -801,8 +798,7 @@ mod tests { #[test] fn test_export_key_to_asc_file() { let context = dummy_context(); - let base64 = include_str!("../test-data/key/public.asc"); - let key = Key::from_base64(base64, KeyType::Public).unwrap(); + let key = Key::from(alice_keypair().public); let blobdir = "$BLOBDIR"; assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok()); let blobdir = context.ctx.get_blobdir().to_str().unwrap(); diff --git a/src/key.rs b/src/key.rs index 388897944..93612cee4 100644 --- a/src/key.rs +++ b/src/key.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use std::io::Cursor; use std::path::Path; -use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey}; +use pgp::composed::Deserializable; use pgp::ser::Serialize; use pgp::types::{KeyTrait, SecretKeyTrait}; @@ -13,6 +13,72 @@ use crate::context::Context; use crate::dc_tools::*; use crate::sql::{self, Sql}; +// Re-export key types +pub use crate::pgp::KeyPair; +pub use pgp::composed::{SignedPublicKey, SignedSecretKey}; + +/// Error type for deltachat key handling. +#[derive(Fail, Debug)] +pub enum Error { + #[fail(display = "Could not decode base64")] + Base64Decode(#[cause] base64::DecodeError, failure::Backtrace), + #[fail(display = "rPGP error: {}", _0)] + PgpError(#[cause] pgp::errors::Error, failure::Backtrace), +} + +impl From for Error { + fn from(err: base64::DecodeError) -> Error { + Error::Base64Decode(err, failure::Backtrace::new()) + } +} + +impl From for Error { + fn from(err: pgp::errors::Error) -> Error { + Error::PgpError(err, failure::Backtrace::new()) + } +} + +pub type Result = std::result::Result; + +/// Convenience trait for working with keys. +/// +/// This trait is implemented for rPGP's [SignedPublicKey] and +/// [SignedSecretKey] types and makes working with them a little +/// easier in the deltachat world. +pub trait DcKey: Serialize + Deserializable { + type KeyType: Serialize + Deserializable; + + fn from_slice(bytes: &[u8]) -> Result { + Ok(::from_bytes(Cursor::new( + bytes, + ))?) + } + + fn from_base64(data: &str) -> Result { + // strip newlines and other whitespace + let cleaned: String = data.trim().split_whitespace().collect(); + let bytes = base64::decode(cleaned.as_bytes())?; + Self::from_slice(&bytes) + } + + fn to_base64(&self) -> Result { + let bytes = self.to_bytes()?; + Ok(base64::encode(&bytes)) + } + + // fn verify(&self) -> Result { + // ::verify(self) + // } +} + +impl DcKey for SignedPublicKey { + type KeyType = SignedPublicKey; +} + +impl DcKey for SignedSecretKey { + type KeyType = SignedSecretKey; +} + /// Cryptographic key #[derive(Debug, PartialEq, Eq, Clone)] pub enum Key { @@ -35,7 +101,7 @@ impl From for Key { impl std::convert::TryFrom for SignedSecretKey { type Error = (); - fn try_from(value: Key) -> Result { + fn try_from(value: Key) -> std::result::Result { match value { Key::Public(_) => Err(()), Key::Secret(key) => Ok(key), @@ -46,7 +112,7 @@ impl std::convert::TryFrom for SignedSecretKey { impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey { type Error = (); - fn try_from(value: &'a Key) -> Result { + fn try_from(value: &'a Key) -> std::result::Result { match value { Key::Public(_) => Err(()), Key::Secret(key) => Ok(key), @@ -57,7 +123,7 @@ impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey { impl std::convert::TryFrom for SignedPublicKey { type Error = (); - fn try_from(value: Key) -> Result { + fn try_from(value: Key) -> std::result::Result { match value { Key::Public(key) => Ok(key), Key::Secret(_) => Err(()), @@ -68,7 +134,7 @@ impl std::convert::TryFrom for SignedPublicKey { impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey { type Error = (); - fn try_from(value: &'a Key) -> Result { + fn try_from(value: &'a Key) -> std::result::Result { match value { Key::Public(key) => Ok(key), Key::Secret(_) => Err(()), @@ -92,7 +158,7 @@ impl Key { if bytes.is_empty() { return None; } - let res: Result = match key_type { + let res: std::result::Result = match key_type { KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes)).map(Into::into), KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes)).map(Into::into), }; @@ -111,7 +177,7 @@ impl Key { key_type: KeyType, ) -> Option<(Self, BTreeMap)> { let bytes = data.as_bytes(); - let res: Result<(Key, _), _> = match key_type { + let res: std::result::Result<(Key, _), _> = match key_type { KeyType::Public => SignedPublicKey::from_armor_single(Cursor::new(bytes)) .map(|(k, h)| (Into::into(k), h)), KeyType::Private => SignedSecretKey::from_armor_single(Cursor::new(bytes)) @@ -127,14 +193,14 @@ impl Key { } } - pub fn from_base64(encoded_data: &str, key_type: KeyType) -> Option { - // strip newlines and other whitespace - let cleaned: String = encoded_data.trim().split_whitespace().collect(); - let bytes = cleaned.as_bytes(); - base64::decode(bytes) - .ok() - .and_then(|decoded| Self::from_slice(&decoded, key_type)) - } + // pub fn from_base64(encoded_data: &str, key_type: KeyType) -> Option { + // // strip newlines and other whitespace + // let cleaned: String = encoded_data.trim().split_whitespace().collect(); + // let bytes = cleaned.as_bytes(); + // base64::decode(bytes) + // .ok() + // .and_then(|decoded| Self::from_slice(&decoded, key_type)) + // } pub fn from_self_public( context: &Context, @@ -242,20 +308,84 @@ impl Key { } } -pub fn dc_key_save_self_keypair( +/// Use of a [KeyPair] for encryption or decryption. +/// +/// This is used by [save_self_keypair] to know what kind of key is +/// being saved. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum KeyPairUse { + /// The default key used to encrypt new messages. + Default, + /// Only used to decrypt existing message. + ReadOnly, +} + +/// Error saving a keypair to the database. +#[derive(Fail, Debug)] +#[fail(display = "SaveKeyError: {}", message)] +pub struct SaveKeyError { + message: String, + #[cause] + cause: failure::Error, + backtrace: failure::Backtrace, +} + +impl SaveKeyError { + fn new(message: impl Into, cause: impl Into) -> Self { + Self { + message: message.into(), + cause: cause.into(), + backtrace: failure::Backtrace::new(), + } + } +} + +/// Save the keypair as an owned keypair for addr. +/// +/// This will save the keypair as keys for the given address. The +/// "self" here refers to the fact that this DC instance owns the +/// keypair. Usually `addr` will be [Config::ConfiguredAddr]. +/// +/// [Config::ConfiguredAddr]: crate::config::Config::ConfiguredAddr +pub fn save_self_keypair( context: &Context, - public_key: &Key, - private_key: &Key, - addr: impl AsRef, - is_default: bool, - sql: &Sql, -) -> bool { + keypair: &KeyPair, + default: KeyPairUse, +) -> std::result::Result<(), SaveKeyError> { + // Should really be one transaction, more refactoring is needed for that. + if default == KeyPairUse::Default { + sql::execute( + context, + &context.sql, + "UPDATE keypairs SET is_default=0;", + params![], + ) + .map_err(|err| SaveKeyError::new("failed to clear default", err))?; + } + let is_default = match default { + KeyPairUse::Default => true, + KeyPairUse::ReadOnly => false, + }; sql::execute( context, - sql, - "INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);", - params![addr.as_ref(), is_default as i32, public_key.to_bytes(), private_key.to_bytes(), time()], - ).is_ok() + &context.sql, + "INSERT INTO keypairs (addr, is_default, public_key, private_key, created) + VALUES (?,?,?,?,?);", + params![ + keypair.addr.to_string(), + is_default as i32, + keypair + .public + .to_bytes() + .map_err(|err| SaveKeyError::new("failed to serialise public key", err))?, + keypair + .secret + .to_bytes() + .map_err(|err| SaveKeyError::new("failed to serialise secret key", err))?, + time() + ], + ) + .map_err(|err| SaveKeyError::new("failed to insert keypair", err)) } /// Make a fingerprint human-readable, in hex format. @@ -287,6 +417,14 @@ pub fn dc_normalize_fingerprint(fp: &str) -> String { #[cfg(test)] mod tests { use super::*; + use crate::test_utils::*; + use std::convert::TryFrom; + + use lazy_static::lazy_static; + + lazy_static! { + static ref KEYPAIR: KeyPair = alice_keypair(); + } #[test] fn test_normalize_fingerprint() { @@ -373,9 +511,9 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD } #[test] - #[ignore] // is too expensive fn test_from_slice_roundtrip() { - let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap(); + let public_key = Key::from(KEYPAIR.public.clone()); + let private_key = Key::from(KEYPAIR.secret.clone()); let binary = public_key.to_bytes(); let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key"); @@ -408,9 +546,9 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD } #[test] - #[ignore] // is too expensive fn test_ascii_roundtrip() { - let (public_key, private_key) = crate::pgp::create_keypair("hello").unwrap(); + let public_key = Key::from(KEYPAIR.public.clone()); + let private_key = Key::from(KEYPAIR.secret.clone()); let s = public_key.to_armored_string(None).unwrap(); let (public_key2, _) = @@ -423,4 +561,12 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD Key::from_armored_string(&s, KeyType::Private).expect("invalid private key"); assert_eq!(private_key, private_key2); } + + #[test] + fn test_split_key() { + let private_key = Key::from(KEYPAIR.secret.clone()); + let public_wrapped = private_key.split_key().unwrap(); + let public = SignedPublicKey::try_from(public_wrapped).unwrap(); + assert_eq!(public.primary_key, KEYPAIR.public.primary_key); + } } diff --git a/src/keyring.rs b/src/keyring.rs index f07a9ee16..25d197cd3 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; -use crate::constants::*; +use crate::constants::KeyType; use crate::context::Context; -use crate::key::*; +use crate::key::Key; use crate::sql::Sql; #[derive(Default, Clone, Debug)] diff --git a/src/peerstate.rs b/src/peerstate.rs index 7442b8db5..c875ccfa3 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -1,5 +1,6 @@ //! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module use std::collections::HashSet; +use std::convert::TryFrom; use std::fmt; use num_traits::FromPrimitive; @@ -7,7 +8,7 @@ use num_traits::FromPrimitive; use crate::aheader::*; use crate::constants::*; use crate::context::Context; -use crate::key::*; +use crate::key::{Key, SignedPublicKey}; use crate::sql::{self, Sql}; #[derive(Debug)] @@ -126,7 +127,7 @@ impl<'a> Peerstate<'a> { res.last_seen_autocrypt = message_time; res.to_save = Some(ToSave::All); res.prefer_encrypt = header.prefer_encrypt; - res.public_key = Some(header.public_key.clone()); + res.public_key = Some(Key::from(header.public_key.clone())); res.recalc_fingerprint(); res @@ -137,7 +138,7 @@ impl<'a> Peerstate<'a> { res.gossip_timestamp = message_time; res.to_save = Some(ToSave::All); - res.gossip_key = Some(gossip_header.public_key.clone()); + res.gossip_key = Some(Key::from(gossip_header.public_key.clone())); res.recalc_fingerprint(); res @@ -293,8 +294,8 @@ impl<'a> Peerstate<'a> { self.to_save = Some(ToSave::All) } - if self.public_key.as_ref() != Some(&header.public_key) { - self.public_key = Some(header.public_key.clone()); + if self.public_key.as_ref() != Some(&Key::from(header.public_key.clone())) { + self.public_key = Some(Key::from(header.public_key.clone())); self.recalc_fingerprint(); self.to_save = Some(ToSave::All); } @@ -309,8 +310,9 @@ impl<'a> Peerstate<'a> { if message_time > self.gossip_timestamp { self.gossip_timestamp = message_time; self.to_save = Some(ToSave::Timestamps); - if self.gossip_key.as_ref() != Some(&gossip_header.public_key) { - self.gossip_key = Some(gossip_header.public_key.clone()); + let hdr_key = Key::from(gossip_header.public_key.clone()); + if self.gossip_key.as_ref() != Some(&hdr_key) { + self.gossip_key = Some(hdr_key); self.recalc_fingerprint(); self.to_save = Some(ToSave::All) } @@ -320,9 +322,13 @@ impl<'a> Peerstate<'a> { pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option { if let Some(key) = self.peek_key(min_verified) { // TODO: avoid cloning + let public_key = match SignedPublicKey::try_from(key.clone()) { + Ok(key) => key, + Err(_) => return None, + }; let header = Aheader::new( self.addr.clone(), - key.clone(), + public_key, EncryptPreference::NoPreference, ); Some(header.to_string()) @@ -450,8 +456,8 @@ impl<'a> Peerstate<'a> { #[cfg(test)] mod tests { use super::*; + use crate::test_utils::*; use pretty_assertions::assert_eq; - use tempfile::TempDir; #[test] @@ -459,11 +465,7 @@ mod tests { let ctx = crate::test_utils::dummy_context(); let addr = "hello@mail.com"; - let pub_key = crate::key::Key::from_base64( - include_str!("../test-data/key/public.asc"), - KeyType::Public, - ) - .unwrap(); + let pub_key = crate::key::Key::from(alice_keypair().public); let mut peerstate = Peerstate { context: &ctx.ctx, @@ -503,12 +505,7 @@ mod tests { fn test_peerstate_double_create() { let ctx = crate::test_utils::dummy_context(); let addr = "hello@mail.com"; - - let pub_key = crate::key::Key::from_base64( - include_str!("../test-data/key/public.asc"), - KeyType::Public, - ) - .unwrap(); + let pub_key = crate::key::Key::from(alice_keypair().public); let peerstate = Peerstate { context: &ctx.ctx, @@ -542,11 +539,7 @@ mod tests { let ctx = crate::test_utils::dummy_context(); let addr = "hello@mail.com"; - let pub_key = crate::key::Key::from_base64( - include_str!("../test-data/key/public.asc"), - KeyType::Public, - ) - .unwrap(); + let pub_key = crate::key::Key::from(alice_keypair().public); let mut peerstate = Peerstate { context: &ctx.ctx, diff --git a/src/pgp.rs b/src/pgp.rs index 3886f2724..bb9d5331b 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -16,6 +16,7 @@ use pgp::types::{ }; use rand::{thread_rng, CryptoRng, Rng}; +use crate::dc_tools::EmailAddress; use crate::error::Result; use crate::key::*; use crate::keyring::*; @@ -111,10 +112,43 @@ pub fn split_armored_data(buf: &[u8]) -> Result<(BlockType, BTreeMap) -> Option<(Key, Key)> { - let user_id = format!("<{}>", addr.as_ref()); +/// Error with generating a PGP keypair. +/// +/// Most of these are likely coding errors rather than user errors +/// since all variability is hardcoded. +#[derive(Fail, Debug)] +#[fail(display = "PgpKeygenError: {}", message)] +pub(crate) struct PgpKeygenError { + message: String, + #[cause] + cause: failure::Error, + backtrace: failure::Backtrace, +} +impl PgpKeygenError { + fn new(message: impl Into, cause: impl Into) -> Self { + Self { + message: message.into(), + cause: cause.into(), + backtrace: failure::Backtrace::new(), + } + } +} + +/// A PGP keypair. +/// +/// This has it's own struct to be able to keep the public and secret +/// keys together as they are one unit. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct KeyPair { + pub addr: EmailAddress, + pub public: SignedPublicKey, + pub secret: SignedSecretKey, +} + +/// Create a new key pair. +pub(crate) fn create_keypair(addr: EmailAddress) -> std::result::Result { + let user_id = format!("<{}>", addr); let key_params = SecretKeyParamsBuilder::default() .key_type(PgpKeyType::Rsa(2048)) .can_create_certificates(true) @@ -146,20 +180,29 @@ pub fn create_keypair(addr: impl AsRef) -> Option<(Key, Key)> { .unwrap(), ) .build() - .expect("invalid key params"); - - let key = key_params.generate().expect("invalid params"); + .map_err(|err| PgpKeygenError::new("invalid key params", failure::err_msg(err)))?; + let key = key_params + .generate() + .map_err(|err| PgpKeygenError::new("invalid params", err))?; let private_key = key.sign(|| "".into()).expect("failed to sign secret key"); let public_key = private_key.public_key(); let public_key = public_key .sign(&private_key, || "".into()) - .expect("failed to sign public key"); + .map_err(|err| PgpKeygenError::new("failed to sign public key", err))?; - private_key.verify().expect("invalid private key generated"); - public_key.verify().expect("invalid public key generated"); + private_key + .verify() + .map_err(|err| PgpKeygenError::new("invalid private key generated", err))?; + public_key + .verify() + .map_err(|err| PgpKeygenError::new("invalid public key generated", err))?; - Some((Key::Public(public_key), Key::Secret(private_key))) + Ok(KeyPair { + addr, + public: public_key, + secret: private_key, + }) } /// Select public key or subkey to use for encryption. @@ -311,6 +354,8 @@ pub fn symm_decrypt( #[cfg(test)] mod tests { use super::*; + use crate::test_utils::*; + use lazy_static::lazy_static; #[test] fn test_split_armored_data_1() { @@ -338,4 +383,177 @@ mod tests { assert!(!base64.is_empty()); assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string())); } + + #[test] + #[ignore] // is too expensive + fn test_create_keypair() { + let keypair0 = create_keypair(EmailAddress::new("foo@bar.de").unwrap()).unwrap(); + let keypair1 = create_keypair(EmailAddress::new("two@zwo.de").unwrap()).unwrap(); + assert_ne!(keypair0.public, keypair1.public); + } + + /// [Key] objects to use in tests. + struct TestKeys { + alice_secret: Key, + alice_public: Key, + bob_secret: Key, + bob_public: Key, + } + + impl TestKeys { + fn new() -> TestKeys { + let alice = alice_keypair(); + let bob = bob_keypair(); + TestKeys { + alice_secret: Key::from(alice.secret.clone()), + alice_public: Key::from(alice.public.clone()), + bob_secret: Key::from(bob.secret.clone()), + bob_public: Key::from(bob.public.clone()), + } + } + } + + /// The original text of [CTEXT_SIGNED] + static CLEARTEXT: &[u8] = b"This is a test"; + + lazy_static! { + /// Initialised [TestKeys] for tests. + static ref KEYS: TestKeys = TestKeys::new(); + + /// A cyphertext encrypted to Alice & Bob, signed by Alice. + static ref CTEXT_SIGNED: String = { + let mut keyring = Keyring::default(); + keyring.add_owned(KEYS.alice_public.clone()); + keyring.add_ref(&KEYS.bob_public); + pk_encrypt(CLEARTEXT, &keyring, Some(&KEYS.alice_secret)).unwrap() + }; + + /// A cyphertext encrypted to Alice & Bob, not signed. + static ref CTEXT_UNSIGNED: String = { + let mut keyring = Keyring::default(); + keyring.add_owned(KEYS.alice_public.clone()); + keyring.add_ref(&KEYS.bob_public); + pk_encrypt(CLEARTEXT, &keyring, None).unwrap() + }; + } + + #[test] + fn test_encrypt_signed() { + assert!(!CTEXT_SIGNED.is_empty()); + assert!(CTEXT_SIGNED.starts_with("-----BEGIN PGP MESSAGE-----")); + } + + #[test] + fn test_encrypt_unsigned() { + assert!(!CTEXT_UNSIGNED.is_empty()); + assert!(CTEXT_UNSIGNED.starts_with("-----BEGIN PGP MESSAGE-----")); + } + + #[test] + fn test_decrypt_singed() { + // Check decrypting as Alice + let mut decrypt_keyring = Keyring::default(); + decrypt_keyring.add_ref(&KEYS.alice_secret); + let mut sig_check_keyring = Keyring::default(); + sig_check_keyring.add_ref(&KEYS.alice_public); + let mut valid_signatures: HashSet = Default::default(); + let plain = pk_decrypt( + CTEXT_SIGNED.as_bytes(), + &decrypt_keyring, + &sig_check_keyring, + Some(&mut valid_signatures), + ) + .map_err(|err| println!("{:?}", err)) + .unwrap(); + assert_eq!(plain, CLEARTEXT); + assert_eq!(valid_signatures.len(), 1); + + // Check decrypting as Bob + let mut decrypt_keyring = Keyring::default(); + decrypt_keyring.add_ref(&KEYS.bob_secret); + let mut sig_check_keyring = Keyring::default(); + sig_check_keyring.add_ref(&KEYS.alice_public); + let mut valid_signatures: HashSet = Default::default(); + let plain = pk_decrypt( + CTEXT_SIGNED.as_bytes(), + &decrypt_keyring, + &sig_check_keyring, + Some(&mut valid_signatures), + ) + .map_err(|err| println!("{:?}", err)) + .unwrap(); + assert_eq!(plain, CLEARTEXT); + assert_eq!(valid_signatures.len(), 1); + } + + #[test] + fn test_decrypt_no_sig_check() { + let mut keyring = Keyring::default(); + keyring.add_ref(&KEYS.alice_secret); + let empty_keyring = Keyring::default(); + let mut valid_signatures: HashSet = Default::default(); + let plain = pk_decrypt( + CTEXT_SIGNED.as_bytes(), + &keyring, + &empty_keyring, + Some(&mut valid_signatures), + ) + .unwrap(); + assert_eq!(plain, CLEARTEXT); + assert_eq!(valid_signatures.len(), 0); + } + + #[test] + fn test_decrypt_signed_no_key() { + // The validation does not have the public key of the signer. + let mut decrypt_keyring = Keyring::default(); + decrypt_keyring.add_ref(&KEYS.bob_secret); + let mut sig_check_keyring = Keyring::default(); + sig_check_keyring.add_ref(&KEYS.bob_public); + let mut valid_signatures: HashSet = Default::default(); + let plain = pk_decrypt( + CTEXT_SIGNED.as_bytes(), + &decrypt_keyring, + &sig_check_keyring, + Some(&mut valid_signatures), + ) + .unwrap(); + assert_eq!(plain, CLEARTEXT); + assert_eq!(valid_signatures.len(), 0); + } + + #[test] + fn test_decrypt_unsigned() { + let mut decrypt_keyring = Keyring::default(); + decrypt_keyring.add_ref(&KEYS.bob_secret); + let sig_check_keyring = Keyring::default(); + decrypt_keyring.add_ref(&KEYS.alice_public); + let mut valid_signatures: HashSet = Default::default(); + let plain = pk_decrypt( + CTEXT_UNSIGNED.as_bytes(), + &decrypt_keyring, + &sig_check_keyring, + Some(&mut valid_signatures), + ) + .unwrap(); + assert_eq!(plain, CLEARTEXT); + assert_eq!(valid_signatures.len(), 0); + } + + #[test] + fn test_decrypt_signed_no_sigret() { + // Check decrypting signed cyphertext without providing the HashSet for signatures. + let mut decrypt_keyring = Keyring::default(); + decrypt_keyring.add_ref(&KEYS.bob_secret); + let mut sig_check_keyring = Keyring::default(); + sig_check_keyring.add_ref(&KEYS.alice_public); + let plain = pk_decrypt( + CTEXT_SIGNED.as_bytes(), + &decrypt_keyring, + &sig_check_keyring, + None, + ) + .unwrap(); + assert_eq!(plain, CLEARTEXT); + } } diff --git a/src/qr.rs b/src/qr.rs index eb4845612..76bd9c6f1 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -9,7 +9,7 @@ use crate::contact::*; use crate::context::Context; use crate::error::Error; use crate::key::dc_format_fingerprint; -use crate::key::*; +use crate::key::dc_normalize_fingerprint; use crate::lot::{Lot, LotState}; use crate::param::*; use crate::peerstate::*; diff --git a/src/securejoin.rs b/src/securejoin.rs index e19b5d1a6..28a8f0f7f 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -12,7 +12,7 @@ use crate::e2ee::*; use crate::error::Error; use crate::events::Event; use crate::headerdef::HeaderDef; -use crate::key::*; +use crate::key::{dc_normalize_fingerprint, Key}; use crate::lot::LotState; use crate::message::Message; use crate::mimeparser::*; diff --git a/src/test_utils.rs b/src/test_utils.rs index 0cfb31bb8..fa98bcd6c 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -5,16 +5,16 @@ use tempfile::{tempdir, TempDir}; use crate::config::Config; -use crate::constants::KeyType; use crate::context::{Context, ContextCallback}; +use crate::dc_tools::EmailAddress; use crate::events::Event; -use crate::key; +use crate::key::{self, DcKey}; /// A Context and temporary directory. /// /// The temporary directory can be used to store the SQLite database, /// see e.g. [test_context] which does this. -pub struct TestContext { +pub(crate) struct TestContext { pub ctx: Context, pub dir: TempDir, } @@ -25,7 +25,7 @@ pub struct TestContext { /// "db.sqlite" in the [TestContext.dir] directory. /// /// [Context]: crate::context::Context -pub fn test_context(callback: Option>) -> TestContext { +pub(crate) fn test_context(callback: Option>) -> TestContext { let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); let cb: Box = match callback { @@ -41,11 +41,11 @@ pub fn test_context(callback: Option>) -> TestContext { /// The context will be opened and use the SQLite database as /// specified in [test_context] but there is no callback hooked up, /// i.e. [Context::call_cb] will always return `0`. -pub fn dummy_context() -> TestContext { +pub(crate) fn dummy_context() -> TestContext { test_context(None) } -pub fn logging_cb(_ctx: &Context, evt: Event) { +pub(crate) fn logging_cb(_ctx: &Context, evt: Event) { match evt { Event::Info(msg) => println!("I: {}", msg), Event::Warning(msg) => println!("W: {}", msg), @@ -54,27 +54,54 @@ pub fn logging_cb(_ctx: &Context, evt: Event) { } } +/// Load a pre-generated keypair for alice@example.com from disk. +/// +/// This saves CPU cycles by avoiding having to generate a key. +/// +/// The keypair was created using (this is purposefully not a doctest, it would be slow): +/// let keypair = crate::pgp::create_keypair(EmailAddress::new("alice@example.com")) +/// .unwrap(); +/// println!("{}", keypair.public.to_base64(64)); +/// println!("{}", keypair.secret.to_base64(64)); +pub(crate) fn alice_keypair() -> key::KeyPair { + let addr = EmailAddress::new("alice@example.com").unwrap(); + let public = + key::SignedPublicKey::from_base64(include_str!("../test-data/key/alice-public.asc")) + .unwrap(); + let secret = + key::SignedSecretKey::from_base64(include_str!("../test-data/key/alice-secret.asc")) + .unwrap(); + key::KeyPair { + addr, + public, + secret, + } +} + /// Creates Alice with a pre-generated keypair. /// -/// Returns the address of the keypair created (alice@example.org). -pub fn configure_alice_keypair(ctx: &Context) -> String { - let addr = String::from("alice@example.org"); - ctx.set_config(Config::ConfiguredAddr, Some(&addr)).unwrap(); - - // The keypair was created using: - // let (public, private) = crate::pgp::dc_pgp_create_keypair("alice@example.com") - // .unwrap(); - // println!("{}", public.to_base64(64)); - // println!("{}", private.to_base64(64)); - let public = - key::Key::from_base64(include_str!("../test-data/key/public.asc"), KeyType::Public) - .unwrap(); - let private = key::Key::from_base64( - include_str!("../test-data/key/private.asc"), - KeyType::Private, - ) - .unwrap(); - let saved = key::dc_key_save_self_keypair(&ctx, &public, &private, &addr, true, &ctx.sql); - assert_eq!(saved, true, "Failed to save Alice's key"); - addr +/// Returns the address of the keypair created (alice@example.com). +pub(crate) fn configure_alice_keypair(ctx: &Context) -> String { + let keypair = alice_keypair(); + ctx.set_config(Config::ConfiguredAddr, Some(&keypair.addr.to_string())) + .unwrap(); + key::save_self_keypair(&ctx, &keypair, key::KeyPairUse::Default) + .expect("Failed to save Alice's key"); + keypair.addr.to_string() +} + +/// Load a pre-generated keypair for bob@example.net from disk. +/// +/// Like [alice_keypair] but a different key and identity. +pub(crate) fn bob_keypair() -> key::KeyPair { + let addr = EmailAddress::new("bob@example.net").unwrap(); + let public = + key::SignedPublicKey::from_base64(include_str!("../test-data/key/bob-public.asc")).unwrap(); + let secret = + key::SignedSecretKey::from_base64(include_str!("../test-data/key/bob-secret.asc")).unwrap(); + key::KeyPair { + addr, + public, + secret, + } } diff --git a/test-data/key/alice-public.asc b/test-data/key/alice-public.asc new file mode 100644 index 000000000..7ee921bc8 --- /dev/null +++ b/test-data/key/alice-public.asc @@ -0,0 +1 @@ +xsBNBF425W8BCADLIbltPzG1vk/V2ov2+eBeJJJnRu1kJHdo6e3oNB+HTIxVde5+7Uq8tTEDZB1O7m9NBUFrXr7UYQsA/86G2jmsyWKTzIu1O/t5kdcNDqsNcTVZAhBu2ixYsYVc3ws6kJONjpXLtD2u3P7vEXU3INiOb2JrBQDT8/ubEm1xas/UirYnP5DMaH068IHRdVEYs9ULFaD5scw1m/94buXYZ1CRt/2hT8iRrtBi6ki8kArnhsZC2Xr0+jRQNMUnG5k7Bwi6saCqVmd7IlqSM6MbfYank30Gi/UyDmyIrOk7daTg6WIqgiVOTHav65EK/aUvvjlr+awM+C+u35rQytzyTitZABEBAAHNEzxhbGljZUBleGFtcGxlLmNvbT7CwIkEEAEIADMCGQEFAl425ZQCGwMECwkIBwYVCAkKCwIDFgIBFiEEsBJRVVptIGB7DRLzYuJiDHjRb8EACgkQYuJiDHjRb8HiZQf+PLDxzWchkHAdQFbxxtoXj66aiknofjlRWHDWvUG4nULZ15tjDjnv3z22Meldr8kSV4r1+ejhLFHou9gTzAYk7eAxiybDd8AJOdK+ZgK/Nn7xjdO+HTZLhNdi+R7EektDyf8WDNktEaS8pZc74VKu4984ESi4PoqVxqGHRiSisH4cw4b2pQYxp32BkIdil7sWnqRUEoCpMoKdw2h0N7/lm+rS7/JR9cdjXaVzy1dYTqAVsTL1FTGy4osOKGOyQbkP+Cm6uNq7kC/Bt+fefsb+c2JycmI1uwdvnG7PoFslKv3lRnfkNSmrcIYlJHUl5z0yAgliophr5fqMfzQpO4zMc87ATQReNuVvAQgAuNjE1i+g4v25UNDPIMgXODU4WztE30074gQs5sZa0DQnDUMsdWc2g1o060YZDojMYJQAtBjlW1Dz8FEE7WsLNohGtRyUWmIgNxE5CpodjpwIZ0MdO4Aji0YM+g+WsOSS8kiHMs+dMFfQJuNKjujGFaMIciSaMMrUmPtzkQ/o8NEJs2Aftw90fpVR+M7Mue3++rcEX09ntbgqkgm8SV6OIrOY2kfILudtybocgYkCTeNVqz5VFXuxrnT4ceyFQ64JkwsZxb+X/pCm4V5Q2TbKRwtdonU8HfAz0nAd5tsNeGmf/dPLOKBCxlNEme399YmzWrT+kJBp7CIH5jlWQKyuLwARAQABwsB2BBgBCAAgBQJeNuWUAhsMFiEEsBJRVVptIGB7DRLzYuJiDHjRb8EACgkQYuJiDHjRb8HrEgf/Xu8eRPPdskwtyd98y64teidBpkHuIjuZKJpNyy2HhdGXQwYbNIzwINg0EJ+u2nkreNF/h2Lu+/saqI8Dai02dpYXjvxJIlCgP2os7sNhVaZSaS4XmmJjkHCfZuIKblZypKDJVc5AceZxrtvUbgG+94+H3zeRWVAA30S5ep6YPvxigvhmQah/sdzY7708/jd9uXcCbkP47PBaXCpuPiYLb3t7z8mOteJb7LOZUmSI1efiLDLTGj7ofkdDfA7E6/nF/1+nq+UIDWqljwiUzeNIJsFlZRa/9/uDEjcQbaDe9/knBs7k9pEDZX5u8SSwSED75L+OvRpFWenp4SSKvd2BUw== \ No newline at end of file diff --git a/test-data/key/alice-secret.asc b/test-data/key/alice-secret.asc new file mode 100644 index 000000000..b09800bc5 --- /dev/null +++ b/test-data/key/alice-secret.asc @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/test-data/key/bob-public.asc b/test-data/key/bob-public.asc new file mode 100644 index 000000000..8a47d4e52 --- /dev/null +++ b/test-data/key/bob-public.asc @@ -0,0 +1 @@ +xsBNBF4wx1cBCADOwLS/xCd8iKDWUsyTfVzWby+ZGKPpamPTvdj0GFgnf0B1EBaA5//PjAzbK5iKio6QNEmZagzJPkXPByJcAIRUm0T16tqDtCvxm+H93YEXpHi/XWOeJw9kohATSqUtsRO0pFJeDvPiMTmQrEmHYoWDSQBfCrowZdvnMAlbJ9JjYOngcMeTxc0jxmPs5s17yFC+1OWu4fwWCyUM3wy1JzdKTcDWryrSkvmgFdUqJ7pJDk1HFTt+x9tvQlK3un9BXiRwv0u0zDSuI8eDH/dRLA4UL9Pq6vmJmBame1BPsE1PA7VzeTSJR2ooJXMT6o2AmH8PPUfRkv3OiWuh7LM5FSpHABEBAAHNETxib2JAZXhhbXBsZS5uZXQ+wsCJBBABCAAzAhkBBQJeMMduAhsDBAsJCAcGFQgJCgsCAxYCARYhBMzLWqn24RQclDFl8dsYsYy89wSHAAoJENsYsYy89wSH9oIIALbpmicuVghM3CloiCgJhPEFLFMaQZRDV/KCVVtBcHAhw6d42q8T50mhs+W3Va5E37DN+wcenj8CgeGPQY3kPp2cnZruYtLhLkZ1+VEay5BQFUMb8kY21XrNTQQET8vc0L8cCLQ7RCgm1tGiFVp1nqbjmGUdoru90ksoufWfoqVPjNrW+9eHFvY/Z7PqchCdMnbKOJiwwv4E3NDTySZ1UVZnDztGy95Aa8OZ3cntvbq4JVi7S+N38rRPPPzpZKx+M4DUGfDAoaq7O/Xemyk1sP6C/NgQvS8rri54PgkMgKSS4TyyEzdM+fzeNYFPXFGTbgj4p0pSueQV7/JUfYHRfe3OwE0EXjDHVwEIAKIHgS2yI2niSCN1tqcbLvkhLrEJCVcpGxmA7asl1flwWYrGOBhNJE2sCuZqkofqw6qrgsQ4GFgUU5xmcBCqIZ49jRu+aY38lT4WDFHSbe/mGtaIhb2ZYK6zo9W7Y3r6ud8hbUKJTDfl9qEvJpX/Y0syMjwng8SZNTdYMWgAE4NwcgMgdU3dMA3RT6ePJ4vKs38hmXmInLyZce+GJzmo2tpZyP8viPS7JpqojoCPB3G5h9aHeakp1Y4XKQaExANeWCyBJEhNwtNEOVEpQ0txFYPyDrtxV5y5e79IUP418r/PHsnH6UnxXGzB6LfVbSeEyDyKEl+w0PrlNklySomTZFUAEQEAAcLAdgQYAQgAIAUCXjDHbgIbDBYhBMzLWqn24RQclDFl8dsYsYy89wSHAAoJENsYsYy89wSHVCIIAIH694HkQLXRAJlXmi8K/xMVP96ywJovL/B5l4S/vk/iR4P1lYsF55A3Z2PK/iFtwAgVsppcBIPBlqSI0GPDMvEIxj7UFOQfQzVpDes29wG8grHJEJqI/4TlRjOacxTGaJ5fIMsLXJD6nLBuoN5Z6zm3LjqIyOx4ZGrwradPO95OMGT2Xll3YNzUqSWe33RJLqNQ5ea9I7+qvKnW5Z9Yt5nQwOo8yD+f5fql8904B3eAyLqxgkdLmngAWmYhc7KOaKdAsx7TXBAKsoeHk51OPk59u7EbX35HWD6snl/phJdUYDXiddyYN/n2ZY9g80ycle2JfgpfrQGlh7oJqgCjZuk= \ No newline at end of file diff --git a/test-data/key/bob-secret.asc b/test-data/key/bob-secret.asc new file mode 100644 index 000000000..e90e46b77 --- /dev/null +++ b/test-data/key/bob-secret.asc @@ -0,0 +1 @@ +xcLYBF4wx1cBCADOwLS/xCd8iKDWUsyTfVzWby+ZGKPpamPTvdj0GFgnf0B1EBaA5//PjAzbK5iKio6QNEmZagzJPkXPByJcAIRUm0T16tqDtCvxm+H93YEXpHi/XWOeJw9kohATSqUtsRO0pFJeDvPiMTmQrEmHYoWDSQBfCrowZdvnMAlbJ9JjYOngcMeTxc0jxmPs5s17yFC+1OWu4fwWCyUM3wy1JzdKTcDWryrSkvmgFdUqJ7pJDk1HFTt+x9tvQlK3un9BXiRwv0u0zDSuI8eDH/dRLA4UL9Pq6vmJmBame1BPsE1PA7VzeTSJR2ooJXMT6o2AmH8PPUfRkv3OiWuh7LM5FSpHABEBAAEACACcqjFMTlqNZwpY3QzfhdLfOgkbPSyXJmLWg7jt3bSO2UICclpa+3E/16O2P+aqtCsq4jQS5+UgaOuE4KcMh+e+JJmwrnE98zyJK9GnCD1VqO9GMoHVyUtEufjsZVecs912uD0hwLrU3u/7zFE7IVCCFsMNQZesLMLg/+lXBWnKmrty5XwRMxoxM0yweiJ2b2wdfntQbur7pSdWrwrdBdU1Vprj2VZ/fG6ASMXQ34QTrkq9dYzRGq4T5r6etC5wi8BpAfQX/eD1ktLc/535JmfRwEXFkbmRKwYbMxVk6hrfE+N9xlg+xmUUIlJv29qrB4Q2UjO9FPG9XetrCfExQQShBADVOrBYlkzDFprBC+m1d+RABPo7D5oCiBtrhX+v1UE8By78DCP8jm2VLqmW0hKfEyQDYfiGcnCmjvDciVzZjaB1+K8ov/1YPZ0I29PlolFROyl49H/uEtLn5UwDExD49koQGbqad16M4lDM+MG9pzsfYOV5luXn1fTblvQHhVDhbQQA+DlzEmQ6RkLLw1ta5pCigCHeqaNU8qy4wadst3rAqwM4FtONtHVUr1T9xSvTGJiSGqfD93kNk3Kn2OyC0dNcboBMhwlrCKGqWoXMEGaO2ayL6jjrwri17WsW19NGubrniIPSKPZY75yahx+PckJ5sHDh3jFkvE1p5ThULpp+3gMEAK3buTiGWap0cKcAQYYCNBtt1mcDhgYnXWSaKDDf+e+yTX+Ts3YdrofhwRmBsTbWLYeE4oLO+0vRhGk5b/DcfoleiiwT61LubJmmtlg6EpPp/aWWq7c1+unzulrYjhfLhaoMBrGkudEw7q+pd/Xd3I0UKrPWHfzGGnmiGYUq1K2sTQPNETxib2JAZXhhbXBsZS5uZXQ+wsCJBBABCAAzAhkBBQJeMMdtAhsDBAsJCAcGFQgJCgsCAxYCARYhBMzLWqn24RQclDFl8dsYsYy89wSHAAoJENsYsYy89wSH8ggH+QHLm+gAwetZs7C8NVcdMXLeKCQGLCn4QOeC0HNcPr94rUROlYSxhWGaZWfLiNwte01Lufj4d/Blz5gn+VHKx6lRGw69vxogZI++ikOgdbZIRYAdhmEun0SXtTm6ha9GvuH9ux8UNlP6IQuR6za2uFEeg33TUCgCh2uuQsYkheeOQ2vDjBZvhE2JVEn53uamAkjDDeDw2d71HNXmYGzJfp6MUhAEV8M/aZMcMgeW5sbzp5c6Xs0I1OQATBPI/wheZS3n5Ar/qWCF2HMvoL7Oy3eBMYgOBBXp9z4UKqFACS5XcKjhZO9mZ0Lt4UTqTprv+L4zvGFeuDmPKlKF2PV6AiTHwtgEXjDHVwEIAKIHgS2yI2niSCN1tqcbLvkhLrEJCVcpGxmA7asl1flwWYrGOBhNJE2sCuZqkofqw6qrgsQ4GFgUU5xmcBCqIZ49jRu+aY38lT4WDFHSbe/mGtaIhb2ZYK6zo9W7Y3r6ud8hbUKJTDfl9qEvJpX/Y0syMjwng8SZNTdYMWgAE4NwcgMgdU3dMA3RT6ePJ4vKs38hmXmInLyZce+GJzmo2tpZyP8viPS7JpqojoCPB3G5h9aHeakp1Y4XKQaExANeWCyBJEhNwtNEOVEpQ0txFYPyDrtxV5y5e79IUP418r/PHsnH6UnxXGzB6LfVbSeEyDyKEl+w0PrlNklySomTZFUAEQEAAQAH/jFUmZbBApktJItvPlH4K7/7w0xxFN/tiuuj3jhaR6Au/YQLv25epivjslnenog1CKeAmkqFTZwbbC1U3s+kDKIx2TFWMqrg+MszSULsDz6Xzxn77MQB23a1CK984tfBWC+/7JTyWjs2j3UZduT6IU/2k2bPHQYRIyubdUdVpptAd+GcuBq1DERJVuSJxcAzHgUydJpj5Ao4v+oznZmKgtzTJiuhz1o+1TEUCojw3etE0RCHZ66yhFaRp4crq89BnltMIDHSf2cEYVhiPblYcBx84c6msMczKU4pJzFnvX+I4h6JEhYdxshVv5JnaLKC3Zrum9IjMCWPZ1Act0hD1m0EAMrLfD8wXEIHsbz2MPAif7Au/g9pGhOYW56DF9ABuP9uttLqhe+7YYsagpJbzOzQRFrdL15LwqKRikjnZoRTmV5JGFeCKUkTXy/5Arpc9MBizwVgA8DnvghJWMNYuCtcSkY5O2WJIGd/HnP/iuv8TTK9FcuZo2hEP0IFphwiTwSfBADMigqzx/aLSqd7Aox3Jt9+UxIhoPUEDY9876ann4Aggapw+IAk/ra/q3+bxsxBJSMO9bhUj3NzldHCYi3GBOrreyvJRaO/b7WCTBUpVuU2kahJZf5lqKCojDdBqLz7PqPi86I6Zpv06fzosI4AsE9UwnALsa2QbQ9utYyg4xbeiwQApV9wOUrAV2SUINxEp0I92aT7DvrQtDwUw8DKJXiX90e7DUjPjDjPdUc/WgRMWmVAvxmeN2UOd3nN1EELeOYVsvITipqO65U8B1GjLZHx2gAYvCY96TAEV7qm57uedh5ciwStFGdSrGxY4rVQ9lFgRGUFsRFwp8E0f7LRDMberMA33sLAdgQYAQgAIAUCXjDHbQIbDBYhBMzLWqn24RQclDFl8dsYsYy89wSHAAoJENsYsYy89wSHrKcH/0icTs9x4JKHU6+TvCjBxzgZIP0eRsm71F/tnDi3oLrIDaOu/9y+c1qbWdoJfEqIWjXSJzRCpChvn2eGU8Vv4V6G/Fpv7XsOCzsWwyFbbJ9MoyTfGBDcywOhStHA47pqBgFCeAu/fBefriBP8iue64ZMc48kYA2mHyoUCfqgMgD65XooePgPiQ1R1TVHskoY3uVAYe0JBElkegjGc6+OBeOWo/cnP0LlkDlmooaUTgA36ept53sjLu5YBt5bsi21owfH6RTm8+azAcxQZB53qERP8oS/7V2dJEt0CBG7+dyHqURLIcEginVboKszq4J8mfOF7wy7sMVvFBX3zWfjxDU= \ No newline at end of file diff --git a/test-data/key/private.asc b/test-data/key/private.asc deleted file mode 100644 index b03d46890..000000000 --- a/test-data/key/private.asc +++ /dev/null @@ -1 +0,0 @@ -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 diff --git a/test-data/key/public.asc b/test-data/key/public.asc deleted file mode 100644 index f1aa7a067..000000000 --- a/test-data/key/public.asc +++ /dev/null @@ -1 +0,0 @@ -xsBNBF086ewBCACmJKuoxIO6T87yi4Q3MyNpMch3Y8KrtHDQyUszU36eqM3Pmd1lFrbcCd8ZWo2pq6OJSwsM/jjRGn1zo2YOaQeJRRrC+KrKGqSUtRSYQBPrPjE2YjSXAMbu8jBI6VVUhHeghriBkK79PY9O/oRhIJC0p14IJe6CQ6OI2fTmTUHF9i/nJ3G4Wb3/K1bU3yVfyPZjTZQPYPvvh03vxHULKurtYkX5DTEMSWsF4qzLMps+l87VuLV9iQnbN7YMRLHHx2KkX5Ivi7JCefhCa54M0K3bDCCxuVWAM5wjQwNZjzR+WL+dYchwoFvuF8NrlzjM9dSv+2rn+J7f99ijSXarzPC7ABEBAAHNEzxhbGljZUBleGFtcGxlLmNvbT7CwIkEEAEIADMCGQEFAl086fgCGwMECwkIBwYVCAkKCwIDFgIBFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcei3ogf/cruUmQ+th52TFHTHdkw9OHUl3MrXtZ7QmHyOAFvbXE/6n5Eeh+eZoF8MWWV72m14Wbs+vTcNQkFVTdOPptkKA8e4cJqwDOHsyAnvQXZ7WNje9+BMzcoipIUawHP4ORFaPDsKLZQ0b4wBbKn8ziea6zjGF0/qljTdoxTtsYpv5wXYuhwbYklrLOqgSa5M7LXUe7E3g9mbg+9iX1GuB8m6GkquJN814Y+xny4xhZzGOfue6SeP12jJMNSjSP7416dRq7794VGnkkW9V/7oFEUKu5wO9FFbgDySOSlEjByGejSGuBmho0iJSjcPjZ7EY/j3M3orq4dpza5C82OeSvxDFc7ATQRdPOnsAQgA5oLxXRLnyugzOmNCy7dxV3JrDZscA6JNlJlDWIShT0YSs+zG9JzDeQql+sYXgUSxOoIayItuXtnFn7tstwGoOnYvadm/e5/7V5fKAQRtCtdN51av62n18Venlm0yNKpROPcZ6M/sc4m6uU6YRZ/a1idal8VGY0wLKlghjIBuIiBoVQ/RnoW+/fhmwIg08dQ5m8hQe3GEOZEeLrTWL/9awPlXK7Y+DmJOoR4qbHWEcRfbzS6q4zW8vk2ztB8ngwbnqYy8zrN1DCICN1gYdlU++uVw6Bb1XfY8Cdldh1VLKpF35mAmjxLZfVDcoObFH3Cv2GB7BEYxv86KC2Y6T74Q/wARAQABwsB2BBgBCAAgBQJdPOn4AhsMFiEE+iaix4d0doj87Q0ek6DcNkbrcegACgkQk6DcNkbrcegLxwf/dXshJnoWqEnRsf6rVb9/Mc66ti+NVQLfNd275dybh/QIJdK3NmSxdnTPIEJRVspJywJoupwXFNrnHG2Ff6QPvHqI+/oNu986r9d7Z1oQibbLHKt8t6kpOfg/xGxotagJuiCQvR9mMjD1DqsdB37SjDxGupZOOJSXWi6KX60IE+uM+QOBfeOZziQwuFmA5wV6RDXIeYJfqrcbeXeR1d0nfNpPHQR1gBiqmxNb6KBbdXD2+EXW60axC7D2b1APmzMlammDliPwsK9U1rc9nuquEBvGDOJf4K+Dzn+mDCqRpP6uAuQ7RKHyim4uyN0wwKObzPqgJCGwjTglkixw+aSTXw== diff --git a/tests/stress.rs b/tests/stress.rs index b75d80468..55e9e35a8 100644 --- a/tests/stress.rs +++ b/tests/stress.rs @@ -1,11 +1,7 @@ //! Stress some functions for testing; if used as a lib, this file is obsolete. -use std::collections::HashSet; - use deltachat::config; use deltachat::context::*; -use deltachat::keyring::*; -use deltachat::pgp; use deltachat::Event; use tempfile::{tempdir, TempDir}; @@ -94,115 +90,6 @@ fn stress_functions(context: &Context) { // free(qr.cast()); } -#[test] -#[ignore] // is too expensive -fn test_encryption_decryption() { - let (public_key, private_key) = pgp::create_keypair("foo@bar.de").unwrap(); - - private_key.split_key().unwrap(); - - let (public_key2, private_key2) = pgp::create_keypair("two@zwo.de").unwrap(); - - assert_ne!(public_key, public_key2); - - let original_text = b"This is a test"; - let mut keyring = Keyring::default(); - keyring.add_owned(public_key.clone()); - keyring.add_ref(&public_key2); - - let ctext_signed = pgp::pk_encrypt(original_text, &keyring, Some(&private_key)).unwrap(); - assert!(!ctext_signed.is_empty()); - assert!(ctext_signed.starts_with("-----BEGIN PGP MESSAGE-----")); - - let ctext_unsigned = pgp::pk_encrypt(original_text, &keyring, None).unwrap(); - assert!(!ctext_unsigned.is_empty()); - assert!(ctext_unsigned.starts_with("-----BEGIN PGP MESSAGE-----")); - - let mut keyring = Keyring::default(); - keyring.add_owned(private_key); - - let mut public_keyring = Keyring::default(); - public_keyring.add_ref(&public_key); - - let mut public_keyring2 = Keyring::default(); - public_keyring2.add_owned(public_key2); - - let mut valid_signatures: HashSet = Default::default(); - - let plain = pgp::pk_decrypt( - ctext_signed.as_bytes(), - &keyring, - &public_keyring, - Some(&mut valid_signatures), - ) - .unwrap(); - - assert_eq!(plain, original_text,); - assert_eq!(valid_signatures.len(), 1); - - valid_signatures.clear(); - - let empty_keyring = Keyring::default(); - let plain = pgp::pk_decrypt( - ctext_signed.as_bytes(), - &keyring, - &empty_keyring, - Some(&mut valid_signatures), - ) - .unwrap(); - assert_eq!(plain, original_text); - assert_eq!(valid_signatures.len(), 0); - - valid_signatures.clear(); - - let plain = pgp::pk_decrypt( - ctext_signed.as_bytes(), - &keyring, - &public_keyring2, - Some(&mut valid_signatures), - ) - .unwrap(); - assert_eq!(plain, original_text); - assert_eq!(valid_signatures.len(), 0); - - valid_signatures.clear(); - - public_keyring2.add_ref(&public_key); - - let plain = pgp::pk_decrypt( - ctext_signed.as_bytes(), - &keyring, - &public_keyring2, - Some(&mut valid_signatures), - ) - .unwrap(); - assert_eq!(plain, original_text); - assert_eq!(valid_signatures.len(), 1); - - valid_signatures.clear(); - - let plain = pgp::pk_decrypt( - ctext_unsigned.as_bytes(), - &keyring, - &public_keyring, - Some(&mut valid_signatures), - ) - .unwrap(); - - assert_eq!(plain, original_text); - - valid_signatures.clear(); - - let mut keyring = Keyring::default(); - keyring.add_ref(&private_key2); - let mut public_keyring = Keyring::default(); - public_keyring.add_ref(&public_key); - - let plain = pgp::pk_decrypt(ctext_signed.as_bytes(), &keyring, &public_keyring, None).unwrap(); - - assert_eq!(plain, original_text); -} - fn cb(_context: &Context, _event: Event) {} #[allow(dead_code)]