keyfork/keyfork-shard/src/openpgp/smartcard.rs

221 lines
7.4 KiB
Rust

use std::collections::HashSet;
use super::openpgp::{
self,
cert::Cert,
packet::{PKESK, SKESK},
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
Fingerprint,
};
use card_backend_pcsc::PcscBackend;
use openpgp_card_sequoia::{state::Open, Card};
#[derive(Clone, Debug)]
pub enum SmartcardFailure {
SmartCardNotFound,
SmartCardHasNoDecrypt,
}
impl std::fmt::Display for SmartcardFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SmartCardNotFound => f.write_str("No smart card backend was stored"),
Self::SmartCardHasNoDecrypt => f.write_str("Selected smart card has no decrypt key"),
}
}
}
impl std::error::Error for SmartcardFailure {}
#[derive(Default)]
pub struct SmartcardManager {
current_card: Option<Card<Open>>,
root: Option<Cert>,
}
impl SmartcardManager {
pub fn new() -> Self {
Default::default()
}
// Sets the root cert, returning the old cert
pub fn set_root_cert(&mut self, cert: impl Into<Option<Cert>>) -> Option<Cert> {
let mut cert = cert.into();
std::mem::swap(&mut self.root, &mut cert);
cert
}
/// Utility function to prompt for a newline from standard input.
pub fn prompt(&self, prompt: impl std::fmt::Display) -> std::io::Result<()> {
eprint!("{prompt}: ");
std::io::stdin().read_line(&mut String::new()).map(|_| ())
}
/// Utility function to obtain a prompt response from the command line.
pub fn prompt_input(&self, prompt: impl std::fmt::Display) -> std::io::Result<String> {
eprint!("{prompt}: ");
let mut output = String::new();
std::io::stdin().read_line(&mut output)?;
Ok(output)
}
/*
/// Return all [`Fingerprint`] for the currently accessible backends.
///
/// NOTE: Only implemented for decryption keys.
pub fn iter_fingerprints() -> impl Iterator<Item = Fingerprint> {
PcscBackend::cards(None).into_iter().flat_map(|iter| {
iter.filter_map(|backend| {
let backend = backend.ok()?;
let mut card = Card::<Open>::new(backend).ok()?;
let transaction = card.transaction().ok()?;
transaction
.fingerprints()
.ok()?
.decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
})
})
}
*/
/// Load any backend.
pub fn load_any_card(&mut self) -> Result<Fingerprint, Box<dyn std::error::Error>> {
PcscBackend::cards(None)?
.next()
.transpose()?
.ok_or(SmartcardFailure::SmartCardNotFound.into())
.and_then(
|backend| -> Result<Fingerprint, Box<dyn std::error::Error>> {
let mut card = Card::<Open>::new(backend)?;
let transaction = card.transaction()?;
let fingerprint = transaction
.fingerprints()?
.decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
.ok_or(SmartcardFailure::SmartCardHasNoDecrypt)?;
drop(transaction);
self.current_card.replace(card);
Ok(fingerprint)
},
)
}
/// Load a backend if any [`Fingerprint`] has been matched by a currently active card.
///
/// NOTE: Only implemented for decryption keys.
pub fn load_any_fingerprint(
&mut self,
fingerprints: impl IntoIterator<Item = Fingerprint>,
) -> Result<Option<Fingerprint>, Box<dyn std::error::Error>> {
// NOTE: This can't be HashSet::from_iter() because from_iter() requires a passed-in state
// I do not want to provide.
let mut requested_fingerprints = HashSet::new();
requested_fingerprints.extend(fingerprints);
let mut had_any_backend = false;
while !had_any_backend {
// Load all backends, confirm if any have any fingerprints
for backend in PcscBackend::cards(None)? {
had_any_backend = true;
let backend = backend?;
let mut card = Card::<Open>::new(backend)?;
let transaction = card.transaction()?;
let mut fingerprint = None;
if let Some(fp) = transaction
.fingerprints()?
.decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
{
if requested_fingerprints.contains(&fp) {
fingerprint.replace(fp);
}
}
drop(transaction);
if fingerprint.is_some() {
self.current_card.replace(card);
return Ok(fingerprint);
}
}
eprintln!("No matching smartcard detected.");
self.prompt("Please plug in a smart card and press enter")?;
}
Ok(None)
}
}
impl VerificationHelper for &mut SmartcardManager {
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
Ok(ids
.iter()
.flat_map(|kh| self.root.as_ref().filter(|cert| cert.key_handle() == *kh))
.cloned()
.collect())
}
fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
for layer in structure.into_iter() {
#[allow(unused_variables)]
match layer {
MessageLayer::Compression { algo } => {}
MessageLayer::Encryption {
sym_algo,
aead_algo,
} => {}
MessageLayer::SignatureGroup { results } => {
for result in results {
if let Err(e) = result {
// FIXME: anyhow leak
return Err(anyhow::anyhow!(e.to_string()));
}
}
}
}
}
Ok(())
}
}
impl DecryptionHelper for &mut SmartcardManager {
fn decrypt<D>(
&mut self,
pkesks: &[PKESK],
_skesks: &[SKESK],
sym_algo: Option<openpgp::types::SymmetricAlgorithm>,
mut decrypt: D,
) -> openpgp::Result<Option<Fingerprint>>
where
D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool,
{
let mut card = self.current_card.take();
let Some(card) = card.as_mut() else {
return Err(SmartcardFailure::SmartCardNotFound.into());
};
let mut transaction = card.transaction()?;
let fp = transaction
.fingerprints()?
.decryption()
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()));
let pin = self.prompt_input("User PIN")?;
let mut user = transaction.to_user_card(pin.as_str().trim())?;
let mut decryptor =
user.decryptor(&|| eprintln!("Touch confirmation needed for decryption"))?;
for pkesk in pkesks {
if pkesk
.decrypt(&mut decryptor, sym_algo)
.map(|(algo, sk)| decrypt(algo, &sk))
.unwrap_or(false)
{
return Ok(fp);
}
}
Err(SmartcardFailure::SmartCardNotFound.into())
}
}