use std::io::Write;
use std::path::Path;
use clap::Parser;
use mkit_attest::Algorithm;
use mkit_core::sign::{KeyPair, load_raw_32, save_key, save_raw_32};
use zeroize::Zeroizing;
use crate::clap_shim;
use crate::commands::attest_factory;
use crate::exit;
use crate::format;
#[derive(Debug, Parser)]
#[command(name = "mkit keygen", about = "Generate a fresh signing key.")]
struct KeygenOpts {
#[arg(long)]
algorithm: Option<String>,
#[arg(long)]
force: bool,
#[arg(long)]
print_pubkey: bool,
}
#[must_use]
pub fn run(args: &[String]) -> u8 {
let parsed = match clap_shim::parse::<KeygenOpts>("mkit keygen", args) {
Ok(o) => o,
Err(code) => return code,
};
let cwd = match std::env::current_dir() {
Ok(p) => p,
Err(e) => return emit_err(&format!("cannot read cwd: {e}"), exit::NOINPUT),
};
let alg_str = parsed
.algorithm
.clone()
.unwrap_or_else(|| "ed25519".to_owned());
let Ok(algorithm) = attest_factory::parse_algorithm(&alg_str) else {
return emit_err(
&format!("unknown algorithm '{alg_str}' — expected one of: ed25519, secp256k1, p256"),
exit::USAGE,
);
};
let layout = match super::resolve_layout(&cwd) {
Ok(layout) => layout,
Err(code) => return code,
};
let cfg = match crate::config::read_or_default(&layout) {
Ok(c) => c,
Err(e) => return emit_err(&format!("config: {e}"), exit::CONFIG_ERROR),
};
let rel_path: &str = match algorithm {
Algorithm::Ed25519 => {
if cfg.signing_key.is_empty() {
crate::config::DEFAULT_SIGNING_KEY
} else {
cfg.signing_key.as_str()
}
}
Algorithm::Secp256k1 => cfg.attest.secp256k1_key_path_or_default(),
Algorithm::P256 => cfg.attest.p256_key_path_or_default(),
#[cfg(feature = "bls-threshold")]
Algorithm::Bls12381Threshold => {
return emit_err(
"BLS threshold keygen is not supported here; use `mkit key generate` (issue #160)",
exit::UNAVAILABLE,
);
}
};
let key_path = match crate::config::resolve_key_path(&layout, rel_path) {
Ok(p) => p,
Err(e) => return emit_err(&format!("{e}"), exit::CONFIG_ERROR),
};
match algorithm {
Algorithm::Ed25519 => run_ed25519(&key_path, parsed.force, parsed.print_pubkey),
Algorithm::Secp256k1 => run_secp256k1(&key_path, parsed.force, parsed.print_pubkey),
Algorithm::P256 => run_p256(&key_path, parsed.force, parsed.print_pubkey),
#[cfg(feature = "bls-threshold")]
Algorithm::Bls12381Threshold => emit_err(
"BLS threshold keygen is not supported here; use `mkit key generate` (issue #160)",
exit::UNAVAILABLE,
),
}
}
fn run_ed25519(key_path: &Path, force: bool, print_pubkey: bool) -> u8 {
let exists = key_path.exists();
if exists && print_pubkey && !force {
let kp = match mkit_core::sign::load_key(key_path) {
Ok(kp) => kp,
Err(e) => return emit_err(&format!("load key: {e}"), exit::GENERAL_ERROR),
};
print_ed25519_pubkey(&kp);
return exit::OK;
}
if exists && !force {
return emit_err(
&format!(
"signing key already exists: {} (pass --force to overwrite)",
key_path.display()
),
exit::GENERAL_ERROR,
);
}
let kp = match KeyPair::generate() {
Ok(kp) => kp,
Err(e) => return emit_err(&format!("rng failed: {e}"), exit::GENERAL_ERROR),
};
if let Err(e) = save_key(key_path, &kp) {
return emit_err(&format!("save key: {e}"), exit::CANTCREAT);
}
let pk_hex = hex32(&kp.public.0);
{
let mut stderr = std::io::stderr().lock();
let _ = writeln!(stderr, "generated signing key at {}", key_path.display());
let _ = writeln!(stderr, "public: ed25519:{pk_hex}");
let _ = writeln!(
stderr,
"identity: {}",
format::short_identity(&mkit_core::Identity::ed25519(kp.public.0))
);
}
if print_pubkey {
let mut stdout = std::io::stdout().lock();
let _ = writeln!(stdout, "ed25519:{pk_hex}");
}
exit::OK
}
fn run_secp256k1(key_path: &Path, force: bool, print_pubkey: bool) -> u8 {
if key_path.exists() && print_pubkey && !force {
let secret = match load_raw_32(key_path) {
Ok(s) => s,
Err(e) => return emit_err(&format!("load key: {e}"), exit::GENERAL_ERROR),
};
let signer = match mkit_attest::signer_k256::Secp256k1Signer::from_seed_zeroizing(&secret) {
Ok(s) => s,
Err(e) => return emit_err(&format!("invalid secp256k1 key: {e}"), exit::GENERAL_ERROR),
};
let pk = signer.public_key_sec1();
let mut stdout = std::io::stdout().lock();
let _ = writeln!(stdout, "secp256k1:{}", hex_lower(&pk));
return exit::OK;
}
if key_path.exists() && !force {
return emit_err(
&format!(
"signing key already exists: {} (pass --force to overwrite)",
key_path.display()
),
exit::GENERAL_ERROR,
);
}
let (signer, secret) = match generate_secp256k1_signer() {
Ok(x) => x,
Err(e) => return emit_err(&e, exit::GENERAL_ERROR),
};
if let Err(e) = save_raw_32(key_path, &secret) {
return emit_err(&format!("save key: {e}"), exit::CANTCREAT);
}
drop(secret);
let pk = signer.public_key_sec1();
{
let mut stderr = std::io::stderr().lock();
let _ = writeln!(stderr, "generated signing key at {}", key_path.display());
let _ = writeln!(stderr, "public: secp256k1:{}", hex_lower(&pk));
}
if print_pubkey {
let mut stdout = std::io::stdout().lock();
let _ = writeln!(stdout, "secp256k1:{}", hex_lower(&pk));
}
exit::OK
}
fn run_p256(key_path: &Path, force: bool, print_pubkey: bool) -> u8 {
if key_path.exists() && print_pubkey && !force {
let secret = match load_raw_32(key_path) {
Ok(s) => s,
Err(e) => return emit_err(&format!("load key: {e}"), exit::GENERAL_ERROR),
};
let signer = match mkit_attest::signer_p256::P256Signer::from_seed_zeroizing(&secret) {
Ok(s) => s,
Err(e) => return emit_err(&format!("invalid p256 key: {e}"), exit::GENERAL_ERROR),
};
let pk = signer.public_key_sec1();
let mut stdout = std::io::stdout().lock();
let _ = writeln!(stdout, "p256:{}", hex_lower(&pk));
return exit::OK;
}
if key_path.exists() && !force {
return emit_err(
&format!(
"signing key already exists: {} (pass --force to overwrite)",
key_path.display()
),
exit::GENERAL_ERROR,
);
}
let (signer, secret) = match generate_p256_signer() {
Ok(x) => x,
Err(e) => return emit_err(&e, exit::GENERAL_ERROR),
};
if let Err(e) = save_raw_32(key_path, &secret) {
return emit_err(&format!("save key: {e}"), exit::CANTCREAT);
}
drop(secret);
let pk = signer.public_key_sec1();
{
let mut stderr = std::io::stderr().lock();
let _ = writeln!(stderr, "generated signing key at {}", key_path.display());
let _ = writeln!(stderr, "public: p256:{}", hex_lower(&pk));
}
if print_pubkey {
let mut stdout = std::io::stdout().lock();
let _ = writeln!(stdout, "p256:{}", hex_lower(&pk));
}
exit::OK
}
fn generate_secp256k1_signer() -> Result<
(
mkit_attest::signer_k256::Secp256k1Signer,
Zeroizing<[u8; 32]>,
),
String,
> {
for _ in 0..256 {
let mut buf: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
getrandom::fill(buf.as_mut_slice()).map_err(|e| format!("rng failed: {e}"))?;
if let Ok(signer) = mkit_attest::signer_k256::Secp256k1Signer::from_seed_zeroizing(&buf) {
return Ok((signer, buf));
}
}
Err("rng produced 256 consecutive invalid secp256k1 scalars (impossible in practice)".into())
}
fn generate_p256_signer()
-> Result<(mkit_attest::signer_p256::P256Signer, Zeroizing<[u8; 32]>), String> {
for _ in 0..256 {
let mut buf: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
getrandom::fill(buf.as_mut_slice()).map_err(|e| format!("rng failed: {e}"))?;
if let Ok(signer) = mkit_attest::signer_p256::P256Signer::from_seed_zeroizing(&buf) {
return Ok((signer, buf));
}
}
Err("rng produced 256 consecutive invalid p256 scalars (impossible in practice)".into())
}
fn print_ed25519_pubkey(kp: &KeyPair) {
let mut stdout = std::io::stdout().lock();
let _ = writeln!(stdout, "ed25519:{}", hex32(&kp.public.0));
}
fn hex32(bytes: &[u8; 32]) -> String {
let h: mkit_core::hash::Hash = *bytes;
mkit_core::hash::to_hex(&h)
}
fn hex_lower(b: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut s = String::with_capacity(b.len() * 2);
for byte in b {
s.push(HEX[(byte >> 4) as usize] as char);
s.push(HEX[(byte & 0x0F) as usize] as char);
}
s
}
use super::error as emit_err;
#[cfg(test)]
mod tests {
use clap::Parser;
use super::KeygenOpts;
#[test]
fn parse_defaults() {
let p = KeygenOpts::try_parse_from(["mkit keygen"]).unwrap();
assert!(p.algorithm.is_none());
assert!(!p.force);
assert!(!p.print_pubkey);
}
#[test]
fn parse_all_flags() {
let p = KeygenOpts::try_parse_from([
"mkit keygen",
"--algorithm",
"secp256k1",
"--force",
"--print-pubkey",
])
.unwrap();
assert_eq!(p.algorithm.as_deref(), Some("secp256k1"));
assert!(p.force);
assert!(p.print_pubkey);
}
#[test]
fn parse_unknown_flag_rejected() {
assert!(KeygenOpts::try_parse_from(["mkit keygen", "--bogus"]).is_err());
}
}