keyfork-shard: move to keyfork-prompt
This commit is contained in:
parent
be74cd8ad1
commit
dc1b36a92c
|
@ -1111,7 +1111,7 @@ dependencies = [
|
||||||
"card-backend",
|
"card-backend",
|
||||||
"card-backend-pcsc",
|
"card-backend-pcsc",
|
||||||
"keyfork-derive-openpgp",
|
"keyfork-derive-openpgp",
|
||||||
"keyfork-pinentry",
|
"keyfork-prompt",
|
||||||
"openpgp-card",
|
"openpgp-card",
|
||||||
"openpgp-card-sequoia",
|
"openpgp-card-sequoia",
|
||||||
"sequoia-openpgp",
|
"sequoia-openpgp",
|
||||||
|
|
|
@ -52,8 +52,18 @@ where
|
||||||
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
||||||
terminal
|
terminal
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
.queue(Print(prompt))?
|
.queue(cursor::MoveTo(0, 0))?;
|
||||||
.flush()?;
|
let mut lines = prompt.lines().peekable();
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
terminal.queue(Print(line))?;
|
||||||
|
if lines.peek().is_some() {
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.flush()?;
|
||||||
|
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
self.read.read_line(&mut line)?;
|
self.read.read_line(&mut line)?;
|
||||||
Ok(line)
|
Ok(line)
|
||||||
|
@ -63,10 +73,21 @@ where
|
||||||
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
|
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
|
||||||
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
||||||
let mut terminal = RawMode::new(&mut terminal)?;
|
let mut terminal = RawMode::new(&mut terminal)?;
|
||||||
|
|
||||||
terminal
|
terminal
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
.queue(Print(prompt))?
|
.queue(cursor::MoveTo(0, 0))?;
|
||||||
.flush()?;
|
let mut lines = prompt.lines().peekable();
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
terminal.queue(Print(line))?;
|
||||||
|
if lines.peek().is_some() {
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.flush()?;
|
||||||
|
|
||||||
let mut passphrase = String::new();
|
let mut passphrase = String::new();
|
||||||
loop {
|
loop {
|
||||||
match read()? {
|
match read()? {
|
||||||
|
@ -75,6 +96,15 @@ where
|
||||||
passphrase.push('\n');
|
passphrase.push('\n');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
if passphrase.pop().is_some() {
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveLeft(1))?
|
||||||
|
.queue(Print(" "))?
|
||||||
|
.queue(cursor::MoveLeft(1))?
|
||||||
|
.flush()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
terminal.queue(Print("*"))?.flush()?;
|
terminal.queue(Print("*"))?.flush()?;
|
||||||
passphrase.push(c);
|
passphrase.push(c);
|
||||||
|
@ -90,14 +120,26 @@ where
|
||||||
pub fn prompt_message(&mut self, prompt: &str) -> Result<()> {
|
pub fn prompt_message(&mut self, prompt: &str) -> Result<()> {
|
||||||
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
||||||
let mut terminal = RawMode::new(&mut terminal)?;
|
let mut terminal = RawMode::new(&mut terminal)?;
|
||||||
|
|
||||||
terminal
|
terminal
|
||||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||||
.queue(Print(prompt))?
|
.queue(cursor::MoveTo(0, 0))?;
|
||||||
|
let mut lines = prompt.lines().peekable();
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
terminal.queue(Print(line))?;
|
||||||
|
if lines.peek().is_some() {
|
||||||
|
terminal
|
||||||
|
.queue(cursor::MoveDown(1))?
|
||||||
|
.queue(cursor::MoveToColumn(0))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal
|
||||||
.queue(cursor::DisableBlinking)?
|
.queue(cursor::DisableBlinking)?
|
||||||
.queue(cursor::MoveDown(1))?
|
.queue(cursor::MoveDown(1))?
|
||||||
.queue(cursor::MoveToColumn(0))?
|
.queue(cursor::MoveToColumn(0))?
|
||||||
.queue(PrintStyledContent(" OK ".negative()))?
|
.queue(PrintStyledContent(" OK ".negative()))?
|
||||||
.flush()?;
|
.flush()?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match read()? {
|
match read()? {
|
||||||
Event::Key(k) => match k.code {
|
Event::Key(k) => match k.code {
|
||||||
|
|
|
@ -10,7 +10,7 @@ license = "AGPL-3.0-only"
|
||||||
default = ["openpgp", "openpgp-card"]
|
default = ["openpgp", "openpgp-card"]
|
||||||
openpgp = ["sequoia-openpgp", "prompt"]
|
openpgp = ["sequoia-openpgp", "prompt"]
|
||||||
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc", "card-backend", "dep:openpgp-card"]
|
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc", "card-backend", "dep:openpgp-card"]
|
||||||
prompt = ["keyfork-pinentry"]
|
prompt = ["keyfork-prompt"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
|
@ -18,7 +18,6 @@ bincode = "1.3.3"
|
||||||
card-backend = { version = "0.2.0", optional = true }
|
card-backend = { version = "0.2.0", optional = true }
|
||||||
card-backend-pcsc = { version = "0.5.0", optional = true }
|
card-backend-pcsc = { version = "0.5.0", optional = true }
|
||||||
keyfork-derive-openpgp = { version = "0.1.0", path = "../keyfork-derive-openpgp" }
|
keyfork-derive-openpgp = { version = "0.1.0", path = "../keyfork-derive-openpgp" }
|
||||||
keyfork-pinentry = { version = "0.5.0", path = "../keyfork-pinentry", optional = true }
|
|
||||||
openpgp-card-sequoia = { version = "0.2.0", optional = true }
|
openpgp-card-sequoia = { version = "0.2.0", optional = true }
|
||||||
openpgp-card = { version = "0.4.0", optional = true }
|
openpgp-card = { version = "0.4.0", optional = true }
|
||||||
sequoia-openpgp = { version = "1.16.1", optional = true }
|
sequoia-openpgp = { version = "1.16.1", optional = true }
|
||||||
|
@ -26,3 +25,4 @@ serde = "1.0.188"
|
||||||
sharks = "0.5.0"
|
sharks = "0.5.0"
|
||||||
smex = { version = "0.1.0", path = "../smex" }
|
smex = { version = "0.1.0", path = "../smex" }
|
||||||
thiserror = "1.0.50"
|
thiserror = "1.0.50"
|
||||||
|
keyfork-prompt = { version = "0.1.0", path = "../keyfork-prompt", optional = true }
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
#[cfg(feature = "openpgp")]
|
#[cfg(feature = "openpgp")]
|
||||||
pub mod openpgp;
|
pub mod openpgp;
|
||||||
|
|
||||||
#[cfg(feature = "prompt")]
|
|
||||||
mod prompt_manager;
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use keyfork_pinentry::ExposeSecret;
|
use std::fs::File;
|
||||||
|
|
||||||
|
use keyfork_prompt::{Error as PromptError, PromptManager};
|
||||||
|
|
||||||
use super::openpgp::{
|
use super::openpgp::{
|
||||||
self,
|
self,
|
||||||
|
@ -9,8 +11,6 @@ use super::openpgp::{
|
||||||
KeyHandle, KeyID,
|
KeyHandle, KeyID,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::prompt_manager::{PinentryError, PromptManager};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -18,8 +18,14 @@ pub enum Error {
|
||||||
#[error("Secret key was not found")]
|
#[error("Secret key was not found")]
|
||||||
SecretKeyNotFound,
|
SecretKeyNotFound,
|
||||||
|
|
||||||
|
#[error("Could not find TTY when prompting")]
|
||||||
|
NoTTY,
|
||||||
|
|
||||||
|
#[error("Could not open TTY: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("Prompt failed: {0}")]
|
#[error("Prompt failed: {0}")]
|
||||||
Prompt(#[from] PinentryError),
|
Prompt(#[from] PromptError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
@ -27,15 +33,20 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
pub struct Keyring {
|
pub struct Keyring {
|
||||||
full_certs: Vec<Cert>,
|
full_certs: Vec<Cert>,
|
||||||
root: Option<Cert>,
|
root: Option<Cert>,
|
||||||
pm: PromptManager,
|
pm: PromptManager<File, File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyring {
|
impl Keyring {
|
||||||
pub fn new(certs: impl AsRef<[Cert]>) -> Result<Self> {
|
pub fn new(certs: impl AsRef<[Cert]>) -> Result<Self> {
|
||||||
|
let tty = std::env::vars()
|
||||||
|
.filter(|(k, _v)| k.as_str() == "GPG_TTY")
|
||||||
|
.next()
|
||||||
|
.ok_or(Error::NoTTY)?
|
||||||
|
.1;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
full_certs: certs.as_ref().to_vec(),
|
full_certs: certs.as_ref().to_vec(),
|
||||||
root: Default::default(),
|
root: Default::default(),
|
||||||
pm: PromptManager::new("keyfork-shard", None)?,
|
pm: PromptManager::new(File::open(&tty)?, File::options().write(true).open(&tty)?)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,14 +68,6 @@ impl Keyring {
|
||||||
pub fn get_cert_for_primary_keyid<'a>(&'a self, keyid: &KeyID) -> Option<&'a Cert> {
|
pub fn get_cert_for_primary_keyid<'a>(&'a self, keyid: &KeyID) -> Option<&'a Cert> {
|
||||||
self.full_certs.iter().find(|cert| &cert.keyid() == keyid)
|
self.full_certs.iter().find(|cert| &cert.keyid() == keyid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: This can't return an iterator because iterators are all different types
|
|
||||||
// and returning different types is naughty
|
|
||||||
fn get_certs_for_pkesk<'a>(&'a self, pkesk: &'a PKESK) -> impl Iterator<Item = &Cert> + 'a {
|
|
||||||
self.full_certs.iter().filter(move |cert| {
|
|
||||||
pkesk.recipient().is_wildcard() || cert.keys().any(|k| &k.keyid() == pkesk.recipient())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VerificationHelper for &mut Keyring {
|
impl VerificationHelper for &mut Keyring {
|
||||||
|
@ -117,7 +120,10 @@ impl DecryptionHelper for &mut Keyring {
|
||||||
let null = NullPolicy::new();
|
let null = NullPolicy::new();
|
||||||
// unoptimized route: use all locally stored certs
|
// unoptimized route: use all locally stored certs
|
||||||
for pkesk in pkesks {
|
for pkesk in pkesks {
|
||||||
for cert in self.get_certs_for_pkesk(pkesk) {
|
for cert in self.full_certs.iter().filter(|cert| {
|
||||||
|
pkesk.recipient().is_wildcard()
|
||||||
|
|| cert.keys().any(|k| &k.keyid() == pkesk.recipient())
|
||||||
|
}) {
|
||||||
#[allow(deprecated, clippy::map_flatten)]
|
#[allow(deprecated, clippy::map_flatten)]
|
||||||
let name = cert
|
let name = cert
|
||||||
.userids()
|
.userids()
|
||||||
|
@ -140,16 +146,16 @@ impl DecryptionHelper for &mut Keyring {
|
||||||
.context("Has unencrypted secret")?
|
.context("Has unencrypted secret")?
|
||||||
} else {
|
} else {
|
||||||
let message = if let Some(name) = name.as_ref() {
|
let message = if let Some(name) = name.as_ref() {
|
||||||
format!("Decryption key for: {} ({name})", secret_key.keyid())
|
format!("Decryption key for {} ({name}): ", secret_key.keyid())
|
||||||
} else {
|
} else {
|
||||||
format!("Decryption key for: {}", secret_key.keyid())
|
format!("Decryption key for {}: ", secret_key.keyid())
|
||||||
};
|
};
|
||||||
let passphrase = self
|
let passphrase = self
|
||||||
.pm
|
.pm
|
||||||
.prompt_passphrase("Decryption passphrase", message)
|
.prompt_passphrase(&message)
|
||||||
.context("Decryption passphrase")?;
|
.context("Decryption passphrase")?;
|
||||||
secret_key
|
secret_key
|
||||||
.decrypt_secret(&passphrase.expose_secret().as_str().into())
|
.decrypt_secret(&passphrase.as_str().into())
|
||||||
.context("has_unencrypted_secret is false, could not decrypt secret")?
|
.context("has_unencrypted_secret is false, could not decrypt secret")?
|
||||||
.into_keypair()
|
.into_keypair()
|
||||||
.context("just-decrypted key")?
|
.context("just-decrypted key")?
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, fs::File};
|
||||||
|
|
||||||
use keyfork_pinentry::ExposeSecret;
|
use keyfork_prompt::{Error as PromptError, PromptManager};
|
||||||
|
|
||||||
use super::openpgp::{
|
use super::openpgp::{
|
||||||
self,
|
self,
|
||||||
|
@ -9,7 +9,6 @@ use super::openpgp::{
|
||||||
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
|
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
|
||||||
Fingerprint,
|
Fingerprint,
|
||||||
};
|
};
|
||||||
use crate::prompt_manager::{PinentryError, PromptManager};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use card_backend_pcsc::PcscBackend;
|
use card_backend_pcsc::PcscBackend;
|
||||||
|
@ -45,8 +44,14 @@ pub enum Error {
|
||||||
#[error("Invalid PIN entered too many times")]
|
#[error("Invalid PIN entered too many times")]
|
||||||
InvalidPIN,
|
InvalidPIN,
|
||||||
|
|
||||||
|
#[error("Could not find TTY when prompting")]
|
||||||
|
NoTTY,
|
||||||
|
|
||||||
|
#[error("Could not open TTY: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("Prompt failed: {0}")]
|
#[error("Prompt failed: {0}")]
|
||||||
Prompt(#[from] PinentryError),
|
Prompt(#[from] PromptError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
@ -65,15 +70,20 @@ fn format_name(input: impl AsRef<str>) -> String {
|
||||||
pub struct SmartcardManager {
|
pub struct SmartcardManager {
|
||||||
current_card: Option<Card<Open>>,
|
current_card: Option<Card<Open>>,
|
||||||
root: Option<Cert>,
|
root: Option<Cert>,
|
||||||
pm: PromptManager,
|
pm: PromptManager<File, File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SmartcardManager {
|
impl SmartcardManager {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
|
let tty = std::env::vars()
|
||||||
|
.filter(|(k, _v)| k.as_str() == "GPG_TTY")
|
||||||
|
.next()
|
||||||
|
.ok_or(Error::NoTTY)?
|
||||||
|
.1;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
current_card: None,
|
current_card: None,
|
||||||
root: None,
|
root: None,
|
||||||
pm: PromptManager::new("keyfork-shard", None)?,
|
pm: PromptManager::new(File::open(&tty)?, File::options().write(true).open(&tty)?)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,13 +242,13 @@ impl DecryptionHelper for &mut SmartcardManager {
|
||||||
.err_count_pw1();
|
.err_count_pw1();
|
||||||
let rpea = "Remaining PIN entry attempts";
|
let rpea = "Remaining PIN entry attempts";
|
||||||
let message = if cardholder_name.is_empty() {
|
let message = if cardholder_name.is_empty() {
|
||||||
format!("Unlock card {card_id}\n\n{rpea}: {attempts}")
|
format!("Unlock card {card_id}\n{rpea}: {attempts}\n\nPIN: ")
|
||||||
} else {
|
} else {
|
||||||
format!("Unlock card {card_id} ({cardholder_name})\n\n{rpea}: {attempts}")
|
format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ")
|
||||||
};
|
};
|
||||||
let temp_pin = self.pm.prompt_passphrase("Smartcard User PIN", message)?;
|
let temp_pin = self.pm.prompt_passphrase(&message)?;
|
||||||
let verification_status =
|
let verification_status =
|
||||||
transaction.verify_user_pin(temp_pin.expose_secret().as_str().trim());
|
transaction.verify_user_pin(temp_pin.as_str().trim());
|
||||||
match verification_status {
|
match verification_status {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
pin.replace(temp_pin);
|
pin.replace(temp_pin);
|
||||||
|
@ -252,7 +262,7 @@ impl DecryptionHelper for &mut SmartcardManager {
|
||||||
}
|
}
|
||||||
let pin = pin.ok_or(Error::InvalidPIN)?;
|
let pin = pin.ok_or(Error::InvalidPIN)?;
|
||||||
let mut user = transaction
|
let mut user = transaction
|
||||||
.to_user_card(pin.expose_secret().as_str().trim())
|
.to_user_card(pin.as_str().trim())
|
||||||
.context("Could not load user smartcard from PIN")?;
|
.context("Could not load user smartcard from PIN")?;
|
||||||
let mut decryptor = user
|
let mut decryptor = user
|
||||||
.decryptor(&|| eprintln!("Touch confirmation needed for decryption"))
|
.decryptor(&|| eprintln!("Touch confirmation needed for decryption"))
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use keyfork_pinentry::{
|
|
||||||
self, default_binary, ConfirmationDialog, MessageDialog, PassphraseInput, SecretString,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum PinentryError {
|
|
||||||
#[error("No pinentry binary found")]
|
|
||||||
NoPinentryFound,
|
|
||||||
|
|
||||||
#[error("{0}")]
|
|
||||||
Internal(#[from] keyfork_pinentry::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T, E = PinentryError> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
/// Display message dialogues, confirmation prompts, and passphrase inputs with keyfork-pinentry.
|
|
||||||
pub struct PromptManager {
|
|
||||||
program_title: String,
|
|
||||||
pinentry_binary: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PromptManager {
|
|
||||||
pub fn new(
|
|
||||||
program_title: impl Into<String>,
|
|
||||||
pinentry_binary: impl Into<Option<PathBuf>>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let path = match pinentry_binary.into() {
|
|
||||||
Some(p) => p,
|
|
||||||
None => default_binary()?,
|
|
||||||
};
|
|
||||||
std::fs::metadata(&path).map_err(|_| PinentryError::NoPinentryFound)?;
|
|
||||||
Ok(Self {
|
|
||||||
program_title: program_title.into(),
|
|
||||||
pinentry_binary: path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn prompt_confirmation(&self, prompt: impl AsRef<str>) -> Result<bool> {
|
|
||||||
ConfirmationDialog::with_binary(self.pinentry_binary.clone())
|
|
||||||
.with_title(&self.program_title)
|
|
||||||
.confirm(prompt.as_ref())
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prompt_message(&self, prompt: impl AsRef<str>) -> Result<()> {
|
|
||||||
MessageDialog::with_binary(self.pinentry_binary.clone())
|
|
||||||
.with_title(&self.program_title)
|
|
||||||
.show_message(prompt.as_ref())
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prompt_passphrase(
|
|
||||||
&self,
|
|
||||||
prompt: impl AsRef<str>,
|
|
||||||
description: impl Into<Option<String>>,
|
|
||||||
) -> Result<SecretString> {
|
|
||||||
match description.into() {
|
|
||||||
Some(desc) => PassphraseInput::with_binary(self.pinentry_binary.clone())
|
|
||||||
.with_title(&self.program_title)
|
|
||||||
.with_prompt(prompt.as_ref())
|
|
||||||
.with_description(&desc)
|
|
||||||
.interact()
|
|
||||||
.map_err(|e| e.into()),
|
|
||||||
None => PassphraseInput::with_binary(self.pinentry_binary.clone())
|
|
||||||
.with_title(&self.program_title)
|
|
||||||
.with_prompt(prompt.as_ref())
|
|
||||||
.interact()
|
|
||||||
.map_err(|e| e.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue