crypto: Use a random pickle key in the sqlite store.

This commit is contained in:
Damir Jelić 2020-10-21 15:13:21 +02:00
parent 959e8450af
commit d175c47a05
4 changed files with 128 additions and 45 deletions

View file

@ -26,7 +26,6 @@ use matrix_sdk_common_macros::async_trait;
use super::{ use super::{
caches::{DeviceStore, GroupSessionStore, SessionStore}, caches::{DeviceStore, GroupSessionStore, SessionStore},
pickle_key::EncryptedPickleKey,
Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session, Changes, CryptoStore, InboundGroupSession, ReadOnlyAccount, Result, Session,
}; };
use crate::identities::{ReadOnlyDevice, UserIdentities}; use crate::identities::{ReadOnlyDevice, UserIdentities};

View file

@ -46,7 +46,7 @@ pub(crate) mod sqlite;
use matrix_sdk_common::identifiers::DeviceIdBox; use matrix_sdk_common::identifiers::DeviceIdBox;
pub use memorystore::MemoryStore; pub use memorystore::MemoryStore;
use pickle_key::EncryptedPickleKey; pub use pickle_key::{EncryptedPickleKey, PickleKey};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "sqlite_cryptostore")] #[cfg(feature = "sqlite_cryptostore")]
pub use sqlite::SqliteStore; pub use sqlite::SqliteStore;

