Skip to content

Commit

Permalink
Merge pull request #200 from joshuagl/joshuagl/issue179
Browse files Browse the repository at this point in the history
Improve handling of native dependencies
  • Loading branch information
lukpueh authored Jan 23, 2020
2 parents ef292ab + 7162761 commit bbcbeca
Show file tree
Hide file tree
Showing 16 changed files with 493 additions and 242 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ matrix:
include:
- python: "2.7"
env: TOXENV=py27
- python: "2.7"
env: TOXENV=purepy27
- python: "3.5"
env: TOXENV=py35
- python: "3.6"
Expand All @@ -14,6 +16,8 @@ matrix:
env: TOXENV=py37
- python: "3.8"
env: TOXENV=py38
- python: "3.8"
env: TOXENV=purepy38

install:
- pip install -U tox coveralls
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## securesystemslib vX.YY.Z

* *behaviour change*
* Default to using pure Python to verify ed25519 signatures when nacl is
unavailable

## securesystemslib v0.13.1

* Fix MANIFEST.in to include all test data in source release (#196)
Expand Down
7 changes: 7 additions & 0 deletions purepy-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
six
python-dateutil
subprocess32; python_version < '3'
mock; python_version < '3.3'
# Pin to versions supported by `coveralls` (see .travis.yml)
# https://github.com/coveralls-clients/coveralls-python/releases/tag/1.8.1
coverage<5.0
51 changes: 43 additions & 8 deletions securesystemslib/ecdsa_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,22 @@
import logging

# Import cryptography modules to support ecdsa keys and signatures.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
CRYPTO = True
NO_CRYPTO_MSG = "ECDSA key support requires the cryptography library"
try:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends.interfaces import PEMSerializationBackend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends.interfaces import PEMSerializationBackend

from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.hazmat.primitives.serialization import load_pem_private_key

import cryptography.exceptions
import cryptography.exceptions
except ImportError:
CRYPTO = False

# Perform object format-checking and add ability to handle/raise exceptions.
import securesystemslib.formats
Expand Down Expand Up @@ -112,6 +117,9 @@ def generate_public_and_private(scheme='ecdsa-sha2-nistp256'):
securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is an
unsupported algorithm.
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
module is not available.
<Side Effects>
None.
Expand All @@ -121,6 +129,9 @@ def generate_public_and_private(scheme='ecdsa-sha2-nistp256'):
'securesystemslib.formats.PEMECDSA_SCHEMA', respectively.
"""

if not CRYPTO: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

# Does 'scheme' have the correct format?
# Verify that 'scheme' is of the correct type, and that it's one of the
# supported ECDSA . It must conform to
Expand Down Expand Up @@ -195,6 +206,9 @@ def create_signature(public_key, private_key, data, scheme='ecdsa-sha2-nistp256'
securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is not
one of the supported signature schemes.
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
module is not available.
<Side Effects>
None.
Expand All @@ -204,6 +218,9 @@ def create_signature(public_key, private_key, data, scheme='ecdsa-sha2-nistp256'
however, the hexlified signature is stored in the dictionary returned.
"""

if not CRYPTO: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

# Do 'public_key' and 'private_key' have the correct format?
# This check will ensure that the arguments conform to
# 'securesystemslib.formats.PEMECDSA_SCHEMA'. Raise
Expand Down Expand Up @@ -281,6 +298,9 @@ def verify_signature(public_key, scheme, signature, data):
securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is
not one of the supported signature schemes.
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
module is not available.
<Side Effects>
None.
Expand All @@ -289,6 +309,9 @@ def verify_signature(public_key, scheme, signature, data):
the private key associated with 'public_key'.
"""

if not CRYPTO: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

# Are the arguments properly formatted?
# If not, raise 'securesystemslib.exceptions.FormatError'.
securesystemslib.formats.PEMECDSA_SCHEMA.check_match(public_key)
Expand Down Expand Up @@ -358,6 +381,9 @@ def create_ecdsa_public_and_private_from_pem(pem, password=None):
securesystemslib.exceptions.UnsupportedAlgorithmError, if the ECDSA key
pair could not be extracted, possibly due to an unsupported algorithm.
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
module is not available.
<Side Effects>
None.
Expand All @@ -366,6 +392,9 @@ def create_ecdsa_public_and_private_from_pem(pem, password=None):
Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'.
"""

if not CRYPTO: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

# Does 'pem' have the correct format?
# This check will ensure 'pem' conforms to
# 'securesystemslib.formats.ECDSARSA_SCHEMA'.
Expand Down Expand Up @@ -439,6 +468,9 @@ def create_ecdsa_encrypted_pem(private_pem, passphrase):
securesystemslib.exceptions.CryptoError, if an ECDSA key in encrypted PEM
format cannot be created.
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
module is not available.
<Side Effects>
None.
Expand All @@ -447,6 +479,9 @@ def create_ecdsa_encrypted_pem(private_pem, passphrase):
Conforms to 'securesystemslib.formats.PEMECDSA_SCHEMA'.
"""

if not CRYPTO: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)

# Does 'private_key' have the correct format?
# Raise 'securesystemslib.exceptions.FormatError' if the check fails.
securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_pem)
Expand Down
82 changes: 25 additions & 57 deletions securesystemslib/ed25519_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,13 @@
# 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.
#
# TODO: Version 0.2.3 of 'pynacl' prints: "UserWarning: reimporting '...' might
# overwrite older definitions." when importing 'nacl.signing'. Suppress user
# warnings temporarily (at least until this issue is fixed by PyNaCl).
#
# Note: A 'pragma: no cover' comment is intended for test 'coverage'. Lines
# or code blocks with this comment should not be flagged as uncovered.
# pynacl will always be install prior to running the unit tests.
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): # pragma: no cover
pass
NACL = True
NO_NACL_MSG = "ed25519 key support requires the nacl library"
try:
import nacl.signing
import nacl.encoding
except ImportError:
NACL = False

# The optimized pure Python implementation of Ed25519. If
# PyNaCl cannot be imported and an attempt to use is made in this module, a
Expand Down Expand Up @@ -155,6 +144,9 @@ def generate_public_and_private():
'securesystemslib.formats.ED25519SEED_SCHEMA', respectively.
"""

if not NACL: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_NACL_MSG)

# Generate ed25519's seed key by calling os.urandom(). The random bytes
# returned should be suitable for cryptographic use and is OS-specific.
# Raise 'NotImplementedError' if a randomness source is not found.
Expand All @@ -165,21 +157,13 @@ def generate_public_and_private():

# Generate the public key. PyNaCl (i.e., 'nacl' module) performs the actual
# key generation.
try:
nacl_key = nacl.signing.SigningKey(seed)
public = nacl_key.verify_key.encode(encoder=nacl.encoding.RawEncoder())

except NameError: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl'
' library and/or its dependencies unavailable.')

nacl_key = nacl.signing.SigningKey(seed)
public = nacl_key.verify_key.encode(encoder=nacl.encoding.RawEncoder())

return public, seed





def create_signature(public_key, private_key, data, scheme):
"""
<Purpose>
Expand Down Expand Up @@ -227,6 +211,9 @@ def create_signature(public_key, private_key, data, scheme):
securesystemslib.exceptions.CryptoError, if a signature cannot be created.
securesystemslib.exceptions.UnsupportedLibraryError, if the PyNaCl ('nacl')
module is unavailable.
<Side Effects>
nacl.signing.SigningKey.sign() called to generate the actual signature.
Expand All @@ -237,6 +224,9 @@ def create_signature(public_key, private_key, data, scheme):
returned.
"""

if not NACL: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_NACL_MSG)

# Does 'public_key' have the correct format?
# This check will ensure 'public_key' conforms to
# 'securesystemslib.formats.ED25519PUBLIC_SCHEMA', which must have length 32
Expand Down Expand Up @@ -265,11 +255,6 @@ def create_signature(public_key, private_key, data, scheme):
nacl_sig = nacl_key.sign(data)
signature = nacl_sig.signature

# The unit tests expect required libraries to be installed.
except NameError: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl'
' library and/or its dependencies unavailable.')

except (ValueError, TypeError, nacl.exceptions.CryptoError) as e:
raise securesystemslib.exceptions.CryptoError('An "ed25519" signature'
' could not be created with PyNaCl.' + str(e))
Expand All @@ -286,7 +271,7 @@ def create_signature(public_key, private_key, data, scheme):



def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
def verify_signature(public_key, scheme, signature, data):
"""
<Purpose>
Determine whether the private key corresponding to 'public_key' produced
Expand All @@ -298,14 +283,12 @@ def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
>>> scheme = 'ed25519'
>>> signature, scheme = \
create_signature(public, private, data, scheme)
>>> verify_signature(public, scheme, signature, data, use_pynacl=False)
True
>>> verify_signature(public, scheme, signature, data, use_pynacl=True)
>>> verify_signature(public, scheme, signature, data)
True
>>> bad_data = b'The sly brown fox jumps over the lazy dog'
>>> bad_signature, scheme = \
create_signature(public, private, bad_data, scheme)
>>> verify_signature(public, scheme, bad_signature, data, use_pynacl=False)
>>> verify_signature(public, scheme, bad_signature, data)
False
<Arguments>
Expand All @@ -323,11 +306,6 @@ def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
Data object used by securesystemslib.ed25519_keys.create_signature() to
generate 'signature'. 'data' is needed here to verify the signature.
use_pynacl:
True, if the ed25519 signature should be verified by PyNaCl. False,
if the signature should be verified with the pure Python implementation
of ed25519 (slower).
<Exceptions>
securesystemslib.exceptions.UnsupportedAlgorithmError. Raised if the
signature scheme 'scheme' is not one supported by
Expand All @@ -337,9 +315,9 @@ def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
improperly formatted.
<Side Effects>
nacl.signing.VerifyKey.verify() called if available, otherwise
securesystemslib._vendor.ed25519.ed25519.checkvalid() called to do the
actual verification. nacl.signing.VerifyKey.verify() called if
'use_pynacl' is True.
verification.
<Returns>
Boolean. True if the signature is valid, False otherwise.
Expand All @@ -357,28 +335,18 @@ def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
# Is 'signature' properly formatted?
securesystemslib.formats.ED25519SIGNATURE_SCHEMA.check_match(signature)

# Is 'use_pynacl' properly formatted?
securesystemslib.formats.BOOLEAN_SCHEMA.check_match(use_pynacl)

# Verify 'signature'. Before returning the Boolean result, ensure 'ed25519'
# was used as the signature scheme. Raise
# 'securesystemslib.exceptions.UnsupportedLibraryError' if 'use_pynacl' is
# True but 'nacl' is unavailable.
# was used as the signature scheme.
public = public_key
valid_signature = False

if scheme in _SUPPORTED_ED25519_SIGNING_SCHEMES:
if use_pynacl:
if NACL:
try:
nacl_verify_key = nacl.signing.VerifyKey(public)
nacl_message = nacl_verify_key.verify(data, signature)
valid_signature = True

# The unit tests expect PyNaCl to be installed.
except NameError: # pragma: no cover
raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl'
' library and/or its dependencies unavailable.')

except nacl.exceptions.BadSignatureError:
pass

Expand Down
2 changes: 1 addition & 1 deletion securesystemslib/hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class PycaDiggestWrapper(object):
<Properties>
algorithm:
Specific for `cryptography.hazmat.primitives.hashes.Hash` object, but
needed for `pyca_crypto_keys.py`
needed for `rsa_keys.py`
digest_size:
Returns original's object digest size.
Expand Down
Loading

0 comments on commit bbcbeca

Please sign in to comment.