diff --git a/Cargo.toml b/Cargo.toml index cd33c61..257f7d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ include = ["src/**", "Cargo.*", "CHANGELOG.md", "LICENSE*", "README.md"] [features] default = ["scgi_srv"] user_management = ["sled", "bincode", "serde/derive", "crc32fast", "lazy_static"] -user_management_advanced = ["rust-argon2", "ring", "user_management"] +user_management_advanced = ["rust-argon2", "user_management"] user_management_routes = ["user_management"] serve_dir = ["mime_guess", "tokio/fs"] ratelimiting = ["dashmap"] @@ -28,6 +28,7 @@ tokio = { version = "0.3.1", features = ["io-util","net","time", "rt"] } uriparse = "0.6.3" percent-encoding = "2.1.0" log = "0.4.11" +ring = "0.16.15" lazy_static = { version = "1.4.0", optional = true } rustls = { version = "0.18.1", features = ["dangerous_configuration"], optional = true} webpki = { version = "0.21.0", optional = true} @@ -39,7 +40,6 @@ bincode = { version = "1.3.1", optional = true } serde = { version = "1.0", optional = true } rust-argon2 = { version = "0.8.2", optional = true } crc32fast = { version = "1.2.1", optional = true } -ring = { version = "0.16.15", optional = true } rcgen = { version = "0.8.5", optional = true } squeegee = { git = "https://gitlab.com/Alch_Emi/squeegee.git", branch = "main", optional = true } diff --git a/src/lib.rs b/src/lib.rs index c820eb5..4a4756b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,16 +5,15 @@ use std::{ time::Duration, }; #[cfg(feature = "gemini_srv")] -use std::{ - convert::TryFrom, - path::PathBuf, -}; +use std::convert::TryFrom; #[cfg(feature = "scgi_srv")] use std::{ collections::HashMap, net::SocketAddr, str::FromStr, }; +#[cfg(any(feature = "gemini_srv", feature = "user_management"))] +use std::path::PathBuf; use tokio::{ io, io::BufReader, @@ -125,7 +124,7 @@ impl ServerInner { #[cfg(feature = "gemini_srv")] stream: TcpStream, #[cfg(feature = "scgi_srv")] - stream: impl AsyncWrite + AsyncRead + Unpin, + stream: impl AsyncWrite + AsyncRead + Unpin + Send, ) -> Result<()> { let fut_accept_request = async { #[cfg(feature = "gemini_srv")] @@ -218,7 +217,7 @@ impl ServerInner { Ok(()) } - async fn send_response(&self, mut response: Response, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> { + async fn send_response(&self, mut response: Response, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { let maybe_body = response.take_body(); let header = response.header(); @@ -280,7 +279,7 @@ impl ServerInner { #[cfg(feature = "gemini_srv")] async fn receive_request( &self, - stream: &mut (impl AsyncBufRead + Unpin), + stream: &mut (impl AsyncBufRead + Unpin + Send), ) -> Result { const HEADER_LIMIT: usize = REQUEST_URI_MAX_LEN + "\r\n".len(); let mut stream = stream.take(HEADER_LIMIT as u64); @@ -377,7 +376,13 @@ impl ServerInner { trace!("Headers received: {:?}", headers); - Ok(Request::new(headers)?) + Ok( + Request::new( + headers, + #[cfg(feature = "user_management")] + self.manager.clone(), + )? + ) } } @@ -653,7 +658,7 @@ impl Server { /// /// `addr` can be anything `tokio` can parse, including just a string like /// "localhost:1965" - pub async fn serve_ip(self, addr: impl ToSocketAddrs) -> Result<()> { + pub async fn serve_ip(self, addr: impl ToSocketAddrs + Send) -> Result<()> { let server = self.build()?; let socket = TcpListener::bind(addr).await?; server.serve_ip(socket).await @@ -677,7 +682,7 @@ impl Default for Server { } } -async fn send_response_header(header: &ResponseHeader, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> { +async fn send_response_header(header: &ResponseHeader, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { let header = format!( "{status} {meta}\r\n", status = header.status.code(), @@ -690,7 +695,7 @@ async fn send_response_header(header: &ResponseHeader, stream: &mut (impl AsyncW Ok(()) } -async fn maybe_send_response_body(maybe_body: Option, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> { +async fn maybe_send_response_body(maybe_body: Option, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { if let Some(body) = maybe_body { send_response_body(body, stream).await?; } @@ -698,7 +703,7 @@ async fn maybe_send_response_body(maybe_body: Option, stream: &mut (impl A Ok(()) } -async fn send_response_body(body: Body, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> { +async fn send_response_body(body: Body, stream: &mut (impl AsyncWrite + Unpin + Send)) -> Result<()> { match body { Body::Bytes(bytes) => stream.write_all(&bytes).await?, Body::Reader(mut reader) => { io::copy(&mut reader, stream).await?; }, diff --git a/src/types.rs b/src/types.rs index ccbf1b2..385fa8c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,7 +1,3 @@ -#[cfg(feature = "gemini_srv")] -pub use rustls::Certificate; -#[cfg(feature = "scgi_srv")] -pub type Certificate = String; pub use uriparse::URIReference; mod meta; diff --git a/src/types/request.rs b/src/types/request.rs index db812c4..ae8b844 100644 --- a/src/types/request.rs +++ b/src/types/request.rs @@ -1,4 +1,5 @@ use std::ops; +use std::convert::TryInto; #[cfg(feature = "scgi_srv")] use std::{ collections::HashMap, @@ -7,9 +8,10 @@ use std::{ use anyhow::*; use percent_encoding::percent_decode_str; use uriparse::URIReference; -use crate::types::Certificate; #[cfg(feature="user_management")] use serde::{Serialize, de::DeserializeOwned}; +#[cfg(feature = "gemini_srv")] +use ring::digest; #[cfg(feature="user_management")] use crate::user_management::{UserManager, User}; @@ -17,7 +19,7 @@ use crate::user_management::{UserManager, User}; pub struct Request { uri: URIReference<'static>, input: Option, - certificate: Option, + certificate: Option<[u8; 32]>, trailing_segments: Option>, #[cfg(feature="user_management")] manager: UserManager, @@ -49,7 +51,13 @@ impl Request { ) .context("Request URI is invalid")? .into_owned(), - headers.get("TLS_CLIENT_HASH").cloned(), + headers.get("TLS_CLIENT_HASH") + .map(|hsh| { + ring::test::from_hex(hsh.as_str()) + .expect("Received invalid certificate fingerprint from upstream") + .try_into() + .expect("Received certificate fingerprint of invalid lenght from upstream") + }), ); uri.normalize(); @@ -140,8 +148,14 @@ impl Request { &self.headers } - pub fn set_cert(&mut self, cert: Option) { - self.certificate = cert; + #[cfg(feature = "gemini_srv")] + pub (crate) fn set_cert(&mut self, cert: Option) { + self.certificate = cert.map(|cert| { + digest::digest(&digest::SHA256, cert.0.as_ref()) + .as_ref() + .try_into() + .expect("SHA256 didn't return 256 bits") + }); } pub fn set_trailing(&mut self, segments: Vec) { @@ -149,7 +163,8 @@ impl Request { } #[allow(clippy::missing_const_for_fn)] - pub fn certificate(&self) -> Option<&Certificate> { + /// Get the fingerprint of the certificate the user is connecting with + pub fn certificate(&self) -> Option<&[u8; 32]> { self.certificate.as_ref() } diff --git a/src/user_management/manager.rs b/src/user_management/manager.rs index 4bdabd9..009f802 100644 --- a/src/user_management/manager.rs +++ b/src/user_management/manager.rs @@ -1,24 +1,8 @@ -use rustls::Certificate; -use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use serde::{Serialize, de::DeserializeOwned}; use crate::user_management::{User, Result}; use crate::user_management::user::{RegisteredUser, NotSignedInUser, PartialUser}; -#[derive(Debug, Clone, Deserialize, Serialize)] -/// Data stored in the certificate tree about a certain certificate -pub struct CertificateData { - #[serde(with = "CertificateDef")] - /// The certificate in question - pub certificate: Certificate, - - /// The username of the user to which this certificate is registered - pub owner_username: String, -} - -#[derive(Serialize, Deserialize)] -#[serde(remote = "Certificate")] -struct CertificateDef(Vec); - #[derive(Debug, Clone)] /// A struct containing information for managing users. /// @@ -43,21 +27,14 @@ impl UserManager { }) } - /// Produce a u32 hash from a certificate, used for [`lookup_certificate()`](Self::lookup_certificate()) - pub fn hash_certificate(cert: &Certificate) -> u32 { - let mut hasher = crc32fast::Hasher::new(); - hasher.update(cert.0.as_ref()); - hasher.finalize() - } - - /// Lookup information about a certificate based on it's u32 hash + /// Lookup the owner of a certificate based on it's fingerprint /// /// # Errors /// An error is thrown if there is an error reading from the database or if data /// recieved from the database is corrupt - pub fn lookup_certificate(&self, cert: u32) -> Result> { - if let Some(bytes) = self.certificates.get(cert.to_le_bytes())? { - Ok(Some(bincode::deserialize(&bytes)?)) + pub fn lookup_certificate(&self, cert: [u8; 32]) -> Result> { + if let Some(bytes) = self.certificates.get(cert)? { + Ok(Some(std::str::from_utf8(bytes.as_ref())?.to_string())) } else { Ok(None) } @@ -116,20 +93,19 @@ impl UserManager { /// Pancis if the database is corrupt pub fn get_user( &self, - cert: Option<&Certificate> + cert: Option<&[u8; 32]> ) -> Result> where UserData: Serialize + DeserializeOwned { if let Some(certificate) = cert { - let cert_hash = Self::hash_certificate(certificate); - if let Some(certificate_data) = self.lookup_certificate(cert_hash)? { - let user_inner = self.lookup_user(&certificate_data.owner_username)? + if let Some(username) = self.lookup_certificate(*certificate)? { + let user_inner = self.lookup_user(&username)? .expect("Database corruption: Certificate data refers to non-existant user"); - Ok(User::SignedIn(user_inner.with_cert(certificate_data.certificate))) + Ok(User::SignedIn(user_inner.with_cert(*certificate))) } else { Ok(User::NotSignedIn(NotSignedInUser { - certificate: certificate.clone(), + certificate: *certificate, manager: self.clone(), })) } diff --git a/src/user_management/mod.rs b/src/user_management/mod.rs index 75b0802..288a65d 100644 --- a/src/user_management/mod.rs +++ b/src/user_management/mod.rs @@ -26,7 +26,6 @@ mod routes; pub use routes::UserManagementRoutes; pub use manager::UserManager; pub use user::User; -pub use manager::CertificateData; // Imports for docs #[allow(unused_imports)] use user::{NotSignedInUser, RegisteredUser}; @@ -39,7 +38,8 @@ pub enum UserManagerError { PasswordNotSet, DatabaseError(sled::Error), DatabaseTransactionError(sled::transaction::TransactionError), - DeserializeError(bincode::Error), + DeserializeBincodeError(bincode::Error), + DeserializeUtf8Error(std::str::Utf8Error), #[cfg(feature = "user_management_advanced")] Argon2Error(argon2::Error), } @@ -58,7 +58,13 @@ impl From for UserManagerError { impl From for UserManagerError { fn from(error: bincode::Error) -> Self { - Self::DeserializeError(error) + Self::DeserializeBincodeError(error) + } +} + +impl From for UserManagerError { + fn from(error: std::str::Utf8Error) -> Self { + Self::DeserializeUtf8Error(error) } } @@ -74,7 +80,8 @@ impl std::error::Error for UserManagerError { match self { Self::DatabaseError(e) => Some(e), Self::DatabaseTransactionError(e) => Some(e), - Self::DeserializeError(e) => Some(e), + Self::DeserializeBincodeError(e) => Some(e), + Self::DeserializeUtf8Error(e) => Some(e), #[cfg(feature = "user_management_advanced")] Self::Argon2Error(e) => Some(e), _ => None @@ -93,8 +100,10 @@ impl std::fmt::Display for UserManagerError { write!(f, "Error accessing the user database: {}", e), Self::DatabaseTransactionError(e) => write!(f, "Error accessing the user database: {}", e), - Self::DeserializeError(e) => + Self::DeserializeBincodeError(e) => write!(f, "Recieved messy data from database, possible corruption: {}", e), + Self::DeserializeUtf8Error(e) => + write!(f, "Recieved invalid UTF-8 from database, possible corruption: {}", e), #[cfg(feature = "user_management_advanced")] Self::Argon2Error(e) => write!(f, "Argon2 Error, likely malformed password hash, possible database corruption: {}", e), diff --git a/src/user_management/routes.rs b/src/user_management/routes.rs index 55b613b..e7f17f3 100644 --- a/src/user_management/routes.rs +++ b/src/user_management/routes.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use tokio::net::ToSocketAddrs; use serde::{Serialize, de::DeserializeOwned}; #[cfg(feature = "dashmap")] @@ -16,7 +15,6 @@ use crate::types::document::HeadingLevel; use crate::user_management::{ User, RegisteredUser, - UserManager, UserManagerError, user::NotSignedInUser, }; @@ -91,7 +89,7 @@ pub trait UserManagementRoutes: private::Sealed { F: Send + Sync + 'static + Future>; } -impl UserManagementRoutes for crate::Server { +impl UserManagementRoutes for crate::Server { /// Add pre-configured routes to the serve to handle authentication /// /// See [`UserManagementRoutes::add_um_routes()`] @@ -187,7 +185,7 @@ lazy_static::lazy_static! { #[cfg(not(feature = "dashmap"))] lazy_static::lazy_static! { - static ref PENDING_REDIRECTS: RwLock> = Default::default(); + static ref PENDING_REDIRECTS: RwLock> = Default::default(); } async fn handle_base(request: Request) -> Result { @@ -408,28 +406,27 @@ fn save_redirect<'a>( #[cfg(not(feature = "dashmap"))] let mut ref_to_map = PENDING_REDIRECTS.write().unwrap(); - let cert_hash = UserManager::hash_certificate(&user.certificate); - debug!("Added \"{}\" as redirect for cert {:x}", redirect, cert_hash); - ref_to_map.insert(cert_hash, redirect); + debug!("Added \"{}\" as redirect for cert {:x?}", redirect, &user.certificate); + ref_to_map.insert(user.certificate, redirect); } } fn get_redirect(user: &RegisteredUser) -> String { - let cert_hash = UserManager::hash_certificate(user.active_certificate().unwrap()); + let cert = user.active_certificate().unwrap(); #[cfg(feature = "dashmap")] - let maybe_redir = PENDING_REDIRECTS.get(&cert_hash).map(|r| r.clone()); + let maybe_redir = PENDING_REDIRECTS.get(cert).map(|r| r.clone()); #[cfg(not(feature = "dashmap"))] let ref_to_map = PENDING_REDIRECTS.read().unwrap(); #[cfg(not(feature = "dashmap"))] - let maybe_redir = ref_to_map.get(&cert_hash).cloned(); + let maybe_redir = ref_to_map.get(cert).cloned(); let redirect = maybe_redir.unwrap_or_else(||"/".to_string()); - debug!("Accessed redirect to \"{}\" for cert {:x}", redirect, cert_hash); + debug!("Accessed redirect to \"{}\" for cert {:x?}", redirect, cert); redirect } mod private { pub trait Sealed {} - impl Sealed for crate::Server {} + impl Sealed for crate::Server {} } diff --git a/src/user_management/user.rs b/src/user_management/user.rs index baa139a..043c1ff 100644 --- a/src/user_management/user.rs +++ b/src/user_management/user.rs @@ -13,13 +13,11 @@ //! the data stored for almost all users. This is accomplished through the //! [`as_mut()`](RegisteredUser::as_mut) method. Changes made this way must be persisted //! using [`save()`](RegisteredUser::save()) or by dropping the user. -use rustls::Certificate; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use sled::Transactional; use crate::user_management::UserManager; use crate::user_management::Result; -use crate::user_management::manager::CertificateData; #[cfg(feature = "user_management_advanced")] const ARGON2_CONFIG: argon2::Config = argon2::Config { @@ -46,7 +44,7 @@ lazy_static::lazy_static! { #[derive(Clone, Debug, Deserialize, Serialize)] pub (crate) struct PartialUser { pub data: UserData, - pub certificates: Vec, + pub certificates: Vec<[u8; 32]>, #[cfg(feature = "user_management_advanced")] pub pass_hash: Option<(Vec, [u8; 32])>, } @@ -94,7 +92,7 @@ pub enum User { /// /// For more information about the user lifecycle and sign-in stages, see [`User`] pub struct NotSignedInUser { - pub (crate) certificate: Certificate, + pub (crate) certificate: [u8; 32], pub (crate) manager: UserManager, } @@ -120,7 +118,7 @@ impl NotSignedInUser { let mut newser = RegisteredUser::new( username, - Some(self.certificate.clone()), + Some(self.certificate), self.manager, PartialUser { data: UserData::default(), @@ -179,9 +177,8 @@ impl NotSignedInUser { } } - let certhash = UserManager::hash_certificate(&self.certificate); - info!("User {} attached certificate with hash {:x}", username, certhash); - user.add_certificate(self.certificate.clone())?; + info!("User {} attached certificate with fingerprint {:x?}", username, &self.certificate[..]); + user.add_certificate(self.certificate)?; user.active_certificate = Some(self.certificate); Ok(Some(user)) } else { @@ -196,7 +193,7 @@ impl NotSignedInUser { /// For more information about the user lifecycle and sign-in stages, see [`User`] pub struct RegisteredUser { username: String, - active_certificate: Option, + active_certificate: Option<[u8; 32]>, manager: UserManager, inner: PartialUser, /// Indicates that [`RegisteredUser::as_mut()`] has been called, but [`RegisteredUser::save()`] has not @@ -208,7 +205,7 @@ impl RegisteredUser { /// Create a new user from parts pub (crate) fn new( username: String, - active_certificate: Option, + active_certificate: Option<[u8; 32]>, manager: UserManager, inner: PartialUser ) -> Self { @@ -226,30 +223,22 @@ impl RegisteredUser { /// This is not to be confused with [`RegisteredUser::add_certificate`], which /// performs the database operations needed to register a new certificate to a user. /// This literally just marks the active certificate. - pub (crate) fn with_cert(mut self, cert: Certificate) -> Self { + pub (crate) fn with_cert(mut self, cert: [u8; 32]) -> Self { self.active_certificate = Some(cert); self } - /// Get the [`Certificate`] that the user is currently using to connect. + /// Get the fingerprint of the certificate that the user is currently using. /// /// If this user was retrieved by a [`UserManager::lookup_user()`], this will be /// [`None`]. In all other cases, this will be [`Some`]. - pub fn active_certificate(&self) -> Option<&Certificate> { + pub fn active_certificate(&self) -> Option<&[u8; 32]> { self.active_certificate.as_ref() } - /// Produce a list of all [`Certificate`]s registered to this account - pub fn all_certificates(&self) -> Vec { - self.inner.certificates - .iter() - .map( - |cid| self.manager.lookup_certificate(*cid) - .expect("Database corruption: User refers to non-existant certificate") - .expect("Error accessing database") - .certificate - ) - .collect() + /// Produce a list of all certificate fingerprints registered to this account + pub fn all_certificates(&self) -> &Vec<[u8; 32]> { + &self.inner.certificates } /// Get the user's current username. @@ -335,18 +324,10 @@ impl RegisteredUser { /// If you have a [`NotSignedInUser`] and are looking for a way to link them to an /// existing user, consider [`NotSignedInUser::attach()`], which contains facilities for /// password checking and automatically performs the user lookup. - pub fn add_certificate(&mut self, certificate: Certificate) -> Result<()> { - let cert_hash = UserManager::hash_certificate(&certificate); - - self.inner.certificates.push(cert_hash); - - let cert_info = CertificateData { - certificate, - owner_username: self.username.clone(), - }; + pub fn add_certificate(&mut self, certificate: [u8; 32]) -> Result<()> { + self.inner.certificates.push(certificate); let inner_serialized = bincode::serialize(&self.inner)?; - let cert_info_serialized = bincode::serialize(&cert_info)?; (&self.manager.users, &self.manager.certificates) .transaction(|(tx_usr, tx_crt)| { @@ -355,8 +336,8 @@ impl RegisteredUser { inner_serialized.clone(), )?; tx_crt.insert( - cert_hash.to_le_bytes().as_ref(), - cert_info_serialized.clone(), + &certificate, + self.username.as_bytes(), )?; Ok(()) })?;