View file

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![allow(dead_code)] use std::convert::TryFrom;
use aes_gcm::{ use aes_gcm::{
aead::{generic_array::GenericArray, Aead, NewAead}, aead::{generic_array::GenericArray, Aead, NewAead},
@ -20,6 +20,7 @@ use aes_gcm::{
}; };
use getrandom::getrandom; use getrandom::getrandom;
use hmac::Hmac; use hmac::Hmac;
use olm_rs::PicklingMode;
use pbkdf2::pbkdf2; use pbkdf2::pbkdf2;
use sha2::Sha256; use sha2::Sha256;
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
@ -57,6 +58,20 @@ pub struct PickleKey {
aes256_key: Vec<u8>, aes256_key: Vec<u8>,
} }
impl TryFrom<Vec<u8>> for PickleKey {
type Error = ();
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() != KEY_SIZE {
Err(())
} else {
Ok(Self {
version: VERSION,
aes256_key: value,
})
}
}
}
impl PickleKey { impl PickleKey {
/// Generate a new random pickle key. /// Generate a new random pickle key.
pub fn new() -> Self { pub fn new() -> Self {
@ -75,6 +90,13 @@ impl PickleKey {
key key
} }
/// Get a `PicklingMode` version of this pickle key.
pub fn pickle_mode(&self) -> PicklingMode {
PicklingMode::Encrypted {
key: self.aes256_key.clone(),
}
}
/// Encrypt and export our pickle key using the given passphrase. /// Encrypt and export our pickle key using the given passphrase.
/// ///
/// # Arguments /// # Arguments

View file

@ -32,9 +32,12 @@ use matrix_sdk_common::{
locks::Mutex, locks::Mutex,
}; };
use sqlx::{query, query_as, sqlite::SqliteConnectOptions, Connection, Executor, SqliteConnection}; use sqlx::{query, query_as, sqlite::SqliteConnectOptions, Connection, Executor, SqliteConnection};
use zeroize::Zeroizing;
use super::{caches::SessionStore, Changes, CryptoStore, CryptoStoreError, Result}; use super::{
caches::SessionStore,
pickle_key::{EncryptedPickleKey, PickleKey},
Changes, CryptoStore, CryptoStoreError, Result,
};
use crate::{ use crate::{
identities::{LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserIdentities, UserIdentity}, identities::{LocalTrust, OwnUserIdentity, ReadOnlyDevice, UserIdentities, UserIdentity},
olm::{ olm::{
@ -44,6 +47,10 @@ use crate::{
}, },
}; };
/// This needs to be 32 bytes long since AES-GCM requires it, otherwise we will
/// panic once we try to pickle a Signing object.
const DEFAULT_PICKLE: &'static str = "DEFAULT_PICKLE_PASSPHRASE_123456";
/// SQLite based implementation of a `CryptoStore`. /// SQLite based implementation of a `CryptoStore`.
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(feature = "docs", doc(cfg(r#sqlite_cryptostore)))] #[cfg_attr(feature = "docs", doc(cfg(r#sqlite_cryptostore)))]
@ -58,7 +65,7 @@ pub struct SqliteStore {
users_for_key_query: Arc<DashSet<UserId>>, users_for_key_query: Arc<DashSet<UserId>>,
connection: Arc<Mutex<SqliteConnection>>, connection: Arc<Mutex<SqliteConnection>>,
pickle_passphrase: Arc<Option<Zeroizing<String>>>, pickle_key: Arc<PickleKey>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -117,20 +124,14 @@ impl SqliteStore {
path: P, path: P,
passphrase: &str, passphrase: &str,
) -> Result<SqliteStore> { ) -> Result<SqliteStore> {
SqliteStore::open_helper( SqliteStore::open_helper(user_id, device_id, path, Some(passphrase)).await
user_id,
device_id,
path,
Some(Zeroizing::new(passphrase.to_owned())),
)
.await
} }
async fn open_helper<P: AsRef<Path>>( async fn open_helper<P: AsRef<Path>>(
user_id: &UserId, user_id: &UserId,
device_id: &DeviceId, device_id: &DeviceId,
path: P, path: P,
passphrase: Option<Zeroizing<String>>, passphrase: Option<&str>,
) -> Result<SqliteStore> { ) -> Result<SqliteStore> {
let path = path.as_ref().join(DATABASE_NAME); let path = path.as_ref().join(DATABASE_NAME);
let options = SqliteConnectOptions::new() let options = SqliteConnectOptions::new()
@ -139,7 +140,15 @@ impl SqliteStore {
.read_only(false) .read_only(false)
.filename(&path); .filename(&path);
let connection = SqliteConnection::connect_with(&options).await?; let mut connection = SqliteConnection::connect_with(&options).await?;
Self::create_tables(&mut connection).await?;
let pickle_key = if let Some(passphrase) = passphrase {
Self::get_or_create_pickle_key(user_id, device_id, &passphrase, &mut connection).await?
} else {
PickleKey::try_from(DEFAULT_PICKLE.as_bytes().to_vec())
.expect("Can't create default pickle key")
};
let store = SqliteStore { let store = SqliteStore {
user_id: Arc::new(user_id.to_owned()), user_id: Arc::new(user_id.to_owned()),
@ -148,11 +157,10 @@ impl SqliteStore {
sessions: SessionStore::new(), sessions: SessionStore::new(),
path: Arc::new(path), path: Arc::new(path),
connection: Arc::new(Mutex::new(connection)), connection: Arc::new(Mutex::new(connection)),
pickle_passphrase: Arc::new(passphrase),
tracked_users: Arc::new(DashSet::new()), tracked_users: Arc::new(DashSet::new()),
users_for_key_query: Arc::new(DashSet::new()), users_for_key_query: Arc::new(DashSet::new()),
pickle_key: Arc::new(pickle_key),
}; };
store.create_tables().await?;
Ok(store) Ok(store)
} }
@ -165,11 +173,8 @@ impl SqliteStore {
.map(|i| i.account_id) .map(|i| i.account_id)
} }
async fn create_tables(&self) -> Result<()> { async fn create_tables(connection: &mut SqliteConnection) -> Result<()> {
let mut connection = self.connection.lock().await; connection
let mut transaction = connection.begin().await?;
transaction
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS accounts ( CREATE TABLE IF NOT EXISTS accounts (
@ -185,7 +190,21 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute(
r#"
CREATE TABLE IF NOT EXISTS pickle_keys (
"id" INTEGER NOT NULL PRIMARY KEY,
"user_id" TEXT NOT NULL,
"device_id" TEXT NOT NULL,
"key" TEXT NOT NULL,
UNIQUE(user_id,device_id)
);
"#,
)
.await?;
connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
@ -204,7 +223,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS tracked_users ( CREATE TABLE IF NOT EXISTS tracked_users (
@ -222,7 +241,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS inbound_group_sessions ( CREATE TABLE IF NOT EXISTS inbound_group_sessions (
@ -243,7 +262,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS group_session_claimed_keys ( CREATE TABLE IF NOT EXISTS group_session_claimed_keys (
@ -261,7 +280,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS group_session_chains ( CREATE TABLE IF NOT EXISTS group_session_chains (
@ -278,7 +297,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS devices ( CREATE TABLE IF NOT EXISTS devices (
@ -298,7 +317,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS algorithms ( CREATE TABLE IF NOT EXISTS algorithms (
@ -315,7 +334,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS device_keys ( CREATE TABLE IF NOT EXISTS device_keys (
@ -333,7 +352,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS device_signatures ( CREATE TABLE IF NOT EXISTS device_signatures (
@ -352,7 +371,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
@ -369,7 +388,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS users_trust_state ( CREATE TABLE IF NOT EXISTS users_trust_state (
@ -384,7 +403,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS cross_signing_keys ( CREATE TABLE IF NOT EXISTS cross_signing_keys (
@ -401,7 +420,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS user_keys ( CREATE TABLE IF NOT EXISTS user_keys (
@ -418,7 +437,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS user_key_signatures ( CREATE TABLE IF NOT EXISTS user_key_signatures (
@ -437,7 +456,7 @@ impl SqliteStore {
) )
.await?; .await?;
transaction connection
.execute( .execute(
r#" r#"
CREATE TABLE IF NOT EXISTS key_value ( CREATE TABLE IF NOT EXISTS key_value (
@ -455,11 +474,59 @@ impl SqliteStore {
) )
.await?; .await?;
transaction.commit().await?; Ok(())
}
async fn save_pickle_key(
user_id: &UserId,
device_id: &DeviceId,
key: EncryptedPickleKey,
connection: &mut SqliteConnection,
) -> Result<()> {
let key = serde_json::to_string(&key)?;
query(
"INSERT INTO pickle_keys (
user_id, device_id, key
) VALUES (?1, ?2, ?3)
ON CONFLICT(user_id, device_id) DO UPDATE SET
key = excluded.key
",
)
.bind(user_id.as_str())
.bind(device_id.as_str())
.bind(key)
.execute(&mut *connection)
.await?;
Ok(()) Ok(())
} }
async fn get_or_create_pickle_key(
user_id: &UserId,
device_id: &DeviceId,
passphrase: &str,
connection: &mut SqliteConnection,
) -> Result<PickleKey> {
let row: Option<(String,)> =
query_as("SELECT key FROM pickle_keys WHERE user_id = ? and device_id = ?")
.bind(user_id.as_str())
.bind(device_id.as_str())
.fetch_optional(&mut *connection)
.await?;
Ok(if let Some(row) = row {
let encrypted: EncryptedPickleKey = serde_json::from_str(&row.0).unwrap();
PickleKey::from_encrypted(passphrase, encrypted)
} else {
let key = PickleKey::new();
let encrypted = key.encrypt(passphrase);
Self::save_pickle_key(user_id, device_id, encrypted, connection).await?;
key
})
}
async fn lazy_load_sessions( async fn lazy_load_sessions(
&self, &self,
connection: &mut SqliteConnection, connection: &mut SqliteConnection,
@ -983,12 +1050,7 @@ impl SqliteStore {
} }
fn get_pickle_mode(&self) -> PicklingMode { fn get_pickle_mode(&self) -> PicklingMode {
match &*self.pickle_passphrase { self.pickle_key.pickle_mode()
Some(p) => PicklingMode::Encrypted {
key: p.as_bytes().to_vec(),
},
None => PicklingMode::Unencrypted,
}
} }
async fn save_inbound_group_session_helper( async fn save_inbound_group_session_helper(
@ -1484,8 +1546,8 @@ impl CryptoStore for SqliteStore {
} }
async fn save_account(&self, account: ReadOnlyAccount) -> Result<()> { async fn save_account(&self, account: ReadOnlyAccount) -> Result<()> {
let pickle = account.pickle(self.get_pickle_mode()).await;
let mut connection = self.connection.lock().await; let mut connection = self.connection.lock().await;
let pickle = account.pickle(self.get_pickle_mode()).await;
query( query(
"INSERT INTO accounts ( "INSERT INTO accounts (