mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
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:
committed by
Floris Bruynooghe
parent
c7eca8deb3
commit
98b3151c5f
@@ -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
|
||||
|
||||
@@ -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<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.
|
||||
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<T, E> {
|
||||
fn unwrap_or_log_default(self, context: &context::Context, message: &str) -> T;
|
||||
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> {
|
||||
@@ -3180,6 +3247,15 @@ impl<T: Default, E: std::fmt::Display> ResultExt<T, E> for Result<T, E> {
|
||||
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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -460,7 +460,7 @@ pub(crate) fn time() -> i64 {
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::dc_tools::EmailAddress;
|
||||
/// let email = match "someone@example.com".parse::<EmailAddress>() {
|
||||
/// 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<Self, Error> {
|
||||
input.parse::<EmailAddress>()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EmailAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}@{}", self.local, self.domain)
|
||||
|
||||
51
src/e2ee.rs
51
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<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) {
|
||||
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<str
|
||||
context,
|
||||
"Generating keypair with {} bits, e={} ...", 2048, 65537,
|
||||
);
|
||||
match pgp::create_keypair(&self_addr) {
|
||||
Some((public_key, private_key)) => {
|
||||
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.
|
||||
|
||||
15
src/error.rs
15
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<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 {
|
||||
fn from(err: mailparse::MailParseError) -> Error {
|
||||
Error::MailParseError(err)
|
||||
|
||||
44
src/imex.rs
44
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();
|
||||
|
||||
206
src/key.rs
206
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<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
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Key {
|
||||
@@ -35,7 +101,7 @@ impl From<SignedSecretKey> for Key {
|
||||
impl std::convert::TryFrom<Key> for SignedSecretKey {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Key) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: Key) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
Key::Public(_) => Err(()),
|
||||
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 {
|
||||
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 {
|
||||
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<Key> for SignedPublicKey {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Key) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: Key) -> std::result::Result<Self, Self::Error> {
|
||||
match value {
|
||||
Key::Public(key) => Ok(key),
|
||||
Key::Secret(_) => Err(()),
|
||||
@@ -68,7 +134,7 @@ impl std::convert::TryFrom<Key> for SignedPublicKey {
|
||||
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey {
|
||||
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 {
|
||||
Key::Public(key) => Ok(key),
|
||||
Key::Secret(_) => Err(()),
|
||||
@@ -92,7 +158,7 @@ impl Key {
|
||||
if bytes.is_empty() {
|
||||
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::Private => SignedSecretKey::from_bytes(Cursor::new(bytes)).map(Into::into),
|
||||
};
|
||||
@@ -111,7 +177,7 @@ impl Key {
|
||||
key_type: KeyType,
|
||||
) -> Option<(Self, BTreeMap<String, String>)> {
|
||||
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<Self> {
|
||||
// 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<Self> {
|
||||
// // 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<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,
|
||||
public_key: &Key,
|
||||
private_key: &Key,
|
||||
addr: impl AsRef<str>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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<String> {
|
||||
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,
|
||||
|
||||
238
src/pgp.rs
238
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<String, Str
|
||||
Ok((typ, headers, bytes))
|
||||
}
|
||||
|
||||
/// Create a new key pair.
|
||||
pub fn create_keypair(addr: impl AsRef<str>) -> 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<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()
|
||||
.key_type(PgpKeyType::Rsa(2048))
|
||||
.can_create_certificates(true)
|
||||
@@ -146,20 +180,29 @@ pub fn create_keypair(addr: impl AsRef<str>) -> 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<T: std::io::Read + std::io::Seek>(
|
||||
#[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<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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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<Box<ContextCallback>>) -> TestContext {
|
||||
pub(crate) fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
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
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
|
||||
1
test-data/key/alice-public.asc
Normal file
1
test-data/key/alice-public.asc
Normal 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==
|
||||
1
test-data/key/alice-secret.asc
Normal file
1
test-data/key/alice-secret.asc
Normal file
@@ -0,0 +1 @@
|
||||
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
|
||||
1
test-data/key/bob-public.asc
Normal file
1
test-data/key/bob-public.asc
Normal 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=
|
||||
1
test-data/key/bob-secret.asc
Normal file
1
test-data/key/bob-secret.asc
Normal file
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -1 +0,0 @@
|
||||
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
|
||||
@@ -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==
|
||||
113
tests/stress.rs
113
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<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) {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
Reference in New Issue
Block a user