Updated the store encryption to use a enum Secret instead of passphrase

This commit is contained in:
multi prise
2025-08-02 11:33:34 +02:00
committed by Damir Jelić
parent 4754ac2cbf
commit 0dfecd78d6
5 changed files with 101 additions and 60 deletions

View File

@@ -52,7 +52,7 @@ use crate::{
repeat_vars, EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
SqliteKeyValueStoreConnExt,
},
OpenStoreError, SqliteStoreConfig,
OpenStoreError, Secret, SqliteStoreConfig,
};
/// The database name.
@@ -83,9 +83,18 @@ impl EncryptableStore for SqliteCryptoStore {
}
impl SqliteCryptoStore {
/// Open the SQLite-based event cache store at the given path using the
/// given passphrase to encrypt private data
pub async fn open(
path: impl AsRef<Path>,
passphrase: Option<&str>,
) -> Result<Self, OpenStoreError> {
Self::open_with_config(SqliteStoreConfig::new(path).passphrase(passphrase)).await
}
/// Open the SQLite-based crypto store at the given path using the given
/// key to encrypt private data.
pub async fn open(
pub async fn open_with_key(
path: impl AsRef<Path>,
key: Option<&[u8; 32]>,
) -> Result<Self, OpenStoreError> {
@@ -94,7 +103,7 @@ impl SqliteCryptoStore {
/// Open the SQLite-based crypto store with the config open config.
pub async fn open_with_config(config: SqliteStoreConfig) -> Result<Self, OpenStoreError> {
let SqliteStoreConfig { path, key, pool_config, runtime_config } = config;
let SqliteStoreConfig { path, pool_config, runtime_config, secret } = config;
fs::create_dir_all(&path).await.map_err(OpenStoreError::CreateDir)?;
@@ -103,7 +112,7 @@ impl SqliteCryptoStore {
let pool = config.create_pool(Runtime::Tokio1)?;
let this = Self::open_with_pool(pool, key.as_deref()).await?;
let this = Self::open_with_pool(pool, secret).await?;
this.pool.get().await?.apply_runtime_config(runtime_config).await?;
Ok(this)
@@ -113,7 +122,7 @@ impl SqliteCryptoStore {
/// pool. The given key will be used to encrypt private data.
async fn open_with_pool(
pool: SqlitePool,
key: Option<&[u8; 32]>,
secret: Option<Secret>,
) -> Result<Self, OpenStoreError> {
let conn = pool.get().await?;
@@ -121,8 +130,8 @@ impl SqliteCryptoStore {
debug!("Opened sqlite store with version {}", version);
run_migrations(&conn, version).await?;
let store_cipher = match key {
Some(k) => Some(Arc::new(conn.get_or_create_store_cipher(k).await?)),
let store_cipher = match secret {
Some(s) => Some(Arc::new(conn.get_or_create_store_cipher(s).await?)),
None => None,
};
@@ -1891,14 +1900,14 @@ mod tests {
assert!(backup_keys.decryption_key.is_some());
}
async fn get_store(name: &str, key: Option<&[u8; 32]>, clear_data: bool) -> SqliteCryptoStore {
async fn get_store(name: &str, passphrase: Option<&str>, clear_data: bool) -> SqliteCryptoStore {
let tmpdir_path = TMP_DIR.path().join(name);
if clear_data {
let _ = fs::remove_dir_all(&tmpdir_path).await;
}
SqliteCryptoStore::open(tmpdir_path.to_str().unwrap(), key)
SqliteCryptoStore::open(tmpdir_path.to_str().unwrap(), passphrase)
.await
.expect("Can't create a key protected store")
}
@@ -1918,18 +1927,15 @@ mod encrypted_tests {
static TMP_DIR: Lazy<TempDir> = Lazy::new(|| tempdir().unwrap());
async fn get_store(name: &str, key: Option<&[u8; 32]>, clear_data: bool) -> SqliteCryptoStore {
async fn get_store(name: &str, passphrase: Option<&str>, clear_data: bool) -> SqliteCryptoStore {
let tmpdir_path = TMP_DIR.path().join(name);
let pass = key.unwrap_or(&[
74, 156, 222, 8, 61, 113, 145, 197, 29, 84, 179, 240, 6, 38, 126, 253, 92, 203, 173,
47, 134, 13, 251, 100, 196, 42, 109, 159, 221, 73, 10, 187,
]);
let pass = passphrase.unwrap_or(&"default_test_password");
if clear_data {
let _ = fs::remove_dir_all(&tmpdir_path).await;
}
SqliteCryptoStore::open(tmpdir_path.to_str().unwrap(), Some(key))
SqliteCryptoStore::open(tmpdir_path.to_str().unwrap(), Some(passphrase))
.await
.expect("Can't create a key protected store")
}

View File

@@ -32,7 +32,9 @@ use matrix_sdk_base::{
};
use matrix_sdk_store_encryption::StoreCipher;
use ruma::{
events::relation::RelationType, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId,
events::{relation::RelationType, secret_storage::key::PassPhrase},
time::SystemTime,
EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, RoomId,
};
use rusqlite::{params_from_iter, OptionalExtension, ToSql, Transaction, TransactionBehavior};
use tokio::{
@@ -47,7 +49,7 @@ use crate::{
repeat_vars, EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
SqliteKeyValueStoreConnExt, SqliteTransactionExt,
},
OpenStoreError, SqliteStoreConfig,
OpenStoreError, Secret, SqliteStoreConfig,
};
mod keys {
@@ -102,8 +104,17 @@ impl EncryptableStore for SqliteEventCacheStore {
impl SqliteEventCacheStore {
/// Open the SQLite-based event cache store at the given path using the
/// given key to encrypt private data.
/// given passphrase to encrypt private data.
pub async fn open(
path: impl AsRef<Path>,
passphrase: Option<&str>,
) -> Result<Self, OpenStoreError> {
Self::open_with_config(SqliteStoreConfig::new(path).passphrase(passphrase)).await
}
/// Open the SQLite-based event cache store at the given path using the
/// given key to encrypt private data.
pub async fn open_with_key(
path: impl AsRef<Path>,
key: Option<&[u8; 32]>,
) -> Result<Self, OpenStoreError> {
@@ -117,7 +128,7 @@ impl SqliteEventCacheStore {
let _timer = timer!("open_with_config");
let SqliteStoreConfig { path, key, pool_config, runtime_config } = config;
let SqliteStoreConfig { path, pool_config, runtime_config, secret } = config;
fs::create_dir_all(&path).await.map_err(OpenStoreError::CreateDir)?;
@@ -126,7 +137,7 @@ impl SqliteEventCacheStore {
let pool = config.create_pool(Runtime::Tokio1)?;
let this = Self::open_with_pool(pool, key.as_ref()).await?;
let this = Self::open_with_pool(pool, secret).await?;
this.write().await?.apply_runtime_config(runtime_config).await?;
Ok(this)
@@ -136,15 +147,15 @@ impl SqliteEventCacheStore {
/// pool. The given key will be used to encrypt private data.
async fn open_with_pool(
pool: SqlitePool,
key: Option<&[u8; 32]>,
secret: Option<Secret>,
) -> Result<Self, OpenStoreError> {
let conn = pool.get().await?;
let version = conn.db_version().await?;
run_migrations(&conn, version).await?;
let store_cipher = match key {
Some(k) => Some(Arc::new(conn.get_or_create_store_cipher(k).await?)),
let store_cipher = match secret {
Some(s) => Some(Arc::new(conn.get_or_create_store_cipher(s).await?)),
None => None,
};
@@ -2443,10 +2454,7 @@ mod encrypted_tests {
Ok(SqliteEventCacheStore::open(
tmpdir_path.to_str().unwrap(),
Some(&[
42, 199, 108, 12, 87, 250, 163, 31, 221, 60, 5, 144, 89, 237, 118, 201, 33, 176,
248, 73, 154, 8, 130, 190, 57, 110, 226, 3, 99, 45, 164, 209,
]),
Some("default_test_password"),
)
.await
.unwrap())

View File

@@ -1,4 +1,3 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -47,13 +46,19 @@ pub use self::state_store::{SqliteStateStore, DATABASE_NAME as STATE_STORE_DATAB
#[cfg(test)]
matrix_sdk_test_utils::init_tracing_for_tests!();
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Secret {
Key([u8; 32]),
PassPhrase(String),
}
/// A configuration structure used for opening a store.
#[derive(Clone)]
pub struct SqliteStoreConfig {
/// Path to the database, without the file name.
path: PathBuf,
/// Key to open the store, if any
key: Option<[u8; 32]>,
/// Secret to open the store, if any
secret: Option<Secret>,
/// The pool configuration for [`deadpool_sqlite`].
pool_config: PoolConfig,
/// The runtime configuration to apply when opening an SQLite connection.
@@ -86,9 +91,9 @@ impl SqliteStoreConfig {
{
Self {
path: path.as_ref().to_path_buf(),
key: None,
pool_config: PoolConfig::new(max(POOL_MINIMUM_SIZE, num_cpus::get_physical() * 4)),
runtime_config: RuntimeConfig::default(),
secret: None,
}
}
@@ -123,9 +128,20 @@ impl SqliteStoreConfig {
self
}
/// Define the passphrase if the store is encoded.
pub fn passphrase(mut self, passphrase: Option<&str>) -> Self {
self.secret = if let Some(passphrase) = passphrase {
Some(Secret::PassPhrase(passphrase.to_owned()))
} else {
None
};
self
}
/// Define the key if the store is encoded.
pub fn key(mut self, key: Option<&[u8; 32]>) -> Self {
self.key = key.map(|key| key.to_owned());
self.secret =
if let Some(key) = key { Some(Secret::Key(key.map(|key| key))) } else { None };
self
}
@@ -229,7 +245,7 @@ mod tests {
path::{Path, PathBuf},
};
use super::{SqliteStoreConfig, POOL_MINIMUM_SIZE};
use super::{Secret, SqliteStoreConfig, POOL_MINIMUM_SIZE};
#[test]
fn test_new() {
@@ -265,14 +281,14 @@ mod tests {
assert_eq!(store_config.path, PathBuf::from("foo"));
assert_eq!(
store_config.key,
Some(
store_config.secret,
Some(Secret::Key(
[
143, 27, 202, 78, 96, 55, 13, 149, 247, 8, 33, 120, 204, 92, 171, 66, 19, 238,
61, 107, 132, 211, 40, 244, 71, 190, 99, 14, 173, 225, 6, 156,
]
.to_owned()
)
))
);
assert_eq!(store_config.pool_config.max_size, 42);
assert!(store_config.runtime_config.optimize.not());

View File

@@ -48,7 +48,7 @@ use crate::{
repeat_vars, EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
SqliteKeyValueStoreConnExt,
},
OpenStoreError, SqliteStoreConfig,
OpenStoreError, Secret, SqliteStoreConfig,
};
mod keys {
@@ -92,9 +92,18 @@ impl fmt::Debug for SqliteStateStore {
}
impl SqliteStateStore {
/// Open the SQLite-based event cache store at the given path using the
/// given passphrase to encrypt private data
pub async fn open(
path: impl AsRef<Path>,
passphrase: Option<&str>,
) -> Result<Self, OpenStoreError> {
Self::open_with_config(SqliteStoreConfig::new(path).passphrase(passphrase)).await
}
/// Open the SQLite-based state store at the given path using the given
/// key to encrypt private data.
pub async fn open(
pub async fn open_with_key(
path: impl AsRef<Path>,
key: Option<&[u8; 32]>,
) -> Result<Self, OpenStoreError> {
@@ -103,7 +112,7 @@ impl SqliteStateStore {
/// Open the SQLite-based state store with the config open config.
pub async fn open_with_config(config: SqliteStoreConfig) -> Result<Self, OpenStoreError> {
let SqliteStoreConfig { path, key, pool_config, runtime_config } = config;
let SqliteStoreConfig { path, pool_config, runtime_config, secret } = config;
fs::create_dir_all(&path).await.map_err(OpenStoreError::CreateDir)?;
@@ -112,7 +121,7 @@ impl SqliteStateStore {
let pool = config.create_pool(Runtime::Tokio1)?;
let this = Self::open_with_pool(pool, key.as_ref()).await?;
let this = Self::open_with_pool(pool, secret).await?;
this.pool.get().await?.apply_runtime_config(runtime_config).await?;
Ok(this)
@@ -122,7 +131,7 @@ impl SqliteStateStore {
/// The given key will be used to encrypt private data.
pub async fn open_with_pool(
pool: SqlitePool,
key: Option<&[u8; 32]>,
secret: Option<Secret>,
) -> Result<Self, OpenStoreError> {
let conn = pool.get().await?;
@@ -133,8 +142,8 @@ impl SqliteStateStore {
version = 1;
}
let store_cipher = match key {
Some(k) => Some(Arc::new(conn.get_or_create_store_cipher(k).await?)),
let store_cipher = match secret {
Some(s) => Some(Arc::new(conn.get_or_create_store_cipher(s).await?)),
None => None,
};
let this = Self { store_cipher, pool };
@@ -2273,11 +2282,7 @@ mod encrypted_tests {
tracing::info!("using store @ {}", tmpdir_path.to_str().unwrap());
Ok(SqliteStateStore::open(
tmpdir_path.to_str().unwrap(),
Some(&[
201, 17, 66, 135, 59, 240, 28, 102, 88, 219, 3, 147, 125, 34, 76, 250, 190, 12,
224, 97, 49, 164, 143, 254, 61, 85, 202, 16, 111, 0, 178, 73,
]),
tmpdir_path.to_str().unwrap(), Some("default_test_password")
)
.await
.unwrap())
@@ -2372,15 +2377,12 @@ mod migration_tests {
use crate::{
error::{Error, Result},
utils::{EncryptableStore as _, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt},
OpenStoreError,
OpenStoreError, Secret,
};
static TMP_DIR: Lazy<TempDir> = Lazy::new(|| tempdir().unwrap());
static NUM: AtomicU32 = AtomicU32::new(0);
const SECRET: &[u8; 32] = &[
119, 34, 208, 91, 14, 160, 239, 67, 198, 122, 9, 53, 211, 80, 103, 251, 61, 199, 26, 142,
193, 240, 77, 36, 5, 187, 164, 112, 218, 38, 150, 249,
];
const SECRET: &str = "secret";
fn new_path() -> PathBuf {
let name = NUM.fetch_add(1, SeqCst).to_string();
@@ -2398,7 +2400,7 @@ mod migration_tests {
init(&conn).await?;
let store_cipher = Some(Arc::new(conn.get_or_create_store_cipher(SECRET).await.unwrap()));
let store_cipher = Some(Arc::new(conn.get_or_create_store_cipher(Secret::PassPhrase(SECRET.to_owned())).await.unwrap()));
let this = SqliteStateStore { store_cipher, pool };
this.run_migrations(&conn, 1, Some(version)).await?;

View File

@@ -24,14 +24,17 @@ use async_trait::async_trait;
use deadpool_sqlite::Object as SqliteAsyncConn;
use itertools::Itertools;
use matrix_sdk_store_encryption::StoreCipher;
use ruma::{serde::Raw, time::SystemTime, OwnedEventId, OwnedRoomId};
use ruma::{
events::secret_storage::key::PassPhrase, serde::Raw, time::SystemTime, OwnedEventId,
OwnedRoomId,
};
use rusqlite::{limits::Limit, OptionalExtension, Params, Row, Statement, Transaction};
use serde::{de::DeserializeOwned, Serialize};
use tracing::{error, warn};
use crate::{
error::{Error, Result},
OpenStoreError, RuntimeConfig,
OpenStoreError, RuntimeConfig, Secret,
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -393,7 +396,7 @@ impl SqliteKeyValueStoreConnExt for rusqlite::Connection {
///
/// ```sql
/// CREATE TABLE "kv" (
/// "key" TEXT PRIMARY KEY NOT NULL,
/// "key" TEXT PRIMARY KEY NOT NULL,d
/// "value" BLOB NOT NULL
/// );
/// ```
@@ -457,16 +460,22 @@ pub(crate) trait SqliteKeyValueStoreAsyncConnExt: SqliteAsyncConnExt {
/// Get the [`StoreCipher`] of the database or create it.
async fn get_or_create_store_cipher(
&self,
key: &[u8; 32],
secret: Secret,
) -> Result<StoreCipher, OpenStoreError> {
let encrypted_cipher = self.get_kv("cipher").await.map_err(OpenStoreError::LoadCipher)?;
let cipher = if let Some(encrypted) = encrypted_cipher {
StoreCipher::import_with_key(key, &encrypted)?
match secret {
Secret::PassPhrase(passphrase) => StoreCipher::import(&passphrase, &encrypted)?,
Secret::Key(key) => StoreCipher::import_with_key(&key, &encrypted)?,
}
} else {
let cipher = StoreCipher::new()?;
//#[cfg(not(test))]
let export = cipher.export_with_key(key);
let export = match secret {
Secret::PassPhrase(passphrase) => cipher.export(&passphrase),
Secret::Key(key) => cipher.export_with_key(&key),
};
//#[cfg(test)]
//let export = cipher._insecure_export_fast_for_testing(passphrase);
self.set_kv("cipher", export?).await.map_err(OpenStoreError::SaveCipher)?;