keyfork: restructure wizard shard key generation

also: `keyfork provision shard`
This commit is contained in:
Ryan Heywood 2025-02-25 17:02:35 -05:00
parent 674e2e93c5
commit a1c3d52c14
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
3 changed files with 156 additions and 91 deletions

View File

@ -12,20 +12,27 @@ type Identifier = (String, Option<String>);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Provisioner { pub enum Provisioner {
OpenPGPCard(openpgp::OpenPGPCard), OpenPGPCard(openpgp::OpenPGPCard),
Shard(openpgp::Shard),
} }
impl std::fmt::Display for Provisioner { impl std::fmt::Display for Provisioner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { f.write_str(self.identifier())
Provisioner::OpenPGPCard(_) => f.write_str("openpgp-card"),
}
} }
} }
impl Provisioner { impl Provisioner {
pub fn identifier(&self) -> &'static str {
match self {
Provisioner::OpenPGPCard(_) => "openpgp-card",
Provisioner::Shard(_) => "shard",
}
}
pub fn discover(&self) -> Result<Vec<Identifier>, Box<dyn std::error::Error>> { pub fn discover(&self) -> Result<Vec<Identifier>, Box<dyn std::error::Error>> {
match self { match self {
Provisioner::OpenPGPCard(o) => o.discover(), Provisioner::OpenPGPCard(o) => o.discover(),
Provisioner::Shard(s) => s.discover(),
} }
} }
@ -44,6 +51,17 @@ impl Provisioner {
let xprv: XPrv = client.request_xprv(&path)?; let xprv: XPrv = client.request_xprv(&path)?;
o.provision(xprv, provisioner) o.provision(xprv, provisioner)
} }
Provisioner::Shard(s) => {
type Prv = <openpgp::Shard as ProvisionExec>::PrivateKey;
type XPrv = ExtendedPrivateKey<Prv>;
let account_index = DerivationIndex::new(provisioner.account, true)?;
let path = <openpgp::Shard as ProvisionExec>::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)?; let xprv = XPrv::new(mnemonic.generate_seed(None))?.derive_path(&path)?;
o.provision(xprv, provisioner) o.provision(xprv, provisioner)
} }
Provisioner::Shard(s) => {
type Prv = <openpgp::Shard as ProvisionExec>::PrivateKey;
type XPrv = ExtendedPrivateKey<Prv>;
let account_index = DerivationIndex::new(provisioner.account, true)?;
let path = <openpgp::Shard as ProvisionExec>::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 { impl ValueEnum for Provisioner {
fn value_variants<'a>() -> &'a [Self] { fn value_variants<'a>() -> &'a [Self] {
&[Self::OpenPGPCard(openpgp::OpenPGPCard)] &[Self::OpenPGPCard(openpgp::OpenPGPCard), Self::Shard(openpgp::Shard)]
} }
fn to_possible_value(&self) -> Option<PossibleValue> { fn to_possible_value(&self) -> Option<PossibleValue> {
Some(PossibleValue::new(match self { Some(PossibleValue::new(self.identifier()))
Self::OpenPGPCard(_) => "openpgp-card",
}))
} }
} }

View File

@ -18,17 +18,11 @@ use keyfork_prompt::default_handler;
use openpgp_card_sequoia::{state::Open, Card}; use openpgp_card_sequoia::{state::Open, Card};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct OpenPGPCard;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
#[error("Provisioner was unable to find a matching smartcard")] #[error("Provisioner was unable to find a matching smartcard")]
struct NoMatchingSmartcard; struct NoMatchingSmartcard;
impl ProvisionExec for OpenPGPCard { fn discover_cards() -> Result<Vec<(String, Option<String>)>, Box<dyn std::error::Error>> {
type PrivateKey = keyfork_derive_openpgp::XPrvKey;
fn discover(&self) -> Result<Vec<(String, Option<String>)>, Box<dyn std::error::Error>> {
let mut idents = vec![]; let mut idents = vec![];
for backend in PcscBackend::cards(None)? { for backend in PcscBackend::cards(None)? {
let backend = backend?; let backend = backend?;
@ -40,17 +34,12 @@ impl ProvisionExec for OpenPGPCard {
idents.push((identifier, name)); idents.push((identifier, name));
} }
Ok(idents) Ok(idents)
} }
fn derivation_prefix() -> keyfork_derive_util::DerivationPath { fn provision_card(
keyfork_derive_path_data::paths::OPENPGP.clone()
}
fn provision(
&self,
xprv: XPrv,
provisioner: config::Provisioner, provisioner: config::Provisioner,
) -> Result<(), Box<dyn std::error::Error>> { xprv: XPrv,
) -> Result<(), Box<dyn std::error::Error>> {
let mut pm = default_handler()?; let mut pm = default_handler()?;
let (user_pin, admin_pin) = get_new_pins(&mut *pm)?; let (user_pin, admin_pin) = get_new_pins(&mut *pm)?;
@ -115,5 +104,50 @@ impl ProvisionExec for OpenPGPCard {
} }
Ok(()) Ok(())
}
#[derive(Clone, Debug)]
pub struct OpenPGPCard;
impl ProvisionExec for OpenPGPCard {
type PrivateKey = keyfork_derive_openpgp::XPrvKey;
fn discover(&self) -> Result<Vec<(String, Option<String>)>, Box<dyn std::error::Error>> {
discover_cards()
}
fn derivation_prefix() -> keyfork_derive_util::DerivationPath {
keyfork_derive_path_data::paths::OPENPGP.clone()
}
fn provision(
&self,
xprv: XPrv,
provisioner: config::Provisioner,
) -> Result<(), Box<dyn std::error::Error>> {
provision_card(provisioner, xprv)
}
}
#[derive(Clone, Debug)]
pub struct Shard;
impl ProvisionExec for Shard {
type PrivateKey = keyfork_derive_openpgp::XPrvKey;
fn discover(&self) -> Result<Vec<(String, Option<String>)>, Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
provision_card(provisioner, xprv)
} }
} }

View File

@ -34,9 +34,7 @@ pub struct PinLength(usize);
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>; type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
// TODO: refactor to use mnemonic derived seed instead of 256 bit entropy to allow for possible fn derive_key(seed: [u8; 64], index: u8) -> Result<Cert> {
// recovery in the future.
fn derive_key(seed: [u8; 32], index: u8) -> Result<Cert> {
let subkeys = vec![ let subkeys = vec![
KeyFlags::empty().set_certification(), KeyFlags::empty().set_certification(),
KeyFlags::empty().set_signing(), KeyFlags::empty().set_signing(),
@ -167,7 +165,9 @@ fn cross_sign_certs(certs: &mut [Cert]) -> Result<(), Box<dyn std::error::Error>
impl GenerateShardSecret { impl GenerateShardSecret {
fn handle(&self) -> Result<()> { 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 pm = default_handler()?;
let mut certs = vec![]; let mut certs = vec![];
let mut seen_cards: HashSet<String> = HashSet::new(); let mut seen_cards: HashSet<String> = HashSet::new();
@ -246,12 +246,18 @@ impl GenerateShardSecret {
if let Some(output_file) = self.output.as_ref() { if let Some(output_file) = self.output.as_ref() {
let output = File::create(output_file)?; 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 { } else {
opgp.shard_and_encrypt( opgp.shard_and_encrypt(
self.threshold, self.threshold,
certs.len() as u8, certs.len() as u8,
&seed, mnemonic.as_bytes(),
&certs[..], &certs[..],
std::io::stdout(), std::io::stdout(),
)?; )?;