Move some crates. (#1941)

Move transport upgrade protocols from `protocols/`
to `transports/`, such that only "application protocols"
that depend on `libp2p-swarm` remain in `protocols/`,
whereas there is no such dependency in `transports/`
outside of integration tests.

Tweak README and top-level CHANGELOG.
This commit is contained in:
Roman Borschel
2021-02-01 16:37:19 +01:00
committed by GitHub
parent 2ecf42a2bf
commit d94d53abbb
41 changed files with 131 additions and 84 deletions

View File

@ -1,40 +0,0 @@
# 0.27.1 [2021-01-27]
- Ensure read buffers are initialised.
[PR 1933](https://github.com/libp2p/rust-libp2p/pull/1933).
# 0.27.0 [2021-01-12]
- Update dependencies.
# 0.26.0 [2020-12-17]
- Update `libp2p-core`.
# 0.25.0 [2020-11-25]
- Update `libp2p-core`.
# 0.24.0 [2020-11-09]
- Update dependencies.
# 0.23.0 [2020-10-16]
- Bump `libp2p-core` dependency.
# 0.22.0 [2020-09-09]
- Bump `libp2p-core` dependency.
# 0.21.0 [2020-08-18]
- Bump `libp2p-core` dependency.
# 0.20.0 [2020-07-01]
- Updated dependencies.
# 0.19.2 [2020-06-22]
- Updated dependencies.

View File

@ -1,21 +0,0 @@
[package]
name = "libp2p-deflate"
edition = "2018"
description = "Deflate encryption protocol for libp2p"
version = "0.27.1"
authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p"
keywords = ["peer-to-peer", "libp2p", "networking"]
categories = ["network-programming", "asynchronous"]
[dependencies]
futures = "0.3.1"
libp2p-core = { version = "0.27.0", path = "../../core" }
flate2 = "1.0"
[dev-dependencies]
async-std = "1.6.2"
libp2p-tcp = { path = "../../transports/tcp" }
quickcheck = "0.9"
rand = "0.7"

View File

@ -1,250 +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.
use futures::{prelude::*, ready};
use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo};
use std::{io, iter, pin::Pin, task::Context, task::Poll};
#[derive(Debug, Copy, Clone)]
pub struct DeflateConfig {
compression: flate2::Compression,
}
impl Default for DeflateConfig {
fn default() -> Self {
DeflateConfig {
compression: flate2::Compression::fast(),
}
}
}
impl UpgradeInfo for DeflateConfig {
type Info = &'static [u8];
type InfoIter = iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
iter::once(b"/deflate/1.0.0")
}
}
impl<C> InboundUpgrade<C> for DeflateConfig
where
C: AsyncRead + AsyncWrite,
{
type Output = DeflateOutput<C>;
type Error = io::Error;
type Future = future::Ready<Result<Self::Output, Self::Error>>;
fn upgrade_inbound(self, r: C, _: Self::Info) -> Self::Future {
future::ok(DeflateOutput::new(r, self.compression))
}
}
impl<C> OutboundUpgrade<C> for DeflateConfig
where
C: AsyncRead + AsyncWrite,
{
type Output = DeflateOutput<C>;
type Error = io::Error;
type Future = future::Ready<Result<Self::Output, Self::Error>>;
fn upgrade_outbound(self, w: C, _: Self::Info) -> Self::Future {
future::ok(DeflateOutput::new(w, self.compression))
}
}
/// Decodes and encodes traffic using DEFLATE.
#[derive(Debug)]
pub struct DeflateOutput<S> {
/// Inner stream where we read compressed data from and write compressed data to.
inner: S,
/// Internal object used to hold the state of the compression.
compress: flate2::Compress,
/// Internal object used to hold the state of the decompression.
decompress: flate2::Decompress,
/// Temporary buffer between `compress` and `inner`. Stores compressed bytes that need to be
/// sent out once `inner` is ready to accept more.
write_out: Vec<u8>,
/// Temporary buffer between `decompress` and `inner`. Stores compressed bytes that need to be
/// given to `decompress`.
read_interm: Vec<u8>,
/// When we read from `inner` and `Ok(0)` is returned, we set this to `true` so that we don't
/// read from it again.
inner_read_eof: bool,
}
impl<S> DeflateOutput<S> {
fn new(inner: S, compression: flate2::Compression) -> Self {
DeflateOutput {
inner,
compress: flate2::Compress::new(compression, false),
decompress: flate2::Decompress::new(false),
write_out: Vec::with_capacity(256),
read_interm: Vec::with_capacity(256),
inner_read_eof: false,
}
}
/// Tries to write the content of `self.write_out` to `self.inner`.
/// Returns `Ready(Ok(()))` if `self.write_out` is empty.
fn flush_write_out(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>>
where S: AsyncWrite + Unpin
{
loop {
if self.write_out.is_empty() {
return Poll::Ready(Ok(()))
}
match AsyncWrite::poll_write(Pin::new(&mut self.inner), cx, &self.write_out) {
Poll::Ready(Ok(0)) => return Poll::Ready(Err(io::ErrorKind::WriteZero.into())),
Poll::Ready(Ok(n)) => self.write_out = self.write_out.split_off(n),
Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
Poll::Pending => return Poll::Pending,
};
}
}
}
impl<S> AsyncRead for DeflateOutput<S>
where S: AsyncRead + Unpin
{
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<Result<usize, io::Error>> {
// We use a `this` variable because the compiler doesn't allow multiple mutable borrows
// across a `Deref`.
let this = &mut *self;
loop {
// Read from `self.inner` into `self.read_interm` if necessary.
if this.read_interm.is_empty() && !this.inner_read_eof {
this.read_interm.resize(this.read_interm.capacity() + 256, 0);
match AsyncRead::poll_read(Pin::new(&mut this.inner), cx, &mut this.read_interm) {
Poll::Ready(Ok(0)) => {
this.inner_read_eof = true;
this.read_interm.clear();
}
Poll::Ready(Ok(n)) => {
this.read_interm.truncate(n)
},
Poll::Ready(Err(err)) => {
this.read_interm.clear();
return Poll::Ready(Err(err))
},
Poll::Pending => {
this.read_interm.clear();
return Poll::Pending
},
}
}
debug_assert!(!this.read_interm.is_empty() || this.inner_read_eof);
let before_out = this.decompress.total_out();
let before_in = this.decompress.total_in();
let ret = this.decompress.decompress(&this.read_interm, buf, if this.inner_read_eof { flate2::FlushDecompress::Finish } else { flate2::FlushDecompress::None })?;
// Remove from `self.read_interm` the bytes consumed by the decompressor.
let consumed = (this.decompress.total_in() - before_in) as usize;
this.read_interm = this.read_interm.split_off(consumed);
let read = (this.decompress.total_out() - before_out) as usize;
if read != 0 || ret == flate2::Status::StreamEnd {
return Poll::Ready(Ok(read))
}
}
}
}
impl<S> AsyncWrite for DeflateOutput<S>
where S: AsyncWrite + Unpin
{
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8])
-> Poll<Result<usize, io::Error>>
{
// We use a `this` variable because the compiler doesn't allow multiple mutable borrows
// across a `Deref`.
let this = &mut *self;
// We don't want to accumulate too much data in `self.write_out`, so we only proceed if it
// is empty.
ready!(this.flush_write_out(cx))?;
// We special-case this, otherwise an empty buffer would make the loop below infinite.
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
// Unfortunately, the compressor might be in a "flushing mode", not accepting any input
// data. We don't want to return `Ok(0)` in that situation, as that would be wrong.
// Instead, we invoke the compressor in a loop until it accepts some of our data.
loop {
let before_in = this.compress.total_in();
this.write_out.reserve(256); // compress_vec uses the Vec's capacity
let ret = this.compress.compress_vec(buf, &mut this.write_out, flate2::FlushCompress::None)?;
let written = (this.compress.total_in() - before_in) as usize;
if written != 0 || ret == flate2::Status::StreamEnd {
return Poll::Ready(Ok(written));
}
}
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
// We use a `this` variable because the compiler doesn't allow multiple mutable borrows
// across a `Deref`.
let this = &mut *self;
ready!(this.flush_write_out(cx))?;
this.compress.compress_vec(&[], &mut this.write_out, flate2::FlushCompress::Sync)?;
loop {
ready!(this.flush_write_out(cx))?;
debug_assert!(this.write_out.is_empty());
// We ask the compressor to flush everything into `self.write_out`.
this.write_out.reserve(256); // compress_vec uses the Vec's capacity
this.compress.compress_vec(&[], &mut this.write_out, flate2::FlushCompress::None)?;
if this.write_out.is_empty() {
break;
}
}
AsyncWrite::poll_flush(Pin::new(&mut this.inner), cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
// We use a `this` variable because the compiler doesn't allow multiple mutable borrows
// across a `Deref`.
let this = &mut *self;
loop {
ready!(this.flush_write_out(cx))?;
// We ask the compressor to flush everything into `self.write_out`.
debug_assert!(this.write_out.is_empty());
this.write_out.reserve(256); // compress_vec uses the Vec's capacity
this.compress.compress_vec(&[], &mut this.write_out, flate2::FlushCompress::Finish)?;
if this.write_out.is_empty() {
break;
}
}
AsyncWrite::poll_close(Pin::new(&mut this.inner), cx)
}
}

View File

@ -1,94 +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.
use futures::{future, prelude::*};
use libp2p_core::{transport::Transport, upgrade};
use libp2p_deflate::DeflateConfig;
use libp2p_tcp::TcpConfig;
use quickcheck::{QuickCheck, RngCore, TestResult};
#[test]
fn deflate() {
fn prop(message: Vec<u8>) -> TestResult {
if message.is_empty() {
return TestResult::discard()
}
async_std::task::block_on(run(message));
TestResult::passed()
}
QuickCheck::new().quickcheck(prop as fn(Vec<u8>) -> TestResult)
}
#[test]
fn lot_of_data() {
let mut v = vec![0; 2 * 1024 * 1024];
rand::thread_rng().fill_bytes(&mut v);
async_std::task::block_on(run(v))
}
async fn run(message1: Vec<u8>) {
let transport = TcpConfig::new()
.and_then(|conn, endpoint| {
upgrade::apply(conn, DeflateConfig::default(), endpoint, upgrade::Version::V1)
});
let mut listener = transport.clone()
.listen_on("/ip4/0.0.0.0/tcp/0".parse().expect("multiaddr"))
.expect("listener");
let listen_addr = listener.by_ref().next().await
.expect("some event")
.expect("no error")
.into_new_address()
.expect("new address");
let message2 = message1.clone();
let listener_task = async_std::task::spawn(async move {
let mut conn = listener
.filter(|e| future::ready(e.as_ref().map(|e| e.is_upgrade()).unwrap_or(false)))
.next()
.await
.expect("some event")
.expect("no error")
.into_upgrade()
.expect("upgrade")
.0
.await
.expect("connection");
let mut buf = vec![0; message2.len()];
conn.read_exact(&mut buf).await.expect("read_exact");
assert_eq!(&buf[..], &message2[..]);
conn.write_all(&message2).await.expect("write_all");
conn.close().await.expect("close")
});
let mut conn = transport.dial(listen_addr).expect("dialer").await.expect("connection");
conn.write_all(&message1).await.expect("write_all");
conn.close().await.expect("close");
let mut buf = Vec::new();
conn.read_to_end(&mut buf).await.expect("read_to_end");
assert_eq!(&buf[..], &message1[..]);
listener_task.await
}

View File

@ -31,10 +31,10 @@ regex = "1.4.0"
[dev-dependencies]
async-std = "1.6.3"
env_logger = "0.8.1"
libp2p-plaintext = { path = "../plaintext" }
libp2p-plaintext = { path = "../../transports/plaintext" }
libp2p-yamux = { path = "../../muxers/yamux" }
libp2p-mplex = { path = "../../muxers/mplex" }
libp2p-noise = { path = "../../protocols/noise" }
libp2p-noise = { path = "../../transports/noise" }
quickcheck = "0.9.2"
hex = "0.4.2"
derive_builder = "0.9.0"

View File

@ -21,7 +21,7 @@ wasm-timer = "0.2"
[dev-dependencies]
async-std = "1.6.2"
libp2p-mplex = { path = "../../muxers/mplex" }
libp2p-noise = { path = "../../protocols/noise" }
libp2p-noise = { path = "../../transports/noise" }
libp2p-tcp = { path = "../../transports/tcp" }
[build-dependencies]

View File

