Skip to content

Commit

Permalink
Test noise-handshake with converted keys (exonum#722) [ECR-1483]
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavel Mukhanov authored and stanislav-tkach committed Jun 8, 2018
1 parent 60f497d commit a14d3be
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 4 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ The project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)
- `storage::base_index` module has become private along with `BaseIndex` and
`BaseIndexIter` types. (#723)

### New features

#### exonum

- `exonum::crypto::x25519` module to convert from Ed25519 keys to X25519 keys
has been introduced.

### Internal improvements

#### exonum
Expand Down
1 change: 1 addition & 0 deletions exonum-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ deserializer
deserializes
deserializing
DESTDIR
diffie
doctests
emsp
Exonum
Expand Down
4 changes: 2 additions & 2 deletions exonum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ os_info = "1.0.1"
chrono = { version = "=0.4.2", features = ["serde"] }
bodyparser = "=0.8.0"
uuid = { version = "=0.6.5", features = ["serde"] }
snow = "=0.1.9"
snow = "=0.1.12"
rust_decimal = "=0.9.0"

exonum_rocksdb = "0.7.2"
exonum_sodiumoxide = "0.0.17"
exonum_sodiumoxide = "0.0.18"
exonum_profiler = { path = "../3rdparty/profiler", version = "0.1.2" }
exonum_flamer = { path = "../3rdparty/flamer", version = "0.1.6", optional = true }

Expand Down
2 changes: 2 additions & 0 deletions exonum/src/crypto.rs → exonum/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ use encoding::{serialize::{encode_hex, FromHex, FromHexError, ToHex},
Offset};
use helpers::Round;

pub mod x25519;

/// The size to crop the string in debug messages.
const BYTES_IN_DEBUG: usize = 4;

Expand Down
106 changes: 106 additions & 0 deletions exonum/src/crypto/x25519.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2018 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! X25519 related types and methods used in Diffie-Hellman key exchange.
use sodiumoxide::crypto::sign::ed25519::{convert_ed_keypair_to_curve25519,
PublicKey as PublicKeySodium,
SecretKey as SecretKeySodium};

use std::fmt;

use crypto::{self, PUBLIC_KEY_LENGTH};

const SECRET_KEY_LENGTH: usize = 32;
const BYTES_IN_DEBUG: usize = 4;

/// Converts Ed25519 keys to Curve25519.
///
/// Ed25519 keys used for signatures can be converted to Curve25519 and used for
/// Diffie-Hellman key exchange.
///
/// # Examples
///
/// The example below generates a pair of secret and public Ed25519 keys and
/// converts it to pair of Curve25519 keys.
///
/// ```
/// use exonum::crypto;
/// # crypto::init();
///
/// let (pk, sk) = crypto::gen_keypair();
/// let (public_key, secret_key) = crypto::x25519::into_x25519_keypair(pk, sk).unwrap();
/// ```
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
pub fn into_x25519_keypair(
pk: crypto::PublicKey,
sk: crypto::SecretKey,
) -> Option<(PublicKey, SecretKey)> {
let pk_sod = PublicKeySodium::from_slice(&pk[..])?;
let sk_sod = SecretKeySodium::from_slice(&sk[..])?;

let (pk, sk) = convert_ed_keypair_to_curve25519(pk_sod, sk_sod);

let mut secret_key = [0; SECRET_KEY_LENGTH];
secret_key.clone_from_slice(&sk.0[..SECRET_KEY_LENGTH]);

Some((PublicKey(pk.0), SecretKey(secret_key)))
}

macro_rules! implement_x25519_type {
($(#[$attr:meta])* struct $name:ident, $size:expr) => (
#[derive(Clone, Copy)]
$(#[$attr])*
pub struct $name([u8; $size]);

impl AsRef<[u8]> for $name {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}

impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, stringify!($name))?;
write!(f, "(")?;
for i in &self.as_ref()[0..BYTES_IN_DEBUG] {
write!(f, "{:02X}", i)?
}
write!(f, ")")
}
}
)
}

implement_x25519_type! {
/// Curve25519 public key used in key exchange.
/// This key cannot be directly generated and can only be converted
/// from Ed25519 `PublicKey`.
///
/// See: [`into_x25519_keypair()`][1]
///
/// [1]: fn.into_x25519_keypair.html
struct PublicKey, PUBLIC_KEY_LENGTH
}

