partial implement client app
This commit is contained in:
69
Cargo.lock
generated
69
Cargo.lock
generated
@@ -20,6 +20,18 @@ dependencies = [
|
|||||||
"password-hash",
|
"password-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.89"
|
version = "0.1.89"
|
||||||
@@ -71,6 +83,19 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake3"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0"
|
||||||
|
dependencies = [
|
||||||
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"constant_time_eq",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@@ -102,6 +127,16 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.49"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -124,6 +159,12 @@ version = "0.9.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -282,6 +323,15 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email_address"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ff"
|
name = "ff"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@@ -298,6 +348,12 @@ version = "0.2.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@@ -405,10 +461,15 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
|||||||
name = "nkode-protocol"
|
name = "nkode-protocol"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"blake3",
|
||||||
|
"email_address",
|
||||||
|
"getset",
|
||||||
"nkode-rs",
|
"nkode-rs",
|
||||||
"opaque-ke",
|
"opaque-ke",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -424,7 +485,9 @@ dependencies = [
|
|||||||
"getset",
|
"getset",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha 0.9.0",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -686,6 +749,12 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signature"
|
name = "signature"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
|||||||
@@ -12,5 +12,10 @@ uuid = { version = "1.19.0", features = ["v4"] }
|
|||||||
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "sync"] }
|
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "sync"] }
|
||||||
nkode-rs = { path = "nkode-rs" }
|
nkode-rs = { path = "nkode-rs" }
|
||||||
zeroize = "1.8.2"
|
zeroize = "1.8.2"
|
||||||
|
blake3 = "1.8.2"
|
||||||
|
email_address = "0.2.9"
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
getset = "0.1.6"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
nkode-rs
2
nkode-rs
Submodule nkode-rs updated: 13f007abf7...e5712b4a92
@@ -1,53 +1,36 @@
|
|||||||
use uuid::Uuid;
|
use nkode_rs::nkode_core::keypad::Keypad;
|
||||||
use zeroize::Zeroizing;
|
use crate::models::app::{CodeLoginData, CodeLoginSession, Icon, IconID, KeyLoginSession, ICON_ID_SIZE};
|
||||||
|
use crate::models::email::Email;
|
||||||
|
use crate::models::opaque::UserSecretKey;
|
||||||
|
use anyhow::Result;
|
||||||
|
use nkode_rs::nkode_core::nkode_cipher::NKodeCipher;
|
||||||
|
use nkode_rs::nkode_core::policy::NKodePolicy;
|
||||||
|
use nkode_rs::from_bytes::FromBytes;
|
||||||
|
use nkode_rs::nkode_core::chacha20prng::Nonce;
|
||||||
|
|
||||||
|
pub trait ServerAPI {
|
||||||
struct LoginSession {
|
async fn register_key(&self, email: &Email, secret_key: &UserSecretKey) -> Result<(), String>;
|
||||||
identity: Vec<u8>,
|
async fn register_code(&self, email: &Email, passcode: &[u64], key_login_session: &KeyLoginSession, data: &CodeLoginData) -> Result<(), String>;
|
||||||
session_id: Uuid,
|
async fn login_key(&self, email: &Email, secret_key: &UserSecretKey) -> Result<KeyLoginSession, String>;
|
||||||
session_key: Zeroizing<Vec<u8>>,
|
async fn login_code(&self, email: &Email, passcode: &[u64]) -> Result<CodeLoginSession, String>;
|
||||||
}
|
|
||||||
|
|
||||||
struct KeyLoginSession(LoginSession);
|
|
||||||
struct CodeLoginSession(LoginSession);
|
|
||||||
|
|
||||||
struct IconID(u128);
|
|
||||||
|
|
||||||
struct Icon {
|
|
||||||
id: IconID,
|
|
||||||
data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CodeLoginData {
|
|
||||||
mask: Vec<u64>,
|
|
||||||
icons: Vec<Icon>,
|
|
||||||
nonce: Vec<u8>,
|
|
||||||
keypad_indices: Vec<u8>
|
|
||||||
}
|
|
||||||
|
|
||||||
trait ServerAPI {
|
|
||||||
async fn register_key(&self, identity: &[u8], secret_key: &[u8]) -> Result<(), String>;
|
|
||||||
async fn register_code(&self, identity: &[u8], passcode: &[u64]) -> Result<(), String>;
|
|
||||||
async fn login_key(&self, identity: &[u8], secret_key: &[u8]) -> Result<KeyLoginSession, String>;
|
|
||||||
async fn login_code(&self, identity: &[u8], passcode: &[u64]) -> Result<CodeLoginSession, String>;
|
|
||||||
async fn get_new_icons(&self, key_login_session: &KeyLoginSession) -> Result<Vec<Icon>, String>;
|
async fn get_new_icons(&self, key_login_session: &KeyLoginSession) -> Result<Vec<Icon>, String>;
|
||||||
async fn set_login_data(&self, key_login_session: &KeyLoginSession, data: &CodeLoginData) -> Result<(), String>;
|
|
||||||
async fn get_login_data(&self, key_login_session: &KeyLoginSession) -> Result<CodeLoginData, String>;
|
async fn get_login_data(&self, key_login_session: &KeyLoginSession) -> Result<CodeLoginData, String>;
|
||||||
async fn is_code_registered(&self, key_login_session: &KeyLoginSession) -> Result<bool, String>;
|
async fn is_code_registered(&self, key_login_session: &KeyLoginSession) -> Result<bool, String>;
|
||||||
|
async fn get_policy(&self) -> Result<NKodePolicy, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RegisterKey;
|
pub struct RegisterKey;
|
||||||
|
|
||||||
impl RegisterKey {
|
impl RegisterKey {
|
||||||
pub async fn register<S: ServerAPI>(api: S,identity: &[u8], secret_key: &[u8]) -> Result<KeyLogin<S>, String> {
|
pub async fn register<S: ServerAPI>(api: S, email: Email, user_secret_key: &UserSecretKey) -> Result<KeyLogin<S>, String> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct KeyLogin<S: ServerAPI> {
|
pub struct KeyLogin<S: ServerAPI> {
|
||||||
api: S,
|
api: S,
|
||||||
identity: Vec<u8>,
|
email: Email,
|
||||||
secret_key: Zeroizing<Vec<u8>>
|
user_secret_key: UserSecretKey
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <S: ServerAPI>KeyLogin<S> {
|
impl <S: ServerAPI>KeyLogin<S> {
|
||||||
@@ -66,7 +49,7 @@ impl <S: ServerAPI>KeyLogin<S> {
|
|||||||
|
|
||||||
pub struct RegisterCode<S: ServerAPI> {
|
pub struct RegisterCode<S: ServerAPI> {
|
||||||
api: S,
|
api: S,
|
||||||
secret_key: Vec<u8>,
|
user_secret_key: UserSecretKey,
|
||||||
key_login_session: KeyLoginSession,
|
key_login_session: KeyLoginSession,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +58,7 @@ impl <S: ServerAPI>RegisterCode<S> {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register(self, identity: &[u8], passcode: &[u64]) -> Result<CodeLoginData, String> {
|
pub async fn register(self, email: Email, passcode: &[u64]) -> Result<CodeLoginData, String> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,43 +68,133 @@ pub struct CodeLogin<S: ServerAPI> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl <S: ServerAPI>CodeLogin<S> {
|
impl <S: ServerAPI>CodeLogin<S> {
|
||||||
pub async fn login(self, identity: &[u8], passcode: &[u64]) -> Result<CodeLoginSession, String> {
|
pub async fn login(self, email: Email, passcode: &[u64]) -> Result<CodeLoginSession, String> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ClientRepo {
|
pub trait ClientRepo {
|
||||||
async fn get_secret_key(&self) -> Result<Vec<u8>, String>;
|
async fn get_secret_key(&self) -> Result<UserSecretKey, String>;
|
||||||
async fn set_secret_key(&mut self, secret_key: &[u8]) -> Result<(),String>;
|
async fn set_secret_key(&self, email: &Email,user_secret_key: &UserSecretKey) -> Result<(),String>;
|
||||||
async fn get_login_data(&self) -> Result<CodeLoginData, String>;
|
async fn get_login_data(&self) -> Result<CodeLoginData, String>;
|
||||||
async fn set_login_data(&mut self, data: CodeLoginData) -> Result<(), String>;
|
async fn set_login_data(&self, data: CodeLoginData) -> Result<(), String>;
|
||||||
async fn set_identity(&mut self, identity: &[u8]) -> Result<(), String>;
|
async fn set_email(&self, email: &Email) -> Result<(), String>;
|
||||||
async fn get_identity(&self) -> Result<&[u8], String>;
|
async fn get_email(&self) -> Result<Email, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClientApp<S: ServerAPI, R: ClientRepo> {
|
pub struct ClientAppKey<S: ServerAPI, R: ClientRepo> {
|
||||||
repo: R,
|
repo: R,
|
||||||
api: S
|
api: S,
|
||||||
|
email: Email,
|
||||||
|
user_secret_key: UserSecretKey,
|
||||||
|
key_login: KeyLoginSession,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <S: ServerAPI, R: ClientRepo> ClientApp<S, R> {
|
impl <S: ServerAPI, R: ClientRepo> ClientAppKey<S, R> {
|
||||||
pub fn new_secret() -> Vec<u8> {
|
pub async fn new_register(email: Email, user_secret_key: UserSecretKey, repo: R, api: S) -> Result<Self, String> {
|
||||||
todo!()
|
repo.set_secret_key(&email, &user_secret_key).await?;
|
||||||
|
api.register_key(&email, &user_secret_key).await?;
|
||||||
|
let key_login = api.login_key(&email, &user_secret_key).await?;
|
||||||
|
Ok(Self {
|
||||||
|
repo,
|
||||||
|
api,
|
||||||
|
email,
|
||||||
|
user_secret_key,
|
||||||
|
key_login,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register_secret(&mut self, identity: &[u8], secret_key: &[u8]) -> Result<(), String> {
|
pub async fn register_code(self) -> Result<ClientAppCodeRegister<S>, String> {
|
||||||
todo!()
|
let icon_nonce = Nonce::new();
|
||||||
|
let icons = self.get_icons(&icon_nonce).await?;
|
||||||
|
let policy = self.api.get_policy().await?;
|
||||||
|
Ok(ClientAppCodeRegister {
|
||||||
|
api: self.api,
|
||||||
|
email: self.email,
|
||||||
|
user_secret_key: self.user_secret_key,
|
||||||
|
key_login: self.key_login,
|
||||||
|
icons,
|
||||||
|
icon_nonce,
|
||||||
|
keypad: Keypad::new(policy),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_new_icons(&self) -> Result<Vec<Icon>, String> {
|
async fn get_icons(&self, icon_nonce: &Nonce) -> Result<Vec<Icon>, String> {
|
||||||
todo!()
|
let chacha20_key = self.user_secret_key.chacha20_secret_key();
|
||||||
|
let mut chacha_cipher = nkode_rs::nkode_core::chacha20prng::ChaCha20PRNG::new(&chacha20_key, icon_nonce);
|
||||||
|
let mut icons = self.api.get_new_icons(&self.key_login).await?;
|
||||||
|
for icon in &mut icons {
|
||||||
|
let bytes = chacha_cipher.read_u8(ICON_ID_SIZE)?;
|
||||||
|
let new_id = IconID::from_bytes(bytes.as_slice()).unwrap();
|
||||||
|
icon.update_id(new_id);
|
||||||
|
}
|
||||||
|
Ok(icons)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn select_icons(&mut self, selected_icons: Vec<IconID>) -> Result<(), String> {
|
pub async fn login_code(self) -> Result<ClientAppCodeLogin<S>, String> {
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_secret(&mut self, secret_key: &[u8]) -> Result<(), String> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ClientAppCodeRegister<S: ServerAPI> {
|
||||||
|
api: S,
|
||||||
|
email: Email,
|
||||||
|
user_secret_key: UserSecretKey,
|
||||||
|
key_login: KeyLoginSession,
|
||||||
|
icon_nonce: Nonce,
|
||||||
|
icons: Vec<Icon>,
|
||||||
|
keypad: Keypad,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <S: ServerAPI>ClientAppCodeRegister<S> {
|
||||||
|
pub async fn register(self, selected_icons: Vec<IconID>) -> Result<ClientAppCodeLogin<S>, String> {
|
||||||
|
let policy = self.api.get_policy().await?;
|
||||||
|
let keypad = Keypad::new(policy.clone());
|
||||||
|
let secret_key = self.user_secret_key.chacha20_secret_key();
|
||||||
|
let (cipher, cipher_nonce) = NKodeCipher::new(policy, &secret_key);
|
||||||
|
let passcode_idx: Vec<usize> = selected_icons
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x1| self.icons.iter().position(|x| x.id() == x1))
|
||||||
|
.collect();
|
||||||
|
let ciphered_nkode = cipher.encipher(&passcode_idx).unwrap();
|
||||||
|
let data = CodeLoginData{
|
||||||
|
mask: ciphered_nkode.mask.clone(),
|
||||||
|
icons: self.icons.clone(),
|
||||||
|
cipher_nonce,
|
||||||
|
icon_nonce: self.icon_nonce.clone(),
|
||||||
|
keypad: keypad.clone(),
|
||||||
|
};
|
||||||
|
self.api.register_code(&self.email, &ciphered_nkode.passcode, &self.key_login, &data).await?;
|
||||||
|
Ok(ClientAppCodeLogin{
|
||||||
|
api: self.api,
|
||||||
|
mask: ciphered_nkode.mask,
|
||||||
|
email: self.email,
|
||||||
|
keypad,
|
||||||
|
cipher,
|
||||||
|
icons: self.icons
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ClientAppCodeLogin<S: ServerAPI> {
|
||||||
|
api: S,
|
||||||
|
email: Email,
|
||||||
|
mask: Vec<u64>,
|
||||||
|
icons: Vec<Icon>,
|
||||||
|
keypad: Keypad,
|
||||||
|
cipher: NKodeCipher
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <S: ServerAPI>ClientAppCodeLogin<S> {
|
||||||
|
pub fn sort_icons(&self) -> Vec<Icon> {
|
||||||
|
nkode_rs::tensor::reorder(
|
||||||
|
&self.icons,
|
||||||
|
self.keypad.indices()
|
||||||
|
).unwrap().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn login(&self, selected_keys: &Vec<usize>) -> Result<CodeLoginSession, String> {
|
||||||
|
let passcode = self.cipher.decipher(selected_keys, self.keypad.indices(), &self.mask).map_err(|e| format!("invalid keys: {e}"))?;
|
||||||
|
self.api.login_code(&self.email, &passcode).await
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/models/app.rs
Normal file
55
src/models/app.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use nkode_rs::nkode_core::chacha20prng::Nonce; use nkode_rs::nkode_core::keypad::Keypad;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use getset::Getters;
|
||||||
|
use nkode_rs::from_bytes::FromBytes;
|
||||||
|
use crate::models::email::Email;
|
||||||
|
use crate::models::opaque::OpaqueSessionKey;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct LoginSession {
|
||||||
|
email: Email,
|
||||||
|
session_id: Uuid,
|
||||||
|
session_key: OpaqueSessionKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KeyLoginSession(LoginSession);
|
||||||
|
pub struct CodeLoginSession(LoginSession);
|
||||||
|
|
||||||
|
pub const ICON_ID_SIZE: usize = 32;
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct IconID([u8; 32]);
|
||||||
|
|
||||||
|
impl FromBytes<ICON_ID_SIZE> for IconID {
|
||||||
|
fn from_array(arr: [u8; ICON_ID_SIZE]) -> Self {
|
||||||
|
Self(arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Getters)]
|
||||||
|
pub struct Icon {
|
||||||
|
#[get = "pub"]
|
||||||
|
id: IconID,
|
||||||
|
#[get = "pub"]
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Icon {
|
||||||
|
pub fn update_id(&mut self, new_id: IconID) {
|
||||||
|
self.id = new_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Getters, Serialize, Deserialize)]
|
||||||
|
pub struct CodeLoginData {
|
||||||
|
#[get = "pub"]
|
||||||
|
pub(crate) mask: Vec<u64>,
|
||||||
|
#[get = "pub"]
|
||||||
|
pub(crate) icons: Vec<Icon>,
|
||||||
|
#[get = "pub"]
|
||||||
|
pub(crate) cipher_nonce: Nonce,
|
||||||
|
#[get = "pub"]
|
||||||
|
pub(crate) icon_nonce: Nonce,
|
||||||
|
#[get = "pub"]
|
||||||
|
pub(crate) keypad: Keypad,
|
||||||
|
}
|
||||||
110
src/models/email.rs
Normal file
110
src/models/email.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
|
use email_address::{EmailAddress, Options};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Email(String);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum EmailError {
|
||||||
|
Empty,
|
||||||
|
InvalidUtf8,
|
||||||
|
InvalidFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Email {
|
||||||
|
pub fn new(s: impl AsRef<str>) -> Result<Self, EmailError> {
|
||||||
|
let s = s.as_ref().trim();
|
||||||
|
if s.is_empty() {
|
||||||
|
return Err(EmailError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strict "address only" validation (no "Name <a@b.com>")
|
||||||
|
EmailAddress::parse_with_options(s, Options::default().without_display_text())
|
||||||
|
.map_err(|_| EmailError::InvalidFormat)?;
|
||||||
|
|
||||||
|
Ok(Self(s.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> String {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_bytes(self) -> Vec<u8> {
|
||||||
|
self.0.into_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EmailError> {
|
||||||
|
let s = std::str::from_utf8(bytes).map_err(|_| EmailError::InvalidUtf8)?;
|
||||||
|
Self::new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- std traits ---- */
|
||||||
|
|
||||||
|
impl fmt::Display for Email {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Email {
|
||||||
|
type Err = EmailError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Email::new(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Email {
|
||||||
|
type Error = EmailError;
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
Email::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Email {
|
||||||
|
type Error = EmailError;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
Email::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for Email {
|
||||||
|
type Error = EmailError;
|
||||||
|
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
Email::from_bytes(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for Email {
|
||||||
|
type Error = EmailError;
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
Email::from_bytes(&value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for Email {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Email {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Email> for Vec<u8> {
|
||||||
|
fn from(value: Email) -> Self {
|
||||||
|
value.into_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,3 @@
|
|||||||
pub mod opaque;
|
pub mod opaque;
|
||||||
|
pub mod app;
|
||||||
|
pub mod email;
|
||||||
@@ -5,10 +5,36 @@ 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 nkode_rs::nkode_core::chacha20prng::SecretKey;
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
use nkode_rs::from_bytes::FromBytes;
|
||||||
|
|
||||||
pub const NONCE_SIZE: usize = 12;
|
const USER_KEY_SIZE: usize = 16;
|
||||||
pub const SESSION_KEY_SIZE: usize = 32;
|
|
||||||
pub const SECRET_KEY_SIZE: usize = 16;
|
pub struct UserSecretKey(Zeroizing<[u8; USER_KEY_SIZE]>);
|
||||||
|
|
||||||
|
impl UserSecretKey {
|
||||||
|
pub fn chacha20_secret_key(&self) -> SecretKey {
|
||||||
|
let out = blake3::derive_key("your-app chacha20 secret key v1", &self.0.as_slice());
|
||||||
|
SecretKey::from_bytes(&out).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromBytes<USER_KEY_SIZE> for UserSecretKey {
|
||||||
|
fn from_array(arr: [u8; USER_KEY_SIZE]) -> Self {
|
||||||
|
Self(Zeroizing::new(arr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OPAQUE_SESSION_KEY_SIZE: usize = 32;
|
||||||
|
|
||||||
|
pub struct OpaqueSessionKey(Zeroizing<[u8; OPAQUE_SESSION_KEY_SIZE]>);
|
||||||
|
|
||||||
|
impl FromBytes<OPAQUE_SESSION_KEY_SIZE> for OpaqueSessionKey {
|
||||||
|
fn from_array(arr: [u8; OPAQUE_SESSION_KEY_SIZE]) -> Self {
|
||||||
|
Self(Zeroizing::new(arr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct NKodeCipherSuite;
|
pub struct NKodeCipherSuite;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use opaque_ke::{
|
|||||||
RegistrationRequest,
|
RegistrationRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::models::opaque::{RegisterSession, LoginSession, NKodeCipherSuite, PasswordFile};
|
use crate::models::opaque::{RegisterSession, LoginSession, NKodeCipherSuite, PasswordFile, OpaqueSessionKey};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ pub trait ServerConnectionLogin {
|
|||||||
&mut self,
|
&mut self,
|
||||||
session_id: &Uuid,
|
session_id: &Uuid,
|
||||||
message: &CredentialFinalization<NKodeCipherSuite>,
|
message: &CredentialFinalization<NKodeCipherSuite>,
|
||||||
) -> Result<(), ClientAuthError>;
|
) -> Result<OpaqueSessionKey, ClientAuthError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- OPAQUE client driver ---
|
// --- OPAQUE client driver ---
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
use nkode_rs::from_bytes::FromBytes;
|
||||||
use opaque_ke::{
|
use opaque_ke::{
|
||||||
rand::rngs::OsRng, CredentialFinalization, CredentialRequest,
|
rand::rngs::OsRng, CredentialFinalization, CredentialRequest,
|
||||||
RegistrationRequest, ServerLogin, ServerLoginParameters,
|
RegistrationRequest, ServerLogin, ServerLoginParameters,
|
||||||
ServerRegistration,
|
ServerRegistration,
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use crate::models::opaque::{LoginSession, NKodeCipherSuite, NKodeServerSetup, PasswordFile, RegisterSession};
|
use crate::models::opaque::{LoginSession, NKodeCipherSuite, NKodeServerSetup, OpaqueSessionKey, PasswordFile, RegisterSession};
|
||||||
use crate::repository::opaque::repos::{OpaqueDatabaseRepo, AuthRepoError, OpaqueSessionRepo};
|
use crate::repository::opaque::repos::{OpaqueDatabaseRepo, AuthRepoError, OpaqueSessionRepo};
|
||||||
|
|
||||||
pub struct RegCache {
|
pub struct RegCache {
|
||||||
@@ -141,7 +142,7 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo> OpaqueAuth<R, S> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
session_id: &Uuid,
|
session_id: &Uuid,
|
||||||
finalize: CredentialFinalization<NKodeCipherSuite>,
|
finalize: CredentialFinalization<NKodeCipherSuite>,
|
||||||
) -> Result<Vec<u8>, String> {
|
) -> Result<OpaqueSessionKey, String> {
|
||||||
let cache = self.session
|
let cache = self.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}"))?;
|
||||||
@@ -154,6 +155,6 @@ impl<R: OpaqueDatabaseRepo, S: OpaqueSessionRepo> OpaqueAuth<R, S> {
|
|||||||
.clear_login_session(session_id)
|
.clear_login_session(session_id)
|
||||||
.map_err(|e| format!("clear login session: {e}"))?;
|
.map_err(|e| format!("clear login session: {e}"))?;
|
||||||
|
|
||||||
Ok(finish.session_key.to_vec())
|
Ok(OpaqueSessionKey::from_bytes(&finish.session_key.to_vec()).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use tokio::sync::Mutex;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use opaque_ke::{CredentialFinalization, CredentialRequest, RegistrationRequest};
|
use opaque_ke::{CredentialFinalization, CredentialRequest, RegistrationRequest};
|
||||||
use crate::models::opaque::{LoginSession, NKodeCipherSuite, NKodeServerSetup, PasswordFile, RegisterSession};
|
use crate::models::opaque::{LoginSession, NKodeCipherSuite, NKodeServerSetup, OpaqueSessionKey, PasswordFile, RegisterSession};
|
||||||
use crate::opaque::client::{ClientAuthError, ServerConnectionLogin, ServerConnectionRegister};
|
use crate::opaque::client::{ClientAuthError, ServerConnectionLogin, ServerConnectionRegister};
|
||||||
use crate::opaque::server::{OpaqueAuth, CredKind, Key, Code};
|
use crate::opaque::server::{OpaqueAuth, CredKind, Key, Code};
|
||||||
use crate::repository::opaque::in_memory::in_memory_auth_repo::InMemoryAuthRepo;
|
use crate::repository::opaque::in_memory::in_memory_auth_repo::InMemoryAuthRepo;
|
||||||
@@ -77,15 +77,15 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
session_id: &Uuid,
|
session_id: &Uuid,
|
||||||
message: &CredentialFinalization<NKodeCipherSuite>,
|
message: &CredentialFinalization<NKodeCipherSuite>,
|
||||||
) -> Result<(), ClientAuthError> {
|
) -> Result<OpaqueSessionKey, ClientAuthError> {
|
||||||
// Server computes its own session key too; we just need it to validate and complete.
|
// Server computes its own session key too; we just need it to validate and complete.
|
||||||
let _server_session_key = self
|
let key = self
|
||||||
.auth
|
.auth
|
||||||
.login_finish::<K>(session_id, message.clone())
|
.login_finish::<K>(session_id, message.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ClientAuthError::Transport(e))?;
|
.map_err(|e| ClientAuthError::Transport(e))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,12 +151,12 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
session_id: &Uuid,
|
session_id: &Uuid,
|
||||||
message: &CredentialFinalization<NKodeCipherSuite>,
|
message: &CredentialFinalization<NKodeCipherSuite>,
|
||||||
) -> Result<(), ClientAuthError> {
|
) -> Result<OpaqueSessionKey, ClientAuthError> {
|
||||||
let mut guard = self.inner.lock().await;
|
let mut guard = self.inner.lock().await;
|
||||||
let _ = guard
|
let key = guard
|
||||||
.login_finish::<K>(session_id, message.clone())
|
.login_finish::<K>(session_id, message.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(ClientAuthError::Transport)?;
|
.map_err(ClientAuthError::Transport)?;
|
||||||
Ok(())
|
Ok(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user