From 5c14910126ccc4dd2f0b3e7ed833b22ae4b53e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Sat, 24 Oct 2020 10:32:17 +0200 Subject: [PATCH 01/13] crypto: WIP cross signing bootstrap. --- .../examples/cross_signing_bootstrap.rs | 120 ++++++++++++++ matrix_sdk/src/client.rs | 29 +++- matrix_sdk_common/Cargo.toml | 2 +- matrix_sdk_crypto/src/error.rs | 3 + matrix_sdk_crypto/src/machine.rs | 35 +++- matrix_sdk_crypto/src/olm/account.rs | 80 +++++---- matrix_sdk_crypto/src/olm/signing.rs | 156 +++++++++++++++++- 7 files changed, 383 insertions(+), 42 deletions(-) create mode 100644 matrix_sdk/examples/cross_signing_bootstrap.rs diff --git a/matrix_sdk/examples/cross_signing_bootstrap.rs b/matrix_sdk/examples/cross_signing_bootstrap.rs new file mode 100644 index 00000000..b6a4e69f --- /dev/null +++ b/matrix_sdk/examples/cross_signing_bootstrap.rs @@ -0,0 +1,120 @@ +use std::{ + collections::BTreeMap, + env, io, + process::exit, + sync::atomic::{AtomicBool, Ordering}, +}; + +use serde_json::json; +use url::Url; + +use matrix_sdk::{ + self, api::r0::uiaa::AuthData, identifiers::UserId, Client, ClientConfig, LoopCtrl, + SyncSettings, +}; + +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, + } +} + +async fn bootstrap(client: Client, user_id: UserId, password: String) { + println!("Bootstrapping a new cross signing identity, press enter to continue."); + + let mut input = String::new(); + + io::stdin() + .read_line(&mut input) + .expect("error: unable to read user input"); + + if let Err(e) = client.bootstrap_cross_signing(None).await { + if let Some(response) = e.uiaa_response() { + let auth_data = auth_data(&user_id, &password, 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); + } + } +} + +async fn login( + homeserver_url: String, + username: &str, + password: &str, +) -> Result<(), matrix_sdk::Error> { + let client_config = ClientConfig::new() + .disable_ssl_verification() + .proxy("http://localhost:8080") + .unwrap(); + 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 response = client + .login(username, password, None, Some("rust-sdk")) + .await?; + + let user_id = &response.user_id; + let client_ref = &client; + let asked = AtomicBool::new(false); + let asked_ref = &asked; + + client + .sync_with_callback(SyncSettings::new(), |_| async move { + let asked = asked_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. + if !asked.load(Ordering::SeqCst) { + tokio::spawn(bootstrap( + (*client).clone(), + (*user_id).clone(), + password.to_string(), + )); + } + + asked.store(true, Ordering::SeqCst); + LoopCtrl::Continue + }) + .await; + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), matrix_sdk::Error> { + tracing_subscriber::fmt::init(); + + let (homeserver_url, username, password) = + match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) { + (Some(a), Some(b), Some(c)) => (a, b, c), + _ => { + eprintln!( + "Usage: {} ", + env::args().next().unwrap() + ); + exit(1) + } + }; + + login(homeserver_url, &username, &password).await +} diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index c5786eff..2f592d73 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -107,7 +107,7 @@ use matrix_sdk_common::{ #[cfg(feature = "encryption")] use matrix_sdk_common::{ api::r0::{ - keys::{get_keys, upload_keys}, + keys::{get_keys, upload_keys, upload_signing_keys::Request as UploadSigningKeysRequest}, to_device::send_event_to_device::{ Request as RumaToDeviceRequest, Response as ToDeviceResponse, }, @@ -1823,6 +1823,33 @@ impl Client { })) } + /// TODO + #[cfg(feature = "encryption")] + #[cfg_attr(feature = "docs", doc(cfg(encryption)))] + pub async fn bootstrap_cross_signing(&self, auth_data: Option>) -> Result<()> { + let olm = self + .base_client + .olm_machine() + .await + .ok_or(Error::AuthenticationRequired)?; + + let (request, signature_request) = olm.bootstrap_cross_signing(false).await?; + + println!("HELLOOO MAKING REQUEST {:#?}", request); + + let request = UploadSigningKeysRequest { + auth: auth_data, + master_key: request.master_key, + self_signing_key: request.self_signing_key, + user_signing_key: request.user_signing_key, + }; + + self.send(request).await?; + self.send(signature_request).await?; + + Ok(()) + } + /// Get a map holding all the devices of an user. /// /// This will always return an empty map if the client hasn't been logged diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 4a074fb7..10094d0d 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -20,7 +20,7 @@ js_int = "0.1.9" [dependencies.ruma] version = "0.0.1" -git = "https://github.com/ruma/ruma" +path = "/home/poljar/werk/priv/ruma/ruma" rev = "db2f58032953ccb6d8ae712d64d713ebdf412598" features = ["client-api", "unstable-pre-spec", "unstable-exhaustive-types"] diff --git a/matrix_sdk_crypto/src/error.rs b/matrix_sdk_crypto/src/error.rs index 39a554bb..1692bd54 100644 --- a/matrix_sdk_crypto/src/error.rs +++ b/matrix_sdk_crypto/src/error.rs @@ -148,6 +148,9 @@ pub enum SignatureError { #[error("the provided JSON object can't be converted to a canonical representation")] CanonicalJsonError(CjsonError), + #[error(transparent)] + JsonError(#[from] SerdeError), + #[error("the signature didn't match the provided key")] VerificationError, } diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index cb80a008..b3d5589a 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -26,6 +26,7 @@ use matrix_sdk_common::{ claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse}, get_keys::Response as KeysQueryResponse, upload_keys, + upload_signatures::Request as UploadSignaturesRequest, }, sync::sync_events::Response as SyncResponse, }, @@ -95,6 +96,7 @@ pub struct OlmMachine { /// State machine handling public user identities and devices, keeping track /// of when a key query needs to be done and handling one. identity_manager: IdentityManager, + cross_signing_request: Arc>>, } #[cfg(not(tarpaulin_include))] @@ -181,13 +183,14 @@ impl OlmMachine { user_id, device_id, account, + user_identity, store, session_manager, group_session_manager, verification_machine, key_request_machine, identity_manager, - user_identity, + cross_signing_request: Arc::new(Mutex::new(None)), } } @@ -369,12 +372,32 @@ impl OlmMachine { /// devices. /// /// Uploading these keys will require user interactive auth. - pub async fn bootstrap_cross_signing(&self) -> StoreResult { - // TODO should we save the request until we get a response? + pub async fn bootstrap_cross_signing( + &self, + reset: bool, + ) -> StoreResult<(UploadSigningKeysRequest, UploadSignaturesRequest)> { let mut identity = self.user_identity.lock().await; - *identity = PrivateCrossSigningIdentity::new(self.user_id().to_owned()).await; - self.store.save_identity(identity.clone()).await?; - Ok(identity.as_upload_request().await) + + if identity.is_empty().await || reset { + info!("Creating new cross signing identity"); + let (id, signature_request) = self.account.bootstrap_cross_signing().await; + let request = id.as_upload_request().await; + + *identity = id; + + self.store.save_identity(identity.clone()).await?; + Ok((request, signature_request)) + } else { + info!("Trying to upload the existing cross signing identity"); + let request = identity.as_upload_request().await; + let device_keys = self.account.unsigned_device_keys(); + // TODO remove this expect. + let signature_request = identity + .sign_device(device_keys) + .await + .expect("Can't sign device keys"); + Ok((request, signature_request)) + } } /// Should device or one-time keys be uploaded to the server. diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 422d127f..49ce924f 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -30,7 +30,9 @@ use tracing::{debug, trace, warn}; #[cfg(test)] use matrix_sdk_common::events::EventType; use matrix_sdk_common::{ - api::r0::keys::{upload_keys, OneTimeKey, SignedKey}, + api::r0::keys::{ + upload_keys, upload_signatures::Request as SignatureUploadRequest, OneTimeKey, SignedKey, + }, encryption::DeviceKeys, events::{room::encrypted::EncryptedEventContent, AnyToDeviceEvent}, identifiers::{ @@ -50,13 +52,16 @@ use olm_rs::{ }; use crate::{ - error::{EventError, OlmResult, SessionCreationError}, + error::{EventError, OlmResult, SessionCreationError, SignatureError}, identities::ReadOnlyDevice, store::Store, OlmError, }; -use super::{EncryptionSettings, InboundGroupSession, OutboundGroupSession, Session}; +use super::{ + EncryptionSettings, InboundGroupSession, OutboundGroupSession, PrivateCrossSigningIdentity, + Session, +}; #[derive(Debug, Clone)] pub struct Account { @@ -618,9 +623,7 @@ impl ReadOnlyAccount { }) } - /// Sign the device keys of the account and return them so they can be - /// uploaded. - pub(crate) async fn device_keys(&self) -> DeviceKeys { + pub(crate) fn unsigned_device_keys(&self) -> DeviceKeys { let identity_keys = self.identity_keys(); let mut keys = BTreeMap::new(); @@ -634,34 +637,41 @@ impl ReadOnlyAccount { identity_keys.ed25519().to_owned(), ); - let device_keys = json!({ - "user_id": (*self.user_id).clone(), - "device_id": (*self.device_id).clone(), - "algorithms": Self::ALGORITHMS, - "keys": keys, - }); - - let mut signatures = BTreeMap::new(); - - let mut signature = BTreeMap::new(); - signature.insert( - DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id), - self.sign_json(&device_keys).await, - ); - signatures.insert((*self.user_id).clone(), signature); - DeviceKeys::new( (*self.user_id).clone(), (*self.device_id).clone(), - vec![ - EventEncryptionAlgorithm::OlmV1Curve25519AesSha2, - EventEncryptionAlgorithm::MegolmV1AesSha2, - ], + Self::ALGORITHMS.iter().map(|a| (&**a).clone()).collect(), keys, - signatures, + BTreeMap::new(), ) } + /// Sign the device keys of the account and return them so they can be + /// uploaded. + pub(crate) async fn device_keys(&self) -> DeviceKeys { + let mut device_keys = self.unsigned_device_keys(); + let jsond_device_keys = serde_json::to_value(&device_keys).unwrap(); + + device_keys + .signatures + .entry(self.user_id().clone()) + .or_insert_with(BTreeMap::new) + .insert( + DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, &self.device_id), + self.sign_json(jsond_device_keys) + .await + .expect("Can't sign own device keys"), + ); + + device_keys + } + + pub(crate) async fn bootstrap_cross_signing( + &self, + ) -> (PrivateCrossSigningIdentity, SignatureUploadRequest) { + PrivateCrossSigningIdentity::new_with_account(self).await + } + /// Convert a JSON value to the canonical representation and sign the JSON /// string. /// @@ -673,10 +683,13 @@ impl ReadOnlyAccount { /// # Panic /// /// Panics if the json value can't be serialized. - pub async fn sign_json(&self, json: &Value) -> String { - let canonical_json = cjson::to_string(json) - .unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", json))); - self.sign(&canonical_json).await + pub async fn sign_json(&self, mut json: Value) -> Result { + let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?; + let _ = json_object.remove("unsigned"); + let _ = json_object.remove("signatures"); + + let canonical_json = cjson::to_string(&json)?; + Ok(self.sign(&canonical_json).await) } pub(crate) async fn signed_one_time_keys_helper( @@ -690,7 +703,10 @@ impl ReadOnlyAccount { "key": key, }); - let signature = self.sign_json(&key_json).await; + let signature = self + .sign_json(key_json) + .await + .expect("Can't sign own one-time keys"); let mut signature_map = BTreeMap::new(); diff --git a/matrix_sdk_crypto/src/olm/signing.rs b/matrix_sdk_crypto/src/olm/signing.rs index dfde208b..d4332501 100644 --- a/matrix_sdk_crypto/src/olm/signing.rs +++ b/matrix_sdk_crypto/src/olm/signing.rs @@ -20,8 +20,12 @@ use aes_gcm::{ }; 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; +use serde_json::{Error as JsonError, Value}; use std::{ collections::BTreeMap, sync::{ @@ -35,16 +39,22 @@ use zeroize::Zeroizing; use olm_rs::{errors::OlmUtilityError, pk::OlmPkSigning, utility::OlmUtility}; use matrix_sdk_common::{ - api::r0::keys::{CrossSigningKey, KeyUsage}, + 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>(input: T) -> String { @@ -163,6 +173,10 @@ impl UserSigning { PickledUserSigning { pickle, public_key } } + async fn sign_user(&self, _: &UserIdentity) -> BTreeMap> { + todo!(); + } + fn from_pickle(pickle: PickledUserSigning, pickle_key: &[u8]) -> Result { let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; @@ -180,6 +194,29 @@ impl SelfSigning { PickledSelfSigning { pickle, public_key } } + async fn sign_device_raw(&self, value: Value) -> Result { + 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 { let inner = Signing::from_pickle(pickle.pickle, pickle_key)?; @@ -346,6 +383,13 @@ impl Signing { utility.ed25519_verify(self.public_key.as_str(), message, signature.as_str()) } + async fn sign_json(&self, mut json: Value) -> Result { + 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)) } @@ -356,6 +400,14 @@ impl PrivateCrossSigningIdentity { &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), @@ -366,6 +418,94 @@ impl PrivateCrossSigningIdentity { } } + pub(crate) async fn sign_device( + &self, + mut device_keys: DeviceKeys, + ) -> Result { + 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(); @@ -520,6 +660,8 @@ impl PrivateCrossSigningIdentity { #[cfg(test)] mod test { + use crate::olm::ReadOnlyAccount; + use super::{PrivateCrossSigningIdentity, Signing}; use matrix_sdk_common::identifiers::{user_id, UserId}; @@ -617,4 +759,14 @@ mod test { &*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()); + } } From 6e83a4bbca6ccd3c8055b2e1123dc6f564982811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 26 Oct 2020 14:49:24 +0100 Subject: [PATCH 02/13] crypto: Split out the signing module into two files. --- matrix_sdk_common/Cargo.toml | 4 +- .../src/olm/{signing.rs => signing/mod.rs} | 354 +--------------- .../src/olm/signing/pk_signing.rs | 380 ++++++++++++++++++ matrix_sdk_crypto/src/verification/sas/mod.rs | 1 + 4 files changed, 390 insertions(+), 349 deletions(-) rename matrix_sdk_crypto/src/olm/{signing.rs => signing/mod.rs} (55%) create mode 100644 matrix_sdk_crypto/src/olm/signing/pk_signing.rs diff --git a/matrix_sdk_common/Cargo.toml b/matrix_sdk_common/Cargo.toml index 10094d0d..b9fc3024 100644 --- a/matrix_sdk_common/Cargo.toml +++ b/matrix_sdk_common/Cargo.toml @@ -20,8 +20,8 @@ js_int = "0.1.9" [dependencies.ruma] version = "0.0.1" -path = "/home/poljar/werk/priv/ruma/ruma" -rev = "db2f58032953ccb6d8ae712d64d713ebdf412598" +git = "https://github.com/ruma/ruma" +rev = "c0eee624311c12ee9c3eb14737988e3f0a60fb3f" features = ["client-api", "unstable-pre-spec", "unstable-exhaustive-types"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/matrix_sdk_crypto/src/olm/signing.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs similarity index 55% rename from matrix_sdk_crypto/src/olm/signing.rs rename to matrix_sdk_crypto/src/olm/signing/mod.rs index d4332501..7f537bae 100644 --- a/matrix_sdk_crypto/src/olm/signing.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -14,18 +14,11 @@ #![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}, -}; +mod pk_signing; + +use matrix_sdk_common::encryption::DeviceKeys; use serde::{Deserialize, Serialize}; -use serde_json::{Error as JsonError, Value}; +use serde_json::Error as JsonError; use std::{ collections::BTreeMap, sync::{ @@ -33,211 +26,18 @@ use std::{ 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, - }, + api::r0::keys::{upload_signatures::Request as SignatureUploadRequest, KeyUsage}, identifiers::UserId, locks::Mutex, }; -use crate::{ - error::SignatureError, - identities::{MasterPubkey, SelfSigningPubkey, UserSigningPubkey}, - requests::UploadSigningKeysRequest, - UserIdentity, -}; +use crate::{error::SignatureError, requests::UploadSigningKeysRequest}; use crate::ReadOnlyAccount; -const NONCE_SIZE: usize = 12; - -fn encode>(input: T) -> String { - encode_config(input, URL_SAFE_NO_PAD) -} - -fn decode>(input: T) -> Result, 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>, - seed: Arc>>, - 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 { - 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> { - todo!(); - } - - fn from_pickle(pickle: PickledUserSigning, pickle_key: &[u8]) -> Result { - 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 { - 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 { - 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, -} +use pk_signing::{MasterSigning, PickledSignings, SelfSigning, Signing, SigningError, UserSigning}; #[derive(Clone, Debug)] pub struct PrivateCrossSigningIdentity { @@ -255,146 +55,6 @@ pub struct PickledCrossSigningIdentity { pub pickle: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] -struct PickledSignings { - master_key: Option, - user_signing_key: Option, - self_signing_key: Option, -} - -#[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); - -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) -> 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 { - 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 { - 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 { - 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 diff --git a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs new file mode 100644 index 00000000..61408b62 --- /dev/null +++ b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs @@ -0,0 +1,380 @@ +// 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::{errors::OlmUtilityError, pk::OlmPkSigning, 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>(input: T) -> String { + encode_config(input, URL_SAFE_NO_PAD) +} + +fn decode>(input: T) -> Result, 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>, + seed: Arc>>, + 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 { + 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); + +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 { + 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( + format!("ed25519:{}", self.inner.public_key().as_str()), + 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 } + } + + pub async fn sign_user(&self, _: &UserIdentity) -> BTreeMap> { + todo!(); + } + + pub fn from_pickle( + pickle: PickledUserSigning, + pickle_key: &[u8], + ) -> Result { + 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_raw(&self, value: Value) -> Result { + 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_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(()) + } + + pub fn from_pickle( + pickle: PickledSelfSigning, + pickle_key: &[u8], + ) -> Result { + 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, + pub user_signing_key: Option, + pub self_signing_key: Option, +} + +#[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) -> 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 { + 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( + format!("ed25519:{}", self.public_key().as_str()), + self.public_key().to_string(), + ); + + CrossSigningKey { + user_id, + usage: vec![usage], + keys, + signatures: BTreeMap::new(), + } + } + + pub async fn verify( + &self, + message: &str, + signature: &Signature, + ) -> Result { + 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 { + 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)) + } +} diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs index b71070a7..5c7d9be4 100644 --- a/matrix_sdk_crypto/src/verification/sas/mod.rs +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -190,6 +190,7 @@ impl Sas { }; let cancel = if done { + // Pass on the signature upload request here as well. self.mark_as_done().await? } else { None From 61a5293af5f3497299b0f52d92ca8bc887df0735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 26 Oct 2020 15:27:42 +0100 Subject: [PATCH 03/13] cyrpto: Document the signing module. --- matrix_sdk_crypto/src/machine.rs | 3 +- matrix_sdk_crypto/src/olm/account.rs | 7 ++- matrix_sdk_crypto/src/olm/signing/mod.rs | 78 +++++++++++++++--------- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index b3d5589a..b9773993 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -380,8 +380,7 @@ impl OlmMachine { if identity.is_empty().await || reset { info!("Creating new cross signing identity"); - let (id, signature_request) = self.account.bootstrap_cross_signing().await; - let request = id.as_upload_request().await; + let (id, request, signature_request) = self.account.bootstrap_cross_signing().await; *identity = id; diff --git a/matrix_sdk_crypto/src/olm/account.rs b/matrix_sdk_crypto/src/olm/account.rs index 49ce924f..150bef6c 100644 --- a/matrix_sdk_crypto/src/olm/account.rs +++ b/matrix_sdk_crypto/src/olm/account.rs @@ -54,6 +54,7 @@ use olm_rs::{ use crate::{ error::{EventError, OlmResult, SessionCreationError, SignatureError}, identities::ReadOnlyDevice, + requests::UploadSigningKeysRequest, store::Store, OlmError, }; @@ -668,7 +669,11 @@ impl ReadOnlyAccount { pub(crate) async fn bootstrap_cross_signing( &self, - ) -> (PrivateCrossSigningIdentity, SignatureUploadRequest) { + ) -> ( + PrivateCrossSigningIdentity, + UploadSigningKeysRequest, + SignatureUploadRequest, + ) { PrivateCrossSigningIdentity::new_with_account(self).await } diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index 7f537bae..60fbb491 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -56,10 +56,20 @@ pub struct PickledCrossSigningIdentity { } 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(); @@ -68,6 +78,7 @@ impl PrivateCrossSigningIdentity { !(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), @@ -78,6 +89,7 @@ impl PrivateCrossSigningIdentity { } } + /// Sign the given device keys with this identity. pub(crate) async fn sign_device( &self, mut device_keys: DeviceKeys, @@ -102,9 +114,20 @@ impl PrivateCrossSigningIdentity { 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, SignatureUploadRequest) { + ) -> (Self, UploadSigningKeysRequest, SignatureUploadRequest) { let master = Signing::new(); let mut public_key = @@ -129,12 +152,14 @@ impl PrivateCrossSigningIdentity { let identity = Self::new_helper(account.user_id(), master).await; let device_keys = account.unsigned_device_keys(); - let request = identity + let signature_request = identity .sign_device(device_keys) .await .expect("Can't sign own device with new cross signign keys"); - (identity, request) + let request = identity.as_upload_request().await; + + (identity, request, signature_request) } async fn new_helper(user_id: &UserId, master: MasterSigning) -> Self { @@ -166,6 +191,8 @@ impl PrivateCrossSigningIdentity { } } + /// Create a new cross signing identity without signing the device that + /// created it. pub(crate) async fn new(user_id: UserId) -> Self { let master = Signing::new(); @@ -175,41 +202,32 @@ impl PrivateCrossSigningIdentity { 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))), - } + 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], @@ -285,6 +303,8 @@ impl PrivateCrossSigningIdentity { }) } + /// 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 @@ -423,7 +443,7 @@ mod test { #[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 (identity, _, _) = PrivateCrossSigningIdentity::new_with_account(&account).await; let master = identity.master_key.lock().await; let master = master.as_ref().unwrap(); From e757d605f5c7daf78547159fd64c0ee61593a41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 27 Oct 2020 13:20:28 +0100 Subject: [PATCH 04/13] crypto: Allow users to be signed as well. --- matrix_sdk_crypto/src/identities/device.rs | 14 ++- matrix_sdk_crypto/src/identities/user.rs | 37 +++++- matrix_sdk_crypto/src/olm/signing/mod.rs | 114 ++++++++++++++++-- .../src/olm/signing/pk_signing.rs | 51 ++++++-- 4 files changed, 194 insertions(+), 22 deletions(-) diff --git a/matrix_sdk_crypto/src/identities/device.rs b/matrix_sdk_crypto/src/identities/device.rs index 0a75ad29..d7bd4463 100644 --- a/matrix_sdk_crypto/src/identities/device.rs +++ b/matrix_sdk_crypto/src/identities/device.rs @@ -62,7 +62,7 @@ pub struct ReadOnlyDevice { device_id: Arc>, algorithms: Arc>, keys: Arc>, - signatures: Arc>>, + pub(crate) signatures: Arc>>, display_name: Arc>, deleted: Arc, trust_state: Arc>, @@ -438,6 +438,18 @@ impl ReadOnlyDevice { ) } + #[cfg(test)] + 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 { json!({ "user_id": &*self.user_id, diff --git a/matrix_sdk_crypto/src/identities/user.rs b/matrix_sdk_crypto/src/identities/user.rs index 70026de0..6d34dc62 100644 --- a/matrix_sdk_crypto/src/identities/user.rs +++ b/matrix_sdk_crypto/src/identities/user.rs @@ -29,6 +29,8 @@ use matrix_sdk_common::{ identifiers::{DeviceKeyId, UserId}, }; +#[cfg(test)] +use crate::olm::PrivateCrossSigningIdentity; use crate::{error::SignatureError, olm::Utility, ReadOnlyDevice}; /// 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 /// 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 .0 .keys @@ -326,7 +331,7 @@ impl SelfSigningPubkey { /// /// Returns an empty result if the signature check succeeded, otherwise a /// 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 .0 .keys @@ -443,7 +448,7 @@ impl PartialEq for UserIdentities { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct UserIdentity { user_id: Arc, - master_key: MasterPubkey, + pub(crate) master_key: MasterPubkey, 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. pub fn user_id(&self) -> &UserId { &self.user_id diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index 60fbb491..97a557a5 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -12,13 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(dead_code, missing_docs)] - mod pk_signing; use matrix_sdk_common::encryption::DeviceKeys; use serde::{Deserialize, Serialize}; -use serde_json::Error as JsonError; +use serde_json::{Error as JsonError, Value}; use std::{ collections::BTreeMap, sync::{ @@ -33,25 +31,40 @@ use matrix_sdk_common::{ locks::Mutex, }; -use crate::{error::SignatureError, requests::UploadSigningKeysRequest}; - -use crate::ReadOnlyAccount; +use crate::{ + error::SignatureError, requests::UploadSigningKeysRequest, ReadOnlyAccount, 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, shared: Arc, - master_key: Arc>>, - user_signing_key: Arc>>, - self_signing_key: Arc>>, + pub(crate) master_key: Arc>>, + pub(crate) user_signing_key: Arc>>, + pub(crate) self_signing_key: Arc>>, } +/// 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, } @@ -89,6 +102,21 @@ impl PrivateCrossSigningIdentity { } } + /// Sign the given public user identity with this private identity. + #[allow(dead_code)] + pub(crate) async fn sign_user( + &self, + user_identity: &UserIdentity, + ) -> Result>, SignatureError> { + self.user_signing_key + .lock() + .await + .as_ref() + .ok_or(SignatureError::MissingSigningKey)? + .sign_user(&user_identity) + .await + } + /// Sign the given device keys with this identity. pub(crate) async fn sign_device( &self, @@ -193,6 +221,7 @@ impl PrivateCrossSigningIdentity { /// 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(); @@ -340,11 +369,18 @@ impl PrivateCrossSigningIdentity { #[cfg(test)] mod test { - use crate::olm::ReadOnlyAccount; + use crate::{ + identities::{ReadOnlyDevice, UserIdentity}, + olm::ReadOnlyAccount, + }; + use std::{collections::BTreeMap, sync::Arc}; use super::{PrivateCrossSigningIdentity, Signing}; - use matrix_sdk_common::identifiers::{user_id, UserId}; + use matrix_sdk_common::{ + api::r0::keys::CrossSigningKey, + identifiers::{user_id, UserId}, + }; use matrix_sdk_test::async_test; fn user_id() -> UserId { @@ -449,4 +485,60 @@ mod test { 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(); + } } diff --git a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs index 61408b62..838dca56 100644 --- a/matrix_sdk_crypto/src/olm/signing/pk_signing.rs +++ b/matrix_sdk_crypto/src/olm/signing/pk_signing.rs @@ -28,7 +28,10 @@ use std::{collections::BTreeMap, sync::Arc}; use thiserror::Error; use zeroize::Zeroizing; -use olm_rs::{errors::OlmUtilityError, pk::OlmPkSigning, utility::OlmUtility}; +use olm_rs::pk::OlmPkSigning; + +#[cfg(test)] +use olm_rs::{errors::OlmUtilityError, utility::OlmUtility}; use matrix_sdk_common::{ api::r0::keys::{CrossSigningKey, KeyUsage}, @@ -121,6 +124,7 @@ pub struct PickledSelfSigning { } impl Signature { + #[cfg(test)] pub fn as_str(&self) -> &str { &self.0 } @@ -183,7 +187,11 @@ impl MasterSigning { .entry(self.public_key.user_id().to_owned()) .or_insert_with(BTreeMap::new) .insert( - format!("ed25519:{}", self.inner.public_key().as_str()), + DeviceKeyId::from_parts( + DeviceKeyAlgorithm::Ed25519, + self.inner.public_key().as_str().into(), + ) + .to_string(), signature.0, ); } @@ -196,8 +204,32 @@ impl UserSigning { PickledUserSigning { pickle, public_key } } - pub async fn sign_user(&self, _: &UserIdentity) -> BTreeMap> { - todo!(); + #[allow(dead_code)] + pub async fn sign_user( + &self, + user: &UserIdentity, + ) -> Result>, 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( @@ -220,13 +252,13 @@ impl SelfSigning { PickledSelfSigning { pickle, public_key } } - pub async fn sign_device_raw(&self, value: Value) -> Result { + pub async fn sign_device_helper(&self, value: Value) -> Result { 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_raw(json_device).await?; + let signature = self.sign_device_helper(json_device).await?; device_keys .signatures @@ -346,7 +378,11 @@ impl Signing { let mut keys = BTreeMap::new(); keys.insert( - format!("ed25519:{}", self.public_key().as_str()), + DeviceKeyId::from_parts( + DeviceKeyAlgorithm::Ed25519, + self.public_key().as_str().into(), + ) + .to_string(), self.public_key().to_string(), ); @@ -358,6 +394,7 @@ impl Signing { } } + #[cfg(test)] pub async fn verify( &self, message: &str, From 2077ea0ddf5a0874ba430ca23ff9b95fe36d6a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 27 Oct 2020 13:48:51 +0100 Subject: [PATCH 05/13] crypto: Split out the device_key signing method. --- matrix_sdk_crypto/src/identities/device.rs | 1 - matrix_sdk_crypto/src/machine.rs | 3 +-- matrix_sdk_crypto/src/olm/signing/mod.rs | 24 +++++++++++++++++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/matrix_sdk_crypto/src/identities/device.rs b/matrix_sdk_crypto/src/identities/device.rs index d7bd4463..f9af780d 100644 --- a/matrix_sdk_crypto/src/identities/device.rs +++ b/matrix_sdk_crypto/src/identities/device.rs @@ -438,7 +438,6 @@ impl ReadOnlyDevice { ) } - #[cfg(test)] pub(crate) fn as_device_keys(&self) -> DeviceKeys { DeviceKeys { user_id: self.user_id().clone(), diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index b9773993..3eccab14 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -389,10 +389,9 @@ impl OlmMachine { } else { info!("Trying to upload the existing cross signing identity"); let request = identity.as_upload_request().await; - let device_keys = self.account.unsigned_device_keys(); // TODO remove this expect. let signature_request = identity - .sign_device(device_keys) + .sign_account(&self.account) .await .expect("Can't sign device keys"); Ok((request, signature_request)) diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index 97a557a5..a0cf353b 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -32,7 +32,8 @@ use matrix_sdk_common::{ }; use crate::{ - error::SignatureError, requests::UploadSigningKeysRequest, ReadOnlyAccount, UserIdentity, + error::SignatureError, requests::UploadSigningKeysRequest, ReadOnlyAccount, ReadOnlyDevice, + UserIdentity, }; use pk_signing::{MasterSigning, PickledSignings, SelfSigning, Signing, SigningError, UserSigning}; @@ -118,7 +119,25 @@ impl PrivateCrossSigningIdentity { } /// Sign the given device keys with this identity. + #[allow(dead_code)] pub(crate) async fn sign_device( + &self, + device: &ReadOnlyDevice, + ) -> Result { + let device_keys = device.as_device_keys(); + self.sign_device_keys(device_keys).await + } + + /// Sign an Olm account with this private identity. + pub(crate) async fn sign_account( + &self, + account: &ReadOnlyAccount, + ) -> Result { + let device_keys = account.unsigned_device_keys(); + self.sign_device_keys(device_keys).await + } + + async fn sign_device_keys( &self, mut device_keys: DeviceKeys, ) -> Result { @@ -179,9 +198,8 @@ impl PrivateCrossSigningIdentity { }; let identity = Self::new_helper(account.user_id(), master).await; - let device_keys = account.unsigned_device_keys(); let signature_request = identity - .sign_device(device_keys) + .sign_account(account) .await .expect("Can't sign own device with new cross signign keys"); From 30a78bb1d66068f923fa848c2ecfc73cf880a62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 27 Oct 2020 14:21:22 +0100 Subject: [PATCH 06/13] crypto: Add the private identity to the Sas object. --- matrix_sdk_crypto/src/verification/machine.rs | 16 ++++++++-- matrix_sdk_crypto/src/verification/sas/mod.rs | 31 ++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs index ba098ddc..d9725520 100644 --- a/matrix_sdk_crypto/src/verification/machine.rs +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -36,7 +36,7 @@ use crate::{ #[derive(Clone, Debug)] pub struct VerificationMachine { account: ReadOnlyAccount, - user_identity: Arc>, + private_identity: Arc>, pub(crate) store: Arc>, verifications: Arc>, outgoing_to_device_messages: Arc>, @@ -50,7 +50,7 @@ impl VerificationMachine { ) -> Self { Self { account, - user_identity: identity, + private_identity: identity, store, verifications: Arc::new(DashMap::new()), outgoing_to_device_messages: Arc::new(DashMap::new()), @@ -62,9 +62,11 @@ impl VerificationMachine { device: ReadOnlyDevice, ) -> Result<(Sas, ToDeviceRequest), CryptoStoreError> { 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( self.account.clone(), + private_identity, device.clone(), self.store.clone(), identity, @@ -158,8 +160,10 @@ impl VerificationMachine { .get_device(&e.sender, &e.content.from_device) .await? { + let private_identity = self.private_identity.lock().await.clone(); match Sas::from_start_event( self.account.clone(), + private_identity, d, self.store.clone(), e, @@ -275,7 +279,13 @@ mod test { let bob_store: Arc> = Arc::new(Box::new(bob_store)); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id()))); 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 .receive_event(&mut wrap_any_to_device_content( bob_sas.user_id(), diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs index 5c7d9be4..b978b780 100644 --- a/matrix_sdk_crypto/src/verification/sas/mod.rs +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -34,6 +34,7 @@ use matrix_sdk_common::{ use crate::{ identities::{LocalTrust, ReadOnlyDevice, UserIdentities}, + olm::PrivateCrossSigningIdentity, store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges}, ReadOnlyAccount, ToDeviceRequest, }; @@ -49,6 +50,7 @@ pub struct Sas { inner: Arc>, store: Arc>, account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, other_identity: Option, flow_id: Arc, @@ -103,6 +105,7 @@ impl Sas { /// sent out through the server to the other device. pub(crate) fn start( account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, store: Arc>, other_identity: Option, @@ -117,6 +120,7 @@ impl Sas { let sas = Sas { inner: Arc::new(Mutex::new(inner)), account, + private_identity, store, other_device, flow_id, @@ -138,6 +142,7 @@ impl Sas { /// the other side. pub(crate) fn from_start_event( account: ReadOnlyAccount, + private_identity: PrivateCrossSigningIdentity, other_device: ReadOnlyDevice, store: Arc>, event: &ToDeviceEvent, @@ -154,6 +159,7 @@ impl Sas { Ok(Sas { inner: Arc::new(Mutex::new(inner)), account, + private_identity, other_device, other_identity, store, @@ -260,9 +266,6 @@ impl Sas { if let UserIdentities::Own(i) = &identity { 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)) } else { @@ -315,9 +318,6 @@ impl Sas { ); 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)) } else { @@ -685,6 +685,7 @@ mod test { }; use crate::{ + olm::PrivateCrossSigningIdentity, store::{CryptoStore, MemoryStore}, verification::test::{get_content_from_request, wrap_any_to_device_content}, ReadOnlyAccount, ReadOnlyDevice, @@ -814,10 +815,24 @@ mod test { let bob_store: Arc> = 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 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( bob.user_id(), get_content_from_request(&bob.accept().unwrap()), From 5c530cf9ee5f83c979b2d339c3e643f396e8aeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 27 Oct 2020 16:39:23 +0100 Subject: [PATCH 07/13] crypto: Upload signatures after verification is done. --- matrix_sdk/src/client.rs | 9 ++ matrix_sdk/src/sas.rs | 8 +- matrix_sdk_crypto/src/machine.rs | 5 + matrix_sdk_crypto/src/olm/signing/mod.rs | 21 ++- matrix_sdk_crypto/src/requests.rs | 21 +++ matrix_sdk_crypto/src/verification/machine.rs | 36 +++-- matrix_sdk_crypto/src/verification/mod.rs | 2 +- matrix_sdk_crypto/src/verification/sas/mod.rs | 138 +++++++++++++++--- 8 files changed, 198 insertions(+), 42 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 2f592d73..274ee528 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -1616,6 +1616,15 @@ impl Client { .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(); + } + } } } } diff --git a/matrix_sdk/src/sas.rs b/matrix_sdk/src/sas.rs index 4f885f70..3db1df47 100644 --- a/matrix_sdk/src/sas.rs +++ b/matrix_sdk/src/sas.rs @@ -38,13 +38,19 @@ impl Sas { /// Confirm that the short auth strings match on both sides. 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 request = ToDeviceRequest::new(req.event_type, &txn_id_string, req.messages); self.http_client.send(request).await?; } + if let Some(s) = signature { + self.http_client.send(s).await?; + } + Ok(()) } diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 3eccab14..e727e067 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -351,6 +351,9 @@ impl OlmMachine { IncomingResponse::SigningKeysUpload(_) => { self.receive_cross_signing_upload_response().await?; } + IncomingResponse::SignatureUpload(_) => { + self.verification_machine.mark_request_as_sent(request_id); + } }; Ok(()) @@ -1785,6 +1788,7 @@ pub(crate) mod test { .confirm() .await .unwrap() + .0 .map(|r| request_to_event(bob.user_id(), &r)) .unwrap(); alice.handle_verification_event(&mut event).await; @@ -1796,6 +1800,7 @@ pub(crate) mod test { .confirm() .await .unwrap() + .0 .map(|r| request_to_event(alice.user_id(), &r)) .unwrap(); diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index a0cf353b..99bfcae9 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -14,9 +14,8 @@ mod pk_signing; -use matrix_sdk_common::encryption::DeviceKeys; use serde::{Deserialize, Serialize}; -use serde_json::{Error as JsonError, Value}; +use serde_json::Error as JsonError; use std::{ collections::BTreeMap, sync::{ @@ -27,7 +26,8 @@ use std::{ use matrix_sdk_common::{ api::r0::keys::{upload_signatures::Request as SignatureUploadRequest, KeyUsage}, - identifiers::UserId, + encryption::DeviceKeys, + identifiers::{DeviceKeyAlgorithm, DeviceKeyId, UserId}, locks::Mutex, }; @@ -108,14 +108,17 @@ impl PrivateCrossSigningIdentity { pub(crate) async fn sign_user( &self, user_identity: &UserIdentity, - ) -> Result>, SignatureError> { - self.user_signing_key + ) -> Result { + let signed_keys = self + .user_signing_key .lock() .await .as_ref() .ok_or(SignatureError::MissingSigningKey)? .sign_user(&user_identity) - .await + .await?; + + Ok(SignatureUploadRequest { signed_keys }) } /// Sign the given device keys with this identity. @@ -190,7 +193,11 @@ impl PrivateCrossSigningIdentity { .signatures .entry(account.user_id().to_owned()) .or_insert_with(BTreeMap::new) - .insert(format!("ed25519:{}", account.device_id()), signature); + .insert( + DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, account.device_id()) + .to_string(), + signature, + ); let master = MasterSigning { inner: master, diff --git a/matrix_sdk_crypto/src/requests.rs b/matrix_sdk_crypto/src/requests.rs index 5b70c744..fb619018 100644 --- a/matrix_sdk_crypto/src/requests.rs +++ b/matrix_sdk_crypto/src/requests.rs @@ -20,6 +20,9 @@ use matrix_sdk_common::{ claim_keys::Response as KeysClaimResponse, get_keys::Response as KeysQueryResponse, upload_keys::{Request as KeysUploadRequest, Response as KeysUploadResponse}, + upload_signatures::{ + Request as SignatureUploadRequest, Response as SignatureUploadResponse, + }, upload_signing_keys::Response as SigningKeysUploadResponse, CrossSigningKey, }, @@ -114,6 +117,9 @@ pub enum OutgoingRequests { /// things, the main use is key requests/forwards and interactive device /// verification. ToDeviceRequest(ToDeviceRequest), + /// Signature upload request, this request is used after a successful device + /// or user verification is done. + SignatureUpload(SignatureUploadRequest), } #[cfg(test)] @@ -144,6 +150,12 @@ impl From for OutgoingRequests { } } +impl From for OutgoingRequests { + fn from(request: SignatureUploadRequest) -> Self { + OutgoingRequests::SignatureUpload(request) + } +} + /// Enum over all the incoming responses we need to receive. #[derive(Debug)] pub enum IncomingResponse<'a> { @@ -161,6 +173,9 @@ pub enum IncomingResponse<'a> { /// The cross signing keys upload response, marking our private cross /// signing identity as shared. 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> { @@ -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 /// request. #[derive(Debug, Clone)] diff --git a/matrix_sdk_crypto/src/verification/machine.rs b/matrix_sdk_crypto/src/verification/machine.rs index d9725520..c3ba5e88 100644 --- a/matrix_sdk_crypto/src/verification/machine.rs +++ b/matrix_sdk_crypto/src/verification/machine.rs @@ -25,7 +25,7 @@ use matrix_sdk_common::{ uuid::Uuid, }; -use super::sas::{content_to_request, Sas}; +use super::sas::{content_to_request, Sas, VerificationResult}; use crate::{ olm::PrivateCrossSigningIdentity, requests::{OutgoingRequest, ToDeviceRequest}, @@ -206,14 +206,28 @@ impl VerificationMachine { self.receive_event_helper(&s, event); if s.is_done() { - if let Some(r) = s.mark_as_done().await? { - self.outgoing_to_device_messages.insert( - r.txn_id, - OutgoingRequest { - request_id: r.txn_id, - request: Arc::new(r.into()), - }, - ); + match s.mark_as_done().await? { + VerificationResult::Ok => (), + VerificationResult::Cancel(r) => { + self.outgoing_to_device_messages.insert( + r.txn_id, + OutgoingRequest { + request_id: r.txn_id, + request: Arc::new(r.into()), + }, + ); + } + 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()), + }, + ); + } } } }; @@ -352,13 +366,13 @@ mod test { let mut event = wrap_any_to_device_content( 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); let mut event = wrap_any_to_device_content( 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); diff --git a/matrix_sdk_crypto/src/verification/mod.rs b/matrix_sdk_crypto/src/verification/mod.rs index 7083c760..d93dc8ec 100644 --- a/matrix_sdk_crypto/src/verification/mod.rs +++ b/matrix_sdk_crypto/src/verification/mod.rs @@ -16,7 +16,7 @@ mod machine; mod sas; pub use machine::VerificationMachine; -pub use sas::Sas; +pub use sas::{Sas, VerificationResult}; #[cfg(test)] pub(crate) mod test { diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs index b978b780..0d1c3a9c 100644 --- a/matrix_sdk_crypto/src/verification/sas/mod.rs +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -19,9 +19,10 @@ mod sas_state; use std::time::Instant; use std::sync::{Arc, Mutex}; -use tracing::{info, trace, warn}; +use tracing::{error, info, trace, warn}; use matrix_sdk_common::{ + api::r0::keys::upload_signatures::Request as SignatureUploadRequest, events::{ key::verification::{ accept::AcceptEventContent, cancel::CancelCode, mac::MacEventContent, @@ -33,6 +34,7 @@ use matrix_sdk_common::{ }; use crate::{ + error::SignatureError, identities::{LocalTrust, ReadOnlyDevice, UserIdentities}, olm::PrivateCrossSigningIdentity, store::{Changes, CryptoStore, CryptoStoreError, DeviceChanges}, @@ -44,6 +46,17 @@ use sas_state::{ 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)] /// Short authentication string object. pub struct Sas { @@ -185,7 +198,9 @@ impl Sas { /// 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 /// the server. - pub async fn confirm(&self) -> Result, CryptoStoreError> { + pub async fn confirm( + &self, + ) -> Result<(Option, Option), CryptoStoreError> { let (content, done) = { let mut guard = self.inner.lock().unwrap(); let sas: InnerSas = (*guard).clone(); @@ -195,27 +210,52 @@ impl Sas { (content, guard.is_done()) }; - let cancel = if done { - // Pass on the signature upload request here as well. - self.mark_as_done().await? - } else { - None - }; + let mac_request = content + .map(|c| self.content_to_request(AnyToDeviceEventContent::KeyVerificationMac(c))); - if cancel.is_some() { - Ok(cancel) + 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(content.map(|c| { - let content = AnyToDeviceEventContent::KeyVerificationMac(c); - self.content_to_request(content) - })) + Ok((mac_request, None)) } } - pub(crate) async fn mark_as_done(&self) -> Result, CryptoStoreError> { + pub(crate) async fn mark_as_done(&self) -> Result { 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 { + None + }; + let mut changes = Changes { devices: DeviceChanges { changed: vec![device], @@ -224,14 +264,68 @@ impl Sas { ..Default::default() }; - if let Some(i) = identity { - changes.identities.changed.push(i); - } + let identity_signature_request = if let Some(i) = identity { + // 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 + } + } + } else { + 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(None) + Ok(merged_request + .map(|r| VerificationResult::SignatureUpload(r)) + .unwrap_or(VerificationResult::Ok)) } else { - Ok(self.cancel()) + Ok(self + .cancel() + .map(|r| VerificationResult::Cancel(r)) + .unwrap_or(VerificationResult::Ok)) } } @@ -857,13 +951,13 @@ mod test { let mut event = wrap_any_to_device_content( 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); let mut event = wrap_any_to_device_content( 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); From cb95f576a52bcede1f6849e33c10a42bb52eea3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 29 Oct 2020 15:37:29 +0100 Subject: [PATCH 08/13] crypto: Clear out the signatures when signing a device. This avoids re-uploading all the existing signatures. --- matrix_sdk_crypto/src/olm/signing/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index 99bfcae9..a387768c 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -122,12 +122,12 @@ impl PrivateCrossSigningIdentity { } /// Sign the given device keys with this identity. - #[allow(dead_code)] pub(crate) async fn sign_device( &self, device: &ReadOnlyDevice, ) -> Result { - let device_keys = device.as_device_keys(); + let mut device_keys = device.as_device_keys(); + device_keys.signatures.clear(); self.sign_device_keys(device_keys).await } From 34bec593899a950eb94bb3ded715c87522c047c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 30 Oct 2020 11:34:55 +0100 Subject: [PATCH 09/13] crypto: Hold on to the private identity in the store. --- matrix_sdk_crypto/src/identities/manager.rs | 3 ++- matrix_sdk_crypto/src/key_request.rs | 8 ++++---- matrix_sdk_crypto/src/machine.rs | 7 ++++++- matrix_sdk_crypto/src/session_manager/sessions.rs | 5 +++-- matrix_sdk_crypto/src/store/mod.rs | 3 +++ 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/matrix_sdk_crypto/src/identities/manager.rs b/matrix_sdk_crypto/src/identities/manager.rs index 993dd5a4..02a440a0 100644 --- a/matrix_sdk_crypto/src/identities/manager.rs +++ b/matrix_sdk_crypto/src/identities/manager.rs @@ -415,9 +415,10 @@ pub(crate) mod test { let user_id = Arc::new(user_id()); let account = ReadOnlyAccount::new(&user_id, &device_id()); let store: Arc> = 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( user_id.clone(), + identity, Arc::new(Box::new(MemoryStore::new())), verification, ); diff --git a/matrix_sdk_crypto/src/key_request.rs b/matrix_sdk_crypto/src/key_request.rs index ba98f62e..afb07840 100644 --- a/matrix_sdk_crypto/src/key_request.rs +++ b/matrix_sdk_crypto/src/key_request.rs @@ -721,8 +721,8 @@ mod test { let account = ReadOnlyAccount::new(&user_id, &alice_device_id()); let store: Arc> = Arc::new(Box::new(MemoryStore::new())); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(bob_id()))); - let verification = VerificationMachine::new(account, identity, store.clone()); - let store = Store::new(user_id.clone(), store, verification); + let verification = VerificationMachine::new(account, identity.clone(), store.clone()); + let store = Store::new(user_id.clone(), identity, store, verification); KeyRequestMachine::new( user_id, @@ -739,8 +739,8 @@ mod test { let device = ReadOnlyDevice::from_account(&account).await; let store: Arc> = Arc::new(Box::new(MemoryStore::new())); let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty(alice_id()))); - let verification = VerificationMachine::new(account, identity, store.clone()); - let store = Store::new(user_id.clone(), store, verification); + let verification = VerificationMachine::new(account, identity.clone(), store.clone()); + let store = Store::new(user_id.clone(), identity, store, verification); store.save_devices(&[device]).await.unwrap(); KeyRequestMachine::new( diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index e727e067..28eca41c 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -147,7 +147,12 @@ impl OlmMachine { let store = Arc::new(store); let verification_machine = 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 = Arc::new(device_id); let outbound_group_sessions = Arc::new(DashMap::new()); let users_for_key_claim = Arc::new(DashMap::new()); diff --git a/matrix_sdk_crypto/src/session_manager/sessions.rs b/matrix_sdk_crypto/src/session_manager/sessions.rs index 29af3b6f..8e18dfb7 100644 --- a/matrix_sdk_crypto/src/session_manager/sessions.rs +++ b/matrix_sdk_crypto/src/session_manager/sessions.rs @@ -354,12 +354,13 @@ mod test { let identity = Arc::new(Mutex::new(PrivateCrossSigningIdentity::empty( 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 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 { inner: account, diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index b19e3b07..e5400081 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -98,6 +98,7 @@ pub type Result = std::result::Result; #[derive(Debug, Clone)] pub(crate) struct Store { user_id: Arc, + identity: Arc>, inner: Arc>, verification_machine: VerificationMachine, } @@ -130,11 +131,13 @@ pub struct DeviceChanges { impl Store { pub fn new( user_id: Arc, + identity: Arc>, store: Arc>, verification_machine: VerificationMachine, ) -> Self { Self { user_id, + identity, inner: store, verification_machine, } From 44cc1cef71ec03144c0d0763d00bd2eb63e07ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 30 Oct 2020 11:41:48 +0100 Subject: [PATCH 10/13] crypto: Let devices hold on to the private identity. --- matrix_sdk_crypto/src/identities/device.rs | 6 +++++- matrix_sdk_crypto/src/identities/user.rs | 4 +++- matrix_sdk_crypto/src/store/mod.rs | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/matrix_sdk_crypto/src/identities/device.rs b/matrix_sdk_crypto/src/identities/device.rs index f9af780d..568d80f9 100644 --- a/matrix_sdk_crypto/src/identities/device.rs +++ b/matrix_sdk_crypto/src/identities/device.rs @@ -40,7 +40,7 @@ use serde_json::{json, Value}; use tracing::warn; use crate::{ - olm::{InboundGroupSession, Session}, + olm::{InboundGroupSession, PrivateCrossSigningIdentity, Session}, store::{Changes, DeviceChanges}, }; #[cfg(test)] @@ -72,6 +72,7 @@ pub struct ReadOnlyDevice { /// A device represents a E2EE capable client of an user. pub struct Device { pub(crate) inner: ReadOnlyDevice, + pub(crate) private_identity: Arc>, pub(crate) verification_machine: VerificationMachine, pub(crate) own_identity: Option, pub(crate) device_owner_identity: Option, @@ -179,6 +180,7 @@ impl Device { #[derive(Debug)] pub struct UserDevices { pub(crate) inner: HashMap, + pub(crate) private_identity: Arc>, pub(crate) verification_machine: VerificationMachine, pub(crate) own_identity: Option, pub(crate) device_owner_identity: Option, @@ -189,6 +191,7 @@ impl UserDevices { pub fn get(&self, device_id: &DeviceId) -> Option { self.inner.get(device_id).map(|d| Device { inner: d.clone(), + private_identity: self.private_identity.clone(), verification_machine: self.verification_machine.clone(), own_identity: self.own_identity.clone(), device_owner_identity: self.device_owner_identity.clone(), @@ -204,6 +207,7 @@ impl UserDevices { pub fn devices(&self) -> impl Iterator + '_ { self.inner.values().map(move |d| Device { inner: d.clone(), + private_identity: self.private_identity.clone(), verification_machine: self.verification_machine.clone(), own_identity: self.own_identity.clone(), device_owner_identity: self.device_owner_identity.clone(), diff --git a/matrix_sdk_crypto/src/identities/user.rs b/matrix_sdk_crypto/src/identities/user.rs index 6d34dc62..02d80297 100644 --- a/matrix_sdk_crypto/src/identities/user.rs +++ b/matrix_sdk_crypto/src/identities/user.rs @@ -788,13 +788,14 @@ pub(crate) mod test { ))); let verification_machine = VerificationMachine::new( ReadOnlyAccount::new(second.user_id(), second.device_id()), - private_identity, + private_identity.clone(), Arc::new(Box::new(MemoryStore::new())), ); let first = Device { inner: first, verification_machine: verification_machine.clone(), + private_identity: private_identity.clone(), own_identity: Some(identity.clone()), device_owner_identity: Some(UserIdentities::Own(identity.clone())), }; @@ -802,6 +803,7 @@ pub(crate) mod test { let second = Device { inner: second, verification_machine, + private_identity: private_identity.clone(), own_identity: Some(identity.clone()), device_owner_identity: Some(UserIdentities::Own(identity.clone())), }; diff --git a/matrix_sdk_crypto/src/store/mod.rs b/matrix_sdk_crypto/src/store/mod.rs index e5400081..3fda11ba 100644 --- a/matrix_sdk_crypto/src/store/mod.rs +++ b/matrix_sdk_crypto/src/store/mod.rs @@ -219,6 +219,7 @@ impl Store { Ok(UserDevices { inner: devices, + private_identity: self.identity.clone(), verification_machine: self.verification_machine.clone(), own_identity, device_owner_identity, @@ -243,6 +244,7 @@ impl Store { .await? .map(|d| Device { inner: d, + private_identity: self.identity.clone(), verification_machine: self.verification_machine.clone(), own_identity, device_owner_identity, From b67cd4ddd2ddb05ff6a288d59a82212b485f500b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 30 Oct 2020 13:21:14 +0100 Subject: [PATCH 11/13] crypto: Create a trusted public cross signing identity when we create a private one. --- matrix_sdk/src/client.rs | 2 -- matrix_sdk_crypto/src/identities/user.rs | 36 +++++++++++++++++++ matrix_sdk_crypto/src/machine.rs | 13 +++++++ matrix_sdk_crypto/src/olm/signing/mod.rs | 45 ++++++++++++++++++++---- 4 files changed, 87 insertions(+), 9 deletions(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 274ee528..14237e4d 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -1844,8 +1844,6 @@ impl Client { let (request, signature_request) = olm.bootstrap_cross_signing(false).await?; - println!("HELLOOO MAKING REQUEST {:#?}", request); - let request = UploadSigningKeysRequest { auth: auth_data, master_key: request.master_key, diff --git a/matrix_sdk_crypto/src/identities/user.rs b/matrix_sdk_crypto/src/identities/user.rs index 02d80297..ea35e5a0 100644 --- a/matrix_sdk_crypto/src/identities/user.rs +++ b/matrix_sdk_crypto/src/identities/user.rs @@ -723,6 +723,7 @@ pub(crate) mod test { use matrix_sdk_common::{ api::r0::keys::get_keys::Response as KeyQueryResponse, identifiers::user_id, locks::Mutex, }; + use matrix_sdk_test::async_test; use super::{OwnUserIdentity, UserIdentities, UserIdentity}; @@ -818,4 +819,39 @@ pub(crate) mod test { assert!(second.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()); + } } diff --git a/matrix_sdk_crypto/src/machine.rs b/matrix_sdk_crypto/src/machine.rs index 28eca41c..16821b18 100644 --- a/matrix_sdk_crypto/src/machine.rs +++ b/matrix_sdk_crypto/src/machine.rs @@ -392,6 +392,19 @@ impl OlmMachine { *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?; Ok((request, signature_request)) } else { diff --git a/matrix_sdk_crypto/src/olm/signing/mod.rs b/matrix_sdk_crypto/src/olm/signing/mod.rs index a387768c..3cc9e3c0 100644 --- a/matrix_sdk_crypto/src/olm/signing/mod.rs +++ b/matrix_sdk_crypto/src/olm/signing/mod.rs @@ -32,8 +32,8 @@ use matrix_sdk_common::{ }; use crate::{ - error::SignatureError, requests::UploadSigningKeysRequest, ReadOnlyAccount, ReadOnlyDevice, - UserIdentity, + error::SignatureError, requests::UploadSigningKeysRequest, OwnUserIdentity, ReadOnlyAccount, + ReadOnlyDevice, UserIdentity, }; use pk_signing::{MasterSigning, PickledSignings, SelfSigning, Signing, SigningError, UserSigning}; @@ -103,6 +103,37 @@ impl PrivateCrossSigningIdentity { } } + pub(crate) async fn as_public_identity(&self) -> Result { + 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( @@ -128,7 +159,7 @@ impl PrivateCrossSigningIdentity { ) -> Result { let mut device_keys = device.as_device_keys(); device_keys.signatures.clear(); - self.sign_device_keys(device_keys).await + self.sign_device_keys(&mut device_keys).await } /// Sign an Olm account with this private identity. @@ -136,13 +167,13 @@ impl PrivateCrossSigningIdentity { &self, account: &ReadOnlyAccount, ) -> Result { - let device_keys = account.unsigned_device_keys(); - self.sign_device_keys(device_keys).await + let mut device_keys = account.unsigned_device_keys(); + self.sign_device_keys(&mut device_keys).await } - async fn sign_device_keys( + pub(crate) async fn sign_device_keys( &self, - mut device_keys: DeviceKeys, + mut device_keys: &mut DeviceKeys, ) -> Result { self.self_signing_key .lock() From b27f1b0e3411dbbe208bda5face7744eac477e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 30 Oct 2020 14:38:29 +0100 Subject: [PATCH 12/13] crypto: Fix some clippy warnings. --- matrix_sdk_crypto/src/identities/user.rs | 2 +- matrix_sdk_crypto/src/verification/sas/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix_sdk_crypto/src/identities/user.rs b/matrix_sdk_crypto/src/identities/user.rs index ea35e5a0..a53c9570 100644 --- a/matrix_sdk_crypto/src/identities/user.rs +++ b/matrix_sdk_crypto/src/identities/user.rs @@ -804,7 +804,7 @@ pub(crate) mod test { let second = Device { inner: second, verification_machine, - private_identity: private_identity.clone(), + private_identity, own_identity: Some(identity.clone()), device_owner_identity: Some(UserIdentities::Own(identity.clone())), }; diff --git a/matrix_sdk_crypto/src/verification/sas/mod.rs b/matrix_sdk_crypto/src/verification/sas/mod.rs index 0d1c3a9c..9ef25bb8 100644 --- a/matrix_sdk_crypto/src/verification/sas/mod.rs +++ b/matrix_sdk_crypto/src/verification/sas/mod.rs @@ -319,12 +319,12 @@ impl Sas { // TODO store the request as well. self.store.save_changes(changes).await?; Ok(merged_request - .map(|r| VerificationResult::SignatureUpload(r)) + .map(VerificationResult::SignatureUpload) .unwrap_or(VerificationResult::Ok)) } else { Ok(self .cancel() - .map(|r| VerificationResult::Cancel(r)) + .map(VerificationResult::Cancel) .unwrap_or(VerificationResult::Ok)) } } From 11fcf5c42f6a04c61f49618f1ae8201d768fec67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 5 Nov 2020 14:33:45 +0100 Subject: [PATCH 13/13] rust-sdk: Document the cross signing bootstrap method. --- matrix_sdk/src/client.rs | 53 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/matrix_sdk/src/client.rs b/matrix_sdk/src/client.rs index 14237e4d..7abfa5df 100644 --- a/matrix_sdk/src/client.rs +++ b/matrix_sdk/src/client.rs @@ -1832,7 +1832,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_attr(feature = "docs", doc(cfg(encryption)))] pub async fn bootstrap_cross_signing(&self, auth_data: Option>) -> Result<()> {