From de1c1666406b406fe6085633200d264fd4e9b35d Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sun, 18 Apr 2021 15:30:01 +0200 Subject: [PATCH] Add --slot option to pws add This patch adds the --slot option the pws add command so that we can add data on a specific slot without using pws set (which might override a programmed slot). --- CHANGELOG.md | 2 +- doc/nitrocli.1 | 11 +++++++---- doc/nitrocli.1.pdf | Bin 48684 -> 48844 bytes src/args.rs | 9 +++++++-- src/commands.rs | 43 ++++++++++++++++++++++++++++++---------- src/tests/pws.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 96 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3758f1d1..926b128f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Unreleased ---------- - Enabled usage of empty PWS slot fields - Changed error reporting format to make up only a single line -- Added the `pws add` subcommand to write to the first unprogrammed slot +- Added the `pws add` subcommand to write to a new slot - Added the `pws update` subcommand to update an existing PWS slot - Added the `--only-aes-key` option to the `reset` command to build a new AES key without performing a factory reset diff --git a/doc/nitrocli.1 b/doc/nitrocli.1 index 24bff555..14cf0490 100644 --- a/doc/nitrocli.1 +++ b/doc/nitrocli.1 @@ -1,4 +1,4 @@ -.TH NITROCLI 1 2021-04-17 +.TH NITROCLI 1 2021-04-18 .SH NAME nitrocli \- access Nitrokey devices .SH SYNOPSIS @@ -279,10 +279,13 @@ Set the content of a PWS slot. \fIname\fR, \fIlogin\fR, and \fIpassword\fR represent the data to write to the slot. .TP -\fBnitrocli pws add \fIslot name login password\fR +\fBnitrocli pws add \fR[\fB\-s\fR|\fB\-\-slot \fIslot\fR] \ +\fIname login password\fR Add a new PWS slot. -This command locates the first free PWS slot and sets its content to the given -values. +If the \fB\-\-slot\fR option is set, this command writes the data to the given +slot and fails if the slot is already programmed. +If the \fB\-\-slot\fR option is not set, this command locates the first free +PWS slot and sets its content to the given values. It fails if all PWS slots are programmed. .TP \fBnitrocli pws update \fIslot\fR \ diff --git a/doc/nitrocli.1.pdf b/doc/nitrocli.1.pdf index 5cd6baaf5210045502e675aad43ca29ec331d5e0..bf761f6be9c908dbc761f84abb90e8e5da0c5d07 100644 GIT binary patch delta 15019 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 QVCP_lr=%2>7lZ%502#=Hk^lez diff --git a/src/args.rs b/src/args.rs index b9a85789..dc6bcea0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -398,9 +398,9 @@ Command! {PwsCommand, [ Set(PwsSetArgs) => |ctx, args: PwsSetArgs| { crate::commands::pws_set(ctx, args.slot, &args.name, &args.login, &args.password) }, - /// Writes data to the first free password safe slot + /// Adds a new password safe slot Add(PwsAddArgs) => |ctx, args: PwsAddArgs| { - crate::commands::pws_add(ctx, &args.name, &args.login, &args.password) + crate::commands::pws_add(ctx, &args.name, &args.login, &args.password, args.slot) }, /// Updates a password safe slot Update(PwsUpdateArgs) => |ctx, args: PwsUpdateArgs| { @@ -460,6 +460,11 @@ pub struct PwsAddArgs { pub login: String, /// The password to store on the slot pub password: String, + /// The number of the slot to write + /// + /// If this option is not set, the first unprogrammed slot is used. + #[structopt(short, long)] + pub slot: Option, } #[derive(Debug, PartialEq, structopt::StructOpt)] diff --git a/src/commands.rs b/src/commands.rs index 9d54e592..18206b19 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1047,25 +1047,48 @@ pub fn pws_set( }) } -/// Write data to the first unprogrammed PWS slot. +/// Add a new PWS slot. pub fn pws_add( ctx: &mut Context<'_>, name: &str, login: &str, password: &str, + slot_idx: Option, ) -> anyhow::Result<()> { with_password_safe(ctx, |ctx, mut pws| { let slots = pws.get_slots()?; - if let Some(slot) = slots.iter().position(Option::is_none) { - let slot = u8::try_from(slot).context("Unexpected number of password slots")?; - pws - .write_slot(slot, name, login, password) - .context("Failed to write PWS slot")?; - println!(ctx, "Added PWS slot {}", slot)?; - Ok(()) + + let slot_idx = if let Some(slot_idx) = slot_idx { + // If the user specified a slot, make sure that it is not programmed + if let Some(slot) = slots.get(usize::from(slot_idx)) { + if slot.is_some() { + Err(anyhow::anyhow!( + "The PWS slot {} is already programmed", + slot_idx + )) + } else { + Ok(slot_idx) + } + } else { + Err(anyhow::anyhow!( + "Encountered invalid slot index: {}", + slot_idx + )) + } } else { - Err(anyhow::anyhow!("All PWS slots are already programmed")) - } + // If the user did not specify a slot, we try to find the first unprogrammed slot + if let Some(slot_idx) = slots.iter().position(Option::is_none) { + u8::try_from(slot_idx).context("Unexpected number of PWS slots") + } else { + Err(anyhow::anyhow!("All PWS slots are already programmed")) + } + }?; + + pws + .write_slot(slot_idx, name, login, password) + .context("Failed to write PWS slot")?; + println!(ctx, "Added PWS slot {}", slot_idx)?; + Ok(()) }) } diff --git a/src/tests/pws.rs b/src/tests/pws.rs index b01e127f..68dde9e6 100644 --- a/src/tests/pws.rs +++ b/src/tests/pws.rs @@ -29,6 +29,17 @@ fn set_invalid_slot(model: nitrokey::Model) { assert_eq!(err, "Failed to write PWS slot"); } +#[test_device] +fn add_invalid_slot(model: nitrokey::Model) { + let err = Nitrocli::new() + .model(model) + .handle(&["pws", "add", "--slot", "100", "name", "login", "1234"]) + .unwrap_err() + .to_string(); + + assert_eq!(err, "Encountered invalid slot index: 100"); +} + #[test_device] fn status(model: nitrokey::Model) -> anyhow::Result<()> { let re = regex::Regex::new( @@ -227,6 +238,43 @@ fn add_full(model: nitrokey::Model) -> anyhow::Result<()> { Ok(()) } +#[test_device] +fn add_existing(model: nitrokey::Model) -> anyhow::Result<()> { + let mut ncli = Nitrocli::new().model(model); + + // Fill slot 0 + let _ = ncli.handle(&["pws", "set", "0", "name0", "login0", "pass0rd"])?; + + // Try to add slot 0 + let res = ncli.handle(&["pws", "add", "--slot", "0", "name", "login", "passw0rd"]); + + let err = res.unwrap_err().to_string(); + assert_eq!(err, "The PWS slot 0 is already programmed"); + Ok(()) +} + +#[test_device] +fn add_slot(model: nitrokey::Model) -> anyhow::Result<()> { + let mut ncli = Nitrocli::new().model(model); + + // Fill slots 0 and 5 + let _ = ncli.handle(&["pws", "set", "0", "name0", "login0", "passw0rd"])?; + let _ = ncli.handle(&["pws", "set", "5", "name5", "login5", "passw5rd"])?; + + // Clear slot 1 (in case it was written to by other slots) + let _ = ncli.handle(&["pws", "clear", "1"])?; + + // Try to add slot 1 + let out = ncli.handle(&["pws", "add", "--slot", "1", "name1", "login1", "passw1rd"])?; + assert_eq!("Added PWS slot 1\n", out); + + assert_slot(model, 0, "name0", "login0", "passw0rd")?; + assert_slot(model, 1, "name1", "login1", "passw1rd")?; + assert_slot(model, 5, "name5", "login5", "passw5rd")?; + + Ok(()) +} + #[test_device] fn add(model: nitrokey::Model) -> anyhow::Result<()> { let mut ncli = Nitrocli::new().model(model);