Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(noise): remove deprecated legacy handshakes #3511

Merged
merged 35 commits into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3816539
Remove tests for deprecated noise spec
thomaseizinger Feb 27, 2023
51c7483
Remove legacy noise handshake
thomaseizinger Feb 27, 2023
a9870bd
Fix fmt and bump `libp2p-noise`
thomaseizinger Feb 27, 2023
d0d21a8
Merge branch 'master' into ft/remove-legacy-noise-impl
thomaseizinger Mar 22, 2023
d912f53
Merge branch 'master' into ft/remove-legacy-noise-impl
thomaseizinger Apr 3, 2023
e3380de
Add PR number
thomaseizinger Apr 3, 2023
1bea5b3
Merge branch 'master' into ft/remove-legacy-noise-impl
thomaseizinger May 1, 2023
2dcdd64
Remove other patterns
thomaseizinger May 1, 2023
34c8f01
Reduce type parameters
thomaseizinger May 1, 2023
733e9b1
Remove a few layers
thomaseizinger May 1, 2023
be3c882
Fail if we don't receive a remote key
thomaseizinger May 1, 2023
8ad9a60
Remove RemoteIdentity::Unknown
thomaseizinger May 1, 2023
40aa9e3
Remove `RemoteIdentity`
thomaseizinger May 1, 2023
e781793
Remove another type parameter
thomaseizinger May 1, 2023
8d06d44
Inline `public_from_bytes`
thomaseizinger May 1, 2023
ef7b398
Inline `verify` function
thomaseizinger May 1, 2023
2a12868
Remove `Protocol` abstraction
thomaseizinger May 1, 2023
5deb7bc
Rename fn to empty to avoid collision with `Default` trait
thomaseizinger May 1, 2023
6c8c8b4
Remove separate "spec" wrapper
thomaseizinger May 1, 2023
028a9e2
Inline `spec` module into `protocol`
thomaseizinger May 1, 2023
1204dc6
Remove `XX` type
thomaseizinger May 1, 2023
a257210
Remove unused public functions
thomaseizinger May 1, 2023
ed91e68
Remove empty lines
thomaseizinger May 1, 2023
cdb8c41
Shrink public API
thomaseizinger May 1, 2023
4a234f8
Remove `ProtocolParams` wrapper
thomaseizinger May 1, 2023
e7925bb
Remove unused `Option`
thomaseizinger May 1, 2023
1c14699
Merge impl blocks
thomaseizinger May 1, 2023
a121b6f
Merge branch 'master' into ft/remove-legacy-noise-impl
thomaseizinger May 2, 2023
785d572
Fix error in creating signature
thomaseizinger May 2, 2023
3adf4d6
Fix compile error
thomaseizinger May 3, 2023
60f117f
Fix gossipsub doc test
thomaseizinger May 3, 2023
8da826a
Fix libp2p doc tests
thomaseizinger May 3, 2023
975958b
Merge branch 'master' into ft/remove-legacy-noise-impl
thomaseizinger May 4, 2023
f530608
Merge branch 'master' into ft/remove-legacy-noise-impl
thomaseizinger May 5, 2023
36836bf
Merge branch 'master' into ft/remove-legacy-noise-impl
mergify[bot] May 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libp2p/src/transport_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
17 changes: 9 additions & 8 deletions protocols/gossipsub/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
//!
Expand All @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion transports/noise/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
20 changes: 10 additions & 10 deletions transports/noise/src/io/framed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -89,15 +89,15 @@ impl<T> NoiseFramed<T, snow::HandshakeState> {
/// 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<C>(self) -> Result<(Option<PublicKey<C>>, Output<T>), Error>
where
C: Protocol<C> + 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<T>), 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()?,
Expand Down
157 changes: 21 additions & 136 deletions transports/noise/src/io/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<C> {
/// 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<C>),

/// 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

Expand All @@ -81,8 +49,6 @@ pub(crate) struct State<T> {
dh_remote_pubkey_sig: Option<Vec<u8>>,
/// The known or received public identity key of the remote, if any.
id_remote_pubkey: Option<identity::PublicKey>,
/// Legacy configuration parameters.
legacy: LegacyConfig,
}

impl<T> State<T> {
Expand All @@ -97,38 +63,35 @@ impl<T> State<T> {
session: snow::HandshakeState,
identity: KeypairIdentity,
expected_remote_key: Option<identity::PublicKey>,
legacy: LegacyConfig,
) -> Self {
Self {
identity,
io: NoiseFramed::new(io, session),
dh_remote_pubkey_sig: None,
id_remote_pubkey: expected_remote_key,
legacy,
}
}
}

impl<T> State<T> {
/// Finish a handshake, yielding the established remote identity and the
/// [`Output`] for communicating on the encrypted channel.
pub(crate) fn finish<C>(self) -> Result<(RemoteIdentity<C>, Output<T>), Error>
where
C: Protocol<C> + AsRef<[u8]>,
{
pub(crate) fn finish(self) -> Result<(identity::PublicKey, Output<T>), 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))
}
}

Expand Down Expand Up @@ -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<T>(state: &mut State<T>) -> 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);
Expand All @@ -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<T>(state: &mut State<T>) -> 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");
Expand Down
Loading