keyfork-shard: enable step 1 decoding with openpgp-card, fix bug with multiple smartcards when decrypting

This commit is contained in:
Ryan Heywood 2023-11-03 20:42:33 -05:00
parent a184c62f42
commit 5b427516c6
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
4 changed files with 76 additions and 41 deletions

View File

@ -25,8 +25,8 @@ fn validate(
// Load certs from path
let certs = discover_certs(key_discovery)?;
let recovery_file = PathBuf::from(if recovery_file == "=" {
eprintln!("loading certs from stdin; note that prompting smartcard devices will not work");
let recovery_file = PathBuf::from(if recovery_file == "-" {
eprintln!("loading certs from stdin; note that prompting smartcard PINs will not work");
"/dev/stdin"
} else {
recovery_file

View File

@ -177,7 +177,14 @@ pub fn combine(
let policy = NullPolicy::new();
let mut keyring = Keyring::new(certs);
let content = metadata.decrypt_with(&policy, &mut keyring)?;
let mut manager = SmartcardManager::new();
let content = if keyring.is_empty() {
let card_fp = manager.load_any_card()?;
eprintln!("key discovery is empty, using hardware smartcard: {card_fp}");
metadata.decrypt_with(&policy, &mut manager)?
} else {
metadata.decrypt_with(&policy, &mut keyring)?
};
let mut cert_parser = CertParser::from_bytes(&content)?;
let root_cert = match cert_parser.next() {
@ -186,7 +193,8 @@ pub fn combine(
None => panic!("No certs found in cert parser"),
};
let certs = cert_parser.collect::<openpgp::Result<Vec<_>>>()?;
keyring.set_root_cert(root_cert);
keyring.set_root_cert(root_cert.clone());
manager.set_root_cert(root_cert);
let mut messages: HashMap<KeyID, EncryptedMessage> =
HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages));
let mut decrypted_messages: HashMap<KeyID, Vec<u8>> = HashMap::new();
@ -231,9 +239,6 @@ pub fn combine(
let left_from_threshold = threshold as usize - decrypted_messages.len();
if left_from_threshold > 0 {
eprintln!("remaining keys: {left_from_threshold}, prompting yubikeys");
// TODO: allow decrypt metadata with Yubikey, avoid require stage 1
let mut manager =
SmartcardManager::new(keyring.root_cert().expect("stage 1 decrypt").clone());
let mut remaining_usable_certs = certs
.iter()
.filter(|cert| messages.contains_key(&cert.keyid()))
@ -241,29 +246,33 @@ pub fn combine(
while threshold as usize - decrypted_messages.len() > 0 {
remaining_usable_certs.retain(|cert| messages.contains_key(&cert.keyid()));
let mut fingerprints = HashMap::new();
let mut key_by_fingerprints = HashMap::new();
let mut total_fingerprints = vec![];
for valid_cert in remaining_usable_certs
.iter()
.map(|cert| cert.with_policy(&policy, None))
{
let valid_cert = valid_cert?;
fingerprints.insert(
valid_cert.keyid(),
valid_cert
.keys()
.for_storage_encryption()
.map(|k| k.fingerprint())
.collect::<Vec<_>>(),
);
let fp = valid_cert
.keys()
.for_storage_encryption()
.map(|k| k.fingerprint())
.collect::<Vec<_>>();
for fp in &fp {
key_by_fingerprints.insert(fp.clone(), valid_cert.keyid());
}
total_fingerprints.extend(fp.iter().cloned());
}
for (cert_id, fingerprints) in fingerprints {
if manager.load_any_fingerprint(fingerprints)?.is_some() {
// manager is loaded with a Card<Open>, utilize in tx
let message = messages.remove(&cert_id);
if let Some(message) = message {
let message = message.decrypt_with(&policy, &mut manager)?;
decrypted_messages.insert(cert_id, message);
}
// Iterate over all fingerprints and use key_by_fingerprints to assoc with Enc. Message
if let Some(fp) = manager.load_any_fingerprint(total_fingerprints)? {
// soundness: `key_by_fingerprints` is extended by the same fps that are then
// inserted into `total_fingerprints`
let cert_keyid = key_by_fingerprints.get(&fp).unwrap().clone();
let message = messages.remove(&cert_keyid);
if let Some(message) = message {
let message = message.decrypt_with(&policy, &mut manager)?;
decrypted_messages.insert(cert_keyid, message);
}
}
}

View File

@ -40,6 +40,10 @@ impl Keyring {
}
}
pub fn is_empty(&self) -> bool {
self.full_certs.is_empty()
}
// 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();

View File

@ -13,34 +13,37 @@ use openpgp_card_sequoia::{state::Open, Card};
#[derive(Clone, Debug)]
pub enum SmartcardFailure {
#[allow(dead_code)]
SmartCardPromptFailed,
SmartCardNotFound,
SmartCardHasNoDecrypt,
}
impl std::fmt::Display for SmartcardFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SmartCardPromptFailed => f.write_str("Attempt to prompt for smart card failed"),
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: Cert,
root: Option<Cert>,
}
impl SmartcardManager {
pub fn new(root: Cert) -> Self {
Self {
current_card: None,
root,
}
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.
@ -57,6 +60,7 @@ impl SmartcardManager {
Ok(output)
}
/*
/// Return all [`Fingerprint`] for the currently accessible backends.
///
/// NOTE: Only implemented for decryption keys.
@ -74,6 +78,29 @@ impl SmartcardManager {
})
})
}
*/
/// 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.
///
@ -125,13 +152,8 @@ impl VerificationHelper for &mut SmartcardManager {
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
Ok(ids
.iter()
.flat_map(|kh| {
if &self.root.key_handle() == kh {
Some(self.root.clone())
} else {
None
}
})
.flat_map(|kh| self.root.as_ref().filter(|cert| cert.key_handle() == *kh))
.cloned()
.collect())
}