Compare commits

..

16 Commits

Author SHA1 Message Date
B. Petersen
e579afc3bd just print events
just print events instead of using the info!() warn!() error!() macros
that result in an event beeing emmited again.
2020-06-01 16:25:41 +02:00
Hocuri
7b3a962498 Sanitize address book 2020-05-31 17:04:25 +02:00
Hocuri
41bba7e780 Fix #880 Don't vary advanced login settings if a user set a particular setting 2020-05-29 20:31:34 +02:00
Friedel Ziegelmayer
419b7d1d5c Merge pull request #1539 from deltachat/sane-configure
refactor(configure): simplify logic and code
2020-05-29 19:32:49 +02:00
dignifiedquire
6d8b4a7ec0 simplify further and apply CR 2020-05-29 19:09:45 +02:00
B. Petersen
84963e198e do autoconfig only when no advanced options are entered
the advanced options are not used anyway later,
but prevent imap/smtp connections from being altered.

nb: we want to stop altering when some advanced options
are entered, however, we want to do this probaby
not depending on autoconfig.
2020-05-29 19:09:45 +02:00
dignifiedquire
408e9946af refactor(configure): cleanup logic 2020-05-29 19:09:45 +02:00
dignifiedquire
43f49f8917 refactor(configure): remove step-counter 2020-05-29 19:09:45 +02:00
Hocuri
b6161c431b Fix #1474 "Sending message to contact with < or > in Recipient gets treated as "Sent" but is not received" (#1476)
Fix #1474 "Sending message to contact with < or > in Recipient gets treated as "Sent" but is not received".

As I was at it, I also extracted the correct name and address from addresses like Mueller, Dave <dave@domain.com>.
2020-05-29 18:14:21 +02:00
Floris Bruynooghe
a236a619ad Finish Key->DcKey refactoring
Migrates .verify() and .split_key() to DcKey.  Removes all remaining
uses of Key.
2020-05-29 11:25:52 +02:00
Floris Bruynooghe
cdbd3d7d84 Move ascii-armored stuff from Key to DcKey
This means all key conversions/serialisation/deserialisation can be
done with DcKey rather than Key.  Also migrate all key conversion
tests to DcKey rather than Key.
2020-05-29 11:25:52 +02:00
Floris Bruynooghe
8efc880b77 Move Keyring and fingerprint to DcKey trait
This moves both the Keyring and the fingerprints to the DcKey trait,
unfortunately I was not able to disentangle these two changes.  The
Keyring now ensures only the right kind of key is added to it.

The keyring now uses the DcKey::load_self method rather than
re-implement the SQL to load keys from the database.  This vastly
simpliefies the use and fixes an error where a failed key load or
unconfigured would result in the message being treated as plain text
and benefits from the in-line key generation path.

For the fingerprint a new type representing it is introduced.  The aim
is to replace more fingerpring uses with this type as now there are
various string representations being passed around and converted
between.  The Display trait is used for the space-separated and
multiline format, which is perhaps not the most obvious but seems
right together with FromStr etc.
2020-05-29 11:25:52 +02:00
jikstra
4bade7e13a Update cargo lock 2020-05-28 20:38:11 +02:00
Jikstra
53099bbfd1 Merge pull request #1537 from deltachat/fix_dc_str_constants
Add missing DC_STR_* constants to deltachat.h
2020-05-28 18:24:11 +02:00
jikstra
7d1d02bf3b Add missing DC_STR_* constants to deltachat.h 2020-05-28 13:41:56 +02:00
bjoern
4f477ec6d2 Merge pull request #1536 from deltachat/prep-1.34
Prep 1.34
2020-05-27 20:33:53 +02:00
15 changed files with 954 additions and 935 deletions

90
Cargo.lock generated
View File

