diff --git a/libp2p/src/transport_ext.rs b/libp2p/src/transport_ext.rs index e66d7395..b51b8e82 100644 --- a/libp2p/src/transport_ext.rs +++ b/libp2p/src/transport_ext.rs @@ -58,7 +58,7 @@ pub trait TransportExt: Transport { /// let transport = tcp::tokio::Transport::new(tcp::Config::default().nodelay(true)) /// .upgrade(upgrade::Version::V1) /// .authenticate( - /// noise::NoiseAuthenticated::xx(&id_keys) + /// noise::Config::new(&id_keys) /// .expect("Signing libp2p-noise static DH keypair failed."), /// ) /// .multiplex(mplex::MplexConfig::new()) diff --git a/protocols/gossipsub/src/lib.rs b/protocols/gossipsub/src/lib.rs index fea17b67..cebd2444 100644 --- a/protocols/gossipsub/src/lib.rs +++ b/protocols/gossipsub/src/lib.rs @@ -92,17 +92,18 @@ //! An example of initialising a gossipsub compatible swarm: //! //! ``` -//! use libp2p_gossipsub::Event; -//! use libp2p_core::{identity::Keypair,transport::{Transport, MemoryTransport}, Multiaddr}; -//! use libp2p_gossipsub::MessageAuthenticity; -//! let local_key = Keypair::generate_ed25519(); -//! let local_peer_id = libp2p_core::PeerId::from(local_key.public()); +//! # use libp2p_gossipsub::Event; +//! # use libp2p_core::{transport::{Transport, MemoryTransport}, Multiaddr}; +//! # use libp2p_gossipsub::MessageAuthenticity; +//! # use libp2p_identity as identity; +//! let local_key = identity::Keypair::generate_ed25519(); +//! let local_peer_id = local_key.public().to_peer_id(); //! //! // Set up an encrypted TCP Transport over the Mplex //! // This is test transport (memory). //! let transport = MemoryTransport::default() //! .upgrade(libp2p_core::upgrade::Version::V1) -//! .authenticate(libp2p_noise::NoiseAuthenticated::xx(&local_key).unwrap()) +//! .authenticate(libp2p_noise::Config::new(&local_key).unwrap()) //! .multiplex(libp2p_mplex::MplexConfig::new()) //! .boxed(); //! @@ -123,11 +124,11 @@ //! // subscribe to the topic //! gossipsub.subscribe(&topic); //! // create the swarm (use an executor in a real example) -//! libp2p_swarm::Swarm::without_executor( +//! libp2p_swarm::SwarmBuilder::without_executor( //! transport, //! gossipsub, //! local_peer_id, -//! ) +//! ).build() //! }; //! //! // Listen on a memory transport. diff --git a/transports/noise/CHANGELOG.md b/transports/noise/CHANGELOG.md index 3b3bce34..27b37a81 100644 --- a/transports/noise/CHANGELOG.md +++ b/transports/noise/CHANGELOG.md @@ -1,8 +1,11 @@ -## 0.42.0 - unreleased +## 0.43.0 - unreleased - Raise MSRV to 1.65. See [PR 3715]. +- Remove deprecated APIs. See [PR 3511]. + +[PR 3511]: https://github.com/libp2p/rust-libp2p/pull/3511 [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 ## 0.42.2 diff --git a/transports/noise/src/io/framed.rs b/transports/noise/src/io/framed.rs index 01e1f9db..0904c024 100644 --- a/transports/noise/src/io/framed.rs +++ b/transports/noise/src/io/framed.rs @@ -22,7 +22,7 @@ //! Noise protocol messages in form of [`NoiseFramed`]. use crate::io::Output; -use crate::{Error, Protocol, PublicKey}; +use crate::{protocol::PublicKey, Error}; use bytes::{Bytes, BytesMut}; use futures::prelude::*; use futures::ready; @@ -89,15 +89,15 @@ impl NoiseFramed { /// transitioning to transport mode because the handshake is incomplete, /// an error is returned. Similarly if the remote's static DH key, if /// present, cannot be parsed. - pub(crate) fn into_transport(self) -> Result<(Option>, Output), Error> - where - C: Protocol + AsRef<[u8]>, - { - let dh_remote_pubkey = self - .session - .get_remote_static() - .map(C::public_from_bytes) - .transpose()?; + pub(crate) fn into_transport(self) -> Result<(PublicKey, Output), Error> { + let dh_remote_pubkey = self.session.get_remote_static().ok_or_else(|| { + Error::Io(io::Error::new( + io::ErrorKind::Other, + "expect key to always be present at end of XX session", + )) + })?; + + let dh_remote_pubkey = PublicKey::from_slice(dh_remote_pubkey)?; let io = NoiseFramed { session: self.session.into_transport_mode()?, diff --git a/transports/noise/src/io/handshake.rs b/transports/noise/src/io/handshake.rs index ea3331d5..18816a3b 100644 --- a/transports/noise/src/io/handshake.rs +++ b/transports/noise/src/io/handshake.rs @@ -27,46 +27,14 @@ mod proto { } use crate::io::{framed::NoiseFramed, Output}; -use crate::protocol::{KeypairIdentity, Protocol, PublicKey}; - +use crate::protocol::{KeypairIdentity, STATIC_KEY_DOMAIN}; use crate::Error; -use crate::LegacyConfig; use bytes::Bytes; use futures::prelude::*; use libp2p_identity as identity; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; use std::io; -/// The identity of the remote established during a handshake. -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -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), -} - ////////////////////////////////////////////////////////////////////////////// // Internal @@ -81,8 +49,6 @@ pub(crate) struct State { dh_remote_pubkey_sig: Option>, /// The known or received public identity key of the remote, if any. id_remote_pubkey: Option, - /// Legacy configuration parameters. - legacy: LegacyConfig, } impl State { @@ -97,14 +63,12 @@ impl State { session: snow::HandshakeState, identity: KeypairIdentity, expected_remote_key: Option, - legacy: LegacyConfig, ) -> Self { Self { identity, io: NoiseFramed::new(io, session), dh_remote_pubkey_sig: None, id_remote_pubkey: expected_remote_key, - legacy, } } } @@ -112,23 +76,22 @@ impl State { impl State { /// Finish a handshake, yielding the established remote identity and the /// [`Output`] for communicating on the encrypted channel. - pub(crate) fn finish(self) -> Result<(RemoteIdentity, Output), Error> - where - C: Protocol + AsRef<[u8]>, - { + pub(crate) fn finish(self) -> Result<(identity::PublicKey, Output), Error> { let (pubkey, io) = self.io.into_transport()?; - let remote = match (self.id_remote_pubkey, 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 Err(Error::BadSignature); - } - } - }; - Ok((remote, io)) + + let id_pk = self + .id_remote_pubkey + .ok_or_else(|| Error::AuthenticationFailed)?; + + let is_valid_signature = self.dh_remote_pubkey_sig.as_ref().map_or(false, |s| { + id_pk.verify(&[STATIC_KEY_DOMAIN.as_bytes(), pubkey.as_ref()].concat(), s) + }); + + if !is_valid_signature { + return Err(Error::BadSignature); + } + + Ok((id_pk, io)) } } @@ -170,60 +133,16 @@ where Ok(()) } -/// A future for receiving a Noise handshake message with a payload -/// identifying the remote. -/// -/// In case `expected_key` is passed, this function will fail if the received key does not match the expected key. -/// In case the remote does not send us a key, the expected key is assumed to be the remote's key. +/// A future for receiving a Noise handshake message with a payload identifying the remote. pub(crate) async fn recv_identity(state: &mut State) -> Result<(), Error> where T: AsyncRead + Unpin, { let msg = recv(state).await?; - let mut reader = BytesReader::from_bytes(&msg[..]); - let mut pb_result = proto::NoiseHandshakePayload::from_reader(&mut reader, &msg[..]); + let pb = proto::NoiseHandshakePayload::from_reader(&mut reader, &msg[..])?; - if pb_result.is_err() && state.legacy.recv_legacy_handshake { - // NOTE: This is support for legacy handshake payloads. As long as - // the frame length is less than 256 bytes, which is the case for - // all protobuf payloads not containing RSA keys, there is no room - // for misinterpretation, since if a two-bytes length prefix is present - // the first byte will be 0, which is always an unexpected protobuf tag - // value because the fields in the .proto file start with 1 and decoding - // thus expects a non-zero first byte. We will therefore always correctly - // fall back to the legacy protobuf parsing in these cases (again, not - // considering RSA keys, for which there may be a probabilistically - // very small chance of misinterpretation). - pb_result = pb_result.or_else(|e| { - if msg.len() > 2 { - let mut buf = [0, 0]; - buf.copy_from_slice(&msg[..2]); - // If there is a second length it must be 2 bytes shorter than the - // frame length, because each length is encoded as a `u16`. - if usize::from(u16::from_be_bytes(buf)) + 2 == msg.len() { - log::debug!("Attempting fallback legacy protobuf decoding."); - let mut reader = BytesReader::from_bytes(&msg[2..]); - proto::NoiseHandshakePayload::from_reader(&mut reader, &msg[2..]) - } else { - Err(e) - } - } else { - Err(e) - } - }); - } - let pb = pb_result?; - - if !pb.identity_key.is_empty() { - let pk = identity::PublicKey::try_decode_protobuf(&pb.identity_key)?; - if let Some(ref k) = state.id_remote_pubkey { - if k != &pk { - return Err(Error::UnexpectedKey); - } - } - state.id_remote_pubkey = Some(pk); - } + state.id_remote_pubkey = Some(identity::PublicKey::try_decode_protobuf(&pb.identity_key)?); if !pb.identity_sig.is_empty() { state.dh_remote_pubkey_sig = Some(pb.identity_sig); @@ -242,43 +161,9 @@ where ..Default::default() }; - if let Some(ref sig) = state.identity.signature { - pb.identity_sig = sig.clone() - } + pb.identity_sig = state.identity.signature.clone(); - let mut msg = if state.legacy.send_legacy_handshake { - let mut msg = Vec::with_capacity(2 + pb.get_size()); - msg.extend_from_slice(&(pb.get_size() as u16).to_be_bytes()); - msg - } else { - Vec::with_capacity(pb.get_size()) - }; - - let mut writer = Writer::new(&mut msg); - pb.write_message(&mut writer).expect("Encoding to succeed"); - state.io.send(&msg).await?; - - Ok(()) -} - -/// Send a Noise handshake message with a payload identifying the local node to the remote. -pub(crate) async fn send_signature_only(state: &mut State) -> Result<(), Error> -where - T: AsyncWrite + Unpin, -{ - let mut pb = proto::NoiseHandshakePayload::default(); - - if let Some(ref sig) = state.identity.signature { - pb.identity_sig = sig.clone() - } - - let mut msg = if state.legacy.send_legacy_handshake { - let mut msg = Vec::with_capacity(2 + pb.get_size()); - msg.extend_from_slice(&(pb.get_size() as u16).to_be_bytes()); - msg - } else { - Vec::with_capacity(pb.get_size()) - }; + let mut msg = Vec::with_capacity(pb.get_size()); let mut writer = Writer::new(&mut msg); pb.write_message(&mut writer).expect("Encoding to succeed"); diff --git a/transports/noise/src/lib.rs b/transports/noise/src/lib.rs index 8e180482..e3b03db2 100644 --- a/transports/noise/src/lib.rs +++ b/transports/noise/src/lib.rs @@ -59,110 +59,81 @@ mod io; mod protocol; -pub use io::handshake::RemoteIdentity; pub use io::Output; -pub use protocol::Protocol; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type X25519Spec = protocol::x25519_spec::X25519Spec; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type X25519 = protocol::x25519::X25519; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type AuthenticKeypair = protocol::AuthenticKeypair; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type Keypair = protocol::Keypair; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type KeypairIdentity = protocol::KeypairIdentity; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type PublicKey = protocol::PublicKey; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type SecretKey = protocol::SecretKey; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type ProtocolParams = protocol::ProtocolParams; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type IK = protocol::IK; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type IX = protocol::IX; - -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub type XX = protocol::XX; - -#[deprecated( - note = "This type has been renamed to drop the `Noise` prefix, refer to it as `noise::Error` instead." -)] -pub type NoiseError = Error; - -#[deprecated( - note = "This type has been renamed to drop the `Noise` prefix, refer to it as `noise::Output` instead." -)] -pub type NoiseOutput = Output; - use crate::handshake::State; use crate::io::handshake; -use futures::future::BoxFuture; +use crate::protocol::{noise_params_into_builder, AuthenticKeypair, Keypair, PARAMS_XX}; use futures::prelude::*; use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_identity as identity; use libp2p_identity::PeerId; +use snow::params::NoiseParams; use std::fmt; use std::fmt::Formatter; use std::pin::Pin; -use zeroize::Zeroize; /// The configuration for the noise handshake. #[derive(Clone)] pub struct Config { - inner: NoiseAuthenticated, + dh_keys: AuthenticKeypair, + params: NoiseParams, + + /// Prologue to use in the noise handshake. + /// + /// The prologue can contain arbitrary data that will be hashed into the noise handshake. + /// For the handshake to succeed, both parties must set the same prologue. + /// + /// For further information, see . + prologue: Vec, } impl Config { /// Construct a new configuration for the noise handshake using the XX handshake pattern. - pub fn new(identity: &identity::Keypair) -> Result { - Ok(Config { - inner: NoiseAuthenticated::xx(identity)?, + let noise_keys = Keypair::new().into_authentic(identity)?; + + Ok(Self { + dh_keys: noise_keys, + params: PARAMS_XX.clone(), + prologue: vec![], }) } /// Set the noise prologue. - pub fn with_prologue(mut self, prologue: Vec) -> Self { - self.inner.config.prologue = prologue; + self.prologue = prologue; self } + + fn into_responder(self, socket: S) -> Result, Error> { + let session = noise_params_into_builder( + self.params, + &self.prologue, + self.dh_keys.keypair.secret(), + None, + ) + .build_responder()?; + + let state = State::new(socket, session, self.dh_keys.identity, None); + + Ok(state) + } + + fn into_initiator(self, socket: S) -> Result, Error> { + let session = noise_params_into_builder( + self.params, + &self.prologue, + self.dh_keys.keypair.secret(), + None, + ) + .build_initiator()?; + + let state = State::new(socket, session, self.dh_keys.identity, None); + + Ok(state) + } } impl UpgradeInfo for Config { @@ -182,8 +153,19 @@ where type Error = Error; type Future = Pin> + Send>>; - fn upgrade_inbound(self, socket: T, info: Self::Info) -> Self::Future { - self.inner.upgrade_inbound(socket, info) + fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { + async move { + let mut state = self.into_responder(socket)?; + + handshake::recv_empty(&mut state).await?; + handshake::send_identity(&mut state).await?; + handshake::recv_identity(&mut state).await?; + + let (pk, io) = state.finish()?; + + Ok((pk.to_peer_id(), io)) + } + .boxed() } } @@ -195,185 +177,19 @@ where type Error = Error; type Future = Pin> + Send>>; - fn upgrade_outbound(self, socket: T, info: Self::Info) -> Self::Future { - self.inner.upgrade_outbound(socket, info) - } -} + fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { + async move { + let mut state = self.into_initiator(socket)?; -/// The protocol upgrade configuration. -#[deprecated( - note = "Use `libp2p_noise::Config` instead. All other handshake patterns are deprecated and will be removed." -)] -#[derive(Clone)] -pub struct NoiseConfig { - dh_keys: AuthenticKeypair, - params: ProtocolParams, + handshake::send_empty(&mut state).await?; + handshake::recv_identity(&mut state).await?; + handshake::send_identity(&mut state).await?; - legacy: LegacyConfig, - remote: R, - _marker: std::marker::PhantomData

, + let (pk, io) = state.finish()?; - /// Prologue to use in the noise handshake. - /// - /// The prologue can contain arbitrary data that will be hashed into the noise handshake. - /// For the handshake to succeed, both parties must set the same prologue. - /// - /// For further information, see . - prologue: Vec, -} - -impl NoiseConfig { - /// Turn the `NoiseConfig` into an authenticated upgrade for use - /// with a `Swarm`. - pub fn into_authenticated(self) -> NoiseAuthenticated { - NoiseAuthenticated { config: self } - } - - /// Set the noise prologue. - pub fn with_prologue(self, prologue: Vec) -> Self { - Self { prologue, ..self } - } - - /// Sets the legacy configuration options to use, if any. - #[deprecated( - since = "0.42.0", - note = "`LegacyConfig` will be removed without replacement." - )] - - pub fn set_legacy_config(&mut self, cfg: LegacyConfig) -> &mut Self { - self.legacy = cfg; - self - } -} - -/// Implement `into_responder` and `into_initiator` for all configs where `R = ()`. -/// -/// This allows us to ignore the `remote` field. - -impl NoiseConfig -where - C: Zeroize + Protocol + AsRef<[u8]>, -{ - fn into_responder(self, socket: S) -> Result, Error> { - let session = self - .params - .into_builder(&self.prologue, self.dh_keys.keypair.secret(), None) - .build_responder()?; - - let state = State::new(socket, session, self.dh_keys.identity, None, self.legacy); - - Ok(state) - } - - fn into_initiator(self, socket: S) -> Result, Error> { - let session = self - .params - .into_builder(&self.prologue, self.dh_keys.keypair.secret(), None) - .build_initiator()?; - - let state = State::new(socket, session, self.dh_keys.identity, None, self.legacy); - - Ok(state) - } -} - -impl NoiseConfig -where - C: Protocol + Zeroize, -{ - /// Create a new `NoiseConfig` for the `IX` handshake pattern. - pub fn ix(dh_keys: AuthenticKeypair) -> Self { - NoiseConfig { - dh_keys, - params: C::params_ix(), - legacy: { LegacyConfig::default() }, - remote: (), - _marker: std::marker::PhantomData, - prologue: Vec::default(), + Ok((pk.to_peer_id(), io)) } - } -} - -impl NoiseConfig -where - C: Protocol + Zeroize, -{ - /// Create a new `NoiseConfig` for the `XX` handshake pattern. - pub fn xx(dh_keys: AuthenticKeypair) -> Self { - NoiseConfig { - dh_keys, - params: C::params_xx(), - legacy: { LegacyConfig::default() }, - remote: (), - _marker: std::marker::PhantomData, - prologue: Vec::default(), - } - } -} - -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 { - dh_keys, - params: C::params_ik(), - legacy: { LegacyConfig::default() }, - remote: (), - _marker: std::marker::PhantomData, - prologue: Vec::default(), - } - } -} - -impl NoiseConfig, identity::PublicKey)> -where - C: Protocol + Zeroize + AsRef<[u8]>, -{ - /// 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 { - dh_keys, - params: C::params_ik(), - legacy: { LegacyConfig::default() }, - remote: (remote_dh, remote_id), - _marker: std::marker::PhantomData, - prologue: Vec::default(), - } - } - - /// Specialised implementation of `into_initiator` for the `IK` handshake where `R != ()`. - fn into_initiator(self, socket: S) -> Result, Error> { - let session = self - .params - .into_builder( - &self.prologue, - self.dh_keys.keypair.secret(), - Some(&self.remote.0), - ) - .build_initiator()?; - - let state = State::new( - socket, - session, - self.dh_keys.identity, - Some(self.remote.1), - self.legacy, - ); - - Ok(state) + .boxed() } } @@ -421,328 +237,3 @@ impl From for Error { Error::InvalidPayload(e.into()) } } - -// Handshake pattern IX ///////////////////////////////////////////////////// - -/// Implements the responder part of the `IX` noise handshake pattern. -/// -/// `IX` is a single round-trip (2 messages) handshake in which each party sends their identity over to the other party. -/// -/// ```raw -/// initiator -{id}-> responder -/// initiator <-{id}- responder -/// ``` - -impl InboundUpgrade for NoiseConfig -where - NoiseConfig: UpgradeInfo, - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, - C: Protocol + AsRef<[u8]> + Zeroize + Clone + Send + 'static, -{ - type Output = (RemoteIdentity, Output); - type Error = Error; - type Future = BoxFuture<'static, Result<(RemoteIdentity, Output), Error>>; - - fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - async move { - let mut state = self.into_responder(socket)?; - - handshake::recv_identity(&mut state).await?; - handshake::send_identity(&mut state).await?; - - state.finish() - } - .boxed() - } -} - -/// Implements the initiator part of the `IX` noise handshake pattern. -/// -/// `IX` is a single round-trip (2 messages) handshake in which each party sends their identity over to the other party. -/// -/// ```raw -/// initiator -{id}-> responder -/// initiator <-{id}- responder -/// ``` - -impl OutboundUpgrade for NoiseConfig -where - NoiseConfig: UpgradeInfo, - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, - C: Protocol + AsRef<[u8]> + Zeroize + Clone + Send + 'static, -{ - type Output = (RemoteIdentity, Output); - type Error = Error; - type Future = BoxFuture<'static, Result<(RemoteIdentity, Output), Error>>; - - fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - async move { - let mut state = self.into_initiator(socket)?; - - handshake::send_identity(&mut state).await?; - handshake::recv_identity(&mut state).await?; - - state.finish() - } - .boxed() - } -} - -/// Implements the responder part of the `XX` noise handshake pattern. -/// -/// `XX` is a 1.5 round-trip (3 messages) handshake. -/// The first message in a noise handshake is unencrypted. In the `XX` handshake pattern, that message -/// is empty and thus does not leak any information. The identities are then exchanged in the second -/// and third message. -/// -/// ```raw -/// initiator --{}--> responder -/// initiator <-{id}- responder -/// initiator -{id}-> responder -/// ``` - -impl InboundUpgrade for NoiseConfig -where - NoiseConfig: UpgradeInfo, - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, - C: Protocol + AsRef<[u8]> + Zeroize + Clone + Send + 'static, -{ - type Output = (RemoteIdentity, Output); - type Error = Error; - type Future = BoxFuture<'static, Result<(RemoteIdentity, Output), Error>>; - - fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - async move { - let mut state = self.into_responder(socket)?; - - handshake::recv_empty(&mut state).await?; - handshake::send_identity(&mut state).await?; - handshake::recv_identity(&mut state).await?; - - state.finish() - } - .boxed() - } -} - -/// Implements the initiator part of the `XX` noise handshake pattern. -/// -/// `XX` is a 1.5 round-trip (3 messages) handshake. -/// The first message in a noise handshake is unencrypted. In the `XX` handshake pattern, that message -/// is empty and thus does not leak any information. The identities are then exchanged in the second -/// and third message. -/// -/// ```raw -/// initiator --{}--> responder -/// initiator <-{id}- responder -/// initiator -{id}-> responder -/// ``` - -impl OutboundUpgrade for NoiseConfig -where - NoiseConfig: UpgradeInfo, - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, - C: Protocol + AsRef<[u8]> + Zeroize + Clone + Send + 'static, -{ - type Output = (RemoteIdentity, Output); - type Error = Error; - type Future = BoxFuture<'static, Result<(RemoteIdentity, Output), Error>>; - - fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - async move { - let mut state = self.into_initiator(socket)?; - - handshake::send_empty(&mut state).await?; - handshake::recv_identity(&mut state).await?; - handshake::send_identity(&mut state).await?; - - state.finish() - } - .boxed() - } -} - -/// Implements the responder part of the `IK` handshake pattern. -/// -/// `IK` is a single round-trip (2 messages) handshake. -/// -/// In the `IK` handshake, the initiator is expected to know the responder's identity already, which -/// is why the responder does not send it in the second message. -/// -/// ```raw -/// initiator -{id}-> responder -/// initiator <-{id}- responder -/// ``` - -impl InboundUpgrade for NoiseConfig -where - NoiseConfig: UpgradeInfo, - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, - C: Protocol + AsRef<[u8]> + Zeroize + Clone + Send + 'static, -{ - type Output = (RemoteIdentity, Output); - type Error = Error; - type Future = BoxFuture<'static, Result<(RemoteIdentity, Output), Error>>; - - fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - async move { - let mut state = self.into_responder(socket)?; - - handshake::recv_identity(&mut state).await?; - handshake::send_signature_only(&mut state).await?; - - state.finish() - } - .boxed() - } -} - -/// Implements the initiator part of the `IK` handshake pattern. -/// -/// `IK` is a single round-trip (2 messages) handshake. -/// -/// In the `IK` handshake, the initiator knows and pre-configures the remote's identity in the -/// [`HandshakeState`](snow::HandshakeState). -/// -/// ```raw -/// initiator -{id}-> responder -/// initiator <-{id}- responder -/// ``` - -impl OutboundUpgrade for NoiseConfig, identity::PublicKey)> -where - NoiseConfig, identity::PublicKey)>: UpgradeInfo, - T: AsyncRead + AsyncWrite + Unpin + Send + 'static, - C: Protocol + AsRef<[u8]> + Zeroize + Clone + Send + 'static, -{ - type Output = (RemoteIdentity, Output); - type Error = Error; - type Future = BoxFuture<'static, Result<(RemoteIdentity, Output), Error>>; - - fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - async move { - let mut state = self.into_initiator(socket)?; - - handshake::send_identity(&mut state).await?; - handshake::recv_identity(&mut state).await?; - - state.finish() - } - .boxed() - } -} - -// Authenticated Upgrades ///////////////////////////////////////////////////// - -/// A `NoiseAuthenticated` transport upgrade that wraps around any -/// `NoiseConfig` handshake and verifies that the remote identified with a -/// [`RemoteIdentity::IdentityKey`], aborting otherwise. -/// -/// See [`NoiseConfig::into_authenticated`]. -/// -/// On success, the upgrade yields the [`PeerId`] obtained from the -/// `RemoteIdentity`. The output of this upgrade is thus directly suitable -/// for creating an [`authenticated`](libp2p_core::transport::upgrade::Authenticate) -/// transport for use with a `Swarm`. -#[derive(Clone)] -#[deprecated( - note = "Use `libp2p_noise::Config` instead. All other handshake patterns are deprecated and will be removed." -)] -pub struct NoiseAuthenticated { - config: NoiseConfig, -} - -impl NoiseAuthenticated { - /// Create a new [`NoiseAuthenticated`] for the `XX` handshake pattern using X25519 DH keys. - /// - /// For now, this is the only combination that is guaranteed to be compatible with other libp2p implementations. - #[deprecated(note = "Use `libp2p_noise::Config::new` instead.")] - pub fn xx(id_keys: &identity::Keypair) -> Result { - let dh_keys = Keypair::::new(); - let noise_keys = dh_keys.into_authentic(id_keys)?; - let config = NoiseConfig::xx(noise_keys); - - Ok(config.into_authenticated()) - } -} - -impl UpgradeInfo for NoiseAuthenticated -where - NoiseConfig: UpgradeInfo, -{ - type Info = as UpgradeInfo>::Info; - type InfoIter = as UpgradeInfo>::InfoIter; - - fn protocol_info(&self) -> Self::InfoIter { - self.config.protocol_info() - } -} - -impl InboundUpgrade for NoiseAuthenticated -where - NoiseConfig: UpgradeInfo - + InboundUpgrade, Output), Error = Error> - + 'static, - as InboundUpgrade>::Future: Send, - T: AsyncRead + AsyncWrite + Send + 'static, - C: Protocol + AsRef<[u8]> + Zeroize + Send + 'static, -{ - type Output = (PeerId, Output); - type Error = Error; - type Future = Pin> + Send>>; - - fn upgrade_inbound(self, socket: T, info: Self::Info) -> Self::Future { - Box::pin( - self.config - .upgrade_inbound(socket, info) - .and_then(|(remote, io)| match remote { - RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), - _ => future::err(Error::AuthenticationFailed), - }), - ) - } -} - -impl OutboundUpgrade for NoiseAuthenticated -where - NoiseConfig: UpgradeInfo - + OutboundUpgrade, Output), Error = Error> - + 'static, - as OutboundUpgrade>::Future: Send, - T: AsyncRead + AsyncWrite + Send + 'static, - C: Protocol + AsRef<[u8]> + Zeroize + Send + 'static, -{ - type Output = (PeerId, Output); - type Error = Error; - type Future = Pin> + Send>>; - - fn upgrade_outbound(self, socket: T, info: Self::Info) -> Self::Future { - Box::pin( - self.config - .upgrade_outbound(socket, info) - .and_then(|(remote, io)| match remote { - RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)), - _ => future::err(Error::AuthenticationFailed), - }), - ) - } -} - -/// Legacy configuration options. -#[derive(Clone, Copy, Default)] -#[deprecated( - since = "0.42.0", - note = "`LegacyConfig` will be removed without replacement." -)] -pub struct LegacyConfig { - /// Whether to continue sending legacy handshake payloads, - /// i.e. length-prefixed protobuf payloads inside a length-prefixed - /// noise frame. These payloads are not interoperable with other - /// libp2p implementations. - pub send_legacy_handshake: bool, - /// Whether to support receiving legacy handshake payloads, - /// i.e. length-prefixed protobuf payloads inside a length-prefixed - /// noise frame. These payloads are not interoperable with other - /// libp2p implementations. - pub recv_legacy_handshake: bool, -} diff --git a/transports/noise/src/protocol.rs b/transports/noise/src/protocol.rs index c8d9da3f..e37c55c7 100644 --- a/transports/noise/src/protocol.rs +++ b/transports/noise/src/protocol.rs @@ -20,174 +20,80 @@ //! Components of a Noise protocol. -pub(crate) mod x25519; -pub(crate) mod x25519_spec; use crate::Error; use libp2p_identity as identity; -use rand::SeedableRng; +use once_cell::sync::Lazy; +use rand::{Rng as _, SeedableRng}; +use snow::params::NoiseParams; +use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES}; use zeroize::Zeroize; -/// The parameters of a Noise protocol, consisting of a choice -/// for a handshake pattern as well as DH, cipher and hash functions. -#[derive(Clone)] -pub struct ProtocolParams(snow::params::NoiseParams); +/// Prefix of static key signatures for domain separation. +pub(crate) const STATIC_KEY_DOMAIN: &str = "noise-libp2p-static-key:"; -impl ProtocolParams { - pub(crate) fn into_builder<'b, C>( - self, - prologue: &'b [u8], - private_key: &'b SecretKey, - remote_public_key: Option<&'b PublicKey>, - ) -> snow::Builder<'b> - where - C: Zeroize + AsRef<[u8]> + Protocol, - { - let mut builder = snow::Builder::with_resolver(self.0, Box::new(Resolver)) - .prologue(prologue.as_ref()) - .local_private_key(private_key.as_ref()); +pub(crate) static PARAMS_XX: Lazy = Lazy::new(|| { + "Noise_XX_25519_ChaChaPoly_SHA256" + .parse() + .expect("Invalid protocol name") +}); - if let Some(remote_public_key) = remote_public_key { - builder = builder.remote_public_key(remote_public_key.as_ref()); - } +pub(crate) fn noise_params_into_builder<'b>( + params: NoiseParams, + prologue: &'b [u8], + private_key: &'b SecretKey, + remote_public_key: Option<&'b PublicKey>, +) -> snow::Builder<'b> { + let mut builder = snow::Builder::with_resolver(params, Box::new(Resolver)) + .prologue(prologue.as_ref()) + .local_private_key(private_key.as_ref()); - builder - } -} - -/// Type tag for the IK handshake pattern. -#[derive(Debug, Clone)] -pub enum IK {} - -/// Type tag for the IX handshake pattern. -#[derive(Debug, Clone)] -pub enum IX {} - -/// Type tag for the XX handshake pattern. -#[derive(Debug, Clone)] -pub enum XX {} - -/// A Noise protocol over DH keys of type `C`. The choice of `C` determines the -/// protocol parameters for each handshake pattern. -#[deprecated( - note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol." -)] -pub trait Protocol { - /// The protocol parameters for the IK handshake pattern. - fn params_ik() -> ProtocolParams; - /// The protocol parameters for the IX handshake pattern. - 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, Error>; - - /// 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)] - #[deprecated] - fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey) -> bool { - false + if let Some(remote_public_key) = remote_public_key { + builder = builder.remote_public_key(remote_public_key.as_ref()); } - /// 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)) - } - - fn sign(id_keys: &identity::Keypair, dh_pk: &PublicKey) -> Result, Error> - where - C: AsRef<[u8]>, - { - Ok(id_keys.sign(dh_pk.as_ref())?) - } + builder } /// DH keypair. #[derive(Clone)] -pub struct Keypair { - secret: SecretKey, - public: PublicKey, +pub(crate) struct Keypair { + secret: SecretKey, + public: PublicKey, } /// A DH keypair that is authentic w.r.t. a [`identity::PublicKey`]. #[derive(Clone)] -pub struct AuthenticKeypair { - pub(crate) keypair: Keypair, +pub(crate) struct AuthenticKeypair { + pub(crate) keypair: Keypair, pub(crate) identity: KeypairIdentity, } -impl AuthenticKeypair { - /// Returns the public DH key of this keypair. - pub fn public_dh_key(&self) -> &PublicKey { - &self.keypair.public - } - - /// Extract the public [`KeypairIdentity`] from this `AuthenticKeypair`, - /// dropping the DH `Keypair`. - #[deprecated( - since = "0.40.0", - note = "This function was only used internally and will be removed in the future unless more usecases come up." - )] - pub fn into_identity(self) -> KeypairIdentity { - self.identity - } -} - /// The associated public identity of a DH keypair. #[derive(Clone)] -pub struct KeypairIdentity { +pub(crate) struct KeypairIdentity { /// The public identity key. - pub public: identity::PublicKey, + pub(crate) public: identity::PublicKey, /// The signature over the public DH key. - pub signature: Option>, + pub(crate) signature: Vec, } -impl Keypair { - /// The public key of the DH keypair. - pub fn public(&self) -> &PublicKey { - &self.public - } - +impl Keypair { /// The secret key of the DH keypair. - pub fn secret(&self) -> &SecretKey { + pub(crate) 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, Error> - where - T: AsRef<[u8]>, - T: Protocol, - { - let sig = T::sign(id_keys, &self.public)?; + pub(crate) fn into_authentic( + self, + id_keys: &identity::Keypair, + ) -> Result { + let sig = id_keys.sign(&[STATIC_KEY_DOMAIN.as_bytes(), self.public.as_ref()].concat())?; let identity = KeypairIdentity { public: id_keys.public(), - signature: Some(sig), + signature: sig, }; Ok(AuthenticKeypair { @@ -195,37 +101,59 @@ impl Keypair { identity, }) } + + /// An "empty" keypair as a starting state for DH computations in `snow`, + /// which get manipulated through the `snow::types::Dh` interface. + pub(crate) fn empty() -> Self { + Keypair { + secret: SecretKey([0u8; 32]), + public: PublicKey([0u8; 32]), + } + } + + /// Create a new X25519 keypair. + pub(crate) fn new() -> Keypair { + let mut sk_bytes = [0u8; 32]; + rand::thread_rng().fill(&mut sk_bytes); + let sk = SecretKey(sk_bytes); // Copy + sk_bytes.zeroize(); + Self::from(sk) + } } /// DH secret key. -#[derive(Clone)] -pub struct SecretKey(T); +#[derive(Clone, Default)] +pub(crate) struct SecretKey([u8; 32]); -impl Drop for SecretKey { +impl Drop for SecretKey { fn drop(&mut self) { self.0.zeroize() } } -impl + Zeroize> AsRef<[u8]> for SecretKey { +impl AsRef<[u8]> for SecretKey { fn as_ref(&self) -> &[u8] { self.0.as_ref() } } /// DH public key. -#[derive(Clone)] -pub struct PublicKey(T); +#[derive(Clone, PartialEq, Default)] +pub(crate) struct PublicKey([u8; 32]); -impl> PartialEq for PublicKey { - fn eq(&self, other: &PublicKey) -> bool { - self.as_ref() == other.as_ref() +impl PublicKey { + pub(crate) fn from_slice(slice: &[u8]) -> Result { + if slice.len() != 32 { + return Err(Error::InvalidLength); + } + + let mut key = [0u8; 32]; + key.copy_from_slice(slice); + Ok(PublicKey(key)) } } -impl> Eq for PublicKey {} - -impl> AsRef<[u8]> for PublicKey { +impl AsRef<[u8]> for PublicKey { fn as_ref(&self) -> &[u8] { self.0.as_ref() } @@ -244,7 +172,7 @@ impl snow::resolvers::CryptoResolver for Resolver { fn resolve_dh(&self, choice: &snow::params::DHChoice) -> Option> { if let snow::params::DHChoice::Curve25519 = choice { - Some(Box::new(Keypair::::default())) + Some(Box::new(Keypair::empty())) } else { None } @@ -304,10 +232,67 @@ impl rand::CryptoRng for Rng {} impl snow::types::Random for Rng {} +impl Default for Keypair { + fn default() -> Self { + Self::new() + } +} + +/// Promote a X25519 secret key into a keypair. +impl From for Keypair { + fn from(secret: SecretKey) -> Keypair { + let public = PublicKey(x25519(secret.0, X25519_BASEPOINT_BYTES)); + Keypair { secret, public } + } +} + +#[doc(hidden)] +impl snow::types::Dh for Keypair { + fn name(&self) -> &'static str { + "25519" + } + fn pub_len(&self) -> usize { + 32 + } + fn priv_len(&self) -> usize { + 32 + } + fn pubkey(&self) -> &[u8] { + self.public.as_ref() + } + fn privkey(&self) -> &[u8] { + self.secret.as_ref() + } + + fn set(&mut self, sk: &[u8]) { + let mut secret = [0u8; 32]; + secret.copy_from_slice(sk); + self.secret = SecretKey(secret); // Copy + self.public = PublicKey(x25519(secret, X25519_BASEPOINT_BYTES)); + secret.zeroize(); + } + + fn generate(&mut self, rng: &mut dyn snow::types::Random) { + let mut secret = [0u8; 32]; + rng.fill_bytes(&mut secret); + self.secret = SecretKey(secret); // Copy + self.public = PublicKey(x25519(secret, X25519_BASEPOINT_BYTES)); + secret.zeroize(); + } + + fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), snow::Error> { + let mut p = [0; 32]; + p.copy_from_slice(&pk[..32]); + let ss = x25519(self.secret.0, p); + shared_secret[..32].copy_from_slice(&ss[..]); + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::X25519Spec; + use crate::protocol::PARAMS_XX; use once_cell::sync::Lazy; #[test] @@ -333,9 +318,9 @@ mod tests { } fn xx_builder(prologue: &'static [u8]) -> snow::Builder<'static> { - X25519Spec::params_xx().into_builder(prologue, TEST_KEY.secret(), None) + noise_params_into_builder(PARAMS_XX.clone(), prologue, TEST_KEY.secret(), None) } // Hack to work around borrow-checker. - static TEST_KEY: Lazy> = Lazy::new(Keypair::::new); + static TEST_KEY: Lazy = Lazy::new(Keypair::new); } diff --git a/transports/noise/src/protocol/x25519.rs b/transports/noise/src/protocol/x25519.rs deleted file mode 100644 index 4fd19097..00000000 --- a/transports/noise/src/protocol/x25519.rs +++ /dev/null @@ -1,292 +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. - -//! Legacy Noise protocols based on X25519. -//! -//! **Note**: This set of protocols is not interoperable with other -//! libp2p implementations. - -use crate::{Error, NoiseConfig, Protocol, ProtocolParams}; -use curve25519_dalek::edwards::CompressedEdwardsY; -use libp2p_core::UpgradeInfo; -use libp2p_identity as identity; -use libp2p_identity::ed25519; -use once_cell::sync::Lazy; -use rand::Rng; -use sha2::{Digest, Sha512}; -use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES}; -use zeroize::Zeroize; - -use super::*; - -static PARAMS_IK: Lazy = Lazy::new(|| { - "Noise_IK_25519_ChaChaPoly_SHA256" - .parse() - .map(ProtocolParams) - .expect("Invalid protocol name") -}); -static PARAMS_IX: Lazy = Lazy::new(|| { - "Noise_IX_25519_ChaChaPoly_SHA256" - .parse() - .map(ProtocolParams) - .expect("Invalid protocol name") -}); -static PARAMS_XX: Lazy = Lazy::new(|| { - "Noise_XX_25519_ChaChaPoly_SHA256" - .parse() - .map(ProtocolParams) - .expect("Invalid protocol name") -}); - -/// A X25519 key. -#[derive(Clone)] -pub struct X25519([u8; 32]); - -impl AsRef<[u8]> for X25519 { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl Zeroize for X25519 { - fn zeroize(&mut self) { - self.0.zeroize() - } -} - -impl UpgradeInfo for NoiseConfig { - type Info = &'static str; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once("/noise/ix/25519/chachapoly/sha256/0.1.0") - } -} - -impl UpgradeInfo for NoiseConfig { - type Info = &'static str; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once("/noise/xx/25519/chachapoly/sha256/0.1.0") - } -} - -impl UpgradeInfo for NoiseConfig { - type Info = &'static str; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once("/noise/ik/25519/chachapoly/sha256/0.1.0") - } -} - -/// Legacy Noise protocol for X25519. -/// -/// **Note**: This `Protocol` provides no configuration that -/// is interoperable with other libp2p implementations. -/// See [`crate::X25519Spec`] instead. -impl Protocol for X25519 { - fn params_ik() -> ProtocolParams { - PARAMS_IK.clone() - } - - fn params_ix() -> ProtocolParams { - PARAMS_IX.clone() - } - - fn params_xx() -> ProtocolParams { - PARAMS_XX.clone() - } - - fn public_from_bytes(bytes: &[u8]) -> Result, Error> { - if bytes.len() != 32 { - return Err(Error::InvalidLength); - } - let mut pk = [0u8; 32]; - pk.copy_from_slice(bytes); - Ok(PublicKey(X25519(pk))) - } - - #[allow(irrefutable_let_patterns)] - fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey) -> bool { - if let Ok(p) = identity::PublicKey::try_into_ed25519(id_pk.clone()) { - PublicKey::from_ed25519(&p).as_ref() == dh_pk.as_ref() - } else { - false - } - } -} - -impl Keypair { - /// Create a new X25519 keypair. - pub fn new() -> Keypair { - let mut sk_bytes = [0u8; 32]; - rand::thread_rng().fill(&mut sk_bytes); - let sk = SecretKey(X25519(sk_bytes)); // Copy - 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, 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) - #[allow(unreachable_patterns)] - pub fn from_identity(id_keys: &identity::Keypair) -> Option> { - let ed25519_keypair = id_keys.clone().try_into_ed25519().ok()?; - let kp = Keypair::from(SecretKey::from_ed25519(&ed25519_keypair.secret())); - let id = KeypairIdentity { - public: id_keys.public(), - signature: None, - }; - Some(AuthenticKeypair { - keypair: kp, - identity: id, - }) - } -} - -impl Default for Keypair { - fn default() -> Self { - Self::new() - } -} - -/// Promote a X25519 secret key into a keypair. -impl From> for Keypair { - fn from(secret: SecretKey) -> Keypair { - let public = PublicKey(X25519(x25519((secret.0).0, X25519_BASEPOINT_BYTES))); - Keypair { secret, public } - } -} - -impl PublicKey { - /// Construct a curve25519 public key from an Ed25519 public key. - pub fn from_ed25519(pk: &ed25519::PublicKey) -> Self { - PublicKey(X25519( - CompressedEdwardsY(pk.encode()) - .decompress() - .expect("An Ed25519 public key is a valid point by construction.") - .to_montgomery() - .0, - )) - } -} - -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, 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 - // the same to yield a Curve25519 keypair with the same public key. - // let ed25519_sk = ed25519::SecretKey::from(ed); - let mut curve25519_sk: [u8; 32] = [0; 32]; - let hash = Sha512::digest(ed25519_sk.as_ref()); - curve25519_sk.copy_from_slice(&hash[..32]); - let sk = SecretKey(X25519(curve25519_sk)); // Copy - curve25519_sk.zeroize(); - sk - } -} - -#[doc(hidden)] -impl snow::types::Dh for Keypair { - fn name(&self) -> &'static str { - "25519" - } - fn pub_len(&self) -> usize { - 32 - } - fn priv_len(&self) -> usize { - 32 - } - fn pubkey(&self) -> &[u8] { - self.public.as_ref() - } - fn privkey(&self) -> &[u8] { - self.secret.as_ref() - } - - fn set(&mut self, sk: &[u8]) { - let mut secret = [0u8; 32]; - secret.copy_from_slice(sk); - self.secret = SecretKey(X25519(secret)); // Copy - self.public = PublicKey(X25519(x25519(secret, X25519_BASEPOINT_BYTES))); - secret.zeroize(); - } - - fn generate(&mut self, rng: &mut dyn snow::types::Random) { - let mut secret = [0u8; 32]; - rng.fill_bytes(&mut secret); - self.secret = SecretKey(X25519(secret)); // Copy - self.public = PublicKey(X25519(x25519(secret, X25519_BASEPOINT_BYTES))); - secret.zeroize(); - } - - fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), snow::Error> { - let mut p = [0; 32]; - p.copy_from_slice(&pk[..32]); - let ss = x25519((self.secret.0).0, p); - shared_secret[..32].copy_from_slice(&ss[..]); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use libp2p_identity::ed25519; - use quickcheck::*; - - // The x25519 public key obtained through ed25519 keypair conversion - // (and thus derived from the converted secret key) must match the x25519 - // public key derived directly from the ed25519 public key. - #[test] - fn prop_public_ed25519_to_x25519_matches() { - fn prop() -> bool { - let ed25519 = ed25519::Keypair::generate(); - let x25519 = Keypair::from(SecretKey::from_ed25519(&ed25519.secret())); - let x25519_public = PublicKey::from_ed25519(&ed25519.public()); - x25519.public == x25519_public - } - - quickcheck(prop as fn() -> _); - } -} diff --git a/transports/noise/src/protocol/x25519_spec.rs b/transports/noise/src/protocol/x25519_spec.rs deleted file mode 100644 index 7f7a5a9c..00000000 --- a/transports/noise/src/protocol/x25519_spec.rs +++ /dev/null @@ -1,200 +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. - -//! [libp2p-noise-spec] compliant Noise protocols based on X25519. -//! -//! [libp2p-noise-spec]: https://github.com/libp2p/specs/tree/master/noise - -use crate::{Error, NoiseConfig, Protocol, ProtocolParams}; -use libp2p_core::UpgradeInfo; -use libp2p_identity as identity; -use rand::Rng; -use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES}; -use zeroize::Zeroize; - -use super::*; - -/// Prefix of static key signatures for domain separation. -const STATIC_KEY_DOMAIN: &str = "noise-libp2p-static-key:"; - -/// A X25519 key. -#[derive(Clone)] -pub struct X25519Spec([u8; 32]); - -impl AsRef<[u8]> for X25519Spec { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl Zeroize for X25519Spec { - fn zeroize(&mut self) { - self.0.zeroize() - } -} - -impl Keypair { - /// An "empty" keypair as a starting state for DH computations in `snow`, - /// which get manipulated through the `snow::types::Dh` interface. - pub(super) fn default() -> Self { - Keypair { - secret: SecretKey(X25519Spec([0u8; 32])), - public: PublicKey(X25519Spec([0u8; 32])), - } - } - - /// Create a new X25519 keypair. - pub fn new() -> Keypair { - let mut sk_bytes = [0u8; 32]; - rand::thread_rng().fill(&mut sk_bytes); - let sk = SecretKey(X25519Spec(sk_bytes)); // Copy - sk_bytes.zeroize(); - Self::from(sk) - } -} - -impl Default for Keypair { - fn default() -> Self { - Self::new() - } -} - -/// Promote a X25519 secret key into a keypair. -impl From> for Keypair { - fn from(secret: SecretKey) -> Keypair { - let public = PublicKey(X25519Spec(x25519((secret.0).0, X25519_BASEPOINT_BYTES))); - Keypair { secret, public } - } -} - -impl UpgradeInfo for NoiseConfig { - type Info = &'static str; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once("/noise") - } -} - -/// **Note**: This is not currentlyy a standardised upgrade. - -impl UpgradeInfo for NoiseConfig { - type Info = &'static str; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once("/noise/ix/25519/chachapoly/sha256/0.1.0") - } -} - -/// **Note**: This is not currently a standardised upgrade. - -impl UpgradeInfo for NoiseConfig { - type Info = &'static str; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once("/noise/ik/25519/chachapoly/sha256/0.1.0") - } -} - -/// Noise protocols for X25519 with libp2p-spec compliant signatures. -/// -/// **Note**: Only the XX handshake pattern is currently guaranteed to be -/// interoperable with other libp2p implementations. -impl Protocol for X25519Spec { - fn params_ik() -> ProtocolParams { - x25519::X25519::params_ik() - } - - fn params_ix() -> ProtocolParams { - x25519::X25519::params_ix() - } - - fn params_xx() -> ProtocolParams { - x25519::X25519::params_xx() - } - - fn public_from_bytes(bytes: &[u8]) -> Result, Error> { - if bytes.len() != 32 { - return Err(Error::InvalidLength); - } - let mut pk = [0u8; 32]; - pk.copy_from_slice(bytes); - Ok(PublicKey(X25519Spec(pk))) - } - - fn verify( - id_pk: &identity::PublicKey, - dh_pk: &PublicKey, - sig: &Option>, - ) -> bool { - sig.as_ref().map_or(false, |s| { - id_pk.verify(&[STATIC_KEY_DOMAIN.as_bytes(), dh_pk.as_ref()].concat(), s) - }) - } - - fn sign(id_keys: &identity::Keypair, dh_pk: &PublicKey) -> Result, Error> { - Ok(id_keys.sign(&[STATIC_KEY_DOMAIN.as_bytes(), dh_pk.as_ref()].concat())?) - } -} - -#[doc(hidden)] -impl snow::types::Dh for Keypair { - fn name(&self) -> &'static str { - "25519" - } - fn pub_len(&self) -> usize { - 32 - } - fn priv_len(&self) -> usize { - 32 - } - fn pubkey(&self) -> &[u8] { - self.public.as_ref() - } - fn privkey(&self) -> &[u8] { - self.secret.as_ref() - } - - fn set(&mut self, sk: &[u8]) { - let mut secret = [0u8; 32]; - secret.copy_from_slice(sk); - self.secret = SecretKey(X25519Spec(secret)); // Copy - self.public = PublicKey(X25519Spec(x25519(secret, X25519_BASEPOINT_BYTES))); - secret.zeroize(); - } - - fn generate(&mut self, rng: &mut dyn snow::types::Random) { - let mut secret = [0u8; 32]; - rng.fill_bytes(&mut secret); - self.secret = SecretKey(X25519Spec(secret)); // Copy - self.public = PublicKey(X25519Spec(x25519(secret, X25519_BASEPOINT_BYTES))); - secret.zeroize(); - } - - fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), snow::Error> { - let mut p = [0; 32]; - p.copy_from_slice(&pk[..32]); - let ss = x25519((self.secret.0).0, p); - shared_secret[..32].copy_from_slice(&ss[..]); - Ok(()) - } -}