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.
This commit is contained in:
Floris Bruynooghe
2020-01-24 00:08:11 +01:00
committed by Floris Bruynooghe
parent c7eca8deb3
commit 98b3151c5f
25 changed files with 699 additions and 294 deletions

View File

@@ -852,6 +852,27 @@ void dc_interrupt_smtp_idle (dc_context_t* context);
void dc_maybe_network (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 // handle chatlists
#define DC_GCL_ARCHIVED_ONLY 0x01 #define DC_GCL_ARCHIVED_ONLY 0x01

View File

@@ -28,6 +28,7 @@ use deltachat::chat::ChatId;
use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL;
use deltachat::contact::Contact; use deltachat::contact::Contact;
use deltachat::context::Context; use deltachat::context::Context;
use deltachat::key::DcKey;
use deltachat::message::MsgId; use deltachat::message::MsgId;
use deltachat::stock::StockMessage; use deltachat::stock::StockMessage;
use deltachat::*; use deltachat::*;
@@ -94,6 +95,14 @@ impl ContextWrapper {
self.translate_cb(Event::Error(msg.to_string())); 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. /// Unlock the context and execute a closure with it.
/// ///
/// This unlocks the context and gets a read lock. The Rust /// 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<T, F>(&self, ctxfn: F) -> Result<T, failure::Error>
where
F: FnOnce(&Context) -> Result<T, failure::Error>,
{
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. /// Translates the callback from the rust style to the C-style version.
unsafe fn translate_cb(&self, event: Event) { unsafe fn translate_cb(&self, event: Event) {
if let Some(ffi_cb) = self.cb { 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(()) .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] #[no_mangle]
pub unsafe extern "C" fn dc_get_chatlist( pub unsafe extern "C" fn dc_get_chatlist(
context: *mut dc_context_t, 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<T, E> { pub trait ResultExt<T, E> {
fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T; fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T;
fn log_err(self, context: &context::Context, message: &str) -> Result<T, E>; fn log_err(self, context: &context::Context, message: &str) -> Result<T, E>;
/// 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<T, E>;
} }
impl<T: Default, E: std::fmt::Display> ResultExt<T, E> for Result<T, E> { impl<T: Default, E: std::fmt::Display> ResultExt<T, E> for Result<T, E> {
@@ -3180,6 +3247,15 @@ impl<T: Default, E: std::fmt::Display> ResultExt<T, E> for Result<T, E> {
err err
}) })
} }
fn log_warn(self, wrapper: &ContextWrapper, message: &str) -> Result<T, E> {
self.map_err(|err| {
unsafe {
wrapper.warning(&format!("{}: {}", message, err));
}
err
})
}
} }
unsafe fn strdup_opt(s: Option<impl AsRef<str>>) -> *mut libc::c_char { unsafe fn strdup_opt(s: Option<impl AsRef<str>>) -> *mut libc::c_char {

View File

@@ -118,6 +118,18 @@ class Account(object):
assert res != ffi.NULL, "config value not found for: {!r}".format(name) assert res != ffi.NULL, "config value not found for: {!r}".format(name)
return from_dc_charpointer(res) 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): def configure(self, **kwargs):
""" set config values and configure this account. """ set config values and configure this account.

View File

@@ -1,4 +1,5 @@
from __future__ import print_function from __future__ import print_function
import py
import pytest import pytest
import os import os
import queue 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 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: class TestOfflineAccountBasic:
def test_wrong_db(self, tmpdir): def test_wrong_db(self, tmpdir):
p = tmpdir.join("hello.db") p = tmpdir.join("hello.db")
@@ -24,6 +36,12 @@ class TestOfflineAccountBasic:
ac1 = Account(p.strpath, os_name="solarpunk") ac1 = Account(p.strpath, os_name="solarpunk")
ac1.get_info() 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): def test_getinfo(self, acfactory):
ac1 = acfactory.get_unconfigured_account() ac1 = acfactory.get_unconfigured_account()
d = ac1.get_info() d = ac1.get_info()

View File

@@ -6,10 +6,9 @@ use std::collections::BTreeMap;
use std::str::FromStr; use std::str::FromStr;
use std::{fmt, str}; use std::{fmt, str};
use crate::constants::*;
use crate::contact::*; use crate::contact::*;
use crate::context::Context; use crate::context::Context;
use crate::key::*; use crate::key::{DcKey, SignedPublicKey};
/// Possible values for encryption preference /// Possible values for encryption preference
#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive)] #[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive)]
@@ -52,13 +51,17 @@ impl str::FromStr for EncryptPreference {
#[derive(Debug)] #[derive(Debug)]
pub struct Aheader { pub struct Aheader {
pub addr: String, pub addr: String,
pub public_key: Key, pub public_key: SignedPublicKey,
pub prefer_encrypt: EncryptPreference, pub prefer_encrypt: EncryptPreference,
} }
impl Aheader { impl Aheader {
/// Creates new autocrypt header /// 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 { Aheader {
addr, addr,
public_key, public_key,
@@ -103,16 +106,19 @@ impl fmt::Display for Aheader {
// adds a whitespace every 78 characters, this allows // adds a whitespace every 78 characters, this allows
// email crate to wrap the lines according to RFC 5322 // email crate to wrap the lines according to RFC 5322
// (which may insert a linebreak before every whitespace) // (which may insert a linebreak before every whitespace)
let keydata = self.public_key.to_base64().chars().enumerate().fold( let keydata = self
String::new(), .public_key
|mut res, (i, c)| { .to_base64()
.unwrap_or_default()
.chars()
.enumerate()
.fold(String::new(), |mut res, (i, c)| {
if i % 78 == 78 - "keydata=".len() { if i % 78 == 78 - "keydata=".len() {
res.push(' ') res.push(' ')
} }
res.push(c); res.push(c);
res res
}, });
);
write!(fmt, " keydata={}", keydata) write!(fmt, " keydata={}", keydata)
} }
} }
@@ -142,22 +148,11 @@ impl str::FromStr for Aheader {
return Err(()); return Err(());
} }
}; };
let public_key: SignedPublicKey = attributes
let public_key = match attributes
.remove("keydata") .remove("keydata")
.and_then(|raw| Key::from_base64(&raw, KeyType::Public)) .ok_or(())
{ .and_then(|raw| SignedPublicKey::from_base64(&raw).or(Err(())))
Some(key) => { .and_then(|key| key.verify().and(Ok(key)).or(Err(())))?;
if key.verify() {
key
} else {
return Err(());
}
}
None => {
return Err(());
}
};
let prefer_encrypt = attributes let prefer_encrypt = attributes
.remove("prefer-encrypt") .remove("prefer-encrypt")
@@ -292,7 +287,7 @@ mod tests {
"{}", "{}",
Aheader::new( Aheader::new(
"test@example.com".to_string(), "test@example.com".to_string(),
Key::from_base64(RAWKEY, KeyType::Public).unwrap(), SignedPublicKey::from_base64(RAWKEY).unwrap(),
EncryptPreference::Mutual EncryptPreference::Mutual
) )
) )
@@ -305,7 +300,7 @@ mod tests {
"{}", "{}",
Aheader::new( Aheader::new(
"test@example.com".to_string(), "test@example.com".to_string(),
Key::from_base64(RAWKEY, KeyType::Public).unwrap(), SignedPublicKey::from_base64(RAWKEY).unwrap(),
EncryptPreference::NoPreference EncryptPreference::NoPreference
) )
) )

