diff --git a/protocols/secio/Cargo.toml b/protocols/secio/Cargo.toml index ca90e7c7aa1..4c89caaab8d 100644 --- a/protocols/secio/Cargo.toml +++ b/protocols/secio/Cargo.toml @@ -13,7 +13,11 @@ log = "0.4.1" protobuf = "2.0.2" rand = "0.3.17" ring = { version = "0.12", features = ["rsa_signing"] } -rust-crypto = "^0.2" +aes-ctr = "0.1.0" +aesni = { version = "0.4.1", features = ["nocheck"], optional = true } +twofish = "0.1.0" +ctr = "0.1" +lazy_static = { version = "0.2.11", optional = true } rw-stream-sink = { path = "../../misc/rw-stream-sink" } eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", optional = true } tokio-io = "0.1.0" @@ -22,6 +26,7 @@ untrusted = "0.5" [features] default = ["secp256k1"] secp256k1 = ["eth-secp256k1"] +aes-all = ["aesni","lazy_static"] [dev-dependencies] libp2p-tcp-transport = { path = "../../transports/tcp" } diff --git a/protocols/secio/src/algo_support.rs b/protocols/secio/src/algo_support.rs index 9c50c2ce37c..86e0b27a1a4 100644 --- a/protocols/secio/src/algo_support.rs +++ b/protocols/secio/src/algo_support.rs @@ -33,13 +33,14 @@ const ECDH_P384: &str = "P-384"; const AES_128: &str = "AES-128"; const AES_256: &str = "AES-256"; +const TWOFISH_CTR: &str = "TwofishCTR"; 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_CIPHERS_PROPOSITION: &str = "AES-128,AES-256,TwofishCTR"; pub(crate) const DEFAULT_DIGESTS_PROPOSITION: &str = "SHA256,SHA512"; @@ -110,6 +111,10 @@ where s.push_str(AES_256); s.push(',') } + Cipher::TwofishCtr => { + s.push_str(TWOFISH_CTR); + s.push(',') + } Cipher::Null => { s.push_str(NULL); s.push(',') @@ -134,6 +139,7 @@ pub fn select_cipher(r: Ordering, ours: &str, theirs: &str) -> Result return Ok(Cipher::Aes128), AES_256 => return Ok(Cipher::Aes256), + TWOFISH_CTR => return Ok(Cipher::TwofishCtr), NULL => return Ok(Cipher::Null), _ => continue } diff --git a/protocols/secio/src/codec/decode.rs b/protocols/secio/src/codec/decode.rs index d32bfe5023f..084400224c5 100644 --- a/protocols/secio/src/codec/decode.rs +++ b/protocols/secio/src/codec/decode.rs @@ -21,7 +21,8 @@ //! Individual messages decoding. use bytes::BytesMut; -use codec::StreamCipher; +use super::StreamCipher; + use error::SecioError; use futures::sink::Sink; use futures::stream::Stream; @@ -87,21 +88,24 @@ where debug!("frame too short when decoding secio frame"); return Err(SecioError::FrameTooShort); } + 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); - 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); + 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/protocols/secio/src/codec/encode.rs b/protocols/secio/src/codec/encode.rs index 103a736d8de..f163ea68d65 100644 --- a/protocols/secio/src/codec/encode.rs +++ b/protocols/secio/src/codec/encode.rs @@ -21,7 +21,7 @@ //! Individual messages encoding. use bytes::BytesMut; -use codec::StreamCipher; +use super::StreamCipher; use futures::sink::Sink; use futures::stream::Stream; use futures::Poll; @@ -62,22 +62,15 @@ where type SinkItem = BytesMut; type SinkError = S::SinkError; - fn start_send(&mut self, item: Self::SinkItem) -> StartSend { - let capacity = item.len() + self.hmac_key.digest_algorithm().output_len; + fn start_send(&mut self, mut data_buf: Self::SinkItem) -> StartSend { - // Apparently this is the fastest way of doing. - // See https://gist.github.com/kirushik/e0d93759b0cd102f814408595c20a9d0 - let mut out_buffer = BytesMut::from(vec![0; capacity]); + // TODO if SinkError gets refactor to SecioError, + // then use try_apply_keystream + 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) - { - 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/protocols/secio/src/codec/mod.rs b/protocols/secio/src/codec/mod.rs index 3bea5cfd028..813be032833 100644 --- a/protocols/secio/src/codec/mod.rs +++ b/protocols/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,7 +35,7 @@ mod encode; /// Type returned by `full_codec`. pub type FullCodec = DecoderMiddleware>>; -pub type StreamCipher = Box; +pub type StreamCipher = Box; /// Takes control of `socket`. Returns an object that implements `future::Sink` and @@ -79,7 +79,7 @@ mod tests { use std::io::Error as IoError; use tokio_io::codec::length_delimited::Framed; - const NULL_IV : [u8; 16] = [0; 16]; + const NULL_IV : [u8; 16] = [0;16]; #[test] fn raw_encode_then_decode() { @@ -90,6 +90,7 @@ mod tests { let cipher_key: [u8; 32] = rand::random(); let hmac_key: [u8; 32] = rand::random(); + let encoder = EncoderMiddleware::new( data_tx, ctr(Cipher::Aes256, &cipher_key, &NULL_IV[..]), @@ -110,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[..]); } fn full_codec_encode_then_decode(cipher: Cipher) { @@ -179,6 +180,11 @@ mod tests { full_codec_encode_then_decode(Cipher::Aes256); } + #[test] + fn full_codec_encode_then_decode_twofish() { + full_codec_encode_then_decode(Cipher::TwofishCtr); + } + #[test] fn full_codec_encode_then_decode_null() { full_codec_encode_then_decode(Cipher::Null); diff --git a/protocols/secio/src/error.rs b/protocols/secio/src/error.rs index 7de3a4a1a33..c2cfc51ae50 100644 --- a/protocols/secio/src/error.rs +++ b/protocols/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, @@ -115,9 +115,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/protocols/secio/src/lib.rs b/protocols/secio/src/lib.rs index a5e303a2e0c..3bf83e3aebb 100644 --- a/protocols/secio/src/lib.rs +++ b/protocols/secio/src/lib.rs @@ -77,10 +77,11 @@ //! `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 ctr; extern crate futures; extern crate libp2p_core; #[macro_use] @@ -92,8 +93,12 @@ extern crate rw_stream_sink; #[cfg(feature = "secp256k1")] extern crate secp256k1; extern crate tokio_io; +extern crate twofish; extern crate untrusted; +#[cfg(feature = "aes-all")] +#[macro_use] +extern crate lazy_static; pub use self::error::SecioError; #[cfg(feature = "secp256k1")] @@ -116,8 +121,8 @@ mod algo_support; mod codec; mod error; mod handshake; -mod stream_cipher; mod structs_proto; +mod stream_cipher; pub use algo_support::{Digest, KeyAgreement}; pub use stream_cipher::Cipher; diff --git a/protocols/secio/src/stream_cipher.rs b/protocols/secio/src/stream_cipher.rs index ae92a3f96bf..6f2db54d572 100644 --- a/protocols/secio/src/stream_cipher.rs +++ b/protocols/secio/src/stream_cipher.rs @@ -19,14 +19,19 @@ // DEALINGS IN THE SOFTWARE. use super::codec::StreamCipher; -use crypto::{aessafe, blockmodes::CtrModeX8, symmetriccipher::SynchronousStreamCipher}; +use aes_ctr::stream_cipher::generic_array::GenericArray; +use aes_ctr::stream_cipher::{NewFixStreamCipher, LoopError, StreamCipherCore}; +use aes_ctr::{Aes128Ctr, Aes256Ctr}; +use ctr::Ctr128; +use twofish::Twofish; /// Possible encryption ciphers. #[derive(Clone, Copy, Debug)] pub enum Cipher { Aes128, Aes256, - Null + TwofishCtr, + Null, } impl Cipher { @@ -35,7 +40,8 @@ impl Cipher { match *self { Cipher::Aes128 => 16, Cipher::Aes256 => 32, - Cipher::Null => 0 + Cipher::TwofishCtr => 32, + Cipher::Null => 0, } } @@ -43,7 +49,7 @@ impl Cipher { #[inline] pub fn iv_size(&self) -> usize { match self { - Cipher::Aes128 | Cipher::Aes256 => 16, + Cipher::Aes128 | Cipher::Aes256 | Cipher::TwofishCtr => 16, Cipher::Null => 0 } } @@ -54,25 +60,123 @@ impl Cipher { #[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) +impl StreamCipherCore for NullCipher { + fn try_apply_keystream(&mut self, _data: &mut [u8]) -> Result<(), LoopError> { + Ok(()) } } /// Returns your stream cipher depending on `Cipher`. +#[cfg(not(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86"))))] +pub fn ctr(key_size: Cipher, key: &[u8], iv: &[u8]) -> StreamCipher { + ctr_int(key_size, key, iv) +} + +/// Returns your stream cipher depending on `Cipher`. +#[cfg(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86")))] +pub fn ctr(key_size: Cipher, key: &[u8], iv: &[u8]) -> StreamCipher { + if *aes_alt::AES_NI { + aes_alt::ctr_alt(key_size, key, iv) + } else { + ctr_int(key_size, key, iv) + } +} + + +#[cfg(all(feature = "aes-all", any(target_arch = "x86_64", target_arch = "x86")))] +mod aes_alt { + extern crate aesni; + use ::codec::StreamCipher; + use ctr::Ctr128; + use self::aesni::{Aes128, Aes256}; + use ctr::stream_cipher::NewFixStreamCipher; + use ctr::stream_cipher::generic_array::GenericArray; + use twofish::Twofish; + use super::{Cipher, NullCipher}; + + 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 + 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: Cipher, key: &[u8], iv: &[u8]) -> StreamCipher { + match key_size { + Cipher::Aes128 => Box::new(Aes128Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + Cipher::Aes256 => Box::new(Aes256Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + Cipher::TwofishCtr => Box::new(Ctr128::::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + Cipher::Null => Box::new(NullCipher), + } + } + +} + #[inline] -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)) - }, - Cipher::Aes256 => { - let aes_dec = aessafe::AesSafe256EncryptorX8::new(key); - Box::new(CtrModeX8::new(aes_dec, iv)) - }, - Cipher::Null => Box::new(NullCipher) +fn ctr_int(key_size: Cipher, key: &[u8], iv: &[u8]) -> StreamCipher { + match key_size { + Cipher::Aes128 => Box::new(Aes128Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + Cipher::Aes256 => Box::new(Aes256Ctr::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + Cipher::TwofishCtr => Box::new(Ctr128::::new( + GenericArray::from_slice(key), + GenericArray::from_slice(iv), + )), + Cipher::Null => Box::new(NullCipher), + } +} + +#[cfg(all( + feature = "aes-all", + any(target_arch = "x86_64", target_arch = "x86"), +))] +#[cfg(test)] +mod tests { + use super::{Cipher, ctr}; + + #[test] + fn assert_non_native_run() { + // this test is for asserting aes unsuported opcode does not break on old cpu + let key = [0;16]; + let iv = [0;16]; + + let mut aes = ctr(Cipher::Aes128, &key, &iv); + let mut content = [0;16]; + assert!(aes + .try_apply_keystream(&mut content).is_ok()); + } } +// 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"), + any(target_feature = "aes", target_feature = "ssse3"), +))] +compile_error!( + "aes-all must be compile without aes and sse3 flags : currently \ + is_x86_feature_detected macro will not detect feature correctly otherwhise. \ + RUSTFLAGS=\"-C target-feature=+aes,+ssse3\" enviromental variable. \ + For x86 target arch additionally enable sse2 target feature." +);