Skip to content

Commit

Permalink
feat(strata-cli)!: add hash version and optional password (#392)
Browse files Browse the repository at this point in the history
* feat(strata-cli)!: add hash version and optional password

* feat(strata-cli): warn user about password strength
  • Loading branch information
storopoli committed Oct 28, 2024
1 parent afaf6b0 commit fd8100f
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 8 deletions.
60 changes: 60 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/strata-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ sled = "0.34.7"
strata-bridge-tx-builder.workspace = true
terrors.workspace = true
tokio.workspace = true
zxcvbn = "3.1.0"

# sha2 fails to compile on windows with the "asm" feature
[target.'cfg(not(target_os = "windows"))'.dependencies]
Expand Down
12 changes: 12 additions & 0 deletions bin/strata-cli/src/cmd/change_pwd.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use argh::FromArgs;
use console::Term;
use rand::thread_rng;
use zxcvbn::Score;

use crate::seed::{password::Password, EncryptedSeedPersister, Seed};

Expand All @@ -12,6 +13,17 @@ pub struct ChangePwdArgs {}
pub async fn change_pwd(_args: ChangePwdArgs, seed: Seed, persister: impl EncryptedSeedPersister) {
let term = Term::stdout();
let mut new_pw = Password::read(true).unwrap();
let entropy = new_pw.entropy();
let _ = term.write_line(format!("Password strength (Overall strength score from 0-4, where anything below 3 is too weak): {}", entropy.score()).as_str());
if entropy.score() <= Score::Two {
let _ = term.write_line(
entropy
.feedback()
.expect("No feedback")
.to_string()
.as_str(),
);
}
let encrypted_seed = seed.encrypt(&mut new_pw, &mut thread_rng()).unwrap();
persister.save(&encrypted_seed).unwrap();
let _ = term.write_line("Password changed successfully");
Expand Down
24 changes: 21 additions & 3 deletions bin/strata-cli/src/seed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ use bdk_wallet::{
use bip39::{Language, Mnemonic};
use console::Term;
use dialoguer::{Confirm, Input};
use password::{IncorrectPassword, Password};
use password::{HashVersion, IncorrectPassword, Password};
use rand::{thread_rng, Rng, RngCore};
use sha2::{Digest, Sha256};
use terrors::OneOf;
use zxcvbn::Score;

use crate::constants::{AES_NONCE_LEN, AES_TAG_LEN, PW_SALT_LEN, SEED_LEN};

Expand Down Expand Up @@ -56,7 +57,10 @@ impl Seed {
rng.fill_bytes(&mut buf[..PW_SALT_LEN + AES_NONCE_LEN]);

let seed_encryption_key = password
.seed_encryption_key(&buf[..PW_SALT_LEN].try_into().expect("cannot fail"))
.seed_encryption_key(
&buf[..PW_SALT_LEN].try_into().expect("cannot fail"),
HashVersion::V0,
)
.map_err(OneOf::new)?;

let (salt_and_nonce, rest) = buf.split_at_mut(PW_SALT_LEN + AES_NONCE_LEN);
Expand Down Expand Up @@ -111,7 +115,10 @@ impl EncryptedSeed {
password: &mut Password,
) -> Result<Seed, OneOf<(argon2::Error, aes_gcm_siv::Error)>> {
let seed_encryption_key = password
.seed_encryption_key(&self.0[..PW_SALT_LEN].try_into().expect("cannot fail"))
.seed_encryption_key(
&self.0[..PW_SALT_LEN].try_into().expect("cannot fail"),
HashVersion::V0,
)
.map_err(OneOf::new)?;

let mut cipher =
Expand Down Expand Up @@ -188,6 +195,17 @@ pub fn load_or_create(
};

let mut password = Password::read(true).map_err(OneOf::new)?;
let entropy = password.entropy();
let _ = term.write_line(format!("Password strength (Overall strength score from 0-4, where anything below 3 is too weak): {}", entropy.score()).as_str());
if entropy.score() <= Score::Two {
let _ = term.write_line(
entropy
.feedback()
.expect("No feedback")
.to_string()
.as_str(),
);
}
let encrypted_seed = match seed.encrypt(&mut password, &mut thread_rng()) {
Ok(es) => es,
Err(e) => {
Expand Down
46 changes: 41 additions & 5 deletions bin/strata-cli/src/seed/password.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use argon2::Argon2;
use argon2::{Algorithm, Argon2, Params, Version};
use dialoguer::Password as InputPassword;
use zxcvbn::{zxcvbn, Entropy};

use super::PW_SALT_LEN;

Expand All @@ -8,13 +9,35 @@ pub struct Password {
seed_encryption_key: Option<[u8; 32]>,
}

pub enum HashVersion {
V0,
}

impl HashVersion {
const fn params(&self) -> (Algorithm, Version, Result<Params, argon2::Error>) {
match self {
HashVersion::V0 => (
Algorithm::Argon2id,
Version::V0x13,
// NOTE: This is the OWASP recommended params for Argon2id
// see https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
Params::new(19_456, 2, 1, Some(32)),
),
}
}
}

impl Password {
pub fn read(new: bool) -> Result<Self, dialoguer::Error> {
let mut input = InputPassword::new();
if new {
input = input
.with_prompt("Create a new password")
.with_confirmation("Confirm password", "Passwords didn't match");
.with_prompt("Create a new password (leave empty for no password, dangerous!)")
.with_confirmation(
"Confirm password (leave empty for no password, dangerous!)",
"Passwords didn't match",
)
.allow_empty_password(true);
} else {
input = input.with_prompt("Enter your password");
}
Expand All @@ -27,17 +50,30 @@ impl Password {
})
}

/// Returns the password entropy.
pub fn entropy(&self) -> Entropy {
zxcvbn(self.inner.as_str(), &[])
}

pub fn seed_encryption_key(
&mut self,
salt: &[u8; PW_SALT_LEN],
version: HashVersion,
) -> Result<&[u8; 32], argon2::Error> {
match self.seed_encryption_key {
Some(ref key) => Ok(key),
None => {
let mut sek = [0u8; 32];
Argon2::default().hash_password_into(self.inner.as_bytes(), salt, &mut sek)?;
let (algo, ver, params) = version.params();
if !self.inner.is_empty() {
Argon2::new(algo, ver, params.expect("valid params")).hash_password_into(
self.inner.as_bytes(),
salt,
&mut sek,
)?;
}
self.seed_encryption_key = Some(sek);
self.seed_encryption_key(salt)
self.seed_encryption_key(salt, version)
}
}
}
Expand Down

0 comments on commit fd8100f

Please sign in to comment.