From de5a02b02a034bda43e843564ef18a8a99b0c27a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 9 Feb 2021 15:41:01 +0100 Subject: [PATCH] matrix-sdk: Initial import of the rust-sdk crypto layer. --- matrix-sdk-android/build.gradle | 16 ++ .../internal/crypto/DefaultCryptoService.kt | 9 + .../matrix/android/sdk/internal/newCrypto.kt | 72 +++++++ rust-sdk/Cargo.toml | 25 +++ rust-sdk/build.rs | 3 + rust-sdk/src/lib.rs | 178 ++++++++++++++++++ rust-sdk/src/olm.udl | 56 ++++++ 7 files changed, 359 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/newCrypto.kt create mode 100644 rust-sdk/Cargo.toml create mode 100644 rust-sdk/build.rs create mode 100644 rust-sdk/src/lib.rs create mode 100644 rust-sdk/src/olm.udl diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 836b49b3f2..707a8ddc3d 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -90,6 +90,20 @@ android { } } +android.libraryVariants.all { variant -> + def t = tasks.register("generate${variant.name.capitalize()}UniffiBindings", Exec) { + // Runs the bindings generation, note that you must have uniffi-bindgen installed + // and in your PATH environment variable + commandLine 'uniffi-bindgen', 'generate', '../rust-sdk/src/olm.udl', + '--language', 'kotlin', + '--out-dir', "${buildDir}/generated/source/uniffi/${variant.name}/java" + } + + variant.javaCompileProvider.get().dependsOn(t) + def sourceSet = variant.sourceSets.find { it.name == variant.name } + sourceSet.java.srcDir new File(buildDir, "generated/source/uniffi/${variant.name}/java") +} + static def gitRevision() { def cmd = "git rev-parse --short=8 HEAD" return cmd.execute().text.trim() @@ -116,6 +130,8 @@ dependencies { def work_version = '2.4.0' def retrofit_version = '2.6.2' + implementation 'net.java.dev.jna:jna:5.6.0@aar' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 678bc9819f..d230008b71 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -50,6 +50,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.OlmMachine import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting @@ -362,6 +363,14 @@ internal class DefaultCryptoService @Inject constructor( return } isStarting.set(true) + try { + val dataDir = "/data/data/im.vector.app.debug/files" + val olmMachine = OlmMachine("@example:localhost", "DEVICEID", dataDir) + Timber.v("HELLLO WORLD STARTING CRYPTO ${olmMachine.identityKeys()}") + } catch (throwable: Throwable) { + Timber.v("HELLLO WORLD FAILED CRYPTO $throwable") + } + Timber.v("HELLLO WORLD STARTING CRYPTO") // Open the store cryptoStore.open() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/newCrypto.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/newCrypto.kt new file mode 100644 index 0000000000..e48bc0f1f7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/newCrypto.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * 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. + */ + +package org.matrix.android.sdk.internal + +import kotlinx.coroutines.withContext +import kotlinx.coroutines.Dispatchers + +import uniffi.olm.OlmMachine as InnerMachine +import uniffi.olm.Device as InnerDevice +import uniffi.olm.Sas as InnerSas + +class Device(inner: InnerDevice, machine: InnerMachine) { + private val machine: InnerMachine = machine + private val inner: InnerDevice = inner + + fun userId(): String { + return this.inner.userId + } + + fun deviceId(): String { + return this.inner.deviceId + } + + fun keys(): Map { + return this.inner.keys + } + + fun startVerification(): InnerSas { + return this.machine.startVerification(this.inner) + } +} + +class OlmMachine(user_id: String, device_id: String, path: String) { + private val inner: InnerMachine = InnerMachine(user_id, device_id, path) + + fun userId(): String { + return this.inner.userId() + } + + fun deviceId(): String { + return this.inner.deviceId() + } + + fun identityKeys(): Map { + return this.inner.identityKeys() + } + + suspend fun slowUserId(): String = withContext(Dispatchers.Default) { + inner.slowUserId() + } + + suspend fun getDevice(user_id: String, device_id: String): Device? = withContext(Dispatchers.IO) { + when (val device: InnerDevice? = inner.getDevice(user_id, device_id)) { + null -> null + else -> Device(device, inner) + } + } +} diff --git a/rust-sdk/Cargo.toml b/rust-sdk/Cargo.toml new file mode 100644 index 0000000000..e582648437 --- /dev/null +++ b/rust-sdk/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "matrix-crypto-bindings" +version = "0.1.0" +authors = ["Damir Jelić "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "lib"] +name = "matrix_crypto" + +[dependencies] +matrix-sdk-crypto = { git = "https://github.com/matrix-org/matrix-rust-sdk/", features = ["sled_cryptostore"] } +matrix-sdk-common = { git = "https://github.com/matrix-org/matrix-rust-sdk/"} +futures = { version = "0.3.12", default_features = false, features = ["executor"] } +tokio = { version = "1.1.1", default_features = false, features = ["rt-multi-thread", "time"] } +serde_json = "1.0.61" +thiserror = "1.0.23" +http = "0.2.3" +uniffi = "0.7.0" + +[build-dependencies] +uniffi_build = "0.7.0" + +[patch.crates-io] +olm-sys = { git = "https://gitlab.gnome.org/poljar/olm-sys/", branch = "android-cross" } diff --git a/rust-sdk/build.rs b/rust-sdk/build.rs new file mode 100644 index 0000000000..bfce95467f --- /dev/null +++ b/rust-sdk/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("./src/olm.udl").unwrap(); +} diff --git a/rust-sdk/src/lib.rs b/rust-sdk/src/lib.rs new file mode 100644 index 0000000000..cdc164f629 --- /dev/null +++ b/rust-sdk/src/lib.rs @@ -0,0 +1,178 @@ +use std::{collections::HashMap, convert::TryFrom, time::Duration}; + +use futures::{ + executor::block_on, + future::{abortable, AbortHandle, Aborted}, + Future, +}; +use http::Response; +use tokio::{runtime::Runtime, time::sleep}; + +use matrix_sdk_common::{ + api::r0::sync::sync_events::Response as SyncResponse, + api::r0::to_device::send_event_to_device::METADATA, + identifiers::{Error as RumaIdentifierError, UserId}, +}; +use matrix_sdk_crypto::{ + store::CryptoStoreError as InnerStoreError, OlmMachine as InnerMachine, ToDeviceRequest, +}; + +pub struct OlmMachine { + inner: InnerMachine, + runtime: Runtime, +} + +#[derive(Debug, thiserror::Error)] +pub enum MachineCreationError { + #[error(transparent)] + Identifier(#[from] RumaIdentifierError), + #[error(transparent)] + CryptoStore(#[from] InnerStoreError), +} + +#[derive(Debug, thiserror::Error)] +pub enum CryptoStoreError { + #[error(transparent)] + CryptoStore(#[from] InnerStoreError), +} + +pub enum RequestType { + KeysQuery, + KeysUpload, + ToDevice, +} + +pub struct Device { + pub user_id: String, + pub device_id: String, + pub keys: HashMap, +} + +pub struct Sas { + pub other_user_id: String, + pub other_device_id: String, + pub flow_id: String, + pub request: Request, +} + +pub struct Request { + pub request_id: String, + pub request_type: RequestType, + pub request_body: String, +} + +impl From for Request { + fn from(r: ToDeviceRequest) -> Self { + Request { + request_id: r.txn_id_string(), + request_type: RequestType::ToDevice, + request_body: serde_json::to_string(&r.messages).unwrap(), + } + } +} + +fn response_from_string(body: &str) -> Response> { + Response::builder() + .status(200) + .body(body.as_bytes().to_vec()) + .expect("Can't create HTTP response") +} + +impl OlmMachine { + pub fn new(user_id: &str, device_id: &str, path: &str) -> Result { + let user_id = UserId::try_from(user_id)?; + let device_id = device_id.into(); + + Ok(OlmMachine { + inner: block_on(InnerMachine::new_with_default_store( + &user_id, + device_id, + path, + Some("DEFAULT_PASSPHRASE"), + ))?, + runtime: Runtime::new().unwrap(), + }) + } + + pub fn user_id(&self) -> String { + self.inner.user_id().to_string() + } + + pub fn device_id(&self) -> String { + self.inner.device_id().to_string() + } + + pub fn get_device(&self, user_id: &str, device_id: &str) -> Option { + let user_id = UserId::try_from(user_id).unwrap(); + + block_on(self.inner.get_device(&user_id, device_id.into())) + .unwrap() + .map(|d| Device { + user_id: d.user_id().to_string(), + device_id: d.device_id().to_string(), + keys: d + .keys() + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + }) + } + + pub fn get_user_devices(&self, user_id: &str) -> Vec { + let user_id = UserId::try_from(user_id).unwrap(); + block_on(self.inner.get_user_devices(&user_id)) + .unwrap() + .devices() + .map(|d| Device { + user_id: d.user_id().to_string(), + device_id: d.device_id().to_string(), + keys: d + .keys() + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + }) + .collect() + } + + pub fn identity_keys(&self) -> HashMap { + self.inner + .identity_keys() + .iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect() + } + + pub fn slow_user_id(&self) -> String { + let machine = self.inner.clone(); + + self.runtime.block_on(async { + sleep(Duration::from_secs(10)).await; + machine.user_id().to_string() + }) + } + + pub fn start_verification(&self, device: &Device) -> Result { + let user_id = UserId::try_from(device.user_id.clone()).unwrap(); + let device_id = device.device_id.as_str().into(); + let device = block_on(self.inner.get_device(&user_id, device_id))?.unwrap(); + + let (sas, request) = block_on(device.start_verification())?; + + Ok(Sas { + other_user_id: sas.other_user_id().to_string(), + other_device_id: sas.other_device_id().to_string(), + flow_id: sas.flow_id().as_str().to_owned(), + request: request.into(), + }) + } + + pub fn receive_sync_response(&self, response: &str) { + let response = response_from_string(response); + let mut response = SyncResponse::try_from(response).expect("Can't parse response"); + + block_on(self.inner.receive_sync_response(&mut response)).unwrap(); + } +} + +include!(concat!(env!("OUT_DIR"), "/olm.uniffi.rs")); diff --git a/rust-sdk/src/olm.udl b/rust-sdk/src/olm.udl new file mode 100644 index 0000000000..cb308e8fa3 --- /dev/null +++ b/rust-sdk/src/olm.udl @@ -0,0 +1,56 @@ +namespace olm {}; + +[Error] +enum MachineCreationError { + "Identifier", + "CryptoStore", +}; + +[Error] +enum CryptoStoreError { + "CryptoStore", +}; + +dictionary Device { + string user_id; + string device_id; + record keys; +}; + +dictionary Sas { + string other_user_id; + string other_device_id; + string flow_id; + Request request; +}; + +dictionary Request { + string request_id; + RequestType request_type; + string request_body; +}; + +enum RequestType { + "KeysQuery", + "KeysUpload", + "ToDevice", +}; + +interface OlmMachine { + [Throws=MachineCreationError] + constructor([ByRef] string user_id, [ByRef] string device_id, [ByRef] string path); + + void receive_sync_response([ByRef] string response); + + record identity_keys(); + + string user_id(); + string slow_user_id(); + string device_id(); + + Device? get_device([ByRef] string user_id, [ByRef] string device_id); + sequence get_user_devices([ByRef] string user_id); + + [Throws=CryptoStoreError] + Sas start_verification([ByRef] Device device); +};