Admin token Argon2 hashing support

Added support for Argon2 hashing support for the `ADMIN_TOKEN` instead
of only supporting a plain text string.

The hash must be a PHC string which can be generated via the `argon2`
CLI **or** via the also built-in hash command in Vaultwarden.

You can simply run `vaultwarden hash` to generate a hash based upon a
password the user provides them self.

Added a warning during startup and within the admin settings panel is
the `ADMIN_TOKEN` is not an Argon2 hash.

Within the admin environment a user can ignore that warning and it will
not be shown for at least 30 days. After that the warning will appear
again unless the `ADMIN_TOKEN` has be converted to an Argon2 hash.

I have also tested this on my RaspberryPi 2b and there the `Bitwarden`
preset takes almost 4.5 seconds to generate/verify the Argon2 hash.

Using the `OWASP` preset it is below 1 second, which I think should be
fine for low-graded hardware. If it is needed people could use lower
memory settings, but in those cases I even doubt Vaultwarden it self
would run. They can always use the `argon2` CLI and generate a faster hash.
This commit is contained in:
BlackDex
2023-02-28 23:09:51 +01:00
parent 337cbfaf22
commit de157b2654
8 changed files with 240 additions and 20 deletions

View File

@ -118,14 +118,22 @@ async fn main() -> Result<(), Error> {
}
const HELP: &str = "\
Alternative implementation of the Bitwarden server API written in Rust
Alternative implementation of the Bitwarden server API written in Rust
USAGE:
vaultwarden
USAGE:
vaultwarden [FLAGS|COMMAND]
FLAGS:
-h, --help Prints help information
-v, --version Prints the app version
COMMAND:
hash [--preset {bitwarden|owasp}] Generate an Argon2id PHC ADMIN_TOKEN
PRESETS: m= t= p=
bitwarden (default) 64MiB, 3 Iterations, 4 Threads
owasp 19MiB, 2 Iterations, 1 Thread
FLAGS:
-h, --help Prints help information
-v, --version Prints the app version
";
pub const VERSION: Option<&str> = option_env!("VW_VERSION");
@ -142,24 +150,88 @@ fn parse_args() {
println!("vaultwarden {version}");
exit(0);
}
}
if let Some(command) = pargs.subcommand().unwrap_or_default() {
if command == "hash" {
use argon2::{
password_hash::SaltString, Algorithm::Argon2id, Argon2, ParamsBuilder, PasswordHasher, Version::V0x13,
};
let mut argon2_params = ParamsBuilder::new();
let preset: Option<String> = pargs.opt_value_from_str(["-p", "--preset"]).unwrap_or_default();
let selected_preset;
match preset.as_deref() {
Some("owasp") => {
selected_preset = "owasp";
argon2_params.m_cost(19456);
argon2_params.t_cost(2);
argon2_params.p_cost(1);
}
_ => {
// Bitwarden preset is the default
selected_preset = "bitwarden";
argon2_params.m_cost(65540);
argon2_params.t_cost(3);
argon2_params.p_cost(4);
}
}
println!("Generate an Argon2id PHC string using the '{selected_preset}' preset:\n");
let password = rpassword::prompt_password("Password: ").unwrap();
if password.len() < 8 {
println!("\nPassword must contain at least 8 characters");
exit(1);
}
let password_verify = rpassword::prompt_password("Confirm Password: ").unwrap();
if password != password_verify {
println!("\nPasswords do not match");
exit(1);
}
let argon2 = Argon2::new(Argon2id, V0x13, argon2_params.build().unwrap());
let salt = SaltString::b64_encode(&crate::crypto::get_random_bytes::<32>()).unwrap();
let argon2_timer = tokio::time::Instant::now();
if let Ok(password_hash) = argon2.hash_password(password.as_bytes(), &salt) {
println!(
"\n\
ADMIN_TOKEN='{password_hash}'\n\n\
Generation of the Argon2id PHC string took: {:?}",
argon2_timer.elapsed()
);
} else {
error!("Unable to generate Argon2id PHC hash.");
exit(1);
}
}
exit(0);
}
}
fn launch_info() {
println!("/--------------------------------------------------------------------\\");
println!("| Starting Vaultwarden |");
println!(
"\
/--------------------------------------------------------------------\\\n\
| Starting Vaultwarden |"
);
if let Some(version) = VERSION {
println!("|{:^68}|", format!("Version {version}"));
}
println!("|--------------------------------------------------------------------|");
println!("| This is an *unofficial* Bitwarden implementation, DO NOT use the |");
println!("| official channels to report bugs/features, regardless of client. |");
println!("| Send usage/configuration questions or feature requests to: |");
println!("| https://vaultwarden.discourse.group/ |");
println!("| Report suspected bugs/issues in the software itself at: |");
println!("| https://github.com/dani-garcia/vaultwarden/issues/new |");
println!("\\--------------------------------------------------------------------/\n");
println!(
"\
|--------------------------------------------------------------------|\n\
| This is an *unofficial* Bitwarden implementation, DO NOT use the |\n\
| official channels to report bugs/features, regardless of client. |\n\
| Send usage/configuration questions or feature requests to: |\n\
| https://github.com/dani-garcia/vaultwarden/discussions or |\n\
| https://vaultwarden.discourse.group/ |\n\
| Report suspected bugs/issues in the software itself at: |\n\
| https://github.com/dani-garcia/vaultwarden/issues/new |\n\
\\--------------------------------------------------------------------/\n"
);
}
fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {