vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 46e0f3c43a81ce9411612c152e414162a9c220ac
parent 2cd17fe7afeaef2a29787999b1cb48a512811571
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Fri, 25 Jun 2021 20:49:44 +0200

Load RSA keys as pem format directly, and using openssl crate, backported from async branch

Diffstat:
Msrc/api/core/mod.rs | 5++---
Msrc/api/core/two_factor/duo.rs | 2+-
Msrc/auth.rs | 28+++++++++++++++++-----------
Msrc/config.rs | 5+----
Msrc/error.rs | 2++
Msrc/main.rs | 64++++++++++++++++++++++------------------------------------------
Msrc/util.rs | 8++++++++
7 files changed, 53 insertions(+), 61 deletions(-)

diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs @@ -27,7 +27,6 @@ pub fn routes() -> Vec<Route> { // // Move this somewhere else // -use rocket::response::Response; use rocket::Route; use rocket_contrib::json::Json; use serde_json::Value; @@ -41,7 +40,7 @@ use crate::{ }; #[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 let _ = uuid; @@ -50,7 +49,7 @@ fn clear_device_token<'a>(uuid: String) -> Response<'a> { // 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/Core/Services/Implementations/DeviceService.cs#L37 - Response::new() + "" } #[put("/devices/identifier/<uuid>/token", data = "<data>")] diff --git a/src/api/core/two_factor/duo.rs b/src/api/core/two_factor/duo.rs @@ -343,7 +343,7 @@ fn parse_duo_values(key: &str, val: &str, ikey: &str, prefix: &str, time: i64) - err!("Invalid ikey") } - let expire = match expire.parse() { + let expire: i64 = match expire.parse() { Ok(e) => e, Err(_) => err!("Invalid expire time"), }; diff --git a/src/auth.rs b/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_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()) { - Ok(key) => key, - Err(e) => panic!("Error loading private RSA Key.\n Error: {}", e), +static PRIVATE_RSA_KEY_VEC: Lazy<Vec<u8>> = Lazy::new(|| { + read_file(&CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key.\n{}", e)) }); -static PUBLIC_RSA_KEY: Lazy<Vec<u8>> = Lazy::new(|| match read_file(&CONFIG.public_rsa_key()) { - Ok(key) => key, - Err(e) => panic!("Error loading public RSA Key.\n Error: {}", e), +static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| { + EncodingKey::from_rsa_pem(&PRIVATE_RSA_KEY_VEC).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{}", 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 { - 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, 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, ""); - - jsonwebtoken::decode(&token, &DecodingKey::from_rsa_der(&PUBLIC_RSA_KEY), &validation) - .map(|d| d.claims) - .map_res("Error decoding JWT") + jsonwebtoken::decode(&token, &&PUBLIC_RSA_KEY, &validation).map(|d| d.claims).map_res("Error decoding JWT") } pub fn decode_login(token: &str) -> Result<LoginJwtClaims, Error> { diff --git a/src/config.rs b/src/config.rs @@ -770,13 +770,10 @@ impl Config { } 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()) } 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 { let inner = &self.inner.read().unwrap().config; diff --git a/src/error.rs b/src/error.rs @@ -50,6 +50,7 @@ use std::time::SystemTimeError as TimeErr; use u2f::u2ferror::U2fError as U2fErr; use webauthn_rs::error::WebauthnError as WebauthnErr; use yubico::yubicoerror::YubicoError as YubiErr; +use openssl::error::ErrorStack as SSLErr; #[derive(Serialize)] pub struct Empty {} @@ -82,6 +83,7 @@ make_error! { Lettre(LettreErr): _has_source, _api_error, Address(AddrErr): _has_source, _api_error, Smtp(SmtpErr): _has_source, _api_error, + OpenSSL(SSLErr): _has_source, _api_error, DieselCon(DieselConErr): _has_source, _api_error, DieselMig(DieselMigErr): _has_source, _api_error, diff --git a/src/main.rs b/src/main.rs @@ -21,7 +21,7 @@ use std::{ fs::create_dir_all, panic, path::Path, - process::{exit, Command}, + process::exit, str::FromStr, thread, time::Duration, @@ -53,7 +53,10 @@ fn main() { let extra_debug = matches!(level, LF::Trace | LF::Debug); check_data_folder(); - check_rsa_keys(); + check_rsa_keys().unwrap_or_else(|_| { + error!("Error creating keys, exiting..."); + exit(1); + }); check_web_vault(); 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 !util::file_exists(&CONFIG.private_rsa_key()) || !util::file_exists(&CONFIG.public_rsa_key()) { - info!("JWT keys don't exist, checking if OpenSSL is available..."); - - Command::new("openssl").arg("version").status().unwrap_or_else(|_| { - info!( - "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 key = CONFIG.rsa_key_filename(); - - let pem = format!("{}.pem", key); - let priv_der = format!("{}.der", key); - let pub_der = format!("{}.pub.der", key); + let priv_path = CONFIG.private_rsa_key(); + let pub_path = CONFIG.public_rsa_key(); - let mut success = Command::new("openssl") - .args(&["genrsa", "-out", &pem]) - .status() - .expect("Failed to create private pem file") - .success(); + if !util::file_exists(&priv_path) { + let rsa_key = openssl::rsa::Rsa::generate(2048)?; - success &= Command::new("openssl") - .args(&["rsa", "-in", &pem, "-outform", "DER", "-out", &priv_der]) - .status() - .expect("Failed to create private der file") - .success(); + let priv_key = rsa_key.private_key_to_pem()?; + crate::util::write_file(&priv_path, &priv_key)?; + info!("Private key created correctly."); + } - 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 !util::file_exists(&pub_path) { + let rsa_key = openssl::rsa::Rsa::private_key_from_pem(&util::read_file(&priv_path)?)?; - if success { - info!("Keys created correctly."); - } else { - error!("Error creating keys, exiting..."); - exit(1); - } + 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() { diff --git a/src/util.rs b/src/util.rs @@ -219,6 +219,14 @@ pub fn read_file(path: &str) -> IOResult<Vec<u8>> { 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> { let mut contents = String::new();