diff --git a/core/Cargo.toml b/core/Cargo.toml index 0de250a8..ba0fe072 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -10,8 +10,11 @@ keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] +asn1_der = "0.6.1" bs58 = "0.2.0" bytes = "0.4" +ed25519-dalek = "1.0.0-pre.1" +failure = "0.1" fnv = "1.0" lazy_static = "1.2" log = "0.4" @@ -24,12 +27,19 @@ protobuf = "2.3" quick-error = "1.2" rand = "0.6" rw-stream-sink = { version = "0.1.0", path = "../misc/rw-stream-sink" } +secp256k1 = { version = "0.12", features = ["rand"], optional = true } +sha2 = "0.8.0" smallvec = "0.6" tokio-executor = "0.1.4" tokio-io = "0.1" tokio-timer = "0.2" unsigned-varint = "0.2" void = "1" +zeroize = "0.5" + +[target.'cfg(not(any(target_os = "emscripten", target_os = "unknown")))'.dependencies] +ring = { version = "0.14", features = ["use_heap"], default-features = false } +untrusted = { version = "0.6" } [dev-dependencies] env_logger = "0.6" @@ -38,8 +48,12 @@ libp2p-tcp = { version = "0.4.0", path = "../transports/tcp" } libp2p-mplex = { version = "0.4.0", path = "../muxers/mplex" } libp2p-secio = { version = "0.4.0", path = "../protocols/secio" } rand = "0.6" +quickcheck = "0.8" tokio = "0.1" tokio-codec = "0.1" tokio-timer = "0.2" assert_matches = "1.3" tokio-mock-task = "0.1" + +[features] +default = ["secp256k1"] diff --git a/core/src/identity.rs b/core/src/identity.rs new file mode 100644 index 00000000..44b877a8 --- /dev/null +++ b/core/src/identity.rs @@ -0,0 +1,216 @@ +// 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. + +//! A node's network identity keys. + +pub mod ed25519; +#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] +pub mod rsa; +#[cfg(feature = "secp256k1")] +pub mod secp256k1; + +pub mod error; + +use self::error::*; +use crate::{PeerId, keys_proto}; + +/// Identity keypair of a node. +/// +/// # Example: Generating RSA keys with OpenSSL +/// +/// ```text +/// openssl genrsa -out private.pem 2048 +/// openssl pkcs8 -in private.pem -inform PEM -topk8 -out private.pk8 -outform DER -nocrypt +/// rm private.pem # optional +/// ``` +/// +/// Loading the keys: +/// +/// ```text +/// let mut bytes = std::fs::read("private.pem").unwrap(); +/// let keypair = Keypair::rsa_from_pkcs8(&mut bytes); +/// ``` +/// +#[derive(Clone)] +pub enum Keypair { + /// An Ed25519 keypair. + Ed25519(ed25519::Keypair), + #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] + /// An RSA keypair. + Rsa(rsa::Keypair), + /// A Secp256k1 keypair. + #[cfg(feature = "secp256k1")] + Secp256k1(secp256k1::Keypair) +} + +impl Keypair { + /// Generate a new Ed25519 keypair. + pub fn generate_ed25519() -> Keypair { + Keypair::Ed25519(ed25519::Keypair::generate()) + } + + /// Generate a new Secp256k1 keypair. + #[cfg(feature = "secp256k1")] + pub fn generate_secp256k1() -> Keypair { + Keypair::Secp256k1(secp256k1::Keypair::generate()) + } + + /// Decode an keypair from a DER-encoded secret key in PKCS#8 PrivateKeyInfo + /// format (i.e. unencrypted) as defined in [RFC5208]. + /// + /// [RFC5208]: https://tools.ietf.org/html/rfc5208#section-5 + #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] + pub fn rsa_from_pkcs8(pkcs8_der: &mut [u8]) -> Result { + rsa::Keypair::from_pkcs8(pkcs8_der).map(Keypair::Rsa) + } + + /// Decode a keypair from a DER-encoded Secp256k1 secret key in an ECPrivateKey + /// structure as defined in [RFC5915]. + /// + /// [RFC5915]: https://tools.ietf.org/html/rfc5915 + #[cfg(feature = "secp256k1")] + pub fn secp256k1_from_der(der: &mut [u8]) -> Result { + secp256k1::SecretKey::from_der(der) + .map(|sk| Keypair::Secp256k1(secp256k1::Keypair::from(sk))) + } + + /// Sign a message using the private key of this keypair, producing + /// a signature that can be verified using the corresponding public key. + pub fn sign(&self, msg: &[u8]) -> Result, SigningError> { + use Keypair::*; + match self { + Ed25519(ref pair) => Ok(pair.sign(msg)), + #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] + Rsa(ref pair) => pair.sign(msg), + #[cfg(feature = "secp256k1")] + Secp256k1(ref pair) => Ok(pair.secret().sign(msg)), + } + } + + /// Get the public key of this keypair. + pub fn public(&self) -> PublicKey { + use Keypair::*; + match self { + Ed25519(pair) => PublicKey::Ed25519(pair.public()), + #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] + Rsa(pair) => PublicKey::Rsa(pair.public()), + #[cfg(feature = "secp256k1")] + Secp256k1(pair) => PublicKey::Secp256k1(pair.public().clone()), + } + } +} + +/// The public key of a node's identity keypair. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PublicKey { + /// A public Ed25519 key. + Ed25519(ed25519::PublicKey), + #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] + /// A public RSA key. + Rsa(rsa::PublicKey), + #[cfg(feature = "secp256k1")] + /// A public Secp256k1 key. + Secp256k1(secp256k1::PublicKey) +} + +impl PublicKey { + /// Verify a signature for a message using this public key, i.e. check + /// that the signature has been produced by the corresponding + /// private key (authenticity), and that the message has not been + /// tampered with (integrity). + pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool { + use PublicKey::*; + match self { + Ed25519(pk) => pk.verify(msg, sig), + #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] + Rsa(pk) => pk.verify(msg, sig), + #[cfg(feature = "secp256k1")] + Secp256k1(pk) => pk.verify(msg, sig) + } + } + + /// Encode the public key into a protobuf structure for storage or + /// exchange with other nodes. + pub fn into_protobuf_encoding(self) -> Vec { + use protobuf::Message; + let mut public_key = keys_proto::PublicKey::new(); + match self { + PublicKey::Ed25519(key) => { + public_key.set_Type(keys_proto::KeyType::Ed25519); + public_key.set_Data(key.encode().to_vec()); + }, + #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] + PublicKey::Rsa(key) => { + public_key.set_Type(keys_proto::KeyType::RSA); + public_key.set_Data(key.encode_x509()); + }, + #[cfg(feature = "secp256k1")] + PublicKey::Secp256k1(key) => { + public_key.set_Type(keys_proto::KeyType::Secp256k1); + public_key.set_Data(key.encode().to_vec()); + }, + }; + + public_key + .write_to_bytes() + .expect("Encoding public key into protobuf failed.") + } + + /// Decode a public key from a protobuf structure, e.g. read from storage + /// or received from another node. + pub fn from_protobuf_encoding(bytes: &[u8]) -> Result { + #[allow(unused_mut)] // Due to conditional compilation. + let mut pubkey = protobuf::parse_from_bytes::(bytes) + .map_err(|e| DecodingError::new("Protobuf", e))?; + + match pubkey.get_Type() { + keys_proto::KeyType::Ed25519 => { + ed25519::PublicKey::decode(pubkey.get_Data()) + .map(PublicKey::Ed25519) + }, + #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] + keys_proto::KeyType::RSA => { + rsa::PublicKey::decode_x509(&pubkey.take_Data()) + .map(PublicKey::Rsa) + } + #[cfg(any(target_os = "emscripten", target_os = "unknown"))] + keys_proto::KeyType::RSA => { + log::debug!("support for RSA was disabled at compile-time"); + Err("Unsupported".to_string().into()) + }, + #[cfg(feature = "secp256k1")] + keys_proto::KeyType::Secp256k1 => { + secp256k1::PublicKey::decode(pubkey.get_Data()) + .map(PublicKey::Secp256k1) + } + #[cfg(not(feature = "secp256k1"))] + keys_proto::KeyType::Secp256k1 => { + log::debug!("support for secp256k1 was disabled at compile-time"); + Err("Unsupported".to_string().into()) + }, + } + } + + /// Convert the `PublicKey` into the corresponding `PeerId`. + pub fn into_peer_id(self) -> PeerId { + self.into() + } +} + diff --git a/core/src/identity/ed25519.rs b/core/src/identity/ed25519.rs new file mode 100644 index 00000000..0b28d404 --- /dev/null +++ b/core/src/identity/ed25519.rs @@ -0,0 +1,193 @@ +// 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. + +//! Ed25519 keys. + +use ed25519_dalek as ed25519; +use failure::Fail; +use super::error::DecodingError; +use zeroize::Zeroize; + +/// An Ed25519 keypair. +pub struct Keypair(ed25519::Keypair); + +impl Keypair { + /// Generate a new Ed25519 keypair. + pub fn generate() -> Keypair { + Keypair(ed25519::Keypair::generate(&mut rand::thread_rng())) + } + + /// Encode the keypair into a byte array by concatenating the bytes + /// of the secret scalar and the compressed public point, + /// an informal standard for encoding Ed25519 keypairs. + pub fn encode(&self) -> [u8; 64] { + self.0.to_bytes() + } + + /// Decode a keypair from the format produced by `encode`, + /// zeroing the input on success. + pub fn decode(kp: &mut [u8]) -> Result { + ed25519::Keypair::from_bytes(kp) + .map(|k| { kp.zeroize(); Keypair(k) }) + .map_err(|e| DecodingError::new("Ed25519 keypair", e.compat())) + } + + /// Sign a message using the private key of this keypair. + pub fn sign(&self, msg: &[u8]) -> Vec { + self.0.sign(msg).to_bytes().to_vec() + } + + /// Get the public key of this keypair. + pub fn public(&self) -> PublicKey { + PublicKey(self.0.public) + } + + /// Get the secret key of this keypair. + pub fn secret(&self) -> SecretKey { + SecretKey::from_bytes(&mut self.0.secret.to_bytes()) + .expect("ed25519::SecretKey::from_bytes(to_bytes(k)) != k") + } +} + +impl Clone for Keypair { + fn clone(&self) -> Keypair { + let mut sk_bytes = self.0.secret.to_bytes(); + let secret = SecretKey::from_bytes(&mut sk_bytes) + .expect("ed25519::SecretKey::from_bytes(to_bytes(k)) != k").0; + let public = ed25519::PublicKey::from_bytes(&self.0.public.to_bytes()) + .expect("ed25519::PublicKey::from_bytes(to_bytes(k)) != k"); + Keypair(ed25519::Keypair { secret, public }) + } +} + +/// Demote an Ed25519 keypair to a secret key. +impl From for SecretKey { + fn from(kp: Keypair) -> SecretKey { + SecretKey(kp.0.secret) + } +} + +/// Promote an Ed25519 secret key into a keypair. +impl From for Keypair { + fn from(sk: SecretKey) -> Keypair { + let secret = sk.0; + let public = ed25519::PublicKey::from(&secret); + Keypair(ed25519::Keypair { secret, public }) + } +} + +/// An Ed25519 public key. +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct PublicKey(ed25519::PublicKey); + +impl PublicKey { + /// Verify the Ed25519 signature on a message using the public key. + pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool { + ed25519::Signature::from_bytes(sig).map(|s| self.0.verify(msg, &s)).is_ok() + } + + /// Encode the public key into a byte array in compressed form, i.e. + /// where one coordinate is represented by a single bit. + pub fn encode(&self) -> [u8; 32] { + self.0.to_bytes() + } + + /// Decode a public key from a byte array as produced by `encode`. + pub fn decode(k: &[u8]) -> Result { + ed25519::PublicKey::from_bytes(k) + .map_err(|e| DecodingError::new("Ed25519 public key", e.compat())) + .map(PublicKey) + } +} + +/// An Ed25519 secret key. +pub struct SecretKey(ed25519::SecretKey); + +/// View the bytes of the secret key. +impl AsRef<[u8]> for SecretKey { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl Clone for SecretKey { + fn clone(&self) -> SecretKey { + let mut sk_bytes = self.0.to_bytes(); + Self::from_bytes(&mut sk_bytes) + .expect("ed25519::SecretKey::from_bytes(to_bytes(k)) != k") + } +} + +impl SecretKey { + /// Generate a new Ed25519 secret key. + pub fn generate() -> SecretKey { + SecretKey(ed25519::SecretKey::generate(&mut rand::thread_rng())) + } + + /// Create an Ed25519 secret key from a byte slice, zeroing the input on success. + /// If the bytes do not constitute a valid Ed25519 secret key, an error is + /// returned. + pub fn from_bytes(mut sk_bytes: impl AsMut<[u8]>) -> Result { + let sk_bytes = sk_bytes.as_mut(); + let secret = ed25519::SecretKey::from_bytes(&*sk_bytes) + .map_err(|e| DecodingError::new("Ed25519 secret key", e.compat()))?; + sk_bytes.zeroize(); + Ok(SecretKey(secret)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck::*; + + fn eq_keypairs(kp1: &Keypair, kp2: &Keypair) -> bool { + kp1.public() == kp2.public() + && + kp1.0.secret.as_bytes() == kp2.0.secret.as_bytes() + } + + #[test] + fn ed25519_keypair_encode_decode() { + fn prop() -> bool { + let kp1 = Keypair::generate(); + let mut kp1_enc = kp1.encode(); + let kp2 = Keypair::decode(&mut kp1_enc).unwrap(); + eq_keypairs(&kp1, &kp2) + && + kp1_enc.iter().all(|b| *b == 0) + } + QuickCheck::new().tests(10).quickcheck(prop as fn() -> _); + } + + #[test] + fn ed25519_keypair_from_secret() { + fn prop() -> bool { + let kp1 = Keypair::generate(); + let mut sk = kp1.0.secret.to_bytes(); + let kp2 = Keypair::from(SecretKey::from_bytes(&mut sk).unwrap()); + eq_keypairs(&kp1, &kp2) + && + sk == [0u8; 32] + } + QuickCheck::new().tests(10).quickcheck(prop as fn() -> _); + } +} + diff --git a/core/src/identity/error.rs b/core/src/identity/error.rs new file mode 100644 index 00000000..b456f484 --- /dev/null +++ b/core/src/identity/error.rs @@ -0,0 +1,88 @@ +// 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. + +//! Errors during identity key operations. + +use std::error::Error; +use std::fmt; + +/// An error during decoding of key material. +#[derive(Debug)] +pub struct DecodingError { + msg: String, + source: Option> +} + +impl DecodingError { + pub(crate) fn new(msg: &str, source: impl Error + Send + Sync + 'static) -> DecodingError { + DecodingError { msg: msg.to_string(), source: Some(Box::new(source)) } + } +} + +impl From for DecodingError { + fn from(s: String) -> DecodingError { + DecodingError { msg: s, source: None } + } +} + +impl fmt::Display for DecodingError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Key decoding error: {}", self.msg) + } +} + +impl Error for DecodingError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.source.as_ref().map(|s| &**s as &dyn Error) + } +} + +/// An error during signing of a message. +#[derive(Debug)] +pub struct SigningError { + msg: String, + source: Option> +} + +/// An error during encoding of key material. +impl SigningError { + pub(crate) fn new(msg: &str, source: impl Error + Send + Sync + 'static) -> SigningError { + SigningError { msg: msg.to_string(), source: Some(Box::new(source)) } + } +} + +impl fmt::Display for SigningError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Key signing error: {}", self.msg) + } +} + +impl From for SigningError { + fn from(s: String) -> SigningError { + SigningError { msg: s, source: None } + } +} + +impl Error for SigningError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.source.as_ref().map(|s| &**s as &dyn Error) + } +} + diff --git a/core/src/identity/rsa.rs b/core/src/identity/rsa.rs new file mode 100644 index 00000000..ad72b0bf --- /dev/null +++ b/core/src/identity/rsa.rs @@ -0,0 +1,259 @@ +// 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. + +//! RSA keys. + +use asn1_der::{Asn1Der, FromDerObject, IntoDerObject, DerObject, DerTag, DerValue, Asn1DerError}; +use lazy_static::lazy_static; +use super::error::*; +use ring::rand::SystemRandom; +use ring::signature::{self, RsaKeyPair, RSA_PKCS1_SHA256, RSA_PKCS1_2048_8192_SHA256}; +use ring::signature::KeyPair; +use std::sync::Arc; +use untrusted::Input; +use zeroize::Zeroize; + +/// An RSA keypair. +#[derive(Clone)] +pub struct Keypair(Arc); + +impl Keypair { + /// Decode an RSA keypair from a DER-encoded private key in PKCS#8 PrivateKeyInfo + /// format (i.e. unencrypted) as defined in [RFC5208]. + /// + /// [RFC5208]: https://tools.ietf.org/html/rfc5208#section-5 + pub fn from_pkcs8(der: &mut [u8]) -> Result { + let kp = RsaKeyPair::from_pkcs8(Input::from(&der[..])) + .map_err(|e| DecodingError::new("RSA PKCS#8 PrivateKeyInfo", e))?; + der.zeroize(); + Ok(Keypair(Arc::new(kp))) + } + + /// Get the public key from the keypair. + pub fn public(&self) -> PublicKey { + PublicKey(self.0.public_key().as_ref().to_vec()) + } + + /// Sign a message with this keypair. + pub fn sign(&self, data: &[u8]) -> Result, SigningError> { + let mut signature = vec![0; self.0.public_modulus_len()]; + let rng = SystemRandom::new(); + match self.0.sign(&RSA_PKCS1_SHA256, &rng, &data, &mut signature) { + Ok(()) => Ok(signature), + Err(e) => Err(SigningError::new("RSA", e)) + } + } +} + +/// An RSA public key. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct PublicKey(Vec); + +impl PublicKey { + /// Verify an RSA signature on a message using the public key. + pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool { + signature::verify(&RSA_PKCS1_2048_8192_SHA256, + Input::from(&self.0), + Input::from(msg), + Input::from(sig)).is_ok() + } + + /// Encode the RSA public key in DER as a PKCS#1 RSAPublicKey structure, + /// as defined in [RFC3447]. + /// + /// [RFC3447]: https://tools.ietf.org/html/rfc3447#appendix-A.1.1 + pub fn encode_pkcs1(&self) -> Vec { + // This is the encoding currently used in-memory, so it is trivial. + self.0.clone() + } + + /// Encode the RSA public key in DER as a X.509 SubjectPublicKeyInfo structure, + /// as defined in [RFC5280]. + /// + /// [RFC5280] https://tools.ietf.org/html/rfc5280#section-4.1 + pub fn encode_x509(&self) -> Vec { + let spki = Asn1SubjectPublicKeyInfo { + algorithmIdentifier: Asn1RsaEncryption { + algorithm: Asn1OidRsaEncryption(), + parameters: () + }, + subjectPublicKey: Asn1SubjectPublicKey(self.clone()) + }; + let mut buf = vec![0u8; spki.serialized_len()]; + spki.serialize(buf.iter_mut()).map(|_| buf) + .expect("RSA X.509 public key encoding failed.") + } + + /// Decode an RSA public key from a DER-encoded X.509 SubjectPublicKeyInfo + /// structure. See also `encode_x509`. + pub fn decode_x509(pk: &[u8]) -> Result { + Asn1SubjectPublicKeyInfo::deserialize(pk.iter()) + .map_err(|e| DecodingError::new("RSA X.509", e)) + .map(|spki| spki.subjectPublicKey.0) + } +} + +////////////////////////////////////////////////////////////////////////////// +// DER encoding / decoding of public keys +// +// Primer: http://luca.ntop.org/Teaching/Appunti/asn1.html +// Playground: https://lapo.it/asn1js/ + +lazy_static! { + /// The DER encoding of the object identifier (OID) 'rsaEncryption' for + /// RSA public keys defined for X.509 in [RFC-3279] and used in + /// SubjectPublicKeyInfo structures defined in [RFC-5280]. + /// + /// [RFC-3279]: https://tools.ietf.org/html/rfc3279#section-2.3.1 + /// [RFC-5280]: https://tools.ietf.org/html/rfc5280#section-4.1 + static ref OID_RSA_ENCRYPTION_DER: DerObject = + DerObject { + tag: DerTag::x06, + value: DerValue { + data: vec![ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 ] + } + }; +} + +/// The ASN.1 OID for "rsaEncryption". +#[derive(Clone)] +struct Asn1OidRsaEncryption(); + +impl IntoDerObject for Asn1OidRsaEncryption { + fn into_der_object(self) -> DerObject { + OID_RSA_ENCRYPTION_DER.clone() + } + fn serialized_len(&self) -> usize { + OID_RSA_ENCRYPTION_DER.serialized_len() + } +} + +impl FromDerObject for Asn1OidRsaEncryption { + fn from_der_object(o: DerObject) -> Result { + if o.tag != DerTag::x06 { + return Err(Asn1DerError::InvalidTag) + } + if o.value != OID_RSA_ENCRYPTION_DER.value { + return Err(Asn1DerError::InvalidEncoding) + } + Ok(Asn1OidRsaEncryption()) + } +} + +/// The ASN.1 AlgorithmIdentifier for "rsaEncryption". +#[derive(Asn1Der)] +struct Asn1RsaEncryption { + algorithm: Asn1OidRsaEncryption, + parameters: () +} + +/// The ASN.1 SubjectPublicKey inside a SubjectPublicKeyInfo, +/// i.e. encoded as a DER BIT STRING. +struct Asn1SubjectPublicKey(PublicKey); + +impl IntoDerObject for Asn1SubjectPublicKey { + fn into_der_object(self) -> DerObject { + let pk_der = (self.0).0; + let mut bit_string = Vec::with_capacity(pk_der.len() + 1); + // The number of bits in pk_der is trivially always a multiple of 8, + // so there are always 0 "unused bits" signaled by the first byte. + bit_string.push(0u8); + bit_string.extend(pk_der); + DerObject::new(DerTag::x03, bit_string.into()) + } + fn serialized_len(&self) -> usize { + DerObject::compute_serialized_len((self.0).0.len() + 1) + } +} + +impl FromDerObject for Asn1SubjectPublicKey { + fn from_der_object(o: DerObject) -> Result { + if o.tag != DerTag::x03 { + return Err(Asn1DerError::InvalidTag) + } + let pk_der: Vec = o.value.data.into_iter().skip(1).collect(); + // We don't parse pk_der further as an ASN.1 RsaPublicKey, since + // we only need the DER encoding for `verify`. + Ok(Asn1SubjectPublicKey(PublicKey(pk_der))) + } +} + +/// ASN.1 SubjectPublicKeyInfo +#[derive(Asn1Der)] +#[allow(non_snake_case)] +struct Asn1SubjectPublicKeyInfo { + algorithmIdentifier: Asn1RsaEncryption, + subjectPublicKey: Asn1SubjectPublicKey +} + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck::*; + use rand::seq::SliceRandom; + use std::fmt; + + const KEY1: &'static [u8] = include_bytes!("test/rsa-2048.pk8"); + const KEY2: &'static [u8] = include_bytes!("test/rsa-3072.pk8"); + const KEY3: &'static [u8] = include_bytes!("test/rsa-4096.pk8"); + + #[derive(Clone)] + struct SomeKeypair(Keypair); + + impl fmt::Debug for SomeKeypair { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "SomeKeypair") + } + } + + impl Arbitrary for SomeKeypair { + fn arbitrary(g: &mut G) -> SomeKeypair { + let mut key = [KEY1, KEY2, KEY3].choose(g).unwrap().to_vec(); + SomeKeypair(Keypair::from_pkcs8(&mut key).unwrap()) + } + } + + #[test] + fn rsa_from_pkcs8() { + assert!(Keypair::from_pkcs8(&mut KEY1.to_vec()).is_ok()); + assert!(Keypair::from_pkcs8(&mut KEY2.to_vec()).is_ok()); + assert!(Keypair::from_pkcs8(&mut KEY3.to_vec()).is_ok()); + } + + #[test] + fn rsa_x509_encode_decode() { + fn prop(SomeKeypair(kp): SomeKeypair) -> Result { + let pk = kp.public(); + PublicKey::decode_x509(&pk.encode_x509()) + .map_err(|e| e.to_string()) + .map(|pk2| pk2 == pk) + } + QuickCheck::new().tests(10).quickcheck(prop as fn(_) -> _); + } + + #[test] + fn rsa_sign_verify() { + fn prop(SomeKeypair(kp): SomeKeypair, msg: Vec) -> Result { + kp.sign(&msg).map(|s| kp.public().verify(&msg, &s)) + } + QuickCheck::new().tests(10).quickcheck(prop as fn(_,_) -> _); + } +} + diff --git a/core/src/identity/secp256k1.rs b/core/src/identity/secp256k1.rs new file mode 100644 index 00000000..f9d49bcd --- /dev/null +++ b/core/src/identity/secp256k1.rs @@ -0,0 +1,158 @@ +// 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. + +//! Secp256k1 keys. + +use asn1_der::{FromDerObject, DerObject}; +use lazy_static::lazy_static; +use sha2::{Digest as ShaDigestTrait, Sha256}; +use secp256k1 as secp; +use secp::{Message, Signature}; +use super::error::DecodingError; +use zeroize::Zeroize; + +// Cached `Secp256k1` context, to avoid recreating it every time. +lazy_static! { + static ref SECP: secp::Secp256k1 = secp::Secp256k1::new(); +} + +/// A Secp256k1 keypair. +#[derive(Clone)] +pub struct Keypair { + secret: SecretKey, + public: PublicKey +} + +impl Keypair { + /// Generate a new sec256k1 `Keypair`. + pub fn generate() -> Keypair { + Keypair::from(SecretKey::generate()) + } + + /// Get the public key of this keypair. + pub fn public(&self) -> &PublicKey { + &self.public + } + + /// Get the secret key of this keypair. + pub fn secret(&self) -> &SecretKey { + &self.secret + } +} + +/// Promote a Secp256k1 secret key into a keypair. +impl From for Keypair { + fn from(secret: SecretKey) -> Keypair { + let public = PublicKey(secp::key::PublicKey::from_secret_key(&SECP, &secret.0)); + Keypair { secret, public } + } +} + +/// Demote a Secp256k1 keypair into a secret key. +impl From for SecretKey { + fn from(kp: Keypair) -> SecretKey { + kp.secret + } +} + +/// A Secp256k1 secret key. +#[derive(Clone)] +pub struct SecretKey(secp::key::SecretKey); + +/// View the bytes of the secret key. +impl AsRef<[u8]> for SecretKey { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl SecretKey { + /// Generate a new Secp256k1 secret key. + pub fn generate() -> SecretKey { + SecretKey(secp::key::SecretKey::new(&mut secp::rand::thread_rng())) + } + + /// Create a secret key from a byte slice, zeroing the slice on success. + /// If the bytes do not constitute a valid Secp256k1 secret key, an + /// error is returned. + pub fn from_bytes(mut sk: impl AsMut<[u8]>) -> Result { + let sk_bytes = sk.as_mut(); + let secret = secp::key::SecretKey::from_slice(&*sk_bytes) + .map_err(|e| DecodingError::new("Secp256k1 secret key", e))?; + Ok(SecretKey(secret)) + } + + /// Decode a DER-encoded Secp256k1 secret key in an ECPrivateKey + /// structure as defined in [RFC5915]. + /// + /// [RFC5915]: https://tools.ietf.org/html/rfc5915 + pub fn from_der(mut der: impl AsMut<[u8]>) -> Result { + // TODO: Stricter parsing. + let der_obj = der.as_mut(); + let obj: Vec = FromDerObject::deserialize((&*der_obj).iter()) + .map_err(|e| DecodingError::new("Secp256k1 DER ECPrivateKey", e))?; + der_obj.zeroize(); + let sk_obj = obj.into_iter().nth(1) + .ok_or_else(|| "Not enough elements in DER".to_string())?; + let mut sk_bytes: Vec = FromDerObject::from_der_object(sk_obj) + .map_err(|e| e.to_string())?; + let sk = SecretKey::from_bytes(&mut sk_bytes)?; + sk_bytes.zeroize(); + Ok(sk) + } + + /// Sign a message with this secret key, producing a DER-encoded + /// ECDSA signature, as defined in [RFC3278]. + /// + /// [RFC3278]: https://tools.ietf.org/html/rfc3278#section-8.2 + pub fn sign(&self, msg: &[u8]) -> Vec { + let m = Message::from_slice(Sha256::digest(&msg).as_ref()) + .expect("digest output length doesn't match secp256k1 input length"); + SECP.sign(&m, &self.0).serialize_der() + } +} + +/// A Secp256k1 public key. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct PublicKey(secp::key::PublicKey); + +impl PublicKey { + /// Verify the Secp256k1 signature on a message using the public key. + pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool { + Message::from_slice(&Sha256::digest(msg)) + .and_then(|m| Signature::from_der(sig) + .and_then(|s| SECP.verify(&m, &s, &self.0))).is_ok() + } + + /// Encode the public key in compressed form, i.e. with one coordinate + /// represented by a single bit. + pub fn encode(&self) -> [u8; 33] { + self.0.serialize() + } + + /// Decode a public key from a byte slice in the the format produced + /// by `encode`. + pub fn decode(k: &[u8]) -> Result { + secp256k1::PublicKey::from_slice(k) + .map_err(|e| DecodingError::new("Secp256k1 public key", e)) + .map(PublicKey) + } +} + diff --git a/core/src/identity/test/rsa-2048.pk8 b/core/src/identity/test/rsa-2048.pk8 new file mode 100644 index 00000000..75b83745 Binary files /dev/null and b/core/src/identity/test/rsa-2048.pk8 differ diff --git a/core/src/identity/test/rsa-3072.pk8 b/core/src/identity/test/rsa-3072.pk8 new file mode 100644 index 00000000..a0bd175f Binary files /dev/null and b/core/src/identity/test/rsa-3072.pk8 differ diff --git a/core/src/identity/test/rsa-4096.pk8 b/core/src/identity/test/rsa-4096.pk8 new file mode 100644 index 00000000..252dda81 Binary files /dev/null and b/core/src/identity/test/rsa-4096.pk8 differ diff --git a/core/src/lib.rs b/core/src/lib.rs index 89c11135..df981ada 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -67,12 +67,12 @@ pub use multiaddr; mod keys_proto; mod peer_id; -mod public_key; #[cfg(test)] mod tests; pub mod either; +pub mod identity; pub mod muxing; pub mod nodes; pub mod protocols_handler; @@ -84,7 +84,7 @@ pub use self::multiaddr::Multiaddr; pub use self::muxing::StreamMuxer; pub use self::peer_id::PeerId; pub use self::protocols_handler::{ProtocolsHandler, ProtocolsHandlerEvent}; -pub use self::public_key::PublicKey; +pub use self::identity::PublicKey; pub use self::swarm::Swarm; pub use self::transport::Transport; pub use self::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo, UpgradeError, ProtocolName}; diff --git a/core/src/peer_id.rs b/core/src/peer_id.rs index fed163f9..9dddf61a 100644 --- a/core/src/peer_id.rs +++ b/core/src/peer_id.rs @@ -42,9 +42,9 @@ impl fmt::Debug for PeerId { impl PeerId { /// Builds a `PeerId` from a public key. #[inline] - pub fn from_public_key(public_key: PublicKey) -> PeerId { - let protobuf = public_key.into_protobuf_encoding(); - let multihash = multihash::encode(multihash::Hash::SHA2256, &protobuf) + pub fn from_public_key(key: PublicKey) -> PeerId { + let key_enc = key.into_protobuf_encoding(); + let multihash = multihash::encode(multihash::Hash::SHA2256, &key_enc) .expect("sha2-256 is always supported"); PeerId { multihash } } @@ -120,9 +120,10 @@ impl PeerId { /// given public key, otherwise `Some` boolean as the result of an equality check. pub fn is_public_key(&self, public_key: &PublicKey) -> Option { let alg = self.multihash.algorithm(); - match multihash::encode(alg, &public_key.clone().into_protobuf_encoding()) { - Ok(compare) => Some(compare == self.multihash), - Err(multihash::EncodeError::UnsupportedType) => None, + let enc = public_key.clone().into_protobuf_encoding(); + match multihash::encode(alg, &enc) { + Ok(h) => Some(h == self.multihash), + Err(multihash::EncodeError::UnsupportedType) => None } } } @@ -195,26 +196,25 @@ impl FromStr for PeerId { #[cfg(test)] mod tests { - use rand::random; - use crate::{PeerId, PublicKey}; + use crate::{PeerId, identity}; #[test] fn peer_id_is_public_key() { - let key = PublicKey::Rsa((0 .. 2048).map(|_| -> u8 { random() }).collect()); - let peer_id = PeerId::from_public_key(key.clone()); + let key = identity::Keypair::generate_ed25519().public(); + let peer_id = key.clone().into_peer_id(); assert_eq!(peer_id.is_public_key(&key), Some(true)); } #[test] fn peer_id_into_bytes_then_from_bytes() { - let peer_id = PublicKey::Rsa((0 .. 2048).map(|_| -> u8 { random() }).collect()).into_peer_id(); + let peer_id = identity::Keypair::generate_ed25519().public().into_peer_id(); let second = PeerId::from_bytes(peer_id.clone().into_bytes()).unwrap(); assert_eq!(peer_id, second); } #[test] fn peer_id_to_base58_then_back() { - let peer_id = PublicKey::Rsa((0 .. 2048).map(|_| -> u8 { random() }).collect()).into_peer_id(); + let peer_id = identity::Keypair::generate_ed25519().public().into_peer_id(); let second: PeerId = peer_id.to_base58().parse().unwrap(); assert_eq!(peer_id, second); } diff --git a/core/src/public_key.rs b/core/src/public_key.rs deleted file mode 100644 index 251b8b66..00000000 --- a/core/src/public_key.rs +++ /dev/null @@ -1,108 +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 crate::{keys_proto, PeerId}; -use log::debug; -use protobuf::{self, Message}; -use std::io::{Error as IoError, ErrorKind as IoErrorKind}; - -/// Public key used by the remote. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PublicKey { - /// DER format. - Rsa(Vec), - /// Format = ??? - // TODO: ^ - Ed25519(Vec), - /// Format = ??? - // TODO: ^ - Secp256k1(Vec), -} - -impl PublicKey { - /// Encodes the public key as a protobuf message. - /// - /// Used at various locations in the wire protocol of libp2p. - #[inline] - pub fn into_protobuf_encoding(self) -> Vec { - let mut public_key = keys_proto::PublicKey::new(); - match self { - PublicKey::Rsa(data) => { - public_key.set_Type(keys_proto::KeyType::RSA); - public_key.set_Data(data); - }, - PublicKey::Ed25519(data) => { - public_key.set_Type(keys_proto::KeyType::Ed25519); - public_key.set_Data(data); - }, - PublicKey::Secp256k1(data) => { - public_key.set_Type(keys_proto::KeyType::Secp256k1); - public_key.set_Data(data); - }, - }; - - public_key - .write_to_bytes() - .expect("protobuf writing should always be valid") - } - - /// Decodes the public key from a protobuf message. - /// - /// Used at various locations in the wire protocol of libp2p. - #[inline] - pub fn from_protobuf_encoding(bytes: &[u8]) -> Result { - let mut pubkey = protobuf::parse_from_bytes::(bytes) - .map_err(|err| { - debug!("failed to parse public key's protobuf encoding"); - IoError::new(IoErrorKind::InvalidData, err) - })?; - - Ok(match pubkey.get_Type() { - keys_proto::KeyType::RSA => { - PublicKey::Rsa(pubkey.take_Data()) - }, - keys_proto::KeyType::Ed25519 => { - PublicKey::Ed25519(pubkey.take_Data()) - }, - keys_proto::KeyType::Secp256k1 => { - PublicKey::Secp256k1(pubkey.take_Data()) - }, - }) - } - - /// Builds a `PeerId` corresponding to the public key of the node. - #[inline] - pub fn into_peer_id(self) -> PeerId { - self.into() - } -} - -#[cfg(test)] -mod tests { - use rand::random; - use crate::PublicKey; - - #[test] - fn key_into_protobuf_then_back() { - let key = PublicKey::Rsa((0 .. 2048).map(|_| -> u8 { random() }).collect()); - let second = PublicKey::from_protobuf_encoding(&key.clone().into_protobuf_encoding()).unwrap(); - assert_eq!(key, second); - } -} diff --git a/core/src/swarm.rs b/core/src/swarm.rs index 73c81a1f..baa0d49a 100644 --- a/core/src/swarm.rs +++ b/core/src/swarm.rs @@ -549,13 +549,11 @@ where TBehaviour: NetworkBehaviour, #[cfg(test)] mod tests { - use crate::peer_id::PeerId; + use crate::{identity, PeerId, PublicKey}; use crate::protocols_handler::{DummyProtocolsHandler, ProtocolsHandler}; - use crate::public_key::PublicKey; use crate::tests::dummy_transport::DummyTransport; use futures::prelude::*; use multiaddr::Multiaddr; - use rand::random; use std::marker::PhantomData; use super::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters, SwarmBuilder}; @@ -601,10 +599,7 @@ mod tests { } fn get_random_id() -> PublicKey { - PublicKey::Rsa((0 .. 2048) - .map(|_| -> u8 { random() }) - .collect() - ) + identity::Keypair::generate_ed25519().public() } #[test] @@ -612,8 +607,8 @@ mod tests { let id = get_random_id(); let transport = DummyTransport::new(); let behaviour = DummyBehaviour{marker: PhantomData}; - let swarm = SwarmBuilder::new(transport, behaviour, - id.into_peer_id()).incoming_limit(Some(4)).build(); + let swarm = SwarmBuilder::new(transport, behaviour, id.into()) + .incoming_limit(Some(4)).build(); assert_eq!(swarm.raw_swarm.incoming_limit(), Some(4)); } @@ -622,8 +617,7 @@ mod tests { let id = get_random_id(); let transport = DummyTransport::new(); let behaviour = DummyBehaviour{marker: PhantomData}; - let swarm = SwarmBuilder::new(transport, behaviour, id.into_peer_id()) - .build(); + let swarm = SwarmBuilder::new(transport, behaviour, id.into()).build(); assert!(swarm.raw_swarm.incoming_limit().is_none()) } diff --git a/core/tests/raw_swarm_dial_error.rs b/core/tests/raw_swarm_dial_error.rs index 3739203f..a9a5a855 100644 --- a/core/tests/raw_swarm_dial_error.rs +++ b/core/tests/raw_swarm_dial_error.rs @@ -19,6 +19,7 @@ // DEALINGS IN THE SOFTWARE. use futures::{future, prelude::*}; +use libp2p_core::identity; use libp2p_core::multiaddr::multiaddr; use libp2p_core::nodes::raw_swarm::{RawSwarm, RawSwarmEvent, RawSwarmReachError, PeerState, UnknownPeerDialErr, IncomingError}; use libp2p_core::{PeerId, Transport, upgrade, upgrade::InboundUpgradeExt, upgrade::OutboundUpgradeExt}; @@ -92,8 +93,8 @@ fn deny_incoming_connec() { // TODO: make creating the transport more elegant ; literaly half of the code of the test // is about creating the transport let mut swarm1: RawSwarm<_, _, _, NodeHandlerWrapperBuilder>, _> = { - let local_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_public_key = local_key.to_public_key(); + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); let transport = libp2p_tcp::TcpConfig::new() .with_upgrade(libp2p_secio::SecioConfig::new(local_key)) .and_then(move |out, endpoint| { @@ -104,12 +105,12 @@ fn deny_incoming_connec() { .map_inbound(move |muxer| (peer_id2, muxer)); upgrade::apply(out.stream, upgrade, endpoint) }); - RawSwarm::new(transport, local_public_key.into_peer_id()) + RawSwarm::new(transport, local_public_key.into()) }; let mut swarm2 = { - let local_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_public_key = local_key.to_public_key(); + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); let transport = libp2p_tcp::TcpConfig::new() .with_upgrade(libp2p_secio::SecioConfig::new(local_key)) .and_then(move |out, endpoint| { @@ -120,7 +121,7 @@ fn deny_incoming_connec() { .map_inbound(move |muxer| (peer_id2, muxer)); upgrade::apply(out.stream, upgrade, endpoint) }); - RawSwarm::new(transport, local_public_key.into_peer_id()) + RawSwarm::new(transport, local_public_key.into()) }; let listen = swarm1.listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()).unwrap(); @@ -173,8 +174,8 @@ fn dial_self() { // TODO: make creating the transport more elegant ; literaly half of the code of the test // is about creating the transport let mut swarm = { - let local_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_public_key = local_key.to_public_key(); + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); let transport = libp2p_tcp::TcpConfig::new() .with_upgrade(libp2p_secio::SecioConfig::new(local_key)) .and_then(move |out, endpoint| { @@ -185,7 +186,7 @@ fn dial_self() { .map_inbound(move |muxer| (peer_id2, muxer)); upgrade::apply(out.stream, upgrade, endpoint) }); - RawSwarm::new(transport, local_public_key.into_peer_id()) + RawSwarm::new(transport, local_public_key.into()) }; let listen = swarm.listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()).unwrap(); @@ -241,8 +242,8 @@ fn dial_self_by_id() { // TODO: make creating the transport more elegant ; literaly half of the code of the test // is about creating the transport let mut swarm: RawSwarm<_, _, _, NodeHandlerWrapperBuilder>, _> = { - let local_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_public_key = local_key.to_public_key(); + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); let transport = libp2p_tcp::TcpConfig::new() .with_upgrade(libp2p_secio::SecioConfig::new(local_key)) .and_then(move |out, endpoint| { @@ -253,7 +254,7 @@ fn dial_self_by_id() { .map_inbound(move |muxer| (peer_id2, muxer)); upgrade::apply(out.stream, upgrade, endpoint) }); - RawSwarm::new(transport, local_public_key.into_peer_id()) + RawSwarm::new(transport, local_public_key.into()) }; let peer_id = swarm.local_peer_id().clone(); @@ -267,8 +268,8 @@ fn multiple_addresses_err() { // TODO: make creating the transport more elegant ; literaly half of the code of the test // is about creating the transport let mut swarm = { - let local_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_public_key = local_key.to_public_key(); + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); let transport = libp2p_tcp::TcpConfig::new() .with_upgrade(libp2p_secio::SecioConfig::new(local_key)) .and_then(move |out, endpoint| { @@ -279,7 +280,7 @@ fn multiple_addresses_err() { .map_inbound(move |muxer| (peer_id2, muxer)); upgrade::apply(out.stream, upgrade, endpoint) }); - RawSwarm::new(transport, local_public_key.into_peer_id()) + RawSwarm::new(transport, local_public_key.into()) }; let mut addresses = Vec::new(); diff --git a/core/tests/raw_swarm_simult.rs b/core/tests/raw_swarm_simult.rs index 5870750d..94a82af3 100644 --- a/core/tests/raw_swarm_simult.rs +++ b/core/tests/raw_swarm_simult.rs @@ -19,6 +19,7 @@ // DEALINGS IN THE SOFTWARE. use futures::{future, prelude::*}; +use libp2p_core::identity; use libp2p_core::nodes::raw_swarm::{RawSwarm, RawSwarmEvent, IncomingError}; use libp2p_core::{Transport, upgrade, upgrade::OutboundUpgradeExt, upgrade::InboundUpgradeExt}; use libp2p_core::protocols_handler::{ProtocolsHandler, KeepAlive, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr}; @@ -108,8 +109,8 @@ fn raw_swarm_simultaneous_connect() { // TODO: make creating the transport more elegant ; literaly half of the code of the test // is about creating the transport let mut swarm1 = { - let local_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_public_key = local_key.to_public_key(); + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); let transport = libp2p_tcp::TcpConfig::new() .with_upgrade(libp2p_secio::SecioConfig::new(local_key)) .and_then(move |out, endpoint| { @@ -124,8 +125,8 @@ fn raw_swarm_simultaneous_connect() { }; let mut swarm2 = { - let local_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_public_key = local_key.to_public_key(); + let local_key = identity::Keypair::generate_ed25519(); + let local_public_key = local_key.public(); let transport = libp2p_tcp::TcpConfig::new() .with_upgrade(libp2p_secio::SecioConfig::new(local_key)) .and_then(move |out, endpoint| { diff --git a/examples/chat.rs b/examples/chat.rs index 28e7be53..3ebf8494 100644 --- a/examples/chat.rs +++ b/examples/chat.rs @@ -51,8 +51,9 @@ use futures::prelude::*; use libp2p::{ + PeerId, NetworkBehaviour, - secio, + identity, tokio_codec::{FramedRead, LinesCodec} }; @@ -60,8 +61,8 @@ fn main() { env_logger::init(); // Create a random PeerId - let local_key = secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_peer_id = local_key.to_peer_id(); + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); println!("Local peer id: {:?}", local_peer_id); // Set up a an encrypted DNS-enabled TCP Transport over the Mplex and Yamux protocols diff --git a/examples/ipfs-kad.rs b/examples/ipfs-kad.rs index ab511d00..30a67d81 100644 --- a/examples/ipfs-kad.rs +++ b/examples/ipfs-kad.rs @@ -25,14 +25,14 @@ use futures::prelude::*; use libp2p::{ - core::PublicKey, - secio, + PeerId, + identity }; fn main() { // Create a random key for ourselves. - let local_key = secio::SecioKeyPair::ed25519_generated().unwrap(); - let local_peer_id = local_key.to_peer_id(); + let local_key = identity::Keypair::generate_ed25519(); + let local_peer_id = PeerId::from(local_key.public()); // Set up a an encrypted DNS-enabled TCP Transport over the Mplex protocol let transport = libp2p::build_development_transport(local_key); @@ -58,10 +58,10 @@ fn main() { }; // Order Kademlia to search for a peer. - let to_search = if let Some(peer_id) = std::env::args().nth(1) { + let to_search: PeerId = if let Some(peer_id) = std::env::args().nth(1) { peer_id.parse().expect("Failed to parse peer ID to find") } else { - PublicKey::Secp256k1((0..32).map(|_| -> u8 { rand::random() }).collect()).into_peer_id() + identity::Keypair::generate_ed25519().public().into() }; println!("Searching for {:?}", to_search); swarm.find_node(to_search); diff --git a/misc/mdns/src/dns.rs b/misc/mdns/src/dns.rs index da4e1e50..b65d996e 100644 --- a/misc/mdns/src/dns.rs +++ b/misc/mdns/src/dns.rs @@ -337,7 +337,7 @@ impl error::Error for MdnsResponseError {} mod tests { use super::*; use dns_parser::Packet; - use libp2p_core::{PeerId, PublicKey}; + use libp2p_core::identity; use std::time::Duration; #[test] @@ -348,7 +348,7 @@ mod tests { #[test] fn build_query_response_correct() { - let my_peer_id = PeerId::from_public_key(PublicKey::Rsa(vec![1, 2, 3, 4])); + let my_peer_id = identity::Keypair::generate_ed25519().public().into_peer_id(); let addr1 = "/ip4/1.2.3.4/tcp/5000".parse().unwrap(); let addr2 = "/ip6/::1/udp/10000".parse().unwrap(); let query = build_query_response( diff --git a/misc/mdns/src/service.rs b/misc/mdns/src/service.rs index d668b59d..4a6b99d6 100644 --- a/misc/mdns/src/service.rs +++ b/misc/mdns/src/service.rs @@ -53,10 +53,11 @@ pub use dns::MdnsResponseError; /// /// ```rust /// # use futures::prelude::*; +/// # use libp2p_core::{identity, PeerId}; /// # use libp2p_mdns::service::{MdnsService, MdnsPacket}; /// # use std::{io, time::Duration}; /// # fn main() { -/// # let my_peer_id = libp2p_core::PublicKey::Rsa(vec![1, 2, 3, 4]).into_peer_id(); +/// # let my_peer_id = PeerId::from(identity::Keypair::generate_ed25519().public()); /// # let my_listened_addrs = Vec::new(); /// let mut service = MdnsService::new().expect("Error while creating mDNS service"); /// let _future_to_poll = futures::stream::poll_fn(move || -> Poll, io::Error> { @@ -536,7 +537,7 @@ impl<'a> fmt::Debug for MdnsPeer<'a> { #[cfg(test)] mod tests { - use libp2p_core::PublicKey; + use libp2p_core::PeerId; use std::{io, time::Duration}; use tokio::{self, prelude::*}; use crate::service::{MdnsPacket, MdnsService}; @@ -544,8 +545,7 @@ mod tests { #[test] fn discover_ourselves() { let mut service = MdnsService::new().unwrap(); - let peer_id = - PublicKey::Rsa((0..32).map(|_| rand::random::()).collect()).into_peer_id(); + let peer_id = PeerId::random(); let stream = stream::poll_fn(move || -> Poll, io::Error> { loop { let packet = match service.poll() { diff --git a/misc/peer-id-generator/src/main.rs b/misc/peer-id-generator/src/main.rs index cc1ebc61..afa95909 100644 --- a/misc/peer-id-generator/src/main.rs +++ b/misc/peer-id-generator/src/main.rs @@ -18,8 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use libp2p_core::PeerId; -use libp2p_secio::SecioKeyPair; +use libp2p_core::identity; use std::{env, str, thread, time::Duration}; fn main() { @@ -67,13 +66,13 @@ fn main() { for _ in 0..num_cpus::get() { let prefix = prefix.clone(); thread::spawn(move || loop { - let private_key: [u8; 32] = rand::random(); - let generated = SecioKeyPair::secp256k1_raw_key(private_key).unwrap(); - let peer_id: PeerId = generated.to_public_key().into_peer_id(); + let keypair = identity::ed25519::Keypair::generate(); + let secret = keypair.secret(); + let peer_id = identity::PublicKey::Ed25519(keypair.public()).into_peer_id(); let base58 = peer_id.to_base58(); if base58[2..].starts_with(&prefix) { println!("Found {:?}", peer_id); - println!("=> Private key = {:?}", private_key); + println!("=> Private key = {:?}", secret.as_ref()); } }); } diff --git a/protocols/identify/src/identify.rs b/protocols/identify/src/identify.rs index 689ec972..0722ba8a 100644 --- a/protocols/identify/src/identify.rs +++ b/protocols/identify/src/identify.rs @@ -221,15 +221,16 @@ pub enum IdentifyEvent { mod tests { use crate::{Identify, IdentifyEvent}; use futures::prelude::*; + use libp2p_core::identity; use libp2p_core::{upgrade, upgrade::OutboundUpgradeExt, upgrade::InboundUpgradeExt, Swarm, Transport}; use std::io; #[test] fn periodic_id_works() { - let node1_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let node1_public_key = node1_key.to_public_key(); - let node2_key = libp2p_secio::SecioKeyPair::ed25519_generated().unwrap(); - let node2_public_key = node2_key.to_public_key(); + let node1_key = identity::Keypair::generate_ed25519(); + let node1_public_key = node1_key.public(); + let node2_key = identity::Keypair::generate_ed25519(); + let node2_public_key = node2_key.public(); let mut swarm1 = { // TODO: make creating the transport more elegant ; literaly half of the code of the test @@ -253,7 +254,7 @@ mod tests { let mut swarm2 = { // TODO: make creating the transport more elegant ; literaly half of the code of the test // is about creating the transport - let local_peer_id = node2_public_key.clone().into_peer_id(); + let local_peer_id = node2_public_key.clone().into(); let transport = libp2p_tcp::TcpConfig::new() .with_upgrade(libp2p_secio::SecioConfig::new(node2_key)) .and_then(move |out, endpoint| { diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index 7ae57d4f..22454a8c 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -67,10 +67,12 @@ impl IdentifySender where T: AsyncWrite { .map(|addr| addr.into_bytes()) .collect(); + let pubkey_bytes = info.public_key.into_protobuf_encoding(); + let mut message = structs_proto::Identify::new(); message.set_agentVersion(info.agent_version); message.set_protocolVersion(info.protocol_version); - message.set_publicKey(info.public_key.into_protobuf_encoding()); + message.set_publicKey(pubkey_bytes); message.set_listenAddrs(listen_addrs); message.set_observedAddr(observed_addr.to_bytes()); message.set_protocols(RepeatedField::from_vec(info.protocols)); @@ -244,9 +246,12 @@ fn parse_proto_msg(msg: BytesMut) -> Result<(IdentifyInfo, Multiaddr), IoError> addrs }; + let public_key = PublicKey::from_protobuf_encoding(msg.get_publicKey()) + .map_err(|e| IoError::new(IoErrorKind::InvalidData, e))?; + let observed_addr = bytes_to_multiaddr(msg.take_observedAddr())?; let info = IdentifyInfo { - public_key: PublicKey::from_protobuf_encoding(msg.get_publicKey())?, + public_key, protocol_version: msg.take_protocolVersion(), agent_version: msg.take_agentVersion(), listen_addrs, @@ -266,13 +271,15 @@ mod tests { use tokio::runtime::current_thread::Runtime; use libp2p_tcp::TcpConfig; use futures::{Future, Stream}; - use libp2p_core::{PublicKey, Transport, upgrade::{apply_outbound, apply_inbound}}; + use libp2p_core::{identity, Transport, upgrade::{apply_outbound, apply_inbound}}; use std::{io, sync::mpsc, thread}; #[test] fn correct_transfer() { // We open a server and a client, send info from the server to the client, and check that // they were successfully received. + let send_pubkey = identity::Keypair::generate_ed25519().public(); + let recv_pubkey = send_pubkey.clone(); let (tx, rx) = mpsc::channel(); @@ -296,7 +303,7 @@ mod tests { .and_then(|sender| { sender.send( IdentifyInfo { - public_key: PublicKey::Ed25519(vec![1, 2, 3, 4, 5, 7]), + public_key: send_pubkey, protocol_version: "proto_version".to_owned(), agent_version: "agent_version".to_owned(), listen_addrs: vec![ @@ -322,7 +329,7 @@ mod tests { }) .and_then(|RemoteInfo { info, observed_addr, .. }| { assert_eq!(observed_addr, "/ip4/100.101.102.103/tcp/5000".parse().unwrap()); - assert_eq!(info.public_key, PublicKey::Ed25519(vec![1, 2, 3, 4, 5, 7])); + assert_eq!(info.public_key, recv_pubkey); assert_eq!(info.protocol_version, "proto_version"); assert_eq!(info.agent_version, "agent_version"); assert_eq!(info.listen_addrs, diff --git a/protocols/noise/Cargo.toml b/protocols/noise/Cargo.toml index 56fee030..a28058ea 100644 --- a/protocols/noise/Cargo.toml +++ b/protocols/noise/Cargo.toml @@ -14,11 +14,15 @@ lazy_static = "1.2" libp2p-core = { version = "0.4.0", path = "../../core" } log = "0.4" rand = "0.6.5" -snow = { version = "0.5.0-alpha1", default-features = false, features = ["ring-resolver"] } +ring = { version = "0.14", features = ["use_heap"], default-features = false } +snow = { version = "0.5.1", features = ["ring-resolver"], default-features = false } tokio-io = "0.1" +x25519-dalek = "0.5" +zeroize = "0.5" [dev-dependencies] env_logger = "0.6" libp2p-tcp = { version = "0.4.0", path = "../../transports/tcp" } quickcheck = "0.8" tokio = "0.1" +sodiumoxide = "0.2" diff --git a/protocols/noise/src/io.rs b/protocols/noise/src/io.rs index e3cbb3bc..e364c0b7 100644 --- a/protocols/noise/src/io.rs +++ b/protocols/noise/src/io.rs @@ -18,7 +18,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::{NoiseError, keys::{PublicKey, Curve25519}, util::to_array}; +use crate::{NoiseError, Protocol, PublicKey}; use futures::Poll; use log::{debug, trace}; use snow; @@ -52,7 +52,7 @@ impl Buffer { } } -/// A type used during handshake phase, exchanging key material with the remote. +/// A type used during the handshake phase, exchanging key material with the remote. pub(super) struct Handshake(NoiseOutput); impl Handshake { @@ -79,14 +79,16 @@ impl Handshake { /// Finish the handshake. /// - /// This turns the noise session into handshake mode and returns the remote's static + /// This turns the noise session into transport mode and returns the remote's static /// public key as well as the established session for further communication. - pub(super) fn finish(self) -> Result<(PublicKey, NoiseOutput), NoiseError> { + pub(super) fn finish(self) -> Result<(PublicKey, NoiseOutput), NoiseError> + where + C: Protocol + { let s = self.0.session.into_transport_mode()?; let p = s.get_remote_static() .ok_or(NoiseError::InvalidKey) - .and_then(to_array) - .map(PublicKey::new)?; + .and_then(C::public_from_bytes)?; Ok((p, NoiseOutput { session: s, .. self.0 })) } } diff --git a/protocols/noise/src/keys.rs b/protocols/noise/src/keys.rs deleted file mode 100644 index aa905628..00000000 --- a/protocols/noise/src/keys.rs +++ /dev/null @@ -1,173 +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 crate::NoiseError; -use curve25519_dalek::{ - constants::{X25519_BASEPOINT, ED25519_BASEPOINT_POINT}, - edwards::CompressedEdwardsY, - montgomery::MontgomeryPoint, - scalar::Scalar -}; - -#[derive(Debug, Clone)] -pub enum Curve25519 {} - -#[derive(Debug, Clone)] -pub enum Ed25519 {} - -/// ECC public key. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PublicKey { - bytes: [u8; 32], - _marker: std::marker::PhantomData -} - -impl PublicKey { - pub(crate) fn new(bytes: [u8; 32]) -> Self { - PublicKey { bytes, _marker: std::marker::PhantomData } - } -} - -impl PublicKey { - /// Attempt to construct an Ed25519 public key from a `libp2p_core::PublicKey`. - pub fn from_core(key: &libp2p_core::PublicKey) -> Result, NoiseError> { - if let libp2p_core::PublicKey::Ed25519(k) = key { - if k.len() == 32 { - let cp = CompressedEdwardsY::from_slice(k); - cp.decompress().ok_or(NoiseError::InvalidKey)?; - return Ok(PublicKey::new(cp.0)) - } - } - Err(NoiseError::InvalidKey) - } - - /// Convert this Edwards 25519 public key into a Curve 25519 public key. - pub fn into_curve_25519(self) -> PublicKey { - let m = CompressedEdwardsY(self.bytes) - .decompress() - .expect("Constructing a PublicKey ensures this is a valid y-coordinate.") - .to_montgomery(); - PublicKey::new(m.0) - } -} - -impl AsRef<[u8]> for PublicKey { - fn as_ref(&self) -> &[u8] { - &self.bytes - } -} - -/// ECC secret key. -#[derive(Clone)] -pub struct SecretKey { - scalar: Scalar -} - -impl SecretKey { - pub(crate) fn new(bytes: [u8; 32]) -> Self { - SecretKey { - scalar: Scalar::from_bytes_mod_order(bytes) - } - } -} - -impl SecretKey { - /// Compute the public key from this secret key. - /// - /// Performs scalar multiplication with Curve25519's base point. - pub fn public(&self) -> PublicKey { - PublicKey::new(self.x25519(&X25519_BASEPOINT).0) - } - - /// Elliptic-Curve Diffie-Hellman key agreement. - /// - /// Performs scalar multiplication with the given public key. - pub fn ecdh(&self, pk: &PublicKey) -> [u8; 32] { - self.x25519(&MontgomeryPoint(pk.bytes)).0 - } - - /// The actual scalar multiplication with a u-coordinate of Curve25519. - fn x25519(&self, p: &MontgomeryPoint) -> MontgomeryPoint { - let mut s = self.scalar.to_bytes(); - // Cf. RFC 7748 section 5 (page 7) - s[0] &= 248; - s[31] &= 127; - s[31] |= 64; - Scalar::from_bits(s) * p - } -} - -impl AsRef<[u8]> for SecretKey { - fn as_ref(&self) -> &[u8] { - self.scalar.as_bytes() - } -} - -/// ECC secret and public key. -#[derive(Clone)] -pub struct Keypair { - secret: SecretKey, - public: PublicKey -} - -impl Keypair { - /// Create a new keypair. - pub fn new(s: SecretKey, p: PublicKey) -> Self { - Keypair { secret: s, public: p } - } - - /// Access this keypair's secret key. - pub fn secret(&self) -> &SecretKey { - &self.secret - } - - /// Access this keypair's public key. - pub fn public(&self) -> &PublicKey { - &self.public - } -} - -impl Into<(SecretKey, PublicKey)> for Keypair { - fn into(self) -> (SecretKey, PublicKey) { - (self.secret, self.public) - } -} - -impl Keypair { - /// Create a fresh Curve25519 keypair. - pub fn gen_curve25519() -> Self { - let secret = SecretKey { - scalar: Scalar::random(&mut rand::thread_rng()) - }; - let public = secret.public(); - Keypair { secret, public } - } -} - -impl Keypair { - /// Create a fresh Ed25519 keypair. - pub fn gen_ed25519() -> Self { - let scalar = Scalar::random(&mut rand::thread_rng()); - let public = PublicKey::new((scalar * ED25519_BASEPOINT_POINT).compress().0); - let secret = SecretKey { scalar }; - Keypair { secret, public } - } -} - diff --git a/protocols/noise/src/lib.rs b/protocols/noise/src/lib.rs index 92a3db12..2bc68584 100644 --- a/protocols/noise/src/lib.rs +++ b/protocols/noise/src/lib.rs @@ -21,7 +21,8 @@ //! [Noise protocol framework][noise] support for libp2p. //! //! This crate provides `libp2p_core::InboundUpgrade` and `libp2p_core::OutboundUpgrade` -//! implementations for various noise handshake patterns, currently IK, IX, and XX. +//! implementations for various noise handshake patterns (currently IK, IX, and XX) +//! over a particular choice of DH key agreement (currently only X25519). //! //! All upgrades produce as output a pair, consisting of the remote's static public key //! and a `NoiseOutput` which represents the established cryptographic session with the @@ -34,11 +35,11 @@ //! ``` //! use libp2p_core::Transport; //! use libp2p_tcp::TcpConfig; -//! use libp2p_noise::{Keypair, NoiseConfig}; +//! use libp2p_noise::{Keypair, X25519, NoiseConfig}; //! //! # fn main() { -//! let keypair = Keypair::gen_curve25519(); -//! let transport = TcpConfig::new().with_upgrade(NoiseConfig::xx(keypair)); +//! let keys = Keypair::::new(); +//! let transport = TcpConfig::new().with_upgrade(NoiseConfig::xx(keys)); //! // ... //! # } //! ``` @@ -47,96 +48,71 @@ mod error; mod io; -mod keys; -mod util; +mod protocol; pub mod rt1; pub mod rt15; pub use error::NoiseError; pub use io::NoiseOutput; -pub use keys::{Curve25519, PublicKey, SecretKey, Keypair}; +pub use protocol::{Keypair, PublicKey, Protocol, ProtocolParams, IX, IK, XX}; +pub use protocol::x25519::X25519; use libp2p_core::{UpgradeInfo, InboundUpgrade, OutboundUpgrade}; -use lazy_static::lazy_static; -use snow; use tokio_io::{AsyncRead, AsyncWrite}; -use util::Resolver; - -lazy_static! { - static ref PARAMS_IK: snow::params::NoiseParams = "Noise_IK_25519_ChaChaPoly_SHA256" - .parse() - .expect("valid pattern"); - - static ref PARAMS_IX: snow::params::NoiseParams = "Noise_IX_25519_ChaChaPoly_SHA256" - .parse() - .expect("valid pattern"); - - static ref PARAMS_XX: snow::params::NoiseParams = "Noise_XX_25519_ChaChaPoly_SHA256" - .parse() - .expect("valid pattern"); -} - -#[derive(Debug, Clone)] -pub enum IK {} - -#[derive(Debug, Clone)] -pub enum IX {} - -#[derive(Debug, Clone)] -pub enum XX {} +use zeroize::Zeroize; /// The protocol upgrade configuration. #[derive(Clone)] -pub struct NoiseConfig { - keypair: Keypair, - params: snow::params::NoiseParams, +pub struct NoiseConfig { + keys: Keypair, + params: ProtocolParams, remote: R, _marker: std::marker::PhantomData

} -impl NoiseConfig { +impl + Zeroize> NoiseConfig { /// Create a new `NoiseConfig` for the IX handshake pattern. - pub fn ix(kp: Keypair) -> Self { + pub fn ix(keys: Keypair) -> Self { NoiseConfig { - keypair: kp, - params: PARAMS_IX.clone(), + keys, + params: C::params_ix(), remote: (), _marker: std::marker::PhantomData } } } -impl NoiseConfig { +impl + Zeroize> NoiseConfig { /// Create a new `NoiseConfig` for the XX handshake pattern. - pub fn xx(kp: Keypair) -> Self { + pub fn xx(keys: Keypair) -> Self { NoiseConfig { - keypair: kp, - params: PARAMS_XX.clone(), + keys, + params: C::params_xx(), remote: (), _marker: std::marker::PhantomData } } } -impl NoiseConfig { +impl + Zeroize> NoiseConfig { /// Create a new `NoiseConfig` for the IK handshake pattern (recipient side). - pub fn ik_listener(kp: Keypair) -> Self { + pub fn ik_listener(keys: Keypair) -> Self { NoiseConfig { - keypair: kp, - params: PARAMS_IK.clone(), + keys, + params: C::params_ik(), remote: (), _marker: std::marker::PhantomData } } } -impl NoiseConfig> { +impl + Zeroize> NoiseConfig> { /// Create a new `NoiseConfig` for the IK handshake pattern (initiator side). - pub fn ik_dialer(kp: Keypair, remote: PublicKey) -> Self { + pub fn ik_dialer(keys: Keypair, remote: PublicKey) -> Self { NoiseConfig { - keypair: kp, - params: PARAMS_IK.clone(), + keys, + params: C::params_ik(), remote, _marker: std::marker::PhantomData } @@ -145,43 +121,38 @@ impl NoiseConfig> { // Handshake pattern IX ///////////////////////////////////////////////////// -impl UpgradeInfo for NoiseConfig { - type Info = &'static [u8]; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once(b"/noise/ix/25519/chachapoly/sha256/0.1.0") - } -} - -impl InboundUpgrade for NoiseConfig +impl InboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite + T: AsyncRead + AsyncWrite, + NoiseConfig: UpgradeInfo, + C: Protocol + AsRef<[u8]> + Zeroize { - type Output = (PublicKey, NoiseOutput); + type Output = (PublicKey, NoiseOutput); type Error = NoiseError; - type Future = rt1::NoiseInboundFuture; + type Future = rt1::NoiseInboundFuture; fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - let session = snow::Builder::with_resolver(self.params, Box::new(Resolver)) - .local_private_key(self.keypair.secret().as_ref()) + let session = self.params.into_builder() + .local_private_key(self.keys.secret().as_ref()) .build_responder() .map_err(NoiseError::from); rt1::NoiseInboundFuture::new(socket, session) } } -impl OutboundUpgrade for NoiseConfig +impl OutboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite + T: AsyncRead + AsyncWrite, + NoiseConfig: UpgradeInfo, + C: Protocol + AsRef<[u8]> + Zeroize { - type Output = (PublicKey, NoiseOutput); + type Output = (PublicKey, NoiseOutput); type Error = NoiseError; - type Future = rt1::NoiseOutboundFuture; + type Future = rt1::NoiseOutboundFuture; fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - let session = snow::Builder::with_resolver(self.params, Box::new(Resolver)) - .local_private_key(self.keypair.secret().as_ref()) + let session = self.params.into_builder() + .local_private_key(self.keys.secret().as_ref()) .build_initiator() .map_err(NoiseError::from); rt1::NoiseOutboundFuture::new(socket, session) @@ -190,43 +161,38 @@ where // Handshake pattern XX ///////////////////////////////////////////////////// -impl UpgradeInfo for NoiseConfig { - type Info = &'static [u8]; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once(b"/noise/xx/25519/chachapoly/sha256/0.1.0") - } -} - -impl InboundUpgrade for NoiseConfig +impl InboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite + T: AsyncRead + AsyncWrite, + NoiseConfig: UpgradeInfo, + C: Protocol + AsRef<[u8]> + Zeroize { - type Output = (PublicKey, NoiseOutput); + type Output = (PublicKey, NoiseOutput); type Error = NoiseError; - type Future = rt15::NoiseInboundFuture; + type Future = rt15::NoiseInboundFuture; fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - let session = snow::Builder::with_resolver(self.params, Box::new(Resolver)) - .local_private_key(self.keypair.secret().as_ref()) + let session = self.params.into_builder() + .local_private_key(self.keys.secret().as_ref()) .build_responder() .map_err(NoiseError::from); rt15::NoiseInboundFuture::new(socket, session) } } -impl OutboundUpgrade for NoiseConfig +impl OutboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite + T: AsyncRead + AsyncWrite, + NoiseConfig: UpgradeInfo, + C: Protocol + AsRef<[u8]> + Zeroize { - type Output = (PublicKey, NoiseOutput); + type Output = (PublicKey, NoiseOutput); type Error = NoiseError; - type Future = rt15::NoiseOutboundFuture; + type Future = rt15::NoiseOutboundFuture; fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - let session = snow::Builder::with_resolver(self.params, Box::new(Resolver)) - .local_private_key(self.keypair.secret().as_ref()) + let session = self.params.into_builder() + .local_private_key(self.keys.secret().as_ref()) .build_initiator() .map_err(NoiseError::from); rt15::NoiseOutboundFuture::new(socket, session) @@ -235,52 +201,38 @@ where // Handshake pattern IK ///////////////////////////////////////////////////// -impl UpgradeInfo for NoiseConfig { - type Info = &'static [u8]; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once(b"/noise/ik/25519/chachapoly/sha256/0.1.0") - } -} - -impl UpgradeInfo for NoiseConfig> { - type Info = &'static [u8]; - type InfoIter = std::iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { - std::iter::once(b"/noise/ik/25519/chachapoly/sha256/0.1.0") - } -} - -impl InboundUpgrade for NoiseConfig +impl InboundUpgrade for NoiseConfig where - T: AsyncRead + AsyncWrite + T: AsyncRead + AsyncWrite, + NoiseConfig: UpgradeInfo, + C: Protocol + AsRef<[u8]> + Zeroize { - type Output = (PublicKey, NoiseOutput); + type Output = (PublicKey, NoiseOutput); type Error = NoiseError; - type Future = rt1::NoiseInboundFuture; + type Future = rt1::NoiseInboundFuture; fn upgrade_inbound(self, socket: T, _: Self::Info) -> Self::Future { - let session = snow::Builder::with_resolver(self.params, Box::new(Resolver)) - .local_private_key(self.keypair.secret().as_ref()) + let session = self.params.into_builder() + .local_private_key(self.keys.secret().as_ref()) .build_responder() .map_err(NoiseError::from); rt1::NoiseInboundFuture::new(socket, session) } } -impl OutboundUpgrade for NoiseConfig> +impl OutboundUpgrade for NoiseConfig> where - T: AsyncRead + AsyncWrite + T: AsyncRead + AsyncWrite, + NoiseConfig>: UpgradeInfo, + C: Protocol + AsRef<[u8]> + Zeroize { - type Output = (PublicKey, NoiseOutput); + type Output = (PublicKey, NoiseOutput); type Error = NoiseError; - type Future = rt1::NoiseOutboundFuture; + type Future = rt1::NoiseOutboundFuture; fn upgrade_outbound(self, socket: T, _: Self::Info) -> Self::Future { - let session = snow::Builder::with_resolver(self.params, Box::new(Resolver)) - .local_private_key(self.keypair.secret().as_ref()) + let session = self.params.into_builder() + .local_private_key(self.keys.secret().as_ref()) .remote_public_key(self.remote.as_ref()) .build_initiator() .map_err(NoiseError::from); diff --git a/protocols/noise/src/protocol.rs b/protocols/noise/src/protocol.rs new file mode 100644 index 00000000..fb4197a7 --- /dev/null +++ b/protocols/noise/src/protocol.rs @@ -0,0 +1,173 @@ +// 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. + +//! Components of a Noise protocol. + +pub mod x25519; + +use crate::NoiseError; +use rand::FromEntropy; +use zeroize::Zeroize; + +/// The parameters of a Noise protocol, consisting of a choice +/// for a handshake pattern as well as DH, cipher and hash functions. +#[derive(Clone)] +pub struct ProtocolParams(snow::params::NoiseParams); + +impl ProtocolParams { + /// Turn the protocol parameters into a session builder. + pub(crate) fn into_builder(self) -> snow::Builder<'static> { + snow::Builder::with_resolver(self.0, Box::new(Resolver)) + } +} + +/// Type tag for the IK handshake pattern. +#[derive(Debug, Clone)] +pub enum IK {} + +/// Type tag for the IX handshake pattern. +#[derive(Debug, Clone)] +pub enum IX {} + +/// Type tag for the XX handshake pattern. +#[derive(Debug, Clone)] +pub enum XX {} + +/// A Noise protocol over DH keys of type `C`. The choice of `C` determines the +/// protocol parameters for each handshake pattern. +pub trait Protocol { + /// The protocol parameters for the IK handshake pattern. + fn params_ik() -> ProtocolParams; + /// The protocol parameters for the IX handshake pattern. + fn params_ix() -> ProtocolParams; + /// The protocol parameters for the XX handshake pattern. + fn params_xx() -> ProtocolParams; + /// Construct a DH public key from a byte slice. + fn public_from_bytes(s: &[u8]) -> Result, NoiseError>; +} + +/// DH keypair. +#[derive(Clone)] +pub struct Keypair { + secret: SecretKey, + public: PublicKey +} + +impl Keypair { + /// The public key of the DH keypair. + pub fn public(&self) -> &PublicKey { + &self.public + } + + /// The secret key of the DH keypair. + pub fn secret(&self) -> &SecretKey { + &self.secret + } +} + +/// DH secret key. +#[derive(Clone)] +pub struct SecretKey(T); + +impl Drop for SecretKey { + fn drop(&mut self) { + self.0.zeroize() + } +} + +impl + Zeroize> AsRef<[u8]> for SecretKey { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +/// DH public key. +#[derive(Clone)] +pub struct PublicKey(T); + +impl> PartialEq for PublicKey { + fn eq(&self, other: &PublicKey) -> bool { + self.as_ref() == other.as_ref() + } +} + +impl> Eq for PublicKey {} + +impl> AsRef<[u8]> for PublicKey { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +/// Custom `snow::CryptoResolver` which delegates to the `RingResolver` +/// for hash functions and symmetric ciphers, while using x25519-dalek +/// for Curve25519 DH. We do not use the default resolver for any of +/// the choices, because it comes with unwanted additional dependencies, +/// notably rust-crypto, and to avoid being affected by changes to +/// the defaults. +struct Resolver; + +impl snow::resolvers::CryptoResolver for Resolver { + fn resolve_rng(&self) -> Option> { + Some(Box::new(Rng(rand::rngs::StdRng::from_entropy()))) + } + + fn resolve_dh(&self, choice: &snow::params::DHChoice) -> Option> { + if let snow::params::DHChoice::Curve25519 = choice { + Some(Box::new(Keypair::::default())) + } else { + None + } + } + + fn resolve_hash(&self, choice: &snow::params::HashChoice) -> Option> { + snow::resolvers::RingResolver.resolve_hash(choice) + } + + fn resolve_cipher(&self, choice: &snow::params::CipherChoice) -> Option> { + snow::resolvers::RingResolver.resolve_cipher(choice) + } +} + +/// Wrapper around a CSPRNG to implement `snow::Random` trait for. +struct Rng(rand::rngs::StdRng); + +impl rand::RngCore for Rng { + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.0.fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + self.0.try_fill_bytes(dest) + } +} + +impl rand::CryptoRng for Rng {} + +impl snow::types::Random for Rng {} + diff --git a/protocols/noise/src/protocol/x25519.rs b/protocols/noise/src/protocol/x25519.rs new file mode 100644 index 00000000..166a16ba --- /dev/null +++ b/protocols/noise/src/protocol/x25519.rs @@ -0,0 +1,307 @@ +// 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. + +//! Noise protocols based on X25519. + +use crate::{NoiseConfig, NoiseError, Protocol, ProtocolParams}; +use curve25519_dalek::edwards::CompressedEdwardsY; +use lazy_static::lazy_static; +use libp2p_core::UpgradeInfo; +use libp2p_core::identity::ed25519; +use rand::Rng; +use ring::digest::{SHA512, digest}; +use x25519_dalek::{X25519_BASEPOINT_BYTES, x25519}; +use zeroize::Zeroize; + +use super::*; + +lazy_static! { + static ref PARAMS_IK: ProtocolParams = "Noise_IK_25519_ChaChaPoly_SHA256" + .parse() + .map(ProtocolParams) + .expect("Invalid protocol name"); + + static ref PARAMS_IX: ProtocolParams = "Noise_IX_25519_ChaChaPoly_SHA256" + .parse() + .map(ProtocolParams) + .expect("Invalid protocol name"); + + static ref PARAMS_XX: ProtocolParams = "Noise_XX_25519_ChaChaPoly_SHA256" + .parse() + .map(ProtocolParams) + .expect("Invalid protocol name"); +} + +/// A X25519 key. +#[derive(Clone)] +pub struct X25519([u8; 32]); + +impl AsRef<[u8]> for X25519 { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Zeroize for X25519 { + fn zeroize(&mut self) { + self.0.zeroize() + } +} + +impl UpgradeInfo for NoiseConfig { + type Info = &'static [u8]; + type InfoIter = std::iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + std::iter::once(b"/noise/ix/25519/chachapoly/sha256/0.1.0") + } +} + +impl UpgradeInfo for NoiseConfig { + type Info = &'static [u8]; + type InfoIter = std::iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + std::iter::once(b"/noise/xx/25519/chachapoly/sha256/0.1.0") + } +} + +impl UpgradeInfo for NoiseConfig { + type Info = &'static [u8]; + type InfoIter = std::iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + std::iter::once(b"/noise/ik/25519/chachapoly/sha256/0.1.0") + } +} + +impl UpgradeInfo for NoiseConfig> { + type Info = &'static [u8]; + type InfoIter = std::iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + std::iter::once(b"/noise/ik/25519/chachapoly/sha256/0.1.0") + } +} + +/// Noise protocols for X25519. +impl Protocol for X25519 { + fn params_ik() -> ProtocolParams { + PARAMS_IK.clone() + } + + fn params_ix() -> ProtocolParams { + PARAMS_IX.clone() + } + + fn params_xx() -> ProtocolParams { + PARAMS_XX.clone() + } + + fn public_from_bytes(bytes: &[u8]) -> Result, NoiseError> { + if bytes.len() != 32 { + return Err(NoiseError::InvalidKey) + } + let mut pk = [0u8; 32]; + pk.copy_from_slice(bytes); + Ok(PublicKey(X25519(pk))) + } +} + +impl Keypair { + /// An "empty" keypair as a starting state for DH computations in `snow`, + /// which get manipulated through the `snow::types::Dh` interface. + pub(super) fn default() -> Self { + Keypair { + secret: SecretKey(X25519([0u8; 32])), + public: PublicKey(X25519([0u8; 32])) + } + } + + /// Create a new X25519 keypair. + pub fn new() -> Keypair { + let mut sk_bytes = [0u8; 32]; + rand::thread_rng().fill(&mut sk_bytes); + let sk = SecretKey(X25519(sk_bytes)); // Copy + sk_bytes.zeroize(); + Self::from(sk) + } +} + +/// Promote a X25519 secret key into a keypair. +impl From> for Keypair { + fn from(secret: SecretKey) -> Keypair { + let public = PublicKey(X25519(x25519((secret.0).0, X25519_BASEPOINT_BYTES))); + Keypair { secret, public } + } +} + +impl PublicKey { + /// Construct a curve25519 public key from an Ed25519 public key. + pub fn from_ed25519(pk: &ed25519::PublicKey) -> Self { + PublicKey(X25519(CompressedEdwardsY(pk.encode()) + .decompress() + .expect("An Ed25519 public key is a valid point by construction.") + .to_montgomery().0)) + } +} + +impl SecretKey { + /// 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. + /// + /// See also: + /// + /// [Noise: Static Key Reuse](http://www.noiseprotocol.org/noise.html#security-considerations) + /// [Ed25519 to Curve25519](https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519) + pub fn from_ed25519(ed25519_sk: &ed25519::SecretKey) -> Self { + // An Ed25519 public key is derived off the left half of the SHA512 of the + // secret scalar, hence a matching conversion of the secret key must do + // the same to yield a Curve25519 keypair with the same public key. + // let ed25519_sk = ed25519::SecretKey::from(ed); + let mut curve25519_sk: [u8; 32] = [0; 32]; + let hash = digest(&SHA512, ed25519_sk.as_ref()); + curve25519_sk.copy_from_slice(&hash.as_ref()[..32]); + let sk = SecretKey(X25519(curve25519_sk)); // Copy + curve25519_sk.zeroize(); + sk + } +} + +#[doc(hidden)] +impl snow::types::Dh for Keypair { + fn name(&self) -> &'static str { "25519" } + fn pub_len(&self) -> usize { 32 } + fn priv_len(&self) -> usize { 32 } + fn pubkey(&self) -> &[u8] { self.public.as_ref() } + fn privkey(&self) -> &[u8] { self.secret.as_ref() } + + fn set(&mut self, sk: &[u8]) { + let mut secret = [0u8; 32]; + secret.copy_from_slice(&sk[..]); + self.secret = SecretKey(X25519(secret)); // Copy + self.public = PublicKey(X25519(x25519(secret, X25519_BASEPOINT_BYTES))); + secret.zeroize(); + } + + fn generate(&mut self, rng: &mut dyn snow::types::Random) { + let mut secret = [0u8; 32]; + rng.fill_bytes(&mut secret); + self.secret = SecretKey(X25519(secret)); // Copy + self.public = PublicKey(X25519(x25519(secret, X25519_BASEPOINT_BYTES))); + secret.zeroize(); + } + + fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), ()> { + let mut p = [0; 32]; + p.copy_from_slice(&pk[.. 32]); + let ss = x25519((self.secret.0).0, p); + shared_secret[.. 32].copy_from_slice(&ss[..]); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use libp2p_core::identity::ed25519; + use quickcheck::*; + use sodiumoxide::crypto::sign; + use std::os::raw::c_int; + use super::*; + use x25519_dalek::StaticSecret; + + // ed25519 to x25519 keypair conversion must yield the same results as + // obtained through libsodium. + #[test] + fn prop_ed25519_to_x25519_matches_libsodium() { + fn prop() -> bool { + let ed25519 = ed25519::Keypair::generate(); + let x25519 = Keypair::from(SecretKey::from_ed25519(&ed25519.secret())); + + let sodium_sec = ed25519_sk_to_curve25519(&sign::SecretKey(ed25519.encode())); + let sodium_pub = ed25519_pk_to_curve25519(&sign::PublicKey(ed25519.public().encode().clone())); + + let our_pub = x25519.public.0; + // libsodium does the [clamping] of the scalar upon key construction, + // just like x25519-dalek, but this module uses the raw byte-oriented x25519 + // function from x25519-dalek, as defined in RFC7748, so "our" secret scalar + // must be clamped before comparing it to the one computed by libsodium. + // That happens in `StaticSecret::from`. + // + // [clamping]: http://www.lix.polytechnique.fr/~smith/ECC/#scalar-clamping + let our_sec = StaticSecret::from((x25519.secret.0).0).to_bytes(); + + sodium_sec.as_ref() == Some(&our_sec) && + sodium_pub.as_ref() == Some(&our_pub.0) + } + + quickcheck(prop as fn() -> _); + } + + // The x25519 public key obtained through ed25519 keypair conversion + // (and thus derived from the converted secret key) must match the x25519 + // public key derived directly from the ed25519 public key. + #[test] + fn prop_public_ed25519_to_x25519_matches() { + fn prop() -> bool { + let ed25519 = ed25519::Keypair::generate(); + let x25519 = Keypair::from(SecretKey::from_ed25519(&ed25519.secret())); + let x25519_public = PublicKey::from_ed25519(&ed25519.public()); + x25519.public == x25519_public + } + + quickcheck(prop as fn() -> _); + } + + // Bindings to libsodium's ed25519 to curve25519 key conversions, to check that + // they agree with the conversions performed in this module. + + extern "C" { + pub fn crypto_sign_ed25519_pk_to_curve25519(c: *mut u8, e: *const u8) -> c_int; + pub fn crypto_sign_ed25519_sk_to_curve25519(c: *mut u8, e: *const u8) -> c_int; + } + + pub fn ed25519_pk_to_curve25519(k: &sign::PublicKey) -> Option<[u8; 32]> { + let mut out = [0u8; 32]; + unsafe { + if crypto_sign_ed25519_pk_to_curve25519(out.as_mut_ptr(), (&k.0).as_ptr()) == 0 { + Some(out) + } else { + None + } + } + } + + pub fn ed25519_sk_to_curve25519(k: &sign::SecretKey) -> Option<[u8; 32]> { + let mut out = [0u8; 32]; + unsafe { + if crypto_sign_ed25519_sk_to_curve25519(out.as_mut_ptr(), (&k.0).as_ptr()) == 0 { + Some(out) + } else { + None + } + } + } +} + diff --git a/protocols/noise/src/rt1.rs b/protocols/noise/src/rt1.rs index bfcc6b30..d44dbbf9 100644 --- a/protocols/noise/src/rt1.rs +++ b/protocols/noise/src/rt1.rs @@ -21,13 +21,15 @@ //! Futures performing 1 round trip. use crate::{ - error::NoiseError, + Protocol, + PublicKey, + NoiseError, io::{Handshake, NoiseOutput}, - keys::{Curve25519, PublicKey} }; use futures::prelude::*; use snow; use std::mem; +use std::marker::PhantomData; use tokio_io::{AsyncRead, AsyncWrite}; /// A future for inbound upgrades. @@ -36,13 +38,22 @@ use tokio_io::{AsyncRead, AsyncWrite}; /// /// 1. receive message /// 2. send message -pub struct NoiseInboundFuture(InboundState); +pub struct NoiseInboundFuture { + state: InboundState, + _phantom: PhantomData +} -impl NoiseInboundFuture { +impl NoiseInboundFuture { pub(super) fn new(io: T, session: Result) -> Self { match session { - Ok(s) => Self(InboundState::RecvHandshake(Handshake::new(io, s))), - Err(e) => Self(InboundState::Err(e)) + Ok(s) => NoiseInboundFuture { + state: InboundState::RecvHandshake(Handshake::new(io, s)), + _phantom: PhantomData + }, + Err(e) => NoiseInboundFuture { + state: InboundState::Err(e), + _phantom: PhantomData + } } } } @@ -55,39 +66,39 @@ enum InboundState { Done } -impl Future for NoiseInboundFuture +impl> Future for NoiseInboundFuture where T: AsyncRead + AsyncWrite { - type Item = (PublicKey, NoiseOutput); + type Item = (PublicKey, NoiseOutput); type Error = NoiseError; fn poll(&mut self) -> Poll { loop { - match mem::replace(&mut self.0, InboundState::Done) { + match mem::replace(&mut self.state, InboundState::Done) { InboundState::RecvHandshake(mut io) => { if io.receive()?.is_ready() { - self.0 = InboundState::SendHandshake(io) + self.state = InboundState::SendHandshake(io) } else { - self.0 = InboundState::RecvHandshake(io); + self.state = InboundState::RecvHandshake(io); return Ok(Async::NotReady) } } InboundState::SendHandshake(mut io) => { if io.send()?.is_ready() { - self.0 = InboundState::Flush(io) + self.state = InboundState::Flush(io) } else { - self.0 = InboundState::SendHandshake(io); + self.state = InboundState::SendHandshake(io); return Ok(Async::NotReady) } } InboundState::Flush(mut io) => { if io.flush()?.is_ready() { - let result = io.finish()?; - self.0 = InboundState::Done; + let result = io.finish::()?; + self.state = InboundState::Done; return Ok(Async::Ready(result)) } else { - self.0 = InboundState::Flush(io); + self.state = InboundState::Flush(io); return Ok(Async::NotReady) } } @@ -104,13 +115,22 @@ where /// /// 1. send message /// 2. receive message -pub struct NoiseOutboundFuture(OutboundState); +pub struct NoiseOutboundFuture { + state: OutboundState, + _phantom: PhantomData +} -impl NoiseOutboundFuture { +impl NoiseOutboundFuture { pub(super) fn new(io: T, session: Result) -> Self { match session { - Ok(s) => Self(OutboundState::SendHandshake(Handshake::new(io, s))), - Err(e) => Self(OutboundState::Err(e)) + Ok(s) => NoiseOutboundFuture { + state: OutboundState::SendHandshake(Handshake::new(io, s)), + _phantom: PhantomData + }, + Err(e) => NoiseOutboundFuture { + state: OutboundState::Err(e), + _phantom: PhantomData + } } } } @@ -123,39 +143,39 @@ enum OutboundState { Done } -impl Future for NoiseOutboundFuture +impl> Future for NoiseOutboundFuture where T: AsyncRead + AsyncWrite { - type Item = (PublicKey, NoiseOutput); + type Item = (PublicKey, NoiseOutput); type Error = NoiseError; fn poll(&mut self) -> Poll { loop { - match mem::replace(&mut self.0, OutboundState::Done) { + match mem::replace(&mut self.state, OutboundState::Done) { OutboundState::SendHandshake(mut io) => { if io.send()?.is_ready() { - self.0 = OutboundState::Flush(io) + self.state = OutboundState::Flush(io) } else { - self.0 = OutboundState::SendHandshake(io); + self.state = OutboundState::SendHandshake(io); return Ok(Async::NotReady) } } OutboundState::Flush(mut io) => { if io.flush()?.is_ready() { - self.0 = OutboundState::RecvHandshake(io) + self.state = OutboundState::RecvHandshake(io) } else { - self.0 = OutboundState::Flush(io); + self.state = OutboundState::Flush(io); return Ok(Async::NotReady) } } OutboundState::RecvHandshake(mut io) => { if io.receive()?.is_ready() { - let result = io.finish()?; - self.0 = OutboundState::Done; + let result = io.finish::()?; + self.state = OutboundState::Done; return Ok(Async::Ready(result)) } else { - self.0 = OutboundState::RecvHandshake(io); + self.state = OutboundState::RecvHandshake(io); return Ok(Async::NotReady) } } diff --git a/protocols/noise/src/rt15.rs b/protocols/noise/src/rt15.rs index c15e8fe6..f1b4f819 100644 --- a/protocols/noise/src/rt15.rs +++ b/protocols/noise/src/rt15.rs @@ -21,13 +21,15 @@ //! Futures performing 1.5 round trips. use crate::{ - error::NoiseError, + Protocol, + PublicKey, + NoiseError, io::{Handshake, NoiseOutput}, - keys::{Curve25519, PublicKey} }; use futures::prelude::*; use snow; use std::mem; +use std::marker::PhantomData; use tokio_io::{AsyncRead, AsyncWrite}; /// A future for inbound upgrades. @@ -37,13 +39,22 @@ use tokio_io::{AsyncRead, AsyncWrite}; /// 1. receive message /// 2. send message /// 3. receive message -pub struct NoiseInboundFuture(InboundState); +pub struct NoiseInboundFuture { + state: InboundState, + _phantom: PhantomData +} -impl NoiseInboundFuture { +impl NoiseInboundFuture { pub(super) fn new(io: T, session: Result) -> Self { match session { - Ok(s) => Self(InboundState::RecvHandshake1(Handshake::new(io, s))), - Err(e) => Self(InboundState::Err(e)) + Ok(s) => NoiseInboundFuture { + state: InboundState::RecvHandshake1(Handshake::new(io, s)), + _phantom: PhantomData + }, + Err(e) => NoiseInboundFuture { + state: InboundState::Err(e), + _phantom: PhantomData + } } } } @@ -57,47 +68,47 @@ enum InboundState { Done } -impl Future for NoiseInboundFuture +impl> Future for NoiseInboundFuture where T: AsyncRead + AsyncWrite { - type Item = (PublicKey, NoiseOutput); + type Item = (PublicKey, NoiseOutput); type Error = NoiseError; fn poll(&mut self) -> Poll { loop { - match mem::replace(&mut self.0, InboundState::Done) { + match mem::replace(&mut self.state, InboundState::Done) { InboundState::RecvHandshake1(mut io) => { if io.receive()?.is_ready() { - self.0 = InboundState::SendHandshake(io) + self.state = InboundState::SendHandshake(io) } else { - self.0 = InboundState::RecvHandshake1(io); + self.state = InboundState::RecvHandshake1(io); return Ok(Async::NotReady) } } InboundState::SendHandshake(mut io) => { if io.send()?.is_ready() { - self.0 = InboundState::Flush(io) + self.state = InboundState::Flush(io) } else { - self.0 = InboundState::SendHandshake(io); + self.state = InboundState::SendHandshake(io); return Ok(Async::NotReady) } } InboundState::Flush(mut io) => { if io.flush()?.is_ready() { - self.0 = InboundState::RecvHandshake2(io) + self.state = InboundState::RecvHandshake2(io) } else { - self.0 = InboundState::Flush(io); + self.state = InboundState::Flush(io); return Ok(Async::NotReady) } } InboundState::RecvHandshake2(mut io) => { if io.receive()?.is_ready() { - let result = io.finish()?; - self.0 = InboundState::Done; + let result = io.finish::()?; + self.state = InboundState::Done; return Ok(Async::Ready(result)) } else { - self.0 = InboundState::RecvHandshake2(io); + self.state = InboundState::RecvHandshake2(io); return Ok(Async::NotReady) } } @@ -115,13 +126,22 @@ where /// 1. send message /// 2. receive message /// 3. send message -pub struct NoiseOutboundFuture(OutboundState); +pub struct NoiseOutboundFuture { + state: OutboundState, + _phantom: PhantomData +} -impl NoiseOutboundFuture { +impl NoiseOutboundFuture { pub(super) fn new(io: T, session: Result) -> Self { match session { - Ok(s) => Self(OutboundState::SendHandshake1(Handshake::new(io, s))), - Err(e) => Self(OutboundState::Err(e)) + Ok(s) => NoiseOutboundFuture { + state: OutboundState::SendHandshake1(Handshake::new(io, s)), + _phantom: PhantomData + }, + Err(e) => NoiseOutboundFuture { + state: OutboundState::Err(e), + _phantom: PhantomData + } } } } @@ -136,55 +156,55 @@ enum OutboundState { Done } -impl Future for NoiseOutboundFuture +impl> Future for NoiseOutboundFuture where T: AsyncRead + AsyncWrite { - type Item = (PublicKey, NoiseOutput); + type Item = (PublicKey, NoiseOutput); type Error = NoiseError; fn poll(&mut self) -> Poll { loop { - match mem::replace(&mut self.0, OutboundState::Done) { + match mem::replace(&mut self.state, OutboundState::Done) { OutboundState::SendHandshake1(mut io) => { if io.send()?.is_ready() { - self.0 = OutboundState::Flush1(io) + self.state = OutboundState::Flush1(io) } else { - self.0 = OutboundState::SendHandshake1(io); + self.state = OutboundState::SendHandshake1(io); return Ok(Async::NotReady) } } OutboundState::Flush1(mut io) => { if io.flush()?.is_ready() { - self.0 = OutboundState::RecvHandshake(io) + self.state = OutboundState::RecvHandshake(io) } else { - self.0 = OutboundState::Flush1(io); + self.state = OutboundState::Flush1(io); return Ok(Async::NotReady) } } OutboundState::RecvHandshake(mut io) => { if io.receive()?.is_ready() { - self.0 = OutboundState::SendHandshake2(io) + self.state = OutboundState::SendHandshake2(io) } else { - self.0 = OutboundState::RecvHandshake(io); + self.state = OutboundState::RecvHandshake(io); return Ok(Async::NotReady) } } OutboundState::SendHandshake2(mut io) => { if io.send()?.is_ready() { - self.0 = OutboundState::Flush2(io) + self.state = OutboundState::Flush2(io) } else { - self.0 = OutboundState::SendHandshake2(io); + self.state = OutboundState::SendHandshake2(io); return Ok(Async::NotReady) } } OutboundState::Flush2(mut io) => { if io.flush()?.is_ready() { - let result = io.finish()?; - self.0 = OutboundState::Done; + let result = io.finish::()?; + self.state = OutboundState::Done; return Ok(Async::Ready(result)) } else { - self.0 = OutboundState::Flush2(io); + self.state = OutboundState::Flush2(io); return Ok(Async::NotReady) } } diff --git a/protocols/noise/src/util.rs b/protocols/noise/src/util.rs deleted file mode 100644 index b9a55b18..00000000 --- a/protocols/noise/src/util.rs +++ /dev/null @@ -1,128 +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 crate::{keys::{Curve25519, Keypair, SecretKey, PublicKey}, NoiseError}; -use rand::FromEntropy; - -pub(crate) fn to_array(bytes: &[u8]) -> Result<[u8; 32], NoiseError> { - if bytes.len() != 32 { - return Err(NoiseError::InvalidKey) - } - let mut m = [0; 32]; - m.copy_from_slice(bytes); - Ok(m) -} - -/// Custom `snow::CryptoResolver` which uses `ring` as much as possible. -pub(crate) struct Resolver; - -impl snow::resolvers::CryptoResolver for Resolver { - fn resolve_rng(&self) -> Option> { - Some(Box::new(Rng(rand::rngs::StdRng::from_entropy()))) - } - - fn resolve_dh(&self, choice: &snow::params::DHChoice) -> Option> { - if let snow::params::DHChoice::Curve25519 = choice { - Some(Box::new(X25519 { keypair: Keypair::gen_curve25519() })) - } else { - None - } - } - - fn resolve_hash(&self, choice: &snow::params::HashChoice) -> Option> { - snow::resolvers::RingResolver.resolve_hash(choice) - } - - fn resolve_cipher(&self, choice: &snow::params::CipherChoice) -> Option> { - snow::resolvers::RingResolver.resolve_cipher(choice) - } -} - -/// Wrapper around a CPRNG to implement `snow::Random` trait for. -struct Rng(rand::rngs::StdRng); - -impl rand::RngCore for Rng { - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest) - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { - self.0.try_fill_bytes(dest) - } -} - -impl rand::CryptoRng for Rng {} - -impl snow::types::Random for Rng {} - -/// X25519 Diffie-Hellman key agreement. -struct X25519 { - keypair: Keypair -} - -impl snow::types::Dh for X25519 { - fn name(&self) -> &'static str { "25519" } - - fn pub_len(&self) -> usize { 32 } - - fn priv_len(&self) -> usize { 32 } - - fn pubkey(&self) -> &[u8] { - &self.keypair.public().as_ref() - } - - fn privkey(&self) -> &[u8] { - &self.keypair.secret().as_ref() - } - - fn set(&mut self, privkey: &[u8]) { - let mut s = [0; 32]; - s.copy_from_slice(privkey); - let secret = SecretKey::new(s); - let public = secret.public(); - self.keypair = Keypair::new(secret, public) - } - - fn generate(&mut self, rng: &mut dyn snow::types::Random) { - let mut s = [0; 32]; - rng.fill_bytes(&mut s); - let secret = SecretKey::new(s); - let public = secret.public(); - self.keypair = Keypair::new(secret, public) - } - - fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), ()> { - let mut p = [0; 32]; - p.copy_from_slice(&pubkey[.. 32]); - let public = PublicKey::new(p); - let result = self.keypair.secret().ecdh(&public); - out[.. 32].copy_from_slice(&result[..]); - Ok(()) - } -} - diff --git a/protocols/noise/tests/smoke.rs b/protocols/noise/tests/smoke.rs index 4b64b22d..f41d80f4 100644 --- a/protocols/noise/tests/smoke.rs +++ b/protocols/noise/tests/smoke.rs @@ -20,7 +20,7 @@ use futures::{future::Either, prelude::*}; use libp2p_core::{Transport, upgrade::{apply_inbound, apply_outbound}}; -use libp2p_noise::{Keypair, PublicKey, Curve25519, NoiseConfig}; +use libp2p_noise::{Keypair, X25519, NoiseConfig}; use libp2p_tcp::TcpConfig; use log::info; use quickcheck::QuickCheck; @@ -30,10 +30,11 @@ use tokio::{self, io}; fn xx() { let _ = env_logger::try_init(); fn prop(message: Vec) -> bool { - let server_keypair = Keypair::gen_curve25519(); + + let server_keypair = Keypair::::new(); let server_transport = TcpConfig::new().with_upgrade(NoiseConfig::xx(server_keypair)); - let client_keypair = Keypair::gen_curve25519(); + let client_keypair = Keypair::::new(); let client_transport = TcpConfig::new().with_upgrade(NoiseConfig::xx(client_keypair)); run(server_transport, client_transport, message); @@ -47,10 +48,10 @@ fn ix() { let _ = env_logger::try_init(); fn prop(message: Vec) -> bool { - let server_keypair = Keypair::gen_curve25519(); + let server_keypair = Keypair::::new(); let server_transport = TcpConfig::new().with_upgrade(NoiseConfig::ix(server_keypair)); - let client_keypair = Keypair::gen_curve25519(); + let client_keypair = Keypair::::new(); let client_transport = TcpConfig::new().with_upgrade(NoiseConfig::ix(client_keypair)); run(server_transport, client_transport, message); @@ -63,7 +64,7 @@ fn ix() { fn ik_xx() { let _ = env_logger::try_init(); fn prop(message: Vec) -> bool { - let server_keypair = Keypair::gen_curve25519(); + let server_keypair = Keypair::::new(); let server_public = server_keypair.public().clone(); let server_transport = TcpConfig::new() .and_then(move |output, endpoint| { @@ -74,7 +75,7 @@ fn ik_xx() { } }); - let client_keypair = Keypair::gen_curve25519(); + let client_keypair = Keypair::::new(); let client_transport = TcpConfig::new() .and_then(move |output, endpoint| { if endpoint.is_dialer() { @@ -90,14 +91,14 @@ fn ik_xx() { QuickCheck::new().max_tests(30).quickcheck(prop as fn(Vec) -> bool) } -fn run(server_transport: T, client_transport: U, message1: Vec) +fn run(server_transport: T, client_transport: U, message1: Vec) where - T: Transport, A)>, + T: Transport, T::Dial: Send + 'static, T::Listener: Send + 'static, T::ListenerUpgrade: Send + 'static, A: io::AsyncRead + io::AsyncWrite + Send + 'static, - U: Transport, B)>, + U: Transport, U::Dial: Send + 'static, U::Listener: Send + 'static, U::ListenerUpgrade: Send + 'static, diff --git a/protocols/secio/Cargo.toml b/protocols/secio/Cargo.toml index ffe379dd..a92d5869 100644 --- a/protocols/secio/Cargo.toml +++ b/protocols/secio/Cargo.toml @@ -26,7 +26,6 @@ lazy_static = "1.2.0" rw-stream-sink = { version = "0.1.0", path = "../../misc/rw-stream-sink" } tokio-io = "0.1.0" sha2 = "0.8.0" -ed25519-dalek = "1.0.0-pre.1" hmac = "0.7.0" [target.'cfg(not(any(target_os = "emscripten", target_os = "unknown")))'.dependencies] diff --git a/protocols/secio/src/error.rs b/protocols/secio/src/error.rs index 0646eae6..ce058e7b 100644 --- a/protocols/secio/src/error.rs +++ b/protocols/secio/src/error.rs @@ -33,7 +33,7 @@ pub enum SecioError { IoError(IoError), /// Protocol buffer error. - Protobuf(ProtobufError), + ProtobufError(ProtobufError), /// Failed to parse one of the handshake protobuf messages. HandshakeParsingFailure, @@ -79,7 +79,7 @@ impl error::Error for SecioError { fn cause(&self) -> Option<&dyn error::Error> { match *self { SecioError::IoError(ref err) => Some(err), - SecioError::Protobuf(ref err) => Some(err), + SecioError::ProtobufError(ref err) => Some(err), // TODO: The type doesn't implement `Error` /*SecioError::CipherError(ref err) => { Some(err) @@ -95,8 +95,8 @@ impl fmt::Display for SecioError { match self { SecioError::IoError(e) => write!(f, "I/O error: {}", e), - SecioError::Protobuf(e) => - write!(f, "protobuf 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 => @@ -144,6 +144,6 @@ impl From for SecioError { impl From for SecioError { #[inline] fn from(err: ProtobufError) -> SecioError { - SecioError::Protobuf(err) + SecioError::ProtobufError(err) } } diff --git a/protocols/secio/src/handshake.rs b/protocols/secio/src/handshake.rs index 77cc3b1e..6e0e989f 100644 --- a/protocols/secio/src/handshake.rs +++ b/protocols/secio/src/handshake.rs @@ -22,7 +22,6 @@ use crate::algo_support; use bytes::BytesMut; use crate::codec::{full_codec, FullCodec, Hmac}; use crate::stream_cipher::{Cipher, ctr}; -use ed25519_dalek::{PublicKey as Ed25519PublicKey, Signature as Ed25519Signature}; use crate::error::SecioError; use crate::exchange; use futures::future; @@ -34,23 +33,13 @@ use log::{debug, trace}; use protobuf::parse_from_bytes as protobuf_parse_from_bytes; use protobuf::Message as ProtobufMessage; use rand::{self, RngCore}; -#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] -use ring::signature::{RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_SHA256, verify as ring_verify}; -#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] -use ring::rand::SystemRandom; -#[cfg(feature = "secp256k1")] -use secp256k1; use sha2::{Digest as ShaDigestTrait, Sha256}; use std::cmp::{self, Ordering}; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use crate::structs_proto::{Exchange, Propose}; use tokio_io::codec::length_delimited; use tokio_io::{AsyncRead, AsyncWrite}; -#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] -use untrusted::Input as UntrustedInput; -use crate::{KeyAgreement, SecioConfig, SecioKeyPairInner}; -#[cfg(feature = "secp256k1")] -use crate::SECP256K1; +use crate::{KeyAgreement, SecioConfig}; // This struct contains the whole context of a handshake, and is filled progressively // throughout the various parts of the handshake. @@ -63,8 +52,8 @@ struct HandshakeContext { struct Local { // Locally-generated random number. The array size can be changed without any repercussion. nonce: [u8; 16], - // Our local public key protobuf structure encoded in bytes: - public_key_in_protobuf_bytes: Vec, + // Our encoded local public key + public_key_encoded: Vec, // Our local proposition's raw bytes: proposition_bytes: Vec } @@ -124,13 +113,12 @@ impl HandshakeContext<()> { .try_fill_bytes(&mut nonce) .map_err(|_| SecioError::NonceGenerationFailed)?; - let public_key_in_protobuf_bytes = - self.config.key.to_public_key().into_protobuf_encoding(); + let public_key_encoded = self.config.key.public().into_protobuf_encoding(); // Send our proposition with our nonce, public key and supported protocols. let mut proposition = Propose::new(); proposition.set_rand(nonce.to_vec()); - proposition.set_pubkey(public_key_in_protobuf_bytes.clone()); + proposition.set_pubkey(public_key_encoded.clone()); if let Some(ref p) = self.config.agreements_prop { trace!("agreements proposition: {}", p); @@ -162,7 +150,7 @@ impl HandshakeContext<()> { config: self.config, state: Local { nonce, - public_key_in_protobuf_bytes, + public_key_encoded, proposition_bytes } }) @@ -180,10 +168,10 @@ impl HandshakeContext { } }; - let public_key_in_protobuf_bytes = prop.take_pubkey(); + let public_key_encoded = prop.take_pubkey(); let nonce = prop.take_rand(); - let pubkey = match PublicKey::from_protobuf_encoding(&public_key_in_protobuf_bytes) { + let pubkey = match PublicKey::from_protobuf_encoding(&public_key_encoded) { Ok(p) => p, Err(_) => { debug!("failed to parse remote's proposition's pubkey protobuf"); @@ -196,14 +184,14 @@ impl HandshakeContext { let hashes_ordering = { let oh1 = { let mut ctx = Sha256::new(); - ctx.input(&public_key_in_protobuf_bytes); + ctx.input(&public_key_encoded); ctx.input(&self.state.nonce); ctx.result() }; let oh2 = { let mut ctx = Sha256::new(); - ctx.input(&self.state.public_key_in_protobuf_bytes); + ctx.input(&self.state.public_key_encoded); ctx.input(&nonce); ctx.result() }; @@ -368,37 +356,10 @@ where let mut exchange = Exchange::new(); exchange.set_epubkey(tmp_pub_key); - exchange.set_signature({ - match context.config.key.inner { - #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] - SecioKeyPairInner::Rsa { ref private, .. } => { - let mut signature = vec![0; private.public_modulus_len()]; - let rng = SystemRandom::new(); - match private.sign(&RSA_PKCS1_SHA256, &rng, &data_to_sign, &mut signature) { - Ok(_) => (), - Err(_) => { - debug!("failed to sign local exchange"); - return Err(SecioError::SigningFailure); - }, - }; - - signature - }, - SecioKeyPairInner::Ed25519 { ref key_pair } => { - let signature = key_pair.sign(&data_to_sign); - signature.to_bytes().to_vec() - }, - #[cfg(feature = "secp256k1")] - SecioKeyPairInner::Secp256k1 { ref private } => { - let data_to_sign = Sha256::digest(&data_to_sign); - let message = secp256k1::Message::from_slice(data_to_sign.as_ref()) - .expect("digest output length doesn't match secp256k1 input length"); - SECP256K1 - .sign(&message, private) - .serialize_der() - }, - } - }); + match context.config.key.sign(&data_to_sign) { + Ok(sig) => exchange.set_signature(sig), + Err(_) => return Err(SecioError::SigningFailure) + } exchange }; let local_exch = exchange.write_to_bytes()?; @@ -445,72 +406,9 @@ where data_to_verify.extend_from_slice(&context.state.remote.local.proposition_bytes); data_to_verify.extend_from_slice(remote_exch.get_epubkey()); - match context.state.remote.public_key { - #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] - PublicKey::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 ring_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) - }, - } - }, - PublicKey::Ed25519(ref remote_public_key) => { - let signature = Ed25519Signature::from_bytes(remote_exch.get_signature()); - let pubkey = Ed25519PublicKey::from_bytes(remote_public_key); - - if let (Ok(signature), Ok(pubkey)) = (signature, pubkey) { - match pubkey.verify(&data_to_verify, &signature) { - Ok(()) => (), - Err(_) => { - debug!("failed to verify the remote's signature"); - return Err(SecioError::SignatureVerificationFailed) - } - } - } else { - debug!("the remote's signature or publickey are in the wrong format"); - return Err(SecioError::SignatureVerificationFailed) - } - }, - #[cfg(feature = "secp256k1")] - PublicKey::Secp256k1(ref remote_public_key) => { - let data_to_verify = Sha256::digest(&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 signature = secp256k1::Signature::from_der(remote_exch.get_signature()); - let remote_public_key = secp256k1::key::PublicKey::from_slice(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) - } - }, - #[cfg(any(target_os = "emscripten", target_os = "unknown"))] - PublicKey::Rsa(_) => { - debug!("support for RSA was disabled at compile-time"); - return Err(SecioError::SignatureVerificationFailed); - }, - #[cfg(not(feature = "secp256k1"))] - PublicKey::Secp256k1(_) => { - debug!("support for secp256k1 was disabled at compile-time"); - return Err(SecioError::SignatureVerificationFailed); - } - }; + if !context.state.remote.public_key.verify(&data_to_verify, remote_exch.get_signature()) { + return Err(SecioError::SignatureVerificationFailed) + } trace!("successfully verified the remote's signature"); Ok((remote_exch, socket, context)) @@ -625,29 +523,27 @@ where D: ::hmac::digest::Input + ::hmac::digest::BlockInput + #[cfg(test)] mod tests { use bytes::BytesMut; + use libp2p_core::identity; use tokio::runtime::current_thread::Runtime; use tokio_tcp::{TcpListener, TcpStream}; - use crate::SecioError; + use crate::{SecioConfig, SecioError}; use super::handshake; use super::stretch_key; use crate::algo_support::Digest; use crate::codec::Hmac; use futures::prelude::*; - use crate::{SecioConfig, SecioKeyPair}; #[test] #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] fn handshake_with_self_succeeds_rsa() { 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 mut private = include_bytes!("../tests/test-rsa-private-key.pk8").to_vec(); + identity::Keypair::rsa_from_pkcs8(&mut private).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 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)); @@ -655,8 +551,8 @@ mod tests { #[test] fn handshake_with_self_succeeds_ed25519() { - let key1 = SecioKeyPair::ed25519_generated().unwrap(); - let key2 = SecioKeyPair::ed25519_generated().unwrap(); + let key1 = identity::Keypair::generate_ed25519(); + let key2 = identity::Keypair::generate_ed25519(); handshake_with_self_succeeds(SecioConfig::new(key1), SecioConfig::new(key2)); } @@ -664,13 +560,13 @@ mod tests { #[cfg(feature = "secp256k1")] 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 mut key = include_bytes!("../tests/test-secp256k1-private-key.der").to_vec(); + identity::Keypair::secp256k1_from_der(&mut key).unwrap() }; let key2 = { - let key = include_bytes!("../tests/test-secp256k1-private-key-2.der"); - SecioKeyPair::secp256k1_from_der(&key[..]).unwrap() + 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)); diff --git a/protocols/secio/src/lib.rs b/protocols/secio/src/lib.rs index 0b5b2425..24c057a7 100644 --- a/protocols/secio/src/lib.rs +++ b/protocols/secio/src/lib.rs @@ -31,8 +31,8 @@ //! ```no_run //! # fn main() { //! use futures::Future; -//! use libp2p_secio::{SecioConfig, SecioKeyPair, SecioOutput}; -//! use libp2p_core::{Multiaddr, upgrade::apply_inbound}; +//! use libp2p_secio::{SecioConfig, SecioOutput}; +//! use libp2p_core::{Multiaddr, identity, upgrade::apply_inbound}; //! use libp2p_core::transport::Transport; //! use libp2p_tcp::TcpConfig; //! use tokio_io::io::write_all; @@ -40,12 +40,9 @@ //! //! let dialer = TcpConfig::new() //! .with_upgrade({ -//! # let private_key = b""; -//! //let private_key = include_bytes!("test-rsa-private-key.pk8"); -//! # let public_key = vec![]; -//! //let public_key = include_bytes!("test-rsa-public-key.der").to_vec(); -//! // See the documentation of `SecioKeyPair`. -//! let keypair = SecioKeyPair::rsa_from_pkcs8(private_key, public_key).unwrap(); +//! # let private_key = &mut []; +//! // See the documentation of `identity::Keypair`. +//! let keypair = identity::Keypair::rsa_from_pkcs8(private_key).unwrap(); //! SecioConfig::new(keypair) //! }) //! .map(|out: SecioOutput<_>, _| out.stream); @@ -84,25 +81,15 @@ extern crate stdweb; pub use self::error::SecioError; -#[cfg(feature = "secp256k1")] -use asn1_der::{FromDerObject, DerObject}; use bytes::BytesMut; -use ed25519_dalek::Keypair as Ed25519KeyPair; use futures::stream::MapErr as StreamMapErr; use futures::{Future, Poll, Sink, StartSend, Stream}; -use lazy_static::lazy_static; -use libp2p_core::{PeerId, PublicKey, upgrade::{UpgradeInfo, InboundUpgrade, OutboundUpgrade}}; +use libp2p_core::{PublicKey, identity, upgrade::{UpgradeInfo, InboundUpgrade, OutboundUpgrade}}; use log::debug; -#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] -use ring::signature::RsaKeyPair; use rw_stream_sink::RwStreamSink; -use std::error::Error; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; use std::iter; -use std::sync::Arc; use tokio_io::{AsyncRead, AsyncWrite}; -#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] -use untrusted::Input; mod algo_support; mod codec; @@ -116,18 +103,12 @@ pub use crate::algo_support::Digest; pub use crate::exchange::KeyAgreement; pub use crate::stream_cipher::Cipher; -// Cached `Secp256k1` context, to avoid recreating it every time. -#[cfg(feature = "secp256k1")] -lazy_static! { - static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); -} - /// 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: SecioKeyPair, + pub(crate) key: identity::Keypair, pub(crate) agreements_prop: Option, pub(crate) ciphers_prop: Option, pub(crate) digests_prop: Option @@ -135,7 +116,7 @@ pub struct SecioConfig { impl SecioConfig { /// Create a new `SecioConfig` with the given keypair. - pub fn new(kp: SecioKeyPair) -> Self { + pub fn new(kp: identity::Keypair) -> Self { SecioConfig { key: kp, agreements_prop: None, @@ -188,163 +169,6 @@ impl SecioConfig { } } -/// Private and public keys of the local node. -/// -/// # Generating offline keys with OpenSSL -/// -/// ## RSA -/// -/// Generating the keys: -/// -/// ```text -/// 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 -/// let key_pair = SecioKeyPair::rsa_from_pkcs8(include_bytes!("private.pk8"), -/// include_bytes!("public.der")); -/// ``` -/// -#[derive(Clone)] -pub struct SecioKeyPair { - inner: SecioKeyPairInner, -} - -impl SecioKeyPair { - /// Builds a `SecioKeyPair` from a PKCS8 private key and public key. - #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] - pub fn rsa_from_pkcs8

( - private: &[u8], - public: P, - ) -> Result> - where - P: Into>, - { - let private = RsaKeyPair::from_pkcs8(Input::from(&private[..])).map_err(Box::new)?; - - Ok(SecioKeyPair { - inner: SecioKeyPairInner::Rsa { - public: public.into(), - private: Arc::new(private), - }, - }) - } - - /// Generates a new Ed25519 key pair and uses it. - pub fn ed25519_generated() -> Result> { - let mut csprng = rand::thread_rng(); - let keypair: Ed25519KeyPair = Ed25519KeyPair::generate::<_>(&mut csprng); - Ok(SecioKeyPair { - inner: SecioKeyPairInner::Ed25519 { - key_pair: Arc::new(keypair), - } - }) - } - - /// Builds a `SecioKeyPair` from a raw ed25519 32 bytes private key. - /// - /// Returns an error if the slice doesn't have the correct length. - pub fn ed25519_raw_key(key: impl AsRef<[u8]>) -> Result> { - let secret = ed25519_dalek::SecretKey::from_bytes(key.as_ref()) - .map_err(|err| err.to_string())?; - let public = ed25519_dalek::PublicKey::from(&secret); - - Ok(SecioKeyPair { - inner: SecioKeyPairInner::Ed25519 { - key_pair: Arc::new(Ed25519KeyPair { - secret, - public, - }), - } - }) - } - - /// Generates a new random sec256k1 key pair. - #[cfg(feature = "secp256k1")] - pub fn secp256k1_generated() -> Result> { - let private = secp256k1::key::SecretKey::new(&mut secp256k1::rand::thread_rng()); - Ok(SecioKeyPair { - inner: SecioKeyPairInner::Secp256k1 { private }, - }) - } - - /// Builds a `SecioKeyPair` from a raw secp256k1 32 bytes private key. - #[cfg(feature = "secp256k1")] - pub fn secp256k1_raw_key(key: K) -> Result> - where - K: AsRef<[u8]>, - { - let private = secp256k1::key::SecretKey::from_slice(key.as_ref())?; - - Ok(SecioKeyPair { - inner: SecioKeyPairInner::Secp256k1 { private }, - }) - } - - /// Builds a `SecioKeyPair` from a secp256k1 private key in DER format. - #[cfg(feature = "secp256k1")] - pub fn secp256k1_from_der(key: K) -> Result> - where - K: AsRef<[u8]>, - { - // See ECPrivateKey in https://tools.ietf.org/html/rfc5915 - let obj: Vec = - FromDerObject::deserialize(key.as_ref().iter()).map_err(|err| err.to_string())?; - let priv_key_obj = obj.into_iter() - .nth(1) - .ok_or_else(|| "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) -> PublicKey { - match self.inner { - #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] - SecioKeyPairInner::Rsa { ref public, .. } => PublicKey::Rsa(public.clone()), - SecioKeyPairInner::Ed25519 { ref key_pair } => { - PublicKey::Ed25519(key_pair.public.as_bytes().to_vec()) - } - #[cfg(feature = "secp256k1")] - SecioKeyPairInner::Secp256k1 { ref private } => { - let pubkey = secp256k1::key::PublicKey::from_secret_key(&SECP256K1, private); - PublicKey::Secp256k1(pubkey.serialize().to_vec()) - } - } - } - - /// Builds a `PeerId` corresponding to the public key of this key pair. - #[inline] - pub fn to_peer_id(&self) -> PeerId { - self.to_public_key().into_peer_id() - } - - // TODO: method to save generated key on disk? -} - -// Inner content of `SecioKeyPair`. -#[derive(Clone)] -enum SecioKeyPairInner { - #[cfg(not(any(target_os = "emscripten", target_os = "unknown")))] - 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, - }, - #[cfg(feature = "secp256k1")] - Secp256k1 { private: secp256k1::key::SecretKey }, -} - /// Output of the secio protocol. pub struct SecioOutput where diff --git a/src/lib.rs b/src/lib.rs index 0bec1445..92ce6e08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ //! Example: //! //! ```rust -//! let key = libp2p::secio::SecioKeyPair::ed25519_generated().unwrap(); +//! let key = libp2p::identity::Keypair::generate_ed25519(); //! let _transport = libp2p::build_development_transport(key); //! // _transport.dial(...); //! ``` @@ -185,6 +185,7 @@ pub mod bandwidth; pub mod simple; pub use self::core::{ + identity, Transport, PeerId, Swarm, transport::TransportError, upgrade::{InboundUpgrade, InboundUpgradeExt, OutboundUpgrade, OutboundUpgradeExt} @@ -202,10 +203,10 @@ use std::{error, time::Duration}; /// > **Note**: This `Transport` is not suitable for production usage, as its implementation /// > reserves the right to support additional protocols or remove deprecated protocols. #[inline] -pub fn build_development_transport(local_private_key: secio::SecioKeyPair) +pub fn build_development_transport(keypair: identity::Keypair) -> impl Transport + Send + Sync), Error = impl error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone { - build_tcp_ws_secio_mplex_yamux(local_private_key) + build_tcp_ws_secio_mplex_yamux(keypair) } /// Builds an implementation of `Transport` that is suitable for usage with the `Swarm`. @@ -214,13 +215,13 @@ pub fn build_development_transport(local_private_key: secio::SecioKeyPair) /// and mplex or yamux as the multiplexing layer. /// /// > **Note**: If you ever need to express the type of this `Transport`. -pub fn build_tcp_ws_secio_mplex_yamux(local_private_key: secio::SecioKeyPair) +pub fn build_tcp_ws_secio_mplex_yamux(keypair: identity::Keypair) -> impl Transport + Send + Sync), Error = impl error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone { CommonTransport::new() - .with_upgrade(secio::SecioConfig::new(local_private_key)) + .with_upgrade(secio::SecioConfig::new(keypair)) .and_then(move |out, endpoint| { - let peer_id = out.remote_key.into_peer_id(); + let peer_id = PeerId::from(out.remote_key); let peer_id2 = peer_id.clone(); let upgrade = core::upgrade::SelectUpgrade::new(yamux::Config::default(), mplex::MplexConfig::new()) // TODO: use a single `.map` instead of two maps