secio: Add NULL cipher and allow more configuration. (#468)

* Introduce NULL cipher and allow more configuration.

* Back to using the hash-code for handshake.

Using `Endpoint` would be incompatible with the existing protocol.

* Add comments.
This commit is contained in:
Toralf Wittner
2018-09-12 09:10:05 +02:00
committed by GitHub
parent 5ecdb71c29
commit 6a5681aed7
12 changed files with 320 additions and 122 deletions

View File

@ -58,9 +58,9 @@ fn main() {
let secio = {
let private_key = include_bytes!("test-rsa-private-key.pk8");
let public_key = include_bytes!("test-rsa-public-key.der").to_vec();
libp2p::secio::SecioConfig {
key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(),
}
let keypair = libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap();
libp2p::secio::SecioConfig::new(keypair)
};
upgrade::or(

View File

@ -58,9 +58,8 @@ fn main() {
let secio = {
let private_key = include_bytes!("test-rsa-private-key.pk8");
let public_key = include_bytes!("test-rsa-public-key.der").to_vec();
libp2p::secio::SecioConfig {
key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(),
}
let keypair = libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap();
libp2p::secio::SecioConfig::new(keypair)
};
upgrade::or(

View File

@ -59,9 +59,9 @@ fn main() {
let secio = {
let private_key = include_bytes!("test-rsa-private-key.pk8");
let public_key = include_bytes!("test-rsa-public-key.der").to_vec();
libp2p::secio::SecioConfig {
key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(),
}
libp2p::secio::SecioConfig::new(
libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap()
)
};
upgrade::or(

View File

@ -71,9 +71,9 @@ fn main() {
let secio = {
let private_key = include_bytes!("test-rsa-private-key.pk8");
let public_key = include_bytes!("test-rsa-public-key.der").to_vec();
libp2p::secio::SecioConfig {
key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(),
}
libp2p::secio::SecioConfig::new(
libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap()
)
};
upgrade::or(

View File

@ -51,9 +51,9 @@ fn main() {
let secio = {
let private_key = include_bytes!("test-rsa-private-key.pk8");
let public_key = include_bytes!("test-rsa-public-key.der").to_vec();
libp2p::secio::SecioConfig {
key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(),
}
libp2p::secio::SecioConfig::new(
libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap()
)
};
upgrade::or(

View File

@ -23,76 +23,173 @@
//! One important part of the SECIO handshake is negotiating algorithms. This is what this module
//! helps you with.
macro_rules! supported_impl {
($mod_name:ident: $ty:ty, $($name:expr => $val:expr),*,) => (
pub mod $mod_name {
use std::cmp::Ordering;
#[allow(unused_imports)]
use stream_cipher::Cipher;
#[allow(unused_imports)]
use ring::{agreement, digest};
use error::SecioError;
use error::SecioError;
use ring::{agreement, digest};
use std::cmp::Ordering;
use stream_cipher::Cipher;
/// String to advertise to the remote.
pub const PROPOSITION_STRING: &'static str = concat_comma!($($name),*);
const ECDH_P256: &str = "P-256";
const ECDH_P384: &str = "P-384";
/// Choose which algorithm to use based on the remote's advertised list.
pub fn select_best(hashes_ordering: Ordering, input: &str) -> Result<$ty, SecioError> {
match hashes_ordering {
Ordering::Less | Ordering::Equal => {
for second_elem in input.split(',') {
$(
if $name == second_elem {
return Ok($val);
}
)+
}
},
Ordering::Greater => {
$(
for second_elem in input.split(',') {
if $name == second_elem {
return Ok($val);
}
}
)+
},
};
const AES_128: &str = "AES-128";
const AES_256: &str = "AES-256";
const NULL: &str = "NULL";
Err(SecioError::NoSupportIntersection(PROPOSITION_STRING, input.to_owned()))
const SHA_256: &str = "SHA256";
const SHA_512: &str = "SHA512";
pub(crate) const DEFAULT_AGREEMENTS_PROPOSITION: &str = "P-256,P-384";
pub(crate) const DEFAULT_CIPHERS_PROPOSITION: &str = "AES-128,AES-256";
pub(crate) const DEFAULT_DIGESTS_PROPOSITION: &str = "SHA256,SHA512";
/// Possible key agreement algorithms.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum KeyAgreement {
EcdhP256,
EcdhP384
}
/// Return a proposition string from the given sequence of `KeyAgreement` values.
pub fn key_agreements_proposition<'a, I>(xchgs: I) -> String
where
I: IntoIterator<Item=&'a KeyAgreement>
{
let mut s = String::new();
for x in xchgs {
match x {
KeyAgreement::EcdhP256 => {
s.push_str(ECDH_P256);
s.push(',')
}
KeyAgreement::EcdhP384 => {
s.push_str(ECDH_P384);
s.push(',')
}
}
);
}
s.pop(); // remove trailing comma if any
s
}
// Concatenates several strings with commas.
macro_rules! concat_comma {
($first:expr, $($rest:expr),*) => (
concat!($first $(, ',', $rest)*)
);
($elem:expr) => (
$elem
);
/// Given two key agreement proposition strings try to figure out a match.
///
/// The `Ordering` parameter determines which argument is preferred. If `Less` or `Equal` we
/// try for each of `theirs` every one of `ours`, for `Greater` it's the other way around.
pub fn select_agreement<'a>(r: Ordering, ours: &str, theirs: &str) -> Result<&'a agreement::Algorithm, SecioError> {
let (a, b) = match r {
Ordering::Less | Ordering::Equal => (theirs, ours),
Ordering::Greater => (ours, theirs)
};
for x in a.split(',') {
if b.split(',').any(|y| x == y) {
match x {
ECDH_P256 => return Ok(&agreement::ECDH_P256),
ECDH_P384 => return Ok(&agreement::ECDH_P384),
_ => continue
}
}
}
Err(SecioError::NoSupportIntersection)
}
// TODO: there's no library in the Rust ecosystem that supports P-521, but the Go & JS
// implementations advertise it
supported_impl!(
exchanges: &'static agreement::Algorithm,
"P-256" => &agreement::ECDH_P256,
"P-384" => &agreement::ECDH_P384,
);
// TODO: the Go & JS implementations advertise Blowfish ; however doing so in Rust leads to
// runtime errors
supported_impl!(
ciphers: Cipher,
"AES-128" => Cipher::Aes128,
"AES-256" => Cipher::Aes256,
);
/// Return a proposition string from the given sequence of `Cipher` values.
pub fn ciphers_proposition<'a, I>(ciphers: I) -> String
where
I: IntoIterator<Item=&'a Cipher>
{
let mut s = String::new();
for c in ciphers {
match c {
Cipher::Aes128 => {
s.push_str(AES_128);
s.push(',')
}
Cipher::Aes256 => {
s.push_str(AES_256);
s.push(',')
}
Cipher::Null => {
s.push_str(NULL);
s.push(',')
}
}
}
s.pop(); // remove trailing comma if any
s
}
/// Given two cipher proposition strings try to figure out a match.
///
/// The `Ordering` parameter determines which argument is preferred. If `Less` or `Equal` we
/// try for each of `theirs` every one of `ours`, for `Greater` it's the other way around.
pub fn select_cipher(r: Ordering, ours: &str, theirs: &str) -> Result<Cipher, SecioError> {
let (a, b) = match r {
Ordering::Less | Ordering::Equal => (theirs, ours),
Ordering::Greater => (ours, theirs)
};
for x in a.split(',') {
if b.split(',').any(|y| x == y) {
match x {
AES_128 => return Ok(Cipher::Aes128),
AES_256 => return Ok(Cipher::Aes256),
NULL => return Ok(Cipher::Null),
_ => continue
}
}
}
Err(SecioError::NoSupportIntersection)
}
/// Possible digest algorithms.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Digest {
Sha256,
Sha512
}
/// Return a proposition string from the given sequence of `Digest` values.
pub fn digests_proposition<'a, I>(digests: I) -> String
where
I: IntoIterator<Item=&'a Digest>
{
let mut s = String::new();
for d in digests {
match d {
Digest::Sha256 => {
s.push_str(SHA_256);
s.push(',')
}
Digest::Sha512 => {
s.push_str(SHA_512);
s.push(',')
}
}
}
s.pop(); // remove trailing comma if any
s
}
/// Given two digest proposition strings try to figure out a match.
///
/// The `Ordering` parameter determines which argument is preferred. If `Less` or `Equal` we
/// try for each of `theirs` every one of `ours`, for `Greater` it's the other way around.
pub fn select_digest<'a>(r: Ordering, ours: &str, theirs: &str) -> Result<&'a digest::Algorithm, SecioError> {
let (a, b) = match r {
Ordering::Less | Ordering::Equal => (theirs, ours),
Ordering::Greater => (ours, theirs)
};
for x in a.split(',') {
if b.split(',').any(|y| x == y) {
match x {
SHA_256 => return Ok(&digest::SHA256),
SHA_512 => return Ok(&digest::SHA512),
_ => continue
}
}
}
Err(SecioError::NoSupportIntersection)
}
supported_impl!(
hashes: &'static digest::Algorithm,
"SHA256" => &digest::SHA256,
"SHA512" => &digest::SHA512,
);

View File

@ -178,4 +178,9 @@ mod tests {
fn full_codec_encode_then_decode_aes256() {
full_codec_encode_then_decode(Cipher::Aes256);
}
#[test]
fn full_codec_encode_then_decode_null() {
full_codec_encode_then_decode(Cipher::Null);
}
}

View File

@ -35,7 +35,7 @@ pub enum SecioError {
HandshakeParsingFailure,
/// There is no protocol supported by both the local and remote hosts.
NoSupportIntersection(&'static str, String),
NoSupportIntersection,
/// Failed to generate nonce.
NonceGenerationFailed,
@ -73,7 +73,7 @@ impl error::Error for SecioError {
SecioError::HandshakeParsingFailure => {
"Failed to parse one of the handshake protobuf messages"
}
SecioError::NoSupportIntersection(_, _) => {
SecioError::NoSupportIntersection => {
"There is no protocol supported by both the local and remote hosts"
}
SecioError::NonceGenerationFailed => "Failed to generate nonce",

View File

@ -45,7 +45,7 @@ use structs_proto::{Exchange, Propose};
use tokio_io::codec::length_delimited;
use tokio_io::{AsyncRead, AsyncWrite};
use untrusted::Input as UntrustedInput;
use {SecioKeyPair, SecioKeyPairInner};
use {SecioConfig, SecioKeyPairInner};
/// Performs a handshake on the given socket.
///
@ -58,7 +58,7 @@ use {SecioKeyPair, SecioKeyPairInner};
/// negotiation.
pub fn handshake<'a, S: 'a>(
socket: S,
local_key: SecioKeyPair,
config: SecioConfig
) -> Box<Future<Item = (FullCodec<S>, PublicKey, Vec<u8>), Error = SecioError> + Send + 'a>
where
S: AsyncRead + AsyncWrite + Send,
@ -68,8 +68,8 @@ where
// This struct contains the whole context of a handshake, and is filled progressively
// throughout the various parts of the handshake.
struct HandshakeContext {
// Filled with this function's parameters.
local_key: SecioKeyPair,
// Filled with this function's parameter.
config: SecioConfig,
rng: rand::SystemRandom,
// Locally-generated random number. The array size can be changed without any repercussion.
@ -111,7 +111,7 @@ where
}
let context = HandshakeContext {
local_key,
config,
rng: rand::SystemRandom::new(),
local_nonce: Default::default(),
local_public_key_in_protobuf_bytes: Vec::new(),
@ -145,14 +145,36 @@ where
// Send our proposition with our nonce, public key and supported protocols.
.and_then(|mut context| {
context.local_public_key_in_protobuf_bytes = context.local_key.to_public_key().into_protobuf_encoding();
context.local_public_key_in_protobuf_bytes = context.config.key.to_public_key().into_protobuf_encoding();
let mut proposition = Propose::new();
proposition.set_rand(context.local_nonce.to_vec());
proposition.set_pubkey(context.local_public_key_in_protobuf_bytes.clone());
proposition.set_exchanges(algo_support::exchanges::PROPOSITION_STRING.into());
proposition.set_ciphers(algo_support::ciphers::PROPOSITION_STRING.into());
proposition.set_hashes(algo_support::hashes::PROPOSITION_STRING.into());
if let Some(ref p) = context.config.agreements_prop {
trace!("agreements proposition: {}", p);
proposition.set_exchanges(p.clone())
} else {
trace!("agreements proposition: {}", algo_support::DEFAULT_AGREEMENTS_PROPOSITION);
proposition.set_exchanges(algo_support::DEFAULT_AGREEMENTS_PROPOSITION.into())
}
if let Some(ref p) = context.config.ciphers_prop {
trace!("ciphers proposition: {}", p);
proposition.set_ciphers(p.clone())
} else {
trace!("ciphers proposition: {}", algo_support::DEFAULT_CIPHERS_PROPOSITION);
proposition.set_ciphers(algo_support::DEFAULT_CIPHERS_PROPOSITION.into())
}
if let Some(ref p) = context.config.digests_prop {
trace!("digests proposition: {}", p);
proposition.set_hashes(p.clone())
} else {
trace!("digests proposition: {}", algo_support::DEFAULT_DIGESTS_PROPOSITION);
proposition.set_hashes(algo_support::DEFAULT_DIGESTS_PROPOSITION.into())
}
let proposition_bytes = proposition.write_to_bytes().unwrap();
context.local_proposition_bytes = proposition_bytes.clone();
@ -226,8 +248,11 @@ where
};
context.chosen_exchange = {
let list = &remote_prop.get_exchanges();
Some(match algo_support::exchanges::select_best(context.hashes_ordering, list) {
let ours = context.config.agreements_prop.as_ref()
.map(|s| s.as_ref())
.unwrap_or(algo_support::DEFAULT_AGREEMENTS_PROPOSITION);
let theirs = &remote_prop.get_exchanges();
Some(match algo_support::select_agreement(context.hashes_ordering, ours, theirs) {
Ok(a) => a,
Err(err) => {
debug!("failed to select an exchange protocol");
@ -236,9 +261,15 @@ where
})
};
context.chosen_cipher = {
let list = &remote_prop.get_ciphers();
Some(match algo_support::ciphers::select_best(context.hashes_ordering, list) {
Ok(a) => a,
let ours = context.config.ciphers_prop.as_ref()
.map(|s| s.as_ref())
.unwrap_or(algo_support::DEFAULT_CIPHERS_PROPOSITION);
let theirs = &remote_prop.get_ciphers();
Some(match algo_support::select_cipher(context.hashes_ordering, ours, theirs) {
Ok(a) => {
debug!("selected cipher: {:?}", a);
a
}
Err(err) => {
debug!("failed to select a cipher protocol");
return Err(err);
@ -246,9 +277,15 @@ where
})
};
context.chosen_hash = {
let list = &remote_prop.get_hashes();
Some(match algo_support::hashes::select_best(context.hashes_ordering, list) {
Ok(a) => a,
let ours = context.config.digests_prop.as_ref()
.map(|s| s.as_ref())
.unwrap_or(algo_support::DEFAULT_DIGESTS_PROPOSITION);
let theirs = &remote_prop.get_hashes();
Some(match algo_support::select_digest(context.hashes_ordering, ours, theirs) {
Ok(a) => {
debug!("selected hash: {:?}", a);
a
}
Err(err) => {
debug!("failed to select a hash protocol");
return Err(err);
@ -285,7 +322,7 @@ where
let mut exchange = Exchange::new();
exchange.set_epubkey(local_tmp_pub_key.clone());
exchange.set_signature({
match context.local_key.inner {
match context.config.key.inner {
SecioKeyPairInner::Rsa { ref private, .. } => {
let mut state = match RSASigningState::new(private.clone()) {
Ok(s) => s,
@ -484,8 +521,7 @@ where
(cipher, hmac)
};
Ok(full_codec(socket, encoding_cipher, encoding_hmac, decoding_cipher,
decoding_hmac))
Ok(full_codec(socket, encoding_cipher, encoding_hmac, decoding_cipher, decoding_hmac))
});
match codec {
@ -571,7 +607,7 @@ mod tests {
use futures::Stream;
use ring::digest::SHA256;
use ring::hmac::SigningKey;
use SecioKeyPair;
use {SecioConfig, SecioKeyPair};
#[test]
fn handshake_with_self_succeeds_rsa() {
@ -587,14 +623,14 @@ mod tests {
SecioKeyPair::rsa_from_pkcs8(private, public).unwrap()
};
handshake_with_self_succeeds(key1, key2);
handshake_with_self_succeeds(SecioConfig::new(key1), SecioConfig::new(key2));
}
#[test]
fn handshake_with_self_succeeds_ed25519() {
let key1 = SecioKeyPair::ed25519_generated().unwrap();
let key2 = SecioKeyPair::ed25519_generated().unwrap();
handshake_with_self_succeeds(key1, key2);
handshake_with_self_succeeds(SecioConfig::new(key1), SecioConfig::new(key2));
}
#[test]
@ -610,10 +646,10 @@ mod tests {
SecioKeyPair::secp256k1_from_der(&key[..]).unwrap()
};
handshake_with_self_succeeds(key1, key2);
handshake_with_self_succeeds(SecioConfig::new(key1), SecioConfig::new(key2));
}
fn handshake_with_self_succeeds(key1: SecioKeyPair, key2: SecioKeyPair) {
fn handshake_with_self_succeeds(key1: SecioConfig, key2: SecioConfig) {
let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
let listener_addr = listener.local_addr().unwrap();

View File

@ -49,10 +49,9 @@
//! //let private_key = include_bytes!("test-rsa-private-key.pk8");
//! # let public_key = vec![];
//! //let public_key = include_bytes!("test-rsa-public-key.der").to_vec();
//! let upgrade = SecioConfig {
//! // See the documentation of `SecioKeyPair`.
//! key: SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(),
//! };
//! // See the documentation of `SecioKeyPair`.
//! let keypair = SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap();
//! let upgrade = SecioConfig::new(keypair);
//!
//! upgrade::map(upgrade, |out: SecioOutput<_>| out.stream)
//! });
@ -120,12 +119,57 @@ mod handshake;
mod stream_cipher;
mod structs_proto;
pub use algo_support::{Digest, KeyAgreement};
pub use stream_cipher::Cipher;
/// Implementation of the `ConnectionUpgrade` trait of `libp2p_core`. Automatically applies
/// secio on any connection.
#[derive(Clone)]
pub struct SecioConfig {
/// Private and public keys of the local node.
pub key: SecioKeyPair,
pub(crate) key: SecioKeyPair,
pub(crate) agreements_prop: Option<String>,
pub(crate) ciphers_prop: Option<String>,
pub(crate) digests_prop: Option<String>
}
impl SecioConfig {
/// Create a new `SecioConfig` with the given keypair.
pub fn new(kp: SecioKeyPair) -> Self {
SecioConfig {
key: kp,
agreements_prop: None,
ciphers_prop: None,
digests_prop: None
}
}
/// Override the default set of supported key agreement algorithms.
pub fn key_agreements<'a, I>(mut self, xs: I) -> Self
where
I: IntoIterator<Item=&'a KeyAgreement>
{
self.agreements_prop = Some(algo_support::key_agreements_proposition(xs));
self
}
/// Override the default set of supported ciphers.
pub fn ciphers<'a, I>(mut self, xs: I) -> Self
where
I: IntoIterator<Item=&'a Cipher>
{
self.ciphers_prop = Some(algo_support::ciphers_proposition(xs));
self
}
/// Override the default set of supported digest algorithms.
pub fn digests<'a, I>(mut self, xs: I) -> Self
where
I: IntoIterator<Item=&'a Digest>
{
self.digests_prop = Some(algo_support::digests_proposition(xs));
self
}
}
/// Private and public keys of the local node.
@ -308,7 +352,7 @@ where
) -> Self::Future {
debug!("Starting secio upgrade");
let fut = SecioMiddleware::handshake(incoming, self.key);
let fut = SecioMiddleware::handshake(incoming, self);
let wrapped = fut.map(|(stream_sink, pubkey, ephemeral)| {
let mapped = stream_sink.map_err(map_err as fn(_) -> _);
SecioOutput {
@ -345,12 +389,12 @@ where
/// communications, plus the public key of the remote, plus the ephemeral public key.
pub fn handshake<'a>(
socket: S,
key_pair: SecioKeyPair,
config: SecioConfig,
) -> Box<Future<Item = (SecioMiddleware<S>, PublicKey, Vec<u8>), Error = SecioError> + Send + 'a>
where
S: 'a,
{
let fut = handshake::handshake(socket, key_pair).map(|(inner, pubkey, ephemeral)| {
let fut = handshake::handshake(socket, config).map(|(inner, pubkey, ephemeral)| {
let inner = SecioMiddleware { inner };
(inner, pubkey, ephemeral)
});

View File

@ -19,12 +19,14 @@
// DEALINGS IN THE SOFTWARE.
use super::codec::StreamCipher;
use crypto::{aessafe, blockmodes::CtrModeX8};
use crypto::{aessafe, blockmodes::CtrModeX8, symmetriccipher::SynchronousStreamCipher};
#[derive(Clone, Copy)]
/// Possible encryption ciphers.
#[derive(Clone, Copy, Debug)]
pub enum Cipher {
Aes128,
Aes256,
Null
}
impl Cipher {
@ -33,20 +35,35 @@ impl Cipher {
match *self {
Cipher::Aes128 => 16,
Cipher::Aes256 => 32,
Cipher::Null => 0
}
}
/// Returns the size of in bytes of the IV expected by the cipher.
#[inline]
pub fn iv_size(&self) -> usize {
16 // CTR 128
match self {
Cipher::Aes128 | Cipher::Aes256 => 16,
Cipher::Null => 0
}
}
}
/// A no-op cipher which does not encrypt or decrypt at all.
/// Obviously only useful for debugging purposes.
#[derive(Clone, Copy, Debug)]
pub struct NullCipher;
impl SynchronousStreamCipher for NullCipher {
fn process(&mut self, input: &[u8], output: &mut [u8]) {
output.copy_from_slice(input)
}
}
/// Returns your stream cipher depending on `Cipher`.
#[inline]
pub fn ctr(key_size: Cipher, key: &[u8], iv: &[u8]) -> StreamCipher {
match key_size {
pub fn ctr(c: Cipher, key: &[u8], iv: &[u8]) -> StreamCipher {
match c {
Cipher::Aes128 => {
let aes_dec = aessafe::AesSafe128EncryptorX8::new(key);
Box::new(CtrModeX8::new(aes_dec, iv))
@ -55,5 +72,7 @@ pub fn ctr(key_size: Cipher, key: &[u8], iv: &[u8]) -> StreamCipher {
let aes_dec = aessafe::AesSafe256EncryptorX8::new(key);
Box::new(CtrModeX8::new(aes_dec, iv))
},
Cipher::Null => Box::new(NullCipher)
}
}

View File

@ -25,9 +25,9 @@
//! Here is a list of all the major concepts of libp2p.
//!
//! ## Multiaddr
//!
//!
//! A `Multiaddr` is a way to reach a node. Examples:
//!
//!
//! * `/ip4/80.123.90.4/tcp/5432`
//! * `/ip6/[::1]/udp/10560`
//! * `/unix//path/to/socket`
@ -92,9 +92,7 @@
//! # #[cfg(all(not(target_os = "emscripten"), feature = "libp2p-secio"))] {
//! use libp2p::{Transport, tcp::TcpConfig, secio::{SecioConfig, SecioKeyPair}};
//! let tcp_transport = TcpConfig::new();
//! let secio_upgrade = SecioConfig {
//! key: SecioKeyPair::ed25519_generated().unwrap(),
//! };
//! let secio_upgrade = SecioConfig::new(SecioKeyPair::ed25519_generated().unwrap());
//! let with_security = tcp_transport.with_upgrade(secio_upgrade);
//! // let _ = with_security.dial(...);
//! // `with_security` also implements the `Transport` trait, and all the connections opened