remove all todos
continuous-integration/drone/push Build is passing Details

- on the server side, remove all unwraps and actually check and return
  to client an error message, by sing if let statements.
- on the client side, remove unneeded uwnraps by making
  req_server_salt() and get_server_salt() return SaltString, remove
  the load from hash function.
This commit is contained in:
ayham 2021-08-19 19:27:08 +03:00
parent 27e68e3eeb
commit 30a20873ea
Signed by: ayham
GPG Key ID: EAB7F5A9DF503678
13 changed files with 185 additions and 86 deletions

View File

@ -5,8 +5,8 @@ use argon2::password_hash::PasswordHash;
use crate::common::command::*;
use crate::common::message::*;
use crate::client::network::cmd::req_server_salt::*;
use crate::client::account::hash::*;
use crate::client::network::cmd::req_server_salt::*;
use tokio::net::TcpStream;
use tokio_rustls::client::TlsStream;
@ -87,13 +87,13 @@ pub async fn acc_auth(
/* decode response */
if !ret_msg.assert_command(Command::Success) || !ret_msg.assert_data() {
Err(io::Error::new(
io::ErrorKind::ConnectionRefused,
format!(
"Failed authorizing account, {}, server returned error: {}.",
username,
ret_msg.get_err()
),
))
io::ErrorKind::ConnectionRefused,
format!(
"Failed authorizing account, {}, server returned error: {}.",
username,
ret_msg.get_err()
),
))
} else {
/* authorized */
Ok(ret_msg.get_data().map_err(|_| {
@ -102,8 +102,8 @@ pub async fn acc_auth(
format!(
"Failed authorizing account, {}, server returned invalid data.",
username
),
)
),
)
})?)
}
}

View File

@ -37,24 +37,24 @@ pub async fn acc_create(
socket: &mut TlsStream<TcpStream>,
username: &str,
email: &str,
password: &str,
passw: &str,
) -> std::io::Result<()> {
/*
* get two server salts for email, and password
* */
let email_server_salt = get_server_salt(socket).await?;
let password_server_salt = get_server_salt(socket).await?;
let passw_server_salt = get_server_salt(socket).await?;
/*
* generate hashes for email, password
* */
let email_hash_phc = hash(email, &email_server_salt, true);
let password_hash_phc = hash(password, &password_server_salt, true);
let passw_hash_phc = hash(passw, &passw_server_salt, true);
/* generate message to be sent to the server */
let data = object! {
email_client_hash_phc: email_hash_phc,
password_client_hash_phc: password_hash_phc,
passw_client_hash_phc: passw_hash_phc,
username: username
};
/* build message request */

View File

@ -1,6 +1,6 @@
use argon2::{
password_hash::{PasswordHasher, SaltString},
Argon2
Argon2,
};
use rand_core::OsRng;
@ -22,15 +22,18 @@ use rand_core::OsRng;
/// ```rust
/// let enc = hash_email("totallyrealemail@anemail.c0m", server_salt).unwrap();
/// ```
pub fn hash(email: &str, server_salt: &str, resalt: bool) -> String {
let mut raw_salt: String = server_salt.to_string();
pub fn hash(email: &str, server_salt: &SaltString, resalt: bool) -> String {
let mut raw_salt: String = server_salt.as_str().to_string();
if resalt {
raw_salt.push_str(SaltString::generate(&mut OsRng).as_str());
}
let salt = SaltString::new(&raw_salt).unwrap(); // TODO: check if server gave longer than possible salt
let salt = SaltString::new(&raw_salt).unwrap();
let argon2id = Argon2::default();
let phc = argon2id.hash_password_simple(&email.as_bytes(), &salt).unwrap();
let phc = argon2id
.hash_password_simple(&email.as_bytes(), &salt)
.unwrap();
phc.to_string()
}

View File

@ -1,5 +1,7 @@
use std::io;
use argon2::password_hash::SaltString;
use crate::common::command::*;
use crate::common::message::*;
@ -21,7 +23,7 @@ use tokio_rustls::client::TlsStream;
/// ```rust
/// let server_salt: [u8; digest::SHA512_OUTPUT_LEN/2] = get_server_salt(tls_client)?;
/// ```
pub async fn get_server_salt(socket: &mut TlsStream<TcpStream>) -> std::io::Result<String> {
pub async fn get_server_salt(socket: &mut TlsStream<TcpStream>) -> std::io::Result<SaltString> {
/*
* request to generate a salt from the server.
* */
@ -39,11 +41,18 @@ pub async fn get_server_salt(socket: &mut TlsStream<TcpStream>) -> std::io::Resu
"Failed getting generated server Salt, received an invalid message.",
))
} else {
ret_msg.get_data().map_err(|_| {
let salt_raw: String = ret_msg.get_data().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"Failed getting generated server Salt, received an invalid message.",
)
)
})?;
/* verify that the salt is actually valid */
SaltString::new(salt_raw.as_str()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"Failed getting generated server Salt, received an invalid message.")
})
}
}