View File

@@ -45,7 +45,7 @@ impl ChatId {
/// An unset 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. /// transitional and should not be used in new code.
pub fn is_unset(self) -> bool { pub fn is_unset(self) -> bool {
self.0 == 0 self.0 == 0

View File

@@ -14,7 +14,7 @@ use crate::events::Event;
use crate::imap::*; use crate::imap::*;
use crate::job::*; use crate::job::*;
use crate::job_thread::JobThread; use crate::job_thread::JobThread;
use crate::key::*; use crate::key::Key;
use crate::login_param::LoginParam; use crate::login_param::LoginParam;
use crate::lot::Lot; use crate::lot::Lot;
use crate::message::{self, Message, MessengerMessage, MsgId}; use crate::message::{self, Message, MessengerMessage, MsgId};

View File

@@ -460,7 +460,7 @@ pub(crate) fn time() -> i64 {
/// ///
/// ``` /// ```
/// use deltachat::dc_tools::EmailAddress; /// use deltachat::dc_tools::EmailAddress;
/// let email = match "someone@example.com".parse::<EmailAddress>() { /// let email = match EmailAddress::new("someone@example.com") {
/// Ok(addr) => addr, /// Ok(addr) => addr,
/// Err(e) => panic!("Error parsing address, error was {}", e), /// 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.domain, "example.com");
/// assert_eq!(email.to_string(), "someone@example.com"); /// assert_eq!(email.to_string(), "someone@example.com");
/// ``` /// ```
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct EmailAddress { pub struct EmailAddress {
pub local: String, pub local: String,
pub domain: String, pub domain: String,
} }
impl EmailAddress {
pub fn new(input: &str) -> Result<Self, Error> {
input.parse::<EmailAddress>()
}
}
impl fmt::Display for EmailAddress { impl fmt::Display for EmailAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}@{}", self.local, self.domain) write!(f, "{}@{}", self.local, self.domain)

View File

@@ -1,6 +1,7 @@
//! End-to-end encryption support. //! End-to-end encryption support.
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom;
use mailparse::{MailHeaderMap, ParsedMail}; use mailparse::{MailHeaderMap, ParsedMail};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
@@ -8,8 +9,9 @@ use num_traits::FromPrimitive;
use crate::aheader::*; use crate::aheader::*;
use crate::config::Config; use crate::config::Config;
use crate::context::Context; use crate::context::Context;
use crate::dc_tools::EmailAddress;
use crate::error::*; use crate::error::*;
use crate::key::*; use crate::key::{self, Key, KeyPairUse, SignedPublicKey};
use crate::keyring::*; use crate::keyring::*;
use crate::peerstate::*; use crate::peerstate::*;
use crate::pgp; use crate::pgp;
@@ -19,7 +21,7 @@ use crate::securejoin::handle_degrade_event;
pub struct EncryptHelper { pub struct EncryptHelper {
pub prefer_encrypt: EncryptPreference, pub prefer_encrypt: EncryptPreference,
pub addr: String, pub addr: String,
pub public_key: Key, pub public_key: SignedPublicKey,
} }
impl EncryptHelper { impl EncryptHelper {
@@ -102,8 +104,8 @@ impl EncryptHelper {
})?; })?;
keyring.add_ref(key); keyring.add_ref(key);
} }
let public_key = Key::from(self.public_key.clone());
keyring.add_ref(&self.public_key); keyring.add_ref(&public_key);
let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql) let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql)
.ok_or_else(|| format_err!("missing own private key"))?; .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 /// 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 /// only generate one key per context even when multiple threads call
/// this function concurrently. /// this function concurrently.
fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str>) -> Result<Key> { fn load_or_generate_self_public_key(
context: &Context,
self_addr: impl AsRef<str>,
) -> Result<SignedPublicKey> {
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql) { 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(); let _guard = context.generating_key_mutex.lock().unwrap();
// Check again in case the key was generated while we were waiting for the lock. // 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) { 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(); let start = std::time::Instant::now();
@@ -207,28 +214,14 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
context, context,
"Generating keypair with {} bits, e={} ...", 2048, 65537, "Generating keypair with {} bits, e={} ...", 2048, 65537,
); );
match pgp::create_keypair(&self_addr) { let keypair = pgp::create_keypair(EmailAddress::new(self_addr.as_ref())?)?;
Some((public_key, private_key)) => { key::save_self_keypair(context, &keypair, KeyPairUse::Default)?;
if dc_key_save_self_keypair( info!(
context, context,
&public_key, "Keypair generated in {:.3}s.",
&private_key, start.elapsed().as_secs()
&self_addr, );
true, Ok(keypair.public)
&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")),
}
} }
/// Returns a reference to the encrypted payload and validates the autocrypt structure. /// Returns a reference to the encrypted payload and validates the autocrypt structure.

