From ac2ddf86df5d944f5af59e6e2817251912f00243 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 17 Dec 2025 13:26:40 -0600 Subject: [PATCH] implement server app --- src/server/app.rs | 56 ++++++++++--------- src/server/models.rs | 10 ++-- .../in_memory/in_memory_opaque_db.rs | 52 +++++++++-------- .../in_memory/in_memory_opaque_session.rs | 46 +++++++-------- src/server/repository/opaque_repo.rs | 31 +++++----- 5 files changed, 104 insertions(+), 91 deletions(-) diff --git a/src/server/app.rs b/src/server/app.rs index 675a094..83caa45 100644 --- a/src/server/app.rs +++ b/src/server/app.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use uuid::Uuid; use opaque_ke::{CredentialFinalization, CredentialRequest, RegistrationRequest, ServerLogin, ServerLoginParameters, ServerRegistration}; use opaque_ke::argon2::password_hash::rand_core::OsRng; @@ -26,7 +27,7 @@ impl ServerApp, ) -> Result { - K::prereq_for_register(&self.opaque_db, identifier) + K::prereq_for_register(&self.opaque_db, identifier).await .map_err(|e| format!("registration prereq failed: {e:?}"))?; let start = ServerRegistration::::start( &self.server_setup, @@ -34,7 +35,7 @@ impl ServerApp ServerApp Result<(), String> { let sess = self.opaque_sess - .get_reg_session(session_id) + .get_reg_session(session_id).await .map_err(|e| format!("get reg session: {e}"))?; - K::prereq_for_register(&self.opaque_db, sess.identifier.as_slice()) + K::prereq_for_register(&self.opaque_db, sess.identifier.as_slice()).await .map_err(|e| format!("registration prereq failed: {e:?}"))?; - K::set_password_file(&mut self.opaque_db, sess.identifier.as_slice(), password_file) + K::set_password_file(&mut self.opaque_db, sess.identifier.as_slice(), password_file).await .map_err(|e| format!("repo write: {e:?}"))?; self.opaque_sess - .clear_reg_session(session_id) + .clear_reg_session(session_id).await .map_err(|e| format!("clear reg session: {e}")) } @@ -62,7 +63,7 @@ impl ServerApp, ) -> Result { - let password_file = K::get_password_file(&self.opaque_db, identifier) + let password_file = K::get_password_file(&self.opaque_db, identifier).await .map_err(|e| format!("repo read: {e:?}"))?; let password_file = @@ -78,7 +79,7 @@ impl ServerApp ServerApp, ) -> Result { let cache = self.opaque_sess - .get_login_session(session_id) + .get_login_session(session_id).await .map_err(|e| format!("get login session: {e}"))?; let finish = cache.server_login .finish(finalize, ServerLoginParameters::default()) .map_err(|e| format!("opaque login finish: {e:?}"))?; - self.opaque_sess - .clear_login_session(session_id) + .clear_login_session(session_id).await .map_err(|e| format!("clear login session: {e}"))?; let session_key = OpaqueSessionKey::from_bytes(&finish.session_key.to_vec()).unwrap(); Ok(LoggedInSession{ @@ -144,36 +144,38 @@ impl ServerApp(repo: &R, id: &[u8]) -> bool { - repo.has_key(id) + async fn has(repo: &R, id: &[u8]) -> bool { + repo.has_key(id).await } - fn get_password_file(repo: &R, id: &[u8]) -> Result { - repo.get_key_passcode_file(id) + async fn get_password_file(repo: &R, id: &[u8]) -> Result { + repo.get_key_passcode_file(id).await } - fn set_password_file(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> { - repo.new_key(id, pf) + async fn set_password_file(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> { + repo.new_key(id, pf).await } } +#[async_trait] impl CredKind for Code { - fn has(repo: &R, id: &[u8]) -> bool { - repo.has_code(id) + async fn has(repo: &R, id: &[u8]) -> bool { + repo.has_code(id).await } - fn get_password_file(repo: &R, id: &[u8]) -> Result { - repo.get_code_passcode_file(id) + async fn get_password_file(repo: &R, id: &[u8]) -> Result { + repo.get_code_passcode_file(id).await } - fn set_password_file(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> { - repo.new_code(id, pf) + async fn set_password_file(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> { + repo.new_code(id, pf).await } - fn prereq_for_register(repo: &R, id: &[u8]) -> Result<(), AuthRepoError> { - if repo.has_key(id) { + async fn prereq_for_register(repo: &R, id: &[u8]) -> Result<(), AuthRepoError> { + if repo.has_key(id).await { Ok(()) } else { Err(AuthRepoError::KeyNotRegistered) diff --git a/src/server/models.rs b/src/server/models.rs index d3fb563..55aa615 100644 --- a/src/server/models.rs +++ b/src/server/models.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use uuid::Uuid; use opaque_ke::ServerLogin; use crate::server::repository::opaque_repo::{AuthRepoError, OpaqueDatabaseRepo}; @@ -14,11 +15,12 @@ pub struct LoginCache { pub server_login: ServerLogin, } +#[async_trait] pub trait CredKind { - fn has(repo: &R, id: &[u8]) -> bool; - fn get_password_file(repo: &R, id: &[u8]) -> Result; - fn set_password_file(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>; - fn prereq_for_register(_repo: &R, _id: &[u8]) -> Result<(), AuthRepoError> { + async fn has(repo: &R, id: &[u8]) -> bool; + async fn get_password_file(repo: &R, id: &[u8]) -> Result; + async fn set_password_file(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>; + async fn prereq_for_register(_repo: &R, _id: &[u8]) -> Result<(), AuthRepoError> { Ok(()) } } \ No newline at end of file diff --git a/src/server/repository/in_memory/in_memory_opaque_db.rs b/src/server/repository/in_memory/in_memory_opaque_db.rs index bc15c95..0ef1a5e 100644 --- a/src/server/repository/in_memory/in_memory_opaque_db.rs +++ b/src/server/repository/in_memory/in_memory_opaque_db.rs @@ -1,11 +1,14 @@ use std::collections::HashMap; use crate::shared::models::opaque::PasswordFile; use crate::server::repository::opaque_repo::{OpaqueDatabaseRepo, AuthRepoError}; +use tokio::sync::Mutex; +use std::sync::Arc; +use async_trait::async_trait; #[derive(Debug, Default)] pub struct InMemoryOpaqueDB { - key_entries: HashMap, - code_entries: HashMap, + key_entries: Arc>>, + code_entries: Arc>>, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -19,66 +22,67 @@ impl InMemoryOpaqueDB { Self::default() } - fn code_exists(&self, identifier: &CodeID) -> bool { - self.code_entries.contains_key(identifier) + async fn code_exists(&self, identifier: &CodeID) -> bool { + self.code_entries.lock().await.contains_key(identifier) } - fn key_exists(&self, identifier: &KeyID) -> bool { - self.key_entries.contains_key(identifier) + async fn key_exists(&self, identifier: &KeyID) -> bool { + self.key_entries.lock().await.contains_key(identifier) } } + +#[async_trait] impl OpaqueDatabaseRepo for InMemoryOpaqueDB { - fn new_key( - &mut self, + async fn new_key( + &self, identifier: &[u8], password_file: PasswordFile, ) -> Result<(), AuthRepoError> { - if self.key_exists(&KeyID(identifier.to_vec())) { + if self.key_exists(&KeyID(identifier.to_vec())).await { return Err(AuthRepoError::UserExists); } - - self.key_entries + self.key_entries.lock().await .insert(KeyID(identifier.to_vec()), password_file); Ok(()) } - fn new_code( - &mut self, + async fn new_code( + &self, identifier: &[u8], password_file: PasswordFile, ) -> Result<(), AuthRepoError> { - if !self.has_key(identifier) { + if !self.has_key(identifier).await { return Err(AuthRepoError::KeyNotRegistered); } - if self.code_exists(&CodeID(identifier.to_vec())) { + if self.code_exists(&CodeID(identifier.to_vec())).await { return Err(AuthRepoError::UserExists); } - self.code_entries + self.code_entries.lock().await .insert(CodeID(identifier.to_vec()), password_file); Ok(()) } - fn has_code(&self, identifier: &[u8]) -> bool { - self.code_entries + async fn has_code(&self, identifier: &[u8]) -> bool { + self.code_entries.lock().await .contains_key(&CodeID(identifier.to_vec())) } - fn has_key(&self, identifier: &[u8]) -> bool { - self.key_entries + async fn has_key(&self, identifier: &[u8]) -> bool { + self.key_entries.lock().await .contains_key(&KeyID(identifier.to_vec())) } - fn get_key_passcode_file(&self, identifier: &[u8]) -> Result { - self.key_entries + async fn get_key_passcode_file(&self, identifier: &[u8]) -> Result { + self.key_entries.lock().await .get(&KeyID(identifier.to_vec())) .cloned() .ok_or(AuthRepoError::KeyNotRegistered) } - fn get_code_passcode_file(&self, identifier: &[u8]) -> Result { - self.code_entries + async fn get_code_passcode_file(&self, identifier: &[u8]) -> Result { + self.code_entries.lock().await .get(&CodeID(identifier.to_vec())) .cloned() .ok_or(AuthRepoError::CodeNotRegistered) diff --git a/src/server/repository/in_memory/in_memory_opaque_session.rs b/src/server/repository/in_memory/in_memory_opaque_session.rs index 0270993..fa999bc 100644 --- a/src/server/repository/in_memory/in_memory_opaque_session.rs +++ b/src/server/repository/in_memory/in_memory_opaque_session.rs @@ -1,5 +1,9 @@ use std::collections::HashMap; +use std::rc::Rc; +use std::sync::Arc; +use async_trait::async_trait; use opaque_ke::ServerLogin; +use tokio::sync::Mutex; use uuid::Uuid; use crate::server::models::{LoginCache, RegCache}; use crate::shared::models::opaque::NKodeCipherSuite; @@ -7,8 +11,8 @@ use crate::server::repository::opaque_repo::OpaqueSessionRepo; #[derive(Default)] pub struct InMemoryOpaqueSession { - reg_sessions: HashMap, - login_sessions: HashMap, + reg_sessions: Arc>>, + login_sessions: Arc>>, } impl InMemoryOpaqueSession { @@ -17,28 +21,27 @@ impl InMemoryOpaqueSession { } } +#[async_trait] impl OpaqueSessionRepo for InMemoryOpaqueSession { - fn new_reg_session(&mut self, identifier: &[u8]) -> Result { + async fn new_reg_session(&self, identifier: &[u8]) -> Result { let cache = RegCache { session_id: Uuid::new_v4(), identifier: identifier.to_vec(), }; - - // Extremely unlikely collision, but keep the invariant anyway. - if self.reg_sessions.contains_key(&cache.session_id) { + if self.reg_sessions.lock().await.contains_key(&cache.session_id) { return Err("session_id collision".to_string()); } - - self.reg_sessions.insert(cache.session_id, RegCache { + self.reg_sessions.lock().await.insert(cache.session_id, RegCache { session_id: cache.session_id, identifier: cache.identifier.clone(), }); - Ok(cache) } - fn get_reg_session(&self, session_id: &Uuid) -> Result { + async fn get_reg_session(&self, session_id: &Uuid) -> Result { self.reg_sessions + .lock() + .await .get(session_id) .map(|c| RegCache { session_id: c.session_id, @@ -47,15 +50,17 @@ impl OpaqueSessionRepo for InMemoryOpaqueSession { .ok_or_else(|| "registration session not found".to_string()) } - fn clear_reg_session(&mut self, session_id: &Uuid) -> Result<(), String> { + async fn clear_reg_session(&self, session_id: &Uuid) -> Result<(), String> { self.reg_sessions + .lock() + .await .remove(session_id) .map(|_| ()) .ok_or_else(|| "registration session not found".to_string()) } - fn new_login_session( - &mut self, + async fn new_login_session( + &self, identifier: &[u8], server_login: ServerLogin, ) -> Result { @@ -64,12 +69,10 @@ impl OpaqueSessionRepo for InMemoryOpaqueSession { identifiers: identifier.to_vec(), server_login, }; - - if self.login_sessions.contains_key(&cache.session_id) { + if self.login_sessions.lock().await.contains_key(&cache.session_id) { return Err("session_id collision".to_string()); } - - self.login_sessions.insert( + self.login_sessions.lock().await.insert( cache.session_id, LoginCache { session_id: cache.session_id, @@ -78,12 +81,11 @@ impl OpaqueSessionRepo for InMemoryOpaqueSession { server_login: cache.server_login.clone(), }, ); - Ok(cache) } - fn get_login_session(&self, session_id: &Uuid) -> Result { - self.login_sessions + async fn get_login_session(&self, session_id: &Uuid) -> Result { + self.login_sessions.lock().await .get(session_id) .map(|c| LoginCache { session_id: c.session_id, @@ -93,8 +95,8 @@ impl OpaqueSessionRepo for InMemoryOpaqueSession { .ok_or_else(|| "login session not found".to_string()) } - fn clear_login_session(&mut self, session_id: &Uuid) -> Result<(), String> { - self.login_sessions + async fn clear_login_session(&self, session_id: &Uuid) -> Result<(), String> { + self.login_sessions.lock().await .remove(session_id) .map(|_| ()) .ok_or_else(|| "login session not found".to_string()) diff --git a/src/server/repository/opaque_repo.rs b/src/server/repository/opaque_repo.rs index 2b30d42..b365928 100644 --- a/src/server/repository/opaque_repo.rs +++ b/src/server/repository/opaque_repo.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use uuid::Uuid; use opaque_ke::ServerLogin; use crate::server::models::{LoginCache, RegCache}; @@ -9,27 +10,29 @@ pub enum AuthRepoError { CodeNotRegistered, } -pub trait OpaqueDatabaseRepo { - fn new_key(&mut self, identifier: &[u8], password_file: PasswordFile) -> Result<(), AuthRepoError>; - fn new_code(&mut self, identifier: &[u8], password_file: PasswordFile) -> Result<(), AuthRepoError>; +#[async_trait] +pub trait OpaqueDatabaseRepo: Send + Sync { + async fn new_key(&self, identifier: &[u8], password_file: PasswordFile) -> Result<(), AuthRepoError>; + async fn new_code(&self, identifier: &[u8], password_file: PasswordFile) -> Result<(), AuthRepoError>; - fn has_code(&self, identifier: &[u8]) -> bool; - fn has_key(&self, identifier: &[u8]) -> bool; + async fn has_code(&self, identifier: &[u8]) -> bool; + async fn has_key(&self, identifier: &[u8]) -> bool; - fn get_key_passcode_file(&self, identifier: &[u8]) -> Result; - fn get_code_passcode_file(&self, identifier: &[u8]) -> Result; + async fn get_key_passcode_file(&self, identifier: &[u8]) -> Result; + async fn get_code_passcode_file(&self, identifier: &[u8]) -> Result; } +#[async_trait] pub trait OpaqueSessionRepo { - fn new_reg_session(&mut self, identifier: &[u8]) -> Result; - fn get_reg_session(&self, session_id: &Uuid) -> Result; - fn clear_reg_session(&mut self, session_id: &Uuid) -> Result<(), String>; + async fn new_reg_session(&self, identifier: &[u8]) -> Result; + async fn get_reg_session(&self, session_id: &Uuid) -> Result; + async fn clear_reg_session(&self, session_id: &Uuid) -> Result<(), String>; - fn new_login_session( - &mut self, + async fn new_login_session( + &self, identifier: &[u8], server_login: ServerLogin, ) -> Result; - fn get_login_session(&self, session_id: &Uuid) -> Result; - fn clear_login_session(&mut self, session_id: &Uuid) -> Result<(), String>; + async fn get_login_session(&self, session_id: &Uuid) -> Result; + async fn clear_login_session(&self, session_id: &Uuid) -> Result<(), String>; } \ No newline at end of file