diff --git a/Cargo.lock b/Cargo.lock index 17cb19d650..a5bd6eacb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,21 @@ dependencies = [ "shlex", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitfield" version = "0.13.2" @@ -1314,8 +1329,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + +[[package]] +name = "darling" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +dependencies = [ + "darling_core 0.14.2", + "darling_macro 0.14.2", ] [[package]] @@ -1332,13 +1357,38 @@ dependencies = [ "syn", ] +[[package]] +name = "darling_core" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + [[package]] name = "darling_macro" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ - "darling_core", + "darling_core 0.10.2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +dependencies = [ + "darling_core 0.14.2", "quote", "syn", ] @@ -1396,25 +1446,56 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" dependencies = [ - "darling", - "derive_builder_core", + "darling 0.10.2", + "derive_builder_core 0.9.0", "proc-macro2", "quote", "syn", ] +[[package]] +name = "derive_builder" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" +dependencies = [ + "derive_builder_macro", +] + [[package]] name = "derive_builder_core" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" dependencies = [ - "darling", + "darling 0.10.2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" +dependencies = [ + "darling 0.14.2", "proc-macro2", "quote", "syn", ] +[[package]] +name = "derive_builder_macro" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" +dependencies = [ + "derive_builder_core 0.11.2", + "syn", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -1647,6 +1728,16 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0198b9d0078e0f30dedc7acbb21c974e838fc8fae3ee170128658a98cb2c1c04" +[[package]] +name = "fancy-regex" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95b4efe5be9104a4a18a9916e86654319895138be727b229820c39257c30dda" +dependencies = [ + "bit-set", + "regex", +] + [[package]] name = "fast-float" version = "0.2.0" @@ -2106,7 +2197,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" dependencies = [ - "quick-error", + "quick-error 1.2.3", ] [[package]] @@ -3282,7 +3373,7 @@ dependencies = [ "circular", "clear_on_drop", "crc24", - "derive_builder", + "derive_builder 0.9.0", "des", "digest 0.9.0", "ed25519-dalek", @@ -3606,6 +3697,12 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.23" @@ -4897,6 +4994,7 @@ dependencies = [ "unicode-segmentation", "unicode-width", "zeroize", + "zxcvbn", ] [[package]] @@ -6422,3 +6520,19 @@ dependencies = [ "syn", "synstructure", ] + +[[package]] +name = "zxcvbn" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568becce91e872373a4b33f24ddc67e5280ae2536ccb8c9d22a25d398b72c8b0" +dependencies = [ + "derive_builder 0.11.2", + "fancy-regex", + "itertools 0.10.5", + "js-sys", + "lazy_static", + "quick-error 2.0.1", + "regex", + "time 0.3.17", +] diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index f68d49995e..1d75e52d9d 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -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" diff --git a/applications/tari_console_wallet/src/init/mod.rs b/applications/tari_console_wallet/src/init/mod.rs index 2526752466..a550c1bf30 100644 --- a/applications/tari_console_wallet/src/init/mod.rs +++ b/applications/tari_console_wallet/src/init/mod.rs @@ -59,6 +59,7 @@ use tari_wallet::{ WalletConfig, WalletSqlite, }; +use zxcvbn::zxcvbn; use crate::{ cli::Cli, @@ -77,6 +78,40 @@ pub enum WalletBoot { Recovery, } +/// Get feedback, if available, for a weak passphrase +fn get_password_feedback(passphrase: &SafePassword) -> Option> { + 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( @@ -105,15 +140,7 @@ pub fn get_or_prompt_password( } fn prompt_password(prompt: &str) -> Result { - 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)) } @@ -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(()) } @@ -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 => { @@ -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()); + } +}