Implement sq key generate --new-password-file.

- See #271.
This commit is contained in:
Justus Winter 2024-09-02 10:03:50 +02:00
parent 230f849307
commit 0cf495bd90
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
5 changed files with 62 additions and 9 deletions

View File

@ -466,14 +466,10 @@ then stdout contains "Secret key: Unencrypted"
_Requirement: We must be able to generate a that does have a _Requirement: We must be able to generate a that does have a
password._ password._
Unfortunately, the `--with-password` option causes `sq` to read the ~~~scenario
password from the terminal, and that makes it hard to do in an
automated test. Thus, this scenario isn't enabled, waiting for a way
to feed `sq` a password as if the user typed it from a terminal.
~~~
given an installed sq given an installed sq
when I run sq --no-cert-store --no-key-store key generate --no-userids --output key.pgp --with-password given file password.txt
when I run sq --no-cert-store --no-key-store key generate --no-userids --output key.pgp --new-password-file password.txt
when I run sq --no-cert-store --no-key-store inspect key.pgp when I run sq --no-cert-store --no-key-store inspect key.pgp
then stdout contains "Secret key: Encrypted" then stdout contains "Secret key: Encrypted"
~~~ ~~~
@ -1603,3 +1599,9 @@ This is an empty file.
~~~{#empty .file add-newline=no} ~~~{#empty .file add-newline=no}
~~~ ~~~
This is a file containing a password.
~~~{#password.txt .file}
hunter2
~~~

View File

@ -252,11 +252,27 @@ Canonical user IDs are of the form `Name (Comment) \
value_enum, value_enum,
)] )]
pub cipher_suite: CipherSuite, pub cipher_suite: CipherSuite,
#[clap(
long = "new-password-file",
value_name = "PASSWORD_FILE",
help = "\
File containing password to encrypt the secret key material",
long_help = "\
File containing password to encrypt the secret key material.
Note that the entire key file will be used as the password including \
any surrounding whitespace like a trailing newline.",
conflicts_with = "without_password",
)]
pub new_password_file: Option<PathBuf>,
#[clap( #[clap(
long = "without-password", long = "without-password",
help = "Don't protect the secret key material with a password", help = "Don't protect the secret key material with a password",
)] )]
pub without_password: bool, pub without_password: bool,
#[clap( #[clap(
long = "expiration", long = "expiration",
value_name = "EXPIRATION", value_name = "EXPIRATION",

View File

@ -121,7 +121,13 @@ pub fn generate(
} }
} }
if ! command.without_password { if let Some(password_file) = command.new_password_file {
let password = std::fs::read(&password_file)
.with_context(|| {
format!("Reading {}", password_file.display())
})?;
builder = builder.set_password(Some(password.into()));
} else if ! command.without_password {
builder = builder.set_password( builder = builder.set_password(
password::prompt_for_new_or_none(&sq, "key")?); password::prompt_for_new_or_none(&sq, "key")?);
} }

View File

@ -347,7 +347,12 @@ impl Sq {
-> (Cert, PathBuf, PathBuf) -> (Cert, PathBuf, PathBuf)
{ {
let mut cmd = self.command(); let mut cmd = self.command();
cmd.args([ "key", "generate", "--without-password" ]); cmd.args([ "key", "generate" ]);
if ! extra_args.contains(&"--new-password-file") {
cmd.arg("--without-password");
}
for arg in extra_args { for arg in extra_args {
cmd.arg(arg); cmd.arg(arg);
} }

View File

@ -44,3 +44,27 @@ fn sq_key_generate_name_email() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn sq_key_generate_with_password() -> Result<()> {
let sq = common::Sq::new();
let password = "hunter2";
let path = sq.base().join("password");
std::fs::write(&path, password)?;
let (cert, _, _) = sq.key_generate(&[
"--new-password-file", &path.display().to_string(),
], &[]);
assert!(cert.is_tsk());
let password = password.into();
for key in cert.keys() {
let secret = key.optional_secret().unwrap();
assert!(secret.is_encrypted());
assert!(secret.clone().decrypt(key.pk_algo(), &password).is_ok());
}
Ok(())
}