cli: Give passphrase error on prompt

Example:

    ✗ Invalid passphrase, please try again
    ? Passphrase: ***
This commit is contained in:
Alexis Sellier 2023-09-02 13:56:56 +02:00 committed by cloudhead
parent 5040f33025
commit 579ddfe725
No known key found for this signature in database
5 changed files with 58 additions and 6 deletions

View File

@ -177,10 +177,11 @@ pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
term::format::Identity::new(profile).styled()
);
let validator = term::io::PassphraseValidator::new(profile.keystore.clone());
let passphrase = if options.stdin {
term::passphrase_stdin()?
} else {
term::passphrase(RAD_PASSPHRASE)?
term::passphrase(RAD_PASSPHRASE, validator)?
};
register(&mut agent, profile, passphrase)?;

View File

@ -30,7 +30,8 @@ pub fn start(
let envs = if profile.keystore.is_encrypted()? {
// Ask passphrase here, otherwise it'll be a fatal error when running the daemon
// without `RAD_PASSPHRASE`.
let Ok(passphrase) = term::io::passphrase(profile::env::RAD_PASSPHRASE) else {
let validator = term::io::PassphraseValidator::new(profile.keystore.clone());
let Ok(passphrase) = term::io::passphrase(profile::env::RAD_PASSPHRASE, validator) else {
anyhow::bail!("your radicle passphrase is required to start your node");
};
Some((profile::env::RAD_PASSPHRASE, passphrase))

View File

@ -1,19 +1,51 @@
use radicle::cob::issue::Issue;
use radicle::cob::thread::{Comment, CommentId};
use radicle::crypto::ssh::keystore::MemorySigner;
use radicle::crypto::Signer;
use radicle::crypto::{ssh::Keystore, Signer};
use radicle::profile::env::RAD_PASSPHRASE;
use radicle::profile::Profile;
pub use radicle_term::io::*;
pub use radicle_term::spinner;
use inquire::validator;
/// Validates secret key passphrases.
#[derive(Clone)]
pub struct PassphraseValidator {
keystore: Keystore,
}
impl PassphraseValidator {
/// Create a new validator.
pub fn new(keystore: Keystore) -> Self {
Self { keystore }
}
}
impl inquire::validator::StringValidator for PassphraseValidator {
fn validate(
&self,
input: &str,
) -> Result<validator::Validation, inquire::error::CustomUserError> {
let passphrase = Passphrase::from(input.to_owned());
if self.keystore.is_valid_passphrase(&passphrase)? {
Ok(validator::Validation::Valid)
} else {
Ok(validator::Validation::Invalid(
validator::ErrorMessage::from("Invalid passphrase, please try again"),
))
}
}
}
/// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user.
pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
if let Ok(signer) = profile.signer() {
return Ok(signer);
}
let passphrase = passphrase(RAD_PASSPHRASE)?;
let validator = PassphraseValidator::new(profile.keystore.clone());
let passphrase = passphrase(RAD_PASSPHRASE, validator)?;
let spinner = spinner("Unsealing key...");
let signer = MemorySigner::load(&profile.keystore, Some(passphrase))?;

View File

@ -138,6 +138,19 @@ impl Keystore {
}
}
/// Check that the passphrase is valid.
pub fn is_valid_passphrase(&self, passphrase: &Passphrase) -> Result<bool, Error> {
let path = self.path.join("radicle");
if !path.exists() {
return Err(Error::Io(io::ErrorKind::NotFound.into()));
}
let secret = ssh_key::PrivateKey::read_openssh_file(&path)?;
let valid = secret.decrypt(passphrase).is_ok();
Ok(valid)
}
/// Check whether the secret key is encrypted.
pub fn is_encrypted(&self) -> Result<bool, Error> {
let path = self.path.join("radicle");

View File

@ -2,6 +2,7 @@ use std::ffi::OsStr;
use std::{env, fmt, io, process};
use inquire::ui::{ErrorMessageRenderConfig, StyleSheet, Styled};
use inquire::validator;
use inquire::InquireError;
use inquire::{ui::Color, ui::RenderConfig, Confirm, CustomType, Password};
use once_cell::sync::Lazy;
@ -11,7 +12,7 @@ use crate::command;
use crate::format;
use crate::{style, Paint};
// TODO: Try not to export this.
pub use inquire;
pub use inquire::Select;
pub const ERROR_PREFIX: Paint<&str> = Paint::red("");
@ -199,7 +200,10 @@ where
Ok(value)
}
pub fn passphrase<K: AsRef<OsStr>>(var: K) -> Result<Passphrase, inquire::InquireError> {
pub fn passphrase<K: AsRef<OsStr>, V: validator::StringValidator + 'static>(
var: K,
validate: V,
) -> Result<Passphrase, inquire::InquireError> {
if let Ok(p) = env::var(var) {
Ok(Passphrase::from(p))
} else {
@ -208,6 +212,7 @@ pub fn passphrase<K: AsRef<OsStr>>(var: K) -> Result<Passphrase, inquire::Inquir
.with_render_config(*CONFIG)
.with_display_mode(inquire::PasswordDisplayMode::Masked)
.without_confirmation()
.with_validator(validate)
.prompt()?,
))
}