keyfork-shard: extract message decryptor functions

This commit is contained in:
Ryan Heywood 2023-12-26 15:17:14 -05:00
parent 1cdbab1a1d
commit ddefe1c6b5
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
1 changed files with 121 additions and 79 deletions

View File

@ -205,6 +205,110 @@ fn get_decryption_keys<'a>(
.secret()
}
fn decode_metadata_v1(buf: &[u8]) -> Result<(u8, Cert, Vec<Cert>)> {
assert_eq!(
SHARD_METADATA_VERSION, buf[0],
"Incompatible metadata version"
);
let threshold = buf[1];
let mut cert_parser =
CertParser::from_bytes(&buf[SHARD_METADATA_OFFSET..]).map_err(Error::Sequoia)?;
let root_cert = match cert_parser.next() {
Some(Ok(c)) => c,
Some(Err(e)) => return Err(Error::Sequoia(e)),
None => panic!("No data found"),
};
let certs = cert_parser
.collect::<openpgp::Result<Vec<_>>>()
.map_err(Error::Sequoia)?;
Ok((threshold, root_cert, certs))
}
// NOTE: When using single-decryptor mechanism, use this method with `threshold = 1` to return a
// single message.
fn decrypt_with_manager(
threshold: u8,
certs: &[Cert],
messages: &mut HashMap<KeyID, EncryptedMessage>,
policy: NullPolicy,
manager: &mut SmartcardManager,
) -> Result<HashMap<KeyID, Vec<u8>>> {
let mut decrypted_messages = HashMap::new();
while threshold as usize - decrypted_messages.len() > 0 {
// Build list of fingerprints that haven't yet been used for decrypting
let mut cert_by_fingerprint = HashMap::new();
let mut unused_fingerprints = vec![];
for valid_cert in certs
.iter()
.filter(|cert| !decrypted_messages.contains_key(&cert.keyid()))
.map(|cert| cert.with_policy(&policy, None))
{
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
let fp = valid_cert
.keys()
.for_storage_encryption()
.map(|k| k.fingerprint())
.collect::<Vec<_>>();
for fp in &fp {
cert_by_fingerprint.insert(fp.clone(), valid_cert.keyid());
}
unused_fingerprints.extend(fp.into_iter());
}
// Iterate over all fingerprints and use key_by_fingerprints to assoc with Enc. Message
if let Some(fp) = manager.load_any_fingerprint(unused_fingerprints)? {
let cert_keyid = cert_by_fingerprint.get(&fp).unwrap().clone();
if let Some(message) = messages.remove(&cert_keyid) {
let message = message.decrypt_with(&policy, &mut *manager)?;
decrypted_messages.insert(cert_keyid, message);
}
}
}
Ok(decrypted_messages)
}
// NOTE: When using single-decryptor mechanism, only a single key should be provided in Keyring to
// decrypt messages with.
fn decrypt_with_keyring(
certs: &[Cert],
policy: &NullPolicy,
keyring: &mut Keyring,
messages: &mut HashMap<KeyID, EncryptedMessage>,
) -> Result<HashMap<KeyID, Vec<u8>>, Error> {
let mut decrypted_messages = HashMap::new();
for valid_cert in certs.iter().map(|cert| cert.with_policy(policy, None)) {
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
let Some(secret_cert) = keyring.get_cert_for_primary_keyid(&valid_cert.keyid()) else {
continue;
};
let secret_cert = secret_cert
.with_policy(policy, None)
.map_err(Error::Sequoia)?;
let keys = get_decryption_keys(&secret_cert).collect::<Vec<_>>();
if !keys.is_empty() {
if let Some(message) = messages.get_mut(&valid_cert.keyid()) {
for (pkesk, key) in message.pkesks.iter_mut().zip(keys) {
pkesk.set_recipient(key.keyid());
}
// we have a pkesk, decrypt via keyring
decrypted_messages.insert(
valid_cert.keyid(),
message.decrypt_with(policy, &mut *keyring)?,
);
}
}
}
Ok(decrypted_messages)
}
pub fn combine(
certs: Vec<Cert>,
metadata: EncryptedMessage,
@ -220,103 +324,41 @@ pub fn combine(
let content = if keyring.is_empty() {
// NOTE: Any card plugged in that can't decrypt, will raise issues.
// This should not be used on a system where OpenPGP cards are available that shouldn't be
// used, due to the nature of how wildcard decryption works.
// used. The manager will try every wildcard packet for the card on the system, and once no
// matching PKESK packet has been found, will return an error.
manager.load_any_card()?;
metadata.decrypt_with(&policy, &mut manager)?
} else {
metadata.decrypt_with(&policy, &mut keyring)?
};
assert_eq!(
SHARD_METADATA_VERSION, content[0],
"incompatible metadata version"
);
let threshold = content[1];
let (threshold, root_cert, certs) = decode_metadata_v1(&content)?;
let mut cert_parser =
CertParser::from_bytes(&content[SHARD_METADATA_OFFSET..]).map_err(Error::Sequoia)?;
let root_cert = match cert_parser.next() {
Some(Ok(c)) => c,
Some(Err(e)) => panic!("Could not find root (first) certificate: {e}"),
None => panic!("No certs found in cert parser"),
};
let certs = cert_parser
.collect::<openpgp::Result<Vec<_>>>()
.map_err(Error::Sequoia)?;
keyring.set_root_cert(root_cert.clone());
manager.set_root_cert(root_cert);
// Generate a controlled binding from certificates to encrypted messages. This is stable
// because we control the order packets are encrypted and certificates are stored.
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();
// NOTE: This is ONLY stable because we control the generation of PKESK packets and
// encode the policy to ourselves.
for valid_cert in certs.iter().map(|cert| cert.with_policy(&policy, None)) {
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
// get keys from keyring for cert
let Some(secret_cert) = keyring.get_cert_for_primary_keyid(&valid_cert.keyid()) else {
continue;
};
let secret_cert = secret_cert
.with_policy(&policy, None)
.map_err(Error::Sequoia)?;
let keys = get_decryption_keys(&secret_cert).collect::<Vec<_>>();
if !keys.is_empty() {
if let Some(message) = messages.get_mut(&valid_cert.keyid()) {
for (pkesk, key) in message.pkesks.iter_mut().zip(keys) {
pkesk.set_recipient(key.keyid());
}
// we have a pkesk, decrypt via keyring
decrypted_messages.insert(
valid_cert.keyid(),
message.decrypt_with(&policy, &mut keyring)?,
);
}
}
}
let mut decrypted_messages =
decrypt_with_keyring(&certs, &policy, &mut keyring, &mut messages)?;
// clean decrypted messages from encrypted messages
messages.retain(|k, _v| !decrypted_messages.contains_key(k));
let left_from_threshold = threshold as usize - decrypted_messages.len();
if left_from_threshold > 0 {
let mut remaining_usable_certs = certs
.iter()
.filter(|cert| messages.contains_key(&cert.keyid()))
.collect::<Vec<_>>();
while threshold as usize - decrypted_messages.len() > 0 {
remaining_usable_certs.retain(|cert| messages.contains_key(&cert.keyid()));
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.map_err(Error::Sequoia)?;
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());
}
// 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);
}
}
}
let new_messages = decrypt_with_manager(
left_from_threshold as u8,
&certs,
&mut messages,
policy,
&mut manager,
)?;
decrypted_messages.extend(new_messages.into_iter());
}
let shares = decrypted_messages