set_master_pass now asks for the master password twice

This commit is contained in:
StNicolay 2023-08-07 13:22:47 +03:00
parent 1cc486bdc5
commit 857f268f1d
Signed by: StNicolay
GPG Key ID: 9693D04DCD962B0D
5 changed files with 128 additions and 42 deletions

View File

@ -0,0 +1,71 @@
use entity::master_pass;
use once_cell::sync::Lazy;
use rand::{rngs::OsRng, RngCore};
use scrypt::{scrypt, Params};
use subtle::ConstantTimeEq;
pub const HASH_LENGTH: usize = 64;
pub const SALT_LENGTH: usize = 64;
static PARAMS: Lazy<Params> = Lazy::new(|| Params::new(14, 8, 1, HASH_LENGTH).unwrap());
/// Hashes the bytes with Scrypt with the given salt
#[inline]
pub fn hash_scrypt(bytes: &[u8], salt: &[u8]) -> [u8; HASH_LENGTH] {
let mut hash = [0; HASH_LENGTH];
scrypt(bytes, salt, &PARAMS, &mut hash).unwrap();
hash
}
/// Verifieble scrypt hashed bytes
pub struct HashedBytes<T, U>
where
T: AsRef<[u8]>,
U: AsRef<[u8]>,
{
pub hash: T,
pub salt: U,
}
impl HashedBytes<[u8; HASH_LENGTH], [u8; SALT_LENGTH]> {
#[inline]
pub fn new(bytes: &[u8]) -> Self {
let mut salt = [0; 64];
OsRng.fill_bytes(&mut salt);
Self {
hash: hash_scrypt(bytes, &salt),
salt,
}
}
}
impl<T, U> HashedBytes<T, U>
where
T: AsRef<[u8]>,
U: AsRef<[u8]>,
{
#[inline]
pub fn verify(&self, bytes: &[u8]) -> bool {
let hash = hash_scrypt(bytes, self.salt.as_ref());
hash.ct_eq(self.hash.as_ref()).into()
}
}
impl<'a> From<&'a master_pass::Model> for HashedBytes<&'a [u8], &'a [u8]> {
#[inline]
fn from(value: &'a master_pass::Model) -> Self {
HashedBytes {
hash: &value.password_hash,
salt: &value.salt,
}
}
}
impl From<master_pass::Model> for HashedBytes<Vec<u8>, Vec<u8>> {
fn from(value: master_pass::Model) -> Self {
Self {
hash: value.password_hash,
salt: value.salt,
}
}
}

View File

@ -1,6 +1,7 @@
//! Functions to encrypt the database models //! Functions to encrypt the database models
pub mod account; pub mod account;
pub mod hashing;
pub mod master_pass; pub mod master_pass;
pub mod passwords; pub mod passwords;
pub mod prelude; pub mod prelude;

View File

