Skip to content

Commit

Permalink
feat(indxeddb): Use the new store-cipher to encrypt our values
Browse files Browse the repository at this point in the history
  • Loading branch information
poljar committed Mar 17, 2022
1 parent 7a590b4 commit beb2d3c
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 35 deletions.
1 change: 1 addition & 0 deletions crates/matrix-sdk-indexeddb/Cargo.toml
Expand Up @@ -14,6 +14,7 @@ default-target = "wasm32-unknown-unknown"
matrix-sdk-base = { path = "../matrix-sdk-base", features = ["store_key"] }
matrix-sdk-crypto = { path = "../matrix-sdk-crypto", optional = true }
matrix-sdk-common = { path = "../matrix-sdk-common" }
matrix-sdk-store-encryption = { path = "../matrix-sdk-store-encryption" }
thiserror = "1.0.25"
anyhow = "1"

Expand Down
131 changes: 96 additions & 35 deletions crates/matrix-sdk-indexeddb/src/cryptostore.rs
Expand Up @@ -35,6 +35,8 @@ use matrix_sdk_crypto::{
},
GossipRequest, ReadOnlyAccount, ReadOnlyDevice, ReadOnlyUserIdentities, SecretInfo,
};
use matrix_sdk_store_encryption::StoreCipher;
use serde::{Deserialize, Serialize};
use wasm_bindgen::JsValue;

