use std::{env, process::ExitCode, str::FromStr}; use keyfork_derive_util::{ request::{DerivationAlgorithm, DerivationRequest, DerivationResponse}, DerivationIndex, DerivationPath, }; use keyforkd_client::Client; use sequoia_openpgp::{packet::UserID, types::KeyFlags}; #[derive(Debug, thiserror::Error)] enum Error { #[error("Bad character: {0}")] BadChar(char), } #[derive(Debug)] struct KeyType { _inner: KeyFlags, } impl Default for KeyType { fn default() -> Self { Self { _inner: KeyFlags::empty(), } } } impl KeyType { fn certify(mut self) -> Self { self._inner = self._inner.set_certification(); self } fn sign(mut self) -> Self { self._inner = self._inner.set_signing(); self } fn encrypt(mut self) -> Self { self._inner = self._inner.set_transport_encryption(); self._inner = self._inner.set_storage_encryption(); self } fn authenticate(mut self) -> Self { self._inner = self._inner.set_authentication(); self } fn inner(&self) -> &KeyFlags { &self._inner } } impl FromStr for KeyType { type Err = Error; fn from_str(s: &str) -> Result { s.chars().try_fold(Self::default(), |s, ch| match ch { 'C' | 'c' => Ok(s.certify()), 'S' | 's' => Ok(s.sign()), 'E' | 'e' => Ok(s.encrypt()), 'A' | 'a' => Ok(s.authenticate()), ch => Err(Error::BadChar(ch)), }) } } fn validate( path: &str, subkey_format: &str, default_userid: &str, ) -> Result<(DerivationPath, Vec, UserID), Box> { let mut pgp_u32 = [0u8; 4]; pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::>()); let index = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; let path = DerivationPath::from_str(path)?; assert_eq!(2, path.len(), "Expected path of m/{index}/account_id'"); let given_index = path.iter().next().expect("checked .len() above"); assert_eq!( &index, given_index, "Expected derivation path starting with m/{index}, got: {given_index}", ); let subkey_format = subkey_format .split(',') .map(KeyType::from_str) .collect::, Error>>()?; assert!( subkey_format[0].inner().for_certification(), "First key must be able to certify" ); Ok((path, subkey_format, UserID::from(default_userid))) } fn run() -> Result<(), Box> { let mut args = env::args(); let program_name = args.next().expect("program name"); let args = args.collect::>(); let (path, subkey_format, default_userid) = match args.as_slice() { [path, subkey_format, default_userid] => validate(path, subkey_format, default_userid)?, _ => panic!("Usage: {program_name} path subkey_format default_userid"), }; let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path); let derived_data: DerivationResponse = Client::discover_socket()? .request(&request.into())? .try_into()?; let subkeys = subkey_format .iter() .map(|kt| kt.inner().clone()) .collect::>(); let cert = keyfork_derive_openpgp::derive(derived_data, subkeys.as_slice(), default_userid)?; use sequoia_openpgp::{ armor::{Kind, Writer}, serialize::Marshal, }; let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?; for packet in cert.into_packets() { packet.serialize(&mut w)?; } w.finalize()?; Ok(()) } fn main() -> ExitCode { if let Err(e) = run() { eprintln!("Error: {e}"); ExitCode::FAILURE } else { ExitCode::SUCCESS } }