From 7b0fec43bef43397c95d6a8a062cfd293423dac1 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 16 Nov 2021 16:18:36 +0100 Subject: [PATCH 1/4] First rough implementation of ECDSA support --- core/Cargo.toml | 3 +- core/src/identity.rs | 42 +++++++++ core/src/identity/ecdsa.rs | 186 +++++++++++++++++++++++++++++++++++++ core/src/keys.proto | 1 + 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 core/src/identity/ecdsa.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 8573f45cf7c..e2d56e3bd82 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -54,8 +54,9 @@ rand07 = { package = "rand", version = "0.7" } prost-build = "0.9" [features] -default = ["secp256k1"] +default = ["secp256k1", "ecdsa"] secp256k1 = ["libsecp256k1"] +ecdsa = [] [[bench]] name = "peer_id" diff --git a/core/src/identity.rs b/core/src/identity.rs index 76ed4c39fd4..1d1d5a8e5cc 100644 --- a/core/src/identity.rs +++ b/core/src/identity.rs @@ -32,6 +32,8 @@ //! (e.g. [ed25519 binary format](https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5)). //! All key types have functions to enable conversion to/from their binary representations. +#[cfg(feature = "ecdsa")] +pub mod ecdsa; pub mod ed25519; #[cfg(not(target_arch = "wasm32"))] pub mod rsa; @@ -71,6 +73,9 @@ pub enum Keypair { /// A Secp256k1 keypair. #[cfg(feature = "secp256k1")] Secp256k1(secp256k1::Keypair), + /// An ECDSA keypair. + #[cfg(feature = "ecdsa")] + Ecdsa(ecdsa::Keypair), } impl Keypair { @@ -85,6 +90,11 @@ impl Keypair { Keypair::Secp256k1(secp256k1::Keypair::generate()) } + #[cfg(feature = "ecdsa")] + pub fn generate_ecdsa(curve_id: ecdsa::CurveId) -> Keypair { + Keypair::Ecdsa(ecdsa::Keypair::generate(curve_id)) + } + /// Decode an keypair from a DER-encoded secret key in PKCS#8 PrivateKeyInfo /// format (i.e. unencrypted) as defined in [RFC5208]. /// @@ -114,6 +124,8 @@ impl Keypair { Rsa(ref pair) => pair.sign(msg), #[cfg(feature = "secp256k1")] Secp256k1(ref pair) => pair.secret().sign(msg), + #[cfg(feature = "ecdsa")] + Ecdsa(ref pair) => Ok(pair.sign(msg)), } } @@ -126,6 +138,8 @@ impl Keypair { Rsa(pair) => PublicKey::Rsa(pair.public()), #[cfg(feature = "secp256k1")] Secp256k1(pair) => PublicKey::Secp256k1(pair.public().clone()), + #[cfg(feature = "ecdsa")] + Ecdsa(pair) => PublicKey::Ecdsa(pair.public()), } } @@ -150,6 +164,12 @@ impl Keypair { "Encoding Secp256k1 key into Protobuf is unsupported", )) } + #[cfg(feature = "ecdsa")] + Self::Ecdsa(_) => { + return Err(DecodingError::new( + "Encoding ECDSA key into Protobuf is unsupported", + )) + } }; Ok(pk.encode_to_vec()) @@ -177,6 +197,9 @@ impl Keypair { keys_proto::KeyType::Secp256k1 => Err(DecodingError::new( "Decoding Secp256k1 key from Protobuf is unsupported.", )), + keys_proto::KeyType::Ecdsa => Err(DecodingError::new( + "Decoding ECDSA key from Protobuf is unsupported.", + )), } } } @@ -199,6 +222,9 @@ pub enum PublicKey { #[cfg(feature = "secp256k1")] /// A public Secp256k1 key. Secp256k1(secp256k1::PublicKey), + /// A public ECDSA key. + #[cfg(feature = "ecdsa")] + Ecdsa(ecdsa::PublicKey), } impl PublicKey { @@ -215,6 +241,8 @@ impl PublicKey { Rsa(pk) => pk.verify(msg, sig), #[cfg(feature = "secp256k1")] Secp256k1(pk) => pk.verify(msg, sig), + #[cfg(feature = "ecdsa")] + Ecdsa(pk) => pk.verify(msg, sig), } } @@ -266,6 +294,11 @@ impl From<&PublicKey> for keys_proto::PublicKey { r#type: keys_proto::KeyType::Secp256k1 as i32, data: key.encode().to_vec(), }, + #[cfg(feature = "ecdsa")] + PublicKey::Ecdsa(key) => keys_proto::PublicKey { + r#type: keys_proto::KeyType::Ecdsa as i32, + data: key.encode_asn1(), + }, } } } @@ -299,6 +332,15 @@ impl TryFrom for PublicKey { log::debug!("support for secp256k1 was disabled at compile-time"); Err(DecodingError::new("Unsupported")) } + #[cfg(feature = "ecdsa")] + keys_proto::KeyType::Ecdsa => { + ecdsa::PublicKey::decode(&pubkey.data).map(PublicKey::Ecdsa) + } + #[cfg(not(feature = "ecdsa"))] + keys_proto::KeyType::Ecdsa => { + log::debug!("support for ECDSA was disabled at compile-time"); + Err(DecodingError::new("Unsupported")) + } } } } diff --git a/core/src/identity/ecdsa.rs b/core/src/identity/ecdsa.rs new file mode 100644 index 00000000000..cc2b9268efd --- /dev/null +++ b/core/src/identity/ecdsa.rs @@ -0,0 +1,186 @@ +// 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. + +//! Ecdsa keys. + +use super::error::{DecodingError, SigningError}; +use core::fmt; +use ring::{ + rand::SystemRandom, + signature::{ + self, EcdsaKeyPair as EcdsaKeyPairImpl, EcdsaSigningAlgorithm, EcdsaVerificationAlgorithm, + KeyPair as RingKeyPair, UnparsedPublicKey as PublicKeyImpl, + }, +}; +use std::sync::Arc; + +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum CurveId { + Secp256r1, +} + +/// An ECDSA keypair. +#[derive(Clone)] +pub struct Keypair { + curve_id: CurveId, + pair: Arc, +} + +impl Keypair { + /// Generate a new random ECDSA p256 keypair. + pub fn generate(curve_id: CurveId) -> Keypair { + let rng = SystemRandom::new(); + let alg = get_sign_alg(curve_id); + let pkcs8 = EcdsaKeyPairImpl::generate_pkcs8(alg, &rng).unwrap(); + let imp = EcdsaKeyPairImpl::from_pkcs8(alg, pkcs8.as_ref()).unwrap(); + Keypair { + curve_id, + pair: Arc::new(imp), + } + } + + /// Sign a message using the private key of this keypair. + pub fn sign(&self, msg: &[u8]) -> Vec { + let rng = SystemRandom::new(); + self.pair.sign(&rng, msg).unwrap().as_ref().to_vec() + } + + // Get the public key of this keypair. + pub fn public(&self) -> PublicKey { + PublicKey { + curve_id: self.curve_id, + key: self.pair.public_key().as_ref().to_owned(), + } + } +} + +impl fmt::Debug for Keypair { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Keypair") + .field("public", &self.public()) + .finish() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PublicKey { + curve_id: CurveId, + key: Vec, +} + +impl PublicKey { + /// Verify the ecdsa signature on a message using the public key. + pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool { + let alg = get_verify_alg(self.curve_id); + let pk = PublicKeyImpl::new(alg, &self.key); + pk.verify(msg, sig).is_ok() + } + + // Encode a public key in ASN.1 format byte buffer. + pub fn encode_asn1(&self) -> Vec { + let bytes = self.key.clone(); + add_asn1_header(self.curve_id, bytes) + } + + // Decode a public key from an ASN.1 byte buffer. + pub fn decode(k: &[u8]) -> Result { + let curve_id = CurveId::Secp256r1; + let key = del_asn1_header(k.to_owned())?; + Ok(PublicKey { curve_id, key }) + // .map_err(|_| DecodingError::new("failed to parse secp256k1 public key")) + // .map(PublicKey) + } +} + +fn get_sign_alg(curve_id: CurveId) -> &'static EcdsaSigningAlgorithm { + match curve_id { + CurveId::Secp256r1 => &signature::ECDSA_P256_SHA256_FIXED_SIGNING, + } +} + +fn get_verify_alg(curve_id: CurveId) -> &'static EcdsaVerificationAlgorithm { + match curve_id { + CurveId::Secp256r1 => &signature::ECDSA_P256_SHA256_FIXED, + } +} + +fn del_asn1_header(asn1_bytes: Vec) -> Result, DecodingError> { + let key_len = 65; + let key = &asn1_bytes[asn1_bytes.len() - key_len..]; + Ok(key.to_owned()) +} + +// SECG named elliptic curve) +fn get_curve_oid(curve_id: CurveId) -> Vec { + match curve_id { + // secp256r1 OID: 1.2.840.10045.3.1.7 + CurveId::Secp256r1 => vec![0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07], + // secp384r1 OID: 1.3.132.0.34 + //CurveId::Secp384r1 => vec![0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22], + } +} + +// ASN1 header. +#[rustfmt::skip] +fn add_asn1_header(curve_id: CurveId, mut key_bytes: Vec) -> Vec { + let mut res = vec![ + // ASN.1 struct type and length. + 0x30, 0x00, + // ASN.1 struct type and length. + 0x30, 0x00, + ]; + + // ecPublicKey (ANSI X9.62 public key type) OID: 1.2.840.10045.2.1 + let mut ec_oid = vec![ 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 ]; + // curve OID + let mut curve_oid = get_curve_oid(curve_id); + + let oids_len = ec_oid.len() + curve_oid.len(); + res.append(&mut ec_oid); + res.append(&mut curve_oid); + + // Update oids length field + res[3] = oids_len as u8; + + // Append key bitstring type and length. + let mut bitstring_type_len = vec![ + 0x03, (key_bytes.len() + 1) as u8, 0x00, + ]; + res.append(&mut bitstring_type_len); + // Append key bitstring. + res.append(&mut key_bytes); + // Update overall length field. + res[1] = (res.len() - 2) as u8; + + res +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ecdsa_generate() { + let pair = Keypair::generate(CurveId::Secp256r1); + + let public = pair.public(); + println!("{:?}", public); + } +} diff --git a/core/src/keys.proto b/core/src/keys.proto index 0a9f1f08ca3..5fbeaf8f6e0 100644 --- a/core/src/keys.proto +++ b/core/src/keys.proto @@ -6,6 +6,7 @@ enum KeyType { RSA = 0; Ed25519 = 1; Secp256k1 = 2; + ECDSA = 3; } message PublicKey { From a821bfbe9fe190d5fd293d92b02c97607e2c9f9c Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 29 Nov 2021 13:09:22 +0100 Subject: [PATCH 2/4] Fix ECDSA ASN.1 PublicKey serialization Furthermore SecretKey can now be serialized/deserialized to/from a byte vector. --- core/src/identity/ecdsa.rs | 102 +++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 9 deletions(-) diff --git a/core/src/identity/ecdsa.rs b/core/src/identity/ecdsa.rs index 1198dcf4e38..e62c37545ad 100644 --- a/core/src/identity/ecdsa.rs +++ b/core/src/identity/ecdsa.rs @@ -22,9 +22,12 @@ use super::error::DecodingError; use core::fmt; -use p256::ecdsa::{ - signature::{Signer, Verifier}, - Signature, SigningKey, VerifyingKey, +use p256::{ + ecdsa::{ + signature::{Signer, Verifier}, + Signature, SigningKey, VerifyingKey, + }, + EncodedPoint, }; /// An ECDSA keypair. @@ -93,6 +96,18 @@ impl SecretKey { pub fn sign(&self, msg: &[u8]) -> Vec { self.0.sign(msg).to_der().as_bytes().to_owned() } + + /// Encode a secret key into a byte buffer. + pub fn to_bytes(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + /// Decode a secret key from a byte buffer. + pub fn from_bytes(buf: &[u8]) -> Result { + SigningKey::from_bytes(buf) + .map_err(|err| DecodingError::new("failed to parse ecdsa p256 secret key").source(err)) + .map(SecretKey) + } } impl fmt::Debug for SecretKey { @@ -115,16 +130,85 @@ impl PublicKey { self.0.verify(msg, &sig).is_ok() } + /// Decode a public key from a byte buffer without compression. + pub fn from_bytes(k: &[u8]) -> Result { + let enc_pt = EncodedPoint::from_bytes(k).map_err(|_| { + DecodingError::new("failed to parse ecdsa p256 public key, bad point encoding") + })?; + + VerifyingKey::from_encoded_point(&enc_pt) + .map_err(|err| DecodingError::new("failed to parse ecdsa p256 public key").source(err)) + .map(PublicKey) + } + + /// Encode a public key into a byte buffer without compression. + pub fn to_bytes(&self) -> Vec { + self.0.to_encoded_point(false).as_bytes().to_owned() + } + /// Encode a public key into a DER encoded byte buffer as defined by SEC1 standard. pub fn encode_der(&self) -> Vec { - self.0.to_encoded_point(false).as_bytes().to_owned() + let buf = self.to_bytes(); + Self::add_asn1_header(&buf) } - // Decode a public key from a DER encoded byte buffer as defined for SEC1 standard. + /// Decode a public key into a DER encoded byte buffer as defined by SEC1 standard. pub fn decode_der(k: &[u8]) -> Result { - VerifyingKey::from_sec1_bytes(k) - .map_err(|_| DecodingError::new("failed to parse ecdsa p256 public key")) - .map(PublicKey) + let buf = Self::del_asn1_header(k).ok_or(DecodingError::new( + "failed to parse asn.1 encoded ecdsa p256 public key", + ))?; + Self::from_bytes(&buf) + } + + // ecPublicKey (ANSI X9.62 public key type) OID: 1.2.840.10045.2.1 + const EC_PUBLIC_KEY_OID: [u8; 9] = [0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01]; + // secp256r1 OID: 1.2.840.10045.3.1.7 + const SECP_256_R1_OID: [u8; 10] = [0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]; + + // Add ASN1 header. + fn add_asn1_header(key_buf: &[u8]) -> Vec { + // ASN.1 struct type and length. + let mut asn1_buf = vec![ + 0x30, + 0x00, + 0x30, + (Self::EC_PUBLIC_KEY_OID.len() + Self::SECP_256_R1_OID.len()) as u8, + ]; + // Append OIDs. + asn1_buf.extend_from_slice(&Self::EC_PUBLIC_KEY_OID); + asn1_buf.extend_from_slice(&Self::SECP_256_R1_OID); + // Append key bitstring type and length. + asn1_buf.extend_from_slice(&[0x03, (key_buf.len() + 1) as u8, 0x00]); + // Append key bitstring value. + asn1_buf.extend_from_slice(key_buf); + // Update overall length field. + asn1_buf[1] = (asn1_buf.len() - 2) as u8; + + asn1_buf + } + + // Check and remove ASN.1 header. + fn del_asn1_header(asn1_buf: &[u8]) -> Option<&[u8]> { + let oids_len = Self::EC_PUBLIC_KEY_OID.len() + Self::SECP_256_R1_OID.len(); + let asn1_head = asn1_buf.get(..4)?; + let oids_buf = asn1_buf.get(4..4 + oids_len)?; + let bitstr_head = asn1_buf.get(4 + oids_len..4 + oids_len + 3)?; + + // Sanity check + if asn1_head[0] != 0x30 + || asn1_head[2] != 0x30 + || asn1_head[3] as usize != oids_len + || &oids_buf[..Self::EC_PUBLIC_KEY_OID.len()] != &Self::EC_PUBLIC_KEY_OID + || &oids_buf[Self::EC_PUBLIC_KEY_OID.len()..] != &Self::SECP_256_R1_OID + || bitstr_head[0] != 0x03 + || bitstr_head[2] != 0x00 + { + return None; + } + + let key_len = bitstr_head[1].checked_sub(1)? as usize; + let key_buf = asn1_buf.get(4 + oids_len + 3..4 + oids_len + 3 + key_len as usize)?; + Some(key_buf) } } @@ -143,7 +227,7 @@ mod tests { use super::*; #[test] - fn ecdsa_signature() { + fn sign_verify() { let pair = Keypair::generate(); let pk = pair.public(); From 2dea5c3303d41cb5324ec0ea3c9ef257d945381c Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 30 Nov 2021 17:27:47 +0100 Subject: [PATCH 3/4] core/CHANGELOG: Add entry --- core/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index eacb3d734b2..67c5ba39f29 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -7,8 +7,11 @@ - Migrate to Rust edition 2021 (see [PR 2339]). +- Add support for ECDSA identities (see [PR 2352]). + [PR 2339]: https://github.com/libp2p/rust-libp2p/pull/2339 -[PR 2350]: https://github.com/libp2p/rust-libp2p/pull/2350/ +[PR 2350]: https://github.com/libp2p/rust-libp2p/pull/2350 +[PR 2352]: https://github.com/libp2p/rust-libp2p/pull/2352 # 0.30.1 [2021-11-16] From deaa48a1277e6ac03b689c7f79ce70edc8cf54c3 Mon Sep 17 00:00:00 2001 From: Max Inden Date: Tue, 30 Nov 2021 17:35:01 +0100 Subject: [PATCH 4/4] core/src/identity/ecdsa: Fix clippy warning --- core/src/identity/ecdsa.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/identity/ecdsa.rs b/core/src/identity/ecdsa.rs index e62c37545ad..b883243b13b 100644 --- a/core/src/identity/ecdsa.rs +++ b/core/src/identity/ecdsa.rs @@ -154,9 +154,9 @@ impl PublicKey { /// Decode a public key into a DER encoded byte buffer as defined by SEC1 standard. pub fn decode_der(k: &[u8]) -> Result { - let buf = Self::del_asn1_header(k).ok_or(DecodingError::new( - "failed to parse asn.1 encoded ecdsa p256 public key", - ))?; + let buf = Self::del_asn1_header(k).ok_or_else(|| { + DecodingError::new("failed to parse asn.1 encoded ecdsa p256 public key") + })?; Self::from_bytes(&buf) }