Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fill command to overwrite SD card #125

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Unreleased
- Added `--usb-path` option that restricts the USB path of the device to
connect to
- Bumped `structopt` dependency to `0.3.17`
- Added the `fill` command that fills the SD card of a Nitrokey Storage device
with random data
- Added the `termion` dependency in version `1.5.5`


0.3.4
Expand Down
47 changes: 47 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ version = "0.1"
[dependencies.nitrokey]
version = "0.7.1"

[dependencies.progressing]
version = "3.0.2"

[dependencies.serde]
version = "1.0"
features = ["derive"]
Expand All @@ -61,6 +64,9 @@ features = ["derive"]
version = "0.3.17"
default-features = false

[dependencies.termion]
version = "1.5.5"

[dependencies.toml]
version = "0.5.6"

Expand Down
10 changes: 10 additions & 0 deletions doc/nitrocli.1
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ open.
.TP
\fBnitrocli hidden close
Close a hidden volume.
.TP
\fBnitrocli fill\fR
robinkrahl marked this conversation as resolved.
Show resolved Hide resolved
Fills the SD card with random data, overwriting all existing data.
This operation takes about one hour to finish for a 16 GB SD card.
It cannot be cancelled, even if the \fBnitrocli\fR process is terminated before
it finishes.

This command requires the admin PIN.
To avoid accidental calls of this command, the user has to enter the PIN even
if it has been cached.

