From 9593dfd03a6ca085d649ca090b6ec5e5f0104e78 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 8 Sep 2020 18:23:30 +0200 Subject: Add --usb-path option to select device This patch adds the --usb-path option as an additional way to filter the Nitrokey device to connect to. While the serial number is a better identifier in theory, the Nitrokey Storage devices do not send their serial number in the USB device descriptor. Having the --usb-path options allows users to select one of multiple Nitrokey Storage devices. While we could directly call the nitrokey::Manager::connect_path function with the specified path, we integrate the --usb-path option into the existing find_device function for consistent error messages and to avoid having to duplicate the --model and --serial-number checks. --- src/args.rs | 3 ++ src/commands.rs | 10 ++++-- src/config.rs | 5 +++ src/tests/run.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 105 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/args.rs b/src/args.rs index 0d77806..80abe17 100644 --- a/src/args.rs +++ b/src/args.rs @@ -23,6 +23,9 @@ pub struct Args { number_of_values = 1 )] pub serial_numbers: Vec, + /// Sets the USB path of the device to connect to + #[structopt(long, global = true)] + pub usb_path: Option, /// Disables the cache for all secrets. #[structopt(long, global = true)] pub no_cache: bool, diff --git a/src/commands.rs b/src/commands.rs index 455ff4d..d352ca2 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -55,6 +55,9 @@ fn format_filter(config: &config::Config) -> String { .collect::>(); filters.push(format!("serial number in [{}]", serial_numbers.join(", "))); } + if let Some(path) = &config.usb_path { + filters.push(format!("usb path={}", path)); + } if filters.is_empty() { String::new() } else { @@ -75,7 +78,8 @@ fn find_device(config: &config::Config) -> anyhow::Result .serial_number .map(|sn| config.serial_numbers.contains(&sn)) .unwrap_or_default() - }); + }) + .filter(|device| config.usb_path.is_none() || config.usb_path.as_ref() == Some(&device.path)); let device = iter .next() @@ -83,8 +87,8 @@ fn find_device(config: &config::Config) -> anyhow::Result anyhow::ensure!( iter.next().is_none(), - "Multiple Nitrokey devices found{}. Use the --model and --serial-number options to \ - select one", + "Multiple Nitrokey devices found{}. Use the --model, --serial-number, and --usb-path options \ + to select one", format_filter(config) ); Ok(device) diff --git a/src/config.rs b/src/config.rs index a609189..6bb6efb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -32,6 +32,8 @@ pub struct Config { #[merge(strategy = merge::vec::overwrite_empty)] #[serde(default, deserialize_with = "deserialize_serial_number_vec")] pub serial_numbers: Vec, + /// The USB path of the device to connect to. + pub usb_path: Option, /// Whether to bypass the cache for all secrets or not. #[merge(strategy = merge::bool::overwrite_false)] #[serde(default)] @@ -74,6 +76,9 @@ impl Config { if !args.serial_numbers.is_empty() { self.serial_numbers = args.serial_numbers.clone(); } + if args.usb_path.is_some() { + self.usb_path = args.usb_path.clone(); + } if args.no_cache { self.no_cache = true; } diff --git a/src/tests/run.rs b/src/tests/run.rs index 4f53608..b39b1da 100644 --- a/src/tests/run.rs +++ b/src/tests/run.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later use std::collections; +use std::ops; use std::path; use super::*; @@ -116,7 +117,7 @@ fn connect_multiple(_model: nitrokey::Model) -> anyhow::Result<()> { let err = res.unwrap_err().to_string(); assert_eq!( err, - "Multiple Nitrokey devices found. Use the --model and --serial-number options to select one" + "Multiple Nitrokey devices found. Use the --model, --serial-number, and --usb-path options to select one" ); } Ok(()) @@ -142,6 +143,32 @@ fn connect_wrong_serial_number(_model: nitrokey::Model) { ); } +#[test_device] +fn connect_usb_path(_model: nitrokey::Model) -> anyhow::Result<()> { + for device in nitrokey::list_devices()? { + let res = Nitrocli::new().handle(&["status", &format!("--usb-path={}", device.path)]); + assert!(res.is_ok()); + let res = res?; + if let Some(model) = device.model { + assert!(res.contains(&format!("model: {}\n", model))); + } + if let Some(sn) = device.serial_number { + assert!(res.contains(&format!("serial number: {}\n", sn))); + } + } + Ok(()) +} + +#[test_device] +fn connect_wrong_usb_path(_model: nitrokey::Model) { + let res = Nitrocli::new().handle(&["status", "--usb-path=not-a-path"]); + let err = res.unwrap_err().to_string(); + assert_eq!( + err, + "Nitrokey device not found (filter: usb path=not-a-path)" + ); +} + #[test_device] fn connect_model(_model: nitrokey::Model) -> anyhow::Result<()> { let devices = nitrokey::list_devices()?; @@ -172,10 +199,71 @@ fn connect_model(_model: nitrokey::Model) -> anyhow::Result<()> { format!( "Multiple Nitrokey devices found (filter: model={}). ", model.to_lowercase() - ) + "Use the --model and --serial-number options to select one" + ) + "Use the --model, --serial-number, and --usb-path options to select one" ); } } Ok(()) } + +#[test_device] +fn connect_usb_path_model_serial(_model: nitrokey::Model) -> anyhow::Result<()> { + let devices = nitrokey::list_devices()?; + for device in devices { + let mut args = Vec::new(); + args.push("status".to_owned()); + args.push(format!("--usb-path={}", device.path)); + if let Some(model) = device.model { + args.push(format!("--model={}", model.to_string().to_lowercase())); + } + if let Some(sn) = device.serial_number { + args.push(format!("--serial-number={}", sn)); + } + + let res = Nitrocli::new().handle(&args.iter().map(ops::Deref::deref).collect::>())?; + if let Some(model) = device.model { + assert!(res.contains(&format!("model: {}\n", model))); + } + if let Some(sn) = device.serial_number { + assert!(res.contains(&format!("serial number: {}\n", sn))); + } + } + Ok(()) +} + +#[test_device] +fn connect_usb_path_model_wrong_serial(_model: nitrokey::Model) -> anyhow::Result<()> { + let devices = nitrokey::list_devices()?; + for device in devices { + let mut args = Vec::new(); + args.push("status".to_owned()); + args.push(format!("--usb-path={}", device.path)); + if let Some(model) = device.model { + args.push(format!("--model={}", model.to_string().to_lowercase())); + } + args.push("--serial-number=0xdeadbeef".to_owned()); + + let res = Nitrocli::new().handle(&args.iter().map(ops::Deref::deref).collect::>()); + let err = res.unwrap_err().to_string(); + if let Some(model) = device.model { + assert_eq!( + err, + format!( + "Nitrokey device not found (filter: model={}, serial number in [0xdeadbeef], usb path={})", + model.to_string().to_lowercase(), + device.path + ) + ); + } else { + assert_eq!( + err, + format!( + "Nitrokey device not found (filter: serial number in [0xdeadbeef], usb path={})", + device.path + ) + ); + } + } + Ok(()) +} -- cgit v1.2.3