keyfork-shard: enable step 1 decoding with openpgp-card, fix bug with multiple smartcards when decrypting
This commit is contained in:
parent
a184c62f42
commit
5b427516c6
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue