From 0dfecd78d63b0de568ca8ef2d3a750156565dbff Mon Sep 17 00:00:00 2001 From: multi prise Date: Sat, 2 Aug 2025 11:33:34 +0200 Subject: [PATCH] Updated the store encryption to use a enum Secret instead of passphrase --- crates/matrix-sdk-sqlite/src/crypto_store.rs | 36 ++++++++++-------- .../src/event_cache_store.rs | 32 ++++++++++------ crates/matrix-sdk-sqlite/src/lib.rs | 34 ++++++++++++----- crates/matrix-sdk-sqlite/src/state_store.rs | 38 ++++++++++--------- crates/matrix-sdk-sqlite/src/utils.rs | 21 +++++++--- 5 files changed, 101 insertions(+), 60 deletions(-) diff --git a/crates/matrix-sdk-sqlite/src/crypto_store.rs b/crates/matrix-sdk-sqlite/src/crypto_store.rs index 88156dc06..1280cb34e 100644 --- a/crates/matrix-sdk-sqlite/src/crypto_store.rs +++ b/crates/matrix-sdk-sqlite/src/crypto_store.rs @@ -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, + passphrase: Option<&str>, + ) -> Result { + 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, key: Option<&[u8; 32]>, ) -> Result { @@ -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 { - 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, ) -> Result { 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 = 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") } diff --git a/crates/matrix-sdk-sqlite/src/event_cache_store.rs b/crates/matrix-sdk-sqlite/src/event_cache_store.rs index b83157b88..dbf4353b0 100644 --- a/crates/matrix-sdk-sqlite/src/event_cache_store.rs +++ b/crates/matrix-sdk-sqlite/src/event_cache_store.rs @@ -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, + passphrase: Option<&str>, + ) -> Result { + 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, key: Option<&[u8; 32]>, ) -> Result { @@ -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, ) -> Result { 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()) diff --git a/crates/matrix-sdk-sqlite/src/lib.rs b/crates/matrix-sdk-sqlite/src/lib.rs index 139c167bd..4230fefdd 100644 --- a/crates/matrix-sdk-sqlite/src/lib.rs +++ b/crates/matrix-sdk-sqlite/src/lib.rs @@ -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, /// 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()); diff --git a/crates/matrix-sdk-sqlite/src/state_store.rs b/crates/matrix-sdk-sqlite/src/state_store.rs index e896e9a82..5f8b91cd6 100644 --- a/crates/matrix-sdk-sqlite/src/state_store.rs +++ b/crates/matrix-sdk-sqlite/src/state_store.rs @@ -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, + passphrase: Option<&str>, + ) -> Result { + 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, key: Option<&[u8; 32]>, ) -> Result { @@ -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 { - 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, ) -> Result { 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 = 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?; diff --git a/crates/matrix-sdk-sqlite/src/utils.rs b/crates/matrix-sdk-sqlite/src/utils.rs index 22d7fc402..ab8c7c693 100644 --- a/crates/matrix-sdk-sqlite/src/utils.rs +++ b/crates/matrix-sdk-sqlite/src/utils.rs @@ -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 { 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)?;