diff --git a/core/src/nodes/raw_swarm.rs b/core/src/nodes/raw_swarm.rs index a69f0511..f650c2c5 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 f8a6370e..20dcf81c 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 00000000..5819ebfa --- /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 e8030c66..8f51b0df 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 5fefbe30..47fef9c8 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 00000000..6b993da8 --- /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 00000000..b6a0ca89 --- /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 00000000..3e9bdb34 --- /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 c4e34b31..6fb93a44 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 fb4197a7..50d7ffc6 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 166a16ba..99fc06e5 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 d44dbbf9..00000000 --- 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 f1b4f819..00000000 --- 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 2991ae5b..71284004 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 ed220fec..19980294 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 d6ed7234..30678a3d 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))