keyfork derive key: initial commit

This commit is contained in:
Ryan Heywood 2025-01-04 01:47:32 -05:00
parent c46f9e48b7
commit 503c6fa0b4
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
4 changed files with 92 additions and 3 deletions

2
Cargo.lock generated
View File

@ -1742,6 +1742,7 @@ dependencies = [
name = "keyfork"
version = "0.2.4"
dependencies = [
"base64",
"card-backend-pcsc",
"clap",
"clap_complete",
@ -1756,6 +1757,7 @@ dependencies = [
"keyfork-shard",
"keyforkd",
"keyforkd-client",
"keyforkd-models",
"openpgp-card",
"openpgp-card-sequoia",
"sequoia-openpgp",

View File

@ -74,6 +74,7 @@ image = { version = "0.25.2", default-features = false }
thiserror = "1.0.56"
tokio = "1.35.1"
v4l = "0.14.0"
base64 = "0.22.1"
[profile.dev.package.keyfork-qrcode]
opt-level = 3

View File

@ -45,3 +45,5 @@ openpgp-card-sequoia = { workspace = true }
openpgp-card = { workspace = true }
clap_complete = { version = "4.4.6", optional = true }
sequoia-openpgp = { workspace = true }
keyforkd-models.workspace = true
base64.workspace = true

View File

@ -1,5 +1,5 @@
use super::Keyfork;
use clap::{Args, Parser, Subcommand};
use clap::{Args, Parser, Subcommand, ValueEnum};
use keyfork_derive_openpgp::{
openpgp::{
@ -10,9 +10,13 @@ use keyfork_derive_openpgp::{
},
XPrvKey,
};
use keyfork_derive_util::DerivationIndex;
use keyfork_derive_path_data::paths;
use keyfork_derive_util::{
request::{DerivationAlgorithm, DerivationRequest, DerivationResponse},
DerivationIndex, DerivationPath, IndexError,
};
use keyforkd_client::Client;
use keyforkd_models::Request;
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
@ -28,7 +32,10 @@ pub enum DeriveSubcommands {
/// 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.
#[command(name = "openpgp")]
OpenPGP(OpenPGP)
OpenPGP(OpenPGP),
/// Derive a bare key for a specific algorithm, in a given format.
Key(Key),
}
#[derive(Args, Clone, Debug)]
@ -37,10 +44,64 @@ pub struct OpenPGP {
user_id: String,
}
/// A format for exporting a key.
#[derive(ValueEnum, Clone, Debug)]
pub enum KeyFormat {
Hex,
Base64,
}
/// An invalid slug was provided.
#[derive(thiserror::Error, Debug)]
pub enum InvalidSlug {
/// The value provided was longer than four bytes.
#[error("The value provided was longer than four bytes: {0}")]
InvalidSize(usize),
/// The value provided was higher than the maximum derivation index.
#[error("The value provided was higher than the maximum derivation index: {0}")]
InvalidValue(#[from] IndexError),
}
#[derive(Clone, Debug)]
pub struct Slug(DerivationIndex);
impl std::str::FromStr for Slug {
type Err = InvalidSlug;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let bytes = s.as_bytes();
let mut parseable_bytes = [0u8; 4];
if bytes.len() <= 4 && !bytes.is_empty() {
parseable_bytes[(4 - bytes.len())..4].copy_from_slice(bytes);
} else {
return Err(InvalidSlug::InvalidSize(bytes.len()));
}
let slug = u32::from_be_bytes(parseable_bytes);
let index = DerivationIndex::new(slug, true)?;
Ok(Slug(index))
}
}
#[derive(Args, Clone, Debug)]
pub struct Key {
/// The derivation algorithm to derive a key for.
derivation_algorithm: DerivationAlgorithm,
/// The output format.
#[arg(value_enum)]
format: KeyFormat,
/// A maximum of four bytes, used for creating the derivation path.
#[arg(value_parser = clap::value_parser!(Slug))]
slug: Slug,
}
impl DeriveSubcommands {
fn handle(&self, account: DerivationIndex) -> Result<()> {
match self {
DeriveSubcommands::OpenPGP(opgp) => opgp.handle(account),
DeriveSubcommands::Key(key) => key.handle(account),
}
}
}
@ -72,6 +133,29 @@ impl OpenPGP {
}
}
impl Key {
pub fn handle(&self, account: DerivationIndex) -> Result<()> {
let mut client = keyforkd_client::Client::discover_socket()?;
let path = DerivationPath::default()
.chain_push(self.slug.0.clone())
.chain_push(account);
let request = DerivationRequest::new(self.derivation_algorithm.clone(), &path);
let request = Request::Derivation(request);
let derived_key: DerivationResponse = client.request(&request)?.try_into()?;
let formatted = match self.format {
KeyFormat::Hex => smex::encode(derived_key.data),
KeyFormat::Base64 => {
use base64::prelude::*;
BASE64_STANDARD.encode(derived_key.data)
}
};
eprintln!("{formatted}");
Ok(())
}
}
#[derive(Parser, Debug, Clone)]
pub struct Derive {
#[command(subcommand)]