base: Initial support for an encrypted sled store.

master
Damir Jelić 2021-01-20 15:57:23 +01:00
parent 06a973a1b8
commit 4a06c9e82d
4 changed files with 96 additions and 32 deletions

View File

@ -181,7 +181,7 @@ pub struct BaseClient {
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
cryptostore: Arc<Mutex<Option<Box<dyn CryptoStore>>>>, cryptostore: Arc<Mutex<Option<Box<dyn CryptoStore>>>>,
store_path: Arc<Option<PathBuf>>, store_path: Arc<Option<PathBuf>>,
store_passphrase: Arc<Zeroizing<String>>, store_passphrase: Arc<Option<Zeroizing<String>>>,
/// Any implementor of EventEmitter will act as the callbacks for various /// Any implementor of EventEmitter will act as the callbacks for various
/// events. /// events.
event_emitter: Arc<RwLock<Option<Emitter>>>, event_emitter: Arc<RwLock<Option<Emitter>>>,
@ -282,8 +282,13 @@ impl BaseClient {
/// previous login call. /// previous login call.
pub fn new_with_config(config: BaseClientConfig) -> Result<Self> { pub fn new_with_config(config: BaseClientConfig) -> Result<Self> {
let store = if let Some(path) = &config.store_path { let store = if let Some(path) = &config.store_path {
if let Some(passphrase) = &config.passphrase {
info!("Opening an encrypted store in path {}", path.display());
SledStore::open_with_passphrase(path, passphrase)?
} else {
info!("Opening store in path {}", path.display()); info!("Opening store in path {}", path.display());
SledStore::open_with_path(path)? SledStore::open_with_path(path)?
}
} else { } else {
SledStore::open()? SledStore::open()?
}; };
@ -301,10 +306,7 @@ impl BaseClient {
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
cryptostore: Mutex::new(config.crypto_store).into(), cryptostore: Mutex::new(config.crypto_store).into(),
store_path: config.store_path.into(), store_path: config.store_path.into(),
store_passphrase: config store_passphrase: config.passphrase.into(),
.passphrase
.unwrap_or_else(|| Zeroizing::new("DEFAULT_PASSPHRASE".to_owned()))
.into(),
event_emitter: RwLock::new(None).into(), event_emitter: RwLock::new(None).into(),
}) })
} }
@ -377,7 +379,7 @@ impl BaseClient {
&session.user_id, &session.user_id,
&session.device_id, &session.device_id,
path, path,
&self.store_passphrase, self.store_passphrase.as_deref().map(|p| p.as_str()),
) )
.await .await
.map_err(OlmError::from)?, .map_err(OlmError::from)?,

View File

@ -14,7 +14,7 @@
mod store_key; mod store_key;
use std::{convert::TryFrom, path::Path, time::SystemTime}; use std::{convert::TryFrom, path::Path, sync::Arc, time::SystemTime};
use futures::stream::{self, Stream}; use futures::stream::{self, Stream};
use matrix_sdk_common::{ use matrix_sdk_common::{
@ -25,6 +25,7 @@ use matrix_sdk_common::{
}, },
identifiers::{RoomId, UserId}, identifiers::{RoomId, UserId},
}; };
use serde::{Deserialize, Serialize};
use sled::{ use sled::{
transaction::{ConflictableTransactionError, TransactionError}, transaction::{ConflictableTransactionError, TransactionError},
@ -34,11 +35,20 @@ use tracing::info;
use crate::deserialized_responses::MemberEvent; use crate::deserialized_responses::MemberEvent;
use self::store_key::{EncryptedEvent, StoreKey};
use super::{Result, RoomInfo, StateChanges, StoreError}; use super::{Result, RoomInfo, StateChanges, StoreError};
#[derive(Debug, Serialize, Deserialize)]
pub enum DatabaseType {
Unencrypted,
Encrypted(store_key::EncryptedStoreKey),
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SledStore { pub struct SledStore {
inner: Db, inner: Db,
store_key: Arc<Option<StoreKey>>,
session: Tree, session: Tree,
account_data: Tree, account_data: Tree,
members: Tree, members: Tree,
@ -55,7 +65,7 @@ pub struct SledStore {
} }
impl SledStore { impl SledStore {
fn open_helper(db: Db) -> Result<Self> { fn open_helper(db: Db, store_key: Option<StoreKey>) -> Result<Self> {
let session = db.open_tree("session")?; let session = db.open_tree("session")?;
let account_data = db.open_tree("account_data")?; let account_data = db.open_tree("account_data")?;
@ -75,6 +85,7 @@ impl SledStore {
Ok(Self { Ok(Self {
inner: db, inner: db,
store_key: store_key.into(),
session, session,
account_data, account_data,
members, members,
@ -94,14 +105,64 @@ impl SledStore {
pub fn open() -> Result<Self> { pub fn open() -> Result<Self> {
let db = Config::new().temporary(true).open()?; let db = Config::new().temporary(true).open()?;
SledStore::open_helper(db) SledStore::open_helper(db, None)
}
pub fn open_with_passphrase(path: impl AsRef<Path>, passphrase: &str) -> Result<Self> {
let path = path.as_ref().join("matrix-sdk-state");
let db = Config::new().temporary(false).path(path).open()?;
let store_key: Option<DatabaseType> = db
.get("store_key")?
.map(|k| serde_json::from_slice(&k).map_err(StoreError::Json))
.transpose()?;
let store_key = if let Some(key) = store_key {
if let DatabaseType::Encrypted(k) = key {
let key = StoreKey::import(passphrase, k).unwrap();
key
} else {
panic!("Trying to open an unencrypted store with a passphrase");
}
} else {
let key = StoreKey::new();
let encrypted_key = DatabaseType::Encrypted(key.export(passphrase));
db.insert("store_key", serde_json::to_vec(&encrypted_key)?)?;
key
};
SledStore::open_helper(db, Some(store_key))
} }
pub fn open_with_path(path: impl AsRef<Path>) -> Result<Self> { pub fn open_with_path(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref().join("matrix-sdk-state"); let path = path.as_ref().join("matrix-sdk-state");
let db = Config::new().temporary(false).path(path).open()?; let db = Config::new().temporary(false).path(path).open()?;
SledStore::open_helper(db) SledStore::open_helper(db, None)
}
fn serialize_event(
&self,
event: &impl Serialize,
) -> std::result::Result<Vec<u8>, serde_json::Error> {
if let Some(key) = &*self.store_key {
let encrypted = key.encrypt(event).unwrap();
serde_json::to_vec(&encrypted)
} else {
serde_json::to_vec(event)
}
}
fn deserialize_event<T: for<'b> Deserialize<'b>>(
&self,
event: &[u8],
) -> std::result::Result<T, serde_json::Error> {
if let Some(key) = &*self.store_key {
let encrypted: EncryptedEvent = serde_json::from_slice(&event)?;
Ok(key.decrypt(encrypted).unwrap())
} else {
serde_json::from_slice(event)
}
} }
pub async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> { pub async fn save_filter(&self, filter_name: &str, filter_id: &str) -> Result<()> {
@ -184,7 +245,7 @@ impl SledStore {
members.insert( members.insert(
format!("{}{}", room.as_str(), &event.state_key).as_str(), format!("{}{}", room.as_str(), &event.state_key).as_str(),
serde_json::to_vec(&event) self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -194,7 +255,7 @@ impl SledStore {
for (user_id, profile) in users { for (user_id, profile) in users {
profiles.insert( profiles.insert(
format!("{}{}", room.as_str(), user_id.as_str()).as_str(), format!("{}{}", room.as_str(), user_id.as_str()).as_str(),
serde_json::to_vec(&profile) self.serialize_event(&profile)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -203,7 +264,7 @@ impl SledStore {
for (event_type, event) in &changes.account_data { for (event_type, event) in &changes.account_data {
account_data.insert( account_data.insert(
event_type.as_str(), event_type.as_str(),
serde_json::to_vec(&event) self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -212,7 +273,7 @@ impl SledStore {
for (event_type, event) in events { for (event_type, event) in events {
room_account_data.insert( room_account_data.insert(
format!("{}{}", room.as_str(), event_type).as_str(), format!("{}{}", room.as_str(), event_type).as_str(),
serde_json::to_vec(&event) self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -229,7 +290,7 @@ impl SledStore {
event.state_key(), event.state_key(),
) )
.as_bytes(), .as_bytes(),
serde_json::to_vec(&event) self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -239,7 +300,7 @@ impl SledStore {
for (room_id, room_info) in &changes.room_infos { for (room_id, room_info) in &changes.room_infos {
rooms.insert( rooms.insert(
room_id.as_bytes(), room_id.as_bytes(),
serde_json::to_vec(room_info) self.serialize_event(room_info)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -247,7 +308,7 @@ impl SledStore {
for (sender, event) in &changes.presence { for (sender, event) in &changes.presence {
presence.insert( presence.insert(
sender.as_bytes(), sender.as_bytes(),
serde_json::to_vec(&event) self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -255,7 +316,7 @@ impl SledStore {
for (room_id, info) in &changes.invited_room_info { for (room_id, info) in &changes.invited_room_info {
striped_rooms.insert( striped_rooms.insert(
room_id.as_str(), room_id.as_str(),
serde_json::to_vec(&info) self.serialize_event(&info)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -264,7 +325,7 @@ impl SledStore {
for event in events.values() { for event in events.values() {
stripped_members.insert( stripped_members.insert(
format!("{}{}", room.as_str(), &event.state_key).as_str(), format!("{}{}", room.as_str(), &event.state_key).as_str(),
serde_json::to_vec(&event) self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -281,7 +342,7 @@ impl SledStore {
event.state_key(), event.state_key(),
) )
.as_bytes(), .as_bytes(),
serde_json::to_vec(&event) self.serialize_event(&event)
.map_err(ConflictableTransactionError::Abort)?, .map_err(ConflictableTransactionError::Abort)?,
)?; )?;
} }
@ -305,7 +366,7 @@ impl SledStore {
Ok(self Ok(self
.presence .presence
.get(user_id.as_bytes())? .get(user_id.as_bytes())?
.map(|e| serde_json::from_slice(&e)) .map(|e| self.deserialize_event(&e))
.transpose()?) .transpose()?)
} }
@ -318,7 +379,7 @@ impl SledStore {
Ok(self Ok(self
.room_state .room_state
.get(format!("{}{}{}", room_id.as_str(), event_type, state_key).as_bytes())? .get(format!("{}{}{}", room_id.as_str(), event_type, state_key).as_bytes())?
.map(|e| serde_json::from_slice(&e)) .map(|e| self.deserialize_event(&e))
.transpose()?) .transpose()?)
} }
@ -330,7 +391,7 @@ impl SledStore {
Ok(self Ok(self
.profiles .profiles
.get(format!("{}{}", room_id.as_str(), user_id.as_str()))? .get(format!("{}{}", room_id.as_str(), user_id.as_str()))?
.map(|p| serde_json::from_slice(&p)) .map(|p| self.deserialize_event(&p))
.transpose()?) .transpose()?)
} }
@ -342,7 +403,7 @@ impl SledStore {
Ok(self Ok(self
.members .members
.get(format!("{}{}", room_id.as_str(), state_key.as_str()))? .get(format!("{}{}", room_id.as_str(), state_key.as_str()))?
.map(|v| serde_json::from_slice(&v)) .map(|v| self.deserialize_event(&v))
.transpose()?) .transpose()?)
} }
@ -375,10 +436,11 @@ impl SledStore {
} }
pub async fn get_room_infos(&self) -> impl Stream<Item = Result<RoomInfo>> { pub async fn get_room_infos(&self) -> impl Stream<Item = Result<RoomInfo>> {
let db = self.clone();
stream::iter( stream::iter(
self.room_info self.room_info
.iter() .iter()
.map(|r| serde_json::from_slice(&r?.1).map_err(StoreError::Json)), .map(move |r| db.deserialize_event(&r?.1).map_err(StoreError::Json)),
) )
} }
} }

View File

@ -265,7 +265,7 @@ impl OlmMachine {
user_id: &UserId, user_id: &UserId,
device_id: &DeviceId, device_id: &DeviceId,
path: impl AsRef<Path>, path: impl AsRef<Path>,
passphrase: &str, passphrase: Option<&str>,
) -> StoreResult<Self> { ) -> StoreResult<Self> {
let store = SledStore::open_with_passphrase(path, passphrase)?; let store = SledStore::open_with_passphrase(path, passphrase)?;
@ -1775,7 +1775,7 @@ pub(crate) mod test {
&user_id(), &user_id(),
&alice_device_id(), &alice_device_id(),
tmpdir.as_ref(), tmpdir.as_ref(),
"test", Some("test"),
) )
.await .await
.unwrap(); .unwrap();
@ -1795,7 +1795,7 @@ pub(crate) mod test {
&user_id, &user_id,
&alice_device_id(), &alice_device_id(),
tmpdir.as_ref(), tmpdir.as_ref(),
"test", Some("test"),
) )
.await .await
.unwrap(); .unwrap();

View File

@ -81,11 +81,11 @@ impl From<TransactionError<serde_json::Error>> for CryptoStoreError {
} }
impl SledStore { impl SledStore {
pub fn open_with_passphrase(path: impl AsRef<Path>, passphrase: &str) -> Result<Self> { pub fn open_with_passphrase(path: impl AsRef<Path>, passphrase: Option<&str>) -> Result<Self> {
let path = path.as_ref().join("matrix-sdk-crypto"); let path = path.as_ref().join("matrix-sdk-crypto");
let db = Config::new().temporary(false).path(path).open()?; let db = Config::new().temporary(false).path(path).open()?;
SledStore::open_helper(db, Some(passphrase)) SledStore::open_helper(db, passphrase)
} }
fn open_helper(db: Db, passphrase: Option<&str>) -> Result<Self> { fn open_helper(db: Db, passphrase: Option<&str>) -> Result<Self> {