Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
91438c5
Add the key logic to the SqliteStoreConfig struct
multisme Jul 31, 2025
8b072ec
Replace the passphrase logic with a key logic in the implementation o…
multisme Jul 31, 2025
a99f0c9
Remove all passphrase mention
multisme Jul 31, 2025
102b4cf
Updated changelog
multisme Jul 31, 2025
65a9bc0
Updated the store encryption to use a enum Secret instead of passphrase
multisme Aug 2, 2025
f517587
Format files
multisme Aug 2, 2025
fd32c5a
Correct some tests
multisme Aug 2, 2025
4b7800b
More reformarting of files
multisme Aug 2, 2025
22e29ac
Refactorize SqliteStoreConfig::key and SqliteStoreConfig::passphrase …
multisme Aug 2, 2025
5af4d49
Revert get_or_create_store_cypher to use
multisme Aug 2, 2025
59c40c4
Comment the use
multisme Aug 2, 2025
734cd94
Update changelog to represent changes
multisme Aug 2, 2025
c838f2a
remove typo
multisme Aug 6, 2025
de0e85f
correct comment on the crypto store file
multisme Aug 6, 2025
f2afd18
correct comment on the state store file
multisme Aug 6, 2025
a308973
Update some expect text to make sure they reflect the use of secret i…
multisme Aug 6, 2025
1def8d6
Uncomment the config directives and allows test to run faster by usin…
multisme Aug 6, 2025
88aaffc
Use of lifetime in order to not clone/copy the data
multisme Aug 6, 2025
4d7dd6c
Update matrix-sdk with new lifetimes
multisme Aug 7, 2025
587331d
Temporary comment insecure function
multisme Aug 7, 2025
1625c16
Refactorize tests config to correspond with the new api
multisme Aug 7, 2025
b157860
Revert "Update matrix-sdk with new lifetimes"
multisme Aug 7, 2025
063ab19
Revert "Use of lifetime in order to not clone/copy the data"
multisme Aug 7, 2025
98a6e36
implement zeroizing of secrets after use
multisme Aug 7, 2025
9718b79
Correct wrong borrow
multisme Aug 7, 2025
d0d68b4
Merge branch 'matrix-org:main' into refactor/improve_performance_sqli…
multisme Aug 11, 2025
69e8ee0
Remove conditional logic for running tests
multisme Aug 11, 2025
601893b
reformat
multisme Aug 11, 2025
f7a24ed
Update changelog
multisme Aug 21, 2025
7c8b3c5
Merge branch 'matrix-org:main' into refactor/improve_performance_sqli…
multisme Aug 21, 2025
ff5ccef
Remove some superfluous change
multisme Aug 21, 2025
d930a36
Reimplement previous tests for the store and on top of the one testin…
multisme Aug 21, 2025
d8dfda6
Replace missing line
multisme Aug 21, 2025
eca9e89
Merge branch 'matrix-org:main' into refactor/improve_performance_sqli…
multisme Aug 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions crates/matrix-sdk-sqlite/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ All notable changes to this project will be documented in this file.

## [Unreleased] - ReleaseDate

### Features
- Implement a new constructrot that allow to open SqliteCryptoStore with a Key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Implement a new constructrot that allow to open SqliteCryptoStore with a Key
- Implement a new constructor that allows to open the SqliteCryptoStore with a cryptographic key.

