From 61f78c8febcbaccaa86cce7094c421a2c8e560a7 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 13 Aug 2019 18:17:08 -0700 Subject: [PATCH] Add abstraction for a cryptographic key --- Makefile | 1 + libp2p/crypto/__init__.py | 0 libp2p/crypto/keys.py | 75 +++++++++++++++ libp2p/crypto/pb/crypto.proto | 20 ++++ libp2p/crypto/pb/crypto_pb2.py | 162 +++++++++++++++++++++++++++++++++ libp2p/crypto/rsa.py | 54 +++++++++++ libp2p/crypto/secp256k1.py | 54 +++++++++++ 7 files changed, 366 insertions(+) create mode 100644 libp2p/crypto/__init__.py create mode 100644 libp2p/crypto/keys.py create mode 100644 libp2p/crypto/pb/crypto.proto create mode 100644 libp2p/crypto/pb/crypto_pb2.py create mode 100644 libp2p/crypto/rsa.py create mode 100644 libp2p/crypto/secp256k1.py diff --git a/Makefile b/Makefile index fcebe0d..28a4582 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,5 @@ lintroll: flake8 $(FILES_TO_LINT) protobufs: + cd libp2p/crypto/pb && protoc --python_out=. crypto.proto cd libp2p/pubsub/pb && protoc --python_out=. rpc.proto diff --git a/libp2p/crypto/__init__.py b/libp2p/crypto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libp2p/crypto/keys.py b/libp2p/crypto/keys.py new file mode 100644 index 0000000..58ceb12 --- /dev/null +++ b/libp2p/crypto/keys.py @@ -0,0 +1,75 @@ +from abc import ABC, abstractmethod +from enum import Enum, unique + +from .pb import crypto_pb2 as protobuf + + +@unique +class KeyType(Enum): + RSA = 0 + Ed25519 = 1 + Secp256k1 = 2 + ECDSA = 3 + + +class Key: + """ + A ``Key`` represents a cryptographic key. + """ + + @abstractmethod + def to_bytes(self) -> bytes: + """ + Returns the byte representation of this key. + """ + ... + + @abstractmethod + def get_type(self) -> KeyType: + """ + Returns the ``KeyType`` for ``self``. + """ + ... + + +class PublicKey(ABC, Key): + """ + A ``PublicKey`` represents a cryptographic public key. + """ + + @abstractmethod + def verify(self, data: bytes, signature: bytes) -> bool: + """ + Verify that ``signature`` is the cryptographic signature of the hash of ``data``. + """ + ... + + def serialize_to_protobuf(self) -> protobuf.PublicKey: + _type = self.get_type() + data = self.to_bytes() + protobuf_key = protobuf.PublicKey() + protobuf_key.key_type = _type.value + protobuf_key.data = data + return protobuf_key + + +class PrivateKey(ABC, Key): + """ + A ``PrivateKey`` represents a cryptographic private key. + """ + + @abstractmethod + def sign(self, data: bytes) -> bytes: + ... + + @abstractmethod + def get_public_key(self) -> PublicKey: + ... + + def serialize_to_protobuf(self) -> protobuf.PrivateKey: + _type = self.get_type() + data = self.to_bytes() + protobuf_key = protobuf.PrivateKey() + protobuf_key.key_type = _type.value + protobuf_key.data = data + return protobuf_key diff --git a/libp2p/crypto/pb/crypto.proto b/libp2p/crypto/pb/crypto.proto new file mode 100644 index 0000000..a12f7b6 --- /dev/null +++ b/libp2p/crypto/pb/crypto.proto @@ -0,0 +1,20 @@ +syntax = "proto2"; + +package crypto.pb; + +enum KeyType { + RSA = 0; + Ed25519 = 1; + Secp256k1 = 2; + ECDSA = 3; +} + +message PublicKey { + required KeyType key_type = 1; + required bytes data = 2; +} + +message PrivateKey { + required KeyType key_type = 1; + required bytes data = 2; +} \ No newline at end of file diff --git a/libp2p/crypto/pb/crypto_pb2.py b/libp2p/crypto/pb/crypto_pb2.py new file mode 100644 index 0000000..0e4aa1f --- /dev/null +++ b/libp2p/crypto/pb/crypto_pb2.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: crypto.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='crypto.proto', + package='crypto.pb', + syntax='proto2', + serialized_options=None, + serialized_pb=_b('\n\x0c\x63rypto.proto\x12\tcrypto.pb\"?\n\tPublicKey\x12$\n\x08key_type\x18\x01 \x02(\x0e\x32\x12.crypto.pb.KeyType\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\"@\n\nPrivateKey\x12$\n\x08key_type\x18\x01 \x02(\x0e\x32\x12.crypto.pb.KeyType\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c*9\n\x07KeyType\x12\x07\n\x03RSA\x10\x00\x12\x0b\n\x07\x45\x64\x32\x35\x35\x31\x39\x10\x01\x12\r\n\tSecp256k1\x10\x02\x12\t\n\x05\x45\x43\x44SA\x10\x03') +) + +_KEYTYPE = _descriptor.EnumDescriptor( + name='KeyType', + full_name='crypto.pb.KeyType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='RSA', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='Ed25519', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='Secp256k1', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ECDSA', index=3, number=3, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=158, + serialized_end=215, +) +_sym_db.RegisterEnumDescriptor(_KEYTYPE) + +KeyType = enum_type_wrapper.EnumTypeWrapper(_KEYTYPE) +RSA = 0 +Ed25519 = 1 +Secp256k1 = 2 +ECDSA = 3 + + + +_PUBLICKEY = _descriptor.Descriptor( + name='PublicKey', + full_name='crypto.pb.PublicKey', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key_type', full_name='crypto.pb.PublicKey.key_type', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data', full_name='crypto.pb.PublicKey.data', index=1, + number=2, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=27, + serialized_end=90, +) + + +_PRIVATEKEY = _descriptor.Descriptor( + name='PrivateKey', + full_name='crypto.pb.PrivateKey', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key_type', full_name='crypto.pb.PrivateKey.key_type', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data', full_name='crypto.pb.PrivateKey.data', index=1, + number=2, type=12, cpp_type=9, label=2, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=92, + serialized_end=156, +) + +_PUBLICKEY.fields_by_name['key_type'].enum_type = _KEYTYPE +_PRIVATEKEY.fields_by_name['key_type'].enum_type = _KEYTYPE +DESCRIPTOR.message_types_by_name['PublicKey'] = _PUBLICKEY +DESCRIPTOR.message_types_by_name['PrivateKey'] = _PRIVATEKEY +DESCRIPTOR.enum_types_by_name['KeyType'] = _KEYTYPE +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +PublicKey = _reflection.GeneratedProtocolMessageType('PublicKey', (_message.Message,), dict( + DESCRIPTOR = _PUBLICKEY, + __module__ = 'crypto_pb2' + # @@protoc_insertion_point(class_scope:crypto.pb.PublicKey) + )) +_sym_db.RegisterMessage(PublicKey) + +PrivateKey = _reflection.GeneratedProtocolMessageType('PrivateKey', (_message.Message,), dict( + DESCRIPTOR = _PRIVATEKEY, + __module__ = 'crypto_pb2' + # @@protoc_insertion_point(class_scope:crypto.pb.PrivateKey) + )) +_sym_db.RegisterMessage(PrivateKey) + + +# @@protoc_insertion_point(module_scope) diff --git a/libp2p/crypto/rsa.py b/libp2p/crypto/rsa.py new file mode 100644 index 0000000..df202af --- /dev/null +++ b/libp2p/crypto/rsa.py @@ -0,0 +1,54 @@ +from typing import Tuple + +import Crypto.PublicKey.RSA as RSA +from Crypto.PublicKey.RSA import RsaKey + +from libp2p.crypto.keys import KeyType, PrivateKey, PublicKey + + +class RSAPublicKey(PublicKey): + def __init__(self, impl: RsaKey) -> None: + self.impl = impl + + def to_bytes(self) -> bytes: + return self.impl.export_key("DER") + + def get_type(self) -> KeyType: + return KeyType.RSA + + def verify(self, data: bytes, signature: bytes) -> bool: + raise NotImplementedError + + +class RSAPrivateKey(PrivateKey): + def __init__(self, impl: RsaKey) -> None: + self.impl = impl + + @classmethod + def new(cls, bits: int = 2048, e: int = 65537) -> "RSAPrivateKey": + private_key_impl = RSA.generate(bits, e=e) + return cls(private_key_impl) + + def to_bytes(self) -> bytes: + return self.impl.export_key("DER") + + def get_type(self) -> KeyType: + return KeyType.RSA + + def sign(self, data: bytes) -> bytes: + raise NotImplementedError + + def get_public_key(self) -> PublicKey: + return RSAPublicKey(self.impl.publickey()) + + +def create_new_key_pair( + bits: int = 2048, e: int = 65537 +) -> Tuple[PrivateKey, PublicKey]: + """ + Returns a new RSA keypair with the requested key size (``bits``) and the given public + exponent ``e``. Sane defaults are provided for both values. + """ + private_key = RSAPrivateKey.new(bits, e) + public_key = private_key.get_public_key() + return private_key, public_key diff --git a/libp2p/crypto/secp256k1.py b/libp2p/crypto/secp256k1.py new file mode 100644 index 0000000..4ad2364 --- /dev/null +++ b/libp2p/crypto/secp256k1.py @@ -0,0 +1,54 @@ +from typing import Tuple + +import coincurve + +from libp2p.crypto.keys import KeyType, PrivateKey, PublicKey + + +class Secp256k1PublicKey(PublicKey): + def __init__(self, impl: coincurve.PublicKey) -> None: + self.impl = impl + + def to_bytes(self) -> bytes: + return self.impl.format() + + def get_type(self) -> KeyType: + return KeyType.Secp256k1 + + def verify(self, data: bytes, signature: bytes) -> bool: + raise NotImplementedError + + +class Secp256k1PrivateKey(PrivateKey): + def __init__(self, impl: coincurve.PrivateKey) -> None: + self.impl = impl + + @classmethod + def new(cls, secret: bytes = None) -> "Secp256k1PrivateKey": + private_key_impl = coincurve.PrivateKey() + return cls(private_key_impl) + + def to_bytes(self) -> bytes: + return self.impl.secret + + def get_type(self) -> KeyType: + return KeyType.Secp256k1 + + def sign(self, data: bytes) -> bytes: + raise NotImplementedError + + def get_public_key(self) -> PublicKey: + public_key_impl = coincurve.PublicKey.from_secret(self.impl.secret) + return Secp256k1PublicKey(public_key_impl) + + +def create_new_key_pair(secret: bytes = None) -> Tuple[PrivateKey, PublicKey]: + """ + Returns a new Secp256k1 keypair derived from the provided ``secret``, + a sequence of bytes corresponding to some integer between 0 and the group order. + + A valid secret is created if ``None`` is passed. + """ + private_key = Secp256k1PrivateKey.new() + public_key = private_key.get_public_key() + return private_key, public_key