View File

@@ -16,6 +16,9 @@ pub enum Error {
#[fail(display = "{:?}", _0)] #[fail(display = "{:?}", _0)]
Message(String), Message(String),
#[fail(display = "{:?}", _0)]
MessageChain(String, #[cause] failure::Error, failure::Backtrace),
#[fail(display = "{:?}", _0)] #[fail(display = "{:?}", _0)]
Image(image_meta::ImageError), Image(image_meta::ImageError),
@@ -118,6 +121,18 @@ impl From<crate::message::InvalidMsgId> for Error {
} }
} }
impl From<crate::key::SaveKeyError> for Error {
fn from(err: crate::key::SaveKeyError) -> Error {
Error::MessageChain(format!("{}", err), err.into(), failure::Backtrace::new())
}
}
impl From<crate::pgp::PgpKeygenError> for Error {
fn from(err: crate::pgp::PgpKeygenError) -> Error {
Error::MessageChain(format!("{}", err), err.into(), failure::Backtrace::new())
}
}
impl From<mailparse::MailParseError> for Error { impl From<mailparse::MailParseError> for Error {
fn from(err: mailparse::MailParseError) -> Error { fn from(err: mailparse::MailParseError) -> Error {
Error::MailParseError(err) Error::MailParseError(err)

View File

@@ -18,7 +18,7 @@ use crate::e2ee;
use crate::error::*; use crate::error::*;
use crate::events::Event; use crate::events::Event;
use crate::job::*; use crate::job::*;
use crate::key::*; use crate::key::{self, Key};
use crate::message::{Message, MsgId}; use crate::message::{Message, MsgId};
use crate::mimeparser::SystemMessage; use crate::mimeparser::SystemMessage;
use crate::param::*; 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))); .and_then(|(k, h)| k.split_key().map(|pub_key| (k, pub_key, h)));
ensure!(keys.is_some(), "Not a valid private key"); ensure!(keys.is_some(), "Not a valid private key");
let (private_key, public_key, header) = keys.unwrap(); let (private_key, public_key, header) = keys.unwrap();
let preferencrypt = header.get("Autocrypt-Prefer-Encrypt"); let preferencrypt = header.get("Autocrypt-Prefer-Encrypt");
match preferencrypt.map(|s| s.as_str()) { match preferencrypt.map(|s| s.as_str()) {
@@ -314,6 +313,7 @@ fn set_self_key(
let self_addr = context.get_config(Config::ConfiguredAddr); let self_addr = context.get_config(Config::ConfiguredAddr);
ensure!(self_addr.is_some(), "Missing self addr"); 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 // XXX maybe better make dc_key_save_self_keypair delete things
sql::execute( sql::execute(
@@ -322,26 +322,24 @@ fn set_self_key(
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;", "DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
params![public_key.to_bytes(), private_key.to_bytes()], params![public_key.to_bytes(), private_key.to_bytes()],
)?; )?;
let (public, secret) = match (public_key, private_key) {
if set_default { (Key::Public(p), Key::Secret(s)) => (p, s),
sql::execute( _ => bail!("wrong keys unpacked"),
context, };
&context.sql, let keypair = pgp::KeyPair {
"UPDATE keypairs SET is_default=0;", addr,
params![], public,
)?; secret,
} };
key::save_self_keypair(
if !dc_key_save_self_keypair(
context, context,
&public_key, &keypair,
&private_key, if set_default {
self_addr.unwrap_or_default(), key::KeyPairUse::Default
set_default, } else {
&context.sql, key::KeyPairUse::ReadOnly
) { },
bail!("Cannot save keypair, internal key-state possibly corrupted now!"); )?;
}
Ok(()) Ok(())
} }
@@ -746,7 +744,6 @@ fn export_key_to_asc_file(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE}; use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
use crate::test_utils::*; use crate::test_utils::*;
use ::pgp::armor::BlockType; use ::pgp::armor::BlockType;
@@ -801,8 +798,7 @@ mod tests {
#[test] #[test]
fn test_export_key_to_asc_file() { fn test_export_key_to_asc_file() {
let context = dummy_context(); let context = dummy_context();
let base64 = include_str!("../test-data/key/public.asc"); let key = Key::from(alice_keypair().public);
let key = Key::from_base64(base64, KeyType::Public).unwrap();
let blobdir = "$BLOBDIR"; let blobdir = "$BLOBDIR";
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok()); assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok());
let blobdir = context.ctx.get_blobdir().to_str().unwrap(); let blobdir = context.ctx.get_blobdir().to_str().unwrap();

View File

@@ -4,7 +4,7 @@ use std::collections::BTreeMap;
use std::io::Cursor; use std::io::Cursor;
use std::path::Path; use std::path::Path;
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey}; use pgp::composed::Deserializable;
use pgp::ser::Serialize; use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait}; use pgp::types::{KeyTrait, SecretKeyTrait};
@@ -13,6 +13,72 @@ use crate::context::Context;
use crate::dc_tools::*; use crate::dc_tools::*;
use crate::sql::{self, Sql}; 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<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Error {
Error::Base64Decode(err, failure::Backtrace::new())
}
}
impl From<pgp::errors::Error> for Error {
fn from(err: pgp::errors::Error) -> Error {
Error::PgpError(err, failure::Backtrace::new())
}
}
pub type Result<T> = std::result::Result<T, Error>;
/// 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<Self::KeyType> {
Ok(<Self::KeyType as Deserializable>::from_bytes(Cursor::new(
bytes,
))?)
}
fn from_base64(data: &str) -> Result<Self::KeyType> {
// 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<String> {
let bytes = self.to_bytes()?;
Ok(base64::encode(&bytes))
}
// fn verify(&self) -> Result<Self::KeyType> {
// <Self as Self::KeyType>::verify(self)
// }
}
impl DcKey for SignedPublicKey {
type KeyType = SignedPublicKey;
}
impl DcKey for SignedSecretKey {
type KeyType = SignedSecretKey;
}
/// Cryptographic key /// Cryptographic key
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum Key { pub enum Key {
@@ -35,7 +101,7 @@ impl From<SignedSecretKey> for Key {
impl std::convert::TryFrom<Key> for SignedSecretKey { impl std::convert::TryFrom<Key> for SignedSecretKey {
type Error = (); type Error = ();
fn try_from(value: Key) -> Result<Self, Self::Error> { fn try_from(value: Key) -> std::result::Result<Self, Self::Error> {
match value { match value {
Key::Public(_) => Err(()), Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key), Key::Secret(key) => Ok(key),
@@ -46,7 +112,7 @@ impl std::convert::TryFrom<Key> for SignedSecretKey {
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey { impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey {
type Error = (); type Error = ();
fn try_from(value: &'a Key) -> Result<Self, Self::Error> { fn try_from(value: &'a Key) -> std::result::Result<Self, Self::Error> {
match value { match value {
Key::Public(_) => Err(()), Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key), Key::Secret(key) => Ok(key),
@@ -57,7 +123,7 @@ impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey {
impl std::convert::TryFrom<Key> for SignedPublicKey { impl std::convert::TryFrom<Key> for SignedPublicKey {
type Error = (); type Error = ();
fn try_from(value: Key) -> Result<Self, Self::Error> { fn try_from(value: Key) -> std::result::Result<Self, Self::Error> {
match value { match value {
Key::Public(key) => Ok(key), Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()), Key::Secret(_) => Err(()),
@@ -68,7 +134,7 @@ impl std::convert::TryFrom<Key> for SignedPublicKey {
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey { impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey {
type Error = (); type Error = ();
fn try_from(value: &'a Key) -> Result<Self, Self::Error> { fn try_from(value: &'a Key) -> std::result::Result<Self, Self::Error> {
match value { match value {
Key::Public(key) => Ok(key), Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()), Key::Secret(_) => Err(()),
@@ -92,7 +158,7 @@ impl Key {
if bytes.is_empty() { if bytes.is_empty() {
return None; return None;
} }
let res: Result<Key, _> = match key_type { let res: std::result::Result<Key, _> = match key_type {
KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes)).map(Into::into), KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes)).map(Into::into),
KeyType::Private => SignedSecretKey::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, key_type: KeyType,
) -> Option<(Self, BTreeMap<String, String>)> { ) -> Option<(Self, BTreeMap<String, String>)> {
let bytes = data.as_bytes(); 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)) KeyType::Public => SignedPublicKey::from_armor_single(Cursor::new(bytes))
.map(|(k, h)| (Into::into(k), h)), .map(|(k, h)| (Into::into(k), h)),
KeyType::Private => SignedSecretKey::from_armor_single(Cursor::new(bytes)) 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<Self> { // pub fn from_base64(encoded_data: &str, key_type: KeyType) -> Option<Self> {
// strip newlines and other whitespace // // strip newlines and other whitespace
let cleaned: String = encoded_data.trim().split_whitespace().collect(); // let cleaned: String = encoded_data.trim().split_whitespace().collect();
let bytes = cleaned.as_bytes(); // let bytes = cleaned.as_bytes();
base64::decode(bytes) // base64::decode(bytes)
.ok() // .ok()
.and_then(|decoded| Self::from_slice(&decoded, key_type)) // .and_then(|decoded| Self::from_slice(&decoded, key_type))
} // }
pub fn from_self_public( pub fn from_self_public(
context: &Context, 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<String>, cause: impl Into<failure::Error>) -> 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, context: &Context,
public_key: &Key, keypair: &KeyPair,
private_key: &Key, default: KeyPairUse,
addr: impl AsRef<str>, ) -> std::result::Result<(), SaveKeyError> {
is_default: bool, // Should really be one transaction, more refactoring is needed for that.
sql: &Sql, if default == KeyPairUse::Default {
) -> bool { 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( sql::execute(
context, context,
sql, &context.sql,
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);", "INSERT INTO keypairs (addr, is_default, public_key, private_key, created)
params![addr.as_ref(), is_default as i32, public_key.to_bytes(), private_key.to_bytes(), time()], VALUES (?,?,?,?,?);",
).is_ok() 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. /// Make a fingerprint human-readable, in hex format.
@@ -287,6 +417,14 @@ pub fn dc_normalize_fingerprint(fp: &str) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::test_utils::*;
use std::convert::TryFrom;
use lazy_static::lazy_static;
lazy_static! {
static ref KEYPAIR: KeyPair = alice_keypair();
}
#[test] #[test]
fn test_normalize_fingerprint() { fn test_normalize_fingerprint() {
@@ -373,9 +511,9 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
} }
#[test] #[test]
#[ignore] // is too expensive
fn test_from_slice_roundtrip() { 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 binary = public_key.to_bytes();
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key"); let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
@@ -408,9 +546,9 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
} }
#[test] #[test]
#[ignore] // is too expensive
fn test_ascii_roundtrip() { 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 s = public_key.to_armored_string(None).unwrap();
let (public_key2, _) = let (public_key2, _) =
@@ -423,4 +561,12 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
Key::from_armored_string(&s, KeyType::Private).expect("invalid private key"); Key::from_armored_string(&s, KeyType::Private).expect("invalid private key");
assert_eq!(private_key, private_key2); 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);
}
} }

View File

@@ -1,8 +1,8 @@
use std::borrow::Cow; use std::borrow::Cow;
use crate::constants::*; use crate::constants::KeyType;
use crate::context::Context; use crate::context::Context;
use crate::key::*; use crate::key::Key;
use crate::sql::Sql; use crate::sql::Sql;
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]

View File

@@ -1,5 +1,6 @@
//! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module //! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module
use std::collections::HashSet; use std::collections::HashSet;
use std::convert::TryFrom;
use std::fmt; use std::fmt;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
@@ -7,7 +8,7 @@ use num_traits::FromPrimitive;
use crate::aheader::*; use crate::aheader::*;
use crate::constants::*; use crate::constants::*;
use crate::context::Context; use crate::context::Context;
use crate::key::*; use crate::key::{Key, SignedPublicKey};
use crate::sql::{self, Sql}; use crate::sql::{self, Sql};
#[derive(Debug)] #[derive(Debug)]
@@ -126,7 +127,7 @@ impl<'a> Peerstate<'a> {
res.last_seen_autocrypt = message_time; res.last_seen_autocrypt = message_time;
res.to_save = Some(ToSave::All); res.to_save = Some(ToSave::All);
res.prefer_encrypt = header.prefer_encrypt; 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.recalc_fingerprint();
res res
@@ -137,7 +138,7 @@ impl<'a> Peerstate<'a> {
res.gossip_timestamp = message_time; res.gossip_timestamp = message_time;
res.to_save = Some(ToSave::All); 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.recalc_fingerprint();
res res
@@ -293,8 +294,8 @@ impl<'a> Peerstate<'a> {
self.to_save = Some(ToSave::All) self.to_save = Some(ToSave::All)
} }
if self.public_key.as_ref() != Some(&header.public_key) { if self.public_key.as_ref() != Some(&Key::from(header.public_key.clone())) {
self.public_key = Some(header.public_key.clone()); self.public_key = Some(Key::from(header.public_key.clone()));
self.recalc_fingerprint(); self.recalc_fingerprint();
self.to_save = Some(ToSave::All); self.to_save = Some(ToSave::All);
} }
@@ -309,8 +310,9 @@ impl<'a> Peerstate<'a> {
if message_time > self.gossip_timestamp { if message_time > self.gossip_timestamp {
self.gossip_timestamp = message_time; self.gossip_timestamp = message_time;
self.to_save = Some(ToSave::Timestamps); self.to_save = Some(ToSave::Timestamps);
if self.gossip_key.as_ref() != Some(&gossip_header.public_key) { let hdr_key = Key::from(gossip_header.public_key.clone());
self.gossip_key = Some(gossip_header.public_key.clone()); if self.gossip_key.as_ref() != Some(&hdr_key) {
self.gossip_key = Some(hdr_key);
self.recalc_fingerprint(); self.recalc_fingerprint();
self.to_save = Some(ToSave::All) self.to_save = Some(ToSave::All)
} }
@@ -320,9 +322,13 @@ impl<'a> Peerstate<'a> {
pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option<String> { pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option<String> {
if let Some(key) = self.peek_key(min_verified) { if let Some(key) = self.peek_key(min_verified) {
// TODO: avoid cloning // TODO: avoid cloning
let public_key = match SignedPublicKey::try_from(key.clone()) {
Ok(key) => key,
Err(_) => return None,
};
let header = Aheader::new( let header = Aheader::new(
self.addr.clone(), self.addr.clone(),
key.clone(), public_key,
EncryptPreference::NoPreference, EncryptPreference::NoPreference,
); );
Some(header.to_string()) Some(header.to_string())
@@ -450,8 +456,8 @@ impl<'a> Peerstate<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::test_utils::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tempfile::TempDir; use tempfile::TempDir;
#[test] #[test]
@@ -459,11 +465,7 @@ mod tests {
let ctx = crate::test_utils::dummy_context(); let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com"; let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64( let pub_key = crate::key::Key::from(alice_keypair().public);
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let mut peerstate = Peerstate { let mut peerstate = Peerstate {
context: &ctx.ctx, context: &ctx.ctx,
@@ -503,12 +505,7 @@ mod tests {
fn test_peerstate_double_create() { fn test_peerstate_double_create() {
let ctx = crate::test_utils::dummy_context(); let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com"; let addr = "hello@mail.com";
let pub_key = crate::key::Key::from(alice_keypair().public);
let pub_key = crate::key::Key::from_base64(
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let peerstate = Peerstate { let peerstate = Peerstate {
context: &ctx.ctx, context: &ctx.ctx,
@@ -542,11 +539,7 @@ mod tests {
let ctx = crate::test_utils::dummy_context(); let ctx = crate::test_utils::dummy_context();
let addr = "hello@mail.com"; let addr = "hello@mail.com";
let pub_key = crate::key::Key::from_base64( let pub_key = crate::key::Key::from(alice_keypair().public);
include_str!("../test-data/key/public.asc"),
KeyType::Public,
)
.unwrap();
let mut peerstate = Peerstate { let mut peerstate = Peerstate {
context: &ctx.ctx, context: &ctx.ctx,

View File

@@ -16,6 +16,7 @@ use pgp::types::{
}; };
use rand::{thread_rng, CryptoRng, Rng}; use rand::{thread_rng, CryptoRng, Rng};
use crate::dc_tools::EmailAddress;
use crate::error::Result; use crate::error::Result;
use crate::key::*; use crate::key::*;
use crate::keyring::*; use crate::keyring::*;
@@ -111,10 +112,43 @@ pub fn split_armored_data(buf: &[u8]) -> Result<(BlockType, BTreeMap<String, Str
Ok((typ, headers, bytes)) Ok((typ, headers, bytes))
} }
/// Create a new key pair. /// Error with generating a PGP keypair.
pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> { ///
let user_id = format!("<{}>", addr.as_ref()); /// 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<String>, cause: impl Into<failure::Error>) -> 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<KeyPair, PgpKeygenError> {
let user_id = format!("<{}>", addr);
let key_params = SecretKeyParamsBuilder::default() let key_params = SecretKeyParamsBuilder::default()
.key_type(PgpKeyType::Rsa(2048)) .key_type(PgpKeyType::Rsa(2048))
.can_create_certificates(true) .can_create_certificates(true)
@@ -146,20 +180,29 @@ pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
.unwrap(), .unwrap(),
) )
.build() .build()
.expect("invalid key params"); .map_err(|err| PgpKeygenError::new("invalid key params", failure::err_msg(err)))?;
let key = key_params
let key = key_params.generate().expect("invalid params"); .generate()
.map_err(|err| PgpKeygenError::new("invalid params", err))?;
let private_key = key.sign(|| "".into()).expect("failed to sign secret key"); let private_key = key.sign(|| "".into()).expect("failed to sign secret key");
let public_key = private_key.public_key(); let public_key = private_key.public_key();
let public_key = public_key let public_key = public_key
.sign(&private_key, || "".into()) .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"); private_key
public_key.verify().expect("invalid public key generated"); .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. /// Select public key or subkey to use for encryption.
@@ -311,6 +354,8 @@ pub fn symm_decrypt<T: std::io::Read + std::io::Seek>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::test_utils::*;
use lazy_static::lazy_static;
#[test] #[test]
fn test_split_armored_data_1() { fn test_split_armored_data_1() {
@@ -338,4 +383,177 @@ mod tests {
assert!(!base64.is_empty()); assert!(!base64.is_empty());
assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string())); 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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);
}
} }

View File

@@ -9,7 +9,7 @@ use crate::contact::*;
use crate::context::Context; use crate::context::Context;
use crate::error::Error; use crate::error::Error;
use crate::key::dc_format_fingerprint; use crate::key::dc_format_fingerprint;
use crate::key::*; use crate::key::dc_normalize_fingerprint;
use crate::lot::{Lot, LotState}; use crate::lot::{Lot, LotState};
use crate::param::*; use crate::param::*;
use crate::peerstate::*; use crate::peerstate::*;

View File

@@ -12,7 +12,7 @@ use crate::e2ee::*;
use crate::error::Error; use crate::error::Error;
use crate::events::Event; use crate::events::Event;
use crate::headerdef::HeaderDef; use crate::headerdef::HeaderDef;
use crate::key::*; use crate::key::{dc_normalize_fingerprint, Key};
use crate::lot::LotState; use crate::lot::LotState;
use crate::message::Message; use crate::message::Message;
use crate::mimeparser::*; use crate::mimeparser::*;

View File

@@ -5,16 +5,16 @@
use tempfile::{tempdir, TempDir}; use tempfile::{tempdir, TempDir};
use crate::config::Config; use crate::config::Config;
use crate::constants::KeyType;
use crate::context::{Context, ContextCallback}; use crate::context::{Context, ContextCallback};
use crate::dc_tools::EmailAddress;
use crate::events::Event; use crate::events::Event;
use crate::key; use crate::key::{self, DcKey};
/// A Context and temporary directory. /// A Context and temporary directory.
/// ///
/// The temporary directory can be used to store the SQLite database, /// The temporary directory can be used to store the SQLite database,
/// see e.g. [test_context] which does this. /// see e.g. [test_context] which does this.
pub struct TestContext { pub(crate) struct TestContext {
pub ctx: Context, pub ctx: Context,
pub dir: TempDir, pub dir: TempDir,
} }
@@ -25,7 +25,7 @@ pub struct TestContext {
/// "db.sqlite" in the [TestContext.dir] directory. /// "db.sqlite" in the [TestContext.dir] directory.
/// ///
/// [Context]: crate::context::Context /// [Context]: crate::context::Context
pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext { pub(crate) fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite"); let dbfile = dir.path().join("db.sqlite");
let cb: Box<ContextCallback> = match callback { let cb: Box<ContextCallback> = match callback {
@@ -41,11 +41,11 @@ pub fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
/// The context will be opened and use the SQLite database as /// The context will be opened and use the SQLite database as
/// specified in [test_context] but there is no callback hooked up, /// specified in [test_context] but there is no callback hooked up,
/// i.e. [Context::call_cb] will always return `0`. /// i.e. [Context::call_cb] will always return `0`.
pub fn dummy_context() -> TestContext { pub(crate) fn dummy_context() -> TestContext {
test_context(None) test_context(None)
} }
pub fn logging_cb(_ctx: &Context, evt: Event) { pub(crate) fn logging_cb(_ctx: &Context, evt: Event) {
match evt { match evt {
Event::Info(msg) => println!("I: {}", msg), Event::Info(msg) => println!("I: {}", msg),
Event::Warning(msg) => println!("W: {}", 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. /// Creates Alice with a pre-generated keypair.
/// ///
/// Returns the address of the keypair created (alice@example.org). /// Returns the address of the keypair created (alice@example.com).
pub fn configure_alice_keypair(ctx: &Context) -> String { pub(crate) fn configure_alice_keypair(ctx: &Context) -> String {
let addr = String::from("alice@example.org"); let keypair = alice_keypair();
ctx.set_config(Config::ConfiguredAddr, Some(&addr)).unwrap(); ctx.set_config(Config::ConfiguredAddr, Some(&keypair.addr.to_string()))
.unwrap();
// The keypair was created using: key::save_self_keypair(&ctx, &keypair, key::KeyPairUse::Default)
// let (public, private) = crate::pgp::dc_pgp_create_keypair("alice@example.com") .expect("Failed to save Alice's key");
// .unwrap(); keypair.addr.to_string()
// println!("{}", public.to_base64(64)); }
// println!("{}", private.to_base64(64));
let public = /// Load a pre-generated keypair for bob@example.net from disk.
key::Key::from_base64(include_str!("../test-data/key/public.asc"), KeyType::Public) ///
.unwrap(); /// Like [alice_keypair] but a different key and identity.
let private = key::Key::from_base64( pub(crate) fn bob_keypair() -> key::KeyPair {
include_str!("../test-data/key/private.asc"), let addr = EmailAddress::new("bob@example.net").unwrap();
KeyType::Private, let public =
) key::SignedPublicKey::from_base64(include_str!("../test-data/key/bob-public.asc")).unwrap();
.unwrap(); let secret =
let saved = key::dc_key_save_self_keypair(&ctx, &public, &private, &addr, true, &ctx.sql); key::SignedSecretKey::from_base64(include_str!("../test-data/key/bob-secret.asc")).unwrap();
assert_eq!(saved, true, "Failed to save Alice's key"); key::KeyPair {
addr addr,
public,
secret,
}
} }

View File

@@ -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==

View File

@@ -0,0 +1 @@
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

View File

@@ -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=

View File

@@ -0,0 +1 @@
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

View File

@@ -1 +0,0 @@
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

View File

@@ -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==

View File

@@ -1,11 +1,7 @@
//! Stress some functions for testing; if used as a lib, this file is obsolete. //! Stress some functions for testing; if used as a lib, this file is obsolete.
use std::collections::HashSet;
use deltachat::config; use deltachat::config;
use deltachat::context::*; use deltachat::context::*;
use deltachat::keyring::*;
use deltachat::pgp;
use deltachat::Event; use deltachat::Event;
use tempfile::{tempdir, TempDir}; use tempfile::{tempdir, TempDir};
@@ -94,115 +90,6 @@ fn stress_functions(context: &Context) {
// free(qr.cast()); // 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<String> = 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) {} fn cb(_context: &Context, _event: Event) {}
#[allow(dead_code)] #[allow(dead_code)]