Merge branch 'crypto-improvements' into new-state-store
commit
3a1eeb6a16
|
@ -9,10 +9,8 @@ use serde_json::json;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
self,
|
self, api::r0::uiaa::AuthData, identifiers::UserId, Client, ClientConfig, LoopCtrl,
|
||||||
api::r0::uiaa::AuthData,
|
SyncSettings,
|
||||||
identifiers::{user_id, UserId},
|
|
||||||
Client, ClientConfig, LoopCtrl, SyncSettings,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> AuthData<'a> {
|
fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> AuthData<'a> {
|
||||||
|
@ -35,7 +33,7 @@ fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> Aut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bootstrap(client: Client) {
|
async fn bootstrap(client: Client, user_id: UserId, password: String) {
|
||||||
println!("Bootstrapping a new cross signing identity, press enter to continue.");
|
println!("Bootstrapping a new cross signing identity, press enter to continue.");
|
||||||
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
@ -46,11 +44,7 @@ async fn bootstrap(client: Client) {
|
||||||
|
|
||||||
if let Err(e) = client.bootstrap_cross_signing(None).await {
|
if let Err(e) = client.bootstrap_cross_signing(None).await {
|
||||||
if let Some(response) = e.uiaa_response() {
|
if let Some(response) = e.uiaa_response() {
|
||||||
let auth_data = auth_data(
|
let auth_data = auth_data(&user_id, &password, response.session.as_deref());
|
||||||
&user_id!("@example:localhost"),
|
|
||||||
"wordpass",
|
|
||||||
response.session.as_deref(),
|
|
||||||
);
|
|
||||||
client
|
client
|
||||||
.bootstrap_cross_signing(Some(auth_data))
|
.bootstrap_cross_signing(Some(auth_data))
|
||||||
.await
|
.await
|
||||||
|
@ -73,10 +67,11 @@ async fn login(
|
||||||
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
|
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
|
||||||
let client = Client::new_with_config(homeserver_url, client_config).unwrap();
|
let client = Client::new_with_config(homeserver_url, client_config).unwrap();
|
||||||
|
|
||||||
client
|
let response = client
|
||||||
.login(username, password, None, Some("rust-sdk"))
|
.login(username, password, None, Some("rust-sdk"))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let user_id = &response.user_id;
|
||||||
let client_ref = &client;
|
let client_ref = &client;
|
||||||
let asked = AtomicBool::new(false);
|
let asked = AtomicBool::new(false);
|
||||||
let asked_ref = &asked;
|
let asked_ref = &asked;
|
||||||
|
@ -85,10 +80,16 @@ async fn login(
|
||||||
.sync_with_callback(SyncSettings::new(), |_| async move {
|
.sync_with_callback(SyncSettings::new(), |_| async move {
|
||||||
let asked = asked_ref;
|
let asked = asked_ref;
|
||||||
let client = &client_ref;
|
let client = &client_ref;
|
||||||
|
let user_id = &user_id;
|
||||||
|
let password = &password;
|
||||||
|
|
||||||
// Wait for sync to be done then ask the user to bootstrap.
|
// Wait for sync to be done then ask the user to bootstrap.
|
||||||
if !asked.load(Ordering::SeqCst) {
|
if !asked.load(Ordering::SeqCst) {
|
||||||
tokio::spawn(bootstrap((*client).clone()));
|
tokio::spawn(bootstrap(
|
||||||
|
(*client).clone(),
|
||||||
|
(*user_id).clone(),
|
||||||
|
password.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
asked.store(true, Ordering::SeqCst);
|
asked.store(true, Ordering::SeqCst);
|
||||||
|
|
|
@ -1451,10 +1451,10 @@ impl Client {
|
||||||
sync_settings: SyncSettings<'_>,
|
sync_settings: SyncSettings<'_>,
|
||||||
) -> Result<sync_events::Response> {
|
) -> Result<sync_events::Response> {
|
||||||
let request = assign!(sync_events::Request::new(), {
|
let request = assign!(sync_events::Request::new(), {
|
||||||
filter: sync_settings.filter,
|
filter: sync_settings.filter.as_ref(),
|
||||||
since: sync_settings.token.as_deref(),
|
since: sync_settings.token.as_deref(),
|
||||||
full_state: sync_settings.full_state,
|
full_state: sync_settings.full_state,
|
||||||
set_presence: PresenceState::Online,
|
set_presence: &PresenceState::Online,
|
||||||
timeout: sync_settings.timeout,
|
timeout: sync_settings.timeout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1551,7 +1551,7 @@ impl Client {
|
||||||
C: Future<Output = LoopCtrl>,
|
C: Future<Output = LoopCtrl>,
|
||||||
{
|
{
|
||||||
let mut sync_settings = sync_settings;
|
let mut sync_settings = sync_settings;
|
||||||
let filter = sync_settings.filter;
|
let filter = sync_settings.filter.clone();
|
||||||
let mut last_sync_time: Option<Instant> = None;
|
let mut last_sync_time: Option<Instant> = None;
|
||||||
|
|
||||||
if sync_settings.token.is_none() {
|
if sync_settings.token.is_none() {
|
||||||
|
@ -1600,6 +1600,15 @@ impl Client {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
OutgoingRequests::SignatureUpload(request) => {
|
||||||
|
// TODO remove this unwrap.
|
||||||
|
if let Ok(resp) = self.send(request.clone()).await {
|
||||||
|
self.base_client
|
||||||
|
.mark_request_as_sent(&r.request_id(), &resp)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1627,7 +1636,7 @@ impl Client {
|
||||||
.expect("No sync token found after initial sync"),
|
.expect("No sync token found after initial sync"),
|
||||||
);
|
);
|
||||||
if let Some(f) = filter.as_ref() {
|
if let Some(f) = filter.as_ref() {
|
||||||
sync_settings = sync_settings.filter(*f);
|
sync_settings = sync_settings.filter(f.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1807,7 +1816,58 @@ impl Client {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO
|
/// Create and upload a new cross signing identity.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `auth_data` - This request requires user interactive auth, the first
|
||||||
|
/// request needs to set this to `None` and will always fail with an
|
||||||
|
/// `UiaaResponse`. The response will contain information for the
|
||||||
|
/// interactive auth and the same request needs to be made but this time
|
||||||
|
/// with some `auth_data` provided.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```no_run
|
||||||
|
/// # use std::{convert::TryFrom, collections::BTreeMap};
|
||||||
|
/// # use matrix_sdk::{Client, identifiers::UserId};
|
||||||
|
/// # use matrix_sdk::api::r0::uiaa::AuthData;
|
||||||
|
/// # use url::Url;
|
||||||
|
/// # use futures::executor::block_on;
|
||||||
|
/// # use serde_json::json;
|
||||||
|
/// # let user_id = UserId::try_from("@alice:example.org").unwrap();
|
||||||
|
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||||
|
/// # let client = Client::new(homeserver).unwrap();
|
||||||
|
/// # block_on(async {
|
||||||
|
///
|
||||||
|
/// fn auth_data<'a>(user: &UserId, password: &str, session: Option<&'a str>) -> AuthData<'a> {
|
||||||
|
/// let mut auth_parameters = BTreeMap::new();
|
||||||
|
/// let identifier = json!({
|
||||||
|
/// "type": "m.id.user",
|
||||||
|
/// "user": user,
|
||||||
|
/// });
|
||||||
|
/// auth_parameters.insert("identifier".to_owned(), identifier);
|
||||||
|
/// auth_parameters.insert("password".to_owned(), password.to_owned().into());
|
||||||
|
/// // This is needed because of https://github.com/matrix-org/synapse/issues/5665
|
||||||
|
/// auth_parameters.insert("user".to_owned(), user.as_str().into());
|
||||||
|
/// AuthData::DirectRequest {
|
||||||
|
/// kind: "m.login.password",
|
||||||
|
/// auth_parameters,
|
||||||
|
/// session,
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// if let Err(e) = client.bootstrap_cross_signing(None).await {
|
||||||
|
/// if let Some(response) = e.uiaa_response() {
|
||||||
|
/// let auth_data = auth_data(&user_id, "wordpass", response.session.as_deref());
|
||||||
|
/// client
|
||||||
|
/// .bootstrap_cross_signing(Some(auth_data))
|
||||||
|
/// .await
|
||||||
|
/// .expect("Couldn't bootstrap cross signing")
|
||||||
|
/// } else {
|
||||||
|
/// panic!("Error durign cross signing bootstrap {:#?}", e);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// # })
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
#[cfg_attr(feature = "docs", doc(cfg(encryption)))]
|
||||||
pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData<'_>>) -> Result<()> {
|
pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData<'_>>) -> Result<()> {
|
||||||
|
@ -1819,8 +1879,6 @@ impl Client {
|
||||||
|
|
||||||
let (request, signature_request) = olm.bootstrap_cross_signing(false).await?;
|
let (request, signature_request) = olm.bootstrap_cross_signing(false).await?;
|
||||||
|
|
||||||
println!("HELLOOO MAKING REQUEST {:#?}", request);
|
|
||||||
|
|
||||||
let request = UploadSigningKeysRequest {
|
let request = UploadSigningKeysRequest {
|
||||||
auth: auth_data,
|
auth: auth_data,
|
||||||
master_key: request.master_key,
|
master_key: request.master_key,
|
||||||
|
|
|
@ -38,13 +38,19 @@ impl Sas {
|
||||||
|
|
||||||
/// Confirm that the short auth strings match on both sides.
|
/// Confirm that the short auth strings match on both sides.
|
||||||
pub async fn confirm(&self) -> Result<()> {
|
pub async fn confirm(&self) -> Result<()> {
|
||||||
if let Some(req) = self.inner.confirm().await? {
|
let (to_device, signature) = self.inner.confirm().await?;
|
||||||
|
|
||||||
|
if let Some(req) = to_device {
|
||||||
let txn_id_string = req.txn_id_string();
|
let txn_id_string = req.txn_id_string();
|
||||||
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
|
let request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages);
|
||||||
|
|
||||||
self.http_client.send(request).await?;
|
self.http_client.send(request).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(s) = signature {
|
||||||
|
self.http_client.send(s).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ use matrix_sdk_common::locks::Mutex;
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
api::r0 as api,
|
api::r0 as api,
|
||||||
events::{
|
events::{
|
||||||
room::member::MemberEventContent, AnyStrippedStateEvent, AnySyncRoomEvent,
|
room::member::MemberEventContent, AnyStrippedStateEvent, AnySyncMessageEvent,
|
||||||
AnySyncStateEvent, SyncStateEvent, AnySyncMessageEvent
|
AnySyncRoomEvent, AnySyncStateEvent, SyncStateEvent,
|
||||||
},
|
},
|
||||||
identifiers::{RoomId, UserId},
|
identifiers::{RoomId, UserId},
|
||||||
locks::RwLock,
|
locks::RwLock,
|
||||||
|
@ -51,11 +51,10 @@ use matrix_sdk_crypto::{
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
use crate::store::RoomType;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Result,
|
error::Result,
|
||||||
session::Session,
|
session::Session,
|
||||||
store::{Room, StateChanges, Store},
|
store::{Room, RoomType, StateChanges, Store},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Token = String;
|
pub type Token = String;
|
||||||
|
@ -104,7 +103,9 @@ pub fn hoist_and_deserialize_state_event(
|
||||||
Ok(ev)
|
Ok(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hoist_room_event_prev_content(event: &Raw<AnySyncRoomEvent>) -> StdResult<AnySyncRoomEvent, serde_json::Error> {
|
fn hoist_room_event_prev_content(
|
||||||
|
event: &Raw<AnySyncRoomEvent>,
|
||||||
|
) -> StdResult<AnySyncRoomEvent, serde_json::Error> {
|
||||||
let prev_content = serde_json::from_str::<AdditionalEventData>(event.json().get())
|
let prev_content = serde_json::from_str::<AdditionalEventData>(event.json().get())
|
||||||
.map(|more_unsigned| more_unsigned.unsigned)
|
.map(|more_unsigned| more_unsigned.unsigned)
|
||||||
.map(|additional| additional.prev_content)?;
|
.map(|additional| additional.prev_content)?;
|
||||||
|
|
|
@ -6,20 +6,17 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use matrix_sdk_common::Raw;
|
|
||||||
use matrix_sdk_common::events::room::canonical_alias::CanonicalAliasEventContent;
|
|
||||||
use matrix_sdk_common::events::room::name::NameEventContent;
|
|
||||||
use matrix_sdk_common::identifiers::RoomAliasId;
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
api::r0::sync::sync_events::RoomSummary as RumaSummary,
|
api::r0::sync::sync_events::RoomSummary as RumaSummary,
|
||||||
events::{
|
events::{
|
||||||
room::{
|
room::{
|
||||||
create::CreateEventContent, encryption::EncryptionEventContent,
|
canonical_alias::CanonicalAliasEventContent, create::CreateEventContent,
|
||||||
member::MemberEventContent,
|
encryption::EncryptionEventContent, member::MemberEventContent, name::NameEventContent,
|
||||||
},
|
},
|
||||||
AnySyncStateEvent, EventContent, SyncStateEvent,
|
AnySyncStateEvent, EventContent, SyncStateEvent,
|
||||||
},
|
},
|
||||||
identifiers::{RoomId, UserId},
|
identifiers::{RoomAliasId, RoomId, UserId},
|
||||||
|
Raw,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -36,8 +33,7 @@ pub struct Store {
|
||||||
room_summaries: Tree,
|
room_summaries: Tree,
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::Session;
|
use crate::{client::hoist_and_deserialize_state_event, Session};
|
||||||
use crate::client::hoist_and_deserialize_state_event;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct StateChanges {
|
pub struct StateChanges {
|
||||||
|
@ -81,7 +77,8 @@ impl StateChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_room(&mut self, room: InnerSummary) {
|
pub fn add_room(&mut self, room: InnerSummary) {
|
||||||
self.room_summaries.insert(room.room_id.as_ref().to_owned(), room);
|
self.room_summaries
|
||||||
|
.insert(room.room_id.as_ref().to_owned(), room);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_state_event(&mut self, room_id: &RoomId, event: AnySyncStateEvent) {
|
pub fn add_state_event(&mut self, room_id: &RoomId, event: AnySyncStateEvent) {
|
||||||
|
@ -169,7 +166,11 @@ impl Room {
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_state_event(&self, summary: &mut InnerSummary, event: &AnySyncStateEvent) -> bool {
|
pub fn handle_state_event(
|
||||||
|
&self,
|
||||||
|
summary: &mut InnerSummary,
|
||||||
|
event: &AnySyncStateEvent,
|
||||||
|
) -> bool {
|
||||||
match event {
|
match event {
|
||||||
AnySyncStateEvent::RoomEncryption(encryption) => {
|
AnySyncStateEvent::RoomEncryption(encryption) => {
|
||||||
info!("MARKING ROOM {} AS ENCRYPTED", self.room_id);
|
info!("MARKING ROOM {} AS ENCRYPTED", self.room_id);
|
||||||
|
|
|
@ -40,7 +40,7 @@ use serde_json::{json, Value};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
olm::{InboundGroupSession, Session},
|
olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session},
|
||||||
store::{Changes, DeviceChanges},
|
store::{Changes, DeviceChanges},
|
||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -62,7 +62,7 @@ pub struct ReadOnlyDevice {
|
||||||
device_id: Arc<Box<DeviceId>>,
|
device_id: Arc<Box<DeviceId>>,
|
||||||
algorithms: Arc<Vec<EventEncryptionAlgorithm>>,
|
algorithms: Arc<Vec<EventEncryptionAlgorithm>>,
|
||||||
keys: Arc<BTreeMap<DeviceKeyId, String>>,
|
keys: Arc<BTreeMap<DeviceKeyId, String>>,
|
||||||
signatures: Arc<BTreeMap<UserId, BTreeMap<DeviceKeyId, String>>>,
|
pub(crate) signatures: Arc<BTreeMap<UserId, BTreeMap<DeviceKeyId, String>>>,
|
||||||
display_name: Arc<Option<String>>,
|
display_name: Arc<Option<String>>,
|
||||||
deleted: Arc<AtomicBool>,
|
deleted: Arc<AtomicBool>,
|
||||||
trust_state: Arc<Atomic<LocalTrust>>,
|
trust_state: Arc<Atomic<LocalTrust>>,
|
||||||
|
@ -72,6 +72,7 @@ pub struct ReadOnlyDevice {
|
||||||
/// A device represents a E2EE capable client of an user.
|
/// A device represents a E2EE capable client of an user.
|
||||||
pub struct Device {
|
pub struct Device {
|
||||||
pub(crate) inner: ReadOnlyDevice,
|
pub(crate) inner: ReadOnlyDevice,
|
||||||
|
pub(crate) private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||||
pub(crate) verification_machine: VerificationMachine,
|
pub(crate) verification_machine: VerificationMachine,
|
||||||
pub(crate) own_identity: Option<OwnUserIdentity>,
|
pub(crate) own_identity: Option<OwnUserIdentity>,
|
||||||
pub(crate) device_owner_identity: Option<UserIdentities>,
|
pub(crate) device_owner_identity: Option<UserIdentities>,
|
||||||
|
@ -179,6 +180,7 @@ impl Device {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UserDevices {
|
pub struct UserDevices {
|
||||||
pub(crate) inner: HashMap<DeviceIdBox, ReadOnlyDevice>,
|
pub(crate) inner: HashMap<DeviceIdBox, ReadOnlyDevice>,
|
||||||
|
pub(crate) private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||||
pub(crate) verification_machine: VerificationMachine,
|
pub(crate) verification_machine: VerificationMachine,
|
||||||
pub(crate) own_identity: Option<OwnUserIdentity>,
|
pub(crate) own_identity: Option<OwnUserIdentity>,
|
||||||
pub(crate) device_owner_identity: Option<UserIdentities>,
|
pub(crate) device_owner_identity: Option<UserIdentities>,
|
||||||
|
@ -189,6 +191,7 @@ impl UserDevices {
|
||||||
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
|
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
|
||||||
self.inner.get(device_id).map(|d| Device {
|
self.inner.get(device_id).map(|d| Device {
|
||||||
inner: d.clone(),
|
inner: d.clone(),
|
||||||
|
private_identity: self.private_identity.clone(),
|
||||||
verification_machine: self.verification_machine.clone(),
|
verification_machine: self.verification_machine.clone(),
|
||||||
own_identity: self.own_identity.clone(),
|
own_identity: self.own_identity.clone(),
|
||||||
device_owner_identity: self.device_owner_identity.clone(),
|
device_owner_identity: self.device_owner_identity.clone(),
|
||||||
|
@ -204,6 +207,7 @@ impl UserDevices {
|
||||||
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
|
pub fn devices(&self) -> impl Iterator<Item = Device> + '_ {
|
||||||
self.inner.values().map(move |d| Device {
|
self.inner.values().map(move |d| Device {
|
||||||
inner: d.clone(),
|
inner: d.clone(),
|
||||||
|
private_identity: self.private_identity.clone(),
|
||||||
verification_machine: self.verification_machine.clone(),
|
verification_machine: self.verification_machine.clone(),
|
||||||
own_identity: self.own_identity.clone(),
|
own_identity: self.own_identity.clone(),
|
||||||
device_owner_identity: self.device_owner_identity.clone(),
|
device_owner_identity: self.device_owner_identity.clone(),
|
||||||
|
@ -438,6 +442,17 @@ impl ReadOnlyDevice {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_device_keys(&self) -> DeviceKeys {
|
||||||
|
DeviceKeys {
|
||||||
|
user_id: self.user_id().clone(),
|
||||||
|
device_id: self.device_id().into(),
|
||||||
|
keys: self.keys().clone(),
|
||||||
|
algorithms: self.algorithms().to_vec(),
|
||||||
|
signatures: self.signatures().to_owned(),
|
||||||
|
unsigned: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn as_signature_message(&self) -> Value {
|
pub(crate) fn as_signature_message(&self) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"user_id": &*self.user_id,
|
"user_id": &*self.user_id,
|
||||||
|
|
|
@ -415,9 +415,10 @@ pub(crate) mod test {
|
||||||
let user_id = Arc::new(user_id());
|
let user_id = Arc::new(user_id());
|
||||||
let account = ReadOnlyAccount::new(&user_id, &device_id());
|
let account = ReadOnlyAccount::new(&user_id, &device_id());
|
||||||
let store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(MemoryStore::new()));
|
let store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(MemoryStore::new()));
|
||||||
let verification = VerificationMachine::new(account.clone(), identity, store);
|
let verification = VerificationMachine::new(account.clone(), identity.clone(), store);
|
||||||
let store = Store::new(
|
let store = Store::new(
|
||||||
user_id.clone(),
|
user_id.clone(),
|
||||||
|
identity,
|
||||||
Arc::new(Box::new(MemoryStore::new())),
|
Arc::new(Box::new(MemoryStore::new())),
|
||||||
verification,
|
verification,
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,6 +29,8 @@ use matrix_sdk_common::{
|
||||||
identifiers::{DeviceKeyId, UserId},
|
identifiers::{DeviceKeyId, UserId},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::olm::PrivateCrossSigningIdentity;
|
||||||
use crate::{error::SignatureError, olm::Utility, ReadOnlyDevice};
|
use crate::{error::SignatureError, olm::Utility, ReadOnlyDevice};
|
||||||
|
|
||||||
/// Wrapper for a cross signing key marking it as the master key.
|
/// Wrapper for a cross signing key marking it as the master key.
|
||||||
|
@ -278,7 +280,10 @@ impl UserSigningPubkey {
|
||||||
///
|
///
|
||||||
/// Returns an empty result if the signature check succeeded, otherwise a
|
/// Returns an empty result if the signature check succeeded, otherwise a
|
||||||
/// SignatureError indicating why the check failed.
|
/// SignatureError indicating why the check failed.
|
||||||
fn verify_master_key(&self, master_key: &MasterPubkey) -> Result<(), SignatureError> {
|
pub(crate) fn verify_master_key(
|
||||||
|
&self,
|
||||||
|
master_key: &MasterPubkey,
|
||||||
|
) -> Result<(), SignatureError> {
|
||||||
let (key_id, key) = self
|
let (key_id, key) = self
|
||||||
.0
|
.0
|
||||||
.keys
|
.keys
|
||||||
|
@ -326,7 +331,7 @@ impl SelfSigningPubkey {
|
||||||
///
|
///
|
||||||
/// Returns an empty result if the signature check succeeded, otherwise a
|
/// Returns an empty result if the signature check succeeded, otherwise a
|
||||||
/// SignatureError indicating why the check failed.
|
/// SignatureError indicating why the check failed.
|
||||||
fn verify_device(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
|
pub(crate) fn verify_device(&self, device: &ReadOnlyDevice) -> Result<(), SignatureError> {
|
||||||
let (key_id, key) = self
|
let (key_id, key) = self
|
||||||
.0
|
.0
|
||||||
.keys
|
.keys
|
||||||
|
@ -443,7 +448,7 @@ impl PartialEq for UserIdentities {
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct UserIdentity {
|
pub struct UserIdentity {
|
||||||
user_id: Arc<UserId>,
|
user_id: Arc<UserId>,
|
||||||
master_key: MasterPubkey,
|
pub(crate) master_key: MasterPubkey,
|
||||||
self_signing_key: SelfSigningPubkey,
|
self_signing_key: SelfSigningPubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,6 +476,32 @@ impl UserIdentity {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub async fn from_private(identity: &PrivateCrossSigningIdentity) -> Self {
|
||||||
|
let master_key = identity
|
||||||
|
.master_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.public_key
|
||||||
|
.clone();
|
||||||
|
let self_signing_key = identity
|
||||||
|
.self_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.public_key
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
user_id: Arc::new(identity.user_id().clone()),
|
||||||
|
master_key,
|
||||||
|
self_signing_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the user id of this identity.
|
/// Get the user id of this identity.
|
||||||
pub fn user_id(&self) -> &UserId {
|
pub fn user_id(&self) -> &UserId {
|
||||||
&self.user_id
|
&self.user_id
|
||||||
|
@ -692,6 +723,7 @@ pub(crate) mod test {
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
api::r0::keys::get_keys::Response as KeyQueryResponse, identifiers::user_id, locks::Mutex,
|
api::r0::keys::get_keys::Response as KeyQueryResponse, identifiers::user_id, locks::Mutex,
|
||||||
};
|
};
|
||||||
|
use matrix_sdk_test::async_test;
|
||||||
|
|
||||||
use super::{OwnUserIdentity, UserIdentities, UserIdentity};
|
use super::{OwnUserIdentity, UserIdentities, UserIdentity};
|
||||||
|
|
||||||
|
@ -757,13 +789,14 @@ pub(crate) mod test {
|
||||||
)));
|
)));
|
||||||
let verification_machine = VerificationMachine::new(
|
let verification_machine = VerificationMachine::new(
|
||||||
ReadOnlyAccount::new(second.user_id(), second.device_id()),
|
ReadOnlyAccount::new(second.user_id(), second.device_id()),
|
||||||
private_identity,
|
private_identity.clone(),
|
||||||
Arc::new(Box::new(MemoryStore::new())),
|
Arc::new(Box::new(MemoryStore::new())),
|
||||||
);
|
);
|
||||||
|
|
||||||
let first = Device {
|
let first = Device {
|
||||||
inner: first,
|
inner: first,
|
||||||
verification_machine: verification_machine.clone(),
|
verification_machine: verification_machine.clone(),
|
||||||
|
private_identity: private_identity.clone(),
|
||||||
own_identity: Some(identity.clone()),
|
own_identity: Some(identity.clone()),
|
||||||
device_owner_identity: Some(UserIdentities::Own(identity.clone())),
|
device_owner_identity: Some(UserIdentities::Own(identity.clone())),
|
||||||
};
|
};
|
||||||
|
@ -771,6 +804,7 @@ pub(crate) mod test {
|
||||||
let second = Device {
|
let second = Device {
|
||||||
inner: second,
|
inner: second,
|
||||||
verification_machine,
|
verification_machine,
|
||||||
|
private_identity,
|
||||||
own_identity: Some(identity.clone()),
|
own_identity: Some(identity.clone()),
|
||||||
device_owner_identity: Some(UserIdentities::Own(identity.clone())),
|
device_owner_identity: Some(UserIdentities::Own(identity.clone())),
|
||||||
};
|
};
|
||||||
|
@ -785,4 +819,39 @@ pub(crate) mod test {
|
||||||
assert!(second.trust_state());
|
assert!(second.trust_state());
|
||||||
assert!(!first.trust_state());
|
assert!(!first.trust_state());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn own_device_with_private_identity() {
|
||||||
|
let response = own_key_query();
|
||||||
|
let (_, device) = device(&response);
|
||||||
|
|
||||||
|
let account = ReadOnlyAccount::new(device.user_id(), device.device_id());
|
||||||
|
let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||||
|
|
||||||
|
let id = Arc::new(Mutex::new(identity.clone()));
|
||||||
|
|
||||||
|
let verification_machine = VerificationMachine::new(
|
||||||
|
ReadOnlyAccount::new(device.user_id(), device.device_id()),
|
||||||
|
id.clone(),
|
||||||
|
Arc::new(Box::new(MemoryStore::new())),
|
||||||
|
);
|
||||||
|
|
||||||
|
let public_identity = identity.as_public_identity().await.unwrap();
|
||||||
|
|
||||||
|
let mut device = Device {
|
||||||
|
inner: device,
|
||||||
|
verification_machine: verification_machine.clone(),
|
||||||
|
private_identity: id.clone(),
|
||||||
|
own_identity: Some(public_identity.clone()),
|
||||||
|
device_owner_identity: Some(public_identity.clone().into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(!device.trust_state());
|
||||||
|
|
||||||
|
let mut device_keys = device.as_device_keys();
|
||||||
|
|
||||||
|
identity.sign_device_keys(&mut device_keys).await.unwrap();
|
||||||
|
device.inner.signatures = Arc::new(device_keys.signatures);
|
||||||
|
assert!(device.trust_state());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -722,8 +722,8 @@ mod test {
|
||||||
let account = ReadOnlyAccount::new(&user_id, &alice_device_id());
|
let account = ReadOnlyAccount::new(&user_id, &alice_device_id());
|
||||||
let store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(MemoryStore::new()));
|
let store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(MemoryStore::new()));
|
||||||
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(bob_id())));
|
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(bob_id())));
|
||||||
let verification = VerificationMachine::new(account, identity, store.clone());
|
let verification = VerificationMachine::new(account, identity.clone(), store.clone());
|
||||||
let store = Store::new(user_id.clone(), store, verification);
|
let store = Store::new(user_id.clone(), identity, store, verification);
|
||||||
|
|
||||||
KeyRequestMachine::new(
|
KeyRequestMachine::new(
|
||||||
user_id,
|
user_id,
|
||||||
|
@ -740,8 +740,8 @@ mod test {
|
||||||
let device = ReadOnlyDevice::from_account(&account).await;
|
let device = ReadOnlyDevice::from_account(&account).await;
|
||||||
let store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(MemoryStore::new()));
|
let store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(MemoryStore::new()));
|
||||||
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())));
|
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())));
|
||||||
let verification = VerificationMachine::new(account, identity, store.clone());
|
let verification = VerificationMachine::new(account, identity.clone(), store.clone());
|
||||||
let store = Store::new(user_id.clone(), store, verification);
|
let store = Store::new(user_id.clone(), identity, store, verification);
|
||||||
store.save_devices(&[device]).await.unwrap();
|
store.save_devices(&[device]).await.unwrap();
|
||||||
|
|
||||||
KeyRequestMachine::new(
|
KeyRequestMachine::new(
|
||||||
|
|
|
@ -147,7 +147,12 @@ impl OlmMachine {
|
||||||
let store = Arc::new(store);
|
let store = Arc::new(store);
|
||||||
let verification_machine =
|
let verification_machine =
|
||||||
VerificationMachine::new(account.clone(), user_identity.clone(), store.clone());
|
VerificationMachine::new(account.clone(), user_identity.clone(), store.clone());
|
||||||
let store = Store::new(user_id.clone(), store, verification_machine.clone());
|
let store = Store::new(
|
||||||
|
user_id.clone(),
|
||||||
|
user_identity.clone(),
|
||||||
|
store,
|
||||||
|
verification_machine.clone(),
|
||||||
|
);
|
||||||
let device_id: Arc<DeviceIdBox> = Arc::new(device_id);
|
let device_id: Arc<DeviceIdBox> = Arc::new(device_id);
|
||||||
let outbound_group_sessions = Arc::new(DashMap::new());
|
let outbound_group_sessions = Arc::new(DashMap::new());
|
||||||
let users_for_key_claim = Arc::new(DashMap::new());
|
let users_for_key_claim = Arc::new(DashMap::new());
|
||||||
|
@ -351,6 +356,9 @@ impl OlmMachine {
|
||||||
IncomingResponse::SigningKeysUpload(_) => {
|
IncomingResponse::SigningKeysUpload(_) => {
|
||||||
self.receive_cross_signing_upload_response().await?;
|
self.receive_cross_signing_upload_response().await?;
|
||||||
}
|
}
|
||||||
|
IncomingResponse::SignatureUpload(_) => {
|
||||||
|
self.verification_machine.mark_request_as_sent(request_id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -380,20 +388,31 @@ impl OlmMachine {
|
||||||
|
|
||||||
if identity.is_empty().await || reset {
|
if identity.is_empty().await || reset {
|
||||||
info!("Creating new cross signing identity");
|
info!("Creating new cross signing identity");
|
||||||
let (id, signature_request) = self.account.bootstrap_cross_signing().await;
|
let (id, request, signature_request) = self.account.bootstrap_cross_signing().await;
|
||||||
let request = id.as_upload_request().await;
|
|
||||||
|
|
||||||
*identity = id;
|
*identity = id;
|
||||||
|
|
||||||
|
let public = identity.as_public_identity().await.expect(
|
||||||
|
"Couldn't create a public version of the identity from a new private identity",
|
||||||
|
);
|
||||||
|
|
||||||
|
let changes = Changes {
|
||||||
|
identities: IdentityChanges {
|
||||||
|
new: vec![public.into()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.store.save_changes(changes).await?;
|
||||||
self.store.save_identity(identity.clone()).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");
|
||||||
let request = identity.as_upload_request().await;
|
let request = identity.as_upload_request().await;
|
||||||
let device_keys = self.account.unsigned_device_keys();
|
|
||||||
// TODO remove this expect.
|
// TODO remove this expect.
|
||||||
let signature_request = identity
|
let signature_request = identity
|
||||||
.sign_device(device_keys)
|
.sign_account(&self.account)
|
||||||
.await
|
.await
|
||||||
.expect("Can't sign device keys");
|
.expect("Can't sign device keys");
|
||||||
Ok((request, signature_request))
|
Ok((request, signature_request))
|
||||||
|
@ -1787,6 +1806,7 @@ pub(crate) mod test {
|
||||||
.confirm()
|
.confirm()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.0
|
||||||
.map(|r| request_to_event(bob.user_id(), &r))
|
.map(|r| request_to_event(bob.user_id(), &r))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
alice.handle_verification_event(&mut event).await;
|
alice.handle_verification_event(&mut event).await;
|
||||||
|
@ -1798,6 +1818,7 @@ pub(crate) mod test {
|
||||||
.confirm()
|
.confirm()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.0
|
||||||
.map(|r| request_to_event(alice.user_id(), &r))
|
.map(|r| request_to_event(alice.user_id(), &r))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ use olm_rs::{
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{EventError, OlmResult, SessionCreationError, SignatureError},
|
error::{EventError, OlmResult, SessionCreationError, SignatureError},
|
||||||
identities::ReadOnlyDevice,
|
identities::ReadOnlyDevice,
|
||||||
|
requests::UploadSigningKeysRequest,
|
||||||
store::Store,
|
store::Store,
|
||||||
OlmError,
|
OlmError,
|
||||||
};
|
};
|
||||||
|
@ -668,7 +669,11 @@ impl ReadOnlyAccount {
|
||||||
|
|
||||||
pub(crate) async fn bootstrap_cross_signing(
|
pub(crate) async fn bootstrap_cross_signing(
|
||||||
&self,
|
&self,
|
||||||
) -> (PrivateCrossSigningIdentity, SignatureUploadRequest) {
|
) -> (
|
||||||
|
PrivateCrossSigningIdentity,
|
||||||
|
UploadSigningKeysRequest,
|
||||||
|
SignatureUploadRequest,
|
||||||
|
) {
|
||||||
PrivateCrossSigningIdentity::new_with_account(self).await
|
PrivateCrossSigningIdentity::new_with_account(self).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,772 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
#![allow(dead_code, missing_docs)]
|
|
||||||
|
|
||||||
use aes_gcm::{
|
|
||||||
aead::{generic_array::GenericArray, Aead, NewAead},
|
|
||||||
Aes256Gcm,
|
|
||||||
};
|
|
||||||
use base64::{decode_config, encode_config, DecodeError, URL_SAFE_NO_PAD};
|
|
||||||
use getrandom::getrandom;
|
|
||||||
use matrix_sdk_common::{
|
|
||||||
encryption::DeviceKeys,
|
|
||||||
identifiers::{DeviceKeyAlgorithm, DeviceKeyId},
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::{Error as JsonError, Value};
|
|
||||||
use std::{
|
|
||||||
collections::BTreeMap,
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
|
||||||
use zeroize::Zeroizing;
|
|
||||||
|
|
||||||
use olm_rs::{errors::OlmUtilityError, pk::OlmPkSigning, utility::OlmUtility};
|
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
|
||||||
api::r0::keys::{
|
|
||||||
upload_signatures::Request as SignatureUploadRequest, CrossSigningKey, KeyUsage,
|
|
||||||
},
|
|
||||||
identifiers::UserId,
|
|
||||||
locks::Mutex,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
error::SignatureError,
|
|
||||||
identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
|
|
||||||
requests::UploadSigningKeysRequest,
|
|
||||||
UserIdentity,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::ReadOnlyAccount;
|
|
||||||
|
|
||||||
const NONCE_SIZE: usize = 12;
|
|
||||||
|
|
||||||
fn encode<T: AsRef<[u8]>>(input: T) -> String {
|
|
||||||
encode_config(input, URL_SAFE_NO_PAD)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
|
|
||||||
decode_config(input, URL_SAFE_NO_PAD)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error type reporting failures in the Signign operations.
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum SigningError {
|
|
||||||
/// Error decoding the base64 encoded pickle data.
|
|
||||||
#[error(transparent)]
|
|
||||||
Decode(#[from] DecodeError),
|
|
||||||
|
|
||||||
/// Error decrypting the pickled signing seed
|
|
||||||
#[error("Error decrypting the pickled signign seed")]
|
|
||||||
Decryption(String),
|
|
||||||
|
|
||||||
/// Error deserializing the pickle data.
|
|
||||||
#[error(transparent)]
|
|
||||||
Json(#[from] JsonError),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Signing {
|
|
||||||
inner: Arc<Mutex<OlmPkSigning>>,
|
|
||||||
seed: Arc<Zeroizing<Vec<u8>>>,
|
|
||||||
public_key: PublicSigningKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for Signing {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("Signing")
|
|
||||||
.field("public_key", &self.public_key.as_str())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Signing {
|
|
||||||
fn eq(&self, other: &Signing) -> bool {
|
|
||||||
self.seed == other.seed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
struct MasterSigning {
|
|
||||||
inner: Signing,
|
|
||||||
public_key: MasterPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
struct PickledMasterSigning {
|
|
||||||
pickle: PickledSigning,
|
|
||||||
public_key: CrossSigningKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
struct PickledUserSigning {
|
|
||||||
pickle: PickledSigning,
|
|
||||||
public_key: CrossSigningKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
struct PickledSelfSigning {
|
|
||||||
pickle: PickledSigning,
|
|
||||||
public_key: CrossSigningKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MasterSigning {
|
|
||||||
async fn pickle(&self, pickle_key: &[u8]) -> PickledMasterSigning {
|
|
||||||
let pickle = self.inner.pickle(pickle_key).await;
|
|
||||||
let public_key = self.public_key.clone().into();
|
|
||||||
PickledMasterSigning { pickle, public_key }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_pickle(pickle: PickledMasterSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
|
||||||
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
inner,
|
|
||||||
public_key: pickle.public_key.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign_subkey<'a>(&self, subkey: &mut CrossSigningKey) {
|
|
||||||
// TODO create a borrowed version of a cross singing key.
|
|
||||||
let subkey_wihtout_signatures = CrossSigningKey {
|
|
||||||
user_id: subkey.user_id.clone(),
|
|
||||||
keys: subkey.keys.clone(),
|
|
||||||
usage: subkey.usage.clone(),
|
|
||||||
signatures: BTreeMap::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let message = cjson::to_string(&subkey_wihtout_signatures)
|
|
||||||
.expect("Can't serialize cross signing subkey");
|
|
||||||
let signature = self.inner.sign(&message).await;
|
|
||||||
|
|
||||||
subkey
|
|
||||||
.signatures
|
|
||||||
.entry(self.public_key.user_id().to_owned())
|
|
||||||
.or_insert_with(BTreeMap::new)
|
|
||||||
.insert(
|
|
||||||
format!("ed25519:{}", self.inner.public_key().as_str()),
|
|
||||||
signature.0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserSigning {
|
|
||||||
async fn pickle(&self, pickle_key: &[u8]) -> PickledUserSigning {
|
|
||||||
let pickle = self.inner.pickle(pickle_key).await;
|
|
||||||
let public_key = self.public_key.clone().into();
|
|
||||||
PickledUserSigning { pickle, public_key }
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign_user(&self, _: &UserIdentity) -> BTreeMap<UserId, BTreeMap<String, Value>> {
|
|
||||||
todo!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_pickle(pickle: PickledUserSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
|
||||||
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
inner,
|
|
||||||
public_key: pickle.public_key.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelfSigning {
|
|
||||||
async fn pickle(&self, pickle_key: &[u8]) -> PickledSelfSigning {
|
|
||||||
let pickle = self.inner.pickle(pickle_key).await;
|
|
||||||
let public_key = self.public_key.clone().into();
|
|
||||||
PickledSelfSigning { pickle, public_key }
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign_device_raw(&self, value: Value) -> Result<Signature, SignatureError> {
|
|
||||||
self.inner.sign_json(value).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> {
|
|
||||||
let json_device = serde_json::to_value(&device_keys)?;
|
|
||||||
let signature = self.sign_device_raw(json_device).await?;
|
|
||||||
|
|
||||||
device_keys
|
|
||||||
.signatures
|
|
||||||
.entry(self.public_key.user_id().to_owned())
|
|
||||||
.or_insert_with(BTreeMap::new)
|
|
||||||
.insert(
|
|
||||||
DeviceKeyId::from_parts(
|
|
||||||
DeviceKeyAlgorithm::Ed25519,
|
|
||||||
self.inner.public_key.as_str().into(),
|
|
||||||
),
|
|
||||||
signature.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_pickle(pickle: PickledSelfSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
|
||||||
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
inner,
|
|
||||||
public_key: pickle.public_key.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
struct SelfSigning {
|
|
||||||
inner: Signing,
|
|
||||||
public_key: SelfSigningPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
struct UserSigning {
|
|
||||||
inner: Signing,
|
|
||||||
public_key: UserSigningPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct PrivateCrossSigningIdentity {
|
|
||||||
user_id: Arc<UserId>,
|
|
||||||
shared: Arc<AtomicBool>,
|
|
||||||
master_key: Arc<Mutex<Option<MasterSigning>>>,
|
|
||||||
user_signing_key: Arc<Mutex<Option<UserSigning>>>,
|
|
||||||
self_signing_key: Arc<Mutex<Option<SelfSigning>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct PickledCrossSigningIdentity {
|
|
||||||
pub user_id: UserId,
|
|
||||||
pub shared: bool,
|
|
||||||
pub pickle: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
struct PickledSignings {
|
|
||||||
master_key: Option<PickledMasterSigning>,
|
|
||||||
user_signing_key: Option<PickledUserSigning>,
|
|
||||||
self_signing_key: Option<PickledSelfSigning>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Signature(String);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
struct PickledSigning(String);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
struct InnerPickle {
|
|
||||||
version: u8,
|
|
||||||
nonce: String,
|
|
||||||
ciphertext: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct PublicSigningKey(Arc<String>);
|
|
||||||
|
|
||||||
impl Signature {
|
|
||||||
fn as_str(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PickledSigning {
|
|
||||||
fn as_str(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PublicSigningKey {
|
|
||||||
fn as_str(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::inherent_to_string)]
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
self.0.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Signing {
|
|
||||||
fn new() -> Self {
|
|
||||||
let seed = OlmPkSigning::generate_seed();
|
|
||||||
Self::from_seed(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_seed(seed: Vec<u8>) -> Self {
|
|
||||||
let inner = OlmPkSigning::new(seed.clone()).expect("Unable to create pk signing object");
|
|
||||||
let public_key = PublicSigningKey(Arc::new(inner.public_key().to_owned()));
|
|
||||||
|
|
||||||
Signing {
|
|
||||||
inner: Arc::new(Mutex::new(inner)),
|
|
||||||
seed: Arc::new(Zeroizing::from(seed)),
|
|
||||||
public_key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_pickle(pickle: PickledSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
|
||||||
let pickled: InnerPickle = serde_json::from_str(pickle.as_str())?;
|
|
||||||
|
|
||||||
let key = GenericArray::from_slice(pickle_key);
|
|
||||||
let cipher = Aes256Gcm::new(key);
|
|
||||||
|
|
||||||
let nonce = decode(pickled.nonce)?;
|
|
||||||
let nonce = GenericArray::from_slice(&nonce);
|
|
||||||
let ciphertext = &decode(pickled.ciphertext)?;
|
|
||||||
|
|
||||||
let seed = cipher
|
|
||||||
.decrypt(&nonce, ciphertext.as_slice())
|
|
||||||
.map_err(|e| SigningError::Decryption(e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(Self::from_seed(seed))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn pickle(&self, pickle_key: &[u8]) -> PickledSigning {
|
|
||||||
let key = GenericArray::from_slice(pickle_key);
|
|
||||||
let cipher = Aes256Gcm::new(key);
|
|
||||||
|
|
||||||
let mut nonce = vec![0u8; NONCE_SIZE];
|
|
||||||
getrandom(&mut nonce).expect("Can't generate nonce to pickle the signing object");
|
|
||||||
let nonce = GenericArray::from_slice(nonce.as_slice());
|
|
||||||
|
|
||||||
let ciphertext = cipher
|
|
||||||
.encrypt(nonce, self.seed.as_slice())
|
|
||||||
.expect("Can't encrypt signing pickle");
|
|
||||||
|
|
||||||
let ciphertext = encode(ciphertext);
|
|
||||||
|
|
||||||
let pickle = InnerPickle {
|
|
||||||
version: 1,
|
|
||||||
nonce: encode(nonce.as_slice()),
|
|
||||||
ciphertext,
|
|
||||||
};
|
|
||||||
|
|
||||||
PickledSigning(serde_json::to_string(&pickle).expect("Can't encode pickled signing"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key(&self) -> &PublicSigningKey {
|
|
||||||
&self.public_key
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cross_signing_key(&self, user_id: UserId, usage: KeyUsage) -> CrossSigningKey {
|
|
||||||
let mut keys = BTreeMap::new();
|
|
||||||
|
|
||||||
keys.insert(
|
|
||||||
format!("ed25519:{}", self.public_key().as_str()),
|
|
||||||
self.public_key().to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
CrossSigningKey {
|
|
||||||
user_id,
|
|
||||||
usage: vec![usage],
|
|
||||||
keys,
|
|
||||||
signatures: BTreeMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn verify(&self, message: &str, signature: &Signature) -> Result<bool, OlmUtilityError> {
|
|
||||||
let utility = OlmUtility::new();
|
|
||||||
utility.ed25519_verify(self.public_key.as_str(), message, signature.as_str())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign_json(&self, mut json: Value) -> Result<Signature, SignatureError> {
|
|
||||||
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
|
||||||
let _ = json_object.remove("signatures");
|
|
||||||
let canonical_json = cjson::to_string(json_object)?;
|
|
||||||
Ok(self.sign(&canonical_json).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign(&self, message: &str) -> Signature {
|
|
||||||
Signature(self.inner.lock().await.sign(message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrivateCrossSigningIdentity {
|
|
||||||
pub fn user_id(&self) -> &UserId {
|
|
||||||
&self.user_id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn is_empty(&self) -> bool {
|
|
||||||
let has_master = self.master_key.lock().await.is_some();
|
|
||||||
let has_user = self.user_signing_key.lock().await.is_some();
|
|
||||||
let has_self = self.self_signing_key.lock().await.is_some();
|
|
||||||
|
|
||||||
!(has_master && has_user && has_self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn empty(user_id: UserId) -> Self {
|
|
||||||
Self {
|
|
||||||
user_id: Arc::new(user_id),
|
|
||||||
shared: Arc::new(AtomicBool::new(false)),
|
|
||||||
master_key: Arc::new(Mutex::new(None)),
|
|
||||||
self_signing_key: Arc::new(Mutex::new(None)),
|
|
||||||
user_signing_key: Arc::new(Mutex::new(None)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn sign_device(
|
|
||||||
&self,
|
|
||||||
mut device_keys: DeviceKeys,
|
|
||||||
) -> Result<SignatureUploadRequest, SignatureError> {
|
|
||||||
self.self_signing_key
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(SignatureError::MissingSigningKey)?
|
|
||||||
.sign_device(&mut device_keys)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut signed_keys = BTreeMap::new();
|
|
||||||
signed_keys
|
|
||||||
.entry((&*self.user_id).to_owned())
|
|
||||||
.or_insert_with(BTreeMap::new)
|
|
||||||
.insert(
|
|
||||||
device_keys.device_id.to_string(),
|
|
||||||
serde_json::to_value(device_keys)?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(SignatureUploadRequest { signed_keys })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn new_with_account(
|
|
||||||
account: &ReadOnlyAccount,
|
|
||||||
) -> (Self, SignatureUploadRequest) {
|
|
||||||
let master = Signing::new();
|
|
||||||
|
|
||||||
let mut public_key =
|
|
||||||
master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master);
|
|
||||||
let signature = account
|
|
||||||
.sign_json(
|
|
||||||
serde_json::to_value(&public_key)
|
|
||||||
.expect("Can't convert own public master key to json"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Can't sign own public master key");
|
|
||||||
public_key
|
|
||||||
.signatures
|
|
||||||
.entry(account.user_id().to_owned())
|
|
||||||
.or_insert_with(BTreeMap::new)
|
|
||||||
.insert(format!("ed25519:{}", account.device_id()), signature);
|
|
||||||
|
|
||||||
let master = MasterSigning {
|
|
||||||
inner: master,
|
|
||||||
public_key: public_key.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let identity = Self::new_helper(account.user_id(), master).await;
|
|
||||||
let device_keys = account.unsigned_device_keys();
|
|
||||||
let request = identity
|
|
||||||
.sign_device(device_keys)
|
|
||||||
.await
|
|
||||||
.expect("Can't sign own device with new cross signign keys");
|
|
||||||
|
|
||||||
(identity, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn new_helper(user_id: &UserId, master: MasterSigning) -> Self {
|
|
||||||
let user = Signing::new();
|
|
||||||
let mut public_key = user.cross_signing_key(user_id.to_owned(), KeyUsage::UserSigning);
|
|
||||||
master.sign_subkey(&mut public_key).await;
|
|
||||||
|
|
||||||
let user = UserSigning {
|
|
||||||
inner: user,
|
|
||||||
public_key: public_key.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let self_signing = Signing::new();
|
|
||||||
let mut public_key =
|
|
||||||
self_signing.cross_signing_key(user_id.to_owned(), KeyUsage::SelfSigning);
|
|
||||||
master.sign_subkey(&mut public_key).await;
|
|
||||||
|
|
||||||
let self_signing = SelfSigning {
|
|
||||||
inner: self_signing,
|
|
||||||
public_key: public_key.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
user_id: Arc::new(user_id.to_owned()),
|
|
||||||
shared: Arc::new(AtomicBool::new(false)),
|
|
||||||
master_key: Arc::new(Mutex::new(Some(master))),
|
|
||||||
self_signing_key: Arc::new(Mutex::new(Some(self_signing))),
|
|
||||||
user_signing_key: Arc::new(Mutex::new(Some(user))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn new(user_id: UserId) -> Self {
|
|
||||||
let master = Signing::new();
|
|
||||||
|
|
||||||
let public_key = master.cross_signing_key(user_id.clone(), KeyUsage::Master);
|
|
||||||
let master = MasterSigning {
|
|
||||||
inner: master,
|
|
||||||
public_key: public_key.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = Signing::new();
|
|
||||||
let mut public_key = user.cross_signing_key(user_id.clone(), KeyUsage::UserSigning);
|
|
||||||
master.sign_subkey(&mut public_key).await;
|
|
||||||
|
|
||||||
let user = UserSigning {
|
|
||||||
inner: user,
|
|
||||||
public_key: public_key.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let self_signing = Signing::new();
|
|
||||||
let mut public_key = self_signing.cross_signing_key(user_id.clone(), KeyUsage::SelfSigning);
|
|
||||||
master.sign_subkey(&mut public_key).await;
|
|
||||||
|
|
||||||
let self_signing = SelfSigning {
|
|
||||||
inner: self_signing,
|
|
||||||
public_key: public_key.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
user_id: Arc::new(user_id),
|
|
||||||
shared: Arc::new(AtomicBool::new(false)),
|
|
||||||
master_key: Arc::new(Mutex::new(Some(master))),
|
|
||||||
self_signing_key: Arc::new(Mutex::new(Some(self_signing))),
|
|
||||||
user_signing_key: Arc::new(Mutex::new(Some(user))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mark_as_shared(&self) {
|
|
||||||
self.shared.store(true, Ordering::SeqCst)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shared(&self) -> bool {
|
|
||||||
self.shared.load(Ordering::SeqCst)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn pickle(
|
|
||||||
&self,
|
|
||||||
pickle_key: &[u8],
|
|
||||||
) -> Result<PickledCrossSigningIdentity, JsonError> {
|
|
||||||
let master_key = if let Some(m) = self.master_key.lock().await.as_ref() {
|
|
||||||
Some(m.pickle(pickle_key).await)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let self_signing_key = if let Some(m) = self.self_signing_key.lock().await.as_ref() {
|
|
||||||
Some(m.pickle(pickle_key).await)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_signing_key = if let Some(m) = self.user_signing_key.lock().await.as_ref() {
|
|
||||||
Some(m.pickle(pickle_key).await)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let pickle = PickledSignings {
|
|
||||||
master_key,
|
|
||||||
user_signing_key,
|
|
||||||
self_signing_key,
|
|
||||||
};
|
|
||||||
|
|
||||||
let pickle = serde_json::to_string(&pickle)?;
|
|
||||||
|
|
||||||
Ok(PickledCrossSigningIdentity {
|
|
||||||
user_id: self.user_id.as_ref().to_owned(),
|
|
||||||
shared: self.shared(),
|
|
||||||
pickle,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Restore the private cross signing identity from a pickle.
|
|
||||||
///
|
|
||||||
/// # Panic
|
|
||||||
///
|
|
||||||
/// Panics if the pickle_key isn't 32 bytes long.
|
|
||||||
pub async fn from_pickle(
|
|
||||||
pickle: PickledCrossSigningIdentity,
|
|
||||||
pickle_key: &[u8],
|
|
||||||
) -> Result<Self, SigningError> {
|
|
||||||
let signings: PickledSignings = serde_json::from_str(&pickle.pickle)?;
|
|
||||||
|
|
||||||
let master = if let Some(m) = signings.master_key {
|
|
||||||
Some(MasterSigning::from_pickle(m, pickle_key)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let self_signing = if let Some(s) = signings.self_signing_key {
|
|
||||||
Some(SelfSigning::from_pickle(s, pickle_key)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_signing = if let Some(u) = signings.user_signing_key {
|
|
||||||
Some(UserSigning::from_pickle(u, pickle_key)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
user_id: Arc::new(pickle.user_id),
|
|
||||||
shared: Arc::new(AtomicBool::from(pickle.shared)),
|
|
||||||
master_key: Arc::new(Mutex::new(master)),
|
|
||||||
self_signing_key: Arc::new(Mutex::new(self_signing)),
|
|
||||||
user_signing_key: Arc::new(Mutex::new(user_signing)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn as_upload_request(&self) -> UploadSigningKeysRequest {
|
|
||||||
let master_key = self
|
|
||||||
.master_key
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.map(|k| k.public_key.into());
|
|
||||||
|
|
||||||
let user_signing_key = self
|
|
||||||
.user_signing_key
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.map(|k| k.public_key.into());
|
|
||||||
|
|
||||||
let self_signing_key = self
|
|
||||||
.self_signing_key
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.map(|k| k.public_key.into());
|
|
||||||
|
|
||||||
UploadSigningKeysRequest {
|
|
||||||
master_key,
|
|
||||||
user_signing_key,
|
|
||||||
self_signing_key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::olm::ReadOnlyAccount;
|
|
||||||
|
|
||||||
use super::{PrivateCrossSigningIdentity, Signing};
|
|
||||||
|
|
||||||
use matrix_sdk_common::identifiers::{user_id, UserId};
|
|
||||||
use matrix_sdk_test::async_test;
|
|
||||||
|
|
||||||
fn user_id() -> UserId {
|
|
||||||
user_id!("@example:localhost")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pickle_key() -> &'static [u8] {
|
|
||||||
&[0u8; 32]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn signing_creation() {
|
|
||||||
let signing = Signing::new();
|
|
||||||
assert!(!signing.public_key().as_str().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_test]
|
|
||||||
async fn signature_verification() {
|
|
||||||
let signing = Signing::new();
|
|
||||||
|
|
||||||
let message = "Hello world";
|
|
||||||
|
|
||||||
let signature = signing.sign(message).await;
|
|
||||||
assert!(signing.verify(message, &signature).await.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_test]
|
|
||||||
async fn pickling_signing() {
|
|
||||||
let signing = Signing::new();
|
|
||||||
let pickled = signing.pickle(pickle_key()).await;
|
|
||||||
|
|
||||||
let unpickled = Signing::from_pickle(pickled, pickle_key()).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(signing.public_key(), unpickled.public_key());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_test]
|
|
||||||
async fn private_identity_creation() {
|
|
||||||
let identity = PrivateCrossSigningIdentity::new(user_id()).await;
|
|
||||||
|
|
||||||
let master_key = identity.master_key.lock().await;
|
|
||||||
let master_key = master_key.as_ref().unwrap();
|
|
||||||
|
|
||||||
assert!(master_key
|
|
||||||
.public_key
|
|
||||||
.verify_subkey(
|
|
||||||
&identity
|
|
||||||
.self_signing_key
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.public_key,
|
|
||||||
)
|
|
||||||
.is_ok());
|
|
||||||
|
|
||||||
assert!(master_key
|
|
||||||
.public_key
|
|
||||||
.verify_subkey(
|
|
||||||
&identity
|
|
||||||
.user_signing_key
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.public_key,
|
|
||||||
)
|
|
||||||
.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_test]
|
|
||||||
async fn identity_pickling() {
|
|
||||||
let identity = PrivateCrossSigningIdentity::new(user_id()).await;
|
|
||||||
|
|
||||||
let pickled = identity.pickle(pickle_key()).await.unwrap();
|
|
||||||
|
|
||||||
let unpickled = PrivateCrossSigningIdentity::from_pickle(pickled, pickle_key())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(identity.user_id, unpickled.user_id);
|
|
||||||
assert_eq!(
|
|
||||||
&*identity.master_key.lock().await,
|
|
||||||
&*unpickled.master_key.lock().await
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
&*identity.user_signing_key.lock().await,
|
|
||||||
&*unpickled.user_signing_key.lock().await
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
&*identity.self_signing_key.lock().await,
|
|
||||||
&*unpickled.self_signing_key.lock().await
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_test]
|
|
||||||
async fn private_identity_signed_by_accound() {
|
|
||||||
let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into());
|
|
||||||
let (identity, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
|
||||||
let master = identity.master_key.lock().await;
|
|
||||||
let master = master.as_ref().unwrap();
|
|
||||||
|
|
||||||
assert!(!master.public_key.signatures().is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,600 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
mod pk_signing;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Error as JsonError;
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
api::r0::keys::{upload_signatures::Request as SignatureUploadRequest, KeyUsage},
|
||||||
|
encryption::DeviceKeys,
|
||||||
|
identifiers::{DeviceKeyAlgorithm, DeviceKeyId, UserId},
|
||||||
|
locks::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::SignatureError, requests::UploadSigningKeysRequest, OwnUserIdentity, ReadOnlyAccount,
|
||||||
|
ReadOnlyDevice, UserIdentity,
|
||||||
|
};
|
||||||
|
|
||||||
|
use pk_signing::{MasterSigning, PickledSignings, SelfSigning, Signing, SigningError, UserSigning};
|
||||||
|
|
||||||
|
/// Private cross signing identity.
|
||||||
|
///
|
||||||
|
/// This object holds the private and public ed25519 key triplet that is used
|
||||||
|
/// for cross signing.
|
||||||
|
///
|
||||||
|
/// The object might be comletely empty or have only some of the key pairs
|
||||||
|
/// available.
|
||||||
|
///
|
||||||
|
/// It can be used to sign devices or other identities.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PrivateCrossSigningIdentity {
|
||||||
|
user_id: Arc<UserId>,
|
||||||
|
shared: Arc<AtomicBool>,
|
||||||
|
pub(crate) master_key: Arc<Mutex<Option<MasterSigning>>>,
|
||||||
|
pub(crate) user_signing_key: Arc<Mutex<Option<UserSigning>>>,
|
||||||
|
pub(crate) self_signing_key: Arc<Mutex<Option<SelfSigning>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pickled version of a `PrivateCrossSigningIdentity`.
|
||||||
|
///
|
||||||
|
/// Can be used to store the identity.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PickledCrossSigningIdentity {
|
||||||
|
/// The user id of the identity owner.
|
||||||
|
pub user_id: UserId,
|
||||||
|
/// Have the public keys of the identity been shared.
|
||||||
|
pub shared: bool,
|
||||||
|
/// The encrypted pickle of the identity.
|
||||||
|
pub pickle: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrivateCrossSigningIdentity {
|
||||||
|
/// Get the user id that this identity belongs to.
|
||||||
|
pub fn user_id(&self) -> &UserId {
|
||||||
|
&self.user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the identity empty.
|
||||||
|
///
|
||||||
|
/// An empty identity doesn't contain any private keys.
|
||||||
|
///
|
||||||
|
/// It is usual for the identity not to contain the master key since the
|
||||||
|
/// master key is only needed to sign the subkeys.
|
||||||
|
///
|
||||||
|
/// An empty identity indicates that either no identity was created for this
|
||||||
|
/// use or that another device created it and hasn't shared it yet with us.
|
||||||
|
pub async fn is_empty(&self) -> bool {
|
||||||
|
let has_master = self.master_key.lock().await.is_some();
|
||||||
|
let has_user = self.user_signing_key.lock().await.is_some();
|
||||||
|
let has_self = self.self_signing_key.lock().await.is_some();
|
||||||
|
|
||||||
|
!(has_master && has_user && has_self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new empty identity.
|
||||||
|
pub(crate) fn empty(user_id: UserId) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: Arc::new(user_id),
|
||||||
|
shared: Arc::new(AtomicBool::new(false)),
|
||||||
|
master_key: Arc::new(Mutex::new(None)),
|
||||||
|
self_signing_key: Arc::new(Mutex::new(None)),
|
||||||
|
user_signing_key: Arc::new(Mutex::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn as_public_identity(&self) -> Result<OwnUserIdentity, SignatureError> {
|
||||||
|
let master = self
|
||||||
|
.master_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(SignatureError::MissingSigningKey)?
|
||||||
|
.public_key
|
||||||
|
.clone();
|
||||||
|
let self_signing = self
|
||||||
|
.self_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(SignatureError::MissingSigningKey)?
|
||||||
|
.public_key
|
||||||
|
.clone();
|
||||||
|
let user_signing = self
|
||||||
|
.user_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(SignatureError::MissingSigningKey)?
|
||||||
|
.public_key
|
||||||
|
.clone();
|
||||||
|
let identity = OwnUserIdentity::new(master, self_signing, user_signing)?;
|
||||||
|
identity.mark_as_verified();
|
||||||
|
|
||||||
|
Ok(identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sign the given public user identity with this private identity.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) async fn sign_user(
|
||||||
|
&self,
|
||||||
|
user_identity: &UserIdentity,
|
||||||
|
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||||
|
let signed_keys = self
|
||||||
|
.user_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(SignatureError::MissingSigningKey)?
|
||||||
|
.sign_user(&user_identity)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(SignatureUploadRequest { signed_keys })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sign the given device keys with this identity.
|
||||||
|
pub(crate) async fn sign_device(
|
||||||
|
&self,
|
||||||
|
device: &ReadOnlyDevice,
|
||||||
|
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||||
|
let mut device_keys = device.as_device_keys();
|
||||||
|
device_keys.signatures.clear();
|
||||||
|
self.sign_device_keys(&mut device_keys).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sign an Olm account with this private identity.
|
||||||
|
pub(crate) async fn sign_account(
|
||||||
|
&self,
|
||||||
|
account: &ReadOnlyAccount,
|
||||||
|
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||||
|
let mut device_keys = account.unsigned_device_keys();
|
||||||
|
self.sign_device_keys(&mut device_keys).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn sign_device_keys(
|
||||||
|
&self,
|
||||||
|
mut device_keys: &mut DeviceKeys,
|
||||||
|
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||||
|
self.self_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(SignatureError::MissingSigningKey)?
|
||||||
|
.sign_device(&mut device_keys)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut signed_keys = BTreeMap::new();
|
||||||
|
signed_keys
|
||||||
|
.entry((&*self.user_id).to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(
|
||||||
|
device_keys.device_id.to_string(),
|
||||||
|
serde_json::to_value(device_keys)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(SignatureUploadRequest { signed_keys })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new identity for the given Olm Account.
|
||||||
|
///
|
||||||
|
/// Returns the new identity, the upload signing keys request and a
|
||||||
|
/// signature upload request that contains the signature of the account
|
||||||
|
/// signed by the self signing key.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `account` - The Olm account that is creating the new identity. The
|
||||||
|
/// account will sign the master key and the self signing key will sign the
|
||||||
|
/// account.
|
||||||
|
pub(crate) async fn new_with_account(
|
||||||
|
account: &ReadOnlyAccount,
|
||||||
|
) -> (Self, UploadSigningKeysRequest, SignatureUploadRequest) {
|
||||||
|
let master = Signing::new();
|
||||||
|
|
||||||
|
let mut public_key =
|
||||||
|
master.cross_signing_key(account.user_id().to_owned(), KeyUsage::Master);
|
||||||
|
let signature = account
|
||||||
|
.sign_json(
|
||||||
|
serde_json::to_value(&public_key)
|
||||||
|
.expect("Can't convert own public master key to json"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Can't sign own public master key");
|
||||||
|
public_key
|
||||||
|
.signatures
|
||||||
|
.entry(account.user_id().to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(
|
||||||
|
DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, account.device_id())
|
||||||
|
.to_string(),
|
||||||
|
signature,
|
||||||
|
);
|
||||||
|
|
||||||
|
let master = MasterSigning {
|
||||||
|
inner: master,
|
||||||
|
public_key: public_key.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let identity = Self::new_helper(account.user_id(), master).await;
|
||||||
|
let signature_request = identity
|
||||||
|
.sign_account(account)
|
||||||
|
.await
|
||||||
|
.expect("Can't sign own device with new cross signign keys");
|
||||||
|
|
||||||
|
let request = identity.as_upload_request().await;
|
||||||
|
|
||||||
|
(identity, request, signature_request)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_helper(user_id: &UserId, master: MasterSigning) -> Self {
|
||||||
|
let user = Signing::new();
|
||||||
|
let mut public_key = user.cross_signing_key(user_id.to_owned(), KeyUsage::UserSigning);
|
||||||
|
master.sign_subkey(&mut public_key).await;
|
||||||
|
|
||||||
|
let user = UserSigning {
|
||||||
|
inner: user,
|
||||||
|
public_key: public_key.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let self_signing = Signing::new();
|
||||||
|
let mut public_key =
|
||||||
|
self_signing.cross_signing_key(user_id.to_owned(), KeyUsage::SelfSigning);
|
||||||
|
master.sign_subkey(&mut public_key).await;
|
||||||
|
|
||||||
|
let self_signing = SelfSigning {
|
||||||
|
inner: self_signing,
|
||||||
|
public_key: public_key.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
user_id: Arc::new(user_id.to_owned()),
|
||||||
|
shared: Arc::new(AtomicBool::new(false)),
|
||||||
|
master_key: Arc::new(Mutex::new(Some(master))),
|
||||||
|
self_signing_key: Arc::new(Mutex::new(Some(self_signing))),
|
||||||
|
user_signing_key: Arc::new(Mutex::new(Some(user))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new cross signing identity without signing the device that
|
||||||
|
/// created it.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) async fn new(user_id: UserId) -> Self {
|
||||||
|
let master = Signing::new();
|
||||||
|
|
||||||
|
let public_key = master.cross_signing_key(user_id.clone(), KeyUsage::Master);
|
||||||
|
let master = MasterSigning {
|
||||||
|
inner: master,
|
||||||
|
public_key: public_key.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::new_helper(&user_id, master).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark the identity as shared.
|
||||||
|
pub fn mark_as_shared(&self) {
|
||||||
|
self.shared.store(true, Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Has the identity been shared.
|
||||||
|
///
|
||||||
|
/// A shared identity here means that the public keys of the identity have
|
||||||
|
/// been uploaded to the server.
|
||||||
|
pub fn shared(&self) -> bool {
|
||||||
|
self.shared.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store the cross signing identity as a pickle.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `pickle_key` - The key that should be used to encrypt the signing
|
||||||
|
/// object, must be 32 bytes long.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This will panic if the provided pickle key isn't 32 bytes long.
|
||||||
|
pub async fn pickle(
|
||||||
|
&self,
|
||||||
|
pickle_key: &[u8],
|
||||||
|
) -> Result<PickledCrossSigningIdentity, JsonError> {
|
||||||
|
let master_key = if let Some(m) = self.master_key.lock().await.as_ref() {
|
||||||
|
Some(m.pickle(pickle_key).await)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let self_signing_key = if let Some(m) = self.self_signing_key.lock().await.as_ref() {
|
||||||
|
Some(m.pickle(pickle_key).await)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_signing_key = if let Some(m) = self.user_signing_key.lock().await.as_ref() {
|
||||||
|
Some(m.pickle(pickle_key).await)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let pickle = PickledSignings {
|
||||||
|
master_key,
|
||||||
|
user_signing_key,
|
||||||
|
self_signing_key,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pickle = serde_json::to_string(&pickle)?;
|
||||||
|
|
||||||
|
Ok(PickledCrossSigningIdentity {
|
||||||
|
user_id: self.user_id.as_ref().to_owned(),
|
||||||
|
shared: self.shared(),
|
||||||
|
pickle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore the private cross signing identity from a pickle.
|
||||||
|
///
|
||||||
|
/// # Panic
|
||||||
|
///
|
||||||
|
/// Panics if the pickle_key isn't 32 bytes long.
|
||||||
|
pub async fn from_pickle(
|
||||||
|
pickle: PickledCrossSigningIdentity,
|
||||||
|
pickle_key: &[u8],
|
||||||
|
) -> Result<Self, SigningError> {
|
||||||
|
let signings: PickledSignings = serde_json::from_str(&pickle.pickle)?;
|
||||||
|
|
||||||
|
let master = if let Some(m) = signings.master_key {
|
||||||
|
Some(MasterSigning::from_pickle(m, pickle_key)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let self_signing = if let Some(s) = signings.self_signing_key {
|
||||||
|
Some(SelfSigning::from_pickle(s, pickle_key)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_signing = if let Some(u) = signings.user_signing_key {
|
||||||
|
Some(UserSigning::from_pickle(u, pickle_key)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
user_id: Arc::new(pickle.user_id),
|
||||||
|
shared: Arc::new(AtomicBool::from(pickle.shared)),
|
||||||
|
master_key: Arc::new(Mutex::new(master)),
|
||||||
|
self_signing_key: Arc::new(Mutex::new(self_signing)),
|
||||||
|
user_signing_key: Arc::new(Mutex::new(user_signing)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the upload request that is needed to share the public keys of this
|
||||||
|
/// identity.
|
||||||
|
pub(crate) async fn as_upload_request(&self) -> UploadSigningKeysRequest {
|
||||||
|
let master_key = self
|
||||||
|
.master_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
|
.map(|k| k.public_key.into());
|
||||||
|
|
||||||
|
let user_signing_key = self
|
||||||
|
.user_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
|
.map(|k| k.public_key.into());
|
||||||
|
|
||||||
|
let self_signing_key = self
|
||||||
|
.self_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
|
.map(|k| k.public_key.into());
|
||||||
|
|
||||||
|
UploadSigningKeysRequest {
|
||||||
|
master_key,
|
||||||
|
user_signing_key,
|
||||||
|
self_signing_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{
|
||||||
|
identities::{ReadOnlyDevice, UserIdentity},
|
||||||
|
olm::ReadOnlyAccount,
|
||||||
|
};
|
||||||
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
|
|
||||||
|
use super::{PrivateCrossSigningIdentity, Signing};
|
||||||
|
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
api::r0::keys::CrossSigningKey,
|
||||||
|
identifiers::{user_id, UserId},
|
||||||
|
};
|
||||||
|
use matrix_sdk_test::async_test;
|
||||||
|
|
||||||
|
fn user_id() -> UserId {
|
||||||
|
user_id!("@example:localhost")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pickle_key() -> &'static [u8] {
|
||||||
|
&[0u8; 32]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signing_creation() {
|
||||||
|
let signing = Signing::new();
|
||||||
|
assert!(!signing.public_key().as_str().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn signature_verification() {
|
||||||
|
let signing = Signing::new();
|
||||||
|
|
||||||
|
let message = "Hello world";
|
||||||
|
|
||||||
|
let signature = signing.sign(message).await;
|
||||||
|
assert!(signing.verify(message, &signature).await.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn pickling_signing() {
|
||||||
|
let signing = Signing::new();
|
||||||
|
let pickled = signing.pickle(pickle_key()).await;
|
||||||
|
|
||||||
|
let unpickled = Signing::from_pickle(pickled, pickle_key()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(signing.public_key(), unpickled.public_key());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn private_identity_creation() {
|
||||||
|
let identity = PrivateCrossSigningIdentity::new(user_id()).await;
|
||||||
|
|
||||||
|
let master_key = identity.master_key.lock().await;
|
||||||
|
let master_key = master_key.as_ref().unwrap();
|
||||||
|
|
||||||
|
assert!(master_key
|
||||||
|
.public_key
|
||||||
|
.verify_subkey(
|
||||||
|
&identity
|
||||||
|
.self_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.public_key,
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
assert!(master_key
|
||||||
|
.public_key
|
||||||
|
.verify_subkey(
|
||||||
|
&identity
|
||||||
|
.user_signing_key
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.public_key,
|
||||||
|
)
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn identity_pickling() {
|
||||||
|
let identity = PrivateCrossSigningIdentity::new(user_id()).await;
|
||||||
|
|
||||||
|
let pickled = identity.pickle(pickle_key()).await.unwrap();
|
||||||
|
|
||||||
|
let unpickled = PrivateCrossSigningIdentity::from_pickle(pickled, pickle_key())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(identity.user_id, unpickled.user_id);
|
||||||
|
assert_eq!(
|
||||||
|
&*identity.master_key.lock().await,
|
||||||
|
&*unpickled.master_key.lock().await
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&*identity.user_signing_key.lock().await,
|
||||||
|
&*unpickled.user_signing_key.lock().await
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&*identity.self_signing_key.lock().await,
|
||||||
|
&*unpickled.self_signing_key.lock().await
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn private_identity_signed_by_accound() {
|
||||||
|
let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into());
|
||||||
|
let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||||
|
let master = identity.master_key.lock().await;
|
||||||
|
let master = master.as_ref().unwrap();
|
||||||
|
|
||||||
|
assert!(!master.public_key.signatures().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn sign_device() {
|
||||||
|
let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into());
|
||||||
|
let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||||
|
|
||||||
|
let mut device = ReadOnlyDevice::from_account(&account).await;
|
||||||
|
let self_signing = identity.self_signing_key.lock().await;
|
||||||
|
let self_signing = self_signing.as_ref().unwrap();
|
||||||
|
|
||||||
|
let mut device_keys = device.as_device_keys();
|
||||||
|
self_signing.sign_device(&mut device_keys).await.unwrap();
|
||||||
|
device.signatures = Arc::new(device_keys.signatures);
|
||||||
|
|
||||||
|
let public_key = &self_signing.public_key;
|
||||||
|
public_key.verify_device(&device).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_test]
|
||||||
|
async fn sign_user_identity() {
|
||||||
|
let account = ReadOnlyAccount::new(&user_id(), "DEVICEID".into());
|
||||||
|
let (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await;
|
||||||
|
|
||||||
|
let bob_account = ReadOnlyAccount::new(&user_id!("@bob:localhost"), "DEVICEID".into());
|
||||||
|
let (bob_private, _, _) = PrivateCrossSigningIdentity::new_with_account(&bob_account).await;
|
||||||
|
let mut bob_public = UserIdentity::from_private(&bob_private).await;
|
||||||
|
|
||||||
|
let user_signing = identity.user_signing_key.lock().await;
|
||||||
|
let user_signing = user_signing.as_ref().unwrap();
|
||||||
|
|
||||||
|
let signatures = user_signing.sign_user(&bob_public).await.unwrap();
|
||||||
|
|
||||||
|
let (key_id, signature) = signatures
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.1
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map(|(k, s)| (k.to_string(), serde_json::from_value(s.to_owned()).unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut master: CrossSigningKey = bob_public.master_key.as_ref().clone();
|
||||||
|
master
|
||||||
|
.signatures
|
||||||
|
.entry(identity.user_id().to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(key_id, signature);
|
||||||
|
|
||||||
|
bob_public.master_key = master.into();
|
||||||
|
|
||||||
|
user_signing
|
||||||
|
.public_key
|
||||||
|
.verify_master_key(bob_public.master_key())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,417 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
use aes_gcm::{
|
||||||
|
aead::{generic_array::GenericArray, Aead, NewAead},
|
||||||
|
Aes256Gcm,
|
||||||
|
};
|
||||||
|
use base64::{decode_config, encode_config, DecodeError, URL_SAFE_NO_PAD};
|
||||||
|
use getrandom::getrandom;
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
encryption::DeviceKeys,
|
||||||
|
identifiers::{DeviceKeyAlgorithm, DeviceKeyId},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{Error as JsonError, Value};
|
||||||
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
|
use thiserror::Error;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
use olm_rs::pk::OlmPkSigning;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use olm_rs::{errors::OlmUtilityError, utility::OlmUtility};
|
||||||
|
|
||||||
|
use matrix_sdk_common::{
|
||||||
|
api::r0::keys::{CrossSigningKey, KeyUsage},
|
||||||
|
identifiers::UserId,
|
||||||
|
locks::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::SignatureError,
|
||||||
|
identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey},
|
||||||
|
UserIdentity,
|
||||||
|
};
|
||||||
|
|
||||||
|
const NONCE_SIZE: usize = 12;
|
||||||
|
|
||||||
|
fn encode<T: AsRef<[u8]>>(input: T) -> String {
|
||||||
|
encode_config(input, URL_SAFE_NO_PAD)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
|
||||||
|
decode_config(input, URL_SAFE_NO_PAD)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error type reporting failures in the Signign operations.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum SigningError {
|
||||||
|
/// Error decoding the base64 encoded pickle data.
|
||||||
|
#[error(transparent)]
|
||||||
|
Decode(#[from] DecodeError),
|
||||||
|
|
||||||
|
/// Error decrypting the pickled signing seed
|
||||||
|
#[error("Error decrypting the pickled signign seed")]
|
||||||
|
Decryption(String),
|
||||||
|
|
||||||
|
/// Error deserializing the pickle data.
|
||||||
|
#[error(transparent)]
|
||||||
|
Json(#[from] JsonError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Signing {
|
||||||
|
inner: Arc<Mutex<OlmPkSigning>>,
|
||||||
|
seed: Arc<Zeroizing<Vec<u8>>>,
|
||||||
|
public_key: PublicSigningKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Signing {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Signing")
|
||||||
|
.field("public_key", &self.public_key.as_str())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Signing {
|
||||||
|
fn eq(&self, other: &Signing) -> bool {
|
||||||
|
self.seed == other.seed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct InnerPickle {
|
||||||
|
version: u8,
|
||||||
|
nonce: String,
|
||||||
|
ciphertext: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct MasterSigning {
|
||||||
|
pub inner: Signing,
|
||||||
|
pub public_key: MasterPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct PickledMasterSigning {
|
||||||
|
pickle: PickledSigning,
|
||||||
|
public_key: CrossSigningKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct PickledUserSigning {
|
||||||
|
pickle: PickledSigning,
|
||||||
|
public_key: CrossSigningKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct PickledSelfSigning {
|
||||||
|
pickle: PickledSigning,
|
||||||
|
public_key: CrossSigningKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signature {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PickledSigning {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct PublicSigningKey(Arc<String>);
|
||||||
|
|
||||||
|
impl PublicSigningKey {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::inherent_to_string)]
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
self.0.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MasterSigning {
|
||||||
|
pub async fn pickle(&self, pickle_key: &[u8]) -> PickledMasterSigning {
|
||||||
|
let pickle = self.inner.pickle(pickle_key).await;
|
||||||
|
let public_key = self.public_key.clone().into();
|
||||||
|
PickledMasterSigning { pickle, public_key }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_pickle(
|
||||||
|
pickle: PickledMasterSigning,
|
||||||
|
pickle_key: &[u8],
|
||||||
|
) -> Result<Self, SigningError> {
|
||||||
|
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner,
|
||||||
|
public_key: pickle.public_key.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sign_subkey<'a>(&self, subkey: &mut CrossSigningKey) {
|
||||||
|
// TODO create a borrowed version of a cross singing key.
|
||||||
|
let subkey_wihtout_signatures = CrossSigningKey {
|
||||||
|
user_id: subkey.user_id.clone(),
|
||||||
|
keys: subkey.keys.clone(),
|
||||||
|
usage: subkey.usage.clone(),
|
||||||
|
signatures: BTreeMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = cjson::to_string(&subkey_wihtout_signatures)
|
||||||
|
.expect("Can't serialize cross signing subkey");
|
||||||
|
let signature = self.inner.sign(&message).await;
|
||||||
|
|
||||||
|
subkey
|
||||||
|
.signatures
|
||||||
|
.entry(self.public_key.user_id().to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(
|
||||||
|
DeviceKeyId::from_parts(
|
||||||
|
DeviceKeyAlgorithm::Ed25519,
|
||||||
|
self.inner.public_key().as_str().into(),
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
signature.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserSigning {
|
||||||
|
pub async fn pickle(&self, pickle_key: &[u8]) -> PickledUserSigning {
|
||||||
|
let pickle = self.inner.pickle(pickle_key).await;
|
||||||
|
let public_key = self.public_key.clone().into();
|
||||||
|
PickledUserSigning { pickle, public_key }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn sign_user(
|
||||||
|
&self,
|
||||||
|
user: &UserIdentity,
|
||||||
|
) -> Result<BTreeMap<UserId, BTreeMap<String, Value>>, SignatureError> {
|
||||||
|
let user_master: &CrossSigningKey = user.master_key().as_ref();
|
||||||
|
let signature = self
|
||||||
|
.inner
|
||||||
|
.sign_json(serde_json::to_value(user_master)?)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut signatures = BTreeMap::new();
|
||||||
|
|
||||||
|
signatures
|
||||||
|
.entry(self.public_key.user_id().to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(
|
||||||
|
DeviceKeyId::from_parts(
|
||||||
|
DeviceKeyAlgorithm::Ed25519,
|
||||||
|
self.inner.public_key.as_str().into(),
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
serde_json::to_value(signature.0)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(signatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_pickle(
|
||||||
|
pickle: PickledUserSigning,
|
||||||
|
pickle_key: &[u8],
|
||||||
|
) -> Result<Self, SigningError> {
|
||||||
|
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner,
|
||||||
|
public_key: pickle.public_key.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelfSigning {
|
||||||
|
pub async fn pickle(&self, pickle_key: &[u8]) -> PickledSelfSigning {
|
||||||
|
let pickle = self.inner.pickle(pickle_key).await;
|
||||||
|
let public_key = self.public_key.clone().into();
|
||||||
|
PickledSelfSigning { pickle, public_key }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sign_device_helper(&self, value: Value) -> Result<Signature, SignatureError> {
|
||||||
|
self.inner.sign_json(value).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sign_device(&self, device_keys: &mut DeviceKeys) -> Result<(), SignatureError> {
|
||||||
|
let json_device = serde_json::to_value(&device_keys)?;
|
||||||
|
let signature = self.sign_device_helper(json_device).await?;
|
||||||
|
|
||||||
|
device_keys
|
||||||
|
.signatures
|
||||||
|
.entry(self.public_key.user_id().to_owned())
|
||||||
|
.or_insert_with(BTreeMap::new)
|
||||||
|
.insert(
|
||||||
|
DeviceKeyId::from_parts(
|
||||||
|
DeviceKeyAlgorithm::Ed25519,
|
||||||
|
self.inner.public_key.as_str().into(),
|
||||||
|
),
|
||||||
|
signature.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_pickle(
|
||||||
|
pickle: PickledSelfSigning,
|
||||||
|
pickle_key: &[u8],
|
||||||
|
) -> Result<Self, SigningError> {
|
||||||
|
let inner = Signing::from_pickle(pickle.pickle, pickle_key)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner,
|
||||||
|
public_key: pickle.public_key.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct SelfSigning {
|
||||||
|
pub inner: Signing,
|
||||||
|
pub public_key: SelfSigningPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct UserSigning {
|
||||||
|
pub inner: Signing,
|
||||||
|
pub public_key: UserSigningPubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PickledSignings {
|
||||||
|
pub master_key: Option<PickledMasterSigning>,
|
||||||
|
pub user_signing_key: Option<PickledUserSigning>,
|
||||||
|
pub self_signing_key: Option<PickledSelfSigning>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Signature(String);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct PickledSigning(String);
|
||||||
|
|
||||||
|
impl Signing {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let seed = OlmPkSigning::generate_seed();
|
||||||
|
Self::from_seed(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_seed(seed: Vec<u8>) -> Self {
|
||||||
|
let inner = OlmPkSigning::new(seed.clone()).expect("Unable to create pk signing object");
|
||||||
|
let public_key = PublicSigningKey(Arc::new(inner.public_key().to_owned()));
|
||||||
|
|
||||||
|
Signing {
|
||||||
|
inner: Arc::new(Mutex::new(inner)),
|
||||||
|
seed: Arc::new(Zeroizing::from(seed)),
|
||||||
|
public_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_pickle(pickle: PickledSigning, pickle_key: &[u8]) -> Result<Self, SigningError> {
|
||||||
|
let pickled: InnerPickle = serde_json::from_str(pickle.as_str())?;
|
||||||
|
|
||||||
|
let key = GenericArray::from_slice(pickle_key);
|
||||||
|
let cipher = Aes256Gcm::new(key);
|
||||||
|
|
||||||
|
let nonce = decode(pickled.nonce)?;
|
||||||
|
let nonce = GenericArray::from_slice(&nonce);
|
||||||
|
let ciphertext = &decode(pickled.ciphertext)?;
|
||||||
|
|
||||||
|
let seed = cipher
|
||||||
|
.decrypt(&nonce, ciphertext.as_slice())
|
||||||
|
.map_err(|e| SigningError::Decryption(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(Self::from_seed(seed))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pickle(&self, pickle_key: &[u8]) -> PickledSigning {
|
||||||
|
let key = GenericArray::from_slice(pickle_key);
|
||||||
|
let cipher = Aes256Gcm::new(key);
|
||||||
|
|
||||||
|
let mut nonce = vec![0u8; NONCE_SIZE];
|
||||||
|
getrandom(&mut nonce).expect("Can't generate nonce to pickle the signing object");
|
||||||
|
let nonce = GenericArray::from_slice(nonce.as_slice());
|
||||||
|
|
||||||
|
let ciphertext = cipher
|
||||||
|
.encrypt(nonce, self.seed.as_slice())
|
||||||
|
.expect("Can't encrypt signing pickle");
|
||||||
|
|
||||||
|
let ciphertext = encode(ciphertext);
|
||||||
|
|
||||||
|
let pickle = InnerPickle {
|
||||||
|
version: 1,
|
||||||
|
nonce: encode(nonce.as_slice()),
|
||||||
|
ciphertext,
|
||||||
|
};
|
||||||
|
|
||||||
|
PickledSigning(serde_json::to_string(&pickle).expect("Can't encode pickled signing"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn public_key(&self) -> &PublicSigningKey {
|
||||||
|
&self.public_key
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cross_signing_key(&self, user_id: UserId, usage: KeyUsage) -> CrossSigningKey {
|
||||||
|
let mut keys = BTreeMap::new();
|
||||||
|
|
||||||
|
keys.insert(
|
||||||
|
DeviceKeyId::from_parts(
|
||||||
|
DeviceKeyAlgorithm::Ed25519,
|
||||||
|
self.public_key().as_str().into(),
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
self.public_key().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
CrossSigningKey {
|
||||||
|
user_id,
|
||||||
|
usage: vec![usage],
|
||||||
|
keys,
|
||||||
|
signatures: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub async fn verify(
|
||||||
|
&self,
|
||||||
|
message: &str,
|
||||||
|
signature: &Signature,
|
||||||
|
) -> Result<bool, OlmUtilityError> {
|
||||||
|
let utility = OlmUtility::new();
|
||||||
|
utility.ed25519_verify(self.public_key.as_str(), message, signature.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sign_json(&self, mut json: Value) -> Result<Signature, SignatureError> {
|
||||||
|
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
||||||
|
let _ = json_object.remove("signatures");
|
||||||
|
let canonical_json = cjson::to_string(json_object)?;
|
||||||
|
Ok(self.sign(&canonical_json).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sign(&self, message: &str) -> Signature {
|
||||||
|
Signature(self.inner.lock().await.sign(message))
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,9 @@ use matrix_sdk_common::{
|
||||||
claim_keys::Response as KeysClaimResponse,
|
claim_keys::Response as KeysClaimResponse,
|
||||||
get_keys::Response as KeysQueryResponse,
|
get_keys::Response as KeysQueryResponse,
|
||||||
upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse},
|
upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse},
|
||||||
|
upload_signatures::{
|
||||||
|
Request as SignatureUploadRequest, Response as SignatureUploadResponse,
|
||||||
|
},
|
||||||
upload_signing_keys::Response as SigningKeysUploadResponse,
|
upload_signing_keys::Response as SigningKeysUploadResponse,
|
||||||
CrossSigningKey,
|
CrossSigningKey,
|
||||||
},
|
},
|
||||||
|
@ -114,6 +117,9 @@ pub enum OutgoingRequests {
|
||||||
/// things, the main use is key requests/forwards and interactive device
|
/// things, the main use is key requests/forwards and interactive device
|
||||||
/// verification.
|
/// verification.
|
||||||
ToDeviceRequest(ToDeviceRequest),
|
ToDeviceRequest(ToDeviceRequest),
|
||||||
|
/// Signature upload request, this request is used after a successful device
|
||||||
|
/// or user verification is done.
|
||||||
|
SignatureUpload(SignatureUploadRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -144,6 +150,12 @@ impl From<ToDeviceRequest> for OutgoingRequests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SignatureUploadRequest> for OutgoingRequests {
|
||||||
|
fn from(request: SignatureUploadRequest) -> Self {
|
||||||
|
OutgoingRequests::SignatureUpload(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Enum over all the incoming responses we need to receive.
|
/// Enum over all the incoming responses we need to receive.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum IncomingResponse<'a> {
|
pub enum IncomingResponse<'a> {
|
||||||
|
@ -161,6 +173,9 @@ pub enum IncomingResponse<'a> {
|
||||||
/// The cross signing keys upload response, marking our private cross
|
/// The cross signing keys upload response, marking our private cross
|
||||||
/// signing identity as shared.
|
/// signing identity as shared.
|
||||||
SigningKeysUpload(&'a SigningKeysUploadResponse),
|
SigningKeysUpload(&'a SigningKeysUploadResponse),
|
||||||
|
/// The cross signing keys upload response, marking our private cross
|
||||||
|
/// signing identity as shared.
|
||||||
|
SignatureUpload(&'a SignatureUploadResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a KeysUploadResponse> for IncomingResponse<'a> {
|
impl<'a> From<&'a KeysUploadResponse> for IncomingResponse<'a> {
|
||||||
|
@ -187,6 +202,12 @@ impl<'a> From<&'a KeysClaimResponse> for IncomingResponse<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a SignatureUploadResponse> for IncomingResponse<'a> {
|
||||||
|
fn from(response: &'a SignatureUploadResponse) -> Self {
|
||||||
|
IncomingResponse::SignatureUpload(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Outgoing request type, holds the unique ID of the request and the actual
|
/// Outgoing request type, holds the unique ID of the request and the actual
|
||||||
/// request.
|
/// request.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -354,12 +354,13 @@ mod test {
|
||||||
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(
|
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(
|
||||||
user_id.clone(),
|
user_id.clone(),
|
||||||
)));
|
)));
|
||||||
let verification = VerificationMachine::new(account.clone(), identity, store.clone());
|
let verification =
|
||||||
|
VerificationMachine::new(account.clone(), identity.clone(), store.clone());
|
||||||
|
|
||||||
let user_id = Arc::new(user_id);
|
let user_id = Arc::new(user_id);
|
||||||
let device_id = Arc::new(device_id);
|
let device_id = Arc::new(device_id);
|
||||||
|
|
||||||
let store = Store::new(user_id.clone(), store, verification);
|
let store = Store::new(user_id.clone(), identity, store, verification);
|
||||||
|
|
||||||
let account = Account {
|
let account = Account {
|
||||||
inner: account,
|
inner: account,
|
||||||
|
|
|
@ -98,6 +98,7 @@ pub type Result<T> = std::result::Result<T, CryptoStoreError>;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Store {
|
pub(crate) struct Store {
|
||||||
user_id: Arc<UserId>,
|
user_id: Arc<UserId>,
|
||||||
|
identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||||
inner: Arc<Box<dyn CryptoStore>>,
|
inner: Arc<Box<dyn CryptoStore>>,
|
||||||
verification_machine: VerificationMachine,
|
verification_machine: VerificationMachine,
|
||||||
}
|
}
|
||||||
|
@ -130,11 +131,13 @@ pub struct DeviceChanges {
|
||||||
impl Store {
|
impl Store {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
user_id: Arc<UserId>,
|
user_id: Arc<UserId>,
|
||||||
|
identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||||
store: Arc<Box<dyn CryptoStore>>,
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
verification_machine: VerificationMachine,
|
verification_machine: VerificationMachine,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
user_id,
|
user_id,
|
||||||
|
identity,
|
||||||
inner: store,
|
inner: store,
|
||||||
verification_machine,
|
verification_machine,
|
||||||
}
|
}
|
||||||
|
@ -216,6 +219,7 @@ impl Store {
|
||||||
|
|
||||||
Ok(UserDevices {
|
Ok(UserDevices {
|
||||||
inner: devices,
|
inner: devices,
|
||||||
|
private_identity: self.identity.clone(),
|
||||||
verification_machine: self.verification_machine.clone(),
|
verification_machine: self.verification_machine.clone(),
|
||||||
own_identity,
|
own_identity,
|
||||||
device_owner_identity,
|
device_owner_identity,
|
||||||
|
@ -240,6 +244,7 @@ impl Store {
|
||||||
.await?
|
.await?
|
||||||
.map(|d| Device {
|
.map(|d| Device {
|
||||||
inner: d,
|
inner: d,
|
||||||
|
private_identity: self.identity.clone(),
|
||||||
verification_machine: self.verification_machine.clone(),
|
verification_machine: self.verification_machine.clone(),
|
||||||
own_identity,
|
own_identity,
|
||||||
device_owner_identity,
|
device_owner_identity,
|
||||||
|
|
|
@ -25,7 +25,7 @@ use matrix_sdk_common::{
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::sas::{content_to_request, Sas};
|
use super::sas::{content_to_request, Sas, VerificationResult};
|
||||||
use crate::{
|
use crate::{
|
||||||
olm::PrivateCrossSigningIdentity,
|
olm::PrivateCrossSigningIdentity,
|
||||||
requests::{OutgoingRequest, ToDeviceRequest},
|
requests::{OutgoingRequest, ToDeviceRequest},
|
||||||
|
@ -36,7 +36,7 @@ use crate::{
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct VerificationMachine {
|
pub struct VerificationMachine {
|
||||||
account: ReadOnlyAccount,
|
account: ReadOnlyAccount,
|
||||||
user_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
private_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
|
||||||
pub(crate) store: Arc<Box<dyn CryptoStore>>,
|
pub(crate) store: Arc<Box<dyn CryptoStore>>,
|
||||||
verifications: Arc<DashMap<String, Sas>>,
|
verifications: Arc<DashMap<String, Sas>>,
|
||||||
outgoing_to_device_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
|
outgoing_to_device_messages: Arc<DashMap<Uuid, OutgoingRequest>>,
|
||||||
|
@ -50,7 +50,7 @@ impl VerificationMachine {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
account,
|
account,
|
||||||
user_identity: identity,
|
private_identity: identity,
|
||||||
store,
|
store,
|
||||||
verifications: Arc::new(DashMap::new()),
|
verifications: Arc::new(DashMap::new()),
|
||||||
outgoing_to_device_messages: Arc::new(DashMap::new()),
|
outgoing_to_device_messages: Arc::new(DashMap::new()),
|
||||||
|
@ -62,9 +62,11 @@ impl VerificationMachine {
|
||||||
device: ReadOnlyDevice,
|
device: ReadOnlyDevice,
|
||||||
) -> Result<(Sas, ToDeviceRequest), CryptoStoreError> {
|
) -> Result<(Sas, ToDeviceRequest), CryptoStoreError> {
|
||||||
let identity = self.store.get_user_identity(device.user_id()).await?;
|
let identity = self.store.get_user_identity(device.user_id()).await?;
|
||||||
|
let private_identity = self.private_identity.lock().await.clone();
|
||||||
|
|
||||||
let (sas, content) = Sas::start(
|
let (sas, content) = Sas::start(
|
||||||
self.account.clone(),
|
self.account.clone(),
|
||||||
|
private_identity,
|
||||||
device.clone(),
|
device.clone(),
|
||||||
self.store.clone(),
|
self.store.clone(),
|
||||||
identity,
|
identity,
|
||||||
|
@ -158,8 +160,10 @@ impl VerificationMachine {
|
||||||
.get_device(&e.sender, &e.content.from_device)
|
.get_device(&e.sender, &e.content.from_device)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
|
let private_identity = self.private_identity.lock().await.clone();
|
||||||
match Sas::from_start_event(
|
match Sas::from_start_event(
|
||||||
self.account.clone(),
|
self.account.clone(),
|
||||||
|
private_identity,
|
||||||
d,
|
d,
|
||||||
self.store.clone(),
|
self.store.clone(),
|
||||||
e,
|
e,
|
||||||
|
@ -202,7 +206,9 @@ impl VerificationMachine {
|
||||||
self.receive_event_helper(&s, event);
|
self.receive_event_helper(&s, event);
|
||||||
|
|
||||||
if s.is_done() {
|
if s.is_done() {
|
||||||
if let Some(r) = s.mark_as_done().await? {
|
match s.mark_as_done().await? {
|
||||||
|
VerificationResult::Ok => (),
|
||||||
|
VerificationResult::Cancel(r) => {
|
||||||
self.outgoing_to_device_messages.insert(
|
self.outgoing_to_device_messages.insert(
|
||||||
r.txn_id,
|
r.txn_id,
|
||||||
OutgoingRequest {
|
OutgoingRequest {
|
||||||
|
@ -211,6 +217,18 @@ impl VerificationMachine {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
VerificationResult::SignatureUpload(r) => {
|
||||||
|
let request_id = Uuid::new_v4();
|
||||||
|
|
||||||
|
self.outgoing_to_device_messages.insert(
|
||||||
|
request_id,
|
||||||
|
OutgoingRequest {
|
||||||
|
request_id,
|
||||||
|
request: Arc::new(r.into()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -275,7 +293,13 @@ mod test {
|
||||||
let bob_store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(bob_store));
|
let bob_store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(bob_store));
|
||||||
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())));
|
let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id())));
|
||||||
let machine = VerificationMachine::new(alice, identity, Arc::new(Box::new(store)));
|
let machine = VerificationMachine::new(alice, identity, Arc::new(Box::new(store)));
|
||||||
let (bob_sas, start_content) = Sas::start(bob, alice_device, bob_store, None);
|
let (bob_sas, start_content) = Sas::start(
|
||||||
|
bob,
|
||||||
|
PrivateCrossSigningIdentity::empty(bob_id()),
|
||||||
|
alice_device,
|
||||||
|
bob_store,
|
||||||
|
None,
|
||||||
|
);
|
||||||
machine
|
machine
|
||||||
.receive_event(&mut wrap_any_to_device_content(
|
.receive_event(&mut wrap_any_to_device_content(
|
||||||
bob_sas.user_id(),
|
bob_sas.user_id(),
|
||||||
|
@ -342,13 +366,13 @@ mod test {
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(
|
let mut event = wrap_any_to_device_content(
|
||||||
alice.user_id(),
|
alice.user_id(),
|
||||||
get_content_from_request(&alice.confirm().await.unwrap().unwrap()),
|
get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()),
|
||||||
);
|
);
|
||||||
bob.receive_event(&mut event);
|
bob.receive_event(&mut event);
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(
|
let mut event = wrap_any_to_device_content(
|
||||||
bob.user_id(),
|
bob.user_id(),
|
||||||
get_content_from_request(&bob.confirm().await.unwrap().unwrap()),
|
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
|
||||||
);
|
);
|
||||||
alice.receive_event(&mut event);
|
alice.receive_event(&mut event);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ mod machine;
|
||||||
mod sas;
|
mod sas;
|
||||||
|
|
||||||
pub use machine::VerificationMachine;
|
pub use machine::VerificationMachine;
|
||||||
pub use sas::Sas;
|
pub use sas::{Sas, VerificationResult};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
|
|
|
@ -19,9 +19,10 @@ mod sas_state;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::{info, trace, warn};
|
use tracing::{error, info, trace, warn};
|
||||||
|
|
||||||
use matrix_sdk_common::{
|
use matrix_sdk_common::{
|
||||||
|
api::r0::keys::upload_signatures::Request as SignatureUploadRequest,
|
||||||
events::{
|
events::{
|
||||||
key::verification::{
|
key::verification::{
|
||||||
accept::AcceptEventContent, cancel::CancelCode, mac::MacEventContent,
|
accept::AcceptEventContent, cancel::CancelCode, mac::MacEventContent,
|
||||||
|
@ -33,7 +34,9 @@ use matrix_sdk_common::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::SignatureError,
|
||||||
identities::{LocalTrust, ReadOnlyDevice, UserIdentities},
|
identities::{LocalTrust, ReadOnlyDevice, UserIdentities},
|
||||||
|
olm::PrivateCrossSigningIdentity,
|
||||||
store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges},
|
store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges},
|
||||||
ReadOnlyAccount, ToDeviceRequest,
|
ReadOnlyAccount, ToDeviceRequest,
|
||||||
};
|
};
|
||||||
|
@ -43,12 +46,24 @@ use sas_state::{
|
||||||
Accepted, Canceled, Confirmed, Created, Done, KeyReceived, MacReceived, SasState, Started,
|
Accepted, Canceled, Confirmed, Created, Done, KeyReceived, MacReceived, SasState, Started,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// A result of a verification flow.
|
||||||
|
pub enum VerificationResult {
|
||||||
|
/// The verification succeeded, nothing needs to be done.
|
||||||
|
Ok,
|
||||||
|
/// The verification was canceled.
|
||||||
|
Cancel(ToDeviceRequest),
|
||||||
|
/// The verification is done and has signatures that need to be uploaded.
|
||||||
|
SignatureUpload(SignatureUploadRequest),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
/// Short authentication string object.
|
/// Short authentication string object.
|
||||||
pub struct Sas {
|
pub struct Sas {
|
||||||
inner: Arc<Mutex<InnerSas>>,
|
inner: Arc<Mutex<InnerSas>>,
|
||||||
store: Arc<Box<dyn CryptoStore>>,
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
account: ReadOnlyAccount,
|
account: ReadOnlyAccount,
|
||||||
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
other_device: ReadOnlyDevice,
|
other_device: ReadOnlyDevice,
|
||||||
other_identity: Option<UserIdentities>,
|
other_identity: Option<UserIdentities>,
|
||||||
flow_id: Arc<String>,
|
flow_id: Arc<String>,
|
||||||
|
@ -103,6 +118,7 @@ impl Sas {
|
||||||
/// sent out through the server to the other device.
|
/// sent out through the server to the other device.
|
||||||
pub(crate) fn start(
|
pub(crate) fn start(
|
||||||
account: ReadOnlyAccount,
|
account: ReadOnlyAccount,
|
||||||
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
other_device: ReadOnlyDevice,
|
other_device: ReadOnlyDevice,
|
||||||
store: Arc<Box<dyn CryptoStore>>,
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
other_identity: Option<UserIdentities>,
|
other_identity: Option<UserIdentities>,
|
||||||
|
@ -117,6 +133,7 @@ impl Sas {
|
||||||
let sas = Sas {
|
let sas = Sas {
|
||||||
inner: Arc::new(Mutex::new(inner)),
|
inner: Arc::new(Mutex::new(inner)),
|
||||||
account,
|
account,
|
||||||
|
private_identity,
|
||||||
store,
|
store,
|
||||||
other_device,
|
other_device,
|
||||||
flow_id,
|
flow_id,
|
||||||
|
@ -138,6 +155,7 @@ impl Sas {
|
||||||
/// the other side.
|
/// the other side.
|
||||||
pub(crate) fn from_start_event(
|
pub(crate) fn from_start_event(
|
||||||
account: ReadOnlyAccount,
|
account: ReadOnlyAccount,
|
||||||
|
private_identity: PrivateCrossSigningIdentity,
|
||||||
other_device: ReadOnlyDevice,
|
other_device: ReadOnlyDevice,
|
||||||
store: Arc<Box<dyn CryptoStore>>,
|
store: Arc<Box<dyn CryptoStore>>,
|
||||||
event: &ToDeviceEvent<StartEventContent>,
|
event: &ToDeviceEvent<StartEventContent>,
|
||||||
|
@ -154,6 +172,7 @@ impl Sas {
|
||||||
Ok(Sas {
|
Ok(Sas {
|
||||||
inner: Arc::new(Mutex::new(inner)),
|
inner: Arc::new(Mutex::new(inner)),
|
||||||
account,
|
account,
|
||||||
|
private_identity,
|
||||||
other_device,
|
other_device,
|
||||||
other_identity,
|
other_identity,
|
||||||
store,
|
store,
|
||||||
|
@ -179,7 +198,9 @@ impl Sas {
|
||||||
/// Does nothing if we're not in a state where we can confirm the short auth
|
/// Does nothing if we're not in a state where we can confirm the short auth
|
||||||
/// string, otherwise returns a `MacEventContent` that needs to be sent to
|
/// string, otherwise returns a `MacEventContent` that needs to be sent to
|
||||||
/// the server.
|
/// the server.
|
||||||
pub async fn confirm(&self) -> Result<Option<ToDeviceRequest>, CryptoStoreError> {
|
pub async fn confirm(
|
||||||
|
&self,
|
||||||
|
) -> Result<(Option<ToDeviceRequest>, Option<SignatureUploadRequest>), CryptoStoreError> {
|
||||||
let (content, done) = {
|
let (content, done) = {
|
||||||
let mut guard = self.inner.lock().unwrap();
|
let mut guard = self.inner.lock().unwrap();
|
||||||
let sas: InnerSas = (*guard).clone();
|
let sas: InnerSas = (*guard).clone();
|
||||||
|
@ -189,26 +210,52 @@ impl Sas {
|
||||||
(content, guard.is_done())
|
(content, guard.is_done())
|
||||||
};
|
};
|
||||||
|
|
||||||
let cancel = if done {
|
let mac_request = content
|
||||||
self.mark_as_done().await?
|
.map(|c| self.content_to_request(AnyToDeviceEventContent::KeyVerificationMac(c)));
|
||||||
|
|
||||||
|
if done {
|
||||||
|
match self.mark_as_done().await? {
|
||||||
|
VerificationResult::Cancel(r) => Ok((Some(r), None)),
|
||||||
|
VerificationResult::Ok => Ok((mac_request, None)),
|
||||||
|
VerificationResult::SignatureUpload(r) => Ok((mac_request, Some(r))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok((mac_request, None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn mark_as_done(&self) -> Result<VerificationResult, CryptoStoreError> {
|
||||||
|
if let Some(device) = self.mark_device_as_verified().await? {
|
||||||
|
let identity = self.mark_identity_as_verified().await?;
|
||||||
|
|
||||||
|
// We only sign devices of our own user here.
|
||||||
|
let signature_request = if device.user_id() == self.user_id() {
|
||||||
|
match self.private_identity.sign_device(&device).await {
|
||||||
|
Ok(r) => Some(r),
|
||||||
|
Err(SignatureError::MissingSigningKey) => {
|
||||||
|
warn!(
|
||||||
|
"Can't sign the device keys for {} {}, \
|
||||||
|
no private user signing key found",
|
||||||
|
device.user_id(),
|
||||||
|
device.device_id(),
|
||||||
|
);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Error signing device keys for {} {} {:?}",
|
||||||
|
device.user_id(),
|
||||||
|
device.device_id(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if cancel.is_some() {
|
|
||||||
Ok(cancel)
|
|
||||||
} else {
|
|
||||||
Ok(content.map(|c| {
|
|
||||||
let content = AnyToDeviceEventContent::KeyVerificationMac(c);
|
|
||||||
self.content_to_request(content)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn mark_as_done(&self) -> Result<Option<ToDeviceRequest>, CryptoStoreError> {
|
|
||||||
if let Some(device) = self.mark_device_as_verified().await? {
|
|
||||||
let identity = self.mark_identity_as_verified().await?;
|
|
||||||
|
|
||||||
let mut changes = Changes {
|
let mut changes = Changes {
|
||||||
devices: DeviceChanges {
|
devices: DeviceChanges {
|
||||||
changed: vec![device],
|
changed: vec![device],
|
||||||
|
@ -217,14 +264,68 @@ impl Sas {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(i) = identity {
|
let identity_signature_request = if let Some(i) = identity {
|
||||||
changes.identities.changed.push(i);
|
// We only sign other users here.
|
||||||
|
let request = if let Some(i) = i.other() {
|
||||||
|
// Signing can fail if the user signing key is missing.
|
||||||
|
match self.private_identity.sign_user(&i).await {
|
||||||
|
Ok(r) => Some(r),
|
||||||
|
Err(SignatureError::MissingSigningKey) => {
|
||||||
|
warn!(
|
||||||
|
"Can't sign the public cross signing keys for {}, \
|
||||||
|
no private user signing key found",
|
||||||
|
i.user_id()
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Error signing the public cross signing keys for {} {:?}",
|
||||||
|
i.user_id(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.store.save_changes(changes).await?;
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(self.cancel())
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
changes.identities.changed.push(i);
|
||||||
|
|
||||||
|
request
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// If there are two signature upload requests, merge them. Otherwise
|
||||||
|
// use the one we have or None.
|
||||||
|
//
|
||||||
|
// Realistically at most one reuqest will be used but let's make
|
||||||
|
// this future proof.
|
||||||
|
let merged_request = if let Some(mut r) = signature_request {
|
||||||
|
if let Some(user_request) = identity_signature_request {
|
||||||
|
r.signed_keys.extend(user_request.signed_keys);
|
||||||
|
Some(r)
|
||||||
|
} else {
|
||||||
|
Some(r)
|
||||||
|
}
|
||||||
|
} else if let Some(r) = identity_signature_request {
|
||||||
|
Some(r)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO store the request as well.
|
||||||
|
self.store.save_changes(changes).await?;
|
||||||
|
Ok(merged_request
|
||||||
|
.map(VerificationResult::SignatureUpload)
|
||||||
|
.unwrap_or(VerificationResult::Ok))
|
||||||
|
} else {
|
||||||
|
Ok(self
|
||||||
|
.cancel()
|
||||||
|
.map(VerificationResult::Cancel)
|
||||||
|
.unwrap_or(VerificationResult::Ok))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,9 +360,6 @@ impl Sas {
|
||||||
if let UserIdentities::Own(i) = &identity {
|
if let UserIdentities::Own(i) = &identity {
|
||||||
i.mark_as_verified();
|
i.mark_as_verified();
|
||||||
}
|
}
|
||||||
// TODO if we have the private part of the user signing
|
|
||||||
// key we should sign and upload a signature for this
|
|
||||||
// identity.
|
|
||||||
|
|
||||||
Ok(Some(identity))
|
Ok(Some(identity))
|
||||||
} else {
|
} else {
|
||||||
|
@ -314,9 +412,6 @@ impl Sas {
|
||||||
);
|
);
|
||||||
|
|
||||||
device.set_trust_state(LocalTrust::Verified);
|
device.set_trust_state(LocalTrust::Verified);
|
||||||
// TODO if this is a device from our own user and we have
|
|
||||||
// the private part of the self signing key, we should sign
|
|
||||||
// the device and upload the signature.
|
|
||||||
|
|
||||||
Ok(Some(device))
|
Ok(Some(device))
|
||||||
} else {
|
} else {
|
||||||
|
@ -684,6 +779,7 @@ mod test {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
olm::PrivateCrossSigningIdentity,
|
||||||
store::{CryptoStore, MemoryStore},
|
store::{CryptoStore, MemoryStore},
|
||||||
verification::test::{get_content_from_request, wrap_any_to_device_content},
|
verification::test::{get_content_from_request, wrap_any_to_device_content},
|
||||||
ReadOnlyAccount, ReadOnlyDevice,
|
ReadOnlyAccount, ReadOnlyDevice,
|
||||||
|
@ -813,10 +909,24 @@ mod test {
|
||||||
|
|
||||||
let bob_store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(bob_store));
|
let bob_store: Arc<Box<dyn CryptoStore>> = Arc::new(Box::new(bob_store));
|
||||||
|
|
||||||
let (alice, content) = Sas::start(alice, bob_device, alice_store, None);
|
let (alice, content) = Sas::start(
|
||||||
|
alice,
|
||||||
|
PrivateCrossSigningIdentity::empty(alice_id()),
|
||||||
|
bob_device,
|
||||||
|
alice_store,
|
||||||
|
None,
|
||||||
|
);
|
||||||
let event = wrap_to_device_event(alice.user_id(), content);
|
let event = wrap_to_device_event(alice.user_id(), content);
|
||||||
|
|
||||||
let bob = Sas::from_start_event(bob, alice_device, bob_store, &event, None).unwrap();
|
let bob = Sas::from_start_event(
|
||||||
|
bob,
|
||||||
|
PrivateCrossSigningIdentity::empty(bob_id()),
|
||||||
|
alice_device,
|
||||||
|
bob_store,
|
||||||
|
&event,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let mut event = wrap_any_to_device_content(
|
let mut event = wrap_any_to_device_content(
|
||||||
bob.user_id(),
|
bob.user_id(),
|
||||||
get_content_from_request(&bob.accept().unwrap()),
|
get_content_from_request(&bob.accept().unwrap()),
|
||||||
|
@ -841,13 +951,13 @@ mod test {
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(
|
let mut event = wrap_any_to_device_content(
|
||||||
alice.user_id(),
|
alice.user_id(),
|
||||||
get_content_from_request(&alice.confirm().await.unwrap().unwrap()),
|
get_content_from_request(&alice.confirm().await.unwrap().0.unwrap()),
|
||||||
);
|
);
|
||||||
bob.receive_event(&mut event);
|
bob.receive_event(&mut event);
|
||||||
|
|
||||||
let mut event = wrap_any_to_device_content(
|
let mut event = wrap_any_to_device_content(
|
||||||
bob.user_id(),
|
bob.user_id(),
|
||||||
get_content_from_request(&bob.confirm().await.unwrap().unwrap()),
|
get_content_from_request(&bob.confirm().await.unwrap().0.unwrap()),
|
||||||
);
|
);
|
||||||
alice.receive_event(&mut event);
|
alice.receive_event(&mut event);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue