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