From 1607fcb3f4aaa8ce42a1423ffdadb941a2eba31c Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 20 Jun 2018 09:47:43 +0200 Subject: [PATCH] Add support for secp256k1 in secio (#258) --- secio/Cargo.toml | 2 + secio/src/handshake.rs | 97 +++++++++++++------ secio/src/lib.rs | 51 ++++++++++ secio/tests/test-secp256k1-private-key-2.der | Bin 0 -> 118 bytes secio/tests/test-secp256k1-private-key.der | Bin 0 -> 118 bytes 5 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 secio/tests/test-secp256k1-private-key-2.der create mode 100644 secio/tests/test-secp256k1-private-key.der diff --git a/secio/Cargo.toml b/secio/Cargo.toml index c35f595b..81fc7b46 100644 --- a/secio/Cargo.toml +++ b/secio/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Parity Technologies "] [dependencies] +asn1_der = "0.5" bytes = "0.4" futures = "0.1" libp2p-core = { path = "../core" } @@ -13,6 +14,7 @@ rand = "0.3.17" ring = { version = "0.12.1", features = ["rsa_signing"] } rust-crypto = "^0.2" rw-stream-sink = { path = "../rw-stream-sink" } +secp256k1 = "0.9" tokio-io = "0.1.0" untrusted = "0.5.1" diff --git a/secio/src/handshake.rs b/secio/src/handshake.rs index ecd5ffca..d7853899 100644 --- a/secio/src/handshake.rs +++ b/secio/src/handshake.rs @@ -36,6 +36,7 @@ use ring::rand::SecureRandom; use ring::signature::verify as signature_verify; use ring::signature::{RSASigningState, RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_SHA256, ED25519}; use ring::{agreement, digest, rand}; +use secp256k1; use std::cmp::{self, Ordering}; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::mem; @@ -66,7 +67,7 @@ where // throughout the various parts of the handshake. struct HandshakeContext { // Filled with this function's parameters. - local_key: SecioKeyPairInner, + local_key: SecioKeyPair, rng: rand::SystemRandom, // Locally-generated random number. The array size can be changed without any repercussion. @@ -108,7 +109,7 @@ where } let context = HandshakeContext { - local_key: local_key.inner, + local_key: local_key, rng: rand::SystemRandom::new(), local_nonce: Default::default(), local_public_key_in_protobuf_bytes: Vec::new(), @@ -143,14 +144,16 @@ where // Send our proposition with our nonce, public key and supported protocols. .and_then(|mut context| { let mut public_key = PublicKeyProtobuf::new(); - match context.local_key { - SecioKeyPairInner::Rsa { ref public, .. } => { + public_key.set_Data(context.local_key.to_public_key().into_raw().0); + match context.local_key.inner { + SecioKeyPairInner::Rsa { .. } => { public_key.set_Type(KeyTypeProtobuf::RSA); - public_key.set_Data(public.clone()); }, - SecioKeyPairInner::Ed25519 { ref key_pair } => { + SecioKeyPairInner::Ed25519 { .. } => { public_key.set_Type(KeyTypeProtobuf::Ed25519); - public_key.set_Data(key_pair.public_key_bytes().to_owned()); + }, + SecioKeyPairInner::Secp256k1 { .. } => { + public_key.set_Type(KeyTypeProtobuf::Secp256k1); }, } context.local_public_key_in_protobuf_bytes = public_key.write_to_bytes().unwrap(); @@ -214,11 +217,8 @@ where 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()); + KeyTypeProtobuf::Secp256k1 => { + SecioPublicKey::Secp256k1(pubkey.take_Data()) }, }); trace!("received proposition from remote ; pubkey = {:?} ; nonce = {:?}", @@ -309,7 +309,7 @@ where let mut exchange = Exchange::new(); exchange.set_epubkey(local_tmp_pub_key.to_vec()); exchange.set_signature({ - match context.local_key { + match context.local_key.inner { SecioKeyPairInner::Rsa { ref private, .. } => { let mut state = match RSASigningState::new(private.clone()) { Ok(s) => s, @@ -335,6 +335,16 @@ where let signature = key_pair.sign(&data_to_sign); signature.as_ref().to_owned() }, + SecioKeyPairInner::Secp256k1 { ref private } => { + let data_to_sign = digest::digest(&digest::SHA256, &data_to_sign); + let message = secp256k1::Message::from_slice(data_to_sign.as_ref()) + .expect("digest output length doesn't match secp256k1 input length"); + let secp256k1 = secp256k1::Secp256k1::with_caps(secp256k1::ContextFlag::SignOnly); + secp256k1 + .sign(&message, private) + .expect("failed to sign message") + .serialize_der(&secp256k1) + }, } }); exchange @@ -419,6 +429,26 @@ where }, } }, + Some(SecioPublicKey::Secp256k1(ref remote_public_key)) => { + let data_to_verify = digest::digest(&digest::SHA256, &data_to_verify); + let message = secp256k1::Message::from_slice(data_to_verify.as_ref()) + .expect("digest output length doesn't match secp256k1 input length"); + let secp256k1 = secp256k1::Secp256k1::with_caps(secp256k1::ContextFlag::VerifyOnly); + let signature = secp256k1::Signature::from_der(&secp256k1, remote_exch.get_signature()); + let remote_public_key = secp256k1::key::PublicKey::from_slice(&secp256k1, remote_public_key); + if let (Ok(signature), Ok(remote_public_key)) = (signature, remote_public_key) { + match secp256k1.verify(&message, &signature, &remote_public_key) { + Ok(()) => (), + Err(_) => { + debug!("failed to verify the remote's signature"); + return Err(SecioError::SignatureVerificationFailed) + }, + } + } else { + debug!("remote's secp256k1 signature has wrong format"); + return Err(SecioError::SignatureVerificationFailed) + } + }, None => unreachable!("we store a Some in the remote public key before reaching \ this point") }; @@ -565,8 +595,6 @@ mod tests { #[test] fn handshake_with_self_succeeds_rsa() { - let mut core = Core::new().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(); @@ -578,29 +606,34 @@ mod tests { let public = include_bytes!("../tests/test-rsa-public-key-2.der").to_vec(); SecioKeyPair::rsa_from_pkcs8(private, public).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(); + + handshake_with_self_succeeds(key1, key2); } #[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(); + handshake_with_self_succeeds(key1, key2); + } + + #[test] + fn handshake_with_self_succeeds_secp256k1() { + let key1 = { + let key = include_bytes!("../tests/test-secp256k1-private-key.der"); + SecioKeyPair::secp256k1_from_der(&key[..]).unwrap() + }; + + let key2 = { + let key = include_bytes!("../tests/test-secp256k1-private-key-2.der"); + SecioKeyPair::secp256k1_from_der(&key[..]).unwrap() + }; + + handshake_with_self_succeeds(key1, key2); + } + + fn handshake_with_self_succeeds(key1: SecioKeyPair, key2: SecioKeyPair) { + let mut core = Core::new().unwrap(); let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap(), &core.handle()).unwrap(); let listener_addr = listener.local_addr().unwrap(); diff --git a/secio/src/lib.rs b/secio/src/lib.rs index b9e243f8..2c7eb279 100644 --- a/secio/src/lib.rs +++ b/secio/src/lib.rs @@ -81,6 +81,7 @@ //! `SecioMiddleware` that implements `Sink` and `Stream` and can be used to send packets of data. //! +extern crate asn1_der; extern crate bytes; extern crate crypto; extern crate futures; @@ -91,11 +92,13 @@ extern crate protobuf; extern crate rand; extern crate ring; extern crate rw_stream_sink; +extern crate secp256k1; extern crate tokio_io; extern crate untrusted; pub use self::error::SecioError; +use asn1_der::{DerObject, traits::FromDerEncoded, traits::FromDerObject}; use bytes::{Bytes, BytesMut}; use futures::stream::MapErr as StreamMapErr; use futures::{Future, Poll, Sink, StartSend, Stream}; @@ -194,6 +197,33 @@ impl SecioKeyPair { .expect("failed to parse generated Ed25519 key")) } + /// Builds a `SecioKeyPair` from a raw secp256k1 32 bytes private key. + pub fn secp256k1_raw_key(key: K) -> Result> + where K: AsRef<[u8]> + { + let secp = secp256k1::Secp256k1::with_caps(secp256k1::ContextFlag::None); + let private = secp256k1::key::SecretKey::from_slice(&secp, key.as_ref())?; + + Ok(SecioKeyPair { + inner: SecioKeyPairInner::Secp256k1 { + private, + }, + }) + } + + /// Builds a `SecioKeyPair` from a secp256k1 private key in DER format. + pub fn secp256k1_from_der(key: K) -> Result> + where K: AsRef<[u8]> + { + // See ECPrivateKey in https://tools.ietf.org/html/rfc5915 + let obj: Vec = FromDerEncoded::with_der_encoded(key.as_ref()) + .map_err(|err| err.to_string())?; + let priv_key_obj = obj.into_iter().nth(1).ok_or("Not enough elements in DER".to_string())?; + let private_key: Vec = FromDerObject::from_der_object(priv_key_obj) + .map_err(|err| err.to_string())?; + SecioKeyPair::secp256k1_raw_key(&private_key) + } + /// Returns the public key corresponding to this key pair. pub fn to_public_key(&self) -> SecioPublicKey { match self.inner { @@ -203,6 +233,12 @@ impl SecioKeyPair { SecioKeyPairInner::Ed25519 { ref key_pair } => { SecioPublicKey::Ed25519(key_pair.public_key_bytes().to_vec()) }, + SecioKeyPairInner::Secp256k1 { ref private } => { + let secp = secp256k1::Secp256k1::with_caps(secp256k1::ContextFlag::SignOnly); + let pubkey = secp256k1::key::PublicKey::from_secret_key(&secp, private) + .expect("wrong secp256k1 private key ; type safety violated"); + SecioPublicKey::Secp256k1(pubkey.serialize().to_vec()) + }, } } @@ -215,6 +251,13 @@ impl SecioKeyPair { SecioKeyPairInner::Ed25519 { ref key_pair } => { PublicKeyBytesSlice(key_pair.public_key_bytes()).into() }, + SecioKeyPairInner::Secp256k1 { ref private } => { + let secp = secp256k1::Secp256k1::with_caps(secp256k1::ContextFlag::None); + let pubkey = secp256k1::key::PublicKey::from_secret_key(&secp, private) + .expect("wrong secp256k1 private key ; type safety violated"); + let pubkey_bytes = pubkey.serialize(); + PublicKeyBytesSlice(&pubkey_bytes).into() + }, } } @@ -233,6 +276,9 @@ enum SecioKeyPairInner { // We use an `Arc` so that we can clone the enum. key_pair: Arc, }, + Secp256k1 { + private: secp256k1::key::SecretKey, + }, } /// Public key used by the remote. @@ -243,6 +289,9 @@ pub enum SecioPublicKey { /// Format = ??? // TODO: ^ Ed25519(Vec), + /// Format = ??? + // TODO: ^ + Secp256k1(Vec), } impl SecioPublicKey { @@ -252,6 +301,7 @@ impl SecioPublicKey { match self { SecioPublicKey::Rsa(ref data) => PublicKeyBytesSlice(data), SecioPublicKey::Ed25519(ref data) => PublicKeyBytesSlice(data), + SecioPublicKey::Secp256k1(ref data) => PublicKeyBytesSlice(data), } } @@ -261,6 +311,7 @@ impl SecioPublicKey { match self { SecioPublicKey::Rsa(data) => PublicKeyBytes(data), SecioPublicKey::Ed25519(data) => PublicKeyBytes(data), + SecioPublicKey::Secp256k1(data) => PublicKeyBytes(data), } } diff --git a/secio/tests/test-secp256k1-private-key-2.der b/secio/tests/test-secp256k1-private-key-2.der new file mode 100644 index 0000000000000000000000000000000000000000..16be96162f4c52b6a5102b8f0a20240b81beafa3 GIT binary patch literal 118 zcmV-+0Ez!FbOHeZ1R!I!h8JT4QOpM>Y9)l$ZpoG&MsGKZsnj0CbgPv40+*l%1_djD z1ON)5L<2$q1o~N3F2qp^@EFa`VT{DGU(k{Z>8@Yx*IT1dZPM|(u~Bp-U+TQb55y$7 YmB-K(+?|oPHAzM)IH4*tpeNsK!_Lt&i2wiq literal 0 HcmV?d00001 diff --git a/secio/tests/test-secp256k1-private-key.der b/secio/tests/test-secp256k1-private-key.der new file mode 100644 index 0000000000000000000000000000000000000000..90bb3973262bade8ffac2fcda0bbe2272d2db295 GIT binary patch literal 118 zcmV-+0Ez!FbOHeZ1Rw`rr4Lx;F0QdZ$24r~bvSY>8n+y>2iNA)km7eQ2Rfh!1_djD z1ON)5L<2$q1g}>*23Nn7hfL!241jNw`NjP-vLXv`oQ)QL)ooCGoS}NgS4L_724=gm YsnmON)=~19A9YXCNYX&9Yj#sPGKFg~xBvhE literal 0 HcmV?d00001