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 3dae594e30d1de8fa270f4d58142d6c05d88e455
parent 1036fcb53c4877f6bd1c4fc608064dc66f87a447
Author: Zack Newman <zack@philomathiclife.com>
Date:   Mon, 11 Dec 2023 12:22:13 -0700

use ed25519 for jwt. fix openbsd compile error

Diffstat:
MCargo.toml | 2+-
Msrc/auth.rs | 70+++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/config.rs | 2+-
Msrc/error.rs | 2++
4 files changed, 41 insertions(+), 35 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -38,7 +38,7 @@ rocket_ws = { version = "0.1.0", default-features = false, features = ["tokio-tu semver = { version = "1.0.20", default-features = false } serde = { version = "1.0.193", default-features = false } serde_json = { version = "1.0.108", default-features = false } -tokio = { version = "1.34.0", default-features = false } +tokio = { version = "1.35.0", default-features = false } tokio-tungstenite = { version = "0.20.1", default-features = false } toml = { version = "0.8.8", default-features = false, features = ["parse"] } totp-lite = { version = "2.0.1", default-features = false } diff --git a/src/auth.rs b/src/auth.rs @@ -5,14 +5,13 @@ use crate::{ use chrono::{Duration, Utc}; use jsonwebtoken::{self, errors::ErrorKind, Algorithm, DecodingKey, EncodingKey, Header}; use num_traits::FromPrimitive; -use openssl::rsa::Rsa; +use openssl::pkey::{Id, PKey}; use serde::de::DeserializeOwned; use serde::ser::Serialize; use std::fs::File; use std::io::{Read, Write}; use std::sync::OnceLock; -const JWT_ALGORITHM: Algorithm = Algorithm::RS256; static DEFAULT_VALIDITY: OnceLock<Duration> = OnceLock::new(); #[inline] fn init_default_validity() { @@ -132,47 +131,52 @@ fn get_jwt_file_download_issuer() -> &'static str { .expect("JWT_FILE_DOWNLOAD_ISSUER must be initialized in main") .as_str() } -static RSA_KEYS: OnceLock<(EncodingKey, DecodingKey)> = OnceLock::new(); -#[allow(clippy::verbose_file_reads)] +const JWT_ALGORITHM: Algorithm = Algorithm::EdDSA; +static ED_KEYS: OnceLock<(EncodingKey, DecodingKey)> = OnceLock::new(); +#[allow(clippy::map_err_ignore, clippy::verbose_file_reads)] #[inline] -fn init_rsa_keys() -> Result<(), Error> { - let mut priv_file = File::options() +fn init_ed_keys() -> Result<(), Error> { + let mut file = File::options() .create(true) .read(true) .write(true) - .open(Config::PRIVATE_RSA_KEY)?; - let mut priv_pem = Vec::with_capacity(2048); - let priv_key = if priv_file.read_to_end(&mut priv_pem)? == 0 { - let rsa_key = openssl::rsa::Rsa::generate(2048)?; - priv_pem = rsa_key.private_key_to_pem()?; - priv_file.write_all(priv_pem.as_slice())?; - rsa_key + .open(Config::PRIVATE_ED25519_KEY)?; + let mut priv_pem = Vec::with_capacity(192); + let ed_key = if file.read_to_end(&mut priv_pem)? == 0 { + let ed_key = PKey::generate_ed25519()?; + priv_pem = ed_key.private_key_to_pem_pkcs8()?; + file.write_all(priv_pem.as_slice())?; + ed_key } else { - Rsa::private_key_from_pem(priv_pem.as_slice())? + let ed_key = PKey::private_key_from_pem(priv_pem.as_slice())?; + if ed_key.id() == Id::ED25519 { + ed_key + } else { + return Err(Error::from(format!( + "{} is not a private Ed25519 key", + Config::PRIVATE_ED25519_KEY + ))); + } }; - assert!( - RSA_KEYS - .set(( - EncodingKey::from_rsa_pem(priv_pem.as_slice())?, - DecodingKey::from_rsa_pem(priv_key.public_key_to_pem()?.as_slice())? - )) - .is_ok(), - "RSA_KEYS must only be initialized once" - ); - Ok(()) + ED_KEYS + .set(( + EncodingKey::from_ed_pem(priv_pem.as_slice())?, + DecodingKey::from_ed_pem(ed_key.public_key_to_pem()?.as_slice())?, + )) + .map_err(|_| Error::from(String::from("ED_KEYS must only be initialized once"))) } #[inline] -fn get_private_rsa_key() -> &'static EncodingKey { - &RSA_KEYS +fn get_private_ed_key() -> &'static EncodingKey { + &ED_KEYS .get() - .expect("RSA_KEYS must be initialized in main") + .expect("ED_KEYS must be initialized in main") .0 } #[inline] -fn get_public_rsa_key() -> &'static DecodingKey { - &RSA_KEYS +fn get_public_ed_key() -> &'static DecodingKey { + &ED_KEYS .get() - .expect("RSA_KEYS must be initialized in main") + .expect("ED_KEYS must be initialized in main") .1 } #[inline] @@ -185,10 +189,10 @@ pub fn init_values() { init_jwt_verifyemail_issuer(); init_jwt_org_api_key_issuer(); init_jwt_file_download_issuer(); - init_rsa_keys().expect("error creating or reading RSA keys"); + init_ed_keys().expect("error creating Ed25519 keys"); } pub fn encode_jwt<T: Serialize>(claims: &T) -> String { - match jsonwebtoken::encode(get_jwt_header(), claims, get_private_rsa_key()) { + match jsonwebtoken::encode(get_jwt_header(), claims, get_private_ed_key()) { Ok(token) => token, Err(e) => panic!("Error encoding jwt {e}"), } @@ -202,7 +206,7 @@ fn decode_jwt<T: DeserializeOwned>(token: &str, issuer: String) -> Result<T, Err validation.validate_nbf = true; validation.set_issuer(&[issuer]); let token = token.replace(char::is_whitespace, ""); - match jsonwebtoken::decode(&token, get_public_rsa_key(), &validation) { + match jsonwebtoken::decode(&token, get_public_ed_key(), &validation) { Ok(d) => Ok(d.claims), Err(err) => match *err.kind() { ErrorKind::InvalidToken => err!("Token is invalid"), diff --git a/src/config.rs b/src/config.rs @@ -173,7 +173,7 @@ impl Config { pub const DATA_FOLDER: &'static str = "data"; pub const DATABASE_URL: &'static str = "data/db.sqlite3"; pub const ICON_CACHE_FOLDER: &'static str = "data/icon_cache"; - pub const PRIVATE_RSA_KEY: &'static str = "data/rsa_key.pem"; + pub const PRIVATE_ED25519_KEY: &'static str = "data/ed25519_key.pem"; pub const SENDS_FOLDER: &'static str = "data/sends"; pub const TMP_FOLDER: &'static str = "data/tmp"; pub const WEB_VAULT_FOLDER: &'static str = "web-vault/"; diff --git a/src/error.rs b/src/error.rs @@ -130,6 +130,8 @@ impl std::fmt::Debug for Error { write!(f, "{}. {}", self.message, s) } } + #[cfg(all(feature = "priv_sep", target_os = "openbsd"))] + ErrorKind::Unveil(ref err) => err.fmt(f), ErrorKind::Json(_) => write!(f, "{}", self.message), ErrorKind::Db(_) | ErrorKind::R2d2(_)