use crate::safe_encode::SafeEncode;
Expand All @@ -60,8 +62,7 @@ mod KEYS {
pub const SECRET_REQUESTS_BY_INFO: &str = "secret_requests_by_info";

// KEYS
#[allow(dead_code)]
pub const PICKLE_KEY: &str = "pickle_key";
pub const STORE_CIPHER: &str = "store_cipher";
pub const ACCOUNT: &str = "account";
pub const PRIVATE_IDENTITY: &str = "private_identity";
}
Expand All @@ -72,6 +73,8 @@ pub struct IndexeddbStore {
name: String,
pub(crate) inner: IdbDatabase,

store_cipher: Arc<Option<StoreCipher>>,

session_cache: SessionStore,
tracked_users_cache: Arc<DashSet<Box<UserId>>>,
users_for_key_query_cache: Arc<DashSet<Box<UserId>>>,
Expand Down Expand Up @@ -130,7 +133,7 @@ pub struct AccountInfo {
}

impl IndexeddbStore {
async fn open_helper(prefix: String) -> Result<Self> {
async fn open_helper(prefix: String, store_cipher: Option<StoreCipher>) -> Result<Self> {
let name = format!("{:0}::matrix-sdk-crypto", prefix);

// Open my_db v1
Expand Down Expand Up @@ -164,6 +167,7 @@ impl IndexeddbStore {
name,
session_cache,
inner: db,
store_cipher: store_cipher.into(),
account_info: RwLock::new(None).into(),
tracked_users_cache: DashSet::new().into(),
users_for_key_query_cache: DashSet::new().into(),
Expand All @@ -172,11 +176,11 @@ impl IndexeddbStore {

/// Open a new IndexeddbStore with default name and no passphrase
pub async fn open() -> Result<Self> {
IndexeddbStore::open_helper("crypto".to_owned()).await
IndexeddbStore::open_helper("crypto".to_owned(), None).await
}

/// Open a new IndexeddbStore with given name and passphrase
pub async fn open_with_passphrase(prefix: String, _passphrase: &str) -> Result<Self> {
pub async fn open_with_passphrase(prefix: String, passphrase: &str) -> Result<Self> {
let name = format!("{:0}::matrix-sdk-crypto-meta", prefix);

let mut db_req: OpenDbRequest = IdbDatabase::open_f64(&name, 1.0)?;
Expand All @@ -190,14 +194,70 @@ impl IndexeddbStore {
Ok(())
}));

let _: IdbDatabase = db_req.into_future().await?;
let db: IdbDatabase = db_req.into_future().await?;

let tx: IdbTransaction<'_> =
db.transaction_on_one_with_mode("matrix-sdk-crypto", IdbTransactionMode::Readonly)?;
let ob = tx.object_store("matrix-sdk-crypto")?;

let store_cipher: Option<Vec<u8>> = ob
.get(&JsValue::from_str(KEYS::STORE_CIPHER))?
.await?
.map(|k| k.into_serde())
.transpose()?;

let store_cipher = match store_cipher {
Some(key) => StoreCipher::import(passphrase, &key)
.map_err(|_| CryptoStoreError::UnpicklingError)?,
None => {
let key = StoreCipher::new().map_err(|e| CryptoStoreError::Backend(anyhow!(e)))?;
let encrypted =
key.export(passphrase).map_err(|e| CryptoStoreError::Backend(anyhow!(e)))?;

let tx: IdbTransaction<'_> = db.transaction_on_one_with_mode(
"matrix-sdk-crypto",
IdbTransactionMode::Readwrite,
)?;
let ob = tx.object_store("matrix-sdk-crypto")?;

ob.put_key_val(
&JsValue::from_str(KEYS::STORE_CIPHER),
&JsValue::from_serde(&encrypted)?,
)?;
tx.await.into_result()?;
key
}
};

IndexeddbStore::open_helper(prefix).await
IndexeddbStore::open_helper(prefix, Some(store_cipher)).await
}

/// Open a new IndexeddbStore with given name and no passphrase
pub async fn open_with_name(name: String) -> Result<Self> {
IndexeddbStore::open_helper(name).await
IndexeddbStore::open_helper(name, None).await
}

fn serialize_value(&self, value: &impl Serialize) -> Result<JsValue, CryptoStoreError> {
if let Some(key) = &*self.store_cipher {
let value =
key.encrypt_value(value).map_err(|e| CryptoStoreError::Backend(anyhow!(e)))?;

Ok(JsValue::from_serde(&value)?)
} else {
Ok(JsValue::from_serde(&value)?)
}
}

fn deserialize_value<T: for<'b> Deserialize<'b>>(
&self,
value: JsValue,
) -> Result<T, CryptoStoreError> {
if let Some(key) = &*self.store_cipher {
let value: Vec<u8> = value.into_serde()?;
key.decrypt_value(&value).map_err(|e| CryptoStoreError::Backend(anyhow!(e)))
} else {
Ok(value.into_serde()?)
}
}

fn get_account_info(&self) -> Option<AccountInfo> {
Expand Down Expand Up @@ -250,13 +310,13 @@ impl IndexeddbStore {

if let Some(a) = &account_pickle {
tx.object_store(KEYS::CORE)?
.put_key_val(&JsValue::from_str(KEYS::ACCOUNT), &JsValue::from_serde(&a)?)?;
.put_key_val(&JsValue::from_str(KEYS::ACCOUNT), &self.serialize_value(&a)?)?;
}

if let Some(i) = &private_identity_pickle {
tx.object_store(KEYS::CORE)?.put_key_val(
&JsValue::from_str(KEYS::PRIVATE_IDENTITY),
&JsValue::from_serde(i)?,
&self.serialize_value(i)?,
)?;
}

Expand All @@ -270,7 +330,7 @@ impl IndexeddbStore {
let pickle = session.pickle().await;
let key = (sender_key, session_id).encode();

sessions.put_key_val(&key, &JsValue::from_serde(&pickle)?)?;
sessions.put_key_val(&key, &self.serialize_value(&pickle)?)?;
}
}

Expand All @@ -284,7 +344,7 @@ impl IndexeddbStore {
let key = (room_id, sender_key, session_id).encode();
let pickle = session.pickle().await;

sessions.put_key_val(&key, &JsValue::from_serde(&pickle)?)?;
sessions.put_key_val(&key, &self.serialize_value(&pickle)?)?;
}
}

Expand All @@ -294,7 +354,7 @@ impl IndexeddbStore {
for session in changes.outbound_group_sessions {
let room_id = session.room_id();
let pickle = session.pickle().await;
sessions.put_key_val(&room_id.encode(), &JsValue::from_serde(&pickle)?)?;
sessions.put_key_val(&room_id.encode(), &self.serialize_value(&pickle)?)?;
}
}

Expand All @@ -307,7 +367,7 @@ impl IndexeddbStore {
let device_store = tx.object_store(KEYS::DEVICES)?;
for device in device_changes.new.iter().chain(&device_changes.changed) {
let key = (device.user_id(), device.device_id()).encode();
let device = JsValue::from_serde(&device)?;
let device = self.serialize_value(&device)?;

device_store.put_key_val(&key, &device)?;
}
Expand All @@ -326,7 +386,7 @@ impl IndexeddbStore {
let identities = tx.object_store(KEYS::IDENTITIES)?;
for identity in identity_changes.changed.iter().chain(&identity_changes.new) {
identities
.put_key_val(&identity.user_id().encode(), &JsValue::from_serde(&identity)?)?;
.put_key_val(&identity.user_id().encode(), &self.serialize_value(&identity)?)?;
}
}

Expand All @@ -349,11 +409,11 @@ impl IndexeddbStore {
if key_request.sent_out {
unsent_secret_requests.delete(&key_request_id)?;
outgoing_secret_requests
.put_key_val(&key_request_id, &JsValue::from_serde(&key_request)?)?;
.put_key_val(&key_request_id, &self.serialize_value(&key_request)?)?;
} else {
outgoing_secret_requests.delete(&key_request_id)?;
unsent_secret_requests
.put_key_val(&key_request_id, &JsValue::from_serde(&key_request)?)?;
.put_key_val(&key_request_id, &self.serialize_value(&key_request)?)?;
}
}
}
Expand Down Expand Up @@ -410,7 +470,7 @@ impl IndexeddbStore {
OutboundGroupSession::from_pickle(
account_info.device_id,
account_info.identity_keys,
value.into_serde()?,
self.deserialize_value(value)?,
)
.map_err(CryptoStoreError::from)?,
))
Expand All @@ -428,15 +488,15 @@ impl IndexeddbStore {
.object_store(KEYS::OUTGOING_SECRET_REQUESTS)?
.get(&jskey)?
.await?
.map(|i| i.into_serde())
.map(|i| self.deserialize_value(i))
.transpose()?;

Ok(match request {
None => tx
.object_store(KEYS::UNSENT_SECRET_REQUESTS)?
.get(&jskey)?
.await?
.map(|i| i.into_serde())
.map(|i| self.deserialize_value(i))
.transpose()?,
Some(request) => Some(request),
})
Expand All @@ -452,8 +512,9 @@ impl IndexeddbStore {
{
self.load_tracked_users().await?;

let account = ReadOnlyAccount::from_pickle(pickle.into_serde()?)
.map_err(CryptoStoreError::from)?;
let pickle = self.deserialize_value(pickle)?;

let account = ReadOnlyAccount::from_pickle(pickle).map_err(CryptoStoreError::from)?;

let account_info = AccountInfo {
user_id: account.user_id.clone(),
Expand All @@ -477,8 +538,10 @@ impl IndexeddbStore {
.get(&JsValue::from_str(KEYS::PRIVATE_IDENTITY))?
.await?
{
let pickle = self.deserialize_value(pickle)?;

Ok(Some(
PrivateCrossSigningIdentity::from_pickle(pickle.into_serde()?)
PrivateCrossSigningIdentity::from_pickle(pickle)
.await
.map_err(|_| CryptoStoreError::UnpicklingError)?,
))
Expand All @@ -504,7 +567,7 @@ impl IndexeddbStore {
.get_all_with_key(&range)?
.await?
.iter()
.filter_map(|f| match f.into_serde() {
.filter_map(|f| match self.deserialize_value(f) {
Ok(p) => Some(Session::from_pickle(
account_info.user_id.clone(),
account_info.device_id.clone(),
Expand Down Expand Up @@ -538,10 +601,8 @@ impl IndexeddbStore {
.get(&key)?
.await?
{
Ok(Some(
InboundGroupSession::from_pickle(pickle.into_serde()?)
.map_err(CryptoStoreError::from)?,
))
let pickle = self.deserialize_value(pickle)?;
Ok(Some(InboundGroupSession::from_pickle(pickle).map_err(CryptoStoreError::from)?))
} else {
Ok(None)
}
Expand All @@ -558,7 +619,7 @@ impl IndexeddbStore {
.get_all()?
.await?
.iter()
.filter_map(|i| i.into_serde().ok())
.filter_map(|i| self.deserialize_value(i).ok())
.filter_map(|p| InboundGroupSession::from_pickle(p).ok())
.collect())
}
Expand Down Expand Up @@ -650,7 +711,7 @@ impl IndexeddbStore {
.object_store(KEYS::DEVICES)?
.get(&key)?
.await?
.map(|i| i.into_serde())
.map(|i| self.deserialize_value(i))
.transpose()?)
}

Expand All @@ -671,7 +732,7 @@ impl IndexeddbStore {
.await?
.iter()
.filter_map(|d| {
let d: ReadOnlyDevice = d.into_serde().ok()?;
let d: ReadOnlyDevice = self.deserialize_value(d).ok()?;
Some((d.device_id().to_owned(), d))
})
.collect::<HashMap<_, _>>())
Expand All @@ -684,7 +745,7 @@ impl IndexeddbStore {
.object_store(KEYS::IDENTITIES)?
.get(&user_id.encode())?
.await?
.map(|i| i.into_serde())
.map(|i| self.deserialize_value(i))
.transpose()?)
}

Expand Down Expand Up @@ -730,7 +791,7 @@ impl IndexeddbStore {
.get_all()?
.await?
.iter()
.filter_map(|i| i.into_serde().ok())
.filter_map(|i| self.deserialize_value(i).ok())
.collect())
}

Expand All @@ -747,15 +808,15 @@ impl IndexeddbStore {
.object_store(KEYS::OUTGOING_SECRET_REQUESTS)?
.get(&jskey)?
.await?
.map(|i| i.into_serde())
.map(|i| self.deserialize_value(i))
.transpose()?;

let request = match request {
None => tx
.object_store(KEYS::UNSENT_SECRET_REQUESTS)?
.get(&jskey)?
.await?
.map(|i| i.into_serde())
.map(|i| self.deserialize_value(i))
.transpose()?,
Some(request) => Some(request),
};
Expand Down

0 comments on commit beb2d3c

Please sign in to comment.