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

yubihsm-server: Allow CLI commands to use loopback connection #274

Merged
merged 1 commit into from
Jun 24, 2019
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
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ log = "0.4"
prost-amino = "0.4.0"
prost-amino-derive = "0.4.0"
rand_os = "0.1"
rpassword = { version = "3", optional = true }
serde = { version = "1", features = ["serde_derive"] }
serde_json = "1"
sha2 = "0.8"
Expand All @@ -57,14 +58,14 @@ features = ["amino-types", "secret-connection"]

[dev-dependencies]
tempfile = "3"
rand = "0.6" # TODO: switch to the getrandom crate
rand = "0.6"

[features]
default = []
softsign = []
ledgertm = ["signatory-ledger-tm"]
yubihsm-mock = ["yubihsm/mockhsm"]
yubihsm-server = ["yubihsm/http-server"]
yubihsm-server = ["yubihsm/http-server", "rpassword"]

# Enable integer overflow checks in release builds for security reasons
[profile.release]
Expand Down
3 changes: 3 additions & 0 deletions src/commands/yubihsm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ pub enum YubihsmCommand {

impl YubihsmCommand {
pub(super) fn config_path(&self) -> Option<&String> {
// Mark that we're invoking a `tmkms yubihsm` command
crate::yubihsm::mark_cli_command();

match self {
YubihsmCommand::Keys(keys) => keys.config_path(),
YubihsmCommand::Setup(setup) => setup.config.as_ref(),
Expand Down
26 changes: 24 additions & 2 deletions src/config/provider/yubihsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ pub struct YubihsmConfig {
/// Serial number of the YubiHSM to connect to
pub serial_number: Option<String>,

/// Listen address for `yubihsm-connector` compatible HTTP server.
/// Configuration for `yubihsm-connector` compatible HTTP server.
#[cfg(feature = "yubihsm-server")]
pub connector_laddr: Option<net::Address>,
pub connector_server: Option<ConnectorServerConfig>,
}

/// Configuration for an individual YubiHSM
Expand Down Expand Up @@ -97,3 +97,25 @@ pub struct SigningKeyConfig {
fn usb_timeout_ms_default() -> u64 {
1000
}

/// Configuration for `yubihsm-connector` compatible service
#[cfg(feature = "yubihsm-server")]
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConnectorServerConfig {
/// Listen address to run the connector service at
pub laddr: net::Address,

/// Connect to the listen address when using `tmkms yubihsm` CLI
pub cli: Option<CliConfig>,
}

/// Overrides for when using the `tmkms yubihsm` command-line interface
#[cfg(feature = "yubihsm-server")]
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CliConfig {
/// Override the auth key to use when using the CLI. This will additionally
/// prompt for a password from the terminal.
pub auth_key: Option<u16>,
}
118 changes: 101 additions & 17 deletions src/yubihsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ use lazy_static::lazy_static;
use std::thread;
use std::{
process,
sync::{Mutex, MutexGuard},
sync::{
atomic::{self, AtomicBool},
Mutex, MutexGuard,
},
};
use yubihsm::{Client, Connector};
#[cfg(feature = "yubihsm-server")]
use zeroize::Zeroizing;
#[cfg(not(feature = "yubihsm-mock"))]
use {
crate::config::provider::yubihsm::AdapterConfig,
Expand All @@ -28,6 +33,20 @@ lazy_static! {
static ref HSM_CLIENT: Mutex<Client> = Mutex::new(init_client());
}

/// Flag indicating we're inside of a `tmkms yubihsm` command
static CLI_COMMAND: AtomicBool = AtomicBool::new(false);

/// Mark that we're in a `tmkms yubihsm` command when initializing the YubiHSM
pub(crate) fn mark_cli_command() {
CLI_COMMAND.store(true, atomic::Ordering::SeqCst);
}

/// Are we running a `tmkms yubihsm` subcommand?
#[cfg(feature = "yubihsm-server")]
fn is_cli_command() -> bool {
CLI_COMMAND.load(atomic::Ordering::SeqCst)
}

/// Get the global HSM connector configured from global settings
pub fn connector() -> &'static Connector {
&HSM_CONNECTOR
Expand All @@ -42,32 +61,54 @@ pub fn client() -> MutexGuard<'static, Client> {
#[cfg(not(feature = "yubihsm-mock"))]
fn init_connector() -> Connector {
let cfg = config();
let serial_number = cfg
.serial_number
.as_ref()
.map(|serial| serial.parse::<SerialNumber>().unwrap());

let connector = match cfg.adapter {
AdapterConfig::Http { ref addr } => connect_http(addr),
AdapterConfig::Usb { timeout_ms } => {
let usb_config = UsbConfig {
serial: cfg
.serial_number
.as_ref()
.map(|serial| serial.parse::<SerialNumber>().unwrap()),
timeout_ms,
};
// Use CLI overrides if enabled and we're in a CLI context
#[cfg(feature = "yubihsm-server")]
{
if let Some(ref connector_server) = cfg.connector_server {
if connector_server.cli.is_some() && is_cli_command() {
let adapter = AdapterConfig::Http {
addr: connector_server.laddr.clone(),
};

Connector::usb(&usb_config)
return init_connector_adapter(&adapter, serial_number);
}
}
};
}

let connector = init_connector_adapter(&cfg.adapter, serial_number);

// Start connector server if configured
#[cfg(feature = "yubihsm-server")]
{
if let Some(ref addr) = cfg.connector_laddr {
run_connnector_server(http_config_for_address(addr), connector.clone())
if let Some(ref connector_server) = cfg.connector_server {
run_connnector_server(
http_config_for_address(&connector_server.laddr),
connector.clone(),
)
}
}

connector
}

/// Initialize a connector from the given adapter
#[cfg(not(feature = "yubihsm-mock"))]
fn init_connector_adapter(adapter: &AdapterConfig, serial: Option<SerialNumber>) -> Connector {
match *adapter {
AdapterConfig::Http { ref addr } => connect_http(addr),
AdapterConfig::Usb { timeout_ms } => {
let usb_config = UsbConfig { serial, timeout_ms };

Connector::usb(&usb_config)
}
}
}

/// Convert a `net::Address` to an `HttpConfig`
#[cfg(not(feature = "yubihsm-mock"))]
fn http_config_for_address(addr: &net::Address) -> HttpConfig {
Expand Down Expand Up @@ -99,14 +140,57 @@ fn init_connector() -> Connector {

/// Get a `yubihsm::Client` configured from the global configuration
fn init_client() -> Client {
let credentials = config().auth.credentials();
let (credentials, reconnect) = client_config();

Client::open(connector().clone(), credentials, true).unwrap_or_else(|e| {
Client::open(connector().clone(), credentials, reconnect).unwrap_or_else(|e| {
status_err!("error connecting to YubiHSM2: {}", e);
process::exit(1);
})
}

/// Get client configuration settings
#[cfg(not(feature = "yubihsm-server"))]
fn client_config() -> (yubihsm::Credentials, bool) {
(config().auth.credentials(), true)
}

/// Get client configuration settings, accounting for `yubihsm-server` server
/// overrides (i.e. local loopback for `tmkms yubihsm` commands)
#[cfg(feature = "yubihsm-server")]
fn client_config() -> (yubihsm::Credentials, bool) {
let cfg = config();

cfg.connector_server
.as_ref()
.and_then(|connector_server| {
connector_server.cli.as_ref().and_then(|cli| {
cli.auth_key.and_then(|auth_key_id| {
if is_cli_command() {
Some((prompt_for_auth_key_password(auth_key_id), false))
} else {
None
}
})
})
})
.unwrap_or_else(|| (cfg.auth.credentials(), true))
}

/// Prompt for the password for the given auth key and generate `yubihsm::Credentials`
#[cfg(feature = "yubihsm-server")]
fn prompt_for_auth_key_password(auth_key_id: u16) -> yubihsm::Credentials {
let prompt = format!(
"Enter password for YubiHSM2 auth key 0x{:04x}: ",
auth_key_id
);

let password = Zeroizing::new(
rpassword::read_password_from_tty(Some(&prompt)).expect("error reading password"),
);

yubihsm::Credentials::from_password(auth_key_id, password.as_bytes())
}

/// Get the YubiHSM-related configuration
pub fn config() -> YubihsmConfig {
let kms_config = app_config();
Expand Down
2 changes: 1 addition & 1 deletion tmkms.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ adapter = { type = "usb" }
auth = { key = 1, password = "password" } # Default YubiHSM admin credentials. Change ASAP!
keys = [{ chain_ids = ["cosmoshub-1"], key = 1 }]
#serial_number = "0123456789" # identify serial number of a specific YubiHSM to connect to
#connector_laddr = "tcp://127.0.0.1:12345" # run yubihsm-connector compatible server
#connector_server = { laddr = "tcp://127.0.0.1:12345", cli = { auth_key = 2 } } # run yubihsm-connector compatible server

[[providers.ledgertm]]
chain_ids = ["cosmoshub-1"]