Compare commits

..

No commits in common. "fa61d31f3f52ee0af14829baee0752ce9aa65b2f" and "307941087a414f11e26b0cd1b3b49d10a38b8727" have entirely different histories.

6 changed files with 65 additions and 137 deletions

View File

@ -11,8 +11,7 @@ authors = ["Ryan Heywood <ryan@distrust.co>"]
# categories = ["api-bindings", "command-line-interface"] # categories = ["api-bindings", "command-line-interface"]
# license = "MIT OR Apache-2.0" # license = "MIT OR Apache-2.0"
license = "MIT" license = "MIT"
# edition = "2018" edition = "2018"
edition = "2021"
[dependencies] [dependencies]
nom = { version = "7", default-features = false } nom = { version = "7", default-features = false }

View File

@ -69,24 +69,6 @@ pub fn default_binary() -> Result<PathBuf> {
which::which("pinentry-curses").map_err(Into::into) which::which("pinentry-curses").map_err(Into::into)
} }
fn convert_multiline(line: &str) -> String {
// convert into multiline
let mut converted_line = String::new();
let mut last_end = 0;
for (start, part) in line.match_indices(&['\n', '\r', '%']) {
converted_line.push_str(line.get(last_end..start).unwrap());
converted_line.push_str(match part {
"\n" => "%0A",
"\r" => "%0D",
"%" => "%25",
fb => panic!("expected index given to match_indices, got: {fb}"),
});
last_end = start + part.len();
}
converted_line.push_str(line.get(last_end..line.len()).unwrap());
converted_line
}
/// A dialog for requesting a passphrase from the user. /// A dialog for requesting a passphrase from the user.
pub struct PassphraseInput<'a> { pub struct PassphraseInput<'a> {
binary: PathBuf, binary: PathBuf,
@ -229,7 +211,7 @@ impl<'a> PassphraseInput<'a> {
pinentry.send_request("SETTITLE", Some(title))?; pinentry.send_request("SETTITLE", Some(title))?;
} }
if let Some(desc) = &self.description { if let Some(desc) = &self.description {
pinentry.send_request("SETDESC", Some(convert_multiline(desc).as_ref()))?; pinentry.send_request("SETDESC", Some(desc))?;
} }
if let Some(error) = &self.error { if let Some(error) = &self.error {
pinentry.send_request("SETERROR", Some(error))?; pinentry.send_request("SETERROR", Some(error))?;

View File

@ -52,10 +52,10 @@ pub enum Error {
SequoiaIo(#[source] std::io::Error), SequoiaIo(#[source] std::io::Error),
#[error("Keyring error: {0}")] #[error("Keyring error: {0}")]
Keyring(#[from] keyring::Error), Keyring(#[from] keyring::KeyringError),
#[error("Smartcard error: {0}")] #[error("Smartcard error: {0}")]
Smartcard(#[from] smartcard::Error), Smartcard(#[from] smartcard::SmartcardError),
#[error("IO error: {0}")] #[error("IO error: {0}")]
Io(#[source] std::io::Error), Io(#[source] std::io::Error),

View File

@ -9,20 +9,18 @@ use super::openpgp::{
KeyHandle, KeyID, KeyHandle, KeyID,
}; };
use crate::prompt_manager::{PinentryError, PromptManager}; use crate::prompt_manager::{PromptManager, PinentryError};
use anyhow::Context;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum KeyringError {
#[error("Secret key was not found")] #[error("Secret key was not found")]
SecretKeyNotFound, SecretKeyNotFound,
#[error("Prompt failed: {0}")] #[error("Prompt failed: {0}")]
Prompt(#[from] PinentryError), Prompt(#[from] PinentryError)
} }
pub type Result<T, E = Error> = std::result::Result<T, E>; pub type Result<T, E = KeyringError> = std::result::Result<T, E>;
pub struct Keyring { pub struct Keyring {
full_certs: Vec<Cert>, full_certs: Vec<Cert>,
@ -91,9 +89,8 @@ impl VerificationHelper for &mut Keyring {
MessageLayer::SignatureGroup { results } => { MessageLayer::SignatureGroup { results } => {
for result in results { for result in results {
if let Err(e) = result { if let Err(e) = result {
// FIXME: anyhow leak: VerificationError impl std::error::Error // FIXME: anyhow leak
// return Err(e.context("Invalid signature")); return Err(anyhow::anyhow!(e.to_string()));
return Err(anyhow::anyhow!("Invalid signature: {e}"));
} }
} }
} }
@ -124,9 +121,7 @@ impl DecryptionHelper for &mut Keyring {
.next() .next()
.map(|userid| userid.userid().name().transpose()) .map(|userid| userid.userid().name().transpose())
.flatten() .flatten()
.transpose() .transpose()?;
.ok()
.flatten();
for key in cert for key in cert
.keys() .keys()
.with_policy(&null, None) .with_policy(&null, None)
@ -135,9 +130,7 @@ impl DecryptionHelper for &mut Keyring {
{ {
let secret_key = key.key().clone(); let secret_key = key.key().clone();
let mut keypair = if secret_key.has_unencrypted_secret() { let mut keypair = if secret_key.has_unencrypted_secret() {
secret_key secret_key.into_keypair()?
.into_keypair()
.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())
@ -146,13 +139,10 @@ impl DecryptionHelper for &mut Keyring {
}; };
let passphrase = self let passphrase = self
.pm .pm
.prompt_passphrase("Decryption passphrase", message) .prompt_passphrase("Decryption passphrase", message)?;
.context("Decryption passphrase")?; let key = secret_key
secret_key .decrypt_secret(&passphrase.expose_secret().as_str().into())?;
.decrypt_secret(&passphrase.expose_secret().as_str().into()) key.into_keypair()?
.context("has_unencrypted_secret is false, could not decrypt secret")?
.into_keypair()
.context("just-decrypted key")?
}; };
if pkesk if pkesk
.decrypt(&mut keypair, sym_algo) .decrypt(&mut keypair, sym_algo)
@ -165,6 +155,6 @@ impl DecryptionHelper for &mut Keyring {
} }
} }
Err(Error::SecretKeyNotFound.into()) Err(KeyringError::SecretKeyNotFound.into())
} }
} }

View File

@ -11,12 +11,11 @@ use super::openpgp::{
}; };
use crate::prompt_manager::{PinentryError, PromptManager}; use crate::prompt_manager::{PinentryError, PromptManager};
use anyhow::Context;
use card_backend_pcsc::PcscBackend; use card_backend_pcsc::PcscBackend;
use openpgp_card_sequoia::{state::Open, types::Error as SequoiaCardError, Card}; use openpgp_card_sequoia::{state::Open, Card};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum SmartcardError {
#[error("No smart card backend was stored")] #[error("No smart card backend was stored")]
SmartCardNotFound, SmartCardNotFound,
@ -24,28 +23,16 @@ pub enum Error {
SmartCardHasNoDecrypt, SmartCardHasNoDecrypt,
#[error("Smart card backend error: {0}")] #[error("Smart card backend error: {0}")]
SmartCardBackend(#[from] card_backend::SmartcardError), SmartCardBackendError(#[from] card_backend::SmartcardError),
#[error("Smartcard password status unavailable: {0}")] #[error("Smart card error: {0}")]
PwStatusBytes(SequoiaCardError), SmartCardEncounteredError(#[from] openpgp_card_sequoia::types::Error),
#[error("Could not open smart card")]
OpenSmartCard(SequoiaCardError),
#[error("Could not initialize transaction")]
Transaction(SequoiaCardError),
#[error("Could not load fingerprints")]
Fingerprints(SequoiaCardError),
#[error("Invalid PIN entered too many times")]
InvalidPIN,
#[error("Prompt failed: {0}")] #[error("Prompt failed: {0}")]
Prompt(#[from] PinentryError), Prompt(#[from] PinentryError),
} }
pub type Result<T, E = Error> = std::result::Result<T, E>; pub type Result<T, E = SmartcardError> = std::result::Result<T, E>;
fn format_name(input: impl AsRef<str>) -> String { fn format_name(input: impl AsRef<str>) -> String {
let mut n = input let mut n = input
@ -85,20 +72,21 @@ impl SmartcardManager {
PcscBackend::cards(None)? PcscBackend::cards(None)?
.next() .next()
.transpose()? .transpose()?
.ok_or(Error::SmartCardNotFound) .ok_or(SmartcardError::SmartCardNotFound)
.and_then(|backend| { .and_then(
let mut card = Card::<Open>::new(backend).map_err(Error::OpenSmartCard)?; |backend| {
let transaction = card.transaction().map_err(Error::Transaction)?; let mut card = Card::<Open>::new(backend)?;
let fingerprint = transaction let transaction = card.transaction()?;
.fingerprints() let fingerprint = transaction
.map_err(Error::Fingerprints)? .fingerprints()?
.decryption() .decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes())) .map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
.ok_or(Error::SmartCardHasNoDecrypt)?; .ok_or(SmartcardError::SmartCardHasNoDecrypt)?;
drop(transaction); drop(transaction);
self.current_card.replace(card); self.current_card.replace(card);
Ok(fingerprint) Ok(fingerprint)
}) },
)
} }
/// Load a backend if any [`Fingerprint`] has been matched by a currently active card. /// Load a backend if any [`Fingerprint`] has been matched by a currently active card.
@ -120,12 +108,11 @@ impl SmartcardManager {
for backend in PcscBackend::cards(None)? { for backend in PcscBackend::cards(None)? {
had_any_backend = true; had_any_backend = true;
let backend = backend?; let backend = backend?;
let mut card = Card::<Open>::new(backend).map_err(Error::OpenSmartCard)?; let mut card = Card::<Open>::new(backend)?;
let transaction = card.transaction().map_err(Error::Transaction)?; let transaction = card.transaction()?;
let mut fingerprint = None; let mut fingerprint = None;
if let Some(fp) = transaction if let Some(fp) = transaction
.fingerprints() .fingerprints()?
.map_err(Error::Fingerprints)?
.decryption() .decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes())) .map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
{ {
@ -140,8 +127,9 @@ impl SmartcardManager {
} }
} }
#[rustfmt::skip] eprintln!("No matching smartcard detected.");
self.pm.prompt_message("Please plug in a smart card and press enter")?; self.pm
.prompt_message("Please plug in a smart card and press enter")?;
} }
Ok(None) Ok(None)
@ -170,7 +158,7 @@ impl VerificationHelper for &mut SmartcardManager {
for result in results { for result in results {
if let Err(e) = result { if let Err(e) = result {
// FIXME: anyhow leak // FIXME: anyhow leak
return Err(anyhow::anyhow!("Verification error: {}", e.to_string())); return Err(anyhow::anyhow!(e.to_string()));
} }
} }
} }
@ -193,59 +181,28 @@ impl DecryptionHelper for &mut SmartcardManager {
{ {
let mut card = self.current_card.take(); let mut card = self.current_card.take();
let Some(card) = card.as_mut() else { let Some(card) = card.as_mut() else {
return Err(Error::SmartCardNotFound.into()); return Err(SmartcardError::SmartCardNotFound.into());
}; };
let mut transaction = card let mut transaction = card.transaction()?;
.transaction()
.context("Could not initialize transaction")?;
let fp = transaction let fp = transaction
.fingerprints() .fingerprints()?
.context("Could not load fingerprints")?
.decryption() .decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes())) .map(|fp| Fingerprint::from_bytes(fp.as_bytes()));
.ok_or(Error::SmartCardHasNoDecrypt)?; let Some(fp) = fp else {
let cardholder_name = format_name( return Err(SmartcardError::SmartCardHasNoDecrypt.into());
transaction };
.cardholder_name() let cardholder_name = format_name(transaction.cardholder_name()?);
.context("Could not load (optionally empty) cardholder name")?, let card_id = transaction.application_identifier()?.ident();
); let message = if cardholder_name.is_empty() {
let card_id = transaction format!("Unlock card {card_id}")
.application_identifier() } else {
.context("Could not load application identifier")? format!("Unlock card {card_id} ({cardholder_name})")
.ident(); };
let pw_status = transaction let pin = self.pm.prompt_passphrase("Smartcard User PIN", message)?;
.pw_status_bytes() let mut user = transaction.to_user_card(pin.expose_secret().as_str().trim())?;
.map_err(Error::PwStatusBytes)?; let mut decryptor =
let mut pin = None; user.decryptor(&|| eprintln!("Touch confirmation needed for decryption"))?;
for _ in 0..pw_status.err_count_pw1() {
transaction.reload_ard()?;
let attempts = transaction
.pw_status_bytes()
.map_err(Error::PwStatusBytes)?
.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}")
} else {
format!("Unlock card {card_id} ({cardholder_name})\n\n{rpea}: {attempts}")
};
let temp_pin = self.pm.prompt_passphrase("Smartcard User PIN", message)?;
if transaction
.verify_user_pin(temp_pin.expose_secret().as_str().trim())
.is_ok()
{
pin.replace(temp_pin);
break;
}
}
let pin = pin.ok_or(Error::InvalidPIN)?;
let mut user = transaction
.to_user_card(pin.expose_secret().as_str().trim())
.context("Could not load user smartcard from PIN")?;
let mut decryptor = user
.decryptor(&|| eprintln!("Touch confirmation needed for decryption"))
.context("Could not decrypt using smartcard")?;
for pkesk in pkesks { for pkesk in pkesks {
if pkesk if pkesk
.decrypt(&mut decryptor, sym_algo) .decrypt(&mut decryptor, sym_algo)
@ -256,6 +213,6 @@ impl DecryptionHelper for &mut SmartcardManager {
} }
} }
Err(Error::SmartCardNotFound.into()) Err(SmartcardError::SmartCardNotFound.into())
} }
} }

View File

@ -66,7 +66,7 @@ impl ShardExec for OpenPGP {
"cert count {} != max {max}", "cert count {} != max {max}",
certs.len() certs.len()
); );
keyfork_shard::openpgp::split(threshold, certs, secret, output).map_err(Into::into) keyfork_shard::openpgp::split(threshold, certs, secret, output)
} }
fn combine<T>( fn combine<T>(