From b142964d3110a0a23c18ffefcbeea5ef6f271a43 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 9 Sep 2019 20:04:35 -0400 Subject: [PATCH 1/8] Adds support for verifying ed25519 signatures, for secio --- libp2p/crypto/ed25519.py | 29 +++++++++++++++++++++++++++++ libp2p/crypto/serialization.py | 2 ++ setup.py | 1 + 3 files changed, 32 insertions(+) create mode 100644 libp2p/crypto/ed25519.py diff --git a/libp2p/crypto/ed25519.py b/libp2p/crypto/ed25519.py new file mode 100644 index 0000000..713e74b --- /dev/null +++ b/libp2p/crypto/ed25519.py @@ -0,0 +1,29 @@ +from Crypto.Hash import SHA256 + +from libp2p.crypto.keys import KeyType, PublicKey +from nacl.public import PublicKey as Ed255129PublicKeyImpl +from nacl.signing import BadSignatureError, VerifyKey + + +class Ed25519PublicKey(PublicKey): + def __init__(self, impl: Ed255129PublicKeyImpl) -> None: + self.impl = impl + + def to_bytes(self) -> bytes: + return bytes(self.impl) + + @classmethod + def from_bytes(cls, key_bytes: bytes) -> "Ed25519PublicKey": + return cls(Ed255129PublicKeyImpl(key_bytes)) + + def get_type(self) -> KeyType: + return KeyType.Ed25519 + + def verify(self, data: bytes, signature: bytes) -> bool: + verify_key = VerifyKey(self.to_bytes()) + h = SHA256.new(data) + try: + verify_key.verify(h, signature) + except BadSignatureError: + return False + return True diff --git a/libp2p/crypto/serialization.py b/libp2p/crypto/serialization.py index dedcf85..6bc686e 100644 --- a/libp2p/crypto/serialization.py +++ b/libp2p/crypto/serialization.py @@ -1,3 +1,4 @@ +from libp2p.crypto.ed25519 import Ed25519PublicKey from libp2p.crypto.keys import KeyType, PrivateKey, PublicKey from libp2p.crypto.rsa import RSAPublicKey from libp2p.crypto.secp256k1 import Secp256k1PrivateKey, Secp256k1PublicKey @@ -5,6 +6,7 @@ from libp2p.crypto.secp256k1 import Secp256k1PrivateKey, Secp256k1PublicKey key_type_to_public_key_deserializer = { KeyType.Secp256k1.value: Secp256k1PublicKey.from_bytes, KeyType.RSA.value: RSAPublicKey.from_bytes, + KeyType.Ed25519.value: Ed25519PublicKey.from_bytes, } key_type_to_private_key_deserializer = { diff --git a/setup.py b/setup.py index 6e6ad67..152a5b1 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ setuptools.setup( "protobuf==3.9.0", "coincurve>=10.0.0,<11.0.0", "fastecdsa==1.7.4", + "pynacl==1.3.0", ], extras_require=extras_require, packages=setuptools.find_packages(exclude=["tests", "tests.*"]), From fa7d1d66a8d771c572cb1a1e1d9876c768d148c3 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 9 Sep 2019 20:18:39 -0400 Subject: [PATCH 2/8] Fix import path --- libp2p/crypto/ed25519.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libp2p/crypto/ed25519.py b/libp2p/crypto/ed25519.py index 713e74b..1b34a70 100644 --- a/libp2p/crypto/ed25519.py +++ b/libp2p/crypto/ed25519.py @@ -1,8 +1,9 @@ from Crypto.Hash import SHA256 from libp2p.crypto.keys import KeyType, PublicKey +from nacl.exceptions import BadSignatureError from nacl.public import PublicKey as Ed255129PublicKeyImpl -from nacl.signing import BadSignatureError, VerifyKey +from nacl.signing import VerifyKey class Ed25519PublicKey(PublicKey): From 5fdca2ffb2460045b9920739ca481b397ced5946 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 9 Sep 2019 20:23:44 -0400 Subject: [PATCH 3/8] Add public key implementation --- libp2p/crypto/ed25519.py | 45 +++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/libp2p/crypto/ed25519.py b/libp2p/crypto/ed25519.py index 1b34a70..61c4631 100644 --- a/libp2p/crypto/ed25519.py +++ b/libp2p/crypto/ed25519.py @@ -1,13 +1,15 @@ from Crypto.Hash import SHA256 -from libp2p.crypto.keys import KeyType, PublicKey +from libp2p.crypto.keys import KeyPair, KeyType, PrivateKey, PublicKey from nacl.exceptions import BadSignatureError -from nacl.public import PublicKey as Ed255129PublicKeyImpl -from nacl.signing import VerifyKey +from nacl.public import PrivateKey as PrivateKeyImpl +from nacl.public import PublicKey as PublicKeyImpl +from nacl.signing import SigningKey, VerifyKey +import nacl.utils as utils class Ed25519PublicKey(PublicKey): - def __init__(self, impl: Ed255129PublicKeyImpl) -> None: + def __init__(self, impl: PublicKeyImpl) -> None: self.impl = impl def to_bytes(self) -> bytes: @@ -15,7 +17,7 @@ class Ed25519PublicKey(PublicKey): @classmethod def from_bytes(cls, key_bytes: bytes) -> "Ed25519PublicKey": - return cls(Ed255129PublicKeyImpl(key_bytes)) + return cls(PublicKeyImpl(key_bytes)) def get_type(self) -> KeyType: return KeyType.Ed25519 @@ -28,3 +30,36 @@ class Ed25519PublicKey(PublicKey): except BadSignatureError: return False return True + + +class Ed25519PrivateKey(PrivateKey): + def __init__(self, impl: PrivateKeyImpl) -> None: + self.impl = impl + + @classmethod + def new(cls, seed: bytes = None) -> "Ed25519PrivateKey": + if not seed: + seed = utils.random() + + private_key_impl = PrivateKeyImpl.from_seed(seed) + return cls(private_key_impl) + + def to_bytes(self) -> bytes: + return bytes(self.impl) + + def get_type(self) -> KeyType: + return KeyType.Ed25519 + + def sign(self, data: bytes) -> bytes: + h = SHA256.new(data) + signing_key = SigningKey(self.to_bytes()) + return signing_key.sign(h) + + def get_public_key(self) -> PublicKey: + return Ed25519PublicKey(self.impl.public_key) + + +def create_new_key_pair(seed: bytes = None) -> KeyPair: + private_key = Ed25519PrivateKey.new(seed) + public_key = private_key.get_public_key() + return KeyPair(private_key, public_key) From 6e5384960453867144a07bfcc374ee1a89796dd8 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Sep 2019 09:31:35 -0700 Subject: [PATCH 4/8] Delete utils in favor of serialization module --- libp2p/crypto/utils.py | 17 ----------------- libp2p/security/insecure/transport.py | 6 ++++-- 2 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 libp2p/crypto/utils.py diff --git a/libp2p/crypto/utils.py b/libp2p/crypto/utils.py deleted file mode 100644 index 8519598..0000000 --- a/libp2p/crypto/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -from .keys import PublicKey -from .pb import crypto_pb2 as protobuf -from .rsa import RSAPublicKey -from .secp256k1 import Secp256k1PublicKey - - -def pubkey_from_protobuf(pubkey_pb: protobuf.PublicKey) -> PublicKey: - if pubkey_pb.key_type == protobuf.RSA: - return RSAPublicKey.from_bytes(pubkey_pb.data) - # TODO: Test against secp256k1 keys - elif pubkey_pb.key_type == protobuf.Secp256k1: - return Secp256k1PublicKey.from_bytes(pubkey_pb.data) - # TODO: Support `Ed25519` and `ECDSA` in the future? - else: - raise ValueError( - f"unsupported key_type={pubkey_pb.key_type}, data={pubkey_pb.data!r}" - ) diff --git a/libp2p/security/insecure/transport.py b/libp2p/security/insecure/transport.py index 7df0575..81e7047 100644 --- a/libp2p/security/insecure/transport.py +++ b/libp2p/security/insecure/transport.py @@ -2,7 +2,7 @@ from typing import Optional from libp2p.crypto.keys import PrivateKey, PublicKey from libp2p.crypto.pb import crypto_pb2 -from libp2p.crypto.utils import pubkey_from_protobuf +from libp2p.crypto.serialization import deserialize_public_key from libp2p.io.abc import ReadWriteCloser from libp2p.network.connection.exceptions import RawConnError from libp2p.network.connection.raw_connection_interface import IRawConnection @@ -75,7 +75,9 @@ class InsecureSession(BaseSession): # Verify if the given `pubkey` matches the given `peer_id` try: - received_pubkey = pubkey_from_protobuf(remote_msg.pubkey) + received_pubkey = deserialize_public_key( + remote_msg.pubkey.SerializeToString() + ) except ValueError: raise HandshakeFailure( f"unknown `key_type` of remote_msg.pubkey={remote_msg.pubkey}" From bbd82798116798f5117b511f91b7a29d8f109efc Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Sep 2019 09:38:59 -0700 Subject: [PATCH 5/8] Add explicit exception if we are missing a deserializer --- libp2p/crypto/exceptions.py | 14 ++++++++++++++ libp2p/crypto/serialization.py | 11 +++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 libp2p/crypto/exceptions.py diff --git a/libp2p/crypto/exceptions.py b/libp2p/crypto/exceptions.py new file mode 100644 index 0000000..a9324df --- /dev/null +++ b/libp2p/crypto/exceptions.py @@ -0,0 +1,14 @@ +from libp2p.exceptions import BaseLibp2pError + + +class CryptographyError(BaseLibp2pError): + pass + + +class MissingDeserializerError(CryptographyError): + """ + Raise if the requested deserialization routine is missing for + some type of cryptographic key. + """ + + pass diff --git a/libp2p/crypto/serialization.py b/libp2p/crypto/serialization.py index 6bc686e..401f936 100644 --- a/libp2p/crypto/serialization.py +++ b/libp2p/crypto/serialization.py @@ -1,4 +1,5 @@ from libp2p.crypto.ed25519 import Ed25519PublicKey +from libp2p.crypto.exceptions import MissingDeserializerError from libp2p.crypto.keys import KeyType, PrivateKey, PublicKey from libp2p.crypto.rsa import RSAPublicKey from libp2p.crypto.secp256k1 import Secp256k1PrivateKey, Secp256k1PublicKey @@ -16,11 +17,17 @@ key_type_to_private_key_deserializer = { def deserialize_public_key(data: bytes) -> PublicKey: f = PublicKey.deserialize_from_protobuf(data) - deserializer = key_type_to_public_key_deserializer[f.key_type] + try: + deserializer = key_type_to_public_key_deserializer[f.key_type] + except KeyError: + raise MissingDeserializerError({"key_type": f.key_type, "key": "public_key"}) return deserializer(f.data) def deserialize_private_key(data: bytes) -> PrivateKey: f = PrivateKey.deserialize_from_protobuf(data) - deserializer = key_type_to_private_key_deserializer[f.key_type] + try: + deserializer = key_type_to_private_key_deserializer[f.key_type] + except KeyError: + raise MissingDeserializerError({"key_type": f.key_type, "key": "private_key"}) return deserializer(f.data) From 487c923791fb3c471e4a8497606863153b4e4f9d Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Sep 2019 09:50:03 -0700 Subject: [PATCH 6/8] add ed25519 private key deserializer --- libp2p/crypto/ed25519.py | 5 +++++ libp2p/crypto/serialization.py | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libp2p/crypto/ed25519.py b/libp2p/crypto/ed25519.py index 61c4631..cde2641 100644 --- a/libp2p/crypto/ed25519.py +++ b/libp2p/crypto/ed25519.py @@ -47,6 +47,11 @@ class Ed25519PrivateKey(PrivateKey): def to_bytes(self) -> bytes: return bytes(self.impl) + @classmethod + def from_bytes(cls, data: bytes) -> "Ed25519PrivateKey": + impl = PrivateKeyImpl(data) + return cls(impl) + def get_type(self) -> KeyType: return KeyType.Ed25519 diff --git a/libp2p/crypto/serialization.py b/libp2p/crypto/serialization.py index 401f936..d453bfe 100644 --- a/libp2p/crypto/serialization.py +++ b/libp2p/crypto/serialization.py @@ -1,4 +1,4 @@ -from libp2p.crypto.ed25519 import Ed25519PublicKey +from libp2p.crypto.ed25519 import Ed25519PrivateKey, Ed25519PublicKey from libp2p.crypto.exceptions import MissingDeserializerError from libp2p.crypto.keys import KeyType, PrivateKey, PublicKey from libp2p.crypto.rsa import RSAPublicKey @@ -11,7 +11,8 @@ key_type_to_public_key_deserializer = { } key_type_to_private_key_deserializer = { - KeyType.Secp256k1.value: Secp256k1PrivateKey.from_bytes + KeyType.Secp256k1.value: Secp256k1PrivateKey.from_bytes, + KeyType.Ed25519.value: Ed25519PrivateKey.from_bytes, } From 673ce40133916614a76baf87a2d20b0bb2b8eba4 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Sep 2019 09:50:49 -0700 Subject: [PATCH 7/8] Add basic tests for ed25519 keys --- tests/crypto/test_ed25519.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/crypto/test_ed25519.py diff --git a/tests/crypto/test_ed25519.py b/tests/crypto/test_ed25519.py new file mode 100644 index 0000000..89c446f --- /dev/null +++ b/tests/crypto/test_ed25519.py @@ -0,0 +1,22 @@ +from libp2p.crypto.ed25519 import create_new_key_pair +from libp2p.crypto.serialization import deserialize_private_key, deserialize_public_key + + +def test_public_key_serialize_deserialize_round_trip(): + key_pair = create_new_key_pair() + public_key = key_pair.public_key + + public_key_bytes = public_key.serialize() + another_public_key = deserialize_public_key(public_key_bytes) + + assert public_key == another_public_key + + +def test_private_key_serialize_deserialize_round_trip(): + key_pair = create_new_key_pair() + private_key = key_pair.private_key + + private_key_bytes = private_key.serialize() + another_private_key = deserialize_private_key(private_key_bytes) + + assert private_key == another_private_key From 75ec2facce27959ccf1dfab537cb5e6f4ae923a5 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 24 Sep 2019 10:07:33 -0700 Subject: [PATCH 8/8] linter fix --- libp2p/crypto/ed25519.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libp2p/crypto/ed25519.py b/libp2p/crypto/ed25519.py index cde2641..11a1668 100644 --- a/libp2p/crypto/ed25519.py +++ b/libp2p/crypto/ed25519.py @@ -1,12 +1,12 @@ from Crypto.Hash import SHA256 - -from libp2p.crypto.keys import KeyPair, KeyType, PrivateKey, PublicKey from nacl.exceptions import BadSignatureError from nacl.public import PrivateKey as PrivateKeyImpl from nacl.public import PublicKey as PublicKeyImpl from nacl.signing import SigningKey, VerifyKey import nacl.utils as utils +from libp2p.crypto.keys import KeyPair, KeyType, PrivateKey, PublicKey + class Ed25519PublicKey(PublicKey): def __init__(self, impl: PublicKeyImpl) -> None: