test client server opaque
This commit is contained in:
91
Cargo.lock
generated
91
Cargo.lock
generated
@@ -273,15 +273,15 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getset"
|
name = "getrandom"
|
||||||
version = "0.1.6"
|
version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912"
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error2",
|
"cfg-if",
|
||||||
"proc-macro2",
|
"libc",
|
||||||
"quote",
|
"r-efi",
|
||||||
"syn",
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -334,10 +334,10 @@ name = "nkode-protocol"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"getset",
|
|
||||||
"opaque-ke",
|
"opaque-ke",
|
||||||
"rand",
|
"rand",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -362,7 +362,7 @@ dependencies = [
|
|||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"getrandom",
|
"getrandom 0.2.16",
|
||||||
"hkdf",
|
"hkdf",
|
||||||
"hmac",
|
"hmac",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -383,6 +383,12 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkcs8"
|
name = "pkcs8"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@@ -402,28 +408,6 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error-attr2"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error2"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error-attr2",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.103"
|
version = "1.0.103"
|
||||||
@@ -442,6 +426,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -469,7 +459,7 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -596,6 +586,27 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
@@ -614,6 +625,7 @@ version = "1.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
@@ -649,6 +661,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.1+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.105"
|
version = "0.2.105"
|
||||||
@@ -694,6 +715,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.31"
|
version = "0.8.31"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ edition = "2024"
|
|||||||
opaque-ke = { version = "4.0.1",default-features = true, features = [ "std","argon2"] }
|
opaque-ke = { version = "4.0.1",default-features = true, features = [ "std","argon2"] }
|
||||||
rand = { version = "0.8.5", features = ["std"] }
|
rand = { version = "0.8.5", features = ["std"] }
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.9"
|
||||||
uuid = "1.19.0"
|
|
||||||
getset = "0.1.6"
|
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
|
uuid = { version = "1.19.0", features = ["v4"] }
|
||||||
|
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "sync"] }
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,13 @@ use opaque_ke::rand::rngs::OsRng;
|
|||||||
use opaque_ke::{
|
use opaque_ke::{
|
||||||
ClientLogin, ClientLoginFinishParameters,
|
ClientLogin, ClientLoginFinishParameters,
|
||||||
ClientRegistration, ClientRegistrationFinishParameters,
|
ClientRegistration, ClientRegistrationFinishParameters,
|
||||||
CredentialFinalization, CredentialRequest, CredentialResponse,
|
CredentialFinalization, CredentialRequest,
|
||||||
RegistrationRequest, RegistrationResponse,
|
RegistrationRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::models::{
|
use crate::models::{RegisterSession, LoginSession, NKodeCipherSuite, PasswordFile};
|
||||||
NKodeCipherSuite,
|
|
||||||
PasswordFile,
|
|
||||||
KeyLoginSession,
|
|
||||||
KeyRegisterSession,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClientAuthError {
|
pub enum ClientAuthError {
|
||||||
@@ -22,8 +19,6 @@ pub enum ClientAuthError {
|
|||||||
Transport(String),
|
Transport(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Normalize auth inputs to (identifier, secret-bytes) ---
|
|
||||||
|
|
||||||
pub struct AuthenticationData {
|
pub struct AuthenticationData {
|
||||||
pub identifier: Vec<u8>,
|
pub identifier: Vec<u8>,
|
||||||
pub secret: Vec<u8>,
|
pub secret: Vec<u8>,
|
||||||
@@ -50,40 +45,13 @@ impl AuthenticationData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Small adapter traits so server can return any “session wrapper” type ---
|
|
||||||
|
|
||||||
pub trait RegStartSession {
|
|
||||||
fn session_id(&self) -> &Uuid;
|
|
||||||
fn response(&self) -> &RegistrationResponse<NKodeCipherSuite>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait LoginStartSession {
|
|
||||||
fn session_id(&self) -> &Uuid;
|
|
||||||
fn response(&self) -> &CredentialResponse<NKodeCipherSuite>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<NKodeCipherSuite> { self.response() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LoginStartSession for KeyLoginSession {
|
|
||||||
fn session_id(&self) -> &Uuid { self.session_id() }
|
|
||||||
fn response(&self) -> &CredentialResponse<NKodeCipherSuite> { self.response() }
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Server connection traits: generic over returned session wrapper types ---
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ServerConnectionRegister {
|
pub trait ServerConnectionRegister {
|
||||||
type Start: RegStartSession + Send;
|
|
||||||
|
|
||||||
async fn start(
|
async fn start(
|
||||||
&mut self,
|
&mut self,
|
||||||
identifier: &[u8],
|
identifier: &[u8],
|
||||||
message: &RegistrationRequest<NKodeCipherSuite>,
|
message: &RegistrationRequest<NKodeCipherSuite>,
|
||||||
) -> Result<Self::Start, ClientAuthError>;
|
) -> Result<RegisterSession, ClientAuthError>;
|
||||||
|
|
||||||
async fn finish(
|
async fn finish(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -94,13 +62,11 @@ pub trait ServerConnectionRegister {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ServerConnectionLogin {
|
pub trait ServerConnectionLogin {
|
||||||
type Start: LoginStartSession + Send;
|
|
||||||
|
|
||||||
async fn start(
|
async fn start(
|
||||||
&mut self,
|
&mut self,
|
||||||
identifier: &[u8],
|
identifier: &[u8],
|
||||||
request: &CredentialRequest<NKodeCipherSuite>,
|
request: &CredentialRequest<NKodeCipherSuite>,
|
||||||
) -> Result<Self::Start, ClientAuthError>;
|
) -> Result<LoginSession, ClientAuthError>;
|
||||||
|
|
||||||
async fn finish(
|
async fn finish(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -125,7 +91,7 @@ impl OpaqueAuthentication {
|
|||||||
.start(&auth.identifier, &start.message)
|
.start(&auth.identifier, &start.message)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ClientAuthError::Transport(format!("server reg start: {e:?}")))?;
|
.map_err(|e| ClientAuthError::Transport(format!("server reg start: {e:?}")))?;
|
||||||
let server_msg = server_start.response().clone();
|
let server_msg = server_start.response;
|
||||||
let finish = start
|
let finish = start
|
||||||
.state
|
.state
|
||||||
.finish(
|
.finish(
|
||||||
@@ -135,10 +101,9 @@ impl OpaqueAuthentication {
|
|||||||
ClientRegistrationFinishParameters::default(),
|
ClientRegistrationFinishParameters::default(),
|
||||||
)
|
)
|
||||||
.map_err(|e| ClientAuthError::Opaque(format!("client reg finish: {e:?}")))?;
|
.map_err(|e| ClientAuthError::Opaque(format!("client reg finish: {e:?}")))?;
|
||||||
// Assuming PasswordFile is Vec<u8> (serialized server-side password file)
|
|
||||||
let password_file: PasswordFile = finish.message.serialize();
|
let password_file: PasswordFile = finish.message.serialize();
|
||||||
server
|
server
|
||||||
.finish(server_start.session_id(), password_file)
|
.finish(&server_start.session_id, password_file)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ClientAuthError::Transport(format!("server reg finish: {e:?}")))?;
|
.map_err(|e| ClientAuthError::Transport(format!("server reg finish: {e:?}")))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -155,7 +120,7 @@ impl OpaqueAuthentication {
|
|||||||
.start(&auth.identifier, &start.message)
|
.start(&auth.identifier, &start.message)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ClientAuthError::Transport(format!("server login start: {e:?}")))?;
|
.map_err(|e| ClientAuthError::Transport(format!("server login start: {e:?}")))?;
|
||||||
let server_msg = server_start.response().clone();
|
let server_msg = server_start.response.clone();
|
||||||
let finish = start
|
let finish = start
|
||||||
.state
|
.state
|
||||||
.finish(
|
.finish(
|
||||||
@@ -166,7 +131,7 @@ impl OpaqueAuthentication {
|
|||||||
)
|
)
|
||||||
.map_err(|e| ClientAuthError::Opaque(format!("client login finish: {e:?}")))?;
|
.map_err(|e| ClientAuthError::Opaque(format!("client login finish: {e:?}")))?;
|
||||||
server
|
server
|
||||||
.finish(server_start.session_id(), &finish.message)
|
.finish(&server_start.session_id, &finish.message)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ClientAuthError::Transport(format!("server login finish: {e:?}")))?;
|
.map_err(|e| ClientAuthError::Transport(format!("server login finish: {e:?}")))?;
|
||||||
Ok(finish.session_key.to_vec())
|
Ok(finish.session_key.to_vec())
|
||||||
|
|||||||
87
src/in_memory_auth_repo.rs
Normal file
87
src/in_memory_auth_repo.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::models::PasswordFile;
|
||||||
|
use crate::server::{AuthRepo, AuthRepoError};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct InMemoryAuthRepo {
|
||||||
|
key_entries: HashMap<KeyID, PasswordFile>,
|
||||||
|
code_entries: HashMap<CodeID, PasswordFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct KeyID(Vec<u8>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct CodeID(Vec<u8>);
|
||||||
|
|
||||||
|
impl InMemoryAuthRepo {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_exists(&self, identifier: &CodeID) -> bool {
|
||||||
|
self.code_entries.contains_key(identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_exists(&self, identifier: &KeyID) -> bool {
|
||||||
|
self.key_entries.contains_key(identifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthRepo for InMemoryAuthRepo {
|
||||||
|
fn new_key(
|
||||||
|
&mut self,
|
||||||
|
identifier: &[u8],
|
||||||
|
password_file: PasswordFile,
|
||||||
|
) -> Result<(), AuthRepoError> {
|
||||||
|
if self.key_exists(&KeyID(identifier.to_vec())) {
|
||||||
|
return Err(AuthRepoError::UserExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.key_entries
|
||||||
|
.insert(KeyID(identifier.to_vec()), password_file);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_code(
|
||||||
|
&mut self,
|
||||||
|
identifier: &[u8],
|
||||||
|
password_file: PasswordFile,
|
||||||
|
) -> Result<(), AuthRepoError> {
|
||||||
|
if !self.has_key(identifier) {
|
||||||
|
return Err(AuthRepoError::KeyNotRegistered);
|
||||||
|
}
|
||||||
|
if self.code_exists(&CodeID(identifier.to_vec())) {
|
||||||
|
return Err(AuthRepoError::UserExists);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.code_entries
|
||||||
|
.insert(CodeID(identifier.to_vec()), password_file);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_code(&self, identifier: &[u8]) -> bool {
|
||||||
|
self.code_entries
|
||||||
|
.contains_key(&CodeID(identifier.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_key(&self, identifier: &[u8]) -> bool {
|
||||||
|
self.key_entries
|
||||||
|
.contains_key(&KeyID(identifier.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError> {
|
||||||
|
self.key_entries
|
||||||
|
.get(&KeyID(identifier.to_vec()))
|
||||||
|
.cloned()
|
||||||
|
.ok_or(AuthRepoError::KeyNotRegistered)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_code_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError> {
|
||||||
|
self.code_entries
|
||||||
|
.get(&CodeID(identifier.to_vec()))
|
||||||
|
.cloned()
|
||||||
|
.ok_or(AuthRepoError::CodeNotRegistered)
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/in_memory_auth_session.rs
Normal file
101
src/in_memory_auth_session.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::server::{RegCache, LoginCache, AuthSession};
|
||||||
|
use opaque_ke::{ServerLogin};
|
||||||
|
use crate::models::NKodeCipherSuite;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct InMemoryAuthSession {
|
||||||
|
reg_sessions: HashMap<Uuid, RegCache>,
|
||||||
|
login_sessions: HashMap<Uuid, LoginCache>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InMemoryAuthSession {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthSession for InMemoryAuthSession {
|
||||||
|
fn new_reg_session(&mut 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) {
|
||||||
|
return Err("session_id collision".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reg_sessions.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> {
|
||||||
|
self.reg_sessions
|
||||||
|
.get(session_id)
|
||||||
|
.map(|c| RegCache {
|
||||||
|
session_id: c.session_id,
|
||||||
|
identifier: c.identifier.clone(),
|
||||||
|
})
|
||||||
|
.ok_or_else(|| "registration session not found".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_reg_session(&mut self, session_id: &Uuid) -> Result<(), String> {
|
||||||
|
self.reg_sessions
|
||||||
|
.remove(session_id)
|
||||||
|
.map(|_| ())
|
||||||
|
.ok_or_else(|| "registration session not found".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_login_session(
|
||||||
|
&mut self,
|
||||||
|
identifier: &[u8],
|
||||||
|
server_login: ServerLogin<NKodeCipherSuite>,
|
||||||
|
) -> Result<LoginCache, String> {
|
||||||
|
let cache = LoginCache {
|
||||||
|
session_id: Uuid::new_v4(),
|
||||||
|
identifiers: identifier.to_vec(),
|
||||||
|
server_login,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.login_sessions.contains_key(&cache.session_id) {
|
||||||
|
return Err("session_id collision".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.login_sessions.insert(
|
||||||
|
cache.session_id,
|
||||||
|
LoginCache {
|
||||||
|
session_id: cache.session_id,
|
||||||
|
identifiers: cache.identifiers.clone(),
|
||||||
|
// move is fine; we already moved into cache, so clone to keep both:
|
||||||
|
server_login: cache.server_login.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_login_session(&self, session_id: &Uuid) -> Result<LoginCache, String> {
|
||||||
|
self.login_sessions
|
||||||
|
.get(session_id)
|
||||||
|
.map(|c| LoginCache {
|
||||||
|
session_id: c.session_id,
|
||||||
|
identifiers: c.identifiers.clone(),
|
||||||
|
server_login: c.server_login.clone(),
|
||||||
|
})
|
||||||
|
.ok_or_else(|| "login session not found".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_login_session(&mut self, session_id: &Uuid) -> Result<(), String> {
|
||||||
|
self.login_sessions
|
||||||
|
.remove(session_id)
|
||||||
|
.map(|_| ())
|
||||||
|
.ok_or_else(|| "login session not found".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
163
src/in_memory_transport.rs
Normal file
163
src/in_memory_transport.rs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use opaque_ke::{CredentialFinalization, CredentialRequest, RegistrationRequest};
|
||||||
|
use crate::client::{ClientAuthError, ServerConnectionLogin, ServerConnectionRegister};
|
||||||
|
use crate::models::{LoginSession, RegisterSession, NKodeCipherSuite, NKodeServerSetup, PasswordFile};
|
||||||
|
use crate::server::{OpaqueAuth, CredKind, Key, Code};
|
||||||
|
use crate::in_memory_auth_repo::InMemoryAuthRepo;
|
||||||
|
use crate::in_memory_auth_session::InMemoryAuthSession;
|
||||||
|
|
||||||
|
pub struct InMemoryServer<K: CredKind> {
|
||||||
|
auth: OpaqueAuth<InMemoryAuthRepo, InMemoryAuthSession>,
|
||||||
|
_kind: PhantomData<K>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: CredKind> InMemoryServer<K> {
|
||||||
|
pub fn new(server_setup: NKodeServerSetup) -> Self {
|
||||||
|
Self {
|
||||||
|
auth: OpaqueAuth::new(server_setup, InMemoryAuthRepo::new(), InMemoryAuthSession::new()),
|
||||||
|
_kind: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience aliases
|
||||||
|
pub type InMemoryKeyServer = InMemoryServer<Key>;
|
||||||
|
pub type InMemoryCodeServer = InMemoryServer<Code>;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<K> ServerConnectionRegister for InMemoryServer<K>
|
||||||
|
where
|
||||||
|
K: CredKind + Send + Sync,
|
||||||
|
{
|
||||||
|
async fn start(
|
||||||
|
&mut self,
|
||||||
|
identifier: &[u8],
|
||||||
|
message: &RegistrationRequest<NKodeCipherSuite>,
|
||||||
|
) -> Result<RegisterSession, ClientAuthError> {
|
||||||
|
// Server API takes ownership; client trait gives us a reference.
|
||||||
|
// opaque-ke request types are typically Clone; if not, you'll need to adjust signatures.
|
||||||
|
self.auth
|
||||||
|
.reg_start::<K>(identifier, message.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| ClientAuthError::Transport(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn finish(
|
||||||
|
&mut self,
|
||||||
|
session_id: &Uuid,
|
||||||
|
password_file: PasswordFile,
|
||||||
|
) -> Result<(), ClientAuthError> {
|
||||||
|
self.auth
|
||||||
|
.reg_finish::<K>(session_id, password_file)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ClientAuthError::Transport(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<K> ServerConnectionLogin for InMemoryServer<K>
|
||||||
|
where
|
||||||
|
K: CredKind + Send + Sync,
|
||||||
|
{
|
||||||
|
async fn start(
|
||||||
|
&mut self,
|
||||||
|
identifier: &[u8],
|
||||||
|
request: &CredentialRequest<NKodeCipherSuite>,
|
||||||
|
) -> Result<LoginSession, ClientAuthError> {
|
||||||
|
self.auth
|
||||||
|
.login_start::<K>(identifier, request.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| ClientAuthError::Transport(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn finish(
|
||||||
|
&mut self,
|
||||||
|
session_id: &Uuid,
|
||||||
|
message: &CredentialFinalization<NKodeCipherSuite>,
|
||||||
|
) -> Result<(), ClientAuthError> {
|
||||||
|
// Server computes its own session key too; we just need it to validate and complete.
|
||||||
|
let _server_session_key = self
|
||||||
|
.auth
|
||||||
|
.login_finish::<K>(session_id, message.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| ClientAuthError::Transport(e))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SharedServer<K> {
|
||||||
|
inner: Arc<Mutex<OpaqueAuth<InMemoryAuthRepo, InMemoryAuthSession>>>,
|
||||||
|
_k: PhantomData<K>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K> SharedServer<K> {
|
||||||
|
pub fn new(inner: Arc<Mutex<OpaqueAuth<InMemoryAuthRepo, InMemoryAuthSession>>>) -> Self {
|
||||||
|
Self { inner, _k: PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<K> ServerConnectionRegister for SharedServer<K>
|
||||||
|
where
|
||||||
|
K: CredKind + Send + Sync,
|
||||||
|
{
|
||||||
|
async fn start(
|
||||||
|
&mut self,
|
||||||
|
identifier: &[u8],
|
||||||
|
message: &RegistrationRequest<NKodeCipherSuite>,
|
||||||
|
) -> Result<RegisterSession, ClientAuthError> {
|
||||||
|
let mut guard = self.inner.lock().await;
|
||||||
|
guard
|
||||||
|
.reg_start::<K>(identifier, message.clone())
|
||||||
|
.await
|
||||||
|
.map_err(ClientAuthError::Transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn finish(
|
||||||
|
&mut self,
|
||||||
|
session_id: &Uuid,
|
||||||
|
password_file: PasswordFile,
|
||||||
|
) -> Result<(), ClientAuthError> {
|
||||||
|
let mut guard = self.inner.lock().await;
|
||||||
|
guard
|
||||||
|
.reg_finish::<K>(session_id, password_file)
|
||||||
|
.await
|
||||||
|
.map_err(ClientAuthError::Transport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<K> ServerConnectionLogin for SharedServer<K>
|
||||||
|
where
|
||||||
|
K: CredKind + Send + Sync,
|
||||||
|
{
|
||||||
|
async fn start(
|
||||||
|
&mut self,
|
||||||
|
identifier: &[u8],
|
||||||
|
request: &CredentialRequest<NKodeCipherSuite>,
|
||||||
|
) -> Result<LoginSession, ClientAuthError> {
|
||||||
|
let mut guard = self.inner.lock().await;
|
||||||
|
guard
|
||||||
|
.login_start::<K>(identifier, request.clone())
|
||||||
|
.await
|
||||||
|
.map_err(ClientAuthError::Transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn finish(
|
||||||
|
&mut self,
|
||||||
|
session_id: &Uuid,
|
||||||
|
message: &CredentialFinalization<NKodeCipherSuite>,
|
||||||
|
) -> Result<(), ClientAuthError> {
|
||||||
|
let mut guard = self.inner.lock().await;
|
||||||
|
let _ = guard
|
||||||
|
.login_finish::<K>(session_id, message.clone())
|
||||||
|
.await
|
||||||
|
.map_err(ClientAuthError::Transport)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
mod models;
|
pub mod models;
|
||||||
mod client;
|
pub mod client;
|
||||||
mod server;
|
pub mod server;
|
||||||
|
pub mod in_memory_auth_repo;
|
||||||
|
pub mod in_memory_auth_session;
|
||||||
|
pub mod in_memory_transport;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use opaque_ke::{RegistrationResponse, Ristretto255, TripleDh, ServerSetup, CredentialResponse, RegistrationUploadLen};
|
use opaque_ke::{Ristretto255, TripleDh, ServerSetup, CredentialResponse, RegistrationUploadLen, RegistrationResponse};
|
||||||
use opaque_ke::keypair::{OprfSeed, PrivateKey};
|
use opaque_ke::keypair::{OprfSeed, PrivateKey};
|
||||||
use sha2::Sha512;
|
use sha2::Sha512;
|
||||||
use opaque_ke::CipherSuite;
|
use opaque_ke::CipherSuite;
|
||||||
use opaque_ke::argon2::Argon2;
|
use opaque_ke::argon2::Argon2;
|
||||||
use opaque_ke::generic_array::GenericArray;
|
use opaque_ke::generic_array::GenericArray;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use getset::Getters;
|
|
||||||
|
|
||||||
pub const NONCE_SIZE: usize = 12;
|
pub const NONCE_SIZE: usize = 12;
|
||||||
pub const SESSION_KEY_SIZE: usize = 32;
|
pub const SESSION_KEY_SIZE: usize = 32;
|
||||||
@@ -22,14 +21,6 @@ impl CipherSuite for NKodeCipherSuite {
|
|||||||
pub type NKodeServerSetup = ServerSetup<NKodeCipherSuite, PrivateKey<Ristretto255>, OprfSeed<Sha512>>;
|
pub type NKodeServerSetup = ServerSetup<NKodeCipherSuite, PrivateKey<Ristretto255>, OprfSeed<Sha512>>;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Getters)]
|
|
||||||
pub struct RegisterSession {
|
|
||||||
#[get = "pub"]
|
|
||||||
response: RegistrationResponse<NKodeCipherSuite>,
|
|
||||||
#[get = "pub"]
|
|
||||||
session_id: Uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type PasswordFile = GenericArray<u8, RegistrationUploadLen<NKodeCipherSuite>>;
|
pub type PasswordFile = GenericArray<u8, RegistrationUploadLen<NKodeCipherSuite>>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -38,7 +29,8 @@ pub struct LoginSession {
|
|||||||
pub session_id: Uuid
|
pub session_id: Uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type KeyRegisterSession = RegisterSession;
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RegisterSession {
|
||||||
pub type KeyLoginSession = LoginSession;
|
pub response: RegistrationResponse<NKodeCipherSuite>,
|
||||||
|
pub session_id: Uuid
|
||||||
|
}
|
||||||
|
|||||||
182
src/server.rs
182
src/server.rs
@@ -1,30 +1,19 @@
|
|||||||
//! Single-file example: remove Key-vs-Code duplication by introducing a CredKind trait
|
|
||||||
//! and implementing the OPAQUE flows once for Registration<K> and Login<K> states.
|
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use opaque_ke::{
|
use opaque_ke::{
|
||||||
rand::rngs::OsRng, CredentialFinalization, CredentialRequest,
|
rand::rngs::OsRng, CredentialFinalization, CredentialRequest,
|
||||||
RegistrationRequest, RegistrationResponse, ServerLogin, ServerLoginParameters,
|
RegistrationRequest, ServerLogin, ServerLoginParameters,
|
||||||
ServerRegistration,
|
ServerRegistration,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use crate::models::{RegisterSession, LoginSession, NKodeCipherSuite, NKodeServerSetup, PasswordFile};
|
||||||
// --- Your crate types (as referenced in your snippet) ---
|
|
||||||
use crate::models::{LoginSession, NKodeCipherSuite, NKodeServerSetup, PasswordFile};
|
|
||||||
|
|
||||||
// ---------------- Errors ----------------
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum AuthRepoError {
|
pub enum AuthRepoError {
|
||||||
UserExists,
|
UserExists,
|
||||||
KeyNotRegistered,
|
KeyNotRegistered,
|
||||||
CodeNotRegistered,
|
CodeNotRegistered,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- Repo abstraction ----------------
|
pub trait AuthRepo {
|
||||||
|
|
||||||
trait AuthRepo {
|
|
||||||
fn new_key(&mut self, identifier: &[u8], password_file: PasswordFile) -> Result<(), AuthRepoError>;
|
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 new_code(&mut self, identifier: &[u8], password_file: PasswordFile) -> Result<(), AuthRepoError>;
|
||||||
|
|
||||||
@@ -35,21 +24,18 @@ trait AuthRepo {
|
|||||||
fn get_code_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError>;
|
fn get_code_passcode_file(&self, identifier: &[u8]) -> Result<PasswordFile, AuthRepoError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- Session abstraction ----------------
|
pub struct RegCache {
|
||||||
|
pub session_id: Uuid,
|
||||||
#[derive(Clone)]
|
pub identifier: Vec<u8>,
|
||||||
struct RegCache {
|
|
||||||
session_id: Uuid,
|
|
||||||
identifier: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LoginCache {
|
pub struct LoginCache {
|
||||||
session_id: Uuid,
|
pub session_id: Uuid,
|
||||||
identifiers: Vec<u8>,
|
pub identifiers: Vec<u8>,
|
||||||
server_login: ServerLogin<NKodeCipherSuite>,
|
pub server_login: ServerLogin<NKodeCipherSuite>,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait AuthSession {
|
pub trait AuthSession {
|
||||||
fn new_reg_session(&mut self, identifier: &[u8]) -> Result<RegCache, String>;
|
fn new_reg_session(&mut self, identifier: &[u8]) -> Result<RegCache, String>;
|
||||||
fn get_reg_session(&self, session_id: &Uuid) -> 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>;
|
fn clear_reg_session(&mut self, session_id: &Uuid) -> Result<(), String>;
|
||||||
@@ -63,40 +49,18 @@ trait AuthSession {
|
|||||||
fn clear_login_session(&mut self, session_id: &Uuid) -> Result<(), String>;
|
fn clear_login_session(&mut self, session_id: &Uuid) -> Result<(), String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- Core OpaqueAuth struct ----------------
|
|
||||||
|
|
||||||
struct OpaqueAuth<State, R: AuthRepo, S: AuthSession> {
|
pub trait CredKind {
|
||||||
server_setup: NKodeServerSetup,
|
|
||||||
user_repo: R,
|
|
||||||
session: S,
|
|
||||||
_state: PhantomData<State>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<State, R, S> OpaqueAuth<State, R, S>
|
|
||||||
where
|
|
||||||
R: AuthRepo,
|
|
||||||
S: AuthSession,
|
|
||||||
{
|
|
||||||
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<R: AuthRepo>(repo: &R, id: &[u8]) -> bool;
|
fn has<R: AuthRepo>(repo: &R, id: &[u8]) -> bool;
|
||||||
fn get_pf<R: AuthRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError>;
|
fn get_pf<R: AuthRepo>(repo: &R, id: &[u8]) -> Result<PasswordFile, AuthRepoError>;
|
||||||
fn put_pf<R: AuthRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>;
|
fn put_pf<R: AuthRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError>;
|
||||||
|
fn prereq_for_register<R: AuthRepo>(_repo: &R, _id: &[u8]) -> Result<(), AuthRepoError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Key;
|
pub struct Key;
|
||||||
struct Code;
|
pub struct Code;
|
||||||
|
|
||||||
impl CredKind for Key {
|
impl CredKind for Key {
|
||||||
fn has<R: AuthRepo>(repo: &R, id: &[u8]) -> bool {
|
fn has<R: AuthRepo>(repo: &R, id: &[u8]) -> bool {
|
||||||
@@ -120,106 +84,74 @@ impl CredKind for Code {
|
|||||||
fn put_pf<R: AuthRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> {
|
fn put_pf<R: AuthRepo>(repo: &mut R, id: &[u8], pf: PasswordFile) -> Result<(), AuthRepoError> {
|
||||||
repo.new_code(id, pf)
|
repo.new_code(id, pf)
|
||||||
}
|
}
|
||||||
|
fn prereq_for_register<R: AuthRepo>(repo: &R, id: &[u8]) -> Result<(), AuthRepoError> {
|
||||||
|
if repo.has_key(id) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(AuthRepoError::KeyNotRegistered)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- State markers ----------------
|
pub struct OpaqueAuth<R: AuthRepo, S: AuthSession> {
|
||||||
|
server_setup: NKodeServerSetup,
|
||||||
struct Registration<K>(PhantomData<K>);
|
user_repo: R,
|
||||||
struct Login<K>(PhantomData<K>);
|
session: S,
|
||||||
|
|
||||||
// Optional: aliases to make call sites read nicely
|
|
||||||
type KeyAuthRegistration<R, S> = OpaqueAuth<Registration<Key>, R, S>;
|
|
||||||
type CodeAuthRegistration<R, S> = OpaqueAuth<Registration<Code>, R, S>;
|
|
||||||
type KeyAuthLogin<R, S> = OpaqueAuth<Login<Key>, R, S>;
|
|
||||||
type CodeAuthLogin<R, S> = OpaqueAuth<Login<Code>, R, S>;
|
|
||||||
|
|
||||||
// ---------------- Return types ----------------
|
|
||||||
|
|
||||||
struct RegSession {
|
|
||||||
session_id: Uuid,
|
|
||||||
response: RegistrationResponse<NKodeCipherSuite>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: you already have crate::protocol::LoginSession, so we use that.
|
impl<R: AuthRepo, S: AuthSession> OpaqueAuth<R, S> {
|
||||||
// If you want it here, uncomment below and remove crate import.
|
pub fn new(server_setup: NKodeServerSetup, user_repo: R, session: S) -> Self {
|
||||||
// struct LoginSession {
|
Self { server_setup, user_repo, session }
|
||||||
// session_id: Uuid,
|
}
|
||||||
// response: CredentialResponse<NKodeCipherSuite>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ---------------- Shared registration flow ----------------
|
pub async fn reg_start<K: CredKind>(
|
||||||
|
|
||||||
impl<K, R, S> OpaqueAuth<Registration<K>, R, S>
|
|
||||||
where
|
|
||||||
K: CredKind,
|
|
||||||
R: AuthRepo,
|
|
||||||
S: AuthSession,
|
|
||||||
{
|
|
||||||
pub async fn start(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
identifier: &[u8],
|
identifier: &[u8],
|
||||||
request: RegistrationRequest<NKodeCipherSuite>,
|
request: RegistrationRequest<NKodeCipherSuite>,
|
||||||
) -> Result<RegSession, String> {
|
) -> Result<RegisterSession, String> {
|
||||||
|
K::prereq_for_register(&self.user_repo, identifier)
|
||||||
|
.map_err(|e| format!("registration prereq failed: {e:?}"))?;
|
||||||
let start = ServerRegistration::<NKodeCipherSuite>::start(
|
let start = ServerRegistration::<NKodeCipherSuite>::start(
|
||||||
&self.server_setup,
|
&self.server_setup,
|
||||||
request,
|
request,
|
||||||
identifier,
|
identifier,
|
||||||
)
|
).map_err(|e| format!("opaque reg start: {e:?}"))?;
|
||||||
.map_err(|e| format!("opaque reg start: {e:?}"))?;
|
let cache = self.session
|
||||||
|
|
||||||
let cache = self
|
|
||||||
.session
|
|
||||||
.new_reg_session(identifier)
|
.new_reg_session(identifier)
|
||||||
.map_err(|e| format!("reg cache: {e}"))?;
|
.map_err(|e| format!("reg cache: {e}"))?;
|
||||||
|
|
||||||
Ok(RegSession {
|
Ok(RegisterSession { session_id: cache.session_id, response: start.message })
|
||||||
session_id: cache.session_id,
|
|
||||||
response: start.message,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn finish(
|
pub async fn reg_finish<K: CredKind>(
|
||||||
&mut self,
|
&mut self,
|
||||||
session_id: &Uuid,
|
session_id: &Uuid,
|
||||||
password_file: PasswordFile,
|
password_file: PasswordFile,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let sess = self
|
let sess = self.session
|
||||||
.session
|
|
||||||
.get_reg_session(session_id)
|
.get_reg_session(session_id)
|
||||||
.map_err(|e| format!("get reg session: {e}"))?;
|
.map_err(|e| format!("get reg session: {e}"))?;
|
||||||
|
K::prereq_for_register(&self.user_repo, sess.identifier.as_slice())
|
||||||
|
.map_err(|e| format!("registration prereq failed: {e:?}"))?;
|
||||||
K::put_pf(&mut self.user_repo, sess.identifier.as_slice(), password_file)
|
K::put_pf(&mut self.user_repo, sess.identifier.as_slice(), password_file)
|
||||||
.map_err(|e| format!("repo write: {e:?}"))?;
|
.map_err(|e| format!("repo write: {e:?}"))?;
|
||||||
|
|
||||||
self.session
|
self.session
|
||||||
.clear_reg_session(session_id)
|
.clear_reg_session(session_id)
|
||||||
.map_err(|e| format!("clear reg session: {e}"))
|
.map_err(|e| format!("clear reg session: {e}"))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------- Shared login flow ----------------
|
pub async fn login_start<K: CredKind>(
|
||||||
|
|
||||||
impl<K, R, S> OpaqueAuth<Login<K>, R, S>
|
|
||||||
where
|
|
||||||
K: CredKind,
|
|
||||||
R: AuthRepo,
|
|
||||||
S: AuthSession,
|
|
||||||
{
|
|
||||||
pub async fn start(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
identifier: &[u8],
|
identifier: &[u8],
|
||||||
request: CredentialRequest<NKodeCipherSuite>,
|
request: CredentialRequest<NKodeCipherSuite>,
|
||||||
) -> Result<LoginSession, String> {
|
) -> Result<LoginSession, String> {
|
||||||
// Lookup password file for K (Key vs Code)
|
|
||||||
let password_file = K::get_pf(&self.user_repo, identifier)
|
let password_file = K::get_pf(&self.user_repo, identifier)
|
||||||
.map_err(|e| format!("repo read: {e:?}"))?;
|
.map_err(|e| format!("repo read: {e:?}"))?;
|
||||||
|
|
||||||
// Deserialize into OPAQUE password file type
|
|
||||||
let password_file =
|
let password_file =
|
||||||
ServerRegistration::<NKodeCipherSuite>::deserialize(password_file.as_slice())
|
ServerRegistration::<NKodeCipherSuite>::deserialize(password_file.as_slice())
|
||||||
.map_err(|e| format!("pf deserialize: {e:?}"))?;
|
.map_err(|e| format!("pf deserialize: {e:?}"))?;
|
||||||
|
|
||||||
// OPAQUE login start
|
|
||||||
let mut server_rng = OsRng;
|
let mut server_rng = OsRng;
|
||||||
let start = ServerLogin::start(
|
let start = ServerLogin::start(
|
||||||
&mut server_rng,
|
&mut server_rng,
|
||||||
@@ -228,36 +160,32 @@ where
|
|||||||
request,
|
request,
|
||||||
identifier,
|
identifier,
|
||||||
ServerLoginParameters::default(),
|
ServerLoginParameters::default(),
|
||||||
)
|
).map_err(|e| format!("opaque login start: {e:?}"))?;
|
||||||
.map_err(|e| format!("opaque login start: {e:?}"))?;
|
|
||||||
|
|
||||||
// Cache server state
|
let cache = self.session
|
||||||
let cache = self
|
|
||||||
.session
|
|
||||||
.new_login_session(identifier, start.state)
|
.new_login_session(identifier, start.state)
|
||||||
.map_err(|e| format!("login cache: {e}"))?;
|
.map_err(|e| format!("login cache: {e}"))?;
|
||||||
|
|
||||||
Ok(LoginSession {
|
Ok(LoginSession { session_id: cache.session_id, response: start.message })
|
||||||
session_id: cache.session_id,
|
|
||||||
response: start.message,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn finish(
|
pub async fn login_finish<K: CredKind>(
|
||||||
&mut self,
|
&mut self,
|
||||||
session_id: &Uuid,
|
session_id: &Uuid,
|
||||||
finalize: CredentialFinalization<NKodeCipherSuite>,
|
finalize: CredentialFinalization<NKodeCipherSuite>,
|
||||||
) -> Result<Vec<u8>, String> {
|
) -> Result<Vec<u8>, String> {
|
||||||
let cache = self
|
let cache = self.session
|
||||||
.session
|
|
||||||
.get_login_session(session_id)
|
.get_login_session(session_id)
|
||||||
.map_err(|e| format!("get login session: {e}"))?;
|
.map_err(|e| format!("get login session: {e}"))?;
|
||||||
|
|
||||||
let finish = cache
|
let finish = cache.server_login
|
||||||
.server_login
|
|
||||||
.finish(finalize, ServerLoginParameters::default())
|
.finish(finalize, ServerLoginParameters::default())
|
||||||
.map_err(|e| format!("opaque login finish: {e:?}"))?;
|
.map_err(|e| format!("opaque login finish: {e:?}"))?;
|
||||||
|
|
||||||
|
self.session
|
||||||
|
.clear_login_session(session_id)
|
||||||
|
.map_err(|e| format!("clear login session: {e}"))?;
|
||||||
|
|
||||||
Ok(finish.session_key.to_vec())
|
Ok(finish.session_key.to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
82
tests/in_memory_test.rs
Normal file
82
tests/in_memory_test.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use opaque_ke::rand::rngs::OsRng;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use nkode_protocol::client::{AuthenticationData, OpaqueAuthentication, ClientAuthError};
|
||||||
|
use nkode_protocol::in_memory_auth_repo::InMemoryAuthRepo;
|
||||||
|
use nkode_protocol::in_memory_auth_session::InMemoryAuthSession;
|
||||||
|
use nkode_protocol::in_memory_transport::{InMemoryKeyServer, InMemoryCodeServer, SharedServer};
|
||||||
|
use nkode_protocol::models::NKodeServerSetup;
|
||||||
|
use nkode_protocol::server::{Code, Key, OpaqueAuth};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn opaque_key_registration_and_login_roundtrip() {
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let server_setup = NKodeServerSetup::new(&mut rng);
|
||||||
|
let mut server = InMemoryKeyServer::new(server_setup);
|
||||||
|
let auth = AuthenticationData::from_secret_key("a@b.com", b"supersecret16bytes");
|
||||||
|
OpaqueAuthentication::register(&auth, &mut server)
|
||||||
|
.await
|
||||||
|
.expect("registration should succeed");
|
||||||
|
let session_key = OpaqueAuthentication::login(&auth, &mut server)
|
||||||
|
.await
|
||||||
|
.expect("login should succeed");
|
||||||
|
assert!(!session_key.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn opaque_code_registration_and_login_roundtrip() {
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let server_setup = NKodeServerSetup::new(&mut rng);
|
||||||
|
let shared = Arc::new(Mutex::new(OpaqueAuth::new(
|
||||||
|
server_setup,
|
||||||
|
InMemoryAuthRepo::new(),
|
||||||
|
InMemoryAuthSession::new(),
|
||||||
|
)));
|
||||||
|
let mut key_server = SharedServer::<Key>::new(shared.clone());
|
||||||
|
let mut code_server = SharedServer::<Code>::new(shared.clone());
|
||||||
|
let email = "c@d.com";
|
||||||
|
let key_auth = AuthenticationData::from_secret_key(email, b"supersecret16bytes");
|
||||||
|
OpaqueAuthentication::register(&key_auth, &mut key_server)
|
||||||
|
.await
|
||||||
|
.expect("key registration should succeed");
|
||||||
|
let code = vec![1usize, 2, 3, 4, 5, 6];
|
||||||
|
let code_auth = AuthenticationData::from_code(email, &code);
|
||||||
|
OpaqueAuthentication::register(&code_auth, &mut code_server)
|
||||||
|
.await
|
||||||
|
.expect("code registration should succeed after key exists");
|
||||||
|
let session_key = OpaqueAuthentication::login(&code_auth, &mut code_server)
|
||||||
|
.await
|
||||||
|
.expect("login should succeed");
|
||||||
|
assert!(!session_key.is_empty());
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn opaque_login_fails_if_not_registered() {
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let server_setup = NKodeServerSetup::new(&mut rng);
|
||||||
|
let mut server = InMemoryKeyServer::new(server_setup);
|
||||||
|
let auth = AuthenticationData::from_secret_key("nope@nope.com", b"supersecret16bytes");
|
||||||
|
let err = OpaqueAuthentication::login(&auth, &mut server)
|
||||||
|
.await
|
||||||
|
.expect_err("login should fail if user not registered");
|
||||||
|
match err {
|
||||||
|
ClientAuthError::Transport(_) => {}
|
||||||
|
other => panic!("unexpected error: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cannot_register_code_before_key() {
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let server_setup = NKodeServerSetup::new(&mut rng);
|
||||||
|
let mut server = InMemoryCodeServer::new(server_setup);
|
||||||
|
let auth = AuthenticationData::from_code("x@y.com", &[1usize,2,3,4]);
|
||||||
|
let err = OpaqueAuthentication::register(&auth, &mut server)
|
||||||
|
.await
|
||||||
|
.expect_err("should fail because key is not registered");
|
||||||
|
match err {
|
||||||
|
ClientAuthError::Transport(msg) => {
|
||||||
|
assert!(msg.contains("KeyNotRegistered"), "msg was: {msg}");
|
||||||
|
}
|
||||||
|
other => panic!("unexpected error: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user