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);
85 println!("✅ Identity ID: {}", did);
87 Ok(())
88 }
89
90 UtilSubcommand::PubkeyToDid { openssh_pub } => {
91 let raw = openssh_pub_to_raw_ed25519(&openssh_pub)
92 .map_err(anyhow::Error::from)
93 .context("Failed to parse OpenSSH public key")?;
94 let did = ed25519_pubkey_to_did_key(&raw);
95 println!("{}", did);
96 Ok(())
97 }
98
99 UtilSubcommand::VerifyAttestation {
100 attestation_file,
101 issuer_pubkey,
102 } => {
103 let rt = tokio::runtime::Runtime::new()?;
104 rt.block_on(handle_verify_attestation(&attestation_file, &issuer_pubkey))
105 }
106 }
107}
108
109impl crate::commands::executable::ExecutableCommand for UtilCommand {
110 fn execute(&self, _ctx: &crate::config::CliConfig) -> anyhow::Result<()> {
111 handle_util(self.clone())
112 }
113}