feat(noise): remove deprecated legacy handshakes

This patch removes all deprecated legacy code from `libp2p-noise` and attempts to collapse all the abstraction layers into something a lot simpler.

Pull-Request: #3511.
This commit is contained in:
Thomas Eizinger
2023-05-08 07:30:15 +02:00
committed by GitHub
parent 15c623c29a
commit b507fe298f
9 changed files with 248 additions and 1375 deletions

View File

@ -58,7 +58,7 @@ pub trait TransportExt: Transport {
/// let transport = tcp::tokio::Transport::new(tcp::Config::default().nodelay(true)) /// let transport = tcp::tokio::Transport::new(tcp::Config::default().nodelay(true))
/// .upgrade(upgrade::Version::V1) /// .upgrade(upgrade::Version::V1)
/// .authenticate( /// .authenticate(
/// noise::NoiseAuthenticated::xx(&id_keys) /// noise::Config::new(&id_keys)
/// .expect("Signing libp2p-noise static DH keypair failed."), /// .expect("Signing libp2p-noise static DH keypair failed."),
/// ) /// )
/// .multiplex(mplex::MplexConfig::new()) /// .multiplex(mplex::MplexConfig::new())

View File

@ -92,17 +92,18 @@
//! An example of initialising a gossipsub compatible swarm: //! An example of initialising a gossipsub compatible swarm:
//! //!
//! ``` //! ```
//! use libp2p_gossipsub::Event; //! # use libp2p_gossipsub::Event;
//! use libp2p_core::{identity::Keypair,transport::{Transport, MemoryTransport}, Multiaddr}; //! # use libp2p_core::{transport::{Transport, MemoryTransport}, Multiaddr};
//! use libp2p_gossipsub::MessageAuthenticity; //! # use libp2p_gossipsub::MessageAuthenticity;
//! let local_key = Keypair::generate_ed25519(); //! # use libp2p_identity as identity;
//! let local_peer_id = libp2p_core::PeerId::from(local_key.public()); //! 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 //! // Set up an encrypted TCP Transport over the Mplex
//! // This is test transport (memory). //! // This is test transport (memory).
//! let transport = MemoryTransport::default() //! let transport = MemoryTransport::default()
//! .upgrade(libp2p_core::upgrade::Version::V1) //! .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()) //! .multiplex(libp2p_mplex::MplexConfig::new())
//! .boxed(); //! .boxed();
//! //!
@ -123,11 +124,11 @@
//! // subscribe to the topic //! // subscribe to the topic
//! gossipsub.subscribe(&topic); //! gossipsub.subscribe(&topic);
//! // create the swarm (use an executor in a real example) //! // create the swarm (use an executor in a real example)
//! libp2p_swarm::Swarm::without_executor( //! libp2p_swarm::SwarmBuilder::without_executor(
//! transport, //! transport,
//! gossipsub, //! gossipsub,
//! local_peer_id, //! local_peer_id,
//! ) //! ).build()
//! }; //! };
//! //!
//! // Listen on a memory transport. //! // Listen on a memory transport.

View File

@ -1,8 +1,11 @@
## 0.42.0 - unreleased ## 0.43.0 - unreleased
- Raise MSRV to 1.65. - Raise MSRV to 1.65.
See [PR 3715]. 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 [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715
## 0.42.2 ## 0.42.2

View File

@ -22,7 +22,7 @@
//! Noise protocol messages in form of [`NoiseFramed`]. //! Noise protocol messages in form of [`NoiseFramed`].
use crate::io::Output; use crate::io::Output;
use crate::{Error, Protocol, PublicKey}; use crate::{protocol::PublicKey, Error};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::prelude::*; use futures::prelude::*;
use futures::ready; use futures::ready;
@ -89,15 +89,15 @@ impl<T> NoiseFramed<T, snow::HandshakeState> {
/// transitioning to transport mode because the handshake is incomplete, /// transitioning to transport mode because the handshake is incomplete,
/// an error is returned. Similarly if the remote's static DH key, if /// an error is returned. Similarly if the remote's static DH key, if
/// present, cannot be parsed. /// present, cannot be parsed.
pub(crate) fn into_transport<C>(self) -> Result<(Option<PublicKey<C>>, Output<T>), Error> pub(crate) fn into_transport(self) -> Result<(PublicKey, Output<T>), Error> {
where let dh_remote_pubkey = self.session.get_remote_static().ok_or_else(|| {
C: Protocol<C> + AsRef<[u8]>, Error::Io(io::Error::new(
{ io::ErrorKind::Other,
let dh_remote_pubkey = self "expect key to always be present at end of XX session",
.session ))
.get_remote_static() })?;
.map(C::public_from_bytes)
.transpose()?; let dh_remote_pubkey = PublicKey::from_slice(dh_remote_pubkey)?;
let io = NoiseFramed { let io = NoiseFramed {
session: self.session.into_transport_mode()?, session: self.session.into_transport_mode()?,

View File

@ -27,46 +27,14 @@ mod proto {
} }
use crate::io::{framed::NoiseFramed, Output}; use crate::io::{framed::NoiseFramed, Output};
use crate::protocol::{KeypairIdentity, Protocol, PublicKey}; use crate::protocol::{KeypairIdentity, STATIC_KEY_DOMAIN};
use crate::Error; use crate::Error;
use crate::LegacyConfig;
use bytes::Bytes; use bytes::Bytes;
use futures::prelude::*; use futures::prelude::*;
use libp2p_identity as identity; use libp2p_identity as identity;
use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
use std::io; 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 // Internal
@ -81,8 +49,6 @@ pub(crate) struct State<T> {
dh_remote_pubkey_sig: Option<Vec<u8>>, dh_remote_pubkey_sig: Option<Vec<u8>>,
/// The known or received public identity key of the remote, if any. /// The known or received public identity key of the remote, if any.
id_remote_pubkey: Option<identity::PublicKey>, id_remote_pubkey: Option<identity::PublicKey>,
/// Legacy configuration parameters.
legacy: LegacyConfig,
} }
impl<T> State<T> { impl<T> State<T> {
@ -97,14 +63,12 @@ impl<T> State<T> {
session: snow::HandshakeState, session: snow::HandshakeState,
identity: KeypairIdentity, identity: KeypairIdentity,
expected_remote_key: Option<identity::PublicKey>, expected_remote_key: Option<identity::PublicKey>,
legacy: LegacyConfig,
) -> Self { ) -> Self {
Self { Self {
identity, identity,
io: NoiseFramed::new(io, session), io: NoiseFramed::new(io, session),
dh_remote_pubkey_sig: None, dh_remote_pubkey_sig: None,
id_remote_pubkey: expected_remote_key, id_remote_pubkey: expected_remote_key,
legacy,
} }
} }
} }
@ -112,23 +76,22 @@ impl<T> State<T> {
impl<T> State<T> { impl<T> State<T> {
/// Finish a handshake, yielding the established remote identity and the /// Finish a handshake, yielding the established remote identity and the
/// [`Output`] for communicating on the encrypted channel. /// [`Output`] for communicating on the encrypted channel.
pub(crate) fn finish<C>(self) -> Result<(RemoteIdentity<C>, Output<T>), Error> pub(crate) fn finish(self) -> Result<(identity::PublicKey, Output<T>), Error> {
where
C: Protocol<C> + AsRef<[u8]>,
{
let (pubkey, io) = self.io.into_transport()?; let (pubkey, io) = self.io.into_transport()?;
let remote = match (self.id_remote_pubkey, pubkey) {
(_, None) => RemoteIdentity::Unknown, let id_pk = self
(None, Some(dh_pk)) => RemoteIdentity::StaticDhKey(dh_pk), .id_remote_pubkey
(Some(id_pk), Some(dh_pk)) => { .ok_or_else(|| Error::AuthenticationFailed)?;
if C::verify(&id_pk, &dh_pk, &self.dh_remote_pubkey_sig) {
RemoteIdentity::IdentityKey(id_pk) let is_valid_signature = self.dh_remote_pubkey_sig.as_ref().map_or(false, |s| {
} else { id_pk.verify(&[STATIC_KEY_DOMAIN.as_bytes(), pubkey.as_ref()].concat(), s)
});
if !is_valid_signature {
return Err(Error::BadSignature); return Err(Error::BadSignature);
} }
}
}; Ok((id_pk, io))
Ok((remote, io))
} }
} }
@ -170,60 +133,16 @@ where
Ok(()) Ok(())
} }
/// A future for receiving a Noise handshake message with a payload /// A future for receiving a Noise handshake message with a payload identifying the remote.
/// 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.
pub(crate) async fn recv_identity<T>(state: &mut State<T>) -> Result<(), Error> pub(crate) async fn recv_identity<T>(state: &mut State<T>) -> Result<(), Error>
where where
T: AsyncRead + Unpin, T: AsyncRead + Unpin,
{ {
let msg = recv(state).await?; let msg = recv(state).await?;
let mut reader = BytesReader::from_bytes(&msg[..]); 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 { state.id_remote_pubkey = Some(identity::PublicKey::try_decode_protobuf(&pb.identity_key)?);
// 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);
}
if !pb.identity_sig.is_empty() { if !pb.identity_sig.is_empty() {
state.dh_remote_pubkey_sig = Some(pb.identity_sig); state.dh_remote_pubkey_sig = Some(pb.identity_sig);
@ -242,43 +161,9 @@ where
..Default::default() ..Default::default()
}; };
if let Some(ref sig) = state.identity.signature { pb.identity_sig = state.identity.signature.clone();
pb.identity_sig = sig.clone()
}
let mut msg = if state.legacy.send_legacy_handshake { let mut msg = Vec::with_capacity(pb.get_size());
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 writer = Writer::new(&mut msg); let mut writer = Writer::new(&mut msg);
pb.write_message(&mut writer).expect("Encoding to succeed"); pb.write_message(&mut writer).expect("Encoding to succeed");

View File

@ -59,110 +59,81 @@
mod io; mod io;
mod protocol; mod protocol;
pub use io::handshake::RemoteIdentity;
pub use io::Output; pub use io::Output;
pub use protocol::Protocol;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type X25519Spec = protocol::x25519_spec::X25519Spec;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type X25519 = protocol::x25519::X25519;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type AuthenticKeypair<T> = protocol::AuthenticKeypair<T>;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type Keypair<T> = protocol::Keypair<T>;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type KeypairIdentity = protocol::KeypairIdentity;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type PublicKey<T> = protocol::PublicKey<T>;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type SecretKey<T> = protocol::SecretKey<T>;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type ProtocolParams = protocol::ProtocolParams;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type IK = protocol::IK;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type IX = protocol::IX;
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub type XX = protocol::XX;
#[deprecated(
note = "This type has been renamed to drop the `Noise` prefix, refer to it as `noise::Error` instead."
)]
pub type NoiseError = Error;
#[deprecated(
note = "This type has been renamed to drop the `Noise` prefix, refer to it as `noise::Output` instead."
)]
pub type NoiseOutput<T> = Output<T>;
use crate::handshake::State; use crate::handshake::State;
use crate::io::handshake; use crate::io::handshake;
use futures::future::BoxFuture; use crate::protocol::{noise_params_into_builder, AuthenticKeypair, Keypair, PARAMS_XX};
use futures::prelude::*; use futures::prelude::*;
use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo};
use libp2p_identity as identity; use libp2p_identity as identity;
use libp2p_identity::PeerId; use libp2p_identity::PeerId;
use snow::params::NoiseParams;
use std::fmt; use std::fmt;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::pin::Pin; use std::pin::Pin;
use zeroize::Zeroize;
/// The configuration for the noise handshake. /// The configuration for the noise handshake.
#[derive(Clone)] #[derive(Clone)]
pub struct Config { pub struct Config {
inner: NoiseAuthenticated<XX, X25519Spec, ()>, dh_keys: AuthenticKeypair,
params: NoiseParams,
/// Prologue to use in the noise handshake.
///
/// The prologue can contain arbitrary data that will be hashed into the noise handshake.
/// For the handshake to succeed, both parties must set the same prologue.
///
/// For further information, see <https://noiseprotocol.org/noise.html#prologue>.
prologue: Vec<u8>,
} }
impl Config { impl Config {
/// Construct a new configuration for the noise handshake using the XX handshake pattern. /// Construct a new configuration for the noise handshake using the XX handshake pattern.
pub fn new(identity: &identity::Keypair) -> Result<Self, Error> { pub fn new(identity: &identity::Keypair) -> Result<Self, Error> {
Ok(Config { let noise_keys = Keypair::new().into_authentic(identity)?;
inner: NoiseAuthenticated::xx(identity)?,
Ok(Self {
dh_keys: noise_keys,
params: PARAMS_XX.clone(),
prologue: vec![],
}) })
} }
/// Set the noise prologue. /// Set the noise prologue.
pub fn with_prologue(mut self, prologue: Vec<u8>) -> Self { pub fn with_prologue(mut self, prologue: Vec<u8>) -> Self {
self.inner.config.prologue = prologue; self.prologue = prologue;
self self
} }
fn into_responder<S>(self, socket: S) -> Result<State<S>, Error> {
let session = noise_params_into_builder(
self.params,
&self.prologue,
self.dh_keys.keypair.secret(),
None,
)
.build_responder()?;
let state = State::new(socket, session, self.dh_keys.identity, None);
Ok(state)
}
fn into_initiator<S>(self, socket: S) -> Result<State<S>, Error> {
let session = noise_params_into_builder(
self.params,
&self.prologue,
self.dh_keys.keypair.secret(),
None,
)
.build_initiator()?;
let state = State::new(socket, session, self.dh_keys.identity, None);
Ok(state)
}
} }
impl UpgradeInfo for Config { impl UpgradeInfo for Config {
@ -182,8 +153,19 @@ where
type Error = Error; type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>; type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_inbound(self, socket: T, info: Self::Info) -> Self::Future { fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
self.inner.upgrade_inbound(socket, info) async move {
let mut state = self.into_responder(socket)?;
handshake::recv_empty(&mut state).await?;
handshake::send_identity(&mut state).await?;
handshake::recv_identity(&mut state).await?;
let (pk, io) = state.finish()?;
Ok((pk.to_peer_id(), io))
}
.boxed()
} }
} }
@ -195,185 +177,19 @@ where
type Error = Error; type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>; type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_outbound(self, socket: T, info: Self::Info) -> Self::Future { fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
self.inner.upgrade_outbound(socket, info) async move {
let mut state = self.into_initiator(socket)?;
handshake::send_empty(&mut state).await?;
handshake::recv_identity(&mut state).await?;
handshake::send_identity(&mut state).await?;
let (pk, io) = state.finish()?;
Ok((pk.to_peer_id(), io))
} }
} .boxed()
/// The protocol upgrade configuration.
#[deprecated(
note = "Use `libp2p_noise::Config` instead. All other handshake patterns are deprecated and will be removed."
)]
#[derive(Clone)]
pub struct NoiseConfig<P, C: Zeroize, R = ()> {
dh_keys: AuthenticKeypair<C>,
params: ProtocolParams,
legacy: LegacyConfig,
remote: R,
_marker: std::marker::PhantomData<P>,
/// Prologue to use in the noise handshake.
///
/// The prologue can contain arbitrary data that will be hashed into the noise handshake.
/// For the handshake to succeed, both parties must set the same prologue.
///
/// For further information, see <https://noiseprotocol.org/noise.html#prologue>.
prologue: Vec<u8>,
}
impl<H, C: Zeroize, R> NoiseConfig<H, C, R> {
/// Turn the `NoiseConfig` into an authenticated upgrade for use
/// with a `Swarm`.
pub fn into_authenticated(self) -> NoiseAuthenticated<H, C, R> {
NoiseAuthenticated { config: self }
}
/// Set the noise prologue.
pub fn with_prologue(self, prologue: Vec<u8>) -> Self {
Self { prologue, ..self }
}
/// Sets the legacy configuration options to use, if any.
#[deprecated(
since = "0.42.0",
note = "`LegacyConfig` will be removed without replacement."
)]
pub fn set_legacy_config(&mut self, cfg: LegacyConfig) -> &mut Self {
self.legacy = cfg;
self
}
}
/// Implement `into_responder` and `into_initiator` for all configs where `R = ()`.
///
/// This allows us to ignore the `remote` field.
impl<H, C> NoiseConfig<H, C, ()>
where
C: Zeroize + Protocol<C> + AsRef<[u8]>,
{
fn into_responder<S>(self, socket: S) -> Result<State<S>, Error> {
let session = self
.params
.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<S>(self, socket: S) -> Result<State<S>, Error> {
let session = self
.params
.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)
}
}
impl<C> NoiseConfig<IX, C>
where
C: Protocol<C> + Zeroize,
{
/// Create a new `NoiseConfig` for the `IX` handshake pattern.
pub fn ix(dh_keys: AuthenticKeypair<C>) -> Self {
NoiseConfig {
dh_keys,
params: C::params_ix(),
legacy: { LegacyConfig::default() },
remote: (),
_marker: std::marker::PhantomData,
prologue: Vec::default(),
}
}
}
impl<C> NoiseConfig<XX, C>
where
C: Protocol<C> + Zeroize,
{
/// Create a new `NoiseConfig` for the `XX` handshake pattern.
pub fn xx(dh_keys: AuthenticKeypair<C>) -> Self {
NoiseConfig {
dh_keys,
params: C::params_xx(),
legacy: { LegacyConfig::default() },
remote: (),
_marker: std::marker::PhantomData,
prologue: Vec::default(),
}
}
}
impl<C> NoiseConfig<IK, C>
where
C: Protocol<C> + Zeroize,
{
/// Create a new `NoiseConfig` for the `IK` handshake pattern (recipient side).
///
/// Since the identity of the local node is known to the remote, this configuration
/// does not transmit a static DH public key or public identity key to the remote.
pub fn ik_listener(dh_keys: AuthenticKeypair<C>) -> Self {
NoiseConfig {
dh_keys,
params: C::params_ik(),
legacy: { LegacyConfig::default() },
remote: (),
_marker: std::marker::PhantomData,
prologue: Vec::default(),
}
}
}
impl<C> NoiseConfig<IK, C, (PublicKey<C>, identity::PublicKey)>
where
C: Protocol<C> + Zeroize + AsRef<[u8]>,
{
/// Create a new `NoiseConfig` for the `IK` handshake pattern (initiator side).
///
/// In this configuration, the remote identity is known to the local node,
/// but the local node still needs to transmit its own public identity.
pub fn ik_dialer(
dh_keys: AuthenticKeypair<C>,
remote_id: identity::PublicKey,
remote_dh: PublicKey<C>,
) -> Self {
NoiseConfig {
dh_keys,
params: C::params_ik(),
legacy: { LegacyConfig::default() },
remote: (remote_dh, remote_id),
_marker: std::marker::PhantomData,
prologue: Vec::default(),
}
}
/// Specialised implementation of `into_initiator` for the `IK` handshake where `R != ()`.
fn into_initiator<S>(self, socket: S) -> Result<State<S>, Error> {
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)
} }
} }
@ -421,328 +237,3 @@ impl From<quick_protobuf::Error> for Error {
Error::InvalidPayload(e.into()) Error::InvalidPayload(e.into())
} }
} }
// Handshake pattern IX /////////////////////////////////////////////////////
/// 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<T, C> InboundUpgrade<T> for NoiseConfig<IX, C>
where
NoiseConfig<IX, C>: UpgradeInfo,
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Clone + Send + 'static,
{
type Output = (RemoteIdentity<C>, Output<T>);
type Error = Error;
type Future = BoxFuture<'static, Result<(RemoteIdentity<C>, Output<T>), Error>>;
fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
async move {
let mut state = self.into_responder(socket)?;
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<T, C> OutboundUpgrade<T> for NoiseConfig<IX, C>
where
NoiseConfig<IX, C>: UpgradeInfo,
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Clone + Send + 'static,
{
type Output = (RemoteIdentity<C>, Output<T>);
type Error = Error;
type Future = BoxFuture<'static, Result<(RemoteIdentity<C>, Output<T>), Error>>;
fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
async move {
let mut state = self.into_initiator(socket)?;
handshake::send_identity(&mut state).await?;
handshake::recv_identity(&mut state).await?;
state.finish()
}
.boxed()
}
}
/// 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<T, C> InboundUpgrade<T> for NoiseConfig<XX, C>
where
NoiseConfig<XX, C>: UpgradeInfo,
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Clone + Send + 'static,
{
type Output = (RemoteIdentity<C>, Output<T>);
type Error = Error;
type Future = BoxFuture<'static, Result<(RemoteIdentity<C>, Output<T>), Error>>;
fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
async move {
let mut state = self.into_responder(socket)?;
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<T, C> OutboundUpgrade<T> for NoiseConfig<XX, C>
where
NoiseConfig<XX, C>: UpgradeInfo,
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Clone + Send + 'static,
{
type Output = (RemoteIdentity<C>, Output<T>);
type Error = Error;
type Future = BoxFuture<'static, Result<(RemoteIdentity<C>, Output<T>), Error>>;
fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
async move {
let mut state = self.into_initiator(socket)?;
handshake::send_empty(&mut state).await?;
handshake::recv_identity(&mut state).await?;
handshake::send_identity(&mut state).await?;
state.finish()
}
.boxed()
}
}
/// 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<T, C> InboundUpgrade<T> for NoiseConfig<IK, C>
where
NoiseConfig<IK, C>: UpgradeInfo,
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Clone + Send + 'static,
{
type Output = (RemoteIdentity<C>, Output<T>);
type Error = Error;
type Future = BoxFuture<'static, Result<(RemoteIdentity<C>, Output<T>), Error>>;
fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
async move {
let mut state = self.into_responder(socket)?;
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<T, C> OutboundUpgrade<T> for NoiseConfig<IK, C, (PublicKey<C>, identity::PublicKey)>
where
NoiseConfig<IK, C, (PublicKey<C>, identity::PublicKey)>: UpgradeInfo,
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Clone + Send + 'static,
{
type Output = (RemoteIdentity<C>, Output<T>);
type Error = Error;
type Future = BoxFuture<'static, Result<(RemoteIdentity<C>, Output<T>), Error>>;
fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
async move {
let mut state = self.into_initiator(socket)?;
handshake::send_identity(&mut state).await?;
handshake::recv_identity(&mut state).await?;
state.finish()
}
.boxed()
}
}
// Authenticated Upgrades /////////////////////////////////////////////////////
/// A `NoiseAuthenticated` transport upgrade that wraps around any
/// `NoiseConfig` handshake and verifies that the remote identified with a
/// [`RemoteIdentity::IdentityKey`], aborting otherwise.
///
/// See [`NoiseConfig::into_authenticated`].
///
/// On success, the upgrade yields the [`PeerId`] obtained from the
/// `RemoteIdentity`. The output of this upgrade is thus directly suitable
/// for creating an [`authenticated`](libp2p_core::transport::upgrade::Authenticate)
/// transport for use with a `Swarm`.
#[derive(Clone)]
#[deprecated(
note = "Use `libp2p_noise::Config` instead. All other handshake patterns are deprecated and will be removed."
)]
pub struct NoiseAuthenticated<P, C: Zeroize, R> {
config: NoiseConfig<P, C, R>,
}
impl NoiseAuthenticated<XX, X25519Spec, ()> {
/// Create a new [`NoiseAuthenticated`] for the `XX` handshake pattern using X25519 DH keys.
///
/// For now, this is the only combination that is guaranteed to be compatible with other libp2p implementations.
#[deprecated(note = "Use `libp2p_noise::Config::new` instead.")]
pub fn xx(id_keys: &identity::Keypair) -> Result<Self, Error> {
let dh_keys = Keypair::<X25519Spec>::new();
let noise_keys = dh_keys.into_authentic(id_keys)?;
let config = NoiseConfig::xx(noise_keys);
Ok(config.into_authenticated())
}
}
impl<P, C: Zeroize, R> UpgradeInfo for NoiseAuthenticated<P, C, R>
where
NoiseConfig<P, C, R>: UpgradeInfo,
{
type Info = <NoiseConfig<P, C, R> as UpgradeInfo>::Info;
type InfoIter = <NoiseConfig<P, C, R> as UpgradeInfo>::InfoIter;
fn protocol_info(&self) -> Self::InfoIter {
self.config.protocol_info()
}
}
impl<T, P, C, R> InboundUpgrade<T> for NoiseAuthenticated<P, C, R>
where
NoiseConfig<P, C, R>: UpgradeInfo
+ InboundUpgrade<T, Output = (RemoteIdentity<C>, Output<T>), Error = Error>
+ 'static,
<NoiseConfig<P, C, R> as InboundUpgrade<T>>::Future: Send,
T: AsyncRead + AsyncWrite + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Send + 'static,
{
type Output = (PeerId, Output<T>);
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_inbound(self, socket: T, info: Self::Info) -> Self::Future {
Box::pin(
self.config
.upgrade_inbound(socket, info)
.and_then(|(remote, io)| match remote {
RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)),
_ => future::err(Error::AuthenticationFailed),
}),
)
}
}
impl<T, P, C, R> OutboundUpgrade<T> for NoiseAuthenticated<P, C, R>
where
NoiseConfig<P, C, R>: UpgradeInfo
+ OutboundUpgrade<T, Output = (RemoteIdentity<C>, Output<T>), Error = Error>
+ 'static,
<NoiseConfig<P, C, R> as OutboundUpgrade<T>>::Future: Send,
T: AsyncRead + AsyncWrite + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Send + 'static,
{
type Output = (PeerId, Output<T>);
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_outbound(self, socket: T, info: Self::Info) -> Self::Future {
Box::pin(
self.config
.upgrade_outbound(socket, info)
.and_then(|(remote, io)| match remote {
RemoteIdentity::IdentityKey(pk) => future::ok((pk.to_peer_id(), io)),
_ => future::err(Error::AuthenticationFailed),
}),
)
}
}
/// Legacy configuration options.
#[derive(Clone, Copy, Default)]
#[deprecated(
since = "0.42.0",
note = "`LegacyConfig` will be removed without replacement."
)]
pub struct LegacyConfig {
/// Whether to continue sending legacy handshake payloads,
/// i.e. length-prefixed protobuf payloads inside a length-prefixed
/// noise frame. These payloads are not interoperable with other
/// libp2p implementations.
pub send_legacy_handshake: bool,
/// Whether to support receiving legacy handshake payloads,
/// i.e. length-prefixed protobuf payloads inside a length-prefixed
/// noise frame. These payloads are not interoperable with other
/// libp2p implementations.
pub recv_legacy_handshake: bool,
}

