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