Fix user management module, rework certificates to use hashes

This commit is contained in:
Emii Tatsuo 2020-12-01 14:43:15 -05:00
parent 244fd25112
commit 8b9fbce489
Signed by: Emi
GPG Key ID: 68FAB2E2E6DFC98B
8 changed files with 90 additions and 111 deletions

View File

@ -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 }

View File

@ -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<Request> {
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<Body>, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
async fn maybe_send_response_body(maybe_body: Option<Body>, 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<Body>, 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?; },

View File

@ -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;

View File

@ -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<String>,
certificate: Option<Certificate>,
certificate: Option<[u8; 32]>,
trailing_segments: Option<Vec<String>>,
#[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<Certificate>) {
self.certificate = cert;
#[cfg(feature = "gemini_srv")]
pub (crate) fn set_cert(&mut self, cert: Option<rustls::Certificate>) {
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<String>) {
@ -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()
}

View File

@ -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<u8>);
#[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<Option<CertificateData>> {
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<Option<String>> {
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<UserData>(
&self,
cert: Option<&Certificate>
cert: Option<&[u8; 32]>
) -> Result<User<UserData>>
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(),
}))
}

View File

@ -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<sled::transaction::TransactionError> for UserManagerError {
impl From<bincode::Error> for UserManagerError {
fn from(error: bincode::Error) -> Self {
Self::DeserializeError(error)
Self::DeserializeBincodeError(error)
}
}
impl From<std::str::Utf8Error> 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),

View File

@ -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<Output = Result<Response>>;
}
impl<A: ToSocketAddrs> UserManagementRoutes for crate::Server<A> {
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<HashMap<u32, String>> = Default::default();
static ref PENDING_REDIRECTS: RwLock<HashMap<[u8; 32], String>> = Default::default();
}
async fn handle_base<UserData: Serialize + DeserializeOwned>(request: Request) -> Result<Response> {
@ -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<T: Serialize + DeserializeOwned>(user: &RegisteredUser<T>) -> 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<A> Sealed for crate::Server<A> {}
impl Sealed for crate::Server {}
}

View File

@ -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<UserData> {
pub data: UserData,
pub certificates: Vec<u32>,
pub certificates: Vec<[u8; 32]>,
#[cfg(feature = "user_management_advanced")]
pub pass_hash: Option<(Vec<u8>, [u8; 32])>,
}
@ -94,7 +92,7 @@ pub enum User<UserData: Serialize + DeserializeOwned> {
///
/// 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<UserData: Serialize + DeserializeOwned> {
username: String,
active_certificate: Option<Certificate>,
active_certificate: Option<[u8; 32]>,
manager: UserManager,
inner: PartialUser<UserData>,
/// Indicates that [`RegisteredUser::as_mut()`] has been called, but [`RegisteredUser::save()`] has not
@ -208,7 +205,7 @@ impl<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> {
/// Create a new user from parts
pub (crate) fn new(
username: String,
active_certificate: Option<Certificate>,
active_certificate: Option<[u8; 32]>,
manager: UserManager,
inner: PartialUser<UserData>
) -> Self {
@ -226,30 +223,22 @@ impl<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> {
/// 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<Certificate> {
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<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> {
/// 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<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> {
inner_serialized.clone(),
)?;
tx_crt.insert(
cert_hash.to_le_bytes().as_ref(),
cert_info_serialized.clone(),
&certificate,
self.username.as_bytes(),
)?;
Ok(())
})?;