diff --git a/CHANGELOG.md b/CHANGELOG.md index 383a1386..32c89bd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Unreleased - Removed the `pws set` subcommand - Added the `--only-aes-key` option to the `reset` command to build a new AES key without performing a factory reset -- Added support for reading PWS passwords from stdin +- Added support for reading PWS passwords and OTP secrets from stdin - Added `NITROCLI_RESOLVED_USB_PATH` environment variable to be used by extensions - Allowed entering of `base32` encoded strings containing spaces diff --git a/doc/nitrocli.1 b/doc/nitrocli.1 index d2891ad7..2df9531a 100644 --- a/doc/nitrocli.1 +++ b/doc/nitrocli.1 @@ -1,4 +1,4 @@ -.TH NITROCLI 1 2021-04-20 +.TH NITROCLI 1 2021-04-24 .SH NAME nitrocli \- access Nitrokey devices .SH SYNOPSIS @@ -182,7 +182,7 @@ If \fB\-\-time\fR is set, it is set to \fItime\fR instead, which must be a Unix timestamp (i.e., the number of seconds since 1970-01-01 00:00:00 UTC). This command might require the user PIN (see the Configuration section). .TP -\fBnitrocli otp set \fIslot name secret \ +\fBnitrocli otp set \fIslot name secret\fR|\fB-\fR \ \fR[\fB\-a\fR|\fB\-\-algorithm \fIalgorithm\fR] \ [\fB\-d\fR|\fB\-\-digits \fIdigits\fR] [\fB\-c\fR|\fB\-\-counter \fIcounter\fR] \ [\fB\-t\fR|\fB\-\-time-window \fItime-window\fR] \ @@ -191,6 +191,8 @@ Configure a one-time password slot. \fIslot\fR is the number of the slot to configure. \fIname\fR is the name of the slot (may not be empty). \fIsecret\fR is the secret value to store in that slot. +If \fIsecret\fR is set to \fB-\fR, the secret is read from the standard +input. The \fB\-\-format\fR option specifies the format of the secret. If it is set to \fBascii\fR, each character of the given secret is interpreted diff --git a/doc/nitrocli.1.pdf b/doc/nitrocli.1.pdf index d48632c3..7ce26862 100644 Binary files a/doc/nitrocli.1.pdf and b/doc/nitrocli.1.pdf differ diff --git a/src/commands.rs b/src/commands.rs index 68e02e73..6863db78 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -824,7 +824,10 @@ fn prepare_base32_secret(secret: &str) -> anyhow::Result { } /// Prepare a secret string in the given format for libnitrokey. -fn prepare_secret(secret: String, format: args::OtpSecretFormat) -> anyhow::Result { +fn prepare_secret( + secret: borrow::Cow<'_, str>, + format: args::OtpSecretFormat, +) -> anyhow::Result { match format { args::OtpSecretFormat::Ascii => prepare_ascii_secret(&secret), args::OtpSecretFormat::Base32 => prepare_base32_secret(&secret), @@ -832,7 +835,7 @@ fn prepare_secret(secret: String, format: args::OtpSecretFormat) -> anyhow::Resu // We need to ensure to provide a string with an even number of // characters in it, just because that's what libnitrokey // expects. So prepend a '0' if that is not the case. - let mut secret = secret; + let mut secret = secret.into_owned(); if secret.len() % 2 != 0 { secret.insert(0, '0') } @@ -843,7 +846,8 @@ fn prepare_secret(secret: String, format: args::OtpSecretFormat) -> anyhow::Resu /// Configure a one-time password slot on the Nitrokey device. pub fn otp_set(ctx: &mut Context<'_>, args: args::OtpSetArgs) -> anyhow::Result<()> { - let secret = prepare_secret(args.secret, args.format)?; + let secret = value_or_stdin(ctx, &args.secret)?; + let secret = prepare_secret(secret, args.format)?; let data = nitrokey::OtpSlotData::new(args.slot, args.name, secret, args.digits.into()); let (algorithm, counter, time_window) = (args.algorithm, args.counter, args.time_window); with_device(ctx, |ctx, device| { diff --git a/src/tests/otp.rs b/src/tests/otp.rs index 8a48adb8..008b4145 100644 --- a/src/tests/otp.rs +++ b/src/tests/otp.rs @@ -103,6 +103,24 @@ fn set_totp_uneven_chars(model: nitrokey::Model) -> anyhow::Result<()> { Ok(()) } +#[test_device] +fn set_stdin(model: nitrokey::Model) -> anyhow::Result<()> { + const SECRET: &str = "12345678901234567890"; + const TIME: &str = stringify!(1111111111); + const OTP: &str = concat!(14050471, "\n"); + + let _ = Nitrocli::new() + .model(model) + .stdin(SECRET) + .handle(&["otp", "set", "-d", "8", "-f", "ascii", "2", "name", "-"])?; + + let out = Nitrocli::new() + .model(model) + .handle(&["otp", "get", "-t", TIME, "2"])?; + assert_eq!(out, OTP); + Ok(()) +} + #[test_device] fn clear(model: nitrokey::Model) -> anyhow::Result<()> { let mut ncli = Nitrocli::new().model(model);