Skip to content

Commit

Permalink
Merge pull request iqlusioninc#58 from iqlusioninc/zaki-support-new-h…
Browse files Browse the repository at this point in the history
…andshake

Support both the Tendermint legacy and v0.33 secret connection handshake
  • Loading branch information
zmanian authored Jun 7, 2020
2 parents cc9448f + 9507aa5 commit 45a7370
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 12 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ wait-timeout = "0.2"
x25519-dalek = "0.6"
yubihsm = { version = "0.33", features = ["setup", "usb"], optional = true }
zeroize = "1"
merlin = "2"

[dev-dependencies]
tempfile = "3"
Expand Down
23 changes: 22 additions & 1 deletion src/config/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
keyring::SecretKeyEncoding,
prelude::*,
};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use signatory::{
ed25519,
encoding::{Decode, Encode},
Expand Down Expand Up @@ -35,6 +35,22 @@ pub struct ValidatorConfig {

/// Height at which to stop signing
pub max_height: Option<tendermint::block::Height>,

/// Use Tendermint v0.33 handshake
#[serde(default = "protocol_default")]
pub protocol_version: TendermintVersion,
}

/// Tendermint secure connection protocol version
#[derive(Deserialize, Serialize, Clone, Debug)]
pub enum TendermintVersion {
/// Legacy V1 SecretConnection Handshake
#[serde(rename = "legacy")]
Legacy,

/// Tendermint v0.33+ SecretConnection Handshake
#[serde(rename = "v0.33")]
V0_33,
}

impl ValidatorConfig {
Expand Down Expand Up @@ -75,3 +91,8 @@ impl ValidatorConfig {
fn reconnect_default() -> bool {
true
}

/// Default value for the `ValidatorConfig` reconnect field
fn protocol_default() -> TendermintVersion {
TendermintVersion::Legacy
}
33 changes: 27 additions & 6 deletions src/connection/secret_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use chacha20poly1305::{
aead::{generic_array::GenericArray, Aead, NewAead},
ChaCha20Poly1305,
};
use merlin::Transcript;
use prost_amino::{encoding::encode_varint, Message};
use signatory::{
ed25519,
Expand Down Expand Up @@ -57,6 +58,7 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {
mut handler: IoHandler,
local_pubkey: &PublicKey,
local_privkey: &dyn Signer<ed25519::Signature>,
v0_33_handshake: bool,
) -> Result<SecretConnection<IoHandler>, Error> {
// Generate ephemeral keys for perfect forward secrecy.
let (local_eph_pubkey, local_eph_privkey) = gen_eph_keys();
Expand All @@ -69,6 +71,8 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {
// Compute common shared secret.
let shared_secret = EphemeralSecret::diffie_hellman(local_eph_privkey, &remote_eph_pubkey);

let mut transcript = Transcript::new(b"TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH");

// Reject all-zero outputs from X25519 (i.e. from low-order points)
//
// See the following for information on potential attacks this check
Expand All @@ -82,9 +86,13 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {

// Sort by lexical order.
let local_eph_pubkey_bytes = *local_eph_pubkey.as_bytes();
let (low_eph_pubkey_bytes, _) =
let (low_eph_pubkey_bytes, high_eph_pubkey_bytes) =
sort32(local_eph_pubkey_bytes, *remote_eph_pubkey.as_bytes());

transcript.append_message(b"EPHEMERAL_LOWER_PUBLIC_KEY", &low_eph_pubkey_bytes);
transcript.append_message(b"EPHEMERAL_UPPER_PUBLIC_KEY", &high_eph_pubkey_bytes);
transcript.append_message(b"DH_SECRET", shared_secret.as_bytes());

// Check if the local ephemeral public key
// was the least, lexicographically sorted.
let loc_is_least = local_eph_pubkey_bytes == low_eph_pubkey_bytes;
Expand All @@ -105,8 +113,16 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {
),
};

let mut sc_mac: [u8; 32] = [0; 32];

transcript.challenge_bytes(b"SECRET_CONNECTION_MAC", &mut sc_mac);

// Sign the challenge bytes for authentication.
let local_signature = sign_challenge(&kdf.challenge, local_privkey)?;
let local_signature = if v0_33_handshake {
sign_challenge(&sc_mac, local_privkey)?
} else {
sign_challenge(&kdf.challenge, local_privkey)?
};

// Share (in secret) each other's pubkey & challenge signature
let auth_sig_msg = match local_pubkey {
Expand All @@ -121,10 +137,15 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {
let remote_sig =
ed25519::Signature::from_bytes(remote_signature).map_err(|_| ErrorKind::CryptoError)?;

Ed25519Verifier::from(&remote_pubkey)
.verify(&kdf.challenge, &remote_sig)
.map_err(|_| ErrorKind::CryptoError)?;

if v0_33_handshake {
Ed25519Verifier::from(&remote_pubkey)
.verify(&sc_mac, &remote_sig)
.map_err(|_| ErrorKind::CryptoError)?;
} else {
Ed25519Verifier::from(&remote_pubkey)
.verify(&kdf.challenge, &remote_sig)
.map_err(|_| ErrorKind::CryptoError)?;
}
// We've authorized.
sc.remote_pubkey = PublicKey::from(remote_pubkey);

Expand Down
3 changes: 2 additions & 1 deletion src/connection/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub fn open_secret_connection(
secret_key: &ed25519::Seed,
peer_id: &Option<node::Id>,
timeout: Option<u16>,
v0_33_handshake: bool,
) -> Result<SecretConnection<TcpStream>, Error> {
let signer = Ed25519Signer::from(secret_key);
let public_key = PublicKey::from(signer.public_key().map_err(|_| Error::from(InvalidKey))?);
Expand All @@ -33,7 +34,7 @@ pub fn open_secret_connection(
socket.set_read_timeout(Some(timeout))?;
socket.set_write_timeout(Some(timeout))?;

let connection = SecretConnection::new(socket, &public_key, &signer)?;
let connection = SecretConnection::new(socket, &public_key, &signer, v0_33_handshake)?;
let actual_peer_id = connection.remote_pubkey().peer_id();

// TODO(tarcieri): move this into `SecretConnection::new`
Expand Down
17 changes: 14 additions & 3 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use crate::{
chain::{self, state::StateErrorKind},
config::ValidatorConfig,
config::{TendermintVersion, ValidatorConfig},
connection::{tcp, unix::UnixConnection, Connection},
error::{Error, ErrorKind::*},
prelude::*,
Expand Down Expand Up @@ -39,8 +39,19 @@ impl Session {
debug!("{}: Connecting to {}...", &config.chain_id, &config.addr);

let seed = config.load_secret_key()?;
let conn =
tcp::open_secret_connection(host, *port, &seed, peer_id, config.timeout)?;
let v0_33_handshake = match config.protocol_version {
TendermintVersion::V0_33 => true,
TendermintVersion::Legacy => false,
};

let conn = tcp::open_secret_connection(
host,
*port,
&seed,
peer_id,
config.timeout,
v0_33_handshake,
)?;

info!(
"[{}@{}] connected to validator successfully",
Expand Down
4 changes: 3 additions & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ impl KmsProcess {
let socket_cp = sock.try_clone().unwrap();
let public_key = secret_connection::PublicKey::from(signer.public_key().unwrap());

KmsConnection::Tcp(SecretConnection::new(socket_cp, &public_key, &signer).unwrap())
KmsConnection::Tcp(
SecretConnection::new(socket_cp, &public_key, &signer, false).unwrap(),
)
}

KmsSocket::UNIX(ref sock) => {
Expand Down
1 change: 1 addition & 0 deletions tmkms.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ chain_id = "cosmoshub-1"
reconnect = true # true is the default
secret_key = "path/to/secret_connection.key"
# max_height = "500000"
protocol_version = "v0.33" # "legacy" is the default

## Signing provider configuration

Expand Down

0 comments on commit 45a7370

Please sign in to comment.