mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-04-25 03:02:12 +00:00
Spec-compliant Noise Protocol (#1545)
* Add spec-compliant x25519 protocol. * Cosmetic protobuf spec compliance. * Keep support for IK/IX. Update changelog. * Update docs. * CI
This commit is contained in:
parent
69fd8ac195
commit
bd5e81fedb
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,9 +1,22 @@
|
||||
# Version ???
|
||||
|
||||
- `libp2p-noise`: Added the `X25519Spec` protocol suite which uses
|
||||
libp2p-noise-spec compliant signatures on static keys as well as the
|
||||
`/noise` protocol upgrade, hence providing a libp2p-noise-spec compliant
|
||||
`XX` handshake. `IK` and `IX` are still supported with `X25519Spec`
|
||||
though not guaranteed to be interoperable with other libp2p
|
||||
implementations as these handshake patterns are not currently
|
||||
included in the libp2p-noise-spec. The `X25519Spec` implementation
|
||||
will eventually replace the current `X25519` implementation, with
|
||||
the former being removed. To upgrade without interruptions, you may
|
||||
temporarily include `NoiseConfig`s for both implementations as
|
||||
alternatives in your transport upgrade pipeline.
|
||||
|
||||
- `libp2p-kad`: Consider fixed (K_VALUE) amount of peers at closest query
|
||||
initialization. Unless `KademliaConfig::set_replication_factor` is used change
|
||||
has no effect.
|
||||
[PR 1536](https://github.com/libp2p/rust-libp2p/pull/1536)
|
||||
|
||||
- `libp2p-tcp`: On listeners started with an IPv6 multi-address the socket
|
||||
option `IPV6_V6ONLY` is set to true. Instead of relying on IPv4-mapped IPv6
|
||||
address support, two listeners can be started if IPv4 and IPv6 should both
|
||||
|
@ -365,10 +365,10 @@ where
|
||||
|
||||
let mut payload_buf = vec![0; len];
|
||||
state.io.read_exact(&mut payload_buf).await?;
|
||||
let pb = payload_proto::Identity::decode(&payload_buf[..])?;
|
||||
let pb = payload_proto::NoiseHandshakePayload::decode(&payload_buf[..])?;
|
||||
|
||||
if !pb.pubkey.is_empty() {
|
||||
let pk = identity::PublicKey::from_protobuf_encoding(&pb.pubkey)
|
||||
if !pb.identity_key.is_empty() {
|
||||
let pk = identity::PublicKey::from_protobuf_encoding(&pb.identity_key)
|
||||
.map_err(|_| NoiseError::InvalidKey)?;
|
||||
if let Some(ref k) = state.id_remote_pubkey {
|
||||
if k != &pk {
|
||||
@ -377,8 +377,8 @@ where
|
||||
}
|
||||
state.id_remote_pubkey = Some(pk);
|
||||
}
|
||||
if !pb.signature.is_empty() {
|
||||
state.dh_remote_pubkey_sig = Some(pb.signature);
|
||||
if !pb.identity_sig.is_empty() {
|
||||
state.dh_remote_pubkey_sig = Some(pb.identity_sig);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -389,12 +389,12 @@ async fn send_identity<T>(state: &mut State<T>) -> Result<(), NoiseError>
|
||||
where
|
||||
T: AsyncWrite + Unpin,
|
||||
{
|
||||
let mut pb = payload_proto::Identity::default();
|
||||
let mut pb = payload_proto::NoiseHandshakePayload::default();
|
||||
if state.send_identity {
|
||||
pb.pubkey = state.identity.public.clone().into_protobuf_encoding()
|
||||
pb.identity_key = state.identity.public.clone().into_protobuf_encoding()
|
||||
}
|
||||
if let Some(ref sig) = state.identity.signature {
|
||||
pb.signature = sig.clone()
|
||||
pb.identity_sig = sig.clone()
|
||||
}
|
||||
let mut buf = Vec::with_capacity(pb.encoded_len());
|
||||
pb.encode(&mut buf).expect("Vec<u8> provides capacity as needed");
|
||||
|
@ -4,8 +4,8 @@ package payload.proto;
|
||||
|
||||
// Payloads for Noise handshake messages.
|
||||
|
||||
message Identity {
|
||||
bytes pubkey = 1;
|
||||
bytes signature = 2;
|
||||
message NoiseHandshakePayload {
|
||||
bytes identity_key = 1;
|
||||
bytes identity_sig = 2;
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,9 @@
|
||||
//! implementations for various noise handshake patterns (currently `IK`, `IX`, and `XX`)
|
||||
//! over a particular choice of Diffie–Hellman key agreement (currently only X25519).
|
||||
//!
|
||||
//! > **Note**: Only the `XX` handshake pattern is currently guaranteed to provide
|
||||
//! > interoperability with other libp2p implementations.
|
||||
//!
|
||||
//! All upgrades produce as output a pair, consisting of the remote's static public key
|
||||
//! and a `NoiseOutput` which represents the established cryptographic session with the
|
||||
//! remote, implementing `futures::io::AsyncRead` and `futures::io::AsyncWrite`.
|
||||
@ -38,11 +41,11 @@
|
||||
//! ```
|
||||
//! use libp2p_core::{identity, Transport, upgrade};
|
||||
//! use libp2p_tcp::TcpConfig;
|
||||
//! use libp2p_noise::{Keypair, X25519, NoiseConfig};
|
||||
//! use libp2p_noise::{Keypair, X25519Spec, NoiseConfig};
|
||||
//!
|
||||
//! # fn main() {
|
||||
//! let id_keys = identity::Keypair::generate_ed25519();
|
||||
//! let dh_keys = Keypair::<X25519>::new().into_authentic(&id_keys).unwrap();
|
||||
//! let dh_keys = Keypair::<X25519Spec>::new().into_authentic(&id_keys).unwrap();
|
||||
//! let noise = NoiseConfig::xx(dh_keys).into_authenticated();
|
||||
//! let builder = TcpConfig::new().upgrade(upgrade::Version::V1).authenticate(noise);
|
||||
//! // let transport = builder.multiplex(...);
|
||||
@ -60,7 +63,8 @@ pub use io::NoiseOutput;
|
||||
pub use io::handshake;
|
||||
pub use io::handshake::{Handshake, RemoteIdentity, IdentityExchange};
|
||||
pub use protocol::{Keypair, AuthenticKeypair, KeypairIdentity, PublicKey, SecretKey};
|
||||
pub use protocol::{Protocol, ProtocolParams, x25519::X25519, IX, IK, XX};
|
||||
pub use protocol::{Protocol, ProtocolParams, IX, IK, XX};
|
||||
pub use protocol::{x25519::X25519, x25519_spec::X25519Spec};
|
||||
|
||||
use futures::prelude::*;
|
||||
use libp2p_core::{identity, PeerId, UpgradeInfo, InboundUpgrade, OutboundUpgrade};
|
||||
|
@ -21,6 +21,7 @@
|
||||
//! Components of a Noise protocol.
|
||||
|
||||
pub mod x25519;
|
||||
pub mod x25519_spec;
|
||||
|
||||
use crate::NoiseError;
|
||||
use libp2p_core::identity;
|
||||
@ -71,6 +72,7 @@ pub trait Protocol<C> {
|
||||
///
|
||||
/// 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
|
||||
}
|
||||
@ -87,6 +89,7 @@ pub trait Protocol<C> {
|
||||
/// 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.
|
||||
#[allow(deprecated)]
|
||||
fn verify(id_pk: &identity::PublicKey, dh_pk: &PublicKey<C>, sig: &Option<Vec<u8>>) -> bool
|
||||
where
|
||||
C: AsRef<[u8]>
|
||||
@ -95,6 +98,13 @@ pub trait Protocol<C> {
|
||||
||
|
||||
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>, NoiseError>
|
||||
where
|
||||
C: AsRef<[u8]>
|
||||
{
|
||||
Ok(id_keys.sign(dh_pk.as_ref())?)
|
||||
}
|
||||
}
|
||||
|
||||
/// DH keypair.
|
||||
@ -151,9 +161,10 @@ impl<T: Zeroize> Keypair<T> {
|
||||
/// 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>, NoiseError>
|
||||
where
|
||||
T: AsRef<[u8]>
|
||||
T: AsRef<[u8]>,
|
||||
T: Protocol<T>
|
||||
{
|
||||
let sig = id_keys.sign(self.public.as_ref())?;
|
||||
let sig = T::sign(id_keys, &self.public)?;
|
||||
|
||||
let identity = KeypairIdentity {
|
||||
public: id_keys.public(),
|
||||
|
@ -18,7 +18,10 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
//! Noise protocols based on X25519.
|
||||
//! Legacy Noise protocols based on X25519.
|
||||
//!
|
||||
//! **Note**: This set of protocols is not interoperable with other
|
||||
//! libp2p implementations.
|
||||
|
||||
use crate::{NoiseConfig, NoiseError, Protocol, ProtocolParams};
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
@ -92,7 +95,11 @@ impl<R> UpgradeInfo for NoiseConfig<IK, X25519, R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Noise protocols for X25519.
|
||||
/// 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()
|
||||
|
150
protocols/noise/src/protocol/x25519_spec.rs
Normal file
150
protocols/noise/src/protocol/x25519_spec.rs
Normal file
@ -0,0 +1,150 @@
|
||||
// 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::{NoiseConfig, NoiseError, Protocol, ProtocolParams};
|
||||
use libp2p_core::UpgradeInfo;
|
||||
use libp2p_core::identity;
|
||||
use rand::Rng;
|
||||
use x25519_dalek::{X25519_BASEPOINT_BYTES, x25519};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::{*, x25519::X25519};
|
||||
|
||||
/// 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> {
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 [u8];
|
||||
type InfoIter = std::iter::Once<Self::Info>;
|
||||
|
||||
fn protocol_info(&self) -> Self::InfoIter {
|
||||
std::iter::once(b"/noise")
|
||||
}
|
||||
}
|
||||
|
||||
/// 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::params_ik()
|
||||
}
|
||||
|
||||
fn params_ix() -> ProtocolParams {
|
||||
X25519::params_ix()
|
||||
}
|
||||
|
||||
fn params_xx() -> ProtocolParams {
|
||||
X25519::params_xx()
|
||||
}
|
||||
|
||||
fn public_from_bytes(bytes: &[u8]) -> Result<PublicKey<X25519Spec>, NoiseError> {
|
||||
if bytes.len() != 32 {
|
||||
return Err(NoiseError::InvalidKey)
|
||||
}
|
||||
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>, NoiseError> {
|
||||
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<(), ()> {
|
||||
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(())
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ use futures::{future::{self, Either}, prelude::*};
|
||||
use libp2p_core::identity;
|
||||
use libp2p_core::upgrade::{self, Negotiated, apply_inbound, apply_outbound};
|
||||
use libp2p_core::transport::{Transport, ListenerEvent};
|
||||
use libp2p_noise::{Keypair, X25519, NoiseConfig, RemoteIdentity, NoiseError, NoiseOutput};
|
||||
use libp2p_noise::{Keypair, X25519, X25519Spec, NoiseConfig, RemoteIdentity, NoiseError, NoiseOutput};
|
||||
use libp2p_tcp::{TcpConfig, TcpTransStream};
|
||||
use log::info;
|
||||
use quickcheck::QuickCheck;
|
||||
@ -38,6 +38,37 @@ fn core_upgrade_compat() {
|
||||
let _ = TcpConfig::new().upgrade(upgrade::Version::V1).authenticate(noise);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xx_spec() {
|
||||
let _ = env_logger::try_init();
|
||||
fn prop(mut messages: Vec<Message>) -> bool {
|
||||
messages.truncate(5);
|
||||
let server_id = identity::Keypair::generate_ed25519();
|
||||
let client_id = identity::Keypair::generate_ed25519();
|
||||
|
||||
let server_id_public = server_id.public();
|
||||
let client_id_public = client_id.public();
|
||||
|
||||
let server_dh = Keypair::<X25519Spec>::new().into_authentic(&server_id).unwrap();
|
||||
let server_transport = TcpConfig::new()
|
||||
.and_then(move |output, endpoint| {
|
||||
upgrade::apply(output, NoiseConfig::xx(server_dh), endpoint, upgrade::Version::V1)
|
||||
})
|
||||
.and_then(move |out, _| expect_identity(out, &client_id_public));
|
||||
|
||||
let client_dh = Keypair::<X25519Spec>::new().into_authentic(&client_id).unwrap();
|
||||
let client_transport = TcpConfig::new()
|
||||
.and_then(move |output, endpoint| {
|
||||
upgrade::apply(output, NoiseConfig::xx(client_dh), endpoint, upgrade::Version::V1)
|
||||
})
|
||||
.and_then(move |out, _| expect_identity(out, &server_id_public));
|
||||
|
||||
run(server_transport, client_transport, messages);
|
||||
true
|
||||
}
|
||||
QuickCheck::new().max_tests(30).quickcheck(prop as fn(Vec<Message>) -> bool)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xx() {
|
||||
let _ = env_logger::try_init();
|
||||
@ -144,15 +175,15 @@ fn ik_xx() {
|
||||
QuickCheck::new().max_tests(30).quickcheck(prop as fn(Vec<Message>) -> bool)
|
||||
}
|
||||
|
||||
type Output = (RemoteIdentity<X25519>, NoiseOutput<Negotiated<TcpTransStream>>);
|
||||
type Output<C> = (RemoteIdentity<C>, NoiseOutput<Negotiated<TcpTransStream>>);
|
||||
|
||||
fn run<T, U, I>(server_transport: T, client_transport: U, messages: I)
|
||||
fn run<T, U, I, C>(server_transport: T, client_transport: U, messages: I)
|
||||
where
|
||||
T: Transport<Output = Output>,
|
||||
T: Transport<Output = Output<C>>,
|
||||
T::Dial: Send + 'static,
|
||||
T::Listener: Send + Unpin + 'static,
|
||||
T::ListenerUpgrade: Send + 'static,
|
||||
U: Transport<Output = Output>,
|
||||
U: Transport<Output = Output<C>>,
|
||||
U::Dial: Send + 'static,
|
||||
U::Listener: Send + 'static,
|
||||
U::ListenerUpgrade: Send + 'static,
|
||||
@ -218,8 +249,8 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn expect_identity(output: Output, pk: &identity::PublicKey)
|
||||
-> impl Future<Output = Result<Output, NoiseError>>
|
||||
fn expect_identity<C>(output: Output<C>, pk: &identity::PublicKey)
|
||||
-> impl Future<Output = Result<Output<C>, NoiseError>>
|
||||
{
|
||||
match output.0 {
|
||||
RemoteIdentity::IdentityKey(ref k) if k == pk => future::ok(output),
|
||||
|
Loading…
x
Reference in New Issue
Block a user