View File

@ -1,5 +1,7 @@
use std::io;
use argon2::password_hash::SaltString;
use crate::common::command::*;
use crate::common::message::*;
@ -28,11 +30,10 @@ pub async fn req_server_salt(
socket: &mut TlsStream<TcpStream>,
username: &str,
salt_type: Command,
) -> std::io::Result<String> {
) -> std::io::Result<SaltString> {
/* enforce salt_type to be either email or password */
assert_eq!(
(salt_type == Command::GetEmailSalt) || (salt_type == Command::GetPasswordSalt),
true
(salt_type == Command::GetEmailSalt) || (salt_type == Command::GetPasswordSalt), true
);
/* generate message to send */
@ -52,10 +53,18 @@ pub async fn req_server_salt(
));
}
Ok(ret_msg.get_data().map_err(|_| {
let salt_raw: String = ret_msg.get_data().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Could not get server salt, received invalid data."),
)
})?)
"Could not get server salt, received invalid data."
)
})?;
/* verify that the salt is actually valid */
SaltString::new(salt_raw.as_str()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Could not get server salt, received invalid salt length."),
)
})
}

View File

@ -2,7 +2,7 @@ use log::warn;
use argon2::{
password_hash::{PasswordHash, PasswordVerifier},
Argon2
Argon2,
};
use crate::common::command::*;
@ -32,11 +32,12 @@ pub async fn acc_auth(
* Parse account data.
* */
/* get json data */
let stringified_data: String =
message.get_data().map_err(|_|
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Failed authorizing account, could not parse message data!"))?;
let stringified_data: String = message.get_data().map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Failed authorizing account, could not parse message data!",
)
})?;
let data = json::parse(&stringified_data).unwrap();
/* get email, password, and username hashes */
let email_client_hash = data["hashed_email"].as_str().unwrap();
@ -79,8 +80,20 @@ pub async fn acc_auth(
* */
let argon2id = Argon2::default();
let parsed_email_hash = PasswordHash::new(&email_server_hash).unwrap(); // TODO: check
if argon2id.verify_password(&email_client_hash.as_bytes(), &parsed_email_hash).is_err() {
let parsed_email_hash = match PasswordHash::new(&email_server_hash) {
Ok(val) => val,
Err(_) => {
return Message::new()
.command(Command::Failure)
.data("Email Hash Invalid")
.send(socket)
.await;
}
};
if argon2id
.verify_password(&email_client_hash.as_bytes(), &parsed_email_hash)
.is_err()
{
return Message::new()
.command(Command::Failure)
.data("Email Incorrect")
@ -88,8 +101,19 @@ pub async fn acc_auth(
.await;
}
let parsed_passw_hash = PasswordHash::new(&passw_server_hash).unwrap(); // TODO: check
if argon2id.verify_password(&passw_client_hash.as_bytes(), &parsed_passw_hash).is_err() {
let parsed_passw_hash = match PasswordHash::new(&passw_server_hash) {
Ok(val) => val,
Err(_) => {
return Message::new()
.command(Command::Failure)
.data("Password Hash Invalid")
.send(socket)
.await;
}
}; if argon2id
.verify_password(&passw_client_hash.as_bytes(), &parsed_passw_hash)
.is_err()
{
return Message::new()
.command(Command::Failure)
.data("Password Incorrect")
@ -97,7 +121,6 @@ pub async fn acc_auth(
.await;
}
/*
* Generate JWT token
* */
@ -123,10 +146,11 @@ pub async fn acc_auth(
let jwt_result = create_jwt_token(
user_id,
beginning_of_time
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
).map_err(|e| { std::io::Error::new(std::io::ErrorKind::Other, e) })?;
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
Message::new()
.command(Command::Success)

View File

@ -1,13 +1,13 @@
use log::warn;
use argon2::password_hash::PasswordHash;
use log::warn;
use crate::common::message::*;
use crate::common::account::portfolio::Portfolio;
use crate::common::message::*;
use crate::server::account::hash::*;
use crate::server::ds::account::Account;
use crate::server::db::cmd::user_exists::*;
use crate::server::ds::account::Account;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
@ -36,8 +36,7 @@ pub async fn acc_create(
let username = data["username"].as_str().unwrap();
/* search for an account with same name */
if !user_exists(sql_conn, username).await
{
if !user_exists(sql_conn, username).await {
/*
* Inform cient that user already exists
* Note: figure out if this is a security? issue
@ -50,16 +49,46 @@ pub async fn acc_create(
}
/* get email, password client PHC strings */
let email_client_hash_phc = data["email_client_hash_phc"].as_str().unwrap(); // TODO: fix this
let password_client_hash_phc = data["password_client_hash_phc"].as_str().unwrap();
let (email_client_hash_phc, passw_client_hash_phc) =
match (data["email_client_hash_phc"].as_str(), data["passw_client_hash_phc"].as_str()) {
(Some(a), Some(b)) => (a, b),
_ => {
/* received empty PHC strings */
return Message::new()
.command(Command::Failure)
.data("Received empty PHC strings")
.send(socket)
.await;
}
};
let email_client = PasswordHash::new(email_client_hash_phc).unwrap();
let password_client = PasswordHash::new(password_client_hash_phc).unwrap();
let (email_client, passw_client) =
match (PasswordHash::new(email_client_hash_phc), PasswordHash::new(passw_client_hash_phc)) {
(Ok(a), Ok(b)) => (a, b),
_ => {
return Message::new()
.command(Command::Failure)
.data("Received invalid PHC strings")
.send(socket)
.await;
}
};
/* store salt _ONLY_ from the PHC string received,
* Note: discard the main hash sent from client after hashing by server */
let email_client_salt = email_client.salt.unwrap(); // TODO check for error
let password_client_salt = password_client.salt.unwrap(); // TODO check for error
let (email_client_salt, passw_client_salt) =
match (email_client.salt, passw_client.salt) {
(Some(a), Some(b)) => (a, b),
_ => {
return Message::new()
.command(Command::Failure)
.data("Received invalid salts")
.send(socket)
.await;
}
};
/* generate account struct */
let mut account: Account = Account {
@ -68,33 +97,45 @@ pub async fn acc_create(
email_hash_phc: "".to_string(),
client_email_salt: email_client_salt.to_string(),
pass_hash_phc: "".to_string(),
client_pass_salt: password_client_salt.to_string(),
passw_hash_phc: "".to_string(),
client_passw_salt: passw_client_salt.to_string(),
portfolio: Portfolio::default(),
transactions: Vec::new(),
};
/*
* Hash the email and password.
* Hash the email and password and store them.
* */
/* hash the email */
account.email_hash_phc = hash(email_client.hash.unwrap().to_string());
/* hash the password */
// TODO: actually merge those functions into one.
account.pass_hash_phc = hash(password_client.hash.unwrap().to_string());
if let (Some(email_client_hash), Some(passw_client_hash)) = (email_client.hash, passw_client.hash) {
account.email_hash_phc = hash(email_client_hash.to_string());
account.passw_hash_phc = hash(passw_client_hash.to_string());
} else {
return Message::new()
.command(Command::Failure)
.data("Received empty hashes")
.send(socket)
.await;
}
/*
* Write the account to the database.
* */
let creation_result = sql_conn.execute("INSERT INTO accounts_schema.accounts \
let creation_result = sql_conn
.execute(
"INSERT INTO accounts_schema.accounts \
(username, email_hash_phc, client_email_salt, pass_hash_phc, client_pass_salt)
VALUES \
($1, $2, $3, $4, $5)",
&[&account.username,
&account.email_hash_phc, &account.client_email_salt,
&account.pass_hash_phc, &account.client_pass_salt]).await;
&[
&account.username,
&account.email_hash_phc,
&account.client_email_salt,
&account.passw_hash_phc,
&account.client_passw_salt,
],
)
.await;
/*
* Send to client SQL result

View File

@ -1,6 +1,6 @@
use argon2::{
password_hash::{PasswordHasher, SaltString},
Argon2
Argon2,
};
use rand_core::OsRng;
@ -25,7 +25,9 @@ pub fn hash(hashed_email: String) -> String {
let salt = SaltString::generate(&mut OsRng);
let argon2id = Argon2::default();
let phc = argon2id.hash_password_simple(&hashed_email.as_bytes(), &salt).unwrap();
let phc = argon2id
.hash_password_simple(&hashed_email.as_bytes(), &salt)
.unwrap();
phc.to_string()
}

View File

@ -22,9 +22,12 @@ pub async fn get_client_salt(
for row in &sql_conn
.query(
format!("SELECT username, {} FROM accounts_schema.accounts WHERE username LIKE $1",
&query_variable).as_str(),
&[&username],
format!(
"SELECT username, {} FROM accounts_schema.accounts WHERE username LIKE $1",
&query_variable
)
.as_str(),
&[&username],
)
.await
.unwrap()

View File

@ -24,16 +24,25 @@ pub async fn get_server_salt(
for row in &sql_conn
.query(
format!("SELECT username, {} FROM accounts_schema.accounts WHERE username LIKE $1",
query_variable).as_str(),
&[&username],
format!(
"SELECT username, {} FROM accounts_schema.accounts WHERE username LIKE $1",
query_variable
)
.as_str(),
&[&username],
)
.await
.unwrap()
{
/* parse PHC string */
let parsed_phc = PasswordHash::new(row.get(1)).unwrap(); // TODO: check if corrupted account
return Ok(parsed_phc.salt.unwrap().to_string());
let parsed_phc = PasswordHash::new(row.get(1)).map_err(|_| {
format!("Account, {}, corrupted, invalid parsed PHC", username)
})?;
if let Some(salt) = parsed_phc.salt {
return Ok(salt.to_string());
} else {
return Err(format!("Account, {}, corrupted, empty salt!", username));
}
}
}

View File

@ -7,8 +7,8 @@ pub mod get_stock;
pub mod create_position;
pub mod create_transaction;
pub mod get_user_hash;
pub mod get_user_id;
pub mod get_client_salt;
pub mod get_server_salt;
pub mod get_user_hash;
pub mod get_user_id;
pub mod user_exists;

View File

@ -8,8 +8,8 @@ pub struct Account {
pub email_hash_phc: String,
pub client_email_salt: String,
pub pass_hash_phc: String,
pub client_pass_salt: String,
pub passw_hash_phc: String,
pub client_passw_salt: String,
pub portfolio: Portfolio,
pub transactions: Vec<Transaction>,
@ -23,8 +23,8 @@ impl std::fmt::Display for Account {
self.username,
self.email_hash_phc,
self.client_email_salt,
self.pass_hash_phc,
self.client_pass_salt,
self.passw_hash_phc,
self.client_passw_salt,
self.portfolio,
self.transactions
)

View File

@ -32,8 +32,7 @@ pub async fn handle_data(
.await
}
Command::GetEmailSalt | Command::GetPasswordSalt => {
let salt =
get_client_salt(sql_conn, client_msg.get_data()?, client_msg.command).await;
let salt = get_client_salt(sql_conn, client_msg.get_data()?, client_msg.command).await;
let mut response = Message::new();
if salt.is_ok() {
response