Merge branch 'password-diceware' into 'master'

Add feature to generate easily rememberable passphrase

See merge request timvisee/ffsend!4
This commit is contained in:
Tim Visée 2018-08-02 00:43:36 +00:00
commit 743b84af67
11 changed files with 166 additions and 11 deletions

38
Cargo.lock generated
View file

@ -130,6 +130,14 @@ name = "cfg-if"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "chbs"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "chrono"
version = "0.4.3"
@ -175,6 +183,14 @@ dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "colored"
version = "1.6.0"
@ -351,6 +367,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "ffsend"
version = "0.0.7"
dependencies = [
"chbs 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
"clipboard 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -931,6 +948,23 @@ dependencies = [
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_syscall"
version = "0.1.40"
@ -1658,10 +1692,12 @@ dependencies = [
"checksum bytes 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dd32989a66957d3f0cba6588f15d4281a733f4e9ffc43fcd2385f57d3bf99ff"
"checksum cc 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "49ec142f5768efb5b7622aebc3fdbdbb8950a4b9ba996393cb76ef7466e8747d"
"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"
"checksum chbs 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7580cf614cddea9bbf9de26bb17faa88aba720c745e65bd96fdfd794d2bf30ee"
"checksum chrono 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a81892f0d5a53f46fc05ef0b917305a81c13f1f13bb59ac91ff595817f0764b1"
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
"checksum clipboard 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b4623b47d8637fc9d47564583d4cc01eb8c8e34e26b2bf348bf4b036acb657"
"checksum clipboard-win 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "289da2fc09ab964a4948a63287c94fcb4698fa823c46da84c3792928c9d36110"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
@ -1746,6 +1782,8 @@ dependencies = [
"checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035"
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum rand 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "12397506224b2f93e6664ffc4f664b29be8208e5157d3d90b44f09b5fae470ea"
"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2"
"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75ecf88252dce580404a22444fc7d626c01815debba56a7f4f536772a5ff19d3"

View file

@ -61,6 +61,7 @@ history = []
no-color = ["colored/no-color"]
[dependencies]
chbs = "0.0.1"
chrono = "0.4"
clap = "2.31"
colored = "1.6"

View file

@ -5,6 +5,12 @@ use ffsend_api::action::password::{
};
use ffsend_api::file::remote_file::RemoteFile;
use ffsend_api::reqwest::Client;
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,
Table,
};
use cmd::matcher::{
main::MainMatcher,
@ -50,10 +56,13 @@ impl<'a> Password<'a> {
// Ensure the owner token is set
ensure_owner_token(file.owner_token_mut(), &matcher_main);
// Get the password to use and whether it was generated
let (password, password_generated) = matcher_password.password();
// Execute an password action
let result = ApiPassword::new(
&file,
&matcher_password.password(),
&password,
None,
).invoke(&client);
if let Err(PasswordError::Expired) = result {
@ -67,6 +76,17 @@ impl<'a> Password<'a> {
#[cfg(feature = "history")]
history_tool::add(&matcher_main, file, true);
// Print the passphrase if one was generated
if password_generated {
let mut table = Table::new();
table.set_format(FormatBuilder::new().padding(0, 2).build());
table.add_row(Row::new(vec![
Cell::new("Passphrase:"),
Cell::new(&password),
]));
table.printstd();
}
// Print a success message
print_success("Password set");

View file

@ -208,12 +208,18 @@ impl<'a> Upload<'a> {
// Build the progress reporter
let progress_reporter: Arc<Mutex<ProgressReporter>> = progress_bar;
// Get the password to use and whether it was generated
let password = matcher_upload.password();
let (password, password_generated) = password
.map(|(p, g)| (Some(p), g))
.unwrap_or((None, false));
// Execute an upload action
let file = ApiUpload::new(
host,
path.clone(),
file_name,
matcher_upload.password(),
password.clone(),
params,
).invoke(&client, &progress_reporter)?;
@ -225,6 +231,12 @@ impl<'a> Upload<'a> {
Cell::new("Share link:"),
Cell::new(url.as_str()),
]));
if password_generated {
table.add_row(Row::new(vec![
Cell::new("Passphrase:"),
Cell::new(&password.unwrap_or("?".into())),
]));
}
if matcher_main.verbose() {
table.add_row(Row::new(vec![
Cell::new("Owner token:"),

View file

@ -0,0 +1,36 @@
use clap::Arg;
use chbs;
use super::{CmdArg, CmdArgFlag};
/// The number of words the passphrase must consist of.
const PASSPHRASE_WORDS: usize = 4;
/// The passphrase generation argument.
pub struct ArgGenPassphrase { }
impl ArgGenPassphrase {
/// Generate a cryptographically secure passphrase that is easily
/// rememberable using diceware.
pub fn gen_passphrase() -> String {
chbs::passphrase(PASSPHRASE_WORDS)
}
}
impl CmdArg for ArgGenPassphrase {
fn name() -> &'static str {
"gen-passphrase"
}
fn build<'b, 'c>() -> Arg<'b, 'c> {
Arg::with_name("gen-passphrase")
.long("gen-passphrase")
.alias("gen-password")
.alias("generate-passphrase")
.alias("generate-password")
.short("P")
.help("Protect the file with a generated passphrase")
}
}
impl CmdArgFlag for ArgGenPassphrase { }

View file

@ -1,4 +1,5 @@
pub mod download_limit;
pub mod gen_passphrase;
pub mod host;
pub mod owner;
pub mod password;
@ -6,6 +7,7 @@ pub mod url;
// Reexport to arg module
pub use self::download_limit::ArgDownloadLimit;
pub use self::gen_passphrase::ArgGenPassphrase;
pub use self::host::ArgHost;
pub use self::owner::ArgOwner;
pub use self::password::ArgPassword;

View file

@ -2,7 +2,14 @@ use clap::ArgMatches;
use ffsend_api::url::Url;
use rpassword::prompt_password_stderr;
use cmd::arg::{ArgOwner, ArgPassword, ArgUrl, CmdArgOption};
use cmd::arg::{
ArgGenPassphrase,
ArgOwner,
ArgPassword,
ArgUrl,
CmdArgFlag,
CmdArgOption,
};
use cmd::matcher::{MainMatcher, Matcher};
use util::check_empty_password;
@ -29,7 +36,15 @@ impl<'a: 'b, 'b> PasswordMatcher<'a> {
}
/// Get the password.
pub fn password(&'a self) -> String {
///
/// The password is returned in the following format:
/// `(password, generated)`
pub fn password(&'a self) -> (String, bool) {
// Generate a passphrase if requested
if ArgGenPassphrase::is_present(self.matches) {
return (ArgGenPassphrase::gen_passphrase(), true);
}
// Get the password, or prompt for it
let password = match ArgPassword::value(self.matches) {
Some(password) => password,
@ -48,7 +63,7 @@ impl<'a: 'b, 'b> PasswordMatcher<'a> {
// Check for empty passwords
check_empty_password(&password, &matcher_main);
password
(password, false)
}
}

View file

@ -4,7 +4,14 @@ use ffsend_api::action::params::{
};
use ffsend_api::url::Url;
use cmd::arg::{ArgDownloadLimit, ArgHost, ArgPassword, CmdArgOption};
use cmd::arg::{
ArgDownloadLimit,
ArgGenPassphrase,
ArgHost,
ArgPassword,
CmdArgFlag,
CmdArgOption,
};
use super::Matcher;
use util::{env_var_present, ErrorHintsBuilder, quit_error_msg};
@ -55,9 +62,24 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
}
/// Get the password.
/// `None` is returned if no password was specified.
pub fn password(&'a self) -> Option<String> {
/// A generated passphrase will be returned if the user requested so,
/// otherwise the specified password is returned.
/// If no password was set, `None` is returned instead.
///
/// The password is returned in the following format:
/// `(password, generated)`
pub fn password(&'a self) -> Option<(String, bool)> {
// Generate a passphrase if requested
if ArgGenPassphrase::is_present(self.matches) {
return Some((
ArgGenPassphrase::gen_passphrase(),
true,
));
}
// Use a specified password or use nothing
ArgPassword::value(self.matches)
.map(|password| (password, false))
}
/// Get the download limit.

View file

@ -1,6 +1,6 @@
use clap::{App, SubCommand};
use cmd::arg::{ArgOwner, ArgPassword, ArgUrl, CmdArg};
use cmd::arg::{ArgGenPassphrase, ArgOwner, ArgPassword, ArgUrl, CmdArg};
/// The password command definition.
pub struct CmdPassword;
@ -14,6 +14,7 @@ impl CmdPassword {
.arg(ArgUrl::build())
.arg(ArgPassword::build()
.help("Specify a password, do not prompt"))
.arg(ArgGenPassphrase::build())
.arg(ArgOwner::build())
}
}

View file

@ -3,9 +3,15 @@ use ffsend_api::action::params::{
PARAMS_DEFAULT_DOWNLOAD_STR as DOWNLOAD_DEFAULT,
};
use cmd::arg::{ArgDownloadLimit, ArgHost, ArgPassword, CmdArg};
use cmd::arg::{
ArgDownloadLimit,
ArgGenPassphrase,
ArgHost,
ArgPassword,
CmdArg,
};
/// The uplaod command definition.
/// The upload command definition.
pub struct CmdUpload;
impl CmdUpload {
@ -22,6 +28,7 @@ impl CmdUpload {
.multiple(false))
.arg(ArgPassword::build()
.help("Protect the file with a password"))
.arg(ArgGenPassphrase::build())
.arg(ArgDownloadLimit::build()
.default_value(DOWNLOAD_DEFAULT))
.arg(ArgHost::build())

View file

@ -1,3 +1,4 @@
extern crate chbs;
extern crate chrono;
#[macro_use]
extern crate clap;