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: verify expected PeerId as part of security handshake #4864

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6748e17
feat: MVP verification of the expected `PeerId` as part of a protocol…
denis2glez Nov 14, 2023
dbedc1d
chore: fix a typo in the doc comments
denis2glez Nov 15, 2023
d800320
fix: introduce `Builder::authenticate2` as an alternative code path
denis2glez Nov 16, 2023
af48270
docs: improve comments related to `Builder::authenticate2`
denis2glez Nov 16, 2023
31c7e61
fix: add `Send` constraint to `Builder::authenticate2` parameters
denis2glez Nov 17, 2023
5c5b728
refactor: split `SecurityUpgrade` into two alternative traits
denis2glez Nov 17, 2023
b686801
docs: mark items related to `Builder::authenticate2` as `#[doc(hidden)]`
denis2glez Nov 17, 2023
c0fad3a
feat: impl `InboundSecurityUpgrade`/`OutboundSecurityUpgrade` for TLS
denis2glez Nov 17, 2023
399666b
feat: impl `InboundSecurityUpgrade`/`OutboundSecurityUpgrade` for Noise
denis2glez Nov 17, 2023
71e0ac8
chore: minor doc comments fixes
denis2glez Nov 19, 2023
5500663
refactor: remove the `PeerId` parameter in `InboundSecurityUpgrade`
denis2glez Nov 19, 2023
a726083
docs: fix several issues in the comments
denis2glez Nov 22, 2023
514b220
refactor: delegate `upgrade_inbound` and `upgrade_outbound` methods
denis2glez Nov 22, 2023
66df049
fix: references to actual and remote `PeerId` respectively
denis2glez Nov 22, 2023
d8440e7
chore: keep grouping of module and use declarations
denis2glez Nov 24, 2023
c386182
fix: make client and server config ad-hoc in `secure_outbound` method
denis2glez Nov 24, 2023
3eb5f1f
fix: use the wording of `actual` and `expected` PeerId
denis2glez Nov 27, 2023
2ede11c
refactor: store the `identity::Keypair` within `Config`
denis2glez Nov 27, 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
24 changes: 9 additions & 15 deletions core/src/transport/upgrade.rs
denis2glez marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ use crate::{
},
upgrade::{
self, apply_inbound, apply_outbound, InboundConnectionUpgrade, InboundUpgradeApply,
OutboundConnectionUpgrade, OutboundUpgradeApply, UpgradeError,
OutboundConnectionUpgrade, OutboundUpgradeApply, SecurityUpgrade, UpgradeError,
},
Negotiated,
};
use futures::{prelude::*, ready};
use futures::{future::LocalBoxFuture, prelude::*, ready};
use libp2p_identity::PeerId;
use multiaddr::Multiaddr;
use std::{
Expand Down Expand Up @@ -99,16 +99,15 @@ where
) -> Authenticated<AndThen<T, impl FnOnce(C, ConnectedPoint) -> Authenticate<C, U> + Clone>>
where
T: Transport<Output = C>,
C: AsyncRead + AsyncWrite + Unpin,
C: AsyncRead + AsyncWrite + Unpin + 'static,
D: AsyncRead + AsyncWrite + Unpin,
U: InboundConnectionUpgrade<Negotiated<C>, Output = (PeerId, D), Error = E>,
U: OutboundConnectionUpgrade<Negotiated<C>, Output = (PeerId, D), Error = E> + Clone,
U: SecurityUpgrade<Negotiated<C>, Output = (PeerId, D), Error = E> + Clone + 'static,
E: Error + 'static,
{
let version = self.version;
Authenticated(Builder::new(
self.inner.and_then(move |conn, endpoint| Authenticate {
inner: upgrade::apply(conn, upgrade, endpoint, version),
inner: upgrade::secure(conn, upgrade, endpoint, version).boxed_local(),
denis2glez marked this conversation as resolved.
Show resolved Hide resolved
}),
version,
))
Expand All @@ -123,23 +122,18 @@ where
pub struct Authenticate<C, U>
where
C: AsyncRead + AsyncWrite + Unpin,
U: InboundConnectionUpgrade<Negotiated<C>> + OutboundConnectionUpgrade<Negotiated<C>>,
U: SecurityUpgrade<Negotiated<C>>,
{
#[pin]
inner: EitherUpgrade<C, U>,
inner: LocalBoxFuture<'static, Result<U::Output, UpgradeError<U::Error>>>,
}

impl<C, U> Future for Authenticate<C, U>
where
C: AsyncRead + AsyncWrite + Unpin,
U: InboundConnectionUpgrade<Negotiated<C>>
+ OutboundConnectionUpgrade<
Negotiated<C>,
Output = <U as InboundConnectionUpgrade<Negotiated<C>>>::Output,
Error = <U as InboundConnectionUpgrade<Negotiated<C>>>::Error,
>,
U: SecurityUpgrade<Negotiated<C>>,
{
type Output = <EitherUpgrade<C, U> as Future>::Output;
type Output = Result<U::Output, UpgradeError<U::Error>>;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
Expand Down
31 changes: 26 additions & 5 deletions core/src/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,21 @@ mod either;
mod error;
mod pending;
mod ready;
mod secure;
mod select;

pub use self::{
denied::DeniedUpgrade, pending::PendingUpgrade, ready::ReadyUpgrade, select::SelectUpgrade,
};
pub use crate::Negotiated;
pub(crate) use apply::{
apply, apply_inbound, apply_outbound, InboundUpgradeApply, OutboundUpgradeApply,
};
pub(crate) use error::UpgradeError;
use futures::future::Future;

pub use self::{
denied::DeniedUpgrade, pending::PendingUpgrade, ready::ReadyUpgrade, select::SelectUpgrade,
};
pub use crate::Negotiated;
use libp2p_identity::PeerId;
pub use multistream_select::{NegotiatedComplete, NegotiationError, ProtocolError, Version};
pub(crate) use secure::secure;

/// Common trait for upgrades that can be applied on inbound substreams, outbound substreams,
/// or both.
Expand Down Expand Up @@ -152,3 +154,22 @@ pub trait OutboundConnectionUpgrade<T>: UpgradeInfo {
/// The `info` is the identifier of the protocol, as produced by `protocol_info`.
fn upgrade_outbound(self, socket: T, info: Self::Info) -> Self::Future;
}

/// Possible security upgrade on a connection
pub trait SecurityUpgrade<T>: UpgradeInfo {
thomaseizinger marked this conversation as resolved.
Show resolved Hide resolved
/// Output after the upgrade has been successfully negotiated and the handshake performed.
type Output;
/// Possible error during the handshake.
type Error;
/// Future that performs the handshake with the remote.
type Future: Future<Output = Result<Self::Output, Self::Error>>;
denis2glez marked this conversation as resolved.
Show resolved Hide resolved

/// After we have determined that the remote supports one of the protocols we support, this
/// method is called to start the handshake.
///
/// The `info` is the identifier of the protocol, as produced by `protocol_info`. Security
/// transports use the optional `peer_id` parameter on outgoing upgrades to validate validate
/// the expected `PeerId`.
fn upgrade_security(self, socket: T, info: Self::Info, peer_id: Option<PeerId>)
-> Self::Future;
}
88 changes: 88 additions & 0 deletions core/src/upgrade/secure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! After a successful protocol negotiation as part of the upgrade process, the `SecurityUpgrade::upgrade_security`
//! method is called and a [`Future`] that performs a handshake is returned.

use crate::upgrade::{SecurityUpgrade, UpgradeError};
use crate::{connection::ConnectedPoint, Negotiated};
use futures::prelude::*;

use libp2p_identity::PeerId;
use multiaddr::Protocol;
use multistream_select::Version;

/// Applies a security upgrade to the inbound and outbound direction of a connection or substream.
pub(crate) async fn secure<C, U>(
conn: C,
up: U,
cp: ConnectedPoint,
v: Version,
) -> Result<U::Output, UpgradeError<U::Error>>
where
C: AsyncRead + AsyncWrite + Unpin,
U: SecurityUpgrade<Negotiated<C>>,
{
match cp {
ConnectedPoint::Dialer { role_override, .. } if role_override.is_dialer() => {
let peer_id = cp
.get_remote_address()
.iter()
.find_map(|protocol| match protocol {
Protocol::P2p(peer_id) => Some(peer_id),
_ => None,
})
.expect("It should have /p2p as part of the address");
denis2glez marked this conversation as resolved.
Show resolved Hide resolved
secure_outbound(conn, up, Some(peer_id), v).await
}
_ => secure_inbound(conn, up, None).await,
}
}

/// Tries to perform a security upgrade on an inbound connection or substream.
async fn secure_inbound<C, U>(
conn: C,
up: U,
peer_id: Option<PeerId>,
) -> Result<U::Output, UpgradeError<U::Error>>
where
C: AsyncRead + AsyncWrite + Unpin,
U: SecurityUpgrade<Negotiated<C>>,
{
let (info, stream) =
multistream_select::listener_select_proto(conn, up.protocol_info()).await?;
let name = info.as_ref().to_owned();
match up.upgrade_security(stream, info, peer_id).await {
Ok(x) => {
tracing::trace!(up=%name, "Secured inbound stream");
Ok(x)
}
Err(e) => {
tracing::trace!(up=%name, "Failed to secure inbound stream");
Err(UpgradeError::Apply(e))
}
}
}
denis2glez marked this conversation as resolved.
Show resolved Hide resolved

/// Tries to perform a security upgrade on an outbound connection or substream.
async fn secure_outbound<C, U>(
conn: C,
up: U,
peer_id: Option<PeerId>,
v: Version,
) -> Result<U::Output, UpgradeError<U::Error>>
where
C: AsyncRead + AsyncWrite + Unpin,
U: SecurityUpgrade<Negotiated<C>>,
{
let (info, stream) =
multistream_select::dialer_select_proto(conn, up.protocol_info(), v).await?;
let name = info.as_ref().to_owned();
match up.upgrade_security(stream, info, peer_id).await {
Ok(x) => {
tracing::trace!(up=%name, "Secured outbound stream");
Ok(x)
}
Err(e) => {
tracing::trace!(up=%name, "Failed to secure outbound stream");
Err(UpgradeError::Apply(e))
}
}
}
Loading