implement server app

This commit is contained in:
2025-12-17 13:26:40 -06:00
parent 3029a41386
commit ac2ddf86df
5 changed files with 104 additions and 91 deletions

View File

@@ -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<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
identifier: &[u8],
request: RegistrationRequest<NKodeCipherSuite>,
) -> Result<OpaqueRegisterSession, String> {
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::<NKodeCipherSuite>::start(
&self.server_setup,
@@ -34,7 +35,7 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
identifier,
).map_err(|e| format!("opaque reg start: {e:?}"))?;
let cache = self.opaque_sess
.new_reg_session(identifier)
.new_reg_session(identifier).await
.map_err(|e| format!("reg cache: {e}"))?;
Ok(OpaqueRegisterSession { session_id: cache.session_id, response: start.message })
@@ -46,14 +47,14 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
password_file: PasswordFile,
) -> 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<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
identifier: &[u8],
request: CredentialRequest<NKodeCipherSuite>,
) -> Result<OpaqueLoginSession, String> {
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<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
ServerLoginParameters::default(),
).map_err(|e| format!("opaque login start: {e:?}"))?;
let cache = self.opaque_sess
.new_login_session(identifier, start.state)
.new_login_session(identifier, start.state).await
.map_err(|e| format!("login cache: {e}"))?;
Ok(OpaqueLoginSession { session_id: cache.session_id, response: start.message })
}
@@ -89,15 +90,14 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
finalize: CredentialFinalization<NKodeCipherSuite>,
) -> Result<LoggedInSession, String> {
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<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo, U: UserRepo> ServerApp<R, S, U
}
}
#[derive(Clone)]
#[derive(Debug,Clone )]
pub struct Key;
#[derive(Clone)]
#[derive(Debug,Clone)]
pub struct Code;
#[async_trait]
impl CredKind for Key {
fn has<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> bool {
repo.has_key(id)
async fn has<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> bool {
repo.has_key(id).await
}
fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError> {
repo.get_key_passcode_file(id)
async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError> {
repo.get_key_passcode_file(id).await
}
fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> {
repo.new_key(id, pf)
async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> {
repo.new_key(id, pf).await
}
}
#[async_trait]
impl CredKind for Code {
fn has<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> bool {
repo.has_code(id)
async fn has<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> bool {
repo.has_code(id).await
}
fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError> {
repo.get_code_passcode_file(id)
async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError> {
repo.get_code_passcode_file(id).await
}
fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> {
repo.new_code(id, pf)
async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> {
repo.new_code(id, pf).await
}
fn prereq_for_register<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<(), AuthRepoError> {
if repo.has_key(id) {
async fn prereq_for_register<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<(), AuthRepoError> {
if repo.has_key(id).await {
Ok(())
} else {
Err(AuthRepoError::KeyNotRegistered)

View File

@@ -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<NKodeCipherSuite>,
}
#[async_trait]
pub trait CredKind {
fn has<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> bool;
fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError>;
fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>;
fn prereq_for_register<R: OpaqueDatabaseRepo>(_repo: &R, _id: &[u8]) -> Result<(), AuthRepoError> {
async fn has<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> bool;
async fn get_password_file<R: OpaqueDatabaseRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError>;
async fn set_password_file<R: OpaqueDatabaseRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>;
async fn prereq_for_register<R: OpaqueDatabaseRepo>(_repo: &R, _id: &[u8]) -> Result<(), AuthRepoError> {
Ok(())
}
}

View File

@@ -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<KeyID, PasswordFile>,
code_entries: HashMap<CodeID, PasswordFile>,
key_entries: Arc<Mutex<HashMap<KeyID, PasswordFile>>>,
code_entries: Arc<Mutex<HashMap<CodeID, PasswordFile>>>,
}
#[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<PasswordFile, AuthRepoError> {
self.key_entries
async fn get_key_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError> {
self.key_entries.lock().await
.get(&KeyID(identifier.to_vec()))
.cloned()
.ok_or(AuthRepoError::KeyNotRegistered)
}
fn get_code_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError> {
self.code_entries
async fn get_code_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError> {
self.code_entries.lock().await
.get(&CodeID(identifier.to_vec()))
.cloned()
.ok_or(AuthRepoError::CodeNotRegistered)

View File

@@ -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<Uuid, RegCache>,
login_sessions: HashMap<Uuid, LoginCache>,
reg_sessions: Arc<Mutex<HashMap<Uuid, RegCache>>>,
login_sessions: Arc<Mutex<HashMap<Uuid, LoginCache>>>,
}
impl InMemoryOpaqueSession {
@@ -17,28 +21,27 @@ impl InMemoryOpaqueSession {
}
}
#[async_trait]
impl OpaqueSessionRepo for InMemoryOpaqueSession {
fn new_reg_session(&mut self, identifier: &[u8]) -> Result<RegCache, String> {
async fn new_reg_session(&self, identifier: &[u8]) -> Result<RegCache, String> {
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<RegCache, String> {
async fn get_reg_session(&self, session_id: &Uuid) -> Result<RegCache, String> {
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<NKodeCipherSuite>,
) -> Result<LoginCache, String> {
@@ -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<LoginCache, String> {
self.login_sessions
async fn get_login_session(&self, session_id: &Uuid) -> Result<LoginCache, String> {
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())

View File

@@ -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<PasswordFile, AuthRepoError>;
fn get_code_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError>;
async fn get_key_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError>;
async fn get_code_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError>;
}
#[async_trait]
pub trait OpaqueSessionRepo {
fn new_reg_session(&mut self, identifier: &[u8]) -> Result<RegCache, String>;
fn get_reg_session(&self, session_id: &Uuid) -> Result<RegCache, String>;
fn clear_reg_session(&mut self, session_id: &Uuid) -> Result<(), String>;
async fn new_reg_session(&self, identifier: &[u8]) -> Result<RegCache, String>;
async fn get_reg_session(&self, session_id: &Uuid) -> Result<RegCache, String>;
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<NKodeCipherSuite>,
) -> Result<LoginCache, String>;
fn get_login_session(&self, session_id: &Uuid) -> Result<LoginCache, String>;
fn clear_login_session(&mut self, session_id: &Uuid) -> Result<(), String>;
async fn get_login_session(&self, session_id: &Uuid) -> Result<LoginCache, String>;
async fn clear_login_session(&self, session_id: &Uuid) -> Result<(), String>;
}