example_secret_storage/
main.rs

1use anyhow::Result;
2use clap::{Parser, Subcommand};
3use matrix_sdk::{
4    authentication::matrix::MatrixSession,
5    encryption::secret_storage::SecretStore,
6    ruma::{events::secret::request::SecretName, OwnedDeviceId, OwnedUserId},
7    AuthSession, Client, SessionMeta, SessionTokens,
8};
9use url::Url;
10
11/// A command line example showcasing how the secret storage support works in
12/// the Matrix Rust SDK.
13///
14/// Secret storage is an account data backed encrypted key/value store. You can
15/// put or get secrets from the store.
16#[derive(Parser, Debug)]
17struct Cli {
18    /// The homeserver to connect to.
19    #[clap(value_parser)]
20    homeserver: Url,
21
22    /// The user ID that should be used to restore the session.
23    #[clap(value_parser)]
24    user_id: OwnedUserId,
25
26    /// The user name that should be used for the login.
27    #[clap(value_parser)]
28    device_id: OwnedDeviceId,
29
30    /// The password that should be used for the login.
31    #[clap(value_parser)]
32    access_token: String,
33
34    /// Set the proxy that should be used for the connection.
35    #[clap(short, long)]
36    proxy: Option<Url>,
37
38    /// Enable verbose logging output.
39    #[clap(short, long, action)]
40    verbose: bool,
41
42    /// The secret storage key, this key will be used to open the secret-store.
43    #[clap(long, action)]
44    secret_store_key: String,
45
46    /// The sub-command to run.
47    #[command(subcommand)]
48    command: Commands,
49}
50
51#[derive(Debug, Subcommand)]
52enum Commands {
53    /// Retrieve a secret from the homeserver.
54    GetSecret { secret_name: SecretName },
55    /// Upload a secret to the homeserver.
56    SetSecret { secret_name: SecretName, secret: String },
57    /// Import all known and specced secrets from the secret store into the
58    /// local database.
59    ///
60    /// **Note**: This command won't strictly do the right thing, as we are
61    /// reusing a device ID and access token from a different device. It will
62    /// import the secrets correctly, but it will sign device keys which don't
63    /// belong to the provided device ID.
64    ImportKnownSecrets,
65}
66
67async fn get_secret(secret_store: SecretStore, secret_name: SecretName) -> Result<()> {
68    let secret = secret_store.get_secret(secret_name.to_owned()).await?;
69
70    if let Some(secret) = secret {
71        println!("Secret: {secret}");
72    } else {
73        println!("No secret with the name {secret_name} found")
74    }
75
76    Ok(())
77}
78
79async fn set_secret(
80    secret_store: SecretStore,
81    secret_name: SecretName,
82    secret: &str,
83) -> Result<()> {
84    secret_store.put_secret(secret_name.to_owned(), secret).await?;
85
86    println!("Secret {secret_name} was successfully encrypted and stored on the homeserver");
87
88    Ok(())
89}
90
91async fn import_known_secrets(client: Client, secret_store: SecretStore) -> Result<()> {
92    secret_store.import_secrets().await?;
93
94    let status = client
95        .encryption()
96        .cross_signing_status()
97        .await
98        .expect("We should be able to get our cross-signing status");
99
100    if status.is_complete() {
101        println!("Successfully imported all the cross-signing keys");
102    } else {
103        eprintln!("Couldn't import all the cross-signing keys: {status:?}");
104    }
105
106    Ok(())
107}
108
109async fn restore_client(cli: &Cli) -> Result<Client> {
110    let builder = Client::builder().homeserver_url(&cli.homeserver);
111
112    let builder = if let Some(proxy) = cli.proxy.as_ref() { builder.proxy(proxy) } else { builder };
113    let client = builder.build().await?;
114
115    // TODO: We should be able to get the device id from `/whoami`.
116    let session = AuthSession::Matrix(MatrixSession {
117        meta: SessionMeta { user_id: cli.user_id.to_owned(), device_id: cli.device_id.to_owned() },
118        tokens: SessionTokens { access_token: cli.access_token.to_owned(), refresh_token: None },
119    });
120
121    client.restore_session(session).await?;
122
123    Ok(client)
124}
125
126#[tokio::main]
127async fn main() -> Result<()> {
128    let cli = Cli::parse();
129
130    if cli.verbose {
131        tracing_subscriber::fmt::init();
132    }
133
134    let client = restore_client(&cli).await?;
135    let secret_store =
136        client.encryption().secret_storage().open_secret_store(&cli.secret_store_key).await?;
137
138    match cli.command {
139        Commands::GetSecret { secret_name } => get_secret(secret_store, secret_name).await,
140        Commands::SetSecret { secret_name, secret } => {
141            set_secret(secret_store, secret_name, &secret).await
142        }
143        Commands::ImportKnownSecrets => import_known_secrets(client, secret_store).await,
144    }
145}