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

protocols/noise: Inline handshake functions into upgrade traits #2909

Merged
merged 31 commits into from Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0002da3
Don't export `Handshake` module
thomaseizinger Sep 16, 2022
6022e59
Inline handshake functions
thomaseizinger Sep 16, 2022
e40ac56
Remove `Handshake` type in favor of BoxFuture
thomaseizinger Sep 16, 2022
ea42fe3
Move construction of session into future
thomaseizinger Sep 16, 2022
bdb6678
Make better use of `?`
thomaseizinger Sep 16, 2022
18e6eb4
Remove `IdentityExchange`
thomaseizinger Sep 16, 2022
7d0c18a
Don't use Deref for things that are not smart pointers
thomaseizinger Sep 16, 2022
269c821
Drop unnecessary generic parameter
thomaseizinger Sep 16, 2022
4b16a45
Adopt docs
thomaseizinger Sep 16, 2022
5072674
Add changelog entry and bump version
thomaseizinger Sep 16, 2022
302a4ab
Update transports/noise/CHANGELOG.md
thomaseizinger Sep 16, 2022
53638b9
Fix clippy warning
thomaseizinger Sep 16, 2022
39552a2
Fix doc links
thomaseizinger Sep 16, 2022
5adfe25
Merge branch 'master' into simplify-noise
thomaseizinger Sep 26, 2022
ae18dd7
Move `into_{responder,initiator}` to `ProtocolParams`
thomaseizinger Sep 26, 2022
fbcbbe7
Merge branch 'master' into simplify-noise
thomaseizinger Sep 30, 2022
fa6ad2b
Apply suggestions from code review
thomaseizinger Sep 30, 2022
9347c9b
Merge branch 'master' into simplify-noise
thomaseizinger Oct 4, 2022
49835a7
Fix bad roles in noise handshake
thomaseizinger Oct 4, 2022
7ce7eef
Merge branch 'simplify-noise' of github.com:libp2p/rust-libp2p into s…
thomaseizinger Oct 4, 2022
020642a
Don't import `BoxFuture` through imported module
thomaseizinger Oct 4, 2022
b2e9df5
Move traits to where clause
thomaseizinger Oct 4, 2022
099c985
Move `into_initiator` and `into_responder` back to `NoiseConfig`
thomaseizinger Oct 4, 2022
1a65ac7
Revert "Move traits to where clause"
thomaseizinger Oct 4, 2022
ee48163
Use backticks in docs
thomaseizinger Oct 4, 2022
b5cdfcb
Don't expose entire DH keypair
thomaseizinger Oct 4, 2022
25927eb
Merge branch 'master' into simplify-noise
thomaseizinger Oct 9, 2022
ec35ad5
Merge branch 'master' into simplify-noise
thomaseizinger Oct 11, 2022
79f3983
Fix clippy error
thomaseizinger Oct 11, 2022
d1a7d3a
Merge branch 'simplify-noise' of github.com:libp2p/rust-libp2p into s…
thomaseizinger Oct 11, 2022
deec76f
Merge branch 'master' into simplify-noise
mxinden Oct 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions transports/noise/CHANGELOG.md
Expand Up @@ -5,9 +5,13 @@
- Introduce `NoiseAuthenticated::xx` constructor, assuming a X25519 DH key exchange. An XX key exchange and X25519 keys
are the most common way of using noise in libp2p and thus deserve a convenience constructor. See [PR 2887].
- Add `NoiseConfig::with_prologue` which allows users to set the noise prologue of the handshake. See [PR 2903].
- Remove `Deref` implementation on `AuthenticKeypair`. See [PR 2909].
- Make `handshake` module private. See [PR 2909].
- Deprecate `AuthenticKeypair::into_identity`. See [PR 2909].

[PR 2887]: https://github.com/libp2p/rust-libp2p/pull/2887
[PR 2903]: https://github.com/libp2p/rust-libp2p/pull/2903
[PR 2909]: https://github.com/libp2p/rust-libp2p/pull/2909

# 0.39.0

Expand Down
262 changes: 46 additions & 216 deletions transports/noise/src/io/handshake.rs
Expand Up @@ -31,10 +31,9 @@ use crate::protocol::{KeypairIdentity, Protocol, PublicKey};
use crate::LegacyConfig;
use bytes::Bytes;
use futures::prelude::*;
use futures::task;
use libp2p_core::identity;
use prost::Message;
use std::{io, pin::Pin, task::Context};
use std::io;

/// The identity of the remote established during a handshake.
pub enum RemoteIdentity<C> {
Expand Down Expand Up @@ -63,199 +62,11 @@ pub enum RemoteIdentity<C> {
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 },
}

/// A future performing a Noise handshake pattern.
pub struct Handshake<T, C>(
Pin<Box<dyn Future<Output = Result<(RemoteIdentity<C>, NoiseOutput<T>), NoiseError>> + Send>>,
);

impl<T, C> Future for Handshake<T, C> {
type Output = Result<(RemoteIdentity<C>, NoiseOutput<T>), NoiseError>;

fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> task::Poll<Self::Output> {
Pin::new(&mut self.0).poll(ctx)
}
}

/// 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<T, C>(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Handshake<T, C>
where
T: AsyncWrite + AsyncRead + Send + Unpin + 'static,
C: Protocol<C> + AsRef<[u8]>,
{
Handshake(Box::pin(async move {
let mut state = State::new(io, session, identity, identity_x, legacy)?;
send_identity(&mut state).await?;
recv_identity(&mut state).await?;
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<T, C>(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Handshake<T, C>
where
T: AsyncWrite + AsyncRead + Send + Unpin + 'static,
C: Protocol<C> + AsRef<[u8]>,
{
Handshake(Box::pin(async move {
let mut state = State::new(io, session, identity, identity_x, legacy)?;
recv_identity(&mut state).await?;
send_identity(&mut state).await?;
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<T, C>(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Handshake<T, C>
where
T: AsyncWrite + AsyncRead + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]>,
{
Handshake(Box::pin(async move {
let mut state = State::new(io, session, identity, identity_x, legacy)?;
send_empty(&mut state).await?;
recv_identity(&mut state).await?;
send_identity(&mut state).await?;
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<T, C>(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Handshake<T, C>
where
T: AsyncWrite + AsyncRead + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]>,
{
Handshake(Box::pin(async move {
let mut state = State::new(io, session, identity, identity_x, legacy)?;
recv_empty(&mut state).await?;
send_identity(&mut state).await?;
recv_identity(&mut state).await?;
state.finish()
}))
}

//////////////////////////////////////////////////////////////////////////////
// Internal

/// Handshake state.
struct State<T> {
pub struct State<T> {
/// The underlying I/O resource.
io: NoiseFramed<T, snow::HandshakeState>,
/// The associated public identity of the local node's static DH keypair,
Expand All @@ -265,8 +76,6 @@ 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>,
/// Whether to send the public identity key of the local node to the remote.
send_identity: bool,
/// Legacy configuration parameters.
legacy: LegacyConfig,
}
Expand All @@ -277,34 +86,27 @@ impl<T> State<T> {
/// 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(
pub fn new(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
session: snow::HandshakeState,
identity: KeypairIdentity,
identity_x: IdentityExchange,
expected_remote_key: Option<identity::PublicKey>,
legacy: LegacyConfig,
) -> Result<Self, NoiseError> {
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),
};
session.map(|s| State {
) -> Self {
Self {
identity,
io: NoiseFramed::new(io, s),
io: NoiseFramed::new(io, session),
dh_remote_pubkey_sig: None,
id_remote_pubkey,
send_identity,
id_remote_pubkey: expected_remote_key,
legacy,
})
}
}
}

impl<T> State<T> {
/// Finish a handshake, yielding the established remote identity and the
/// [`NoiseOutput`] for communicating on the encrypted channel.
fn finish<C>(self) -> Result<(RemoteIdentity<C>, NoiseOutput<T>), NoiseError>
pub fn finish<C>(self) -> Result<(RemoteIdentity<C>, NoiseOutput<T>), NoiseError>
where
C: Protocol<C> + AsRef<[u8]>,
{
Expand Down Expand Up @@ -340,7 +142,7 @@ where
}

/// A future for receiving a Noise handshake message with an empty payload.
async fn recv_empty<T>(state: &mut State<T>) -> Result<(), NoiseError>
pub async fn recv_empty<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncRead + Unpin,
{
Expand All @@ -354,7 +156,7 @@ where
}

/// A future for sending a Noise handshake message with an empty payload.
async fn send_empty<T>(state: &mut State<T>) -> Result<(), NoiseError>
pub async fn send_empty<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncWrite + Unpin,
{
Expand All @@ -364,7 +166,10 @@ where

/// A future for receiving a Noise handshake message with a payload
/// identifying the remote.
async fn recv_identity<T>(state: &mut State<T>) -> Result<(), NoiseError>
///
/// 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.
pub async fn recv_identity<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncRead + Unpin,
{
Expand Down Expand Up @@ -421,16 +226,41 @@ where
}

/// Send a Noise handshake message with a payload identifying the local node to the remote.
async fn send_identity<T>(state: &mut State<T>) -> Result<(), NoiseError>
pub async fn send_identity<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncWrite + Unpin,
{
let mut pb = payload_proto::NoiseHandshakePayload::default();
let mut pb = payload_proto::NoiseHandshakePayload {
identity_key: state.identity.public.to_protobuf_encoding(),
..Default::default()
};

if state.send_identity {
pb.identity_key = state.identity.public.to_protobuf_encoding()
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.encoded_len());
msg.extend_from_slice(&(pb.encoded_len() as u16).to_be_bytes());
msg
} else {
Vec::with_capacity(pb.encoded_len())
};

pb.encode(&mut msg)
.expect("Vec<u8> provides capacity as needed");
state.io.send(&msg).await?;

Ok(())
}

/// Send a Noise handshake message with a payload identifying the local node to the remote.
pub async fn send_signature_only<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncWrite + Unpin,
{
let mut pb = payload_proto::NoiseHandshakePayload::default();

if let Some(ref sig) = state.identity.signature {
pb.identity_sig = sig.clone()
}
Expand Down