From 8537eb38b94578e1aa1c7f88fd37f604570a5620 Mon Sep 17 00:00:00 2001 From: Roman Borschel Date: Tue, 7 May 2019 10:22:42 +0200 Subject: [PATCH] Integrate identity keys with libp2p-noise for authentication. (#1027) * Integrate use of identity keys into libp2p-noise. In order to make libp2p-noise usable with a `Swarm`, which requires a `Transport::Output` that is a pair of a peer ID and an implementation of `StreamMuxer`, it is necessary to bridge the gap between static DH public keys and public identity keys from which peer IDs are derived. Because the DH static keys and the identity keys need not be related, it is thus generally necessary that the public identity keys are exchanged as part of the Noise handshake, which the Noise protocol accomodates for through the use of handshake message payloads. The implementation of the existing (IK, IX, XX) handshake patterns is thus changed to send the public identity keys in the handshake payloads. Additionally, to facilitate the use of any identity keypair with Noise handshakes, the static DH public keys are signed using the identity keypairs and the signatures sent alongside the public identity key in handshake payloads, unless the static DH public key is "linked" to the public identity key by other means, e.g. when an Ed25519 identity keypair is (re)used as an X25519 keypair. * libp2p-noise doesn't build for wasm. Thus the development transport needs to be still constructed with secio for transport security when building for wasm. * Documentation tweaks. * For consistency, avoid wildcard enum imports. * For consistency, avoid wildcard enum imports. * Slightly simplify io::handshake::State::finish. * Simplify creation of 2-byte arrays. * Remove unnecessary cast and obey 100 char line limit. * Update protocols/noise/src/protocol.rs Co-Authored-By: romanb * Address more review comments. * Cosmetics * Cosmetics * Give authentic DH keypairs a distinct type. This has a couple of advantages: * Signing the DH public key only needs to happen once, before creating a `NoiseConfig` for an authenticated handshake. * The identity keypair only needs to be borrowed and can be dropped if it is not used further outside of the Noise protocol, since it is no longer needed during Noise handshakes. * It is explicit in the construction of a `NoiseConfig` for a handshake pattern, whether it operates with a plain `Keypair` or a keypair that is authentic w.r.t. a public identity key and future handshake patterns may be built with either. * The function signatures for constructing `NoiseConfig`s for handshake patterns are simplified and a few unnecessary trait bounds removed. * Post-merge corrections. * Add note on experimental status of libp2p-noise. --- core/src/nodes/raw_swarm.rs | 4 +- protocols/noise/Cargo.toml | 3 +- protocols/noise/make_proto.sh | 9 + protocols/noise/src/error.rs | 22 + protocols/noise/src/io.rs | 52 +- protocols/noise/src/io/handshake.rs | 586 ++++++++++++++++++ .../noise/src/io/handshake/payload.proto | 9 + protocols/noise/src/io/handshake/payload.rs | 260 ++++++++ protocols/noise/src/lib.rs | 171 +++-- protocols/noise/src/protocol.rs | 84 ++- protocols/noise/src/protocol/x25519.rs | 62 +- protocols/noise/src/rt1.rs | 187 ------ protocols/noise/src/rt15.rs | 216 ------- protocols/noise/tests/smoke.rs | 90 ++- protocols/secio/src/lib.rs | 1 + src/lib.rs | 7 +- 16 files changed, 1205 insertions(+), 558 deletions(-) create mode 100755 protocols/noise/make_proto.sh create mode 100644 protocols/noise/src/io/handshake.rs create mode 100644 protocols/noise/src/io/handshake/payload.proto create mode 100644 protocols/noise/src/io/handshake/payload.rs delete mode 100644 protocols/noise/src/rt1.rs delete mode 100644 protocols/noise/src/rt15.rs diff --git a/core/src/nodes/raw_swarm.rs b/core/src/nodes/raw_swarm.rs index a69f0511a23..f650c2c5168 100644 --- a/core/src/nodes/raw_swarm.rs +++ b/core/src/nodes/raw_swarm.rs @@ -994,8 +994,8 @@ where TConnInfo: Clone, TPeerId: AsRef<[u8]> + Send + 'static, { - // Start by polling the listeners for events, but only - // if numer of incoming connection does not exceed the limit. + // Start by polling the listeners for events, but only if the number + // of incoming connections does not exceed the limit. match self.incoming_limit { Some(x) if self.incoming_negotiated().count() >= (x as usize) => (), diff --git a/protocols/noise/Cargo.toml b/protocols/noise/Cargo.toml index f8a6370eef4..20dcf81c932 100644 --- a/protocols/noise/Cargo.toml +++ b/protocols/noise/Cargo.toml @@ -13,9 +13,10 @@ futures = "0.1" lazy_static = "1.2" libp2p-core = { version = "0.7.0", path = "../../core" } log = "0.4" +protobuf = "2.3" rand = "0.6.5" ring = { version = "0.14", features = ["use_heap"], default-features = false } -snow = { version = "0.5.1", features = ["ring-resolver"], default-features = false } +snow = { version = "0.5.2", features = ["ring-resolver"], default-features = false } tokio-io = "0.1" x25519-dalek = "0.5" zeroize = "0.5" diff --git a/protocols/noise/make_proto.sh b/protocols/noise/make_proto.sh new file mode 100755 index 00000000000..5819ebfad8b --- /dev/null +++ b/protocols/noise/make_proto.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +sudo docker run --rm -v `pwd`:/usr/code:z -w /usr/code rust /bin/bash -c " \ + apt-get update; \ + apt-get install -y protobuf-compiler; \ + cargo install --version 2.3.0 protobuf-codegen; \ + protoc --rust_out ./src/io/handshake/ ./src/io/handshake/payload.proto" + +sudo chown $USER:$USER ./src/io/handshake/payload.rs diff --git a/protocols/noise/src/error.rs b/protocols/noise/src/error.rs index e8030c662e3..8f51b0dfb50 100644 --- a/protocols/noise/src/error.rs +++ b/protocols/noise/src/error.rs @@ -18,6 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use libp2p_core::identity; use snow::SnowError; use std::{error::Error, fmt, io}; @@ -30,6 +31,10 @@ pub enum NoiseError { Noise(SnowError), /// A public key is invalid. InvalidKey, + /// A handshake payload is invalid. + InvalidPayload(protobuf::ProtobufError), + /// A signature was required and could not be created. + SigningError(identity::error::SigningError), #[doc(hidden)] __Nonexhaustive } @@ -40,6 +45,8 @@ impl fmt::Display for NoiseError { NoiseError::Io(e) => write!(f, "{}", e), NoiseError::Noise(e) => write!(f, "{}", e), NoiseError::InvalidKey => f.write_str("invalid public key"), + NoiseError::InvalidPayload(e) => write!(f, "{}", e), + NoiseError::SigningError(e) => write!(f, "{}", e), NoiseError::__Nonexhaustive => f.write_str("__Nonexhaustive") } } @@ -51,6 +58,8 @@ impl Error for NoiseError { NoiseError::Io(e) => Some(e), NoiseError::Noise(_) => None, // TODO: `SnowError` should implement `Error`. NoiseError::InvalidKey => None, + NoiseError::InvalidPayload(e) => Some(e), + NoiseError::SigningError(e) => Some(e), NoiseError::__Nonexhaustive => None } } @@ -67,3 +76,16 @@ impl From for NoiseError { NoiseError::Noise(e) } } + +impl From for NoiseError { + fn from(e: protobuf::ProtobufError) -> Self { + NoiseError::InvalidPayload(e) + } +} + +impl From for NoiseError { + fn from(e: identity::error::SigningError) -> Self { + NoiseError::SigningError(e) + } +} + diff --git a/protocols/noise/src/io.rs b/protocols/noise/src/io.rs index 5fefbe3021c..47fef9c8969 100644 --- a/protocols/noise/src/io.rs +++ b/protocols/noise/src/io.rs @@ -18,7 +18,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{NoiseError, Protocol, PublicKey}; +//! Noise protocol I/O. + +pub mod handshake; + use futures::Poll; use log::{debug, trace}; use snow; @@ -34,7 +37,7 @@ struct Buffer { inner: Box<[u8; TOTAL_BUFFER_LEN]> } -/// A mutable borrow of all byte byffers, backed by `Buffer`. +/// A mutable borrow of all byte buffers, backed by `Buffer`. struct BufferBorrow<'a> { read: &'a mut [u8], read_crypto: &'a mut [u8], @@ -52,47 +55,6 @@ impl Buffer { } } -/// A type used during the handshake phase, exchanging key material with the remote. -pub(super) struct Handshake(NoiseOutput); - -impl Handshake { - pub(super) fn new(io: T, session: snow::Session) -> Self { - Handshake(NoiseOutput::new(io, session)) - } -} - -impl Handshake { - /// Send handshake message to remote. - pub(super) fn send(&mut self) -> Poll<(), io::Error> { - Ok(self.0.poll_write(&[])?.map(|_| ())) - } - - /// Flush handshake message to remote. - pub(super) fn flush(&mut self) -> Poll<(), io::Error> { - self.0.poll_flush() - } - - /// Receive handshake message from remote. - pub(super) fn receive(&mut self) -> Poll<(), io::Error> { - Ok(self.0.poll_read(&mut [])?.map(|_| ())) - } - - /// Finish the handshake. - /// - /// This turns the noise session into transport mode and returns the remote's static - /// public key as well as the established session for further communication. - pub(super) fn finish(self) -> Result<(PublicKey, NoiseOutput), NoiseError> - where - C: Protocol - { - let s = self.0.session.into_transport_mode()?; - let p = s.get_remote_static() - .ok_or(NoiseError::InvalidKey) - .and_then(C::public_from_bytes)?; - Ok((p, NoiseOutput { session: s, .. self.0 })) - } -} - /// A noise session to a remote. /// /// `T` is the type of the underlying I/O resource. @@ -388,6 +350,8 @@ impl AsyncWrite for NoiseOutput { /// When [`io::ErrorKind::WouldBlock`] is returned, the given buffer and offset /// may have been updated (i.e. a byte may have been read) and must be preserved /// for the next invocation. +/// +/// Returns `None` if EOF has been encountered. fn read_frame_len(io: &mut R, buf: &mut [u8; 2], off: &mut usize) -> io::Result> { @@ -410,6 +374,8 @@ fn read_frame_len(io: &mut R, buf: &mut [u8; 2], off: &mut usize) /// When [`io::ErrorKind::WouldBlock`] is returned, the given offset /// may have been updated (i.e. a byte may have been written) and must /// be preserved for the next invocation. +/// +/// Returns `false` if EOF has been encountered. fn write_frame_len(io: &mut W, buf: &[u8; 2], off: &mut usize) -> io::Result { diff --git a/protocols/noise/src/io/handshake.rs b/protocols/noise/src/io/handshake.rs new file mode 100644 index 00000000000..6b993da8944 --- /dev/null +++ b/protocols/noise/src/io/handshake.rs @@ -0,0 +1,586 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Noise protocol handshake I/O. + +mod payload; + +use crate::error::NoiseError; +use crate::protocol::{Protocol, PublicKey, KeypairIdentity}; +use libp2p_core::identity; +use futures::{future, Async, Future, future::FutureResult, Poll}; +use std::{mem, io}; +use tokio_io::{io as nio, AsyncWrite, AsyncRead}; +use protobuf::Message; + +use super::NoiseOutput; + +/// A future performing a Noise handshake pattern. +pub struct Handshake( + Box as Future>::Item, + Error = as Future>::Error + > + Send> +); + +impl Future for Handshake { + type Error = NoiseError; + type Item = (RemoteIdentity, NoiseOutput); + + fn poll(&mut self) -> Poll { + self.0.poll() + } +} + +/// The identity of the remote established during a handshake. +pub enum RemoteIdentity { + /// The remote provided no identifying information. + /// + /// The identity of the remote is unknown and must be obtained through + /// a different, out-of-band channel. + Unknown, + + /// The remote provided a static DH public key. + /// + /// The static DH public key is authentic in the sense that a successful + /// handshake implies that the remote possesses a corresponding secret key. + /// + /// > **Note**: To rule out active attacks like a MITM, trust in the public key must + /// > still be established, e.g. by comparing the key against an expected or + /// > otherwise known public key. + StaticDhKey(PublicKey), + + /// The remote provided a public identity key in addition to a static DH + /// public key and the latter is authentic w.r.t. the former. + /// + /// > **Note**: To rule out active attacks like a MITM, trust in the public key must + /// > still be established, e.g. by comparing the key against an expected or + /// > otherwise known public key. + IdentityKey(identity::PublicKey) +} + +/// The options for identity exchange in an authenticated handshake. +/// +/// > **Note**: Even if a remote's public identity key is known a priori, +/// > unless the authenticity of the key is [linked](Protocol::linked) to +/// > the authenticity of a remote's static DH public key, an authenticated +/// > handshake will still send the associated signature of the provided +/// > local [`KeypairIdentity`] in order for the remote to verify that the static +/// > DH public key is authentic w.r.t. the known public identity key. +pub enum IdentityExchange { + /// Send the local public identity to the remote. + /// + /// The remote identity is unknown (i.e. expected to be received). + Mutual, + /// Send the local public identity to the remote. + /// + /// The remote identity is known. + Send { remote: identity::PublicKey }, + /// Don't send the local public identity to the remote. + /// + /// The remote identity is unknown, i.e. expected to be received. + Receive, + /// Don't send the local public identity to the remote. + /// + /// The remote identity is known, thus identities must be mutually known + /// in order for the handshake to succeed. + None { remote: identity::PublicKey } +} + +impl Handshake +where + T: AsyncRead + AsyncWrite + Send + 'static, + C: Protocol + AsRef<[u8]> + Send + 'static, +{ + /// Creates an authenticated Noise handshake for the initiator of a + /// single roundtrip (2 message) handshake pattern. + /// + /// Subject to the chosen [`IdentityExchange`], this message sequence + /// identifies the local node to the remote with the first message payload + /// (i.e. unencrypted) and expects the remote to identify itself in the + /// second message payload. + /// + /// This message sequence is suitable for authenticated 2-message Noise handshake + /// patterns where the static keys of the initiator and responder are either + /// known (i.e. appear in the pre-message pattern) or are sent with + /// the first and second message, respectively (e.g. `IK` or `IX`). + /// + /// ```raw + /// initiator -{id}-> responder + /// initiator <-{id}- responder + /// ``` + pub fn rt1_initiator( + io: T, + session: Result, + identity: KeypairIdentity, + identity_x: IdentityExchange + ) -> Handshake { + Handshake(Box::new( + State::new(io, session, identity, identity_x) + .and_then(State::send_identity) + .and_then(State::recv_identity) + .and_then(State::finish))) + } + + /// Creates an authenticated Noise handshake for the responder of a + /// single roundtrip (2 message) handshake pattern. + /// + /// Subject to the chosen [`IdentityExchange`], this message sequence expects the + /// remote to identify itself in the first message payload (i.e. unencrypted) + /// and identifies the local node to the remote in the second message payload. + /// + /// This message sequence is suitable for authenticated 2-message Noise handshake + /// patterns where the static keys of the initiator and responder are either + /// known (i.e. appear in the pre-message pattern) or are sent with the first + /// and second message, respectively (e.g. `IK` or `IX`). + /// + /// ```raw + /// initiator -{id}-> responder + /// initiator <-{id}- responder + /// ``` + pub fn rt1_responder( + io: T, + session: Result, + identity: KeypairIdentity, + identity_x: IdentityExchange, + ) -> Handshake { + Handshake(Box::new( + State::new(io, session, identity, identity_x) + .and_then(State::recv_identity) + .and_then(State::send_identity) + .and_then(State::finish))) + } + + /// Creates an authenticated Noise handshake for the initiator of a + /// 1.5-roundtrip (3 message) handshake pattern. + /// + /// Subject to the chosen [`IdentityExchange`], this message sequence expects + /// the remote to identify itself in the second message payload and + /// identifies the local node to the remote in the third message payload. + /// The first (unencrypted) message payload is always empty. + /// + /// This message sequence is suitable for authenticated 3-message Noise handshake + /// patterns where the static keys of the responder and initiator are either known + /// (i.e. appear in the pre-message pattern) or are sent with the second and third + /// message, respectively (e.g. `XX`). + /// + /// ```raw + /// initiator --{}--> responder + /// initiator <-{id}- responder + /// initiator -{id}-> responder + /// ``` + pub fn rt15_initiator( + io: T, + session: Result, + identity: KeypairIdentity, + identity_x: IdentityExchange + ) -> Handshake { + Handshake(Box::new( + State::new(io, session, identity, identity_x) + .and_then(State::send_empty) + .and_then(State::recv_identity) + .and_then(State::send_identity) + .and_then(State::finish))) + } + + /// Creates an authenticated Noise handshake for the responder of a + /// 1.5-roundtrip (3 message) handshake pattern. + /// + /// Subject to the chosen [`IdentityExchange`], this message sequence + /// identifies the local node in the second message payload and expects + /// the remote to identify itself in the third message payload. The first + /// (unencrypted) message payload is always empty. + /// + /// This message sequence is suitable for authenticated 3-message Noise handshake + /// patterns where the static keys of the responder and initiator are either known + /// (i.e. appear in the pre-message pattern) or are sent with the second and third + /// message, respectively (e.g. `XX`). + /// + /// ```raw + /// initiator --{}--> responder + /// initiator <-{id}- responder + /// initiator -{id}-> responder + /// ``` + pub fn rt15_responder( + io: T, + session: Result, + identity: KeypairIdentity, + identity_x: IdentityExchange + ) -> Handshake { + Handshake(Box::new( + State::new(io, session, identity, identity_x) + .and_then(State::recv_empty) + .and_then(State::send_identity) + .and_then(State::recv_identity) + .and_then(State::finish))) + } +} + +////////////////////////////////////////////////////////////////////////////// +// Internal + +/// Handshake state. +struct State { + /// The underlying I/O resource. + io: NoiseOutput, + /// The associated public identity of the local node's static DH keypair, + /// which can be sent to the remote as part of an authenticated handshake. + identity: KeypairIdentity, + /// The received signature over the remote's static DH public key, if any. + dh_remote_pubkey_sig: Option>, + /// The known or received public identity key of the remote, if any. + id_remote_pubkey: Option, + /// Whether to send the public identity key of the local node to the remote. + send_identity: bool, +} + +impl io::Read for State { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.io.read(buf) + } +} + +impl io::Write for State { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.io.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.io.flush() + } +} + +impl AsyncRead for State {} + +impl AsyncWrite for State { + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.io.shutdown() + } +} + +impl State { + /// Initializes the state for a new Noise handshake, using the given local + /// identity keypair and local DH static public key. The handshake messages + /// will be sent and received on the given I/O resource and using the + /// provided session for cryptographic operations according to the chosen + /// Noise handshake pattern. + fn new( + io: T, + session: Result, + identity: KeypairIdentity, + identity_x: IdentityExchange + ) -> FutureResult { + let (id_remote_pubkey, send_identity) = match identity_x { + IdentityExchange::Mutual => (None, true), + IdentityExchange::Send { remote } => (Some(remote), true), + IdentityExchange::Receive => (None, false), + IdentityExchange::None { remote } => (Some(remote), false) + }; + future::result(session.map(|s| + State { + identity, + io: NoiseOutput::new(io, s), + dh_remote_pubkey_sig: None, + id_remote_pubkey, + send_identity + } + )) + } +} + +impl State +{ + /// Finish a handshake, yielding the established remote identity and the + /// [`NoiseOutput`] for communicating on the encrypted channel. + fn finish(self) -> FutureResult<(RemoteIdentity, NoiseOutput), NoiseError> + where + C: Protocol + AsRef<[u8]> + { + let dh_remote_pubkey = match self.io.session.get_remote_static() { + None => None, + Some(k) => match C::public_from_bytes(k) { + Err(e) => return future::err(e), + Ok(dh_pk) => Some(dh_pk) + } + }; + match self.io.session.into_transport_mode() { + Err(e) => future::err(e.into()), + Ok(s) => { + let remote = match (self.id_remote_pubkey, dh_remote_pubkey) { + (_, None) => RemoteIdentity::Unknown, + (None, Some(dh_pk)) => RemoteIdentity::StaticDhKey(dh_pk), + (Some(id_pk), Some(dh_pk)) => { + if C::verify(&id_pk, &dh_pk, &self.dh_remote_pubkey_sig) { + RemoteIdentity::IdentityKey(id_pk) + } else { + return future::err(NoiseError::InvalidKey) + } + } + }; + future::ok((remote, NoiseOutput { session: s, .. self.io })) + } + } + } +} + +impl State { + /// Creates a future that sends a Noise handshake message with an empty payload. + fn send_empty(self) -> SendEmpty { + SendEmpty { state: SendState::Write(self) } + } + + /// Creates a future that expects to receive a Noise handshake message with an empty payload. + fn recv_empty(self) -> RecvEmpty { + RecvEmpty { state: RecvState::Read(self) } + } + + /// Creates a future that sends a Noise handshake message with a payload identifying + /// the local node to the remote. + fn send_identity(self) -> SendIdentity { + SendIdentity { state: SendIdentityState::Init(self) } + } + + /// Creates a future that expects to receive a Noise handshake message with a + /// payload identifying the remote. + fn recv_identity(self) -> RecvIdentity { + RecvIdentity { state: RecvIdentityState::Init(self) } + } +} + +////////////////////////////////////////////////////////////////////////////// +// Handshake Message Futures + +// RecvEmpty ----------------------------------------------------------------- + +/// A future for receiving a Noise handshake message with an empty payload. +/// +/// Obtained from [`Handshake::recv_empty`]. +struct RecvEmpty { + state: RecvState +} + +enum RecvState { + Read(State), + Done +} + +impl Future for RecvEmpty +where + T: AsyncRead +{ + type Error = NoiseError; + type Item = State; + + fn poll(&mut self) -> Poll { + match mem::replace(&mut self.state, RecvState::Done) { + RecvState::Read(mut st) => { + if !st.io.poll_read(&mut [])?.is_ready() { + self.state = RecvState::Read(st); + return Ok(Async::NotReady) + } + Ok(Async::Ready(st)) + }, + RecvState::Done => panic!("RecvEmpty polled after completion") + } + } +} + +// SendEmpty ----------------------------------------------------------------- + +/// A future for sending a Noise handshake message with an empty payload. +/// +/// Obtained from [`Handshake::send_empty`]. +struct SendEmpty { + state: SendState +} + +enum SendState { + Write(State), + Flush(State), + Done +} + +impl Future for SendEmpty +where + T: AsyncWrite +{ + type Error = NoiseError; + type Item = State; + + fn poll(&mut self) -> Poll { + loop { + match mem::replace(&mut self.state, SendState::Done) { + SendState::Write(mut st) => { + if !st.io.poll_write(&mut [])?.is_ready() { + self.state = SendState::Write(st); + return Ok(Async::NotReady) + } + self.state = SendState::Flush(st); + }, + SendState::Flush(mut st) => { + if !st.io.poll_flush()?.is_ready() { + self.state = SendState::Flush(st); + return Ok(Async::NotReady) + } + return Ok(Async::Ready(st)) + } + SendState::Done => panic!("SendEmpty polled after completion") + } + } + } +} + +// RecvIdentity -------------------------------------------------------------- + +/// A future for receiving a Noise handshake message with a payload +/// identifying the remote. +/// +/// Obtained from [`Handshake::recv_identity`]. +struct RecvIdentity { + state: RecvIdentityState +} + +enum RecvIdentityState { + Init(State), + ReadPayloadLen(nio::ReadExact, [u8; 2]>), + ReadPayload(nio::ReadExact, Vec>), + Done +} + +impl Future for RecvIdentity +where + T: AsyncRead, +{ + type Error = NoiseError; + type Item = State; + + fn poll(&mut self) -> Poll { + loop { + match mem::replace(&mut self.state, RecvIdentityState::Done) { + RecvIdentityState::Init(st) => { + self.state = RecvIdentityState::ReadPayloadLen(nio::read_exact(st, [0, 0])); + }, + RecvIdentityState::ReadPayloadLen(mut read_len) => { + if let Async::Ready((st, bytes)) = read_len.poll()? { + let len = u16::from_be_bytes(bytes) as usize; + let buf = vec![0; len]; + self.state = RecvIdentityState::ReadPayload(nio::read_exact(st, buf)); + } else { + self.state = RecvIdentityState::ReadPayloadLen(read_len); + return Ok(Async::NotReady); + } + }, + RecvIdentityState::ReadPayload(mut read_payload) => { + if let Async::Ready((mut st, bytes)) = read_payload.poll()? { + let pb: payload::Identity = protobuf::parse_from_bytes(&bytes)?; + if !pb.pubkey.is_empty() { + let pk = identity::PublicKey::from_protobuf_encoding(pb.get_pubkey()) + .map_err(|_| NoiseError::InvalidKey)?; + if let Some(ref k) = st.id_remote_pubkey { + if k != &pk { + return Err(NoiseError::InvalidKey) + } + } + st.id_remote_pubkey = Some(pk); + } + if !pb.signature.is_empty() { + st.dh_remote_pubkey_sig = Some(pb.signature) + } + return Ok(Async::Ready(st)) + } else { + self.state = RecvIdentityState::ReadPayload(read_payload); + return Ok(Async::NotReady) + } + }, + RecvIdentityState::Done => panic!("RecvIdentity polled after completion") + } + } + } +} + +// SendIdentity -------------------------------------------------------------- + +/// A future for sending a Noise handshake message with a payload +/// identifying the local node to the remote. +/// +/// Obtained from [`Handshake::send_identity`]. +struct SendIdentity { + state: SendIdentityState +} + +enum SendIdentityState { + Init(State), + WritePayloadLen(nio::WriteAll, [u8; 2]>, Vec), + WritePayload(nio::WriteAll, Vec>), + Flush(State), + Done +} + +impl Future for SendIdentity +where + T: AsyncWrite, +{ + type Error = NoiseError; + type Item = State; + + fn poll(&mut self) -> Poll { + loop { + match mem::replace(&mut self.state, SendIdentityState::Done) { + SendIdentityState::Init(st) => { + let mut pb = payload::Identity::new(); + if st.send_identity { + pb.set_pubkey(st.identity.public.clone().into_protobuf_encoding()); + } + if let Some(ref sig) = st.identity.signature { + pb.set_signature(sig.clone()); + } + let pb_bytes = pb.write_to_bytes()?; + let len = (pb_bytes.len() as u16).to_be_bytes(); + let write_len = nio::write_all(st, len); + self.state = SendIdentityState::WritePayloadLen(write_len, pb_bytes); + }, + SendIdentityState::WritePayloadLen(mut write_len, payload) => { + if let Async::Ready((st, _)) = write_len.poll()? { + self.state = SendIdentityState::WritePayload(nio::write_all(st, payload)); + } else { + self.state = SendIdentityState::WritePayloadLen(write_len, payload); + return Ok(Async::NotReady) + } + }, + SendIdentityState::WritePayload(mut write_payload) => { + if let Async::Ready((st, _)) = write_payload.poll()? { + self.state = SendIdentityState::Flush(st); + } else { + self.state = SendIdentityState::WritePayload(write_payload); + return Ok(Async::NotReady) + } + }, + SendIdentityState::Flush(mut st) => { + if !st.poll_flush()?.is_ready() { + self.state = SendIdentityState::Flush(st); + return Ok(Async::NotReady) + } + return Ok(Async::Ready(st)) + }, + SendIdentityState::Done => panic!("SendIdentity polled after completion") + } + } + } +} + diff --git a/protocols/noise/src/io/handshake/payload.proto b/protocols/noise/src/io/handshake/payload.proto new file mode 100644 index 00000000000..b6a0ca89460 --- /dev/null +++ b/protocols/noise/src/io/handshake/payload.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +// Payloads for Noise handshake messages. + +message Identity { + bytes pubkey = 1; + bytes signature = 2; +} + diff --git a/protocols/noise/src/io/handshake/payload.rs b/protocols/noise/src/io/handshake/payload.rs new file mode 100644 index 00000000000..3e9bdb342e5 --- /dev/null +++ b/protocols/noise/src/io/handshake/payload.rs @@ -0,0 +1,260 @@ +// This file is generated by rust-protobuf 2.3.0. Do not edit +// @generated + +// https://github.com/Manishearth/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy)] + +#![cfg_attr(rustfmt, rustfmt_skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unsafe_code)] +#![allow(unused_imports)] +#![allow(unused_results)] + +use protobuf::Message as Message_imported_for_functions; +use protobuf::ProtobufEnum as ProtobufEnum_imported_for_functions; + +#[derive(PartialEq,Clone,Default)] +pub struct Identity { + // message fields + pub pubkey: ::std::vec::Vec, + pub signature: ::std::vec::Vec, + // special fields + pub unknown_fields: ::protobuf::UnknownFields, + pub cached_size: ::protobuf::CachedSize, +} + +impl Identity { + pub fn new() -> Identity { + ::std::default::Default::default() + } + + // bytes pubkey = 1; + + pub fn clear_pubkey(&mut self) { + self.pubkey.clear(); + } + + // Param is passed by value, moved + pub fn set_pubkey(&mut self, v: ::std::vec::Vec) { + self.pubkey = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_pubkey(&mut self) -> &mut ::std::vec::Vec { + &mut self.pubkey + } + + // Take field + pub fn take_pubkey(&mut self) -> ::std::vec::Vec { + ::std::mem::replace(&mut self.pubkey, ::std::vec::Vec::new()) + } + + pub fn get_pubkey(&self) -> &[u8] { + &self.pubkey + } + + // bytes signature = 2; + + pub fn clear_signature(&mut self) { + self.signature.clear(); + } + + // Param is passed by value, moved + pub fn set_signature(&mut self, v: ::std::vec::Vec) { + self.signature = v; + } + + // Mutable pointer to the field. + // If field is not initialized, it is initialized with default value first. + pub fn mut_signature(&mut self) -> &mut ::std::vec::Vec { + &mut self.signature + } + + // Take field + pub fn take_signature(&mut self) -> ::std::vec::Vec { + ::std::mem::replace(&mut self.signature, ::std::vec::Vec::new()) + } + + pub fn get_signature(&self) -> &[u8] { + &self.signature + } +} + +impl ::protobuf::Message for Identity { + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream) -> ::protobuf::ProtobufResult<()> { + while !is.eof()? { + let (field_number, wire_type) = is.read_tag_unpack()?; + match field_number { + 1 => { + ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.pubkey)?; + }, + 2 => { + ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.signature)?; + }, + _ => { + ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u32 { + let mut my_size = 0; + if !self.pubkey.is_empty() { + my_size += ::protobuf::rt::bytes_size(1, &self.pubkey); + } + if !self.signature.is_empty() { + my_size += ::protobuf::rt::bytes_size(2, &self.signature); + } + my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields()); + self.cached_size.set(my_size); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream) -> ::protobuf::ProtobufResult<()> { + if !self.pubkey.is_empty() { + os.write_bytes(1, &self.pubkey)?; + } + if !self.signature.is_empty() { + os.write_bytes(2, &self.signature)?; + } + os.write_unknown_fields(self.get_unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn get_cached_size(&self) -> u32 { + self.cached_size.get() + } + + fn get_unknown_fields(&self) -> &::protobuf::UnknownFields { + &self.unknown_fields + } + + fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields { + &mut self.unknown_fields + } + + fn as_any(&self) -> &::std::any::Any { + self as &::std::any::Any + } + fn as_any_mut(&mut self) -> &mut ::std::any::Any { + self as &mut ::std::any::Any + } + fn into_any(self: Box) -> ::std::boxed::Box<::std::any::Any> { + self + } + + fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor { + Self::descriptor_static() + } + + fn new() -> Identity { + Identity::new() + } + + fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor { + static mut descriptor: ::protobuf::lazy::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::lazy::Lazy { + lock: ::protobuf::lazy::ONCE_INIT, + ptr: 0 as *const ::protobuf::reflect::MessageDescriptor, + }; + unsafe { + descriptor.get(|| { + let mut fields = ::std::vec::Vec::new(); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( + "pubkey", + |m: &Identity| { &m.pubkey }, + |m: &mut Identity| { &mut m.pubkey }, + )); + fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>( + "signature", + |m: &Identity| { &m.signature }, + |m: &mut Identity| { &mut m.signature }, + )); + ::protobuf::reflect::MessageDescriptor::new::( + "Identity", + fields, + file_descriptor_proto() + ) + }) + } + } + + fn default_instance() -> &'static Identity { + static mut instance: ::protobuf::lazy::Lazy = ::protobuf::lazy::Lazy { + lock: ::protobuf::lazy::ONCE_INIT, + ptr: 0 as *const Identity, + }; + unsafe { + instance.get(Identity::new) + } + } +} + +impl ::protobuf::Clear for Identity { + fn clear(&mut self) { + self.clear_pubkey(); + self.clear_signature(); + self.unknown_fields.clear(); + } +} + +impl ::std::fmt::Debug for Identity { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for Identity { + fn as_ref(&self) -> ::protobuf::reflect::ProtobufValueRef { + ::protobuf::reflect::ProtobufValueRef::Message(self) + } +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x1esrc/io/handshake/payload.proto\"@\n\x08Identity\x12\x16\n\x06pubke\ + y\x18\x01\x20\x01(\x0cR\x06pubkey\x12\x1c\n\tsignature\x18\x02\x20\x01(\ + \x0cR\tsignatureJ\xe0\x01\n\x06\x12\x04\0\0\x07\x01\n\x08\n\x01\x0c\x12\ + \x03\0\0\x12\n4\n\x02\x04\0\x12\x04\x04\0\x07\x012(\x20Payloads\x20for\ + \x20Noise\x20handshake\x20messages.\n\n\n\n\x03\x04\0\x01\x12\x03\x04\ + \x08\x10\n\x0b\n\x04\x04\0\x02\0\x12\x03\x05\x08\x19\n\r\n\x05\x04\0\x02\ + \0\x04\x12\x04\x05\x08\x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x05\ + \x08\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x05\x0e\x14\n\x0c\n\x05\x04\0\ + \x02\0\x03\x12\x03\x05\x17\x18\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x06\x08\ + \x1c\n\r\n\x05\x04\0\x02\x01\x04\x12\x04\x06\x08\x05\x19\n\x0c\n\x05\x04\ + \0\x02\x01\x05\x12\x03\x06\x08\r\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\ + \x06\x0e\x17\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x06\x1a\x1bb\x06proto\ + 3\ +"; + +static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy { + lock: ::protobuf::lazy::ONCE_INIT, + ptr: 0 as *const ::protobuf::descriptor::FileDescriptorProto, +}; + +fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto { + ::protobuf::parse_from_bytes(file_descriptor_proto_data).unwrap() +} + +pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + unsafe { + file_descriptor_proto_lazy.get(|| { + parse_descriptor_proto() + }) + } +} diff --git a/protocols/noise/src/lib.rs b/protocols/noise/src/lib.rs index c4e34b31b5f..6fb93a440d9 100644 --- a/protocols/noise/src/lib.rs +++ b/protocols/noise/src/lib.rs @@ -20,8 +20,11 @@ //! [Noise protocol framework][noise] support for libp2p. //! +//! > **Note**: This crate is still experimental and subject to major breaking changes +//! > both on the API and the wire protocol. +//! //! This crate provides `libp2p_core::InboundUpgrade` and `libp2p_core::OutboundUpgrade` -//! implementations for various noise handshake patterns (currently IK, IX, and XX) +//! implementations for various noise handshake patterns (currently `IK`, `IX`, and `XX`) //! over a particular choice of DH key agreement (currently only X25519). //! //! All upgrades produce as output a pair, consisting of the remote's static public key @@ -33,13 +36,15 @@ //! Example: //! //! ``` -//! use libp2p_core::Transport; +//! use libp2p_core::{identity, Transport}; //! use libp2p_tcp::TcpConfig; //! use libp2p_noise::{Keypair, X25519, NoiseConfig}; //! //! # fn main() { -//! let keys = Keypair::::new(); -//! let transport = TcpConfig::new().with_upgrade(NoiseConfig::xx(keys)); +//! let id_keys = identity::Keypair::generate_ed25519(); +//! let dh_keys = Keypair::::new().into_authentic(&id_keys).unwrap(); +//! let noise = NoiseConfig::xx(dh_keys); +//! let transport = TcpConfig::new().with_upgrade(noise); //! // ... //! # } //! ``` @@ -50,32 +55,33 @@ mod error; mod io; mod protocol; -pub mod rt1; -pub mod rt15; - pub use error::NoiseError; pub use io::NoiseOutput; -pub use protocol::{Keypair, PublicKey, Protocol, ProtocolParams, IX, IK, XX}; -pub use protocol::x25519::X25519; +pub use io::handshake::{Handshake, RemoteIdentity, IdentityExchange}; +pub use protocol::{Keypair, AuthenticKeypair, KeypairIdentity, PublicKey, SecretKey}; +pub use protocol::{Protocol, ProtocolParams, x25519::X25519, IX, IK, XX}; -use libp2p_core::{UpgradeInfo, InboundUpgrade, OutboundUpgrade, upgrade::Negotiated}; +use libp2p_core::{identity, UpgradeInfo, InboundUpgrade, OutboundUpgrade, upgrade::Negotiated}; use tokio_io::{AsyncRead, AsyncWrite}; use zeroize::Zeroize; /// The protocol upgrade configuration. #[derive(Clone)] pub struct NoiseConfig { - keys: Keypair, + dh_keys: AuthenticKeypair, params: ProtocolParams, remote: R, _marker: std::marker::PhantomData

} -impl + Zeroize> NoiseConfig { - /// Create a new `NoiseConfig` for the IX handshake pattern. - pub fn ix(keys: Keypair) -> Self { +impl NoiseConfig +where + C: Protocol + Zeroize +{ + /// Create a new `NoiseConfig` for the `IX` handshake pattern. + pub fn ix(dh_keys: AuthenticKeypair) -> Self { NoiseConfig { - keys, + dh_keys, params: C::params_ix(), remote: (), _marker: std::marker::PhantomData @@ -83,11 +89,14 @@ impl + Zeroize> NoiseConfig { } } -impl + Zeroize> NoiseConfig { - /// Create a new `NoiseConfig` for the XX handshake pattern. - pub fn xx(keys: Keypair) -> Self { +impl NoiseConfig +where + C: Protocol + Zeroize +{ + /// Create a new `NoiseConfig` for the `XX` handshake pattern. + pub fn xx(dh_keys: AuthenticKeypair) -> Self { NoiseConfig { - keys, + dh_keys, params: C::params_xx(), remote: (), _marker: std::marker::PhantomData @@ -95,11 +104,17 @@ impl + Zeroize> NoiseConfig { } } -impl + Zeroize> NoiseConfig { - /// Create a new `NoiseConfig` for the IK handshake pattern (recipient side). - pub fn ik_listener(keys: Keypair) -> Self { +impl NoiseConfig +where + C: Protocol + Zeroize +{ + /// Create a new `NoiseConfig` for the `IK` handshake pattern (recipient side). + /// + /// Since the identity of the local node is known to the remote, this configuration + /// does not transmit a static DH public key or public identity key to the remote. + pub fn ik_listener(dh_keys: AuthenticKeypair) -> Self { NoiseConfig { - keys, + dh_keys, params: C::params_ik(), remote: (), _marker: std::marker::PhantomData @@ -107,13 +122,23 @@ impl + Zeroize> NoiseConfig { } } -impl + Zeroize> NoiseConfig> { - /// Create a new `NoiseConfig` for the IK handshake pattern (initiator side). - pub fn ik_dialer(keys: Keypair, remote: PublicKey) -> Self { +impl NoiseConfig, identity::PublicKey)> +where + C: Protocol + Zeroize +{ + /// Create a new `NoiseConfig` for the `IK` handshake pattern (initiator side). + /// + /// In this configuration, the remote identity is known to the local node, + /// but the local node still needs to transmit its own public identity. + pub fn ik_dialer( + dh_keys: AuthenticKeypair, + remote_id: identity::PublicKey, + remote_dh: PublicKey + ) -> Self { NoiseConfig { - keys, + dh_keys, params: C::params_ik(), - remote, + remote: (remote_dh, remote_id), _marker: std::marker::PhantomData } } @@ -123,39 +148,43 @@ impl + Zeroize> NoiseConfig> { impl InboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite, NoiseConfig: UpgradeInfo, - C: Protocol + AsRef<[u8]> + Zeroize + T: AsyncRead + AsyncWrite + Send + 'static, + C: Protocol + AsRef<[u8]> + Zeroize + Send + 'static, { - type Output = (PublicKey, NoiseOutput>); + type Output = (RemoteIdentity, NoiseOutput>); type Error = NoiseError; - type Future = rt1::NoiseInboundFuture, C>; + type Future = Handshake, C>; fn upgrade_inbound(self, socket: Negotiated, _: Self::Info) -> Self::Future { let session = self.params.into_builder() - .local_private_key(self.keys.secret().as_ref()) + .local_private_key(self.dh_keys.secret().as_ref()) .build_responder() .map_err(NoiseError::from); - rt1::NoiseInboundFuture::new(socket, session) + Handshake::rt1_responder(socket, session, + self.dh_keys.into_identity(), + IdentityExchange::Mutual) } } impl OutboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite, NoiseConfig: UpgradeInfo, - C: Protocol + AsRef<[u8]> + Zeroize + T: AsyncRead + AsyncWrite + Send + 'static, + C: Protocol + AsRef<[u8]> + Zeroize + Send + 'static, { - type Output = (PublicKey, NoiseOutput>); + type Output = (RemoteIdentity, NoiseOutput>); type Error = NoiseError; - type Future = rt1::NoiseOutboundFuture, C>; + type Future = Handshake, C>; fn upgrade_outbound(self, socket: Negotiated, _: Self::Info) -> Self::Future { let session = self.params.into_builder() - .local_private_key(self.keys.secret().as_ref()) + .local_private_key(self.dh_keys.secret().as_ref()) .build_initiator() .map_err(NoiseError::from); - rt1::NoiseOutboundFuture::new(socket, session) + Handshake::rt1_initiator(socket, session, + self.dh_keys.into_identity(), + IdentityExchange::Mutual) } } @@ -163,39 +192,43 @@ where impl InboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite, NoiseConfig: UpgradeInfo, - C: Protocol + AsRef<[u8]> + Zeroize + T: AsyncRead + AsyncWrite + Send + 'static, + C: Protocol + AsRef<[u8]> + Zeroize + Send + 'static, { - type Output = (PublicKey, NoiseOutput>); + type Output = (RemoteIdentity, NoiseOutput>); type Error = NoiseError; - type Future = rt15::NoiseInboundFuture, C>; + type Future = Handshake, C>; fn upgrade_inbound(self, socket: Negotiated, _: Self::Info) -> Self::Future { let session = self.params.into_builder() - .local_private_key(self.keys.secret().as_ref()) + .local_private_key(self.dh_keys.secret().as_ref()) .build_responder() .map_err(NoiseError::from); - rt15::NoiseInboundFuture::new(socket, session) + Handshake::rt15_responder(socket, session, + self.dh_keys.into_identity(), + IdentityExchange::Mutual) } } impl OutboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite, NoiseConfig: UpgradeInfo, - C: Protocol + AsRef<[u8]> + Zeroize + T: AsyncRead + AsyncWrite + Send + 'static, + C: Protocol + AsRef<[u8]> + Zeroize + Send + 'static, { - type Output = (PublicKey, NoiseOutput>); + type Output = (RemoteIdentity, NoiseOutput>); type Error = NoiseError; - type Future = rt15::NoiseOutboundFuture, C>; + type Future = Handshake, C>; fn upgrade_outbound(self, socket: Negotiated, _: Self::Info) -> Self::Future { let session = self.params.into_builder() - .local_private_key(self.keys.secret().as_ref()) + .local_private_key(self.dh_keys.secret().as_ref()) .build_initiator() .map_err(NoiseError::from); - rt15::NoiseOutboundFuture::new(socket, session) + Handshake::rt15_initiator(socket, session, + self.dh_keys.into_identity(), + IdentityExchange::Mutual) } } @@ -203,40 +236,44 @@ where impl InboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite, NoiseConfig: UpgradeInfo, - C: Protocol + AsRef<[u8]> + Zeroize + T: AsyncRead + AsyncWrite + Send + 'static, + C: Protocol + AsRef<[u8]> + Zeroize + Send + 'static, { - type Output = (PublicKey, NoiseOutput>); + type Output = (RemoteIdentity, NoiseOutput>); type Error = NoiseError; - type Future = rt1::NoiseInboundFuture, C>; + type Future = Handshake, C>; fn upgrade_inbound(self, socket: Negotiated, _: Self::Info) -> Self::Future { let session = self.params.into_builder() - .local_private_key(self.keys.secret().as_ref()) + .local_private_key(self.dh_keys.secret().as_ref()) .build_responder() .map_err(NoiseError::from); - rt1::NoiseInboundFuture::new(socket, session) + Handshake::rt1_responder(socket, session, + self.dh_keys.into_identity(), + IdentityExchange::Receive) } } -impl OutboundUpgrade for NoiseConfig> +impl OutboundUpgrade for NoiseConfig, identity::PublicKey)> where - T: AsyncRead + AsyncWrite, - NoiseConfig>: UpgradeInfo, - C: Protocol + AsRef<[u8]> + Zeroize + NoiseConfig, identity::PublicKey)>: UpgradeInfo, + T: AsyncRead + AsyncWrite + Send + 'static, + C: Protocol + AsRef<[u8]> + Zeroize + Send + 'static, { - type Output = (PublicKey, NoiseOutput>); + type Output = (RemoteIdentity, NoiseOutput>); type Error = NoiseError; - type Future = rt1::NoiseOutboundFuture, C>; + type Future = Handshake, C>; fn upgrade_outbound(self, socket: Negotiated, _: Self::Info) -> Self::Future { let session = self.params.into_builder() - .local_private_key(self.keys.secret().as_ref()) - .remote_public_key(self.remote.as_ref()) + .local_private_key(self.dh_keys.secret().as_ref()) + .remote_public_key(self.remote.0.as_ref()) .build_initiator() .map_err(NoiseError::from); - rt1::NoiseOutboundFuture::new(socket, session) + Handshake::rt1_initiator(socket, session, + self.dh_keys.into_identity(), + IdentityExchange::Send { remote: self.remote.1 }) } } diff --git a/protocols/noise/src/protocol.rs b/protocols/noise/src/protocol.rs index fb4197a7770..50d7ffc6041 100644 --- a/protocols/noise/src/protocol.rs +++ b/protocols/noise/src/protocol.rs @@ -23,6 +23,7 @@ pub mod x25519; use crate::NoiseError; +use libp2p_core::identity; use rand::FromEntropy; use zeroize::Zeroize; @@ -59,15 +60,80 @@ pub trait Protocol { fn params_ix() -> ProtocolParams; /// The protocol parameters for the XX handshake pattern. fn params_xx() -> ProtocolParams; + /// Construct a DH public key from a byte slice. fn public_from_bytes(s: &[u8]) -> Result, NoiseError>; + + /// Determines whether the authenticity of the given DH static public key + /// and public identity key is linked, i.e. that proof of ownership of a + /// secret key for the static DH public key implies that the key is + /// authentic w.r.t. the given public identity key. + /// + /// The trivial case is when the keys are byte for byte identical. + #[allow(unused_variables)] + fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey) -> bool { + false + } + + /// Verifies that a given static DH public key is authentic w.r.t. a + /// given public identity key in the context of an optional signature. + /// + /// The given static DH public key is assumed to already be authentic + /// in the sense that possession of a corresponding secret key has been + /// established, as is the case at the end of a Noise handshake involving + /// static DH keys. + /// + /// If the public keys are [`linked`](Protocol::linked), verification succeeds + /// without a signature, otherwise a signature over the static DH public key + /// must be given and is verified with the public identity key, establishing + /// the authenticity of the static DH public key w.r.t. the public identity key. + fn verify(id_pk: &identity::PublicKey, dh_pk: &PublicKey, sig: &Option>) -> bool + where + C: AsRef<[u8]> + { + Self::linked(id_pk, dh_pk) + || + sig.as_ref().map_or(false, |s| id_pk.verify(dh_pk.as_ref(), s)) + } } /// DH keypair. #[derive(Clone)] pub struct Keypair { secret: SecretKey, - public: PublicKey + public: PublicKey, +} + +/// A DH keypair that is authentic w.r.t. a [`identity::PublicKey`]. +#[derive(Clone)] +pub struct AuthenticKeypair { + keypair: Keypair, + identity: KeypairIdentity +} + +impl AuthenticKeypair { + /// Extract the public [`KeypairIdentity`] from this `AuthenticKeypair`, + /// dropping the DH `Keypair`. + pub fn into_identity(self) -> KeypairIdentity { + self.identity + } +} + +impl std::ops::Deref for AuthenticKeypair { + type Target = Keypair; + + fn deref(&self) -> &Self::Target { + &self.keypair + } +} + +/// The associated public identity of a DH keypair. +#[derive(Clone)] +pub struct KeypairIdentity { + /// The public identity key. + pub public: identity::PublicKey, + /// The signature over the public DH key. + pub signature: Option> } impl Keypair { @@ -80,6 +146,22 @@ impl Keypair { pub fn secret(&self) -> &SecretKey { &self.secret } + + /// Turn this DH keypair into a [`AuthenticKeypair`], i.e. a DH keypair that + /// is authentic w.r.t. the given identity keypair, by signing the DH public key. + pub fn into_authentic(self, id_keys: &identity::Keypair) -> Result, NoiseError> + where + T: AsRef<[u8]> + { + let sig = id_keys.sign(self.public.as_ref())?; + + let identity = KeypairIdentity { + public: id_keys.public(), + signature: Some(sig) + }; + + Ok(AuthenticKeypair { keypair: self, identity }) + } } /// DH secret key. diff --git a/protocols/noise/src/protocol/x25519.rs b/protocols/noise/src/protocol/x25519.rs index 166a16bad69..99fc06e5b2d 100644 --- a/protocols/noise/src/protocol/x25519.rs +++ b/protocols/noise/src/protocol/x25519.rs @@ -24,7 +24,7 @@ use crate::{NoiseConfig, NoiseError, Protocol, ProtocolParams}; use curve25519_dalek::edwards::CompressedEdwardsY; use lazy_static::lazy_static; use libp2p_core::UpgradeInfo; -use libp2p_core::identity::ed25519; +use libp2p_core::{identity, identity::ed25519}; use rand::Rng; use ring::digest::{SHA512, digest}; use x25519_dalek::{X25519_BASEPOINT_BYTES, x25519}; @@ -92,7 +92,7 @@ impl UpgradeInfo for NoiseConfig { } } -impl UpgradeInfo for NoiseConfig> { +impl UpgradeInfo for NoiseConfig, identity::PublicKey)> { type Info = &'static [u8]; type InfoIter = std::iter::Once; @@ -123,6 +123,14 @@ impl Protocol for X25519 { pk.copy_from_slice(bytes); Ok(PublicKey(X25519(pk))) } + + fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey) -> bool { + if let identity::PublicKey::Ed25519(ref p) = id_pk { + PublicKey::from_ed25519(p).as_ref() == dh_pk.as_ref() + } else { + false + } + } } impl Keypair { @@ -143,6 +151,38 @@ impl Keypair { sk_bytes.zeroize(); Self::from(sk) } + + /// Creates an X25519 `Keypair` from an [`identity::Keypair`], if possible. + /// + /// The returned keypair will be [associated with](KeypairIdentity) the + /// given identity keypair. + /// + /// Returns `None` if the given identity keypair cannot be used as an X25519 keypair. + /// + /// > **Note**: If the identity keypair is already used in the context + /// > of other cryptographic protocols outside of Noise, e.g. for + /// > signing in the `secio` protocol, it should be preferred to + /// > create a new static X25519 keypair for use in the Noise protocol. + /// > + /// > See also: + /// > + /// > * [Noise: Static Key Reuse](http://www.noiseprotocol.org/noise.html#security-considerations) + pub fn from_identity(id_keys: &identity::Keypair) -> Option> { + match id_keys { + identity::Keypair::Ed25519(p) => { + let kp = Keypair::from(SecretKey::from_ed25519(&p.secret())); + let id = KeypairIdentity { + public: id_keys.public(), + signature: None + }; + Some(AuthenticKeypair { + keypair: kp, + identity: id + }) + } + _ => None + } + } } /// Promote a X25519 secret key into a keypair. @@ -166,15 +206,15 @@ impl PublicKey { impl SecretKey { /// Construct a X25519 secret key from a Ed25519 secret key. /// - /// *Note*: If the Ed25519 secret key is already used in the context - /// of other cryptographic protocols outside of Noise, e.g. for - /// signing in the `secio` protocol, it should be preferred to - /// create a new keypair for use in the Noise protocol. - /// - /// See also: - /// - /// [Noise: Static Key Reuse](http://www.noiseprotocol.org/noise.html#security-considerations) - /// [Ed25519 to Curve25519](https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519) + /// > **Note**: If the Ed25519 secret key is already used in the context + /// > of other cryptographic protocols outside of Noise, e.g. for + /// > signing in the `secio` protocol, it should be preferred to + /// > create a new keypair for use in the Noise protocol. + /// > + /// > See also: + /// > + /// > * [Noise: Static Key Reuse](http://www.noiseprotocol.org/noise.html#security-considerations) + /// > * [Ed25519 to Curve25519](https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519) pub fn from_ed25519(ed25519_sk: &ed25519::SecretKey) -> Self { // An Ed25519 public key is derived off the left half of the SHA512 of the // secret scalar, hence a matching conversion of the secret key must do diff --git a/protocols/noise/src/rt1.rs b/protocols/noise/src/rt1.rs deleted file mode 100644 index d44dbbf9f9c..00000000000 --- a/protocols/noise/src/rt1.rs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Futures performing 1 round trip. - -use crate::{ - Protocol, - PublicKey, - NoiseError, - io::{Handshake, NoiseOutput}, -}; -use futures::prelude::*; -use snow; -use std::mem; -use std::marker::PhantomData; -use tokio_io::{AsyncRead, AsyncWrite}; - -/// A future for inbound upgrades. -/// -/// It will perform the following steps: -/// -/// 1. receive message -/// 2. send message -pub struct NoiseInboundFuture { - state: InboundState, - _phantom: PhantomData -} - -impl NoiseInboundFuture { - pub(super) fn new(io: T, session: Result) -> Self { - match session { - Ok(s) => NoiseInboundFuture { - state: InboundState::RecvHandshake(Handshake::new(io, s)), - _phantom: PhantomData - }, - Err(e) => NoiseInboundFuture { - state: InboundState::Err(e), - _phantom: PhantomData - } - } - } -} - -enum InboundState { - RecvHandshake(Handshake), - SendHandshake(Handshake), - Flush(Handshake), - Err(NoiseError), - Done -} - -impl> Future for NoiseInboundFuture -where - T: AsyncRead + AsyncWrite -{ - type Item = (PublicKey, NoiseOutput); - type Error = NoiseError; - - fn poll(&mut self) -> Poll { - loop { - match mem::replace(&mut self.state, InboundState::Done) { - InboundState::RecvHandshake(mut io) => { - if io.receive()?.is_ready() { - self.state = InboundState::SendHandshake(io) - } else { - self.state = InboundState::RecvHandshake(io); - return Ok(Async::NotReady) - } - } - InboundState::SendHandshake(mut io) => { - if io.send()?.is_ready() { - self.state = InboundState::Flush(io) - } else { - self.state = InboundState::SendHandshake(io); - return Ok(Async::NotReady) - } - } - InboundState::Flush(mut io) => { - if io.flush()?.is_ready() { - let result = io.finish::()?; - self.state = InboundState::Done; - return Ok(Async::Ready(result)) - } else { - self.state = InboundState::Flush(io); - return Ok(Async::NotReady) - } - } - InboundState::Err(e) => return Err(e), - InboundState::Done => panic!("NoiseInboundFuture::poll called after completion") - } - } - } -} - -/// A future for outbound upgrades. -/// -/// It will perform the following steps: -/// -/// 1. send message -/// 2. receive message -pub struct NoiseOutboundFuture { - state: OutboundState, - _phantom: PhantomData -} - -impl NoiseOutboundFuture { - pub(super) fn new(io: T, session: Result) -> Self { - match session { - Ok(s) => NoiseOutboundFuture { - state: OutboundState::SendHandshake(Handshake::new(io, s)), - _phantom: PhantomData - }, - Err(e) => NoiseOutboundFuture { - state: OutboundState::Err(e), - _phantom: PhantomData - } - } - } -} - -enum OutboundState { - SendHandshake(Handshake), - Flush(Handshake), - RecvHandshake(Handshake), - Err(NoiseError), - Done -} - -impl> Future for NoiseOutboundFuture -where - T: AsyncRead + AsyncWrite -{ - type Item = (PublicKey, NoiseOutput); - type Error = NoiseError; - - fn poll(&mut self) -> Poll { - loop { - match mem::replace(&mut self.state, OutboundState::Done) { - OutboundState::SendHandshake(mut io) => { - if io.send()?.is_ready() { - self.state = OutboundState::Flush(io) - } else { - self.state = OutboundState::SendHandshake(io); - return Ok(Async::NotReady) - } - } - OutboundState::Flush(mut io) => { - if io.flush()?.is_ready() { - self.state = OutboundState::RecvHandshake(io) - } else { - self.state = OutboundState::Flush(io); - return Ok(Async::NotReady) - } - } - OutboundState::RecvHandshake(mut io) => { - if io.receive()?.is_ready() { - let result = io.finish::()?; - self.state = OutboundState::Done; - return Ok(Async::Ready(result)) - } else { - self.state = OutboundState::RecvHandshake(io); - return Ok(Async::NotReady) - } - } - OutboundState::Err(e) => return Err(e), - OutboundState::Done => panic!("NoiseOutboundFuture::poll called after completion") - } - } - } -} diff --git a/protocols/noise/src/rt15.rs b/protocols/noise/src/rt15.rs deleted file mode 100644 index f1b4f819bf5..00000000000 --- a/protocols/noise/src/rt15.rs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Futures performing 1.5 round trips. - -use crate::{ - Protocol, - PublicKey, - NoiseError, - io::{Handshake, NoiseOutput}, -}; -use futures::prelude::*; -use snow; -use std::mem; -use std::marker::PhantomData; -use tokio_io::{AsyncRead, AsyncWrite}; - -/// A future for inbound upgrades. -/// -/// It will perform the following steps: -/// -/// 1. receive message -/// 2. send message -/// 3. receive message -pub struct NoiseInboundFuture { - state: InboundState, - _phantom: PhantomData -} - -impl NoiseInboundFuture { - pub(super) fn new(io: T, session: Result) -> Self { - match session { - Ok(s) => NoiseInboundFuture { - state: InboundState::RecvHandshake1(Handshake::new(io, s)), - _phantom: PhantomData - }, - Err(e) => NoiseInboundFuture { - state: InboundState::Err(e), - _phantom: PhantomData - } - } - } -} - -enum InboundState { - RecvHandshake1(Handshake), - SendHandshake(Handshake), - Flush(Handshake), - RecvHandshake2(Handshake), - Err(NoiseError), - Done -} - -impl> Future for NoiseInboundFuture -where - T: AsyncRead + AsyncWrite -{ - type Item = (PublicKey, NoiseOutput); - type Error = NoiseError; - - fn poll(&mut self) -> Poll { - loop { - match mem::replace(&mut self.state, InboundState::Done) { - InboundState::RecvHandshake1(mut io) => { - if io.receive()?.is_ready() { - self.state = InboundState::SendHandshake(io) - } else { - self.state = InboundState::RecvHandshake1(io); - return Ok(Async::NotReady) - } - } - InboundState::SendHandshake(mut io) => { - if io.send()?.is_ready() { - self.state = InboundState::Flush(io) - } else { - self.state = InboundState::SendHandshake(io); - return Ok(Async::NotReady) - } - } - InboundState::Flush(mut io) => { - if io.flush()?.is_ready() { - self.state = InboundState::RecvHandshake2(io) - } else { - self.state = InboundState::Flush(io); - return Ok(Async::NotReady) - } - } - InboundState::RecvHandshake2(mut io) => { - if io.receive()?.is_ready() { - let result = io.finish::()?; - self.state = InboundState::Done; - return Ok(Async::Ready(result)) - } else { - self.state = InboundState::RecvHandshake2(io); - return Ok(Async::NotReady) - } - } - InboundState::Err(e) => return Err(e), - InboundState::Done => panic!("NoiseInboundFuture::poll called after completion") - } - } - } -} - -/// A future for outbound upgrades. -/// -/// It will perform the following steps: -/// -/// 1. send message -/// 2. receive message -/// 3. send message -pub struct NoiseOutboundFuture { - state: OutboundState, - _phantom: PhantomData -} - -impl NoiseOutboundFuture { - pub(super) fn new(io: T, session: Result) -> Self { - match session { - Ok(s) => NoiseOutboundFuture { - state: OutboundState::SendHandshake1(Handshake::new(io, s)), - _phantom: PhantomData - }, - Err(e) => NoiseOutboundFuture { - state: OutboundState::Err(e), - _phantom: PhantomData - } - } - } -} - -enum OutboundState { - SendHandshake1(Handshake), - Flush1(Handshake), - RecvHandshake(Handshake), - SendHandshake2(Handshake), - Flush2(Handshake), - Err(NoiseError), - Done -} - -impl> Future for NoiseOutboundFuture -where - T: AsyncRead + AsyncWrite -{ - type Item = (PublicKey, NoiseOutput); - type Error = NoiseError; - - fn poll(&mut self) -> Poll { - loop { - match mem::replace(&mut self.state, OutboundState::Done) { - OutboundState::SendHandshake1(mut io) => { - if io.send()?.is_ready() { - self.state = OutboundState::Flush1(io) - } else { - self.state = OutboundState::SendHandshake1(io); - return Ok(Async::NotReady) - } - } - OutboundState::Flush1(mut io) => { - if io.flush()?.is_ready() { - self.state = OutboundState::RecvHandshake(io) - } else { - self.state = OutboundState::Flush1(io); - return Ok(Async::NotReady) - } - } - OutboundState::RecvHandshake(mut io) => { - if io.receive()?.is_ready() { - self.state = OutboundState::SendHandshake2(io) - } else { - self.state = OutboundState::RecvHandshake(io); - return Ok(Async::NotReady) - } - } - OutboundState::SendHandshake2(mut io) => { - if io.send()?.is_ready() { - self.state = OutboundState::Flush2(io) - } else { - self.state = OutboundState::SendHandshake2(io); - return Ok(Async::NotReady) - } - } - OutboundState::Flush2(mut io) => { - if io.flush()?.is_ready() { - let result = io.finish::()?; - self.state = OutboundState::Done; - return Ok(Async::Ready(result)) - } else { - self.state = OutboundState::Flush2(io); - return Ok(Async::NotReady) - } - } - OutboundState::Err(e) => return Err(e), - OutboundState::Done => panic!("NoiseOutboundFuture::poll called after completion") - } - } - } -} diff --git a/protocols/noise/tests/smoke.rs b/protocols/noise/tests/smoke.rs index 2991ae5bc26..712840046d0 100644 --- a/protocols/noise/tests/smoke.rs +++ b/protocols/noise/tests/smoke.rs @@ -18,10 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{future::Either, prelude::*}; -use libp2p_core::{Transport, transport::ListenerEvent, upgrade::{apply_inbound, apply_outbound}}; -use libp2p_noise::{Keypair, X25519, NoiseConfig}; -use libp2p_tcp::TcpConfig; +use futures::{future::{self, Either}, prelude::*}; +use libp2p_core::identity; +use libp2p_core::upgrade::{Negotiated, apply_inbound, apply_outbound}; +use libp2p_core::transport::{Transport, ListenerEvent}; +use libp2p_noise::{Keypair, X25519, NoiseConfig, RemoteIdentity, NoiseError, NoiseOutput}; +use libp2p_tcp::{TcpConfig, TcpTransStream}; use log::info; use quickcheck::QuickCheck; use tokio::{self, io}; @@ -30,12 +32,21 @@ use tokio::{self, io}; fn xx() { let _ = env_logger::try_init(); fn prop(message: Vec) -> bool { + let server_id = identity::Keypair::generate_ed25519(); + let client_id = identity::Keypair::generate_ed25519(); - let server_keypair = Keypair::::new(); - let server_transport = TcpConfig::new().with_upgrade(NoiseConfig::xx(server_keypair)); + let server_id_public = server_id.public(); + let client_id_public = client_id.public(); - let client_keypair = Keypair::::new(); - let client_transport = TcpConfig::new().with_upgrade(NoiseConfig::xx(client_keypair)); + let server_dh = Keypair::::new().into_authentic(&server_id).unwrap(); + let server_transport = TcpConfig::new() + .with_upgrade(NoiseConfig::xx(server_dh)) + .and_then(move |out, _| expect_identity(out, &client_id_public)); + + let client_dh = Keypair::::new().into_authentic(&client_id).unwrap(); + let client_transport = TcpConfig::new() + .with_upgrade(NoiseConfig::xx(client_dh)) + .and_then(move |out, _| expect_identity(out, &server_id_public)); run(server_transport, client_transport, message); true @@ -47,12 +58,21 @@ fn xx() { fn ix() { let _ = env_logger::try_init(); fn prop(message: Vec) -> bool { + let server_id = identity::Keypair::generate_ed25519(); + let client_id = identity::Keypair::generate_ed25519(); - let server_keypair = Keypair::::new(); - let server_transport = TcpConfig::new().with_upgrade(NoiseConfig::ix(server_keypair)); + let server_id_public = server_id.public(); + let client_id_public = client_id.public(); - let client_keypair = Keypair::::new(); - let client_transport = TcpConfig::new().with_upgrade(NoiseConfig::ix(client_keypair)); + let server_dh = Keypair::::new().into_authentic(&server_id).unwrap(); + let server_transport = TcpConfig::new() + .with_upgrade(NoiseConfig::ix(server_dh)) + .and_then(move |out, _| expect_identity(out, &client_id_public)); + + let client_dh = Keypair::::new().into_authentic(&client_id).unwrap(); + let client_transport = TcpConfig::new() + .with_upgrade(NoiseConfig::ix(client_dh)) + .and_then(move |out, _| expect_identity(out, &server_id_public)); run(server_transport, client_transport, message); true @@ -64,26 +84,36 @@ fn ix() { fn ik_xx() { let _ = env_logger::try_init(); fn prop(message: Vec) -> bool { - let server_keypair = Keypair::::new(); - let server_public = server_keypair.public().clone(); + let server_id = identity::Keypair::generate_ed25519(); + let server_id_public = server_id.public(); + + let client_id = identity::Keypair::generate_ed25519(); + let client_id_public = client_id.public(); + + let server_dh = Keypair::::new().into_authentic(&server_id).unwrap(); + let server_dh_public = server_dh.public().clone(); let server_transport = TcpConfig::new() .and_then(move |output, endpoint| { if endpoint.is_listener() { - Either::A(apply_inbound(output, NoiseConfig::ik_listener(server_keypair))) + Either::A(apply_inbound(output, NoiseConfig::ik_listener(server_dh))) } else { - Either::B(apply_outbound(output, NoiseConfig::xx(server_keypair))) + Either::B(apply_outbound(output, NoiseConfig::xx(server_dh))) } - }); + }) + .and_then(move |out, _| expect_identity(out, &client_id_public)); - let client_keypair = Keypair::::new(); + let client_dh = Keypair::::new().into_authentic(&client_id).unwrap(); + let server_id_public2 = server_id_public.clone(); let client_transport = TcpConfig::new() .and_then(move |output, endpoint| { if endpoint.is_dialer() { - Either::A(apply_outbound(output, NoiseConfig::ik_dialer(client_keypair, server_public))) + Either::A(apply_outbound(output, + NoiseConfig::ik_dialer(client_dh, server_id_public, server_dh_public))) } else { - Either::B(apply_inbound(output, NoiseConfig::xx(client_keypair))) + Either::B(apply_inbound(output, NoiseConfig::xx(client_dh))) } - }); + }) + .and_then(move |out, _| expect_identity(out, &server_id_public2)); run(server_transport, client_transport, message); true @@ -91,18 +121,18 @@ fn ik_xx() { QuickCheck::new().max_tests(30).quickcheck(prop as fn(Vec) -> bool) } -fn run(server_transport: T, client_transport: U, message1: Vec) +type Output = (RemoteIdentity, NoiseOutput>); + +fn run(server_transport: T, client_transport: U, message1: Vec) where - T: Transport, + T: Transport, T::Dial: Send + 'static, T::Listener: Send + 'static, T::ListenerUpgrade: Send + 'static, - A: io::AsyncRead + io::AsyncWrite + Send + 'static, - U: Transport, + U: Transport, U::Dial: Send + 'static, U::Listener: Send + 'static, U::ListenerUpgrade: Send + 'static, - B: io::AsyncRead + io::AsyncWrite + Send + 'static { let message2 = message1.clone(); @@ -144,3 +174,11 @@ where tokio::run(future) } +fn expect_identity(output: Output, pk: &identity::PublicKey) + -> impl Future +{ + match output.0 { + RemoteIdentity::IdentityKey(ref k) if k == pk => future::ok(output), + _ => panic!("Unexpected remote identity") + } +} diff --git a/protocols/secio/src/lib.rs b/protocols/secio/src/lib.rs index ed220fec7ea..199802945e9 100644 --- a/protocols/secio/src/lib.rs +++ b/protocols/secio/src/lib.rs @@ -88,6 +88,7 @@ mod codec; mod error; mod exchange; mod handshake; +// #[allow(rust_2018_idioms)] mod structs_proto; mod stream_cipher; diff --git a/src/lib.rs b/src/lib.rs index d6ed72346af..30678a3d09e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -241,15 +241,14 @@ pub fn build_tcp_ws_secio_mplex_yamux(keypair: identity::Keypair) { CommonTransport::new() .with_upgrade(secio::SecioConfig::new(keypair)) - .and_then(move |out, endpoint| { - let peer_id = PeerId::from(out.remote_key); + .and_then(move |output, endpoint| { + let peer_id = output.remote_key.into_peer_id(); let peer_id2 = peer_id.clone(); let upgrade = core::upgrade::SelectUpgrade::new(yamux::Config::default(), mplex::MplexConfig::new()) // TODO: use a single `.map` instead of two maps .map_inbound(move |muxer| (peer_id, muxer)) .map_outbound(move |muxer| (peer_id2, muxer)); - - core::upgrade::apply(out.stream, upgrade, endpoint) + core::upgrade::apply(output.stream, upgrade, endpoint) .map(|(id, muxer)| (id, core::muxing::StreamMuxerBox::new(muxer))) }) .with_timeout(Duration::from_secs(20))