fix: make it impossible to overwrite default key

Replacing default key
when a profile is already part of
verified groups results in
`[The message was sent with non-verified encryption. See 'Info' for more details]`
messages for other users.

It is still possible
to import the default key before
Delta Chat generates the key.
This commit is contained in:
link2xt
2025-02-24 17:27:47 +00:00
committed by l
parent 3b51e22b2e
commit 8c2207d15e
13 changed files with 129 additions and 155 deletions

View File

@@ -38,7 +38,7 @@ use crate::provider::{Protocol, Socket, UsernamePattern};
use crate::smtp::Smtp;
use crate::sync::Sync::*;
use crate::tools::time;
use crate::{chat, e2ee, provider};
use crate::{chat, provider};
use crate::{stock_str, EventType};
use deltachat_contact_tools::addr_cmp;
@@ -473,9 +473,6 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Configure
progress!(ctx, 920);
e2ee::ensure_secret_key_exists(ctx).await?;
info!(ctx, "key generation completed");
ctx.set_config_internal(Config::FetchedExistingMsgs, config::from_bool(false))
.await?;
ctx.scheduler.interrupt_inbox().await;

View File

@@ -138,7 +138,7 @@ pub async fn has_backup(_context: &Context, dir_name: &Path) -> Result<String> {
}
}
async fn set_self_key(context: &Context, armored: &str, set_default: bool) -> Result<()> {
async fn set_self_key(context: &Context, armored: &str) -> Result<()> {
// try hard to only modify key-state
let (private_key, header) = SignedSecretKey::from_asc(armored)?;
let public_key = private_key.split_public_key()?;
@@ -170,16 +170,7 @@ async fn set_self_key(context: &Context, armored: &str, set_default: bool) -> Re
public: public_key,
secret: private_key,
};
key::store_self_keypair(
context,
&keypair,
if set_default {
key::KeyPairUse::Default
} else {
key::KeyPairUse::ReadOnly
},
)
.await?;
key::store_self_keypair(context, &keypair).await?;
info!(context, "stored self key: {:?}", keypair.secret.key_id());
Ok(())
@@ -599,10 +590,10 @@ where
}
/// Imports secret key from a file.
async fn import_secret_key(context: &Context, path: &Path, set_default: bool) -> Result<()> {
async fn import_secret_key(context: &Context, path: &Path) -> Result<()> {
let buf = read_file(context, path).await?;
let armored = std::string::String::from_utf8_lossy(&buf);
set_self_key(context, &armored, set_default).await?;
set_self_key(context, &armored).await?;
Ok(())
}
@@ -624,8 +615,7 @@ async fn import_self_keys(context: &Context, path: &Path) -> Result<()> {
"Importing secret key from {} as the default key.",
path.display()
);
let set_default = true;
import_secret_key(context, path, set_default).await?;
import_secret_key(context, path).await?;
return Ok(());
}
@@ -643,14 +633,13 @@ async fn import_self_keys(context: &Context, path: &Path) -> Result<()> {
} else {
continue;
};
let set_default = !name_f.contains("legacy");
info!(
context,
"Considering key file: {}.",
path_plus_name.display()
);
if let Err(err) = import_secret_key(context, &path_plus_name, set_default).await {
if let Err(err) = import_secret_key(context, &path_plus_name).await {
warn!(
context,
"Failed to import secret key from {}: {:#}.",
@@ -871,7 +860,7 @@ mod tests {
assert_eq!(bytes, key.to_asc(None).into_bytes());
let alice = &TestContext::new_alice().await;
let alice = &TestContext::new().await;
if let Err(err) = imex(alice, ImexMode::ImportSelfKeys, Path::new(&filename), None).await {
panic!("got error on import: {err:#}");
}
@@ -893,7 +882,7 @@ mod tests {
panic!("got error on export: {err:#}");
}
let context2 = TestContext::new_alice().await;
let context2 = TestContext::new().await;
if let Err(err) = imex(
&context2.ctx,
ImexMode::ImportSelfKeys,
@@ -920,15 +909,18 @@ mod tests {
let alice = &TestContext::new_alice().await;
let old_key = key::load_self_secret_key(alice).await?;
imex(alice, ImexMode::ImportSelfKeys, export_dir.path(), None).await?;
let new_key = key::load_self_secret_key(alice).await?;
assert_ne!(new_key, old_key);
assert_eq!(
key::load_self_secret_keyring(alice).await?,
vec![new_key, old_key]
assert!(
imex(alice, ImexMode::ImportSelfKeys, export_dir.path(), None)
.await
.is_err()
);
// Importing a second key is not allowed anymore,
// even as a non-default key.
assert_eq!(key::load_self_secret_key(alice).await?, old_key);
assert_eq!(key::load_self_secret_keyring(alice).await?, vec![old_key]);
let msg = alice.recv_msg(&sent).await;
assert!(msg.get_showpadlock());
assert_eq!(msg.chat_id, alice.get_self_chat().await.id);

View File

@@ -75,7 +75,7 @@ pub async fn continue_key_transfer(
let file = open_file_std(context, filename)?;
let sc = normalize_setup_code(setup_code);
let armored_key = decrypt_setup_file(&sc, file).await?;
set_self_key(context, &armored_key, true).await?;
set_self_key(context, &armored_key).await?;
context.set_config_bool(Config::BccSelf, true).await?;
Ok(())
@@ -315,18 +315,19 @@ mod tests {
alice2.recv_msg(&sent).await;
let msg = alice2.get_last_msg().await;
assert!(msg.is_setupmessage());
// Send a message that cannot be decrypted because the keys are
// not synchronized yet.
let sent = alice2.send_text(msg.chat_id, "Test").await;
let trashed_message = alice.recv_msg_opt(&sent).await;
assert!(trashed_message.is_none());
assert_ne!(alice.get_last_msg().await.get_text(), "Test");
assert_eq!(
crate::key::load_self_secret_keyring(&alice2).await?.len(),
0
);
// Transfer the key.
alice2.set_config(Config::BccSelf, Some("0")).await?;
continue_key_transfer(&alice2, msg.id, &setup_code).await?;
assert_eq!(alice2.get_config_bool(Config::BccSelf).await?, true);
assert_eq!(
crate::key::load_self_secret_keyring(&alice2).await?.len(),
1
);
// Alice sends a message to self from the new device.
let sent = alice2.send_text(msg.chat_id, "Test").await;

View File

@@ -289,7 +289,7 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
.spawn_blocking(move || crate::pgp::create_keypair(addr, keytype))
.await??;
store_self_keypair(context, &keypair, KeyPairUse::Default).await?;
store_self_keypair(context, &keypair).await?;
info!(
context,
"Keypair generated in {:.3}s.",
@@ -326,18 +326,6 @@ pub(crate) async fn load_keypair(context: &Context) -> Result<Option<KeyPair>> {
})
}
/// Use of a key pair for encryption or decryption.
///
/// This is used by `store_self_keypair` to know what kind of key is
/// being saved.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum KeyPairUse {
/// The default key used to encrypt new messages.
Default,
/// Only used to decrypt existing message.
ReadOnly,
}
/// Store the keypair as an owned keypair for addr in the database.
///
/// This will save the keypair as keys for the given address. The
@@ -350,11 +338,7 @@ pub enum KeyPairUse {
/// same key again overwrites it.
///
/// [Config::ConfiguredAddr]: crate::config::Config::ConfiguredAddr
pub(crate) async fn store_self_keypair(
context: &Context,
keypair: &KeyPair,
default: KeyPairUse,
) -> Result<()> {
pub(crate) async fn store_self_keypair(context: &Context, keypair: &KeyPair) -> Result<()> {
let mut config_cache_lock = context.sql.config_cache.write().await;
let new_key_id = context
.sql
@@ -362,29 +346,28 @@ pub(crate) async fn store_self_keypair(
let public_key = DcKey::to_bytes(&keypair.public);
let secret_key = DcKey::to_bytes(&keypair.secret);
let is_default = match default {
KeyPairUse::Default => true,
KeyPairUse::ReadOnly => false,
};
// private_key and public_key columns
// are UNIQUE since migration 107,
// so this fails if we already have this key.
transaction
.execute(
"INSERT OR REPLACE INTO keypairs (public_key, private_key)
"INSERT INTO keypairs (public_key, private_key)
VALUES (?,?)",
(&public_key, &secret_key),
)
.context("Failed to insert keypair")?;
if is_default {
let new_key_id = transaction.last_insert_rowid();
transaction.execute(
"INSERT OR REPLACE INTO config (keyname, value) VALUES ('key_id', ?)",
(new_key_id,),
)?;
Ok(Some(new_key_id))
} else {
Ok(None)
}
let new_key_id = transaction.last_insert_rowid();
// This will fail if we already have `key_id`.
//
// Setting default key is only possible if we don't
// have a key already.
transaction.execute(
"INSERT INTO config (keyname, value) VALUES ('key_id', ?)",
(new_key_id,),
)?;
Ok(Some(new_key_id))
})
.await?;
@@ -405,7 +388,7 @@ pub async fn preconfigure_keypair(context: &Context, secret_data: &str) -> Resul
let secret = SignedSecretKey::from_asc(secret_data)?.0;
let public = secret.split_public_key()?;
let keypair = KeyPair { public, secret };
store_self_keypair(context, &keypair, KeyPairUse::Default).await?;
store_self_keypair(context, &keypair).await?;
Ok(())
}
@@ -700,6 +683,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
assert_eq!(pubkey.primary_key, KEYPAIR.public.primary_key);
}
/// Tests that setting a default key second time is not allowed.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_save_self_key_twice() {
// Saving the same key twice should result in only one row in
@@ -714,13 +698,13 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
.unwrap()
};
assert_eq!(nrows().await, 0);
store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default)
.await
.unwrap();
store_self_keypair(&ctx, &KEYPAIR).await.unwrap();
assert_eq!(nrows().await, 1);
store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default)
.await
.unwrap();
// Saving a second key fails.
let res = store_self_keypair(&ctx, &KEYPAIR).await;
assert!(res.is_err());
assert_eq!(nrows().await, 1);
}

View File

@@ -4705,8 +4705,10 @@ async fn test_outgoing_msg_forgery() -> Result<()> {
// We need Bob only to encrypt the forged message to Alice's key, actually Bob doesn't
// participate in the scenario.
let bob = &TestContext::new().await;
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 0);
bob.configure_addr("bob@example.net").await;
imex(bob, ImexMode::ImportSelfKeys, export_dir.path(), None).await?;
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
let malice = &TestContext::new().await;
malice.configure_addr(alice_addr).await;
@@ -4714,6 +4716,7 @@ async fn test_outgoing_msg_forgery() -> Result<()> {
.send_recv_accept(bob, malice, "hi from bob")
.await
.chat_id;
assert_eq!(crate::key::load_self_secret_keyring(bob).await?.len(), 1);
let sent_msg = malice.send_text(malice_chat_id, "hi from malice").await;
let msg = alice.recv_msg(&sent_msg).await;

View File

@@ -33,7 +33,7 @@ use crate::contact::{Contact, ContactId, Modifier, Origin};
use crate::context::Context;
use crate::e2ee::EncryptHelper;
use crate::events::{Event, EventEmitter, EventType, Events};
use crate::key::{self, DcKey, KeyPairUse};
use crate::key::{self, DcKey};
use crate::message::{update_msg_state, Message, MessageState, MsgId, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::peerstate::Peerstate;
@@ -271,7 +271,7 @@ impl TestContextBuilder {
let test_context = TestContext::new_internal(Some(name), self.log_sink).await;
test_context.configure_addr(&addr).await;
key::store_self_keypair(&test_context, &key_pair, KeyPairUse::Default)
key::store_self_keypair(&test_context, &key_pair)
.await
.expect("Failed to save key");
test_context