implement_x25519_type! {
/// Curve25519 secret key used in key exchange.
/// This key cannot be directly generated and can only be converted
/// from Ed25519 `SecretKey`.
///
/// See: [`into_x25519_keypair()`][1]
///
/// [1]: fn.into_x25519_keypair.html
struct SecretKey, SECRET_KEY_LENGTH
}
3 changes: 3 additions & 0 deletions exonum/src/events/noise/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ use events::{codec::MessagesCodec,

pub mod wrapper;

#[cfg(test)]
mod tests;

type HandshakeResult = Box<Future<Item = Framed<TcpStream, MessagesCodec>, Error = io::Error>>;

#[derive(Debug, Clone)]
Expand Down
86 changes: 86 additions & 0 deletions exonum/src/events/noise/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2018 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use snow::types::Dh;
use snow::wrappers::crypto_wrapper::Dh25519;
use snow::NoiseBuilder;

use crypto::PUBLIC_KEY_LENGTH;
use crypto::{gen_keypair, x25519::into_x25519_keypair};

#[test]
fn test_convert_ed_to_curve_dh() {
// Generate Ed25519 keys for initiator and responder.
let (public_key_i, secret_key_i) = gen_keypair();
let (public_key_r, secret_key_r) = gen_keypair();

// Convert to Curve25519 keys.
let (public_key_i, secret_key_i) = into_x25519_keypair(public_key_i, secret_key_i).unwrap();
let (public_key_r, secret_key_r) = into_x25519_keypair(public_key_r, secret_key_r).unwrap();

// Do DH.
let mut keypair_i: Dh25519 = Default::default();
keypair_i.set(secret_key_i.as_ref());
let mut output_i = [0u8; PUBLIC_KEY_LENGTH];
keypair_i.dh(public_key_r.as_ref(), &mut output_i);

let mut keypair_r: Dh25519 = Default::default();
keypair_r.set(secret_key_r.as_ref());
let mut output_r = [0u8; PUBLIC_KEY_LENGTH];
keypair_r.dh(public_key_i.as_ref(), &mut output_r);

assert_eq!(output_i, output_r);
}

#[test]
fn test_converted_keys_handshake() {
const MSG_SIZE: usize = 4096;
static PATTERN: &'static str = "Noise_XK_25519_ChaChaPoly_SHA256";

// Handshake initiator keypair.
let (public_key_i, secret_key_i) = gen_keypair();
// Handshake responder keypair.
let (public_key_r, secret_key_r) = gen_keypair();

// Convert to Curve25519 keys.
let (_, secret_key_i) = into_x25519_keypair(public_key_i, secret_key_i).unwrap();
let (public_key_r, secret_key_r) = into_x25519_keypair(public_key_r, secret_key_r).unwrap();

let mut h_i = NoiseBuilder::new(PATTERN.parse().unwrap())
.local_private_key(secret_key_i.as_ref())
.remote_public_key(public_key_r.as_ref())
.build_initiator()
.expect("Unable to create initiator");

let mut h_r = NoiseBuilder::new(PATTERN.parse().unwrap())
.local_private_key(secret_key_r.as_ref())
.build_responder()
.expect("Unable to create responder");

let mut buffer_msg = [0u8; MSG_SIZE * 2];
let mut buffer_out = [0u8; MSG_SIZE * 2];

let len = h_i.write_message(&[0u8; 0], &mut buffer_msg).unwrap();
h_r.read_message(&buffer_msg[..len], &mut buffer_out)
.unwrap();
let len = h_r.write_message(&[0u8; 0], &mut buffer_msg).unwrap();
h_i.read_message(&buffer_msg[..len], &mut buffer_out)
.unwrap();
let len = h_i.write_message(&[0u8; 0], &mut buffer_msg).unwrap();
h_r.read_message(&buffer_msg[..len], &mut buffer_out)
.unwrap();

h_r.into_transport_mode()
.expect("Unable to transition session into transport mode");
}
4 changes: 2 additions & 2 deletions exonum/src/events/noise/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,15 @@ impl NoiseWrapper {
let mut buf = vec![0u8; len];
let len = self.session
.read_message(input, &mut buf)
.map_err(|e| NoiseError::new(format!("Error while reading noise message: {:?}", e.0)))?;
.map_err(|e| NoiseError::new(format!("Error while reading noise message: {:?}", e)))?;
Ok((len, buf))
}

fn write(&mut self, msg: &[u8]) -> Result<(usize, Vec<u8>), NoiseError> {
let mut buf = vec![0u8; NOISE_MAX_MESSAGE_LENGTH];
let len = self.session
.write_message(msg, &mut buf)
.map_err(|e| NoiseError::new(format!("Error while writing noise message: {:?}", e.0)))?;
.map_err(|e| NoiseError::new(format!("Error while writing noise message: {:?}", e)))?;
Ok((len, buf))
}

Expand Down

0 comments on commit a14d3be

Please sign in to comment.