Skip to content

Commit 01b8b4f

Browse files
robinkrahld-e-s-o
authored andcommitted
Add --only-aes-key option to reset command
This patch adds an --only-aes-key option to the reset command to only build a new AES key without performing a full factory reset. Fixes #69
1 parent 333d9de commit 01b8b4f

File tree

6 files changed

+96
-19
lines changed

6 files changed

+96
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Unreleased
1010
- Fixed pinentry dialog highlighting some messages incorrectly as errors
1111
- Switched to using GitHub Actions as the project's CI pipeline
1212
- Bumped `nitrokey` dependency to `0.9.0`
13+
- Added the `--only-aes-key` option to the `reset` command to build a new AES
14+
key without performing a factory reset
1315

1416

1517
0.4.0

doc/nitrocli.1

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.TH NITROCLI 1 2021-04-14
1+
.TH NITROCLI 1 2021-04-17
22
.SH NAME
33
nitrocli \- access Nitrokey devices
44
.SH SYNOPSIS
@@ -79,12 +79,16 @@ This command locks the password safe (see the Password safe section). On the
7979
Nitrokey Storage, it will also close any active encrypted or hidden volumes (see
8080
the Storage section).
8181
.TP
82-
.B nitrocli reset
82+
.B nitrocli reset \fR[\fB\-\-only-aes-key\fR]
8383
Perform a factory reset on the Nitrokey.
8484
This command performs a factory reset on the OpenPGP smart card, clears the
8585
flash storage and builds a new AES key.
8686
The user PIN is reset to 123456, the admin PIN to 12345678.
8787

88+
If the \fB\-\-only-aes-key\fR option is set, the command does not perform a
89+
full factory reset but only creates a new AES key.
90+
The AES key is for example used to encrypt the password safe.
91+
8892
This command requires the admin PIN.
8993
To avoid accidental calls of this command, the user has to enter the PIN even
9094
if it has been cached.

doc/nitrocli.1.pdf

250 Bytes
Binary file not shown.

