Skip to content

Commit

Permalink
feat: improved passphrase flow (#5279)
Browse files Browse the repository at this point in the history
Description
---
Improves the flow for setting or changing a passphrase.

Closes [issue 5127](#5127).

Motivation and Context
---
When setting or
[changing](#5175) a wallet
passphrase, the console wallet provides
[feedback](#5111) on the
strength of the provided passphrase. In the case of a weak passphrase,
it does not prompt the user to choose a better one.

This PR implements a better flow for this process, as shown in [this
flowchart](#5127 (comment)).

How Has This Been Tested?
---
Tested manually.

What process can a PR reviewer use to test or verify this change?
---
Testing needs to be done manually to assert that the process represented
by the linked flowchart is implemented. Manual testing should cover the
entire flow for these two operations:
- Setting the passphrase for a new wallet
- Changing the passphrase for an existing wallet

Breaking Changes
---
None.
  • Loading branch information
AaronFeickert authored Mar 31, 2023
1 parent d041d34 commit ac21da6
Showing 1 changed file with 84 additions and 33 deletions.
117 changes: 84 additions & 33 deletions applications/tari_console_wallet/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,69 @@ pub enum WalletBoot {
Recovery,
}

/// Get and confirm a passphrase from the user, with feedback
/// This is intended to be used for new or changed passphrases
///
/// You must provide the initial and confirmation prompts to pass to the user
///
/// We do several things:
/// - Prompt the user for a passphrase
/// - Have the user confirm the passphrase
/// - Score the passphrase
/// - If the passphrase is weak (or empty), give feedback and ask the user what to do:
/// - Proceed with the weak (or empty) passphrase
/// - Choose a better passphrase
/// - Cancel the operation
///
/// If the passphrase and confirmation don't match, or if the user cancels, returns an error
/// Otherwise, returns the passphrase as a `SafePassword`
fn get_new_passphrase(prompt: &str, confirm: &str) -> Result<SafePassword, ExitError> {
// We may need to prompt for a passphrase multiple times
loop {
// Prompt the user for a passphrase and confirm it
let passphrase = prompt_password(prompt)?;
let confirmed = prompt_password(confirm)?;
if passphrase.reveal() != confirmed.reveal() {
return Err(ExitError::new(ExitCode::InputError, "Passphrases don't match!"));
}

// Score the passphrase and provide feedback
let weak = display_password_feedback(&passphrase);

// If the passphrase is weak, see if the user wishes to change it
if weak {
println!("Would you like to choose a different passphrase?");
println!(" y/Y: Yes, choose a different passphrase");
println!(" n/N: No, use this passphrase");
println!(" Enter anything else if you changed your mind and want to cancel");

let mut input = "".to_string();
std::io::stdin().read_line(&mut input);

match input.trim().to_lowercase().as_str() {
// Choose a different passphrase
"y" => {
continue;
},
// Use this passphrase
"n" => {
return Ok(passphrase);
},
// By default, we cancel to be safe
_ => {
return Err(ExitError::new(
ExitCode::InputError,
"Canceling with unchanged passphrase!",
));
},
}
} else {
// The passphrase is fine, so return it
return Ok(passphrase);
}
}
}

/// Get feedback, if available, for a weak passphrase
fn get_password_feedback(passphrase: &SafePassword) -> Option<Vec<String>> {
std::str::from_utf8(passphrase.reveal())
Expand All @@ -88,27 +151,35 @@ fn get_password_feedback(passphrase: &SafePassword) -> Option<Vec<String>> {
.map(|suggestion| suggestion.into_iter().map(|item| item.to_string()).collect())
}

// Display password feedback to the user
fn display_password_feedback(passphrase: &SafePassword) {
/// Display passphrase feedback to the user
///
/// Returns `true` if and only if the passphrase is weak
fn display_password_feedback(passphrase: &SafePassword) -> bool {
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!();
println!("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!();

true
} else if let Some(feedback) = get_password_feedback(passphrase) {
// The scoring library provided feedback
println!();
println!(
"However, the password you chose is weak; a determined attacker with access to your device may be able to \
guess it."
"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!();

true
} else {
// There is no feedback to provide
// The Force is strong with this one
false
}
}

Expand Down Expand Up @@ -162,17 +233,8 @@ pub async fn change_password(
)
.await?;

let new = prompt_password("New wallet password: ")?;
let confirmed = prompt_password("Confirm new password: ")?;

if new.reveal() != confirmed.reveal() {
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(&new);
// Get a new passphrase
let new = get_new_passphrase("New wallet passphrase: ", "Confirm new passphrase: ")?;

// Use the existing and new passphrases to attempt to change the wallet passphrase
wallet.db.change_passphrase(&existing, &new).map_err(|e| match e {
Expand Down Expand Up @@ -655,24 +717,13 @@ pub(crate) fn boot_with_password(

let password = match boot_mode {
WalletBoot::New => {
debug!(target: LOG_TARGET, "Prompting for password.");
let password = prompt_password("Create wallet password: ")?;
let confirmed = prompt_password("Confirm wallet password: ")?;

if password.reveal() != confirmed.reveal() {
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
// Get a new passphrase
debug!(target: LOG_TARGET, "Prompting for passphrase.");
get_new_passphrase("Create wallet passphrase: ", "Confirm wallet passphrase: ")?
},
WalletBoot::Existing | WalletBoot::Recovery => {
debug!(target: LOG_TARGET, "Prompting for password.");
prompt_password("Enter wallet password: ")?
debug!(target: LOG_TARGET, "Prompting for passphrase.");
prompt_password("Enter wallet passphrase: ")?
},
};

Expand Down

0 comments on commit ac21da6

Please sign in to comment.