mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-28 00:40:01 +02:00 
			
		
		
		
	Load RSA keys as pem format directly, and using openssl crate, backported from async branch
This commit is contained in:
		| @@ -27,7 +27,6 @@ pub fn routes() -> Vec<Route> { | |||||||
| // | // | ||||||
| // Move this somewhere else | // Move this somewhere else | ||||||
| // | // | ||||||
| use rocket::response::Response; |  | ||||||
| use rocket::Route; | use rocket::Route; | ||||||
| use rocket_contrib::json::Json; | use rocket_contrib::json::Json; | ||||||
| use serde_json::Value; | use serde_json::Value; | ||||||
| @@ -41,7 +40,7 @@ use crate::{ | |||||||
| }; | }; | ||||||
|  |  | ||||||
| #[put("/devices/identifier/<uuid>/clear-token")] | #[put("/devices/identifier/<uuid>/clear-token")] | ||||||
| fn clear_device_token<'a>(uuid: String) -> Response<'a> { | fn clear_device_token<'a>(uuid: String) -> &'static str { | ||||||
|     // This endpoint doesn't have auth header |     // This endpoint doesn't have auth header | ||||||
|  |  | ||||||
|     let _ = uuid; |     let _ = uuid; | ||||||
| @@ -50,7 +49,7 @@ fn clear_device_token<'a>(uuid: String) -> Response<'a> { | |||||||
|     // This only clears push token |     // This only clears push token | ||||||
|     // https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109 |     // https://github.com/bitwarden/core/blob/master/src/Api/Controllers/DevicesController.cs#L109 | ||||||
|     // https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37 |     // https://github.com/bitwarden/core/blob/master/src/Core/Services/Implementations/DeviceService.cs#L37 | ||||||
|     Response::new() |     "" | ||||||
| } | } | ||||||
|  |  | ||||||
| #[put("/devices/identifier/<uuid>/token", data = "<data>")] | #[put("/devices/identifier/<uuid>/token", data = "<data>")] | ||||||
|   | |||||||
| @@ -343,7 +343,7 @@ fn parse_duo_values(key: &str, val: &str, ikey: &str, prefix: &str, time: i64) - | |||||||
|         err!("Invalid ikey") |         err!("Invalid ikey") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let expire = match expire.parse() { |     let expire: i64 = match expire.parse() { | ||||||
|         Ok(e) => e, |         Ok(e) => e, | ||||||
|         Err(_) => err!("Invalid expire time"), |         Err(_) => err!("Invalid expire time"), | ||||||
|     }; |     }; | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								src/auth.rs
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/auth.rs
									
									
									
									
									
								
							| @@ -27,17 +27,26 @@ static JWT_VERIFYEMAIL_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|verifyema | |||||||
| static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin())); | static JWT_ADMIN_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|admin", CONFIG.domain_origin())); | ||||||
| static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin())); | static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.domain_origin())); | ||||||
|  |  | ||||||
| static PRIVATE_RSA_KEY: Lazy<Vec<u8>> = Lazy::new(|| match read_file(&CONFIG.private_rsa_key()) { | static PRIVATE_RSA_KEY_VEC: Lazy<Vec<u8>> = Lazy::new(|| { | ||||||
|     Ok(key) => key, |     read_file(&CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key.\n{}", e)) | ||||||
|     Err(e) => panic!("Error loading private RSA Key.\n Error: {}", e), |  | ||||||
| }); | }); | ||||||
| static PUBLIC_RSA_KEY: Lazy<Vec<u8>> = Lazy::new(|| match read_file(&CONFIG.public_rsa_key()) { | static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| { | ||||||
|     Ok(key) => key, |     EncodingKey::from_rsa_pem(&PRIVATE_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{}", e)) | ||||||
|     Err(e) => panic!("Error loading public RSA Key.\n Error: {}", e), | }); | ||||||
|  | static PUBLIC_RSA_KEY_VEC: Lazy<Vec<u8>> = Lazy::new(|| { | ||||||
|  |     read_file(&CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key.\n{}", e)) | ||||||
|  | }); | ||||||
|  | static PUBLIC_RSA_KEY: Lazy<DecodingKey> = Lazy::new(|| { | ||||||
|  |     DecodingKey::from_rsa_pem(&PUBLIC_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding public RSA Key.\n{}", e)) | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | pub fn load_keys() { | ||||||
|  |     Lazy::force(&PRIVATE_RSA_KEY); | ||||||
|  |     Lazy::force(&PUBLIC_RSA_KEY); | ||||||
|  | } | ||||||
|  |  | ||||||
| pub fn encode_jwt<T: Serialize>(claims: &T) -> String { | pub fn encode_jwt<T: Serialize>(claims: &T) -> String { | ||||||
|     match jsonwebtoken::encode(&JWT_HEADER, claims, &EncodingKey::from_rsa_der(&PRIVATE_RSA_KEY)) { |     match jsonwebtoken::encode(&JWT_HEADER, claims, &PRIVATE_RSA_KEY) { | ||||||
|         Ok(token) => token, |         Ok(token) => token, | ||||||
|         Err(e) => panic!("Error encoding jwt {}", e), |         Err(e) => panic!("Error encoding jwt {}", e), | ||||||
|     } |     } | ||||||
| @@ -55,10 +64,7 @@ fn decode_jwt<T: DeserializeOwned>(token: &str, issuer: String) -> Result<T, Err | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let token = token.replace(char::is_whitespace, ""); |     let token = token.replace(char::is_whitespace, ""); | ||||||
|  |     jsonwebtoken::decode(&token, &&PUBLIC_RSA_KEY, &validation).map(|d| d.claims).map_res("Error decoding JWT") | ||||||
|     jsonwebtoken::decode(&token, &DecodingKey::from_rsa_der(&PUBLIC_RSA_KEY), &validation) |  | ||||||
|         .map(|d| d.claims) |  | ||||||
|         .map_res("Error decoding JWT") |  | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn decode_login(token: &str) -> Result<LoginJwtClaims, Error> { | pub fn decode_login(token: &str) -> Result<LoginJwtClaims, Error> { | ||||||
|   | |||||||
| @@ -770,13 +770,10 @@ impl Config { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn private_rsa_key(&self) -> String { |     pub fn private_rsa_key(&self) -> String { | ||||||
|         format!("{}.der", CONFIG.rsa_key_filename()) |  | ||||||
|     } |  | ||||||
|     pub fn private_rsa_key_pem(&self) -> String { |  | ||||||
|         format!("{}.pem", CONFIG.rsa_key_filename()) |         format!("{}.pem", CONFIG.rsa_key_filename()) | ||||||
|     } |     } | ||||||
|     pub fn public_rsa_key(&self) -> String { |     pub fn public_rsa_key(&self) -> String { | ||||||
|         format!("{}.pub.der", CONFIG.rsa_key_filename()) |         format!("{}.pub.pem", CONFIG.rsa_key_filename()) | ||||||
|     } |     } | ||||||
|     pub fn mail_enabled(&self) -> bool { |     pub fn mail_enabled(&self) -> bool { | ||||||
|         let inner = &self.inner.read().unwrap().config; |         let inner = &self.inner.read().unwrap().config; | ||||||
|   | |||||||
| @@ -50,6 +50,7 @@ use std::time::SystemTimeError as TimeErr; | |||||||
| use u2f::u2ferror::U2fError as U2fErr; | use u2f::u2ferror::U2fError as U2fErr; | ||||||
| use webauthn_rs::error::WebauthnError as WebauthnErr; | use webauthn_rs::error::WebauthnError as WebauthnErr; | ||||||
| use yubico::yubicoerror::YubicoError as YubiErr; | use yubico::yubicoerror::YubicoError as YubiErr; | ||||||
|  | use openssl::error::ErrorStack as SSLErr; | ||||||
|  |  | ||||||
| #[derive(Serialize)] | #[derive(Serialize)] | ||||||
| pub struct Empty {} | pub struct Empty {} | ||||||
| @@ -82,6 +83,7 @@ make_error! { | |||||||
|     Lettre(LettreErr): _has_source, _api_error, |     Lettre(LettreErr): _has_source, _api_error, | ||||||
|     Address(AddrErr):  _has_source, _api_error, |     Address(AddrErr):  _has_source, _api_error, | ||||||
|     Smtp(SmtpErr):     _has_source, _api_error, |     Smtp(SmtpErr):     _has_source, _api_error, | ||||||
|  |     OpenSSL(SSLErr):   _has_source, _api_error, | ||||||
|  |  | ||||||
|     DieselCon(DieselConErr): _has_source, _api_error, |     DieselCon(DieselConErr): _has_source, _api_error, | ||||||
|     DieselMig(DieselMigErr): _has_source, _api_error, |     DieselMig(DieselMigErr): _has_source, _api_error, | ||||||
|   | |||||||
							
								
								
									
										68
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -21,7 +21,7 @@ use std::{ | |||||||
|     fs::create_dir_all, |     fs::create_dir_all, | ||||||
|     panic, |     panic, | ||||||
|     path::Path, |     path::Path, | ||||||
|     process::{exit, Command}, |     process::exit, | ||||||
|     str::FromStr, |     str::FromStr, | ||||||
|     thread, |     thread, | ||||||
|     time::Duration, |     time::Duration, | ||||||
| @@ -53,7 +53,10 @@ fn main() { | |||||||
|     let extra_debug = matches!(level, LF::Trace | LF::Debug); |     let extra_debug = matches!(level, LF::Trace | LF::Debug); | ||||||
|  |  | ||||||
|     check_data_folder(); |     check_data_folder(); | ||||||
|     check_rsa_keys(); |     check_rsa_keys().unwrap_or_else(|_| { | ||||||
|  |         error!("Error creating keys, exiting..."); | ||||||
|  |         exit(1); | ||||||
|  |     }); | ||||||
|     check_web_vault(); |     check_web_vault(); | ||||||
|  |  | ||||||
|     create_icon_cache_folder(); |     create_icon_cache_folder(); | ||||||
| @@ -249,52 +252,29 @@ fn check_data_folder() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn check_rsa_keys() { | fn check_rsa_keys()-> Result<(), crate::error::Error> { | ||||||
|     // If the RSA keys don't exist, try to create them |     // If the RSA keys don't exist, try to create them | ||||||
|     if !util::file_exists(&CONFIG.private_rsa_key()) || !util::file_exists(&CONFIG.public_rsa_key()) { |     let priv_path = CONFIG.private_rsa_key(); | ||||||
|         info!("JWT keys don't exist, checking if OpenSSL is available..."); |     let pub_path = CONFIG.public_rsa_key(); | ||||||
|  |  | ||||||
|         Command::new("openssl").arg("version").status().unwrap_or_else(|_| { |     if !util::file_exists(&priv_path) { | ||||||
|             info!( |         let rsa_key = openssl::rsa::Rsa::generate(2048)?; | ||||||
|                 "Can't create keys because OpenSSL is not available, make sure it's installed and available on the PATH" |  | ||||||
|             ); |  | ||||||
|             exit(1); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         info!("OpenSSL detected, creating keys..."); |         let priv_key = rsa_key.private_key_to_pem()?; | ||||||
|  |         crate::util::write_file(&priv_path, &priv_key)?; | ||||||
|         let key = CONFIG.rsa_key_filename(); |         info!("Private key created correctly."); | ||||||
|  |  | ||||||
|         let pem = format!("{}.pem", key); |  | ||||||
|         let priv_der = format!("{}.der", key); |  | ||||||
|         let pub_der = format!("{}.pub.der", key); |  | ||||||
|  |  | ||||||
|         let mut success = Command::new("openssl") |  | ||||||
|             .args(&["genrsa", "-out", &pem]) |  | ||||||
|             .status() |  | ||||||
|             .expect("Failed to create private pem file") |  | ||||||
|             .success(); |  | ||||||
|  |  | ||||||
|         success &= Command::new("openssl") |  | ||||||
|             .args(&["rsa", "-in", &pem, "-outform", "DER", "-out", &priv_der]) |  | ||||||
|             .status() |  | ||||||
|             .expect("Failed to create private der file") |  | ||||||
|             .success(); |  | ||||||
|  |  | ||||||
|         success &= Command::new("openssl") |  | ||||||
|             .args(&["rsa", "-in", &priv_der, "-inform", "DER"]) |  | ||||||
|             .args(&["-RSAPublicKey_out", "-outform", "DER", "-out", &pub_der]) |  | ||||||
|             .status() |  | ||||||
|             .expect("Failed to create public der file") |  | ||||||
|             .success(); |  | ||||||
|  |  | ||||||
|         if success { |  | ||||||
|             info!("Keys created correctly."); |  | ||||||
|         } else { |  | ||||||
|             error!("Error creating keys, exiting..."); |  | ||||||
|             exit(1); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if !util::file_exists(&pub_path) { | ||||||
|  |         let rsa_key = openssl::rsa::Rsa::private_key_from_pem(&util::read_file(&priv_path)?)?; | ||||||
|  |  | ||||||
|  |         let pub_key = rsa_key.public_key_to_pem()?; | ||||||
|  |         crate::util::write_file(&pub_path, &pub_key)?; | ||||||
|  |         info!("Public key created correctly."); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     auth::load_keys(); | ||||||
|  |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
| fn check_web_vault() { | fn check_web_vault() { | ||||||
|   | |||||||
| @@ -219,6 +219,14 @@ pub fn read_file(path: &str) -> IOResult<Vec<u8>> { | |||||||
|     Ok(contents) |     Ok(contents) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn write_file(path: &str, content: &[u8]) -> Result<(), crate::error::Error> { | ||||||
|  |     use std::io::Write; | ||||||
|  |     let mut f = File::create(path)?; | ||||||
|  |     f.write_all(content)?; | ||||||
|  |     f.flush()?; | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
| pub fn read_file_string(path: &str) -> IOResult<String> { | pub fn read_file_string(path: &str) -> IOResult<String> { | ||||||
|     let mut contents = String::new(); |     let mut contents = String::new(); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user