mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-12 09:31:20 +00:00
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:
@ -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.
|
@ -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"
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
|
@ -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]
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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.
|
@ -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"
|
@ -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();
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
@ -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;
|
||||
}
|
@ -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 Diffie–Hellman key agreement (currently only X25519).
|
||||
//!
|
||||
//! > **Note**: Only the `XX` handshake pattern is currently guaranteed to provide
|
||||
//! > interoperability with other libp2p implementations.
|
||||
//!
|
||||
//! All upgrades produce as output a pair, consisting of the remote's static public key
|
||||
//! and a `NoiseOutput` which represents the established cryptographic session with the
|
||||
//! remote, implementing `futures::io::AsyncRead` and `futures::io::AsyncWrite`.
|
||||
//!
|
||||
//! # 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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.
|
@ -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"
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package structs;
|
||||
|
||||
message Exchange {
|
||||
optional bytes id = 1;
|
||||
optional bytes pubkey = 2;
|
||||
}
|
@ -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>))
|
||||
}
|
@ -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.
|
@ -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"
|
@ -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()
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
Reference in New Issue
Block a user