diff --git a/transports/noise/CHANGELOG.md b/transports/noise/CHANGELOG.md index bae8fd51edb..6c7b94c5876 100644 --- a/transports/noise/CHANGELOG.md +++ b/transports/noise/CHANGELOG.md @@ -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 diff --git a/transports/noise/src/io/handshake.rs b/transports/noise/src/io/handshake.rs index 35099ea84dc..c32eef9690f 100644 --- a/transports/noise/src/io/handshake.rs +++ b/transports/noise/src/io/handshake.rs @@ -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 { @@ -63,199 +62,11 @@ pub enum RemoteIdentity { 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( - Pin, NoiseOutput), NoiseError>> + Send>>, -); - -impl Future for Handshake { - type Output = Result<(RemoteIdentity, NoiseOutput), NoiseError>; - - fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> task::Poll { - 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( - io: T, - session: Result, - identity: KeypairIdentity, - identity_x: IdentityExchange, - legacy: LegacyConfig, -) -> Handshake -where - T: AsyncWrite + AsyncRead + Send + Unpin + 'static, - C: Protocol + 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( - io: T, - session: Result, - identity: KeypairIdentity, - identity_x: IdentityExchange, - legacy: LegacyConfig, -) -> Handshake -where - T: AsyncWrite + AsyncRead + Send + Unpin + 'static, - C: Protocol + 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( - io: T, - session: Result, - identity: KeypairIdentity, - identity_x: IdentityExchange, - legacy: LegacyConfig, -) -> Handshake -where - T: AsyncWrite + AsyncRead + Unpin + Send + 'static, - C: Protocol + 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( - io: T, - session: Result, - identity: KeypairIdentity, - identity_x: IdentityExchange, - legacy: LegacyConfig, -) -> Handshake -where - T: AsyncWrite + AsyncRead + Unpin + Send + 'static, - C: Protocol + 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 { +pub struct State { /// The underlying I/O resource. io: NoiseFramed, /// The associated public identity of the local node's static DH keypair, @@ -265,8 +76,6 @@ struct State { dh_remote_pubkey_sig: Option>, /// The known or received public identity key of the remote, if any. id_remote_pubkey: Option, - /// Whether to send the public identity key of the local node to the remote. - send_identity: bool, /// Legacy configuration parameters. legacy: LegacyConfig, } @@ -277,34 +86,27 @@ impl State { /// 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, + session: snow::HandshakeState, identity: KeypairIdentity, - identity_x: IdentityExchange, + expected_remote_key: Option, legacy: LegacyConfig, - ) -> Result { - 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 State { /// Finish a handshake, yielding the established remote identity and the /// [`NoiseOutput`] for communicating on the encrypted channel. - fn finish(self) -> Result<(RemoteIdentity, NoiseOutput), NoiseError> + pub fn finish(self) -> Result<(RemoteIdentity, NoiseOutput), NoiseError> where C: Protocol + AsRef<[u8]>, { @@ -340,7 +142,7 @@ where } /// A future for receiving a Noise handshake message with an empty payload. -async fn recv_empty(state: &mut State) -> Result<(), NoiseError> +pub async fn recv_empty(state: &mut State) -> Result<(), NoiseError> where T: AsyncRead + Unpin, { @@ -354,7 +156,7 @@ where } /// A future for sending a Noise handshake message with an empty payload. -async fn send_empty(state: &mut State) -> Result<(), NoiseError> +pub async fn send_empty(state: &mut State) -> Result<(), NoiseError> where T: AsyncWrite + Unpin, { @@ -364,7 +166,10 @@ where /// A future for receiving a Noise handshake message with a payload /// identifying the remote. -async fn recv_identity(state: &mut State) -> 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(state: &mut State) -> Result<(), NoiseError> where T: AsyncRead + Unpin, { @@ -421,16 +226,41 @@ where } /// Send a Noise handshake message with a payload identifying the local node to the remote. -async fn send_identity(state: &mut State) -> Result<(), NoiseError> +pub async fn send_identity(state: &mut State) -> 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 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(state: &mut State) -> 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() } diff --git a/transports/noise/src/lib.rs b/transports/noise/src/lib.rs index c1b55d2076b..ac341c2802d 100644 --- a/transports/noise/src/lib.rs +++ b/transports/noise/src/lib.rs @@ -58,16 +58,17 @@ mod io; mod protocol; pub use error::NoiseError; -pub use io::handshake; -pub use io::handshake::{Handshake, IdentityExchange, RemoteIdentity}; +pub use io::handshake::RemoteIdentity; pub use io::NoiseOutput; pub use protocol::{x25519::X25519, x25519_spec::X25519Spec}; pub use protocol::{AuthenticKeypair, Keypair, KeypairIdentity, PublicKey, SecretKey}; pub use protocol::{Protocol, ProtocolParams, IK, IX, XX}; +use crate::handshake::State; +use crate::io::handshake; +use futures::future::BoxFuture; use futures::prelude::*; use libp2p_core::{identity, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; -use snow::HandshakeState; use std::pin::Pin; use zeroize::Zeroize; @@ -108,30 +109,31 @@ impl NoiseConfig { } } -impl NoiseConfig +/// Implement `into_responder` and `into_initiator` for all configs where `R = ()`. +/// +/// This allows us to ignore the `remote` field. +impl NoiseConfig where - C: Zeroize + AsRef<[u8]>, + C: Zeroize + Protocol + AsRef<[u8]>, { - fn into_responder(self) -> Result { - let state = self + fn into_responder(self, socket: S) -> Result, NoiseError> { + let session = self .params - .into_builder() - .prologue(self.prologue.as_ref()) - .local_private_key(self.dh_keys.secret().as_ref()) - .build_responder() - .map_err(NoiseError::from)?; + .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) -> Result { - let state = self + fn into_initiator(self, socket: S) -> Result, NoiseError> { + let session = self .params - .into_builder() - .prologue(self.prologue.as_ref()) - .local_private_key(self.dh_keys.secret().as_ref()) - .build_initiator() - .map_err(NoiseError::from)?; + .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) } @@ -193,7 +195,7 @@ where impl NoiseConfig, identity::PublicKey)> where - C: Protocol + Zeroize, + C: Protocol + Zeroize + AsRef<[u8]>, { /// Create a new `NoiseConfig` for the `IK` handshake pattern (initiator side). /// @@ -213,10 +215,38 @@ where prologue: Vec::default(), } } -} -// Handshake pattern IX ///////////////////////////////////////////////////// + /// Specialised implementation of `into_initiator` for the `IK` handshake where `R != ()`. + fn into_initiator(self, socket: S) -> Result, NoiseError> { + 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) + } +} +/// 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, @@ -225,22 +255,29 @@ where { type Output = (RemoteIdentity, NoiseOutput); type Error = NoiseError; - type Future = Handshake; + type Future = BoxFuture<'static, Result<(RemoteIdentity, NoiseOutput), NoiseError>>; fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - let config = self.legacy; - let identity = self.dh_keys.clone().into_identity(); + async move { + let mut state = self.into_responder(socket)?; - handshake::rt1_responder( - socket, - self.into_responder(), - identity, - IdentityExchange::Mutual, - config, - ) + 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, @@ -249,24 +286,33 @@ where { type Output = (RemoteIdentity, NoiseOutput); type Error = NoiseError; - type Future = Handshake; + type Future = BoxFuture<'static, Result<(RemoteIdentity, NoiseOutput), NoiseError>>; fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - let legacy = self.legacy; - let identity = self.dh_keys.clone().into_identity(); + async move { + let mut state = self.into_initiator(socket)?; - handshake::rt1_initiator( - socket, - self.into_initiator(), - identity, - IdentityExchange::Mutual, - legacy, - ) + handshake::send_identity(&mut state).await?; + handshake::recv_identity(&mut state).await?; + + state.finish() + } + .boxed() } } -// Handshake pattern XX ///////////////////////////////////////////////////// - +/// 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, @@ -275,22 +321,34 @@ where { type Output = (RemoteIdentity, NoiseOutput); type Error = NoiseError; - type Future = Handshake; + type Future = BoxFuture<'static, Result<(RemoteIdentity, NoiseOutput), NoiseError>>; fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - let legacy = self.legacy; - let identity = self.dh_keys.clone().into_identity(); + async move { + let mut state = self.into_responder(socket)?; - handshake::rt15_responder( - socket, - self.into_responder(), - identity, - IdentityExchange::Mutual, - legacy, - ) + 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, @@ -299,48 +357,67 @@ where { type Output = (RemoteIdentity, NoiseOutput); type Error = NoiseError; - type Future = Handshake; + type Future = BoxFuture<'static, Result<(RemoteIdentity, NoiseOutput), NoiseError>>; fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - let legacy = self.legacy; - let identity = self.dh_keys.clone().into_identity(); + async move { + let mut state = self.into_initiator(socket)?; - handshake::rt15_initiator( - socket, - self.into_initiator(), - identity, - IdentityExchange::Mutual, - legacy, - ) + handshake::send_empty(&mut state).await?; + handshake::recv_identity(&mut state).await?; + handshake::send_identity(&mut state).await?; + + state.finish() + } + .boxed() } } -// Handshake pattern IK ///////////////////////////////////////////////////// - -impl InboundUpgrade for NoiseConfig +/// 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, + NoiseConfig: UpgradeInfo, T: AsyncRead + AsyncWrite + Unpin + Send + 'static, C: Protocol + AsRef<[u8]> + Zeroize + Clone + Send + 'static, { type Output = (RemoteIdentity, NoiseOutput); type Error = NoiseError; - type Future = Handshake; + type Future = BoxFuture<'static, Result<(RemoteIdentity, NoiseOutput), NoiseError>>; fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - let legacy = self.legacy; - let identity = self.dh_keys.clone().into_identity(); + async move { + let mut state = self.into_responder(socket)?; - handshake::rt1_responder( - socket, - self.into_responder(), - identity, - IdentityExchange::Receive, - legacy, - ) + 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, @@ -349,27 +426,18 @@ where { type Output = (RemoteIdentity, NoiseOutput); type Error = NoiseError; - type Future = Handshake; + type Future = BoxFuture<'static, Result<(RemoteIdentity, NoiseOutput), NoiseError>>; fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - let session = self - .params - .into_builder() - .prologue(self.prologue.as_ref()) - .local_private_key(self.dh_keys.secret().as_ref()) - .remote_public_key(self.remote.0.as_ref()) - .build_initiator() - .map_err(NoiseError::from); - - handshake::rt1_initiator( - socket, - session, - self.dh_keys.into_identity(), - IdentityExchange::Send { - remote: self.remote.1, - }, - self.legacy, - ) + async move { + let mut state = self.into_initiator(socket)?; + + handshake::send_identity(&mut state).await?; + handshake::recv_identity(&mut state).await?; + + state.finish() + } + .boxed() } } @@ -479,51 +547,3 @@ pub struct LegacyConfig { /// libp2p implementations. pub recv_legacy_handshake: bool, } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn handshake_hashes_disagree_if_prologue_differs() { - let alice = new_xx_config() - .with_prologue(b"alice prologue".to_vec()) - .into_initiator() - .unwrap(); - let bob = new_xx_config() - .with_prologue(b"bob prologue".to_vec()) - .into_responder() - .unwrap(); - - let alice_handshake_hash = alice.get_handshake_hash(); - let bob_handshake_hash = bob.get_handshake_hash(); - - assert_ne!(alice_handshake_hash, bob_handshake_hash) - } - - #[test] - fn handshake_hashes_agree_if_prologue_is_the_same() { - let alice = new_xx_config() - .with_prologue(b"shared knowledge".to_vec()) - .into_initiator() - .unwrap(); - let bob = new_xx_config() - .with_prologue(b"shared knowledge".to_vec()) - .into_responder() - .unwrap(); - - let alice_handshake_hash = alice.get_handshake_hash(); - let bob_handshake_hash = bob.get_handshake_hash(); - - assert_eq!(alice_handshake_hash, bob_handshake_hash) - } - - fn new_xx_config() -> NoiseConfig { - let dh_keys = Keypair::::new(); - let noise_keys = dh_keys - .into_authentic(&identity::Keypair::generate_ed25519()) - .unwrap(); - - NoiseConfig::xx(noise_keys) - } -} diff --git a/transports/noise/src/protocol.rs b/transports/noise/src/protocol.rs index aa2acb150a9..9b57f05d0ba 100644 --- a/transports/noise/src/protocol.rs +++ b/transports/noise/src/protocol.rs @@ -34,9 +34,24 @@ use zeroize::Zeroize; pub struct ProtocolParams(snow::params::NoiseParams); impl ProtocolParams { - /// Turn the protocol parameters into a session builder. - pub(crate) fn into_builder(self) -> snow::Builder<'static> { - snow::Builder::with_resolver(self.0, Box::new(Resolver)) + 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()); + + if let Some(remote_public_key) = remote_public_key { + builder = builder.remote_public_key(remote_public_key.as_ref()); + } + + builder } } @@ -118,26 +133,27 @@ pub struct Keypair { /// A DH keypair that is authentic w.r.t. a [`identity::PublicKey`]. #[derive(Clone)] pub struct AuthenticKeypair { - keypair: Keypair, - identity: KeypairIdentity, + 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 } } -impl std::ops::Deref for AuthenticKeypair { - type Target = Keypair; - - fn deref(&self) -> &Self::Target { - &self.keypair - } -} - /// The associated public identity of a DH keypair. #[derive(Clone)] pub struct KeypairIdentity { @@ -288,3 +304,40 @@ impl rand::RngCore for Rng { impl rand::CryptoRng for Rng {} impl snow::types::Random for Rng {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::X25519; + + #[test] + fn handshake_hashes_disagree_if_prologue_differs() { + let alice = xx_builder(b"alice prologue").build_initiator().unwrap(); + let bob = xx_builder(b"bob prologue").build_responder().unwrap(); + + let alice_handshake_hash = alice.get_handshake_hash(); + let bob_handshake_hash = bob.get_handshake_hash(); + + assert_ne!(alice_handshake_hash, bob_handshake_hash) + } + + #[test] + fn handshake_hashes_agree_if_prologue_is_the_same() { + let alice = xx_builder(b"shared knowledge").build_initiator().unwrap(); + let bob = xx_builder(b"shared knowledge").build_responder().unwrap(); + + let alice_handshake_hash = alice.get_handshake_hash(); + let bob_handshake_hash = bob.get_handshake_hash(); + + assert_eq!(alice_handshake_hash, bob_handshake_hash) + } + + fn xx_builder(prologue: &'static [u8]) -> snow::Builder<'static> { + X25519::params_xx().into_builder(prologue, TEST_KEY.secret(), None) + } + + // Hack to work around borrow-checker. + lazy_static::lazy_static! { + static ref TEST_KEY: Keypair = Keypair::::new(); + } +} diff --git a/transports/noise/tests/smoke.rs b/transports/noise/tests/smoke.rs index 16dcf4383d1..c69ebe03132 100644 --- a/transports/noise/tests/smoke.rs +++ b/transports/noise/tests/smoke.rs @@ -197,7 +197,7 @@ fn ik_xx() { let client_id_public = client_id.public(); let server_dh = Keypair::::new().into_authentic(&server_id).unwrap(); - let server_dh_public = server_dh.public().clone(); + let server_dh_public = server_dh.public_dh_key().clone(); let server_transport = TcpTransport::default() .and_then(move |output, endpoint| { if endpoint.is_listener() {