View File

@ -20,29 +20,30 @@
//! Components of a Noise protocol. //! Components of a Noise protocol.
pub(crate) mod x25519;
pub(crate) mod x25519_spec;
use crate::Error; use crate::Error;
use libp2p_identity as identity; use libp2p_identity as identity;
use rand::SeedableRng; use once_cell::sync::Lazy;
use rand::{Rng as _, SeedableRng};
use snow::params::NoiseParams;
use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES};
use zeroize::Zeroize; use zeroize::Zeroize;
/// The parameters of a Noise protocol, consisting of a choice /// Prefix of static key signatures for domain separation.
/// for a handshake pattern as well as DH, cipher and hash functions. pub(crate) const STATIC_KEY_DOMAIN: &str = "noise-libp2p-static-key:";
#[derive(Clone)]
pub struct ProtocolParams(snow::params::NoiseParams);
impl ProtocolParams { pub(crate) static PARAMS_XX: Lazy<NoiseParams> = Lazy::new(|| {
pub(crate) fn into_builder<'b, C>( "Noise_XX_25519_ChaChaPoly_SHA256"
self, .parse()
.expect("Invalid protocol name")
});
pub(crate) fn noise_params_into_builder<'b>(
params: NoiseParams,
prologue: &'b [u8], prologue: &'b [u8],
private_key: &'b SecretKey<C>, private_key: &'b SecretKey,
remote_public_key: Option<&'b PublicKey<C>>, remote_public_key: Option<&'b PublicKey>,
) -> snow::Builder<'b> ) -> snow::Builder<'b> {
where let mut builder = snow::Builder::with_resolver(params, Box::new(Resolver))
C: Zeroize + AsRef<[u8]> + Protocol<C>,
{
let mut builder = snow::Builder::with_resolver(self.0, Box::new(Resolver))
.prologue(prologue.as_ref()) .prologue(prologue.as_ref())
.local_private_key(private_key.as_ref()); .local_private_key(private_key.as_ref());
@ -51,143 +52,48 @@ impl ProtocolParams {
} }
builder builder
}
}
/// Type tag for the IK handshake pattern.
#[derive(Debug, Clone)]
pub enum IK {}
/// Type tag for the IX handshake pattern.
#[derive(Debug, Clone)]
pub enum IX {}
/// Type tag for the XX handshake pattern.
#[derive(Debug, Clone)]
pub enum XX {}
/// A Noise protocol over DH keys of type `C`. The choice of `C` determines the
/// protocol parameters for each handshake pattern.
#[deprecated(
note = "This type will be made private in the future. Use `libp2p_noise::Config::new` instead to use the noise protocol."
)]
pub trait Protocol<C> {
/// The protocol parameters for the IK handshake pattern.
fn params_ik() -> ProtocolParams;
/// The protocol parameters for the IX handshake pattern.
fn params_ix() -> ProtocolParams;
/// The protocol parameters for the XX handshake pattern.
fn params_xx() -> ProtocolParams;
/// Construct a DH public key from a byte slice.
fn public_from_bytes(s: &[u8]) -> Result<PublicKey<C>, Error>;
/// Determines whether the authenticity of the given DH static public key
/// and public identity key is linked, i.e. that proof of ownership of a
/// secret key for the static DH public key implies that the key is
/// authentic w.r.t. the given public identity key.
///
/// The trivial case is when the keys are byte for byte identical.
#[allow(unused_variables)]
#[deprecated]
fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey<C>) -> bool {
false
}
/// Verifies that a given static DH public key is authentic w.r.t. a
/// given public identity key in the context of an optional signature.
///
/// The given static DH public key is assumed to already be authentic
/// in the sense that possession of a corresponding secret key has been
/// established, as is the case at the end of a Noise handshake involving
/// static DH keys.
///
/// If the public keys are [`linked`](Protocol::linked), verification succeeds
/// without a signature, otherwise a signature over the static DH public key
/// must be given and is verified with the public identity key, establishing
/// the authenticity of the static DH public key w.r.t. the public identity key.
fn verify(id_pk: &identity::PublicKey, dh_pk: &PublicKey<C>, sig: &Option<Vec<u8>>) -> bool
where
C: AsRef<[u8]>,
{
Self::linked(id_pk, dh_pk)
|| sig
.as_ref()
.map_or(false, |s| id_pk.verify(dh_pk.as_ref(), s))
}
fn sign(id_keys: &identity::Keypair, dh_pk: &PublicKey<C>) -> Result<Vec<u8>, Error>
where
C: AsRef<[u8]>,
{
Ok(id_keys.sign(dh_pk.as_ref())?)
}
} }
/// DH keypair. /// DH keypair.
#[derive(Clone)] #[derive(Clone)]
pub struct Keypair<T: Zeroize> { pub(crate) struct Keypair {
secret: SecretKey<T>, secret: SecretKey,
public: PublicKey<T>, public: PublicKey,
} }
/// A DH keypair that is authentic w.r.t. a [`identity::PublicKey`]. /// A DH keypair that is authentic w.r.t. a [`identity::PublicKey`].
#[derive(Clone)] #[derive(Clone)]
pub struct AuthenticKeypair<T: Zeroize> { pub(crate) struct AuthenticKeypair {
pub(crate) keypair: Keypair<T>, pub(crate) keypair: Keypair,
pub(crate) identity: KeypairIdentity, pub(crate) identity: KeypairIdentity,
} }
impl<T: Zeroize> AuthenticKeypair<T> {
/// Returns the public DH key of this keypair.
pub fn public_dh_key(&self) -> &PublicKey<T> {
&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
}
}
/// The associated public identity of a DH keypair. /// The associated public identity of a DH keypair.
#[derive(Clone)] #[derive(Clone)]
pub struct KeypairIdentity { pub(crate) struct KeypairIdentity {
/// The public identity key. /// The public identity key.
pub public: identity::PublicKey, pub(crate) public: identity::PublicKey,
/// The signature over the public DH key. /// The signature over the public DH key.
pub signature: Option<Vec<u8>>, pub(crate) signature: Vec<u8>,
} }
impl<T: Zeroize> Keypair<T> { impl Keypair {
/// The public key of the DH keypair.
pub fn public(&self) -> &PublicKey<T> {
&self.public
}
/// The secret key of the DH keypair. /// The secret key of the DH keypair.
pub fn secret(&self) -> &SecretKey<T> { pub(crate) fn secret(&self) -> &SecretKey {
&self.secret &self.secret
} }
/// Turn this DH keypair into a [`AuthenticKeypair`], i.e. a DH keypair that /// Turn this DH keypair into a [`AuthenticKeypair`], i.e. a DH keypair that
/// is authentic w.r.t. the given identity keypair, by signing the DH public key. /// is authentic w.r.t. the given identity keypair, by signing the DH public key.
pub fn into_authentic(self, id_keys: &identity::Keypair) -> Result<AuthenticKeypair<T>, Error> pub(crate) fn into_authentic(
where self,
T: AsRef<[u8]>, id_keys: &identity::Keypair,
T: Protocol<T>, ) -> Result<AuthenticKeypair, Error> {
{ let sig = id_keys.sign(&[STATIC_KEY_DOMAIN.as_bytes(), self.public.as_ref()].concat())?;
let sig = T::sign(id_keys, &self.public)?;
let identity = KeypairIdentity { let identity = KeypairIdentity {
public: id_keys.public(), public: id_keys.public(),
signature: Some(sig), signature: sig,
}; };
Ok(AuthenticKeypair { Ok(AuthenticKeypair {
@ -195,37 +101,59 @@ impl<T: Zeroize> Keypair<T> {
identity, identity,
}) })
} }
/// An "empty" keypair as a starting state for DH computations in `snow`,
/// which get manipulated through the `snow::types::Dh` interface.
pub(crate) fn empty() -> Self {
Keypair {
secret: SecretKey([0u8; 32]),
public: PublicKey([0u8; 32]),
}
}
/// Create a new X25519 keypair.
pub(crate) fn new() -> Keypair {
let mut sk_bytes = [0u8; 32];
rand::thread_rng().fill(&mut sk_bytes);
let sk = SecretKey(sk_bytes); // Copy
sk_bytes.zeroize();
Self::from(sk)
}
} }
/// DH secret key. /// DH secret key.
#[derive(Clone)] #[derive(Clone, Default)]
pub struct SecretKey<T: Zeroize>(T); pub(crate) struct SecretKey([u8; 32]);
impl<T: Zeroize> Drop for SecretKey<T> { impl Drop for SecretKey {
fn drop(&mut self) { fn drop(&mut self) {
self.0.zeroize() self.0.zeroize()
} }
} }
impl<T: AsRef<[u8]> + Zeroize> AsRef<[u8]> for SecretKey<T> { impl AsRef<[u8]> for SecretKey {
fn as_ref(&self) -> &[u8] { fn as_ref(&self) -> &[u8] {
self.0.as_ref() self.0.as_ref()
} }
} }
/// DH public key. /// DH public key.
#[derive(Clone)] #[derive(Clone, PartialEq, Default)]
pub struct PublicKey<T>(T); pub(crate) struct PublicKey([u8; 32]);
impl<T: AsRef<[u8]>> PartialEq for PublicKey<T> { impl PublicKey {
fn eq(&self, other: &PublicKey<T>) -> bool { pub(crate) fn from_slice(slice: &[u8]) -> Result<Self, Error> {
self.as_ref() == other.as_ref() if slice.len() != 32 {
return Err(Error::InvalidLength);
}
let mut key = [0u8; 32];
key.copy_from_slice(slice);
Ok(PublicKey(key))
} }
} }
impl<T: AsRef<[u8]>> Eq for PublicKey<T> {} impl AsRef<[u8]> for PublicKey {
impl<T: AsRef<[u8]>> AsRef<[u8]> for PublicKey<T> {
fn as_ref(&self) -> &[u8] { fn as_ref(&self) -> &[u8] {
self.0.as_ref() self.0.as_ref()
} }
@ -244,7 +172,7 @@ impl snow::resolvers::CryptoResolver for Resolver {
fn resolve_dh(&self, choice: &snow::params::DHChoice) -> Option<Box<dyn snow::types::Dh>> { fn resolve_dh(&self, choice: &snow::params::DHChoice) -> Option<Box<dyn snow::types::Dh>> {
if let snow::params::DHChoice::Curve25519 = choice { if let snow::params::DHChoice::Curve25519 = choice {
Some(Box::new(Keypair::<x25519_spec::X25519Spec>::default())) Some(Box::new(Keypair::empty()))
} else { } else {
None None
} }
@ -304,10 +232,67 @@ impl rand::CryptoRng for Rng {}
impl snow::types::Random for Rng {} impl snow::types::Random for Rng {}
impl Default for Keypair {
fn default() -> Self {
Self::new()
}
}
/// Promote a X25519 secret key into a keypair.
impl From<SecretKey> for Keypair {
fn from(secret: SecretKey) -> Keypair {
let public = PublicKey(x25519(secret.0, X25519_BASEPOINT_BYTES));
Keypair { secret, public }
}
}
#[doc(hidden)]
impl snow::types::Dh for Keypair {
fn name(&self) -> &'static str {
"25519"
}
fn pub_len(&self) -> usize {
32
}
fn priv_len(&self) -> usize {
32
}
fn pubkey(&self) -> &[u8] {
self.public.as_ref()
}
fn privkey(&self) -> &[u8] {
self.secret.as_ref()
}
fn set(&mut self, sk: &[u8]) {
let mut secret = [0u8; 32];
secret.copy_from_slice(sk);
self.secret = SecretKey(secret); // Copy
self.public = PublicKey(x25519(secret, X25519_BASEPOINT_BYTES));
secret.zeroize();
}
fn generate(&mut self, rng: &mut dyn snow::types::Random) {
let mut secret = [0u8; 32];
rng.fill_bytes(&mut secret);
self.secret = SecretKey(secret); // Copy
self.public = PublicKey(x25519(secret, X25519_BASEPOINT_BYTES));
secret.zeroize();
}
fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), snow::Error> {
let mut p = [0; 32];
p.copy_from_slice(&pk[..32]);
let ss = x25519(self.secret.0, p);
shared_secret[..32].copy_from_slice(&ss[..]);
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::X25519Spec; use crate::protocol::PARAMS_XX;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
#[test] #[test]
@ -333,9 +318,9 @@ mod tests {
} }
fn xx_builder(prologue: &'static [u8]) -> snow::Builder<'static> { fn xx_builder(prologue: &'static [u8]) -> snow::Builder<'static> {
X25519Spec::params_xx().into_builder(prologue, TEST_KEY.secret(), None) noise_params_into_builder(PARAMS_XX.clone(), prologue, TEST_KEY.secret(), None)
} }
// Hack to work around borrow-checker. // Hack to work around borrow-checker.
static TEST_KEY: Lazy<Keypair<X25519Spec>> = Lazy::new(Keypair::<X25519Spec>::new); static TEST_KEY: Lazy<Keypair> = Lazy::new(Keypair::new);
} }

View File

@ -1,292 +0,0 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//! Legacy Noise protocols based on X25519.
//!
//! **Note**: This set of protocols is not interoperable with other
//! libp2p implementations.
use crate::{Error, NoiseConfig, Protocol, ProtocolParams};
use curve25519_dalek::edwards::CompressedEdwardsY;
use libp2p_core::UpgradeInfo;
use libp2p_identity as identity;
use libp2p_identity::ed25519;
use once_cell::sync::Lazy;
use rand::Rng;
use sha2::{Digest, Sha512};
use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES};
use zeroize::Zeroize;
use super::*;
static PARAMS_IK: Lazy<ProtocolParams> = Lazy::new(|| {
"Noise_IK_25519_ChaChaPoly_SHA256"
.parse()
.map(ProtocolParams)
.expect("Invalid protocol name")
});
static PARAMS_IX: Lazy<ProtocolParams> = Lazy::new(|| {
"Noise_IX_25519_ChaChaPoly_SHA256"
.parse()
.map(ProtocolParams)
.expect("Invalid protocol name")
});
static PARAMS_XX: Lazy<ProtocolParams> = Lazy::new(|| {
"Noise_XX_25519_ChaChaPoly_SHA256"
.parse()
.map(ProtocolParams)
.expect("Invalid protocol name")
});
/// A X25519 key.
#[derive(Clone)]
pub struct X25519([u8; 32]);
impl AsRef<[u8]> for X25519 {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl Zeroize for X25519 {
fn zeroize(&mut self) {
self.0.zeroize()
}
}
impl UpgradeInfo for NoiseConfig<IX, X25519> {
type Info = &'static str;
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once("/noise/ix/25519/chachapoly/sha256/0.1.0")
}
}
impl UpgradeInfo for NoiseConfig<XX, X25519> {
type Info = &'static str;
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once("/noise/xx/25519/chachapoly/sha256/0.1.0")
}
}
impl<R> UpgradeInfo for NoiseConfig<IK, X25519, R> {
type Info = &'static str;
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once("/noise/ik/25519/chachapoly/sha256/0.1.0")
}
}
/// Legacy Noise protocol for X25519.
///
/// **Note**: This `Protocol` provides no configuration that
/// is interoperable with other libp2p implementations.
/// See [`crate::X25519Spec`] instead.
impl Protocol<X25519> for X25519 {
fn params_ik() -> ProtocolParams {
PARAMS_IK.clone()
}
fn params_ix() -> ProtocolParams {
PARAMS_IX.clone()
}
fn params_xx() -> ProtocolParams {
PARAMS_XX.clone()
}
fn public_from_bytes(bytes: &[u8]) -> Result<PublicKey<X25519>, Error> {
if bytes.len() != 32 {
return Err(Error::InvalidLength);
}
let mut pk = [0u8; 32];
pk.copy_from_slice(bytes);
Ok(PublicKey(X25519(pk)))
}
#[allow(irrefutable_let_patterns)]
fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey<X25519>) -> bool {
if let Ok(p) = identity::PublicKey::try_into_ed25519(id_pk.clone()) {
PublicKey::from_ed25519(&p).as_ref() == dh_pk.as_ref()
} else {
false
}
}
}
impl Keypair<X25519> {
/// Create a new X25519 keypair.
pub fn new() -> Keypair<X25519> {
let mut sk_bytes = [0u8; 32];
rand::thread_rng().fill(&mut sk_bytes);
let sk = SecretKey(X25519(sk_bytes)); // Copy
sk_bytes.zeroize();
Self::from(sk)
}
/// Creates an X25519 `Keypair` from an [`identity::Keypair`], if possible.
///
/// The returned keypair will be [associated with](KeypairIdentity) the
/// given identity keypair.
///
/// Returns `None` if the given identity keypair cannot be used as an X25519 keypair.
///
/// > **Note**: If the identity keypair is already used in the context
/// > of other cryptographic protocols outside of Noise, it should be preferred to
/// > create a new static X25519 keypair for use in the Noise protocol.
/// >
/// > See also:
/// >
/// > * [Noise: Static Key Reuse](http://www.noiseprotocol.org/noise.html#security-considerations)
#[allow(unreachable_patterns)]
pub fn from_identity(id_keys: &identity::Keypair) -> Option<AuthenticKeypair<X25519>> {
let ed25519_keypair = id_keys.clone().try_into_ed25519().ok()?;
let kp = Keypair::from(SecretKey::from_ed25519(&ed25519_keypair.secret()));
let id = KeypairIdentity {
public: id_keys.public(),
signature: None,
};
Some(AuthenticKeypair {
keypair: kp,
identity: id,
})
}
}
impl Default for Keypair<X25519> {
fn default() -> Self {
Self::new()
}
}
/// Promote a X25519 secret key into a keypair.
impl From<SecretKey<X25519>> for Keypair<X25519> {
fn from(secret: SecretKey<X25519>) -> Keypair<X25519> {
let public = PublicKey(X25519(x25519((secret.0).0, X25519_BASEPOINT_BYTES)));
Keypair { secret, public }
}
}
impl PublicKey<X25519> {
/// Construct a curve25519 public key from an Ed25519 public key.
pub fn from_ed25519(pk: &ed25519::PublicKey) -> Self {
PublicKey(X25519(
CompressedEdwardsY(pk.encode())
.decompress()
.expect("An Ed25519 public key is a valid point by construction.")
.to_montgomery()
.0,
))
}
}
impl SecretKey<X25519> {
/// Construct a X25519 secret key from a Ed25519 secret key.
///
/// > **Note**: If the Ed25519 secret key is already used in the context
/// > of other cryptographic protocols outside of Noise, it should be preferred
/// > to create a new keypair for use in the Noise protocol.
/// >
/// > See also:
/// >
/// > * [Noise: Static Key Reuse](http://www.noiseprotocol.org/noise.html#security-considerations)
/// > * [Ed25519 to Curve25519](https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519)
pub fn from_ed25519(ed25519_sk: &ed25519::SecretKey) -> Self {
// An Ed25519 public key is derived off the left half of the SHA512 of the
// secret scalar, hence a matching conversion of the secret key must do
// the same to yield a Curve25519 keypair with the same public key.
// let ed25519_sk = ed25519::SecretKey::from(ed);
let mut curve25519_sk: [u8; 32] = [0; 32];
let hash = Sha512::digest(ed25519_sk.as_ref());
curve25519_sk.copy_from_slice(&hash[..32]);
let sk = SecretKey(X25519(curve25519_sk)); // Copy
curve25519_sk.zeroize();
sk
}
}
#[doc(hidden)]
impl snow::types::Dh for Keypair<X25519> {
fn name(&self) -> &'static str {
"25519"
}
fn pub_len(&self) -> usize {
32
}
fn priv_len(&self) -> usize {
32
}
fn pubkey(&self) -> &[u8] {
self.public.as_ref()
}
fn privkey(&self) -> &[u8] {
self.secret.as_ref()
}
fn set(&mut self, sk: &[u8]) {
let mut secret = [0u8; 32];
secret.copy_from_slice(sk);
self.secret = SecretKey(X25519(secret)); // Copy
self.public = PublicKey(X25519(x25519(secret, X25519_BASEPOINT_BYTES)));
secret.zeroize();
}
fn generate(&mut self, rng: &mut dyn snow::types::Random) {
let mut secret = [0u8; 32];
rng.fill_bytes(&mut secret);
self.secret = SecretKey(X25519(secret)); // Copy
self.public = PublicKey(X25519(x25519(secret, X25519_BASEPOINT_BYTES)));
secret.zeroize();
}
fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), snow::Error> {
let mut p = [0; 32];
p.copy_from_slice(&pk[..32]);
let ss = x25519((self.secret.0).0, p);
shared_secret[..32].copy_from_slice(&ss[..]);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use libp2p_identity::ed25519;
use quickcheck::*;
// The x25519 public key obtained through ed25519 keypair conversion
// (and thus derived from the converted secret key) must match the x25519
// public key derived directly from the ed25519 public key.
#[test]
fn prop_public_ed25519_to_x25519_matches() {
fn prop() -> bool {
let ed25519 = ed25519::Keypair::generate();
let x25519 = Keypair::from(SecretKey::from_ed25519(&ed25519.secret()));
let x25519_public = PublicKey::from_ed25519(&ed25519.public());
x25519.public == x25519_public
}
quickcheck(prop as fn() -> _);
}
}

