[libp2p-secio] Remove crate. (#1940)

* Remove the SECIO transport protocol.

* Remove stray mentions of secio.
This commit is contained in:
Roman Borschel 2021-02-01 14:39:44 +01:00 committed by GitHub
parent 1a01dd16cb
commit 2ecf42a2bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 3 additions and 2375 deletions

View File

@ -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)

View File

@ -114,7 +114,6 @@ members = [
"protocols/plaintext",
"protocols/pnet",
"protocols/request-response",
"protocols/secio",
"swarm",
"swarm-derive",
"transports/dns",

View File

@ -158,8 +158,7 @@ impl Keypair<X25519> {
/// 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<X25519> {
/// 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:
/// >

View File

@ -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.

View File

@ -1,55 +0,0 @@
[package]
name = "libp2p-secio"
edition = "2018"
description = "Secio encryption protocol for libp2p"
version = "0.27.0"
authors = ["Parity Technologies <admin@parity.io>"]
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" }

View File

@ -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();
}

View File

@ -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<Item=&'a KeyAgreement>
{
let mut s = String::new();
for x in xchgs {
match x {
KeyAgreement::EcdhP256 => {
s.push_str(ECDH_P256);
s.push(',')
}
KeyAgreement::EcdhP384 => {
s.push_str(ECDH_P384);
s.push(',')
}
}
}
s.pop(); // remove trailing comma if any
s
}
/// 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<KeyAgreement, SecioError> {
let (a, b) = match r {
Ordering::Less | Ordering::Equal => (theirs, ours),
Ordering::Greater => (ours, theirs)
};
for x in a.split(',') {
if b.split(',').any(|y| x == y) {
match x {
ECDH_P256 => return Ok(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<Item=&'a Cipher>
{
let mut s = String::new();
for c in ciphers {
match c {
Cipher::Aes128 => {
s.push_str(AES_128);
s.push(',')
}
Cipher::Aes256 => {
s.push_str(AES_256);
s.push(',')
}
Cipher::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<Cipher, SecioError> {
let (a, b) = match r {
Ordering::Less | Ordering::Equal => (theirs, ours),
Ordering::Greater => (ours, theirs)
};
for x in a.split(',') {
if b.split(',').any(|y| x == y) {
match x {
AES_128 => return Ok(Cipher::Aes128),
AES_256 => return Ok(Cipher::Aes256),
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<Item=&'a Digest>
{
let mut s = String::new();
for d in digests {
match d {
Digest::Sha256 => {
s.push_str(SHA_256);
s.push(',')
}
Digest::Sha512 => {
s.push_str(SHA_512);
s.push(',')
}
}
}
s.pop(); // remove trailing comma if any
s
}
/// Given two digest proposition strings try to figure out a match.
///
/// The `Ordering` parameter determines which argument is preferred. If `Less` or `Equal` we
/// try for each of `theirs` every one of `ours`, for `Greater` it's the other way around.
pub fn select_digest(r: Ordering, ours: &str, theirs: &str) -> Result<Digest, SecioError> {
let (a, b) = match r {
Ordering::Less | Ordering::Equal => (theirs, ours),
Ordering::Greater => (ours, theirs)
};
for x in a.split(',') {
if b.split(',').any(|y| x == y) {
match x {
SHA_256 => return Ok(Digest::Sha256),
SHA_512 => return Ok(Digest::Sha512),
_ => continue
}
}
}
Err(SecioError::NoSupportIntersection)
}
#[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"));
}
}

View File

@ -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<S> = DecoderMiddleware<EncoderMiddleware<LenPrefixCodec<S>>>;
pub type StreamCipher = Box<dyn stream_cipher::StreamCipher + Send>;
#[derive(Debug, Clone)]
pub enum Hmac {
Sha256(hmac::Hmac<Sha256>),
Sha512(hmac::Hmac<Sha512>),
}
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<u8> {
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<u8>` 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<S>(
socket: LenPrefixCodec<S>,
cipher_encoding: StreamCipher,
encoding_hmac: Hmac,
cipher_decoder: StreamCipher,
decoding_hmac: Hmac,
remote_nonce: Vec<u8>
) -> FullCodec<S>
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::<Vec<u8>>(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);
}
}

View File

@ -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<Item = Vec<u8>>`. 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<S> {
cipher_state: StreamCipher,
hmac: Hmac,
#[pin]
raw_stream: S,
nonce: Vec<u8>
}
impl<S> DecoderMiddleware<S> {
/// 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<u8>) -> DecoderMiddleware<S> {
DecoderMiddleware {
cipher_state: cipher,
hmac,
raw_stream,
nonce
}
}
}
impl<S> Stream for DecoderMiddleware<S>
where
S: TryStream<Ok = Vec<u8>>,
S::Error: Into<SecioError>,
{
type Item = Result<Vec<u8>, SecioError>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
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<S, I> Sink<I> for DecoderMiddleware<S>
where
S: Sink<I>,
{
type Error = S::Error;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
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<Result<(), Self::Error>> {
let this = self.project();
Sink::poll_flush(this.raw_stream, cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let this = self.project();
Sink::poll_close(this.raw_stream, cx)
}
}

View File

@ -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<S> {
cipher_state: StreamCipher,
hmac: Hmac,
#[pin]
raw_sink: S,
}
impl<S> EncoderMiddleware<S> {
pub fn new(raw: S, cipher: StreamCipher, hmac: Hmac) -> EncoderMiddleware<S> {
EncoderMiddleware {
cipher_state: cipher,
hmac,
raw_sink: raw,
}
}
}
impl<S> Sink<Vec<u8>> for EncoderMiddleware<S>
where
S: Sink<Vec<u8>>,
{
type Error = S::Error;
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let this = self.project();
Sink::poll_ready(this.raw_sink, cx)
}
fn start_send(self: Pin<&mut Self>, mut data_buf: Vec<u8>) -> 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<Result<(), Self::Error>> {
let this = self.project();
Sink::poll_flush(this.raw_sink, cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
let this = self.project();
Sink::poll_close(this.raw_sink, cx)
}
}
impl<S> Stream for EncoderMiddleware<S>
where
S: Stream,
{
type Item = S::Item;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
Stream::poll_next(this.raw_sink, cx)
}
}

View File

@ -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<T> {
stream: BoxStream<'static, io::Result<Vec<u8>>>,
sink: Pin<Box<dyn Sink<Vec<u8>, Error = io::Error> + Send>>,
_mark: std::marker::PhantomData<T>
}
impl<T> fmt::Debug for LenPrefixCodec<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("LenPrefixCodec")
}
}
static_assertions::const_assert! {
std::mem::size_of::<u32>() <= std::mem::size_of::<usize>()
}
impl<T> LenPrefixCodec<T>
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<Vec<u8>>| 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<T> Stream for LenPrefixCodec<T>
where
T: AsyncRead + AsyncWrite + Send + 'static
{
type Item = io::Result<Vec<u8>>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.stream.poll_next_unpin(cx)
}
}
impl<T> Sink<Vec<u8>> for LenPrefixCodec<T>
where
T: AsyncRead + AsyncWrite + Send + 'static
{
type Error = io::Error;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.sink).poll_ready(cx)
}
fn start_send(mut self: Pin<&mut Self>, item: Vec<u8>) -> Result<(), Self::Error> {
Pin::new(&mut self.sink).start_send(item)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.sink).poll_flush(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.sink).poll_close(cx)
}
}
impl<T> Unpin for LenPrefixCodec<T> {
}

View File

@ -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<LoopError> for SecioError {
fn from(err: LoopError) -> SecioError {
SecioError::CipherError(err)
}
}
impl From<IoError> for SecioError {
fn from(err: IoError) -> SecioError {
SecioError::IoError(err)
}
}
impl From<prost::DecodeError> for SecioError {
fn from(err: prost::DecodeError) -> SecioError {
SecioError::ProtobufError(err)
}
}

View File

@ -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<Output = Result<(AgreementPrivateKey, Vec<u8>), 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<Output = Result<Vec<u8>, SecioError>>
{
platform::agree(algorithm, my_private_key.0, other_public_key, out_size)
}

View File

@ -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<Output = Result<(AgreementPrivateKey, Vec<u8>), 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<Output = Result<Vec<u8>, 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)
}

View File

@ -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<T>(SendWrapper<T>);
impl<T> Future for SendSyncHack<T>
where T: Future + Unpin {
type Output = T::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
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<Output = Result<(AgreementPrivateKey, Vec<u8>), 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<u8>`.
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<Output = Result<Vec<u8>, 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::SubtleCrypto, JsValue> {
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
}

View File

@ -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<S>(socket: S, config: SecioConfig)
-> Result<(FullCodec<S>, PublicKey, Vec<u8>), 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<u8> 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<u8> 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<D>(hmac: ::hmac::Hmac<D>, result: &mut [u8])
where D: ::hmac::digest::Update + ::hmac::digest::BlockInput +
::hmac::digest::FixedOutput + ::hmac::digest::Reset + Default + Clone,
::hmac::Hmac<D>: 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,
]
);
}
}

View File

@ -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<String>,
pub(crate) ciphers_prop: Option<String>,
pub(crate) digests_prop: Option<String>,
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<Item=&'a KeyAgreement>
{
self.agreements_prop = Some(algo_support::key_agreements_proposition(xs));
self
}
/// Override the default set of supported ciphers.
pub fn ciphers<'a, I>(mut self, xs: I) -> Self
where
I: IntoIterator<Item=&'a Cipher>
{
self.ciphers_prop = Some(algo_support::ciphers_proposition(xs));
self
}
/// Override the default set of supported digest algorithms.
pub fn digests<'a, I>(mut self, xs: I) -> Self
where
I: IntoIterator<Item=&'a Digest>
{
self.digests_prop = Some(algo_support::digests_proposition(xs));
self
}
/// 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<T>(self, socket: T) -> impl Future<Output = Result<(PeerId, SecioOutput<T>), 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<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
/// The encrypted stream.
pub stream: RwStreamSink<StreamMapErr<SecioMiddleware<S>, 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<u8>,
}
impl UpgradeInfo for SecioConfig {
type Info = &'static [u8];
type InfoIter = iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
iter::once(b"/secio/1.0.0")
}
}
impl<T> InboundUpgrade<T> for SecioConfig
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
type Output = (PeerId, SecioOutput<T>);
type Error = SecioError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future {
Box::pin(self.handshake(socket))
}
}
impl<T> OutboundUpgrade<T> for SecioConfig
where
T: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
type Output = (PeerId, SecioOutput<T>);
type Error = SecioError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send>>;
fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future {
Box::pin(self.handshake(socket))
}
}
impl<S> AsyncRead for SecioOutput<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8])
-> Poll<Result<usize, io::Error>>
{
AsyncRead::poll_read(Pin::new(&mut self.stream), cx, buf)
}
}
impl<S> AsyncWrite for SecioOutput<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8])
-> Poll<Result<usize, io::Error>>
{
AsyncWrite::poll_write(Pin::new(&mut self.stream), cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<Result<(), io::Error>>
{
AsyncWrite::poll_flush(Pin::new(&mut self.stream), cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>)
-> Poll<Result<(), io::Error>>
{
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<S> {
inner: codec::FullCodec<S>,
}
impl<S> SecioMiddleware<S>
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<Output = Result<(SecioMiddleware<S>, PublicKey, Vec<u8>), SecioError>>
{
handshake::handshake(socket, config).map_ok(|(inner, pubkey, ephemeral)| {
let inner = SecioMiddleware { inner };
(inner, pubkey, ephemeral)
})
}
}
impl<S> Sink<Vec<u8>> for SecioMiddleware<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
type Error = io::Error;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Sink::poll_ready(Pin::new(&mut self.inner), cx)
}
fn start_send(mut self: Pin<&mut Self>, item: Vec<u8>) -> Result<(), Self::Error> {
Sink::start_send(Pin::new(&mut self.inner), item)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Sink::poll_flush(Pin::new(&mut self.inner), cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Sink::poll_close(Pin::new(&mut self.inner), cx)
}
}
impl<S> Stream for SecioMiddleware<S>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static
{
type Item = Result<Vec<u8>, SecioError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Stream::poll_next(Pin::new(&mut self.inner), cx)
}
}

View File

@ -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<Aes128>;
/// AES-256 in CTR mode
pub type Aes256Ctr = Ctr128<Aes256>;
/// 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::<Twofish>::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::<Twofish>::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."
);

View File

@ -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;
}

View File

@ -37,8 +37,6 @@ pub trait TransportExt: Transport {
{
BandwidthLogging::new(self)
}
// TODO: add methods to easily upgrade for secio/mplex/yamux
}
impl<TTransport> TransportExt for TTransport where TTransport: Transport {}