diff --git a/Cargo.lock b/Cargo.lock index 31de429..26b0c64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,17 @@ dependencies = [ "password-hash", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -322,6 +333,7 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" name = "nkode-protocol" version = "0.1.0" dependencies = [ + "async-trait", "getset", "opaque-ke", "rand", diff --git a/Cargo.toml b/Cargo.toml index 10eb996..36bebf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,5 @@ rand = { version = "0.8.5", features = ["std"] } sha2 = "0.10.9" uuid = "1.19.0" getset = "0.1.6" +async-trait = "0.1.89" diff --git a/src/client.rs b/src/client.rs index 58163f5..4867a8d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,57 +1,46 @@ -use opaque_ke::argon2::password_hash::rand_core::OsRng; -use opaque_ke::{ - CredentialFinalization, CredentialRequest, RegistrationRequest, - ClientLogin, ClientLoginFinishParameters, ClientRegistration, - ClientRegistrationFinishParameters -}; +use async_trait::async_trait; use uuid::Uuid; -use crate::protocol::{PasswordFile, KeyLoginPhaseISession, KeyRegisterSession, NKodeCipherSuite}; -trait ServerConnectionRegister { - async fn start( - &mut self, - identifier: &[u8], - message: &RegistrationRequest - ) -> Result; +use opaque_ke::rand::rngs::OsRng; +use opaque_ke::{ + ClientLogin, ClientLoginFinishParameters, + ClientRegistration, ClientRegistrationFinishParameters, + CredentialFinalization, CredentialRequest, CredentialResponse, + RegistrationRequest, RegistrationResponse, +}; - async fn finish( - &mut self, - session_id: &Uuid, - password_file: PasswordFile, - ) -> Result<(), String>; +use crate::models::{ + NKodeCipherSuite, + PasswordFile, + KeyLoginSession, + KeyRegisterSession, +}; + +#[derive(Debug)] +pub enum ClientAuthError { + Opaque(String), + Transport(String), } -trait ServerConnectionLogin { - async fn start( - &mut self, - identifier: &[u8], - request_bytes: &CredentialRequest - ) -> Result; +// --- Normalize auth inputs to (identifier, secret-bytes) --- - async fn finish( - &mut self, - session_id: &Uuid, - message: &CredentialFinalization - ) -> Result<(), String>; -} - -struct AuthenticationData { - identifier: Vec, - secret: Vec, +pub struct AuthenticationData { + pub identifier: Vec, + pub secret: Vec, } impl AuthenticationData { - fn from_secret_key(email: &String, secret_key: &[u8]) -> Self { + pub fn from_secret_key(email: &str, secret_key: &[u8]) -> Self { Self { identifier: email.as_bytes().to_vec(), secret: secret_key.to_vec(), } } - fn from_code(email: &String, code: &[usize]) -> Self { + pub fn from_code(email: &str, code: &[usize]) -> Self { + // fixed-width so it's stable across 32-bit vs 64-bit platforms let mut secret = Vec::with_capacity(code.len() * 8); for &n in code { - // fixed-width so it's stable across 32-bit vs 64-bit platforms secret.extend_from_slice(&(n as u64).to_le_bytes()); } Self { @@ -61,32 +50,125 @@ impl AuthenticationData { } } -struct OpaqueAuthentication; +// --- Small adapter traits so server can return any “session wrapper” type --- + +pub trait RegStartSession { + fn session_id(&self) -> &Uuid; + fn response(&self) -> &RegistrationResponse; +} + +pub trait LoginStartSession { + fn session_id(&self) -> &Uuid; + fn response(&self) -> &CredentialResponse; +} + +// If your protocol types already have these methods, just implement the traits: +impl RegStartSession for KeyRegisterSession { + fn session_id(&self) -> &Uuid { self.session_id() } + fn response(&self) -> &RegistrationResponse { self.response() } +} + +impl LoginStartSession for KeyLoginSession { + fn session_id(&self) -> &Uuid { self.session_id() } + fn response(&self) -> &CredentialResponse { self.response() } +} + +// --- Server connection traits: generic over returned session wrapper types --- + +#[async_trait] +pub trait ServerConnectionRegister { + type Start: RegStartSession + Send; + + async fn start( + &mut self, + identifier: &[u8], + message: &RegistrationRequest, + ) -> Result; + + async fn finish( + &mut self, + session_id: &Uuid, + password_file: PasswordFile, + ) -> Result<(), ClientAuthError>; +} + +#[async_trait] +pub trait ServerConnectionLogin { + type Start: LoginStartSession + Send; + + async fn start( + &mut self, + identifier: &[u8], + request: &CredentialRequest, + ) -> Result; + + async fn finish( + &mut self, + session_id: &Uuid, + message: &CredentialFinalization, + ) -> Result<(), ClientAuthError>; +} + +// --- OPAQUE client driver --- + +pub struct OpaqueAuthentication; impl OpaqueAuthentication { - async fn register( - key_data: &AuthenticationData, - server: &mut impl ServerConnectionRegister - ) -> Result<(), String> - { - let mut client_rng = OsRng; - let client_reg_start = ClientRegistration::::start(&mut client_rng, &key_data.secret).expect("error starting registration"); - let server_response = server.start(&key_data.identifier, &client_reg_start.message).await.expect("error getting server response"); - let client_finish = client_reg_start.state.finish(&mut client_rng, &key_data.secret, server_response.response().clone(), ClientRegistrationFinishParameters::default()).expect(""); - server.finish(server_response.session_id(), client_finish.message.serialize()).await.expect("server to finish secret reg without error"); + pub async fn register( + auth: &AuthenticationData, + server: &mut impl ServerConnectionRegister, + ) -> Result<(), ClientAuthError> { + let mut rng = OsRng; + let start = ClientRegistration::::start(&mut rng, &auth.secret) + .map_err(|e| ClientAuthError::Opaque(format!("client reg start: {e:?}")))?; + let server_start = server + .start(&auth.identifier, &start.message) + .await + .map_err(|e| ClientAuthError::Transport(format!("server reg start: {e:?}")))?; + let server_msg = server_start.response().clone(); + let finish = start + .state + .finish( + &mut rng, + &auth.secret, + server_msg, + ClientRegistrationFinishParameters::default(), + ) + .map_err(|e| ClientAuthError::Opaque(format!("client reg finish: {e:?}")))?; + // Assuming PasswordFile is Vec (serialized server-side password file) + let password_file: PasswordFile = finish.message.serialize(); + server + .finish(server_start.session_id(), password_file) + .await + .map_err(|e| ClientAuthError::Transport(format!("server reg finish: {e:?}")))?; Ok(()) } - async fn login( - auth_data: &AuthenticationData, - server: &mut impl ServerConnectionLogin - ) -> Result, String> - { - let mut client_rng = OsRng; - let client_start = ClientLogin::::start(&mut client_rng, &auth_data.secret).expect("client secret key login to start result"); - let server_response = server.start(&auth_data.identifier, &client_start.message).await.expect("server secret key login start response"); - let client_finish = client_start.state.finish(&mut client_rng, &auth_data.secret, server_response.response().clone(), ClientLoginFinishParameters::default()).expect(""); - server.finish(server_response.session_id(), &client_finish.message).await.expect("server secret key login to finish"); - Ok(client_finish.session_key.to_vec()) + pub async fn login( + auth: &AuthenticationData, + server: &mut impl ServerConnectionLogin, + ) -> Result, ClientAuthError> { + let mut rng = OsRng; + let start = ClientLogin::::start(&mut rng, &auth.secret) + .map_err(|e| ClientAuthError::Opaque(format!("client login start: {e:?}")))?; + let server_start = server + .start(&auth.identifier, &start.message) + .await + .map_err(|e| ClientAuthError::Transport(format!("server login start: {e:?}")))?; + let server_msg = server_start.response().clone(); + let finish = start + .state + .finish( + &mut rng, + &auth.secret, + server_msg, + ClientLoginFinishParameters::default(), + ) + .map_err(|e| ClientAuthError::Opaque(format!("client login finish: {e:?}")))?; + server + .finish(server_start.session_id(), &finish.message) + .await + .map_err(|e| ClientAuthError::Transport(format!("server login finish: {e:?}")))?; + Ok(finish.session_key.to_vec()) } } diff --git a/src/lib.rs b/src/lib.rs index 617ad7c..42b4fa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,3 @@ -mod protocol; +mod models; mod client; mod server; diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..11b4a1d --- /dev/null +++ b/src/models.rs @@ -0,0 +1,44 @@ +use opaque_ke::{RegistrationResponse, Ristretto255, TripleDh, ServerSetup, CredentialResponse, RegistrationUploadLen}; +use opaque_ke::keypair::{OprfSeed, PrivateKey}; +use sha2::Sha512; +use opaque_ke::CipherSuite; +use opaque_ke::argon2::Argon2; +use opaque_ke::generic_array::GenericArray; +use uuid::Uuid; +use getset::Getters; + +pub const NONCE_SIZE: usize = 12; +pub const SESSION_KEY_SIZE: usize = 32; +pub const SECRET_KEY_SIZE: usize = 16; + +pub struct NKodeCipherSuite; + +impl CipherSuite for NKodeCipherSuite { + type OprfCs = Ristretto255; + type KeyExchange = TripleDh; + type Ksf = Argon2<'static>; +} + +pub type NKodeServerSetup = ServerSetup, OprfSeed>; + + +#[derive(Debug, Clone, PartialEq, Eq, Getters)] +pub struct RegisterSession { + #[get = "pub"] + response: RegistrationResponse, + #[get = "pub"] + session_id: Uuid +} + +pub type PasswordFile = GenericArray>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LoginSession { + pub response: CredentialResponse, + pub session_id: Uuid +} + +pub type KeyRegisterSession = RegisterSession; + +pub type KeyLoginSession = LoginSession; + diff --git a/src/protocol.rs b/src/protocol.rs deleted file mode 100644 index d216a20..0000000 --- a/src/protocol.rs +++ /dev/null @@ -1,196 +0,0 @@ -use opaque_ke::{CredentialFinalization, CredentialRequest, RegistrationRequest, RegistrationResponse, Ristretto255, TripleDh, ServerLogin, ServerLoginParameters, ServerLoginStartResult, ServerRegistration, ServerSetup, ClientRegistrationStartResult, ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult, ClientLoginStartResult, ClientRegistration, ClientRegistrationFinishParameters, CredentialResponse, RegistrationUploadLen, ServerLoginFinishResult}; -use opaque_ke::errors::ProtocolError; -use opaque_ke::keypair::{OprfSeed, PrivateKey}; -use sha2::Sha512; -use opaque_ke::CipherSuite; -use opaque_ke::argon2::Argon2; -use opaque_ke::generic_array::GenericArray; -use uuid::Uuid; -use opaque_ke::rand::rngs::OsRng; -use std::marker::PhantomData; -use getset::Getters; - -pub const NONCE_SIZE: usize = 12; -pub const SESSION_KEY_SIZE: usize = 32; -pub const SECRET_KEY_SIZE: usize = 16; - -pub struct NKodeCipherSuite; - -impl CipherSuite for NKodeCipherSuite { - type OprfCs = Ristretto255; - type KeyExchange = TripleDh; - type Ksf = Argon2<'static>; -} - -pub type NKodeServerSetup = ServerSetup, OprfSeed>; - - -#[derive(Debug, Clone, PartialEq, Eq, Getters)] -pub struct RegisterSession { - #[get = "pub"] - response: RegistrationResponse, - #[get = "pub"] - session_id: Uuid -} - -pub type PasswordFile = GenericArray>; - - -pub async fn register_secret_key( - email: &String, - key: &[u8; SECRET_KEY_SIZE], - server: &mut NKodeServer -) -> Result<(),String> -where - R: Repo, - S: Sessions -{ - let mut client_rng = OsRng; - let client_reg_start = ClientRegistration::::start(&mut client_rng, key).expect("error starting registration"); - let server_response = server.start(email, &client_reg_start.message).await.expect("error getting server response"); - let client_finish = client_reg_start.state.finish(&mut client_rng, key, server_response.response, ClientRegistrationFinishParameters::default()).expect(""); - server.finish(&server_response.session_id, &client_finish.message.serialize()).await.expect("server to finish secret reg without error"); - Ok(()) -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LoginSession { - pub response: CredentialResponse, - pub session_id: Uuid -} - - -pub async fn login_secret_key(email: &String, key: &[u8; SECRET_KEY_SIZE], server: &mut NKodeServer) -> Result, String> -where - R: Repo, - S: Sessions -{ - let mut client_rng = OsRng; - let client_start = ClientLogin::::start(&mut client_rng, key).expect("client secret key login to start result"); - let server_response = server.start(email, client_start.message).await.expect("server secret key login start response"); - let client_finish = client_start.state.finish(&mut client_rng, key,server_response.response,ClientLoginFinishParameters::default()).expect(""); - server.finish(&server_response.session_id, client_finish.message).await.expect("server secret key login to finish"); - Ok(client_finish.session_key.to_vec()) -} - -pub trait Repo {} -pub trait Sessions {} - -pub struct NKodeServer { - server_setup: NKodeServerSetup, - repo: R, - session: S, - _state: PhantomData -} - -pub type KeyRegisterSession = RegisterSession; -pub struct KeyRegister; -pub struct KeyLoginI; -pub struct CodeRegister; - -impl NKodeServer -where - R: Repo, - S: Sessions -{ - pub fn new(server_setup: NKodeServerSetup, repo: R, sessions: S) -> Self { - todo!() - } - - pub async fn start( - &mut self, - email: &String, - message: &RegistrationRequest - ) -> Result { - todo!() - } - - pub async fn finish( - &mut self, - session_id: &Uuid, - password_file: &PasswordFile - ) -> Result, ProtocolError> { - todo!() - } -} - -pub type KeyLoginPhaseISession = LoginSession; - -impl NKodeServer -where - R: Repo, - S: Sessions -{ - pub async fn start( - &mut self, - email: &String, - request_bytes:CredentialRequest - ) -> Result { - todo!() - } - - pub async fn finish( - &mut self, - session_id: &Uuid, - message: CredentialFinalization - ) -> Result, ProtocolError> { - todo!() - } -} - -pub type CodeRegisterSession = RegisterSession; -pub struct CodeLogin; - -impl NKodeServer -where - R: Repo, - S: Sessions -{ - pub fn new(server_setup: NKodeServerSetup) -> Self { - todo!() - } - - pub async fn start( - &mut self, - email: &String, - message: &RegistrationRequest - ) -> Result { - todo!() - } - - pub async fn finish( - &mut self, - session_id: &Uuid, - password_file: &PasswordFile) -> Result, ProtocolError> { - todo!() - } -} - -pub type CodeLoginSession = LoginSession; - -impl NKodeServer -where - R: Repo, - S: Sessions -{ - pub async fn start( - &mut self, - email: &String, - request_bytes:CredentialRequest - ) -> Result { - todo!() - } - - pub async fn finish( - &mut self, - session_id: &Uuid, - message:CredentialFinalization - ) -> Result { - todo!() - } -} - -pub struct LoggedIn { - session_key: Vec, - session_id: Uuid, -} diff --git a/src/server.rs b/src/server.rs index a74b0cf..a7d34fb 100644 --- a/src/server.rs +++ b/src/server.rs @@ -11,7 +11,7 @@ use opaque_ke::{ use uuid::Uuid; // --- Your crate types (as referenced in your snippet) --- -use crate::protocol::{LoginSession, NKodeCipherSuite, NKodeServerSetup, PasswordFile}; +use crate::models::{LoginSession, NKodeCipherSuite, NKodeServerSetup, PasswordFile}; // ---------------- Errors ----------------