1use std::io::Write;
31use std::path::Path;
32
33use clap::Parser;
34use mkit_attest::Algorithm;
35use mkit_core::sign::{KeyPair, load_raw_32, save_key, save_raw_32};
36use zeroize::Zeroizing;
37
38use crate::clap_shim;
39use crate::commands::attest_factory;
40use crate::exit;
41use crate::format;
42
43#[derive(Debug, Parser)]
44#[command(name = "mkit keygen", about = "Generate a fresh signing key.")]
45struct KeygenOpts {
46 #[arg(long)]
48 algorithm: Option<String>,
49 #[arg(long)]
51 force: bool,
52 #[arg(long)]
54 print_pubkey: bool,
55}
56
57#[must_use]
58pub fn run(args: &[String]) -> u8 {
59 let parsed = match clap_shim::parse::<KeygenOpts>("mkit keygen", args) {
60 Ok(o) => o,
61 Err(code) => return code,
62 };
63
64 let cwd = match std::env::current_dir() {
65 Ok(p) => p,
66 Err(e) => return emit_err(&format!("cannot read cwd: {e}"), exit::NOINPUT),
67 };
68
69 let alg_str = parsed
70 .algorithm
71 .clone()
72 .unwrap_or_else(|| "ed25519".to_owned());
73 let Ok(algorithm) = attest_factory::parse_algorithm(&alg_str) else {
74 return emit_err(
75 &format!("unknown algorithm '{alg_str}' — expected one of: ed25519, secp256k1, p256"),
76 exit::USAGE,
77 );
78 };
79
80 let layout = match super::resolve_layout(&cwd) {
86 Ok(layout) => layout,
87 Err(code) => return code,
88 };
89 let cfg = match crate::config::read_or_default(&layout) {
90 Ok(c) => c,
91 Err(e) => return emit_err(&format!("config: {e}"), exit::CONFIG_ERROR),
92 };
93 let rel_path: &str = match algorithm {
94 Algorithm::Ed25519 => {
95 if cfg.signing_key.is_empty() {
96 crate::config::DEFAULT_SIGNING_KEY
97 } else {
98 cfg.signing_key.as_str()
99 }
100 }
101 Algorithm::Secp256k1 => cfg.attest.secp256k1_key_path_or_default(),
102 Algorithm::P256 => cfg.attest.p256_key_path_or_default(),
103 #[cfg(feature = "bls-threshold")]
104 Algorithm::Bls12381Threshold => {
105 return emit_err(
106 "BLS threshold keygen is not supported here; use `mkit key generate` (issue #160)",
107 exit::UNAVAILABLE,
108 );
109 }
110 };
111 let key_path = match crate::config::resolve_key_path(&layout, rel_path) {
112 Ok(p) => p,
113 Err(e) => return emit_err(&format!("{e}"), exit::CONFIG_ERROR),
114 };
115
116 match algorithm {
117 Algorithm::Ed25519 => run_ed25519(&key_path, parsed.force, parsed.print_pubkey),
118 Algorithm::Secp256k1 => run_secp256k1(&key_path, parsed.force, parsed.print_pubkey),
119 Algorithm::P256 => run_p256(&key_path, parsed.force, parsed.print_pubkey),
120 #[cfg(feature = "bls-threshold")]
121 Algorithm::Bls12381Threshold => emit_err(
122 "BLS threshold keygen is not supported here; use `mkit key generate` (issue #160)",
123 exit::UNAVAILABLE,
124 ),
125 }
126}
127
128fn run_ed25519(key_path: &Path, force: bool, print_pubkey: bool) -> u8 {
129 let exists = key_path.exists();
130 if exists && print_pubkey && !force {
134 let kp = match mkit_core::sign::load_key(key_path) {
135 Ok(kp) => kp,
136 Err(e) => return emit_err(&format!("load key: {e}"), exit::GENERAL_ERROR),
137 };
138 print_ed25519_pubkey(&kp);
139 return exit::OK;
140 }
141 if exists && !force {
142 return emit_err(
143 &format!(
144 "signing key already exists: {} (pass --force to overwrite)",
145 key_path.display()
146 ),
147 exit::GENERAL_ERROR,
148 );
149 }
150 let kp = match KeyPair::generate() {
151 Ok(kp) => kp,
152 Err(e) => return emit_err(&format!("rng failed: {e}"), exit::GENERAL_ERROR),
153 };
154 if let Err(e) = save_key(key_path, &kp) {
155 return emit_err(&format!("save key: {e}"), exit::CANTCREAT);
156 }
157 let pk_hex = hex32(&kp.public.0);
158 {
159 let mut stderr = std::io::stderr().lock();
160 let _ = writeln!(stderr, "generated signing key at {}", key_path.display());
161 let _ = writeln!(stderr, "public: ed25519:{pk_hex}");
162 let _ = writeln!(
163 stderr,
164 "identity: {}",
165 format::short_identity(&mkit_core::Identity::ed25519(kp.public.0))
166 );
167 }
168 if print_pubkey {
169 let mut stdout = std::io::stdout().lock();
171 let _ = writeln!(stdout, "ed25519:{pk_hex}");
172 }
173 exit::OK
174}
175
176fn run_secp256k1(key_path: &Path, force: bool, print_pubkey: bool) -> u8 {
177 if key_path.exists() && print_pubkey && !force {
179 let secret = match load_raw_32(key_path) {
180 Ok(s) => s,
181 Err(e) => return emit_err(&format!("load key: {e}"), exit::GENERAL_ERROR),
182 };
183 let signer = match mkit_attest::signer_k256::Secp256k1Signer::from_seed_zeroizing(&secret) {
187 Ok(s) => s,
188 Err(e) => return emit_err(&format!("invalid secp256k1 key: {e}"), exit::GENERAL_ERROR),
189 };
190 let pk = signer.public_key_sec1();
191 let mut stdout = std::io::stdout().lock();
192 let _ = writeln!(stdout, "secp256k1:{}", hex_lower(&pk));
193 return exit::OK;
194 }
195 if key_path.exists() && !force {
196 return emit_err(
197 &format!(
198 "signing key already exists: {} (pass --force to overwrite)",
199 key_path.display()
200 ),
201 exit::GENERAL_ERROR,
202 );
203 }
204
205 let (signer, secret) = match generate_secp256k1_signer() {
210 Ok(x) => x,
211 Err(e) => return emit_err(&e, exit::GENERAL_ERROR),
212 };
213 if let Err(e) = save_raw_32(key_path, &secret) {
214 return emit_err(&format!("save key: {e}"), exit::CANTCREAT);
215 }
216 drop(secret);
217
218 let pk = signer.public_key_sec1();
219 {
220 let mut stderr = std::io::stderr().lock();
221 let _ = writeln!(stderr, "generated signing key at {}", key_path.display());
222 let _ = writeln!(stderr, "public: secp256k1:{}", hex_lower(&pk));
223 }
224 if print_pubkey {
225 let mut stdout = std::io::stdout().lock();
226 let _ = writeln!(stdout, "secp256k1:{}", hex_lower(&pk));
227 }
228 exit::OK
229}
230
231fn run_p256(key_path: &Path, force: bool, print_pubkey: bool) -> u8 {
232 if key_path.exists() && print_pubkey && !force {
233 let secret = match load_raw_32(key_path) {
234 Ok(s) => s,
235 Err(e) => return emit_err(&format!("load key: {e}"), exit::GENERAL_ERROR),
236 };
237 let signer = match mkit_attest::signer_p256::P256Signer::from_seed_zeroizing(&secret) {
239 Ok(s) => s,
240 Err(e) => return emit_err(&format!("invalid p256 key: {e}"), exit::GENERAL_ERROR),
241 };
242 let pk = signer.public_key_sec1();
243 let mut stdout = std::io::stdout().lock();
244 let _ = writeln!(stdout, "p256:{}", hex_lower(&pk));
245 return exit::OK;
246 }
247 if key_path.exists() && !force {
248 return emit_err(
249 &format!(
250 "signing key already exists: {} (pass --force to overwrite)",
251 key_path.display()
252 ),
253 exit::GENERAL_ERROR,
254 );
255 }
256
257 let (signer, secret) = match generate_p256_signer() {
258 Ok(x) => x,
259 Err(e) => return emit_err(&e, exit::GENERAL_ERROR),
260 };
261 if let Err(e) = save_raw_32(key_path, &secret) {
262 return emit_err(&format!("save key: {e}"), exit::CANTCREAT);
263 }
264 drop(secret);
265
266 let pk = signer.public_key_sec1();
267 {
268 let mut stderr = std::io::stderr().lock();
269 let _ = writeln!(stderr, "generated signing key at {}", key_path.display());
270 let _ = writeln!(stderr, "public: p256:{}", hex_lower(&pk));
271 }
272 if print_pubkey {
273 let mut stdout = std::io::stdout().lock();
274 let _ = writeln!(stdout, "p256:{}", hex_lower(&pk));
275 }
276 exit::OK
277}
278
279fn generate_secp256k1_signer() -> Result<
290 (
291 mkit_attest::signer_k256::Secp256k1Signer,
292 Zeroizing<[u8; 32]>,
293 ),
294 String,
295> {
296 for _ in 0..256 {
297 let mut buf: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
298 getrandom::fill(buf.as_mut_slice()).map_err(|e| format!("rng failed: {e}"))?;
299 if let Ok(signer) = mkit_attest::signer_k256::Secp256k1Signer::from_seed_zeroizing(&buf) {
300 return Ok((signer, buf));
301 }
302 }
304 Err("rng produced 256 consecutive invalid secp256k1 scalars (impossible in practice)".into())
305}
306
307fn generate_p256_signer()
308-> Result<(mkit_attest::signer_p256::P256Signer, Zeroizing<[u8; 32]>), String> {
309 for _ in 0..256 {
310 let mut buf: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
311 getrandom::fill(buf.as_mut_slice()).map_err(|e| format!("rng failed: {e}"))?;
312 if let Ok(signer) = mkit_attest::signer_p256::P256Signer::from_seed_zeroizing(&buf) {
313 return Ok((signer, buf));
314 }
315 }
316 Err("rng produced 256 consecutive invalid p256 scalars (impossible in practice)".into())
317}
318
319fn print_ed25519_pubkey(kp: &KeyPair) {
320 let mut stdout = std::io::stdout().lock();
321 let _ = writeln!(stdout, "ed25519:{}", hex32(&kp.public.0));
322}
323
324fn hex32(bytes: &[u8; 32]) -> String {
327 let h: mkit_core::hash::Hash = *bytes;
328 mkit_core::hash::to_hex(&h)
329}
330
331fn hex_lower(b: &[u8]) -> String {
332 const HEX: &[u8; 16] = b"0123456789abcdef";
333 let mut s = String::with_capacity(b.len() * 2);
334 for byte in b {
335 s.push(HEX[(byte >> 4) as usize] as char);
336 s.push(HEX[(byte & 0x0F) as usize] as char);
337 }
338 s
339}
340
341use super::error as emit_err;
342
343#[cfg(test)]
344mod tests {
345 use clap::Parser;
346
347 use super::KeygenOpts;
348
349 #[test]
350 fn parse_defaults() {
351 let p = KeygenOpts::try_parse_from(["mkit keygen"]).unwrap();
352 assert!(p.algorithm.is_none());
353 assert!(!p.force);
354 assert!(!p.print_pubkey);
355 }
356
357 #[test]
358 fn parse_all_flags() {
359 let p = KeygenOpts::try_parse_from([
360 "mkit keygen",
361 "--algorithm",
362 "secp256k1",
363 "--force",
364 "--print-pubkey",
365 ])
366 .unwrap();
367 assert_eq!(p.algorithm.as_deref(), Some("secp256k1"));
368 assert!(p.force);
369 assert!(p.print_pubkey);
370 }
371
372 #[test]
373 fn parse_unknown_flag_rejected() {
374 assert!(KeygenOpts::try_parse_from(["mkit keygen", "--bogus"]).is_err());
375 }
376}