mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-22 14:21:33 +00:00
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:
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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 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);
|
||||
const AES_128: &str = "AES-128";
|
||||
const AES_256: &str = "AES-256";
|
||||
const NULL: &str = "NULL";
|
||||
|
||||
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(',')
|
||||
}
|
||||
},
|
||||
Ordering::Greater => {
|
||||
$(
|
||||
for second_elem in input.split(',') {
|
||||
if $name == second_elem {
|
||||
return Ok($val);
|
||||
KeyAgreement::EcdhP384 => {
|
||||
s.push_str(ECDH_P384);
|
||||
s.push(',')
|
||||
}
|
||||
}
|
||||
)+
|
||||
},
|
||||
}
|
||||
s.pop(); // remove trailing comma if any
|
||||
s
|
||||
}
|
||||
|
||||
/// 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)
|
||||
};
|
||||
|
||||
Err(SecioError::NoSupportIntersection(PROPOSITION_STRING, input.to_owned()))
|
||||
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)
|
||||
}
|
||||
|
||||
// Concatenates several strings with commas.
|
||||
macro_rules! concat_comma {
|
||||
($first:expr, $($rest:expr),*) => (
|
||||
concat!($first $(, ',', $rest)*)
|
||||
);
|
||||
($elem:expr) => (
|
||||
$elem
|
||||
);
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
// 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,
|
||||
);
|
||||
/// 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)
|
||||
}
|
||||
|
||||
// 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,
|
||||
);
|
||||
|
||||
supported_impl!(
|
||||
hashes: &'static digest::Algorithm,
|
||||
"SHA256" => &digest::SHA256,
|
||||
"SHA512" => &digest::SHA512,
|
||||
);
|
||||
/// 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)
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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(),
|
||||
//! };
|
||||
//! 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)
|
||||
});
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user