From d3903811c6d4e7a9b718b6dd089f52f5f1a7aebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Mon, 24 Feb 2020 17:19:00 +0100 Subject: [PATCH] rust-sdk: Add initial crytpto code. --- Cargo.toml | 12 +-- src/crypto/mod.rs | 18 +++++ src/crypto/olm.rs | 191 ++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 20 ----- src/lib.rs | 3 + src/session.rs | 2 +- 6 files changed, 220 insertions(+), 26 deletions(-) create mode 100644 src/crypto/mod.rs create mode 100644 src/crypto/olm.rs diff --git a/Cargo.toml b/Cargo.toml index 969e24a1..7edebbbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" version = "0.1.0" +[features] +default = [] +encryption = ["olm-rs", "serde/derive", "serde_json"] + [dependencies] js_int = "0.1.2" futures = "0.3.4" @@ -22,13 +26,11 @@ ruma-events = "0.15.0" log = "0.4.8" ruma-identifiers = "0.14.1" -serde_json = "1.0.48" -serde_urlencoded = "0.6.1" url = "2.1.1" -[dependencies.serde] -version = "1.0.104" -features = ["derive"] +olm-rs = { git = "https://gitlab.gnome.org/jhaye/olm-rs/", optional = true} +serde = { version = "1.0.104", optional = true, features = ["derive"] } +serde_json = { version = "*", optional = true } [dev-dependencies] tokio = { version = "0.2.11", features = ["full"] } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 00000000..08b5dba4 --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,18 @@ +// 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. + +// TODO remove this. +#[allow(dead_code)] + +mod olm; diff --git a/src/crypto/olm.rs b/src/crypto/olm.rs new file mode 100644 index 00000000..a049cc69 --- /dev/null +++ b/src/crypto/olm.rs @@ -0,0 +1,191 @@ +// 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 olm_rs::account::OlmAccount; +use std::collections::{hash_map::Iter, hash_map::Keys, hash_map::Values, HashMap}; + +use serde; +use serde::Deserialize; + + +/// Struct representing the parsed result of `OlmAccount::identity_keys()`. +#[derive(Deserialize, Debug, PartialEq)] +pub struct IdentityKeys { + #[serde(flatten)] + keys: HashMap, +} + +impl IdentityKeys { + /// Get the public part of the ed25519 key of the account. + pub fn ed25519(&self) -> &str { + &self.keys["ed25519"] + } + + /// Get the public part of the curve25519 key of the account. + pub fn curve25519(&self) -> &str { + &self.keys["curve25519"] + } + + /// Get a reference to the key of the given key type. + pub fn get(&self, key_type: &str) -> Option<&str> { + let ret = self.keys.get(key_type); + ret.map(|x| &**x) + } + + /// An iterator visiting all public keys of the account. + pub fn values(&self) -> Values { + self.keys.values() + } + + /// An iterator visiting all key types of the account. + pub fn keys(&self) -> Keys { + self.keys.keys() + } + + /// An iterator visiting all key-type, key pairs of the account. + pub fn iter(&self) -> Iter { + self.keys.iter() + } + + /// Returns true if the account contains a key with the given key type. + pub fn contains_key(&self, key_type: &str) -> bool { + self.keys.contains_key(key_type) + } +} + +#[derive(Deserialize, Debug, PartialEq)] +/// Struct representing the the one-time keys. +/// The keys can be accessed in a map-like fashion. +pub struct OneTimeKeys { + #[serde(flatten)] + keys: HashMap>, +} + +impl OneTimeKeys { + /// Get the HashMap containing the curve25519 one-time keys. + /// This is the same as using `get("curve25519").unwrap()` + pub fn curve25519(&self) -> &HashMap { + &self.keys["curve25519"] + } + + /// Get a reference to the hashmap corresponding to given key type. + pub fn get(&self, key_type: &str) -> Option<&HashMap> { + self.keys.get(key_type) + } + + /// An iterator visiting all one-time key hashmaps in an arbitrary order. + pub fn values(&self) -> Values> { + self.keys.values() + } + + /// An iterator visiting all one-time key types in an arbitrary order. + pub fn keys(&self) -> Keys> { + self.keys.keys() + } + + /// An iterator visiting all one-time key types and their respective + /// key hashmaps in an arbitrary order. + pub fn iter(&self) -> Iter> { + self.keys.iter() + } + + /// Returns `true` if the struct contains the given key type. + /// This does not mean that there are any keys for the given key type. + pub fn contains_key(&self, key_type: &str) -> bool { + self.keys.contains_key(key_type) + } +} + +pub struct Account { + inner: OlmAccount, + shared: bool, +} + +impl Account { + /// Create a new account. + pub fn new() -> Self { + Account { + inner: OlmAccount::new(), + shared: false, + } + } + + /// Get the public parts of the identity keys for the account. + pub fn identity_keys(&self) -> IdentityKeys { + serde_json::from_str(&self.inner.identity_keys()).expect("Can't parse the identity keys") + } + + /// Has the account been shared with the server. + pub fn shared(&self) -> bool { + self.shared + } + + /// Get the one-time keys of the account. + /// + /// This can be empty, keys need to be generated first. + pub fn one_time_keys(&self) -> OneTimeKeys { + serde_json::from_str(&self.inner.one_time_keys()).expect("Can't parse the one-time keys") + } + + /// Generate count number of one-time keys. + pub fn generate_one_time_keys(&self, count: usize) { + self.inner.generate_one_time_keys(count); + } + + /// Get the maximum number of one-time keys the account can hold. + pub fn max_one_time_keys(&self) -> usize { + self.inner.max_number_of_one_time_keys() + } + +} + +#[cfg(test)] +mod test { + use crate::crypto::olm::Account; + + #[test] + fn account_creation() { + let account = Account::new(); + let identyty_keys = account.identity_keys(); + + assert!(!account.shared()); + assert!(!identyty_keys.ed25519().is_empty()); + assert_ne!(identyty_keys.values().len(), 0); + assert_ne!(identyty_keys.keys().len(), 0); + assert_ne!(identyty_keys.iter().len(), 0); + assert!(identyty_keys.contains_key("ed25519")); + assert_eq!(identyty_keys.ed25519(), identyty_keys.get("ed25519").unwrap()); + assert!(!identyty_keys.curve25519().is_empty()); + } + + #[test] + fn one_time_keys_creation() { + let account = Account::new(); + let one_time_keys = account.one_time_keys(); + + assert!(one_time_keys.curve25519().is_empty()); + assert_ne!(account.max_one_time_keys(), 0); + + account.generate_one_time_keys(10); + let one_time_keys = account.one_time_keys(); + + assert!(!one_time_keys.curve25519().is_empty()); + assert_ne!(one_time_keys.values().len(), 0); + assert_ne!(one_time_keys.keys().len(), 0); + assert_ne!(one_time_keys.iter().len(), 0); + assert!(one_time_keys.contains_key("curve25519")); + assert_eq!(one_time_keys.curve25519().keys().len(), 10); + assert_eq!(one_time_keys.curve25519(), one_time_keys.get("curve25519").unwrap()); + } +} diff --git a/src/error.rs b/src/error.rs index 60db8b68..a910509e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,8 +21,6 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; use reqwest::Error as ReqwestError; use ruma_api::error::FromHttpResponseError as RumaResponseError; use ruma_api::error::IntoHttpError as RumaIntoHttpError; -use serde_json::Error as SerdeJsonError; -use serde_urlencoded::ser::Error as SerdeUrlEncodedSerializeError; use url::ParseError; /// An error that can occur during client operations. @@ -37,8 +35,6 @@ impl Display for Error { InnerError::Uri(_) => "Provided string could not be converted into a URI.", InnerError::RumaResponseError(_) => "An error occurred converting between ruma_client_api and hyper types.", InnerError::IntoHttpError(_) => "An error occurred converting between ruma_client_api and hyper types.", - InnerError::SerdeJson(_) => "A serialization error occurred.", - InnerError::SerdeUrlEncodedSerialize(_) => "An error occurred serializing data to a query string.", }; write!(f, "{}", message) @@ -60,10 +56,6 @@ pub(crate) enum InnerError { RumaResponseError(RumaResponseError), /// An error converting between ruma_client_api types and Hyper types. IntoHttpError(RumaIntoHttpError), - /// An error when serializing or deserializing a JSON value. - SerdeJson(SerdeJsonError), - /// An error when serializing a query string value. - SerdeUrlEncodedSerialize(SerdeUrlEncodedSerializeError), } impl From for Error { @@ -84,18 +76,6 @@ impl From for Error { } } -impl From for Error { - fn from(error: SerdeJsonError) -> Self { - Self(InnerError::SerdeJson(error)) - } -} - -impl From for Error { - fn from(error: SerdeUrlEncodedSerializeError) -> Self { - Self(InnerError::SerdeUrlEncodedSerialize(error)) - } -} - impl From for Error { fn from(error: ReqwestError) -> Self { Self(InnerError::Reqwest(error)) diff --git a/src/lib.rs b/src/lib.rs index e0806b1f..d7e33f71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,9 @@ mod base_client; mod error; mod session; +#[cfg(feature = "encryption")] +mod crypto; + pub use async_client::{AsyncClient, AsyncClientConfig, SyncSettings}; pub use base_client::{Client, Room}; diff --git a/src/session.rs b/src/session.rs index 0559fd3c..6df86eff 100644 --- a/src/session.rs +++ b/src/session.rs @@ -18,7 +18,7 @@ use ruma_identifiers::UserId; /// A user session, containing an access token and information about the associated user account. -#[derive(Clone, Debug, serde::Deserialize, Eq, Hash, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Session { /// The access token used for this session. pub access_token: String,