crypto: Store private identities and accounts with the Changes struct as well.
parent
e65915e159
commit
e20b1efae9
|
@ -53,6 +53,7 @@ use crate::{
|
||||||
olm::{
|
olm::{
|
||||||
Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys,
|
Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys,
|
||||||
InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity, ReadOnlyAccount,
|
InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity, ReadOnlyAccount,
|
||||||
|
SessionType,
|
||||||
},
|
},
|
||||||
requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest},
|
requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest},
|
||||||
session_manager::{GroupSessionManager, SessionManager},
|
session_manager::{GroupSessionManager, SessionManager},
|
||||||
|
@ -365,10 +366,15 @@ impl OlmMachine {
|
||||||
|
|
||||||
/// Mark the cross signing identity as shared.
|
/// Mark the cross signing identity as shared.
|
||||||
async fn receive_cross_signing_upload_response(&self) -> StoreResult<()> {
|
async fn receive_cross_signing_upload_response(&self) -> StoreResult<()> {
|
||||||
self.user_identity.lock().await.mark_as_shared();
|
let identity = self.user_identity.lock().await;
|
||||||
self.store
|
identity.mark_as_shared();
|
||||||
.save_identity((&*self.user_identity.lock().await).clone())
|
|
||||||
.await
|
let changes = Changes {
|
||||||
|
private_identity: Some(identity.clone()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.store.save_changes(changes).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new cross signing identity and get the upload request to push
|
/// Create a new cross signing identity and get the upload request to push
|
||||||
|
@ -400,11 +406,12 @@ impl OlmMachine {
|
||||||
new: vec![public.into()],
|
new: vec![public.into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
private_identity: Some(identity.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.store.save_changes(changes).await?;
|
self.store.save_changes(changes).await?;
|
||||||
self.store.save_identity(identity.clone()).await?;
|
|
||||||
Ok((request, signature_request))
|
Ok((request, signature_request))
|
||||||
} else {
|
} else {
|
||||||
info!("Trying to upload the existing cross signing identity");
|
info!("Trying to upload the existing cross signing identity");
|
||||||
|
@ -833,7 +840,18 @@ impl OlmMachine {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
changes.sessions.push(decrypted.session);
|
// New sessions modify the account so we need to save that
|
||||||
|
// one as well.
|
||||||
|
match decrypted.session {
|
||||||
|
SessionType::New(s) => {
|
||||||
|
changes.sessions.push(s);
|
||||||
|
changes.account = Some(self.account.inner.clone());
|
||||||
|
}
|
||||||
|
SessionType::Existing(s) => {
|
||||||
|
changes.sessions.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
changes.message_hashes.push(decrypted.message_hash);
|
changes.message_hashes.push(decrypted.message_hash);
|
||||||
|
|
||||||
if let Some(group_session) = decrypted.inbound_group_session {
|
if let Some(group_session) = decrypted.inbound_group_session {
|
||||||
|
@ -1285,7 +1303,10 @@ pub(crate) mod test {
|
||||||
};
|
};
|
||||||
|
|
||||||
let decrypted = bob.decrypt_to_device_event(&event).await.unwrap();
|
let decrypted = bob.decrypt_to_device_event(&event).await.unwrap();
|
||||||
bob.store.save_sessions(&[decrypted.session]).await.unwrap();
|
bob.store
|
||||||
|
.save_sessions(&[decrypted.session.session()])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
(alice, bob)
|
(alice, bob)
|
||||||
}
|
}
|
||||||
|
@ -1617,7 +1638,10 @@ pub(crate) mod test {
|
||||||
|
|
||||||
let decrypted = bob.decrypt_to_device_event(&event).await.unwrap();
|
let decrypted = bob.decrypt_to_device_event(&event).await.unwrap();
|
||||||
|
|
||||||
bob.store.save_sessions(&[decrypted.session]).await.unwrap();
|
bob.store
|
||||||
|
.save_sessions(&[decrypted.session.session()])
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
bob.store
|
bob.store
|
||||||
.save_inbound_group_sessions(&[decrypted.inbound_group_session.unwrap()])
|
.save_inbound_group_sessions(&[decrypted.inbound_group_session.unwrap()])
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -57,7 +57,7 @@ use crate::{
|
||||||
file_encryption::encode,
|
file_encryption::encode,
|
||||||
identities::ReadOnlyDevice,
|
identities::ReadOnlyDevice,
|
||||||
requests::UploadSigningKeysRequest,
|
requests::UploadSigningKeysRequest,
|
||||||
store::Store,
|
store::{Changes, Store},
|
||||||
OlmError,
|
OlmError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -72,9 +72,25 @@ pub struct Account {
|
||||||
pub(crate) store: Store,
|
pub(crate) store: Store,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SessionType {
|
||||||
|
New(Session),
|
||||||
|
Existing(Session),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionType {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn session(self) -> Session {
|
||||||
|
match self {
|
||||||
|
SessionType::New(s) => s,
|
||||||
|
SessionType::Existing(s) => s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OlmDecryptionInfo {
|
pub struct OlmDecryptionInfo {
|
||||||
pub session: Session,
|
pub session: SessionType,
|
||||||
pub message_hash: OlmMessageHash,
|
pub message_hash: OlmMessageHash,
|
||||||
pub event: Raw<AnyToDeviceEvent>,
|
pub event: Raw<AnyToDeviceEvent>,
|
||||||
pub signing_key: String,
|
pub signing_key: String,
|
||||||
|
@ -274,7 +290,7 @@ impl Account {
|
||||||
sender: &UserId,
|
sender: &UserId,
|
||||||
sender_key: &str,
|
sender_key: &str,
|
||||||
message: OlmMessage,
|
message: OlmMessage,
|
||||||
) -> OlmResult<(Session, Raw<AnyToDeviceEvent>, String)> {
|
) -> OlmResult<(SessionType, Raw<AnyToDeviceEvent>, String)> {
|
||||||
// First try to decrypt using an existing session.
|
// First try to decrypt using an existing session.
|
||||||
let (session, plaintext) = if let Some(d) = self
|
let (session, plaintext) = if let Some(d) = self
|
||||||
.try_decrypt_olm_message(sender, sender_key, &message)
|
.try_decrypt_olm_message(sender, sender_key, &message)
|
||||||
|
@ -282,7 +298,7 @@ impl Account {
|
||||||
{
|
{
|
||||||
// Decryption succeeded, de-structure the session/plaintext out of
|
// Decryption succeeded, de-structure the session/plaintext out of
|
||||||
// the Option.
|
// the Option.
|
||||||
d
|
(SessionType::Existing(d.0), d.1)
|
||||||
} else {
|
} else {
|
||||||
// Decryption failed with every known session, let's try to create a
|
// Decryption failed with every known session, let's try to create a
|
||||||
// new session.
|
// new session.
|
||||||
|
@ -329,7 +345,7 @@ impl Account {
|
||||||
// Decrypt our message, this shouldn't fail since we're using a
|
// Decrypt our message, this shouldn't fail since we're using a
|
||||||
// newly created Session.
|
// newly created Session.
|
||||||
let plaintext = session.decrypt(message).await?;
|
let plaintext = session.decrypt(message).await?;
|
||||||
(session, plaintext)
|
(SessionType::New(session), plaintext)
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("Successfully decrypted a Olm message: {}", plaintext);
|
trace!("Successfully decrypted a Olm message: {}", plaintext);
|
||||||
|
@ -340,7 +356,20 @@ impl Account {
|
||||||
// We might created a new session but decryption might still
|
// We might created a new session but decryption might still
|
||||||
// have failed, store it for the error case here, this is fine
|
// have failed, store it for the error case here, this is fine
|
||||||
// since we don't expect this to happen often or at all.
|
// since we don't expect this to happen often or at all.
|
||||||
self.store.save_sessions(&[session]).await?;
|
match session {
|
||||||
|
SessionType::New(s) => {
|
||||||
|
let changes = Changes {
|
||||||
|
account: Some(self.inner.clone()),
|
||||||
|
sessions: vec![s],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
self.store.save_changes(changes).await?;
|
||||||
|
}
|
||||||
|
SessionType::Existing(s) => {
|
||||||
|
self.store.save_sessions(&[s]).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,7 @@ mod session;
|
||||||
mod signing;
|
mod signing;
|
||||||
mod utility;
|
mod utility;
|
||||||
|
|
||||||
pub(crate) use account::{Account, OlmDecryptionInfo};
|
pub(crate) use account::{Account, OlmDecryptionInfo, SessionType};
|
||||||
pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount};
|
pub use account::{AccountPickle, OlmMessageHash, PickledAccount, ReadOnlyAccount};
|
||||||
pub use group_sessions::{
|
pub use group_sessions::{
|
||||||
EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle,
|
EncryptionSettings, ExportedRoomKey, InboundGroupSession, InboundGroupSessionPickle,
|
||||||
|
|
|
@ -220,10 +220,6 @@ impl CryptoStore for MemoryStore {
|
||||||
Ok(self.values.get(key).map(|v| v.to_owned()))
|
Ok(self.values.get(key).map(|v| v.to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_identity(&self, _: PrivateCrossSigningIdentity) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>> {
|
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ pub(crate) struct Store {
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub struct Changes {
|
pub struct Changes {
|
||||||
pub account: Option<ReadOnlyAccount>,
|
pub account: Option<ReadOnlyAccount>,
|
||||||
|
pub private_identity: Option<PrivateCrossSigningIdentity>,
|
||||||
pub sessions: Vec<Session>,
|
pub sessions: Vec<Session>,
|
||||||
pub message_hashes: Vec<OlmMessageHash>,
|
pub message_hashes: Vec<OlmMessageHash>,
|
||||||
pub inbound_group_sessions: Vec<InboundGroupSession>,
|
pub inbound_group_sessions: Vec<InboundGroupSession>,
|
||||||
|
@ -345,14 +346,6 @@ pub trait CryptoStore: Debug {
|
||||||
/// * `account` - The account that should be stored.
|
/// * `account` - The account that should be stored.
|
||||||
async fn save_account(&self, account: ReadOnlyAccount) -> Result<()>;
|
async fn save_account(&self, account: ReadOnlyAccount) -> Result<()>;
|
||||||
|
|
||||||
/// Save the given privat identity in the store.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `identity` - The private cross signing identity that should be saved
|
|
||||||
/// in the store.
|
|
||||||
async fn save_identity(&self, identity: PrivateCrossSigningIdentity) -> Result<()>;
|
|
||||||
|
|
||||||
/// Try to load a private cross signing identity, if one is stored.
|
/// Try to load a private cross signing identity, if one is stored.
|
||||||
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>>;
|
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>>;
|
||||||
|
|
||||||
|
|
|
@ -1504,6 +1504,73 @@ impl SqliteStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn save_identity(
|
||||||
|
&self,
|
||||||
|
connection: &mut SqliteConnection,
|
||||||
|
identity: PrivateCrossSigningIdentity,
|
||||||
|
) -> Result<()> {
|
||||||
|
let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?;
|
||||||
|
let pickle = identity.pickle(self.get_pickle_key()).await?;
|
||||||
|
|
||||||
|
query(
|
||||||
|
"INSERT INTO private_identities (
|
||||||
|
account_id, user_id, pickle, shared
|
||||||
|
) VALUES (?1, ?2, ?3, ?4)
|
||||||
|
ON CONFLICT(account_id, user_id) DO UPDATE SET
|
||||||
|
pickle = excluded.pickle,
|
||||||
|
shared = excluded.shared
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(account_id)
|
||||||
|
.bind(pickle.user_id.as_str())
|
||||||
|
.bind(pickle.pickle)
|
||||||
|
.bind(pickle.shared)
|
||||||
|
.execute(&mut *connection)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_account_helper(
|
||||||
|
&self,
|
||||||
|
connection: &mut SqliteConnection,
|
||||||
|
account: ReadOnlyAccount,
|
||||||
|
) -> Result<()> {
|
||||||
|
let pickle = account.pickle(self.get_pickle_mode()).await;
|
||||||
|
|
||||||
|
query(
|
||||||
|
"INSERT INTO accounts (
|
||||||
|
user_id, device_id, pickle, shared, uploaded_key_count
|
||||||
|
) VALUES (?1, ?2, ?3, ?4, ?5)
|
||||||
|
ON CONFLICT(user_id, device_id) DO UPDATE SET
|
||||||
|
pickle = excluded.pickle,
|
||||||
|
shared = excluded.shared,
|
||||||
|
uploaded_key_count = excluded.uploaded_key_count
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(pickle.user_id.as_str())
|
||||||
|
.bind(pickle.device_id.as_str())
|
||||||
|
.bind(pickle.pickle.as_str())
|
||||||
|
.bind(pickle.shared)
|
||||||
|
.bind(pickle.uploaded_signed_key_count)
|
||||||
|
.execute(&mut *connection)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let account_id: (i64,) =
|
||||||
|
query_as("SELECT id FROM accounts WHERE user_id = ? and device_id = ?")
|
||||||
|
.bind(self.user_id.as_str())
|
||||||
|
.bind(self.device_id.as_str())
|
||||||
|
.fetch_one(&mut *connection)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
*self.account_info.lock().unwrap() = Some(AccountInfo {
|
||||||
|
account_id: account_id.0,
|
||||||
|
identity_keys: account.identity_keys.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn save_user_helper(
|
async fn save_user_helper(
|
||||||
&self,
|
&self,
|
||||||
mut connection: &mut SqliteConnection,
|
mut connection: &mut SqliteConnection,
|
||||||
|
@ -1607,65 +1674,7 @@ impl CryptoStore for SqliteStore {
|
||||||
|
|
||||||
async fn save_account(&self, account: ReadOnlyAccount) -> Result<()> {
|
async fn save_account(&self, account: ReadOnlyAccount) -> Result<()> {
|
||||||
let mut connection = self.connection.lock().await;
|
let mut connection = self.connection.lock().await;
|
||||||
let pickle = account.pickle(self.get_pickle_mode()).await;
|
self.save_account_helper(&mut connection, account).await
|
||||||
|
|
||||||
query(
|
|
||||||
"INSERT INTO accounts (
|
|
||||||
user_id, device_id, pickle, shared, uploaded_key_count
|
|
||||||
) VALUES (?1, ?2, ?3, ?4, ?5)
|
|
||||||
ON CONFLICT(user_id, device_id) DO UPDATE SET
|
|
||||||
pickle = excluded.pickle,
|
|
||||||
shared = excluded.shared,
|
|
||||||
uploaded_key_count = excluded.uploaded_key_count
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(pickle.user_id.as_str())
|
|
||||||
.bind(pickle.device_id.as_str())
|
|
||||||
.bind(pickle.pickle.as_str())
|
|
||||||
.bind(pickle.shared)
|
|
||||||
.bind(pickle.uploaded_signed_key_count)
|
|
||||||
.execute(&mut *connection)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let account_id: (i64,) =
|
|
||||||
query_as("SELECT id FROM accounts WHERE user_id = ? and device_id = ?")
|
|
||||||
.bind(self.user_id.as_str())
|
|
||||||
.bind(self.device_id.as_str())
|
|
||||||
.fetch_one(&mut *connection)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
*self.account_info.lock().unwrap() = Some(AccountInfo {
|
|
||||||
account_id: account_id.0,
|
|
||||||
identity_keys: account.identity_keys.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn save_identity(&self, identity: PrivateCrossSigningIdentity) -> Result<()> {
|
|
||||||
let account_id = self.account_id().ok_or(CryptoStoreError::AccountUnset)?;
|
|
||||||
|
|
||||||
let pickle = identity.pickle(self.get_pickle_key()).await?;
|
|
||||||
|
|
||||||
let mut connection = self.connection.lock().await;
|
|
||||||
|
|
||||||
query(
|
|
||||||
"INSERT INTO private_identities (
|
|
||||||
account_id, user_id, pickle, shared
|
|
||||||
) VALUES (?1, ?2, ?3, ?4)
|
|
||||||
ON CONFLICT(account_id, user_id) DO UPDATE SET
|
|
||||||
pickle = excluded.pickle,
|
|
||||||
shared = excluded.shared
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(account_id)
|
|
||||||
.bind(pickle.user_id.as_str())
|
|
||||||
.bind(pickle.pickle)
|
|
||||||
.bind(pickle.shared)
|
|
||||||
.execute(&mut *connection)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>> {
|
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>> {
|
||||||
|
@ -1702,6 +1711,14 @@ impl CryptoStore for SqliteStore {
|
||||||
let mut connection = self.connection.lock().await;
|
let mut connection = self.connection.lock().await;
|
||||||
let mut transaction = connection.begin().await?;
|
let mut transaction = connection.begin().await?;
|
||||||
|
|
||||||
|
if let Some(account) = changes.account {
|
||||||
|
self.save_account_helper(&mut transaction, account).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(identity) = changes.private_identity {
|
||||||
|
self.save_identity(&mut transaction, identity).await?;
|
||||||
|
}
|
||||||
|
|
||||||
self.save_sessions_helper(&mut transaction, &changes.sessions)
|
self.save_sessions_helper(&mut transaction, &changes.sessions)
|
||||||
.await?;
|
.await?;
|
||||||
self.save_inbound_group_sessions(&mut transaction, &changes.inbound_group_sessions)
|
self.save_inbound_group_sessions(&mut transaction, &changes.inbound_group_sessions)
|
||||||
|
@ -1718,7 +1735,6 @@ impl CryptoStore for SqliteStore {
|
||||||
.await?;
|
.await?;
|
||||||
self.save_user_identities(&mut transaction, &changes.identities.changed)
|
self.save_user_identities(&mut transaction, &changes.identities.changed)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
self.save_olm_hashses(&mut transaction, &changes.message_hashes)
|
self.save_olm_hashses(&mut transaction, &changes.message_hashes)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -2409,7 +2425,12 @@ mod test {
|
||||||
assert!(store.load_identity().await.unwrap().is_none());
|
assert!(store.load_identity().await.unwrap().is_none());
|
||||||
let identity = PrivateCrossSigningIdentity::new((&*store.user_id).clone()).await;
|
let identity = PrivateCrossSigningIdentity::new((&*store.user_id).clone()).await;
|
||||||
|
|
||||||
store.save_identity(identity.clone()).await.unwrap();
|
let changes = Changes {
|
||||||
|
private_identity: Some(identity.clone()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
store.save_changes(changes).await.unwrap();
|
||||||
let loaded_identity = store.load_identity().await.unwrap().unwrap();
|
let loaded_identity = store.load_identity().await.unwrap().unwrap();
|
||||||
assert_eq!(identity.user_id(), loaded_identity.user_id());
|
assert_eq!(identity.user_id(), loaded_identity.user_id());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue