mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-04-24 18:52:14 +00:00
[libp2p-secio] Remove crate. (#1940)
* Remove the SECIO transport protocol. * Remove stray mentions of secio.
This commit is contained in:
parent
1a01dd16cb
commit
2ecf42a2bf
@ -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)
|
||||
|
@ -114,7 +114,6 @@ members = [
|
||||
"protocols/plaintext",
|
||||
"protocols/pnet",
|
||||
"protocols/request-response",
|
||||
"protocols/secio",
|
||||
"swarm",
|
||||
"swarm-derive",
|
||||
"transports/dns",
|
||||
|
@ -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:
|
||||
/// >
|
||||
|
@ -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.
|
@ -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" }
|
@ -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();
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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> {
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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."
|
||||
);
|
@ -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;
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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 {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user