diff --git a/authentication.py b/authentication.py index 6191627b..085c1553 100644 --- a/authentication.py +++ b/authentication.py @@ -157,7 +157,7 @@ def has_valid_signature_for(self, placeholder, payload): return self._member.verify(payload, self._signature) def _is_sig_empty(self): - return self._signature == "" or self._signature == "\x00" * self._member.signature_length + return not self._signature or self._signature == "\x00" * self._member.signature_length def __init__(self, encoding="default"): diff --git a/community.py b/community.py index b4b9fc26..ef7ff290 100644 --- a/community.py +++ b/community.py @@ -20,6 +20,7 @@ from twisted.internet.task import LoopingCall, deferLater from twisted.python.threadable import isInIOThread +from crypto import DispersyPublicKey, DispersyCrypto from .authentication import NoAuthentication, MemberAuthentication, DoubleMemberAuthentication from .bloomfilter import BloomFilter from .candidate import Candidate, WalkCandidate @@ -481,13 +482,14 @@ def _download_master_member_identity(self): self._logger.debug("using dummy master member") try: - public_key, = self._dispersy.database.execute(u"SELECT public_key FROM member WHERE id = ?", (self._master_member.database_id,)).next() + public_key_binary, = self._dispersy.database.execute(u"SELECT public_key FROM member WHERE id = ?", (self._master_member.database_id,)).next() + public_key = DispersyPublicKey.from_bytes(public_key_binary) except StopIteration: pass else: if public_key: self._logger.debug("%s found master member", self._cid.encode("HEX")) - self._master_member = self._dispersy.get_member(public_key=str(public_key)) + self._master_member = self._dispersy.get_member(public_key=public_key) assert self._master_member.public_key self.cancel_pending_task("download master member identity") else: @@ -1841,9 +1843,12 @@ def get_member(self, *argv, **kwargs): assert isinstance(mid, str) assert isinstance(public_key, str) assert isinstance(private_key, str) - assert not mid or len(mid) == 20 - assert not public_key or self._dispersy.crypto.is_valid_public_bin(public_key) - assert not private_key or self._dispersy.crypto.is_valid_private_bin(private_key) + assert not mid or len(mid) == 32 + assert not public_key or DispersyCrypto.is_valid_public_key(public_key) + assert not private_key or DispersyCrypto.is_valid_private_key(private_key) + + public_key = DispersyPublicKey.from_bytes(public_key) if public_key else None + private_key = DispersyPublicKey.from_bytes(private_key) if private_key else None member = self._dispersy.get_member(mid=mid, public_key=public_key, private_key=private_key) # We only need to check if this member has an identity message in this community if we still don't have the full diff --git a/conversion.py b/conversion.py index 5138b614..4736aa48 100644 --- a/conversion.py +++ b/conversion.py @@ -6,6 +6,7 @@ from M2Crypto.EC import ECError +from crypto import DispersyCrypto from .authentication import Authentication, NoAuthentication, MemberAuthentication, DoubleMemberAuthentication from .bloomfilter import BloomFilter from .candidate import Candidate @@ -54,7 +55,8 @@ def __init__(self, community, dispersy_version, community_version): # the messages that this instance can handle, and that this instance produces, is identified # by _prefix. self._prefix = dispersy_version + community_version + community.cid - assert len(self._prefix) == 22 # when this assumption changes, we need to ensure the + # 34 is the hashlength, 32, plus 2. + assert len(self._prefix) == 34 # when this assumption changes, we need to ensure the # dispersy_version and community_version properties are # returned correctly @@ -82,11 +84,12 @@ def can_decode_message(self, data): """ Returns True when DATA can be decoded using this conversion. """ - # at least a length of 23, as we need the prefix + 1 byte messagetype + # at least a length of 35, as we need the prefix + 1 byte messagetype assert isinstance(data, str), type(data) - assert len(data) >= 23 + prefix_length = 34 + assert len(data) > prefix_length - return (len(data) >= 23 and data[:22] == self._prefix) + return len(data) > prefix_length and data[:prefix_length] == self._prefix @abstractmethod def decode_meta_message(self, data): @@ -100,7 +103,7 @@ def decode_meta_message(self, data): def decode_message(self, address, data, verify=True, source=u"unknown"): """ DATA is a string, where the first byte is the on-the-wire Dispersy version, the second byte - is the on-the-wire Community version and the following 20 bytes is the Community Identifier. + is the on-the-wire Community version and the following 32 bytes is the Community Identifier. The rest is the message payload. Returns a Message instance. @@ -118,7 +121,7 @@ def can_encode_message(self, message): def encode_message(self, message, sign=True): """ Encode a Message instance into a binary string where the first byte is the on-the-wire - Dispersy version, the second byte is the on-the-wire Community version and the following 20 + Dispersy version, the second byte is the on-the-wire Community version and the following 32 bytes is the Community Identifier. The rest is the message payload. Returns a binary string. @@ -281,8 +284,8 @@ def _decode_missing_sequence(self, placeholder, offset, data): if len(data) < offset + 29: raise DropPacket("Insufficient packet size") - member_id = data[offset:offset + 20] - offset += 20 + member_id = data[offset:offset + 32] + offset += 32 member = self._community.get_member(mid=member_id) if member is None: raise DropPacket("Unknown member") @@ -387,10 +390,10 @@ def _encode_missing_identity(self, message): return (message.payload.mid,) def _decode_missing_identity(self, placeholder, offset, data): - if len(data) < offset + 20: + if len(data) < offset + 32: raise DropPacket("Insufficient packet size") - return offset + 20, placeholder.meta.payload.Implementation(placeholder.meta.payload, data[offset:offset + 20]) + return offset + 32, placeholder.meta.payload.Implementation(placeholder.meta.payload, data[offset:offset + 32]) def _encode_destroy_community(self, message): if message.payload.is_soft_kill: @@ -906,7 +909,7 @@ def _encode_member_authentication(self, container, message): container.append(message.authentication.member.mid) elif encoding == "bin": assert message.authentication.member.public_key - assert self._community.dispersy.crypto.is_valid_public_bin(message.authentication.member.public_key), message.authentication.member.public_key.encode("HEX") + assert DispersyCrypto.is_valid_public_key(message.authentication.member.public_key), message.authentication.member.public_key.encode("HEX") container.extend((self._struct_H.pack(len(message.authentication.member.public_key)), message.authentication.member.public_key)) else: raise NotImplementedError(encoding) @@ -1070,10 +1073,10 @@ def _decode_member_authentication(self, placeholder): encoding = self.__get_authentication_encoding(authentication) if encoding == "sha1": - if len(data) < offset + 20: + if len(data) < offset + 32: raise DropPacket("Insufficient packet size (_decode_member_authentication sha1)") - member_id = data[offset:offset + 20] - offset += 20 + member_id = data[offset:offset + 32] + offset += 32 try: member = self._community.get_member(mid=member_id) @@ -1121,11 +1124,11 @@ def _decode_double_member_authentication(self, placeholder): encoding = self.__get_authentication_encoding(authentication) if encoding == "sha1": for _ in range(2): - member_id = data[offset:offset + 20] + member_id = data[offset:offset + 32] member = self._community.get_member(mid=member_id) if not member: raise DelayPacketByMissingMember(self._community, member_id) - offset += 20 + offset += 32 members.append(member) elif encoding == "bin": @@ -1164,10 +1167,11 @@ def can_decode_message(self, data): """ Returns True when DATA can be decoded using this conversion. """ + prefix_length = 34 assert isinstance(data, str), type(data) - return (len(data) >= 23 and - data[:22] == self._prefix and - data[22] in self._decode_message_map) + return (len(data) > prefix_length and + data[:prefix_length] == self._prefix and + data[prefix_length] in self._decode_message_map) def decode_meta_message(self, data): """ @@ -1177,7 +1181,7 @@ def decode_meta_message(self, data): if not self.can_decode_message(data): raise DropPacket("Cannot decode message") - return self._decode_message_map[data[22]].meta + return self._decode_message_map[data[34]].meta @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name} {return_value}") def decode_message(self, candidate, data, verify=True, allow_empty_signature=False, source="unknown"): @@ -1199,10 +1203,10 @@ def decode_message(self, candidate, data, verify=True, allow_empty_signature=Fal if not self.can_decode_message(data): raise DropPacket("Cannot decode message") - decode_functions = self._decode_message_map[data[22]] + decode_functions = self._decode_message_map[data[34]] # placeholder - placeholder = self.Placeholder(candidate, decode_functions.meta, 23, data, verify, allow_empty_signature) + placeholder = self.Placeholder(candidate, decode_functions.meta, 35, data, verify, allow_empty_signature) # authentication decode_functions.authentication(placeholder) diff --git a/crypto.py b/crypto.py index 00d0b8ce..dcc4e2ec 100644 --- a/crypto.py +++ b/crypto.py @@ -1,424 +1,277 @@ -from hashlib import sha1 -from math import ceil -from struct import Struct -import logging - -from M2Crypto import EC, BIO +""" +All cryptography for Dispersy. +""" +from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric.ec import SECT233K1, SECT409K1, SECT571R1, generate_private_key, ECDSA +from cryptography.hazmat.primitives.hashes import SHA256, SHA512, SHA384, Hash +from cryptography.hazmat.primitives.serialization import load_pem_public_key, load_der_public_key, Encoding, \ + load_pem_private_key, load_der_private_key, PublicFormat, PrivateFormat, BestAvailableEncryption, NoEncryption + +DEFAULT_SECURITY_LEVELS = {"low": SECT233K1, + "medium": SECT409K1, + "high": SECT571R1} + + +class DispersyCrypto: + """ + Cryptography helper methods for Dispersy. + """ + @staticmethod + def is_valid_public_key(string): + """ + Verify if this binary string contains a public key. -# Add libnacl submodule to the python path -import sys -import os -sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'libnacl')) + :param string: a byte string possibly containing a public key + :return: a boolean + """ + for encoding in ["PEM", "DER"]: + try: + DispersyPublicKey.from_bytes(string, encoding) + return True + except (ValueError, UnsupportedAlgorithm): + pass -import libnacl.dual + return False -from .util import attach_runtime_statistics -from libnacl.encode import hex_encode + @staticmethod + def is_valid_private_key(string): + """ + Verify if this binary string contains a public/private keypair. -_STRUCT_L = Struct(">L") + :param string: a byte string possible containing a private key + :return: a boolean + """ + for encoding in DispersyKey.ENCODINGS: + try: + DispersyPrivateKey.from_bytes(string, encoding) + return True + except (ValueError, UnsupportedAlgorithm): + pass -# Allow all available curves. -# Niels: 16-12-2013, if it starts with NID_ -_CURVES = dict((unicode(curve), (getattr(EC, curve), "M2Crypto")) for curve in dir(EC) if curve.startswith("NID_")) + return False -# We want to provide a few default curves. We will change these curves as new become available and -# old ones to small to provide sufficient security. -_CURVES.update({u"very-low": (EC.NID_sect163k1, "M2Crypto"), - u"low": (EC.NID_sect233k1, "M2Crypto"), - u"medium": (EC.NID_sect409k1, "M2Crypto"), - u"high": (EC.NID_sect571r1, "M2Crypto")}) -# Add custom curves, not provided by M2Crypto -_CURVES.update({u'curve25519': (None, "libnacl")}) +class DispersyKey(object): + """ + A wrapper around a key (pair). + """ + ENCODINGS = {"DER", "PEM"} + HASH_ALGORITHMS = {"SHA256": SHA256, + "SHA384": SHA384, + "SHA512": SHA512} -logger = logging.getLogger(__name__) + def __init__(self, key): + """ + Create a new DispersyPublicKey instance. -class DispersyCrypto(object): + :param key: an EllipticCurve instance + """ + self.key = key @property - def security_levels(self): - """ - Returns the different security levels supported by this crypto class - @rtype: [unicode] + def curve(self): """ - raise NotImplementedError() + The curve instance by which the key is backed. - def generate_key(self, security_level): + :return: a """ - Generate a new key using the specified security_level - @param security_level: Level of security, supported levels can be obtained using .security_levels. - @type security_level: unicode + return self.key.curve - @rtype key + @property + def curve_name(self): """ - raise NotImplementedError() - - def key_to_bin(self, key): - "Convert a key to the binary format." - raise NotImplementedError() - - def key_to_hash(self, key): - "Get a hash representation from a key." - raise NotImplementedError() - - def key_from_public_bin(self, string): - "Convert a public key stored in the binary format to a key object." - raise NotImplementedError() - - def key_from_private_bin(self, string): - "Convert a public/private keypair stored in the binary format to a key object." - raise NotImplementedError() - - def is_valid_public_bin(self, string): - "Verify if this binary string contains a public key." - raise NotImplementedError() - - def is_valid_private_bin(self, string): - "Verify if this binary string contains a public/private keypair." - raise NotImplementedError() + The name of the curve backing the key instance. - def is_valid_signature(self, key, string, signature): - "Verify if the signature matches the one generated by key/string pair." - raise NotImplementedError() + :return: a string + """ + return self.key.curve.name - def create_signature(self, key, string): - "Create a signature using this key for this string." - raise NotImplementedError() + def has_private_key(self): + """ + Whether this is a DispersyPublicKey or DispersyPrivateKey instance. - def get_signature_length(self, key): - "Get the length of a signature created using this key in bytes." + :return: a boolean + """ raise NotImplementedError() + def hash(self, hash_algorithm="SHA256"): + """ + Get a hash of the public part of this key. -class ECCrypto(DispersyCrypto): - """ - A crypto object which provides a layer between Dispersy and low level eccrypographic features. - - Most methods are implemented by: - @author: Boudewijn Schoon - @organization: Technical University Delft - @contact: dispersy@frayja.com - - However since then, most functionality was completely rewritten by: - @author: Niels Zeilemaker - """ + :param hash_algorithm: one of HASH_ALGORITHMS + :return: a binary string + """ + digest = Hash(self.HASH_ALGORITHMS[hash_algorithm](), default_backend()) + digest.update(self.public_bytes()) + return digest.finalize() - def _progress(self, *args): - "Called when no feedback needs to be given." - pass + @property + def key_size(self): + return self.key.curve.key_size @property - def security_levels(self): + def signature_length(self): """ - Returns the names of all available curves. - @rtype: [unicode] - """ - return _CURVES.keys() + TODO: Determine the signature length. See conversion.py, L1111. - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def generate_key(self, security_level): + These seem somehow longer than expected. For example: 233 bits -> 60 bytes, but actually is 64 bytes. + :return: """ - Generate a new Elliptic Curve object with a new public / private key pair. - - Security can be u'low', u'medium', or u'high' depending on how secure you need your Elliptic - Curve to be. Currently these values translate into: - - very-low: NID_sect163k1 ~42 byte signatures - - low: NID_sect233k1 ~60 byte signatures - - medium: NID_sect409k1 ~104 byte signatures - - high: NID_sect571r1 ~144 byte signatures - - Besides these predefined curves, all other curves provided by M2Crypto are also available. For - a full list of available curves, see ec_get_curves(). + temporary_key = generate_private_key(self.curve, default_backend()) + signature = temporary_key.sign(b"", ECDSA(SHA256())) + return len(signature) - @param security_level: Level of security {u'very-low', u'low', u'medium', or u'high'}. - @type security_level: unicode + def public_bytes(self, encoding="PEM", encoding_format="SubjectPublicKeyInfo"): """ - assert isinstance(security_level, unicode) - assert security_level in _CURVES + Get this public key in byte form. - curve = _CURVES[security_level] - if curve[1] == "M2Crypto": - return M2CryptoSK(curve[0]) - - if curve[1] == "libnacl": - return LibNaCLSK() - - def key_to_bin(self, ec): - "Convert the key to a binary format." - assert isinstance(ec, DispersyKey), ec - return ec.key_to_bin() - - def key_to_hash(self, ec): - "Get a hash representation from a key." - assert isinstance(ec, DispersyKey), ec - return ec.key_to_hash() - - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def is_valid_private_bin(self, string): - "Returns True if the input is a valid public/private keypair stored in a binary format" - try: - self.key_from_private_bin(string) - except: - return False - return True - - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def is_valid_public_bin(self, string): - "Returns True if the input is a valid public key" - try: - self.key_from_public_bin(string) - except: - return False - return True - - def key_from_private_bin(self, string): - "Get the EC from a public/private keypair stored in a binary format." - if string.startswith("LibNaCLSK:"): - return LibNaCLSK(string[10:]) - return M2CryptoSK(keystring=string) - - def key_from_public_bin(self, string): - "Get the EC from a public key in binary format." - if string.startswith("LibNaCLPK:"): - return LibNaCLPK(string[10:]) - return M2CryptoPK(keystring=string) - - def get_signature_length(self, ec): - """ - Returns the length, in bytes, of each signature made using EC. + :param encoding: either "PEM" or "DER" + :param encoding_format: either "X.509 subjectPublicKeyInfo with PKCS#1", "Raw PKCS#1" or "OpenSSH" + :return: a byte string """ - assert isinstance(ec, DispersyKey), ec - return ec.get_signature_length() + return self.key.public_bytes(Encoding[encoding], PublicFormat[encoding_format]) - def create_signature(self, ec, data): + @staticmethod + def verify(key, signature, data, hash_algorithm="SHA256"): """ - Returns the signature of DIGEST made using EC. - """ - assert isinstance(ec, DispersyKey), ec - assert isinstance(data, str), type(data) - return ec.signature(data) + Verify whether the data was signed for this public key. - def is_valid_signature(self, ec, data, signature): - """ - Returns True when SIGNATURE matches the DIGEST made using EC. + :param key: a public key to check against + :param signature: a bytes object with DER encoded contents (See RFC 3279) + :param data: the signed data + :param hash_algorithm: the hash algorithm used in signing + :return: a boolean """ - assert isinstance(ec, DispersyKey), ec - assert isinstance(data, str), type(data) - assert isinstance(signature, str), type(signature) - assert len(signature) == self.get_signature_length(ec), [len(signature), self.get_signature_length(ec)] - + algorithm = ECDSA(DispersyKey.HASH_ALGORITHMS[hash_algorithm]()) try: - return ec.verify(signature, data) - except: + key.verify(signature, data, algorithm) + except InvalidSignature: return False - -class NoVerifyCrypto(ECCrypto): - """ - A crypto object which assumes all signatures are valid. Usefull to reduce CPU overhead. - - """ - def is_valid_signature(self, ec, digest, signature): return True -class NoCrypto(NoVerifyCrypto): +class DispersyPublicKey(DispersyKey): """ - A crypto object which does not create a valid signatures, and assumes all signatures are valid. - Usefull to reduce CPU overhead. + A wrapper around a public key. """ + AVAILABLE_FORMATS = PublicFormat.__members__.keys() - def create_signature(self, ec, digest): - return "0" * self.get_signature_length(ec) - - -class DispersyKey(object): - - def pub(self): - raise NotImplementedError() - - def has_secret_key(self): - raise NotImplementedError() - - def key_to_bin(self): - raise NotImplementedError() - - def key_to_hash(self): - if self.has_secret_key(): - return sha1(self.pub().key_to_bin()).digest() - return sha1(self.key_to_bin()).digest() - - -class M2CryptoPK(DispersyKey): - - def __init__(self, ec_pub=None, keystring=None): - if ec_pub: - self.ec = ec_pub - elif keystring: - self.ec = self.key_from_pem("-----BEGIN PUBLIC KEY-----\n%s-----END PUBLIC KEY-----\n" % keystring.encode("BASE64")) - - def pub(self): - return self - - def has_secret_key(self): - return False - - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def pem_to_bin(self, pem): - """ - Convert a key in the PEM format into a key in the binary format. - @note: Enrcypted pem's are NOT supported and will silently fail. + def __init__(self, key): """ - return "".join(pem.split("\n")[1:-2]).decode("BASE64") + Create a new DispersyPublicKey instance. - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def key_to_pem(self): - "Convert a key to the PEM format." - bio = BIO.MemoryBuffer() - self.ec.save_pub_key_bio(bio) - return bio.read_all() - - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def key_from_pem(self, pem): - "Get the EC from a public PEM." - return EC.load_pub_key_bio(BIO.MemoryBuffer(pem)) + :param key: an EllipticCurvePublicKey instance + """ + super(DispersyPublicKey, self).__init__(key) + self.public_key = self - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def key_to_bin(self): - return self.pem_to_bin(self.key_to_pem()) + def public_bytes(self, encoding="PEM", encoding_format="SubjectPublicKeyInfo"): + return self.key.public_bytes(Encoding[encoding], PublicFormat[encoding_format]) - def get_signature_length(self): - return int(ceil(len(self.ec) / 8.0)) * 2 + @property + def has_private_key(self): + return False - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def verify(self, signature, data): - length = len(signature) / 2 - r = signature[:length] - # remove all "\x00" prefixes - while r and r[0] == "\x00": - r = r[1:] - # prepend "\x00" when the most significant bit is set - if ord(r[0]) & 128: - r = "\x00" + r + def verify(self, signature, data, hash_algorithm="SHA256"): + return DispersyKey.verify(self.key, signature, data, hash_algorithm) - s = signature[length:] - # remove all "\x00" prefixes - while s and s[0] == "\x00": - s = s[1:] - # prepend "\x00" when the most significant bit is set - if ord(s[0]) & 128: - s = "\x00" + s + @staticmethod + def from_bytes(string, encoding="PEM"): + """ + Load a DispersyPublicKey from a byte string. - mpi_r = _STRUCT_L.pack(len(r)) + r - mpi_s = _STRUCT_L.pack(len(s)) + s + :param string: the byte string containing the public key + :param encoding: the encoding used. One of "PEM" or "DER" + :return: a new DispersyPublicKey instance + """ + if Encoding[encoding] is Encoding.PEM: + loaded_key = load_pem_public_key(string, backend=default_backend()) + elif Encoding[encoding] is Encoding.DER: + loaded_key = load_der_public_key(string, backend=default_backend()) + else: + raise UnknownKeyEncodingException(encoding) - # mpi_r3 = bn_to_mpi(bin_to_bn(signature[:length])) - # mpi_s3 = bn_to_mpi(bin_to_bn(signature[length:])) + return DispersyPublicKey(loaded_key) - # if not mpi_r == mpi_r3: - # raise RuntimeError([mpi_r.encode("HEX"), mpi_r3.encode("HEX")]) - # if not mpi_s == mpi_s3: - # raise RuntimeError([mpi_s.encode("HEX"), mpi_s3.encode("HEX")]) - digest = sha1(data).digest() - return bool(self.ec.verify_dsa(digest, mpi_r, mpi_s)) +class DispersyPrivateKey(DispersyKey): + """ + A wrapper around a key pair. + """ + AVAILABLE_FORMATS = PrivateFormat.__members__.keys() + def __init__(self, key=None, security_level="high"): + """ + Create a new private-public key pair. -class M2CryptoSK(M2CryptoPK): + :param key: the private key + :param security_level: a curve to generate a new key from, if one is not provided + """ + key = key or generate_private_key(DEFAULT_SECURITY_LEVELS[security_level], backend=default_backend()) + super(DispersyPrivateKey, self).__init__(key) - def __init__(self, curve=None, keystring=None, filename=None): - if curve: - self.ec = EC.gen_params(curve) - self.ec.gen_key() + self.public_key = DispersyPublicKey(self.key.public_key()) - elif keystring: - self.ec = self.key_from_pem("-----BEGIN EC PRIVATE KEY-----\n%s-----END EC PRIVATE KEY-----\n" % keystring.encode("BASE64")) + def public_bytes(self, encoding="PEM", encoding_format="SubjectPublicKeyInfo"): + return self.public_key.public_bytes(encoding, encoding_format) - elif filename: - # this workaround is needed to run Tribler on Windows 64 bit - membuf = BIO.MemoryBuffer(open(filename, 'rb').read()) - self.ec = EC.load_key_bio(membuf) - membuf.close() + def private_bytes(self, encoding="PEM", encoding_format="PKCS8", password=None): + """ + Get this private key in byte form. - def pub(self): - return M2CryptoPK(ec_pub=self.ec.pub()) + :param encoding: either "PEM" or "DER" + :param encoding_format: either "X.509 subjectPublicKeyInfo with PKCS#1", "Raw PKCS#1" or "OpenSSH" + :param password: an optional password in case the private key is to be encoded + :return: a byte string + """ + encryption = BestAvailableEncryption(password) if password else NoEncryption() + return self.key.private_bytes(Encoding[encoding], PrivateFormat[encoding_format], encryption) - def has_secret_key(self): + @property + def has_private_key(self): return True - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def key_to_pem(self): - "Convert a key to the PEM format." - bio = BIO.MemoryBuffer() - self.ec.save_key_bio(bio, None, lambda *args: "") - return bio.read_all() - - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def key_from_pem(self, pem): - "Get the EC from a public/private keypair stored in the PEM." - def get_password(*args): - return "" - return EC.load_key_bio(BIO.MemoryBuffer(pem), get_password) - - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def signature(self, msg): - length = int(ceil(len(self.ec) / 8.0)) - digest = sha1(msg).digest() - - mpi_r, mpi_s = self.ec.sign_dsa(digest) - length_r, = _STRUCT_L.unpack_from(mpi_r) - r = mpi_r[-min(length, length_r):] - length_s, = _STRUCT_L.unpack_from(mpi_s) - s = mpi_s[-min(length, length_s):] - - return "".join(("\x00" * (length - len(r)), r, "\x00" * (length - len(s)), s)) - - -class LibNaCLPK(DispersyKey): - - def __init__(self, binarykey="", pk=None, hex_vk=None): - if binarykey: - pk, vk = binarykey[:libnacl.crypto_box_SECRETKEYBYTES], binarykey[libnacl.crypto_box_SECRETKEYBYTES: libnacl.crypto_box_SECRETKEYBYTES + libnacl.crypto_sign_SEEDBYTES] - hex_vk = hex_encode(vk) - - self.key = libnacl.public.PublicKey(pk) - self.veri = libnacl.sign.Verifier(hex_vk) - - def pub(self): - return self - - def has_secret_key(self): - return False - - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def verify(self, signature, msg): - return self.veri.verify(signature + msg) + def sign(self, data, hash_algorithm="SHA256"): + """ + Sign the provided data. - def key_to_bin(self): - return "LibNaCLPK:" + self.key.pk + self.veri.vk + Note that in order to verify the signature created by this method, the + hash algorithm used in creating the signature needs to be known. It is + therefore advised not to deviate from the default algorithm. - def get_signature_length(self): - return libnacl.crypto_sign_BYTES + :param data: the data to sign + :param hash_algorithm: the hash algorithm to use in signing + :return: a bytes object with DER encoded contents (see RFC 3279) + """ + algorithm = ECDSA(self.HASH_ALGORITHMS[hash_algorithm]()) + return self.key.sign(data, algorithm) + def verify(self, signature, data, hash_algorithm="SHA256"): + return DispersyKey.verify(self.key.public_key(), signature, data, hash_algorithm) -class LibNaCLSK(LibNaCLPK): + @staticmethod + def from_bytes(string, encoding="PEM", password=None): + """ + Load a private key from a byte string. - def __init__(self, binarykey=""): - if binarykey: - crypt, seed = binarykey[:libnacl.crypto_box_SECRETKEYBYTES], binarykey[libnacl.crypto_box_SECRETKEYBYTES : libnacl.crypto_box_SECRETKEYBYTES + libnacl.crypto_sign_SEEDBYTES] - self.key = libnacl.dual.DualSecret(crypt, seed) + :param string: the byte string containing the private key + :param encoding: the encoding used + :param password: an optional password in case the private key is encoded + :return: a new DispersyPrivateKey instance + """ + if Encoding[encoding] is Encoding.PEM: + loaded_key = load_pem_private_key(string, password=password, backend=default_backend()) + elif Encoding[encoding] is Encoding.DER: + loaded_key = load_der_private_key(string, password=password, backend=default_backend()) else: - self.key = libnacl.dual.DualSecret() - self.veri = libnacl.sign.Verifier(self.key.hex_vk()) + raise UnknownKeyEncodingException(encoding) - def pub(self): - return LibNaCLPK(pk=self.key.pk, hex_vk=self.veri.hex_vk()) + return DispersyPrivateKey(loaded_key) - def has_secret_key(self): - return True - @attach_runtime_statistics(u"{0.__class__.__name__}.{function_name}") - def signature(self, msg): - return self.key.signature(msg) - - def key_to_bin(self): - return "LibNaCLSK:" + self.key.sk + self.key.seed +class UnknownKeyEncodingException(Exception): + """ + Thrown when a key encoding wasn't recognised. + """ + pass diff --git a/discovery/community.py b/discovery/community.py index 3e1330a1..1b1bcafc 100644 --- a/discovery/community.py +++ b/discovery/community.py @@ -130,7 +130,7 @@ class PossibleTasteBuddy(TasteBuddy): def __init__(self, overlap, preferences, timestamp, candidate_mid, received_from): assert isinstance(timestamp, (long, float)), type(timestamp) assert isinstance(candidate_mid, str), type(candidate_mid) - assert len(candidate_mid) == 20, len(candidate_mid) + assert len(candidate_mid) == 32, len(candidate_mid) assert isinstance(received_from, WalkCandidate), type(received_from) TasteBuddy.__init__(self, overlap, preferences, None) @@ -391,7 +391,7 @@ def is_taste_buddy(self, candidate): def is_taste_buddy_mid(self, mid): assert isinstance(mid, str) - assert len(mid) == 20 + assert len(mid) == 32 for tb in self.yield_taste_buddies(): if mid == tb.candidate_mid: @@ -419,7 +419,7 @@ def is_recent_taste_buddy(self, candidate): def is_recent_taste_buddy_mid(self, mid): assert isinstance(mid, str) - assert len(mid) == 20 + assert len(mid) == 32 return mid in self.recent_taste_buddies @@ -481,7 +481,7 @@ def has_possible_taste_buddies(self, candidate): def is_possible_taste_buddy_mid(self, mid): assert isinstance(mid, str) - assert len(mid) == 20 + assert len(mid) == 32 for ptb in self.possible_taste_buddies: if mid == ptb.candidate_mid: diff --git a/dispersy.py b/dispersy.py index 83acdc92..3c1bc613 100644 --- a/dispersy.py +++ b/dispersy.py @@ -53,10 +53,10 @@ from twisted.python.failure import Failure from twisted.python.threadable import isInIOThread +from crypto import DispersyPrivateKey, DispersyCrypto, DispersyPublicKey from .authentication import MemberAuthentication, DoubleMemberAuthentication from .candidate import LoopbackCandidate, WalkCandidate, Candidate from .community import Community -from .crypto import DispersyCrypto, ECCrypto from .destination import CommunityDestination, CandidateDestination from .discovery.community import DiscoveryCommunity from .dispersydatabase import DispersyDatabase @@ -85,7 +85,7 @@ class Dispersy(TaskManager): outgoing data for, possibly, multiple communities. """ - def __init__(self, endpoint, working_directory, database_filename=u"dispersy.db", crypto=ECCrypto()): + def __init__(self, endpoint, working_directory, database_filename=u"dispersy.db"): """ Initialise a Dispersy instance. @@ -101,7 +101,6 @@ def __init__(self, endpoint, working_directory, database_filename=u"dispersy.db" assert isinstance(endpoint, Endpoint), type(endpoint) assert isinstance(working_directory, unicode), type(working_directory) assert isinstance(database_filename, unicode), type(database_filename) - assert isinstance(crypto, DispersyCrypto), type(crypto) super(Dispersy, self).__init__() self._logger = logging.getLogger(self.__class__.__name__) @@ -125,8 +124,6 @@ def __init__(self, endpoint, working_directory, database_filename=u"dispersy.db" database_filename = os.path.join(database_directory, database_filename) self._database = DispersyDatabase(database_filename) - self._crypto = crypto - # indicates what our connection type is. currently it can be u"unknown", u"public", or # u"symmetric-NAT" self._connection_type = u"unknown" @@ -358,14 +355,6 @@ def database(self): """ return self._database - @property - def crypto(self): - """ - The Dispersy crypto singleton. - @rtype: DispersyCrypto - """ - return self._crypto - @property def statistics(self): """ @@ -445,15 +434,13 @@ def detach_progress_handler(self, func): def get_progress_handlers(self): return self._progress_handlers - def get_member(self, mid="", public_key="", private_key=""): + def get_member(self, mid="", public_key=None, private_key=None): """Returns a Member instance associated with public_key. Since we have the public_key, we can create this user if it doesn't yet. Hence, this method always succeeds. @param public_key: The public key of the member we want to obtain. @param private_key: The public/private key pair of the member we want to obtain. - @type public_key: string - @type private_key: string @return: The Member instance associated with public_key. @rtype: Member @@ -461,81 +448,76 @@ def get_member(self, mid="", public_key="", private_key=""): assert sum(map(bool, (mid, public_key, private_key))) == 1, \ "Only one of the three optional arguments may be passed: %s" % str((mid, public_key, private_key)) assert isinstance(mid, str) - assert isinstance(public_key, str) - assert isinstance(private_key, str) - assert not mid or len(mid) == 20, (mid.encode("HEX"), len(mid)) - assert not public_key or self.crypto.is_valid_public_bin(public_key) - assert not private_key or self.crypto.is_valid_private_bin(private_key) + assert not mid or len(mid) == 32, (mid.encode("HEX"), len(mid)) if not mid: if public_key: - mid = sha1(public_key).digest() + mid = public_key.hash() elif private_key: - _key = self.crypto.key_from_private_bin(private_key) - mid = self.crypto.key_to_hash(_key.pub()) + mid = private_key.hash() member = self._member_cache_by_hash.get(mid) if member: return member if private_key: - key = self.crypto.key_from_private_bin(private_key) - public_key = self.crypto.key_to_bin(key.pub()) + public_key = private_key.public_key - elif public_key: - key = self.crypto.key_from_public_bin(public_key) - - # both public and private keys are valid at this point + # both public and private keys are valid from this point # The member is not cached, let's try to get it from the database row = self.database.execute(u"SELECT id, public_key, private_key FROM member WHERE mid = ? LIMIT 1", (buffer(mid),)).fetchone() + private_key_bytes = private_key.private_bytes() + public_key_bytes = public_key.public_bytes() + if row: database_id, public_key_from_db, private_key_from_db = row - public_key_from_db = "" if public_key_from_db is None else str(public_key_from_db) - private_key_from_db = "" if private_key_from_db is None else str(private_key_from_db) # the private key that was passed as an argument overrules everything, update db if neccesary if private_key: assert public_key - if private_key_from_db != private_key: + if private_key_from_db != private_key_bytes: self.database.execute(u"UPDATE member SET public_key = ?, private_key = ? WHERE id = ?", - (buffer(public_key), buffer(private_key), database_id)) + (buffer(public_key_bytes), buffer(private_key_bytes), database_id)) else: # the private key from the database overrules the public key argument if private_key_from_db: - key = self.crypto.key_from_private_bin(private_key_from_db) + key = DispersyPrivateKey.from_bytes(private_key_from_db) # the public key argument overrules anything in the database elif public_key: - if public_key_from_db != public_key: + if public_key_from_db != public_key_bytes: self.database.execute(u"UPDATE member SET public_key = ? WHERE id = ?", - (buffer(public_key), database_id)) + (buffer(public_key_bytes), database_id)) # no priv/pubkey arguments passed, maybe use the public key from the database elif public_key_from_db: - key = self.crypto.key_from_public_bin(public_key_from_db) + key = DispersyPublicKey.from_bytes(public_key_from_db) else: return DummyMember(self, database_id, mid) # the member is not in the database, insert it elif public_key or private_key: + if private_key: assert public_key + # The MID or public/private keys are not in the database, store them. database_id = self.database.execute( u"INSERT INTO member (mid, public_key, private_key) VALUES (?, ?, ?)", - (buffer(mid), buffer(public_key), buffer(private_key)), get_lastrowid=True) + (buffer(mid), buffer(public_key_bytes), buffer(private_key_bytes)), get_lastrowid=True) else: # We could't find the key on the DB, nothing else to do database_id = self.database.execute(u"INSERT INTO member (mid) VALUES (?)", - (buffer(mid),), get_lastrowid=True) + (buffer(mid),), get_lastrowid=True) return DummyMember(self, database_id, mid) - member = Member(self, key, database_id, mid) + key = private_key or public_key + member = Member(self, key, database_id) # store in cache self._member_cache_by_hash[member.mid] = member @@ -546,13 +528,12 @@ def get_member(self, mid="", public_key="", private_key=""): return member - def get_new_member(self, securitylevel=u"medium"): + def get_new_member(self, security_level=u"medium"): """ Returns a Member instance created from a newly generated public key. """ - assert isinstance(securitylevel, unicode), type(securitylevel) - key = self.crypto.generate_key(securitylevel) - return self.get_member(private_key=self.crypto.key_to_bin(key)) + key = DispersyPrivateKey(security_level=security_level) + return self.get_member(private_key=key) def get_member_from_database_id(self, database_id): """ diff --git a/libnacl b/libnacl deleted file mode 160000 index 64faa347..00000000 --- a/libnacl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 64faa34715a42d7c581492a78d9e18e8dd355df6 diff --git a/member.py b/member.py index c53d2001..a33c10c8 100644 --- a/member.py +++ b/member.py @@ -9,7 +9,8 @@ def __init__(self, dispersy, database_id, mid): assert isinstance(dispersy, Dispersy), type(dispersy) assert isinstance(database_id, int), type(database_id) assert isinstance(mid, str), type(mid) - assert len(mid) == 20, len(mid) + # The SHA256 hash is 32 bytes in size + assert len(mid) == 32, len(mid) self._logger = logging.getLogger(self.__class__.__name__) @@ -19,7 +20,7 @@ def __init__(self, dispersy, database_id, mid): @property def mid(self): """ - The member id. This is the 20 byte sha1 hash over the public key. + The member id. This is the 32 byte sha256 hash over the public key. """ return self._mid @@ -70,7 +71,7 @@ def __str__(self): class Member(DummyMember): - def __init__(self, dispersy, key, database_id, mid=None): + def __init__(self, dispersy, key, database_id): """ Create a new Member instance. """ @@ -80,23 +81,11 @@ def __init__(self, dispersy, key, database_id, mid=None): assert isinstance(key, DispersyKey), type(key) assert isinstance(database_id, int), type(database_id) - if not mid: - mid = dispersy.crypto.key_to_hash(key.pub()) - super(Member, self).__init__(dispersy, database_id, mid) + super(Member, self).__init__(dispersy, database_id, mid=key.hash()) - public_key = dispersy.crypto.key_to_bin(key.pub()) + self._key = key - if key.has_secret_key(): - private_key = key - else: - private_key = None - - self._crypto = dispersy.crypto self._database = dispersy.database - self._public_key = public_key - self._private_key = private_key - self._ec = key - self._signature_length = self._crypto.get_signature_length(self._ec) self._has_identity = set() @property @@ -106,7 +95,7 @@ def public_key(self): This is binary representation of the public key. """ - return self._public_key + return self._key.public_bytes() @property def private_key(self): @@ -118,14 +107,14 @@ def private_key(self): It may be an empty string when the private key is not yet available. In this case the sign method will raise a RuntimeError. """ - return self._private_key + return self._key.private_bytes() @property def signature_length(self): """ The length, in bytes, of a signature. """ - return self._signature_length + return self._key.signature_length def add_identity(self, community): self._has_identity.add(community.cid) @@ -164,8 +153,7 @@ def verify(self, data, signature, offset=0, length=0): # DATA is to small, we expect len(DATA[OFFSET:OFFSET+LENGTH]) to be LENGTH return False - if self._public_key and self._signature_length == len(signature): - return self._crypto.is_valid_signature(self._ec, data[offset:offset + length], signature) + return self._key.verify(signature, data[offset:offset + length]) def sign(self, data, offset=0, length=0): """ @@ -186,8 +174,8 @@ def sign(self, data, offset=0, length=0): # DATA is to small, we expect len(DATA[OFFSET:OFFSET+LENGTH]) to be LENGTH raise ValueError("LENGTH is larger than the available DATA") - if self._private_key: - return self._crypto.create_signature(self._ec, data[offset:offset + length]) + if self._key.has_private_key: + return self._key.sign(data[offset:offset + length]) else: raise RuntimeError("unable to sign data without the private key") @@ -210,7 +198,7 @@ def __hash__(self): """ Allows Member classes to be used as keys in a dictionary. """ - return self._public_key.__hash__() + return hash(self._key) def __str__(self): """ diff --git a/message.py b/message.py index 1c9091c4..68ca4592 100644 --- a/message.py +++ b/message.py @@ -73,7 +73,7 @@ class DelayPacketByMissingMember(DelayPacket): def __init__(self, community, missing_member_id): assert isinstance(missing_member_id, str), type(missing_member_id) - assert len(missing_member_id) == 20, len(missing_member_id) + assert len(missing_member_id) == 32, len(missing_member_id) super(DelayPacketByMissingMember, self).__init__(community, "Missing member") self._missing_member_id = missing_member_id diff --git a/payload.py b/payload.py index fcc3fc1d..95dad4c0 100644 --- a/payload.py +++ b/payload.py @@ -525,7 +525,7 @@ class Implementation(Payload.Implementation): def __init__(self, meta, mid): assert isinstance(mid, str) - assert len(mid) == 20 + assert len(mid) == 32 super(MissingIdentityPayload.Implementation, self).__init__(meta) self._mid = mid diff --git a/tests/debugcommunity/node.py b/tests/debugcommunity/node.py index 03702af2..fdc2ac12 100644 --- a/tests/debugcommunity/node.py +++ b/tests/debugcommunity/node.py @@ -30,14 +30,14 @@ class DebugNode(object): node.init_my_member() """ - def __init__(self, testclass, dispersy, communityclass=DebugCommunity, c_master_member=None, curve=u"low"): + def __init__(self, testclass, dispersy, communityclass=DebugCommunity, c_master_member=None, security_level=u"low"): super(DebugNode, self).__init__() self._logger = logging.getLogger(self.__class__.__name__) self._testclass = testclass self._dispersy = dispersy - self._my_member = self._dispersy.get_new_member(curve) - self._my_pub_member = Member(self._dispersy, self._my_member._ec.pub(), self._my_member.database_id) + self._my_member = self._dispersy.get_new_member(security_level) + self._my_pub_member = Member(self._dispersy, self._my_member._key.public_key, self._my_member.database_id) if c_master_member == None: self._community = communityclass.create_community(self._dispersy, self._my_member) diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 3e6feb5a..0ea9dc37 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -1,78 +1,83 @@ from unittest import TestCase -from ..crypto import ECCrypto +from ..crypto import DispersyPrivateKey, DEFAULT_SECURITY_LEVELS, DispersyPublicKey -class TestLowLevelCrypto(TestCase): +class TestDispersyKey(TestCase): - @classmethod - def setUpClass(cls): - super(TestLowLevelCrypto, cls).setUpClass() - cls.crypto = ECCrypto() + def setUp(self): + self.data = "".join(chr(i % 256) for i in range(1024)) def test_sign_and_verify(self): """ - Creates each curve, signs some data, and finally verifies the signature. + Creates a curve for each security_level, signs some data, and finally verifies the signature. """ - data = "".join(chr(i % 256) for i in xrange(1024)) - for curve in self.crypto.security_levels: - ec = self.crypto.generate_key(curve) - signature = self.crypto.create_signature(ec, data) - self.assertEqual(len(signature), self.crypto.get_signature_length(ec)) - self.assertTrue(self.crypto.is_valid_signature(ec, data, signature)) - - self.assertFalse(self.crypto.is_valid_signature(ec, data, "-" * self.crypto.get_signature_length(ec))) - self.assertFalse(self.crypto.is_valid_signature(ec, "---", signature)) - - for i in xrange(len(signature)): - # invert one bit in the ith character of the signature - invalid_signature = list(signature) - invalid_signature[i] = chr(ord(invalid_signature[i]) ^ 1) - invalid_signature = "".join(invalid_signature) - self.assertNotEqual(signature, invalid_signature) - self.assertFalse(self.crypto.is_valid_signature(ec, data, invalid_signature)) - - def test_serialise_binary(self): + for security_level in DEFAULT_SECURITY_LEVELS.keys(): + key = DispersyPrivateKey(security_level=security_level) + signature = key.sign(self.data) + + self.assertTrue(key.verify(signature, self.data)) + + def test_serialise_private(self): + """ + Create, serialise and deserialize a key for each curve. + """ + for security_level in DEFAULT_SECURITY_LEVELS.keys(): + key = DispersyPrivateKey(security_level=security_level) + binary = key.private_bytes() + key2 = DispersyPrivateKey.from_bytes(binary) + + self.assertEqual(key2.hash(), key.hash()) + + def test_serialise_public(self): """ Creates and serialises each curve. """ - data = "".join(chr(i % 256) for i in xrange(1024)) - for curve in self.crypto.security_levels: - ec = self.crypto.generate_key(curve) - ec_pub = ec.pub() - - signature = self.crypto.create_signature(ec, data) - self.assertEqual(len(signature), self.crypto.get_signature_length(ec)) - self.assertTrue(self.crypto.is_valid_signature(ec, data, signature)) - - public = self.crypto.key_to_bin(ec_pub) - self.assertTrue(self.crypto.is_valid_public_bin(public), public) - self.assertEqual(public, self.crypto.key_to_bin(ec_pub)) - - private = self.crypto.key_to_bin(ec) - self.assertTrue(self.crypto.is_valid_private_bin(private), private) - self.assertEqual(private, self.crypto.key_to_bin(ec)) - - ec_clone = self.crypto.key_from_public_bin(public) - self.assertTrue(self.crypto.is_valid_signature(ec_clone, data, signature)) - ec_clone = self.crypto.key_from_private_bin(private) - self.assertTrue(self.crypto.is_valid_signature(ec_clone, data, signature)) - - def test_performance(self): - from time import time - import sys - import os - - ec = self.crypto.generate_key(u"very-low") - - data = [os.urandom(1024) for i in xrange(1000)] - for curve in [u"very-low", u"low", u"medium", u"high", u"curve25519"]: - t1 = time() - ec = self.crypto.generate_key(curve) - t2 = time() - signatures = [self.crypto.create_signature(ec, msg) for msg in data] - t3 = time() - verfified = [self.crypto.is_valid_signature(ec, msg, signature) for msg, signature in zip(data, signatures)] - # print >> sys.stderr, curve, "verify", time() - t3, "sign", t3 - t2, "genkey", t2 - t1 - - assert all(verfified) + for security_level in DEFAULT_SECURITY_LEVELS.keys(): + key = DispersyPrivateKey(security_level=security_level) + public_key = key.public_key + signature = key.sign(self.data) + + binary = public_key.public_bytes() + key2 = DispersyPublicKey.from_bytes(binary) + + self.assertTrue(key2.verify(signature, self.data)) + + def test_sign_not_valid(self): + """ + A signature created with another key shouldn't be valid. + """ + for security_level in DEFAULT_SECURITY_LEVELS.keys(): + key1 = DispersyPrivateKey(security_level=security_level) + key2 = DispersyPrivateKey(security_level=security_level) + signature1 = key1.sign(self.data) + signature2 = key2.sign(self.data) + + self.assertFalse(key1.verify(signature2, self.data)) + self.assertFalse(key2.verify(signature1, self.data)) + + def test_sign_verify_with_public(self): + """ + Signature verification should be possible with a public key only. + """ + for security_level in DEFAULT_SECURITY_LEVELS.keys(): + key = DispersyPrivateKey(security_level=security_level) + public_key = key.public_key + signature = key.sign(self.data) + + self.assertTrue(public_key.verify(signature, self.data)) + + def test_sign_verify_with_other_public(self): + """ + Signature verification with another public key should fail. + """ + for security_level in DEFAULT_SECURITY_LEVELS.keys(): + key1 = DispersyPrivateKey(security_level=security_level) + key2 = DispersyPrivateKey(security_level=security_level) + public_key1 = key1.public_key + public_key2 = key2.public_key + signature1 = key1.sign(self.data) + signature2 = key2.sign(self.data) + + self.assertFalse(public_key1.verify(signature2, self.data)) + self.assertFalse(public_key2.verify(signature1, self.data)) diff --git a/tests/test_member.py b/tests/test_member.py index 37681198..d0506d06 100644 --- a/tests/test_member.py +++ b/tests/test_member.py @@ -1,3 +1,4 @@ +from crypto import DispersyPrivateKey from .dispersytestclass import DispersyTestFunc from ..util import call_on_reactor_thread @@ -6,72 +7,70 @@ class TestMember(DispersyTestFunc): def test_verify(self): self._test_verify(u"medium") - self._test_verify(u"curve25519") @call_on_reactor_thread - def _test_verify(self, curve): + def _test_verify(self, security_level): """ Test test member.verify assuming create_signature works properly. """ - ec = self._dispersy.crypto.generate_key(curve) - member = self._dispersy.get_member(private_key=self._dispersy.crypto.key_to_bin(ec)) + key = DispersyPrivateKey(security_level=security_level) + member = self._dispersy.get_member(private_key=key) # sign and verify "0123456789"[0:10] - self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "0123456789"))) - self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=0)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=0)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=9)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=9)) - self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=10)) - self.assertTrue(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=10)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=11)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=11)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=666)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "0123456789"), offset=0, length=666)) + self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "0123456789"))) + self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=0)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=0)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=9)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=9)) + self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=10)) + self.assertTrue(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=10)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=11)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=11)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=666)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "0123456789"), offset=0, length=666)) # sign and verify "0123456789"[1:10] - self.assertTrue(member.verify("123456789", self._dispersy.crypto.create_signature(ec, "123456789"))) - self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=0)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=0)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=8)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=8)) - self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=9)) - self.assertTrue(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=9)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=10)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=10)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=666)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "123456789"), offset=1, length=666)) + self.assertTrue(member.verify("123456789", self._dispersy.crypto.create_signature(key, "123456789"))) + self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=0)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=0)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=8)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=8)) + self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=9)) + self.assertTrue(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=9)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=10)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=10)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=666)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "123456789"), offset=1, length=666)) # sign and verify "0123456789"[0:9] - self.assertTrue(member.verify("012345678", self._dispersy.crypto.create_signature(ec, "012345678"))) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=0)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=0)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=8)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=8)) - self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=9)) - self.assertTrue(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=9)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=10)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=10)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=666)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "012345678"), offset=0, length=666)) + self.assertTrue(member.verify("012345678", self._dispersy.crypto.create_signature(key, "012345678"))) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=0)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=0)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=8)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=8)) + self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=9)) + self.assertTrue(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=9)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=10)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=10)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=666)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "012345678"), offset=0, length=666)) # sign and verify "0123456789"[1:9] - self.assertTrue(member.verify("12345678", self._dispersy.crypto.create_signature(ec, "12345678"))) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=0)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=0)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=7)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=7)) - self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=8)) - self.assertTrue(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=8)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=9)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=9)) - self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=666)) - self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(ec, "12345678"), offset=1, length=666)) + self.assertTrue(member.verify("12345678", self._dispersy.crypto.create_signature(key, "12345678"))) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=0)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=0)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=7)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=7)) + self.assertTrue(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=8)) + self.assertTrue(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=8)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=9)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=9)) + self.assertFalse(member.verify("0123456789", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=666)) + self.assertFalse(member.verify("0123456789E", self._dispersy.crypto.create_signature(key, "12345678"), offset=1, length=666)) def test_sign(self): self._test_sign(u"medium") - self._test_sign(u"curve25519") @call_on_reactor_thread def _test_sign(self, curve): diff --git a/tool/createkey.py b/tool/createkey.py deleted file mode 100755 index 3fbea814..00000000 --- a/tool/createkey.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python2 - -""" -Create one or more Elliptic Curves. - -Typically the following curves are used: -- very-low: NID_sect163k1 ~42 byte signatures -- low: NID_sect233k1 ~60 byte signatures -- medium: NID_sect409k1 ~104 byte signatures -- high: NID_sect571r1 ~144 byte signatures -""" - -import argparse -import time -from hashlib import sha1 - -from M2Crypto import EC -# From: http://docs.python.org/2/tutorial/modules.html#intra-package-references -# Note that both explicit and implicit relative imports are based on the name of the current -# module. Since the name of the main module is always "__main__", modules intended for use as the -# main module of a Python application should always use absolute imports. -from dispersy.crypto import ECCrypto, _CURVES - -def ec_name(eccrypto, curve): - assert isinstance(curve, unicode) - curve_id = _CURVES[curve] - - for name in dir(EC): - value = getattr(EC, name) - if isinstance(value, int) and value == curve_id: - return name - -def create_key(eccrypto, curves): - for index, curve in enumerate(curves): - if index > 0: - print - - private_pem = "" - public_pem = "" - - ec = eccrypto.generate_key(curve) - if hasattr(ec, 'key_to_pem'): - print "KEP" - private_pem = ec.key_to_pem() - public_pem = ec.pub().key_to_pem() - - private_bin = eccrypto.key_to_bin(ec) - public_bin = eccrypto.key_to_bin(ec.pub()) - print "generated:", time.ctime() - print "curve:", ec_name(eccrypto, curve) - print "len:", len(ec.ec), "bits ~", eccrypto.get_signature_length(ec), "bytes signature" - print "pub:", len(public_bin), public_bin.encode("HEX") - print "prv:", len(private_bin), private_bin.encode("HEX") - print "pub-sha1", sha1(public_bin).digest().encode("HEX") - print "prv-sha1", sha1(private_bin).digest().encode("HEX") - print public_pem.strip() - print private_pem.strip() - -def main(): - eccrypto = ECCrypto() - - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("curves", - metavar="CURVE", - nargs="+", - choices=sorted([str(curve) for curve in eccrypto.security_levels]), - help="EC curves to create") - args = parser.parse_args() - - create_key(eccrypto, (unicode(curve) for curve in args.curves)) - -if __name__ == "__main__": - main() diff --git a/twisted/plugins/tracker_plugin.py b/twisted/plugins/tracker_plugin.py index 3488e54a..d15afcdc 100644 --- a/twisted/plugins/tracker_plugin.py +++ b/twisted/plugins/tracker_plugin.py @@ -40,7 +40,6 @@ sys.path.insert(0, DISPERSY_ROOT_PATH) from dispersy.candidate import LoopbackCandidate -from dispersy.crypto import NoVerifyCrypto, NoCrypto from dispersy.discovery.community import DiscoveryCommunity from dispersy.dispersy import Dispersy from dispersy.endpoint import StandaloneEndpoint @@ -64,8 +63,8 @@ class TrackerDispersy(Dispersy): - def __init__(self, endpoint, working_directory, silent=False, crypto=NoVerifyCrypto()): - super(TrackerDispersy, self).__init__(endpoint, working_directory, u":memory:", crypto) + def __init__(self, endpoint, working_directory, silent=False): + super(TrackerDispersy, self).__init__(endpoint, working_directory, u":memory:") # location of persistent storage self._persistent_storage_filename = os.path.join(working_directory, "persistent-storage.data") @@ -201,12 +200,6 @@ def makeService(self, options): tracker_service = TrackerMultiService(options["logfile"], options["statedir"]) tracker_service.setName("Dispersy Tracker") - # crypto - if options["crypto"] == 'NoCrypto': - crypto = NoCrypto() - else: - crypto = NoVerifyCrypto() - container = [None] manhole_namespace = {} if options["manhole"]: @@ -225,8 +218,7 @@ def run(): dispersy = TrackerDispersy(StandaloneEndpoint(options["port"], options["ip"]), unicode(options["statedir"]), - bool(options["silent"]), - crypto) + bool(options["silent"])) container[0] = dispersy manhole_namespace['dispersy'] = dispersy