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:
Roman Borschel 2020-05-06 14:30:23 +02:00 committed by GitHub
parent 69fd8ac195
commit bd5e81fedb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 242 additions and 26 deletions

View File

@ -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

View File

@ -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");

View File

@ -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;
}

View File

@ -27,6 +27,9 @@
//! implementations for various noise handshake patterns (currently `IK`, `IX`, and `XX`)
//! over a particular choice of DiffieHellman 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};

View File

@ -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(),

View File

@ -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()

View 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(())
}
}

View File

@ -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),