auths_cli/commands/
utils.rs1use anyhow::{Context, Result, anyhow};
2use clap::{Parser, Subcommand};
3use ring::signature::KeyPair;
4use std::convert::TryInto;
5use std::path::PathBuf;
6
7use auths_crypto::{ed25519_pubkey_to_did_key, openssh_pub_to_raw_ed25519};
8use auths_id::identity::helpers::{encode_seed_as_pkcs8, load_keypair_from_der_or_seed};
9
10use crate::commands::device::verify_attestation::handle_verify_attestation;
11
12#[derive(Parser, Debug, Clone)]
14#[command(name = "util", about = "Utility commands for common operations.")]
15pub struct UtilCommand {
16 #[command(subcommand)]
17 pub command: UtilSubcommand,
18}
19
20#[derive(Subcommand, Debug, Clone)]
22pub enum UtilSubcommand {
23 DeriveDid {
25 #[arg(
26 long,
27 help = "The 32-byte Ed25519 seed encoded as a 64-character hex string."
28 )]
29 seed_hex: String,
30 },
31
32 #[command(name = "pubkey-to-did")]
34 PubkeyToDid {
35 #[arg(help = "OpenSSH Ed25519 public key line.")]
37 openssh_pub: String,
38 },
39
40 VerifyAttestation {
42 #[arg(long, value_parser, value_name = "FILE_PATH")]
44 attestation_file: PathBuf,
45
46 #[arg(long, value_name = "HEX_PUBKEY")]
48 issuer_pubkey: String,
49 },
50}
51
52pub fn handle_util(cmd: UtilCommand) -> Result<()> {
53 match cmd.command {
54 UtilSubcommand::DeriveDid { seed_hex } => {
55 let bytes =
57 hex::decode(seed_hex.trim()).context("Failed to decode seed from hex string")?;
58 if bytes.len() != 32 {
60 return Err(anyhow!(
61 "Seed must be exactly 32 bytes (64 hex characters), got {} bytes",
62 bytes.len()
63 ));
64 }
65
66 let seed: [u8; 32] = bytes
68 .try_into()
69 .expect("Length already checked, conversion should succeed"); let pkcs8_der =
73 encode_seed_as_pkcs8(&seed).context("Failed to encode seed as PKCS#8")?;
74 let keypair = load_keypair_from_der_or_seed(&pkcs8_der)
75 .context("Failed to construct Ed25519 keypair from seed")?;
76
77 let pubkey_bytes = keypair.public_key().as_ref();
79 let pubkey_fixed: [u8; 32] = pubkey_bytes
80 .try_into()
81 .context("Failed to convert public key to fixed array")?; let did = ed25519_pubkey_to_did_key(&pubkey_fixed);
84 if crate::ux::format::is_json_mode() {
85 crate::ux::format::JsonResponse::success(
86 "derive-did",
87 &serde_json::json!({ "did": did }),
88 )
89 .print()?;
90 } else {
91 println!("✅ Identity ID: {}", did);
92 }
93 Ok(())
94 }
95
96 UtilSubcommand::PubkeyToDid { openssh_pub } => {
97 let raw = openssh_pub_to_raw_ed25519(&openssh_pub)
98 .map_err(anyhow::Error::from)
99 .context("Failed to parse OpenSSH public key")?;
100 let did = ed25519_pubkey_to_did_key(&raw);
101 if crate::ux::format::is_json_mode() {
102 crate::ux::format::JsonResponse::success(
103 "pubkey-to-did",
104 &serde_json::json!({ "did": did }),
105 )
106 .print()?;
107 } else {
108 println!("{}", did);
109 }
110 Ok(())
111 }
112
113 UtilSubcommand::VerifyAttestation {
114 attestation_file,
115 issuer_pubkey,
116 } => {
117 let rt = tokio::runtime::Runtime::new()?;
118 rt.block_on(handle_verify_attestation(&attestation_file, &issuer_pubkey))
119 }
120 }
121}
122
123impl crate::commands::executable::ExecutableCommand for UtilCommand {
124 fn execute(&self, _ctx: &crate::config::CliConfig) -> anyhow::Result<()> {
125 handle_util(self.clone())
126 }
127}