Skip to content

Commit

Permalink
secret_connection: refactor in prep for Tendermint v0.34 work
Browse files Browse the repository at this point in the history
This commit contains a set of initial refactoring to better support
multiple versions of Secret Connection.

It leans more heavily on the (newly extracted) `protocol::Version` enum,
moving much of the protocol framing there in advance so as to lean on
the enum itself to decide how messages are framed.
  • Loading branch information
tony-iqlusion committed Oct 21, 2020
1 parent 60a568d commit bf6a127
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 127 deletions.
25 changes: 4 additions & 21 deletions src/config/validator.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Validator configuration
use crate::connection::secret_connection;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tendermint::{chain, net};

/// Validator configuration
#[derive(Clone, Deserialize, Debug)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ValidatorConfig {
/// Address of the validator (`tcp://` or `unix://`)
Expand All @@ -27,29 +28,11 @@ 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,
/// Version of Secret Connection protocol to use when connecting
pub protocol_version: secret_connection::Version,
}

/// Default value for the `ValidatorConfig` reconnect field
fn reconnect_default() -> bool {
true
}

/// Default value for the `ValidatorConfig` reconnect field
fn protocol_default() -> TendermintVersion {
TendermintVersion::Legacy
}
107 changes: 19 additions & 88 deletions src/connection/secret_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,31 @@
mod amino_types;
mod kdf;
mod nonce;
mod protocol;
mod public_key;

