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 // Load certs from path
let certs = discover_certs(key_discovery)?; let certs = discover_certs(key_discovery)?;
let recovery_file = PathBuf::from(if recovery_file == "=" { let recovery_file = PathBuf::from(if recovery_file == "-" {
eprintln!("loading certs from stdin; note that prompting smartcard devices will not work"); eprintln!("loading certs from stdin; note that prompting smartcard PINs will not work");
"/dev/stdin" "/dev/stdin"
} else { } else {
recovery_file recovery_file

View File

@ -177,7 +177,14 @@ pub fn combine(
let policy = NullPolicy::new(); let policy = NullPolicy::new();
let mut keyring = Keyring::new(certs); 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 mut cert_parser = CertParser::from_bytes(&content)?;
let root_cert = match cert_parser.next() { let root_cert = match cert_parser.next() {
@ -186,7 +193,8 @@ pub fn combine(
None => panic!("No certs found in cert parser"), None => panic!("No certs found in cert parser"),
}; };
let certs = cert_parser.collect::<openpgp::Result<Vec<_>>>()?; 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> = let mut messages: HashMap<KeyID, EncryptedMessage> =
HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages)); HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages));
let mut decrypted_messages: HashMap<KeyID, Vec<u8>> = HashMap::new(); 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(); let left_from_threshold = threshold as usize - decrypted_messages.len();
if left_from_threshold > 0 { if left_from_threshold > 0 {
eprintln!("remaining keys: {left_from_threshold}, prompting yubikeys"); 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 let mut remaining_usable_certs = certs
.iter() .iter()
.filter(|cert| messages.contains_key(&cert.keyid())) .filter(|cert| messages.contains_key(&cert.keyid()))
@ -241,29 +246,33 @@ pub fn combine(
while threshold as usize - decrypted_messages.len() > 0 { while threshold as usize - decrypted_messages.len() > 0 {
remaining_usable_certs.retain(|cert| messages.contains_key(&cert.keyid())); 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 for valid_cert in remaining_usable_certs
.iter() .iter()
.map(|cert| cert.with_policy(&policy, None)) .map(|cert| cert.with_policy(&policy, None))
{ {
let valid_cert = valid_cert?; let valid_cert = valid_cert?;
fingerprints.insert( let fp = valid_cert
valid_cert.keyid(), .keys()
valid_cert .for_storage_encryption()
.keys() .map(|k| k.fingerprint())
.for_storage_encryption() .collect::<Vec<_>>();
.map(|k| k.fingerprint()) for fp in &fp {
.collect::<Vec<_>>(), 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() { // Iterate over all fingerprints and use key_by_fingerprints to assoc with Enc. Message
// manager is loaded with a Card<Open>, utilize in tx if let Some(fp) = manager.load_any_fingerprint(total_fingerprints)? {
let message = messages.remove(&cert_id); // soundness: `key_by_fingerprints` is extended by the same fps that are then
if let Some(message) = message { // inserted into `total_fingerprints`
let message = message.decrypt_with(&policy, &mut manager)?; let cert_keyid = key_by_fingerprints.get(&fp).unwrap().clone();
decrypted_messages.insert(cert_id, message); 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 // Sets the root cert, returning the old cert
pub fn set_root_cert(&mut self, cert: impl Into<Option<Cert>>) -> Option<Cert> { pub fn set_root_cert(&mut self, cert: impl Into<Option<Cert>>) -> Option<Cert> {
let mut cert = cert.into(); let mut cert = cert.into();

View File

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