From 371c546f9385ca3bf026c840cea4f9787e48df31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 26 Sep 2023 12:57:05 +0200 Subject: [PATCH] Add an example for the secret storage support --- Cargo.lock | 13 +++ examples/secret_storage/Cargo.toml | 20 ++++ examples/secret_storage/src/main.rs | 148 ++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 examples/secret_storage/Cargo.toml create mode 100644 examples/secret_storage/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 0b8931d4c4..a72d3982f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1660,6 +1660,19 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "example-secret-storage" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "futures-util", + "matrix-sdk", + "tokio", + "tracing-subscriber", + "url", +] + [[package]] name = "example-timeline" version = "0.1.0" diff --git a/examples/secret_storage/Cargo.toml b/examples/secret_storage/Cargo.toml new file mode 100644 index 0000000000..503b5d485e --- /dev/null +++ b/examples/secret_storage/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "example-secret-storage" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "example-secret-storage" +test = false + +[dependencies] +anyhow = "1" +tokio = { version = "1.24.2", features = ["macros", "rt-multi-thread"] } +clap = { version = "4.0.15", features = ["derive"] } +futures-util = "0.3.24" +tracing-subscriber = "0.3.16" +url = "2.3.1" +# when copy-pasting this, please use a git dependency or make sure that you +# have copied the example as it was at the time of the release you use. +matrix-sdk = { path = "../../crates/matrix-sdk" } diff --git a/examples/secret_storage/src/main.rs b/examples/secret_storage/src/main.rs new file mode 100644 index 0000000000..1175c46126 --- /dev/null +++ b/examples/secret_storage/src/main.rs @@ -0,0 +1,148 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; +use matrix_sdk::{ + encryption::secret_storage::SecretStore, + matrix_auth::{MatrixSession, MatrixSessionTokens}, + ruma::{events::secret::request::SecretName, OwnedDeviceId, OwnedUserId}, + AuthSession, Client, SessionMeta, +}; +use url::Url; + +/// A command line example showcasing how the secret storage support works in +/// the Matrix Rust SDK. +/// +/// Secret storage is an account data backed encrypted key/value store. You can +/// put or get secrets from the store. +#[derive(Parser, Debug)] +struct Cli { + /// The homeserver to connect to. + #[clap(value_parser)] + homeserver: Url, + + /// The user ID that should be used to restore the session. + #[clap(value_parser)] + user_id: OwnedUserId, + + /// The user name that should be used for the login. + #[clap(value_parser)] + device_id: OwnedDeviceId, + + /// The password that should be used for the login. + #[clap(value_parser)] + access_token: String, + + /// Set the proxy that should be used for the connection. + #[clap(short, long)] + proxy: Option, + + /// Enable verbose logging output. + #[clap(short, long, action)] + verbose: bool, + + /// The secret storage key, this key will be used to open the secret-store. + #[clap(long, action)] + secret_store_key: String, + + /// The sub-command to run. + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + /// Retrieve a secret from the homeserver. + GetSecret { secret_name: SecretName }, + /// Upload a secret to the homeserver. + SetSecret { secret_name: SecretName, secret: String }, + /// Import all known and specced secrets from the secret store into the + /// local database. + /// + /// **Note**: This command won't strictly do the right thing, as we are + /// reusing a device ID and access token from a different device. It will + /// import the secrets correctly, but it will sign device keys which don't + /// belong to the provided device ID. + ImportKnownSecrets, +} + +async fn get_secret(secret_store: SecretStore, secret_name: SecretName) -> Result<()> { + let secret = secret_store.get_secret(secret_name.to_owned()).await?; + + if let Some(secret) = secret { + println!("Secret: {secret}"); + } else { + println!("No secret with the name {secret_name} found") + } + + Ok(()) +} + +async fn set_secret( + secret_store: SecretStore, + secret_name: SecretName, + secret: &str, +) -> Result<()> { + secret_store.put_secret(secret_name.to_owned(), secret).await?; + + println!("Secret {secret_name} was successfully encrypted and stored on the homeserver"); + + Ok(()) +} + +async fn import_known_secrets(client: Client, secret_store: SecretStore) -> Result<()> { + secret_store.import_secrets().await?; + + let status = client + .encryption() + .cross_signing_status() + .await + .expect("We should be able to get our cross-signing status"); + + if status.is_complete() { + println!("Successfully imported all the cross-signing keys"); + } else { + eprintln!("Couldn't import all the cross-signing keys: {status:?}"); + } + + Ok(()) +} + +async fn restore_client(cli: &Cli) -> Result { + let builder = Client::builder().homeserver_url(&cli.homeserver); + + let builder = if let Some(proxy) = cli.proxy.as_ref() { builder.proxy(proxy) } else { builder }; + let client = builder.build().await?; + + // TODO: We should be able to get the device id from `/whoami`. + let session = AuthSession::Matrix(MatrixSession { + meta: SessionMeta { user_id: cli.user_id.to_owned(), device_id: cli.device_id.to_owned() }, + tokens: MatrixSessionTokens { + access_token: cli.access_token.to_owned(), + refresh_token: None, + }, + }); + + client.restore_session(session).await?; + + Ok(client) +} + +#[tokio::main] +async fn main() -> Result<()> { + let cli = Cli::parse(); + + if cli.verbose { + tracing_subscriber::fmt::init(); + } + + let client = restore_client(&cli).await?; + let secret_store = + client.encryption().secret_storage().open_secret_store(&cli.secret_store_key).await?; + + match cli.command { + Commands::GetSecret { secret_name } => get_secret(secret_store, secret_name).await, + Commands::SetSecret { secret_name, secret } => { + set_secret(secret_store, secret_name, &secret).await + } + Commands::ImportKnownSecrets => import_known_secrets(client, secret_store).await, + } +}