From d35cdf7f0a9822f73f4e1d18494350840de2a421 Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Mon, 27 May 2019 08:09:48 -0700 Subject: [PATCH 1/3] Move storage status subcommand into status command In an attempt to rework the structure of the storage command to better accommodate future requirements for allowing to change the read-write state of the unencrypted volume (as well as potentially the encrypted one), this change removes the storage status subcommand and merges its output into the storage command. --- nitrocli/CHANGELOG.md | 6 +++ nitrocli/README.md | 19 ++++---- nitrocli/doc/nitrocli.1 | 9 ++-- nitrocli/doc/nitrocli.1.pdf | Bin 18096 -> 18107 bytes nitrocli/src/args.rs | 10 ---- nitrocli/src/commands.rs | 88 +++++++++++++++++----------------- nitrocli/src/tests/status.rs | 30 +++++++++++- nitrocli/src/tests/storage.rs | 37 +++++++------- 8 files changed, 106 insertions(+), 93 deletions(-) diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index 4376d842..aa408102 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -1,3 +1,9 @@ +Unreleased +---------- +- Removed `storage status` subcommand + - Moved its output into `status` command + + 0.2.4 ----- - Added the `reset` command to perform a factory reset diff --git a/nitrocli/README.md b/nitrocli/README.md index 515e06a0..280e4941 100644 --- a/nitrocli/README.md +++ b/nitrocli/README.md @@ -21,7 +21,6 @@ The following commands are currently supported: - storage: Work with the Nitrokey's storage. - open: Open the encrypted volume. The user PIN needs to be entered. - close: Close the encrypted volume. - - status: Print information about the Nitrokey's storage. - hidden: - create: Create a hidden volume. - open: Open a hidden volume with a password. @@ -59,16 +58,14 @@ Status: firmware version: 0.47 user retry count: 3 admin retry count: 3 - -$ nitrocli storage status -Status: - SD card ID: 0x05dcad1d - firmware: unlocked - storage keys: created - volumes: - unencrypted: active - encrypted: active - hidden: inactive + Storage: + SD card ID: 0x05dcad1d + firmware: unlocked + storage keys: created + volumes: + unencrypted: active + encrypted: active + hidden: inactive # Close it again. $ nitrocli storage close diff --git a/nitrocli/doc/nitrocli.1 b/nitrocli/doc/nitrocli.1 index 90293354..ee6ea113 100644 --- a/nitrocli/doc/nitrocli.1 +++ b/nitrocli/doc/nitrocli.1 @@ -34,7 +34,9 @@ Print the nitrocli version and exit. .TP .B nitrocli status Print the status of the connected Nitrokey device, including the stick serial -number, the firmware version, and the PIN retry count. +number, the firmware version, and the PIN retry count. If the device is a +Nitrokey Storage, also print storage related information including the SD card +serial number, the encryption status, and the status of the volumes. .TP .B nitrocli lock Lock the Nitrokey. @@ -69,11 +71,6 @@ The user PIN that is required to open the volume is queried using \fBnitrocli storage close Close the encrypted volume on the Nitrokey Storage. .TP -\fBnitrocli storage status -Print the status of the connected Nitrokey Storage device's storage. The -printed information includes the SD card serial number, the encryption -status, and the status of the volumes. -.TP \fBnitrocli storage hidden create \fIslot\fR \fIstart\fR \fIend\fR Create a new hidden volume inside the encrypted volume. \fIslot\fR must indicate one of the four available slots. \fIstart\fR and \fIend\fR represent, diff --git a/nitrocli/doc/nitrocli.1.pdf b/nitrocli/doc/nitrocli.1.pdf index fa4e4bc66cfb5cc214efd2d2dc98ea3cab63b97a..2be0f025c0f013d17b812f8feaa8fa182ee7c049 100644 GIT binary patch delta 14564 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 SL^f_9J3Ar`jij;^;{O48ksBTW delta 14582 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 diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index 10a097e8..0b0429ac 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -250,7 +250,6 @@ Enum! {StorageCommand, [ Close => ("close", storage_close), Hidden => ("hidden", storage_hidden), Open => ("open", storage_open), - Status => ("status", storage_status), ]} /// Execute a storage subcommand. @@ -295,15 +294,6 @@ fn storage_close(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { commands::storage_close(ctx) } -/// Print the status of the nitrokey's storage. -fn storage_status(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { - let mut parser = argparse::ArgumentParser::new(); - parser.set_description("Prints the status of the Nitrokey's storage"); - parse(ctx, parser, args)?; - - commands::storage_status(ctx) -} - Enum! {HiddenCommand, [ Close => ("close", storage_hidden_close), Create => ("create", storage_hidden_create), diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 7bb314c2..d1ded113 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -258,6 +258,39 @@ where }) } +/// Pretty print the status of a Nitrokey Storage. +fn print_storage_status( + ctx: &mut args::ExecCtx<'_>, + status: &nitrokey::StorageStatus, +) -> Result<()> { + println!( + ctx, + r#" Storage: + SD card ID: {id:#x} + firmware: {fw} + storage keys: {sk} + volumes: + unencrypted: {vu} + encrypted: {ve} + hidden: {vh}"#, + id = status.serial_number_sd_card, + fw = if status.firmware_locked { + "locked" + } else { + "unlocked" + }, + sk = if status.stick_initialized { + "created" + } else { + "not created" + }, + vu = get_volume_status(&status.unencrypted_volume), + ve = get_volume_status(&status.encrypted_volume), + vh = get_volume_status(&status.hidden_volume), + )?; + Ok(()) +} + /// Query and pretty print the status that is common to all Nitrokey devices. fn print_status( ctx: &mut args::ExecCtx<'_>, @@ -267,6 +300,7 @@ fn print_status( let serial_number = device .get_serial_number() .map_err(|err| get_error("Could not query the serial number", err))?; + println!( ctx, r#"Status: @@ -282,7 +316,16 @@ fn print_status( urc = device.get_user_retry_count(), arc = device.get_admin_retry_count(), )?; - Ok(()) + + if let nitrokey::DeviceWrapper::Storage(device) = device { + let status = device + .get_status() + .map_err(|err| get_error("Getting Storage status failed", err))?; + + print_storage_status(ctx, &status) + } else { + Ok(()) + } } /// Inquire the status of the nitrokey. @@ -399,49 +442,6 @@ pub fn storage_hidden_close(ctx: &mut args::ExecCtx<'_>) -> Result<()> { .map_err(|err| get_error("Closing hidden volume failed", err)) } -/// Pretty print the status of a Nitrokey Storage. -fn print_storage_status( - ctx: &mut args::ExecCtx<'_>, - status: &nitrokey::StorageStatus, -) -> Result<()> { - println!( - ctx, - r#"Status: - SD card ID: {id:#x} - firmware: {fw} - storage keys: {sk} - volumes: - unencrypted: {vu} - encrypted: {ve} - hidden: {vh}"#, - id = status.serial_number_sd_card, - fw = if status.firmware_locked { - "locked" - } else { - "unlocked" - }, - sk = if status.stick_initialized { - "created" - } else { - "not created" - }, - vu = get_volume_status(&status.unencrypted_volume), - ve = get_volume_status(&status.encrypted_volume), - vh = get_volume_status(&status.hidden_volume), - )?; - Ok(()) -} - -/// Connect to and pretty print the status of a Nitrokey Storage. -pub fn storage_status(ctx: &mut args::ExecCtx<'_>) -> Result<()> { - let device = get_storage_device(ctx)?; - let status = device - .get_status() - .map_err(|err| get_error("Getting Storage status failed", err))?; - - print_storage_status(ctx, &status) -} - /// Return a String representation of the given Option. fn format_option(option: Option) -> String { match option { diff --git a/nitrocli/src/tests/status.rs b/nitrocli/src/tests/status.rs index 83ce61fa..7aac5ad6 100644 --- a/nitrocli/src/tests/status.rs +++ b/nitrocli/src/tests/status.rs @@ -37,10 +37,10 @@ fn not_found() { } #[test_device] -fn output(device: nitrokey::DeviceWrapper) -> crate::Result<()> { +fn output_pro(device: nitrokey::Pro) -> crate::Result<()> { let re = regex::Regex::new( r#"^Status: - model: (Pro|Storage) + model: Pro serial number: 0x[[:xdigit:]]{8} firmware version: \d+\.\d+ user retry count: [0-3] @@ -53,3 +53,29 @@ $"#, assert!(re.is_match(&out), out); Ok(()) } + +#[test_device] +fn output_storage(device: nitrokey::Storage) -> crate::Result<()> { + let re = regex::Regex::new( + r#"^Status: + model: Storage + serial number: 0x[[:xdigit:]]{8} + firmware version: \d+\.\d+ + user retry count: [0-3] + admin retry count: [0-3] + Storage: + SD card ID: 0x[[:xdigit:]]{8} + firmware: (un)?locked + storage keys: (not )?created + volumes: + unencrypted: (read-only|active|inactive) + encrypted: (read-only|active|inactive) + hidden: (read-only|active|inactive) +$"#, + ) + .unwrap(); + + let out = Nitrocli::with_dev(device).handle(&["status"])?; + assert!(re.is_match(&out), out); + Ok(()) +} diff --git a/nitrocli/src/tests/storage.rs b/nitrocli/src/tests/storage.rs index be933cad..5b45bdc9 100644 --- a/nitrocli/src/tests/storage.rs +++ b/nitrocli/src/tests/storage.rs @@ -19,15 +19,6 @@ use super::*; -#[test_device] -fn status_on_pro(device: nitrokey::Pro) { - let res = Nitrocli::with_dev(device).handle(&["storage", "status"]); - assert_eq!( - res.unwrap_str_err(), - "This command is only available on the Nitrokey Storage", - ); -} - #[test_device] fn status_open_close(device: nitrokey::Storage) -> crate::Result<()> { fn make_re(open: Option) -> regex::Regex { @@ -42,14 +33,11 @@ fn status_open_close(device: nitrokey::Storage) -> crate::Result<()> { None => "(read-only|active|inactive)", }; let re = format!( - r#"^Status: - SD card ID: 0x[[:xdigit:]]{{8}} - firmware: (un)?locked - storage keys: (not )?created - volumes: - unencrypted: (read-only|active|inactive) - encrypted: {} - hidden: (read-only|active|inactive) + r#" + volumes: + unencrypted: (read-only|active|inactive) + encrypted: {} + hidden: (read-only|active|inactive) $"#, encrypted ); @@ -57,20 +45,29 @@ $"#, } let mut ncli = Nitrocli::with_dev(device); - let out = ncli.handle(&["storage", "status"])?; + let out = ncli.handle(&["status"])?; assert!(make_re(None).is_match(&out), out); let _ = ncli.handle(&["storage", "open"])?; - let out = ncli.handle(&["storage", "status"])?; + let out = ncli.handle(&["status"])?; assert!(make_re(Some(true)).is_match(&out), out); let _ = ncli.handle(&["storage", "close"])?; - let out = ncli.handle(&["storage", "status"])?; + let out = ncli.handle(&["status"])?; assert!(make_re(Some(false)).is_match(&out), out); Ok(()) } +#[test_device] +fn encrypted_open_on_pro(device: nitrokey::Pro) { + let res = Nitrocli::with_dev(device).handle(&["storage", "open"]); + assert_eq!( + res.unwrap_str_err(), + "This command is only available on the Nitrokey Storage", + ); +} + #[test_device] fn encrypted_open_close(device: nitrokey::Storage) -> crate::Result<()> { let mut ncli = Nitrocli::with_dev(device); From a00e3f75349dc5f48abf441fd4e5c369c2e2055a Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Mon, 27 May 2019 09:20:00 -0700 Subject: [PATCH 2/3] Make storage hidden subcommand a top-level command This patch marks the next step in the process of restructuring the storage command. Specifically, it promotes the storage hidden subcommand to a top-level command, hidden. --- nitrocli/CHANGELOG.md | 1 + nitrocli/README.md | 10 ++++---- nitrocli/doc/nitrocli.1 | 10 ++++---- nitrocli/doc/nitrocli.1.pdf | Bin 18107 -> 18095 bytes nitrocli/src/args.rs | 34 ++++++++++---------------- nitrocli/src/commands.rs | 11 +++------ nitrocli/src/tests/hidden.rs | 44 ++++++++++++++++++++++++++++++++++ nitrocli/src/tests/mod.rs | 1 + nitrocli/src/tests/storage.rs | 24 ------------------- 9 files changed, 72 insertions(+), 63 deletions(-) create mode 100644 nitrocli/src/tests/hidden.rs diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index aa408102..2840f299 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -1,5 +1,6 @@ Unreleased ---------- +- Changed `storage hidden` subcommand to `hidden` top-level command - Removed `storage status` subcommand - Moved its output into `status` command diff --git a/nitrocli/README.md b/nitrocli/README.md index 280e4941..b93119aa 100644 --- a/nitrocli/README.md +++ b/nitrocli/README.md @@ -18,13 +18,13 @@ The following commands are currently supported: - config: Access the Nitrokey's configuration - get: Read the current configuration. - set: Change the configuration. -- storage: Work with the Nitrokey's storage. +- storage: Work with the Nitrokey Storage's storage. - open: Open the encrypted volume. The user PIN needs to be entered. - close: Close the encrypted volume. - - hidden: - - create: Create a hidden volume. - - open: Open a hidden volume with a password. - - close: Close a hidden volume. +- hidden: Work with the Nitrokey Storage's hidden volume. + - create: Create a hidden volume. + - open: Open a hidden volume with a password. + - close: Close a hidden volume. - otp: Access one-time passwords (OTP). - get: Generate a one-time password. - set: Set an OTP slot. diff --git a/nitrocli/doc/nitrocli.1 b/nitrocli/doc/nitrocli.1 index ee6ea113..3c5406d2 100644 --- a/nitrocli/doc/nitrocli.1 +++ b/nitrocli/doc/nitrocli.1 @@ -71,7 +71,7 @@ The user PIN that is required to open the volume is queried using \fBnitrocli storage close Close the encrypted volume on the Nitrokey Storage. .TP -\fBnitrocli storage hidden create \fIslot\fR \fIstart\fR \fIend\fR +\fBnitrocli hidden create \fIslot\fR \fIstart\fR \fIend\fR Create a new hidden volume inside the encrypted volume. \fIslot\fR must indicate one of the four available slots. \fIstart\fR and \fIend\fR represent, respectively, the start and end position of the hidden volume inside the @@ -79,14 +79,14 @@ encrypted volume, as a percentage of the encrypted volume's size. This command requires a password which is later used to look up the hidden volume to open. Unlike a PIN, this password is not cached by \fBgpg\-agent\fR(1). .TP -\fBnitrocli storage hidden open +\fBnitrocli hidden open Open a hidden volume. The volume to open is determined based on the password entered, which must have a minimum of six characters. Only one hidden volume can be active at any point in time and previously opened volumes will be automatically closed. Similarly, the encrypted volume will be closed if it was open. .TP -\fBnitrocli storage hidden close +\fBnitrocli hidden close Close a hidden volume. .SS One-time passwords @@ -275,7 +275,7 @@ The new user PIN to set. This variable is only used by the \fBpin set\fR command for the \fBuser\fR type. .TP .B NITROCLI_PASSWORD -A password used by commands that require one (e.g., \fBstorage hidden open\fR). +A password used by commands that require one (e.g., \fBhidden open\fR). .TP .B NITROCLI_NO_CACHE If this variable is present in the environment, do not cache any inquired @@ -288,7 +288,7 @@ Use the \fBpin clear\fR command to clear secrets from the cache. .SS Storage Create a hidden volume in the first available slot, starting at half the size of the encrypted volume (i.e., 50%) and stretching all the way to its end (100%): - $ \fBnitrocli storage hidden create 0 50 100\fR + $ \fBnitrocli hidden create 0 50 100\fR .SS One-time passwords Configure a one-time password slot with a hexadecimal secret representation: diff --git a/nitrocli/doc/nitrocli.1.pdf b/nitrocli/doc/nitrocli.1.pdf index 2be0f025c0f013d17b812f8feaa8fa182ee7c049..81abc315bcde4fdb7124f454e305e2fb2a56796b 100644 GIT binary patch delta 6810 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 delta 6835 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 Rg%Mx~gp`X*_L&^%e*qX6CU5`% diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index 0b0429ac..c37e5d25 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -121,6 +121,7 @@ impl From for nitrokey::Model { #[allow(unused_doc_comments)] Enum! {Command, [ Config => ("config", config), + Hidden => ("hidden", hidden), Lock => ("lock", lock), Otp => ("otp", otp), Pin => ("pin", pin), @@ -248,7 +249,6 @@ fn reset(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { Enum! {StorageCommand, [ Close => ("close", storage_close), - Hidden => ("hidden", storage_hidden), Open => ("open", storage_open), ]} @@ -295,13 +295,13 @@ fn storage_close(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { } Enum! {HiddenCommand, [ - Close => ("close", storage_hidden_close), - Create => ("create", storage_hidden_create), - Open => ("open", storage_hidden_open), + Close => ("close", hidden_close), + Create => ("create", hidden_create), + Open => ("open", hidden_open), ]} -/// Execute a storage hidden subcommand. -fn storage_hidden(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { +/// Execute a hidden subcommand. +fn hidden(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { let mut subcommand = HiddenCommand::Open; let help = cmd_help!(subcommand); let mut subargs = vec![]; @@ -320,19 +320,11 @@ fn storage_hidden(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { parser.stop_on_first_argument(true); parse(ctx, parser, args)?; - subargs.insert( - 0, - format!( - "nitrocli {} {} {}", - Command::Storage, - StorageCommand::Hidden, - subcommand - ), - ); + subargs.insert(0, format!("nitrocli {} {}", Command::Hidden, subcommand)); subcommand.execute(ctx, subargs) } -fn storage_hidden_create(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { +fn hidden_create(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { let mut slot: u8 = 0; let mut start: u8 = 0; let mut end: u8 = 0; @@ -357,23 +349,23 @@ fn storage_hidden_create(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> ); parse(ctx, parser, args)?; - commands::storage_hidden_create(ctx, slot, start, end) + commands::hidden_create(ctx, slot, start, end) } -fn storage_hidden_open(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { +fn hidden_open(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { let mut parser = argparse::ArgumentParser::new(); parser.set_description("Opens a hidden volume on a Nitrokey Storage"); parse(ctx, parser, args)?; - commands::storage_hidden_open(ctx) + commands::hidden_open(ctx) } -fn storage_hidden_close(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { +fn hidden_close(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { let mut parser = argparse::ArgumentParser::new(); parser.set_description("Closes the hidden volume on a Nitrokey Storage"); parse(ctx, parser, args)?; - commands::storage_hidden_close(ctx) + commands::hidden_close(ctx) } /// Execute a config subcommand. diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index d1ded113..6374611f 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -389,12 +389,7 @@ pub fn storage_close(ctx: &mut args::ExecCtx<'_>) -> Result<()> { } /// Create a hidden volume. -pub fn storage_hidden_create( - ctx: &mut args::ExecCtx<'_>, - slot: u8, - start: u8, - end: u8, -) -> Result<()> { +pub fn hidden_create(ctx: &mut args::ExecCtx<'_>, slot: u8, start: u8, end: u8) -> Result<()> { let device = get_storage_device(ctx)?; let pwd_entry = pinentry::PwdEntry::from(&device)?; let pwd = if let Some(pwd) = &ctx.password { @@ -412,7 +407,7 @@ pub fn storage_hidden_create( } /// Open a hidden volume. -pub fn storage_hidden_open(ctx: &mut args::ExecCtx<'_>) -> Result<()> { +pub fn hidden_open(ctx: &mut args::ExecCtx<'_>) -> Result<()> { let device = get_storage_device(ctx)?; let pwd_entry = pinentry::PwdEntry::from(&device)?; let pwd = if let Some(pwd) = &ctx.password { @@ -434,7 +429,7 @@ pub fn storage_hidden_open(ctx: &mut args::ExecCtx<'_>) -> Result<()> { } /// Close a previously opened hidden volume. -pub fn storage_hidden_close(ctx: &mut args::ExecCtx<'_>) -> Result<()> { +pub fn hidden_close(ctx: &mut args::ExecCtx<'_>) -> Result<()> { unsafe { sync() }; get_storage_device(ctx)? diff --git a/nitrocli/src/tests/hidden.rs b/nitrocli/src/tests/hidden.rs new file mode 100644 index 00000000..483a8014 --- /dev/null +++ b/nitrocli/src/tests/hidden.rs @@ -0,0 +1,44 @@ +// hidden.rs + +// ************************************************************************* +// * Copyright (C) 2019 Daniel Mueller (deso@posteo.net) * +// * * +// * This program is free software: you can redistribute it and/or modify * +// * it under the terms of the GNU General Public License as published by * +// * the Free Software Foundation, either version 3 of the License, or * +// * (at your option) any later version. * +// * * +// * This program is distributed in the hope that it will be useful, * +// * but WITHOUT ANY WARRANTY; without even the implied warranty of * +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +// * GNU General Public License for more details. * +// * * +// * You should have received a copy of the GNU General Public License * +// * along with this program. If not, see . * +// ************************************************************************* + +use super::*; + +#[test_device] +fn hidden_create_open_close(device: nitrokey::Storage) -> crate::Result<()> { + let mut ncli = Nitrocli::with_dev(device); + let out = ncli.handle(&["hidden", "create", "0", "50", "100"])?; + assert!(out.is_empty()); + + let out = ncli.handle(&["hidden", "open"])?; + assert!(out.is_empty()); + + let device = nitrokey::Storage::connect()?; + assert!(!device.get_status()?.encrypted_volume.active); + assert!(device.get_status()?.hidden_volume.active); + drop(device); + + let out = ncli.handle(&["hidden", "close"])?; + assert!(out.is_empty()); + + let device = nitrokey::Storage::connect()?; + assert!(!device.get_status()?.encrypted_volume.active); + assert!(!device.get_status()?.hidden_volume.active); + + Ok(()) +} diff --git a/nitrocli/src/tests/mod.rs b/nitrocli/src/tests/mod.rs index b1e16185..2d6f93cc 100644 --- a/nitrocli/src/tests/mod.rs +++ b/nitrocli/src/tests/mod.rs @@ -37,6 +37,7 @@ const NITROKEY_DEFAULT_USER_PIN: &str = "123456"; fn dummy() {} mod config; +mod hidden; mod lock; mod otp; mod pin; diff --git a/nitrocli/src/tests/storage.rs b/nitrocli/src/tests/storage.rs index 5b45bdc9..a1c6ecf2 100644 --- a/nitrocli/src/tests/storage.rs +++ b/nitrocli/src/tests/storage.rs @@ -88,27 +88,3 @@ fn encrypted_open_close(device: nitrokey::Storage) -> crate::Result<()> { Ok(()) } - -#[test_device] -fn hidden_create_open_close(device: nitrokey::Storage) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); - let out = ncli.handle(&["storage", "hidden", "create", "0", "50", "100"])?; - assert!(out.is_empty()); - - let out = ncli.handle(&["storage", "hidden", "open"])?; - assert!(out.is_empty()); - - let device = nitrokey::Storage::connect()?; - assert!(!device.get_status()?.encrypted_volume.active); - assert!(device.get_status()?.hidden_volume.active); - drop(device); - - let out = ncli.handle(&["storage", "hidden", "close"])?; - assert!(out.is_empty()); - - let device = nitrokey::Storage::connect()?; - assert!(!device.get_status()?.encrypted_volume.active); - assert!(!device.get_status()?.hidden_volume.active); - - Ok(()) -} From e6d89a69521db96e27d65d8284acfa81f0ff4b2d Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Mon, 27 May 2019 09:30:46 -0700 Subject: [PATCH 3/3] Rename storage command to encrypted This change is the last step in the process of restructuring the storage command. In particular, now that functionality pertaining hidden volumes has been moved out into a dedicated top-level command, it renames said command to encrypted, because dealing with the encrypted volume is the only functionality it provides. --- nitrocli/CHANGELOG.md | 1 + nitrocli/README.md | 2 +- nitrocli/doc/nitrocli.1 | 4 +-- nitrocli/doc/nitrocli.1.pdf | Bin 18095 -> 18095 bytes nitrocli/src/args.rs | 26 +++++++++--------- nitrocli/src/commands.rs | 4 +-- .../src/tests/{storage.rs => encrypted.rs} | 12 ++++---- nitrocli/src/tests/lock.rs | 2 +- nitrocli/src/tests/mod.rs | 2 +- 9 files changed, 27 insertions(+), 26 deletions(-) rename nitrocli/src/tests/{storage.rs => encrypted.rs} (90%) diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index 2840f299..716ebc67 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -1,6 +1,7 @@ Unreleased ---------- - Changed `storage hidden` subcommand to `hidden` top-level command +- Renamed `storage` command to `encrypted` - Removed `storage status` subcommand - Moved its output into `status` command diff --git a/nitrocli/README.md b/nitrocli/README.md index b93119aa..0a30696b 100644 --- a/nitrocli/README.md +++ b/nitrocli/README.md @@ -18,7 +18,7 @@ The following commands are currently supported: - config: Access the Nitrokey's configuration - get: Read the current configuration. - set: Change the configuration. -- storage: Work with the Nitrokey Storage's storage. +- encrypted: Work with the Nitrokey Storage's encrypted volume. - open: Open the encrypted volume. The user PIN needs to be entered. - close: Close the encrypted volume. - hidden: Work with the Nitrokey Storage's hidden volume. diff --git a/nitrocli/doc/nitrocli.1 b/nitrocli/doc/nitrocli.1 index 3c5406d2..3c1e1e4a 100644 --- a/nitrocli/doc/nitrocli.1 +++ b/nitrocli/doc/nitrocli.1 @@ -63,12 +63,12 @@ this overlay (which is required to achieve plausible deniability of the existence of hidden volumes), the burden of ensuring that data on the encrypted volume does not overlap with data on one of the hidden volumes is on the user. .TP -\fBnitrocli storage open +\fBnitrocli encrypted open Open the encrypted volume on the Nitrokey Storage. The user PIN that is required to open the volume is queried using \fBpinentry\fR(1) and cached by \fBgpg\-agent\fR(1). .TP -\fBnitrocli storage close +\fBnitrocli encrypted close Close the encrypted volume on the Nitrokey Storage. .TP \fBnitrocli hidden create \fIslot\fR \fIstart\fR \fIend\fR diff --git a/nitrocli/doc/nitrocli.1.pdf b/nitrocli/doc/nitrocli.1.pdf index 81abc315bcde4fdb7124f454e305e2fb2a56796b..0384072862c06edf95202a162fd3e67cc329a9fa 100644 GIT binary patch delta 635 zcmZ4A%ecOmaRWbh{nCzO-dh7W*J~*)I)0_4<;szh$16{@cOH*kq#;^mv2}@d?^W?7 zt2dmp)jwL+`#U!=^NH!lM6Kh;<9DChUhSo0eJCxvcK)^NtpN(|mZ!cQp8&1VgFp34+t{`@0HddB=CUwTJUtXXs|>YHH|gVQA@S zU}WIzYGi6+;AUa&XyoSVV&vpxWM@NAMJ$({9anKlVo^y&QED2Op}CQfA(yJEtG^o; E0GpcU5C8xG delta 635 zcmZ4A%ecOmaRWbhz3o-0Ygr2gwsS(@QetMxU) z6X#aPbiI}Oe)`a*j_jnvA$|V)%ckakn-W&ma^~7EyS3}M7drG7iM&+5y=L25efG)s z`6O;9e01n9`m8oJ^$YXN|4&!4O;667J6Y%0(~lR;4D35zU-!NH^~T@&&v#O^&TiiG z^vB;{)`u?exF1=2sNzp-D@m?Q(9sIrJ0|ct&UewRM^5O`0~b|3A~@@7><1c`3yu zMX8CoTtHTSQWlq)f`I~vHrmX~Q)i@NW^9mbXl$0Kn`Dt>u4`zRW}<5V1iGflsY%IZ zMwY3TX@-;6+Z7R~(AD0P)zHk!)M)Z#dow8OoP9HKnoAwriBtH%L6s8JeF{&Aw znHuRDn5!EYsB3cR`{t*(B$lLVxL6q(7+M;b8krkdf>dl4b+%!Svota@Ffp|-F)*-j zvotp~admSvvUGDZH88idG;lLz#kV{q7)!&T^ E0L*^l6#xJL diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index c37e5d25..c0439387 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -121,6 +121,7 @@ impl From for nitrokey::Model { #[allow(unused_doc_comments)] Enum! {Command, [ Config => ("config", config), + Encrypted => ("encrypted", encrypted), Hidden => ("hidden", hidden), Lock => ("lock", lock), Otp => ("otp", otp), @@ -128,7 +129,6 @@ Enum! {Command, [ Pws => ("pws", pws), Reset => ("reset", reset), Status => ("status", status), - Storage => ("storage", storage), ]} Enum! {ConfigCommand, [ @@ -247,18 +247,18 @@ fn reset(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { commands::reset(ctx) } -Enum! {StorageCommand, [ - Close => ("close", storage_close), - Open => ("open", storage_open), +Enum! {EncryptedCommand, [ + Close => ("close", encrypted_close), + Open => ("open", encrypted_open), ]} -/// Execute a storage subcommand. -fn storage(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { - let mut subcommand = StorageCommand::Open; +/// Execute an encrypted subcommand. +fn encrypted(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { + let mut subcommand = EncryptedCommand::Open; let help = cmd_help!(subcommand); let mut subargs = vec![]; let mut parser = argparse::ArgumentParser::new(); - parser.set_description("Interacts with the device's storage"); + parser.set_description("Interacts with the device's encrypted volume"); let _ = parser .refer(&mut subcommand) @@ -272,26 +272,26 @@ fn storage(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { parser.stop_on_first_argument(true); parse(ctx, parser, args)?; - subargs.insert(0, format!("nitrocli {} {}", Command::Storage, subcommand)); + subargs.insert(0, format!("nitrocli {}", subcommand)); subcommand.execute(ctx, subargs) } /// Open the encrypted volume on the nitrokey. -fn storage_open(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { +fn encrypted_open(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { let mut parser = argparse::ArgumentParser::new(); parser.set_description("Opens the encrypted volume on a Nitrokey Storage"); parse(ctx, parser, args)?; - commands::storage_open(ctx) + commands::encrypted_open(ctx) } /// Close the previously opened encrypted volume. -fn storage_close(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { +fn encrypted_close(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { let mut parser = argparse::ArgumentParser::new(); parser.set_description("Closes the encrypted volume on a Nitrokey Storage"); parse(ctx, parser, args)?; - commands::storage_close(ctx) + commands::encrypted_close(ctx) } Enum! {HiddenCommand, [ diff --git a/nitrocli/src/commands.rs b/nitrocli/src/commands.rs index 6374611f..0d30bcaf 100644 --- a/nitrocli/src/commands.rs +++ b/nitrocli/src/commands.rs @@ -362,7 +362,7 @@ pub fn reset(ctx: &mut args::ExecCtx<'_>) -> Result<()> { } /// Open the encrypted volume on the nitrokey. -pub fn storage_open(ctx: &mut args::ExecCtx<'_>) -> Result<()> { +pub fn encrypted_open(ctx: &mut args::ExecCtx<'_>) -> Result<()> { let device = get_storage_device(ctx)?; let pin_entry = pinentry::PinEntry::from(pinentry::PinType::User, &device)?; @@ -376,7 +376,7 @@ pub fn storage_open(ctx: &mut args::ExecCtx<'_>) -> Result<()> { } /// Close the previously opened encrypted volume. -pub fn storage_close(ctx: &mut args::ExecCtx<'_>) -> Result<()> { +pub fn encrypted_close(ctx: &mut args::ExecCtx<'_>) -> Result<()> { // Flush all filesystem caches to disk. We are mostly interested in // making sure that the encrypted volume on the nitrokey we are // about to close is not closed while not all data was written to diff --git a/nitrocli/src/tests/storage.rs b/nitrocli/src/tests/encrypted.rs similarity index 90% rename from nitrocli/src/tests/storage.rs rename to nitrocli/src/tests/encrypted.rs index a1c6ecf2..8aef8643 100644 --- a/nitrocli/src/tests/storage.rs +++ b/nitrocli/src/tests/encrypted.rs @@ -1,4 +1,4 @@ -// storage.rs +// encrypted.rs // ************************************************************************* // * Copyright (C) 2019 Daniel Mueller (deso@posteo.net) * @@ -48,11 +48,11 @@ $"#, let out = ncli.handle(&["status"])?; assert!(make_re(None).is_match(&out), out); - let _ = ncli.handle(&["storage", "open"])?; + let _ = ncli.handle(&["encrypted", "open"])?; let out = ncli.handle(&["status"])?; assert!(make_re(Some(true)).is_match(&out), out); - let _ = ncli.handle(&["storage", "close"])?; + let _ = ncli.handle(&["encrypted", "close"])?; let out = ncli.handle(&["status"])?; assert!(make_re(Some(false)).is_match(&out), out); @@ -61,7 +61,7 @@ $"#, #[test_device] fn encrypted_open_on_pro(device: nitrokey::Pro) { - let res = Nitrocli::with_dev(device).handle(&["storage", "open"]); + let res = Nitrocli::with_dev(device).handle(&["encrypted", "open"]); assert_eq!( res.unwrap_str_err(), "This command is only available on the Nitrokey Storage", @@ -71,7 +71,7 @@ fn encrypted_open_on_pro(device: nitrokey::Pro) { #[test_device] fn encrypted_open_close(device: nitrokey::Storage) -> crate::Result<()> { let mut ncli = Nitrocli::with_dev(device); - let out = ncli.handle(&["storage", "open"])?; + let out = ncli.handle(&["encrypted", "open"])?; assert!(out.is_empty()); let device = nitrokey::Storage::connect()?; @@ -79,7 +79,7 @@ fn encrypted_open_close(device: nitrokey::Storage) -> crate::Result<()> { assert!(!device.get_status()?.hidden_volume.active); drop(device); - let out = ncli.handle(&["storage", "close"])?; + let out = ncli.handle(&["encrypted", "close"])?; assert!(out.is_empty()); let device = nitrokey::Storage::connect()?; diff --git a/nitrocli/src/tests/lock.rs b/nitrocli/src/tests/lock.rs index 19933507..d23d2ae7 100644 --- a/nitrocli/src/tests/lock.rs +++ b/nitrocli/src/tests/lock.rs @@ -31,7 +31,7 @@ fn lock_pro(device: nitrokey::Pro) -> crate::Result<()> { #[test_device] fn lock_storage(device: nitrokey::Storage) -> crate::Result<()> { let mut ncli = Nitrocli::with_dev(device); - let _ = ncli.handle(&["storage", "open"])?; + let _ = ncli.handle(&["encrypted", "open"])?; let out = ncli.handle(&["lock"])?; assert!(out.is_empty()); diff --git a/nitrocli/src/tests/mod.rs b/nitrocli/src/tests/mod.rs index 2d6f93cc..70a3d204 100644 --- a/nitrocli/src/tests/mod.rs +++ b/nitrocli/src/tests/mod.rs @@ -37,6 +37,7 @@ const NITROKEY_DEFAULT_USER_PIN: &str = "123456"; fn dummy() {} mod config; +mod encrypted; mod hidden; mod lock; mod otp; @@ -45,7 +46,6 @@ mod pws; mod reset; mod run; mod status; -mod storage; /// A trait simplifying checking for expected errors. pub trait UnwrapError {