diff --git a/CHANGELOG.md b/CHANGELOG.md index d6def015..ed746cc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ - [`libp2p-plaintext` CHANGELOG](protocols/plaintext/CHANGELOG.md) - [`libp2p-pnet` CHANGELOG](protocols/pnet/CHANGELOG.md) - [`libp2p-request-response` CHANGELOG](protocols/request-response/CHANGELOG.md) -- [`libp2p-secio` CHANGELOG](protocols/secio/CHANGELOG.md) - [`libp2p-swarm` CHANGELOG](swarm/CHANGELOG.md) - [`libp2p-tcp` CHANGELOG](transports/tcp/CHANGELOG.md) - [`libp2p-uds` CHANGELOG](transports/uds/CHANGELOG.md) diff --git a/Cargo.toml b/Cargo.toml index 29a17956..42418746 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,7 +114,6 @@ members = [ "protocols/plaintext", "protocols/pnet", "protocols/request-response", - "protocols/secio", "swarm", "swarm-derive", "transports/dns", diff --git a/protocols/noise/src/protocol/x25519.rs b/protocols/noise/src/protocol/x25519.rs index 80bba174..389c5b94 100644 --- a/protocols/noise/src/protocol/x25519.rs +++ b/protocols/noise/src/protocol/x25519.rs @@ -158,8 +158,7 @@ impl Keypair { /// Returns `None` if the given identity keypair cannot be used as an X25519 keypair. /// /// > **Note**: If the identity keypair is already used in the context - /// > of other cryptographic protocols outside of Noise, e.g. for - /// > signing in the `secio` protocol, it should be preferred to + /// > of other cryptographic protocols outside of Noise, it should be preferred to /// > create a new static X25519 keypair for use in the Noise protocol. /// > /// > See also: @@ -205,9 +204,8 @@ impl SecretKey { /// Construct a X25519 secret key from a Ed25519 secret key. /// /// > **Note**: If the Ed25519 secret key is already used in the context - /// > of other cryptographic protocols outside of Noise, e.g. for - /// > signing in the `secio` protocol, it should be preferred to - /// > create a new keypair for use in the Noise protocol. + /// > of other cryptographic protocols outside of Noise, it should be preferred + /// > to create a new keypair for use in the Noise protocol. /// > /// > See also: /// > diff --git a/protocols/secio/CHANGELOG.md b/protocols/secio/CHANGELOG.md deleted file mode 100644 index c3ead659..00000000 --- a/protocols/secio/CHANGELOG.md +++ /dev/null @@ -1,40 +0,0 @@ -# 0.27.0 [unreleased] - -- Update dependencies. - -# 0.26.0 [2020-12-17] - -- Update `libp2p-core`. - -# 0.25.0 [2020-11-25] - -- Update `libp2p-core`. - -# 0.24.0 [2020-11-09] - -- Update dependencies. - -# 0.23.0 [2020-10-16] - -- Update dependencies. - -# 0.22.0 [2020-09-09] - -- As of this release, SECIO is deprecated. Please use `libp2p-noise` instead. - For some more context, [see here](https://blog.ipfs.io/2020-08-07-deprecating-secio/). - -- Bump `libp2p-core` dependency. - -# 0.21.0 [2020-08-18] - -- Bump `libp2p-core` dependency. - -# 0.20.0 [2020-07-01] - -- Updated dependencies. -- Conditional compilation fixes for the `wasm32-wasi` target - ([PR 1633](https://github.com/libp2p/rust-libp2p/pull/1633)). - -# 0.19.2 [2020-06-22] - -- Updated dependencies. diff --git a/protocols/secio/Cargo.toml b/protocols/secio/Cargo.toml deleted file mode 100644 index bc5fcd5f..00000000 --- a/protocols/secio/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -[package] -name = "libp2p-secio" -edition = "2018" -description = "Secio encryption protocol for libp2p" -version = "0.27.0" -authors = ["Parity Technologies "] -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[badges] -maintenance = { status = "deprecated" } - -[dependencies] -aes-ctr = "0.3" -aesni = { version = "0.6", features = ["nocheck"], optional = true } -ctr = "0.3" -futures = "0.3.1" -hmac = "0.9.0" -lazy_static = "1.2.0" -libp2p-core = { version = "0.27.0", path = "../../core" } -log = "0.4.6" -prost = "0.6.1" -pin-project = "1.0.0" -quicksink = "0.1" -rand = "0.7" -rw-stream-sink = "0.2.0" -sha2 = "0.9.1" -static_assertions = "1" -twofish = "0.2.0" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -ring = { version = "0.16.9", features = ["alloc"], default-features = false } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -js-sys = "0.3.10" -parity-send-wrapper = "0.1" -wasm-bindgen = "0.2.33" -wasm-bindgen-futures = "0.4.5" -web-sys = { version = "0.3.10", features = ["Crypto", "CryptoKey", "SubtleCrypto", "Window"] } - -[build-dependencies] -prost-build = "0.6" - -[features] -default = ["secp256k1"] -secp256k1 = [] -aes-all = ["aesni"] - -[dev-dependencies] -async-std = "1.6.2" -criterion = "0.3" -libp2p-mplex = { path = "../../muxers/mplex" } -libp2p-tcp = { path = "../../transports/tcp" } diff --git a/protocols/secio/build.rs b/protocols/secio/build.rs deleted file mode 100644 index 1b0feff6..00000000 --- a/protocols/secio/build.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -fn main() { - prost_build::compile_protos(&["src/structs.proto"], &["src"]).unwrap(); -} - diff --git a/protocols/secio/src/algo_support.rs b/protocols/secio/src/algo_support.rs deleted file mode 100644 index 5e6d5e46..00000000 --- a/protocols/secio/src/algo_support.rs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! This module contains some utilities for algorithm support exchange. -//! -//! One important part of the SECIO handshake is negotiating algorithms. This is what this module -//! helps you with. - -use crate::error::SecioError; -#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))] -use ring::digest; -use std::cmp::Ordering; -use crate::stream_cipher::Cipher; -use crate::KeyAgreement; - -const ECDH_P256: &str = "P-256"; -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,TwofishCTR"; -pub(crate) const DEFAULT_DIGESTS_PROPOSITION: &str = "SHA256,SHA512"; - -/// Return a proposition string from the given sequence of `KeyAgreement` values. -pub fn key_agreements_proposition<'a, I>(xchgs: I) -> String -where - I: IntoIterator -{ - let mut s = String::new(); - for x in xchgs { - match x { - KeyAgreement::EcdhP256 => { - s.push_str(ECDH_P256); - s.push(',') - } - KeyAgreement::EcdhP384 => { - s.push_str(ECDH_P384); - s.push(',') - } - } - } - s.pop(); // remove trailing comma if any - s -} - -/// 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(r: Ordering, ours: &str, theirs: &str) -> Result { - let (a, b) = match r { - Ordering::Less | Ordering::Equal => (theirs, ours), - Ordering::Greater => (ours, theirs) - }; - for x in a.split(',') { - if b.split(',').any(|y| x == y) { - match x { - ECDH_P256 => return Ok(KeyAgreement::EcdhP256), - ECDH_P384 => return Ok(KeyAgreement::EcdhP384), - _ => continue - } - } - } - Err(SecioError::NoSupportIntersection) -} - - -/// Return a proposition string from the given sequence of `Cipher` values. -pub fn ciphers_proposition<'a, I>(ciphers: I) -> String -where - I: IntoIterator -{ - 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::TwofishCtr => { - s.push_str(TWOFISH_CTR); - s.push(',') - } - Cipher::Null => { - s.push_str(NULL); - s.push(',') - } - } - } - s.pop(); // remove trailing comma if any - s -} - -/// Given two cipher proposition strings try to figure out a match. -/// -/// The `Ordering` parameter determines which argument is preferred. If `Less` or `Equal` we -/// try for each of `theirs` every one of `ours`, for `Greater` it's the other way around. -pub fn select_cipher(r: Ordering, ours: &str, theirs: &str) -> Result { - 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), - TWOFISH_CTR => return Ok(Cipher::TwofishCtr), - NULL => return Ok(Cipher::Null), - _ => continue - } - } - } - Err(SecioError::NoSupportIntersection) -} - - -/// Possible digest algorithms. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Digest { - Sha256, - Sha512 -} - -impl Digest { - /// Returns the size in bytes of a digest of this kind. - #[inline] - pub fn num_bytes(&self) -> usize { - match *self { - Digest::Sha256 => 256 / 8, - Digest::Sha512 => 512 / 8, - } - } -} - -/// Return a proposition string from the given sequence of `Digest` values. -pub fn digests_proposition<'a, I>(digests: I) -> String -where - I: IntoIterator -{ - 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(r: Ordering, ours: &str, theirs: &str) -> Result { - 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) -} - -#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))] -impl Into<&'static digest::Algorithm> for Digest { - #[inline] - fn into(self) -> &'static digest::Algorithm { - match self { - Digest::Sha256 => &digest::SHA256, - Digest::Sha512 => &digest::SHA512, - } - } -} - -#[cfg(test)] -mod tests { - #[test] - fn cipher_non_null() { - // This test serves as a safe-guard against accidentally pushing to master a commit that - // sets this constant to `NULL`. - assert!(!super::DEFAULT_CIPHERS_PROPOSITION.contains("NULL")); - } -} diff --git a/protocols/secio/src/codec.rs b/protocols/secio/src/codec.rs deleted file mode 100644 index 6a3bfb19..00000000 --- a/protocols/secio/src/codec.rs +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Individual messages encoding and decoding. Use this after the algorithms have been -//! successfully negotiated. - -mod decode; -mod encode; -mod len_prefix; - -use aes_ctr::stream_cipher; -use crate::algo_support::Digest; -use decode::DecoderMiddleware; -use encode::EncoderMiddleware; -use futures::prelude::*; -use hmac::{self, Mac, NewMac}; -use sha2::{Sha256, Sha512}; - -pub use len_prefix::LenPrefixCodec; - -/// Type returned by `full_codec`. -pub type FullCodec = DecoderMiddleware>>; - -pub type StreamCipher = Box; - -#[derive(Debug, Clone)] -pub enum Hmac { - Sha256(hmac::Hmac), - Sha512(hmac::Hmac), -} - -impl Hmac { - /// Returns the size of the hash in bytes. - #[inline] - pub fn num_bytes(&self) -> usize { - match *self { - Hmac::Sha256(_) => 32, - Hmac::Sha512(_) => 64, - } - } - - /// Builds a `Hmac` from an algorithm and key. - pub fn from_key(algorithm: Digest, key: &[u8]) -> Self { - // TODO: it would be nice to tweak the hmac crate to add an equivalent to new_varkey that - // never errors - match algorithm { - Digest::Sha256 => Hmac::Sha256(hmac::Hmac::new_varkey(key) - .expect("Hmac::new_varkey accepts any key length")), - Digest::Sha512 => Hmac::Sha512(hmac::Hmac::new_varkey(key) - .expect("Hmac::new_varkey accepts any key length")), - } - } - - /// Signs the data. - // TODO: better return type? - pub fn sign(&self, crypted_data: &[u8]) -> Vec { - match *self { - Hmac::Sha256(ref hmac) => { - let mut hmac = hmac.clone(); - hmac.update(crypted_data); - hmac.finalize().into_bytes().to_vec() - }, - Hmac::Sha512(ref hmac) => { - let mut hmac = hmac.clone(); - hmac.update(crypted_data); - hmac.finalize().into_bytes().to_vec() - }, - } - } - - /// Verifies that the data matches the expected hash. - // TODO: better error? - pub fn verify(&self, crypted_data: &[u8], expected_hash: &[u8]) -> Result<(), ()> { - match *self { - Hmac::Sha256(ref hmac) => { - let mut hmac = hmac.clone(); - hmac.update(crypted_data); - hmac.verify(expected_hash).map_err(|_| ()) - }, - Hmac::Sha512(ref hmac) => { - let mut hmac = hmac.clone(); - hmac.update(crypted_data); - hmac.verify(expected_hash).map_err(|_| ()) - }, - } - } -} - -/// Takes control of `socket`. Returns an object that implements `future::Sink` and -/// `future::Stream`. The `Stream` and `Sink` produce and accept `Vec` objects. -/// -/// The conversion between the stream/sink items and the socket is done with the given cipher and -/// hash algorithm (which are generally decided during the handshake). -pub fn full_codec( - socket: LenPrefixCodec, - cipher_encoding: StreamCipher, - encoding_hmac: Hmac, - cipher_decoder: StreamCipher, - decoding_hmac: Hmac, - remote_nonce: Vec -) -> FullCodec -where - S: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - let encoder = EncoderMiddleware::new(socket, cipher_encoding, encoding_hmac); - DecoderMiddleware::new(encoder, cipher_decoder, decoding_hmac, remote_nonce) -} - - -#[cfg(test)] -mod tests { - use super::{full_codec, DecoderMiddleware, EncoderMiddleware, Hmac, LenPrefixCodec}; - use crate::algo_support::Digest; - use crate::stream_cipher::{ctr, Cipher}; - use crate::error::SecioError; - use async_std::net::{TcpListener, TcpStream}; - use futures::{prelude::*, channel::mpsc, channel::oneshot}; - - const NULL_IV : [u8; 16] = [0; 16]; - - #[test] - fn raw_encode_then_decode() { - let (data_tx, data_rx) = mpsc::channel::>(256); - - let cipher_key: [u8; 32] = rand::random(); - let hmac_key: [u8; 32] = rand::random(); - - let mut encoder = EncoderMiddleware::new( - data_tx, - ctr(Cipher::Aes256, &cipher_key, &NULL_IV[..]), - Hmac::from_key(Digest::Sha256, &hmac_key), - ); - - let mut decoder = DecoderMiddleware::new( - data_rx.map(|v| Ok::<_, SecioError>(v)), - ctr(Cipher::Aes256, &cipher_key, &NULL_IV[..]), - Hmac::from_key(Digest::Sha256, &hmac_key), - Vec::new() - ); - - let data = b"hello world"; - async_std::task::block_on(async move { - encoder.send(data.to_vec()).await.unwrap(); - let rx = decoder.next().await.unwrap().unwrap(); - assert_eq!(rx, data); - }); - } - - fn full_codec_encode_then_decode(cipher: Cipher) { - let cipher_key: [u8; 32] = rand::random(); - let cipher_key_clone = cipher_key.clone(); - let key_size = cipher.key_size(); - let hmac_key: [u8; 16] = rand::random(); - let hmac_key_clone = hmac_key.clone(); - let data = b"hello world"; - let data_clone = data.clone(); - let nonce = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let (l_a_tx, l_a_rx) = oneshot::channel(); - - let nonce2 = nonce.clone(); - let server = async { - let listener = TcpListener::bind(&"127.0.0.1:0").await.unwrap(); - let listener_addr = listener.local_addr().unwrap(); - l_a_tx.send(listener_addr).unwrap(); - - let (connec, _) = listener.accept().await.unwrap(); - let codec = full_codec( - LenPrefixCodec::new(connec, 1024), - ctr(cipher, &cipher_key[..key_size], &NULL_IV[..]), - Hmac::from_key(Digest::Sha256, &hmac_key), - ctr(cipher, &cipher_key[..key_size], &NULL_IV[..]), - Hmac::from_key(Digest::Sha256, &hmac_key), - nonce2.clone() - ); - - let outcome = codec.map(|v| v.unwrap()).concat().await; - assert_eq!(outcome, data_clone); - }; - - let client = async { - let listener_addr = l_a_rx.await.unwrap(); - let stream = TcpStream::connect(&listener_addr).await.unwrap(); - let mut codec = full_codec( - LenPrefixCodec::new(stream, 1024), - ctr(cipher, &cipher_key_clone[..key_size], &NULL_IV[..]), - Hmac::from_key(Digest::Sha256, &hmac_key_clone), - ctr(cipher, &cipher_key_clone[..key_size], &NULL_IV[..]), - Hmac::from_key(Digest::Sha256, &hmac_key_clone), - Vec::new() - ); - codec.send(nonce.into()).await.unwrap(); - codec.send(data.to_vec().into()).await.unwrap(); - }; - - async_std::task::block_on(future::join(client, server)); - } - - #[test] - fn full_codec_encode_then_decode_aes128() { - full_codec_encode_then_decode(Cipher::Aes128); - } - - #[test] - fn full_codec_encode_then_decode_aes256() { - 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/codec/decode.rs b/protocols/secio/src/codec/decode.rs deleted file mode 100644 index 8ea0cd4e..00000000 --- a/protocols/secio/src/codec/decode.rs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Individual messages decoding. - -use super::{Hmac, StreamCipher}; - -use crate::error::SecioError; -use futures::prelude::*; -use log::debug; -use std::{cmp::min, pin::Pin, task::Context, task::Poll}; - -/// Wraps around a `Stream>`. The buffers produced by the underlying stream -/// are decoded using the cipher and hmac. -/// -/// This struct implements `Stream`, whose stream item are frames of data without the length -/// prefix. The mechanism for removing the length prefix and splitting the incoming data into -/// frames isn't handled by this module. -/// -/// Also implements `Sink` for convenience. -#[pin_project::pin_project] -pub struct DecoderMiddleware { - cipher_state: StreamCipher, - hmac: Hmac, - #[pin] - raw_stream: S, - nonce: Vec -} - -impl DecoderMiddleware { - /// Create a new decoder for the given stream, using the provided cipher and HMAC. - /// - /// The `nonce` parameter denotes a sequence of bytes which are expected to be found at the - /// beginning of the stream and are checked for equality. - pub fn new(raw_stream: S, cipher: StreamCipher, hmac: Hmac, nonce: Vec) -> DecoderMiddleware { - DecoderMiddleware { - cipher_state: cipher, - hmac, - raw_stream, - nonce - } - } -} - -impl Stream for DecoderMiddleware -where - S: TryStream>, - S::Error: Into, -{ - type Item = Result, SecioError>; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - - let frame = match TryStream::try_poll_next(this.raw_stream, cx) { - Poll::Ready(Some(Ok(t))) => t, - Poll::Ready(None) => return Poll::Ready(None), - Poll::Pending => return Poll::Pending, - Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err.into()))), - }; - - if frame.len() < this.hmac.num_bytes() { - debug!("frame too short when decoding secio frame"); - return Poll::Ready(Some(Err(SecioError::FrameTooShort))); - } - let content_length = frame.len() - this.hmac.num_bytes(); - { - let (crypted_data, expected_hash) = frame.split_at(content_length); - debug_assert_eq!(expected_hash.len(), this.hmac.num_bytes()); - - if this.hmac.verify(crypted_data, expected_hash).is_err() { - debug!("hmac mismatch when decoding secio frame"); - return Poll::Ready(Some(Err(SecioError::HmacNotMatching))); - } - } - - let mut data_buf = frame; - data_buf.truncate(content_length); - this.cipher_state.decrypt(&mut data_buf); - - if !this.nonce.is_empty() { - let n = min(data_buf.len(), this.nonce.len()); - if data_buf[.. n] != this.nonce[.. n] { - return Poll::Ready(Some(Err(SecioError::NonceVerificationFailed))) - } - this.nonce.drain(.. n); - data_buf.drain(.. n); - } - - Poll::Ready(Some(Ok(data_buf))) - } -} - -impl Sink for DecoderMiddleware -where - S: Sink, -{ - type Error = S::Error; - - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - Sink::poll_ready(this.raw_stream, cx) - } - - fn start_send(self: Pin<&mut Self>, item: I) -> Result<(), Self::Error> { - let this = self.project(); - Sink::start_send(this.raw_stream, item) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - Sink::poll_flush(this.raw_stream, cx) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - Sink::poll_close(this.raw_stream, cx) - } -} diff --git a/protocols/secio/src/codec/encode.rs b/protocols/secio/src/codec/encode.rs deleted file mode 100644 index c4bbf5ac..00000000 --- a/protocols/secio/src/codec/encode.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Individual messages encoding. - -use super::{Hmac, StreamCipher}; -use futures::prelude::*; -use std::{pin::Pin, task::Context, task::Poll}; - -/// Wraps around a `Sink`. Encodes the buffers passed to it and passes it to the underlying sink. -/// -/// This struct implements `Sink`. It expects individual frames of data, and outputs individual -/// frames as well, most notably without the length prefix. The mechanism for adding the length -/// prefix is not covered by this module. -/// -/// Also implements `Stream` for convenience. -#[pin_project::pin_project] -pub struct EncoderMiddleware { - cipher_state: StreamCipher, - hmac: Hmac, - #[pin] - raw_sink: S, -} - -impl EncoderMiddleware { - pub fn new(raw: S, cipher: StreamCipher, hmac: Hmac) -> EncoderMiddleware { - EncoderMiddleware { - cipher_state: cipher, - hmac, - raw_sink: raw, - } - } -} - -impl Sink> for EncoderMiddleware -where - S: Sink>, -{ - type Error = S::Error; - - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - Sink::poll_ready(this.raw_sink, cx) - } - - fn start_send(self: Pin<&mut Self>, mut data_buf: Vec) -> Result<(), Self::Error> { - let this = self.project(); - // TODO if SinkError gets refactor to SecioError, then use try_apply_keystream - this.cipher_state.encrypt(&mut data_buf[..]); - let signature = this.hmac.sign(&data_buf[..]); - data_buf.extend_from_slice(signature.as_ref()); - Sink::start_send(this.raw_sink, data_buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - Sink::poll_flush(this.raw_sink, cx) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - Sink::poll_close(this.raw_sink, cx) - } -} - -impl Stream for EncoderMiddleware -where - S: Stream, -{ - type Item = S::Item; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - Stream::poll_next(this.raw_sink, cx) - } -} diff --git a/protocols/secio/src/codec/len_prefix.rs b/protocols/secio/src/codec/len_prefix.rs deleted file mode 100644 index 18882026..00000000 --- a/protocols/secio/src/codec/len_prefix.rs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use futures::{prelude::*, stream::BoxStream}; -use quicksink::Action; -use std::{fmt, io, pin::Pin, task::{Context, Poll}}; - -/// `Stream` & `Sink` that reads and writes a length prefix in front of the actual data. -pub struct LenPrefixCodec { - stream: BoxStream<'static, io::Result>>, - sink: Pin, Error = io::Error> + Send>>, - _mark: std::marker::PhantomData -} - -impl fmt::Debug for LenPrefixCodec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("LenPrefixCodec") - } -} - -static_assertions::const_assert! { - std::mem::size_of::() <= std::mem::size_of::() -} - -impl LenPrefixCodec -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - pub fn new(socket: T, max_len: usize) -> Self { - let (r, w) = socket.split(); - - let stream = futures::stream::unfold(r, move |mut r| async move { - let mut len = [0; 4]; - if let Err(e) = r.read_exact(&mut len).await { - if e.kind() == io::ErrorKind::UnexpectedEof { - return None - } - return Some((Err(e), r)) - } - let n = u32::from_be_bytes(len) as usize; - if n > max_len { - let msg = format!("data length {} exceeds allowed maximum {}", n, max_len); - return Some((Err(io::Error::new(io::ErrorKind::PermissionDenied, msg)), r)) - } - let mut v = vec![0; n]; - if let Err(e) = r.read_exact(&mut v).await { - return Some((Err(e), r)) - } - Some((Ok(v), r)) - }); - - let sink = quicksink::make_sink(w, move |mut w, action: Action>| async move { - match action { - Action::Send(data) => { - if data.len() > max_len { - log::error!("data length {} exceeds allowed maximum {}", data.len(), max_len) - } - w.write_all(&(data.len() as u32).to_be_bytes()).await?; - w.write_all(&data).await? - } - Action::Flush => w.flush().await?, - Action::Close => w.close().await? - } - Ok(w) - }); - - LenPrefixCodec { - stream: stream.boxed(), - sink: Box::pin(sink), - _mark: std::marker::PhantomData - } - } -} - -impl Stream for LenPrefixCodec -where - T: AsyncRead + AsyncWrite + Send + 'static -{ - type Item = io::Result>; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.stream.poll_next_unpin(cx) - } -} - -impl Sink> for LenPrefixCodec -where - T: AsyncRead + AsyncWrite + Send + 'static -{ - type Error = io::Error; - - fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.sink).poll_ready(cx) - } - - fn start_send(mut self: Pin<&mut Self>, item: Vec) -> Result<(), Self::Error> { - Pin::new(&mut self.sink).start_send(item) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.sink).poll_flush(cx) - } - - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.sink).poll_close(cx) - } -} - -impl Unpin for LenPrefixCodec { -} diff --git a/protocols/secio/src/error.rs b/protocols/secio/src/error.rs deleted file mode 100644 index 16b0dcdf..00000000 --- a/protocols/secio/src/error.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Defines the `SecioError` enum that groups all possible errors in SECIO. - -use aes_ctr::stream_cipher::LoopError; -use std::error; -use std::fmt; -use std::io::Error as IoError; - -/// Error at the SECIO layer communication. -#[derive(Debug)] -pub enum SecioError { - /// I/O error. - IoError(IoError), - - /// Protocol buffer error. - ProtobufError(prost::DecodeError), - - /// Failed to parse one of the handshake protobuf messages. - HandshakeParsingFailure, - - /// There is no protocol supported by both the local and remote hosts. - NoSupportIntersection, - - /// Failed to generate nonce. - NonceGenerationFailed, - - /// Failed to generate ephemeral key. - EphemeralKeyGenerationFailed, - - /// Failed to sign a message with our local private key. - SigningFailure, - - /// The signature of the exchange packet doesn't verify the remote public key. - SignatureVerificationFailed, - - /// Failed to generate the secret shared key from the ephemeral key. - SecretGenerationFailed, - - /// The final check of the handshake failed. - NonceVerificationFailed, - - /// Error with block cipher. - CipherError(LoopError), - - /// The received frame was of invalid length. - FrameTooShort, - - /// The hashes of the message didn't match. - HmacNotMatching, - - /// We received an invalid proposition from remote. - InvalidProposition(&'static str), - - #[doc(hidden)] - __Nonexhaustive -} - -impl error::Error for SecioError { - fn cause(&self) -> Option<&dyn error::Error> { - match *self { - SecioError::IoError(ref err) => Some(err), - SecioError::ProtobufError(ref err) => Some(err), - // TODO: The type doesn't implement `Error` - /*SecioError::CipherError(ref err) => { - Some(err) - },*/ - _ => None, - } - } -} - -impl fmt::Display for SecioError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self { - SecioError::IoError(e) => - write!(f, "I/O error: {}", e), - SecioError::ProtobufError(e) => - write!(f, "Protobuf error: {}", e), - SecioError::HandshakeParsingFailure => - f.write_str("Failed to parse one of the handshake protobuf messages"), - SecioError::NoSupportIntersection => - f.write_str("There is no protocol supported by both the local and remote hosts"), - SecioError::NonceGenerationFailed => - f.write_str("Failed to generate nonce"), - SecioError::EphemeralKeyGenerationFailed => - f.write_str("Failed to generate ephemeral key"), - SecioError::SigningFailure => - f.write_str("Failed to sign a message with our local private key"), - SecioError::SignatureVerificationFailed => - f.write_str("The signature of the exchange packet doesn't verify the remote public key"), - SecioError::SecretGenerationFailed => - f.write_str("Failed to generate the secret shared key from the ephemeral key"), - SecioError::NonceVerificationFailed => - f.write_str("The final check of the handshake failed"), - SecioError::CipherError(e) => - write!(f, "Error while decoding/encoding data: {:?}", e), - SecioError::FrameTooShort => - f.write_str("The received frame was of invalid length"), - SecioError::HmacNotMatching => - f.write_str("The hashes of the message didn't match"), - SecioError::InvalidProposition(msg) => - write!(f, "invalid proposition: {}", msg), - SecioError::__Nonexhaustive => - f.write_str("__Nonexhaustive") - } - } -} - -impl From for SecioError { - fn from(err: LoopError) -> SecioError { - SecioError::CipherError(err) - } -} - -impl From for SecioError { - fn from(err: IoError) -> SecioError { - SecioError::IoError(err) - } -} - -impl From for SecioError { - fn from(err: prost::DecodeError) -> SecioError { - SecioError::ProtobufError(err) - } -} diff --git a/protocols/secio/src/exchange.rs b/protocols/secio/src/exchange.rs deleted file mode 100644 index 1ae4120b..00000000 --- a/protocols/secio/src/exchange.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! This module handles the key agreement process. Typically ECDH. - -use futures::prelude::*; -use crate::SecioError; - -#[path = "exchange/impl_ring.rs"] -#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))] -mod platform; -#[path = "exchange/impl_webcrypto.rs"] -#[cfg(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown"))] -mod platform; - -/// Possible key agreement algorithms. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum KeyAgreement { - EcdhP256, - EcdhP384 -} - -/// Opaque private key type. -pub struct AgreementPrivateKey(platform::AgreementPrivateKey); - -/// Generates a new key pair as part of the exchange. -/// -/// Returns the opaque private key and the corresponding public key. -#[inline] -pub fn generate_agreement(algorithm: KeyAgreement) -> impl Future), SecioError>> { - platform::generate_agreement(algorithm).map_ok(|(pr, pu)| (AgreementPrivateKey(pr), pu)) -} - -/// Finish the agreement. On success, returns the shared key that both remote agreed upon. -#[inline] -pub fn agree(algorithm: KeyAgreement, my_private_key: AgreementPrivateKey, other_public_key: &[u8], out_size: usize) - -> impl Future, SecioError>> -{ - platform::agree(algorithm, my_private_key.0, other_public_key, out_size) -} - diff --git a/protocols/secio/src/exchange/impl_ring.rs b/protocols/secio/src/exchange/impl_ring.rs deleted file mode 100644 index b7f42be7..00000000 --- a/protocols/secio/src/exchange/impl_ring.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Implementation of the key agreement process using the `ring` library. - -use crate::{KeyAgreement, SecioError}; -use futures::{future, prelude::*}; -use log::debug; -use ring::agreement as ring_agreement; -use ring::rand as ring_rand; - -impl Into<&'static ring_agreement::Algorithm> for KeyAgreement { - #[inline] - fn into(self) -> &'static ring_agreement::Algorithm { - match self { - KeyAgreement::EcdhP256 => &ring_agreement::ECDH_P256, - KeyAgreement::EcdhP384 => &ring_agreement::ECDH_P384, - } - } -} - -/// Opaque private key type. -pub type AgreementPrivateKey = ring_agreement::EphemeralPrivateKey; - -/// Generates a new key pair as part of the exchange. -/// -/// Returns the opaque private key and the corresponding public key. -pub fn generate_agreement(algorithm: KeyAgreement) -> impl Future), SecioError>> { - let rng = ring_rand::SystemRandom::new(); - - match ring_agreement::EphemeralPrivateKey::generate(algorithm.into(), &rng) { - Ok(tmp_priv_key) => { - let r = tmp_priv_key.compute_public_key() - .map_err(|_| SecioError::EphemeralKeyGenerationFailed) - .map(move |tmp_pub_key| (tmp_priv_key, tmp_pub_key.as_ref().to_vec())); - future::ready(r) - }, - Err(_) => { - debug!("failed to generate ECDH key"); - future::ready(Err(SecioError::EphemeralKeyGenerationFailed)) - }, - } -} - -/// Finish the agreement. On success, returns the shared key that both remote agreed upon. -pub fn agree(algorithm: KeyAgreement, my_private_key: AgreementPrivateKey, other_public_key: &[u8], _out_size: usize) - -> impl Future, SecioError>> -{ - let ret = ring_agreement::agree_ephemeral(my_private_key, - &ring_agreement::UnparsedPublicKey::new(algorithm.into(), other_public_key), - SecioError::SecretGenerationFailed, - |key_material| Ok(key_material.to_vec())); - future::ready(ret) -} diff --git a/protocols/secio/src/exchange/impl_webcrypto.rs b/protocols/secio/src/exchange/impl_webcrypto.rs deleted file mode 100644 index a7a363ca..00000000 --- a/protocols/secio/src/exchange/impl_webcrypto.rs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Implementation of the key agreement process using the WebCrypto API. - -use crate::{KeyAgreement, SecioError}; -use futures::prelude::*; -use parity_send_wrapper::SendWrapper; -use std::{io, pin::Pin, task::Context, task::Poll}; -use wasm_bindgen::prelude::*; - -/// Opaque private key type. Contains the private key and the `SubtleCrypto` object. -pub type AgreementPrivateKey = SendSyncHack<(JsValue, web_sys::SubtleCrypto)>; - -/// We use a `SendWrapper` from the `send_wrapper` crate around our JS data type. JavaScript data -/// types are not `Send`/`Sync`, but since WASM is single-threaded we know that we're only ever -/// going to access them from the same thread. -pub struct SendSyncHack(SendWrapper); - -impl Future for SendSyncHack -where T: Future + Unpin { - type Output = T::Output; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { - self.0.poll_unpin(cx) - } -} - -/// Generates a new key pair as part of the exchange. -/// -/// Returns the opaque private key and the corresponding public key. -pub fn generate_agreement(algorithm: KeyAgreement) - -> impl Future), SecioError>> -{ - let future = async move { - // First step is to create the `SubtleCrypto` object. - let crypto = build_crypto_future().await?; - - // We then generate the ephemeral key. - let key_pair = { - let obj = build_curve_obj(algorithm); - - let usages = js_sys::Array::new(); - usages.push(&JsValue::from_str("deriveKey")); - usages.push(&JsValue::from_str("deriveBits")); - - let promise = crypto.generate_key_with_object(&obj, true, usages.as_ref())?; - wasm_bindgen_futures::JsFuture::from(promise).await? - }; - - // WebCrypto has generated a key-pair. Let's split this key pair into a private key and a - // public key. - let (private, public) = { - let private = js_sys::Reflect::get(&key_pair, &JsValue::from_str("privateKey")); - let public = js_sys::Reflect::get(&key_pair, &JsValue::from_str("publicKey")); - match (private, public) { - (Ok(pr), Ok(pu)) => (pr, pu), - (Err(err), _) => return Err(err), - (_, Err(err)) => return Err(err), - } - }; - - // Then we turn the public key into an `ArrayBuffer`. - let public = { - let promise = crypto.export_key("raw", &public.into())?; - wasm_bindgen_futures::JsFuture::from(promise).await? - }; - - // And finally we convert this `ArrayBuffer` into a `Vec`. - let public = js_sys::Uint8Array::new(&public); - let mut public_buf = vec![0; public.length() as usize]; - public.copy_to(&mut public_buf); - Ok((SendSyncHack(SendWrapper::new((private, crypto))), public_buf)) - }; - - let future = future - .map_err(|err| { - SecioError::IoError(io::Error::new(io::ErrorKind::Other, format!("{:?}", err))) - }); - SendSyncHack(SendWrapper::new(Box::pin(future))) -} - -/// Finish the agreement. On success, returns the shared key that both remote agreed upon. -pub fn agree(algorithm: KeyAgreement, key: AgreementPrivateKey, other_public_key: &[u8], out_size: usize) - -> impl Future, SecioError>> -{ - let other_public_key = { - // This unsafe is here because the lifetime of `other_public_key` must not outlive the - // `tmp_view`. This is guaranteed by the fact that we clone this array right below. - // See also https://github.com/rustwasm/wasm-bindgen/issues/1303 - let tmp_view = unsafe { js_sys::Uint8Array::view(other_public_key) }; - js_sys::Uint8Array::new(tmp_view.as_ref()) - }; - - let future = async move { - let (private_key, crypto) = key.0.take(); - - // We start by importing the remote's public key into the WebCrypto world. - let public_key = { - // Note: contrary to what one might think, we shouldn't add the "deriveBits" usage. - let promise = crypto - .import_key_with_object( - "raw", &js_sys::Object::from(other_public_key.buffer()), - &build_curve_obj(algorithm), false, &js_sys::Array::new() - )?; - wasm_bindgen_futures::JsFuture::from(promise).await? - }; - - // We then derive the final private key. - let bytes = { - let derive_params = build_curve_obj(algorithm); - let _ = js_sys::Reflect::set(derive_params.as_ref(), &JsValue::from_str("public"), &public_key); - let promise = crypto - .derive_bits_with_object( - &derive_params, - &web_sys::CryptoKey::from(private_key), - 8 * out_size as u32 - )?; - wasm_bindgen_futures::JsFuture::from(promise).await? - }; - - let bytes = js_sys::Uint8Array::new(&bytes); - let mut buf = vec![0; bytes.length() as usize]; - bytes.copy_to(&mut buf); - Ok(buf) - }; - - let future = future - .map_err(|err: JsValue| { - SecioError::IoError(io::Error::new(io::ErrorKind::Other, format!("{:?}", err))) - }); - SendSyncHack(SendWrapper::new(Box::pin(future))) -} - -/// Builds a future that returns the `SubtleCrypto` object. -async fn build_crypto_future() -> Result { - web_sys::window() - .ok_or_else(|| JsValue::from_str("Window object not available")) - .and_then(|window| window.crypto()) - .map(|crypto| crypto.subtle()) -} - -/// Builds a `EcKeyGenParams` object. -/// See https://developer.mozilla.org/en-US/docs/Web/API/EcKeyGenParams -fn build_curve_obj(algorithm: KeyAgreement) -> js_sys::Object { - let obj = js_sys::Object::new(); - let _ = js_sys::Reflect::set(obj.as_ref(), &JsValue::from_str("name"), &JsValue::from_str("ECDH")); - let _ = js_sys::Reflect::set(obj.as_ref(), &JsValue::from_str("namedCurve"), &JsValue::from_str(match algorithm { - KeyAgreement::EcdhP256 => "P-256", - KeyAgreement::EcdhP384 => "P-384", - })); - obj -} diff --git a/protocols/secio/src/handshake.rs b/protocols/secio/src/handshake.rs deleted file mode 100644 index d1ff63d0..00000000 --- a/protocols/secio/src/handshake.rs +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::SecioConfig; -use crate::algo_support; -use crate::codec::{full_codec, FullCodec, Hmac, LenPrefixCodec}; -use crate::error::SecioError; -use crate::exchange; -use crate::stream_cipher::ctr; -use crate::structs_proto::{Exchange, Propose}; -use futures::prelude::*; -use libp2p_core::PublicKey; -use log::{debug, trace}; -use prost::Message; -use rand::{self, RngCore}; -use sha2::{Digest as ShaDigestTrait, Sha256}; -use std::{cmp::{self, Ordering}, io}; - - -/// Performs a handshake on the given socket. -/// -/// This function expects that the remote is identified with `remote_public_key`, and the remote -/// will expect that we are identified with `local_key`. Any mismatch somewhere will produce a -/// `SecioError`. -/// -/// On success, returns an object that implements the `Sink` and `Stream` trait whose items are -/// buffers of data, plus the public key of the remote, plus the ephemeral public key used during -/// negotiation. -pub async fn handshake(socket: S, config: SecioConfig) - -> Result<(FullCodec, PublicKey, Vec), SecioError> -where - S: AsyncRead + AsyncWrite + Send + Unpin + 'static -{ - let mut socket = LenPrefixCodec::new(socket, config.max_frame_len); - - let local_nonce = { - let mut local_nonce = [0; 16]; - rand::thread_rng() - .try_fill_bytes(&mut local_nonce) - .map_err(|_| SecioError::NonceGenerationFailed)?; - local_nonce - }; - - let local_public_key_encoded = config.key.public().into_protobuf_encoding(); - - // Send our proposition with our nonce, public key and supported protocols. - let local_proposition = Propose { - rand: Some(local_nonce.to_vec()), - pubkey: Some(local_public_key_encoded.clone()), - exchanges: if let Some(ref p) = config.agreements_prop { - trace!("agreements proposition: {}", p); - Some(p.clone()) - } else { - trace!("agreements proposition: {}", algo_support::DEFAULT_AGREEMENTS_PROPOSITION); - Some(algo_support::DEFAULT_AGREEMENTS_PROPOSITION.into()) - }, - ciphers: if let Some(ref p) = config.ciphers_prop { - trace!("ciphers proposition: {}", p); - Some(p.clone()) - } else { - trace!("ciphers proposition: {}", algo_support::DEFAULT_CIPHERS_PROPOSITION); - Some(algo_support::DEFAULT_CIPHERS_PROPOSITION.into()) - }, - hashes: if let Some(ref p) = config.digests_prop { - trace!("digests proposition: {}", p); - Some(p.clone()) - } else { - Some(algo_support::DEFAULT_DIGESTS_PROPOSITION.into()) - } - }; - - let local_proposition_bytes = { - let mut buf = Vec::with_capacity(local_proposition.encoded_len()); - local_proposition.encode(&mut buf).expect("Vec provides capacity as needed"); - buf - }; - trace!("starting handshake; local nonce = {:?}", local_nonce); - - trace!("sending proposition to remote"); - socket.send(local_proposition_bytes.clone()).await?; - - // Receive the remote's proposition. - let remote_proposition_bytes = match socket.next().await { - Some(b) => b?, - None => { - debug!("unexpected eof while waiting for remote's proposition"); - return Err(SecioError::IoError(io::ErrorKind::UnexpectedEof.into())) - }, - }; - - let remote_proposition = match Propose::decode(&remote_proposition_bytes[..]) { - Ok(prop) => prop, - Err(_) => { - debug!("failed to parse remote's proposition protobuf message"); - return Err(SecioError::HandshakeParsingFailure); - } - }; - - let remote_public_key_encoded = remote_proposition.pubkey.unwrap_or_default(); - let remote_nonce = remote_proposition.rand.unwrap_or_default(); - - let remote_public_key = match PublicKey::from_protobuf_encoding(&remote_public_key_encoded) { - Ok(p) => p, - Err(_) => { - debug!("failed to parse remote's proposition's pubkey protobuf"); - return Err(SecioError::HandshakeParsingFailure); - }, - }; - trace!("received proposition from remote; pubkey = {:?}; nonce = {:?}", - remote_public_key, remote_nonce); - - // In order to determine which protocols to use, we compute two hashes and choose - // based on which hash is larger. - let hashes_ordering = { - let oh1 = { - let mut ctx = Sha256::new(); - ctx.update(&remote_public_key_encoded); - ctx.update(&local_nonce); - ctx.finalize() - }; - - let oh2 = { - let mut ctx = Sha256::new(); - ctx.update(&local_public_key_encoded); - ctx.update(&remote_nonce); - ctx.finalize() - }; - - oh1.cmp(&oh2) - }; - - let chosen_exchange = { - let ours = config.agreements_prop.as_ref() - .map(|s| s.as_ref()) - .unwrap_or(algo_support::DEFAULT_AGREEMENTS_PROPOSITION); - let theirs = &remote_proposition.exchanges.unwrap_or_default(); - match algo_support::select_agreement(hashes_ordering, ours, theirs) { - Ok(a) => a, - Err(err) => { - debug!("failed to select an exchange protocol"); - return Err(err); - } - } - }; - - let chosen_cipher = { - let ours = config.ciphers_prop.as_ref() - .map(|s| s.as_ref()) - .unwrap_or(algo_support::DEFAULT_CIPHERS_PROPOSITION); - let theirs = &remote_proposition.ciphers.unwrap_or_default(); - match algo_support::select_cipher(hashes_ordering, ours, theirs) { - Ok(a) => { - debug!("selected cipher: {:?}", a); - a - } - Err(err) => { - debug!("failed to select a cipher protocol"); - return Err(err); - } - } - }; - - let chosen_hash = { - let ours = config.digests_prop.as_ref() - .map(|s| s.as_ref()) - .unwrap_or(algo_support::DEFAULT_DIGESTS_PROPOSITION); - let theirs = &remote_proposition.hashes.unwrap_or_default(); - match algo_support::select_digest(hashes_ordering, ours, theirs) { - Ok(a) => { - debug!("selected hash: {:?}", a); - a - } - Err(err) => { - debug!("failed to select a hash protocol"); - return Err(err); - } - } - }; - - // Generate an ephemeral key for the negotiation. - let (tmp_priv_key, tmp_pub_key) = exchange::generate_agreement(chosen_exchange).await?; - - // Send the ephemeral pub key to the remote in an `Exchange` struct. The `Exchange` also - // contains a signature of the two propositions encoded with our static public key. - let local_exchange = { - let mut data_to_sign = local_proposition_bytes.clone(); - data_to_sign.extend_from_slice(&remote_proposition_bytes); - data_to_sign.extend_from_slice(&tmp_pub_key); - - Exchange { - epubkey: Some(tmp_pub_key.clone()), - signature: match config.key.sign(&data_to_sign) { - Ok(sig) => Some(sig), - Err(_) => return Err(SecioError::SigningFailure) - } - } - }; - let local_exch = { - let mut buf = Vec::with_capacity(local_exchange.encoded_len()); - local_exchange.encode(&mut buf).expect("Vec provides capacity as needed"); - buf - }; - - // Send our local `Exchange`. - trace!("sending exchange to remote"); - socket.send(local_exch).await?; - - // Receive the remote's `Exchange`. - let remote_exch = { - let raw = match socket.next().await { - Some(r) => r?, - None => { - debug!("unexpected eof while waiting for remote's exchange"); - return Err(SecioError::IoError(io::ErrorKind::UnexpectedEof.into())) - }, - }; - - match Exchange::decode(&raw[..]) { - Ok(e) => { - trace!("received and decoded the remote's exchange"); - e - }, - Err(err) => { - debug!("failed to parse remote's exchange protobuf; {:?}", err); - return Err(SecioError::HandshakeParsingFailure); - } - } - }; - - // Check the validity of the remote's `Exchange`. This verifies that the remote was really - // the sender of its proposition, and that it is the owner of both its global and ephemeral - // keys. - { - let mut data_to_verify = remote_proposition_bytes.clone(); - data_to_verify.extend_from_slice(&local_proposition_bytes); - data_to_verify.extend_from_slice(remote_exch.epubkey.as_deref().unwrap_or_default()); - - if !remote_public_key.verify(&data_to_verify, &remote_exch.signature.unwrap_or_default()) { - return Err(SecioError::SignatureVerificationFailed) - } - - trace!("successfully verified the remote's signature"); - } - - // Generate a key from the local ephemeral private key and the remote ephemeral public key, - // derive from it a cipher key, an iv, and a hmac key, and build the encoder/decoder. - let key_material = exchange::agree( - chosen_exchange, - tmp_priv_key, - &remote_exch.epubkey.unwrap_or_default(), - chosen_hash.num_bytes() - ).await?; - - // Generate a key from the local ephemeral private key and the remote ephemeral public key, - // derive from it a cipher key, an iv, and a hmac key, and build the encoder/decoder. - let mut codec = { - let cipher_key_size = chosen_cipher.key_size(); - let iv_size = chosen_cipher.iv_size(); - - let key = Hmac::from_key(chosen_hash, &key_material); - let mut longer_key = vec![0u8; 2 * (iv_size + cipher_key_size + 20)]; - stretch_key(key, &mut longer_key); - - let (local_infos, remote_infos) = { - let (first_half, second_half) = longer_key.split_at(longer_key.len() / 2); - match hashes_ordering { - Ordering::Equal => { - let msg = "equal digest of public key and nonce for local and remote"; - return Err(SecioError::InvalidProposition(msg)) - } - Ordering::Less => (second_half, first_half), - Ordering::Greater => (first_half, second_half), - } - }; - - let (encoding_cipher, encoding_hmac) = { - let (iv, rest) = local_infos.split_at(iv_size); - let (cipher_key, mac_key) = rest.split_at(cipher_key_size); - let hmac = Hmac::from_key(chosen_hash, mac_key); - let cipher = ctr(chosen_cipher, cipher_key, iv); - (cipher, hmac) - }; - - let (decoding_cipher, decoding_hmac) = { - let (iv, rest) = remote_infos.split_at(iv_size); - let (cipher_key, mac_key) = rest.split_at(cipher_key_size); - let hmac = Hmac::from_key(chosen_hash, mac_key); - let cipher = ctr(chosen_cipher, cipher_key, iv); - (cipher, hmac) - }; - - full_codec( - socket, - encoding_cipher, - encoding_hmac, - decoding_cipher, - decoding_hmac, - local_nonce.to_vec() - ) - }; - - // We send back their nonce to check if the connection works. - trace!("checking encryption by sending back remote's nonce"); - codec.send(remote_nonce).await?; - - Ok((codec, remote_public_key, tmp_pub_key)) -} - -/// Custom algorithm translated from reference implementations. Needs to be the same algorithm -/// amongst all implementations. -fn stretch_key(hmac: Hmac, result: &mut [u8]) { - match hmac { - Hmac::Sha256(hmac) => stretch_key_inner(hmac, result), - Hmac::Sha512(hmac) => stretch_key_inner(hmac, result), - } -} - -fn stretch_key_inner(hmac: ::hmac::Hmac, result: &mut [u8]) -where D: ::hmac::digest::Update + ::hmac::digest::BlockInput + - ::hmac::digest::FixedOutput + ::hmac::digest::Reset + Default + Clone, - ::hmac::Hmac: Clone + ::hmac::crypto_mac::Mac -{ - use ::hmac::Mac; - const SEED: &[u8] = b"key expansion"; - - let mut init_ctxt = hmac.clone(); - init_ctxt.update(SEED); - let mut a = init_ctxt.finalize().into_bytes(); - - let mut j = 0; - while j < result.len() { - let mut context = hmac.clone(); - context.update(a.as_ref()); - context.update(SEED); - let b = context.finalize().into_bytes(); - - let todo = cmp::min(b.as_ref().len(), result.len() - j); - - result[j..j + todo].copy_from_slice(&b.as_ref()[..todo]); - - j += todo; - - let mut context = hmac.clone(); - context.update(a.as_ref()); - a = context.finalize().into_bytes(); - } -} - -#[cfg(test)] -mod tests { - use super::{handshake, stretch_key}; - use crate::{algo_support::Digest, codec::Hmac, SecioConfig}; - use libp2p_core::identity; - use futures::{prelude::*, channel::oneshot}; - - #[test] - #[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))] - fn handshake_with_self_succeeds_rsa() { - let key1 = { - let mut private = include_bytes!("../tests/test-rsa-private-key.pk8").to_vec(); - identity::Keypair::rsa_from_pkcs8(&mut private).unwrap() - }; - - let key2 = { - let mut private = include_bytes!("../tests/test-rsa-private-key-2.pk8").to_vec(); - identity::Keypair::rsa_from_pkcs8(&mut private).unwrap() - }; - - handshake_with_self_succeeds(SecioConfig::new(key1), SecioConfig::new(key2)); - } - - #[test] - fn handshake_with_self_succeeds_ed25519() { - let key1 = identity::Keypair::generate_ed25519(); - let key2 = identity::Keypair::generate_ed25519(); - handshake_with_self_succeeds(SecioConfig::new(key1), SecioConfig::new(key2)); - } - - #[test] - #[cfg(feature = "secp256k1")] - fn handshake_with_self_succeeds_secp256k1() { - let key1 = { - let mut key = include_bytes!("../tests/test-secp256k1-private-key.der").to_vec(); - identity::Keypair::secp256k1_from_der(&mut key).unwrap() - }; - - let key2 = { - let mut key = include_bytes!("../tests/test-secp256k1-private-key-2.der").to_vec(); - identity::Keypair::secp256k1_from_der(&mut key).unwrap() - }; - - handshake_with_self_succeeds(SecioConfig::new(key1), SecioConfig::new(key2)); - } - - fn handshake_with_self_succeeds(key1: SecioConfig, key2: SecioConfig) { - let (l_a_tx, l_a_rx) = oneshot::channel(); - - async_std::task::spawn(async move { - let listener = async_std::net::TcpListener::bind(&"127.0.0.1:0").await.unwrap(); - l_a_tx.send(listener.local_addr().unwrap()).unwrap(); - let connec = listener.accept().await.unwrap().0; - let mut codec = handshake(connec, key1).await.unwrap().0; - while let Some(packet) = codec.next().await { - let packet = packet.unwrap(); - if !packet.is_empty() { - codec.send(packet.into()).await.unwrap(); - } - } - }); - - async_std::task::block_on(async move { - let listen_addr = l_a_rx.await.unwrap(); - let connec = async_std::net::TcpStream::connect(&listen_addr).await.unwrap(); - let mut codec = handshake(connec, key2).await.unwrap().0; - codec.send(b"hello".to_vec().into()).await.unwrap(); - let mut packets_stream = codec.filter(|p| future::ready(!p.as_ref().unwrap().is_empty())); - let packet = packets_stream.next().await.unwrap(); - assert_eq!(packet.unwrap(), b"hello"); - }); - } - - #[test] - fn stretch() { - let mut output = [0u8; 32]; - - let key1 = Hmac::from_key(Digest::Sha256, &[]); - stretch_key(key1, &mut output); - assert_eq!( - &output, - &[ - 103, 144, 60, 199, 85, 145, 239, 71, 79, 198, 85, 164, 32, 53, 143, 205, 50, 48, - 153, 10, 37, 32, 85, 1, 226, 61, 193, 1, 154, 120, 207, 80, - ] - ); - - let key2 = Hmac::from_key( - Digest::Sha256, - &[ - 157, 166, 80, 144, 77, 193, 198, 6, 23, 220, 87, 220, 191, 72, 168, 197, 54, 33, - 219, 225, 84, 156, 165, 37, 149, 224, 244, 32, 170, 79, 125, 35, 171, 26, 178, 176, - 92, 168, 22, 27, 205, 44, 229, 61, 152, 21, 222, 81, 241, 81, 116, 236, 74, 166, - 89, 145, 5, 162, 108, 230, 55, 54, 9, 17, - ], - ); - stretch_key(key2, &mut output); - assert_eq!( - &output, - &[ - 39, 151, 182, 63, 180, 175, 224, 139, 42, 131, 130, 116, 55, 146, 62, 31, 157, 95, - 217, 15, 73, 81, 10, 83, 243, 141, 64, 227, 103, 144, 99, 121, - ] - ); - - let key3 = Hmac::from_key( - Digest::Sha256, - &[ - 98, 219, 94, 104, 97, 70, 139, 13, 185, 110, 56, 36, 66, 3, 80, 224, 32, 205, 102, - 170, 59, 32, 140, 245, 86, 102, 231, 68, 85, 249, 227, 243, 57, 53, 171, 36, 62, - 225, 178, 74, 89, 142, 151, 94, 183, 231, 208, 166, 244, 130, 130, 209, 248, 65, - 19, 48, 127, 127, 55, 82, 117, 154, 124, 108, - ], - ); - stretch_key(key3, &mut output); - assert_eq!( - &output, - &[ - 28, 39, 158, 206, 164, 16, 211, 194, 99, 43, 208, 36, 24, 141, 90, 93, 157, 236, - 238, 111, 170, 0, 60, 11, 49, 174, 177, 121, 30, 12, 182, 25, - ] - ); - } -} diff --git a/protocols/secio/src/lib.rs b/protocols/secio/src/lib.rs deleted file mode 100644 index 7332c725..00000000 --- a/protocols/secio/src/lib.rs +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! The `secio` protocol is a middleware that will encrypt and decrypt communications going -//! through a socket (or anything that implements `AsyncRead + AsyncWrite`). -//! -//! # Usage -//! -//! The `SecioConfig` implements [`InboundUpgrade`] and [`OutboundUpgrade`] and thus -//! serves as a connection upgrade for authentication of a transport. -//! See [`authenticate`](libp2p_core::transport::upgrade::Builder::authenticate). -//! -//! ```no_run -//! # fn main() { -//! use futures::prelude::*; -//! use libp2p_secio::{SecioConfig, SecioOutput}; -//! use libp2p_core::{PeerId, Multiaddr, identity, upgrade}; -//! use libp2p_core::transport::Transport; -//! use libp2p_mplex::MplexConfig; -//! use libp2p_tcp::TcpConfig; -//! -//! // Create a local peer identity. -//! let local_keys = identity::Keypair::generate_ed25519(); -//! -//! // Create a `Transport`. -//! let transport = TcpConfig::new() -//! .upgrade(upgrade::Version::V1) -//! .authenticate(SecioConfig::new(local_keys.clone())) -//! .multiplex(MplexConfig::default()); -//! -//! // The transport can be used with a `Network` from `libp2p-core`, or a -//! // `Swarm` from from `libp2p-swarm`. See the documentation of these -//! // crates for mode details. -//! -//! // let network = Network::new(transport, local_keys.public().into_peer_id()); -//! // let swarm = Swarm::new(transport, behaviour, local_keys.public().into_peer_id()); -//! # } -//! ``` -//! - -pub use self::error::SecioError; - -use futures::stream::MapErr as StreamMapErr; -use futures::prelude::*; -use libp2p_core::{PeerId, PublicKey, identity, upgrade::{UpgradeInfo, InboundUpgrade, OutboundUpgrade}}; -use log::debug; -use rw_stream_sink::RwStreamSink; -use std::{io, iter, pin::Pin, task::Context, task::Poll}; - -mod algo_support; -mod codec; -mod error; -mod exchange; -mod handshake; -mod structs_proto { - include!(concat!(env!("OUT_DIR"), "/spipe.pb.rs")); -} -mod stream_cipher; - -pub use crate::algo_support::Digest; -pub use crate::exchange::KeyAgreement; -pub use crate::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(crate) key: identity::Keypair, - pub(crate) agreements_prop: Option, - pub(crate) ciphers_prop: Option, - pub(crate) digests_prop: Option, - pub(crate) max_frame_len: usize -} - -impl SecioConfig { - /// Create a new `SecioConfig` with the given keypair. - pub fn new(kp: identity::Keypair) -> Self { - SecioConfig { - key: kp, - agreements_prop: None, - ciphers_prop: None, - digests_prop: None, - max_frame_len: 8 * 1024 * 1024 - } - } - - /// Override the default set of supported key agreement algorithms. - pub fn key_agreements<'a, I>(mut self, xs: I) -> Self - where - I: IntoIterator - { - 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 - { - 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 - { - self.digests_prop = Some(algo_support::digests_proposition(xs)); - self - } - - /// Override the default max. frame length of 8MiB. - pub fn max_frame_len(mut self, n: usize) -> Self { - self.max_frame_len = n; - self - } - - fn handshake(self, socket: T) -> impl Future), SecioError>> - where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static - { - debug!("Starting secio upgrade"); - SecioMiddleware::handshake(socket, self) - .map_ok(|(stream_sink, pubkey, ephemeral)| { - let mapped = stream_sink.map_err(map_err as fn(_) -> _); - let peer = pubkey.clone().into_peer_id(); - let io = SecioOutput { - stream: RwStreamSink::new(mapped), - remote_key: pubkey, - ephemeral_public_key: ephemeral - }; - (peer, io) - }) - } -} - -/// Output of the secio protocol. -pub struct SecioOutput -where - S: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - /// The encrypted stream. - pub stream: RwStreamSink, fn(SecioError) -> io::Error>>, - /// The public key of the remote. - pub remote_key: PublicKey, - /// Ephemeral public key used during the negotiation. - pub ephemeral_public_key: Vec, -} - -impl UpgradeInfo for SecioConfig { - type Info = &'static [u8]; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - iter::once(b"/secio/1.0.0") - } -} - -impl InboundUpgrade for SecioConfig -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - type Output = (PeerId, SecioOutput); - type Error = SecioError; - type Future = Pin> + Send>>; - - fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - Box::pin(self.handshake(socket)) - } -} - -impl OutboundUpgrade for SecioConfig -where - T: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - type Output = (PeerId, SecioOutput); - type Error = SecioError; - type Future = Pin> + Send>>; - - fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - Box::pin(self.handshake(socket)) - } -} - -impl AsyncRead for SecioOutput -where - S: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) - -> Poll> - { - AsyncRead::poll_read(Pin::new(&mut self.stream), cx, buf) - } -} - -impl AsyncWrite for SecioOutput -where - S: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) - -> Poll> - { - AsyncWrite::poll_write(Pin::new(&mut self.stream), cx, buf) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll> - { - AsyncWrite::poll_flush(Pin::new(&mut self.stream), cx) - } - - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll> - { - AsyncWrite::poll_close(Pin::new(&mut self.stream), cx) - } -} - -fn map_err(err: SecioError) -> io::Error { - debug!("error during secio handshake {:?}", err); - io::Error::new(io::ErrorKind::InvalidData, err) -} - -/// Wraps around an object that implements `AsyncRead` and `AsyncWrite`. -/// -/// Implements `Sink` and `Stream` whose items are frames of data. Each frame is encoded -/// individually, so you are encouraged to group data in few frames if possible. -pub struct SecioMiddleware { - inner: codec::FullCodec, -} - -impl SecioMiddleware -where - S: AsyncRead + AsyncWrite + Send + Unpin + 'static, -{ - /// Attempts to perform a handshake on the given socket. - /// - /// On success, produces a `SecioMiddleware` that can then be used to encode/decode - /// communications, plus the public key of the remote, plus the ephemeral public key. - pub fn handshake(socket: S, config: SecioConfig) - -> impl Future, PublicKey, Vec), SecioError>> - { - handshake::handshake(socket, config).map_ok(|(inner, pubkey, ephemeral)| { - let inner = SecioMiddleware { inner }; - (inner, pubkey, ephemeral) - }) - } -} - -impl Sink> for SecioMiddleware -where - S: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - type Error = io::Error; - - fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Sink::poll_ready(Pin::new(&mut self.inner), cx) - } - - fn start_send(mut self: Pin<&mut Self>, item: Vec) -> Result<(), Self::Error> { - Sink::start_send(Pin::new(&mut self.inner), item) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Sink::poll_flush(Pin::new(&mut self.inner), cx) - } - - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Sink::poll_close(Pin::new(&mut self.inner), cx) - } -} - -impl Stream for SecioMiddleware -where - S: AsyncRead + AsyncWrite + Unpin + Send + 'static -{ - type Item = Result, SecioError>; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Stream::poll_next(Pin::new(&mut self.inner), cx) - } -} diff --git a/protocols/secio/src/stream_cipher.rs b/protocols/secio/src/stream_cipher.rs deleted file mode 100644 index ab15de1a..00000000 --- a/protocols/secio/src/stream_cipher.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use super::codec::StreamCipher; -use aes_ctr::stream_cipher::generic_array::GenericArray; -use aes_ctr::stream_cipher::{NewStreamCipher, LoopError, SyncStreamCipher}; -use aes_ctr::{Aes128Ctr, Aes256Ctr}; -use ctr::Ctr128; -use twofish::Twofish; - -/// Possible encryption ciphers. -#[derive(Clone, Copy, Debug)] -pub enum Cipher { - Aes128, - Aes256, - TwofishCtr, - Null, -} - -impl Cipher { - /// Returns the size of in bytes of the key expected by the cipher. - pub fn key_size(&self) -> usize { - match *self { - Cipher::Aes128 => 16, - Cipher::Aes256 => 32, - Cipher::TwofishCtr => 32, - Cipher::Null => 0, - } - } - - /// Returns the size of in bytes of the IV expected by the cipher. - #[inline] - pub fn iv_size(&self) -> usize { - match self { - Cipher::Aes128 | Cipher::Aes256 | Cipher::TwofishCtr => 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 SyncStreamCipher 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 { - use crate::codec::StreamCipher; - use ctr::Ctr128; - use aesni::{Aes128, Aes256}; - use ctr::stream_cipher::NewStreamCipher; - use ctr::stream_cipher::generic_array::GenericArray; - use lazy_static::lazy_static; - 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] -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]; - aes.encrypt(&mut content); - - } -} - -// 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." -); diff --git a/protocols/secio/src/structs.proto b/protocols/secio/src/structs.proto deleted file mode 100644 index a35de4c8..00000000 --- a/protocols/secio/src/structs.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto2"; - -package spipe.pb; - -message Propose { - optional bytes rand = 1; - optional bytes pubkey = 2; - optional string exchanges = 3; - optional string ciphers = 4; - optional string hashes = 5; -} - -message Exchange { - optional bytes epubkey = 1; - optional bytes signature = 2; -} \ No newline at end of file diff --git a/protocols/secio/tests/test-rsa-private-key-2.pk8 b/protocols/secio/tests/test-rsa-private-key-2.pk8 deleted file mode 100644 index 85ff4154..00000000 Binary files a/protocols/secio/tests/test-rsa-private-key-2.pk8 and /dev/null differ diff --git a/protocols/secio/tests/test-rsa-private-key.pk8 b/protocols/secio/tests/test-rsa-private-key.pk8 deleted file mode 100644 index 452b7af1..00000000 Binary files a/protocols/secio/tests/test-rsa-private-key.pk8 and /dev/null differ diff --git a/protocols/secio/tests/test-secp256k1-private-key-2.der b/protocols/secio/tests/test-secp256k1-private-key-2.der deleted file mode 100644 index 16be9616..00000000 Binary files a/protocols/secio/tests/test-secp256k1-private-key-2.der and /dev/null differ diff --git a/protocols/secio/tests/test-secp256k1-private-key.der b/protocols/secio/tests/test-secp256k1-private-key.der deleted file mode 100644 index 90bb3973..00000000 Binary files a/protocols/secio/tests/test-secp256k1-private-key.der and /dev/null differ diff --git a/src/transport_ext.rs b/src/transport_ext.rs index e2e37fe5..de77007b 100644 --- a/src/transport_ext.rs +++ b/src/transport_ext.rs @@ -37,8 +37,6 @@ pub trait TransportExt: Transport { { BandwidthLogging::new(self) } - - // TODO: add methods to easily upgrade for secio/mplex/yamux } impl TransportExt for TTransport where TTransport: Transport {}