Skip to content

Commit

Permalink
yubihsm: add account key support to keys generate (#101)
Browse files Browse the repository at this point in the history
Adds support for generating account (secp256k1) keys inside of a
YubiHSM2, and updates README.txsigner.md to note this.
  • Loading branch information
tony-iqlusion authored Jul 2, 2020
1 parent 3b53847 commit 9f4a961
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 45 deletions.
30 changes: 18 additions & 12 deletions README.txsigner.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand All @@ -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
Expand Down
3 changes: 0 additions & 3 deletions src/commands/yubihsm/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
99 changes: 69 additions & 30 deletions src/commands/yubihsm/keys/generate.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -59,12 +59,12 @@ pub struct GenerateCommand {

/// Key ID to generate
#[options(free, help = "key ID to generate")]
key_ids: Vec<u16>,
key_ids: Vec<String>,
}

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 {}",
Expand All @@ -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;
Expand All @@ -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(
Expand Down
11 changes: 11 additions & 0 deletions src/config/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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"),
}
}
}

0 comments on commit 9f4a961

Please sign in to comment.