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
|
||||
|
||||
pub mod account;
|
||||
pub mod hashing;
|
||||
pub mod master_pass;
|
||||
pub mod passwords;
|
||||
pub mod prelude;
|
||||
|
@ -1,35 +1,6 @@
|
||||
use super::hashing::HashedBytes;
|
||||
use entity::master_pass;
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use scrypt::{scrypt, Params};
|
||||
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 {
|
||||
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
|
||||
#[inline]
|
||||
fn from_unencrypted(user_id: u64, password: &str) -> Self {
|
||||
let mut salt = [0; SALT_LENGTH];
|
||||
OsRng.fill_bytes(&mut salt);
|
||||
let password_hash = hash_password(password.as_bytes(), &salt);
|
||||
let hash = HashedBytes::new(password.as_bytes());
|
||||
Self {
|
||||
user_id: Set(user_id),
|
||||
salt: Set(salt.into()),
|
||||
password_hash: Set(password_hash.into()),
|
||||
password_hash: Set(hash.hash.to_vec()),
|
||||
salt: Set(hash.salt.to_vec()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,67 @@
|
||||
use crate::{change_state, prelude::*};
|
||||
use cryptography::hashing::HashedBytes;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
/// Actually sets the master password
|
||||
async fn get_master_pass(
|
||||
async fn get_master_pass2(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: DatabaseConnection,
|
||||
dialogue: MainDialogue,
|
||||
mut ids: MessageIds,
|
||||
hash: HashedBytes<[u8; 64], [u8; 64]>,
|
||||
master_pass: String,
|
||||
) -> crate::Result<()> {
|
||||
dialogue.exit().await?;
|
||||
let user_id = Set(msg.from().ok_or(NoUserInfo)?.id.0);
|
||||
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
let model =
|
||||
spawn_blocking(move || master_pass::ActiveModel::from_unencrypted(user_id, &master_pass))
|
||||
.await?;
|
||||
if !hash.verify(master_pass.as_bytes()) {
|
||||
ids.alter_message(
|
||||
&bot,
|
||||
"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?;
|
||||
|
||||
ids.alter_message(&bot, "Success", deletion_markup(), None)
|
||||
.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(())
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::prelude::*;
|
||||
use cryptography::hashing::HashedBytes;
|
||||
use log::error;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
@ -14,8 +15,9 @@ async fn check_master_pass(
|
||||
|
||||
let is_valid = match model {
|
||||
Some(model) => {
|
||||
let hash: HashedBytes<_, _> = model.into();
|
||||
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 => {
|
||||
error!("User was put into the GetMasterPass state with no master password set");
|
||||
|
Loading…
Reference in New Issue
Block a user