// main.rs
// *************************************************************************
// * Copyright (C) 2017-2018 Daniel Mueller (deso@posteo.net) *
// * *
// * This program is free software: you can redistribute it and/or modify *
// * it under the terms of the GNU General Public License as published by *
// * the Free Software Foundation, either version 3 of the License, or *
// * (at your option) any later version. *
// * *
// * This program is distributed in the hope that it will be useful, *
// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
// * GNU General Public License for more details. *
// * *
// * You should have received a copy of the GNU General Public License *
// * along with this program. If not, see . *
// *************************************************************************
#![deny(
dead_code,
duplicate_associated_type_bindings,
illegal_floating_point_literal_pattern,
improper_ctypes,
intra_doc_link_resolution_failure,
late_bound_lifetime_arguments,
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
no_mangle_generic_items,
non_shorthand_field_patterns,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
plugin_as_library,
private_in_public,
proc_macro_derive_resolution_fallback,
safe_packed_borrows,
stable_features,
trivial_bounds,
trivial_numeric_casts,
type_alias_bounds,
tyvar_behind_raw_pointer,
unconditional_recursion,
unions_with_drop_fields,
unreachable_code,
unreachable_patterns,
unstable_features,
unstable_name_collisions,
unused,
unused_comparisons,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_results,
where_clauses_object_safety,
while_true,
)]
#![warn(
bad_style,
future_incompatible,
nonstandard_style,
renamed_and_removed_lints,
rust_2018_compatibility,
rust_2018_idioms,
)]
//! Nitrocli is a program providing a command line interface to certain
//! commands of the Nitrokey Storage device.
mod error;
mod pinentry;
use std::process;
use std::result;
use libnitrokey;
use crate::error::Error;
type Result = result::Result;
const PIN_TYPE: pinentry::PinType = pinentry::PinType::User;
/// Create an `error::Error` with an error message of the format `msg: err`.
fn get_error(msg: &str, err: &libnitrokey::CommandError) -> Error {
Error::Error(format!("{}: {:?}", msg, err))
}
/// Connect to a Nitrokey Storage device and return it.
fn get_storage_device() -> Result {
libnitrokey::Storage::connect()
.or_else(|_| Err(Error::Error("Nitrokey device not found".to_string())))
}
/// Return a string representation of the given volume status.
fn get_volume_status(status: &libnitrokey::VolumeStatus) -> &'static str {
if status.active {
if status.read_only {
"read-only"
} else {
"active"
}
} else {
"inactive"
}
}
/// Pretty print the response of a status command.
fn print_status(status: &libnitrokey::StorageStatus) {
println!("Status:");
// We omit displaying information about the smartcard here as this
// program really is only about the SD card portion of the device.
println!(" SD card ID: {:#x}", status.serial_number_sd_card);
println!(" firmware version: {}.{}",
status.firmware_version_major,
status.firmware_version_minor);
println!(" firmware: {}",
if status.firmware_locked {
"locked".to_string()
} else {
"unlocked".to_string()
});
println!(" storage keys: {}",
if status.stick_initialized {
"created".to_string()
} else {
"not created".to_string()
});
println!(" user retry count: {}", status.user_retry_count);
println!(" admin retry count: {}", status.admin_retry_count);
println!(" volumes:");
println!(" unencrypted: {}", get_volume_status(&status.unencrypted_volume));
println!(" encrypted: {}", get_volume_status(&status.encrypted_volume));
println!(" hidden: {}", get_volume_status(&status.hidden_volume));
}
/// Inquire the status of the nitrokey.
fn status() -> Result<()> {
let status = get_storage_device()?
.get_status()
.map_err(|err| get_error("Getting Storage status failed", &err))?;
print_status(&status);
Ok(())
}
/// Open the encrypted volume on the nitrokey.
fn open() -> Result<()> {
let device = get_storage_device()?;
let mut retry = 3;
let mut error_msg: Option<&str> = None;
loop {
// TODO: Rethink the usage of String::from_utf8_lossy here. We may
// not want to silently modify the password!
let passphrase = pinentry::inquire_passphrase(PIN_TYPE, error_msg)?;
let passphrase = String::from_utf8_lossy(&passphrase);
match device.enable_encrypted_volume(&passphrase) {
Ok(()) => return Ok(()),
Err(err) => match err {
libnitrokey::CommandError::WrongPassword => {
pinentry::clear_passphrase(PIN_TYPE)?;
retry -= 1;
if retry > 0 {
error_msg = Some("Wrong password, please reenter");
continue;
}
let error = "Opening encrypted volume failed: Wrong password";
return Err(Error::Error(error.to_string()));
},
err => return Err(get_error("Opening encrypted volume failed", &err)),
},
};
}
}
#[link(name = "c")]
extern "C" {
fn sync();
}
/// Close the previously opened encrypted volume.
fn close() -> Result<()> {
// Flush all filesystem caches to disk. We are mostly interested in
// making sure that the encrypted volume on the nitrokey we are
// about to close is not closed while not all data was written to
// it.
unsafe { sync() };
get_storage_device()?
.disable_encrypted_volume()
.map_err(|err| get_error("Closing encrypted volume failed", &err))
}
/// Clear the PIN stored when opening the nitrokey's encrypted volume.
fn clear() -> Result<()> {
pinentry::clear_passphrase(PIN_TYPE)
}
// A macro for generating a match of the different supported commands.
// Each supplied command is converted into a string and matched against.
macro_rules! commands {
( $str:expr, [ $( $command:expr), *] ) => {
match &*$str.to_string() {
$(
stringify!($command) => {
if let Err(err) = $command() {
println!("{}", err);
return 1
}
return 0
},
)*
x => {
println!("Invalid command: {}", x);
println!("Available commands: {}", stringify!( $($command)* ));
return 1
},
}
}
}
fn run() -> i32 {
let argv: Vec = std::env::args().collect();
if argv.len() != 2 {
println!("Usage: {} ", argv[0]);
return 1;
}
commands!(&argv[1], [status, open, close, clear]);
}
fn main() {
process::exit(run());
}