keyfork-derive-path-data: move all pathcrafting here
This commit is contained in:
parent
35ab5e65a4
commit
b26f296a75
|
@ -1681,6 +1681,7 @@ dependencies = [
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"keyfork-bin",
|
"keyfork-bin",
|
||||||
"keyfork-derive-openpgp",
|
"keyfork-derive-openpgp",
|
||||||
|
"keyfork-derive-path-data",
|
||||||
"keyfork-derive-util",
|
"keyfork-derive-util",
|
||||||
"keyfork-entropy",
|
"keyfork-entropy",
|
||||||
"keyfork-mnemonic",
|
"keyfork-mnemonic",
|
||||||
|
@ -1748,6 +1749,7 @@ version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
"keyfork-derive-path-data",
|
||||||
"keyfork-derive-util",
|
"keyfork-derive-util",
|
||||||
"keyforkd-client",
|
"keyforkd-client",
|
||||||
"sequoia-openpgp",
|
"sequoia-openpgp",
|
||||||
|
@ -1759,6 +1761,7 @@ name = "keyfork-derive-path-data"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"keyfork-derive-util",
|
"keyfork-derive-util",
|
||||||
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -16,3 +16,4 @@ ed25519-dalek = "2.0.0"
|
||||||
sequoia-openpgp = { version = "1.17.0", default-features = false }
|
sequoia-openpgp = { version = "1.17.0", default-features = false }
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
thiserror = "1.0.49"
|
thiserror = "1.0.49"
|
||||||
|
keyfork-derive-path-data = { version = "0.1.1", path = "../keyfork-derive-path-data" }
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use std::{env, process::ExitCode, str::FromStr};
|
use std::{env, process::ExitCode, str::FromStr};
|
||||||
|
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||||
|
use keyfork_derive_path_data::paths;
|
||||||
use keyforkd_client::Client;
|
use keyforkd_client::Client;
|
||||||
|
|
||||||
use ed25519_dalek::SigningKey;
|
use ed25519_dalek::SigningKey;
|
||||||
|
@ -78,16 +79,14 @@ fn validate(
|
||||||
subkey_format: &str,
|
subkey_format: &str,
|
||||||
default_userid: &str,
|
default_userid: &str,
|
||||||
) -> Result<(DerivationPath, Vec<KeyType>, UserID), Box<dyn std::error::Error>> {
|
) -> Result<(DerivationPath, Vec<KeyType>, UserID), Box<dyn std::error::Error>> {
|
||||||
let mut pgp_u32 = [0u8; 4];
|
let index = paths::OPENPGP.inner().first().unwrap();
|
||||||
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
|
|
||||||
let index = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
|
|
||||||
|
|
||||||
let path = DerivationPath::from_str(path)?;
|
let path = DerivationPath::from_str(path)?;
|
||||||
assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'");
|
assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'");
|
||||||
|
|
||||||
let given_index = path.iter().next().expect("checked .len() above");
|
let given_index = path.iter().next().expect("checked .len() above");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&index, given_index,
|
index, given_index,
|
||||||
"Expected derivation path starting with m/{index}, got: {given_index}",
|
"Expected derivation path starting with m/{index}, got: {given_index}",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -8,3 +8,4 @@ license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
keyfork-derive-util = { version = "0.2.0", path = "../keyfork-derive-util", default-features = false, registry = "distrust" }
|
keyfork-derive-util = { version = "0.2.0", path = "../keyfork-derive-util", default-features = false, registry = "distrust" }
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
|
|
@ -2,32 +2,128 @@
|
||||||
|
|
||||||
#![allow(clippy::unreadable_literal)]
|
#![allow(clippy::unreadable_literal)]
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||||
|
|
||||||
/// The default derivation path for OpenPGP.
|
pub mod paths {
|
||||||
pub static OPENPGP: DerivationIndex = DerivationIndex::new_unchecked(7366512, true);
|
use super::*;
|
||||||
|
|
||||||
|
/// The default derivation path for OpenPGP.
|
||||||
|
pub static OPENPGP: Lazy<DerivationPath> = Lazy::new(|| {
|
||||||
|
DerivationPath::default().chain_push(DerivationIndex::new_unchecked(
|
||||||
|
u32::from_be_bytes(*b"\x00pgp"),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The derivation path for OpenPGP certificates used for sharding.
|
||||||
|
pub static OPENPGP_SHARD: Lazy<DerivationPath> = Lazy::new(|| {
|
||||||
|
DerivationPath::default()
|
||||||
|
.chain_push(DerivationIndex::new_unchecked(
|
||||||
|
u32::from_be_bytes(*b"\x00pgp"),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.chain_push(DerivationIndex::new_unchecked(
|
||||||
|
u32::from_be_bytes(*b"shrd"),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The derivation path for OpenPGP certificates used for disaster recovery.
|
||||||
|
pub static OPENPGP_DISASTER_RECOVERY: Lazy<DerivationPath> = Lazy::new(|| {
|
||||||
|
DerivationPath::default()
|
||||||
|
.chain_push(DerivationIndex::new_unchecked(
|
||||||
|
u32::from_be_bytes(*b"\x00pgp"),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
.chain_push(DerivationIndex::new_unchecked(
|
||||||
|
u32::from_be_bytes(*b"\x00\x00dr"),
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine if a prefix matches and whether the next index exists.
|
||||||
|
fn prefix_matches(given: &DerivationPath, target: &DerivationPath) -> Option<DerivationIndex> {
|
||||||
|
if given.len() <= target.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if target
|
||||||
|
.iter()
|
||||||
|
.zip(given.iter())
|
||||||
|
.all(|(left, right)| left == right)
|
||||||
|
{
|
||||||
|
given.iter().nth(target.len()).cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A derivation target.
|
/// A derivation target.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum Target {
|
pub enum Target {
|
||||||
/// An OpenPGP key, whose account is the given index.
|
/// An OpenPGP key, whose account is the given index.
|
||||||
OpenPGP(DerivationIndex),
|
OpenPGP(DerivationIndex),
|
||||||
|
|
||||||
|
/// An OpenPGP key used for sharding.
|
||||||
|
OpenPGPShard(DerivationIndex),
|
||||||
|
|
||||||
|
/// An OpenPGP key used for disaster recovery.
|
||||||
|
OpenPGPDisasterRecovery(DerivationIndex),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Target {
|
impl std::fmt::Display for Target {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::OpenPGP(account) => {
|
Target::OpenPGP(account) => {
|
||||||
write!(f, "OpenPGP key (account {account})")
|
write!(f, "OpenPGP key (account {account})")
|
||||||
}
|
}
|
||||||
|
Target::OpenPGPShard(shard_index) => {
|
||||||
|
write!(f, "OpenPGP Shard key (shard index {shard_index})")
|
||||||
|
}
|
||||||
|
Target::OpenPGPDisasterRecovery(account) => {
|
||||||
|
write!(f, "OpenPGP Disaster Recovery key (account {account})")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! test_match {
|
||||||
|
($var:ident, $shard:path, $target:path) => {
|
||||||
|
if let Some(index) = prefix_matches($var, &$shard) {
|
||||||
|
return Some($target(index));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Determine the closest [`Target`] for the given path. This method is intended to be used by
|
/// Determine the closest [`Target`] for the given path. This method is intended to be used by
|
||||||
/// `keyforkd` to provide an optional textual prompt to what a client is attempting to derive.
|
/// `keyforkd` to provide an optional textual prompt to what a client is attempting to derive.
|
||||||
pub fn guess_target(path: &DerivationPath) -> Option<Target> {
|
pub fn guess_target(path: &DerivationPath) -> Option<Target> {
|
||||||
Some(match path.iter().collect::<Vec<_>>()[..] {
|
test_match!(path, paths::OPENPGP_SHARD, Target::OpenPGPShard);
|
||||||
[t, index] if t == &OPENPGP => Target::OpenPGP(index.clone()),
|
test_match!(
|
||||||
_ => return None,
|
path,
|
||||||
})
|
paths::OPENPGP_DISASTER_RECOVERY,
|
||||||
|
Target::OpenPGPDisasterRecovery
|
||||||
|
);
|
||||||
|
test_match!(path, paths::OPENPGP, Target::OpenPGP);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let index = DerivationIndex::new(5312, false).unwrap();
|
||||||
|
let dr_key = paths::OPENPGP_DISASTER_RECOVERY
|
||||||
|
.clone()
|
||||||
|
.chain_push(index.clone());
|
||||||
|
match guess_target(&dr_key) {
|
||||||
|
Some(Target::OpenPGPDisasterRecovery(idx)) if idx == index => (),
|
||||||
|
bad => panic!("invalid value: {bad:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,3 +44,4 @@ openpgp-card-sequoia = { version = "0.2.0", default-features = false }
|
||||||
openpgp-card = "0.4.1"
|
openpgp-card = "0.4.1"
|
||||||
clap_complete = { version = "4.4.6", optional = true }
|
clap_complete = { version = "4.4.6", optional = true }
|
||||||
sequoia-openpgp = { version = "1.17.0", default-features = false, features = ["compression"] }
|
sequoia-openpgp = { version = "1.17.0", default-features = false, features = ["compression"] }
|
||||||
|
keyfork-derive-path-data = { version = "0.1.1", path = "../derive/keyfork-derive-path-data" }
|
||||||
|
|
|
@ -11,6 +11,7 @@ use keyfork_derive_openpgp::{
|
||||||
XPrvKey,
|
XPrvKey,
|
||||||
};
|
};
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||||
|
use keyfork_derive_path_data::paths;
|
||||||
use keyforkd_client::Client;
|
use keyforkd_client::Client;
|
||||||
|
|
||||||
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>;
|
||||||
|
@ -37,12 +38,7 @@ impl DeriveSubcommands {
|
||||||
fn handle(&self, account: DerivationIndex) -> Result<()> {
|
fn handle(&self, account: DerivationIndex) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
DeriveSubcommands::OpenPGP { user_id } => {
|
DeriveSubcommands::OpenPGP { user_id } => {
|
||||||
let mut pgp_u32 = [0u8; 4];
|
let path = paths::OPENPGP.clone().chain_push(account);
|
||||||
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?
|
// TODO: should this be customizable?
|
||||||
let subkeys = vec![
|
let subkeys = vec![
|
||||||
KeyFlags::empty().set_certification(),
|
KeyFlags::empty().set_certification(),
|
||||||
|
|
|
@ -11,16 +11,24 @@ 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_path_data::paths;
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||||
|
use keyfork_mnemonic::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::Mnemonic;
|
|
||||||
|
|
||||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||||
|
|
||||||
|
@ -42,17 +50,8 @@ fn derive_key(seed: [u8; 32], index: u8) -> Result<Cert> {
|
||||||
KeyFlags::empty().set_authentication(),
|
KeyFlags::empty().set_authentication(),
|
||||||
];
|
];
|
||||||
|
|
||||||
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 mut shrd_u32 = [0u8; 4];
|
|
||||||
shrd_u32[..].copy_from_slice(&"shrd".bytes().collect::<Vec<u8>>());
|
|
||||||
let account = DerivationIndex::new(u32::from_be_bytes(shrd_u32), true)?;
|
|
||||||
let subkey = DerivationIndex::new(u32::from(index), true)?;
|
let subkey = DerivationIndex::new(u32::from(index), true)?;
|
||||||
let path = DerivationPath::default()
|
let path = paths::OPENPGP_SHARD.clone().chain_push(subkey);
|
||||||
.chain_push(chain)
|
|
||||||
.chain_push(account)
|
|
||||||
.chain_push(subkey);
|
|
||||||
let xprv = XPrv::new(seed)
|
let xprv = XPrv::new(seed)
|
||||||
.expect("could not construct master key from seed")
|
.expect("could not construct master key from seed")
|
||||||
.derive_path(&path)?;
|
.derive_path(&path)?;
|
||||||
|
@ -193,16 +192,21 @@ fn generate_shard_secret(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bottoms_up(key_discovery: &Path, threshold: u8, output_shardfile: &Path, output_cert: &Path, user_id: &str,) -> Result<()> {
|
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 entropy = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
||||||
let mnemonic = Mnemonic::from_array(entropy);
|
let mnemonic = Mnemonic::from_array(entropy);
|
||||||
let seed = mnemonic.generate_seed(None);
|
let seed = mnemonic.generate_seed(None);
|
||||||
|
|
||||||
// TODO: should this allow for customizing the account index from 0? Potential for key reuse
|
// TODO: should this allow for customizing the account index from 0? Potential for key reuse
|
||||||
// errors.
|
// errors.
|
||||||
let path = DerivationPath::default()
|
let path = paths::OPENPGP_DISASTER_RECOVERY
|
||||||
.chain_push(DerivationIndex::new(u32::from_be_bytes(*b"\x00pgp"), true)?)
|
.clone()
|
||||||
.chain_push(DerivationIndex::new(u32::from_be_bytes(*b"\x00\x00dr"), true)?)
|
|
||||||
.chain_push(DerivationIndex::new(0, true)?);
|
.chain_push(DerivationIndex::new(0, true)?);
|
||||||
let subkeys = [
|
let subkeys = [
|
||||||
KeyFlags::empty().set_certification(),
|
KeyFlags::empty().set_certification(),
|
||||||
|
@ -227,7 +231,13 @@ fn bottoms_up(key_discovery: &Path, threshold: u8, output_shardfile: &Path, outp
|
||||||
let certs = OpenPGP::<DefaultTerminal>::discover_certs(key_discovery)?;
|
let certs = OpenPGP::<DefaultTerminal>::discover_certs(key_discovery)?;
|
||||||
|
|
||||||
let shardfile = File::create(output_shardfile)?;
|
let shardfile = File::create(output_shardfile)?;
|
||||||
opgp.shard_and_encrypt(threshold, certs.len() as u8, &entropy, &certs[..], shardfile)?;
|
opgp.shard_and_encrypt(
|
||||||
|
threshold,
|
||||||
|
certs.len() as u8,
|
||||||
|
&entropy,
|
||||||
|
&certs[..],
|
||||||
|
shardfile,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -299,7 +309,13 @@ impl WizardSubcommands {
|
||||||
output_shardfile,
|
output_shardfile,
|
||||||
output_cert,
|
output_cert,
|
||||||
user_id,
|
user_id,
|
||||||
} => bottoms_up(key_discovery, *threshold, output_shardfile, output_cert, user_id),
|
} => bottoms_up(
|
||||||
|
key_discovery,
|
||||||
|
*threshold,
|
||||||
|
output_shardfile,
|
||||||
|
output_cert,
|
||||||
|
user_id,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue