2020-07-21 08:38:14 +00:00
|
|
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2020-07-30 23:22:48 +00:00
|
|
|
use std::{collections::BTreeMap, fmt, sync::Arc};
|
2020-07-21 08:38:14 +00:00
|
|
|
|
2020-07-21 10:03:05 +00:00
|
|
|
use matrix_sdk_common::{
|
|
|
|
events::{
|
|
|
|
room::encrypted::{CiphertextInfo, EncryptedEventContent, OlmV1Curve25519AesSha2Content},
|
|
|
|
EventType,
|
|
|
|
},
|
2020-08-10 10:39:00 +00:00
|
|
|
identifiers::{DeviceId, DeviceKeyAlgorithm, UserId},
|
2020-09-02 09:45:35 +00:00
|
|
|
instant::{Duration, Instant},
|
2020-07-21 10:03:05 +00:00
|
|
|
locks::Mutex,
|
|
|
|
};
|
2020-08-02 12:05:43 +00:00
|
|
|
use olm_rs::{errors::OlmSessionError, session::OlmSession, PicklingMode};
|
2020-09-02 09:45:35 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2020-08-02 12:05:43 +00:00
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
|
|
|
use super::IdentityKeys;
|
|
|
|
use crate::{
|
2020-09-02 10:11:06 +00:00
|
|
|
error::{EventError, OlmResult, SessionUnpicklingError},
|
2020-08-17 14:17:28 +00:00
|
|
|
ReadOnlyDevice,
|
2020-08-02 12:05:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
pub use olm_rs::{
|
|
|
|
session::{OlmMessage, PreKeyMessage},
|
|
|
|
utility::OlmUtility,
|
|
|
|
};
|
2020-07-21 10:03:05 +00:00
|
|
|
|
2020-07-21 08:38:14 +00:00
|
|
|
/// Cryptographic session that enables secure communication between two
|
|
|
|
/// `Account`s
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Session {
|
2020-07-21 10:40:23 +00:00
|
|
|
pub(crate) user_id: Arc<UserId>,
|
|
|
|
pub(crate) device_id: Arc<Box<DeviceId>>,
|
|
|
|
pub(crate) our_identity_keys: Arc<IdentityKeys>,
|
2020-07-21 08:38:14 +00:00
|
|
|
pub(crate) inner: Arc<Mutex<OlmSession>>,
|
|
|
|
pub(crate) session_id: Arc<String>,
|
|
|
|
pub(crate) sender_key: Arc<String>,
|
|
|
|
pub(crate) creation_time: Arc<Instant>,
|
|
|
|
pub(crate) last_use_time: Arc<Instant>,
|
|
|
|
}
|
|
|
|
|
2020-08-11 13:49:04 +00:00
|
|
|
#[cfg(not(tarpaulin_include))]
|
2020-07-21 08:38:14 +00:00
|
|
|
impl fmt::Debug for Session {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.debug_struct("Session")
|
|
|
|
.field("session_id", &self.session_id())
|
|
|
|
.field("sender_key", &self.sender_key)
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Session {
|
|
|
|
/// Decrypt the given Olm message.
|
|
|
|
///
|
|
|
|
/// Returns the decrypted plaintext or an `OlmSessionError` if decryption
|
|
|
|
/// failed.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `message` - The Olm message that should be decrypted.
|
|
|
|
pub async fn decrypt(&mut self, message: OlmMessage) -> Result<String, OlmSessionError> {
|
|
|
|
let plaintext = self.inner.lock().await.decrypt(message)?;
|
|
|
|
self.last_use_time = Arc::new(Instant::now());
|
|
|
|
Ok(plaintext)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Encrypt the given plaintext as a OlmMessage.
|
|
|
|
///
|
|
|
|
/// Returns the encrypted Olm message.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `plaintext` - The plaintext that should be encrypted.
|
2020-07-21 10:03:05 +00:00
|
|
|
pub(crate) async fn encrypt_helper(&mut self, plaintext: &str) -> OlmMessage {
|
2020-07-21 08:38:14 +00:00
|
|
|
let message = self.inner.lock().await.encrypt(plaintext);
|
|
|
|
self.last_use_time = Arc::new(Instant::now());
|
|
|
|
message
|
|
|
|
}
|
|
|
|
|
2020-07-21 10:03:05 +00:00
|
|
|
/// Encrypt the given event content content as an m.room.encrypted event
|
|
|
|
/// content.
|
2020-07-21 11:04:51 +00:00
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `recipient_device` - The device for which this message is going to be
|
|
|
|
/// encrypted, this needs to be the device that was used to create this
|
|
|
|
/// session with.
|
|
|
|
///
|
|
|
|
/// * `event_type` - The type of the event.
|
|
|
|
///
|
|
|
|
/// * `content` - The content of the event.
|
2020-07-21 10:03:05 +00:00
|
|
|
pub async fn encrypt(
|
|
|
|
&mut self,
|
2020-08-17 14:17:28 +00:00
|
|
|
recipient_device: &ReadOnlyDevice,
|
2020-07-21 10:03:05 +00:00
|
|
|
event_type: EventType,
|
|
|
|
content: Value,
|
|
|
|
) -> OlmResult<EncryptedEventContent> {
|
|
|
|
let recipient_signing_key = recipient_device
|
2020-08-10 10:39:00 +00:00
|
|
|
.get_key(DeviceKeyAlgorithm::Ed25519)
|
2020-07-21 10:03:05 +00:00
|
|
|
.ok_or(EventError::MissingSigningKey)?;
|
|
|
|
|
|
|
|
let payload = json!({
|
2020-07-21 10:46:06 +00:00
|
|
|
"sender": self.user_id.as_str(),
|
|
|
|
"sender_device": self.device_id.as_ref(),
|
2020-07-21 10:03:05 +00:00
|
|
|
"keys": {
|
2020-07-21 10:46:06 +00:00
|
|
|
"ed25519": self.our_identity_keys.ed25519(),
|
2020-07-21 10:03:05 +00:00
|
|
|
},
|
|
|
|
"recipient": recipient_device.user_id(),
|
|
|
|
"recipient_keys": {
|
|
|
|
"ed25519": recipient_signing_key,
|
|
|
|
},
|
|
|
|
"type": event_type,
|
|
|
|
"content": content,
|
|
|
|
});
|
|
|
|
|
|
|
|
let plaintext = cjson::to_string(&payload)
|
|
|
|
.unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", payload)));
|
|
|
|
|
|
|
|
let ciphertext = self.encrypt_helper(&plaintext).await.to_tuple();
|
|
|
|
|
|
|
|
let message_type = ciphertext.0;
|
|
|
|
let ciphertext = CiphertextInfo::new(ciphertext.1, (message_type as u32).into());
|
|
|
|
|
|
|
|
let mut content = BTreeMap::new();
|
2020-07-21 10:46:06 +00:00
|
|
|
content.insert((&*self.sender_key).to_owned(), ciphertext);
|
2020-07-21 10:03:05 +00:00
|
|
|
|
|
|
|
Ok(EncryptedEventContent::OlmV1Curve25519AesSha2(
|
|
|
|
OlmV1Curve25519AesSha2Content::new(
|
|
|
|
content,
|
2020-07-21 10:46:06 +00:00
|
|
|
self.our_identity_keys.curve25519().to_string(),
|
2020-07-21 10:03:05 +00:00
|
|
|
),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2020-07-21 08:38:14 +00:00
|
|
|
/// Check if a pre-key Olm message was encrypted for this session.
|
|
|
|
///
|
|
|
|
/// Returns true if it matches, false if not and a OlmSessionError if there
|
|
|
|
/// was an error checking if it matches.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `their_identity_key` - The identity/curve25519 key of the account
|
|
|
|
/// that encrypted this Olm message.
|
|
|
|
///
|
|
|
|
/// * `message` - The pre-key Olm message that should be checked.
|
|
|
|
pub async fn matches(
|
|
|
|
&self,
|
|
|
|
their_identity_key: &str,
|
|
|
|
message: PreKeyMessage,
|
|
|
|
) -> Result<bool, OlmSessionError> {
|
|
|
|
self.inner
|
|
|
|
.lock()
|
|
|
|
.await
|
|
|
|
.matches_inbound_session_from(their_identity_key, message)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the unique identifier for this session.
|
|
|
|
pub fn session_id(&self) -> &str {
|
|
|
|
&self.session_id
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Store the session as a base64 encoded string.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `pickle_mode` - The mode that was used to pickle the session, either
|
|
|
|
/// an unencrypted mode or an encrypted using passphrase.
|
2020-09-02 09:45:35 +00:00
|
|
|
pub async fn pickle(&self, pickle_mode: PicklingMode) -> PickledSession {
|
|
|
|
let pickle = self.inner.lock().await.pickle(pickle_mode);
|
|
|
|
|
|
|
|
PickledSession {
|
|
|
|
pickle: SessionPickle::from(pickle),
|
|
|
|
sender_key: self.sender_key.to_string(),
|
|
|
|
// FIXME this should use the duration from the unix epoch.
|
|
|
|
creation_time: self.creation_time.elapsed(),
|
|
|
|
last_use_time: self.last_use_time.elapsed(),
|
|
|
|
}
|
2020-07-21 08:38:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Restore a Session from a previously pickled string.
|
|
|
|
///
|
2020-09-02 10:11:06 +00:00
|
|
|
/// Returns the restored Olm Session or a `SessionUnpicklingError` if there
|
|
|
|
/// was an error.
|
2020-07-21 08:38:14 +00:00
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
2020-07-21 10:59:15 +00:00
|
|
|
/// * `user_id` - Our own user id that the session belongs to.
|
|
|
|
///
|
|
|
|
/// * `device_id` - Our own device id that the session belongs to.
|
|
|
|
///
|
|
|
|
/// * `our_idenity_keys` - An clone of the Arc to our own identity keys.
|
|
|
|
///
|
2020-09-02 09:45:35 +00:00
|
|
|
/// * `pickle` - The pickled version of the `Session`.
|
2020-07-21 08:38:14 +00:00
|
|
|
///
|
|
|
|
/// * `pickle_mode` - The mode that was used to pickle the session, either
|
|
|
|
/// an unencrypted mode or an encrypted using passphrase.
|
|
|
|
pub fn from_pickle(
|
2020-07-21 10:40:23 +00:00
|
|
|
user_id: Arc<UserId>,
|
|
|
|
device_id: Arc<Box<DeviceId>>,
|
|
|
|
our_identity_keys: Arc<IdentityKeys>,
|
2020-09-02 09:45:35 +00:00
|
|
|
pickle: PickledSession,
|
2020-07-21 08:38:14 +00:00
|
|
|
pickle_mode: PicklingMode,
|
2020-09-02 10:11:06 +00:00
|
|
|
) -> Result<Self, SessionUnpicklingError> {
|
2020-09-02 09:45:35 +00:00
|
|
|
let session = OlmSession::unpickle(pickle.pickle.0, pickle_mode)?;
|
2020-07-21 08:38:14 +00:00
|
|
|
let session_id = session.session_id();
|
|
|
|
|
2020-09-02 09:45:35 +00:00
|
|
|
// FIXME this should use the UNIX epoch.
|
|
|
|
let now = Instant::now();
|
|
|
|
|
2020-09-02 10:11:06 +00:00
|
|
|
let creation_time = now
|
|
|
|
.checked_sub(pickle.creation_time)
|
|
|
|
.ok_or(SessionUnpicklingError::SessionTimestampError)?;
|
|
|
|
let last_use_time = now
|
|
|
|
.checked_sub(pickle.last_use_time)
|
|
|
|
.ok_or(SessionUnpicklingError::SessionTimestampError)?;
|
2020-09-02 09:45:35 +00:00
|
|
|
|
2020-07-21 08:38:14 +00:00
|
|
|
Ok(Session {
|
2020-07-21 10:40:23 +00:00
|
|
|
user_id,
|
|
|
|
device_id,
|
|
|
|
our_identity_keys,
|
2020-07-21 08:38:14 +00:00
|
|
|
inner: Arc::new(Mutex::new(session)),
|
|
|
|
session_id: Arc::new(session_id),
|
2020-09-02 09:45:35 +00:00
|
|
|
sender_key: Arc::new(pickle.sender_key),
|
2020-07-21 08:38:14 +00:00
|
|
|
creation_time: Arc::new(creation_time),
|
|
|
|
last_use_time: Arc::new(last_use_time),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Session {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.session_id() == other.session_id()
|
|
|
|
}
|
|
|
|
}
|
2020-09-02 09:45:35 +00:00
|
|
|
|
|
|
|
/// A pickled version of a `Session`.
|
|
|
|
///
|
|
|
|
/// Holds all the information that needs to be stored in a database to restore
|
|
|
|
/// a Session.
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub struct PickledSession {
|
|
|
|
/// The pickle string holding the Olm Session.
|
|
|
|
pub pickle: SessionPickle,
|
|
|
|
/// The curve25519 key of the other user that we share this session with.
|
|
|
|
pub sender_key: String,
|
|
|
|
/// The relative time elapsed since the session was created.
|
|
|
|
pub creation_time: Duration,
|
|
|
|
/// The relative time elapsed since the session was last used.
|
|
|
|
pub last_use_time: Duration,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The typed representation of a base64 encoded string of the Olm Session pickle.
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub struct SessionPickle(String);
|
|
|
|
|
|
|
|
impl From<String> for SessionPickle {
|
|
|
|
fn from(picle_string: String) -> Self {
|
|
|
|
SessionPickle(picle_string)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SessionPickle {
|
|
|
|
/// Get the string representation of the pickle.
|
|
|
|
pub fn as_str(&self) -> &str {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|