Add commands for importing and exporting room keys (#233)

This commit is contained in:
Ulyssa 2024-03-28 20:58:34 -07:00 committed by GitHub
parent b4e9c213e6
commit 2327658e8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 116 additions and 3 deletions

View file

@ -61,12 +61,22 @@ Log out of
View a list of joined rooms. View a list of joined rooms.
.It Sy ":spaces" .It Sy ":spaces"
View a list of joined spaces. View a list of joined spaces.
.It Sy ":verify"
View a list of ongoing E2EE verifications.
.It Sy ":welcome" .It Sy ":welcome"
View the startup Welcome window. View the startup Welcome window.
.El .El
.Sh "E2EE COMMANDS"
.Bl -tag -width Ds
.It Sy ":keys export [path] [passphrase]"
Export and encrypt keys to
.Pa path .
.It Sy ":keys import [path] [passphrase]"
Import and decrypt keys from
.Pa path .
.It Sy ":verify"
View a list of ongoing E2EE verifications.
.El
.Sh "MESSAGE COMMANDS" .Sh "MESSAGE COMMANDS"
.Bl -tag -width Ds .Bl -tag -width Ds
.It Sy ":download" .It Sy ":download"

View file

@ -420,6 +420,15 @@ pub enum HomeserverAction {
Logout(String, bool), Logout(String, bool),
} }
/// An action performed against the user's room keys.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum KeysAction {
/// Export room keys to a file, encrypted with a passphrase.
Export(String, String),
/// Import room keys from a file, encrypted with a passphrase.
Import(String, String),
}
/// An action that the main program loop should. /// An action that the main program loop should.
/// ///
/// See [the commands module][super::commands] for where these are usually created. /// See [the commands module][super::commands] for where these are usually created.
@ -428,6 +437,9 @@ pub enum IambAction {
/// Perform an action against the homeserver. /// Perform an action against the homeserver.
Homeserver(HomeserverAction), Homeserver(HomeserverAction),
/// Perform an action over room keys.
Keys(KeysAction),
/// Perform an action on the currently selected message. /// Perform an action on the currently selected message.
Message(MessageAction), Message(MessageAction),
@ -485,6 +497,7 @@ impl ApplicationAction for IambAction {
fn is_edit_sequence(&self, _: &EditContext) -> SequenceStatus { fn is_edit_sequence(&self, _: &EditContext) -> SequenceStatus {
match self { match self {
IambAction::Homeserver(..) => SequenceStatus::Break, IambAction::Homeserver(..) => SequenceStatus::Break,
IambAction::Keys(..) => SequenceStatus::Break,
IambAction::Message(..) => SequenceStatus::Break, IambAction::Message(..) => SequenceStatus::Break,
IambAction::Room(..) => SequenceStatus::Break, IambAction::Room(..) => SequenceStatus::Break,
IambAction::OpenLink(..) => SequenceStatus::Break, IambAction::OpenLink(..) => SequenceStatus::Break,
@ -498,6 +511,7 @@ impl ApplicationAction for IambAction {
fn is_last_action(&self, _: &EditContext) -> SequenceStatus { fn is_last_action(&self, _: &EditContext) -> SequenceStatus {
match self { match self {
IambAction::Homeserver(..) => SequenceStatus::Atom, IambAction::Homeserver(..) => SequenceStatus::Atom,
IambAction::Keys(..) => SequenceStatus::Atom,
IambAction::Message(..) => SequenceStatus::Atom, IambAction::Message(..) => SequenceStatus::Atom,
IambAction::OpenLink(..) => SequenceStatus::Atom, IambAction::OpenLink(..) => SequenceStatus::Atom,
IambAction::Room(..) => SequenceStatus::Atom, IambAction::Room(..) => SequenceStatus::Atom,
@ -511,6 +525,7 @@ impl ApplicationAction for IambAction {
fn is_last_selection(&self, _: &EditContext) -> SequenceStatus { fn is_last_selection(&self, _: &EditContext) -> SequenceStatus {
match self { match self {
IambAction::Homeserver(..) => SequenceStatus::Ignore, IambAction::Homeserver(..) => SequenceStatus::Ignore,
IambAction::Keys(..) => SequenceStatus::Ignore,
IambAction::Message(..) => SequenceStatus::Ignore, IambAction::Message(..) => SequenceStatus::Ignore,
IambAction::Room(..) => SequenceStatus::Ignore, IambAction::Room(..) => SequenceStatus::Ignore,
IambAction::OpenLink(..) => SequenceStatus::Ignore, IambAction::OpenLink(..) => SequenceStatus::Ignore,
@ -526,6 +541,7 @@ impl ApplicationAction for IambAction {
IambAction::Homeserver(..) => false, IambAction::Homeserver(..) => false,
IambAction::Message(..) => false, IambAction::Message(..) => false,
IambAction::Room(..) => false, IambAction::Room(..) => false,
IambAction::Keys(..) => false,
IambAction::Send(..) => false, IambAction::Send(..) => false,
IambAction::OpenLink(..) => false, IambAction::OpenLink(..) => false,
IambAction::ToggleScrollbackFocus => false, IambAction::ToggleScrollbackFocus => false,
@ -585,6 +601,9 @@ pub enum IambError {
#[error("Cryptographic storage error: {0}")] #[error("Cryptographic storage error: {0}")]
CryptoStore(#[from] matrix_sdk::encryption::CryptoStoreError), CryptoStore(#[from] matrix_sdk::encryption::CryptoStoreError),
#[error("Failed to import room keys: {0}")]
FailedKeyImport(#[from] matrix_sdk::encryption::RoomKeyImportError),
/// A failure related to the cryptographic store. /// A failure related to the cryptographic store.
#[error("Cannot export keys from sled: {0}")] #[error("Cannot export keys from sled: {0}")]
UpgradeSled(#[from] crate::sled_export::SledMigrationError), UpgradeSled(#[from] crate::sled_export::SledMigrationError),
@ -1767,7 +1786,7 @@ fn complete_cmdarg(
match cmd.name.as_str() { match cmd.name.as_str() {
"cancel" | "dms" | "edit" | "redact" | "reply" => vec![], "cancel" | "dms" | "edit" | "redact" | "reply" => vec![],
"members" | "rooms" | "spaces" | "welcome" => vec![], "members" | "rooms" | "spaces" | "welcome" => vec![],
"download" | "open" | "upload" => complete_path(text, cursor), "download" | "keys" | "open" | "upload" => complete_path(text, cursor),
"react" | "unreact" => complete_emoji(text, cursor, store), "react" | "unreact" => complete_emoji(text, cursor, store),
"invite" => complete_users(text, cursor, store), "invite" => complete_users(text, cursor, store),

View file

@ -19,6 +19,7 @@ use crate::base::{
HomeserverAction, HomeserverAction,
IambAction, IambAction,
IambId, IambId,
KeysAction,
MessageAction, MessageAction,
ProgramCommand, ProgramCommand,
ProgramCommands, ProgramCommands,
@ -102,6 +103,29 @@ fn iamb_invite(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
return Ok(step); return Ok(step);
} }
fn iamb_keys(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
let mut args = desc.arg.strings()?;
if args.len() != 3 {
return Err(CommandError::InvalidArgument);
}
let act = args.remove(0);
let path = args.remove(0);
let passphrase = args.remove(0);
let act = match act.as_str() {
"export" => KeysAction::Export(path, passphrase),
"import" => KeysAction::Import(path, passphrase),
_ => return Err(CommandError::InvalidArgument),
};
let vact = IambAction::Keys(act);
let step = CommandStep::Continue(vact.into(), ctx.context.clone());
return Ok(step);
}
fn iamb_verify(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { fn iamb_verify(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
let mut args = desc.arg.strings()?; let mut args = desc.arg.strings()?;
@ -523,6 +547,7 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) {
f: iamb_invite, f: iamb_invite,
}); });
cmds.add_command(ProgramCommand { name: "join".into(), aliases: vec![], f: iamb_join }); cmds.add_command(ProgramCommand { name: "join".into(), aliases: vec![], f: iamb_join });
cmds.add_command(ProgramCommand { name: "keys".into(), aliases: vec![], f: iamb_keys });
cmds.add_command(ProgramCommand { cmds.add_command(ProgramCommand {
name: "leave".into(), name: "leave".into(),
aliases: vec![], aliases: vec![],
@ -959,4 +984,31 @@ mod tests {
let res = cmds.input_cmd("redact Removed Removed", ctx.clone()); let res = cmds.input_cmd("redact Removed Removed", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument)); assert_eq!(res, Err(CommandError::InvalidArgument));
} }
#[test]
fn test_cmd_keys() {
let mut cmds = setup_commands();
let ctx = EditContext::default();
let res = cmds.input_cmd("keys import /a/b/c pword", ctx.clone()).unwrap();
let act = IambAction::Keys(KeysAction::Import("/a/b/c".into(), "pword".into()));
assert_eq!(res, vec![(act.into(), ctx.clone())]);
let res = cmds.input_cmd("keys export /a/b/c pword", ctx.clone()).unwrap();
let act = IambAction::Keys(KeysAction::Export("/a/b/c".into(), "pword".into()));
assert_eq!(res, vec![(act.into(), ctx.clone())]);
// Invalid invocations.
let res = cmds.input_cmd("keys", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument));
let res = cmds.input_cmd("keys import", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument));
let res = cmds.input_cmd("keys import foo", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument));
let res = cmds.input_cmd("keys import foo bar baz", ctx.clone());
assert_eq!(res, Err(CommandError::InvalidArgument));
}
} }

View file

@ -87,6 +87,7 @@ use crate::{
IambId, IambId,
IambInfo, IambInfo,
IambResult, IambResult,
KeysAction,
ProgramAction, ProgramAction,
ProgramContext, ProgramContext,
ProgramStore, ProgramStore,
@ -529,6 +530,7 @@ impl Application {
None None
}, },
IambAction::Keys(act) => self.keys_command(act, ctx, store).await?,
IambAction::Message(act) => { IambAction::Message(act) => {
self.screen.current_window_mut()?.message_command(act, ctx, store).await? self.screen.current_window_mut()?.message_command(act, ctx, store).await?
}, },
@ -603,6 +605,36 @@ impl Application {
} }
} }
async fn keys_command(
&mut self,
action: KeysAction,
_: ProgramContext,
store: &mut ProgramStore,
) -> IambResult<EditInfo> {
let encryption = store.application.worker.client.encryption();
match action {
KeysAction::Export(path, passphrase) => {
encryption
.export_room_keys(path.into(), &passphrase, |_| true)
.await
.map_err(IambError::from)?;
Ok(Some("Successfully exported room keys".into()))
},
KeysAction::Import(path, passphrase) => {
let res = encryption
.import_room_keys(path.into(), &passphrase)
.await
.map_err(IambError::from)?;
let msg = format!("Imported {} of {} keys", res.imported_count, res.total_count);
Ok(Some(msg.into()))
},
}
}
fn handle_info(&mut self, info: InfoMessage) { fn handle_info(&mut self, info: InfoMessage) {
match info { match info {
InfoMessage::Message(info) => { InfoMessage::Message(info) => {