From 68204c0fb61d4eec3e1ad40f956c44d773cdd16a Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 2 Jul 2020 09:14:42 -0700 Subject: [PATCH] yubihsm: add account key support to `keys generate` Adds support for generating account (secp256k1) keys inside of a YubiHSM2, and updates README.txsigner.md to note this. --- README.txsigner.md | 30 ++++---- src/commands/yubihsm/keys.rs | 3 - src/commands/yubihsm/keys/generate.rs | 99 +++++++++++++++++++-------- src/config/provider.rs | 11 +++ 4 files changed, 98 insertions(+), 45 deletions(-) diff --git a/README.txsigner.md b/README.txsigner.md index 0908a64a..fb32a693 100644 --- a/README.txsigner.md +++ b/README.txsigner.md @@ -177,20 +177,11 @@ the following arguments: ``` $ tmkms yubihsm keys generate -t account -l "columbus-3 oracle signer" -b columbus-oracle-key.enc 0x123 + Generated account (secp256k1) key 0x0123 ``` -If the operation succeeded, you should now see the key listed when you run -`tmkms yubihsm keys list`, flagged as being an `[acct]` key: - -``` -$ tmkms yubihsm keys list -Listing keys in YubiHSM #0001234567: -- 0x0001: [cons] cosmosvalconspub1zcjduepqpxg30wtw7tlt750lhl3fdjfex6eq7tj3gfer3ugrzahd27srflhqv6ep6j -- 0x0123: [acct] terra13tdvxsauagu33glu74u93mdka7ahvm5a6yfr76 -``` - -Finally, add the generated key to your [`tmkms.toml`] config file's -`[[providers.yubihsm]]` section (under `keys`): +If that succeeded, you can now add the generated key to your [`tmkms.toml`] +config file's `[[providers.yubihsm]]` section (under `keys`): ```toml [[providers.yubihsm]] @@ -205,6 +196,21 @@ keys = [ This will register the newly generated key as an account key on the provided chain IDs (i.e. `columbus-3` in this case) +Finally, confirm you see the key listed when you run +`tmkms yubihsm keys list`, flagged as being an `[acct]` key: + +``` +$ tmkms yubihsm keys list +Listing keys in YubiHSM #0001234567: +- 0x0001: [cons] cosmosvalconspub1zcjduepqpxg30wtw7tlt750lhl3fdjfex6eq7tj3gfer3ugrzahd27srflhqv6ep6j +- 0x0123: [acct] terra13tdvxsauagu33glu74u93mdka7ahvm5a6yfr76 +``` + +If the newly generated account key is properly configured for the desired chain +the `list` command should display its Bech32-formatted account address. Make a +note of this as you'll need to configure it as `[[tx_signer.account_address]]` +(see below). + ### `softsign`: creating account keys To create a new "soft" account key (randomly generated using the host OS's diff --git a/src/commands/yubihsm/keys.rs b/src/commands/yubihsm/keys.rs index 7ae0c30f..8aebd849 100644 --- a/src/commands/yubihsm/keys.rs +++ b/src/commands/yubihsm/keys.rs @@ -11,9 +11,6 @@ use self::{ use abscissa_core::{Command, Help, Options, Runnable}; use std::path::PathBuf; -/// Default key type to generate -pub const DEFAULT_KEY_TYPE: &str = "ed25519"; - /// Default YubiHSM2 domain (internal partitioning) pub const DEFAULT_DOMAINS: yubihsm::Domain = yubihsm::Domain::DOM1; diff --git a/src/commands/yubihsm/keys/generate.rs b/src/commands/yubihsm/keys/generate.rs index a87eaa39..d3978f1c 100644 --- a/src/commands/yubihsm/keys/generate.rs +++ b/src/commands/yubihsm/keys/generate.rs @@ -1,7 +1,7 @@ //! Generate a new key within the YubiHSM2 use super::*; -use crate::prelude::*; +use crate::{config::provider::KeyType, prelude::*}; use abscissa_core::{Command, Options, Runnable}; use chrono::{SecondsFormat, Utc}; use std::{ @@ -59,12 +59,12 @@ pub struct GenerateCommand { /// Key ID to generate #[options(free, help = "key ID to generate")] - key_ids: Vec, + key_ids: Vec, } -impl Runnable for GenerateCommand { - /// Generate an Ed25519 signing key inside a YubiHSM2 device - fn run(&self) { +impl GenerateCommand { + /// Parse the key ID provided in the arguments + pub fn parse_key_id(&self) -> u16 { if self.key_ids.len() != 1 { status_err!( "expected exactly 1 key ID to generate, got {}", @@ -73,17 +73,37 @@ impl Runnable for GenerateCommand { process::exit(1); } - let key_id = self.key_ids[0]; + let key_id_str = &self.key_ids[0]; + + if key_id_str.starts_with("0x") { + u16::from_str_radix(&key_id_str[2..], 16).ok() + } else { + key_id_str.parse().ok() + } + .unwrap_or_else(|| { + status_err!("couldn't parse key ID: {}", key_id_str); + process::exit(1); + }) + } - if let Some(key_type) = self.key_type.as_ref() { - if key_type != DEFAULT_KEY_TYPE { - status_err!( - "only supported key type is: ed25519 (given: \"{}\")", - key_type - ); + /// Parse the key type provided in the arguments + pub fn parse_key_type(&self) -> KeyType { + match self.key_type.as_ref().map(AsRef::as_ref) { + Some("account") => KeyType::Account, + Some("consensus") | None => KeyType::Consensus, // default + Some(other) => { + status_err!("invalid key type: {}", other); process::exit(1); } } + } +} + +impl Runnable for GenerateCommand { + /// Generate an Ed25519 signing key inside a YubiHSM2 device + fn run(&self) { + let key_id = self.parse_key_id(); + let key_type = self.parse_key_type(); let hsm = crate::yubihsm::client(); let mut capabilities = DEFAULT_CAPABILITIES; @@ -99,39 +119,58 @@ impl Runnable for GenerateCommand { Some(ref l) => l.to_owned(), None => match self.bech32_prefix { Some(ref prefix) => format!("{}:{}", prefix, timestamp), - None => format!("ed25519:{}", timestamp), + None => format!("{}:{}", key_type, timestamp), }, } .as_ref(), ); + let algorithm = match key_type { + KeyType::Account => yubihsm::asymmetric::Algorithm::EcK256, + KeyType::Consensus => yubihsm::asymmetric::Algorithm::Ed25519, + }; + if let Err(e) = hsm.generate_asymmetric_key( key_id, label, DEFAULT_DOMAINS, // TODO(tarcieri): customize domains capabilities, - yubihsm::asymmetric::Algorithm::Ed25519, + algorithm, ) { status_err!("couldn't generate key #{}: {}", key_id, e); process::exit(1); } - let public_key = PublicKey::from_raw_ed25519( - hsm.get_public_key(key_id) - .unwrap_or_else(|e| { - status_err!("couldn't get public key for key #{}: {}", key_id, e); - process::exit(1); - }) - .as_ref(), - ) - .unwrap(); - - let public_key_string = match self.bech32_prefix { - Some(ref prefix) => public_key.to_bech32(prefix), - None => public_key.to_hex(), - }; - - status_ok!("Generated", "key 0x{:04x}: {}", key_id, public_key_string); + match key_type { + KeyType::Account => { + // TODO(tarcieri): generate and show account ID (fingerprint) + status_ok!("Generated", "account (secp256k1) key 0x{:04x}", key_id) + } + KeyType::Consensus => { + // TODO(tarcieri): use KeyFormat (when available) to format Bech32 + let public_key = PublicKey::from_raw_ed25519( + hsm.get_public_key(key_id) + .unwrap_or_else(|e| { + status_err!("couldn't get public key for key #{}: {}", key_id, e); + process::exit(1); + }) + .as_ref(), + ) + .unwrap(); + + let public_key_string = match self.bech32_prefix { + Some(ref prefix) => public_key.to_bech32(prefix), + None => public_key.to_hex(), + }; + + status_ok!( + "Generated", + "consensus (ed25519) key 0x{:04x}: {}", + key_id, + public_key_string + ) + } + } if let Some(ref backup_file) = self.backup_file { create_encrypted_backup( diff --git a/src/config/provider.rs b/src/config/provider.rs index eba7cbe8..db285686 100644 --- a/src/config/provider.rs +++ b/src/config/provider.rs @@ -13,7 +13,9 @@ use self::ledgertm::LedgerTendermintConfig; use self::softsign::SoftsignConfig; #[cfg(feature = "yubihsm")] use self::yubihsm::YubihsmConfig; + use serde::Deserialize; +use std::fmt; /// Provider configuration #[derive(Default, Deserialize, Debug)] @@ -54,3 +56,12 @@ impl Default for KeyType { KeyType::Consensus } } + +impl fmt::Display for KeyType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + KeyType::Account => f.write_str("account"), + KeyType::Consensus => f.write_str("consensus"), + } + } +}