@@ -114,7 +114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423"
dependencies = [
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -230,7 +230,7 @@ checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -636,7 +636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d"
dependencies = [
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -673,7 +673,7 @@ dependencies = [
"proc-macro2",
"quote 1.0.6",
"strsim",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -684,7 +684,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -775,7 +775,7 @@ name = "deltachat_derive"
version = "2.0.0"
dependencies = [
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -802,7 +802,7 @@ dependencies = [
"derive_builder_core",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -814,7 +814,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -1036,7 +1036,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
"synstructure",
]
@@ -1169,7 +1169,7 @@ dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -1499,9 +1499,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.39"
version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7"
checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177"
dependencies = [
"wasm-bindgen",
]
@@ -1569,9 +1569,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.70"
version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
[[package]]
name = "libm"
@@ -1788,7 +1788,7 @@ checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -2034,7 +2034,7 @@ checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -2404,9 +2404,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.3.7"
version = "1.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
checksum = "226ddd1197737bcb937489322ec1b9edaac1709d46792886e70f2113923585a6"
dependencies = [
"aho-corasick",
"memchr",
@@ -2416,9 +2416,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.17"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "remove_dir_all"
@@ -2447,7 +2447,7 @@ checksum = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -2674,7 +2674,7 @@ checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -2768,7 +2768,7 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
[[package]]
name = "smol"
version = "0.1.10"
source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#41311e709401773c167819d98a56e0c1762d2ba5"
source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#1ba84935e4e48be927c7f8f4a9cb5c05614135c3"
dependencies = [
"async-task",
"crossbeam-deque",
@@ -2855,7 +2855,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -2897,9 +2897,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.23"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269"
checksum = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de"
dependencies = [
"proc-macro2",
"quote 1.0.6",
@@ -2923,7 +2923,7 @@ checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
"unicode-xid 0.2.0",
]
@@ -2977,7 +2977,7 @@ checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
]
[[package]]
@@ -3174,9 +3174,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasm-bindgen"
version = "0.2.62"
version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551"
checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -3184,24 +3184,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.62"
version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94"
checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04"
checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6"
dependencies = [
"cfg-if",
"js-sys",
@@ -3211,9 +3211,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.62"
version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776"
checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3"
dependencies = [
"quote 1.0.6",
"wasm-bindgen-macro-support",
@@ -3221,28 +3221,28 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.62"
version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a"
checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.62"
version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad"
checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd"
[[package]]
name = "web-sys"
version = "0.3.39"
version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642"
checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -3335,6 +3335,6 @@ checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
dependencies = [
"proc-macro2",
"quote 1.0.6",
"syn 1.0.23",
"syn 1.0.27",
"synstructure",
]

View File

@@ -50,7 +50,7 @@ bitflags = "1.1.0"
debug_stub_derive = "0.3.0"
sanitize-filename = "0.2.1"
stop-token = { version = "0.1.1", features = ["unstable"] }
mailparse = "0.12.0"
mailparse = "0.12.1"
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
native-tls = "0.2.3"
image = { version = "0.22.4", default-features=false, features = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "webp", "bmp"] }

View File

@@ -4423,7 +4423,11 @@ void dc_event_unref(dc_event_t* event);
#define DC_STR_LOCATION 66
#define DC_STR_STICKER 67
#define DC_STR_DEVICE_MESSAGES 68
#define DC_STR_COUNT 68
#define DC_STR_SAVED_MESSAGES 69
#define DC_STR_DEVICE_MESSAGES_HINT 70
#define DC_STR_WELCOME_MESSAGE 71
#define DC_STR_UNKNOWN_SENDER_FOR_CHAT 72
#define DC_STR_COUNT 72
/*
* @}

View File

@@ -20,7 +20,6 @@ use deltachat::context::*;
use deltachat::oauth2::*;
use deltachat::securejoin::*;
use deltachat::Event;
use log::{error, info, warn};
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::config::OutputStreamType;
use rustyline::error::ReadlineError;
@@ -36,34 +35,34 @@ use self::cmdline::*;
/// Event Handler
fn receive_event(event: Event) {
let yellow = Color::Yellow.normal();
let red = Color::Red.normal();
match event {
Event::Info(msg) => {
/* do not show the event as this would fill the screen */
info!("{}", msg);
println!("[INFO] {}", msg);
}
Event::SmtpConnected(msg) => {
info!("[SMTP_CONNECTED] {}", msg);
println!("[INFO SMTP_CONNECTED] {}", msg);
}
Event::ImapConnected(msg) => {
info!("[IMAP_CONNECTED] {}", msg);
println!("[INFO IMAP_CONNECTED] {}", msg);
}
Event::SmtpMessageSent(msg) => {
info!("[SMTP_MESSAGE_SENT] {}", msg);
println!("[INFO SMTP_MESSAGE_SENT] {}", msg);
}
Event::Warning(msg) => {
warn!("{}", msg);
println!("[WARNING] {}", msg);
}
Event::Error(msg) => {
error!("{}", msg);
println!("[ERROR] {}", red.paint(msg));
}
Event::ErrorNetwork(msg) => {
error!("[NETWORK] msg={}", msg);
println!("[ERROR NETWORK] msg={}", red.paint(msg));
}
Event::ErrorSelfNotInGroup(msg) => {
error!("[SELF_NOT_IN_GROUP] {}", msg);
println!("[ERROR SELF_NOT_IN_GROUP] {}", red.paint(msg));
}
Event::MsgsChanged { chat_id, msg_id } => {
info!(
println!(
"{}",
yellow.paint(format!(
"Received MSGS_CHANGED(chat_id={}, msg_id={})",
@@ -72,40 +71,40 @@ fn receive_event(event: Event) {
);
}
Event::ContactsChanged(_) => {
info!("{}", yellow.paint("Received CONTACTS_CHANGED()"));
println!("{}", yellow.paint("Received CONTACTS_CHANGED()"));
}
Event::LocationChanged(contact) => {
info!(
println!(
"{}",
yellow.paint(format!("Received LOCATION_CHANGED(contact={:?})", contact))
);
}
Event::ConfigureProgress(progress) => {
info!(
println!(
"{}",
yellow.paint(format!("Received CONFIGURE_PROGRESS({} ‰)", progress))
);
}
Event::ImexProgress(progress) => {
info!(
println!(
"{}",
yellow.paint(format!("Received IMEX_PROGRESS({} ‰)", progress))
);
}
Event::ImexFileWritten(file) => {
info!(
println!(
"{}",
yellow.paint(format!("Received IMEX_FILE_WRITTEN({})", file.display()))
);
}
Event::ChatModified(chat) => {
info!(
println!(
"{}",
yellow.paint(format!("Received CHAT_MODIFIED({})", chat))
);
}
_ => {
info!("Received {:?}", event);
println!("Received {}", yellow.paint(format!("{:?}", event)));
}
}
}

View File

@@ -4,7 +4,7 @@ mod auto_mozilla;
mod auto_outlook;
mod read_url;
use anyhow::{bail, ensure, format_err, Result};
use anyhow::{bail, ensure, format_err, Context as _, Result};
use async_std::prelude::*;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
@@ -66,75 +66,11 @@ impl Context {
}
async fn inner_configure(&self) -> Result<()> {
let mut success = false;
let mut param_autoconfig: Option<LoginParam> = None;
info!(self, "Configure ...");
// Variables that are shared between steps:
let mut param = LoginParam::from_database(self, "").await;
// need all vars here to be mutable because rust thinks the same step could be called multiple times
// and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward
let mut param_domain = "undefined.undefined".to_owned();
let mut param_addr_urlencoded: String =
"Internal Error: this value should never be used".to_owned();
let mut keep_flags = 0;
let mut step_counter: u8 = 0;
let (_s, r) = async_std::sync::channel(1);
let mut imap = Imap::new(r);
let was_configured_before = self.is_configured().await;
while !self.shall_stop_ongoing().await {
step_counter += 1;
match exec_step(
self,
&mut imap,
&mut param,
&mut param_domain,
&mut param_autoconfig,
&mut param_addr_urlencoded,
&mut keep_flags,
&mut step_counter,
)
.await
{
Ok(step) => {
success = true;
match step {
Step::Continue => {}
Step::Done => break,
}
}
Err(err) => {
error!(self, "{}", err);
success = false;
break;
}
}
}
if imap.is_connected() {
imap.disconnect(self).await;
}
// remember the entered parameters on success
// and restore to last-entered on failure.
// this way, the parameters visible to the ui are always in-sync with the current configuration.
if success {
LoginParam::from_database(self, "")
.await
.save_to_database(self, "configured_raw_")
.await
.ok();
} else {
LoginParam::from_database(self, "configured_raw_")
.await
.save_to_database(self, "")
.await
.ok();
}
let mut param = LoginParam::from_database(self, "").await;
let success = configure(self, &mut param).await;
if let Some(provider) = provider::get_provider_info(&param.addr) {
if !was_configured_before {
@@ -158,314 +94,296 @@ impl Context {
}
}
if success {
progress!(self, 1000);
Ok(())
match success {
Ok(_) => {
progress!(self, 1000);
Ok(())
}
Err(err) => {
error!(self, "Configure Failed: {}", err);
progress!(self, 0);
Err(err)
}
}
}
}
async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
let mut param_autoconfig: Option<LoginParam> = None;
let mut keep_flags = 0;
// Read login parameters from the database
progress!(ctx, 1);
ensure!(!param.addr.is_empty(), "Please enter an email address.");
// Step 1: Load the parameters and check email-address and password
if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 {
// the used oauth2 addr may differ, check this.
// if dc_get_oauth2_addr() is not available in the oauth2 implementation, just use the given one.
progress!(ctx, 10);
if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, &param.addr, &param.mail_pw)
.await
.and_then(|e| e.parse().ok())
{
info!(ctx, "Authorized address is {}", oauth2_addr);
param.addr = oauth2_addr;
ctx.sql
.set_raw_config(ctx, "addr", Some(param.addr.as_str()))
.await?;
}
progress!(ctx, 20);
}
// no oauth? - just continue it's no error
let parsed: EmailAddress = param.addr.parse().context("Bad email-address")?;
let param_domain = parsed.domain;
let param_addr_urlencoded = utf8_percent_encode(&param.addr, NON_ALPHANUMERIC).to_string();
// Step 2: Autoconfig
progress!(ctx, 200);
// param.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then
// param.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for
// autoconfig or not
if param.mail_server.is_empty()
&& param.mail_port == 0
&& param.send_server.is_empty()
&& param.send_port == 0
&& param.send_user.is_empty()
&& (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0
{
// no advanced parameters entered by the user: query provider-database or do Autoconfig
keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2;
if let Some(new_param) = get_offline_autoconfig(ctx, &param) {
// got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting
param_autoconfig = Some(new_param);
}
if param_autoconfig.is_none() {
param_autoconfig =
get_autoconfig(ctx, param, &param_domain, &param_addr_urlencoded).await;
}
}
// C. Do we have any autoconfig result?
progress!(ctx, 500);
if let Some(ref cfg) = param_autoconfig {
info!(ctx, "Got autoconfig: {}", &cfg);
if !cfg.mail_user.is_empty() {
param.mail_user = cfg.mail_user.clone();
}
// all other values are always NULL when entering autoconfig
param.mail_server = cfg.mail_server.clone();
param.mail_port = cfg.mail_port;
param.send_server = cfg.send_server.clone();
param.send_port = cfg.send_port;
param.send_user = cfg.send_user.clone();
param.server_flags = cfg.server_flags;
// although param_autoconfig's data are no longer needed from,
// it is used to later to prevent trying variations of port/server/logins
}
param.server_flags |= keep_flags;
// Step 3: Fill missing fields with defaults
if param.mail_server.is_empty() {
param.mail_server = format!("imap.{}", param_domain,)
}
if param.mail_port == 0 {
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
143
} else {
progress!(self, 0);
Err(format_err!("Configure failed"))
993
}
}
if param.mail_user.is_empty() {
param.mail_user = param.addr.clone();
}
if param.send_server.is_empty() && !param.mail_server.is_empty() {
param.send_server = param.mail_server.clone();
if param.send_server.starts_with("imap.") {
param.send_server = param.send_server.replacen("imap", "smtp", 1);
}
}
if param.send_port == 0 {
param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
587
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
25
} else {
465
}
}
if param.send_user.is_empty() && !param.mail_user.is_empty() {
param.send_user = param.mail_user.clone();
}
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
param.send_pw = param.mail_pw.clone()
}
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
param.server_flags |= DC_LP_AUTH_NORMAL as i32
}
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) {
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= if param.send_port == 143 {
DC_LP_IMAP_SOCKET_STARTTLS as i32
} else {
DC_LP_IMAP_SOCKET_SSL as i32
}
}
if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) {
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= if param.send_port == 587 {
DC_LP_SMTP_SOCKET_STARTTLS as i32
} else if param.send_port == 25 {
DC_LP_SMTP_SOCKET_PLAIN as i32
} else {
DC_LP_SMTP_SOCKET_SSL as i32
}
}
// do we have a complete configuration?
ensure!(
!param.mail_server.is_empty()
&& param.mail_port != 0
&& !param.mail_user.is_empty()
&& !param.mail_pw.is_empty()
&& !param.send_server.is_empty()
&& param.send_port != 0
&& !param.send_user.is_empty()
&& !param.send_pw.is_empty()
&& param.server_flags != 0,
"Account settings incomplete."
);
progress!(ctx, 600);
// try to connect to IMAP - if we did not got an autoconfig,
// do some further tries with different settings and username variations
let (_s, r) = async_std::sync::channel(1);
let mut imap = Imap::new(r);
try_imap_connections(ctx, param, param_autoconfig.is_some(), &mut imap).await?;
progress!(ctx, 800);
try_smtp_connections(ctx, param, param_autoconfig.is_some()).await?;
progress!(ctx, 900);
let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await
|| ctx.get_config_bool(Config::MvboxMove).await;
imap.configure_folders(ctx, create_mvbox)
.await
.context("configuring folders failed")?;
imap.select_with_uidvalidity(ctx, "INBOX")
.await
.context("could not read INBOX status")?;
drop(imap);
progress!(ctx, 910);
// configuration success - write back the configured parameters with the
// "configured_" prefix; also write the "configured"-flag */
// the trailing underscore is correct
param.save_to_database(ctx, "configured_").await?;
ctx.sql.set_raw_config_bool(ctx, "configured", true).await?;
progress!(ctx, 920);
e2ee::ensure_secret_key_exists(ctx).await?;
info!(ctx, "key generation completed");
progress!(ctx, 940);
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn exec_step(
ctx: &Context,
imap: &mut Imap,
param: &mut LoginParam,
param_domain: &mut String,
param_autoconfig: &mut Option<LoginParam>,
param_addr_urlencoded: &mut String,
keep_flags: &mut i32,
step_counter: &mut u8,
) -> Result<Step> {
const STEP_12_USE_AUTOCONFIG: u8 = 12;
const STEP_13_AFTER_AUTOCONFIG: u8 = 13;
#[derive(Debug, PartialEq, Eq)]
enum AutoconfigProvider {
Mozilla,
Outlook,
}
match *step_counter {
// Read login parameters from the database
1 => {
progress!(ctx, 1);
ensure!(!param.addr.is_empty(), "Please enter an email address.");
}
// Step 1: Load the parameters and check email-address and password
2 => {
if 0 != param.server_flags & DC_LP_AUTH_OAUTH2 {
// the used oauth2 addr may differ, check this.
// if dc_get_oauth2_addr() is not available in the oauth2 implementation,
// just use the given one.
progress!(ctx, 10);
if let Some(oauth2_addr) = dc_get_oauth2_addr(ctx, &param.addr, &param.mail_pw)
.await
.and_then(|e| e.parse().ok())
{
info!(ctx, "Authorized address is {}", oauth2_addr);
param.addr = oauth2_addr;
ctx.sql
.set_raw_config(ctx, "addr", Some(param.addr.as_str()))
.await?;
}
progress!(ctx, 20);
}
// no oauth? - just continue it's no error
}
3 => {
if let Ok(parsed) = param.addr.parse() {
let parsed: EmailAddress = parsed;
*param_domain = parsed.domain;
*param_addr_urlencoded =
utf8_percent_encode(&param.addr, NON_ALPHANUMERIC).to_string();
} else {
bail!("Bad email-address.");
}
}
// Step 2: Autoconfig
4 => {
progress!(ctx, 200);
#[derive(Debug, PartialEq, Eq)]
struct AutoconfigSource {
provider: AutoconfigProvider,
url: String,
}
if param.mail_server.is_empty()
&& param.mail_port == 0
/* && param.mail_user.is_empty() -- the user can enter a loginname which is used by autoconfig then */
&& param.send_server.is_empty()
&& param.send_port == 0
&& param.send_user.is_empty()
/* && param.send_pw.is_empty() -- the password cannot be auto-configured and is no criterion for autoconfig or not */
&& (param.server_flags & !DC_LP_AUTH_OAUTH2) == 0
{
// no advanced parameters entered by the user: query provider-database or do Autoconfig
*keep_flags = param.server_flags & DC_LP_AUTH_OAUTH2;
if let Some(new_param) = get_offline_autoconfig(ctx, &param) {
// got parameters from our provider-database, skip Autoconfig, preserve the OAuth2 setting
*param_autoconfig = Some(new_param);
*step_counter = STEP_12_USE_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop
}
} else {
// advanced parameters entered by the user: skip Autoconfig
*step_counter = STEP_13_AFTER_AUTOCONFIG - 1; // minus one as step_counter is increased on next loop
}
}
/* A. Search configurations from the domain used in the email-address, prefer encrypted */
5 => {
if param_autoconfig.is_none() {
let url = format!(
impl AutoconfigSource {
fn all(domain: &str, addr: &str) -> [Self; 5] {
[
AutoconfigSource {
provider: AutoconfigProvider::Mozilla,
url: format!(
"https://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
*param_autoconfig = moz_autoconfigure(ctx, &url, &param).await.ok();
}
}
6 => {
progress!(ctx, 300);
if param_autoconfig.is_none() {
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
let url = format!(
domain, addr,
),
},
// the doc does not mention `emailaddress=`, however, Thunderbird adds it, see https://releases.mozilla.org/pub/thunderbird/ , which makes some sense
AutoconfigSource {
provider: AutoconfigProvider::Mozilla,
url: format!(
"https://{}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
*param_autoconfig = moz_autoconfigure(ctx, &url, &param).await.ok();
}
}
/* Outlook section start ------------- */
/* Outlook uses always SSL but different domains (this comment describes the next two steps) */
7 => {
progress!(ctx, 310);
if param_autoconfig.is_none() {
let url = format!("https://{}/autodiscover/autodiscover.xml", param_domain);
*param_autoconfig = outlk_autodiscover(ctx, &url, &param).await.ok();
}
}
8 => {
progress!(ctx, 320);
if param_autoconfig.is_none() {
let url = format!(
domain, addr
),
},
AutoconfigSource {
provider: AutoconfigProvider::Outlook,
url: format!("https://{}/autodiscover/autodiscover.xml", domain),
},
// Outlook uses always SSL but different domains (this comment describes the next two steps)
AutoconfigSource {
provider: AutoconfigProvider::Outlook,
url: format!(
"https://{}{}/autodiscover/autodiscover.xml",
"autodiscover.", param_domain
);
*param_autoconfig = outlk_autodiscover(ctx, &url, &param).await.ok();
}
}
/* ----------- Outlook section end */
9 => {
progress!(ctx, 330);
if param_autoconfig.is_none() {
let url = format!(
"http://autoconfig.{}/mail/config-v1.1.xml?emailaddress={}",
param_domain, param_addr_urlencoded
);
*param_autoconfig = moz_autoconfigure(ctx, &url, &param).await.ok();
}
}
10 => {
progress!(ctx, 340);
if param_autoconfig.is_none() {
// do not transfer the email-address unencrypted
let url = format!(
"http://{}/.well-known/autoconfig/mail/config-v1.1.xml",
param_domain
);
*param_autoconfig = moz_autoconfigure(ctx, &url, &param).await.ok();
}
}
/* B. If we have no configuration yet, search configuration in Thunderbird's centeral database */
11 => {
progress!(ctx, 350);
if param_autoconfig.is_none() {
/* always SSL for Thunderbird's database */
let url = format!("https://autoconfig.thunderbird.net/v1.1/{}", param_domain);
*param_autoconfig = moz_autoconfigure(ctx, &url, &param).await.ok();
}
}
/* C. Do we have any autoconfig result?
If you change the match-number here, also update STEP_12_COPY_AUTOCONFIG above
*/
STEP_12_USE_AUTOCONFIG => {
progress!(ctx, 500);
if let Some(ref cfg) = param_autoconfig {
info!(ctx, "Got autoconfig: {}", &cfg);
if !cfg.mail_user.is_empty() {
param.mail_user = cfg.mail_user.clone();
}
param.mail_server = cfg.mail_server.clone(); /* all other values are always NULL when entering autoconfig */
param.mail_port = cfg.mail_port;
param.send_server = cfg.send_server.clone();
param.send_port = cfg.send_port;
param.send_user = cfg.send_user.clone();
param.server_flags = cfg.server_flags;
/* although param_autoconfig's data are no longer needed from,
it is used to later to prevent trying variations of port/server/logins */
}
param.server_flags |= *keep_flags;
}
// Step 3: Fill missing fields with defaults
// If you change the match-number here, also update STEP_13_AFTER_AUTOCONFIG above
STEP_13_AFTER_AUTOCONFIG => {
if param.mail_server.is_empty() {
param.mail_server = format!("imap.{}", param_domain,)
}
if param.mail_port == 0 {
param.mail_port = if 0 != param.server_flags & (0x100 | 0x400) {
143
} else {
993
}
}
if param.mail_user.is_empty() {
param.mail_user = param.addr.clone();
}
if param.send_server.is_empty() && !param.mail_server.is_empty() {
param.send_server = param.mail_server.clone();
if param.send_server.starts_with("imap.") {
param.send_server = param.send_server.replacen("imap", "smtp", 1);
}
}
if param.send_port == 0 {
param.send_port = if 0 != param.server_flags & DC_LP_SMTP_SOCKET_STARTTLS as i32 {
587
} else if 0 != param.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32 {
25
} else {
465
}
}
if param.send_user.is_empty() && !param.mail_user.is_empty() {
param.send_user = param.mail_user.clone();
}
if param.send_pw.is_empty() && !param.mail_pw.is_empty() {
param.send_pw = param.mail_pw.clone()
}
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_AUTH_FLAGS as i32) {
param.server_flags &= !(DC_LP_AUTH_FLAGS as i32);
param.server_flags |= DC_LP_AUTH_NORMAL as i32
}
if !dc_exactly_one_bit_set(param.server_flags & DC_LP_IMAP_SOCKET_FLAGS as i32) {
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS as i32);
param.server_flags |= if param.send_port == 143 {
DC_LP_IMAP_SOCKET_STARTTLS as i32
} else {
DC_LP_IMAP_SOCKET_SSL as i32
}
}
if !dc_exactly_one_bit_set(param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32)) {
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= if param.send_port == 587 {
DC_LP_SMTP_SOCKET_STARTTLS as i32
} else if param.send_port == 25 {
DC_LP_SMTP_SOCKET_PLAIN as i32
} else {
DC_LP_SMTP_SOCKET_SSL as i32
}
}
/* do we have a complete configuration? */
if param.mail_server.is_empty()
|| param.mail_port == 0
|| param.mail_user.is_empty()
|| param.mail_pw.is_empty()
|| param.send_server.is_empty()
|| param.send_port == 0
|| param.send_user.is_empty()
|| param.send_pw.is_empty()
|| param.server_flags == 0
{
bail!("Account settings incomplete.");
}
}
14 => {
progress!(ctx, 600);
/* try to connect to IMAP - if we did not got an autoconfig,
do some further tries with different settings and username variations */
try_imap_connections(ctx, param, param_autoconfig.is_some(), imap).await?;
}
15 => {
progress!(ctx, 800);
try_smtp_connections(ctx, param, param_autoconfig.is_some()).await?;
}
16 => {
progress!(ctx, 900);
"autodiscover.", domain
),
},
// always SSL for Thunderbird's database
AutoconfigSource {
provider: AutoconfigProvider::Mozilla,
url: format!("https://autoconfig.thunderbird.net/v1.1/{}", domain),
},
]
}
let create_mvbox = ctx.get_config_bool(Config::MvboxWatch).await
|| ctx.get_config_bool(Config::MvboxMove).await;
async fn fetch(&self, ctx: &Context, param: &LoginParam) -> Result<LoginParam> {
let params = match self.provider {
AutoconfigProvider::Mozilla => moz_autoconfigure(ctx, &self.url, &param).await?,
AutoconfigProvider::Outlook => outlk_autodiscover(ctx, &self.url, &param).await?,
};
if let Err(err) = imap.configure_folders(ctx, create_mvbox).await {
bail!("configuring folders failed: {:?}", err);
}
Ok(params)
}
}
if let Err(err) = imap.select_with_uidvalidity(ctx, "INBOX").await {
bail!("could not read INBOX status: {:?}", err);
}
}
17 => {
progress!(ctx, 910);
// configuration success - write back the configured parameters with the
// "configured_" prefix; also write the "configured"-flag */
// the trailing underscore is correct
param.save_to_database(ctx, "configured_").await?;
ctx.sql.set_raw_config_bool(ctx, "configured", true).await?;
}
18 => {
progress!(ctx, 920);
// we generate the keypair just now - we could also postpone this until the first message is sent, however,
// this may result in a unexpected and annoying delay when the user sends his very first message
// (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow.
e2ee::ensure_secret_key_exists(ctx).await?;
info!(ctx, "key generation completed");
progress!(ctx, 940);
return Ok(Step::Done);
}
_ => {
bail!("Internal error: step counter out of bound");
/// Retrieve available autoconfigurations.
///
/// A Search configurations from the domain used in the email-address, prefer encrypted
/// B. If we have no configuration yet, search configuration in Thunderbird's centeral database
async fn get_autoconfig(
ctx: &Context,
param: &LoginParam,
param_domain: &str,
param_addr_urlencoded: &str,
) -> Option<LoginParam> {
let sources = AutoconfigSource::all(param_domain, param_addr_urlencoded);
let mut progress = 300;
for source in &sources {
let res = source.fetch(ctx, param).await;
progress!(ctx, progress);
progress += 10;
if let Ok(res) = res {
return Some(res);
}
}
Ok(Step::Continue)
None
}
#[derive(Debug)]
enum Step {
Done,
Continue,
}
#[allow(clippy::unnecessary_unwrap)]
fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<LoginParam> {
info!(
context,
@@ -481,37 +399,35 @@ fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<Login
// however, rewriting the code to "if let" would make things less obvious,
// esp. if we allow more combinations of servers (pop, jmap).
// therefore, #[allow(clippy::unnecessary_unwrap)] is added above.
if imap.is_some() && smtp.is_some() {
let imap = imap.unwrap();
let smtp = smtp.unwrap();
if let Some(imap) = imap {
if let Some(smtp) = smtp {
let mut p = LoginParam::new();
p.addr = param.addr.clone();
let mut p = LoginParam::new();
p.addr = param.addr.clone();
p.mail_server = imap.hostname.to_string();
p.mail_user = imap.apply_username_pattern(param.addr.clone());
p.mail_port = imap.port as i32;
p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
p.server_flags |= match imap.socket {
provider::Socket::STARTTLS => DC_LP_IMAP_SOCKET_STARTTLS,
provider::Socket::SSL => DC_LP_IMAP_SOCKET_SSL,
};
p.mail_server = imap.hostname.to_string();
p.mail_user = imap.apply_username_pattern(param.addr.clone());
p.mail_port = imap.port as i32;
p.imap_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
p.server_flags |= match imap.socket {
provider::Socket::STARTTLS => DC_LP_IMAP_SOCKET_STARTTLS,
provider::Socket::SSL => DC_LP_IMAP_SOCKET_SSL,
};
p.send_server = smtp.hostname.to_string();
p.send_user = smtp.apply_username_pattern(param.addr.clone());
p.send_port = smtp.port as i32;
p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
p.server_flags |= match smtp.socket {
provider::Socket::STARTTLS => DC_LP_SMTP_SOCKET_STARTTLS as i32,
provider::Socket::SSL => DC_LP_SMTP_SOCKET_SSL as i32,
};
p.send_server = smtp.hostname.to_string();
p.send_user = smtp.apply_username_pattern(param.addr.clone());
p.send_port = smtp.port as i32;
p.smtp_certificate_checks = CertificateChecks::AcceptInvalidCertificates;
p.server_flags |= match smtp.socket {
provider::Socket::STARTTLS => DC_LP_SMTP_SOCKET_STARTTLS as i32,
provider::Socket::SSL => DC_LP_SMTP_SOCKET_SSL as i32,
};
info!(context, "offline autoconfig found: {}", p);
return Some(p);
} else {
info!(context, "offline autoconfig found, but no servers defined");
return None;
info!(context, "offline autoconfig found: {}", p);
return Some(p);
}
}
info!(context, "offline autoconfig found, but no servers defined");
return None;
}
provider::Status::BROKEN => {
info!(context, "offline autoconfig found, provider is broken");
@@ -525,19 +441,32 @@ fn get_offline_autoconfig(context: &Context, param: &LoginParam) -> Option<Login
async fn try_imap_connections(
context: &Context,
mut param: &mut LoginParam,
param: &mut LoginParam,
was_autoconfig: bool,
imap: &mut Imap,
) -> Result<bool> {
// progress 650 and 660
if let Ok(val) = try_imap_connection(context, &mut param, was_autoconfig, 0, imap).await {
return Ok(val);
}
progress!(context, 670);
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
param.server_flags |= DC_LP_IMAP_SOCKET_SSL;
param.mail_port = 993;
) -> Result<()> {
// manually_set_param is used to check whether a particular setting was set manually by the user.
// If yes, we do not want to change it to avoid confusing error messages
// (you set port 443, but the app tells you it couldn't connect on port 993).
let manually_set_param = LoginParam::from_database(context, "").await;
// progress 650 and 660
if try_imap_connection(context, param, &manually_set_param, was_autoconfig, 0, imap)
.await
.is_ok()
{
return Ok(()); // we directly return here if it was autoconfig or the connection succeeded
}
progress!(context, 670);
// try_imap_connection() changed the flags and port. Change them back:
if manually_set_param.server_flags & DC_LP_IMAP_SOCKET_FLAGS == 0 {
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
param.server_flags |= DC_LP_IMAP_SOCKET_SSL;
}
if manually_set_param.mail_port == 0 {
param.mail_port = 993;
}
if let Some(at) = param.mail_user.find('@') {
param.mail_user = param.mail_user.split_at(at).0.to_string();
}
@@ -545,35 +474,43 @@ async fn try_imap_connections(
param.send_user = param.send_user.split_at(at).0.to_string();
}
// progress 680 and 690
try_imap_connection(context, &mut param, was_autoconfig, 1, imap).await
try_imap_connection(context, param, &manually_set_param, was_autoconfig, 1, imap).await
}
async fn try_imap_connection(
context: &Context,
param: &mut LoginParam,
manually_set_param: &LoginParam,
was_autoconfig: bool,
variation: usize,
imap: &mut Imap,
) -> Result<bool> {
if try_imap_one_param(context, &param, imap).await.is_ok() {
return Ok(true);
) -> Result<()> {
if try_imap_one_param(context, param, imap).await.is_ok() {
return Ok(());
}
if was_autoconfig {
return Ok(false);
return Ok(());
}
progress!(context, 650 + variation * 30);
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS;
if try_imap_one_param(context, &param, imap).await.is_ok() {
return Ok(true);
if manually_set_param.server_flags & DC_LP_IMAP_SOCKET_FLAGS == 0 {
param.server_flags &= !(DC_LP_IMAP_SOCKET_FLAGS);
param.server_flags |= DC_LP_IMAP_SOCKET_STARTTLS;
if try_imap_one_param(context, &param, imap).await.is_ok() {
return Ok(());
}
}
progress!(context, 660 + variation * 30);
param.mail_port = 143;
try_imap_one_param(context, &param, imap).await?;
Ok(true)
if manually_set_param.mail_port == 0 {
param.mail_port = 143;
try_imap_one_param(context, param, imap).await
} else {
Err(format_err!("no more possible configs"))
}
}
async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Imap) -> Result<()> {
@@ -592,41 +529,51 @@ async fn try_imap_one_param(context: &Context, param: &LoginParam, imap: &mut Im
return Ok(());
}
if context.shall_stop_ongoing().await {
bail!("Interrupted");
}
bail!("Could not connect: {}", inf);
}
async fn try_smtp_connections(
context: &Context,
mut param: &mut LoginParam,
param: &mut LoginParam,
was_autoconfig: bool,
) -> Result<()> {
// manually_set_param is used to check whether a particular setting was set manually by the user.
// If yes, we do not want to change it to avoid confusing error messages
// (you set port 443, but the app tells you it couldn't connect on port 993).
let manually_set_param = LoginParam::from_database(context, "").await;
let mut smtp = Smtp::new();
/* try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do a second try with STARTTLS-587 */
if try_smtp_one_param(context, &param, &mut smtp).await.is_ok() {
// try to connect to SMTP - if we did not got an autoconfig, the first try was SSL-465 and we do
// a second try with STARTTLS-587
if try_smtp_one_param(context, param, &mut smtp).await.is_ok() {
return Ok(());
}
if was_autoconfig {
return Ok(());
}
progress!(context, 850);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.send_port = 587;
if try_smtp_one_param(context, &param, &mut smtp).await.is_ok() {
if manually_set_param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32) == 0 {
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
}
if manually_set_param.send_port == 0 {
param.send_port = 587;
}
if try_smtp_one_param(context, param, &mut smtp).await.is_ok() {
return Ok(());
}
progress!(context, 860);
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
param.send_port = 25;
try_smtp_one_param(context, &param, &mut smtp).await?;
Ok(())
if manually_set_param.server_flags & (DC_LP_SMTP_SOCKET_FLAGS as i32) == 0 {
param.server_flags &= !(DC_LP_SMTP_SOCKET_FLAGS as i32);
param.server_flags |= DC_LP_SMTP_SOCKET_STARTTLS as i32;
}
if manually_set_param.send_port == 0 {
param.send_port = 25;
}
try_smtp_one_param(context, param, &mut smtp).await
}
async fn try_smtp_one_param(context: &Context, param: &LoginParam, smtp: &mut Smtp) -> Result<()> {

View File

@@ -3,6 +3,8 @@
use async_std::path::PathBuf;
use deltachat_derive::*;
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use crate::aheader::EncryptPreference;
use crate::chat::ChatId;
@@ -12,7 +14,7 @@ use crate::context::Context;
use crate::dc_tools::*;
use crate::error::{bail, ensure, format_err, Result};
use crate::events::Event;
use crate::key::{DcKey, Key, SignedPublicKey};
use crate::key::{DcKey, SignedPublicKey};
use crate::login_param::LoginParam;
use crate::message::{MessageState, MsgId};
use crate::mimeparser::AvatarAction;
@@ -238,6 +240,8 @@ impl Contact {
"Cannot create contact with empty address"
);
let (name, addr) = sanitize_name_and_addr(name, addr);
let (contact_id, sth_modified) =
Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated).await?;
let blocked = Contact::is_blocked_load(context, contact_id).await;
@@ -512,8 +516,9 @@ impl Contact {
let mut modify_cnt = 0;
for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() {
let (name, addr) = sanitize_name_and_addr(name, addr);
let name = normalize_name(name);
match Contact::add_or_lookup(context, name, addr, Origin::AddressBook).await {
match Contact::add_or_lookup(context, name, &addr, Origin::AddressBook).await {
Err(err) => {
warn!(
context,
@@ -691,18 +696,20 @@ impl Contact {
})
.await;
ret += &p;
let self_key = Key::from(SignedPublicKey::load_self(context).await?);
let p = context.stock_str(StockMessage::FingerPrints).await;
ret += &format!(" {}:", p);
let fingerprint_self = self_key.formatted_fingerprint();
let fingerprint_self = SignedPublicKey::load_self(context)
.await?
.fingerprint()
.to_string();
let fingerprint_other_verified = peerstate
.peek_key(PeerstateVerifiedStatus::BidirectVerified)
.map(|k| k.formatted_fingerprint())
.map(|k| k.fingerprint().to_string())
.unwrap_or_default();
let fingerprint_other_unverified = peerstate
.peek_key(PeerstateVerifiedStatus::Unverified)
.map(|k| k.formatted_fingerprint())
.map(|k| k.fingerprint().to_string())
.unwrap_or_default();
if loginparam.addr < peerstate.addr {
cat_fingerprint(&mut ret, &loginparam.addr, &fingerprint_self, "");
@@ -1028,6 +1035,24 @@ pub fn addr_normalize(addr: &str) -> &str {
norm
}
fn sanitize_name_and_addr(name: impl AsRef<str>, addr: impl AsRef<str>) -> (String, String) {
lazy_static! {
static ref ADDR_WITH_NAME_REGEX: Regex = Regex::new("(.*)<(.*)>").unwrap();
}
if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) {
(
if name.as_ref().is_empty() {
normalize_name(&captures[1])
} else {
name.as_ref().to_string()
},
captures[2].to_string(),
)
} else {
(name.as_ref().to_string(), addr.as_ref().to_string())
}
}
async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) {
if contact_id <= DC_CONTACT_ID_LAST_SPECIAL {
return;
@@ -1202,6 +1227,10 @@ mod tests {
assert_eq!(may_be_valid_addr("u@d.tt"), true);
assert_eq!(may_be_valid_addr("u@.tt"), false);
assert_eq!(may_be_valid_addr("@d.tt"), false);
assert_eq!(may_be_valid_addr("<da@d.tt"), false);
assert_eq!(may_be_valid_addr("sk <@d.tt>"), false);
assert_eq!(may_be_valid_addr("as@sd.de>"), false);
assert_eq!(may_be_valid_addr("ask dkl@dd.tt"), false);
}
#[test]
@@ -1284,9 +1313,10 @@ mod tests {
"Name two\ntwo@deux.net\n",
"Invalid\n+1234567890\n", // invalid, should be ignored
"\nthree@drei.sam\n",
"Name two\ntwo@deux.net\n" // should not be added again
"Name two\ntwo@deux.net\n", // should not be added again
"\nWonderland, Alice <alice@w.de>\n",
);
assert_eq!(Contact::add_address_book(&t.ctx, book).await.unwrap(), 3);
assert_eq!(Contact::add_address_book(&t.ctx, book).await.unwrap(), 4);
// check first added contact, this does not modify because of lower origin
let (contact_id, sth_modified) =
@@ -1362,6 +1392,19 @@ mod tests {
assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)");
assert!(!contact.is_blocked());
// Fourth contact:
let (contact_id, sth_modified) =
Contact::add_or_lookup(&t.ctx, "", "alice@w.de", Origin::IncomingUnknownTo)
.await
.unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "Alice Wonderland");
assert_eq!(contact.get_display_name(), "Alice Wonderland");
assert_eq!(contact.get_addr(), "alice@w.de");
assert_eq!(contact.get_name_n_addr(), "Alice Wonderland (alice@w.de)");
// check SELF
let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF)
.await
@@ -1528,4 +1571,50 @@ mod tests {
assert!(addr_cmp(" aa@aa.ORG ", "AA@AA.ORG"));
assert!(addr_cmp(" mailto:AA@AA.ORG", "Aa@Aa.orG"));
}
#[async_std::test]
async fn test_name_in_address() {
let t = dummy_context().await;
let contact_id = Contact::create(&t.ctx, "", "<dave@example.org>")
.await
.unwrap();
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_addr(), "dave@example.org");
let contact_id = Contact::create(&t.ctx, "", "Mueller, Dave <dave@example.org>")
.await
.unwrap();
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "Dave Mueller");
assert_eq!(contact.get_addr(), "dave@example.org");
let contact_id = Contact::create(&t.ctx, "name1", "name2 <dave@example.org>")
.await
.unwrap();
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "name1");
assert_eq!(contact.get_addr(), "dave@example.org");
assert!(Contact::create(&t.ctx, "", "<dskjfdslk@sadklj.dk")
.await
.is_err());
assert!(Contact::create(&t.ctx, "", "<dskjf>dslk@sadklj.dk>")
.await
.is_err());
assert!(Contact::create(&t.ctx, "", "dskjfdslksadklj.dk")
.await
.is_err());
assert!(Contact::create(&t.ctx, "", "dskjfdslk@sadklj.dk>")
.await
.is_err());
assert!(Contact::create(&t.ctx, "", "dskjf@dslk@sadkljdk")
.await
.is_err());
assert!(Contact::create(&t.ctx, "", "dskjf dslk@d.e").await.is_err());
assert!(Contact::create(&t.ctx, "", "<dskjf dslk@sadklj.dk")
.await
.is_err());
}
}

View File

@@ -15,7 +15,7 @@ use crate::dc_tools::duration_to_str;
use crate::error::*;
use crate::events::{Event, EventEmitter, Events};
use crate::job::{self, Action};
use crate::key::{DcKey, Key, SignedPublicKey};
use crate::key::{DcKey, SignedPublicKey};
use crate::login_param::LoginParam;
use crate::lot::Lot;
use crate::message::{self, Message, MessengerMessage, MsgId};
@@ -285,7 +285,7 @@ impl Context {
.query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![])
.await;
let fingerprint_str = match SignedPublicKey::load_self(self).await {
Ok(key) => Key::from(key).fingerprint(),
Ok(key) => key.fingerprint().hex(),
Err(err) => format!("<key failure: {}>", err),
};

View File

@@ -482,15 +482,6 @@ pub struct InvalidEmailError {
addr: String,
}
impl InvalidEmailError {
fn new(msg: impl Into<String>, addr: impl Into<String>) -> InvalidEmailError {
InvalidEmailError {
message: msg.into(),
addr: addr.into(),
}
}
}
/// Very simple email address wrapper.
///
/// Represents an email address, right now just the `name@domain` portion.
@@ -530,17 +521,24 @@ impl FromStr for EmailAddress {
/// Performs a dead-simple parse of an email address.
fn from_str(input: &str) -> Result<EmailAddress, InvalidEmailError> {
if input.is_empty() {
return Err(InvalidEmailError::new("empty string is not valid", input));
}
let parts: Vec<&str> = input.rsplitn(2, '@').collect();
let err = |msg: &str| {
Err(InvalidEmailError {
message: msg.to_string(),
addr: input.to_string(),
})
};
if input.is_empty() {
return err("empty string is not valid");
}
let parts: Vec<&str> = input.rsplitn(2, '@').collect();
if input
.chars()
.any(|c| c.is_whitespace() || c == '<' || c == '>')
{
return err("Email must not contain whitespaces, '>' or '<'");
}
match &parts[..] {
[domain, local] => {
if local.is_empty() {

View File

@@ -11,7 +11,7 @@ use crate::context::Context;
use crate::error::*;
use crate::headerdef::HeaderDef;
use crate::headerdef::HeaderDefMap;
use crate::key::{DcKey, Key, SignedPublicKey, SignedSecretKey};
use crate::key::{DcKey, SignedPublicKey, SignedSecretKey};
use crate::keyring::*;
use crate::peerstate::*;
use crate::pgp;
@@ -93,7 +93,7 @@ impl EncryptHelper {
mail_to_encrypt: lettre_email::PartBuilder,
peerstates: Vec<(Option<Peerstate<'_>>, &str)>,
) -> Result<String> {
let mut keyring = Keyring::default();
let mut keyring: Keyring<SignedPublicKey> = Keyring::new();
for (peerstate, addr) in peerstates
.into_iter()
@@ -104,9 +104,8 @@ impl EncryptHelper {
})?;
keyring.add(key);
}
let public_key = Key::from(self.public_key);
keyring.add(public_key);
let sign_key = Key::from(SignedSecretKey::load_self(context).await?);
keyring.add(self.public_key.clone());
let sign_key = SignedSecretKey::load_self(context).await?;
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
@@ -151,40 +150,33 @@ pub async fn try_decrypt(
}
/* possibly perform decryption */
let mut public_keyring_for_validate = Keyring::default();
let mut out_mail = None;
let private_keyring: Keyring<SignedSecretKey> = Keyring::new_self(context).await?;
let mut public_keyring_for_validate: Keyring<SignedPublicKey> = Keyring::new();
let mut signatures = HashSet::default();
let self_addr = context.get_config(Config::ConfiguredAddr).await;
if let Some(self_addr) = self_addr {
if let Ok(private_keyring) =
Keyring::load_self_private_for_decrypting(context, self_addr).await
{
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &from).await;
}
if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate).await?;
}
if let Some(key) = peerstate.gossip_key {
public_keyring_for_validate.add(key);
}
if let Some(key) = peerstate.public_key {
public_keyring_for_validate.add(key);
}
}
out_mail = decrypt_if_autocrypt_message(
context,
mail,
private_keyring,
public_keyring_for_validate,
&mut signatures,
)
.await?;
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &from).await;
}
if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate).await?;
}
if let Some(key) = peerstate.gossip_key {
public_keyring_for_validate.add(key);
}
if let Some(key) = peerstate.public_key {
public_keyring_for_validate.add(key);
}
}
let out_mail = decrypt_if_autocrypt_message(
context,
mail,
private_keyring,
public_keyring_for_validate,
&mut signatures,
)
.await?;
Ok((out_mail, signatures))
}
@@ -218,8 +210,8 @@ fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a ParsedMail
async fn decrypt_if_autocrypt_message<'a>(
context: &Context,
mail: &ParsedMail<'a>,
private_keyring: Keyring,
public_keyring_for_validate: Keyring,
private_keyring: Keyring<SignedSecretKey>,
public_keyring_for_validate: Keyring<SignedPublicKey>,
ret_valid_signatures: &mut HashSet<String>,
) -> Result<Option<Vec<u8>>> {
// The returned bool is true if we detected an Autocrypt-encrypted
@@ -250,8 +242,8 @@ async fn decrypt_if_autocrypt_message<'a>(
/// Returns Ok(None) if nothing encrypted was found.
async fn decrypt_part(
mail: &ParsedMail<'_>,
private_keyring: Keyring,
public_keyring_for_validate: Keyring,
private_keyring: Keyring<SignedSecretKey>,
public_keyring_for_validate: Keyring<SignedPublicKey>,
ret_valid_signatures: &mut HashSet<String>,
) -> Result<Option<Vec<u8>>> {
let data = mail.get_body_raw()?;

View File

@@ -1,5 +1,6 @@
//! # Import/export module
use std::any::Any;
use std::cmp::{max, min};
use async_std::path::{Path, PathBuf};
@@ -16,7 +17,7 @@ use crate::dc_tools::*;
use crate::e2ee;
use crate::error::*;
use crate::events::Event;
use crate::key::{self, DcKey, Key, SignedSecretKey};
use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey};
use crate::message::{Message, MsgId};
use crate::mimeparser::SystemMessage;
use crate::param::*;
@@ -181,7 +182,7 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<St
passphrase.len() >= 2,
"Passphrase must be at least 2 chars long."
);
let private_key = Key::from(SignedSecretKey::load_self(context).await?);
let private_key = SignedSecretKey::load_self(context).await?;
let ac_headers = match context.get_config_bool(Config::E2eeEnabled).await {
false => None,
true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
@@ -291,12 +292,8 @@ async fn set_self_key(
prefer_encrypt_required: bool,
) -> Result<()> {
// try hard to only modify key-state
let keys = Key::from_armored_string(armored, KeyType::Private)
.and_then(|(k, h)| if k.verify() { Some((k, h)) } else { None })
.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 (private_key, header) = SignedSecretKey::from_asc(armored)?;
let public_key = private_key.split_public_key()?;
let preferencrypt = header.get("Autocrypt-Prefer-Encrypt");
match preferencrypt.map(|s| s.as_str()) {
Some(headerval) => {
@@ -322,15 +319,10 @@ async fn set_self_key(
let self_addr = context.get_config(Config::ConfiguredAddr).await;
ensure!(self_addr.is_some(), "Missing self addr");
let addr = EmailAddress::new(&self_addr.unwrap_or_default())?;
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,
public: public_key,
secret: private_key,
};
key::store_self_keypair(
context,
@@ -696,9 +688,9 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|row| {
let id = row.get(0)?;
let public_key_blob: Vec<u8> = row.get(1)?;
let public_key = Key::from_slice(&public_key_blob, KeyType::Public);
let public_key = SignedPublicKey::from_slice(&public_key_blob);
let private_key_blob: Vec<u8> = row.get(2)?;
let private_key = Key::from_slice(&private_key_blob, KeyType::Private);
let private_key = SignedSecretKey::from_slice(&private_key_blob);
let is_default: i32 = row.get(3)?;
Ok((id, public_key, private_key, is_default))
@@ -741,22 +733,32 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
/*******************************************************************************
* Classic key export
******************************************************************************/
async fn export_key_to_asc_file(
async fn export_key_to_asc_file<T>(
context: &Context,
dir: impl AsRef<Path>,
id: Option<i64>,
key: &Key,
) -> std::io::Result<()> {
key: &T,
) -> std::io::Result<()>
where
T: DcKey + Any,
{
let file_name = {
let kind = if key.is_public() { "public" } else { "private" };
let any_key = key as &dyn Any;
let kind = if any_key.downcast_ref::<SignedPublicKey>().is_some() {
"public"
} else if any_key.downcast_ref::<SignedPublicKey>().is_some() {
"private"
} else {
"unknown"
};
let id = id.map_or("default".into(), |i| i.to_string());
dir.as_ref().join(format!("{}-key-{}.asc", kind, &id))
};
info!(context, "Exporting key {}", file_name.display());
dc_delete_file(context, &file_name).await;
let res = key.write_asc_to_file(&file_name, context).await;
let content = key.to_asc(None).into_bytes();
let res = dc_write_file(context, &file_name, &content).await;
if res.is_err() {
error!(context, "Cannot write key to {}", file_name.display());
} else {
@@ -822,7 +824,7 @@ mod tests {
#[async_std::test]
async fn test_export_key_to_asc_file() {
let context = dummy_context().await;
let key = Key::from(alice_keypair().public);
let key = alice_keypair().public;
let blobdir = "$BLOBDIR";
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key)
.await

View File

@@ -1,9 +1,9 @@
//! Cryptographic key module
use std::collections::BTreeMap;
use std::fmt;
use std::io::Cursor;
use async_std::path::Path;
use async_trait::async_trait;
use num_traits::FromPrimitive;
use pgp::composed::Deserializable;
@@ -13,7 +13,7 @@ use pgp::types::{KeyTrait, SecretKeyTrait};
use crate::config::Config;
use crate::constants::*;
use crate::context::Context;
use crate::dc_tools::{dc_write_file, time, EmailAddress, InvalidEmailError};
use crate::dc_tools::{time, EmailAddress, InvalidEmailError};
use crate::sql;
// Re-export key types
@@ -50,8 +50,8 @@ pub type Result<T> = std::result::Result<T, Error>;
/// [SignedSecretKey] types and makes working with them a little
/// easier in the deltachat world.
#[async_trait]
pub trait DcKey: Serialize + Deserializable {
type KeyType: Serialize + Deserializable;
pub trait DcKey: Serialize + Deserializable + KeyTrait + Clone {
type KeyType: Serialize + Deserializable + KeyTrait + Clone;
/// Create a key from some bytes.
fn from_slice(bytes: &[u8]) -> Result<Self::KeyType> {
@@ -68,18 +68,45 @@ pub trait DcKey: Serialize + Deserializable {
Self::from_slice(&bytes)
}
/// Create a key from an ASCII-armored string.
///
/// Returns the key and a map of any headers which might have been set in
/// the ASCII-armored representation.
fn from_asc(data: &str) -> Result<(Self::KeyType, BTreeMap<String, String>)> {
let bytes = data.as_bytes();
Self::KeyType::from_armor_single(Cursor::new(bytes)).map_err(Error::Pgp)
}
/// Load the users' default key from the database.
async fn load_self(context: &Context) -> Result<Self::KeyType>;
/// Serialise the key to a base64 string.
fn to_base64(&self) -> String {
/// Serialise the key as bytes.
fn to_bytes(&self) -> Vec<u8> {
// Not using Serialize::to_bytes() to make clear *why* it is
// safe to ignore this error.
// Because we write to a Vec<u8> the io::Write impls never
// fail and we can hide this error.
let mut buf = Vec::new();
self.to_writer(&mut buf).unwrap();
base64::encode(&buf)
buf
}
/// Serialise the key to a base64 string.
fn to_base64(&self) -> String {
base64::encode(&DcKey::to_bytes(self))
}
/// Serialise the key to ASCII-armored representation.
///
/// Each header line must be terminated by `\r\n`. Only allows setting one
/// header as a simplification since that's the only way it's used so far.
// Since .to_armored_string() are actual methods on SignedPublicKey and
// SignedSecretKey we can not generically implement this.
fn to_asc(&self, header: Option<(&str, &str)>) -> String;
/// The fingerprint for the key.
fn fingerprint(&self) -> Fingerprint {
Fingerprint::new(KeyTrait::fingerprint(self))
}
}
@@ -110,6 +137,22 @@ impl DcKey for SignedPublicKey {
Err(err) => Err(err.into()),
}
}
fn to_asc(&self, header: Option<(&str, &str)>) -> String {
// Not using .to_armored_string() to make clear *why* it is
// safe to ignore this error.
// Because we write to a Vec<u8> the io::Write impls never
// fail and we can hide this error.
let headers = header.map(|(key, value)| {
let mut m = BTreeMap::new();
m.insert(key.to_string(), value.to_string());
m
});
let mut buf = Vec::new();
self.to_armored_writer(&mut buf, headers.as_ref())
.unwrap_or_default();
std::string::String::from_utf8(buf).unwrap_or_default()
}
}
#[async_trait]
@@ -139,6 +182,39 @@ impl DcKey for SignedSecretKey {
Err(err) => Err(err.into()),
}
}
fn to_asc(&self, header: Option<(&str, &str)>) -> String {
// Not using .to_armored_string() to make clear *why* it is
// safe to do these unwraps.
// Because we write to a Vec<u8> the io::Write impls never
// fail and we can hide this error. The string is always ASCII.
let headers = header.map(|(key, value)| {
let mut m = BTreeMap::new();
m.insert(key.to_string(), value.to_string());
m
});
let mut buf = Vec::new();
self.to_armored_writer(&mut buf, headers.as_ref())
.unwrap_or_default();
std::string::String::from_utf8(buf).unwrap_or_default()
}
}
/// Deltachat extension trait for secret keys.
///
/// Provides some convenience wrappers only applicable to [SignedSecretKey].
pub trait DcSecretKey {
/// Create a public key from a private one.
fn split_public_key(&self) -> Result<SignedPublicKey>;
}
impl DcSecretKey for SignedSecretKey {
fn split_public_key(&self) -> Result<SignedPublicKey> {
self.verify()?;
let unsigned_pubkey = SecretKeyTrait::public_key(self);
let signed_pubkey = unsigned_pubkey.sign(self, || "".into())?;
Ok(signed_pubkey)
}
}
async fn generate_keypair(context: &Context) -> Result<KeyPair> {
@@ -189,193 +265,6 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
}
}
/// Cryptographic key
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Key {
Public(SignedPublicKey),
Secret(SignedSecretKey),
}
impl From<SignedPublicKey> for Key {
fn from(key: SignedPublicKey) -> Self {
Key::Public(key)
}
}
impl From<SignedSecretKey> for Key {
fn from(key: SignedSecretKey) -> Self {
Key::Secret(key)
}
}
impl std::convert::TryFrom<Key> for SignedSecretKey {
type Error = ();
fn try_from(value: Key) -> std::result::Result<Self, Self::Error> {
match value {
Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key),
}
}
}
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey {
type Error = ();
fn try_from(value: &'a Key) -> std::result::Result<Self, Self::Error> {
match value {
Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key),
}
}
}
impl std::convert::TryFrom<Key> for SignedPublicKey {
type Error = ();
fn try_from(value: Key) -> std::result::Result<Self, Self::Error> {
match value {
Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()),
}
}
}
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey {
type Error = ();
fn try_from(value: &'a Key) -> std::result::Result<Self, Self::Error> {
match value {
Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()),
}
}
}
impl Key {
pub fn is_public(&self) -> bool {
match self {
Key::Public(_) => true,
Key::Secret(_) => false,
}
}
pub fn is_secret(&self) -> bool {
!self.is_public()
}
pub fn from_slice(bytes: &[u8], key_type: KeyType) -> Result<Self> {
if bytes.is_empty() {
return Err(Error::Empty);
}
let res = match key_type {
KeyType::Public => SignedPublicKey::from_bytes(Cursor::new(bytes))?.into(),
KeyType::Private => SignedSecretKey::from_bytes(Cursor::new(bytes))?.into(),
};
Ok(res)
}
pub fn from_armored_string(
data: &str,
key_type: KeyType,
) -> Option<(Self, BTreeMap<String, String>)> {
let bytes = data.as_bytes();
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))
.map(|(k, h)| (Into::into(k), h)),
};
match res {
Ok(res) => Some(res),
Err(err) => {
eprintln!("Invalid key bytes: {:?}", err);
None
}
}
}
pub fn to_bytes(&self) -> Vec<u8> {
match self {
Key::Public(k) => k.to_bytes().unwrap_or_default(),
Key::Secret(k) => k.to_bytes().unwrap_or_default(),
}
}
pub fn verify(&self) -> bool {
match self {
Key::Public(k) => k.verify().is_ok(),
Key::Secret(k) => k.verify().is_ok(),
}
}
pub fn to_base64(&self) -> String {
let buf = self.to_bytes();
base64::encode(&buf)
}
pub fn to_armored_string(
&self,
headers: Option<&BTreeMap<String, String>>,
) -> pgp::errors::Result<String> {
match self {
Key::Public(k) => k.to_armored_string(headers),
Key::Secret(k) => k.to_armored_string(headers),
}
}
/// Each header line must be terminated by `\r\n`
pub fn to_asc(&self, header: Option<(&str, &str)>) -> String {
let headers = header.map(|(key, value)| {
let mut m = BTreeMap::new();
m.insert(key.to_string(), value.to_string());
m
});
self.to_armored_string(headers.as_ref())
.expect("failed to serialize key")
}
pub async fn write_asc_to_file(
&self,
file: impl AsRef<Path>,
context: &Context,
) -> std::io::Result<()> {
let file_content = self.to_asc(None).into_bytes();
let res = dc_write_file(context, &file, &file_content).await;
if res.is_err() {
error!(context, "Cannot write key to {}", file.as_ref().display());
}
res
}
pub fn fingerprint(&self) -> String {
match self {
Key::Public(k) => hex::encode_upper(k.fingerprint()),
Key::Secret(k) => hex::encode_upper(k.fingerprint()),
}
}
pub fn formatted_fingerprint(&self) -> String {
let rawhex = self.fingerprint();
dc_format_fingerprint(&rawhex)
}
pub fn split_key(&self) -> Option<Key> {
match self {
Key::Public(_) => None,
Key::Secret(k) => {
let pub_key = k.public_key();
pub_key.sign(k, || "".into()).map(Key::Public).ok()
}
}
}
}
/// Use of a [KeyPair] for encryption or decryption.
///
/// This is used by [store_self_keypair] to know what kind of key is
@@ -425,14 +314,8 @@ pub async fn store_self_keypair(
) -> std::result::Result<(), SaveKeyError> {
// Everything should really be one transaction, more refactoring
// is needed for that.
let public_key = keypair
.public
.to_bytes()
.map_err(|err| SaveKeyError::new("failed to serialise public key", err))?;
let secret_key = keypair
.secret
.to_bytes()
.map_err(|err| SaveKeyError::new("failed to serialise secret key", err))?;
let public_key = DcKey::to_bytes(&keypair.public);
let secret_key = DcKey::to_bytes(&keypair.secret);
context
.sql
.execute(
@@ -470,6 +353,62 @@ pub async fn store_self_keypair(
Ok(())
}
/// A key fingerprint
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Fingerprint(Vec<u8>);
impl Fingerprint {
pub fn new(v: Vec<u8>) -> Fingerprint {
Fingerprint(v)
}
/// Make a hex string from the fingerprint.
///
/// Use [std::fmt::Display] or [ToString::to_string] to get a
/// human-readable formatted string.
pub fn hex(&self) -> String {
hex::encode_upper(&self.0)
}
}
/// Make a human-readable fingerprint.
impl fmt::Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Split key into chunks of 4 with space and newline at 20 chars
for (i, c) in self.hex().chars().enumerate() {
if i > 0 && i % 20 == 0 {
writeln!(f)?;
} else if i > 0 && i % 4 == 0 {
write!(f, " ")?;
}
write!(f, "{}", c)?;
}
Ok(())
}
}
/// Parse a human-readable or otherwise formatted fingerprint.
impl std::str::FromStr for Fingerprint {
type Err = hex::FromHexError;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
let hex_repr: String = input
.chars()
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
.collect();
let v: Vec<u8> = hex::decode(hex_repr)?;
Ok(Fingerprint(v))
}
}
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
pub fn dc_normalize_fingerprint(fp: &str) -> String {
fp.to_uppercase()
.chars()
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
.collect()
}
/// Make a fingerprint human-readable, in hex format.
pub fn dc_format_fingerprint(fingerprint: &str) -> String {
// split key into chunks of 4 with space, and 20 newline
@@ -488,19 +427,10 @@ pub fn dc_format_fingerprint(fingerprint: &str) -> String {
res
}
/// Bring a human-readable or otherwise formatted fingerprint back to the 40-characters-uppercase-hex format.
pub fn dc_normalize_fingerprint(fp: &str) -> String {
fp.to_uppercase()
.chars()
.filter(|&c| c >= '0' && c <= '9' || c >= 'A' && c <= 'F')
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
use std::convert::TryFrom;
use async_std::sync::Arc;
use lazy_static::lazy_static;
@@ -516,9 +446,19 @@ mod tests {
assert_eq!(fingerprint, "1234567890ABCDABCDEFABCDEF");
}
#[test]
fn test_format_fingerprint() {
let fingerprint = dc_format_fingerprint("1234567890ABCDABCDEFABCDEF1234567890ABCD");
assert_eq!(
fingerprint,
"1234 5678 90AB CDAB CDEF\nABCD EF12 3456 7890 ABCD"
);
}
#[test]
fn test_from_armored_string() {
let (private_key, _) = Key::from_armored_string(
let (private_key, _) = SignedSecretKey::from_asc(
"-----BEGIN PGP PRIVATE KEY BLOCK-----
xcLYBF0fgz4BCADnRUV52V4xhSsU56ZaAn3+3oG86MZhXy4X8w14WZZDf0VJGeTh
@@ -576,58 +516,64 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
7yPJeQ==
=KZk/
-----END PGP PRIVATE KEY BLOCK-----",
KeyType::Private,
)
.expect("failed to decode"); // NOTE: if you take out the ===GU1/ part, everything passes!
let binary = private_key.to_bytes();
Key::from_slice(&binary, KeyType::Private).expect("invalid private key");
.expect("failed to decode");
let binary = DcKey::to_bytes(&private_key);
SignedSecretKey::from_slice(&binary).expect("invalid private key");
}
#[test]
fn test_format_fingerprint() {
let fingerprint = dc_format_fingerprint("1234567890ABCDABCDEFABCDEF1234567890ABCD");
fn test_asc_roundtrip() {
let key = KEYPAIR.public.clone();
let asc = key.to_asc(Some(("spam", "ham")));
let (key2, hdrs) = SignedPublicKey::from_asc(&asc).unwrap();
assert_eq!(key, key2);
assert_eq!(hdrs.len(), 1);
assert_eq!(hdrs.get("spam"), Some(&String::from("ham")));
assert_eq!(
fingerprint,
"1234 5678 90AB CDAB CDEF\nABCD EF12 3456 7890 ABCD"
);
let key = KEYPAIR.secret.clone();
let asc = key.to_asc(Some(("spam", "ham")));
let (key2, hdrs) = SignedSecretKey::from_asc(&asc).unwrap();
assert_eq!(key, key2);
assert_eq!(hdrs.len(), 1);
assert_eq!(hdrs.get("spam"), Some(&String::from("ham")));
}
#[test]
fn test_from_slice_roundtrip() {
let public_key = Key::from(KEYPAIR.public.clone());
let private_key = Key::from(KEYPAIR.secret.clone());
let public_key = KEYPAIR.public.clone();
let private_key = KEYPAIR.secret.clone();
let binary = public_key.to_bytes();
let public_key2 = Key::from_slice(&binary, KeyType::Public).expect("invalid public key");
let binary = DcKey::to_bytes(&public_key);
let public_key2 = SignedPublicKey::from_slice(&binary).expect("invalid public key");
assert_eq!(public_key, public_key2);
let binary = private_key.to_bytes();
let private_key2 = Key::from_slice(&binary, KeyType::Private).expect("invalid private key");
let binary = DcKey::to_bytes(&private_key);
let private_key2 = SignedSecretKey::from_slice(&binary).expect("invalid private key");
assert_eq!(private_key, private_key2);
}
#[test]
fn test_from_slice_bad_data() {
let mut bad_data: [u8; 4096] = [0; 4096];
for i in 0..4096 {
bad_data[i] = (i & 0xff) as u8;
}
for j in 0..(4096 / 40) {
let bad_key = Key::from_slice(
&bad_data[j..j + 4096 / 2 + j],
if 0 != j & 1 {
KeyType::Public
} else {
KeyType::Private
},
);
assert!(bad_key.is_err());
let slice = &bad_data[j..j + 4096 / 2 + j];
assert!(SignedPublicKey::from_slice(slice).is_err());
assert!(SignedSecretKey::from_slice(slice).is_err());
}
}
#[test]
fn test_base64_roundtrip() {
let key = KEYPAIR.public.clone();
let base64 = key.to_base64();
let key2 = SignedPublicKey::from_base64(&base64).unwrap();
assert_eq!(key, key2);
}
#[async_std::test]
async fn test_load_self_existing() {
let alice = alice_keypair();
@@ -685,29 +631,10 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
assert_eq!(res0.unwrap(), res1.unwrap());
}
#[test]
fn test_ascii_roundtrip() {
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, _) =
Key::from_armored_string(&s, KeyType::Public).expect("invalid public key");
assert_eq!(public_key, public_key2);
let s = private_key.to_armored_string(None).unwrap();
println!("{}", &s);
let (private_key2, _) =
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);
let pubkey = KEYPAIR.secret.split_public_key().unwrap();
assert_eq!(pubkey.primary_key, KEYPAIR.public.primary_key);
}
#[async_std::test]
@@ -755,4 +682,35 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
// )
// .unwrap();
// }
#[test]
fn test_fingerprint_from_str() {
let res = Fingerprint::new(vec![1, 2, 4, 8, 16, 32, 64, 128, 255]);
let fp: Fingerprint = "0102040810204080FF".parse().unwrap();
assert_eq!(fp, res);
let fp: Fingerprint = "zzzz 0102 0408\n1020 4080 FF zzz".parse().unwrap();
assert_eq!(fp, res);
let err = "1".parse::<Fingerprint>().err().unwrap();
assert_eq!(err, hex::FromHexError::OddLength);
}
#[test]
fn test_fingerprint_hex() {
let fp = Fingerprint::new(vec![1, 2, 4, 8, 16, 32, 64, 128, 255]);
assert_eq!(fp.hex(), "0102040810204080FF");
}
#[test]
fn test_fingerprint_to_string() {
let fp = Fingerprint::new(vec![
1, 2, 4, 8, 16, 32, 64, 128, 255, 1, 2, 4, 8, 16, 32, 64, 128, 255,
]);
assert_eq!(
fp.to_string(),
"0102 0408 1020 4080 FF01\n0204 0810 2040 80FF"
);
}
}

View File

@@ -1,17 +1,47 @@
//! Keyring to perform rpgp operations with.
use anyhow::Result;
use crate::constants::KeyType;
use crate::context::Context;
use crate::key::Key;
use crate::key::{self, DcKey};
#[derive(Default, Clone, Debug)]
pub struct Keyring {
keys: Vec<Key>,
/// An in-memory keyring.
///
/// Instances are usually constructed just for the rpgp operation and
/// short-lived.
#[derive(Clone, Debug, Default)]
pub struct Keyring<T>
where
T: DcKey,
{
keys: Vec<T>,
}
impl Keyring {
pub fn add(&mut self, key: Key) {
self.keys.push(key)
impl<T> Keyring<T>
where
T: DcKey<KeyType = T>,
{
/// New empty keyring.
pub fn new() -> Keyring<T> {
Keyring { keys: Vec::new() }
}
/// Create a new keyring with the the user's secret key loaded.
pub async fn new_self(context: &Context) -> Result<Keyring<T>, key::Error> {
let mut keyring: Keyring<T> = Keyring::new();
keyring.load_self(context).await?;
Ok(keyring)
}
/// Load the user's key into the keyring.
pub async fn load_self(&mut self, context: &Context) -> Result<(), key::Error> {
self.add(T::load_self(context).await?);
Ok(())
}
/// Add a key to the keyring.
pub fn add(&mut self, key: T) {
self.keys.push(key);
}
pub fn len(&self) -> usize {
@@ -22,26 +52,41 @@ impl Keyring {
self.keys.is_empty()
}
pub fn keys(&self) -> &[Key] {
/// A vector with reference to all the keys in the keyring.
pub fn keys(&self) -> &[T] {
&self.keys
}
}
pub async fn load_self_private_for_decrypting(
context: &Context,
self_addr: impl AsRef<str>,
) -> Result<Self> {
let blob: Vec<u8> = context
.sql
.query_get_value_result(
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
paramsv![self_addr.as_ref().to_string()],
)
.await?
.unwrap_or_default();
#[cfg(test)]
mod tests {
use super::*;
use crate::key::{SignedPublicKey, SignedSecretKey};
use crate::test_utils::*;
let key = async_std::task::spawn_blocking(move || Key::from_slice(&blob, KeyType::Private))
.await?;
#[test]
fn test_keyring_add_keys() {
let alice = alice_keypair();
let mut pub_ring: Keyring<SignedPublicKey> = Keyring::new();
pub_ring.add(alice.public.clone());
assert_eq!(pub_ring.keys(), [alice.public]);
Ok(Self { keys: vec![key] })
let mut sec_ring: Keyring<SignedSecretKey> = Keyring::new();
sec_ring.add(alice.secret.clone());
assert_eq!(sec_ring.keys(), [alice.secret]);
}
#[async_std::test]
async fn test_keyring_load_self() {
// new_self() implies load_self()
let t = dummy_context().await;
configure_alice_keypair(&t.ctx).await;
let alice = alice_keypair();
let pub_ring: Keyring<SignedPublicKey> = Keyring::new_self(&t.ctx).await.unwrap();
assert_eq!(pub_ring.keys(), [alice.public]);
let sec_ring: Keyring<SignedSecretKey> = Keyring::new_self(&t.ctx).await.unwrap();
assert_eq!(sec_ring.keys(), [alice.secret]);
}
}

View File

@@ -6,9 +6,8 @@ use std::fmt;
use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::constants::*;
use crate::context::Context;
use crate::key::{Key, SignedPublicKey};
use crate::key::{DcKey, SignedPublicKey};
use crate::sql::Sql;
#[derive(Debug)]
@@ -32,12 +31,12 @@ pub struct Peerstate<'a> {
pub last_seen: i64,
pub last_seen_autocrypt: i64,
pub prefer_encrypt: EncryptPreference,
pub public_key: Option<Key>,
pub public_key: Option<SignedPublicKey>,
pub public_key_fingerprint: Option<String>,
pub gossip_key: Option<Key>,
pub gossip_key: Option<SignedPublicKey>,
pub gossip_timestamp: i64,
pub gossip_key_fingerprint: Option<String>,
pub verified_key: Option<Key>,
pub verified_key: Option<SignedPublicKey>,
pub verified_key_fingerprint: Option<String>,
pub to_save: Option<ToSave>,
pub degrade_event: Option<DegradeEvent>,
@@ -127,7 +126,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(Key::from(header.public_key.clone()));
res.public_key = Some(header.public_key.clone());
res.recalc_fingerprint();
res
@@ -138,7 +137,7 @@ impl<'a> Peerstate<'a> {
res.gossip_timestamp = message_time;
res.to_save = Some(ToSave::All);
res.gossip_key = Some(Key::from(gossip_header.public_key.clone()));
res.gossip_key = Some(gossip_header.public_key.clone());
res.recalc_fingerprint();
res
@@ -220,15 +219,15 @@ impl<'a> Peerstate<'a> {
res.public_key = row
.get(4)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public).ok());
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
res.gossip_key = row
.get(6)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public).ok());
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
res.verified_key = row
.get(9)
.ok()
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Public).ok());
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
Ok(res)
})
@@ -239,7 +238,7 @@ impl<'a> Peerstate<'a> {
pub fn recalc_fingerprint(&mut self) {
if let Some(ref public_key) = self.public_key {
let old_public_fingerprint = self.public_key_fingerprint.take();
self.public_key_fingerprint = Some(public_key.fingerprint());
self.public_key_fingerprint = Some(public_key.fingerprint().hex());
if old_public_fingerprint.is_none()
|| self.public_key_fingerprint.is_none()
@@ -254,7 +253,7 @@ impl<'a> Peerstate<'a> {
if let Some(ref gossip_key) = self.gossip_key {
let old_gossip_fingerprint = self.gossip_key_fingerprint.take();
self.gossip_key_fingerprint = Some(gossip_key.fingerprint());
self.gossip_key_fingerprint = Some(gossip_key.fingerprint().hex());
if old_gossip_fingerprint.is_none()
|| self.gossip_key_fingerprint.is_none()
@@ -300,8 +299,8 @@ impl<'a> Peerstate<'a> {
self.to_save = Some(ToSave::All)
}
if self.public_key.as_ref() != Some(&Key::from(header.public_key.clone())) {
self.public_key = Some(Key::from(header.public_key.clone()));
if self.public_key.as_ref() != Some(&header.public_key) {
self.public_key = Some(header.public_key.clone());
self.recalc_fingerprint();
self.to_save = Some(ToSave::All);
}
@@ -316,9 +315,8 @@ impl<'a> Peerstate<'a> {
if message_time > self.gossip_timestamp {
self.gossip_timestamp = message_time;
self.to_save = Some(ToSave::Timestamps);
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);
if self.gossip_key.as_ref() != Some(&gossip_header.public_key) {
self.gossip_key = Some(gossip_header.public_key.clone());
self.recalc_fingerprint();
self.to_save = Some(ToSave::All)
}
@@ -367,7 +365,7 @@ impl<'a> Peerstate<'a> {
}
}
pub fn take_key(mut self, min_verified: PeerstateVerifiedStatus) -> Option<Key> {
pub fn take_key(mut self, min_verified: PeerstateVerifiedStatus) -> Option<SignedPublicKey> {
match min_verified {
PeerstateVerifiedStatus::BidirectVerified => self.verified_key.take(),
PeerstateVerifiedStatus::Unverified => {
@@ -376,7 +374,7 @@ impl<'a> Peerstate<'a> {
}
}
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&Key> {
pub fn peek_key(&self, min_verified: PeerstateVerifiedStatus) -> Option<&SignedPublicKey> {
match min_verified {
PeerstateVerifiedStatus::BidirectVerified => self.verified_key.as_ref(),
PeerstateVerifiedStatus::Unverified => self
@@ -495,7 +493,7 @@ mod tests {
let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from(alice_keypair().public);
let pub_key = alice_keypair().public;
let mut peerstate = Peerstate {
context: &ctx.ctx,
@@ -504,12 +502,12 @@ mod tests {
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
public_key_fingerprint: Some(pub_key.fingerprint().hex()),
gossip_key: Some(pub_key.clone()),
gossip_timestamp: 12,
gossip_key_fingerprint: Some(pub_key.fingerprint()),
gossip_key_fingerprint: Some(pub_key.fingerprint().hex()),
verified_key: Some(pub_key.clone()),
verified_key_fingerprint: Some(pub_key.fingerprint()),
verified_key_fingerprint: Some(pub_key.fingerprint().hex()),
to_save: Some(ToSave::All),
degrade_event: None,
};
@@ -527,7 +525,7 @@ mod tests {
peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new);
let peerstate_new2 =
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint())
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint().hex())
.await
.expect("failed to load peerstate from db");
assert_eq!(peerstate, peerstate_new2);
@@ -537,7 +535,7 @@ mod tests {
async fn test_peerstate_double_create() {
let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from(alice_keypair().public);
let pub_key = alice_keypair().public;
let peerstate = Peerstate {
context: &ctx.ctx,
@@ -546,7 +544,7 @@ mod tests {
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
public_key_fingerprint: Some(pub_key.fingerprint().hex()),
gossip_key: None,
gossip_timestamp: 12,
gossip_key_fingerprint: None,
@@ -571,7 +569,7 @@ mod tests {
let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com";
let pub_key = crate::key::Key::from(alice_keypair().public);
let pub_key = alice_keypair().public;
let mut peerstate = Peerstate {
context: &ctx.ctx,
@@ -580,7 +578,7 @@ mod tests {
last_seen_autocrypt: 11,
prefer_encrypt: EncryptPreference::Mutual,
public_key: Some(pub_key.clone()),
public_key_fingerprint: Some(pub_key.fingerprint()),
public_key_fingerprint: Some(pub_key.fingerprint().hex()),
gossip_key: None,
gossip_timestamp: 12,
gossip_key_fingerprint: None,

View File

@@ -1,7 +1,6 @@
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp)
use std::collections::{BTreeMap, HashSet};
use std::convert::TryInto;
use std::io;
use std::io::Cursor;
@@ -19,8 +18,8 @@ use rand::{thread_rng, CryptoRng, Rng};
use crate::constants::KeyGenType;
use crate::dc_tools::EmailAddress;
use crate::error::{bail, ensure, format_err, Result};
use crate::key::*;
use crate::keyring::*;
use crate::key::DcKey;
use crate::keyring::Keyring;
pub const HEADER_AUTOCRYPT: &str = "autocrypt-prefer-encrypt";
pub const HEADER_SETUPCODE: &str = "passphrase-begin";
@@ -240,8 +239,8 @@ fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<SignedPublicKeyOrSu
/// and signs it using `private_key_for_signing`.
pub async fn pk_encrypt(
plain: &[u8],
public_keys_for_encryption: Keyring,
private_key_for_signing: Option<Key>,
public_keys_for_encryption: Keyring<SignedPublicKey>,
private_key_for_signing: Option<SignedSecretKey>,
) -> Result<String> {
let lit_msg = Message::new_literal_bytes("", plain);
@@ -249,18 +248,14 @@ pub async fn pk_encrypt(
let pkeys: Vec<SignedPublicKeyOrSubkey> = public_keys_for_encryption
.keys()
.iter()
.filter_map(|key| key.try_into().ok().and_then(select_pk_for_encryption))
.filter_map(|key| select_pk_for_encryption(key))
.collect();
let pkeys_refs: Vec<&SignedPublicKeyOrSubkey> = pkeys.iter().collect();
let mut rng = thread_rng();
// TODO: measure time
let encrypted_msg = if let Some(ref private_key) = private_key_for_signing {
let skey: &SignedSecretKey = private_key
.try_into()
.map_err(|_| format_err!("Invalid private key"))?;
let encrypted_msg = if let Some(ref skey) = private_key_for_signing {
lit_msg
.sign(skey, || "".into(), Default::default())
.and_then(|msg| msg.compress(CompressionAlgorithm::ZLIB))
@@ -280,19 +275,15 @@ pub async fn pk_encrypt(
#[allow(clippy::implicit_hasher)]
pub async fn pk_decrypt(
ctext: Vec<u8>,
private_keys_for_decryption: Keyring,
public_keys_for_validation: Keyring,
private_keys_for_decryption: Keyring<SignedSecretKey>,
public_keys_for_validation: Keyring<SignedPublicKey>,
ret_signature_fingerprints: Option<&mut HashSet<String>>,
) -> Result<Vec<u8>> {
let msgs = async_std::task::spawn_blocking(move || {
let cursor = Cursor::new(ctext);
let (msg, _) = Message::from_armor_single(cursor)?;
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption
.keys()
.iter()
.filter_map(|key| key.try_into().ok())
.collect();
let skeys: Vec<&SignedSecretKey> = private_keys_for_decryption.keys().iter().collect();
let (decryptor, _) = msg.decrypt(|| "".into(), || "".into(), &skeys[..])?;
decryptor.collect::<pgp::errors::Result<Vec<_>>>()
@@ -311,15 +302,12 @@ pub async fn pk_decrypt(
let fingerprints = async_std::task::spawn_blocking(move || {
let dec_msg = &msgs[0];
let pkeys = public_keys_for_validation
.keys()
.iter()
.filter_map(|key| -> Option<&SignedPublicKey> { key.try_into().ok() });
let pkeys = public_keys_for_validation.keys();
let mut fingerprints = Vec::new();
for pkey in pkeys {
if dec_msg.verify(&pkey.primary_key).is_ok() {
let fp = hex::encode_upper(pkey.fingerprint());
let fp = DcKey::fingerprint(pkey).hex();
fingerprints.push(fp);
}
}
@@ -424,10 +412,10 @@ mod tests {
/// [Key] objects to use in tests.
struct TestKeys {
alice_secret: Key,
alice_public: Key,
bob_secret: Key,
bob_public: Key,
alice_secret: SignedSecretKey,
alice_public: SignedPublicKey,
bob_secret: SignedSecretKey,
bob_public: SignedPublicKey,
}
impl TestKeys {
@@ -435,10 +423,10 @@ mod tests {
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()),
alice_secret: alice.secret.clone(),
alice_public: alice.public.clone(),
bob_secret: bob.secret.clone(),
bob_public: bob.public.clone(),
}
}
}
@@ -452,7 +440,7 @@ mod tests {
/// A cyphertext encrypted to Alice & Bob, signed by Alice.
static ref CTEXT_SIGNED: String = {
let mut keyring = Keyring::default();
let mut keyring = Keyring::new();
keyring.add(KEYS.alice_public.clone());
keyring.add(KEYS.bob_public.clone());
smol::block_on(pk_encrypt(CLEARTEXT, keyring, Some(KEYS.alice_secret.clone()))).unwrap()
@@ -460,7 +448,7 @@ mod tests {
/// A cyphertext encrypted to Alice & Bob, not signed.
static ref CTEXT_UNSIGNED: String = {
let mut keyring = Keyring::default();
let mut keyring = Keyring::new();
keyring.add(KEYS.alice_public.clone());
keyring.add(KEYS.bob_public.clone());
smol::block_on(pk_encrypt(CLEARTEXT, keyring, None)).unwrap()
@@ -482,9 +470,9 @@ mod tests {
#[async_std::test]
async fn test_decrypt_singed() {
// Check decrypting as Alice
let mut decrypt_keyring = Keyring::default();
let mut decrypt_keyring: Keyring<SignedSecretKey> = Keyring::new();
decrypt_keyring.add(KEYS.alice_secret.clone());
let mut sig_check_keyring = Keyring::default();
let mut sig_check_keyring: Keyring<SignedPublicKey> = Keyring::new();
sig_check_keyring.add(KEYS.alice_public.clone());
let mut valid_signatures: HashSet<String> = Default::default();
let plain = pk_decrypt(
@@ -500,9 +488,9 @@ mod tests {
assert_eq!(valid_signatures.len(), 1);
// Check decrypting as Bob
let mut decrypt_keyring = Keyring::default();
let mut decrypt_keyring = Keyring::new();
decrypt_keyring.add(KEYS.bob_secret.clone());
let mut sig_check_keyring = Keyring::default();
let mut sig_check_keyring = Keyring::new();
sig_check_keyring.add(KEYS.alice_public.clone());
let mut valid_signatures: HashSet<String> = Default::default();
let plain = pk_decrypt(
@@ -520,9 +508,9 @@ mod tests {
#[async_std::test]
async fn test_decrypt_no_sig_check() {
let mut keyring = Keyring::default();
let mut keyring = Keyring::new();
keyring.add(KEYS.alice_secret.clone());
let empty_keyring = Keyring::default();
let empty_keyring = Keyring::new();
let mut valid_signatures: HashSet<String> = Default::default();
let plain = pk_decrypt(
CTEXT_SIGNED.as_bytes().to_vec(),
@@ -539,9 +527,9 @@ mod tests {
#[async_std::test]
async fn test_decrypt_signed_no_key() {
// The validation does not have the public key of the signer.
let mut decrypt_keyring = Keyring::default();
let mut decrypt_keyring = Keyring::new();
decrypt_keyring.add(KEYS.bob_secret.clone());
let mut sig_check_keyring = Keyring::default();
let mut sig_check_keyring = Keyring::new();
sig_check_keyring.add(KEYS.bob_public.clone());
let mut valid_signatures: HashSet<String> = Default::default();
let plain = pk_decrypt(
@@ -558,10 +546,9 @@ mod tests {
#[async_std::test]
async fn test_decrypt_unsigned() {
let mut decrypt_keyring = Keyring::default();
let mut decrypt_keyring = Keyring::new();
decrypt_keyring.add(KEYS.bob_secret.clone());
let sig_check_keyring = Keyring::default();
decrypt_keyring.add(KEYS.alice_public.clone());
let sig_check_keyring = Keyring::new();
let mut valid_signatures: HashSet<String> = Default::default();
let plain = pk_decrypt(
CTEXT_UNSIGNED.as_bytes().to_vec(),
@@ -578,9 +565,9 @@ mod tests {
#[async_std::test]
async fn test_decrypt_signed_no_sigret() {
// Check decrypting signed cyphertext without providing the HashSet for signatures.
let mut decrypt_keyring = Keyring::default();
let mut decrypt_keyring = Keyring::new();
decrypt_keyring.add(KEYS.bob_secret.clone());
let mut sig_check_keyring = Keyring::default();
let mut sig_check_keyring = Keyring::new();
sig_check_keyring.add(KEYS.alice_public.clone());
let plain = pk_decrypt(
CTEXT_SIGNED.as_bytes().to_vec(),

View File

@@ -14,7 +14,7 @@ use crate::e2ee::*;
use crate::error::{bail, Error};
use crate::events::Event;
use crate::headerdef::HeaderDef;
use crate::key::{dc_normalize_fingerprint, DcKey, Key, SignedPublicKey};
use crate::key::{dc_normalize_fingerprint, DcKey, SignedPublicKey};
use crate::lot::LotState;
use crate::message::Message;
use crate::mimeparser::*;
@@ -142,7 +142,7 @@ pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> O
async fn get_self_fingerprint(context: &Context) -> Option<String> {
match SignedPublicKey::load_self(context).await {
Ok(key) => Some(Key::from(key).fingerprint()),
Ok(key) => Some(key.fingerprint().hex()),
Err(_) => {
warn!(context, "get_self_fingerprint(): failed to load key");
None