diff --git a/src/protocol.rs b/src/protocol.rs index 143d371..d216a20 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -53,12 +53,10 @@ where Ok(()) } -#[derive(Debug, Clone, PartialEq, Eq, Getters)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct LoginSession { - #[get = "pub"] - response: CredentialResponse, - #[get = "pub"] - session_id: Uuid + pub response: CredentialResponse, + pub session_id: Uuid } diff --git a/src/server.rs b/src/server.rs index d9d5247..a74b0cf 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,117 +1,274 @@ +//! Single-file example: remove Key-vs-Code duplication by introducing a CredKind trait +//! and implementing the OPAQUE flows once for Registration and Login states. + use std::marker::PhantomData; -use opaque_ke::{CredentialFinalization, CredentialRequest, Identifiers, RegistrationRequest}; -use crate::protocol::{KeyLoginPhaseISession, NKodeCipherSuite, PasswordFile}; + +use opaque_ke::{ + rand::rngs::OsRng, CredentialFinalization, CredentialRequest, + RegistrationRequest, RegistrationResponse, ServerLogin, ServerLoginParameters, + ServerRegistration, +}; use uuid::Uuid; +// --- Your crate types (as referenced in your snippet) --- +use crate::protocol::{LoginSession, NKodeCipherSuite, NKodeServerSetup, PasswordFile}; + +// ---------------- Errors ---------------- + #[derive(Debug)] enum AuthRepoError { UserExists, KeyNotRegistered, - CodeNotRegistered + CodeNotRegistered, } +// ---------------- Repo abstraction ---------------- trait AuthRepo { - fn new_key(email: String, password_file: PasswordFile) -> Result<(), AuthRepoError>; - fn new_code(email: String, password_file: PasswordFile) -> Result<(), AuthRepoError>; - fn has_code(email: String) -> bool; - fn has_key(email: String) -> bool; - fn get_key_passcode_file(email: String) -> Result; - fn get_code_passcode_file(email: String) -> Result; + fn new_key(&mut self, identifier: &[u8], password_file: PasswordFile) -> Result<(), AuthRepoError>; + fn new_code(&mut self, identifier: &[u8], password_file: PasswordFile) -> Result<(), AuthRepoError>; + + fn has_code(&self, identifier: &[u8]) -> bool; + 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; } -struct RegSession; -struct LoginSession; +// ---------------- Session abstraction ---------------- + +#[derive(Clone)] +struct RegCache { + session_id: Uuid, + identifier: Vec, +} + +struct LoginCache { + session_id: Uuid, + identifiers: Vec, + server_login: ServerLogin, +} trait AuthSession { - fn new_reg_session(identifier: &[u8], request: &RegistrationRequest) -> Result; - fn get_reg_session(session_id: Uuid) -> Result; - fn new_login_session(identifier: &[u8], request: &CredentialRequest) -> Result; - fn get_login_session(session_id: Uuid) -> Result; + 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>; + + fn new_login_session( + &mut 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>; } -struct OpaqueAuth { +// ---------------- Core OpaqueAuth struct ---------------- + +struct OpaqueAuth { + server_setup: NKodeServerSetup, user_repo: R, session: S, - _state: PhantomData + _state: PhantomData, } -struct KeyAuthRegistration; -impl OpaqueAuth +impl OpaqueAuth +where + R: AuthRepo, + S: AuthSession, { - async fn start( + pub fn new(server_setup: NKodeServerSetup, user_repo: R, session: S) -> Self { + Self { + server_setup, + user_repo, + session, + _state: PhantomData, + } + } +} + +// ---------------- “Kind” trait: Key vs Code differences live here ---------------- + +trait CredKind { + fn has(repo: &R, id: &[u8]) -> bool; + fn get_pf(repo: &R, id: &[u8]) -> Result; + fn put_pf(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>; +} + +struct Key; +struct Code; + +impl CredKind for Key { + fn has(repo: &R, id: &[u8]) -> bool { + repo.has_key(id) + } + fn get_pf(repo: &R, id: &[u8]) -> Result { + repo.get_key_passcode_file(id) + } + fn put_pf(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> { + repo.new_key(id, pf) + } +} + +impl CredKind for Code { + fn has(repo: &R, id: &[u8]) -> bool { + repo.has_code(id) + } + fn get_pf(repo: &R, id: &[u8]) -> Result { + repo.get_code_passcode_file(id) + } + fn put_pf(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> { + repo.new_code(id, pf) + } +} + +// ---------------- State markers ---------------- + +struct Registration(PhantomData); +struct Login(PhantomData); + +// Optional: aliases to make call sites read nicely +type KeyAuthRegistration = OpaqueAuth, R, S>; +type CodeAuthRegistration = OpaqueAuth, R, S>; +type KeyAuthLogin = OpaqueAuth, R, S>; +type CodeAuthLogin = OpaqueAuth, R, S>; + +// ---------------- Return types ---------------- + +struct RegSession { + session_id: Uuid, + response: RegistrationResponse, +} + +// NOTE: you already have crate::protocol::LoginSession, so we use that. +// If you want it here, uncomment below and remove crate import. +// struct LoginSession { +// session_id: Uuid, +// response: CredentialResponse, +// } + +// ---------------- Shared registration flow ---------------- + +impl OpaqueAuth, R, S> +where + K: CredKind, + R: AuthRepo, + S: AuthSession, +{ + pub async fn start( &mut self, identifier: &[u8], - message: &RegistrationRequest + request: RegistrationRequest, ) -> Result { - todo!() + let start = ServerRegistration::::start( + &self.server_setup, + request, + identifier, + ) + .map_err(|e| format!("opaque reg start: {e:?}"))?; + + let cache = self + .session + .new_reg_session(identifier) + .map_err(|e| format!("reg cache: {e}"))?; + + Ok(RegSession { + session_id: cache.session_id, + response: start.message, + }) } - async fn finish( + pub async fn finish( &mut self, session_id: &Uuid, password_file: PasswordFile, ) -> Result<(), String> { - todo!() + let sess = self + .session + .get_reg_session(session_id) + .map_err(|e| format!("get reg session: {e}"))?; + + K::put_pf(&mut self.user_repo, sess.identifier.as_slice(), password_file) + .map_err(|e| format!("repo write: {e:?}"))?; + + self.session + .clear_reg_session(session_id) + .map_err(|e| format!("clear reg session: {e}")) } } -struct KeyAuthLogin; -impl OpaqueAuth +// ---------------- Shared login flow ---------------- + +impl OpaqueAuth, R, S> +where + K: CredKind, + R: AuthRepo, + S: AuthSession, { - async fn start( + pub async fn start( &mut self, identifier: &[u8], - request_bytes: &CredentialRequest + request: CredentialRequest, ) -> Result { - todo!() + // Lookup password file for K (Key vs Code) + let password_file = K::get_pf(&self.user_repo, identifier) + .map_err(|e| format!("repo read: {e:?}"))?; + + // Deserialize into OPAQUE password file type + let password_file = + ServerRegistration::::deserialize(password_file.as_slice()) + .map_err(|e| format!("pf deserialize: {e:?}"))?; + + // OPAQUE login start + let mut server_rng = OsRng; + let start = ServerLogin::start( + &mut server_rng, + &self.server_setup, + Some(password_file), + request, + identifier, + ServerLoginParameters::default(), + ) + .map_err(|e| format!("opaque login start: {e:?}"))?; + + // Cache server state + let cache = self + .session + .new_login_session(identifier, start.state) + .map_err(|e| format!("login cache: {e}"))?; + + Ok(LoginSession { + session_id: cache.session_id, + response: start.message, + }) } - async fn finish( + pub async fn finish( &mut self, session_id: &Uuid, - message: &CredentialFinalization - ) -> Result<(), String> { - todo!() + finalize: CredentialFinalization, + ) -> Result, String> { + let cache = self + .session + .get_login_session(session_id) + .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:?}"))?; + + Ok(finish.session_key.to_vec()) } } -struct CodeAuthRegistration; -impl OpaqueAuth -{ - async fn start( - &mut self, - identifier: &[u8], - message: &RegistrationRequest - ) -> Result { - todo!() - } - - async fn finish( - &mut self, - session_id: &Uuid, - password_file: PasswordFile, - ) -> Result<(), String> { - todo!() - } -} - -struct CodeAuthLogin; -impl OpaqueAuth -{ - async fn start( - &mut self, - identifier: &[u8], - request_bytes: &CredentialRequest - ) -> Result { - todo!() - } - - async fn finish( - &mut self, - session_id: &Uuid, - message: &CredentialFinalization - ) -> Result<(), String> { - todo!() - } -} +// ---------------- Usage notes ---------------- +// +// You now have these concrete “types” for your call sites: +// +// KeyAuthRegistration == OpaqueAuth, R, S> +// CodeAuthRegistration == OpaqueAuth, R, S> +// KeyAuthLogin == OpaqueAuth, R, S> +// CodeAuthLogin == OpaqueAuth, R, S> +// +// And you only wrote the reg/login OPAQUE logic once.