Skip to content

Commit

Permalink
protocols/noise: Inline handshake functions into upgrade traits (#2909
Browse files Browse the repository at this point in the history
)

- Remove `Deref` implementation on `AuthenticKeypair`.
- Make `handshake` module private.
- Deprecate `AuthenticKeypair::into_identity`.
  • Loading branch information
thomaseizinger committed Oct 11, 2022
1 parent 31a45f2 commit eba2efe
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 375 deletions.
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

0 comments on commit eba2efe

Please sign in to comment.