keyfork: enum-trait-ify choose-your-own commands
This commit is contained in:
parent
536e6da5ad
commit
5219c5a99f
|
@ -1,5 +1,5 @@
|
||||||
use super::Keyfork;
|
use super::Keyfork;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
|
||||||
use keyfork_derive_openpgp::{
|
use keyfork_derive_openpgp::{
|
||||||
openpgp::{
|
openpgp::{
|
||||||
|
@ -27,45 +27,51 @@ pub enum DeriveSubcommands {
|
||||||
/// It is recommended to use the default expiration of one day and to change the expiration
|
/// It is recommended to use the default expiration of one day and to change the expiration
|
||||||
/// using an external utility, to ensure the Certify key is usable.
|
/// using an external utility, to ensure the Certify key is usable.
|
||||||
#[command(name = "openpgp")]
|
#[command(name = "openpgp")]
|
||||||
OpenPGP {
|
OpenPGP(OpenPGP)
|
||||||
/// Default User ID for the certificate, using the OpenPGP User ID format.
|
}
|
||||||
user_id: String,
|
|
||||||
},
|
#[derive(Args, Clone, Debug)]
|
||||||
|
pub struct OpenPGP {
|
||||||
|
/// Default User ID for the certificate, using the OpenPGP User ID format.
|
||||||
|
user_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeriveSubcommands {
|
impl DeriveSubcommands {
|
||||||
fn handle(&self, account: DerivationIndex) -> Result<()> {
|
fn handle(&self, account: DerivationIndex) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
DeriveSubcommands::OpenPGP { user_id } => {
|
DeriveSubcommands::OpenPGP(opgp) => opgp.handle(account),
|
||||||
let mut pgp_u32 = [0u8; 4];
|
}
|
||||||
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
|
}
|
||||||
let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
|
}
|
||||||
let path = DerivationPath::default()
|
|
||||||
.chain_push(chain)
|
|
||||||
.chain_push(account);
|
|
||||||
// TODO: should this be customizable?
|
|
||||||
let subkeys = vec![
|
|
||||||
KeyFlags::empty().set_certification(),
|
|
||||||
KeyFlags::empty().set_signing(),
|
|
||||||
KeyFlags::empty()
|
|
||||||
.set_transport_encryption()
|
|
||||||
.set_storage_encryption(),
|
|
||||||
KeyFlags::empty().set_authentication(),
|
|
||||||
];
|
|
||||||
let xprv = Client::discover_socket()?.request_xprv::<XPrvKey>(&path)?;
|
|
||||||
let default_userid = UserID::from(user_id.as_str());
|
|
||||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &default_userid)?;
|
|
||||||
|
|
||||||
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
impl OpenPGP {
|
||||||
|
pub fn handle(&self, account: DerivationIndex) -> Result<()> {
|
||||||
|
let mut pgp_u32 = [0u8; 4];
|
||||||
|
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
|
||||||
|
let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
|
||||||
|
let path = DerivationPath::default()
|
||||||
|
.chain_push(chain)
|
||||||
|
.chain_push(account);
|
||||||
|
// TODO: should this be customizable?
|
||||||
|
let subkeys = vec![
|
||||||
|
KeyFlags::empty().set_certification(),
|
||||||
|
KeyFlags::empty().set_signing(),
|
||||||
|
KeyFlags::empty()
|
||||||
|
.set_transport_encryption()
|
||||||
|
.set_storage_encryption(),
|
||||||
|
KeyFlags::empty().set_authentication(),
|
||||||
|
];
|
||||||
|
let xprv = Client::discover_socket()?.request_xprv::<XPrvKey>(&path)?;
|
||||||
|
let default_userid = UserID::from(self.user_id.as_str());
|
||||||
|
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &default_userid)?;
|
||||||
|
|
||||||
for packet in cert.into_packets() {
|
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
||||||
packet.serialize(&mut w)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
w.finalize()?;
|
for packet in cert.into_packets() {
|
||||||
}
|
packet.serialize(&mut w)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.finalize()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
use super::Keyfork;
|
use super::Keyfork;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Args, Parser, Subcommand};
|
||||||
use std::{
|
use std::{collections::HashSet, fs::File, io::IsTerminal, path::PathBuf};
|
||||||
collections::HashSet,
|
|
||||||
fs::File,
|
|
||||||
io::IsTerminal,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use card_backend_pcsc::PcscBackend;
|
use card_backend_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
|
use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
|
||||||
|
|
||||||
use keyfork_derive_openpgp::{
|
use keyfork_derive_openpgp::{
|
||||||
openpgp::{self, packet::UserID, types::KeyFlags, Cert, serialize::Marshal, armor::{Writer, Kind}},
|
openpgp::{
|
||||||
|
self,
|
||||||
|
armor::{Kind, Writer},
|
||||||
|
packet::UserID,
|
||||||
|
serialize::Marshal,
|
||||||
|
types::KeyFlags,
|
||||||
|
Cert,
|
||||||
|
},
|
||||||
XPrv,
|
XPrv,
|
||||||
};
|
};
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath, VariableLengthSeed};
|
use keyfork_derive_util::{DerivationIndex, DerivationPath, VariableLengthSeed};
|
||||||
|
use keyfork_mnemonic_util::Mnemonic;
|
||||||
use keyfork_prompt::{
|
use keyfork_prompt::{
|
||||||
default_terminal,
|
default_terminal,
|
||||||
validators::{SecurePinValidator, Validator},
|
validators::{SecurePinValidator, Validator},
|
||||||
DefaultTerminal, Message, PromptHandler,
|
DefaultTerminal, Message, PromptHandler,
|
||||||
};
|
};
|
||||||
use keyfork_mnemonic_util::Mnemonic;
|
|
||||||
|
|
||||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||||
|
|
||||||
|
@ -109,202 +111,205 @@ fn factory_reset_current_card(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_shard_secret(
|
|
||||||
threshold: u8,
|
|
||||||
max: u8,
|
|
||||||
keys_per_shard: u8,
|
|
||||||
output_file: &Option<PathBuf>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
|
||||||
let mut pm = default_terminal()?;
|
|
||||||
let mut certs = vec![];
|
|
||||||
let mut seen_cards: HashSet<String> = HashSet::new();
|
|
||||||
let stdout = std::io::stdout();
|
|
||||||
if output_file.is_none() {
|
|
||||||
assert!(
|
|
||||||
!stdout.is_terminal(),
|
|
||||||
"not printing shard to terminal, redirect output"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let user_pin_validator = SecurePinValidator {
|
|
||||||
min_length: Some(6),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.to_fn();
|
|
||||||
let admin_pin_validator = SecurePinValidator {
|
|
||||||
min_length: Some(8),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.to_fn();
|
|
||||||
|
|
||||||
for index in 0..max {
|
|
||||||
let cert = derive_key(seed, index)?;
|
|
||||||
for i in 0..keys_per_shard {
|
|
||||||
pm.prompt_message(Message::Text(format!(
|
|
||||||
"Please remove all keys and insert key #{} for user #{}",
|
|
||||||
(i as u16) + 1,
|
|
||||||
(index as u16) + 1,
|
|
||||||
)))?;
|
|
||||||
let card_backend = loop {
|
|
||||||
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
|
|
||||||
break c;
|
|
||||||
}
|
|
||||||
pm.prompt_message(Message::Text(
|
|
||||||
"No smart card was found. Please plug in a smart card and press enter"
|
|
||||||
.to_string(),
|
|
||||||
))?;
|
|
||||||
};
|
|
||||||
let user_pin = pm.prompt_validated_passphrase(
|
|
||||||
"Please enter the new smartcard User PIN: ",
|
|
||||||
3,
|
|
||||||
&user_pin_validator,
|
|
||||||
)?;
|
|
||||||
let admin_pin = pm.prompt_validated_passphrase(
|
|
||||||
"Please enter the new smartcard Admin PIN: ",
|
|
||||||
3,
|
|
||||||
&admin_pin_validator,
|
|
||||||
)?;
|
|
||||||
factory_reset_current_card(
|
|
||||||
&mut seen_cards,
|
|
||||||
user_pin.trim(),
|
|
||||||
admin_pin.trim(),
|
|
||||||
&cert,
|
|
||||||
card_backend,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
certs.push(cert);
|
|
||||||
}
|
|
||||||
|
|
||||||
let opgp = OpenPGP::<DefaultTerminal>::new();
|
|
||||||
|
|
||||||
if let Some(output_file) = output_file {
|
|
||||||
let output = File::create(output_file)?;
|
|
||||||
opgp.shard_and_encrypt(threshold, certs.len() as u8, &seed, &certs[..], output)?;
|
|
||||||
} else {
|
|
||||||
opgp.shard_and_encrypt(
|
|
||||||
threshold,
|
|
||||||
certs.len() as u8,
|
|
||||||
&seed,
|
|
||||||
&certs[..],
|
|
||||||
std::io::stdout(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bottoms_up(key_discovery: &Path, threshold: u8, output_shardfile: &Path, output_cert: &Path, user_id: &str,) -> Result<()> {
|
|
||||||
let entropy = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
|
||||||
let mnemonic = Mnemonic::from_nonstandard_bytes(entropy);
|
|
||||||
// TODO: make this return const size, since is hash based
|
|
||||||
let seed = mnemonic.generate_seed(None);
|
|
||||||
|
|
||||||
// TODO: should this allow for customizing the account index from 0? Potential for key reuse
|
|
||||||
// errors.
|
|
||||||
let path = DerivationPath::default()
|
|
||||||
.chain_push(DerivationIndex::new(u32::from_be_bytes(*b"\x00pgp"), true)?)
|
|
||||||
.chain_push(DerivationIndex::new(u32::from_be_bytes(*b"\x00\x00dr"), true)?)
|
|
||||||
.chain_push(DerivationIndex::new(0, true)?);
|
|
||||||
let subkeys = [
|
|
||||||
KeyFlags::empty().set_certification(),
|
|
||||||
KeyFlags::empty().set_signing(),
|
|
||||||
KeyFlags::empty()
|
|
||||||
.set_transport_encryption()
|
|
||||||
.set_storage_encryption(),
|
|
||||||
KeyFlags::empty().set_authentication(),
|
|
||||||
];
|
|
||||||
let xprv = XPrv::new(VariableLengthSeed::new(&seed))
|
|
||||||
.expect("could not construct master key from seed")
|
|
||||||
.derive_path(&path)?;
|
|
||||||
let userid = UserID::from(user_id);
|
|
||||||
|
|
||||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
|
|
||||||
let certfile = File::create(output_cert)?;
|
|
||||||
let mut w = Writer::new(certfile, Kind::PublicKey)?;
|
|
||||||
cert.serialize(&mut w)?;
|
|
||||||
w.finalize()?;
|
|
||||||
|
|
||||||
let opgp = OpenPGP::<DefaultTerminal>::new();
|
|
||||||
let certs = OpenPGP::<DefaultTerminal>::discover_certs(key_discovery)?;
|
|
||||||
|
|
||||||
let shardfile = File::create(output_shardfile)?;
|
|
||||||
opgp.shard_and_encrypt(threshold, certs.len() as u8, &entropy, &certs[..], shardfile)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand, Clone, Debug)]
|
#[derive(Subcommand, Clone, Debug)]
|
||||||
pub enum WizardSubcommands {
|
pub enum WizardSubcommands {
|
||||||
/// Create a 256 bit secret and shard the secret to smart cards.
|
GenerateShardSecret(GenerateShardSecret),
|
||||||
///
|
BottomsUp(BottomsUp),
|
||||||
/// Smart cards will need to be plugged in periodically during the wizard, where they will be factory reset and
|
}
|
||||||
/// provisioned to `m/pgp'/shrd'/<share index>`. The secret can then be recovered with `keyfork recover shard` or
|
|
||||||
/// `keyfork recover remote-shard`. The share file will be printed to standard output.
|
|
||||||
GenerateShardSecret {
|
|
||||||
/// The minimum amount of keys required to decrypt the secret.
|
|
||||||
#[arg(long)]
|
|
||||||
threshold: u8,
|
|
||||||
|
|
||||||
/// The maximum amount of shards.
|
/// Create a 256 bit secret and shard the secret to smart cards.
|
||||||
#[arg(long)]
|
///
|
||||||
max: u8,
|
/// Smart cards will need to be plugged in periodically during the wizard, where they will be
|
||||||
|
/// factory reset and provisioned to `m/pgp'/shrd'/<share index>`. The secret can then be recovered
|
||||||
|
/// with `keyfork recover shard` or `keyfork recover remote-shard`. The share file will be printed
|
||||||
|
/// to standard output.
|
||||||
|
#[derive(Args, Clone, Debug)]
|
||||||
|
pub struct GenerateShardSecret {
|
||||||
|
/// The minimum amount of keys required to decrypt the secret.
|
||||||
|
#[arg(long)]
|
||||||
|
threshold: u8,
|
||||||
|
|
||||||
/// The amount of smart cards to provision per-shard.
|
/// The maximum amount of shards.
|
||||||
#[arg(long, default_value = "1")]
|
#[arg(long)]
|
||||||
keys_per_shard: u8,
|
max: u8,
|
||||||
|
|
||||||
/// The file to write the generated shard file to.
|
/// The amount of smart cards to provision per-shard.
|
||||||
#[arg(long)]
|
#[arg(long, default_value = "1")]
|
||||||
output: Option<PathBuf>,
|
keys_per_shard: u8,
|
||||||
},
|
|
||||||
|
|
||||||
/// Create a 256 bit secret and shard the secret to previously known OpenPGP certificates,
|
/// The file to write the generated shard file to.
|
||||||
/// deriving the default OpenPGP certificate for the secret.
|
#[arg(long)]
|
||||||
///
|
output: Option<PathBuf>,
|
||||||
/// This command was purpose-built for DEFCON and is not intended to be used normally, as it
|
}
|
||||||
/// implies keys used for sharding have been generated by a custom source.
|
|
||||||
BottomsUp {
|
|
||||||
/// The location of OpenPGP certificates to use when sharding.
|
|
||||||
key_discovery: PathBuf,
|
|
||||||
|
|
||||||
/// The minimum amount of keys required to decrypt the secret.
|
/// Create a 256 bit secret and shard the secret to previously known OpenPGP certificates,
|
||||||
#[arg(long)]
|
/// deriving the default OpenPGP certificate for the secret.
|
||||||
threshold: u8,
|
///
|
||||||
|
/// This command was purpose-built for DEFCON and is not intended to be used normally, as it
|
||||||
|
/// implies keys used for sharding have been generated by a custom source.
|
||||||
|
#[derive(Args, Clone, Debug)]
|
||||||
|
pub struct BottomsUp {
|
||||||
|
/// The location of OpenPGP certificates to use when sharding.
|
||||||
|
key_discovery: PathBuf,
|
||||||
|
|
||||||
/// The file to write the generated shard file to.
|
/// The minimum amount of keys required to decrypt the secret.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
output_shardfile: PathBuf,
|
threshold: u8,
|
||||||
|
|
||||||
/// The file to write the generated OpenPGP certificate to.
|
/// The file to write the generated shard file to.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
output_cert: PathBuf,
|
output_shardfile: PathBuf,
|
||||||
|
|
||||||
/// The User ID for the generated OpenPGP certificate.
|
/// The file to write the generated OpenPGP certificate to.
|
||||||
#[arg(long, default_value = "Disaster Recovery")]
|
#[arg(long)]
|
||||||
user_id: String,
|
output_cert: PathBuf,
|
||||||
},
|
|
||||||
|
/// The User ID for the generated OpenPGP certificate.
|
||||||
|
#[arg(long, default_value = "Disaster Recovery")]
|
||||||
|
user_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WizardSubcommands {
|
impl WizardSubcommands {
|
||||||
|
// dispatch
|
||||||
fn handle(&self) -> Result<()> {
|
fn handle(&self) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
WizardSubcommands::GenerateShardSecret {
|
WizardSubcommands::GenerateShardSecret(gss) => gss.handle(),
|
||||||
threshold,
|
WizardSubcommands::BottomsUp(bu) => bu.handle(),
|
||||||
max,
|
|
||||||
keys_per_shard,
|
|
||||||
output,
|
|
||||||
} => generate_shard_secret(*threshold, *max, *keys_per_shard, output),
|
|
||||||
WizardSubcommands::BottomsUp {
|
|
||||||
key_discovery,
|
|
||||||
threshold,
|
|
||||||
output_shardfile,
|
|
||||||
output_cert,
|
|
||||||
user_id,
|
|
||||||
} => bottoms_up(key_discovery, *threshold, output_shardfile, output_cert, user_id),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GenerateShardSecret {
|
||||||
|
fn handle(&self) -> Result<()> {
|
||||||
|
let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
||||||
|
let mut pm = default_terminal()?;
|
||||||
|
let mut certs = vec![];
|
||||||
|
let mut seen_cards: HashSet<String> = HashSet::new();
|
||||||
|
let stdout = std::io::stdout();
|
||||||
|
if self.output.is_none() {
|
||||||
|
assert!(
|
||||||
|
!stdout.is_terminal(),
|
||||||
|
"not printing shard to terminal, redirect output"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_pin_validator = SecurePinValidator {
|
||||||
|
min_length: Some(6),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.to_fn();
|
||||||
|
let admin_pin_validator = SecurePinValidator {
|
||||||
|
min_length: Some(8),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.to_fn();
|
||||||
|
|
||||||
|
for index in 0..self.max {
|
||||||
|
let cert = derive_key(seed, index)?;
|
||||||
|
for i in 0..self.keys_per_shard {
|
||||||
|
pm.prompt_message(Message::Text(format!(
|
||||||
|
"Please remove all keys and insert key #{} for user #{}",
|
||||||
|
(i as u16) + 1,
|
||||||
|
(index as u16) + 1,
|
||||||
|
)))?;
|
||||||
|
let card_backend = loop {
|
||||||
|
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
|
||||||
|
break c;
|
||||||
|
}
|
||||||
|
pm.prompt_message(Message::Text(
|
||||||
|
"No smart card was found. Please plug in a smart card and press enter"
|
||||||
|
.to_string(),
|
||||||
|
))?;
|
||||||
|
};
|
||||||
|
let user_pin = pm.prompt_validated_passphrase(
|
||||||
|
"Please enter the new smartcard User PIN: ",
|
||||||
|
3,
|
||||||
|
&user_pin_validator,
|
||||||
|
)?;
|
||||||
|
let admin_pin = pm.prompt_validated_passphrase(
|
||||||
|
"Please enter the new smartcard Admin PIN: ",
|
||||||
|
3,
|
||||||
|
&admin_pin_validator,
|
||||||
|
)?;
|
||||||
|
factory_reset_current_card(
|
||||||
|
&mut seen_cards,
|
||||||
|
user_pin.trim(),
|
||||||
|
admin_pin.trim(),
|
||||||
|
&cert,
|
||||||
|
card_backend,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
certs.push(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
let opgp = OpenPGP::<DefaultTerminal>::new();
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
} else {
|
||||||
|
opgp.shard_and_encrypt(
|
||||||
|
self.threshold,
|
||||||
|
certs.len() as u8,
|
||||||
|
&seed,
|
||||||
|
&certs[..],
|
||||||
|
std::io::stdout(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BottomsUp {
|
||||||
|
fn handle(&self) -> Result<()> {
|
||||||
|
let entropy = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
||||||
|
let mnemonic = Mnemonic::from_nonstandard_bytes(entropy);
|
||||||
|
// TODO: make this return const size, since is hash based
|
||||||
|
let seed = mnemonic.generate_seed(None);
|
||||||
|
|
||||||
|
// TODO: should this allow for customizing the account index from 0? Potential for key reuse
|
||||||
|
// errors.
|
||||||
|
let path = DerivationPath::default()
|
||||||
|
.chain_push(DerivationIndex::new(u32::from_be_bytes(*b"\x00pgp"), true)?)
|
||||||
|
.chain_push(DerivationIndex::new(
|
||||||
|
u32::from_be_bytes(*b"\x00\x00dr"),
|
||||||
|
true,
|
||||||
|
)?)
|
||||||
|
.chain_push(DerivationIndex::new(0, true)?);
|
||||||
|
let subkeys = [
|
||||||
|
KeyFlags::empty().set_certification(),
|
||||||
|
KeyFlags::empty().set_signing(),
|
||||||
|
KeyFlags::empty()
|
||||||
|
.set_transport_encryption()
|
||||||
|
.set_storage_encryption(),
|
||||||
|
KeyFlags::empty().set_authentication(),
|
||||||
|
];
|
||||||
|
let xprv = XPrv::new(VariableLengthSeed::new(&seed))
|
||||||
|
.expect("could not construct master key from seed")
|
||||||
|
.derive_path(&path)?;
|
||||||
|
let userid = UserID::from(self.user_id.as_str());
|
||||||
|
|
||||||
|
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
|
||||||
|
let certfile = File::create(&self.output_cert)?;
|
||||||
|
let mut w = Writer::new(certfile, Kind::PublicKey)?;
|
||||||
|
cert.serialize(&mut w)?;
|
||||||
|
w.finalize()?;
|
||||||
|
|
||||||
|
let opgp = OpenPGP::<DefaultTerminal>::new();
|
||||||
|
let certs = OpenPGP::<DefaultTerminal>::discover_certs(&self.key_discovery)?;
|
||||||
|
|
||||||
|
let shardfile = File::create(&self.output_shardfile)?;
|
||||||
|
opgp.shard_and_encrypt(
|
||||||
|
self.threshold,
|
||||||
|
certs.len() as u8,
|
||||||
|
&entropy,
|
||||||
|
&certs[..],
|
||||||
|
shardfile,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
pub struct Wizard {
|
pub struct Wizard {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|
Loading…
Reference in New Issue