@ -30,7 +30,7 @@ void = "1.0"
[dev-dependencies]
futures-timer = "3.0"
libp2p-noise = { path = "../noise" }
libp2p-noise = { path = "../../transports/noise" }
libp2p-yamux = { path = "../../muxers/yamux" }
quickcheck = "0.9.0"

View File

@ -1,73 +0,0 @@
# 0.29.0 [2021-01-12]
- Update dependencies.
# 0.28.0 [2020-12-17]
- Update `libp2p-core`.
# 0.27.0 [2020-11-25]
- Update `libp2p-core`.
# 0.26.0 [2020-11-09]
- Update dependencies.
# 0.25.0 [2020-10-16]
- Update dependencies.
# 0.24.0 [2020-09-09]
- Bump `libp2p-core` dependency.
- Remove fallback legacy handshake payload decoding by default.
To continue supporting inbound legacy handshake payloads,
`recv_legacy_handshake` must be configured on the `LegacyConfig`.
# 0.23.0 [2020-08-18]
- Bump `libp2p-core` dependency.
# 0.22.0 [2020-08-03]
**NOTE**: For a smooth upgrade path from `0.20` to `> 0.21`
on an existing deployment, this version must not be skipped
or the provided `LegacyConfig` used!
- Stop sending length-prefixed protobuf frames in handshake
payloads by default. See [issue 1631](https://github.com/libp2p/rust-libp2p/issues/1631).
The new `LegacyConfig` is provided to optionally
configure sending the legacy handshake. Note: This release
always supports receiving legacy handshake payloads. A future
release will also move receiving legacy handshake payloads
into a `LegacyConfig` option. However, all legacy configuration
options will eventually be removed, so this is primarily to allow
delaying the handshake upgrade or keeping compatibility with a network
whose peers are slow to upgrade, without having to freeze the
version of `libp2p-noise` altogether in these projects.
# 0.21.0 [2020-07-17]
**NOTE**: For a smooth upgrade path from `0.20` to `> 0.21`
on an existing deployment, this version must not be skipped!
- Add support for reading handshake protobuf frames without
length prefixes in preparation for no longer sending them.
See [issue 1631](https://github.com/libp2p/rust-libp2p/issues/1631).
- Update the `snow` dependency to the latest patch version.
# 0.20.0 [2020-07-01]
- Updated dependencies.
- Conditional compilation fixes for the `wasm32-wasi` target
([PR 1633](https://github.com/libp2p/rust-libp2p/pull/1633)).
# 0.19.1 [2020-06-22]
- Re-add noise upgrades for IK and IX
([PR 1580](https://github.com/libp2p/rust-libp2p/pull/1580)).
- Updated dependencies.

View File

@ -1,38 +0,0 @@
[package]
name = "libp2p-noise"
description = "Cryptographic handshake protocol using the noise framework."
version = "0.29.0"
authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p"
edition = "2018"
[dependencies]
bytes = "1"
curve25519-dalek = "3.0.0"
futures = "0.3.1"
lazy_static = "1.2"
libp2p-core = { version = "0.27.0", path = "../../core" }
log = "0.4"
prost = "0.7"
rand = "0.7.2"
sha2 = "0.9.1"
static_assertions = "1"
x25519-dalek = "1.1.0"
zeroize = "1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
snow = { version = "0.7.1", features = ["ring-resolver"], default-features = false }
[target.'cfg(target_arch = "wasm32")'.dependencies]
snow = { version = "0.7.1", features = ["default-resolver"], default-features = false }
[dev-dependencies]
async-io = "1.2.0"
env_logger = "0.8.1"
libp2p-tcp = { path = "../../transports/tcp" }
quickcheck = "0.9.0"
sodiumoxide = "0.2.5"
[build-dependencies]
prost-build = "0.7"

View File

@ -1,23 +0,0 @@
// Copyright 2020 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.
fn main() {
prost_build::compile_protos(&["src/io/handshake/payload.proto"], &["src"]).unwrap();
}

View File

@ -1,96 +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.
use libp2p_core::identity;
use snow::error::Error as SnowError;
use std::{error::Error, fmt, io};
/// libp2p_noise error type.
#[derive(Debug)]
pub enum NoiseError {
/// An I/O error has been encountered.
Io(io::Error),
/// An noise framework error has been encountered.
Noise(SnowError),
/// A public key is invalid.
InvalidKey,
/// Authentication in a [`NoiseAuthenticated`](crate::NoiseAuthenticated)
/// upgrade failed.
AuthenticationFailed,
/// A handshake payload is invalid.
InvalidPayload(prost::DecodeError),
/// A signature was required and could not be created.
SigningError(identity::error::SigningError),
#[doc(hidden)]
__Nonexhaustive
}
impl fmt::Display for NoiseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NoiseError::Io(e) => write!(f, "{}", e),
NoiseError::Noise(e) => write!(f, "{}", e),
NoiseError::InvalidKey => f.write_str("invalid public key"),
NoiseError::InvalidPayload(e) => write!(f, "{}", e),
NoiseError::AuthenticationFailed => f.write_str("Authentication failed"),
NoiseError::SigningError(e) => write!(f, "{}", e),
NoiseError::__Nonexhaustive => f.write_str("__Nonexhaustive")
}
}
}
impl Error for NoiseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
NoiseError::Io(e) => Some(e),
NoiseError::Noise(_) => None, // TODO: `SnowError` should implement `Error`.
NoiseError::InvalidKey => None,
NoiseError::AuthenticationFailed => None,
NoiseError::InvalidPayload(e) => Some(e),
NoiseError::SigningError(e) => Some(e),
NoiseError::__Nonexhaustive => None
}
}
}
impl From<io::Error> for NoiseError {
fn from(e: io::Error) -> Self {
NoiseError::Io(e)
}
}
impl From<SnowError> for NoiseError {
fn from(e: SnowError) -> Self {
NoiseError::Noise(e)
}
}
impl From<prost::DecodeError> for NoiseError {
fn from(e: prost::DecodeError) -> Self {
NoiseError::InvalidPayload(e)
}
}
impl From<identity::error::SigningError> for NoiseError {
fn from(e: identity::error::SigningError) -> Self {
NoiseError::SigningError(e)
}
}

View File

@ -1,141 +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.
//! Noise protocol I/O.
mod framed;
pub mod handshake;
use bytes::Bytes;
use framed::{MAX_FRAME_LEN, NoiseFramed};
use futures::ready;
use futures::prelude::*;
use log::trace;
use std::{cmp::min, fmt, io, pin::Pin, task::{Context, Poll}};
/// A noise session to a remote.
///
/// `T` is the type of the underlying I/O resource.
pub struct NoiseOutput<T> {
io: NoiseFramed<T, snow::TransportState>,
recv_buffer: Bytes,
recv_offset: usize,
send_buffer: Vec<u8>,
send_offset: usize,
}
impl<T> fmt::Debug for NoiseOutput<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NoiseOutput")
.field("io", &self.io)
.finish()
}
}
impl<T> NoiseOutput<T> {
fn new(io: NoiseFramed<T, snow::TransportState>) -> Self {
NoiseOutput {
io,
recv_buffer: Bytes::new(),
recv_offset: 0,
send_buffer: Vec::new(),
send_offset: 0,
}
}
}
impl<T: AsyncRead + Unpin> AsyncRead for NoiseOutput<T> {
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<io::Result<usize>> {
loop {
let len = self.recv_buffer.len();
let off = self.recv_offset;
if len > 0 {
let n = min(len - off, buf.len());
buf[.. n].copy_from_slice(&self.recv_buffer[off .. off + n]);
trace!("read: copied {}/{} bytes", off + n, len);
self.recv_offset += n;
if len == self.recv_offset {
trace!("read: frame consumed");
// Drop the existing view so `NoiseFramed` can reuse
// the buffer when polling for the next frame below.
self.recv_buffer = Bytes::new();
}
return Poll::Ready(Ok(n))
}
match Pin::new(&mut self.io).poll_next(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(Ok(0)),
Poll::Ready(Some(Err(e))) => return Poll::Ready(Err(e)),
Poll::Ready(Some(Ok(frame))) => {
self.recv_buffer = frame;
self.recv_offset = 0;
}
}
}
}
}
impl<T: AsyncWrite + Unpin> AsyncWrite for NoiseOutput<T> {
fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
let this = Pin::into_inner(self);
let mut io = Pin::new(&mut this.io);
let frame_buf = &mut this.send_buffer;
// The MAX_FRAME_LEN is the maximum buffer size before a frame must be sent.
if this.send_offset == MAX_FRAME_LEN {
trace!("write: sending {} bytes", MAX_FRAME_LEN);
ready!(io.as_mut().poll_ready(cx))?;
io.as_mut().start_send(&frame_buf)?;
this.send_offset = 0;
}
let off = this.send_offset;
let n = min(MAX_FRAME_LEN, off.saturating_add(buf.len()));
this.send_buffer.resize(n, 0u8);
let n = min(MAX_FRAME_LEN - off, buf.len());
this.send_buffer[off .. off + n].copy_from_slice(&buf[.. n]);
this.send_offset += n;
trace!("write: buffered {} bytes", this.send_offset);
return Poll::Ready(Ok(n))
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let this = Pin::into_inner(self);
let mut io = Pin::new(&mut this.io);
let frame_buf = &mut this.send_buffer;
// Check if there is still one more frame to send.
if this.send_offset > 0 {
ready!(io.as_mut().poll_ready(cx))?;
trace!("flush: sending {} bytes", this.send_offset);
io.as_mut().start_send(&frame_buf)?;
this.send_offset = 0;
}
io.as_mut().poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>>{
ready!(self.as_mut().poll_flush(cx))?;
Pin::new(&mut self.io).poll_close(cx)
}
}

View File

@ -1,439 +0,0 @@
// Copyright 2020 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.
//! This module provides a `Sink` and `Stream` for length-delimited
//! Noise protocol messages in form of [`NoiseFramed`].
use bytes::{Bytes, BytesMut};
use crate::{NoiseError, Protocol, PublicKey};
use crate::io::NoiseOutput;
use futures::ready;
use futures::prelude::*;
use log::{debug, trace};
use snow;
use std::{fmt, io, pin::Pin, task::{Context, Poll}};
/// Max. size of a noise message.
const MAX_NOISE_MSG_LEN: usize = 65535;
/// Space given to the encryption buffer to hold key material.
const EXTRA_ENCRYPT_SPACE: usize = 1024;
/// Max. length for Noise protocol message payloads.
pub const MAX_FRAME_LEN: usize = MAX_NOISE_MSG_LEN - EXTRA_ENCRYPT_SPACE;
static_assertions::const_assert! {
MAX_FRAME_LEN + EXTRA_ENCRYPT_SPACE <= MAX_NOISE_MSG_LEN
}
/// A `NoiseFramed` is a `Sink` and `Stream` for length-delimited
/// Noise protocol messages.
///
/// `T` is the type of the underlying I/O resource and `S` the
/// type of the Noise session state.
pub struct NoiseFramed<T, S> {
io: T,
session: S,
read_state: ReadState,
write_state: WriteState,
read_buffer: Vec<u8>,
write_buffer: Vec<u8>,
decrypt_buffer: BytesMut,
}
impl<T, S> fmt::Debug for NoiseFramed<T, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NoiseFramed")
.field("read_state", &self.read_state)
.field("write_state", &self.write_state)
.finish()
}
}
impl<T> NoiseFramed<T, snow::HandshakeState> {
/// Creates a nwe `NoiseFramed` for beginning a Noise protocol handshake.
pub fn new(io: T, state: snow::HandshakeState) -> Self {
NoiseFramed {
io,
session: state,
read_state: ReadState::Ready,
write_state: WriteState::Ready,
read_buffer: Vec::new(),
write_buffer: Vec::new(),
decrypt_buffer: BytesMut::new(),
}
}
/// Converts the `NoiseFramed` into a `NoiseOutput` encrypted data stream
/// once the handshake is complete, including the static DH [`PublicKey`]
/// of the remote, if received.
///
/// If the underlying Noise protocol session state does not permit
/// transitioning to transport mode because the handshake is incomplete,
/// an error is returned. Similarly if the remote's static DH key, if
/// present, cannot be parsed.
pub fn into_transport<C>(self) -> Result<(Option<PublicKey<C>>, NoiseOutput<T>), NoiseError>
where
C: Protocol<C> + AsRef<[u8]>
{
let dh_remote_pubkey = match self.session.get_remote_static() {
None => None,
Some(k) => match C::public_from_bytes(k) {
Err(e) => return Err(e),
Ok(dh_pk) => Some(dh_pk)
}
};
match self.session.into_transport_mode() {
Err(e) => Err(e.into()),
Ok(s) => {
let io = NoiseFramed {
session: s,
io: self.io,
read_state: ReadState::Ready,
write_state: WriteState::Ready,
read_buffer: self.read_buffer,
write_buffer: self.write_buffer,
decrypt_buffer: self.decrypt_buffer,
};
Ok((dh_remote_pubkey, NoiseOutput::new(io)))
}
}
}
}
/// The states for reading Noise protocol frames.
#[derive(Debug)]
enum ReadState {
/// Ready to read another frame.
Ready,
/// Reading frame length.
ReadLen { buf: [u8; 2], off: usize },
/// Reading frame data.
ReadData { len: usize, off: usize },
/// EOF has been reached (terminal state).
///
/// The associated result signals if the EOF was unexpected or not.
Eof(Result<(), ()>),
/// A decryption error occurred (terminal state).
DecErr
}
/// The states for writing Noise protocol frames.
#[derive(Debug)]
enum WriteState {
/// Ready to write another frame.
Ready,
/// Writing the frame length.
WriteLen { len: usize, buf: [u8; 2], off: usize },
/// Writing the frame data.
WriteData { len: usize, off: usize },
/// EOF has been reached unexpectedly (terminal state).
Eof,
/// An encryption error occurred (terminal state).
EncErr
}
impl WriteState {
fn is_ready(&self) -> bool {
if let WriteState::Ready = self {
return true
}
false
}
}
impl<T, S> futures::stream::Stream for NoiseFramed<T, S>
where
T: AsyncRead + Unpin,
S: SessionState + Unpin
{
type Item = io::Result<Bytes>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut this = Pin::into_inner(self);
loop {
trace!("read state: {:?}", this.read_state);
match this.read_state {
ReadState::Ready => {
this.read_state = ReadState::ReadLen { buf: [0, 0], off: 0 };
}
ReadState::ReadLen { mut buf, mut off } => {
let n = match read_frame_len(&mut this.io, cx, &mut buf, &mut off) {
Poll::Ready(Ok(Some(n))) => n,
Poll::Ready(Ok(None)) => {
trace!("read: eof");
this.read_state = ReadState::Eof(Ok(()));
return Poll::Ready(None)
}
Poll::Ready(Err(e)) => {
return Poll::Ready(Some(Err(e)))
}
Poll::Pending => {
this.read_state = ReadState::ReadLen { buf, off };
return Poll::Pending;
}
};
trace!("read: frame len = {}", n);
if n == 0 {
trace!("read: empty frame");
this.read_state = ReadState::Ready;
continue
}
this.read_buffer.resize(usize::from(n), 0u8);
this.read_state = ReadState::ReadData { len: usize::from(n), off: 0 }
}
ReadState::ReadData { len, ref mut off } => {
let n = {
let f = Pin::new(&mut this.io).poll_read(cx, &mut this.read_buffer[*off .. len]);
match ready!(f) {
Ok(n) => n,
Err(e) => return Poll::Ready(Some(Err(e))),
}
};
trace!("read: {}/{} bytes", *off + n, len);
if n == 0 {
trace!("read: eof");
this.read_state = ReadState::Eof(Err(()));
return Poll::Ready(Some(Err(io::ErrorKind::UnexpectedEof.into())))
}
*off += n;
if len == *off {
trace!("read: decrypting {} bytes", len);
this.decrypt_buffer.resize(len, 0);
if let Ok(n) = this.session.read_message(&this.read_buffer, &mut this.decrypt_buffer) {
this.decrypt_buffer.truncate(n);
trace!("read: payload len = {} bytes", n);
this.read_state = ReadState::Ready;
// Return an immutable view into the current buffer.
// If the view is dropped before the next frame is
// read, the `BytesMut` will reuse the same buffer
// for the next frame.
let view = this.decrypt_buffer.split().freeze();
return Poll::Ready(Some(Ok(view)))
} else {
debug!("read: decryption error");
this.read_state = ReadState::DecErr;
return Poll::Ready(Some(Err(io::ErrorKind::InvalidData.into())))
}
}
}
ReadState::Eof(Ok(())) => {
trace!("read: eof");
return Poll::Ready(None)
}
ReadState::Eof(Err(())) => {
trace!("read: eof (unexpected)");
return Poll::Ready(Some(Err(io::ErrorKind::UnexpectedEof.into())))
}
ReadState::DecErr => return Poll::Ready(Some(Err(io::ErrorKind::InvalidData.into())))
}
}
}
}
impl<T, S> futures::sink::Sink<&Vec<u8>> for NoiseFramed<T, S>
where
T: AsyncWrite + Unpin,
S: SessionState + Unpin
{
type Error = io::Error;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let mut this = Pin::into_inner(self);
loop {
trace!("write state {:?}", this.write_state);
match this.write_state {
WriteState::Ready => {
return Poll::Ready(Ok(()));
}
WriteState::WriteLen { len, mut buf, mut off } => {
trace!("write: frame len ({}, {:?}, {}/2)", len, buf, off);
match write_frame_len(&mut this.io, cx, &mut buf, &mut off) {
Poll::Ready(Ok(true)) => (),
Poll::Ready(Ok(false)) => {
trace!("write: eof");
this.write_state = WriteState::Eof;
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()))
}
Poll::Ready(Err(e)) => {
return Poll::Ready(Err(e))
}
Poll::Pending => {
this.write_state = WriteState::WriteLen { len, buf, off };
return Poll::Pending
}
}
this.write_state = WriteState::WriteData { len, off: 0 }
}
WriteState::WriteData { len, ref mut off } => {
let n = {
let f = Pin::new(&mut this.io).poll_write(cx, &this.write_buffer[*off .. len]);
match ready!(f) {
Ok(n) => n,
Err(e) => return Poll::Ready(Err(e)),
}
};
if n == 0 {
trace!("write: eof");
this.write_state = WriteState::Eof;
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()))
}
*off += n;
trace!("write: {}/{} bytes written", *off, len);
if len == *off {
trace!("write: finished with {} bytes", len);
this.write_state = WriteState::Ready;
}
}
WriteState::Eof => {
trace!("write: eof");
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()))
}
WriteState::EncErr => return Poll::Ready(Err(io::ErrorKind::InvalidData.into()))
}
}
}
fn start_send(self: Pin<&mut Self>, frame: &Vec<u8>) -> Result<(), Self::Error> {
assert!(frame.len() <= MAX_FRAME_LEN);
let mut this = Pin::into_inner(self);
assert!(this.write_state.is_ready());
this.write_buffer.resize(frame.len() + EXTRA_ENCRYPT_SPACE, 0u8);
match this.session.write_message(frame, &mut this.write_buffer[..]) {
Ok(n) => {
trace!("write: cipher text len = {} bytes", n);
this.write_buffer.truncate(n);
this.write_state = WriteState::WriteLen {
len: n,
buf: u16::to_be_bytes(n as u16),
off: 0
};
return Ok(())
}
Err(e) => {
log::error!("encryption error: {:?}", e);
this.write_state = WriteState::EncErr;
return Err(io::ErrorKind::InvalidData.into())
}
}
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
ready!(self.as_mut().poll_ready(cx))?;
Pin::new(&mut self.io).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
ready!(self.as_mut().poll_flush(cx))?;
Pin::new(&mut self.io).poll_close(cx)
}
}
/// A stateful context in which Noise protocol messages can be read and written.
pub trait SessionState {
fn read_message(&mut self, msg: &[u8], buf: &mut [u8]) -> Result<usize, snow::Error>;
fn write_message(&mut self, msg: &[u8], buf: &mut [u8]) -> Result<usize, snow::Error>;
}
impl SessionState for snow::HandshakeState {
fn read_message(&mut self, msg: &[u8], buf: &mut [u8]) -> Result<usize, snow::Error> {
self.read_message(msg, buf)
}
fn write_message(&mut self, msg: &[u8], buf: &mut [u8]) -> Result<usize, snow::Error> {
self.write_message(msg, buf)
}
}
impl SessionState for snow::TransportState {
fn read_message(&mut self, msg: &[u8], buf: &mut [u8]) -> Result<usize, snow::Error> {
self.read_message(msg, buf)
}
fn write_message(&mut self, msg: &[u8], buf: &mut [u8]) -> Result<usize, snow::Error> {
self.write_message(msg, buf)
}
}
/// Read 2 bytes as frame length from the given source into the given buffer.
///
/// Panics if `off >= 2`.
///
/// When [`Poll::Pending`] is returned, the given buffer and offset
/// may have been updated (i.e. a byte may have been read) and must be preserved
/// for the next invocation.
///
/// Returns `None` if EOF has been encountered.
fn read_frame_len<R: AsyncRead + Unpin>(
mut io: &mut R,
cx: &mut Context<'_>,
buf: &mut [u8; 2],
off: &mut usize,
) -> Poll<io::Result<Option<u16>>> {
loop {
match ready!(Pin::new(&mut io).poll_read(cx, &mut buf[*off ..])) {
Ok(n) => {
if n == 0 {
return Poll::Ready(Ok(None));
}
*off += n;
if *off == 2 {
return Poll::Ready(Ok(Some(u16::from_be_bytes(*buf))));
}
},
Err(e) => {
return Poll::Ready(Err(e));
},
}
}
}
/// Write 2 bytes as frame length from the given buffer into the given sink.
///
/// Panics if `off >= 2`.
///
/// When [`Poll::Pending`] is returned, the given offset
/// may have been updated (i.e. a byte may have been written) and must
/// be preserved for the next invocation.
///
/// Returns `false` if EOF has been encountered.
fn write_frame_len<W: AsyncWrite + Unpin>(
mut io: &mut W,
cx: &mut Context<'_>,
buf: &[u8; 2],
off: &mut usize,
) -> Poll<io::Result<bool>> {
loop {
match ready!(Pin::new(&mut io).poll_write(cx, &buf[*off ..])) {
Ok(n) => {
if n == 0 {
return Poll::Ready(Ok(false))
}
*off += n;
if *off == 2 {
return Poll::Ready(Ok(true))
}
}
Err(e) => {
return Poll::Ready(Err(e));
}
}
}
}

View File

@ -1,455 +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.
//! Noise protocol handshake I/O.
mod payload_proto {
include!(concat!(env!("OUT_DIR"), "/payload.proto.rs"));
}
use bytes::Bytes;
use crate::LegacyConfig;
use crate::error::NoiseError;
use crate::protocol::{Protocol, PublicKey, KeypairIdentity};
use crate::io::{NoiseOutput, framed::NoiseFramed};
use libp2p_core::identity;
use futures::prelude::*;
use futures::task;
use prost::Message;
use std::{io, pin::Pin, task::Context};
/// The identity of the remote established during a handshake.
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)
}
/// The options for identity exchange in an authenticated handshake.
///
/// > **Note**: Even if a remote's public identity key is known a priori,
/// > unless the authenticity of the key is [linked](Protocol::linked) to
/// > the authenticity of a remote's static DH public key, an authenticated
/// > handshake will still send the associated signature of the provided
/// > local [`KeypairIdentity`] in order for the remote to verify that the static
/// > DH public key is authentic w.r.t. the known public identity key.
pub enum IdentityExchange {
/// Send the local public identity to the remote.
///
/// The remote identity is unknown (i.e. expected to be received).
Mutual,
/// Send the local public identity to the remote.
///
/// The remote identity is known.
Send { remote: identity::PublicKey },
/// Don't send the local public identity to the remote.
///
/// The remote identity is unknown, i.e. expected to be received.
Receive,
/// Don't send the local public identity to the remote.
///
/// The remote identity is known, thus identities must be mutually known
/// in order for the handshake to succeed.
None { remote: identity::PublicKey }
}
/// A future performing a Noise handshake pattern.
pub struct Handshake<T, C>(
Pin<Box<dyn Future<
Output = Result<(RemoteIdentity<C>, NoiseOutput<T>), NoiseError>,
> + Send>>
);
impl<T, C> Future for Handshake<T, C> {
type Output = Result<(RemoteIdentity<C>, NoiseOutput<T>), NoiseError>;
fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> task::Poll<Self::Output> {
Pin::new(&mut self.0).poll(ctx)
}
}
/// Creates an authenticated Noise handshake for the initiator of a
/// single roundtrip (2 message) handshake pattern.
///
/// Subject to the chosen [`IdentityExchange`], this message sequence
/// identifies the local node to the remote with the first message payload
/// (i.e. unencrypted) and expects the remote to identify itself in the
/// second message payload.
///
/// This message sequence is suitable for authenticated 2-message Noise handshake
/// patterns where the static keys of the initiator and responder are either
/// known (i.e. appear in the pre-message pattern) or are sent with
/// the first and second message, respectively (e.g. `IK` or `IX`).
///
/// ```raw
/// initiator -{id}-> responder
/// initiator <-{id}- responder
/// ```
pub fn rt1_initiator<T, C>(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Handshake<T, C>
where
T: AsyncWrite + AsyncRead + Send + Unpin + 'static,
C: Protocol<C> + AsRef<[u8]>
{
Handshake(Box::pin(async move {
let mut state = State::new(io, session, identity, identity_x, legacy)?;
send_identity(&mut state).await?;
recv_identity(&mut state).await?;
state.finish()
}))
}
/// Creates an authenticated Noise handshake for the responder of a
/// single roundtrip (2 message) handshake pattern.
///
/// Subject to the chosen [`IdentityExchange`], this message sequence expects the
/// remote to identify itself in the first message payload (i.e. unencrypted)
/// and identifies the local node to the remote in the second message payload.
///
/// This message sequence is suitable for authenticated 2-message Noise handshake
/// patterns where the static keys of the initiator and responder are either
/// known (i.e. appear in the pre-message pattern) or are sent with the first
/// and second message, respectively (e.g. `IK` or `IX`).
///
/// ```raw
/// initiator -{id}-> responder
/// initiator <-{id}- responder
/// ```
pub fn rt1_responder<T, C>(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Handshake<T, C>
where
T: AsyncWrite + AsyncRead + Send + Unpin + 'static,
C: Protocol<C> + AsRef<[u8]>
{
Handshake(Box::pin(async move {
let mut state = State::new(io, session, identity, identity_x, legacy)?;
recv_identity(&mut state).await?;
send_identity(&mut state).await?;
state.finish()
}))
}
/// Creates an authenticated Noise handshake for the initiator of a
/// 1.5-roundtrip (3 message) handshake pattern.
///
/// Subject to the chosen [`IdentityExchange`], this message sequence expects
/// the remote to identify itself in the second message payload and
/// identifies the local node to the remote in the third message payload.
/// The first (unencrypted) message payload is always empty.
///
/// This message sequence is suitable for authenticated 3-message Noise handshake
/// patterns where the static keys of the responder and initiator are either known
/// (i.e. appear in the pre-message pattern) or are sent with the second and third
/// message, respectively (e.g. `XX`).
///
/// ```raw
/// initiator --{}--> responder
/// initiator <-{id}- responder
/// initiator -{id}-> responder
/// ```
pub fn rt15_initiator<T, C>(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Handshake<T, C>
where
T: AsyncWrite + AsyncRead + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]>
{
Handshake(Box::pin(async move {
let mut state = State::new(io, session, identity, identity_x, legacy)?;
send_empty(&mut state).await?;
recv_identity(&mut state).await?;
send_identity(&mut state).await?;
state.finish()
}))
}
/// Creates an authenticated Noise handshake for the responder of a
/// 1.5-roundtrip (3 message) handshake pattern.
///
/// Subject to the chosen [`IdentityExchange`], this message sequence
/// identifies the local node in the second message payload and expects
/// the remote to identify itself in the third message payload. The first
/// (unencrypted) message payload is always empty.
///
/// This message sequence is suitable for authenticated 3-message Noise handshake
/// patterns where the static keys of the responder and initiator are either known
/// (i.e. appear in the pre-message pattern) or are sent with the second and third
/// message, respectively (e.g. `XX`).
///
/// ```raw
/// initiator --{}--> responder
/// initiator <-{id}- responder
/// initiator -{id}-> responder
/// ```
pub fn rt15_responder<T, C>(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Handshake<T, C>
where
T: AsyncWrite + AsyncRead + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]>
{
Handshake(Box::pin(async move {
let mut state = State::new(io, session, identity, identity_x, legacy)?;
recv_empty(&mut state).await?;
send_identity(&mut state).await?;
recv_identity(&mut state).await?;
state.finish()
}))
}
//////////////////////////////////////////////////////////////////////////////
// Internal
/// Handshake state.
struct State<T> {
/// The underlying I/O resource.
io: NoiseFramed<T, snow::HandshakeState>,
/// The associated public identity of the local node's static DH keypair,
/// which can be sent to the remote as part of an authenticated handshake.
identity: KeypairIdentity,
/// The received signature over the remote's static DH public key, if any.
dh_remote_pubkey_sig: Option<Vec<u8>>,
/// The known or received public identity key of the remote, if any.
id_remote_pubkey: Option<identity::PublicKey>,
/// Whether to send the public identity key of the local node to the remote.
send_identity: bool,
/// Legacy configuration parameters.
legacy: LegacyConfig,
}
impl<T> State<T> {
/// Initializes the state for a new Noise handshake, using the given local
/// identity keypair and local DH static public key. The handshake messages
/// will be sent and received on the given I/O resource and using the
/// provided session for cryptographic operations according to the chosen
/// Noise handshake pattern.
fn new(
io: T,
session: Result<snow::HandshakeState, NoiseError>,
identity: KeypairIdentity,
identity_x: IdentityExchange,
legacy: LegacyConfig,
) -> Result<Self, NoiseError> {
let (id_remote_pubkey, send_identity) = match identity_x {
IdentityExchange::Mutual => (None, true),
IdentityExchange::Send { remote } => (Some(remote), true),
IdentityExchange::Receive => (None, false),
IdentityExchange::None { remote } => (Some(remote), false)
};
session.map(|s|
State {
identity,
io: NoiseFramed::new(io, s),
dh_remote_pubkey_sig: None,
id_remote_pubkey,
send_identity,
legacy,
}
)
}
}
impl<T> State<T>
{
/// Finish a handshake, yielding the established remote identity and the
/// [`NoiseOutput`] for communicating on the encrypted channel.
fn finish<C>(self) -> Result<(RemoteIdentity<C>, NoiseOutput<T>), NoiseError>
where
C: Protocol<C> + AsRef<[u8]>
{
let (pubkey, io) = self.io.into_transport()?;
let remote = match (self.id_remote_pubkey, pubkey) {
(_, None) => RemoteIdentity::Unknown,
(None, Some(dh_pk)) => RemoteIdentity::StaticDhKey(dh_pk),
(Some(id_pk), Some(dh_pk)) => {
if C::verify(&id_pk, &dh_pk, &self.dh_remote_pubkey_sig) {
RemoteIdentity::IdentityKey(id_pk)
} else {
return Err(NoiseError::InvalidKey)
}
}
};
Ok((remote, io))
}
}
//////////////////////////////////////////////////////////////////////////////
// Handshake Message Futures
/// A future for receiving a Noise handshake message.
async fn recv<T>(state: &mut State<T>) -> Result<Bytes, NoiseError>
where
T: AsyncRead + Unpin
{
match state.io.next().await {
None => Err(io::Error::new(io::ErrorKind::UnexpectedEof, "eof").into()),
Some(Err(e)) => Err(e.into()),
Some(Ok(m)) => Ok(m),
}
}
/// A future for receiving a Noise handshake message with an empty payload.
async fn recv_empty<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncRead + Unpin
{
let msg = recv(state).await?;
if !msg.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Unexpected handshake payload.").into())
}
Ok(())
}
/// A future for sending a Noise handshake message with an empty payload.
async fn send_empty<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncWrite + Unpin
{
state.io.send(&Vec::new()).await?;
Ok(())
}
/// A future for receiving a Noise handshake message with a payload
/// identifying the remote.
async fn recv_identity<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncRead + Unpin,
{
let msg = recv(state).await?;
let mut pb_result = payload_proto::NoiseHandshakePayload::decode(&msg[..]);
if pb_result.is_err() && state.legacy.recv_legacy_handshake {
// 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.");
payload_proto::NoiseHandshakePayload::decode(&msg[2 ..])
} else {
Err(e)
}
} else {
Err(e)
}
});
}
let pb = pb_result?;
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 {
return Err(NoiseError::InvalidKey)
}
}
state.id_remote_pubkey = Some(pk);
}
if !pb.identity_sig.is_empty() {
state.dh_remote_pubkey_sig = Some(pb.identity_sig);
}
Ok(())
}
/// Send a Noise handshake message with a payload identifying the local node to the remote.
async fn send_identity<T>(state: &mut State<T>) -> Result<(), NoiseError>
where
T: AsyncWrite + Unpin,
{
let mut pb = payload_proto::NoiseHandshakePayload::default();
if state.send_identity {
pb.identity_key = state.identity.public.clone().into_protobuf_encoding()
}
if let Some(ref sig) = state.identity.signature {
pb.identity_sig = sig.clone()
}
let mut msg =
if state.legacy.send_legacy_handshake {
let mut msg = Vec::with_capacity(2 + pb.encoded_len());
msg.extend_from_slice(&(pb.encoded_len() as u16).to_be_bytes());
msg
} else {
Vec::with_capacity(pb.encoded_len())
};
pb.encode(&mut msg).expect("Vec<u8> provides capacity as needed");
state.io.send(&msg).await?;
Ok(())
}

View File

@ -1,11 +0,0 @@
syntax = "proto3";
package payload.proto;
// Payloads for Noise handshake messages.
message NoiseHandshakePayload {
bytes identity_key = 1;
bytes identity_sig = 2;
bytes data = 3;
}

View File

@ -1,408 +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.
//! [Noise protocol framework][noise] support for libp2p.
//!
//! > **Note**: This crate is still experimental and subject to major breaking changes
//! > both on the API and the wire protocol.
//!
//! This crate provides `libp2p_core::InboundUpgrade` and `libp2p_core::OutboundUpgrade`
//! 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`.
//!
//! # Usage
//!
//! Example:
//!
//! ```
//! use libp2p_core::{identity, Transport, upgrade};
//! use libp2p_tcp::TcpConfig;
//! use libp2p_noise::{Keypair, X25519Spec, NoiseConfig};
//!
//! # fn main() {
//! let id_keys = identity::Keypair::generate_ed25519();
//! 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(...);
//! # }
//! ```
//!
//! [noise]: http://noiseprotocol.org/
mod error;
mod io;
mod protocol;
pub use error::NoiseError;
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, IX, IK, XX};
pub use protocol::{x25519::X25519, x25519_spec::X25519Spec};
use futures::prelude::*;
use libp2p_core::{identity, PeerId, UpgradeInfo, InboundUpgrade, OutboundUpgrade};
use std::pin::Pin;
use zeroize::Zeroize;
/// The protocol upgrade configuration.
#[derive(Clone)]
pub struct NoiseConfig<P, C: Zeroize, R = ()> {
dh_keys: AuthenticKeypair<C>,
params: ProtocolParams,
legacy: LegacyConfig,
remote: R,
_marker: std::marker::PhantomData<P>
}
impl<H, C: Zeroize, R> NoiseConfig<H, C, R> {
/// Turn the `NoiseConfig` into an authenticated upgrade for use
/// with a [`Network`](libp2p_core::Network).
pub fn into_authenticated(self) -> NoiseAuthenticated<H, C, R> {
NoiseAuthenticated { config: self }
}
/// Sets the legacy configuration options to use, if any.
pub fn set_legacy_config(&mut self, cfg: LegacyConfig) -> &mut Self {
self.legacy = cfg;
self
}
}
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
}
}
}
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
}
}
}
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
}
}
}
impl<C> NoiseConfig<IK, C, (PublicKey<C>, identity::PublicKey)>
where
C: Protocol<C> + Zeroize
{
/// 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
}
}
}
// Handshake pattern IX /////////////////////////////////////////////////////
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 + Send + 'static,
{
type Output = (RemoteIdentity<C>, NoiseOutput<T>);
type Error = NoiseError;
type Future = Handshake<T, C>;
fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
let session = self.params.into_builder()
.local_private_key(self.dh_keys.secret().as_ref())
.build_responder()
.map_err(NoiseError::from);
handshake::rt1_responder(socket, session,
self.dh_keys.into_identity(),
IdentityExchange::Mutual,
self.legacy)
}
}
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 + Send + 'static,
{
type Output = (RemoteIdentity<C>, NoiseOutput<T>);
type Error = NoiseError;
type Future = Handshake<T, C>;
fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
let session = self.params.into_builder()
.local_private_key(self.dh_keys.secret().as_ref())
.build_initiator()
.map_err(NoiseError::from);
handshake::rt1_initiator(socket, session,
self.dh_keys.into_identity(),
IdentityExchange::Mutual,
self.legacy)
}
}
// Handshake pattern XX /////////////////////////////////////////////////////
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 + Send + 'static,
{
type Output = (RemoteIdentity<C>, NoiseOutput<T>);
type Error = NoiseError;
type Future = Handshake<T, C>;
fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
let session = self.params.into_builder()
.local_private_key(self.dh_keys.secret().as_ref())
.build_responder()
.map_err(NoiseError::from);
handshake::rt15_responder(socket, session,
self.dh_keys.into_identity(),
IdentityExchange::Mutual,
self.legacy)
}
}
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 + Send + 'static,
{
type Output = (RemoteIdentity<C>, NoiseOutput<T>);
type Error = NoiseError;
type Future = Handshake<T, C>;
fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
let session = self.params.into_builder()
.local_private_key(self.dh_keys.secret().as_ref())
.build_initiator()
.map_err(NoiseError::from);
handshake::rt15_initiator(socket, session,
self.dh_keys.into_identity(),
IdentityExchange::Mutual,
self.legacy)
}
}
// Handshake pattern IK /////////////////////////////////////////////////////
impl<T, C, R> InboundUpgrade<T> for NoiseConfig<IK, C, R>
where
NoiseConfig<IK, C, R>: UpgradeInfo,
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
C: Protocol<C> + AsRef<[u8]> + Zeroize + Send + 'static,
{
type Output = (RemoteIdentity<C>, NoiseOutput<T>);
type Error = NoiseError;
type Future = Handshake<T, C>;
fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
let session = self.params.into_builder()
.local_private_key(self.dh_keys.secret().as_ref())
.build_responder()
.map_err(NoiseError::from);
handshake::rt1_responder(socket, session,
self.dh_keys.into_identity(),
IdentityExchange::Receive,
self.legacy)
}
}
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 + Send + 'static,
{
type Output = (RemoteIdentity<C>, NoiseOutput<T>);
type Error = NoiseError;
type Future = Handshake<T, C>;
fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
let session = self.params.into_builder()
.local_private_key(self.dh_keys.secret().as_ref())
.remote_public_key(self.remote.0.as_ref())
.build_initiator()
.map_err(NoiseError::from);
handshake::rt1_initiator(socket, session,
self.dh_keys.into_identity(),
IdentityExchange::Send { remote: self.remote.1 },
self.legacy)
}
}
// 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 [`Network`](libp2p_core::Network).
#[derive(Clone)]
pub struct NoiseAuthenticated<P, C: Zeroize, R> {
config: NoiseConfig<P, C, R>
}
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>, NoiseOutput<T>),
Error = NoiseError
> + '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, NoiseOutput<T>);
type Error = NoiseError;
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.into_peer_id(), io)),
_ => future::err(NoiseError::AuthenticationFailed)
}))
}
}
impl<T, P, C, R> OutboundUpgrade<T> for NoiseAuthenticated<P, C, R>
where
NoiseConfig<P, C, R>: UpgradeInfo + OutboundUpgrade<T,
Output = (RemoteIdentity<C>, NoiseOutput<T>),
Error = NoiseError
> + '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, NoiseOutput<T>);
type Error = NoiseError;
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.into_peer_id(), io)),
_ => future::err(NoiseError::AuthenticationFailed)
}))
}
}
/// Legacy configuration options.
#[derive(Clone)]
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,
}
impl Default for LegacyConfig {
fn default() -> Self {
Self {
send_legacy_handshake: false,
recv_legacy_handshake: false,
}
}
}

View File

@ -1,277 +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.
//! Components of a Noise protocol.
pub mod x25519;
pub mod x25519_spec;
use crate::NoiseError;
use libp2p_core::identity;
use rand::SeedableRng;
use zeroize::Zeroize;
/// The parameters of a Noise protocol, consisting of a choice
/// for a handshake pattern as well as DH, cipher and hash functions.
#[derive(Clone)]
pub struct ProtocolParams(snow::params::NoiseParams);
impl ProtocolParams {
/// Turn the protocol parameters into a session builder.
pub(crate) fn into_builder(self) -> snow::Builder<'static> {
snow::Builder::with_resolver(self.0, Box::new(Resolver))
}
}
/// 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.
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>, NoiseError>;
/// 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.
#[allow(deprecated)]
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>, NoiseError>
where
C: AsRef<[u8]>
{
Ok(id_keys.sign(dh_pk.as_ref())?)
}
}
/// DH keypair.
#[derive(Clone)]
pub struct Keypair<T: Zeroize> {
secret: SecretKey<T>,
public: PublicKey<T>,
}
/// A DH keypair that is authentic w.r.t. a [`identity::PublicKey`].
#[derive(Clone)]
pub struct AuthenticKeypair<T: Zeroize> {
keypair: Keypair<T>,
identity: KeypairIdentity
}
impl<T: Zeroize> AuthenticKeypair<T> {
/// Extract the public [`KeypairIdentity`] from this `AuthenticKeypair`,
/// dropping the DH `Keypair`.
pub fn into_identity(self) -> KeypairIdentity {
self.identity
}
}
impl<T: Zeroize> std::ops::Deref for AuthenticKeypair<T> {
type Target = Keypair<T>;
fn deref(&self) -> &Self::Target {
&self.keypair
}
}
/// The associated public identity of a DH keypair.
#[derive(Clone)]
pub struct KeypairIdentity {
/// The public identity key.
pub public: identity::PublicKey,
/// The signature over the public DH key.
pub signature: Option<Vec<u8>>
}
impl<T: Zeroize> Keypair<T> {
/// The public key of the DH keypair.
pub fn public(&self) -> &PublicKey<T> {
&self.public
}
/// The secret key of the DH keypair.
pub fn secret(&self) -> &SecretKey<T> {
&self.secret
}
/// 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.
pub fn into_authentic(self, id_keys: &identity::Keypair) -> Result<AuthenticKeypair<T>, NoiseError>
where
T: AsRef<[u8]>,
T: Protocol<T>
{
let sig = T::sign(id_keys, &self.public)?;
let identity = KeypairIdentity {
public: id_keys.public(),
signature: Some(sig)
};
Ok(AuthenticKeypair { keypair: self, identity })
}
}
/// DH secret key.
#[derive(Clone)]
pub struct SecretKey<T: Zeroize>(T);
impl<T: Zeroize> Drop for SecretKey<T> {
fn drop(&mut self) {
self.0.zeroize()
}
}
impl<T: AsRef<[u8]> + Zeroize> AsRef<[u8]> for SecretKey<T> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
/// DH public key.
#[derive(Clone)]
pub struct PublicKey<T>(T);
impl<T: AsRef<[u8]>> PartialEq for PublicKey<T> {
fn eq(&self, other: &PublicKey<T>) -> bool {
self.as_ref() == other.as_ref()
}
}
impl<T: AsRef<[u8]>> Eq for PublicKey<T> {}
impl<T: AsRef<[u8]>> AsRef<[u8]> for PublicKey<T> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
/// Custom `snow::CryptoResolver` which delegates to either the
/// `RingResolver` on native or the `DefaultResolver` on wasm
/// for hash functions and symmetric ciphers, while using x25519-dalek
/// for Curve25519 DH.
struct Resolver;
impl snow::resolvers::CryptoResolver for Resolver {
fn resolve_rng(&self) -> Option<Box<dyn snow::types::Random>> {
Some(Box::new(Rng(rand::rngs::StdRng::from_entropy())))
}
fn resolve_dh(&self, choice: &snow::params::DHChoice) -> Option<Box<dyn snow::types::Dh>> {
if let snow::params::DHChoice::Curve25519 = choice {
Some(Box::new(Keypair::<x25519::X25519>::default()))
} else {
None
}
}
fn resolve_hash(&self, choice: &snow::params::HashChoice) -> Option<Box<dyn snow::types::Hash>> {
#[cfg(target_arch = "wasm32")]
{
snow::resolvers::DefaultResolver.resolve_hash(choice)
}
#[cfg(not(target_arch = "wasm32"))]
{
snow::resolvers::RingResolver.resolve_hash(choice)
}
}
fn resolve_cipher(&self, choice: &snow::params::CipherChoice) -> Option<Box<dyn snow::types::Cipher>> {
#[cfg(target_arch = "wasm32")]
{
snow::resolvers::DefaultResolver.resolve_cipher(choice)
}
#[cfg(not(target_arch = "wasm32"))]
{
snow::resolvers::RingResolver.resolve_cipher(choice)
}
}
}
/// Wrapper around a CSPRNG to implement `snow::Random` trait for.
struct Rng(rand::rngs::StdRng);
impl rand::RngCore for Rng {
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.0.try_fill_bytes(dest)
}
}
impl rand::CryptoRng for Rng {}
impl snow::types::Random for Rng {}

View File

@ -1,343 +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::{NoiseConfig, NoiseError, Protocol, ProtocolParams};
use curve25519_dalek::edwards::CompressedEdwardsY;
use lazy_static::lazy_static;
use libp2p_core::UpgradeInfo;
use libp2p_core::{identity, identity::ed25519};
use rand::Rng;
use sha2::{Sha512, Digest};
use x25519_dalek::{X25519_BASEPOINT_BYTES, x25519};
use zeroize::Zeroize;
use super::*;
lazy_static! {
static ref PARAMS_IK: ProtocolParams = "Noise_IK_25519_ChaChaPoly_SHA256"
.parse()
.map(ProtocolParams)
.expect("Invalid protocol name");
static ref PARAMS_IX: ProtocolParams = "Noise_IX_25519_ChaChaPoly_SHA256"
.parse()
.map(ProtocolParams)
.expect("Invalid protocol name");
static ref PARAMS_XX: ProtocolParams = "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 [u8];
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once(b"/noise/ix/25519/chachapoly/sha256/0.1.0")
}
}
impl UpgradeInfo for NoiseConfig<XX, X25519> {
type Info = &'static [u8];
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once(b"/noise/xx/25519/chachapoly/sha256/0.1.0")
}
}
impl<R> UpgradeInfo for NoiseConfig<IK, X25519, R> {
type Info = &'static [u8];
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once(b"/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>, NoiseError> {
if bytes.len() != 32 {
return Err(NoiseError::InvalidKey)
}
let mut pk = [0u8; 32];
pk.copy_from_slice(bytes);
Ok(PublicKey(X25519(pk)))
}
fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey<X25519>) -> bool {
if let identity::PublicKey::Ed25519(ref p) = id_pk {
PublicKey::from_ed25519(p).as_ref() == dh_pk.as_ref()
} else {
false
}
}
}
impl Keypair<X25519> {
/// 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(X25519([0u8; 32])),
public: PublicKey(X25519([0u8; 32]))
}
}
/// 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)
pub fn from_identity(id_keys: &identity::Keypair) -> Option<AuthenticKeypair<X25519>> {
match id_keys {
identity::Keypair::Ed25519(p) => {
let kp = Keypair::from(SecretKey::from_ed25519(&p.secret()));
let id = KeypairIdentity {
public: id_keys.public(),
signature: None
};
Some(AuthenticKeypair {
keypair: kp,
identity: id
})
}
_ => None
}
}
}
/// 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.as_ref()[..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<(), ()> {
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 libp2p_core::identity::ed25519;
use quickcheck::*;
use sodiumoxide::crypto::sign;
use std::os::raw::c_int;
use super::*;
use x25519_dalek::StaticSecret;
// ed25519 to x25519 keypair conversion must yield the same results as
// obtained through libsodium.
#[test]
fn prop_ed25519_to_x25519_matches_libsodium() {
fn prop() -> bool {
let ed25519 = ed25519::Keypair::generate();
let x25519 = Keypair::from(SecretKey::from_ed25519(&ed25519.secret()));
let sodium_sec = ed25519_sk_to_curve25519(&sign::SecretKey(ed25519.encode()));
let sodium_pub = ed25519_pk_to_curve25519(&sign::PublicKey(ed25519.public().encode().clone()));
let our_pub = x25519.public.0;
// libsodium does the [clamping] of the scalar upon key construction,
// just like x25519-dalek, but this module uses the raw byte-oriented x25519
// function from x25519-dalek, as defined in RFC7748, so "our" secret scalar
// must be clamped before comparing it to the one computed by libsodium.
// That happens in `StaticSecret::from`.
//
// [clamping]: http://www.lix.polytechnique.fr/~smith/ECC/#scalar-clamping
let our_sec = StaticSecret::from((x25519.secret.0).0).to_bytes();
sodium_sec.as_ref() == Some(&our_sec) &&
sodium_pub.as_ref() == Some(&our_pub.0)
}
quickcheck(prop as fn() -> _);
}
// 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() -> _);
}
// Bindings to libsodium's ed25519 to curve25519 key conversions, to check that
// they agree with the conversions performed in this module.
extern "C" {
pub fn crypto_sign_ed25519_pk_to_curve25519(c: *mut u8, e: *const u8) -> c_int;
pub fn crypto_sign_ed25519_sk_to_curve25519(c: *mut u8, e: *const u8) -> c_int;
}
pub fn ed25519_pk_to_curve25519(k: &sign::PublicKey) -> Option<[u8; 32]> {
let mut out = [0u8; 32];
unsafe {
if crypto_sign_ed25519_pk_to_curve25519(out.as_mut_ptr(), (&k.0).as_ptr()) == 0 {
Some(out)
} else {
None
}
}
}
pub fn ed25519_sk_to_curve25519(k: &sign::SecretKey) -> Option<[u8; 32]> {
let mut out = [0u8; 32];
unsafe {
if crypto_sign_ed25519_sk_to_curve25519(out.as_mut_ptr(), (&k.0).as_ptr()) == 0 {
Some(out)
} else {
None
}
}
}
}

View File

@ -1,170 +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::{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")
}
}
/// **Note**: This is not currentlyy a standardised upgrade.
impl UpgradeInfo for NoiseConfig<IX, X25519Spec> {
type Info = &'static [u8];
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once(b"/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 [u8];
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once(b"/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::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

@ -1,272 +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.
use async_io::Async;
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, X25519Spec, NoiseConfig, RemoteIdentity, NoiseError, NoiseOutput};
use libp2p_tcp::TcpConfig;
use log::info;
use quickcheck::QuickCheck;
use std::{convert::TryInto, io, net::TcpStream};
#[allow(dead_code)]
fn core_upgrade_compat() {
// Tests API compaibility with the libp2p-core upgrade API,
// i.e. if it compiles, the "test" is considered a success.
let id_keys = identity::Keypair::generate_ed25519();
let dh_keys = Keypair::<X25519>::new().into_authentic(&id_keys).unwrap();
let noise = NoiseConfig::xx(dh_keys).into_authenticated();
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();
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::<X25519>::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::<X25519>::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 ix() {
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::<X25519>::new().into_authentic(&server_id).unwrap();
let server_transport = TcpConfig::new()
.and_then(move |output, endpoint| {
upgrade::apply(output, NoiseConfig::ix(server_dh), endpoint, upgrade::Version::V1)
})
.and_then(move |out, _| expect_identity(out, &client_id_public));
let client_dh = Keypair::<X25519>::new().into_authentic(&client_id).unwrap();
let client_transport = TcpConfig::new()
.and_then(move |output, endpoint| {
upgrade::apply(output, NoiseConfig::ix(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 ik_xx() {
let _ = env_logger::try_init();
fn prop(mut messages: Vec<Message>) -> bool {
messages.truncate(5);
let server_id = identity::Keypair::generate_ed25519();
let server_id_public = server_id.public();
let client_id = identity::Keypair::generate_ed25519();
let client_id_public = client_id.public();
let server_dh = Keypair::<X25519>::new().into_authentic(&server_id).unwrap();
let server_dh_public = server_dh.public().clone();
let server_transport = TcpConfig::new()
.and_then(move |output, endpoint| {
if endpoint.is_listener() {
Either::Left(apply_inbound(output, NoiseConfig::ik_listener(server_dh)))
} else {
Either::Right(apply_outbound(output, NoiseConfig::xx(server_dh),
upgrade::Version::V1))
}
})
.and_then(move |out, _| expect_identity(out, &client_id_public));
let client_dh = Keypair::<X25519>::new().into_authentic(&client_id).unwrap();
let server_id_public2 = server_id_public.clone();
let client_transport = TcpConfig::new()
.and_then(move |output, endpoint| {
if endpoint.is_dialer() {
Either::Left(apply_outbound(output,
NoiseConfig::ik_dialer(client_dh, server_id_public, server_dh_public),
upgrade::Version::V1))
} else {
Either::Right(apply_inbound(output, NoiseConfig::xx(client_dh)))
}
})
.and_then(move |out, _| expect_identity(out, &server_id_public2));
run(server_transport, client_transport, messages);
true
}
QuickCheck::new().max_tests(30).quickcheck(prop as fn(Vec<Message>) -> bool)
}
type Output<C> = (RemoteIdentity<C>, NoiseOutput<Negotiated<Async<TcpStream>>>);
fn run<T, U, I, C>(server_transport: T, client_transport: U, messages: I)
where
T: Transport<Output = Output<C>>,
T::Dial: Send + 'static,
T::Listener: Send + Unpin + 'static,
T::ListenerUpgrade: Send + 'static,
U: Transport<Output = Output<C>>,
U::Dial: Send + 'static,
U::Listener: Send + 'static,
U::ListenerUpgrade: Send + 'static,
I: IntoIterator<Item = Message> + Clone
{
futures::executor::block_on(async {
let mut server: T::Listener = server_transport
.listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap())
.unwrap();
let server_address = server.try_next()
.await
.expect("some event")
.expect("no error")
.into_new_address()
.expect("listen address");
let outbound_msgs = messages.clone();
let client_fut = async {
let mut client_session = client_transport.dial(server_address.clone())
.unwrap()
.await
.map(|(_, session)| session)
.expect("no error");
for m in outbound_msgs {
let n = (m.0.len() as u64).to_be_bytes();
client_session.write_all(&n[..]).await.expect("len written");
client_session.write_all(&m.0).await.expect("no error")
}
client_session.flush().await.expect("no error");
};
let server_fut = async {
let mut server_session = server.try_next()
.await
.expect("some event")
.map(ListenerEvent::into_upgrade)
.expect("no error")
.map(|client| client.0)
.expect("listener upgrade")
.await
.map(|(_, session)| session)
.expect("no error");
for m in messages {
let len = {
let mut n = [0; 8];
match server_session.read_exact(&mut n).await {
Ok(()) => u64::from_be_bytes(n),
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => 0,
Err(e) => panic!("error reading len: {}", e)
}
};
info!("server: reading message ({} bytes)", len);
let mut server_buffer = vec![0; len.try_into().unwrap()];
server_session.read_exact(&mut server_buffer).await.expect("no error");
assert_eq!(server_buffer, m.0)
}
};
futures::future::join(server_fut, client_fut).await;
})
}
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),
_ => panic!("Unexpected remote identity")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Message(Vec<u8>);
impl quickcheck::Arbitrary for Message {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
let s = 1 + g.next_u32() % (128 * 1024);
let mut v = vec![0; s.try_into().unwrap()];
g.fill_bytes(&mut v);
Message(v)
}
}

View File

@ -21,7 +21,7 @@ wasm-timer = "0.2"
[dev-dependencies]
async-std = "1.6.2"
libp2p-tcp = { path = "../../transports/tcp" }
libp2p-noise = { path = "../../protocols/noise" }
libp2p-noise = { path = "../../transports/noise" }
libp2p-yamux = { path = "../../muxers/yamux" }
libp2p-mplex = { path = "../../muxers/mplex" }
quickcheck = "0.9.0"

View File

@ -1,48 +0,0 @@
# 0.27.0 [2021-01-12]
- Update dependencies.
# 0.26.0 [2020-12-17]
- Update `libp2p-core`.
# 0.25.0 [2020-11-25]
- Update `libp2p-core`.
# 0.24.1 [2020-11-11]
- Ensure that no follow-up protocol data is dropped at the end of the
plaintext protocol handshake.
[PR 1831](https://github.com/libp2p/rust-libp2p/pull/1831).
# 0.24.0 [2020-11-09]
- Update dependencies.
# 0.23.0 [2020-10-16]
- Improve error logging
[PR 1759](https://github.com/libp2p/rust-libp2p/pull/1759).
- Update dependencies.
- Only prefix handshake messages with the message length in bytes as an unsigned
varint. Return a plain socket once handshaking succeeded. See [issue
1760](https://github.com/libp2p/rust-libp2p/issues/1760) for details.
# 0.22.0 [2020-09-09]
- Bump `libp2p-core` dependency.
# 0.21.0 [2020-08-18]
- Bump `libp2p-core` dependency.
# 0.20.0 [2020-07-01]
- Updated dependencies.
# 0.19.1 [2020-06-22]
- Updated dependencies.

View File

@ -1,29 +0,0 @@
[package]
name = "libp2p-plaintext"
edition = "2018"
description = "Plaintext encryption dummy protocol for libp2p"
version = "0.27.0"
authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p"
keywords = ["peer-to-peer", "libp2p", "networking"]
categories = ["network-programming", "asynchronous"]
[dependencies]
bytes = "1"
futures = "0.3.1"
asynchronous-codec = "0.5.0"
libp2p-core = { version = "0.27.0", path = "../../core" }
log = "0.4.8"
prost = "0.7"
unsigned-varint = { version = "0.6", features = ["asynchronous_codec"] }
void = "1.0.2"
[dev-dependencies]
env_logger = "0.8.1"
quickcheck = "0.9.0"
rand = "0.7"
[build-dependencies]
prost-build = "0.7"

View File

@ -1,24 +0,0 @@
// Copyright 2020 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.
fn main() {
prost_build::compile_protos(&["src/structs.proto"], &["src"]).unwrap();
}

View File

@ -1,74 +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.
use std::error;
use std::fmt;
use std::io::Error as IoError;
#[derive(Debug)]
pub enum PlainTextError {
/// I/O error.
IoError(IoError),
/// Failed to parse the handshake protobuf message.
InvalidPayload(Option<prost::DecodeError>),
/// The peer id of the exchange isn't consistent with the remote public key.
InvalidPeerId,
}
impl error::Error for PlainTextError {
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
PlainTextError::IoError(ref err) => Some(err),
PlainTextError::InvalidPayload(Some(ref err)) => Some(err),
_ => None,
}
}
}
impl fmt::Display for PlainTextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
PlainTextError::IoError(e) =>
write!(f, "I/O error: {}", e),
PlainTextError::InvalidPayload(protobuf_error) => {
match protobuf_error {
Some(e) => write!(f, "Protobuf error: {}", e),
None => f.write_str("Failed to parse one of the handshake protobuf messages")
}
},
PlainTextError::InvalidPeerId =>
f.write_str("The peer id of the exchange isn't consistent with the remote public key"),
}
}
}
impl From<IoError> for PlainTextError {
fn from(err: IoError) -> PlainTextError {
PlainTextError::IoError(err)
}
}
impl From<prost::DecodeError> for PlainTextError {
fn from(err: prost::DecodeError) -> PlainTextError {
PlainTextError::InvalidPayload(Some(err))
}
}

View File

@ -1,145 +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.
use crate::PlainText2Config;
use crate::error::PlainTextError;
use crate::structs_proto::Exchange;
use bytes::{Bytes, BytesMut};
use futures::prelude::*;
use asynchronous_codec::Framed;
use libp2p_core::{PublicKey, PeerId};
use log::{debug, trace};
use prost::Message;
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
use unsigned_varint::codec::UviBytes;
struct HandshakeContext<T> {
config: PlainText2Config,
state: T
}
// HandshakeContext<()> --with_local-> HandshakeContext<Local>
struct Local {
// Our local exchange's raw bytes:
exchange_bytes: Vec<u8>,
}
// HandshakeContext<Local> --with_remote-> HandshakeContext<Remote>
pub struct Remote {
// The remote's peer ID:
pub peer_id: PeerId,
// The remote's public key:
pub public_key: PublicKey,
}
impl HandshakeContext<Local> {
fn new(config: PlainText2Config) -> Result<Self, PlainTextError> {
let exchange = Exchange {
id: Some(config.local_public_key.clone().into_peer_id().to_bytes()),
pubkey: Some(config.local_public_key.clone().into_protobuf_encoding())
};
let mut buf = Vec::with_capacity(exchange.encoded_len());
exchange.encode(&mut buf).expect("Vec<u8> provides capacity as needed");
Ok(Self {
config,
state: Local {
exchange_bytes: buf
}
})
}
fn with_remote(self, exchange_bytes: BytesMut)
-> Result<HandshakeContext<Remote>, PlainTextError>
{
let prop = match Exchange::decode(exchange_bytes) {
Ok(prop) => prop,
Err(e) => {
debug!("failed to parse remote's exchange protobuf message");
return Err(PlainTextError::InvalidPayload(Some(e)));
},
};
let pb_pubkey = prop.pubkey.unwrap_or_default();
let public_key = match PublicKey::from_protobuf_encoding(pb_pubkey.as_slice()) {
Ok(p) => p,
Err(_) => {
debug!("failed to parse remote's exchange's pubkey protobuf");
return Err(PlainTextError::InvalidPayload(None));
},
};
let peer_id = match PeerId::from_bytes(&prop.id.unwrap_or_default()) {
Ok(p) => p,
Err(_) => {
debug!("failed to parse remote's exchange's id protobuf");
return Err(PlainTextError::InvalidPayload(None));
},
};
// Check the validity of the remote's `Exchange`.
if peer_id != public_key.clone().into_peer_id() {
debug!("the remote's `PeerId` isn't consistent with the remote's public key");
return Err(PlainTextError::InvalidPeerId)
}
Ok(HandshakeContext {
config: self.config,
state: Remote {
peer_id,
public_key,
}
})
}
}
pub async fn handshake<S>(socket: S, config: PlainText2Config)
-> Result<(S, Remote, Bytes), PlainTextError>
where
S: AsyncRead + AsyncWrite + Send + Unpin,
{
// The handshake messages all start with a variable-length integer indicating the size.
let mut framed_socket = Framed::new(socket, UviBytes::default());
trace!("starting handshake");
let context = HandshakeContext::new(config)?;
trace!("sending exchange to remote");
framed_socket.send(BytesMut::from(&context.state.exchange_bytes[..])).await?;
trace!("receiving the remote's exchange");
let context = match framed_socket.next().await {
Some(p) => context.with_remote(p?)?,
None => {
debug!("unexpected eof while waiting for remote's exchange");
let err = IoError::new(IoErrorKind::BrokenPipe, "unexpected eof");
return Err(err.into());
}
};
// The `Framed` wrapper may have buffered additional data that
// was already received but is no longer part of the plaintext
// handshake. We need to capture that data before dropping
// the `Framed` wrapper via `Framed::into_inner()`.
let read_buffer = framed_socket.read_buffer().clone().freeze();
trace!("received exchange from remote; pubkey = {:?}", context.state.public_key);
Ok((framed_socket.into_inner(), context.state, read_buffer))
}

View File

@ -1,213 +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.
use crate::error::PlainTextError;
use bytes::Bytes;
use futures::future::{self, Ready};
use futures::prelude::*;
use futures::future::BoxFuture;
use libp2p_core::{
identity,
InboundUpgrade,
OutboundUpgrade,
UpgradeInfo,
PeerId,
PublicKey,
};
use log::debug;
use std::{io, iter, pin::Pin, task::{Context, Poll}};
use void::Void;
mod error;
mod handshake;
mod structs_proto {
include!(concat!(env!("OUT_DIR"), "/structs.rs"));
}
/// `PlainText1Config` is an insecure connection handshake for testing purposes only.
///
/// > **Note**: Given that `PlainText1Config` has no notion of exchanging peer identity information it is not compatible
/// > with the `libp2p_core::transport::upgrade::Builder` pattern. See
/// > [`PlainText2Config`](struct.PlainText2Config.html) if compatibility is needed. Even though not compatible with the
/// > Builder pattern one can still do an upgrade *manually*:
///
/// ```
/// # use libp2p_core::transport::{ Transport, memory::MemoryTransport };
/// # use libp2p_plaintext::PlainText1Config;
/// #
/// MemoryTransport::default()
/// .and_then(move |io, endpoint| {
/// libp2p_core::upgrade::apply(
/// io,
/// PlainText1Config{},
/// endpoint,
/// libp2p_core::transport::upgrade::Version::V1,
/// )
/// })
/// .map(|plaintext, _endpoint| {
/// unimplemented!();
/// // let peer_id = somehow_derive_peer_id();
/// // return (peer_id, plaintext);
/// });
/// ```
#[derive(Debug, Copy, Clone)]
pub struct PlainText1Config;
impl UpgradeInfo for PlainText1Config {
type Info = &'static [u8];
type InfoIter = iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
iter::once(b"/plaintext/1.0.0")
}
}
impl<C> InboundUpgrade<C> for PlainText1Config {
type Output = C;
type Error = Void;
type Future = Ready<Result<C, Self::Error>>;
fn upgrade_inbound(self, i: C, _: Self::Info) -> Self::Future {
future::ready(Ok(i))
}
}
impl<C> OutboundUpgrade<C> for PlainText1Config {
type Output = C;
type Error = Void;
type Future = Ready<Result<C, Self::Error>>;
fn upgrade_outbound(self, i: C, _: Self::Info) -> Self::Future {
future::ready(Ok(i))
}
}
/// `PlainText2Config` is an insecure connection handshake for testing purposes only, implementing
/// the libp2p plaintext connection handshake specification.
#[derive(Clone)]
pub struct PlainText2Config {
pub local_public_key: identity::PublicKey,
}
impl UpgradeInfo for PlainText2Config {
type Info = &'static [u8];
type InfoIter = iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
iter::once(b"/plaintext/2.0.0")
}
}
impl<C> InboundUpgrade<C> for PlainText2Config
where
C: AsyncRead + AsyncWrite + Send + Unpin + 'static
{
type Output = (PeerId, PlainTextOutput<C>);
type Error = PlainTextError;
type Future = BoxFuture<'static, Result<Self::Output, Self::Error>>;
fn upgrade_inbound(self, socket: C, _: Self::Info) -> Self::Future {
Box::pin(self.handshake(socket))
}
}
impl<C> OutboundUpgrade<C> for PlainText2Config
where
C: AsyncRead + AsyncWrite + Send + Unpin + 'static
{
type Output = (PeerId, PlainTextOutput<C>);
type Error = PlainTextError;
type Future = BoxFuture<'static, Result<Self::Output, Self::Error>>;
fn upgrade_outbound(self, socket: C, _: Self::Info) -> Self::Future {
Box::pin(self.handshake(socket))
}
}
impl PlainText2Config {
async fn handshake<T>(self, socket: T) -> Result<(PeerId, PlainTextOutput<T>), PlainTextError>
where
T: AsyncRead + AsyncWrite + Send + Unpin + 'static
{
debug!("Starting plaintext handshake.");
let (socket, remote, read_buffer) = handshake::handshake(socket, self).await?;
debug!("Finished plaintext handshake.");
Ok((
remote.peer_id,
PlainTextOutput {
socket,
remote_key: remote.public_key,
read_buffer,
}
))
}
}
/// Output of the plaintext protocol.
pub struct PlainTextOutput<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
/// The plaintext stream.
pub socket: S,
/// The public key of the remote.
pub remote_key: PublicKey,
/// Remaining bytes that have been already buffered
/// during the handshake but are not part of the
/// handshake. These must be consumed first by `poll_read`.
read_buffer: Bytes,
}
impl<S: AsyncRead + AsyncWrite + Unpin> AsyncRead for PlainTextOutput<S> {
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8])
-> Poll<Result<usize, io::Error>>
{
if !self.read_buffer.is_empty() {
let n = std::cmp::min(buf.len(), self.read_buffer.len());
let b = self.read_buffer.split_to(n);
buf[..n].copy_from_slice(&b[..]);
return Poll::Ready(Ok(n))
}
AsyncRead::poll_read(Pin::new(&mut self.socket), cx, buf)
}
}
impl<S: AsyncRead + AsyncWrite + Unpin> AsyncWrite for PlainTextOutput<S> {
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8])
-> Poll<Result<usize, io::Error>>
{
AsyncWrite::poll_write(Pin::new(&mut self.socket), cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<Result<(), io::Error>>
{
AsyncWrite::poll_flush(Pin::new(&mut self.socket), cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<Result<(), io::Error>>
{
AsyncWrite::poll_close(Pin::new(&mut self.socket), cx)
}
}

View File

@ -1,8 +0,0 @@
syntax = "proto2";
package structs;
message Exchange {
optional bytes id = 1;
optional bytes pubkey = 2;
}

View File

@ -1,121 +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.
use futures::io::{AsyncWriteExt, AsyncReadExt};
use futures::stream::TryStreamExt;
use libp2p_core::{
identity,
multiaddr::Multiaddr,
transport::{Transport, ListenerEvent},
upgrade,
};
use libp2p_plaintext::PlainText2Config;
use log::debug;
use quickcheck::QuickCheck;
#[test]
fn variable_msg_length() {
let _ = env_logger::try_init();
fn prop(msg: Vec<u8>) {
let mut msg_to_send = msg.clone();
let msg_to_receive = msg;
let server_id = identity::Keypair::generate_ed25519();
let server_id_public = server_id.public();
let client_id = identity::Keypair::generate_ed25519();
let client_id_public = client_id.public();
futures::executor::block_on(async {
let server_transport = libp2p_core::transport::MemoryTransport{}.and_then(
move |output, endpoint| {
upgrade::apply(
output,
PlainText2Config{local_public_key: server_id_public},
endpoint,
libp2p_core::upgrade::Version::V1,
)
}
);
let client_transport = libp2p_core::transport::MemoryTransport{}.and_then(
move |output, endpoint| {
upgrade::apply(
output,
PlainText2Config{local_public_key: client_id_public},
endpoint,
libp2p_core::upgrade::Version::V1,
)
}
);
let server_address: Multiaddr = format!(
"/memory/{}",
std::cmp::Ord::max(1, rand::random::<u64>())
).parse().unwrap();
let mut server = server_transport.listen_on(server_address.clone()).unwrap();
// Ignore server listen address event.
let _ = server.try_next()
.await
.expect("some event")
.expect("no error")
.into_new_address()
.expect("listen address");
let client_fut = async {
debug!("dialing {:?}", server_address);
let (received_server_id, mut client_channel) = client_transport.dial(server_address).unwrap().await.unwrap();
assert_eq!(received_server_id, server_id.public().into_peer_id());
debug!("Client: writing message.");
client_channel.write_all(&mut msg_to_send).await.expect("no error");
debug!("Client: flushing channel.");
client_channel.flush().await.expect("no error");
};
let server_fut = async {
let mut server_channel = server.try_next()
.await
.expect("some event")
.map(ListenerEvent::into_upgrade)
.expect("no error")
.map(|client| client.0)
.expect("listener upgrade xyz")
.await
.map(|(_, session)| session)
.expect("no error");
let mut server_buffer = vec![0; msg_to_receive.len()];
debug!("Server: reading message.");
server_channel.read_exact(&mut server_buffer).await.expect("reading client message");
assert_eq!(server_buffer, msg_to_receive);
};
futures::future::join(server_fut, client_fut).await;
})
}
QuickCheck::new().max_tests(30).quickcheck(prop as fn(Vec<u8>))
}

View File

@ -1,11 +0,0 @@
# 0.20.0 [2020-12-17]
- Update dependencies.
# 0.19.2 [2020-10-16]
- Update dependencies.
# 0.19.1 [2020-06-22]
- Updated dependencies.

View File

@ -1,21 +0,0 @@
[package]
name = "libp2p-pnet"
edition = "2018"
description = "Private swarm support for libp2p"
version = "0.20.0"
authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p"
keywords = ["peer-to-peer", "libp2p", "networking"]
categories = ["network-programming", "asynchronous"]
[dependencies]
futures = "0.3.1"
log = "0.4.8"
salsa20 = "0.7"
sha3 = "0.9"
rand = "0.7"
pin-project = "1.0.2"
[dev-dependencies]
quickcheck = "0.9.0"

View File

@ -1,153 +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.
use futures::{
io::{self, AsyncWrite},
ready,
task::{Context, Poll},
};
use log::trace;
use pin_project::pin_project;
use salsa20::{cipher::SyncStreamCipher, XSalsa20};
use std::{fmt, pin::Pin};
/// A writer that encrypts and forwards to an inner writer
#[pin_project]
pub struct CryptWriter<W> {
#[pin]
inner: W,
buf: Vec<u8>,
cipher: XSalsa20,
}
impl<W: AsyncWrite> CryptWriter<W> {
/// Creates a new `CryptWriter` with the specified buffer capacity.
pub fn with_capacity(capacity: usize, inner: W, cipher: XSalsa20) -> CryptWriter<W> {
CryptWriter {
inner,
buf: Vec::with_capacity(capacity),
cipher,
}
}
/// Gets a pinned mutable reference to the inner writer.
///
/// It is inadvisable to directly write to the inner writer.
pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut W> {
self.project().inner
}
}
/// Write the contents of a Vec<u8> into an AsyncWrite.
///
/// The handling 0 byte progress and the Interrupted error was taken from BufWriter in async_std.
///
/// If this fn returns Ready(Ok(())), the buffer has been completely flushed and is empty.
fn poll_flush_buf<W: AsyncWrite>(
inner: &mut Pin<&mut W>,
buf: &mut Vec<u8>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
let mut ret = Poll::Ready(Ok(()));
let mut written = 0;
let len = buf.len();
while written < len {
match inner.as_mut().poll_write(cx, &buf[written..]) {
Poll::Ready(Ok(n)) => {
if n > 0 {
// we made progress, so try again
written += n;
} else {
// we got Ok but got no progress whatsoever, so bail out so we don't spin writing 0 bytes.
ret = Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"Failed to write buffered data",
)));
break;
}
}
Poll::Ready(Err(e)) => {
// Interrupted is the only error that we consider to be recoverable by trying again
if e.kind() != io::ErrorKind::Interrupted {
// for any other error, don't try again
ret = Poll::Ready(Err(e));
break;
}
}
Poll::Pending => {
ret = Poll::Pending;
break;
}
}
}
if written > 0 {
buf.drain(..written);
}
if let Poll::Ready(Ok(())) = ret { debug_assert!(buf.is_empty()); }
ret
}
impl<W: AsyncWrite> AsyncWrite for CryptWriter<W> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let mut this = self.project();
// completely flush the buffer, returning pending if not possible
ready!(poll_flush_buf(&mut this.inner, this.buf, cx))?;
// if we get here, the buffer is empty
debug_assert!(this.buf.is_empty());
let res = Pin::new(&mut *this.buf).poll_write(cx, buf);
if let Poll::Ready(Ok(count)) = res {
this.cipher.apply_keystream(&mut this.buf[0..count]);
trace!("encrypted {} bytes", count);
} else {
debug_assert!(false);
};
// flush immediately afterwards, but if we get a pending we don't care
if let Poll::Ready(Err(e)) = poll_flush_buf(&mut this.inner, this.buf, cx) {
Poll::Ready(Err(e))
} else {
res
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let mut this = self.project();
ready!(poll_flush_buf(&mut this.inner, this.buf, cx))?;
this.inner.poll_flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let mut this = self.project();
ready!(poll_flush_buf(&mut this.inner, this.buf, cx))?;
this.inner.poll_close(cx)
}
}
impl<W: AsyncWrite + fmt::Debug> fmt::Debug for CryptWriter<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CryptWriter")
.field("writer", &self.inner)
.field("buf", &self.buf)
.finish()
}
}