src/args.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Command! {
103103
/// Accesses the password safe
104104
Pws(PwsArgs) => |ctx, args: PwsArgs| args.subcmd.execute(ctx),
105105
/// Performs a factory reset
106-
Reset => crate::commands::reset,
106+
Reset(ResetArgs) => |ctx, args: ResetArgs| crate::commands::reset(ctx, args.only_aes_key),
107107
/// Prints the status of the connected Nitrokey device
108108
Status => crate::commands::status,
109109
/// Interacts with the device's unencrypted volume
@@ -445,6 +445,13 @@ pub struct PwsStatusArgs {
445445
pub all: bool,
446446
}
447447

448+
#[derive(Debug, PartialEq, structopt::StructOpt)]
449+
pub struct ResetArgs {
450+
/// Only build a new AES key instead of performing a full factory reset.
451+
#[structopt(long)]
452+
pub only_aes_key: bool,
453+
}
454+
448455
#[derive(Debug, PartialEq, structopt::StructOpt)]
449456
pub struct UnencryptedArgs {
450457
#[structopt(subcommand)]

src/commands.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ pub fn fill(ctx: &mut Context<'_>, attach: bool) -> anyhow::Result<()> {
513513
}
514514

515515
/// Perform a factory reset.
516-
pub fn reset(ctx: &mut Context<'_>) -> anyhow::Result<()> {
516+
pub fn reset(ctx: &mut Context<'_>, only_aes_key: bool) -> anyhow::Result<()> {
517517
with_device(ctx, |ctx, mut device| {
518518
let pin_entry = pinentry::PinEntry::from(args::PinType::Admin, &device)?;
519519

@@ -522,20 +522,28 @@ pub fn reset(ctx: &mut Context<'_>) -> anyhow::Result<()> {
522522
pinentry::clear(&pin_entry).context("Failed to clear cached secret")?;
523523

524524
try_with_pin(ctx, &pin_entry, |pin| {
525-
device
526-
.factory_reset(&pin)
527-
.context("Failed to reset to factory settings")?;
528-
// Work around for a timing issue between factory_reset and
529-
// build_aes_key, see
530-
// https://github.com/Nitrokey/nitrokey-storage-firmware/issues/80
531-
thread::sleep(time::Duration::from_secs(3));
532-
// Another work around for spurious WrongPassword returns of
533-
// build_aes_key after a factory reset on Pro devices.
534-
// https://github.com/Nitrokey/nitrokey-pro-firmware/issues/57
535-
let _ = device.get_user_retry_count();
536-
device
537-
.build_aes_key(nitrokey::DEFAULT_ADMIN_PIN)
538-
.context("Failed to rebuild AES key")
525+
if only_aes_key {
526+
// Similar to the else arm, we have to execute this command to avoid WrongPassword errors
527+
let _ = device.get_user_retry_count();
528+
device
529+
.build_aes_key(&pin)
530+
.context("Failed to rebuild AES key")
531+
} else {
532+
device
533+
.factory_reset(&pin)
534+
.context("Failed to reset to factory settings")?;
535+
// Work around for a timing issue between factory_reset and
536+
// build_aes_key, see
537+
// https://github.com/Nitrokey/nitrokey-storage-firmware/issues/80
538+
thread::sleep(time::Duration::from_secs(3));
539+
// Another work around for spurious WrongPassword returns of
540+
// build_aes_key after a factory reset on Pro devices.
541+
// https://github.com/Nitrokey/nitrokey-pro-firmware/issues/57
542+
let _ = device.get_user_retry_count();
543+
device
544+
.build_aes_key(nitrokey::DEFAULT_ADMIN_PIN)
545+
.context("Failed to rebuild AES key")
546+
}
539547
})
540548
})
541549
}

src/tests/reset.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// reset.rs
22

3-
// Copyright (C) 2019-2020 The Nitrocli Developers
3+
// Copyright (C) 2019-2021 The Nitrocli Developers
44
// SPDX-License-Identifier: GPL-3.0-or-later
55

66
use nitrokey::Authenticate;
@@ -43,3 +43,59 @@ fn reset(model: nitrokey::Model) -> anyhow::Result<()> {
4343

4444
Ok(())
4545
}
46+
47+
#[test_device]
48+
fn reset_only_aes_key(model: nitrokey::Model) -> anyhow::Result<()> {
49+
const NEW_USER_PIN: &str = "654321";
50+
const NAME: &str = "slotname";
51+
const LOGIN: &str = "sloglogin";
52+
const PASSWORD: &str = "slotpassword";
53+
54+
let mut ncli = Nitrocli::new().model(model).new_user_pin(NEW_USER_PIN);
55+
56+
// Change the user PIN
57+
let _ = ncli.handle(&["pin", "set", "user"])?;
58+
59+
// Add an entry to the PWS
60+
{
61+
let mut manager = nitrokey::force_take()?;
62+
let mut device = manager.connect_model(model)?;
63+
let mut pws = device.get_password_safe(NEW_USER_PIN)?;
64+
pws.write_slot(0, NAME, LOGIN, PASSWORD)?;
65+
}
66+
67+
// Build AES key
68+
let mut ncli = Nitrocli::new().model(model);
69+
let out = ncli.handle(&["reset", "--only-aes-key"])?;
70+
assert!(out.is_empty());
71+
72+
// Check that 1) the password store works, i.e., there is an AES key,
73+
// that 2) we can no longer access the stored data, i.e., the AES has
74+
// been replaced, and that 3) the changed user PIN still works, i.e.,
75+
// we did not perform a factory reset.
76+
{
77+
let mut manager = nitrokey::force_take()?;
78+
let mut device = manager.connect_model(model)?;
79+
let pws = device.get_password_safe(NEW_USER_PIN)?;
80+
let slot = pws.get_slot_unchecked(0)?;
81+
82+
if let Ok(name) = slot.get_name() {
83+
assert_ne!(NAME, &name);
84+
}
85+
if let Ok(login) = slot.get_login() {
86+
assert_ne!(LOGIN, &login);
87+
}
88+
if let Ok(password) = slot.get_password() {
89+
assert_ne!(PASSWORD, &password);
90+
}
91+
}
92+
93+
// Reset the user PIN for other tests
94+
let mut ncli = ncli
95+
.user_pin(NEW_USER_PIN)
96+
.new_user_pin(nitrokey::DEFAULT_USER_PIN);
97+
let out = ncli.handle(&["pin", "set", "user"])?;
98+
assert!(out.is_empty());
99+
100+
Ok(())
101+
}

0 commit comments

Comments
 (0)