Skip to content
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
1 change: 1 addition & 0 deletions shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pkgs.mkShell {
buildInputs = with pkgs; [
rustup
python3
];
shellHook = ''
rustup default stable
Expand Down
53 changes: 25 additions & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<password::v2::PasswordStore, password::PasswordError> {
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) => {
Expand All @@ -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.",
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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);
}
}
}
Expand All @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions src/password/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ pub enum PasswordError {
Io(IoError),
OutdatedRoosterBinaryError,
InvalidJsonError,
CorruptionError,
CorruptionLikelyError,
InvalidPasswordError,
NeedUpgradeErrorFromV1,
NoUpgradeError,
EmptyPasswordError,
Expand Down
36 changes: 18 additions & 18 deletions src/password/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ impl PasswordStore {
})?;

// The encrypted password data.
let mut blob: Vec<u8> = Vec::new();
reader.read_to_end(&mut blob)?;
let mut cipher_blob: Vec<u8> = Vec::new();
reader.read_to_end(&mut cipher_blob)?;

// Derive a 256 bits encryption key from the password.
let key = generate_encryption_key(
Expand All @@ -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(),
Expand All @@ -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,
Expand Down
13 changes: 6 additions & 7 deletions tests/test-commands-add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
2 changes: 1 addition & 1 deletion tests/test-corruption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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."));
}
19 changes: 6 additions & 13 deletions tests/test-password-retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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."));
}
Loading