From a1c3d52c14a631f8dee9d860d875309bf6c9c3be Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 25 Feb 2025 17:02:35 -0500 Subject: [PATCH] keyfork: restructure wizard shard key generation also: `keyfork provision shard` --- crates/keyfork/src/cli/provision/mod.rs | 39 +++- crates/keyfork/src/cli/provision/openpgp.rs | 190 ++++++++++++-------- crates/keyfork/src/cli/wizard.rs | 18 +- 3 files changed, 156 insertions(+), 91 deletions(-) diff --git a/crates/keyfork/src/cli/provision/mod.rs b/crates/keyfork/src/cli/provision/mod.rs index 480faea..426dbbe 100644 --- a/crates/keyfork/src/cli/provision/mod.rs +++ b/crates/keyfork/src/cli/provision/mod.rs @@ -12,20 +12,27 @@ type Identifier = (String, Option); #[derive(Debug, Clone)] pub enum Provisioner { OpenPGPCard(openpgp::OpenPGPCard), + Shard(openpgp::Shard), } impl std::fmt::Display for Provisioner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Provisioner::OpenPGPCard(_) => f.write_str("openpgp-card"), - } + f.write_str(self.identifier()) } } impl Provisioner { + pub fn identifier(&self) -> &'static str { + match self { + Provisioner::OpenPGPCard(_) => "openpgp-card", + Provisioner::Shard(_) => "shard", + } + } + pub fn discover(&self) -> Result, Box> { match self { Provisioner::OpenPGPCard(o) => o.discover(), + Provisioner::Shard(s) => s.discover(), } } @@ -44,6 +51,17 @@ impl Provisioner { let xprv: XPrv = client.request_xprv(&path)?; o.provision(xprv, provisioner) } + Provisioner::Shard(s) => { + type Prv = ::PrivateKey; + type XPrv = ExtendedPrivateKey; + let account_index = DerivationIndex::new(provisioner.account, true)?; + let path = ::derivation_prefix() + .chain_push(account_index); + let mut client = keyforkd_client::Client::discover_socket()?; + let xprv: XPrv = client.request_xprv(&path)?; + panic!(); + s.provision(xprv, provisioner) + } } } @@ -62,19 +80,26 @@ impl Provisioner { let xprv = XPrv::new(mnemonic.generate_seed(None))?.derive_path(&path)?; o.provision(xprv, provisioner) } + Provisioner::Shard(s) => { + type Prv = ::PrivateKey; + type XPrv = ExtendedPrivateKey; + let account_index = DerivationIndex::new(provisioner.account, true)?; + let path = ::derivation_prefix() + .chain_push(account_index); + let xprv = XPrv::new(mnemonic.generate_seed(None))?.derive_path(&path)?; + s.provision(xprv, provisioner) + } } } } impl ValueEnum for Provisioner { fn value_variants<'a>() -> &'a [Self] { - &[Self::OpenPGPCard(openpgp::OpenPGPCard)] + &[Self::OpenPGPCard(openpgp::OpenPGPCard), Self::Shard(openpgp::Shard)] } fn to_possible_value(&self) -> Option { - Some(PossibleValue::new(match self { - Self::OpenPGPCard(_) => "openpgp-card", - })) + Some(PossibleValue::new(self.identifier())) } } diff --git a/crates/keyfork/src/cli/provision/openpgp.rs b/crates/keyfork/src/cli/provision/openpgp.rs index f639726..55b6aad 100644 --- a/crates/keyfork/src/cli/provision/openpgp.rs +++ b/crates/keyfork/src/cli/provision/openpgp.rs @@ -18,28 +18,102 @@ use keyfork_prompt::default_handler; use openpgp_card_sequoia::{state::Open, Card}; use std::path::PathBuf; -#[derive(Clone, Debug)] -pub struct OpenPGPCard; - #[derive(thiserror::Error, Debug)] #[error("Provisioner was unable to find a matching smartcard")] struct NoMatchingSmartcard; +fn discover_cards() -> Result)>, Box> { + let mut idents = vec![]; + for backend in PcscBackend::cards(None)? { + let backend = backend?; + let mut card = Card::::new(backend)?; + let mut transaction = card.transaction()?; + let identifier = transaction.application_identifier()?.ident(); + let name = transaction.cardholder_name()?; + let name = (!name.is_empty()).then_some(name); + idents.push((identifier, name)); + } + Ok(idents) +} + +fn provision_card( + provisioner: config::Provisioner, + xprv: XPrv, +) -> Result<(), Box> { + let mut pm = default_handler()?; + + let (user_pin, admin_pin) = get_new_pins(&mut *pm)?; + + let subkeys = vec![ + KeyFlags::empty().set_certification(), + KeyFlags::empty().set_signing(), + KeyFlags::empty() + .set_transport_encryption() + .set_storage_encryption(), + KeyFlags::empty().set_authentication(), + ]; + + let userid = match provisioner.metadata.as_ref().and_then(|m| m.get("userid")) { + Some(userid) => UserID::from(userid.as_str()), + None => UserID::from("Keyfork-Provisioned Key"), + }; + let cert = keyfork_derive_openpgp::derive(xprv.clone(), &subkeys, &userid)?; + + if !provisioner + .metadata + .as_ref() + .is_some_and(|m| m.contains_key("_skip_cert_output")) + { + let cert_output = match provisioner.metadata.as_ref().and_then(|m| m.get("output")) { + Some(cert_output) => PathBuf::from(cert_output), + None => { + let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc"); + eprintln!( + "Writing OpenPGP certificate to: {path}", + path = path.display() + ); + path + } + }; + + let cert_output_file = std::fs::File::create(cert_output)?; + let mut writer = Writer::new(cert_output_file, Kind::PublicKey)?; + cert.serialize(&mut writer)?; + writer.finalize()?; + } + + let mut has_provisioned = false; + + for backend in PcscBackend::cards(None)? { + let backend = backend?; + + let result = factory_reset_current_card( + &mut |identifier| identifier == provisioner.identifier, + user_pin.trim(), + admin_pin.trim(), + &cert, + &keyfork_derive_openpgp::openpgp::policy::StandardPolicy::new(), + backend, + )?; + + has_provisioned = has_provisioned || result; + } + + if !has_provisioned { + return Err(NoMatchingSmartcard)?; + } + + Ok(()) +} + +#[derive(Clone, Debug)] +pub struct OpenPGPCard; + impl ProvisionExec for OpenPGPCard { type PrivateKey = keyfork_derive_openpgp::XPrvKey; fn discover(&self) -> Result)>, Box> { - let mut idents = vec![]; - for backend in PcscBackend::cards(None)? { - let backend = backend?; - let mut card = Card::::new(backend)?; - let mut transaction = card.transaction()?; - let identifier = transaction.application_identifier()?.ident(); - let name = transaction.cardholder_name()?; - let name = (!name.is_empty()).then_some(name); - idents.push((identifier, name)); - } - Ok(idents) + discover_cards() } fn derivation_prefix() -> keyfork_derive_util::DerivationPath { @@ -51,69 +125,29 @@ impl ProvisionExec for OpenPGPCard { xprv: XPrv, provisioner: config::Provisioner, ) -> Result<(), Box> { - let mut pm = default_handler()?; - - let (user_pin, admin_pin) = get_new_pins(&mut *pm)?; - - let subkeys = vec![ - KeyFlags::empty().set_certification(), - KeyFlags::empty().set_signing(), - KeyFlags::empty() - .set_transport_encryption() - .set_storage_encryption(), - KeyFlags::empty().set_authentication(), - ]; - - let userid = match provisioner.metadata.as_ref().and_then(|m| m.get("userid")) { - Some(userid) => UserID::from(userid.as_str()), - None => UserID::from("Keyfork-Provisioned Key"), - }; - let cert = keyfork_derive_openpgp::derive(xprv.clone(), &subkeys, &userid)?; - - if !provisioner - .metadata - .as_ref() - .is_some_and(|m| m.contains_key("_skip_cert_output")) - { - let cert_output = match provisioner.metadata.as_ref().and_then(|m| m.get("output")) { - Some(cert_output) => PathBuf::from(cert_output), - None => { - let path = PathBuf::from(cert.fingerprint().to_string()).with_extension("asc"); - eprintln!( - "Writing OpenPGP certificate to: {path}", - path = path.display() - ); - path - } - }; - - let cert_output_file = std::fs::File::create(cert_output)?; - let mut writer = Writer::new(cert_output_file, Kind::PublicKey)?; - cert.serialize(&mut writer)?; - writer.finalize()?; - } - - let mut has_provisioned = false; - - for backend in PcscBackend::cards(None)? { - let backend = backend?; - - let result = factory_reset_current_card( - &mut |identifier| identifier == provisioner.identifier, - user_pin.trim(), - admin_pin.trim(), - &cert, - &keyfork_derive_openpgp::openpgp::policy::StandardPolicy::new(), - backend, - )?; - - has_provisioned = has_provisioned || result; - } - - if !has_provisioned { - return Err(NoMatchingSmartcard)?; - } - - Ok(()) + provision_card(provisioner, xprv) + } +} + +#[derive(Clone, Debug)] +pub struct Shard; + +impl ProvisionExec for Shard { + type PrivateKey = keyfork_derive_openpgp::XPrvKey; + + fn discover(&self) -> Result)>, Box> { + discover_cards() + } + + fn derivation_prefix() -> keyfork_derive_util::DerivationPath { + keyfork_derive_path_data::paths::OPENPGP_SHARD.clone() + } + + fn provision( + &self, + xprv: XPrv, + provisioner: config::Provisioner, + ) -> Result<(), Box> { + provision_card(provisioner, xprv) } } diff --git a/crates/keyfork/src/cli/wizard.rs b/crates/keyfork/src/cli/wizard.rs index ac5ef32..b3a1363 100644 --- a/crates/keyfork/src/cli/wizard.rs +++ b/crates/keyfork/src/cli/wizard.rs @@ -34,9 +34,7 @@ pub struct PinLength(usize); type Result> = std::result::Result; -// TODO: refactor to use mnemonic derived seed instead of 256 bit entropy to allow for possible -// recovery in the future. -fn derive_key(seed: [u8; 32], index: u8) -> Result { +fn derive_key(seed: [u8; 64], index: u8) -> Result { let subkeys = vec![ KeyFlags::empty().set_certification(), KeyFlags::empty().set_signing(), @@ -167,7 +165,9 @@ fn cross_sign_certs(certs: &mut [Cert]) -> Result<(), Box impl GenerateShardSecret { fn handle(&self) -> Result<()> { - let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?; + let root_entropy = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?; + let mnemonic = Mnemonic::from_array(root_entropy); + let seed = mnemonic.generate_seed(None); let mut pm = default_handler()?; let mut certs = vec![]; let mut seen_cards: HashSet = HashSet::new(); @@ -246,12 +246,18 @@ impl GenerateShardSecret { if let Some(output_file) = self.output.as_ref() { let output = File::create(output_file)?; - opgp.shard_and_encrypt(self.threshold, certs.len() as u8, &seed, &certs[..], output)?; + opgp.shard_and_encrypt( + self.threshold, + certs.len() as u8, + mnemonic.as_bytes(), + &certs[..], + output, + )?; } else { opgp.shard_and_encrypt( self.threshold, certs.len() as u8, - &seed, + mnemonic.as_bytes(), &certs[..], std::io::stdout(), )?;