mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-31 18:28:20 +02:00 
			
		
		
		
	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:
		| @@ -201,6 +201,19 @@ fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp | ||||
| fn _validate_token(token: &str) -> bool { | ||||
|     match CONFIG.admin_token().as_ref() { | ||||
|         None => false, | ||||
|         Some(t) if t.starts_with("$argon2") => { | ||||
|             use argon2::password_hash::PasswordVerifier; | ||||
|             match argon2::password_hash::PasswordHash::new(t) { | ||||
|                 Ok(h) => { | ||||
|                     // NOTE: hash params from `ADMIN_TOKEN` are used instead of what is configured in the `Argon2` instance. | ||||
|                     argon2::Argon2::default().verify_password(token.trim().as_ref(), &h).is_ok() | ||||
|                 } | ||||
|                 Err(e) => { | ||||
|                     error!("The configured Argon2 PHC in `ADMIN_TOKEN` is invalid: {e}"); | ||||
|                     false | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         Some(t) => crate::crypto::ct_eq(t.trim(), token.trim()), | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -19,7 +19,7 @@ static CONFIG_FILE: Lazy<String> = Lazy::new(|| { | ||||
|  | ||||
| pub static CONFIG: Lazy<Config> = Lazy::new(|| { | ||||
|     Config::load().unwrap_or_else(|e| { | ||||
|         println!("Error loading config:\n\t{e:?}\n"); | ||||
|         println!("Error loading config:\n  {e:?}\n"); | ||||
|         exit(12) | ||||
|     }) | ||||
| }); | ||||
| @@ -872,6 +872,23 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> { | ||||
|         err!("`EVENT_CLEANUP_SCHEDULE` is not a valid cron expression") | ||||
|     } | ||||
|  | ||||
|     if !cfg.disable_admin_token { | ||||
|         match cfg.admin_token.as_ref() { | ||||
|             Some(t) if t.starts_with("$argon2") => { | ||||
|                 if let Err(e) = argon2::password_hash::PasswordHash::new(t) { | ||||
|                     err!(format!("The configured Argon2 PHC in `ADMIN_TOKEN` is invalid: '{e}'")) | ||||
|                 } | ||||
|             } | ||||
|             Some(_) => { | ||||
|                 println!( | ||||
|                     "[NOTICE] You are using a plain text `ADMIN_TOKEN` which is insecure.\n\ | ||||
|                 Please generate a secure Argon2 PHC string by using `vaultwarden hash` or `argon2`.\n\ | ||||
|                 See: https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token\n" | ||||
|                 ); | ||||
|             } | ||||
|             _ => {} | ||||
|         } | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										106
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -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> { | ||||
|   | ||||
							
								
								
									
										37
									
								
								src/static/scripts/admin_settings.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										37
									
								
								src/static/scripts/admin_settings.js
									
									
									
									
										vendored
									
									
								
							| @@ -157,6 +157,41 @@ function masterCheck(check_id, inputs_query) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| // This will check if the ADMIN_TOKEN is not a Argon2 hashed value. | ||||
| // Else it will show a warning, unless someone has closed it. | ||||
| // Then it will not show this warning for 30 days. | ||||
| function checkAdminToken() { | ||||
|     const admin_token = document.getElementById("input_admin_token"); | ||||
|     const disable_admin_token = document.getElementById("input_disable_admin_token"); | ||||
|     if (!disable_admin_token.checked && !admin_token.value.startsWith("$argon2")) { | ||||
|         // Check if the warning has been closed before and 30 days have passed | ||||
|         const admin_token_warning_closed = localStorage.getItem("admin_token_warning_closed"); | ||||
|         if (admin_token_warning_closed !== null) { | ||||
|             const closed_date = new Date(parseInt(admin_token_warning_closed)); | ||||
|             const current_date = new Date(); | ||||
|             const thirtyDays = 1000*60*60*24*30; | ||||
|             if (current_date - closed_date < thirtyDays) { | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // When closing the alert, store the current date/time in the browser | ||||
|         const admin_token_warning = document.getElementById("admin_token_warning"); | ||||
|         admin_token_warning.addEventListener("closed.bs.alert", function() { | ||||
|             const d = new Date(); | ||||
|             localStorage.setItem("admin_token_warning_closed", d.getTime()); | ||||
|         }); | ||||
|  | ||||
|         // Display the warning | ||||
|         admin_token_warning.classList.remove("d-none"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // This will check for specific configured values, and when needed will show a warning div | ||||
| function showWarnings() { | ||||
|     checkAdminToken(); | ||||
| } | ||||
|  | ||||
| const config_form = document.getElementById("config-form"); | ||||
|  | ||||
| // onLoad events | ||||
| @@ -192,4 +227,6 @@ document.addEventListener("DOMContentLoaded", (/*event*/) => { | ||||
|     } | ||||
|  | ||||
|     config_form.addEventListener("submit", saveConfig); | ||||
|  | ||||
|     showWarnings(); | ||||
| }); | ||||
| @@ -1,4 +1,10 @@ | ||||
| <main class="container-xl"> | ||||
|     <div id="admin_token_warning" class="alert alert-warning alert-dismissible fade show d-none"> | ||||
|         <button type="button" class="btn-close" data-bs-target="admin_token_warning" data-bs-dismiss="alert" aria-label="Close"></button> | ||||
|         You are using a plain text `ADMIN_TOKEN` which is insecure.<br> | ||||
|         Please generate a secure Argon2 PHC string by using `vaultwarden hash` or `argon2`.<br> | ||||
|         See: <a href="https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page#secure-the-admin_token" target="_blank" rel="noopener noreferrer">Enabling admin page - Secure the `ADMIN_TOKEN`</a> | ||||
|     </div> | ||||
|     <div id="config-block" class="align-items-center p-3 mb-3 bg-secondary rounded shadow"> | ||||
|         <div> | ||||
|             <h6 class="text-white mb-3">Configuration</h6> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user