extern crate lesspass;
extern crate ring;
extern crate rpassword;
extern crate structopt;
use lesspass::*;
use ring::digest;
use structopt::StructOpt;
use std::io::Write;
#[derive(StructOpt)]
#[structopt(raw(after_help = r#""EXAMPLES:
Generate a password:
lesspass example.org contact@example.org password
Generate the fingerprint of a master password:
lesspass password -F
Generate a 32-characters password using SHA-512:
echo password | lesspass example.org contact@example.org --sha512 -l 32
Generate the entropy of a password, using 10,000 iterations:
lesspass example.org contact@example.org password -i 10000 -E > entropy.txt
Generate an alphanumeric password using the previously saved entropy:
cat entropy.txt | lesspass -S
The two previous examples are obviously equivalent to:
lesspass example.org contact@example.org password -i 10000 -S
""#))]
pub struct Args {
#[structopt(name = "website")]
website: Option<String>,
#[structopt(name = "login")]
login: Option<String>,
#[structopt(name = "password")]
master_password: Option<String>,
#[structopt(short = "i", long = "iterations", default_value = "100000")]
iterations: u32,
#[structopt(short = "l", long = "length", default_value = "16")]
length: u8,
#[structopt(short = "c", long = "counter", default_value = "1")]
counter: u8,
#[structopt(long = "sha256")]
sha256: bool,
#[structopt(long = "sha384")]
sha384: bool,
#[structopt(long = "sha512")]
sha512: bool,
#[structopt(short = "L", long = "no-lower")]
exclude_lower: bool,
#[structopt(short = "U", long = "no-upper")]
exclude_upper: bool,
#[structopt(short = "N", long = "no-numbers")]
exclude_numbers: bool,
#[structopt(short = "S", long = "no-symbols")]
exclude_symbols: bool,
#[structopt(short = "E", long = "return-entropy")]
return_entropy: bool,
#[structopt(short = "F", long = "print-fingerprint")]
print_fingerprint: bool
}
fn main() {
if let Err(err) = run() {
let mut out = std::io::stderr();
let res = if !err.is_empty() {
out.write_all(err.as_bytes()).map_err(|_| ())
} else {
Args::clap().write_long_help(&mut out).map_err(|_| ())
};
std::process::exit(if res.is_ok() { 1 } else { 2 })
}
}
fn run() -> Result<(), &'static str> {
let Args {
website, login, master_password,
iterations, length, counter,
sha256, sha384, sha512,
exclude_lower, exclude_upper, exclude_numbers, exclude_symbols,
return_entropy, print_fingerprint
} = Args::from_args();
let mut out = std::io::stdout();
let algorithm = match (sha256, sha384, sha512) {
(false, false, false) |
(true , false, false) => &digest::SHA256,
(false, true , false) => &digest::SHA384,
(false, false, true ) => &digest::SHA512,
_ => return Err("Only one algorithm must be provided.")
};
let mut charset = CharacterSet::All;
if exclude_lower { charset.remove(CharacterSet::Lowercase); }
if exclude_upper { charset.remove(CharacterSet::Uppercase); }
if exclude_numbers { charset.remove(CharacterSet::Numbers); }
if exclude_symbols { charset.remove(CharacterSet::Symbols); }
if charset.is_empty() {
return Err("Not all characters can be excluded from the generation algorithm.")
}
if length < 6 || length > 64 {
return Err("The length must be an integer in the [6; 64] range.")
}
if counter > 99 {
return Err("The counter must be an integer in the [0; 99] range.")
}
if iterations < 1 || iterations > 100_000_000 {
return Err("The iterations must be an integer in the [1; 100,000,000] range.")
}
let entropy = match (website, login, master_password) {
(pass, None, None) => {
if print_fingerprint {
let master_password = match pass {
Some(pass) => pass,
None => read_password()? };
print_buffer_hex(get_fingerprint(&master_password).as_ref(), &mut out)?;
return Ok(())
}
let entropy = match pass {
Some(pass) => pass,
None => {
if atty::is(atty::Stream::Stdin) {
return Err("")
}
read_password()?
}
};
match parse_entropy(&entropy) {
Some(entropy) => {
entropy
},
None => {
return Err("Invalid entropy format.")
}
}
},
(Some(website), Some(login), pass) => {
let master_password = match pass {
Some(pass) => pass,
None => read_password()? };
let salt = generate_salt(&website, &login, counter);
if print_fingerprint {
print_buffer_hex(get_fingerprint(&master_password).as_ref(), &mut out)?;
}
generate_entropy(&master_password, &salt, algorithm, iterations)
},
_ => {
return Err("")
}
};
if return_entropy {
print_buffer_hex(&entropy, &mut out)?;
} else {
let password = render_password(&entropy, charset, length);
println!("{}", password);
}
Ok(())
}
fn print_buffer_hex(buf: &[u8], out: &mut Write) -> Result<(), &'static str> {
for byte in buf {
write!(out, "{:02x}", byte).map_err(|_| "Unable to write to standard output.")?;
}
out.write(b"\n").map_err(|_| "Unable to write to standard output.")?;
Ok(())
}
fn read_password() -> Result<String, &'static str> {
if atty::is(atty::Stream::Stdin) {
rpassword::read_password().map_err(|_| "Unable to read password or entropy.")
} else {
let stdin = std::io::stdin();
let mut input = String::new();
if stdin.read_line(&mut input).is_err() {
return Err("Unable to read password or entropy from standard input.")
}
if input.ends_with('\n') {
let new_len = input.len() - (if input.ends_with("\r\n") { 2 } else { 1 });
input.truncate(new_len);
}
Ok(input)
}
}
fn parse_entropy(entropy: &str) -> Option<Vec<u8>> {
if entropy.len() != 64 {
return None
}
let mut vec = Vec::with_capacity(32);
for i in 0..32 {
vec.push(
u8::from_str_radix(&entropy[i*2 .. i*2+2], 16).ok()?
);
}
Some(vec)
}