@ -1,35 +1,6 @@
use super::hashing::HashedBytes;
use entity::master_pass; use entity::master_pass;
use once_cell::sync::Lazy;
use rand::{rngs::OsRng, RngCore};
use scrypt::{scrypt, Params};
use sea_orm::ActiveValue::Set; use sea_orm::ActiveValue::Set;
use subtle::ConstantTimeEq;
const HASH_LENGTH: usize = 64;
const SALT_LENGTH: usize = 64;
static PARAMS: Lazy<Params> = Lazy::new(|| Params::new(14, 8, 1, HASH_LENGTH).unwrap());
/// Hashes the password with Scrypt with the given salt
#[inline]
fn hash_password(password: &[u8], salt: &[u8]) -> [u8; HASH_LENGTH] {
let mut password_hash = [0; HASH_LENGTH];
scrypt(password, salt, &PARAMS, &mut password_hash).unwrap();
password_hash
}
pub trait VerifyMasterPassExt {
fn verify(&self, password: &str) -> bool;
}
impl VerifyMasterPassExt for master_pass::Model {
/// Checks that the given password hash matches the one of the model
#[inline]
fn verify(&self, password: &str) -> bool {
let hashed = hash_password(password.as_bytes(), &self.salt);
hashed.ct_eq(&self.password_hash).into()
}
}
pub trait MasterPassFromUnencryptedExt { pub trait MasterPassFromUnencryptedExt {
fn from_unencrypted(user_id: u64, password: &str) -> master_pass::ActiveModel; fn from_unencrypted(user_id: u64, password: &str) -> master_pass::ActiveModel;
@ -39,13 +10,11 @@ impl MasterPassFromUnencryptedExt for master_pass::ActiveModel {
/// Hashes the password and creates an ActiveModel with all fields set to Set variant /// Hashes the password and creates an ActiveModel with all fields set to Set variant
#[inline] #[inline]
fn from_unencrypted(user_id: u64, password: &str) -> Self { fn from_unencrypted(user_id: u64, password: &str) -> Self {
let mut salt = [0; SALT_LENGTH]; let hash = HashedBytes::new(password.as_bytes());
OsRng.fill_bytes(&mut salt);
let password_hash = hash_password(password.as_bytes(), &salt);
Self { Self {
user_id: Set(user_id), user_id: Set(user_id),
salt: Set(salt.into()), password_hash: Set(hash.hash.to_vec()),
password_hash: Set(password_hash.into()), salt: Set(hash.salt.to_vec()),
} }
} }
} }

View File

@ -1,24 +1,67 @@
use crate::{change_state, prelude::*}; use crate::{change_state, prelude::*};
use cryptography::hashing::HashedBytes;
use sea_orm::ActiveValue::Set;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
/// Actually sets the master password async fn get_master_pass2(
async fn get_master_pass(
bot: Throttle<Bot>, bot: Throttle<Bot>,
msg: Message, msg: Message,
db: DatabaseConnection, db: DatabaseConnection,
dialogue: MainDialogue, dialogue: MainDialogue,
mut ids: MessageIds, mut ids: MessageIds,
hash: HashedBytes<[u8; 64], [u8; 64]>,
master_pass: String, master_pass: String,
) -> crate::Result<()> { ) -> crate::Result<()> {
dialogue.exit().await?; dialogue.exit().await?;
let user_id = Set(msg.from().ok_or(NoUserInfo)?.id.0);
let user_id = msg.from().ok_or(NoUserInfo)?.id.0; if !hash.verify(master_pass.as_bytes()) {
let model = ids.alter_message(
spawn_blocking(move || master_pass::ActiveModel::from_unencrypted(user_id, &master_pass)) &bot,
.await?; "The passwords didn't match. Use the command again",
deletion_markup(),
None,
)
.await?;
return Ok(());
}
let model = master_pass::ActiveModel {
user_id,
password_hash: Set(hash.hash.to_vec()),
salt: Set(hash.salt.to_vec()),
};
model.insert(&db).await?; model.insert(&db).await?;
ids.alter_message(&bot, "Success", deletion_markup(), None) ids.alter_message(&bot, "Success", deletion_markup(), None)
.await?; .await?;
Ok(())
}
/// Actually sets the master password
#[inline]
async fn get_master_pass(
bot: Throttle<Bot>,
_: Message,
_: DatabaseConnection,
dialogue: MainDialogue,
mut ids: MessageIds,
master_pass: String,
) -> crate::Result<()> {
let hash = spawn_blocking(move || HashedBytes::new(master_pass.as_bytes())).await?;
ids.alter_message(&bot, "Send it again", None, None).await?;
change_state!(
dialogue,
ids,
(hash),
State::GetNewMasterPass,
get_master_pass2
);
Ok(()) Ok(())
} }

View File

@ -1,4 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use cryptography::hashing::HashedBytes;
use log::error; use log::error;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
@ -14,8 +15,9 @@ async fn check_master_pass(
let is_valid = match model { let is_valid = match model {
Some(model) => { Some(model) => {
let hash: HashedBytes<_, _> = model.into();
let master_pass = master_pass.to_owned(); let master_pass = master_pass.to_owned();
spawn_blocking(move || model.verify(&master_pass)).await? spawn_blocking(move || hash.verify(master_pass.as_bytes())).await?
} }
None => { None => {
error!("User was put into the GetMasterPass state with no master password set"); error!("User was put into the GetMasterPass state with no master password set");