From 66193eb4dbffff1f25d98b62b791d12a82a401e4 Mon Sep 17 00:00:00 2001 From: cheme Date: Thu, 2 Aug 2018 10:53:12 +0200 Subject: [PATCH 01/10] Replace rust-crypto dependency by RustCrypto project crate aes-ctr --- secio/Cargo.toml | 5 ++++- secio/src/algo_support.rs | 2 +- secio/src/codec/decode.rs | 31 +++++++++++++++------------ secio/src/codec/encode.rs | 26 +++++++++------------- secio/src/codec/mod.rs | 45 ++++++++++++++------------------------- secio/src/error.rs | 10 ++++----- secio/src/handshake.rs | 7 +++--- secio/src/lib.rs | 25 +++++++++++++++++++++- 8 files changed, 80 insertions(+), 71 deletions(-) diff --git a/secio/Cargo.toml b/secio/Cargo.toml index 4fff69af24f..7b96464a598 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -13,12 +13,15 @@ log = "0.4.1" protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } -rust-crypto = "^0.2" +aes-ctr = "0.1.0" rw-stream-sink = { path = "../rw-stream-sink" } eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", optional = true } tokio-io = "0.1.0" untrusted = "0.5.1" +#[dependencies.aes-ctr] +#path = "../../../tmprrepos/stream-ciphers/aes-ctr" + [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] diff --git a/secio/src/algo_support.rs b/secio/src/algo_support.rs index 454060cc612..732926bb663 100644 --- a/secio/src/algo_support.rs +++ b/secio/src/algo_support.rs @@ -28,7 +28,7 @@ macro_rules! supported_impl { pub mod $mod_name { use std::cmp::Ordering; #[allow(unused_imports)] - use crypto::aes::KeySize; + use KeySize; #[allow(unused_imports)] use ring::{agreement, digest}; use error::SecioError; diff --git a/secio/src/codec/decode.rs b/secio/src/codec/decode.rs index 5f41750ae01..ed894fa90c2 100644 --- a/secio/src/codec/decode.rs +++ b/secio/src/codec/decode.rs @@ -21,7 +21,7 @@ //! Individual messages decoding. use bytes::BytesMut; -use crypto::symmetriccipher::SynchronousStreamCipher; +use super::StreamCipher; use error::SecioError; use futures::sink::Sink; @@ -40,7 +40,7 @@ use ring::hmac; /// /// Also implements `Sink` for convenience. pub struct DecoderMiddleware { - cipher_state: Box, + cipher_state: Box, hmac_key: hmac::VerificationKey, // TODO: when a new version of ring is released, we can use `hmac_key.digest_algorithm().output_len` instead hmac_num_bytes: usize, @@ -51,7 +51,7 @@ impl DecoderMiddleware { #[inline] pub fn new( raw_stream: S, - cipher: Box, + cipher: Box, hmac_key: hmac::VerificationKey, hmac_num_bytes: usize, // TODO: remove this parameter ) -> DecoderMiddleware { @@ -88,21 +88,24 @@ where debug!("frame too short when decoding secio frame"); return Err(SecioError::FrameTooShort); } - - let (crypted_data, expected_hash) = frame.split_at(frame.len() - hmac_num_bytes); - debug_assert_eq!(expected_hash.len(), hmac_num_bytes); - - if hmac::verify(&self.hmac_key, crypted_data, expected_hash).is_err() { - debug!("hmac mismatch when decoding secio frame"); - return Err(SecioError::HmacNotMatching); + let content_length = frame.len() - hmac_num_bytes; + { + let (crypted_data, expected_hash) = frame.split_at(content_length); + debug_assert_eq!(expected_hash.len(), hmac_num_bytes); + + if hmac::verify(&self.hmac_key, crypted_data, expected_hash).is_err() { + debug!("hmac mismatch when decoding secio frame"); + return Err(SecioError::HmacNotMatching); + } } - // Note that there is no way to decipher in place with rust-crypto right now. - let mut decrypted_data = crypted_data.to_vec(); + let mut data_buf = frame.to_vec(); + data_buf.truncate(content_length); self.cipher_state - .process(&crypted_data, &mut decrypted_data); + .try_apply_keystream(&mut data_buf) + .map_err::(|e|e.into())?; - Ok(Async::Ready(Some(decrypted_data))) + Ok(Async::Ready(Some(data_buf))) } } diff --git a/secio/src/codec/encode.rs b/secio/src/codec/encode.rs index d14c8bf1c95..6b8aa1ed693 100644 --- a/secio/src/codec/encode.rs +++ b/secio/src/codec/encode.rs @@ -21,7 +21,7 @@ //! Individual messages encoding. use bytes::BytesMut; -use crypto::symmetriccipher::SynchronousStreamCipher; +use super::StreamCipher; use futures::sink::Sink; use futures::stream::Stream; use futures::Poll; @@ -36,7 +36,7 @@ use ring::hmac; /// /// Also implements `Stream` for convenience. pub struct EncoderMiddleware { - cipher_state: Box, + cipher_state: Box, hmac_key: hmac::SigningKey, raw_sink: S, } @@ -44,7 +44,7 @@ pub struct EncoderMiddleware { impl EncoderMiddleware { pub fn new( raw_sink: S, - cipher: Box, + cipher: Box, hmac_key: hmac::SigningKey, ) -> EncoderMiddleware { EncoderMiddleware { @@ -63,21 +63,15 @@ where type SinkError = S::SinkError; fn start_send(&mut self, item: Self::SinkItem) -> StartSend { - let capacity = item.len() + self.hmac_key.digest_algorithm().output_len; - // Apparently this is the fastest way of doing. - // See https://gist.github.com/kirushik/e0d93759b0cd102f814408595c20a9d0 - let mut out_buffer = BytesMut::from(vec![0; capacity]); + let mut out_data = item; + // TODO if SinkError gets refactor to SecioError, + // then use try_apply_keystream + self.cipher_state.apply_keystream(&mut out_data[..]); + let signature = hmac::sign(&self.hmac_key, &out_data[..]); + out_data.extend_from_slice(signature.as_ref()); + self.raw_sink.start_send(out_data) - { - let (out_data, out_sign) = out_buffer.split_at_mut(item.len()); - self.cipher_state.process(&item, out_data); - - let signature = hmac::sign(&self.hmac_key, out_data); - out_sign.copy_from_slice(signature.as_ref()); - } - - self.raw_sink.start_send(out_buffer) } #[inline] diff --git a/secio/src/codec/mod.rs b/secio/src/codec/mod.rs index cff010754ea..feef03fd1e8 100644 --- a/secio/src/codec/mod.rs +++ b/secio/src/codec/mod.rs @@ -24,7 +24,7 @@ use self::decode::DecoderMiddleware; use self::encode::EncoderMiddleware; -use crypto::symmetriccipher::SynchronousStreamCipher; +use aes_ctr::stream_cipher::StreamCipherCore; use ring::hmac; use tokio_io::codec::length_delimited; use tokio_io::{AsyncRead, AsyncWrite}; @@ -35,6 +35,9 @@ mod encode; /// Type returned by `full_codec`. pub type FullCodec = DecoderMiddleware>>; +pub type StreamCipher = StreamCipherCore; + + /// Takes control of `socket`. Returns an object that implements `future::Sink` and /// `future::Stream`. The `Stream` and `Sink` produce and accept `BytesMut` objects. /// @@ -42,9 +45,9 @@ pub type FullCodec = DecoderMiddleware( socket: length_delimited::Framed, - cipher_encoding: Box, + cipher_encoding: Box, encoding_hmac: hmac::SigningKey, - cipher_decoder: Box, + cipher_decoder: Box, decoding_hmac: hmac::VerificationKey, ) -> FullCodec where @@ -61,12 +64,11 @@ mod tests { extern crate tokio_tcp; use self::tokio_tcp::TcpListener; use self::tokio_tcp::TcpStream; + use ::{ctr, KeySize}; use super::full_codec; use super::DecoderMiddleware; use super::EncoderMiddleware; use bytes::BytesMut; - use crypto::aessafe::AesSafe256Encryptor; - use crypto::blockmodes::CtrMode; use error::SecioError; use futures::sync::mpsc::channel; use futures::{Future, Sink, Stream}; @@ -77,6 +79,8 @@ mod tests { use std::io::Error as IoError; use tokio_io::codec::length_delimited::Framed; + const NULL_IV : [u8; 16] = [0;16]; + #[test] fn raw_encode_then_decode() { let (data_tx, data_rx) = channel::(256); @@ -86,20 +90,15 @@ mod tests { let cipher_key: [u8; 32] = rand::random(); let hmac_key: [u8; 32] = rand::random(); + let encoder = EncoderMiddleware::new( data_tx, - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key, &NULL_IV[..]), SigningKey::new(&SHA256, &hmac_key), ); let decoder = DecoderMiddleware::new( data_rx, - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key, &NULL_IV[..]), VerificationKey::new(&SHA256, &hmac_key), 32, ); @@ -133,15 +132,9 @@ mod tests { full_codec( connec, - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key, &NULL_IV[..]), SigningKey::new(&SHA256, &hmac_key), - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key, &NULL_IV[..]), VerificationKey::new(&SHA256, &hmac_key), ) }, @@ -154,15 +147,9 @@ mod tests { full_codec( stream, - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key_clone), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key_clone, &NULL_IV[..]), SigningKey::new(&SHA256, &hmac_key_clone), - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key_clone), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key_clone, &NULL_IV[..]), VerificationKey::new(&SHA256, &hmac_key_clone), ) }); diff --git a/secio/src/error.rs b/secio/src/error.rs index dd6adf31d64..a2bde175476 100644 --- a/secio/src/error.rs +++ b/secio/src/error.rs @@ -20,7 +20,7 @@ //! Defines the `SecioError` enum that groups all possible errors in SECIO. -use crypto::symmetriccipher::SymmetricCipherError; +use aes_ctr::stream_cipher::LoopError; use std::error; use std::fmt; use std::io::Error as IoError; @@ -55,8 +55,8 @@ pub enum SecioError { /// The final check of the handshake failed. NonceVerificationFailed, - /// Error while decoding/encoding data. - CipherError(SymmetricCipherError), + /// Error with block cipher. + CipherError(LoopError), /// The received frame was of invalid length. FrameTooShort, @@ -111,9 +111,9 @@ impl fmt::Display for SecioError { } } -impl From for SecioError { +impl From for SecioError { #[inline] - fn from(err: SymmetricCipherError) -> SecioError { + fn from(err: LoopError) -> SecioError { SecioError::CipherError(err) } } diff --git a/secio/src/handshake.rs b/secio/src/handshake.rs index 2165eb4b7d4..42e7bc97204 100644 --- a/secio/src/handshake.rs +++ b/secio/src/handshake.rs @@ -21,7 +21,7 @@ use algo_support; use bytes::BytesMut; use codec::{full_codec, FullCodec}; -use crypto::aes::{ctr, KeySize}; +use super::{KeySize, ctr}; use error::SecioError; use futures::future; use futures::sink::Sink; @@ -456,7 +456,6 @@ where let (cipher_key_size, iv_size) = match chosen_cipher { KeySize::KeySize128 => (16, 16), KeySize::KeySize256 => (32, 16), - _ => panic!() }; let mut longer_key = vec![0u8; 2 * (iv_size + cipher_key_size + 20)]; @@ -487,8 +486,8 @@ where (cipher, hmac) }; - Ok(full_codec(socket, Box::new(encoding_cipher), encoding_hmac, - Box::new(decoding_cipher), decoding_hmac)) + Ok(full_codec(socket, encoding_cipher, encoding_hmac, + decoding_cipher, decoding_hmac)) }); match codec { diff --git a/secio/src/lib.rs b/secio/src/lib.rs index 17c2fd2bf46..c745d7415a2 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -78,10 +78,10 @@ //! `SecioMiddleware` that implements `Sink` and `Stream` and can be used to send packets of data. //! +extern crate aes_ctr; #[cfg(feature = "secp256k1")] extern crate asn1_der; extern crate bytes; -extern crate crypto; extern crate futures; extern crate libp2p_core; #[macro_use] @@ -110,6 +110,8 @@ use std::error::Error; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::iter; use std::sync::Arc; +use aes_ctr::stream_cipher::generic_array::GenericArray; +use aes_ctr::stream_cipher::{NewFixStreamCipher, StreamCipherCore}; use tokio_io::{AsyncRead, AsyncWrite}; use untrusted::Input; @@ -119,6 +121,27 @@ mod error; mod handshake; mod structs_proto; +/// AES key size +#[derive(Clone, Copy)] +pub enum KeySize { + KeySize128, + KeySize256, +} + +pub fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + + match key_size { + KeySize::KeySize128 => Box::new(aes_ctr::Aes128Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + KeySize::KeySize256 => Box::new(aes_ctr::Aes256Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + } +} + /// Implementation of the `ConnectionUpgrade` trait of `libp2p_core`. Automatically applies /// secio on any connection. #[derive(Clone)] From c4a4a86787c86b4ef986c0656adb5d236dd45d4d Mon Sep 17 00:00:00 2001 From: cheme Date: Thu, 2 Aug 2018 12:37:32 +0200 Subject: [PATCH 02/10] Support for x86 build supporting both aesni and aessoft at runtime. Feature gated behind 'aes-all'. Building requires RUSTFLAGS="-C target-feature=+aes,+ssse3" and RUSTDOCFLAGS="-C target-feature=+aes,+ssse3". Only support x86 case. --- secio/Cargo.toml | 6 +-- secio/src/algo_support.rs | 2 +- secio/src/codec/encode.rs | 10 ++--- secio/src/codec/mod.rs | 2 +- secio/src/handshake.rs | 2 +- secio/src/lib.rs | 23 +--------- secio/src/stream_cipher.rs | 87 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 secio/src/stream_cipher.rs diff --git a/secio/Cargo.toml b/secio/Cargo.toml index 7b96464a598..5d7e8eee770 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -14,17 +14,17 @@ protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } aes-ctr = "0.1.0" +aes-soft = { version = "0.2", optional = true } +ctr = { version = "0.1", optional = true } rw-stream-sink = { path = "../rw-stream-sink" } eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", optional = true } tokio-io = "0.1.0" untrusted = "0.5.1" -#[dependencies.aes-ctr] -#path = "../../../tmprrepos/stream-ciphers/aes-ctr" - [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] +aes-all = ["ctr","aes-soft"] [dev-dependencies] libp2p-tcp-transport = { path = "../tcp-transport" } diff --git a/secio/src/algo_support.rs b/secio/src/algo_support.rs index 732926bb663..8cff3725d41 100644 --- a/secio/src/algo_support.rs +++ b/secio/src/algo_support.rs @@ -28,7 +28,7 @@ macro_rules! supported_impl { pub mod $mod_name { use std::cmp::Ordering; #[allow(unused_imports)] - use KeySize; + use stream_cipher::KeySize; #[allow(unused_imports)] use ring::{agreement, digest}; use error::SecioError; diff --git a/secio/src/codec/encode.rs b/secio/src/codec/encode.rs index 6b8aa1ed693..9a01c847c48 100644 --- a/secio/src/codec/encode.rs +++ b/secio/src/codec/encode.rs @@ -64,13 +64,13 @@ where fn start_send(&mut self, item: Self::SinkItem) -> StartSend { - let mut out_data = item; + let mut data_buf = item; // TODO if SinkError gets refactor to SecioError, // then use try_apply_keystream - self.cipher_state.apply_keystream(&mut out_data[..]); - let signature = hmac::sign(&self.hmac_key, &out_data[..]); - out_data.extend_from_slice(signature.as_ref()); - self.raw_sink.start_send(out_data) + self.cipher_state.apply_keystream(&mut data_buf[..]); + let signature = hmac::sign(&self.hmac_key, &data_buf[..]); + data_buf.extend_from_slice(signature.as_ref()); + self.raw_sink.start_send(data_buf) } diff --git a/secio/src/codec/mod.rs b/secio/src/codec/mod.rs index feef03fd1e8..cbf922e57d7 100644 --- a/secio/src/codec/mod.rs +++ b/secio/src/codec/mod.rs @@ -64,7 +64,7 @@ mod tests { extern crate tokio_tcp; use self::tokio_tcp::TcpListener; use self::tokio_tcp::TcpStream; - use ::{ctr, KeySize}; + use stream_cipher::{ctr, KeySize}; use super::full_codec; use super::DecoderMiddleware; use super::EncoderMiddleware; diff --git a/secio/src/handshake.rs b/secio/src/handshake.rs index 42e7bc97204..f7c163c60c3 100644 --- a/secio/src/handshake.rs +++ b/secio/src/handshake.rs @@ -21,7 +21,7 @@ use algo_support; use bytes::BytesMut; use codec::{full_codec, FullCodec}; -use super::{KeySize, ctr}; +use stream_cipher::{KeySize, ctr}; use error::SecioError; use futures::future; use futures::sink::Sink; diff --git a/secio/src/lib.rs b/secio/src/lib.rs index c745d7415a2..a902c2aaaa0 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -110,8 +110,6 @@ use std::error::Error; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::iter; use std::sync::Arc; -use aes_ctr::stream_cipher::generic_array::GenericArray; -use aes_ctr::stream_cipher::{NewFixStreamCipher, StreamCipherCore}; use tokio_io::{AsyncRead, AsyncWrite}; use untrusted::Input; @@ -120,27 +118,8 @@ mod codec; mod error; mod handshake; mod structs_proto; +mod stream_cipher; -/// AES key size -#[derive(Clone, Copy)] -pub enum KeySize { - KeySize128, - KeySize256, -} - -pub fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { - - match key_size { - KeySize::KeySize128 => Box::new(aes_ctr::Aes128Ctr::new( - GenericArray::from_slice(key), - GenericArray::from_slice(iv), - )), - KeySize::KeySize256 => Box::new(aes_ctr::Aes256Ctr::new( - GenericArray::from_slice(key), - GenericArray::from_slice(iv), - )), - } -} /// Implementation of the `ConnectionUpgrade` trait of `libp2p_core`. Automatically applies /// secio on any connection. diff --git a/secio/src/stream_cipher.rs b/secio/src/stream_cipher.rs new file mode 100644 index 00000000000..24bb9e26c71 --- /dev/null +++ b/secio/src/stream_cipher.rs @@ -0,0 +1,87 @@ + +use aes_ctr::stream_cipher::generic_array::GenericArray; +use aes_ctr::stream_cipher::{NewFixStreamCipher, StreamCipherCore}; +use aes_ctr::{Aes128Ctr, Aes256Ctr}; + +#[derive(Clone, Copy)] +pub enum KeySize { + KeySize128, + KeySize256, +} + +/// Returns your stream cipher depending on `KeySize`. +#[cfg(not(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86"))))] +pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + ctr_int(key_size, key, iv) +} + +/// Returns your stream cipher depending on `KeySize`. +#[cfg(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86")))] +pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + if is_x86_feature_detected!("aes") + && is_x86_feature_detected!("sse2") + && is_x86_feature_detected!("sse3") { + ctr_int(key_size, key, iv) + } else { + aes_alt::ctr_alt(key_size, key, iv) + } +} + + +#[cfg(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86")))] +mod aes_alt { + extern crate ctr; + extern crate aes_soft; + use self::ctr::Ctr128; + use self::aes_soft::{Aes128, Aes256}; + use self::ctr::stream_cipher::{NewFixStreamCipher, StreamCipherCore}; + use self::ctr::stream_cipher::generic_array::GenericArray; + use super::KeySize; + + /// AES-128 in CTR mode + pub type Aes128Ctr = Ctr128; + /// AES-256 in CTR mode + pub type Aes256Ctr = Ctr128; + /// Returns alternate stream cipher if target functionalities does not allow standard one. + /// Eg : aes without sse + pub fn ctr_alt(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + match key_size { + KeySize::KeySize128 => Box::new(Aes128Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + KeySize::KeySize256 => Box::new(Aes256Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + } + } + +} + +#[inline] +fn ctr_int(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + match key_size { + KeySize::KeySize128 => Box::new(Aes128Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + KeySize::KeySize256 => Box::new(Aes256Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + } +} + + +// aesni compile check for aes-all (aes-all import aesni through aes_ctr only if those checks pass) +#[cfg(all( + feature = "aes-all", + any(target_arch = "x86_64", target_arch = "x86"), + not(all(target_feature = "aes", target_feature = "ssse3")), +))] +compile_error!( + "enable aes and ssse3 target features if using aes-all to build, e.g. with \ + RUSTFLAGS=\"-C target-feature=+aes,+ssse3\" enviromental variable. \ + For x86 target arch additionally enable sse2 target feature." +); From 8537e962881caca328b25dac3ab6e1a58cb7b731 Mon Sep 17 00:00:00 2001 From: cheme Date: Mon, 6 Aug 2018 11:57:38 +0200 Subject: [PATCH 03/10] Change CI to build with aes ni on all-features --- .circleci/config.yml | 2 +- secio/Cargo.toml | 3 ++- secio/src/codec/mod.rs | 2 +- secio/src/lib.rs | 3 +++ secio/src/stream_cipher.rs | 29 +++++++++++++++++++++++++---- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 92f98ee33da..33ff82662ee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p cargo test --no-default-features - run: name: Run tests, inside a docker image, with all features - command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p cargo test --all-features + command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p sh -c 'RUSTFLAGS="-C target-feature=+aes,+ssse3,+sse2" RUSTDOCFLAGS="-C target-feature=+aes,+ssse3,+sse2" cargo test --all-features' - save_cache: key: test-cache paths: diff --git a/secio/Cargo.toml b/secio/Cargo.toml index 5d7e8eee770..28eb54283d5 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -16,6 +16,7 @@ ring = { version = "0.12.1", features = ["rsa_signing"] } aes-ctr = "0.1.0" aes-soft = { version = "0.2", optional = true } ctr = { version = "0.1", optional = true } +lazy_static = { version = "0.2.11", optional = true } rw-stream-sink = { path = "../rw-stream-sink" } eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", optional = true } tokio-io = "0.1.0" @@ -24,7 +25,7 @@ untrusted = "0.5.1" [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] -aes-all = ["ctr","aes-soft"] +aes-all = ["ctr","aes-soft","lazy_static"] [dev-dependencies] libp2p-tcp-transport = { path = "../tcp-transport" } diff --git a/secio/src/codec/mod.rs b/secio/src/codec/mod.rs index cbf922e57d7..637659e74f2 100644 --- a/secio/src/codec/mod.rs +++ b/secio/src/codec/mod.rs @@ -111,7 +111,7 @@ mod tests { let (_, decoded) = tokio_current_thread::block_on_all(data_sent.join(data_received)) .map_err(|_| ()) .unwrap(); - assert_eq!(decoded.unwrap(), data); + assert_eq!(&decoded.unwrap()[..], &data[..]); } #[test] diff --git a/secio/src/lib.rs b/secio/src/lib.rs index a902c2aaaa0..6ebfde4c2e0 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -95,6 +95,9 @@ extern crate secp256k1; extern crate tokio_io; extern crate untrusted; +#[cfg(feature = "aes-all")] +#[macro_use] +extern crate lazy_static; pub use self::error::SecioError; #[cfg(feature = "secp256k1")] diff --git a/secio/src/stream_cipher.rs b/secio/src/stream_cipher.rs index 24bb9e26c71..a77d0adaf6c 100644 --- a/secio/src/stream_cipher.rs +++ b/secio/src/stream_cipher.rs @@ -14,13 +14,11 @@ pub enum KeySize { pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { ctr_int(key_size, key, iv) } - + /// Returns your stream cipher depending on `KeySize`. #[cfg(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86")))] pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { - if is_x86_feature_detected!("aes") - && is_x86_feature_detected!("sse2") - && is_x86_feature_detected!("sse3") { + if *aes_alt::AES_NI { ctr_int(key_size, key, iv) } else { aes_alt::ctr_alt(key_size, key, iv) @@ -38,6 +36,13 @@ mod aes_alt { use self::ctr::stream_cipher::generic_array::GenericArray; use super::KeySize; + lazy_static! { + pub static ref AES_NI: bool = is_x86_feature_detected!("aes") + && is_x86_feature_detected!("sse2") + && is_x86_feature_detected!("sse3"); + + } + /// AES-128 in CTR mode pub type Aes128Ctr = Ctr128; /// AES-256 in CTR mode @@ -73,6 +78,22 @@ fn ctr_int(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box Date: Tue, 7 Aug 2018 09:52:09 +0200 Subject: [PATCH 04/10] Switch aes-all logic (is_x86 runtime test did not work as I thought when RUSTC aes and sse are enabled). To build with aes-all, aes and sse flag should not be set. --- .circleci/config.yml | 2 +- secio/Cargo.toml | 4 ++-- secio/src/lib.rs | 1 - secio/src/stream_cipher.rs | 13 +++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 33ff82662ee..92f98ee33da 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,7 +36,7 @@ jobs: command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p cargo test --no-default-features - run: name: Run tests, inside a docker image, with all features - command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p sh -c 'RUSTFLAGS="-C target-feature=+aes,+ssse3,+sse2" RUSTDOCFLAGS="-C target-feature=+aes,+ssse3,+sse2" cargo test --all-features' + command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p cargo test --all-features - save_cache: key: test-cache paths: diff --git a/secio/Cargo.toml b/secio/Cargo.toml index 28eb54283d5..aac2b99f8e5 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -14,7 +14,7 @@ protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } aes-ctr = "0.1.0" -aes-soft = { version = "0.2", optional = true } +aesni = { git="https://github.com/cheme/block-ciphers.git", features = ["nocheck"], optional = true } ctr = { version = "0.1", optional = true } lazy_static = { version = "0.2.11", optional = true } rw-stream-sink = { path = "../rw-stream-sink" } @@ -25,7 +25,7 @@ untrusted = "0.5.1" [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] -aes-all = ["ctr","aes-soft","lazy_static"] +aes-all = ["ctr","aesni","lazy_static"] [dev-dependencies] libp2p-tcp-transport = { path = "../tcp-transport" } diff --git a/secio/src/lib.rs b/secio/src/lib.rs index 6ebfde4c2e0..5476e5e9c20 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -123,7 +123,6 @@ mod handshake; mod structs_proto; mod stream_cipher; - /// Implementation of the `ConnectionUpgrade` trait of `libp2p_core`. Automatically applies /// secio on any connection. #[derive(Clone)] diff --git a/secio/src/stream_cipher.rs b/secio/src/stream_cipher.rs index a77d0adaf6c..fdcf782bce5 100644 --- a/secio/src/stream_cipher.rs +++ b/secio/src/stream_cipher.rs @@ -19,9 +19,9 @@ pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box Box { if *aes_alt::AES_NI { - ctr_int(key_size, key, iv) - } else { aes_alt::ctr_alt(key_size, key, iv) + } else { + ctr_int(key_size, key, iv) } } @@ -29,9 +29,9 @@ pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box Date: Thu, 9 Aug 2018 16:21:45 +0200 Subject: [PATCH 05/10] Use latest aesni from crates.io (with pr to disable compile time checks). --- secio/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secio/Cargo.toml b/secio/Cargo.toml index aac2b99f8e5..16040d36656 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -14,7 +14,7 @@ protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } aes-ctr = "0.1.0" -aesni = { git="https://github.com/cheme/block-ciphers.git", features = ["nocheck"], optional = true } +aesni = { version = "0.4.1", features = ["nocheck"], optional = true } ctr = { version = "0.1", optional = true } lazy_static = { version = "0.2.11", optional = true } rw-stream-sink = { path = "../rw-stream-sink" } From cfb99901f983c40f4c5037e3b4bd91fb8b646aac Mon Sep 17 00:00:00 2001 From: cheme Date: Thu, 2 Aug 2018 10:53:12 +0200 Subject: [PATCH 06/10] Replace rust-crypto dependency by RustCrypto project crate aes-ctr --- secio/Cargo.toml | 5 ++++- secio/src/algo_support.rs | 2 +- secio/src/codec/decode.rs | 31 +++++++++++++++------------ secio/src/codec/encode.rs | 26 +++++++++------------- secio/src/codec/mod.rs | 45 ++++++++++++++------------------------- secio/src/error.rs | 10 ++++----- secio/src/handshake.rs | 7 +++--- secio/src/lib.rs | 25 +++++++++++++++++++++- 8 files changed, 80 insertions(+), 71 deletions(-) diff --git a/secio/Cargo.toml b/secio/Cargo.toml index 4fff69af24f..7b96464a598 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -13,12 +13,15 @@ log = "0.4.1" protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } -rust-crypto = "^0.2" +aes-ctr = "0.1.0" rw-stream-sink = { path = "../rw-stream-sink" } eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", optional = true } tokio-io = "0.1.0" untrusted = "0.5.1" +#[dependencies.aes-ctr] +#path = "../../../tmprrepos/stream-ciphers/aes-ctr" + [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] diff --git a/secio/src/algo_support.rs b/secio/src/algo_support.rs index 454060cc612..732926bb663 100644 --- a/secio/src/algo_support.rs +++ b/secio/src/algo_support.rs @@ -28,7 +28,7 @@ macro_rules! supported_impl { pub mod $mod_name { use std::cmp::Ordering; #[allow(unused_imports)] - use crypto::aes::KeySize; + use KeySize; #[allow(unused_imports)] use ring::{agreement, digest}; use error::SecioError; diff --git a/secio/src/codec/decode.rs b/secio/src/codec/decode.rs index 5f41750ae01..ed894fa90c2 100644 --- a/secio/src/codec/decode.rs +++ b/secio/src/codec/decode.rs @@ -21,7 +21,7 @@ //! Individual messages decoding. use bytes::BytesMut; -use crypto::symmetriccipher::SynchronousStreamCipher; +use super::StreamCipher; use error::SecioError; use futures::sink::Sink; @@ -40,7 +40,7 @@ use ring::hmac; /// /// Also implements `Sink` for convenience. pub struct DecoderMiddleware { - cipher_state: Box, + cipher_state: Box, hmac_key: hmac::VerificationKey, // TODO: when a new version of ring is released, we can use `hmac_key.digest_algorithm().output_len` instead hmac_num_bytes: usize, @@ -51,7 +51,7 @@ impl DecoderMiddleware { #[inline] pub fn new( raw_stream: S, - cipher: Box, + cipher: Box, hmac_key: hmac::VerificationKey, hmac_num_bytes: usize, // TODO: remove this parameter ) -> DecoderMiddleware { @@ -88,21 +88,24 @@ where debug!("frame too short when decoding secio frame"); return Err(SecioError::FrameTooShort); } - - let (crypted_data, expected_hash) = frame.split_at(frame.len() - hmac_num_bytes); - debug_assert_eq!(expected_hash.len(), hmac_num_bytes); - - if hmac::verify(&self.hmac_key, crypted_data, expected_hash).is_err() { - debug!("hmac mismatch when decoding secio frame"); - return Err(SecioError::HmacNotMatching); + let content_length = frame.len() - hmac_num_bytes; + { + let (crypted_data, expected_hash) = frame.split_at(content_length); + debug_assert_eq!(expected_hash.len(), hmac_num_bytes); + + if hmac::verify(&self.hmac_key, crypted_data, expected_hash).is_err() { + debug!("hmac mismatch when decoding secio frame"); + return Err(SecioError::HmacNotMatching); + } } - // Note that there is no way to decipher in place with rust-crypto right now. - let mut decrypted_data = crypted_data.to_vec(); + let mut data_buf = frame.to_vec(); + data_buf.truncate(content_length); self.cipher_state - .process(&crypted_data, &mut decrypted_data); + .try_apply_keystream(&mut data_buf) + .map_err::(|e|e.into())?; - Ok(Async::Ready(Some(decrypted_data))) + Ok(Async::Ready(Some(data_buf))) } } diff --git a/secio/src/codec/encode.rs b/secio/src/codec/encode.rs index d14c8bf1c95..6b8aa1ed693 100644 --- a/secio/src/codec/encode.rs +++ b/secio/src/codec/encode.rs @@ -21,7 +21,7 @@ //! Individual messages encoding. use bytes::BytesMut; -use crypto::symmetriccipher::SynchronousStreamCipher; +use super::StreamCipher; use futures::sink::Sink; use futures::stream::Stream; use futures::Poll; @@ -36,7 +36,7 @@ use ring::hmac; /// /// Also implements `Stream` for convenience. pub struct EncoderMiddleware { - cipher_state: Box, + cipher_state: Box, hmac_key: hmac::SigningKey, raw_sink: S, } @@ -44,7 +44,7 @@ pub struct EncoderMiddleware { impl EncoderMiddleware { pub fn new( raw_sink: S, - cipher: Box, + cipher: Box, hmac_key: hmac::SigningKey, ) -> EncoderMiddleware { EncoderMiddleware { @@ -63,21 +63,15 @@ where type SinkError = S::SinkError; fn start_send(&mut self, item: Self::SinkItem) -> StartSend { - let capacity = item.len() + self.hmac_key.digest_algorithm().output_len; - // Apparently this is the fastest way of doing. - // See https://gist.github.com/kirushik/e0d93759b0cd102f814408595c20a9d0 - let mut out_buffer = BytesMut::from(vec![0; capacity]); + let mut out_data = item; + // TODO if SinkError gets refactor to SecioError, + // then use try_apply_keystream + self.cipher_state.apply_keystream(&mut out_data[..]); + let signature = hmac::sign(&self.hmac_key, &out_data[..]); + out_data.extend_from_slice(signature.as_ref()); + self.raw_sink.start_send(out_data) - { - let (out_data, out_sign) = out_buffer.split_at_mut(item.len()); - self.cipher_state.process(&item, out_data); - - let signature = hmac::sign(&self.hmac_key, out_data); - out_sign.copy_from_slice(signature.as_ref()); - } - - self.raw_sink.start_send(out_buffer) } #[inline] diff --git a/secio/src/codec/mod.rs b/secio/src/codec/mod.rs index cff010754ea..feef03fd1e8 100644 --- a/secio/src/codec/mod.rs +++ b/secio/src/codec/mod.rs @@ -24,7 +24,7 @@ use self::decode::DecoderMiddleware; use self::encode::EncoderMiddleware; -use crypto::symmetriccipher::SynchronousStreamCipher; +use aes_ctr::stream_cipher::StreamCipherCore; use ring::hmac; use tokio_io::codec::length_delimited; use tokio_io::{AsyncRead, AsyncWrite}; @@ -35,6 +35,9 @@ mod encode; /// Type returned by `full_codec`. pub type FullCodec = DecoderMiddleware>>; +pub type StreamCipher = StreamCipherCore; + + /// Takes control of `socket`. Returns an object that implements `future::Sink` and /// `future::Stream`. The `Stream` and `Sink` produce and accept `BytesMut` objects. /// @@ -42,9 +45,9 @@ pub type FullCodec = DecoderMiddleware( socket: length_delimited::Framed, - cipher_encoding: Box, + cipher_encoding: Box, encoding_hmac: hmac::SigningKey, - cipher_decoder: Box, + cipher_decoder: Box, decoding_hmac: hmac::VerificationKey, ) -> FullCodec where @@ -61,12 +64,11 @@ mod tests { extern crate tokio_tcp; use self::tokio_tcp::TcpListener; use self::tokio_tcp::TcpStream; + use ::{ctr, KeySize}; use super::full_codec; use super::DecoderMiddleware; use super::EncoderMiddleware; use bytes::BytesMut; - use crypto::aessafe::AesSafe256Encryptor; - use crypto::blockmodes::CtrMode; use error::SecioError; use futures::sync::mpsc::channel; use futures::{Future, Sink, Stream}; @@ -77,6 +79,8 @@ mod tests { use std::io::Error as IoError; use tokio_io::codec::length_delimited::Framed; + const NULL_IV : [u8; 16] = [0;16]; + #[test] fn raw_encode_then_decode() { let (data_tx, data_rx) = channel::(256); @@ -86,20 +90,15 @@ mod tests { let cipher_key: [u8; 32] = rand::random(); let hmac_key: [u8; 32] = rand::random(); + let encoder = EncoderMiddleware::new( data_tx, - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key, &NULL_IV[..]), SigningKey::new(&SHA256, &hmac_key), ); let decoder = DecoderMiddleware::new( data_rx, - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key, &NULL_IV[..]), VerificationKey::new(&SHA256, &hmac_key), 32, ); @@ -133,15 +132,9 @@ mod tests { full_codec( connec, - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key, &NULL_IV[..]), SigningKey::new(&SHA256, &hmac_key), - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key, &NULL_IV[..]), VerificationKey::new(&SHA256, &hmac_key), ) }, @@ -154,15 +147,9 @@ mod tests { full_codec( stream, - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key_clone), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key_clone, &NULL_IV[..]), SigningKey::new(&SHA256, &hmac_key_clone), - Box::new(CtrMode::new( - AesSafe256Encryptor::new(&cipher_key_clone), - vec![0; 16], - )), + ctr(KeySize::KeySize256, &cipher_key_clone, &NULL_IV[..]), VerificationKey::new(&SHA256, &hmac_key_clone), ) }); diff --git a/secio/src/error.rs b/secio/src/error.rs index dd6adf31d64..a2bde175476 100644 --- a/secio/src/error.rs +++ b/secio/src/error.rs @@ -20,7 +20,7 @@ //! Defines the `SecioError` enum that groups all possible errors in SECIO. -use crypto::symmetriccipher::SymmetricCipherError; +use aes_ctr::stream_cipher::LoopError; use std::error; use std::fmt; use std::io::Error as IoError; @@ -55,8 +55,8 @@ pub enum SecioError { /// The final check of the handshake failed. NonceVerificationFailed, - /// Error while decoding/encoding data. - CipherError(SymmetricCipherError), + /// Error with block cipher. + CipherError(LoopError), /// The received frame was of invalid length. FrameTooShort, @@ -111,9 +111,9 @@ impl fmt::Display for SecioError { } } -impl From for SecioError { +impl From for SecioError { #[inline] - fn from(err: SymmetricCipherError) -> SecioError { + fn from(err: LoopError) -> SecioError { SecioError::CipherError(err) } } diff --git a/secio/src/handshake.rs b/secio/src/handshake.rs index 2165eb4b7d4..42e7bc97204 100644 --- a/secio/src/handshake.rs +++ b/secio/src/handshake.rs @@ -21,7 +21,7 @@ use algo_support; use bytes::BytesMut; use codec::{full_codec, FullCodec}; -use crypto::aes::{ctr, KeySize}; +use super::{KeySize, ctr}; use error::SecioError; use futures::future; use futures::sink::Sink; @@ -456,7 +456,6 @@ where let (cipher_key_size, iv_size) = match chosen_cipher { KeySize::KeySize128 => (16, 16), KeySize::KeySize256 => (32, 16), - _ => panic!() }; let mut longer_key = vec![0u8; 2 * (iv_size + cipher_key_size + 20)]; @@ -487,8 +486,8 @@ where (cipher, hmac) }; - Ok(full_codec(socket, Box::new(encoding_cipher), encoding_hmac, - Box::new(decoding_cipher), decoding_hmac)) + Ok(full_codec(socket, encoding_cipher, encoding_hmac, + decoding_cipher, decoding_hmac)) }); match codec { diff --git a/secio/src/lib.rs b/secio/src/lib.rs index 17c2fd2bf46..c745d7415a2 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -78,10 +78,10 @@ //! `SecioMiddleware` that implements `Sink` and `Stream` and can be used to send packets of data. //! +extern crate aes_ctr; #[cfg(feature = "secp256k1")] extern crate asn1_der; extern crate bytes; -extern crate crypto; extern crate futures; extern crate libp2p_core; #[macro_use] @@ -110,6 +110,8 @@ use std::error::Error; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::iter; use std::sync::Arc; +use aes_ctr::stream_cipher::generic_array::GenericArray; +use aes_ctr::stream_cipher::{NewFixStreamCipher, StreamCipherCore}; use tokio_io::{AsyncRead, AsyncWrite}; use untrusted::Input; @@ -119,6 +121,27 @@ mod error; mod handshake; mod structs_proto; +/// AES key size +#[derive(Clone, Copy)] +pub enum KeySize { + KeySize128, + KeySize256, +} + +pub fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + + match key_size { + KeySize::KeySize128 => Box::new(aes_ctr::Aes128Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + KeySize::KeySize256 => Box::new(aes_ctr::Aes256Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + } +} + /// Implementation of the `ConnectionUpgrade` trait of `libp2p_core`. Automatically applies /// secio on any connection. #[derive(Clone)] From d6b1a3ea61b3a78a2e61238bf141b0adc8d5da54 Mon Sep 17 00:00:00 2001 From: cheme Date: Thu, 2 Aug 2018 12:37:32 +0200 Subject: [PATCH 07/10] Support for x86 build supporting both aesni and aessoft at runtime. Feature gated behind 'aes-all'. Building requires RUSTFLAGS="-C target-feature=+aes,+ssse3" and RUSTDOCFLAGS="-C target-feature=+aes,+ssse3". Only support x86 case. --- secio/Cargo.toml | 6 +-- secio/src/algo_support.rs | 2 +- secio/src/codec/encode.rs | 10 ++--- secio/src/codec/mod.rs | 2 +- secio/src/handshake.rs | 2 +- secio/src/lib.rs | 23 +--------- secio/src/stream_cipher.rs | 87 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 secio/src/stream_cipher.rs diff --git a/secio/Cargo.toml b/secio/Cargo.toml index 7b96464a598..5d7e8eee770 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -14,17 +14,17 @@ protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } aes-ctr = "0.1.0" +aes-soft = { version = "0.2", optional = true } +ctr = { version = "0.1", optional = true } rw-stream-sink = { path = "../rw-stream-sink" } eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", optional = true } tokio-io = "0.1.0" untrusted = "0.5.1" -#[dependencies.aes-ctr] -#path = "../../../tmprrepos/stream-ciphers/aes-ctr" - [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] +aes-all = ["ctr","aes-soft"] [dev-dependencies] libp2p-tcp-transport = { path = "../tcp-transport" } diff --git a/secio/src/algo_support.rs b/secio/src/algo_support.rs index 732926bb663..8cff3725d41 100644 --- a/secio/src/algo_support.rs +++ b/secio/src/algo_support.rs @@ -28,7 +28,7 @@ macro_rules! supported_impl { pub mod $mod_name { use std::cmp::Ordering; #[allow(unused_imports)] - use KeySize; + use stream_cipher::KeySize; #[allow(unused_imports)] use ring::{agreement, digest}; use error::SecioError; diff --git a/secio/src/codec/encode.rs b/secio/src/codec/encode.rs index 6b8aa1ed693..9a01c847c48 100644 --- a/secio/src/codec/encode.rs +++ b/secio/src/codec/encode.rs @@ -64,13 +64,13 @@ where fn start_send(&mut self, item: Self::SinkItem) -> StartSend { - let mut out_data = item; + let mut data_buf = item; // TODO if SinkError gets refactor to SecioError, // then use try_apply_keystream - self.cipher_state.apply_keystream(&mut out_data[..]); - let signature = hmac::sign(&self.hmac_key, &out_data[..]); - out_data.extend_from_slice(signature.as_ref()); - self.raw_sink.start_send(out_data) + self.cipher_state.apply_keystream(&mut data_buf[..]); + let signature = hmac::sign(&self.hmac_key, &data_buf[..]); + data_buf.extend_from_slice(signature.as_ref()); + self.raw_sink.start_send(data_buf) } diff --git a/secio/src/codec/mod.rs b/secio/src/codec/mod.rs index feef03fd1e8..cbf922e57d7 100644 --- a/secio/src/codec/mod.rs +++ b/secio/src/codec/mod.rs @@ -64,7 +64,7 @@ mod tests { extern crate tokio_tcp; use self::tokio_tcp::TcpListener; use self::tokio_tcp::TcpStream; - use ::{ctr, KeySize}; + use stream_cipher::{ctr, KeySize}; use super::full_codec; use super::DecoderMiddleware; use super::EncoderMiddleware; diff --git a/secio/src/handshake.rs b/secio/src/handshake.rs index 42e7bc97204..f7c163c60c3 100644 --- a/secio/src/handshake.rs +++ b/secio/src/handshake.rs @@ -21,7 +21,7 @@ use algo_support; use bytes::BytesMut; use codec::{full_codec, FullCodec}; -use super::{KeySize, ctr}; +use stream_cipher::{KeySize, ctr}; use error::SecioError; use futures::future; use futures::sink::Sink; diff --git a/secio/src/lib.rs b/secio/src/lib.rs index c745d7415a2..a902c2aaaa0 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -110,8 +110,6 @@ use std::error::Error; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::iter; use std::sync::Arc; -use aes_ctr::stream_cipher::generic_array::GenericArray; -use aes_ctr::stream_cipher::{NewFixStreamCipher, StreamCipherCore}; use tokio_io::{AsyncRead, AsyncWrite}; use untrusted::Input; @@ -120,27 +118,8 @@ mod codec; mod error; mod handshake; mod structs_proto; +mod stream_cipher; -/// AES key size -#[derive(Clone, Copy)] -pub enum KeySize { - KeySize128, - KeySize256, -} - -pub fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { - - match key_size { - KeySize::KeySize128 => Box::new(aes_ctr::Aes128Ctr::new( - GenericArray::from_slice(key), - GenericArray::from_slice(iv), - )), - KeySize::KeySize256 => Box::new(aes_ctr::Aes256Ctr::new( - GenericArray::from_slice(key), - GenericArray::from_slice(iv), - )), - } -} /// Implementation of the `ConnectionUpgrade` trait of `libp2p_core`. Automatically applies /// secio on any connection. diff --git a/secio/src/stream_cipher.rs b/secio/src/stream_cipher.rs new file mode 100644 index 00000000000..24bb9e26c71 --- /dev/null +++ b/secio/src/stream_cipher.rs @@ -0,0 +1,87 @@ + +use aes_ctr::stream_cipher::generic_array::GenericArray; +use aes_ctr::stream_cipher::{NewFixStreamCipher, StreamCipherCore}; +use aes_ctr::{Aes128Ctr, Aes256Ctr}; + +#[derive(Clone, Copy)] +pub enum KeySize { + KeySize128, + KeySize256, +} + +/// Returns your stream cipher depending on `KeySize`. +#[cfg(not(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86"))))] +pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + ctr_int(key_size, key, iv) +} + +/// Returns your stream cipher depending on `KeySize`. +#[cfg(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86")))] +pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + if is_x86_feature_detected!("aes") + && is_x86_feature_detected!("sse2") + && is_x86_feature_detected!("sse3") { + ctr_int(key_size, key, iv) + } else { + aes_alt::ctr_alt(key_size, key, iv) + } +} + + +#[cfg(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86")))] +mod aes_alt { + extern crate ctr; + extern crate aes_soft; + use self::ctr::Ctr128; + use self::aes_soft::{Aes128, Aes256}; + use self::ctr::stream_cipher::{NewFixStreamCipher, StreamCipherCore}; + use self::ctr::stream_cipher::generic_array::GenericArray; + use super::KeySize; + + /// AES-128 in CTR mode + pub type Aes128Ctr = Ctr128; + /// AES-256 in CTR mode + pub type Aes256Ctr = Ctr128; + /// Returns alternate stream cipher if target functionalities does not allow standard one. + /// Eg : aes without sse + pub fn ctr_alt(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + match key_size { + KeySize::KeySize128 => Box::new(Aes128Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + KeySize::KeySize256 => Box::new(Aes256Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + } + } + +} + +#[inline] +fn ctr_int(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { + match key_size { + KeySize::KeySize128 => Box::new(Aes128Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + KeySize::KeySize256 => Box::new(Aes256Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + } +} + + +// aesni compile check for aes-all (aes-all import aesni through aes_ctr only if those checks pass) +#[cfg(all( + feature = "aes-all", + any(target_arch = "x86_64", target_arch = "x86"), + not(all(target_feature = "aes", target_feature = "ssse3")), +))] +compile_error!( + "enable aes and ssse3 target features if using aes-all to build, e.g. with \ + RUSTFLAGS=\"-C target-feature=+aes,+ssse3\" enviromental variable. \ + For x86 target arch additionally enable sse2 target feature." +); From 7c8f52e415fa6b17522d561cc03010acef63c535 Mon Sep 17 00:00:00 2001 From: cheme Date: Mon, 6 Aug 2018 11:57:38 +0200 Subject: [PATCH 08/10] Change CI to build with aes ni on all-features --- .circleci/config.yml | 2 +- secio/Cargo.toml | 3 ++- secio/src/codec/mod.rs | 2 +- secio/src/lib.rs | 3 +++ secio/src/stream_cipher.rs | 29 +++++++++++++++++++++++++---- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7fc1b9aa612..0c42490fa22 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,7 +37,7 @@ jobs: command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p cargo test --no-default-features - run: name: Run tests, inside a docker image, with all features - command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p cargo test --all-features + command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p sh -c 'RUSTFLAGS="-C target-feature=+aes,+ssse3,+sse2" RUSTDOCFLAGS="-C target-feature=+aes,+ssse3,+sse2" cargo test --all-features' - save_cache: key: test-cache paths: diff --git a/secio/Cargo.toml b/secio/Cargo.toml index 5d7e8eee770..28eb54283d5 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -16,6 +16,7 @@ ring = { version = "0.12.1", features = ["rsa_signing"] } aes-ctr = "0.1.0" aes-soft = { version = "0.2", optional = true } ctr = { version = "0.1", optional = true } +lazy_static = { version = "0.2.11", optional = true } rw-stream-sink = { path = "../rw-stream-sink" } eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", optional = true } tokio-io = "0.1.0" @@ -24,7 +25,7 @@ untrusted = "0.5.1" [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] -aes-all = ["ctr","aes-soft"] +aes-all = ["ctr","aes-soft","lazy_static"] [dev-dependencies] libp2p-tcp-transport = { path = "../tcp-transport" } diff --git a/secio/src/codec/mod.rs b/secio/src/codec/mod.rs index cbf922e57d7..637659e74f2 100644 --- a/secio/src/codec/mod.rs +++ b/secio/src/codec/mod.rs @@ -111,7 +111,7 @@ mod tests { let (_, decoded) = tokio_current_thread::block_on_all(data_sent.join(data_received)) .map_err(|_| ()) .unwrap(); - assert_eq!(decoded.unwrap(), data); + assert_eq!(&decoded.unwrap()[..], &data[..]); } #[test] diff --git a/secio/src/lib.rs b/secio/src/lib.rs index a902c2aaaa0..6ebfde4c2e0 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -95,6 +95,9 @@ extern crate secp256k1; extern crate tokio_io; extern crate untrusted; +#[cfg(feature = "aes-all")] +#[macro_use] +extern crate lazy_static; pub use self::error::SecioError; #[cfg(feature = "secp256k1")] diff --git a/secio/src/stream_cipher.rs b/secio/src/stream_cipher.rs index 24bb9e26c71..a77d0adaf6c 100644 --- a/secio/src/stream_cipher.rs +++ b/secio/src/stream_cipher.rs @@ -14,13 +14,11 @@ pub enum KeySize { pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { ctr_int(key_size, key, iv) } - + /// Returns your stream cipher depending on `KeySize`. #[cfg(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86")))] pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box { - if is_x86_feature_detected!("aes") - && is_x86_feature_detected!("sse2") - && is_x86_feature_detected!("sse3") { + if *aes_alt::AES_NI { ctr_int(key_size, key, iv) } else { aes_alt::ctr_alt(key_size, key, iv) @@ -38,6 +36,13 @@ mod aes_alt { use self::ctr::stream_cipher::generic_array::GenericArray; use super::KeySize; + lazy_static! { + pub static ref AES_NI: bool = is_x86_feature_detected!("aes") + && is_x86_feature_detected!("sse2") + && is_x86_feature_detected!("sse3"); + + } + /// AES-128 in CTR mode pub type Aes128Ctr = Ctr128; /// AES-256 in CTR mode @@ -73,6 +78,22 @@ fn ctr_int(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box Date: Tue, 7 Aug 2018 09:52:09 +0200 Subject: [PATCH 09/10] Switch aes-all logic (is_x86 runtime test did not work as I thought when RUSTC aes and sse are enabled). To build with aes-all, aes and sse flag should not be set. --- .circleci/config.yml | 2 +- secio/Cargo.toml | 4 ++-- secio/src/lib.rs | 1 - secio/src/stream_cipher.rs | 13 +++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c42490fa22..7fc1b9aa612 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,7 +37,7 @@ jobs: command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p cargo test --no-default-features - run: name: Run tests, inside a docker image, with all features - command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p sh -c 'RUSTFLAGS="-C target-feature=+aes,+ssse3,+sse2" RUSTDOCFLAGS="-C target-feature=+aes,+ssse3,+sse2" cargo test --all-features' + command: docker run --rm -v "/cache/cargo/registry:/usr/local/cargo/registry" -v "/cache/target:/app/target" -it rust-libp2p cargo test --all-features - save_cache: key: test-cache paths: diff --git a/secio/Cargo.toml b/secio/Cargo.toml index 28eb54283d5..aac2b99f8e5 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -14,7 +14,7 @@ protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } aes-ctr = "0.1.0" -aes-soft = { version = "0.2", optional = true } +aesni = { git="https://github.com/cheme/block-ciphers.git", features = ["nocheck"], optional = true } ctr = { version = "0.1", optional = true } lazy_static = { version = "0.2.11", optional = true } rw-stream-sink = { path = "../rw-stream-sink" } @@ -25,7 +25,7 @@ untrusted = "0.5.1" [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] -aes-all = ["ctr","aes-soft","lazy_static"] +aes-all = ["ctr","aesni","lazy_static"] [dev-dependencies] libp2p-tcp-transport = { path = "../tcp-transport" } diff --git a/secio/src/lib.rs b/secio/src/lib.rs index 6ebfde4c2e0..5476e5e9c20 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -123,7 +123,6 @@ mod handshake; mod structs_proto; mod stream_cipher; - /// Implementation of the `ConnectionUpgrade` trait of `libp2p_core`. Automatically applies /// secio on any connection. #[derive(Clone)] diff --git a/secio/src/stream_cipher.rs b/secio/src/stream_cipher.rs index a77d0adaf6c..fdcf782bce5 100644 --- a/secio/src/stream_cipher.rs +++ b/secio/src/stream_cipher.rs @@ -19,9 +19,9 @@ pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box Box { if *aes_alt::AES_NI { - ctr_int(key_size, key, iv) - } else { aes_alt::ctr_alt(key_size, key, iv) + } else { + ctr_int(key_size, key, iv) } } @@ -29,9 +29,9 @@ pub(crate) fn ctr(key_size: KeySize, key: &[u8], iv: &[u8]) -> Box Date: Thu, 9 Aug 2018 16:21:45 +0200 Subject: [PATCH 10/10] Use latest aesni from crates.io (with pr to disable compile time checks). --- secio/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secio/Cargo.toml b/secio/Cargo.toml index aac2b99f8e5..16040d36656 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -14,7 +14,7 @@ protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } aes-ctr = "0.1.0" -aesni = { git="https://github.com/cheme/block-ciphers.git", features = ["nocheck"], optional = true } +aesni = { version = "0.4.1", features = ["nocheck"], optional = true } ctr = { version = "0.1", optional = true } lazy_static = { version = "0.2.11", optional = true } rw-stream-sink = { path = "../rw-stream-sink" }