aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2020-01-25 20:33:57 +0100
committerDaniel Mueller <deso@posteo.net>2020-09-07 10:14:21 -0700
commit6917be7ab3c5a9d47866a45855c836a9cc6f86ff (patch)
treeca75486d09c9656e1bbf8ac7c68b5807483cef01
parent4d25d79f18cd2c5627c46727b425c745c78cf942 (diff)
downloadnitrocli-6917be7ab3c5a9d47866a45855c836a9cc6f86ff.tar.gz
nitrocli-6917be7ab3c5a9d47866a45855c836a9cc6f86ff.tar.bz2
Add --serial-number option
This patch adds the --serial-number option that allows the user to filter the attached Nitrokey devices by serial number. As the Nitrokey Storage does not include its serial number in the USB device descriptor and as we don't want to connect to it just to query the serial number, this option only works for Nitrokey Storage devices.
-rw-r--r--CHANGELOG.md2
-rw-r--r--doc/config.example.toml3
-rw-r--r--doc/nitrocli.126
-rw-r--r--doc/nitrocli.1.pdfbin40972 -> 41970 bytes
-rw-r--r--src/args.rs10
-rw-r--r--src/commands.rs25
-rw-r--r--src/config.rs25
-rw-r--r--src/main.rs1
-rw-r--r--src/tests/status.rs10
9 files changed, 93 insertions, 9 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc288ee..29a9faa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@ Unreleased
- Reworked connection handling for multiple attached Nitrokey devices:
- Fail if multiple attached devices match the filter options (or no filter
options are set)
+ - Added `--serial-number` option that restricts the serial number of the
+ device to connect to
0.3.4
diff --git a/doc/config.example.toml b/doc/config.example.toml
index a427749..eefdfa0 100644
--- a/doc/config.example.toml
+++ b/doc/config.example.toml
@@ -4,6 +4,9 @@
# The model to connect to (string, "pro" or "storage", default: not set).
model = "pro"
+# The serial number of the device to connect to (list of strings, default:
+# empty).
+serial_numbers = ["0xf00baa", "deadbeef"]
# Do not cache secrets (boolean, default: false).
no_cache = true
# The log level (integer, default: 0).
diff --git a/doc/nitrocli.1 b/doc/nitrocli.1
index 680af3b..8b04de6 100644
--- a/doc/nitrocli.1
+++ b/doc/nitrocli.1
@@ -12,16 +12,25 @@ It can be used to access the encrypted volume, the one-time password generator,
and the password safe.
.SS Device selection
Per default, \fBnitrocli\fR connects to any attached Nitrokey device.
-You can use the \fB\-\-model\fR option to select the device to connect to.
-\fBnitrocli\fR fails if more than one attached Nitrokey device matches
-this filter or if multiple Nitrokey devices are attached and this option
-is not set.
+You can use the \fB\-\-model\fR and \fB\-\-serial-number\fR options to select
+the device to connect to.
+\fBnitrocli\fR fails if more than one attached Nitrokey device matches this
+filter or if multiple Nitrokey devices are attached and none of the filter
+options is set.
.SH OPTIONS
.TP
\fB\-m\fR, \fB\-\-model pro\fR|\fBstorage\fR
Restrict connections to the given device model, see the Device selection
section.
.TP
+\fB\-\-serial-number \fIserial-number\fR
+Restrict connections to the given serial number, see the Device selection
+section.
+\fIserial-number\fR must be a hex string with an optional 0x prefix.
+This option can be set multiple times to allow any of the given serial numbers.
+Nitrokey Storage devices never match this restriction as they do not expose
+their serial number in the USB device descriptor.
+.TP
\fB\-\-no\-cache\fR
If this option is set, nitrocli will not cache any inquired secrets using
\fBgpg\-agent\fR(1) but ask for them each time they are needed.
@@ -302,6 +311,10 @@ The following values can be set in the configuration file:
Restrict connections to the given device model (string, default: not set, see
\fB\-\-model\fR).
.TP
+.B serial_numbers
+Restrict connections to the given serial numbers (list of strings, default:
+empty, see \fB\-\-serial-number\fR).
+.TP
.B no_cache
If set to true, do not cache any inquired secrets (boolean, default: false,
see \fB\-\-no\-cache\fR).
@@ -311,6 +324,7 @@ Set the log level (integer, default: 0, see \fB\-\-verbose\fR).
.P
The configuration file must use the TOML format, for example:
model = "pro"
+ serial_numbers = ["0xf00baa", "deadbeef"]
no_cache = false
verbosity = 0
@@ -343,6 +357,10 @@ configuration (see the Config file section):
Restrict connections to the given device model (string, default: not set, see
\fB\-\-model\fR).
.TP
+.B NITROCLI_SERIAL_NUMBERS
+Restrict connections to the given list of serial numbers (comma-separated list
+of strings, default: empty, see \fB\-\-serial-number\fR).
+.TP
.B NITROCLI_NO_CACHE
If set to true, do not cache any inquired secrets (boolean, default: false,
see \fB\-\-no\-cache\fR).
diff --git a/doc/nitrocli.1.pdf b/doc/nitrocli.1.pdf
index 015f379..73041ae 100644
--- a/doc/nitrocli.1.pdf
+++ b/doc/nitrocli.1.pdf
Binary files differ
diff --git a/src/args.rs b/src/args.rs
index 3052afa..0d77806 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -13,6 +13,16 @@ pub struct Args {
/// Selects the device model to connect to
#[structopt(short, long, global = true, possible_values = &DeviceModel::all_str())]
pub model: Option<DeviceModel>,
+ /// Sets the serial number of the device to connect to. Can be set
+ /// multiple times to allow multiple serial numbers
+ // TODO: Add short options (avoid collisions).
+ #[structopt(
+ long = "serial-number",
+ global = true,
+ multiple = true,
+ number_of_values = 1
+ )]
+ pub serial_numbers: Vec<nitrokey::SerialNumber>,
/// 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 05038e0..f718571 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -43,10 +43,22 @@ fn set_log_level(ctx: &mut Context<'_>) {
/// Create a filter string from the program configuration.
fn format_filter(config: &config::Config) -> String {
+ let mut filters = Vec::new();
if let Some(model) = config.model {
- format!(" (filter: model={})", model.as_ref())
- } else {
+ filters.push(format!("model={}", model.as_ref()));
+ }
+ if !config.serial_numbers.is_empty() {
+ let serial_numbers = config
+ .serial_numbers
+ .iter()
+ .map(ToString::to_string)
+ .collect::<Vec<_>>();
+ filters.push(format!("serial number in [{}]", serial_numbers.join(", ")));
+ }
+ if filters.is_empty() {
String::new()
+ } else {
+ format!(" (filter: {})", filters.join(", "))
}
}
@@ -56,7 +68,14 @@ fn find_device(config: &config::Config) -> anyhow::Result<nitrokey::DeviceInfo>
let nkmodel = config.model.map(nitrokey::Model::from);
let mut iter = devices
.into_iter()
- .filter(|device| nkmodel.is_none() || device.model == nkmodel);
+ .filter(|device| nkmodel.is_none() || device.model == nkmodel)
+ .filter(|device| {
+ config.serial_numbers.is_empty()
+ || device
+ .serial_number
+ .map(|sn| config.serial_numbers.contains(&sn))
+ .unwrap_or_default()
+ });
let device = iter
.next()
diff --git a/src/config.rs b/src/config.rs
index aceda38..6f0cd17 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -5,6 +5,9 @@
use std::fs;
use std::path;
+use std::str::FromStr as _;
+
+use serde::de::Error as _;
use crate::args;
@@ -20,10 +23,14 @@ const CONFIG_FILE: &str = "config.toml";
/// The configuration for nitrocli, usually read from configuration
/// files and environment variables.
-#[derive(Clone, Copy, Debug, Default, PartialEq, merge::Merge, serde::Deserialize)]
+#[derive(Clone, Debug, Default, PartialEq, merge::Merge, serde::Deserialize)]
pub struct Config {
/// The model to connect to.
pub model: Option<args::DeviceModel>,
+ /// The serial numbers of the device to connect to.
+ #[merge(strategy = merge::vec::overwrite_empty)]
+ #[serde(default, deserialize_with = "deserialize_serial_number_vec")]
+ pub serial_numbers: Vec<nitrokey::SerialNumber>,
/// Whether to bypass the cache for all secrets or not.
#[merge(strategy = merge::bool::overwrite_false)]
#[serde(default)]
@@ -34,6 +41,18 @@ pub struct Config {
pub verbosity: u8,
}
+fn deserialize_serial_number_vec<'de, D>(d: D) -> Result<Vec<nitrokey::SerialNumber>, D::Error>
+where
+ D: serde::Deserializer<'de>,
+{
+ let strings: Vec<String> = serde::Deserialize::deserialize(d).map_err(D::Error::custom)?;
+ let result: Result<Vec<_>, _> = strings
+ .iter()
+ .map(|s| nitrokey::SerialNumber::from_str(s))
+ .collect();
+ result.map_err(D::Error::custom)
+}
+
impl Config {
pub fn load() -> anyhow::Result<Self> {
use merge::Merge as _;
@@ -51,6 +70,10 @@ impl Config {
if args.model.is_some() {
self.model = args.model;
}
+ if !args.serial_numbers.is_empty() {
+ // TODO: Don't clone.
+ self.serial_numbers = args.serial_numbers.clone();
+ }
if args.no_cache {
self.no_cache = true;
}
diff --git a/src/main.rs b/src/main.rs
index 16715f9..baad15c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,6 @@
// Copyright (C) 2017-2020 The Nitrocli Developers
// SPDX-License-Identifier: GPL-3.0-or-later
-#![allow(clippy::trivially_copy_pass_by_ref)]
#![warn(
bad_style,
dead_code,
diff --git a/src/tests/status.rs b/src/tests/status.rs
index 7946929..268e36f 100644
--- a/src/tests/status.rs
+++ b/src/tests/status.rs
@@ -28,6 +28,16 @@ fn not_found_pro() {
assert_eq!(err, "Nitrokey device not found (filter: model=pro)");
}
+#[test_device]
+fn not_found_by_serial_number() {
+ let res = Nitrocli::new().handle(&["status", "--model=storage", "--serial-number=deadbeef"]);
+ let err = res.unwrap_err().to_string();
+ assert_eq!(
+ err,
+ "Nitrokey device not found (filter: model=storage, serial number in [0xdeadbeef])"
+ );
+}
+
#[test_device(pro)]
fn output_pro(model: nitrokey::Model) -> anyhow::Result<()> {
let re = regex::Regex::new(