Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commands for importing and exporting room keys #233

Merged
merged 1 commit into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 12 additions & 2 deletions docs/iamb.1
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,22 @@ Log out of
View a list of joined rooms.
.It Sy ":spaces"
View a list of joined spaces.
.It Sy ":verify"
View a list of ongoing E2EE verifications.
.It Sy ":welcome"
View the startup Welcome window.
.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"
.Bl -tag -width Ds
.It Sy ":download"
Expand Down
21 changes: 20 additions & 1 deletion src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,15 @@ pub enum HomeserverAction {
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.
///
/// See [the commands module][super::commands] for where these are usually created.
Expand All @@ -428,6 +437,9 @@ pub enum IambAction {
/// Perform an action against the homeserver.
Homeserver(HomeserverAction),

/// Perform an action over room keys.
Keys(KeysAction),

/// Perform an action on the currently selected message.
Message(MessageAction),

Expand Down Expand Up @@ -485,6 +497,7 @@ impl ApplicationAction for IambAction {
fn is_edit_sequence(&self, _: &EditContext) -> SequenceStatus {
match self {
IambAction::Homeserver(..) => SequenceStatus::Break,
IambAction::Keys(..) => SequenceStatus::Break,
IambAction::Message(..) => SequenceStatus::Break,
IambAction::Room(..) => SequenceStatus::Break,
IambAction::OpenLink(..) => SequenceStatus::Break,
Expand All @@ -498,6 +511,7 @@ impl ApplicationAction for IambAction {
fn is_last_action(&self, _: &EditContext) -> SequenceStatus {
match self {
IambAction::Homeserver(..) => SequenceStatus::Atom,
IambAction::Keys(..) => SequenceStatus::Atom,
IambAction::Message(..) => SequenceStatus::Atom,
IambAction::OpenLink(..) => SequenceStatus::Atom,
IambAction::Room(..) => SequenceStatus::Atom,
Expand All @@ -511,6 +525,7 @@ impl ApplicationAction for IambAction {
fn is_last_selection(&self, _: &EditContext) -> SequenceStatus {
match self {
IambAction::Homeserver(..) => SequenceStatus::Ignore,
IambAction::Keys(..) => SequenceStatus::Ignore,
IambAction::Message(..) => SequenceStatus::Ignore,
IambAction::Room(..) => SequenceStatus::Ignore,
IambAction::OpenLink(..) => SequenceStatus::Ignore,
Expand All @@ -526,6 +541,7 @@ impl ApplicationAction for IambAction {
IambAction::Homeserver(..) => false,
IambAction::Message(..) => false,
IambAction::Room(..) => false,
IambAction::Keys(..) => false,
IambAction::Send(..) => false,
IambAction::OpenLink(..) => false,
IambAction::ToggleScrollbackFocus => false,
Expand Down Expand Up @@ -585,6 +601,9 @@ pub enum IambError {
#[error("Cryptographic storage error: {0}")]
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.
#[error("Cannot export keys from sled: {0}")]
UpgradeSled(#[from] crate::sled_export::SledMigrationError),
Expand Down Expand Up @@ -1767,7 +1786,7 @@ fn complete_cmdarg(
match cmd.name.as_str() {
"cancel" | "dms" | "edit" | "redact" | "reply" => 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),

"invite" => complete_users(text, cursor, store),
Expand Down
52 changes: 52 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::base::{
HomeserverAction,
IambAction,
IambId,
KeysAction,
MessageAction,
ProgramCommand,
ProgramCommands,
Expand Down Expand Up @@ -102,6 +103,29 @@ fn iamb_invite(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
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 {
let mut args = desc.arg.strings()?;

Expand Down Expand Up @@ -523,6 +547,7 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) {
f: iamb_invite,
});
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 {
name: "leave".into(),
aliases: vec![],
Expand Down Expand Up @@ -959,4 +984,31 @@ mod tests {
let res = cmds.input_cmd("redact Removed Removed", ctx.clone());
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));
}
}
32 changes: 32 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ use crate::{
IambId,
IambInfo,
IambResult,
KeysAction,
ProgramAction,
ProgramContext,
ProgramStore,
Expand Down Expand Up @@ -529,6 +530,7 @@ impl Application {

None
},
IambAction::Keys(act) => self.keys_command(act, ctx, store).await?,
IambAction::Message(act) => {
self.screen.current_window_mut()?.message_command(act, ctx, store).await?
},
Expand Down Expand Up @@ -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) {
match info {
InfoMessage::Message(info) => {
Expand Down