Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provide password feedback #5111

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 122 additions & 8 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 applications/tari_console_wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ tracing = "0.1.26"
unicode-segmentation = "1.6.0"
unicode-width = "0.1"
zeroize = "1"
zxcvbn = "2"

[dependencies.tari_core]
path = "../../base_layer/core"
Expand Down
78 changes: 68 additions & 10 deletions applications/tari_console_wallet/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ use tari_wallet::{
WalletConfig,
WalletSqlite,
};
use zxcvbn::zxcvbn;

use crate::{
cli::Cli,
Expand All @@ -77,6 +78,40 @@ pub enum WalletBoot {
Recovery,
}

/// Get feedback, if available, for a weak passphrase
fn get_password_feedback(passphrase: &SafePassword) -> Option<Vec<String>> {
std::str::from_utf8(passphrase.reveal())
.ok()
.and_then(|passphrase| zxcvbn(passphrase, &[]).ok())
.and_then(|scored| scored.feedback().to_owned())
.map(|feedback| feedback.suggestions().to_owned())
.map(|suggestion| suggestion.into_iter().map(|item| item.to_string()).collect())
}

// Display password feedback to the user
fn display_password_feedback(passphrase: &SafePassword) {
if passphrase.reveal().is_empty() {
// The passphrase is empty, which the scoring library doesn't handle
println!("However, an empty password puts your wallet at risk against an attacker with access to this device.");
println!("Use this only if you are sure that your device is safe from prying eyes!");
println!();
} else if let Some(feedback) = get_password_feedback(passphrase) {
// The scoring library provided feedback
println!(
"However, the password you chose is weak; a determined attacker with access to your device may be able to \
guess it."
);
println!("You may want to consider changing it to a stronger one.");
println!("Here are some suggestions:");
for suggestion in feedback {
println!("- {}", suggestion);
}
println!();
} else {
// There is no feedback to provide
}
}

/// Gets the password provided by command line argument or environment variable if available.
/// Otherwise prompts for the password to be typed in.
pub fn get_or_prompt_password(
Expand Down Expand Up @@ -105,15 +140,7 @@ pub fn get_or_prompt_password(
}

fn prompt_password(prompt: &str) -> Result<SafePassword, ExitError> {
let password = loop {
let pass = prompt_password_stdout(prompt).map_err(|e| ExitError::new(ExitCode::IOError, e))?;
if pass.is_empty() {
println!("Password cannot be empty!");
continue;
} else {
break pass;
}
};
let password = prompt_password_stdout(prompt).map_err(|e| ExitError::new(ExitCode::IOError, e))?;

Ok(SafePassword::from(password))
}
Expand Down Expand Up @@ -144,7 +171,14 @@ pub async fn change_password(
// .await
// .map_err(|e| ExitError::new(ExitCode::WalletError, e))?;

println!("Wallet password changed successfully.");
println!("Passwords match.");

// If the passphrase is weak, let the user know
display_password_feedback(&passphrase);

// TODO: remove this warning when this functionality is added
println!();
println!("WARNING: Password change functionality is not yet completed, so continue to use your existing password!");

Ok(())
}
Expand Down Expand Up @@ -625,6 +659,11 @@ pub(crate) fn boot_with_password(
return Err(ExitError::new(ExitCode::InputError, "Passwords don't match!"));
}

println!("Passwords match.");

// If the passphrase is weak, let the user know
display_password_feedback(&password);

password
},
WalletBoot::Existing | WalletBoot::Recovery => {
Expand All @@ -635,3 +674,22 @@ pub(crate) fn boot_with_password(

Ok((boot_mode, password))
}

#[cfg(test)]
mod test {
use tari_utilities::SafePassword;

use super::get_password_feedback;

#[test]
fn weak_password() {
let weak_password = SafePassword::from("weak");
assert!(get_password_feedback(&weak_password).is_some());
}

#[test]
fn strong_password() {
let strong_password = SafePassword::from("This is a reasonably strong password!");
assert!(get_password_feedback(&strong_password).is_none());
}
}