set_master_pass now asks for the master password twice
This commit is contained in:
parent
1cc486bdc5
commit
857f268f1d
71
cryptography/src/hashing.rs
Normal file
71
cryptography/src/hashing.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user