keyfork-shard: move to keyfork-prompt
This commit is contained in:
parent
be74cd8ad1
commit
dc1b36a92c
|
@ -1111,7 +1111,7 @@ dependencies = [
|
|||
"card-backend",
|
||||
"card-backend-pcsc",
|
||||
"keyfork-derive-openpgp",
|
||||
"keyfork-pinentry",
|
||||
"keyfork-prompt",
|
||||
"openpgp-card",
|
||||
"openpgp-card-sequoia",
|
||||
"sequoia-openpgp",
|
||||
|
|
|
@ -52,8 +52,18 @@ where
|
|||
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
||||
terminal
|
||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||
.queue(Print(prompt))?
|
||||
.flush()?;
|
||||
.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.flush()?;
|
||||
|
||||
let mut line = String::new();
|
||||
self.read.read_line(&mut line)?;
|
||||
Ok(line)
|
||||
|
@ -63,10 +73,21 @@ where
|
|||
pub fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
|
||||
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
||||
let mut terminal = RawMode::new(&mut terminal)?;
|
||||
|
||||
terminal
|
||||
.queue(terminal::Clear(terminal::ClearType::All))?
|
||||
.queue(Print(prompt))?
|
||||
.flush()?;
|
||||
.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.flush()?;
|
||||
|
||||
let mut passphrase = String::new();
|
||||
loop {
|
||||
match read()? {
|
||||
|
@ -75,6 +96,15 @@ where
|
|||
passphrase.push('\n');
|
||||
break;
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
if passphrase.pop().is_some() {
|
||||
terminal
|
||||
.queue(cursor::MoveLeft(1))?
|
||||
.queue(Print(" "))?
|
||||
.queue(cursor::MoveLeft(1))?
|
||||
.flush()?;
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
terminal.queue(Print("*"))?.flush()?;
|
||||
passphrase.push(c);
|
||||
|
@ -90,14 +120,26 @@ where
|
|||
pub fn prompt_message(&mut self, prompt: &str) -> Result<()> {
|
||||
let mut terminal = AlternateScreen::new(&mut self.write)?;
|
||||
let mut terminal = RawMode::new(&mut terminal)?;
|
||||
|
||||
terminal
|
||||
.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::MoveDown(1))?
|
||||
.queue(cursor::MoveToColumn(0))?
|
||||
.queue(PrintStyledContent(" OK ".negative()))?
|
||||
.flush()?;
|
||||
|
||||
loop {
|
||||
match read()? {
|
||||
Event::Key(k) => match k.code {
|
||||
|
|
|
@ -10,7 +10,7 @@ license = "AGPL-3.0-only"
|
|||
default = ["openpgp", "openpgp-card"]
|
||||
openpgp = ["sequoia-openpgp", "prompt"]
|
||||
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc", "card-backend", "dep:openpgp-card"]
|
||||
prompt = ["keyfork-pinentry"]
|
||||
prompt = ["keyfork-prompt"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
|
@ -18,7 +18,6 @@ bincode = "1.3.3"
|
|||
card-backend = { version = "0.2.0", optional = true }
|
||||
card-backend-pcsc = { version = "0.5.0", optional = true }
|
||||
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 = { version = "0.4.0", optional = true }
|
||||
sequoia-openpgp = { version = "1.16.1", optional = true }
|
||||
|
@ -26,3 +25,4 @@ serde = "1.0.188"
|
|||
sharks = "0.5.0"
|
||||
smex = { version = "0.1.0", path = "../smex" }
|
||||
thiserror = "1.0.50"
|
||||
keyfork-prompt = { version = "0.1.0", path = "../keyfork-prompt", optional = true }
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
#[cfg(feature = "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::{
|
||||
self,
|
||||
|
@ -9,8 +11,6 @@ use super::openpgp::{
|
|||
KeyHandle, KeyID,
|
||||
};
|
||||
|
||||
use crate::prompt_manager::{PinentryError, PromptManager};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
@ -18,8 +18,14 @@ pub enum Error {
|
|||
#[error("Secret key was not found")]
|
||||
SecretKeyNotFound,
|
||||
|
||||
#[error("Could not find TTY when prompting")]
|
||||
NoTTY,
|
||||
|
||||
#[error("Could not open TTY: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Prompt failed: {0}")]
|
||||
Prompt(#[from] PinentryError),
|
||||
Prompt(#[from] PromptError),
|
||||
}
|
||||
|
||||
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 {
|
||||
full_certs: Vec<Cert>,
|
||||
root: Option<Cert>,
|
||||
pm: PromptManager,
|
||||
pm: PromptManager<File, File>,
|
||||
}
|
||||
|
||||
impl Keyring {
|
||||
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 {
|
||||
full_certs: certs.as_ref().to_vec(),
|
||||
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> {
|
||||
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 {
|
||||
|
@ -117,7 +120,10 @@ impl DecryptionHelper for &mut Keyring {
|
|||
let null = NullPolicy::new();
|
||||
// unoptimized route: use all locally stored certs
|
||||
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)]
|
||||
let name = cert
|
||||
.userids()
|
||||
|
@ -140,16 +146,16 @@ impl DecryptionHelper for &mut Keyring {
|
|||
.context("Has unencrypted secret")?
|
||||
} else {
|
||||
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 {
|
||||
format!("Decryption key for: {}", secret_key.keyid())
|
||||
format!("Decryption key for {}: ", secret_key.keyid())
|
||||
};
|
||||
let passphrase = self
|
||||
.pm
|
||||
.prompt_passphrase("Decryption passphrase", message)
|
||||
.prompt_passphrase(&message)
|
||||
.context("Decryption passphrase")?;
|
||||
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")?
|
||||
.into_keypair()
|
||||
.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::{
|
||||
self,
|
||||
|
@ -9,7 +9,6 @@ use super::openpgp::{
|
|||
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
|
||||
Fingerprint,
|
||||
};
|
||||
use crate::prompt_manager::{PinentryError, PromptManager};
|
||||
|
||||
use anyhow::Context;
|
||||
use card_backend_pcsc::PcscBackend;
|
||||
|
@ -45,8 +44,14 @@ pub enum Error {
|
|||
#[error("Invalid PIN entered too many times")]
|
||||
InvalidPIN,
|
||||
|
||||
#[error("Could not find TTY when prompting")]
|
||||
NoTTY,
|
||||
|
||||
#[error("Could not open TTY: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Prompt failed: {0}")]
|
||||
Prompt(#[from] PinentryError),
|
||||
Prompt(#[from] PromptError),
|
||||
}
|
||||
|
||||
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 {
|
||||
current_card: Option<Card<Open>>,
|
||||
root: Option<Cert>,
|
||||
pm: PromptManager,
|
||||
pm: PromptManager<File, File>,
|
||||
}
|
||||
|
||||
impl SmartcardManager {
|
||||
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 {
|
||||
current_card: 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();
|
||||
let rpea = "Remaining PIN entry attempts";
|
||||
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 {
|
||||
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 =
|
||||
transaction.verify_user_pin(temp_pin.expose_secret().as_str().trim());
|
||||
transaction.verify_user_pin(temp_pin.as_str().trim());
|
||||
match verification_status {
|
||||
Ok(_) => {
|
||||
pin.replace(temp_pin);
|
||||
|
@ -252,7 +262,7 @@ impl DecryptionHelper for &mut SmartcardManager {
|
|||
}
|
||||
let pin = pin.ok_or(Error::InvalidPIN)?;
|
||||
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")?;
|
||||
let mut decryptor = user
|
||||
.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