2017-10-30 10:22:38 +01:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
//! # Implementation of the `secio` protocol.
|
|
|
|
//!
|
|
|
|
//! The `secio` protocol is a middleware that will encrypt and decrypt communications going
|
|
|
|
//! through a socket (or anything that implements `AsyncRead + AsyncWrite`).
|
|
|
|
//!
|
|
|
|
//! You can add the `secio` layer over a socket by calling `SecioMiddleware::handshake()`. This
|
|
|
|
//! method will perform a handshake with the host, and return a future that corresponds to the
|
|
|
|
//! moment when the handshake succeeds or errored. On success, the future produces a
|
|
|
|
//! `SecioMiddleware` that implements `Sink` and `Stream` and can be used to send packets of data.
|
2017-11-02 11:58:02 +01:00
|
|
|
//!
|
|
|
|
//! However for integration with the rest of `libp2p` you are encouraged to use the
|
2017-12-04 15:50:14 +01:00
|
|
|
//! `SecioConfig` struct instead. This struct implements the `ConnectionUpgrade` trait and
|
2017-11-02 11:58:02 +01:00
|
|
|
//! will automatically apply secio on any incoming or outgoing connection.
|
2017-10-30 10:22:38 +01:00
|
|
|
|
|
|
|
extern crate bytes;
|
|
|
|
extern crate crypto;
|
|
|
|
extern crate futures;
|
2017-11-02 11:58:02 +01:00
|
|
|
extern crate libp2p_swarm;
|
2017-10-30 10:22:38 +01:00
|
|
|
extern crate protobuf;
|
|
|
|
extern crate rand;
|
|
|
|
extern crate ring;
|
2017-11-02 11:58:02 +01:00
|
|
|
extern crate rw_stream_sink;
|
2017-10-30 10:22:38 +01:00
|
|
|
extern crate tokio_core;
|
|
|
|
extern crate tokio_io;
|
|
|
|
extern crate untrusted;
|
|
|
|
|
|
|
|
pub use self::error::SecioError;
|
|
|
|
|
2017-11-02 11:58:02 +01:00
|
|
|
use bytes::{Bytes, BytesMut};
|
2017-10-30 10:22:38 +01:00
|
|
|
use futures::{Future, Poll, StartSend, Sink, Stream};
|
2017-11-02 11:58:02 +01:00
|
|
|
use futures::stream::MapErr as StreamMapErr;
|
2017-10-30 10:22:38 +01:00
|
|
|
use ring::signature::RSAKeyPair;
|
2017-11-02 11:58:02 +01:00
|
|
|
use rw_stream_sink::RwStreamSink;
|
2017-12-04 15:39:40 +01:00
|
|
|
use std::error::Error;
|
2017-11-02 11:58:02 +01:00
|
|
|
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
|
|
|
|
use std::iter;
|
2017-10-30 10:22:38 +01:00
|
|
|
use std::sync::Arc;
|
|
|
|
use tokio_io::{AsyncRead, AsyncWrite};
|
2017-12-04 15:39:40 +01:00
|
|
|
use untrusted::Input;
|
2017-10-30 10:22:38 +01:00
|
|
|
|
|
|
|
mod algo_support;
|
|
|
|
mod codec;
|
|
|
|
mod error;
|
|
|
|
mod keys_proto;
|
|
|
|
mod handshake;
|
|
|
|
mod structs_proto;
|
|
|
|
|
2017-11-02 11:58:02 +01:00
|
|
|
/// Implementation of the `ConnectionUpgrade` trait of `libp2p_swarm`. Automatically applies any
|
|
|
|
/// secio on any connection.
|
|
|
|
#[derive(Clone)]
|
2017-12-04 15:50:14 +01:00
|
|
|
pub struct SecioConfig {
|
2017-12-04 15:39:40 +01:00
|
|
|
/// Private and public keys of the local node.
|
|
|
|
pub key: SecioKeyPair,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Private and public keys of the local node.
|
|
|
|
///
|
|
|
|
/// # Generating offline keys with OpenSSL
|
|
|
|
///
|
|
|
|
/// ## RSA
|
|
|
|
///
|
|
|
|
/// Generating the keys:
|
|
|
|
///
|
|
|
|
/// ```ignore
|
|
|
|
/// openssl genrsa -out private.pem 2048
|
|
|
|
/// openssl rsa -in private.pem -outform DER -pubout -out public.der
|
|
|
|
/// openssl pkcs8 -in private.pem -topk8 -nocrypt -out private.pk8
|
|
|
|
/// rm private.pem # optional
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Loading the keys:
|
|
|
|
///
|
|
|
|
/// ```ignore
|
|
|
|
/// ley key_pair = SecioKeyPair::rsa_from_pkcs8(include_bytes!("private.pk8"),
|
|
|
|
/// include_bytes!("public.der"));
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct SecioKeyPair {
|
|
|
|
inner: SecioKeyPairInner,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SecioKeyPair {
|
|
|
|
pub fn rsa_from_pkcs8<P>(private: &[u8], public: P)
|
|
|
|
-> Result<SecioKeyPair, Box<Error + Send + Sync>>
|
|
|
|
where P: Into<Vec<u8>>
|
|
|
|
{
|
|
|
|
let private = RSAKeyPair::from_pkcs8(Input::from(&private[..]))
|
|
|
|
.map_err(|err| Box::new(err))?;
|
|
|
|
|
|
|
|
Ok(SecioKeyPair {
|
|
|
|
inner: SecioKeyPairInner::Rsa {
|
|
|
|
public: public.into(),
|
|
|
|
private: Arc::new(private),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inner content of `SecioKeyPair`.
|
|
|
|
#[derive(Clone)]
|
|
|
|
enum SecioKeyPairInner {
|
|
|
|
Rsa {
|
|
|
|
public: Vec<u8>,
|
|
|
|
private: Arc<RSAKeyPair>,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum SecioPublicKey<'a> {
|
|
|
|
/// DER format.
|
|
|
|
Rsa(&'a [u8]),
|
2017-11-02 11:58:02 +01:00
|
|
|
}
|
|
|
|
|
2017-12-04 15:50:14 +01:00
|
|
|
impl<S> libp2p_swarm::ConnectionUpgrade<S> for SecioConfig
|
2017-11-02 11:58:02 +01:00
|
|
|
where S: AsyncRead + AsyncWrite + 'static
|
|
|
|
{
|
|
|
|
type Output = RwStreamSink<
|
|
|
|
StreamMapErr<
|
|
|
|
SecioMiddleware<S>,
|
|
|
|
fn(SecioError) -> IoError,
|
|
|
|
>,
|
|
|
|
>;
|
|
|
|
type Future = Box<Future<Item = Self::Output, Error = IoError>>;
|
|
|
|
type NamesIter = iter::Once<(Bytes, ())>;
|
|
|
|
type UpgradeIdentifier = ();
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn protocol_names(&self) -> Self::NamesIter {
|
|
|
|
iter::once(("/secio/1.0.0".into(), ()))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn upgrade(self, incoming: S, _: ()) -> Self::Future {
|
|
|
|
let fut = SecioMiddleware::handshake(
|
|
|
|
incoming,
|
2017-12-04 15:39:40 +01:00
|
|
|
self.key,
|
2017-11-02 11:58:02 +01:00
|
|
|
);
|
|
|
|
let wrapped = fut.map(|stream_sink| {
|
|
|
|
let mapped = stream_sink.map_err(map_err as fn(_) -> _);
|
|
|
|
RwStreamSink::new(mapped)
|
|
|
|
}).map_err(map_err);
|
|
|
|
Box::new(wrapped)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn map_err(err: SecioError) -> IoError {
|
|
|
|
IoError::new(IoErrorKind::InvalidData, err)
|
|
|
|
}
|
|
|
|
|
2017-10-30 10:22:38 +01:00
|
|
|
/// 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>,
|
|
|
|
remote_pubkey_der: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S> SecioMiddleware<S>
|
2017-11-13 10:27:33 +01:00
|
|
|
where S: AsyncRead + AsyncWrite
|
2017-10-30 10:22:38 +01:00
|
|
|
{
|
|
|
|
/// Attempts to perform a handshake on the given socket.
|
|
|
|
///
|
|
|
|
/// On success, produces a `SecioMiddleware` that can then be used to encode/decode
|
|
|
|
/// communications.
|
|
|
|
pub fn handshake<'a>(
|
|
|
|
socket: S,
|
2017-12-04 15:39:40 +01:00
|
|
|
key_pair: SecioKeyPair,
|
2017-10-30 10:22:38 +01:00
|
|
|
) -> Box<Future<Item = SecioMiddleware<S>, Error = SecioError> + 'a>
|
|
|
|
where S: 'a
|
|
|
|
{
|
2017-12-04 15:39:40 +01:00
|
|
|
let SecioKeyPairInner::Rsa { private, public } = key_pair.inner;
|
|
|
|
|
|
|
|
let fut = handshake::handshake(socket, public, private)
|
2017-10-30 10:22:38 +01:00
|
|
|
.map(|(inner, pubkey)| {
|
|
|
|
SecioMiddleware {
|
|
|
|
inner: inner,
|
|
|
|
remote_pubkey_der: pubkey,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Box::new(fut)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the public key of the remote in the `DER` format.
|
|
|
|
#[inline]
|
2017-12-04 15:39:40 +01:00
|
|
|
pub fn remote_public_key_der(&self) -> SecioPublicKey {
|
|
|
|
SecioPublicKey::Rsa(&self.remote_pubkey_der)
|
2017-10-30 10:22:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S> Sink for SecioMiddleware<S>
|
2017-11-13 10:27:33 +01:00
|
|
|
where S: AsyncRead + AsyncWrite
|
2017-10-30 10:22:38 +01:00
|
|
|
{
|
|
|
|
type SinkItem = BytesMut;
|
|
|
|
type SinkError = IoError;
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
|
|
|
|
self.inner.start_send(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
|
|
|
|
self.inner.poll_complete()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S> Stream for SecioMiddleware<S>
|
2017-11-13 10:27:33 +01:00
|
|
|
where S: AsyncRead + AsyncWrite
|
2017-10-30 10:22:38 +01:00
|
|
|
{
|
|
|
|
type Item = Vec<u8>;
|
|
|
|
type Error = SecioError;
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
|
|
|
self.inner.poll()
|
|
|
|
}
|
|
|
|
}
|