View File

@ -1,369 +0,0 @@
// Copyright 2020 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.
//! The `pnet` protocol implements *Pre-shared Key Based Private Networks in libp2p*,
//! as specified in [the spec](https://github.com/libp2p/specs/blob/master/pnet/Private-Networks-PSK-V1.md)
//!
//! Libp2p nodes configured with a pre-shared key can only communicate with other nodes with
//! the same key.
mod crypt_writer;
use crypt_writer::CryptWriter;
use futures::prelude::*;
use log::trace;
use pin_project::pin_project;
use rand::RngCore;
use salsa20::{
cipher::{NewStreamCipher, SyncStreamCipher},
Salsa20, XSalsa20,
};
use sha3::{digest::ExtendableOutput, Shake128};
use std::{
error,
fmt::{self, Write},
io,
io::Error as IoError,
num::ParseIntError,
pin::Pin,
str::FromStr,
task::{Context, Poll},
};
const KEY_SIZE: usize = 32;
const NONCE_SIZE: usize = 24;
const WRITE_BUFFER_SIZE: usize = 1024;
const FINGERPRINT_SIZE: usize = 16;
/// A pre-shared key, consisting of 32 bytes of random data.
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct PreSharedKey([u8; KEY_SIZE]);
impl PreSharedKey {
/// Create a new pre shared key from raw bytes
pub fn new(data: [u8; KEY_SIZE]) -> Self {
Self(data)
}
/// Compute PreSharedKey fingerprint identical to the go-libp2p fingerprint.
/// The computation of the fingerprint is not specified in the spec.
///
/// This provides a way to check that private keys are properly configured
/// without dumping the key itself to the console.
pub fn fingerprint(&self) -> Fingerprint {
use std::io::{Read, Write};
let mut enc = [0u8; 64];
let nonce: [u8; 8] = *b"finprint";
let mut out = [0u8; 16];
let mut cipher = Salsa20::new(&self.0.into(), &nonce.into());
cipher.apply_keystream(&mut enc);
let mut hasher = Shake128::default();
hasher.write_all(&enc).expect("shake128 failed");
hasher.finalize_xof().read_exact(&mut out).expect("shake128 failed");
Fingerprint(out)
}
}
fn parse_hex_key(s: &str) -> Result<[u8; KEY_SIZE], KeyParseError> {
if s.len() == KEY_SIZE * 2 {
let mut r = [0u8; KEY_SIZE];
for i in 0..KEY_SIZE {
r[i] = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16)
.map_err(KeyParseError::InvalidKeyChar)?;
}
Ok(r)
} else {
Err(KeyParseError::InvalidKeyLength)
}
}
fn to_hex(bytes: &[u8]) -> String {
let mut hex = String::with_capacity(bytes.len() * 2);
for byte in bytes {
write!(hex, "{:02x}", byte).expect("Can't fail on writing to string");
}
hex
}
/// Parses a PreSharedKey from a key file
///
/// currently supports only base16 encoding.
impl FromStr for PreSharedKey {
type Err = KeyParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let [keytype, encoding, key] = *s.lines().take(3).collect::<Vec<_>>().as_slice() {
if keytype != "/key/swarm/psk/1.0.0/" {
return Err(KeyParseError::InvalidKeyType);
}
if encoding != "/base16/" {
return Err(KeyParseError::InvalidKeyEncoding);
}
parse_hex_key(key.trim_end()).map(PreSharedKey)
} else {
Err(KeyParseError::InvalidKeyFile)
}
}
}
impl fmt::Debug for PreSharedKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("PreSharedKey")
.field(&to_hex(&self.0))
.finish()
}
}
/// Dumps a PreSharedKey in key file format compatible with go-libp2p
impl fmt::Display for PreSharedKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "/key/swarm/psk/1.0.0/")?;
writeln!(f, "/base16/")?;
writeln!(f, "{}", to_hex(&self.0))
}
}
/// A PreSharedKey fingerprint computed from a PreSharedKey
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Fingerprint([u8; FINGERPRINT_SIZE]);
/// Dumps the fingerprint as hex
impl fmt::Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", to_hex(&self.0))
}
}
/// Error when parsing a PreSharedKey
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum KeyParseError {
/// file does not have the expected structure
InvalidKeyFile,
/// unsupported key type
InvalidKeyType,
/// unsupported key encoding. Currently only base16 is supported
InvalidKeyEncoding,
/// Key is of the wrong length
InvalidKeyLength,
/// key string contains a char that is not consistent with the specified encoding
InvalidKeyChar(ParseIntError),
}
impl fmt::Display for KeyParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl error::Error for KeyParseError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
KeyParseError::InvalidKeyChar(ref err) => Some(err),
_ => None,
}
}
}
/// Private network configuration
#[derive(Debug, Copy, Clone)]
pub struct PnetConfig {
/// the PreSharedKey to use for encryption
key: PreSharedKey,
}
impl PnetConfig {
pub fn new(key: PreSharedKey) -> Self {
Self { key }
}
/// upgrade a connection to use pre shared key encryption.
///
/// the upgrade works by both sides exchanging 24 byte nonces and then encrypting
/// subsequent traffic with XSalsa20
pub async fn handshake<TSocket>(
self,
mut socket: TSocket,
) -> Result<PnetOutput<TSocket>, PnetError>
where
TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static,
{
trace!("exchanging nonces");
let mut local_nonce = [0u8; NONCE_SIZE];
let mut remote_nonce = [0u8; NONCE_SIZE];
rand::thread_rng().fill_bytes(&mut local_nonce);
socket
.write_all(&local_nonce)
.await
.map_err(PnetError::HandshakeError)?;
socket
.read_exact(&mut remote_nonce)
.await
.map_err(PnetError::HandshakeError)?;
trace!("setting up ciphers");
let write_cipher = XSalsa20::new(&self.key.0.into(), &local_nonce.into());
let read_cipher = XSalsa20::new(&self.key.0.into(), &remote_nonce.into());
Ok(PnetOutput::new(socket, write_cipher, read_cipher))
}
}
/// The result of a handshake. This implements AsyncRead and AsyncWrite and can therefore
/// be used as base for additional upgrades.
#[pin_project]
pub struct PnetOutput<S> {
#[pin]
inner: CryptWriter<S>,
read_cipher: XSalsa20,
}
impl<S: AsyncRead + AsyncWrite> PnetOutput<S> {
fn new(inner: S, write_cipher: XSalsa20, read_cipher: XSalsa20) -> Self {
Self {
inner: CryptWriter::with_capacity(WRITE_BUFFER_SIZE, inner, write_cipher),
read_cipher,
}
}
}
impl<S: AsyncRead + AsyncWrite> AsyncRead for PnetOutput<S> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<Result<usize, io::Error>> {
let this = self.project();
let result = this.inner.get_pin_mut().poll_read(cx, buf);
if let Poll::Ready(Ok(size)) = &result {
trace!("read {} bytes", size);
this.read_cipher.apply_keystream(&mut buf[..*size]);
trace!("decrypted {} bytes", size);
}
result
}
}
impl<S: AsyncRead + AsyncWrite> AsyncWrite for PnetOutput<S> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>> {
self.project().inner.poll_write(cx, buf)
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
self.project().inner.poll_flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
self.project().inner.poll_close(cx)
}
}
/// Error when writing or reading private swarms
#[derive(Debug)]
pub enum PnetError {
/// Error during handshake.
HandshakeError(IoError),
/// I/O error.
IoError(IoError),
}
impl From<IoError> for PnetError {
#[inline]
fn from(err: IoError) -> PnetError {
PnetError::IoError(err)
}
}
impl error::Error for PnetError {
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
PnetError::HandshakeError(ref err) => Some(err),
PnetError::IoError(ref err) => Some(err),
}
}
}
impl fmt::Display for PnetError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
PnetError::HandshakeError(e) => write!(f, "Handshake error: {}", e),
PnetError::IoError(e) => write!(f, "I/O error: {}", e),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck::*;
impl Arbitrary for PreSharedKey {
fn arbitrary<G: Gen>(g: &mut G) -> PreSharedKey {
let mut key = [0; KEY_SIZE];
g.fill_bytes(&mut key);
PreSharedKey(key)
}
}
#[test]
fn psk_tostring_parse() {
fn prop(key: PreSharedKey) -> bool {
let text = key.to_string();
text.parse::<PreSharedKey>()
.map(|res| res == key)
.unwrap_or(false)
}
QuickCheck::new()
.tests(10)
.quickcheck(prop as fn(PreSharedKey) -> _);
}
#[test]
fn psk_parse_failure() {
use KeyParseError::*;
assert_eq!("".parse::<PreSharedKey>().unwrap_err(), InvalidKeyFile);
assert_eq!(
"a\nb\nc".parse::<PreSharedKey>().unwrap_err(),
InvalidKeyType
);
assert_eq!(
"/key/swarm/psk/1.0.0/\nx\ny"
.parse::<PreSharedKey>()
.unwrap_err(),
InvalidKeyEncoding
);
assert_eq!(
"/key/swarm/psk/1.0.0/\n/base16/\ny"
.parse::<PreSharedKey>()
.unwrap_err(),
InvalidKeyLength
);
}
#[test]
fn fingerprint() {
// checked against go-ipfs output
let key = "/key/swarm/psk/1.0.0/\n/base16/\n6189c5cf0b87fb800c1a9feeda73c6ab5e998db48fb9e6a978575c770ceef683".parse::<PreSharedKey>().unwrap();
let expected = "45fc986bbc9388a11d939df26f730f0c";
let actual = key.fingerprint().to_string();
assert_eq!(expected, actual);
}
}

View File

@ -25,7 +25,7 @@ wasm-timer = "0.2"
[dev-dependencies]
async-std = "1.6.2"
libp2p-noise = { path = "../noise" }
libp2p-noise = { path = "../../transports/noise" }
libp2p-tcp = { path = "../../transports/tcp" }
libp2p-yamux = { path = "../../muxers/yamux" }
rand = "0.7"