.SS One-time passwords
The Nitrokey Pro and the Nitrokey Storage support the generation of one-time
Expand Down
Binary file modified doc/nitrocli.1.pdf
Binary file not shown.
2 changes: 2 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ Command! {
Config(ConfigArgs) => |ctx, args: ConfigArgs| args.subcmd.execute(ctx),
/// Interacts with the device's encrypted volume
Encrypted(EncryptedArgs) => |ctx, args: EncryptedArgs| args.subcmd.execute(ctx),
/// Fills the SD card with random data
Fill => crate::commands::fill,
/// Interacts with the device's hidden volume
Hidden(HiddenArgs) => |ctx, args: HiddenArgs| args.subcmd.execute(ctx),
/// Lists the attached Nitrokey devices
Expand Down
34 changes: 34 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use nitrokey::GetPasswordSafe;

use crate::args;
use crate::config;
use crate::output;
use crate::pinentry;
use crate::Context;

Expand Down Expand Up @@ -461,6 +462,39 @@ pub fn list(ctx: &mut Context<'_>, no_connect: bool) -> anyhow::Result<()> {
Ok(())
}

/// Fill the SD card with random data
pub fn fill(ctx: &mut Context<'_>) -> anyhow::Result<()> {
with_storage_device(ctx, |ctx, mut device| {
let pin_entry = pinentry::PinEntry::from(args::PinType::Admin, &device)?;

// Similar to reset, we want the user to re-enter the admin PIN even if is cached to avoid
// accidental data loss.
pinentry::clear(&pin_entry).context("Failed to clear cached secret")?;
d-e-s-o marked this conversation as resolved.
Show resolved Hide resolved

try_with_pin(ctx, &pin_entry, |pin| {
device.fill_sd_card(&pin).context("Failed to fill SD card")
})?;

let mut progress_bar = output::ProgressBar::new();
progress_bar.draw(ctx)?;
while !progress_bar.is_finished() {
use nitrokey::OperationStatus;

thread::sleep(time::Duration::from_secs(1));
let status = device
.get_operation_status()
.context("Failed to query operation status")?;
match status {
OperationStatus::Ongoing(progress) => progress_bar.update(progress)?,
OperationStatus::Idle => progress_bar.finish(),
};
progress_bar.draw(ctx)?;
}

Ok(())
})
}

/// Perform a factory reset.
pub fn reset(ctx: &mut Context<'_>) -> anyhow::Result<()> {
with_device(ctx, |ctx, mut device| {
Expand Down
14 changes: 12 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ mod arg_util;
mod args;
mod commands;
mod config;
mod output;
mod pinentry;
#[cfg(test)]
mod tests;
Expand Down Expand Up @@ -98,6 +99,8 @@ pub struct Context<'io> {
pub stdout: &'io mut dyn io::Write,
/// The `Write` object used as standard error throughout the program.
pub stderr: &'io mut dyn io::Write,
/// Whether `stdout` is a TTY.
pub is_tty: bool,
/// The admin PIN, if provided through an environment variable.
pub admin_pin: Option<ffi::OsString>,
/// The user PIN, if provided through an environment variable.
Expand All @@ -118,14 +121,20 @@ pub struct Context<'io> {
}

impl<'io> Context<'io> {
fn from_env<O, E>(stdout: &'io mut O, stderr: &'io mut E, config: config::Config) -> Context<'io>
fn from_env<O, E>(
stdout: &'io mut O,
stderr: &'io mut E,
is_tty: bool,
config: config::Config,
) -> Context<'io>
where
O: io::Write,
E: io::Write,
{
Context {
stdout,
stderr,
is_tty,
admin_pin: env::var_os(NITROCLI_ADMIN_PIN),
user_pin: env::var_os(NITROCLI_USER_PIN),
new_admin_pin: env::var_os(NITROCLI_NEW_ADMIN_PIN),
Expand Down Expand Up @@ -154,8 +163,9 @@ fn main() {

let rc = match config::Config::load() {
Ok(config) => {
let is_tty = termion::is_tty(&stdout);
let args = env::args().collect::<Vec<_>>();
let ctx = &mut Context::from_env(&mut stdout, &mut stderr, config);
let ctx = &mut Context::from_env(&mut stdout, &mut stderr, is_tty, config);

run(ctx, args)
}
Expand Down
106 changes: 106 additions & 0 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// commands.rs

// Copyright (C) 2020 The Nitrocli Developers
// SPDX-License-Identifier: GPL-3.0-or-later

use crate::Context;

/// A progress bar that can be printed to an interactive output.
pub struct ProgressBar {
/// Whether to redraw the entire progress bar in the next call to `draw`.
redraw: bool,
/// The current progress of the progress bar (0 <= progress <= 100).
progress: u8,
/// Toogled on every call to `draw` to print a pulsing indicator.
toggle: bool,
/// Whether this progress bar finished.
finished: bool,
}

impl ProgressBar {
/// Creates a new empty progress bar.
pub fn new() -> ProgressBar {
ProgressBar {
redraw: true,
progress: 0,
toggle: false,
finished: false,
}
}

/// Whether this progress bar is finished.
pub fn is_finished(&self) -> bool {
self.finished
}

/// Updates the progress bar with the given progress (0 <= progress <= 100).
pub fn update(&mut self, progress: u8) -> anyhow::Result<()> {
anyhow::ensure!(!self.finished, "Tried to update finished progress bar");
anyhow::ensure!(
progress <= 100,
"Progress bar value out of range: {}",
progress
);
if progress != self.progress {
self.redraw = true;
self.progress = progress;
}
self.toggle = !self.toggle;
Ok(())
}

/// Finish this progress bar.
///
/// A finished progress bar may no longer be updated.
pub fn finish(&mut self) {
self.finished = true;
self.redraw = true;
self.progress = 100;
}

/// Print the progress bar to the stdout set in the given context.
///
/// On every call of this method (as long as the progress bar is not finished), a pulsing
/// indicator is printed to show that the process is still running. If there was progress since
/// the last call to `draw`, or if this is the first call, this function will also print the
/// progress bar itself.
pub fn draw(&self, ctx: &mut Context<'_>) -> anyhow::Result<()> {
use anyhow::Context as _;
use termion::cursor::DetectCursorPos as _;
use termion::raw::IntoRawMode as _;

if !ctx.is_tty {
return Ok(());
}

let pos = ctx
.stdout
.into_raw_mode()
.context("Failed to activate raw mode")?
.cursor_pos()
.context("Failed to query cursor position")?;
let progress_char = if self.toggle && !self.finished {
"."
} else {
" "
};
if self.redraw {
use progressing::Baring as _;;

let mut progress_bar = progressing::mapping::Bar::with_range(0, 100);
progress_bar.set(self.progress);

print!(ctx, "{}", termion::clear::CurrentLine)?;
print!(ctx, "{}", termion::cursor::Goto(1, pos.1))?;
print!(ctx, " {} {}", progress_char, progress_bar)?;
if self.finished {
println!(ctx)?;
}
} else {
print!(ctx, "{}{}", termion::cursor::Goto(2, pos.1), progress_char)?;
}

ctx.stdout.flush()?;
Ok(())
}
}
6 changes: 6 additions & 0 deletions src/redefine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ macro_rules! println {
};
}

macro_rules! print {
($ctx:expr, $($arg:tt)*) => {
write!($ctx.stdout, $($arg)*)
};
}

macro_rules! eprintln {
($ctx:expr) => {
writeln!($ctx.stderr, "")
Expand Down
15 changes: 15 additions & 0 deletions src/tests/fill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// fill.rs

// Copyright (C) 2020 The Nitrocli Developers
// SPDX-License-Identifier: GPL-3.0-or-later

use super::*;

// Ignore this test as it takes about one hour to execute
#[ignore]
#[test_device(storage)]
fn fill(model: nitrokey::Model) -> anyhow::Result<()> {
let res = Nitrocli::new().model(model).handle(&["fill"]);
assert!(res.is_ok());
Ok(())
}
2 changes: 2 additions & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use nitrokey_test::test as test_device;

mod config;
mod encrypted;
mod fill;
mod hidden;
mod list;
mod lock;
Expand Down Expand Up @@ -98,6 +99,7 @@ impl Nitrocli {
let ctx = &mut crate::Context {
stdout: &mut stdout,
stderr: &mut stderr,
is_tty: false,
admin_pin: self.admin_pin.clone(),
user_pin: self.user_pin.clone(),
new_admin_pin: self.new_admin_pin.clone(),
Expand Down