View File

@ -1,200 +0,0 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//! [libp2p-noise-spec] compliant Noise protocols based on X25519.
//!
//! [libp2p-noise-spec]: https://github.com/libp2p/specs/tree/master/noise
use crate::{Error, NoiseConfig, Protocol, ProtocolParams};
use libp2p_core::UpgradeInfo;
use libp2p_identity as identity;
use rand::Rng;
use x25519_dalek::{x25519, X25519_BASEPOINT_BYTES};
use zeroize::Zeroize;
use super::*;
/// Prefix of static key signatures for domain separation.
const STATIC_KEY_DOMAIN: &str = "noise-libp2p-static-key:";
/// A X25519 key.
#[derive(Clone)]
pub struct X25519Spec([u8; 32]);
impl AsRef<[u8]> for X25519Spec {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl Zeroize for X25519Spec {
fn zeroize(&mut self) {
self.0.zeroize()
}
}
impl Keypair<X25519Spec> {
/// An "empty" keypair as a starting state for DH computations in `snow`,
/// which get manipulated through the `snow::types::Dh` interface.
pub(super) fn default() -> Self {
Keypair {
secret: SecretKey(X25519Spec([0u8; 32])),
public: PublicKey(X25519Spec([0u8; 32])),
}
}
/// Create a new X25519 keypair.
pub fn new() -> Keypair<X25519Spec> {
let mut sk_bytes = [0u8; 32];
rand::thread_rng().fill(&mut sk_bytes);
let sk = SecretKey(X25519Spec(sk_bytes)); // Copy
sk_bytes.zeroize();
Self::from(sk)
}
}
impl Default for Keypair<X25519Spec> {
fn default() -> Self {
Self::new()
}
}
/// Promote a X25519 secret key into a keypair.
impl From<SecretKey<X25519Spec>> for Keypair<X25519Spec> {
fn from(secret: SecretKey<X25519Spec>) -> Keypair<X25519Spec> {
let public = PublicKey(X25519Spec(x25519((secret.0).0, X25519_BASEPOINT_BYTES)));
Keypair { secret, public }
}
}
impl UpgradeInfo for NoiseConfig<XX, X25519Spec> {
type Info = &'static str;
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once("/noise")
}
}
/// **Note**: This is not currentlyy a standardised upgrade.
impl UpgradeInfo for NoiseConfig<IX, X25519Spec> {
type Info = &'static str;
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once("/noise/ix/25519/chachapoly/sha256/0.1.0")
}
}
/// **Note**: This is not currently a standardised upgrade.
impl<R> UpgradeInfo for NoiseConfig<IK, X25519Spec, R> {
type Info = &'static str;
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once("/noise/ik/25519/chachapoly/sha256/0.1.0")
}
}
/// Noise protocols for X25519 with libp2p-spec compliant signatures.
///
/// **Note**: Only the XX handshake pattern is currently guaranteed to be
/// interoperable with other libp2p implementations.
impl Protocol<X25519Spec> for X25519Spec {
fn params_ik() -> ProtocolParams {
x25519::X25519::params_ik()
}
fn params_ix() -> ProtocolParams {
x25519::X25519::params_ix()
}
fn params_xx() -> ProtocolParams {
x25519::X25519::params_xx()
}
fn public_from_bytes(bytes: &[u8]) -> Result<PublicKey<X25519Spec>, Error> {
if bytes.len() != 32 {
return Err(Error::InvalidLength);
}
let mut pk = [0u8; 32];
pk.copy_from_slice(bytes);
Ok(PublicKey(X25519Spec(pk)))
}
fn verify(
id_pk: &identity::PublicKey,
dh_pk: &PublicKey<X25519Spec>,
sig: &Option<Vec<u8>>,
) -> bool {
sig.as_ref().map_or(false, |s| {
id_pk.verify(&[STATIC_KEY_DOMAIN.as_bytes(), dh_pk.as_ref()].concat(), s)
})
}
fn sign(id_keys: &identity::Keypair, dh_pk: &PublicKey<X25519Spec>) -> Result<Vec<u8>, Error> {
Ok(id_keys.sign(&[STATIC_KEY_DOMAIN.as_bytes(), dh_pk.as_ref()].concat())?)
}
}
#[doc(hidden)]
impl snow::types::Dh for Keypair<X25519Spec> {
fn name(&self) -> &'static str {
"25519"
}
fn pub_len(&self) -> usize {
32
}
fn priv_len(&self) -> usize {
32
}
fn pubkey(&self) -> &[u8] {
self.public.as_ref()
}
fn privkey(&self) -> &[u8] {
self.secret.as_ref()
}
fn set(&mut self, sk: &[u8]) {
let mut secret = [0u8; 32];
secret.copy_from_slice(sk);
self.secret = SecretKey(X25519Spec(secret)); // Copy
self.public = PublicKey(X25519Spec(x25519(secret, X25519_BASEPOINT_BYTES)));
secret.zeroize();
}
fn generate(&mut self, rng: &mut dyn snow::types::Random) {
let mut secret = [0u8; 32];
rng.fill_bytes(&mut secret);
self.secret = SecretKey(X25519Spec(secret)); // Copy
self.public = PublicKey(X25519Spec(x25519(secret, X25519_BASEPOINT_BYTES)));
secret.zeroize();
}
fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), snow::Error> {
let mut p = [0; 32];
p.copy_from_slice(&pk[..32]);
let ss = x25519((self.secret.0).0, p);
shared_secret[..32].copy_from_slice(&ss[..]);
Ok(())
}
}