Add support for secp256k1 in secio (#258)

This commit is contained in:
Pierre Krieger 2018-06-20 09:47:43 +02:00 committed by GitHub
parent fd4c5fad44
commit 1607fcb3f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 32 deletions

View File

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
[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"

View File

@ -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();
@ -579,28 +607,33 @@ mod tests {
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();

View File

@ -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<K>(key: K) -> Result<SecioKeyPair, Box<Error + Send + Sync>>
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<K>(key: K) -> Result<SecioKeyPair, Box<Error + Send + Sync>>
where K: AsRef<[u8]>
{
// See ECPrivateKey in https://tools.ietf.org/html/rfc5915
let obj: Vec<DerObject> = 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<u8> = 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<Ed25519KeyPair>,
},
Secp256k1 {
private: secp256k1::key::SecretKey,
},
}
/// Public key used by the remote.
@ -243,6 +289,9 @@ pub enum SecioPublicKey {
/// Format = ???
// TODO: ^
Ed25519(Vec<u8>),
/// Format = ???
// TODO: ^
Secp256k1(Vec<u8>),
}
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),
}
}

Binary file not shown.

Binary file not shown.