Skip to content

Commit

Permalink
refactor(sql): recreate keypairs table
Browse files Browse the repository at this point in the history
Removed unused `addr` and `created` field.
`is_default` boolean flag is moved into `config` row
pointing to the current default key.
  • Loading branch information
link2xt committed Dec 11, 2023
1 parent 0066e7b commit bbff5e5
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 56 deletions.
19 changes: 5 additions & 14 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::log::LogExt;
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::provider::{get_provider_by_id, Provider};
use crate::sync::{self, Sync::*, SyncData};
use crate::tools::{get_abs_path, improve_single_line_input, EmailAddress};
use crate::tools::{get_abs_path, improve_single_line_input};

/// The available configuration keys.
#[derive(
Expand Down Expand Up @@ -343,6 +343,10 @@ pub enum Config {
/// until `chat_id.accept()` is called.
#[strum(props(default = "0"))]
VerifiedOneOnOneChats,

/// Row ID of the key in the `keypairs` table
/// used for signatures, encryption to self and included in `Autocrypt` header.
KeyId,
}

impl Config {
Expand Down Expand Up @@ -627,8 +631,6 @@ impl Context {
///
/// This should only be used by test code and during configure.
pub(crate) async fn set_primary_self_addr(&self, primary_new: &str) -> Result<()> {
let old_addr = self.get_config(Config::ConfiguredAddr).await?;

// add old primary address (if exists) to secondary addresses
let mut secondary_addrs = self.get_all_self_addrs().await?;
// never store a primary address also as a secondary
Expand All @@ -642,17 +644,6 @@ impl Context {
self.set_config(Config::ConfiguredAddr, Some(primary_new))
.await?;

if let Some(old_addr) = old_addr {
let old_addr = EmailAddress::new(&old_addr)?;
let old_keypair = crate::key::load_keypair(self, &old_addr).await?;

if let Some(mut old_keypair) = old_keypair {
old_keypair.addr = EmailAddress::new(primary_new)?;
crate::key::store_self_keypair(self, &old_keypair, crate::key::KeyPairUse::Default)
.await?;
}
}

Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,7 @@ mod tests {
"socks5_port",
"socks5_user",
"socks5_password",
"key_id",
];
let t = TestContext::new().await;
let info = t.get_info().await.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/imex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ async fn export_self_keys(context: &Context, dir: &Path) -> Result<()> {
let keys = context
.sql
.query_map(
"SELECT id, public_key, private_key, is_default FROM keypairs;",
"SELECT id, public_key, private_key, id=(SELECT value FROM config WHERE keyname='key_id') FROM keypairs;",
(),
|row| {
let id = row.get(0)?;
Expand Down
78 changes: 37 additions & 41 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::constants::KeyGenType;
use crate::context::Context;
use crate::log::LogExt;
use crate::pgp::KeyPair;
use crate::tools::{time, EmailAddress};
use crate::tools::EmailAddress;

/// Convenience trait for working with keys.
///
Expand Down Expand Up @@ -82,10 +82,9 @@ pub(crate) async fn load_self_public_key(context: &Context) -> Result<SignedPubl
match context
.sql
.query_row_optional(
r#"SELECT public_key
FROM keypairs
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
AND is_default=1"#,
"SELECT public_key
FROM keypairs
WHERE id=(SELECT value FROM config WHERE keyname='key_id')",
(),
|row| {
let bytes: Vec<u8> = row.get(0)?;
Expand All @@ -106,10 +105,9 @@ pub(crate) async fn load_self_secret_key(context: &Context) -> Result<SignedSecr
match context
.sql
.query_row_optional(
r#"SELECT private_key
FROM keypairs
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
AND is_default=1"#,
"SELECT private_key
FROM keypairs
WHERE id=(SELECT value FROM config WHERE keyname='key_id')",
(),
|row| {
let bytes: Vec<u8> = row.get(0)?;
Expand All @@ -132,8 +130,7 @@ pub(crate) async fn load_self_secret_keyring(context: &Context) -> Result<Vec<Si
.query_map(
r#"SELECT private_key
FROM keypairs
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
ORDER BY is_default DESC"#,
ORDER BY id=(SELECT value FROM config WHERE keyname='key_id') DESC"#,
(),
|row| row.get::<_, Vec<u8>>(0),
|keys| keys.collect::<Result<Vec<_>, _>>().map_err(Into::into),
Expand Down Expand Up @@ -233,13 +230,10 @@ pub(crate) async fn load_keypair(
let res = context
.sql
.query_row_optional(
r#"
SELECT public_key, private_key
FROM keypairs
WHERE addr=?1
AND is_default=1;
"#,
(addr,),
"SELECT public_key, private_key
FROM keypairs
WHERE id=(SELECT value FROM config WHERE keyname='key_id')",
(),
|row| {
let pub_bytes: Vec<u8> = row.get(0)?;
let sec_bytes: Vec<u8> = row.get(1)?;
Expand Down Expand Up @@ -288,42 +282,44 @@ pub async fn store_self_keypair(
keypair: &KeyPair,
default: KeyPairUse,
) -> Result<()> {
context
let mut config_cache_lock = context.sql.config_cache.write().await;
let new_key_id = context
.sql
.transaction(|transaction| {
let public_key = DcKey::to_bytes(&keypair.public);
let secret_key = DcKey::to_bytes(&keypair.secret);
transaction
.execute(
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
(&public_key, &secret_key),
)
.context("failed to remove old use of key")?;
if default == KeyPairUse::Default {
transaction
.execute("UPDATE keypairs SET is_default=0;", ())
.context("failed to clear default")?;
}

let is_default = match default {
KeyPairUse::Default => i32::from(true),
KeyPairUse::ReadOnly => i32::from(false),
KeyPairUse::Default => true,
KeyPairUse::ReadOnly => false,
};

let addr = keypair.addr.to_string();
let t = time();

transaction
.execute(
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created)
VALUES (?,?,?,?,?);",
(addr, is_default, &public_key, &secret_key, t),
"INSERT OR REPLACE INTO keypairs (public_key, private_key)
VALUES (?,?)",
(&public_key, &secret_key),
)
.context("failed to insert keypair")?;

Ok(())
.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)
}
})
.await?;

if let Some(new_key_id) = new_key_id {
// Update config cache if transaction succeeded and changed current default key.
config_cache_lock.insert("key_id".to_string(), Some(new_key_id.to_string()));
}

Ok(())
}

Expand Down
30 changes: 30 additions & 0 deletions src/sql/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,36 @@ CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
.await?;
}

if dbversion < 107 {
sql.execute_migration(
"CREATE TABLE new_keypairs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
private_key UNIQUE NOT NULL,
public_key UNIQUE NOT NULL
);
INSERT OR IGNORE INTO new_keypairs SELECT id, private_key, public_key FROM keypairs;
INSERT OR IGNORE
INTO config (keyname, value)
VALUES
('key_id', (SELECT id FROM new_keypairs
WHERE private_key=
(SELECT private_key FROM keypairs
WHERE addr=(SELECT value FROM config WHERE keyname='configured_addr')
AND is_default=1)));
-- We do not drop the old `keypairs` table for now,
-- but move it to `old_keypairs`. We can remove it later
-- in next migrations. This may be needed for recovery
-- in case something is wrong with the migration.
ALTER TABLE keypairs RENAME TO old_keypairs;
ALTER TABLE new_keypairs RENAME TO keypairs;
",
107,
)
.await?;
}

let new_version = sql
.get_raw_config_int(VERSION_CFG)
.await?
Expand Down

0 comments on commit bbff5e5

Please sign in to comment.