diff --git a/libp2p/examples/echo-dialer.rs b/libp2p/examples/echo-dialer.rs index 696eb33b..3cca46e1 100644 --- a/libp2p/examples/echo-dialer.rs +++ b/libp2p/examples/echo-dialer.rs @@ -63,8 +63,8 @@ fn main() { let plain_text = upgrade::PlainTextConfig; let secio = { - let private_key = include_bytes!("test-private-key.pk8"); - let public_key = include_bytes!("test-public-key.der").to_vec(); + let private_key = include_bytes!("test-rsa-private-key.pk8"); + let public_key = include_bytes!("test-rsa-public-key.der").to_vec(); libp2p::secio::SecioConfig { key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(), } diff --git a/libp2p/examples/echo-server.rs b/libp2p/examples/echo-server.rs index 307100fb..76aece4a 100644 --- a/libp2p/examples/echo-server.rs +++ b/libp2p/examples/echo-server.rs @@ -62,8 +62,8 @@ fn main() { let plain_text = upgrade::PlainTextConfig; let secio = { - let private_key = include_bytes!("test-private-key.pk8"); - let public_key = include_bytes!("test-public-key.der").to_vec(); + let private_key = include_bytes!("test-rsa-private-key.pk8"); + let public_key = include_bytes!("test-rsa-public-key.der").to_vec(); libp2p::secio::SecioConfig { key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(), } diff --git a/libp2p/examples/floodsub.rs b/libp2p/examples/floodsub.rs index cd2164c8..4bd74566 100644 --- a/libp2p/examples/floodsub.rs +++ b/libp2p/examples/floodsub.rs @@ -62,8 +62,8 @@ fn main() { let plain_text = upgrade::PlainTextConfig; let secio = { - let private_key = include_bytes!("test-private-key.pk8"); - let public_key = include_bytes!("test-public-key.der").to_vec(); + let private_key = include_bytes!("test-rsa-private-key.pk8"); + let public_key = include_bytes!("test-rsa-public-key.der").to_vec(); libp2p::secio::SecioConfig { key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(), } diff --git a/libp2p/examples/kademlia.rs b/libp2p/examples/kademlia.rs index 5e430a36..54ae1cf2 100644 --- a/libp2p/examples/kademlia.rs +++ b/libp2p/examples/kademlia.rs @@ -66,8 +66,8 @@ fn main() { let plain_text = upgrade::PlainTextConfig; let secio = { - let private_key = include_bytes!("test-private-key.pk8"); - let public_key = include_bytes!("test-public-key.der").to_vec(); + let private_key = include_bytes!("test-rsa-private-key.pk8"); + let public_key = include_bytes!("test-rsa-public-key.der").to_vec(); libp2p::secio::SecioConfig { key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(), } @@ -96,7 +96,7 @@ fn main() { // incoming connections, and that will automatically apply secio and multiplex on top // of any opened stream. - let my_peer_id = PeerId::from_public_key(include_bytes!("test-public-key.der")); + let my_peer_id = PeerId::from_public_key(include_bytes!("test-rsa-public-key.der")); println!("Local peer id is: {:?}", my_peer_id); // Let's put this `transport` into a Kademlia *swarm*. The swarm will handle all the incoming diff --git a/libp2p/examples/ping-client.rs b/libp2p/examples/ping-client.rs index 2574344e..2a9e61de 100644 --- a/libp2p/examples/ping-client.rs +++ b/libp2p/examples/ping-client.rs @@ -54,8 +54,8 @@ fn main() { let plain_text = upgrade::PlainTextConfig; let secio = { - let private_key = include_bytes!("test-private-key.pk8"); - let public_key = include_bytes!("test-public-key.der").to_vec(); + let private_key = include_bytes!("test-rsa-private-key.pk8"); + let public_key = include_bytes!("test-rsa-public-key.der").to_vec(); libp2p::secio::SecioConfig { key: libp2p::secio::SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(), } diff --git a/libp2p/examples/test-private-key.pk8 b/libp2p/examples/test-rsa-private-key.pk8 similarity index 100% rename from libp2p/examples/test-private-key.pk8 rename to libp2p/examples/test-rsa-private-key.pk8 diff --git a/libp2p/examples/test-public-key.der b/libp2p/examples/test-rsa-public-key.der similarity index 100% rename from libp2p/examples/test-public-key.der rename to libp2p/examples/test-rsa-public-key.der diff --git a/secio/README.md b/secio/README.md index 40ca6447..5a50baaa 100644 --- a/secio/README.md +++ b/secio/README.md @@ -28,9 +28,9 @@ let mut core = Core::new().unwrap(); let transport = TcpConfig::new(core.handle()) .with_upgrade({ # let private_key = b""; - //let private_key = include_bytes!("test-private-key.pk8"); + //let private_key = include_bytes!("test-rsa-private-key.pk8"); # let public_key = vec![]; - //let public_key = include_bytes!("test-public-key.der").to_vec(); + //let public_key = include_bytes!("test-rsa-public-key.der").to_vec(); SecioConfig { // See the documentation of `SecioKeyPair`. key: SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(), diff --git a/secio/src/handshake.rs b/secio/src/handshake.rs index 20347b1d..cfed718a 100644 --- a/secio/src/handshake.rs +++ b/secio/src/handshake.rs @@ -34,30 +34,29 @@ use ring::agreement::EphemeralPrivateKey; use ring::hmac::{SigningContext, SigningKey, VerificationKey}; use ring::rand::SecureRandom; use ring::signature::verify as signature_verify; -use ring::signature::{RSAKeyPair, RSASigningState, RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_SHA256}; +use ring::signature::{RSASigningState, RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_SHA256, ED25519}; use ring::{agreement, digest, rand}; use std::cmp::{self, Ordering}; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::mem; -use std::sync::Arc; use structs_proto::{Exchange, Propose}; use tokio_io::codec::length_delimited; use tokio_io::{AsyncRead, AsyncWrite}; use untrusted::Input as UntrustedInput; +use {SecioKeyPair, SecioKeyPairInner, SecioPublicKey}; /// 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_public_key`. Obviously `local_private_key` must -/// be paired with `local_public_key`. Any mismatch somewhere will produce a `SecioError`. +/// 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. pub fn handshake<'a, S: 'a>( socket: S, - local_public_key: Vec, - local_private_key: Arc, -) -> Box, Vec), Error = SecioError> + 'a> + local_key: SecioKeyPair, +) -> Box, SecioPublicKey), Error = SecioError> + 'a> where S: AsyncRead + AsyncWrite, { @@ -67,8 +66,7 @@ where // throughout the various parts of the handshake. struct HandshakeContext { // Filled with this function's parameters. - local_public_key: Vec, - local_private_key: Arc, + local_key: SecioKeyPairInner, rng: rand::SystemRandom, // Locally-generated random number. The array size can be changed without any repercussion. @@ -81,7 +79,7 @@ where // The remote proposition's raw bytes. remote_proposition_bytes: BytesMut, remote_public_key_in_protobuf_bytes: Vec, - remote_public_key: Vec, + remote_public_key: Option, // The remote peer's version of `local_nonce`. // If the NONCE size is actually part of the protocol, we can change this to a fixed-size @@ -110,15 +108,14 @@ where } let context = HandshakeContext { - local_public_key: local_public_key, - local_private_key: local_private_key, + local_key: local_key.inner, rng: rand::SystemRandom::new(), local_nonce: Default::default(), local_public_key_in_protobuf_bytes: Vec::new(), local_proposition_bytes: Vec::new(), remote_proposition_bytes: BytesMut::new(), remote_public_key_in_protobuf_bytes: Vec::new(), - remote_public_key: Vec::new(), + remote_public_key: None, remote_nonce: Vec::new(), hashes_ordering: Ordering::Equal, chosen_exchange: None, @@ -139,16 +136,23 @@ where .and_then(|mut context| { context.rng.fill(&mut context.local_nonce) .map_err(|_| SecioError::NonceGenerationFailed)?; - trace!("starting handshake ; local pubkey = {:?} ; local nonce = {:?}", - context.local_public_key, context.local_nonce); + trace!("starting handshake ; local nonce = {:?}", context.local_nonce); Ok(context) }) // Send our proposition with our nonce, public key and supported protocols. .and_then(|mut context| { let mut public_key = PublicKeyProtobuf::new(); - public_key.set_Type(KeyTypeProtobuf::RSA); - public_key.set_Data(context.local_public_key.clone()); + match context.local_key { + SecioKeyPairInner::Rsa { ref public, .. } => { + public_key.set_Type(KeyTypeProtobuf::RSA); + public_key.set_Data(public.clone()); + }, + SecioKeyPairInner::Ed25519 { ref key_pair } => { + public_key.set_Type(KeyTypeProtobuf::Ed25519); + public_key.set_Data(key_pair.public_key_bytes().to_owned()); + }, + } context.local_public_key_in_protobuf_bytes = public_key.write_to_bytes().unwrap(); let mut proposition = Propose::new(); @@ -202,18 +206,21 @@ where } }; - // TODO: For now we suppose that the key is in the RSA format because that's - // the only thing the Go and JS implementations support. - match pubkey.get_Type() { - KeyTypeProtobuf::RSA => (), + context.remote_nonce = prop.take_rand(); + context.remote_public_key = Some(match pubkey.get_Type() { + KeyTypeProtobuf::RSA => { + SecioPublicKey::Rsa(pubkey.take_Data()) + }, + KeyTypeProtobuf::Ed25519 => { + SecioPublicKey::Ed25519(pubkey.take_Data()) + }, format => { + // TODO: support secp255k1 let err = IoError::new(IoErrorKind::Other, "unsupported protocol"); debug!("unsupported remote pubkey format {:?}", format); return Err(err.into()); }, - }; - context.remote_public_key = pubkey.take_Data(); - context.remote_nonce = prop.take_rand(); + }); trace!("received proposition from remote ; pubkey = {:?} ; nonce = {:?}", context.remote_public_key, context.remote_nonce); Ok((prop, socket, context)) @@ -302,25 +309,33 @@ where let mut exchange = Exchange::new(); exchange.set_epubkey(local_tmp_pub_key.to_vec()); exchange.set_signature({ - let mut state = match RSASigningState::new(context.local_private_key.clone()) { - Ok(s) => s, - Err(_) => { - debug!("failed to sign local exchange"); - return Err(SecioError::SigningFailure); - }, - }; - let mut signature = vec![0; context.local_private_key.public_modulus_len()]; - match state.sign(&RSA_PKCS1_SHA256, &context.rng, &data_to_sign, - &mut signature) - { - Ok(_) => (), - Err(_) => { - debug!("failed to sign local exchange"); - return Err(SecioError::SigningFailure); - }, - }; + match context.local_key { + SecioKeyPairInner::Rsa { ref private, .. } => { + let mut state = match RSASigningState::new(private.clone()) { + Ok(s) => s, + Err(_) => { + debug!("failed to sign local exchange"); + return Err(SecioError::SigningFailure); + }, + }; + let mut signature = vec![0; private.public_modulus_len()]; + match state.sign(&RSA_PKCS1_SHA256, &context.rng, &data_to_sign, + &mut signature) + { + Ok(_) => (), + Err(_) => { + debug!("failed to sign local exchange"); + return Err(SecioError::SigningFailure); + }, + }; - signature + signature + }, + SecioKeyPairInner::Ed25519 { ref key_pair } => { + let signature = key_pair.sign(&data_to_sign); + signature.as_ref().to_owned() + }, + } }); exchange }; @@ -374,20 +389,39 @@ where data_to_verify.extend_from_slice(&context.local_proposition_bytes); data_to_verify.extend_from_slice(remote_exch.get_epubkey()); - // TODO: The ring library doesn't like some stuff in our DER public key, therefore - // we scrap the first 24 bytes of the key. A proper fix would be to write a DER - // parser, but that's not trivial. - match signature_verify(&RSA_PKCS1_2048_8192_SHA256, - UntrustedInput::from(&context.remote_public_key[24..]), - UntrustedInput::from(&data_to_verify), - UntrustedInput::from(remote_exch.get_signature())) - { - Ok(()) => (), - Err(_) => { - debug!("failed to verify the remote's signature"); - return Err(SecioError::SignatureVerificationFailed) + match context.remote_public_key { + Some(SecioPublicKey::Rsa(ref remote_public_key)) => { + // TODO: The ring library doesn't like some stuff in our DER public key, + // therefore we scrap the first 24 bytes of the key. A proper fix would + // be to write a DER parser, but that's not trivial. + match signature_verify(&RSA_PKCS1_2048_8192_SHA256, + UntrustedInput::from(&remote_public_key[24..]), + UntrustedInput::from(&data_to_verify), + UntrustedInput::from(remote_exch.get_signature())) + { + Ok(()) => (), + Err(_) => { + debug!("failed to verify the remote's signature"); + return Err(SecioError::SignatureVerificationFailed) + }, + } }, - } + Some(SecioPublicKey::Ed25519(ref remote_public_key)) => { + match signature_verify(&ED25519, + UntrustedInput::from(remote_public_key), + UntrustedInput::from(&data_to_verify), + UntrustedInput::from(remote_exch.get_signature())) + { + Ok(()) => (), + Err(_) => { + debug!("failed to verify the remote's signature"); + return Err(SecioError::SignatureVerificationFailed) + }, + } + }, + None => unreachable!("we store a Some in the remote public key before reaching \ + this point") + }; trace!("successfully verified the remote's signature"); Ok((remote_exch, socket, context)) @@ -470,7 +504,7 @@ where match nonce { Some(ref n) if n == &context.local_nonce => { trace!("secio handshake success"); - Ok((rest, context.remote_public_key)) + Ok((rest, context.remote_public_key.expect("we stored a Some earlier"))) }, None => { debug!("unexpected eof during nonce check"); @@ -527,25 +561,23 @@ mod tests { use futures::Stream; use ring::digest::SHA256; use ring::hmac::SigningKey; - use ring::signature::RSAKeyPair; - use std::sync::Arc; - use untrusted::Input; + use SecioKeyPair; #[test] - fn handshake_with_self_succeeds() { + fn handshake_with_self_succeeds_rsa() { let mut core = Core::new().unwrap(); - let private_key1 = { - let pkcs8 = include_bytes!("../tests/test-private-key.pk8"); - Arc::new(RSAKeyPair::from_pkcs8(Input::from(&pkcs8[..])).unwrap()) + let key1 = { + let private = include_bytes!("../tests/test-rsa-private-key.pk8"); + let public = include_bytes!("../tests/test-rsa-public-key.der").to_vec(); + SecioKeyPair::rsa_from_pkcs8(private, public).unwrap() }; - let public_key1 = include_bytes!("../tests/test-public-key.der").to_vec(); - let private_key2 = { - let pkcs8 = include_bytes!("../tests/test-private-key-2.pk8"); - Arc::new(RSAKeyPair::from_pkcs8(Input::from(&pkcs8[..])).unwrap()) + let key2 = { + let private = include_bytes!("../tests/test-rsa-private-key-2.pk8"); + let public = include_bytes!("../tests/test-rsa-public-key-2.der").to_vec(); + SecioKeyPair::rsa_from_pkcs8(private, public).unwrap() }; - let public_key2 = include_bytes!("../tests/test-public-key-2.der").to_vec(); let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap(), &core.handle()).unwrap(); let listener_addr = listener.local_addr().unwrap(); @@ -554,11 +586,34 @@ mod tests { .incoming() .into_future() .map_err(|(e, _)| e.into()) - .and_then(move |(connec, _)| handshake(connec.unwrap().0, public_key1, private_key1)); + .and_then(move |(connec, _)| handshake(connec.unwrap().0, key1)); let client = TcpStream::connect(&listener_addr, &core.handle()) .map_err(|e| e.into()) - .and_then(move |stream| handshake(stream, public_key2, private_key2)); + .and_then(move |stream| handshake(stream, key2)); + + core.run(server.join(client)).unwrap(); + } + + #[test] + fn handshake_with_self_succeeds_ed25519() { + let mut core = Core::new().unwrap(); + + let key1 = SecioKeyPair::ed25519_generated().unwrap(); + let key2 = SecioKeyPair::ed25519_generated().unwrap(); + + let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap(), &core.handle()).unwrap(); + let listener_addr = listener.local_addr().unwrap(); + + let server = listener + .incoming() + .into_future() + .map_err(|(e, _)| e.into()) + .and_then(move |(connec, _)| handshake(connec.unwrap().0, key1)); + + let client = TcpStream::connect(&listener_addr, &core.handle()) + .map_err(|e| e.into()) + .and_then(move |stream| handshake(stream, key2)); core.run(server.join(client)).unwrap(); } diff --git a/secio/src/lib.rs b/secio/src/lib.rs index 79ad6741..b6d80997 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -49,9 +49,9 @@ //! let transport = TcpConfig::new(core.handle()) //! .with_upgrade({ //! # let private_key = b""; -//! //let private_key = include_bytes!("test-private-key.pk8"); +//! //let private_key = include_bytes!("test-rsa-private-key.pk8"); //! # let public_key = vec![]; -//! //let public_key = include_bytes!("test-public-key.der").to_vec(); +//! //let public_key = include_bytes!("test-rsa-public-key.der").to_vec(); //! let upgrade = SecioConfig { //! // See the documentation of `SecioKeyPair`. //! key: SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(), @@ -99,8 +99,9 @@ pub use self::error::SecioError; use bytes::{Bytes, BytesMut}; use futures::stream::MapErr as StreamMapErr; use futures::{Future, Poll, Sink, StartSend, Stream}; -use libp2p_core::Multiaddr; -use ring::signature::RSAKeyPair; +use libp2p_core::{Multiaddr, PeerId}; +use ring::signature::{Ed25519KeyPair, RSAKeyPair}; +use ring::rand::SystemRandom; use rw_stream_sink::RwStreamSink; use std::error::Error; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; @@ -152,6 +153,7 @@ pub struct SecioKeyPair { } impl SecioKeyPair { + /// Builds a `SecioKeyPair` from a PKCS8 private key and public key. pub fn rsa_from_pkcs8

( private: &[u8], public: P, @@ -169,6 +171,30 @@ impl SecioKeyPair { }, }) } + + /// Builds a `SecioKeyPair` from a PKCS8 ED25519 private key. + pub fn ed25519_from_pkcs8(key: K) -> Result> + where K: AsRef<[u8]> + { + let key_pair = + Ed25519KeyPair::from_pkcs8(Input::from(key.as_ref())).map_err(|err| Box::new(err))?; + + Ok(SecioKeyPair { + inner: SecioKeyPairInner::Ed25519 { + key_pair: Arc::new(key_pair), + }, + }) + } + + /// Generates a new Ed25519 key pair and uses it. + pub fn ed25519_generated() -> Result> { + let rng = SystemRandom::new(); + let gen = Ed25519KeyPair::generate_pkcs8(&rng).map_err(|err| Box::new(err))?; + Ok(SecioKeyPair::ed25519_from_pkcs8(&gen[..]) + .expect("failed to parse generated Ed25519 key")) + } + + // TODO: method to save generated key on disk? } // Inner content of `SecioKeyPair`. @@ -176,14 +202,41 @@ impl SecioKeyPair { enum SecioKeyPairInner { Rsa { public: Vec, + // We use an `Arc` so that we can clone the enum. private: Arc, }, + Ed25519 { + // We use an `Arc` so that we can clone the enum. + key_pair: Arc, + }, } +/// Public key used by the remote. #[derive(Debug, Clone)] pub enum SecioPublicKey { /// DER format. Rsa(Vec), + /// Format = ??? + // TODO: ^ + Ed25519(Vec), +} + +impl SecioPublicKey { + /// Builds a `PeerId` corresponding to the public key of the node. + #[inline] + pub fn to_peer_id(&self) -> PeerId { + match self { + &SecioPublicKey::Rsa(ref data) => PeerId::from_public_key(data), + &SecioPublicKey::Ed25519(ref data) => PeerId::from_public_key(data), + } + } +} + +impl From for PeerId { + #[inline] + fn from(key: SecioPublicKey) -> PeerId { + key.to_peer_id() + } } impl libp2p_core::ConnectionUpgrade for SecioConfig @@ -251,12 +304,11 @@ where where S: 'a, { - let SecioKeyPairInner::Rsa { private, public } = key_pair.inner; - - let fut = handshake::handshake(socket, public, private).map(|(inner, pubkey)| { + let fut = handshake::handshake(socket, key_pair).map(|(inner, pubkey)| { let inner = SecioMiddleware { inner }; - (inner, SecioPublicKey::Rsa(pubkey)) + (inner, pubkey) }); + Box::new(fut) } } diff --git a/secio/tests/test-private-key-2.pk8 b/secio/tests/test-rsa-private-key-2.pk8 similarity index 100% rename from secio/tests/test-private-key-2.pk8 rename to secio/tests/test-rsa-private-key-2.pk8 diff --git a/secio/tests/test-private-key.pk8 b/secio/tests/test-rsa-private-key.pk8 similarity index 100% rename from secio/tests/test-private-key.pk8 rename to secio/tests/test-rsa-private-key.pk8 diff --git a/secio/tests/test-public-key-2.der b/secio/tests/test-rsa-public-key-2.der similarity index 100% rename from secio/tests/test-public-key-2.der rename to secio/tests/test-rsa-public-key-2.der diff --git a/secio/tests/test-public-key.der b/secio/tests/test-rsa-public-key.der similarity index 100% rename from secio/tests/test-public-key.der rename to secio/tests/test-rsa-public-key.der