use crate::{Map, chain, keyring, prelude::*};
use abscissa_core::Command;
use clap::Parser;
use cometbft::{CometbftKey, PublicKey};
use std::{path::PathBuf, process};
#[allow(deprecated)]
use k256::elliptic_curve::generic_array::GenericArray;
#[derive(Command, Debug, Default, Parser)]
pub struct ListCommand {
#[clap(short = 'c', long = "config")]
pub config: Option<PathBuf>,
}
impl Runnable for ListCommand {
fn run(&self) {
let key_formatters = load_key_formatters();
let hsm = crate::yubihsm::client();
let serial_number = hsm
.device_info()
.unwrap_or_else(|e| {
status_err!("couldn't get YubiHSM serial number: {}", e);
process::exit(1);
})
.serial_number;
let objects = hsm.list_objects(&[]).unwrap_or_else(|e| {
status_err!("couldn't list YubiHSM objects: {}", e);
process::exit(1);
});
let mut keys = objects
.iter()
.filter(|o| o.object_type == yubihsm::object::Type::AsymmetricKey)
.collect::<Vec<_>>();
keys.sort_by(|k1, k2| k1.object_id.cmp(&k2.object_id));
if keys.is_empty() {
status_err!("no keys in this YubiHSM (#{})", serial_number);
process::exit(0);
}
println!("Listing keys in YubiHSM #{serial_number}:");
for key in &keys {
display_key_info(&hsm, key, &key_formatters);
}
}
}
fn load_key_formatters() -> Map<u16, keyring::Format> {
let chain_formatters = load_chain_formatters();
let cfg = crate::yubihsm::config();
let mut map = Map::new();
for key_config in &cfg.keys {
if key_config.chain_ids.len() == 1 {
if let Some(formatter) = chain_formatters.get(&key_config.chain_ids[0]) {
if map.insert(key_config.key, formatter.clone()).is_some() {
status_err!("duplicate YubiHSM config for key: 0x{:04x}", key_config.key);
process::exit(1);
}
}
}
}
map
}
fn load_chain_formatters() -> Map<chain::Id, keyring::Format> {
let cfg = APP.config();
let mut map = Map::new();
for chain in &cfg.chain {
if map
.insert(chain.id.clone(), chain.key_format.clone())
.is_some()
{
status_err!("duplicate chain config for '{}'", chain.id);
process::exit(1);
}
}
map
}
fn display_key_info(
hsm: &yubihsm::Client,
key: &yubihsm::object::Entry,
key_formatters: &Map<u16, keyring::Format>,
) {
let key_info = hsm
.get_object_info(key.object_id, yubihsm::object::Type::AsymmetricKey)
.unwrap_or_else(|e| {
status_err!(
"couldn't get object info for asymmetric key #{}: {}",
key.object_id,
e
);
process::exit(1);
});
let public_key = hsm.get_public_key(key.object_id).unwrap_or_else(|e| {
status_err!(
"couldn't get public key for asymmetric key #{}: {}",
key.object_id,
e
);
process::exit(1);
});
let key_id = format!("- 0x{:04x}", key.object_id);
let cometbft_key = match public_key.algorithm {
yubihsm::asymmetric::Algorithm::EcK256 => {
#[allow(deprecated)]
let pk = GenericArray::from_slice(public_key.as_ref());
let compressed_pubkey = k256::EncodedPoint::from_untagged_bytes(pk).compress();
CometbftKey::AccountKey(
PublicKey::from_raw_secp256k1(compressed_pubkey.as_ref()).unwrap(),
)
}
yubihsm::asymmetric::Algorithm::Ed25519 => {
let pk = PublicKey::from_raw_ed25519(public_key.as_ref()).unwrap();
CometbftKey::ConsensusKey(pk)
}
other => {
status_attr_err!(key_id, "unsupported algorithm: {:?}", other);
return;
}
};
let key_type = match cometbft_key {
CometbftKey::AccountKey(_) => "acct",
CometbftKey::ConsensusKey(_) => "cons",
};
let key_serialized = match key_formatters.get(&key.object_id) {
Some(key_formatter) => key_formatter.serialize(cometbft_key),
None => match cometbft_key {
CometbftKey::AccountKey(k) => k.to_hex(),
CometbftKey::ConsensusKey(k) => k.to_hex(),
},
};
status_attr_ok!(key_id, "[{}] {}", key_type, key_serialized);
println!(" label: \"{}\"", &key_info.label);
}