mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
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:
parent
406f5b0187
commit
2d015797ef
3 changed files with 96 additions and 120 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
46
tuf/keys.py
46
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.')
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue