From 286ae8a12bbc2e16a6c847c7cd41a40c5b837df1 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Tue, 7 Jul 2026 16:09:38 +0200 Subject: [PATCH] feat(store-encryption): Add the `EncryptableValue`. This patch introduces the `EncryptableValue` trait to represent usual operations on a value to be encoded by `encrypt_value_data`. --- crates/matrix-sdk-sqlite/src/utils.rs | 9 ++- crates/matrix-sdk-store-encryption/src/lib.rs | 56 ++++++++++++++++++- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/crates/matrix-sdk-sqlite/src/utils.rs b/crates/matrix-sdk-sqlite/src/utils.rs index 80933bab7..efb581e8c 100644 --- a/crates/matrix-sdk-sqlite/src/utils.rs +++ b/crates/matrix-sdk-sqlite/src/utils.rs @@ -23,7 +23,7 @@ use std::{ use async_trait::async_trait; use deadpool_sync::InteractError; use itertools::Itertools; -use matrix_sdk_store_encryption::StoreCipher; +use matrix_sdk_store_encryption::{EncryptableValue, StoreCipher}; use ruma::{OwnedEventId, OwnedRoomId, serde::Raw, time::SystemTime}; use rusqlite::{OptionalExtension, Params, Row, Statement, Transaction, limits::Limit}; use serde::{Serialize, de::DeserializeOwned}; @@ -641,12 +641,15 @@ pub(crate) trait EncryptableStore { } } - fn encode_value(&self, value: Vec) -> Result> { + fn encode_value(&self, value: V) -> Result> + where + V: EncryptableValue + Into>, + { if let Some(key) = self.get_cypher() { let encrypted = key.encrypt_value_data(value)?; Ok(rmp_serde::to_vec_named(&encrypted)?) } else { - Ok(value) + Ok(value.into()) } } diff --git a/crates/matrix-sdk-store-encryption/src/lib.rs b/crates/matrix-sdk-store-encryption/src/lib.rs index 2f1aaa79b..47fb76137 100644 --- a/crates/matrix-sdk-store-encryption/src/lib.rs +++ b/crates/matrix-sdk-store-encryption/src/lib.rs @@ -448,13 +448,16 @@ impl StoreCipher { /// assert_eq!(value, decrypted); /// # anyhow::Ok(()) }; /// ``` - pub fn encrypt_value_data(&self, mut data: Vec) -> Result { + pub fn encrypt_value_data(&self, mut data: D) -> Result + where + D: EncryptableValue, + { let nonce = Keys::get_nonce(); let cipher = XChaCha20Poly1305::new(self.inner.encryption_key()); - let ciphertext = cipher.encrypt(XNonce::from_slice(&nonce), data.as_ref())?; + let ciphertext = cipher.encrypt(XNonce::from_slice(&nonce), data.as_bytes())?; - data.zeroize(); + data.zeroiize(); Ok(EncryptedValue { version: VERSION, ciphertext, nonce }) } @@ -808,6 +811,53 @@ struct EncryptedStoreCipher { pub ciphertext_info: CipherTextInfo, } +/// A trait to get a slice of bytes and to zeroize a data, which are the +/// required operations for [`StoreCipher::encrypt_value_data`]. +/// +/// The goal of this trait was to call [`Zeroize`] efficiently on `Vec` and +/// `&[u8]`. We could call `vec.iter_mut().zeroize()` but the implementation of +/// `Zeroize` on `Vec` does a bit more than that as it clears the vector and +/// zeroizes the spare capacity as a best effort. +pub trait EncryptableValue { + /// Get the encodable value as bytes. + fn as_bytes(&self) -> &[u8]; + + /// Zeroize the encodable value. + /// + /// Called `zeroiize` to avoid clashes with [`Zeroize::zeroize`]. + fn zeroiize(&mut self); +} + +impl EncryptableValue for Vec { + fn as_bytes(&self) -> &[u8] { + AsRef::as_ref(self) + } + + fn zeroiize(&mut self) { + Zeroize::zeroize(self); + } +} + +impl EncryptableValue for String { + fn as_bytes(&self) -> &[u8] { + str::as_bytes(self) + } + + fn zeroiize(&mut self) { + Zeroize::zeroize(self); + } +} + +impl EncryptableValue for &mut [u8] { + fn as_bytes(&self) -> &[u8] { + self + } + + fn zeroiize(&mut self) { + self.iter_mut().zeroize(); + } +} + #[cfg(test)] mod tests { use serde_json::{Value, json};