diff --git a/nitrocli/CHANGELOG.md b/nitrocli/CHANGELOG.md index 4376d842..716ebc67 100644 --- a/nitrocli/CHANGELOG.md +++ b/nitrocli/CHANGELOG.md @@ -1,3 +1,11 @@ +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 + + 0.2.4 ----- - Added the `reset` command to perform a factory reset diff --git a/nitrocli/README.md b/nitrocli/README.md index 515e06a0..0a30696b 100644 --- a/nitrocli/README.md +++ b/nitrocli/README.md @@ -18,14 +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. +- 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. - - status: Print information about the Nitrokey's storage. - - 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. @@ -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..3c1e1e4a 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. @@ -61,20 +63,15 @@ 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 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 +\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 @@ -82,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 @@ -278,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 @@ -291,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 fa4e4bc6..03840728 100644 Binary files a/nitrocli/doc/nitrocli.1.pdf and b/nitrocli/doc/nitrocli.1.pdf differ diff --git a/nitrocli/src/args.rs b/nitrocli/src/args.rs index 10a097e8..c0439387 100644 --- a/nitrocli/src/args.rs +++ b/nitrocli/src/args.rs @@ -121,13 +121,14 @@ 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), Pin => ("pin", pin), Pws => ("pws", pws), Reset => ("reset", reset), Status => ("status", status), - Storage => ("storage", storage), ]} Enum! {ConfigCommand, [ @@ -246,20 +247,18 @@ fn reset(ctx: &mut ExecCtx<'_>, args: Vec) -> Result<()> { commands::reset(ctx) } -Enum! {StorageCommand, [ - Close => ("close", storage_close), - Hidden => ("hidden", storage_hidden), - Open => ("open", storage_open), - Status => ("status", storage_status), +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) @@ -273,45 +272,36 @@ 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) -} - -/// 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) + commands::encrypted_close(ctx) } 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![]; @@ -330,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; @@ -367,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 7bb314c2..0d30bcaf 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. @@ -319,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)?; @@ -333,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 @@ -346,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 { @@ -369,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 { @@ -391,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)? @@ -399,49 +437,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/storage.rs b/nitrocli/src/tests/encrypted.rs similarity index 63% rename from nitrocli/src/tests/storage.rs rename to nitrocli/src/tests/encrypted.rs index be933cad..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) * @@ -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,56 +45,41 @@ $"#, } 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 _ = ncli.handle(&["encrypted", "open"])?; + 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 _ = ncli.handle(&["encrypted", "close"])?; + let out = ncli.handle(&["status"])?; assert!(make_re(Some(false)).is_match(&out), out); Ok(()) } #[test_device] -fn encrypted_open_close(device: nitrokey::Storage) -> crate::Result<()> { - let mut ncli = Nitrocli::with_dev(device); - let out = ncli.handle(&["storage", "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", "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(()) +fn encrypted_open_on_pro(device: nitrokey::Pro) { + let res = Nitrocli::with_dev(device).handle(&["encrypted", "open"]); + assert_eq!( + res.unwrap_str_err(), + "This command is only available on the Nitrokey Storage", + ); } #[test_device] -fn hidden_create_open_close(device: nitrokey::Storage) -> crate::Result<()> { +fn encrypted_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"])?; + let out = ncli.handle(&["encrypted", "open"])?; assert!(out.is_empty()); let device = nitrokey::Storage::connect()?; - assert!(!device.get_status()?.encrypted_volume.active); - assert!(device.get_status()?.hidden_volume.active); + assert!(device.get_status()?.encrypted_volume.active); + assert!(!device.get_status()?.hidden_volume.active); drop(device); - let out = ncli.handle(&["storage", "hidden", "close"])?; + let out = ncli.handle(&["encrypted", "close"])?; assert!(out.is_empty()); let device = nitrokey::Storage::connect()?; 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/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 b1e16185..70a3d204 100644 --- a/nitrocli/src/tests/mod.rs +++ b/nitrocli/src/tests/mod.rs @@ -37,6 +37,8 @@ const NITROKEY_DEFAULT_USER_PIN: &str = "123456"; fn dummy() {} mod config; +mod encrypted; +mod hidden; mod lock; mod otp; mod pin; @@ -44,7 +46,6 @@ mod pws; mod reset; mod run; mod status; -mod storage; /// A trait simplifying checking for expected errors. pub trait UnwrapError { 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(()) +}