use std::path::PathBuf;
use age::{secrecy::SecretString, Identity, Recipient};
use clap::Parser;
use dialoguer::Password;
use miette::{miette, Context as _, IntoDiagnostic as _, Result};
use pinentry::PassphraseInput;
use tokio::fs::read_to_string;
#[derive(Debug, Clone, Parser)]
pub struct PassphraseArgs {
#[cfg_attr(docsrs, doc("\n\n**Flag**: `-P, --passphrase-path PATH`"))]
#[arg(short = 'P', long)]
pub passphrase_path: Option<PathBuf>,
#[cfg_attr(docsrs, doc("\n\n**Flag**: `--insecure-passphrase STRING`"))]
#[arg(long, conflicts_with = "passphrase_path")]
pub insecure_passphrase: Option<SecretString>,
}
impl PassphraseArgs {
pub async fn require(&self) -> Result<Passphrase> {
self.get(false).await
}
pub async fn require_with_confirmation(&self) -> Result<Passphrase> {
self.get(true).await
}
pub async fn require_phrase(&self) -> Result<SecretString> {
self.get_phrase(false).await
}
pub async fn require_phrase_with_confirmation(&self) -> Result<SecretString> {
self.get_phrase(true).await
}
async fn get(&self, confirm: bool) -> Result<Passphrase> {
self.get_phrase(confirm).await.map(Passphrase::new)
}
async fn get_phrase(&self, confirm: bool) -> Result<SecretString> {
if let Some(ref phrase) = self.insecure_passphrase {
Ok(phrase.clone())
} else if let Some(ref path) = self.passphrase_path {
Ok(read_to_string(path)
.await
.into_diagnostic()
.wrap_err("reading keyfile")?
.trim()
.into())
} else {
if let Some(mut input) = PassphraseInput::with_default_binary() {
input
.with_prompt("Passphrase:")
.required("Cannot use an empty passphrase");
if confirm {
input.with_confirmation("Confirm passphrase:", "Passphrases do not match");
}
input.interact().map_err(|err| miette!("{err}"))
} else {
let mut prompt = Password::new().with_prompt("Passphrase");
if confirm {
prompt =
prompt.with_confirmation("Confirm passphrase", "Passphrases do not match");
}
let phrase = prompt.interact().into_diagnostic()?;
Ok(phrase.into())
}
}
}
}
pub struct Passphrase(age::scrypt::Recipient, age::scrypt::Identity);
impl Passphrase {
pub fn new(secret: SecretString) -> Self {
Self(
age::scrypt::Recipient::new(secret.clone()),
age::scrypt::Identity::new(secret),
)
}
}
impl Recipient for Passphrase {
fn wrap_file_key(
&self,
file_key: &age_core::format::FileKey,
) -> std::result::Result<
(
Vec<age_core::format::Stanza>,
std::collections::HashSet<String>,
),
age::EncryptError,
> {
self.0.wrap_file_key(file_key)
}
}
impl Identity for Passphrase {
fn unwrap_stanza(
&self,
stanza: &age_core::format::Stanza,
) -> Option<std::result::Result<age_core::format::FileKey, age::DecryptError>> {
self.1.unwrap_stanza(stanza)
}
fn unwrap_stanzas(
&self,
stanzas: &[age_core::format::Stanza],
) -> Option<std::result::Result<age_core::format::FileKey, age::DecryptError>> {
self.1.unwrap_stanzas(stanzas)
}
}