Skip to content
This repository has been archived by the owner on Jun 3, 2020. It is now read-only.

Add tmkms yubihsm keys import command #107

Merged
merged 4 commits into from
Nov 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions src/commands/yubihsm/keys/generate.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
use abscissa::Callable;
use std::process;

use super::*;
use signatory::ed25519;
use tendermint::public_keys::ConsensusKey;
use yubihsm;

/// 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;

/// Default YubiHSM2 permissions for generated keys
pub const DEFAULT_CAPABILITIES: yubihsm::Capability = yubihsm::Capability::ASYMMETRIC_SIGN_EDDSA;

/// The `yubihsm keys generate` subcommand
#[derive(Debug, Default, Options)]
pub struct GenerateCommand {
Expand Down
125 changes: 125 additions & 0 deletions src/commands/yubihsm/keys/import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
extern crate serde_json;

use super::*;

use std::fs::File;
use std::io::prelude::*;
use std::process;
use std::str;

use abscissa::Callable;
use signatory::ed25519;
use signatory::ed25519::Seed;
use subtle_encoding::base64;
use tendermint::public_keys::ConsensusKey;
use yubihsm;

use serde_json::Value;

/// The `yubihsm keys import` subcommand
#[derive(Debug, Default, Options)]
pub struct ImportCommand {
/// Path to the validator configuration file
#[options(short = "p", long = "path")]
pub path: Option<String>,

/// Type of key to import (default 'ed25519')
#[options(short = "t")]
pub key_type: Option<String>,

/// Label for imported key
#[options(short = "l", long = "label")]
pub label: Option<String>,

/// Key ID for imported key
#[options(free)]
key_id: Option<u16>,
}

impl Callable for ImportCommand {
fn call(&self) {
if self.path.is_none() {
status_err!("must provide a valid path to priv_validator.json");
process::exit(1);
}

if self.key_id.is_none() {
status_err!("must provide a unique key_id");
process::exit(1);
}

match &self.key_type {
Some(ref key_type) => if key_type != DEFAULT_KEY_TYPE {
status_err!(
"only supported key type is: ed25519 (given: \"{}\")",
key_type
);
process::exit(1);
},
None => (),
}

if let Some(path) = &self.path {
let mut f = File::open(path).unwrap_or_else(|e| {
status_err!("couldn't open validator config file {}: {}", path, e);
process::exit(1);
});

let mut contents = Vec::new();
f.read_to_end(&mut contents).unwrap_or_else(|e| {
status_err!("couldn't read validator config file {}: {}", path, e);
process::exit(1);
});
let v: Value = serde_json::from_slice(&contents).unwrap();
let s = v["priv_key"]["value"].as_str().unwrap_or_else(|| {
Copy link
Contributor

@tony-iqlusion tony-iqlusion Nov 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One alternative to consider for this sort of thing is using serde_derive to derive Deserialize for the structure of priv_validator.json.

I'm not sure it matters as this code is probably effectively "done" in that the priv_validator.json format is unlikely to change and this is literally the only use case which matters for tmkms, but I thought I'd at least throw it out there.

Copy link
Contributor Author

@liamsi liamsi Nov 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I also thought about this. But I think we can keep this for now. If we ever need other fields, we can change to using serde_derive.

status_err!("couldn't read validator private key from config: {}", path);
process::exit(1);
});

let key = base64::decode(s).unwrap_or_else(|e| {
status_err!("couldn't decode validator private key from config: {}", e);
process::exit(1);
});
Seed::from_keypair(&key).unwrap_or_else(|e| {
status_err!("invalid key in validator config: {}", e);
process::exit(1);
});
let label =
yubihsm::ObjectLabel::from(self.label.as_ref().map(|l| l.as_ref()).unwrap_or(""));

let mut hsm = yubihsm::get_hsm_client();

if let Err(e) = hsm.put_asymmetric_key(
self.key_id.unwrap(),
label,
DEFAULT_DOMAINS,
DEFAULT_CAPABILITIES,
yubihsm::AsymmetricAlg::Ed25519,
key,
) {
status_err!("couldn't import key #{}: {}", self.key_id.unwrap(), e);
process::exit(1);
}

let public_key = ed25519::PublicKey::from_bytes(
hsm.get_pubkey(self.key_id.unwrap()).unwrap_or_else(|e| {
status_err!(
"couldn't get public key for key #{}: {}",
self.key_id.unwrap(),
e
);
process::exit(1);
}),
).unwrap();

status_ok!(
"Imported",
"key #{}: {}",
self.key_id.unwrap(),
ConsensusKey::from(public_key)
);
}
}
}

impl_command!(ImportCommand);
19 changes: 18 additions & 1 deletion src/commands/yubihsm/keys/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
mod generate;
mod help;
mod import;
mod list;

use abscissa::Callable;
use yubihsm;

use self::{generate::GenerateCommand, help::HelpCommand, list::ListCommand};
use self::{
generate::GenerateCommand, help::HelpCommand, import::ImportCommand, list::ListCommand,
};

/// 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;

/// Default YubiHSM2 permissions for generated keys
pub const DEFAULT_CAPABILITIES: yubihsm::Capability = yubihsm::Capability::ASYMMETRIC_SIGN_EDDSA;

/// The `yubihsm keys` subcommand
#[derive(Debug, Options)]
Expand All @@ -15,6 +28,9 @@ pub enum KeysCommand {
#[options(help = "show help for the 'yubihsm keys' subcommand")]
Help(HelpCommand),

#[options(help = "import validator signing key for the 'yubihsm keys' subcommand")]
Import(ImportCommand),

#[options(help = "list all suitable Ed25519 keys in the HSM")]
List(ListCommand),
}
Expand All @@ -37,6 +53,7 @@ impl Callable for KeysCommand {
match self {
KeysCommand::Generate(generate) => generate.call(),
KeysCommand::Help(help) => help.call(),
KeysCommand::Import(import) => import.call(),
KeysCommand::List(list) => list.call(),
}
}
Expand Down
2 changes: 2 additions & 0 deletions tests/cli/yubihsm/keys/import.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//! Integration tests for the `yubihsm keys import` subcommand
// TODO: `yubihsm keys import` tests