From 2d015797ef1149dcb8fce7d86b93e307eec5874e Mon Sep 17 00:00:00 2001 From: vladdd Date: Fri, 7 Mar 2014 23:21:54 -0500 Subject: [PATCH] Update affected ed25519 modules. Update modules affected by the changes made to the latest versions of pyca-ed25519 and pyca-pynacl: Do not use the unsafe key and signature generation functions of pure python ed25519, but do support the signature verification routine. Developers must use the faster and secure pynacl+libsodium to generate ed25519 keys and signatures. Temporarily suppress pynacl's import warning error. Minor edits to comments and code. --- tests/unit/test_ed25519_keys.py | 8 +- tuf/ed25519_keys.py | 162 +++++++++++++------------------- tuf/keys.py | 46 +++++---- 3 files changed, 96 insertions(+), 120 deletions(-) diff --git a/tests/unit/test_ed25519_keys.py b/tests/unit/test_ed25519_keys.py index a65a85dc..9a922b88 100755 --- a/tests/unit/test_ed25519_keys.py +++ b/tests/unit/test_ed25519_keys.py @@ -41,13 +41,7 @@ def test_generate_public_and_private(self): self.assertEqual(True, tuf.formats.ED25519PUBLIC_SCHEMA.matches(pub)) self.assertEqual(True, tuf.formats.ED25519SEED_SCHEMA.matches(priv)) - # Check for invalid argument. - self.assertRaises(tuf.FormatError, - ed25519.generate_public_and_private, 'True') - - self.assertRaises(tuf.FormatError, - ed25519.generate_public_and_private, 2048) - + def test_create_signature(self): global public diff --git a/tuf/ed25519_keys.py b/tuf/ed25519_keys.py index 5a8303ec..0c7f3398 100755 --- a/tuf/ed25519_keys.py +++ b/tuf/ed25519_keys.py @@ -34,11 +34,12 @@ https://github.com/pyca/pynacl https://github.com/jedisct1/libsodium http://nacl.cr.yp.to/ + https://github.com/pyca/ed25519 The ed25519-related functions included here are generate(), create_signature() and verify_signature(). The 'ed25519' and PyNaCl (i.e., 'nacl') modules used - by ed25519_keys.py generate the actual ed25519 keys and the functions listed - above can be viewed as an easy-to-use public interface. + by ed25519_keys.py perform the actual ed25519 computations and the functions + listed above can be viewed as an easy-to-use public interface. """ # Help with Python 3 compatibility, where the print statement is a function, an @@ -52,6 +53,11 @@ # public/private keys are hexlified. import binascii +# NOTE: 'warnings' needed to temporarily suppress user warnings raised by +# 'pynacl' (as of version 0.2.3). +# http://docs.python.org/2/library/warnings.html#temporarily-suppressing-warnings +import warnings + # 'os' required to generate OS-specific randomness (os.urandom) suitable for # cryptographic use. # http://docs.python.org/2/library/os.html#miscellaneous-functions @@ -67,13 +73,23 @@ # https://github.com/pyca/ed25519 # https://github.com/pyca/pynacl # -# PyNaCl's 'cffi' dependency may thrown an 'IOError' exception when -# importing 'nacl.signing'. -try: - import nacl.signing - import nacl.encoding -except (ImportError, IOError): - pass +# Import the PyNaCl library, if available. It is recommended this library be +# used over the pure python implementation of ed25519, due to its speedier +# routines and side-channel protections available in the libsodium library. +# +# NOTE: Version 0.2.3 of 'pynacl' prints: "UserWarning: reimporting '...' might +# overwrite older definitions." when importing 'nacl.signing' below. Suppress +# user warnings temporarily (at least until this issue is fixed). +with warnings.catch_warnings(): + warnings.simplefilter('ignore') + try: + import nacl.signing + import nacl.encoding + + # PyNaCl's 'cffi' dependency may raise an 'IOError' exception when importing + # 'nacl.signing'. + except (ImportError, IOError): + pass # The optimized pure Python implementation of ed25519 provided by TUF. If # PyNaCl cannot be imported and an attempt to use is made in this module, a @@ -90,13 +106,13 @@ # Supported ed25519 signing method: 'ed25519'. The pure Python # implementation (i.e., 'tuf._vendor.ed25519.ed25519') and PyNaCl -# (i.e., 'nacl', libsodium+Python bindgs) modules are currently supported in +# (i.e., 'nacl', libsodium+Python bindings) modules are currently supported in # the creationg of 'ed25519' signatures. Previously, a distinction was made # between signatures made by the pure Python implementation and PyNaCl. -_SUPPORTED_ED25519_SIGNING_METHODS = ['ed25519',] +_SUPPORTED_ED25519_SIGNING_METHODS = ['ed25519'] -def generate_public_and_private(use_pynacl=False): +def generate_public_and_private(): """ Generate a pair of ed25519 public and private keys. @@ -109,45 +125,28 @@ def generate_public_and_private(use_pynacl=False): An ed25519 seed key is a random 32-byte string. Public keys are also 32 bytes. - >>> public, private = generate_public_and_private(use_pynacl=False) - >>> tuf.formats.ED25519PUBLIC_SCHEMA.matches(public) - True - >>> tuf.formats.ED25519SEED_SCHEMA.matches(private) - True - >>> public, private = generate_public_and_private(use_pynacl=True) + >>> public, private = generate_public_and_private() >>> tuf.formats.ED25519PUBLIC_SCHEMA.matches(public) True >>> tuf.formats.ED25519SEED_SCHEMA.matches(private) True - use_pynacl: - True, if the ed25519 keys should be generated with PyNaCl. False, if the - keys should be generated with the pure Python implementation of ed25519 - (slower). + None. - tuf.FormatError, if 'use_pynacl' is not a Boolean. - - tuf.UnsupportedLibraryError, if the PyNaCl ('nacl') module is unavailable - and 'use_pynacl' is True. + tuf.UnsupportedLibraryError, if the PyNaCl ('nacl') module is unavailable. NotImplementedError, if a randomness source is not found by 'os.urandom'. The ed25519 keys are generated by first creating a random 32-byte seed - with os.urandom() and then calling ed25519's - ed25519.25519.publickey(seed) or PyNaCl's nacl.signing.SigningKey(). + with os.urandom() and then calling PyNaCl's nacl.signing.SigningKey(). A (public, private) tuple that conform to 'tuf.formats.ED25519PUBLIC_SCHEMA' and 'tuf.formats.ED25519SEED_SCHEMA', respectively. """ - - # Does 'use_pynacl' have the correct format? - # This check will ensure 'use_pynacl' conforms to 'tuf.formats.BOOLEAN_SCHEMA'. - # Raise 'tuf.FormatError' if the check fails. - tuf.formats.BOOLEAN_SCHEMA.check_match(use_pynacl) # Generate ed25519's seed key by calling os.urandom(). The random bytes # returned should be suitable for cryptographic use and is OS-specific. @@ -157,19 +156,14 @@ def generate_public_and_private(use_pynacl=False): seed = os.urandom(32) public = None - if use_pynacl: - # Generate the public key. PyNaCl (i.e., 'nacl' module) performs - # the actual key generation. - try: - nacl_key = nacl.signing.SigningKey(seed) - public = str(nacl_key.verify_key) - except NameError: - message = 'The PyNaCl library and/or its dependencies unavailable.' - raise tuf.UnsupportedLibraryError(message) - - # Use the pure Python implementation of ed25519. - else: - public = tuf._vendor.ed25519.ed25519.publickey(seed) + # Generate the public key. PyNaCl (i.e., 'nacl' module) performs + # the actual key generation. + try: + nacl_key = nacl.signing.SigningKey(seed) + public = str(nacl_key.verify_key) + except NameError: + message = 'The PyNaCl library and/or its dependencies unavailable.' + raise tuf.UnsupportedLibraryError(message) return public, seed @@ -177,28 +171,27 @@ def generate_public_and_private(use_pynacl=False): -def create_signature(public_key, private_key, data, use_pynacl=False): +def create_signature(public_key, private_key, data): """ Return a (signature, method) tuple, where the method is 'ed25519' and - generated by either the pure python implemenation, or by PyNaCl - (i.e., 'nacl'). The signature returns conforms to + generated by PyNaCl (i.e., 'nacl'). The signature returns conforms to 'tuf.formats.ED25519SIGNATURE_SCHEMA', and has the form: '\xae\xd7\x9f\xaf\x95{bP\x9e\xa8YO Z\x86\x9d...' A signature is a 64-byte string. - >>> public, private = generate_public_and_private(use_pynacl=False) + >>> public, private = generate_public_and_private() >>> data = 'The quick brown fox jumps over the lazy dog' >>> signature, method = \ - create_signature(public, private, data, use_pynacl=False) + create_signature(public, private, data) >>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature) True >>> method == 'ed25519' True >>> signature, method = \ - create_signature(public, private, data, use_pynacl=True) + create_signature(public, private, data) >>> tuf.formats.ED25519SIGNATURE_SCHEMA.matches(signature) True >>> method == 'ed25519' @@ -213,11 +206,6 @@ def create_signature(public_key, private_key, data, use_pynacl=False): data: Data object used by create_signature() to generate the signature. - - use_pynacl: - True, if the ed25519 signature should be generated with PyNaCl. False, - if the signature should be generated with the pure Python implementation - of ed25519 (much slower). tuf.FormatError, if the arguments are improperly formatted. @@ -225,8 +213,7 @@ def create_signature(public_key, private_key, data, use_pynacl=False): tuf.CryptoError, if a signature cannot be created. - tuf._vendor.ed25519.ed25519.signature() or nacl.signing.SigningKey.sign() - called to generate the actual signature. + nacl.signing.SigningKey.sign() called to generate the actual signature. A signature dictionary conformat to 'tuf.format.SIGNATURE_SCHEMA'. @@ -243,13 +230,8 @@ def create_signature(public_key, private_key, data, use_pynacl=False): # Is 'private_key' properly formatted? tuf.formats.ED25519SEED_SCHEMA.check_match(private_key) - # Is 'use_pynacl' properly formatted? - tuf.formats.BOOLEAN_SCHEMA.check_match(use_pynacl) - # Signing the 'data' object requires a seed and public key. - # 'tuf._vendor.ed25519.ed25519.py' generates the actual 64-byte signature in - # pure Python. nacl.signing.SigningKey.sign() generates the signature if - # 'use_pynacl' is True. + # nacl.signing.SigningKey.sign() generates the signature. public = public_key private = private_key @@ -258,34 +240,20 @@ def create_signature(public_key, private_key, data, use_pynacl=False): # The private and public keys have been validated above by 'tuf.formats' and # should be 32-byte strings. - if use_pynacl: - method = 'ed25519' - try: - nacl_key = nacl.signing.SigningKey(private) - nacl_sig = nacl_key.sign(data) - signature = nacl_sig.signature - - except NameError: - message = 'The PyNaCl library and/or its dependencies unavailable.' - raise tuf.UnsupportedLibraryError(message) - - except (ValueError, nacl.signing.CryptoError): - message = 'An "ed25519" signature could not be created with PyNaCl.' - raise tuf.CryptoError(message) - - # Generate an "ed25519" signature with the pure python implementation. - else: - # tuf._vendor.ed25519.ed25519.signature() requires both the seed and - # public keys. It calculates the SHA512 of the seed key, which is 32 bytes. - method = 'ed25519' - try: - signature = tuf._vendor.ed25519.ed25519.signature(data, private, public) - - # 'Exception' raised by ed25519.py for any exception that may occur. - except Exception, e: - message = 'An "ed25519" signature could not be generated in pure Python.' - raise tuf.CryptoError(message) + method = 'ed25519' + try: + nacl_key = nacl.signing.SigningKey(private) + nacl_sig = nacl_key.sign(data) + signature = nacl_sig.signature + except NameError: + message = 'The PyNaCl library and/or its dependencies unavailable.' + raise tuf.UnsupportedLibraryError(message) + + except (ValueError, TypeError, nacl.exceptions.CryptoError): + message = 'An "ed25519" signature could not be created with PyNaCl.' + raise tuf.CryptoError(message) + return signature, method @@ -299,17 +267,17 @@ def verify_signature(public_key, method, signature, data, use_pynacl=False): 'signature'. verify_signature() will use the public key, the 'method' and 'sig', and 'data' arguments to complete the verification. - >>> public, private = generate_public_and_private(use_pynacl=False) + >>> public, private = generate_public_and_private() >>> data = 'The quick brown fox jumps over the lazy dog' >>> signature, method = \ - create_signature(public, private, data, use_pynacl=False) + create_signature(public, private, data) >>> verify_signature(public, method, signature, data, use_pynacl=False) True >>> verify_signature(public, method, signature, data, use_pynacl=True) True >>> bad_data = 'The sly brown fox jumps over the lazy dog' >>> bad_signature, method = \ - create_signature(public, private, bad_data, use_pynacl=False) + create_signature(public, private, bad_data) >>> verify_signature(public, method, bad_signature, data, use_pynacl=False) False @@ -378,10 +346,12 @@ def verify_signature(public_key, method, signature, data, use_pynacl=False): nacl_message = nacl_verify_key.verify(data, signature) if nacl_message == data: valid_signature = True + except NameError: message = 'The PyNaCl library and/or its dependencies unavailable.' raise tuf.UnsupportedLibraryError(message) - except nacl.signing.BadSignatureError: + + except nacl.exceptions.BadSignatureError: pass # Verify 'ed25519' signature with pure Python implementation. diff --git a/tuf/keys.py b/tuf/keys.py index b115221b..6bf646e2 100755 --- a/tuf/keys.py +++ b/tuf/keys.py @@ -48,6 +48,11 @@ # hexlified. import binascii +# NOTE: 'warnings' needed to temporarily suppress user warnings raised by +# 'pynacl' (as of version 0.2.3). +# http://docs.python.org/2/library/warnings.html#temporarily-suppressing-warnings +import warnings + # 'pycrypto' is the only currently supported library for the creation of RSA # keys. # https://github.com/dlitz/pycrypto @@ -81,12 +86,21 @@ # Import the PyNaCl library, if available. It is recommended this library be # used over the pure python implementation of ed25519, due to its speedier # routines and side-channel protections available in the libsodium library. -try: - import nacl - import nacl.signing - _available_crypto_libraries.append('pynacl') -except (ImportError, IOError): - pass + +# NOTE: Version 0.2.3 of 'pynacl' prints: "UserWarning: reimporting '...' might +# overwrite older definitions." when importing 'nacl.signing' below. Suppress +# user warnings temporarily (at least until this issue is fixed). +with warnings.catch_warnings(): + warnings.simplefilter('ignore') + try: + import nacl + import nacl.signing + _available_crypto_libraries.append('pynacl') + + # PyNaCl's 'cffi' dependency may raise an 'IOError' exception when importing + # 'nacl.signing'. + except (ImportError, IOError): + pass # The optimized version of the ed25519 library provided by default is imported # regardless of the availability of PyNaCl. @@ -280,11 +294,11 @@ def generate_ed25519_key(): # provided by pyca and available in TUF. if 'pynacl' in _available_crypto_libraries: public, private = \ - tuf.ed25519_keys.generate_public_and_private(use_pynacl=True) + tuf.ed25519_keys.generate_public_and_private() else: - public, private = \ - tuf.ed25519_keys.generate_public_and_private(use_pynacl=False) - + message = 'The required PyNaCl library is unavailable.' + raise tuf.UnsupportedLibraryError(message) + # Generate the keyid of the ED25519 key. 'key_value' corresponds to the # 'keyval' entry of the 'ED25519KEY_SCHEMA' dictionary. The private key # information is not included in the generation of the 'keyid' identifier. @@ -646,15 +660,13 @@ def create_signature(key_dict, data): elif keytype == 'ed25519': public = binascii.unhexlify(public) private = binascii.unhexlify(private) - if _ED25519_CRYPTO_LIBRARY == 'pynacl' \ - and 'pynacl' in _available_crypto_libraries: - sig, method = tuf.ed25519_keys.create_signature(public, private, - data, use_pynacl=True) + if 'pynacl' in _available_crypto_libraries: + sig, method = tuf.ed25519_keys.create_signature(public, private, data) - # Fall back to using the optimized pure python implementation of ed25519. else: - sig, method = tuf.ed25519_keys.create_signature(public, private, - data, use_pynacl=False) + message = 'The required PyNaCl library is unavailable.' + raise tuf.UnsupportedLibraryError(message) + else: raise TypeError('Invalid key type.')