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.
This commit is contained in:
vladdd 2014-03-07 23:21:54 -05:00
parent 406f5b0187
commit 2d015797ef
3 changed files with 96 additions and 120 deletions

View file

@ -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

View file

@ -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():
"""
<Purpose>
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
<Arguments>
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.
<Exceptions>
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'.
<Side Effects>
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().
<Returns>
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):
"""
<Purpose>
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).
<Exceptions>
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.
<Side Effects>
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.
<Returns>
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.

View file

@ -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.')