From 01b553bed21e55f9fa294bb70a584aafdbb81f81 Mon Sep 17 00:00:00 2001 From: optout <13562139+optout21@users.noreply.github.com> Date: Mon, 5 May 2025 15:06:14 +0200 Subject: [PATCH] Check for strong password --- README.md | 3 +- secretstore/Cargo.toml | 2 +- secretstore/src/secretstore.rs | 112 +++++++++++++++++++++++-- secretstore/src/test_secretstore.rs | 86 +++++++++++++++---- seedstore-tool/Cargo.toml | 4 +- seedstore/Cargo.toml | 4 +- seedstore/examples/create_seedstore.rs | 3 +- seedstore/src/keystore.rs | 9 +- seedstore/src/seedstore.rs | 14 +++- seedstore/src/test_keystore.rs | 5 +- seedstore/src/test_seedstore.rs | 5 +- seedstore/src/tool.rs | 29 +++++-- 12 files changed, 232 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 2221d51..fe297ef 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ Steps to mitigate the risks: ## TODO -- encorce strong password - version 1.0, with format guarantee - (later) breaking challenge bounty @@ -49,7 +48,7 @@ Writing out seed secret: use seedstore::SeedStoreCreator; let seedstore = SeedStoreCreator::new_from_data(&entropy_bytes, None, None)?; - SeedStoreCreator::write_to_file(&seedstore, "./sample.secret", "PasswordVEWFVFDHHEBNJS3")?; + SeedStoreCreator::write_to_file(&seedstore, "./sample.secret", "PasswordVEWFVFDHHEBNJS3", None)?; } ``` diff --git a/secretstore/Cargo.toml b/secretstore/Cargo.toml index 6c59e68..770cc29 100644 --- a/secretstore/Cargo.toml +++ b/secretstore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "secretstore" -version = "0.9.1" +version = "0.9.3" description = "Store a secret (such as a private key) in an encrypted file" license = "MIT" edition = "2021" diff --git a/secretstore/src/secretstore.rs b/secretstore/src/secretstore.rs index daba540..2ad4a9e 100644 --- a/secretstore/src/secretstore.rs +++ b/secretstore/src/secretstore.rs @@ -129,6 +129,7 @@ impl SecretStore { &self, path_for_secret_file: &str, encryption_password: &str, + allow_weak_password: Option, ) -> Result<(), String> { let file_exists = fs::exists(path_for_secret_file).map_err(|e| { format!( @@ -154,7 +155,8 @@ impl SecretStore { })?; // Create contents - let encrypted_payload = self.assemble_encrypted_payload(encryption_password)?; + let encrypted_payload = + self.assemble_encrypted_payload(encryption_password, allow_weak_password)?; // Set restricted permissions #[cfg(feature = "unixfilepermissions")] @@ -191,7 +193,11 @@ impl SecretStore { Ok(()) } - pub fn assemble_encrypted_payload(&self, encryption_password: &str) -> Result, String> { + pub fn assemble_encrypted_payload( + &self, + encryption_password: &str, + allow_weak_password: Option, + ) -> Result, String> { let mut encrypted = self.scrambled_secret_data.clone(); let _res = encrypt_scrambled_secret_data( &mut encrypted, @@ -199,6 +205,7 @@ impl SecretStore { self.encryption_version, encryption_password, &self.encryption_aux_data, + allow_weak_password, )?; assemble_payload( self.format_version, @@ -221,6 +228,13 @@ impl SecretStore { xor::XorEncryptor::encrypt_with_key(scrambled_data, &self.ephemeral_scrambling_key)?; Ok(()) } + + /// Validate if an encryption password is acceptable (strong enough) + pub fn validate_password(encryption_password: &str) -> Result<(), String> { + let _res = validate_password_len(encryption_password)?; + let _res = validate_password_char_types(encryption_password)?; + Ok(()) + } } impl Zeroize for SecretStore { @@ -289,8 +303,13 @@ impl SecretStoreCreator { secretstore: &SecretStore, path_for_secret_file: &str, encryption_password: &str, + allow_weak_password: Option, ) -> Result<(), String> { - secretstore.write_to_file(path_for_secret_file, encryption_password) + secretstore.write_to_file( + path_for_secret_file, + encryption_password, + allow_weak_password, + ) } } @@ -666,7 +685,7 @@ fn scramble_encrypted_secret_data( Ok(()) } -fn validate_password(encryption_password: &str) -> Result<(), String> { +fn validate_password_len(encryption_password: &str) -> Result<(), String> { if encryption_password.len() < PASSWORD_MIN_LEN { return Err(format!( "Password is too short! ({} vs {})", @@ -677,6 +696,42 @@ fn validate_password(encryption_password: &str) -> Result<(), String> { Ok(()) } +fn validate_password_char_types(encryption_password: &str) -> Result<(), String> { + let mut count_lowercase_letter = 0; + let mut count_uppercase_letter = 0; + let mut count_digit = 0; + let mut count_other = 0; + + for c in encryption_password.chars() { + if c >= 'a' && c <= 'z' { + count_lowercase_letter += 1; + } else if c >= 'A' && c <= 'Z' { + count_uppercase_letter += 1; + } else if c >= '0' && c <= '9' { + count_digit += 1; + } else { + count_other += 1; + } + } + + if count_lowercase_letter < 2 { + return Err(format!("Password needs to contain lowercase letters")); + } + if count_uppercase_letter < 2 { + return Err(format!("Password needs to contain uppercase letters")); + } + if count_digit < 1 { + return Err(format!("Password needs to contain digits (at least one)")); + } + if count_other < 1 { + return Err(format!( + "Password needs to contain special characters (at least one)" + )); + } + + Ok(()) +} + /// Take scrambled secret, and encrypt it. /// Caution: unencrypted secret is available internally for a short time. fn encrypt_scrambled_secret_data( @@ -685,8 +740,11 @@ fn encrypt_scrambled_secret_data( encryption_version: EncryptionVersion, encryption_password: &str, aux_data: &EncryptionAuxData, + allow_weak_password: Option, ) -> Result<(), String> { - let _res = validate_password(encryption_password)?; + if !(allow_weak_password.unwrap_or(false)) { + let _res = SecretStore::validate_password(encryption_password)?; + } // first de-scramble let _res = descramble_secret(scrambled, scrambling_key)?; @@ -715,7 +773,11 @@ fn checksum_of_payload(payload: &[u8]) -> Result, String> { #[cfg(test)] mod tests { - use super::{two_bytes_from_u16, u16_from_two_bytes}; + use super::{ + two_bytes_from_u16, u16_from_two_bytes, validate_password_char_types, validate_password_len, + }; + + const GOOD_PASS: &str = "Hgg7+kJ$hf7kl"; #[test] fn parse_u16() { @@ -728,4 +790,42 @@ mod tests { let (b1, b2) = two_bytes_from_u16(0x7348); assert_eq!(u16_from_two_bytes(b1, b2), 0x7348) } + + #[test] + fn password_len() { + assert!(validate_password_len(GOOD_PASS).is_ok()); + assert_eq!( + validate_password_len("SHO").err().unwrap(), + "Password is too short! (3 vs 7)" + ); + } + + #[test] + fn password_char_types() { + assert!(validate_password_char_types(GOOD_PASS).is_ok()); + assert!(validate_password_char_types("abc").is_err()); + assert!(validate_password_char_types("ABC").is_err()); + assert!(validate_password_char_types("123").is_err()); + assert!(validate_password_char_types("+-=").is_err()); + assert!(validate_password_char_types("abAB").is_err()); + assert!(validate_password_char_types("abAB12").is_err()); + assert!(validate_password_char_types("abAB1+").is_ok()); + + assert_eq!( + validate_password_char_types("AB1+").err().unwrap(), + "Password needs to contain lowercase letters" + ); + assert_eq!( + validate_password_char_types("ab1+").err().unwrap(), + "Password needs to contain uppercase letters" + ); + assert_eq!( + validate_password_char_types("abAB+").err().unwrap(), + "Password needs to contain digits (at least one)" + ); + assert_eq!( + validate_password_char_types("abAB1").err().unwrap(), + "Password needs to contain special characters (at least one)" + ); + } } diff --git a/secretstore/src/test_secretstore.rs b/secretstore/src/test_secretstore.rs index 8a90852..4a41422 100644 --- a/secretstore/src/test_secretstore.rs +++ b/secretstore/src/test_secretstore.rs @@ -7,12 +7,13 @@ use zeroize::Zeroize; const PASSWORD1: &str = "password"; const PASSWORD2: &str = "This is a different password, ain't it?"; +const PASSWORD3: &str = "aA1+bB2+cC3"; const NONSECRET_DATA1: &str = "010203"; const SECRET_DATA1: &str = "0102030405060708"; const PAYLOAD_V1_EV3_CHACHA: &str = - "53530103010203030db42435f1ff810be24030aee961eb14528c7bb8303431d947b0f5a7e5a0b5648b9c481fd4e1cfad7918007525accb0b6e1b4a66683e6d1264dac8ecda5ba82cdbcbadccd5cd05"; + "53530103010203030d030921f21328f89184142fa199644f687ce5b26d36f0f6d23f627bf7279e316460fbe4586e6d86f9180037904ec7d67bb525cfc1db2f1264e2b5f724163f2cdef41383f6d8a1"; const PAYLOAD_V1_EV2_SCRYPT: &str = - "53530103010203020e24799f2ebaf27d4cd517136dd57ad71b0800b44fe9ca543c2f4dd8349e1b"; + "53530103010203020ede40d43fd48c18a5c94ad74452685f320800b16b2e86bc31d503924774d5"; const PAYLOAD_V1_EV1_XOR: &str = "5353010301020301f6ecb1e25f4945ae0605638d75c6a34208006e59b27ff2c90dd87c320627"; @@ -31,7 +32,7 @@ fn create_payload_from_data( ) -> Vec { let store = create_store_from_data(nonsecret_data, secret_data); let payload = store - .assemble_encrypted_payload(encryption_password) + .assemble_encrypted_payload(encryption_password, None) .unwrap(); payload } @@ -46,7 +47,7 @@ fn create_from_data() { assert_eq!(store.secret_data().unwrap().clone(), secret_data); // uncomment for obtaining actual output - // let payload = store.assemble_encrypted_payload(&PASSWORD1).unwrap(); + // let payload = store.assemble_encrypted_payload(&PASSWORD3).unwrap(); // assert_eq!(payload.to_lower_hex_string(), "_placeholder_"); store.zeroize(); @@ -55,7 +56,7 @@ fn create_from_data() { #[test] fn create_from_payload_const_scrypt() { let payload = Vec::from_hex(PAYLOAD_V1_EV2_SCRYPT).unwrap(); - let password = PASSWORD1.to_owned(); + let password = PASSWORD3.to_owned(); let mut store = create_store_from_payload(&payload, &password); @@ -70,7 +71,7 @@ fn create_from_payload_const_scrypt() { assert_eq!( store - .assemble_encrypted_payload(&password) + .assemble_encrypted_payload(&password, None) .unwrap() .to_lower_hex_string(), PAYLOAD_V1_EV2_SCRYPT @@ -82,7 +83,7 @@ fn create_from_payload_const_scrypt() { #[test] fn create_from_payload_const_chacha() { let payload = Vec::from_hex(PAYLOAD_V1_EV3_CHACHA).unwrap(); - let password = PASSWORD1.to_owned(); + let password = PASSWORD3.to_owned(); let mut store = create_store_from_payload(&payload, &password); @@ -97,7 +98,7 @@ fn create_from_payload_const_chacha() { assert_eq!( store - .assemble_encrypted_payload(&password) + .assemble_encrypted_payload(&password, None) .unwrap() .to_lower_hex_string(), PAYLOAD_V1_EV3_CHACHA @@ -129,7 +130,7 @@ fn create_from_payload_const_xor() { fn create_from_payload_generated() { let nonsecret_data = Vec::from_hex(NONSECRET_DATA1).unwrap(); let secret_data = Vec::from_hex(SECRET_DATA1).unwrap(); - let password = PASSWORD1.to_owned(); + let password = PASSWORD3.to_owned(); let payload = create_payload_from_data(nonsecret_data.clone(), &secret_data, &password); let mut store = create_store_from_payload(&payload, &password); @@ -138,7 +139,7 @@ fn create_from_payload_generated() { assert_eq!(store.secret_data().unwrap().clone(), secret_data); // Note: cannot assert full payload, contains dynamic fields - let payload = store.assemble_encrypted_payload(&password).unwrap(); + let payload = store.assemble_encrypted_payload(&password, None).unwrap(); assert_eq!(payload.len(), 39); assert_eq!(payload[0..9].to_lower_hex_string(), "53530103010203020e"); @@ -149,7 +150,7 @@ fn create_from_payload_generated() { fn create_from_payload_wrong_pw() { let nonsecret_data = Vec::from_hex(NONSECRET_DATA1).unwrap(); let secret_data = Vec::from_hex(SECRET_DATA1).unwrap(); - let password = PASSWORD2.to_owned(); + let password = PASSWORD3.to_owned(); let payload = create_payload_from_data(nonsecret_data.clone(), &secret_data, &password); let mut store = create_store_from_payload(&payload, &password); @@ -158,7 +159,7 @@ fn create_from_payload_wrong_pw() { assert_eq!(store.secret_data().unwrap().clone(), secret_data); // Note: cannot assert full payload, contains dynamic fields - let payload = store.assemble_encrypted_payload(&password).unwrap(); + let payload = store.assemble_encrypted_payload(&password, None).unwrap(); assert_eq!(payload.len(), 39); assert_eq!(payload[0..9].to_lower_hex_string(), "53530103010203020e"); @@ -205,7 +206,7 @@ fn neg_create_from_data_nonsecret_too_long() { fn neg_create_from_payload_with_wrong_checksum() { let nonsecret_data = Vec::from_hex(NONSECRET_DATA1).unwrap(); let secret_data = Vec::from_hex(SECRET_DATA1).unwrap(); - let password = PASSWORD1.to_owned(); + let password = PASSWORD3.to_owned(); let mut payload = create_payload_from_data(nonsecret_data.clone(), &secret_data, &password); let payload_len = payload.len(); assert!(payload_len > 0); @@ -233,8 +234,8 @@ fn write_to_file() { let store = create_store_from_data(nonsecret_data.clone(), &secret_data); let temp_file = get_temp_file_name(); - let password = PASSWORD1.to_owned(); - let _res = store.write_to_file(&temp_file, &password).unwrap(); + let password = PASSWORD3.to_owned(); + let _res = store.write_to_file(&temp_file, &password, None).unwrap(); // check the file let contents = fs::read(&temp_file).unwrap(); @@ -253,6 +254,42 @@ fn write_to_file() { let _res = fs::remove_file(&temp_file); } +#[test] +fn neg_write_to_file_weak_password() { + let nonsecret_data = Vec::from_hex(NONSECRET_DATA1).unwrap(); + let secret_data = Vec::from_hex(SECRET_DATA1).unwrap(); + let store = create_store_from_data(nonsecret_data.clone(), &secret_data); + + let temp_file = get_temp_file_name(); + let password = PASSWORD1.to_owned(); + let res = store.write_to_file(&temp_file, &password, None); + assert_eq!( + res.err().unwrap(), + "Password needs to contain uppercase letters" + ); +} + +#[test] +fn write_to_file_weak_password() { + let nonsecret_data = Vec::from_hex(NONSECRET_DATA1).unwrap(); + let secret_data = Vec::from_hex(SECRET_DATA1).unwrap(); + let store = create_store_from_data(nonsecret_data.clone(), &secret_data); + + let temp_file = get_temp_file_name(); + let password = PASSWORD1.to_owned(); + let _res = store + .write_to_file(&temp_file, &password, Some(true)) + .unwrap(); + + // check the file + let contents = fs::read(&temp_file).unwrap(); + // Note: cannot assert full contents, it contains dynamic fields + assert_eq!(contents.len(), 39); + assert_eq!(contents[0..9].to_lower_hex_string(), "53530103010203020e"); + + let _res = fs::remove_file(&temp_file); +} + #[test] fn read_from_file() { let temp_file = get_temp_file_name(); @@ -261,7 +298,7 @@ fn read_from_file() { let payload = Vec::from_hex(PAYLOAD_V1_EV2_SCRYPT).unwrap(); let _res = fs::write(&temp_file, &payload).unwrap(); - let password = PASSWORD1.to_owned(); + let password = PASSWORD3.to_owned(); let store = SecretStore::new_from_encrypted_file(&temp_file, &password).unwrap(); assert_eq!( @@ -290,7 +327,7 @@ fn neg_read_from_file_scrypt_wrong_pw_wrong_result() { assert_eq!(store.nonsecret_data().to_lower_hex_string(), "010203"); assert_eq!( store.secret_data().unwrap().to_lower_hex_string(), - "ffc771ed7dfa9558" + "a9ba74fa163b15bb" ); let _res = fs::remove_file(&temp_file); @@ -340,7 +377,7 @@ fn neg_create_password_too_short() { let short_password = "no"; let _res = fs::remove_file(&temp_file); - let res = SecretStoreCreator::write_to_file(&store, &temp_file, short_password); + let res = SecretStoreCreator::write_to_file(&store, &temp_file, short_password, None); assert_eq!(res.err().unwrap(), "Password is too short! (2 vs 7)"); let _res = fs::remove_file(&temp_file); @@ -359,3 +396,16 @@ fn scramble_descramble_arbitrary() { let _res = store.descramble_data(&mut data).unwrap(); assert_eq!(data.to_lower_hex_string(), data_hex); } + +#[test] +fn validate_password() { + assert!(SecretStore::validate_password(PASSWORD3).is_ok()); + assert_eq!( + SecretStore::validate_password("abAB1+").err().unwrap(), + "Password is too short! (6 vs 7)" + ); + assert_eq!( + SecretStore::validate_password("xMn+J=dh!Z").err().unwrap(), + "Password needs to contain digits (at least one)" + ); +} diff --git a/seedstore-tool/Cargo.toml b/seedstore-tool/Cargo.toml index ae97ccf..82868d6 100644 --- a/seedstore-tool/Cargo.toml +++ b/seedstore-tool/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "seedstore-tool" -version = "0.9.1" +version = "0.9.3" edition = "2021" [dependencies] bip39 = "2.1.0" -seedstore = { version = "0.9.1", path = "../seedstore", features = ["toolhelper"] } +seedstore = { version = "0.9.3", path = "../seedstore", features = ["toolhelper"] } diff --git a/seedstore/Cargo.toml b/seedstore/Cargo.toml index 3ca2fe2..111512b 100644 --- a/seedstore/Cargo.toml +++ b/seedstore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "seedstore" -version = "0.9.2" +version = "0.9.3" description = "Store bitcoin secret material (BIP39 mnemonic entropy, or similar) in an encrypted file" license = "MIT" edition = "2021" @@ -16,7 +16,7 @@ toolhelper = ["rpassword"] bip39 = { version = "2.1.0", features = ["zeroize"] } bitcoin = "0.32.5" rpassword = { version = "7.4.0", optional = true } -secretstore = { version = "0.9.1", path = "../secretstore" } +secretstore = { version = "0.9.3", path = "../secretstore" } zeroize = "1.8.1" [dev-dependencies] diff --git a/seedstore/examples/create_seedstore.rs b/seedstore/examples/create_seedstore.rs index aef8aac..75b5bd0 100644 --- a/seedstore/examples/create_seedstore.rs +++ b/seedstore/examples/create_seedstore.rs @@ -12,7 +12,8 @@ fn main() -> Result<(), String> { let path_for_secret_file = format!("{}/sample.secret", temp_dir().to_str().unwrap()); let mut seedstore = SeedStoreCreator::new_from_data(&dummy_entropy, None, None)?; - let _res = SeedStoreCreator::write_to_file(&seedstore, &path_for_secret_file, user_password)?; + let _res = + SeedStoreCreator::write_to_file(&seedstore, &path_for_secret_file, user_password, None)?; println!("Secret entropy written to file {}", path_for_secret_file); seedstore.zeroize(); diff --git a/seedstore/src/keystore.rs b/seedstore/src/keystore.rs index db0a8d4..d3e0a98 100644 --- a/seedstore/src/keystore.rs +++ b/seedstore/src/keystore.rs @@ -114,11 +114,13 @@ impl KeyStore { &self, path_for_secret_file: &str, encryption_password: &str, + allow_weak_password: Option, ) -> Result<(), String> { SecretStoreCreator::write_to_file( &self.secretstore, path_for_secret_file, encryption_password, + allow_weak_password, ) } @@ -203,7 +205,12 @@ impl KeyStoreCreator { seedstore: &KeyStore, path_for_secret_file: &str, encryption_password: &str, + allow_weak_password: Option, ) -> Result<(), String> { - seedstore.write_to_file(path_for_secret_file, encryption_password) + seedstore.write_to_file( + path_for_secret_file, + encryption_password, + allow_weak_password, + ) } } diff --git a/seedstore/src/seedstore.rs b/seedstore/src/seedstore.rs index 38c3f04..ee8322a 100644 --- a/seedstore/src/seedstore.rs +++ b/seedstore/src/seedstore.rs @@ -114,11 +114,13 @@ impl SeedStore { &self, path_for_secret_file: &str, encryption_password: &str, + allow_weak_password: Option, ) -> Result<(), String> { SecretStoreCreator::write_to_file( &self.secretstore, path_for_secret_file, encryption_password, + allow_weak_password, ) } @@ -361,6 +363,11 @@ impl SeedStore { Ok(signature) } + + /// Validate if an encryption password is acceptable (strong enough) + pub fn validate_password(encryption_password: &str) -> Result<(), String> { + SecretStore::validate_password(encryption_password) + } } impl Zeroize for SeedStore { @@ -416,8 +423,13 @@ impl SeedStoreCreator { seedstore: &SeedStore, path_for_secret_file: &str, encryption_password: &str, + allow_weak_password: Option, ) -> Result<(), String> { - seedstore.write_to_file(path_for_secret_file, encryption_password) + seedstore.write_to_file( + path_for_secret_file, + encryption_password, + allow_weak_password, + ) } } diff --git a/seedstore/src/test_keystore.rs b/seedstore/src/test_keystore.rs index 8abf45d..4e55244 100644 --- a/seedstore/src/test_keystore.rs +++ b/seedstore/src/test_keystore.rs @@ -7,6 +7,7 @@ use zeroize::Zeroize; const PASSWORD1: &str = "password"; const PASSWORD2: &str = "This is a different password, ain't it?"; +const PASSWORD3: &str = "aA1+bB2+cC3"; const PAYLOAD_V1_EV2_SCRYPT: &str = "535301042a2b2c2d020ef2b6285346559b45dd51fa64ecb5d4e82000adb162a64389dee81bbf1a788b0626961153c6e6edefe6aa03b07e44d2d3d727bb318d87"; const SECRETKEY1: &str = "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"; const PUBKEY1: &str = "03e93e38d9069fea998726eb25a5e9bdaadae9161ef8e63508dba807334dced88b"; @@ -84,8 +85,8 @@ fn write_to_file() { let store = KeyStoreCreator::new_from_data(&secret_key).unwrap(); let temp_file = get_temp_file_name(); - let password = PASSWORD1.to_owned(); - let _res = store.write_to_file(&temp_file, &password).unwrap(); + let password = PASSWORD3.to_owned(); + let _res = store.write_to_file(&temp_file, &password, None).unwrap(); // check the file let contents = fs::read(&temp_file).unwrap(); diff --git a/seedstore/src/test_seedstore.rs b/seedstore/src/test_seedstore.rs index 30eb2c8..d572e56 100644 --- a/seedstore/src/test_seedstore.rs +++ b/seedstore/src/test_seedstore.rs @@ -8,6 +8,7 @@ use zeroize::Zeroize; const PASSWORD1: &str = "password"; const PASSWORD2: &str = "This is a different password, ain't it?"; +const PASSWORD3: &str = "aA1+bB2+cC3"; const ENTROPY_OIL12: &str = "99d33a674ce99d33a674ce99d33a674c"; const PAYLOAD_V1_EV3_CHACHA: &str = "53530104002a2b2c030d6a14b96b3dc98ad33c2dc35966f1d019ae236ce28b8f003388bd0c6f6d6fa18c1ff12521a46bd2e52000e8b03820e69c000daddc7dbcf76a6a0137097893246d83033a6249cd89a21e3a3f8e8626"; @@ -170,8 +171,8 @@ fn write_to_file() { let store = SeedStoreCreator::new_from_data(&entropy, None, None).unwrap(); let temp_file = get_temp_file_name(); - let password = PASSWORD1.to_owned(); - let _res = store.write_to_file(&temp_file, &password).unwrap(); + let password = PASSWORD3.to_owned(); + let _res = store.write_to_file(&temp_file, &password, None).unwrap(); // check the file let contents = fs::read(&temp_file).unwrap(); diff --git a/seedstore/src/tool.rs b/seedstore/src/tool.rs index 4c34f33..a67b216 100644 --- a/seedstore/src/tool.rs +++ b/seedstore/src/tool.rs @@ -23,6 +23,7 @@ struct Config { network: Option, program_name: String, test_canned_input: Vec, + allow_weak_password: bool, } /// Utility tool implementation: tool to create or check an encrypted secret seed file. @@ -38,6 +39,7 @@ impl Config { network: None, program_name: "tool".to_owned(), test_canned_input: Vec::new(), + allow_weak_password: false, } } } @@ -78,7 +80,7 @@ impl SeedStoreTool { println!("{}: Set or check secret seed file", progname); println!(""); println!( - "{} [--help] [--set] [--file ] [--signet] [--net ]", + "{} [--help] [--set] [--file ] [--signet] [--net ] [--weakpw]", progname ); println!(" --set: If specified, mnemominc is prompted for, and secret is saved. Secret file must not exist."); @@ -95,6 +97,7 @@ impl SeedStoreTool { " --net Network byte. Default is mainnet ({})", DEFAULT_NETWORK ); + println!(" --weakpw Allow weak encryption password"); println!(" --help Print usage (this)"); println!(""); } @@ -136,6 +139,8 @@ impl SeedStoreTool { } } else if *a == "--help" { config.mode = Mode::Help; + } else if *a == "--weakpw" { + config.allow_weak_password = true; } else { return Err(format!("Unknown argument {}", a)); } @@ -196,6 +201,9 @@ impl SeedStoreTool { println!("Seedphrase entered, seems OK"); let password = self.read_password()?; + if !self.config.allow_weak_password { + let _res = SeedStore::validate_password(&password)?; + } let passphrase = self.read_passphrase_if_needed(&password)?; @@ -210,8 +218,13 @@ impl SeedStoreTool { let xpub = self.print_info(&seedstore)?; - let _res = SeedStoreCreator::write_to_file(&seedstore, &self.config.filename, &password) - .map_err(|e| format!("Could not write secret file, {}", e))?; + let _res = SeedStoreCreator::write_to_file( + &seedstore, + &self.config.filename, + &password, + Some(self.config.allow_weak_password), + ) + .map_err(|e| format!("Could not write secret file, {}", e))?; println!("Seed written to encrypted file: {}", self.config.filename); @@ -363,6 +376,7 @@ mod tests { const PAYLOAD_V1_EV2_SCRYPT: &str = "53530104002a2b2c020ee3d3970706fbe9f680eb68763af3c849100010907fc4f2740c7613422df300488137c1e6af59"; const PASSWORD1: &str = "password"; + const PASSWORD3: &str = "aA1+bB2+cC3"; const XPUB1: &str = "xpub6CDDB17Xj7pDDWedpLsED1JbPPQmyuapHmAzQEEs2P57hciCjwQ3ov7TfGsTZftAM2gVdPzE55L6gUvHguwWjY82518zw1Z3VbDeWgx3Jqs"; const MNEMO1: &str = "oil oil oil oil oil oil oil oil oil oil oil oil"; @@ -390,6 +404,7 @@ mod tests { PASSWORD1.to_owned(), // repeat encryption password "".to_owned(), // passphrase ], + allow_weak_password: false, }; let mut tool = SeedStoreTool::new_from_config(config); @@ -412,10 +427,11 @@ mod tests { program_name: "tool".to_owned(), test_canned_input: vec![ MNEMO1.to_owned(), // mnemonic - PASSWORD1.to_owned(), // encryption password - PASSWORD1.to_owned(), // repeat encryption password + PASSWORD3.to_owned(), // encryption password + PASSWORD3.to_owned(), // repeat encryption password "".to_owned(), // passphrase ], + allow_weak_password: false, }; let mut tool = SeedStoreTool::new_from_config(config); @@ -425,7 +441,7 @@ mod tests { // Read back the file { - let store = SeedStore::new_from_encrypted_file(&temp_file, PASSWORD1, None).unwrap(); + let store = SeedStore::new_from_encrypted_file(&temp_file, PASSWORD3, None).unwrap(); assert_eq!(store.network_as_u8(), network); assert_eq!(store.get_xpub().unwrap().to_string(), "tpubDCRo9GmRAvEWANJ5iSfMEqPoq3uYvjBPAAjrDj5iQMxAq7DCs5orw7m9xJes8hWYAwKuH3T63WrKfzzw7g9ucbjq4LUu5cgCLUPMN7gUkrL"); @@ -449,6 +465,7 @@ mod tests { "password".to_owned(), // encryption password "passsword".to_owned(), // repeat encryption password ], + allow_weak_password: false, }; let mut tool = SeedStoreTool::new_from_config(config);