([#5472](https://github.com/matrix-org/matrix-rust-sdk/pull/5472))

### Refactor
- [breaking] Change the logic for opening a store so as to use a `Secret` enum in the function `open_with_pool` instead of a `passphrase`
([#5472](https://github.com/matrix-org/matrix-rust-sdk/pull/5472))

## [0.13.0] - 2025-07-10

### Security Fixes
Expand Down
1 change: 1 addition & 0 deletions crates/matrix-sdk-sqlite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ thiserror.workspace = true
tokio = { workspace = true, features = ["fs"] }
tracing.workspace = true
vodozemac.workspace = true
zeroize.workspace = true

[dev-dependencies]
assert_matches.workspace = true
Expand Down
27 changes: 18 additions & 9 deletions crates/matrix-sdk-sqlite/src/crypto_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use crate::{
repeat_vars, EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
SqliteKeyValueStoreConnExt,
},
OpenStoreError, SqliteStoreConfig,
OpenStoreError, Secret, SqliteStoreConfig,
};

/// The database name.
Expand Down Expand Up @@ -92,9 +92,18 @@ impl SqliteCryptoStore {
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_with_key(
path: impl AsRef<Path>,
key: Option<&[u8; 32]>,
) -> Result<Self, OpenStoreError> {
Self::open_with_config(SqliteStoreConfig::new(path).key(key)).await
}

/// 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, passphrase, pool_config, runtime_config } = config;
let SqliteStoreConfig { path, pool_config, runtime_config, secret } = config;

fs::create_dir_all(&path).await.map_err(OpenStoreError::CreateDir)?;

Expand All @@ -103,26 +112,26 @@ impl SqliteCryptoStore {

let pool = config.create_pool(Runtime::Tokio1)?;

let this = Self::open_with_pool(pool, passphrase.as_deref()).await?;
let this = Self::open_with_pool(pool, secret).await?;
this.pool.get().await?.apply_runtime_config(runtime_config).await?;

Ok(this)
}

/// Create an SQLite-based crypto store using the given SQLite database
/// pool. The given passphrase will be used to encrypt private data.
/// pool. The given secret will be used to encrypt private data.
async fn open_with_pool(
pool: SqlitePool,
passphrase: Option<&str>,
secret: Option<Secret>,
) -> Result<Self, OpenStoreError> {
let conn = pool.get().await?;

let version = conn.db_version().await?;
debug!("Opened sqlite store with version {}", version);
run_migrations(&conn, version).await?;

let store_cipher = match passphrase {
Some(p) => Some(Arc::new(conn.get_or_create_store_cipher(p).await?)),
let store_cipher = match secret {
Some(s) => Some(Arc::new(conn.get_or_create_store_cipher(s).await?)),
None => None,
};

Expand Down Expand Up @@ -1871,7 +1880,7 @@ mod tests {

SqliteCryptoStore::open(tmpdir_path.to_str().unwrap(), passphrase)
.await
.expect("Can't create a passphrase protected store")
.expect("Can't create a secret protected store")
}

cryptostore_integration_tests!();
Expand Down Expand Up @@ -1903,7 +1912,7 @@ mod encrypted_tests {

SqliteCryptoStore::open(tmpdir_path.to_str().unwrap(), Some(pass))
.await
.expect("Can't create a passphrase protected store")
.expect("Can't create a secret protected store")
}

cryptostore_integration_tests!();
Expand Down
23 changes: 16 additions & 7 deletions crates/matrix-sdk-sqlite/src/event_cache_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ use crate::{
repeat_vars, time_to_timestamp, EncryptableStore, Key, SqliteAsyncConnExt,
SqliteKeyValueStoreAsyncConnExt, SqliteKeyValueStoreConnExt, SqliteTransactionExt,
},
OpenStoreError, SqliteStoreConfig,
OpenStoreError, Secret, SqliteStoreConfig,
};

mod keys {
Expand Down Expand Up @@ -126,14 +126,23 @@ impl SqliteEventCacheStore {
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> {
Self::open_with_config(SqliteStoreConfig::new(path).key(key)).await
}

/// Open the SQLite-based event cache store with the config open config.
#[instrument(skip(config), fields(path = ?config.path))]
pub async fn open_with_config(config: SqliteStoreConfig) -> Result<Self, OpenStoreError> {
debug!(?config);

let _timer = timer!("open_with_config");

let SqliteStoreConfig { path, passphrase, pool_config, runtime_config } = config;
let SqliteStoreConfig { path, pool_config, runtime_config, secret } = config;

fs::create_dir_all(&path).await.map_err(OpenStoreError::CreateDir)?;

Expand All @@ -142,25 +151,25 @@ impl SqliteEventCacheStore {

let pool = config.create_pool(Runtime::Tokio1)?;

let this = Self::open_with_pool(pool, passphrase.as_deref()).await?;
let this = Self::open_with_pool(pool, secret).await?;
this.write().await?.apply_runtime_config(runtime_config).await?;

Ok(this)
}

/// Open an SQLite-based event cache store using the given SQLite database
/// pool. The given passphrase will be used to encrypt private data.
/// pool. The given secret will be used to encrypt private data.
async fn open_with_pool(
pool: SqlitePool,
passphrase: Option<&str>,
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 passphrase {
Some(p) => Some(Arc::new(conn.get_or_create_store_cipher(p).await?)),
let store_cipher = match secret {
Some(s) => Some(Arc::new(conn.get_or_create_store_cipher(s).await?)),
None => None,
};

Expand Down
58 changes: 51 additions & 7 deletions crates/matrix-sdk-sqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use std::{
};

use deadpool_sqlite::PoolConfig;
use zeroize::Zeroize;

#[cfg(feature = "crypto-store")]
pub use self::crypto_store::SqliteCryptoStore;
Expand All @@ -43,13 +44,21 @@ 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, Zeroize)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should derive ZeroizeOnDrop here so we don't need the manual zeroize() calls.

pub enum Secret {
#[zeroize]
Key([u8; 32]),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be inside a Box or inside a Zeroizing struct so we avoid unintended copies due to moves being a memcpy call in Rust land. More info here: https://benma.github.io/2020/10/16/rust-zeroize-move.html.

In short, every time you pass the Secret to a function a memcpy call will copy the contents of the array.

If a Box is used, the pointer is instead copied.

#[zeroize]
PassPhrase(String),
Comment on lines +49 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enum variants need to be documented.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, this will produce unintended copies, Zeroizing<String> would make sense.

}

/// A configuration structure used for opening a store.
#[derive(Clone)]
pub struct SqliteStoreConfig {
/// Path to the database, without the file name.
path: PathBuf,
/// Passphrase to open the store, if any.
passphrase: Option<String>,
/// 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.
Expand Down Expand Up @@ -82,9 +91,9 @@ impl SqliteStoreConfig {
{
Self {
path: path.as_ref().to_path_buf(),
passphrase: None,
pool_config: PoolConfig::new(max(POOL_MINIMUM_SIZE, num_cpus::get_physical() * 4)),
runtime_config: RuntimeConfig::default(),
secret: None,
}
}

Expand Down Expand Up @@ -121,7 +130,13 @@ impl SqliteStoreConfig {

/// Define the passphrase if the store is encoded.
pub fn passphrase(mut self, passphrase: Option<&str>) -> Self {
self.passphrase = passphrase.map(|passphrase| passphrase.to_owned());
self.secret = passphrase.map(|passphrase| Secret::PassPhrase(passphrase.to_owned()));
self
}

/// Define the key if the store is encoded.
pub fn key(mut self, key: Option<&[u8; 32]>) -> Self {
self.secret = key.map(|key| Secret::Key(*key));
self
}

Expand Down Expand Up @@ -225,7 +240,7 @@ mod tests {
path::{Path, PathBuf},
};

use super::{SqliteStoreConfig, POOL_MINIMUM_SIZE};
use super::{Secret, SqliteStoreConfig, POOL_MINIMUM_SIZE};

#[test]
fn test_new() {
Expand All @@ -248,7 +263,7 @@ mod tests {
}

#[test]
fn test_store_config() {
fn test_store_config_when_passphrase() {
let store_config = SqliteStoreConfig::new(Path::new("foo"))
.passphrase(Some("bar"))
.pool_max_size(42)
Expand All @@ -257,7 +272,36 @@ mod tests {
.journal_size_limit(44);

assert_eq!(store_config.path, PathBuf::from("foo"));
assert_eq!(store_config.passphrase, Some("bar".to_owned()));
assert_eq!(store_config.secret, Some(Secret::PassPhrase("bar".to_owned())));
assert_eq!(store_config.pool_config.max_size, 42);
assert!(store_config.runtime_config.optimize.not());
assert_eq!(store_config.runtime_config.cache_size, 43);
assert_eq!(store_config.runtime_config.journal_size_limit, 44);
}

#[test]
fn test_store_config_when_key() {
let store_config = SqliteStoreConfig::new(Path::new("foo"))
.key(Some(&[
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,
]))
.pool_max_size(42)
.optimize(false)
.cache_size(43)
.journal_size_limit(44);

assert_eq!(store_config.path, PathBuf::from("foo"));
assert_eq!(
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());
assert_eq!(store_config.runtime_config.cache_size, 43);
Expand Down
31 changes: 21 additions & 10 deletions crates/matrix-sdk-sqlite/src/state_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ use crate::{
repeat_vars, EncryptableStore, Key, SqliteAsyncConnExt, SqliteKeyValueStoreAsyncConnExt,
SqliteKeyValueStoreConnExt,
},
OpenStoreError, SqliteStoreConfig,
OpenStoreError, Secret, SqliteStoreConfig,
};

mod keys {
Expand Down Expand Up @@ -91,17 +91,26 @@ impl fmt::Debug for SqliteStateStore {

impl SqliteStateStore {
/// Open the SQLite-based state store at the given path using the given
/// passphrase 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 state 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> {
Self::open_with_config(SqliteStoreConfig::new(path).key(key)).await
}

/// 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, passphrase, pool_config, runtime_config } = config;
let SqliteStoreConfig { path, pool_config, runtime_config, secret } = config;

fs::create_dir_all(&path).await.map_err(OpenStoreError::CreateDir)?;

Expand All @@ -110,17 +119,17 @@ impl SqliteStateStore {

let pool = config.create_pool(Runtime::Tokio1)?;

let this = Self::open_with_pool(pool, passphrase.as_deref()).await?;
let this = Self::open_with_pool(pool, secret).await?;
this.pool.get().await?.apply_runtime_config(runtime_config).await?;

Ok(this)
}

/// Create an SQLite-based state store using the given SQLite database pool.
/// The given passphrase will be used to encrypt private data.
/// The given secret will be used to encrypt private data.
pub async fn open_with_pool(
pool: SqlitePool,
passphrase: Option<&str>,
secret: Option<Secret>,
) -> Result<Self, OpenStoreError> {
let conn = pool.get().await?;

Expand All @@ -131,8 +140,8 @@ impl SqliteStateStore {
version = 1;
}

let store_cipher = match passphrase {
Some(p) => Some(Arc::new(conn.get_or_create_store_cipher(p).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 };
Expand Down Expand Up @@ -2317,7 +2326,7 @@ 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());
Expand All @@ -2340,7 +2349,9 @@ 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?;

Expand Down
Loading
Loading