pub use self::{amino_types::AuthSigMessage, kdf::Kdf, nonce::Nonce, public_key::PublicKey};
pub use self::{
amino_types::AuthSigMessage, kdf::Kdf, nonce::Nonce, protocol::Version, public_key::PublicKey,
};
use crate::{
error::{Error, ErrorKind},
key_utils,
};
use bytes::BufMut;
use chacha20poly1305::{
aead::{generic_array::GenericArray, AeadInPlace, NewAead},
ChaCha20Poly1305,
};
use ed25519_dalek::{self as ed25519, Signer, Verifier, SECRET_KEY_LENGTH};
use merlin::Transcript;
use prost_amino::{encoding::encode_varint, Message};
use prost_amino::Message;
use rand_core::{OsRng, RngCore};
use std::{
cmp,
convert::{TryFrom, TryInto},
io::{self, Read, Write},
marker::{Send, Sync},
path::Path,
slice,
};
use subtle::ConstantTimeEq;
use x25519_dalek::{EphemeralSecret, PublicKey as EphemeralPublic};
Expand Down Expand Up @@ -64,9 +67,9 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {

/// Performs handshake and returns a new authenticated SecretConnection.
pub fn new(
mut handler: IoHandler,
mut io_handler: IoHandler,
local_privkey: &ed25519::Keypair,
v0_33_handshake: bool,
protocol_version: Version,
) -> Result<SecretConnection<IoHandler>, Error> {
let local_pubkey = PublicKey::from(local_privkey);

Expand All @@ -76,7 +79,8 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {
// Write local ephemeral pubkey and receive one too.
// NOTE: every 32-byte string is accepted as a Curve25519 public key
// (see DJB's Curve25519 paper: http://cr.yp.to/ecdh/curve25519-20060209.pdf)
let remote_eph_pubkey = share_eph_pubkey(&mut handler, &local_eph_pubkey)?;
let remote_eph_pubkey =
share_eph_pubkey(&mut io_handler, &local_eph_pubkey, protocol_version)?;

// Compute common shared secret.
let shared_secret = EphemeralSecret::diffie_hellman(local_eph_privkey, &remote_eph_pubkey);
Expand Down Expand Up @@ -111,7 +115,7 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {

// Construct SecretConnection.
let mut sc = SecretConnection {
io_handler: handler,
io_handler,
recv_buffer: vec![],
recv_nonce: Nonce::default(),
send_nonce: Nonce::default(),
Expand All @@ -125,7 +129,7 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {
transcript.challenge_bytes(b"SECRET_CONNECTION_MAC", &mut sc_mac);

// Sign the challenge bytes for authentication.
let local_signature = if v0_33_handshake {
let local_signature = if protocol_version.has_transcript() {
sign_challenge(&sc_mac, local_privkey)?
} else {
sign_challenge(&kdf.challenge, local_privkey)?
Expand All @@ -144,7 +148,7 @@ impl<IoHandler: Read + Write + Send + Sync> SecretConnection<IoHandler> {
let remote_sig = ed25519::Signature::try_from(auth_sig_msg.sig.as_slice())
.map_err(|_| ErrorKind::CryptoError)?;

if v0_33_handshake {
if protocol_version.has_transcript() {
remote_pubkey
.verify(&sc_mac, &remote_sig)
.map_err(|_| ErrorKind::CryptoError)?;
Expand Down Expand Up @@ -322,93 +326,20 @@ fn gen_eph_keys() -> (EphemeralPublic, EphemeralSecret) {
fn share_eph_pubkey<IoHandler: Read + Write + Send + Sync>(
handler: &mut IoHandler,
local_eph_pubkey: &EphemeralPublic,
protocol_version: Version,
) -> Result<EphemeralPublic, Error> {
// Send our pubkey and receive theirs in tandem.
// TODO(ismail): on the go side this is done in parallel, here we do send and receive after
// each other. thread::spawn would require a static lifetime.
// Should still work though.
handler.write_all(&protocol_version.encode_initial_handshake(&local_eph_pubkey))?;

let mut buf = vec![0; 0];
let local_eph_pubkey_vec = local_eph_pubkey.as_bytes();
// Note: this is not regular protobuf encoding but raw length prefixed amino encoding;
// amino prefixes with the total length, and the raw bytes array's length, too:
encode_varint((local_eph_pubkey_vec.len() + 1) as u64, &mut buf); // 33
encode_varint(local_eph_pubkey_vec.len() as u64, &mut buf); // 32
buf.put_slice(local_eph_pubkey_vec); // raw bytes

// TODO(ismail): we probably do *not* need the double length delimiting here or in tendermint)
// this is the sending part of:
// https://github.com/tendermint/tendermint/blob/013b9cef642f875634c614019ab13b17570778ad/p2p/conn/secret_connection.go#L208-L238
handler.write_all(&buf)?;
let mut response_len = 0u8;
handler.read_exact(slice::from_mut(&mut response_len))?;

let mut buf = vec![0; 34];
let mut buf = vec![0; response_len as usize];
handler.read_exact(&mut buf)?;

// this is the receiving part of:
// https://github.com/tendermint/tendermint/blob/013b9cef642f875634c614019ab13b17570778ad/p2p/conn/secret_connection.go#L208-L238
let mut remote_eph_pubkey_fixed: [u8; 32] = Default::default();
if buf[0] != 33 || buf[1] != 32 {
return Err(ErrorKind::ProtocolError.into());
}
// after total length (33) and byte length (32), we expect the raw bytes
// of the pub key:
remote_eph_pubkey_fixed.copy_from_slice(&buf[2..34]);

if is_low_order_point(&remote_eph_pubkey_fixed) {
Err(ErrorKind::InvalidKey.into())
} else {
Ok(EphemeralPublic::from(remote_eph_pubkey_fixed))
}
}

/// Reject low order points listed on <https://cr.yp.to/ecdh.html>
///
/// These points contain low-order X25519 field elements. Rejecting them is
/// suggested in the "May the Fourth" paper under Section 5:
/// Software Countermeasures (see "Rejecting Known Bad Points" subsection):
///
/// <https://eprint.iacr.org/2017/806.pdf>
fn is_low_order_point(point: &[u8; 32]) -> bool {
// Note: as these are public points and do not interact with secret-key
// material in any way, this check does not need to be performed in
// constant-time.
match point {
// 0 (order 4)
&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] => {
true
}

// 1 (order 1)
[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] => {
true
}

// 325606250916557431795983626356110631294008115727848805560023387167927233504 (order 8)
&[0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00] => {
true
}

// 39382357235489614581723060781553021112529911719440698176882885853963445705823 (order 8)
&[0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57] => {
true
}

// p - 1 (order 2)
[0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f] => {
true
}

// p (order 4) */
[0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f] => {
true
}

// p + 1 (order 1)
[0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f] => {
true
}
_ => false,
}
protocol_version.decode_initial_handshake(&buf)
}

/// Return is of the form lo, hi
Expand Down
118 changes: 118 additions & 0 deletions src/connection/secret_connection/protocol.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Secret Connection Protocol: message framing and versioning
use crate::{
error::{Error, ErrorKind},
prelude::*,
};
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use x25519_dalek::PublicKey as EphemeralPublic;

/// Size of an X25519 or Ed25519 public key
const PUBLIC_KEY_SIZE: usize = 32;

/// Protocol version (based on the Tendermint version)
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
#[allow(non_camel_case_types)]
pub enum Version {
/// Pre-Tendermint v0.33
#[serde(rename = "legacy")]
Legacy,

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

impl Version {
/// Does this version of Secret Connection use a transcript hash
pub fn has_transcript(self) -> bool {
self != Version::Legacy
}

/// Encode the initial handshake message (i.e. first one sent by both peers)
pub fn encode_initial_handshake(self, eph_pubkey: &EphemeralPublic) -> Vec<u8> {
let mut buf = Vec::new();

// Note: this is not regular protobuf encoding but raw length prefixed amino encoding;
// amino prefixes with the total length, and the raw bytes array's length, too:
buf.push(PUBLIC_KEY_SIZE as u8 + 1);
buf.push(PUBLIC_KEY_SIZE as u8);
buf.extend_from_slice(eph_pubkey.as_bytes());
buf
}

/// Decode the initial handshake message
pub fn decode_initial_handshake(self, bytes: &[u8]) -> Result<EphemeralPublic, Error> {
// this is the receiving part of:
// https://github.com/tendermint/tendermint/blob/013b9cef642f875634c614019ab13b17570778ad/p2p/conn/secret_connection.go#L208-L238

// Check that the length matches what we expect and the length prefix is correct
if bytes.len() != 33 || bytes[0] != 32 {
fail!(
ErrorKind::ProtocolError,
"malformed handshake message (protocol version mismatch?)"
);
}

let eph_pubkey_bytes: [u8; 32] = bytes[1..].try_into().unwrap();
let eph_pubkey = EphemeralPublic::from(eph_pubkey_bytes);

// Reject the key if it is of low order
if is_low_order_point(&eph_pubkey) {
return Err(ErrorKind::InvalidKey.into());
}

Ok(eph_pubkey)
}
}

/// Reject low order points listed on <https://cr.yp.to/ecdh.html>
///
/// These points contain low-order X25519 field elements. Rejecting them is
/// suggested in the "May the Fourth" paper under Section 5:
/// Software Countermeasures (see "Rejecting Known Bad Points" subsection):
///
/// <https://eprint.iacr.org/2017/806.pdf>
fn is_low_order_point(point: &EphemeralPublic) -> bool {
// Note: as these are public points and do not interact with secret-key
// material in any way, this check does not need to be performed in
// constant-time.
match point.as_bytes() {
// 0 (order 4)
&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] => {
true
}

// 1 (order 1)
[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] => {
true
}

// 325606250916557431795983626356110631294008115727848805560023387167927233504 (order 8)
&[0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00] => {
true
}

// 39382357235489614581723060781553021112529911719440698176882885853963445705823 (order 8)
&[0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57] => {
true
}

// p - 1 (order 2)
[0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f] => {
true
}

// p (order 4) */
[0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f] => {
true
}

// p + 1 (order 1)
[0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f] => {
true
}
_ => false,
}
}
6 changes: 3 additions & 3 deletions src/connection/tcp.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! TCP socket connection to a validator
use super::secret_connection::{PublicKey, SecretConnection};
use super::secret_connection::{self, PublicKey, SecretConnection};
use crate::{
error::{Error, ErrorKind::*},
key_utils,
Expand All @@ -20,7 +20,7 @@ pub fn open_secret_connection(
identity_key_path: &Option<PathBuf>,
peer_id: &Option<node::Id>,
timeout: Option<u16>,
v0_33_handshake: bool,
protocol_version: secret_connection::Version,
) -> Result<SecretConnection<TcpStream>, Error> {
let identity_key_path = identity_key_path.as_ref().ok_or_else(|| {
format_err!(
Expand All @@ -39,7 +39,7 @@ pub fn open_secret_connection(
socket.set_read_timeout(Some(timeout))?;
socket.set_write_timeout(Some(timeout))?;

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

// TODO(tarcieri): move this into `SecretConnection::new`
Expand Down
Loading

0 comments on commit bf6a127

Please sign in to comment.