use std::{ collections::{HashMap, VecDeque}, io::{Read, Write}, path::Path, str::FromStr, }; use keyfork_derive_openpgp::derive_util::{ request::{DerivationAlgorithm, DerivationRequest}, DerivationPath, }; use openpgp::{ armor::{Kind, Writer}, cert::{Cert, CertParser, ValidCert}, packet::{Packet, Tag, UserID, PKESK, SEIP}, parse::{ stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper}, Parse, }, policy::{NullPolicy, Policy, StandardPolicy}, serialize::{ stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer}, Marshal, }, types::KeyFlags, Fingerprint, KeyID, PacketPile, }; pub use sequoia_openpgp as openpgp; use sharks::{Share, Sharks}; mod keyring; use keyring::Keyring; mod smartcard; use smartcard::SmartcardManager; /// Shard metadata verson 1: /// 1 byte: Version /// 1 byte: Threshold /// OpenPGP Packet Pile of Certs const SHARD_METADATA_VERSION: u8 = 1; const SHARD_METADATA_OFFSET: usize = 2; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Error with creating Share: {0}")] Share(String), #[error("Error combining shares: {0}")] CombineShares(String), #[error("Derived secret hash {0} != expected {1}")] InvalidSecret(Fingerprint, Fingerprint), #[error("OpenPGP error: {0}")] Sequoia(#[source] anyhow::Error), #[error("OpenPGP IO error: {0}")] SequoiaIo(#[source] std::io::Error), #[error("Keyring error: {0}")] Keyring(#[from] keyring::Error), #[error("Smartcard error: {0}")] Smartcard(#[from] smartcard::Error), #[error("IO error: {0}")] Io(#[source] std::io::Error), #[error("Derivation path: {0}")] DerivationPath(#[from] keyfork_derive_openpgp::derive_util::path::Error), #[error("Derivation request: {0}")] DerivationRequest(#[from] keyfork_derive_openpgp::derive_util::request::DerivationError), #[error("Keyfork OpenPGP: {0}")] KeyforkOpenPGP(#[from] keyfork_derive_openpgp::Error), } pub type Result = std::result::Result; #[derive(Debug, Clone)] pub struct EncryptedMessage { pkesks: Vec, message: SEIP, } impl EncryptedMessage { pub fn new(pkesks: &mut Vec, seip: SEIP) -> Self { Self { pkesks: std::mem::take(pkesks), message: seip, } } pub fn decrypt_with(&self, policy: &'_ dyn Policy, decryptor: H) -> Result> where H: VerificationHelper + DecryptionHelper, { let mut packets = vec![]; for pkesk in &self.pkesks { let mut packet = vec![]; pkesk.serialize(&mut packet).map_err(Error::Sequoia)?; let message = Message::new(&mut packets); let mut message = ArbitraryWriter::new(message, Tag::PKESK).map_err(Error::Sequoia)?; message.write_all(&packet).map_err(Error::SequoiaIo)?; message.finalize().map_err(Error::Sequoia)?; } let mut packet = vec![]; self.message .serialize(&mut packet) .map_err(Error::Sequoia)?; let message = Message::new(&mut packets); let mut message = ArbitraryWriter::new(message, Tag::SEIP).map_err(Error::Sequoia)?; message.write_all(&packet).map_err(Error::SequoiaIo)?; message.finalize().map_err(Error::Sequoia)?; let mut decryptor = DecryptorBuilder::from_bytes(&packets) .map_err(Error::Sequoia)? .with_policy(policy, None, decryptor) .map_err(Error::Sequoia)?; let mut content = vec![]; decryptor .read_to_end(&mut content) .map_err(Error::SequoiaIo)?; Ok(content) } } pub fn discover_certs(path: impl AsRef) -> Result> { let path = path.as_ref(); if path.is_file() { let mut vec = vec![]; for cert in CertParser::from_file(path).map_err(Error::Sequoia)? { vec.push(cert.map_err(Error::Sequoia)?); } Ok(vec) } else { let mut vec = vec![]; for entry in path .read_dir() .map_err(Error::Io)? .filter_map(Result::ok) .filter(|p| p.path().is_file()) { vec.push(Cert::from_file(entry.path()).map_err(Error::Sequoia)?); } Ok(vec) } } pub fn parse_messages(reader: impl Read + Send + Sync) -> Result> { let mut pkesks = Vec::new(); let mut encrypted_messages = VecDeque::new(); for packet in PacketPile::from_reader(reader) .map_err(Error::Sequoia)? .into_children() { match packet { Packet::PKESK(p) => pkesks.push(p), Packet::SEIP(s) => { encrypted_messages.push_back(EncryptedMessage::new(&mut pkesks, s)); } s => { panic!("Invalid variant found: {}", s.tag()); } } } Ok(encrypted_messages) } fn get_encryption_keys<'a>( cert: &'a ValidCert, ) -> openpgp::cert::prelude::ValidKeyAmalgamationIter< 'a, openpgp::packet::key::PublicParts, openpgp::packet::key::UnspecifiedRole, > { cert.keys() .alive() .revoked(false) .supported() .for_storage_encryption() } fn get_decryption_keys<'a>( cert: &'a ValidCert, ) -> openpgp::cert::prelude::ValidKeyAmalgamationIter< 'a, openpgp::packet::key::SecretParts, openpgp::packet::key::UnspecifiedRole, > { cert.keys() /* .alive() .revoked(false) .supported() */ .for_storage_encryption() .secret() } pub fn combine( certs: Vec, metadata: EncryptedMessage, messages: Vec, mut output: impl Write, ) -> Result<()> { // Be as liberal as possible when decrypting. // We don't want to invalidate someone's keys just because the old sig expired. let policy = NullPolicy::new(); let mut keyring = Keyring::new(certs)?; let mut manager = SmartcardManager::new()?; 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. 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 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::>>() .map_err(Error::Sequoia)?; keyring.set_root_cert(root_cert.clone()); manager.set_root_cert(root_cert); let mut messages: HashMap = HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages)); let mut decrypted_messages: HashMap> = 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::>(); 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)?, ); } } } // 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::>(); 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::>(); 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 shares = decrypted_messages .values() .map(|message| Share::try_from(message.as_slice())) .collect::, &str>>() .map_err(|e| Error::Share(e.to_string()))?; let secret = Sharks(threshold) .recover(&shares) .map_err(|e| Error::CombineShares(e.to_string()))?; let userid = UserID::from("keyfork-sss"); let kdr = DerivationRequest::new( DerivationAlgorithm::Ed25519, &DerivationPath::from_str("m/7366512'/0'")?, ) .derive_with_master_seed(secret.to_vec())?; let derived_cert = keyfork_derive_openpgp::derive( kdr, &[KeyFlags::empty().set_certification().set_signing()], userid, )?; // NOTE: Signatures on certs will be different. Compare fingerprints instead. let derived_fp = derived_cert.fingerprint(); let expected_fp = keyring .root_cert() .expect("cert was previously set") .fingerprint(); if derived_fp != expected_fp { return Err(Error::InvalidSecret(derived_fp, expected_fp)); } output .write_all(smex::encode(&secret).as_bytes()) .map_err(Error::Io)?; Ok(()) } pub fn split(threshold: u8, certs: Vec, secret: &[u8], output: impl Write) -> Result<()> { // build cert to sign encrypted shares let userid = UserID::from("keyfork-sss"); let kdr = DerivationRequest::new( DerivationAlgorithm::Ed25519, &DerivationPath::from_str("m/7366512'/0'")?, ) .derive_with_master_seed(secret.to_vec())?; let derived_cert = keyfork_derive_openpgp::derive( kdr, &[KeyFlags::empty().set_certification().set_signing()], userid, )?; let signing_key = derived_cert .primary_key() .parts_into_secret() .map_err(Error::Sequoia)? .key() .clone() .into_keypair() .map_err(Error::Sequoia)?; let sharks = Sharks(threshold); let dealer = sharks.dealer(secret); let shares = dealer.map(|s| Vec::from(&s)).collect::>(); let policy = StandardPolicy::new(); let mut writer = Writer::new(output, Kind::Message).map_err(Error::SequoiaIo)?; let mut total_recipients = vec![]; let mut messages = vec![]; for (share, cert) in shares.iter().zip(certs) { total_recipients.push(cert.clone()); let valid_cert = cert.with_policy(&policy, None).map_err(Error::Sequoia)?; let encryption_keys = get_encryption_keys(&valid_cert).collect::>(); let mut message_output = vec![]; let message = Message::new(&mut message_output); let message = Encryptor2::for_recipients( message, encryption_keys .iter() .map(|k| Recipient::new(KeyID::wildcard(), k.key())), ) .build() .map_err(Error::Sequoia)?; let message = Signer::new(message, signing_key.clone()) .build() .map_err(Error::Sequoia)?; let mut message = LiteralWriter::new(message) .build() .map_err(Error::Sequoia)?; message.write_all(share).map_err(Error::SequoiaIo)?; message.finalize().map_err(Error::Sequoia)?; messages.push(message_output); } let mut pp = vec![SHARD_METADATA_VERSION, threshold]; // store derived cert to verify provided shares derived_cert.serialize(&mut pp).map_err(Error::Sequoia)?; for recipient in &total_recipients { recipient.serialize(&mut pp).map_err(Error::Sequoia)?; } // verify packet pile for (packet_cert, cert) in openpgp::cert::CertParser::from_bytes(&pp[2..]) .map_err(Error::Sequoia)? .skip(1) .zip(total_recipients.iter()) { if packet_cert.map_err(Error::Sequoia)? != *cert { panic!( "packet pile could not recreate cert: {}", cert.fingerprint() ); } } let valid_certs = total_recipients .iter() .map(|c| c.with_policy(&policy, None)) .collect::>>() .map_err(Error::Sequoia)?; let total_recipients = valid_certs.iter().flat_map(|vc| { get_encryption_keys(vc).map(|key| Recipient::new(KeyID::wildcard(), key.key())) }); // metadata let mut message_output = vec![]; let message = Message::new(&mut message_output); let message = Encryptor2::for_recipients(message, total_recipients) .build() .map_err(Error::Sequoia)?; let mut message = LiteralWriter::new(message) .build() .map_err(Error::Sequoia)?; message.write_all(&pp).map_err(Error::SequoiaIo)?; message.finalize().map_err(Error::Sequoia)?; writer .write_all(&message_output) .map_err(Error::SequoiaIo)?; for message in messages { writer.write_all(&message).map_err(Error::SequoiaIo)?; } writer.finalize().map_err(Error::SequoiaIo)?; Ok(()) }