Repeat prompt for passwords when generating artifacts.

- The function common::password::prompt_for_password is intended for
    creating artifacts.  For example, if a new key or subkey is
    generated, or a message should be encrypted using a password.  The
    cost of mistyping is high, so we prompt twice.

  - If the user mistypes, repeating the process allows for graceful
    recovery, which seems to be in the best interest of the user.

  - Make the function repeated the prompts if the user mistypes.
    Rename it to better indicate intent.  Adjust documentation.

  - Fixes #145.
This commit is contained in:
Justus Winter 2023-12-15 15:13:27 +01:00
parent 1346e3013c
commit 68bf9e91f2
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
6 changed files with 36 additions and 23 deletions

View File

@ -26,7 +26,7 @@ use crate::cli::types::FileOrStdin;
use crate::cli::types::MetadataTime;
use crate::Config;
use crate::Result;
use crate::common::prompt_for_password;
use crate::common::password;
use crate::load_certs;
use crate::commands::CompressionMode;
@ -101,7 +101,7 @@ pub fn encrypt<'a, 'b: 'a>(
let mut passwords: Vec<crypto::Password> = Vec::with_capacity(npasswords);
for n in 0..npasswords {
let nprompt = format!("Enter password {}", n + 1);
if let Some(password) = prompt_for_password(
if let Some(password) = password::prompt_for_new(
if npasswords > 1 {
&nprompt
} else {

View File

@ -12,7 +12,7 @@ use openpgp::Packet;
use openpgp::Result;
use sequoia_openpgp as openpgp;
use crate::common::prompt_for_password;
use crate::common::password;
use crate::Config;
use crate::cli::types::FileOrStdout;
use crate::cli;
@ -103,7 +103,7 @@ pub fn generate(
if command.with_password {
builder = builder.set_password(
prompt_for_password(
password::prompt_for_new(
"Enter password to protect the key",
)?);
}

View File

@ -5,7 +5,7 @@ use openpgp::Packet;
use openpgp::Result;
use sequoia_openpgp as openpgp;
use crate::common::prompt_for_password;
use crate::common;
use crate::Config;
use crate::cli;
use crate::decrypt_key;
@ -48,7 +48,7 @@ pub fn password(
} else if let Some(path) = command.new_password_file {
Some(std::fs::read(path)?.into())
} else {
prompt_for_password("New password")?
common::password::prompt_for_new("New password")?
};
if let Some(new) = new_password {

View File

@ -30,10 +30,10 @@ use crate::cli::key::SubkeyCommand;
use crate::cli::key::SubkeyRevokeCommand;
use crate::cli::types::FileOrStdout;
use crate::commands::get_primary_keys;
use crate::common;
use crate::common::NULL_POLICY;
use crate::common::RevocationOutput;
use crate::common::get_secret_signer;
use crate::common::prompt_for_password;
use crate::common::read_cert;
use crate::common::read_secret;
use crate::parse_notations;
@ -266,7 +266,7 @@ fn subkey_add(
if command.with_password {
(
keys.into_iter().next().unwrap().0,
prompt_for_password(
common::password::prompt_for_new(
"Please enter password to encrypt the new subkey",
)?
)

View File

@ -16,8 +16,7 @@ mod revoke;
pub use revoke::get_secret_signer;
pub use revoke::RevocationOutput;
mod password;
pub use password::prompt_for_password;
pub mod password;
pub const NULL_POLICY: &NullPolicy = &NullPolicy::new();

View File

@ -1,3 +1,5 @@
//! Common password-related functionality such as prompting.
use openpgp::crypto::Password;
use openpgp::Result;
use rpassword::prompt_password;
@ -8,25 +10,37 @@ const REPEAT_PROMPT: &str = "Repeat password";
/// Prompts twice for a new password and returns an optional [`Password`].
///
/// Prompts twice for comparison and only returns a [`Password`] in a [`Result`]
/// if both inputs match and are not empty.
/// Returns [`None`](Option::None), if the password is empty.
pub fn prompt_for_password(
/// This function is intended for creating artifacts. For example, if
/// a new key or subkey is generated, or a message should be encrypted
/// using a password. The cost of mistyping is high, so we prompt
/// twice.
///
/// If the two entered passwords match, the result is returned. If
/// the password was the empty string, `None` is returned.
///
/// If the passwords differ, an error message is printed and the
/// process is repeated.
pub fn prompt_for_new(
prompt: &str,
) -> Result<Option<Password>> {
let width = prompt.len().max(REPEAT_PROMPT.len());
let p0 = format!("{:>1$}: ", prompt, width);
let p1 = format!("{:>1$}: ", REPEAT_PROMPT, width);
let password = prompt_password(&p0)?;
let password_repeat = prompt_password(&p1)?;
if password != password_repeat {
return Err(anyhow::anyhow!("The passwords do not match!"));
}
loop {
let password = prompt_password(&p0)?;
let password_repeat = prompt_password(&p1)?;
if password.is_empty() {
Ok(None)
} else {
Ok(Some(password.into()))
if password != password_repeat {
wprintln!("The passwords do not match. Please try again.");
wprintln!();
continue;
}
return if password.is_empty() {
Ok(None)
} else {
Ok(Some(password.into()))
};
}
}