diff --git a/shell.nix b/shell.nix index cdfaaed..4c7183a 100644 --- a/shell.nix +++ b/shell.nix @@ -2,6 +2,7 @@ pkgs.mkShell { buildInputs = with pkgs; [ rustup + python3 ]; shellHook = '' rustup default stable diff --git a/src/lib.rs b/src/lib.rs index e9b7c87..2758bf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,32 +103,15 @@ fn get_password_store( let mut input: SafeVec = SafeVec::new(Vec::new()); file.read_to_end(input.inner_mut()).map_err(|_| 1)?; - return get_password_store_from_input_interactive(&input, 3, false, false, io).map_err(|_| 1); + return get_password_store_from_input_interactive(&input, 3, false, io).map_err(|_| 1); } fn get_password_store_from_input_interactive( input: &SafeVec, retries: i32, force_upgrade: bool, - retry: bool, io: &mut impl CliInputOutput, ) -> Result { - if retries == 0 { - io.error( - "Decryption of your Rooster file keeps failing. \ - Your Rooster file is probably corrupted.", - OutputType::Error, - ); - return Err(password::PasswordError::CorruptionLikelyError); - } - - if retry { - io.error( - "Woops, that's not the right password. Let's try again.", - OutputType::Error, - ); - } - let master_password = match ask_master_password(io) { Ok(p) => p, Err(err) => { @@ -147,10 +130,6 @@ fn get_password_store_from_input_interactive( Ok(store) => { return Ok(store); } - Err(password::PasswordError::CorruptionError) => { - io.error("Your Rooster file is corrupted.", OutputType::Error); - return Err(password::PasswordError::CorruptionError); - } Err(password::PasswordError::OutdatedRoosterBinaryError) => { io.error( "I could not open the Rooster file because your version of Rooster is outdated.", @@ -162,7 +141,7 @@ fn get_password_store_from_input_interactive( ); return Err(password::PasswordError::OutdatedRoosterBinaryError); } - Err(password::PasswordError::Io(err)) => { + Err(password::PasswordError::Io(err)) => { io.error( format!("I couldn't open your Rooster file (reason: {:?})", err), OutputType::Error, @@ -179,7 +158,7 @@ fn get_password_store_from_input_interactive( if line.starts_with('y') { // This time we'll try to upgrade return get_password_store_from_input_interactive( - &input, retries, true, false, io, + &input, retries, true, io, ); } else if line.starts_with('n') { // The user doesn't want to upgrade, that's fine @@ -202,8 +181,26 @@ fn get_password_store_from_input_interactive( } } } - _ => { - return get_password_store_from_input_interactive(&input, retries - 1, false, true, io); + Err(password::PasswordError::InvalidPasswordError) => { + if retries - 1 <= 0 { + io.error( + "Woops, that's not the right password. Aborting.", + OutputType::Error, + ); + return Err(password::PasswordError::InvalidPasswordError); + } + io.error( + "Woops, that's not the right password. Let's try again.", + OutputType::Error, + ); + return get_password_store_from_input_interactive(&input, retries - 1, false, io); + } + Err(err) => { + io.error( + format!("I couldn't open your Rooster file (reason: {:?})", err), + OutputType::Error, + ); + return Err(err); } } } @@ -218,8 +215,8 @@ fn get_password_store_from_input( Ok(store) => { return Ok(store); } - Err(password::PasswordError::CorruptionError) => { - return Err(password::PasswordError::CorruptionError); + Err(password::PasswordError::InvalidPasswordError) => { + return Err(password::PasswordError::InvalidPasswordError); } Err(password::PasswordError::OutdatedRoosterBinaryError) => { return Err(password::PasswordError::OutdatedRoosterBinaryError); diff --git a/src/password/mod.rs b/src/password/mod.rs index 7d5dce2..9bf4c90 100644 --- a/src/password/mod.rs +++ b/src/password/mod.rs @@ -16,8 +16,7 @@ pub enum PasswordError { Io(IoError), OutdatedRoosterBinaryError, InvalidJsonError, - CorruptionError, - CorruptionLikelyError, + InvalidPasswordError, NeedUpgradeErrorFromV1, NoUpgradeError, EmptyPasswordError, diff --git a/src/password/v2.rs b/src/password/v2.rs index d12cd00..7469556 100644 --- a/src/password/v2.rs +++ b/src/password/v2.rs @@ -279,8 +279,8 @@ impl PasswordStore { })?; // The encrypted password data. - let mut blob: Vec = Vec::new(); - reader.read_to_end(&mut blob)?; + let mut cipher_blob: Vec = Vec::new(); + reader.read_to_end(&mut cipher_blob)?; // Derive a 256 bits encryption key from the password. let key = generate_encryption_key( @@ -291,8 +291,22 @@ impl PasswordStore { scrypt_p, ); + // Verify the HMAC, must be done before decrypting + let hmac_blob = digest_blob_with_metadata( + version, + scrypt_log2_n, + scrypt_r, + scrypt_p, + &iv, + &salt, + cipher_blob.deref(), + )?; + if !verify_signature(old_signature_mac.as_slice(), hmac_blob.deref(), key.deref()) { + return Err(PasswordError::InvalidPasswordError); + } + // Decrypt the data. - let passwords = match aes::decrypt(blob.deref(), key.as_ref(), iv.as_ref()) { + let passwords = match aes::decrypt(cipher_blob.deref(), key.as_ref(), iv.as_ref()) { Ok(decrypted) => { let encoded = SafeString::from_string( String::from_utf8_lossy(decrypted.as_ref()).into_owned(), @@ -306,24 +320,10 @@ impl PasswordStore { } } Err(_) => { - return Err(PasswordError::DecryptionError); + return Err(PasswordError::InvalidPasswordError); } }; - let blob = digest_blob_with_metadata( - version, - scrypt_log2_n, - scrypt_r, - scrypt_p, - &iv, - &salt, - blob.deref(), - ) - .unwrap(); - if !verify_signature(old_signature_mac.as_slice(), blob.deref(), key.deref()) { - return Err(PasswordError::CorruptionError); - } - Ok(PasswordStore { key: key, scrypt_log2_n: scrypt_log2_n, diff --git a/tests/test-commands-add.rs b/tests/test-commands-add.rs index 5f84d6a..e1c94ba 100644 --- a/tests/test-commands-add.rs +++ b/tests/test-commands-add.rs @@ -14,14 +14,13 @@ fn test_command_add() { ) ); - assert_eq!( - 0, - main_with_args( - &["rooster", "add", "-s", "Youtube", "yt@example.com"], - &mut CursorInputOutput::new("", "xxxx\nabcd\n"), - &rooster_file - ) + let mut inout = CursorInputOutput::new("", "xxxx\nabcd\n"); + let result = main_with_args( + &["rooster", "add", "-s", "Youtube", "yt@example.com"], + &mut inout, + &rooster_file ); + assert_eq!(0, result); // Password exists assert_eq!( diff --git a/tests/test-corruption.rs b/tests/test-corruption.rs index d37404c..09a7928 100644 --- a/tests/test-corruption.rs +++ b/tests/test-corruption.rs @@ -40,5 +40,5 @@ fn test_corruption() { ); let output_as_vecu8 = io.stderr_cursor.into_inner(); let output_as_string = String::from_utf8_lossy(output_as_vecu8.as_slice()); - assert!(output_as_string.contains("Your Rooster file is corrupted")); + assert!(output_as_string.contains("Woops, that's not the right password. Let's try again.")); } diff --git a/tests/test-password-retry.rs b/tests/test-password-retry.rs index 0f87278..5d9cdf4 100644 --- a/tests/test-password-retry.rs +++ b/tests/test-password-retry.rs @@ -14,14 +14,9 @@ fn test_password_retry_ok() { ) ); - assert_eq!( - 0, - main_with_args( - &["rooster", "list"], - &mut CursorInputOutput::new("", "nok\nnok\nxxxx\n"), - &rooster_file - ) - ); + let mut io = CursorInputOutput::new("", "nok\nnok\nxxxx\n"); + let result = main_with_args(&["rooster", "list"], &mut io, &rooster_file); + assert_eq!(0, result); } #[test] @@ -37,11 +32,9 @@ fn test_password_retry_nok() { ); let mut io = CursorInputOutput::new("", "nok\nnok\nnok\n"); - assert_eq!( - 1, - main_with_args(&["rooster", "list"], &mut io, &rooster_file) - ); + let result = main_with_args(&["rooster", "list"], &mut io, &rooster_file); + assert_eq!(1, result); let output_as_vecu8 = io.stderr_cursor.into_inner(); let output_as_string = String::from_utf8_lossy(output_as_vecu8.as_slice()); - assert!(output_as_string.contains("Decryption of your Rooster file keeps failing")); + assert!(output_as_string.contains("Woops, that's not the right password. Aborting.")); }