Skip to content

Commit bbcbeca

Browse files
author
lukpueh
authored
Merge pull request #200 from joshuagl/joshuagl/issue179
Improve handling of native dependencies
2 parents ef292ab + 7162761 commit bbcbeca

16 files changed

+493
-242
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ matrix:
66
include:
77
- python: "2.7"
88
env: TOXENV=py27
9+
- python: "2.7"
10+
env: TOXENV=purepy27
911
- python: "3.5"
1012
env: TOXENV=py35
1113
- python: "3.6"
@@ -14,6 +16,8 @@ matrix:
1416
env: TOXENV=py37
1517
- python: "3.8"
1618
env: TOXENV=py38
19+
- python: "3.8"
20+
env: TOXENV=purepy38
1721

1822
install:
1923
- pip install -U tox coveralls

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## securesystemslib vX.YY.Z
4+
5+
* *behaviour change*
6+
* Default to using pure Python to verify ed25519 signatures when nacl is
7+
unavailable
8+
39
## securesystemslib v0.13.1
410

511
* Fix MANIFEST.in to include all test data in source release (#196)

purepy-requirements.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
six
2+
python-dateutil
3+
subprocess32; python_version < '3'
4+
mock; python_version < '3.3'
5+
# Pin to versions supported by `coveralls` (see .travis.yml)
6+
# https://github.com/coveralls-clients/coveralls-python/releases/tag/1.8.1
7+
coverage<5.0

securesystemslib/ecdsa_keys.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,22 @@
4343
import logging
4444

4545
# Import cryptography modules to support ecdsa keys and signatures.
46-
from cryptography.hazmat.backends import default_backend
47-
from cryptography.hazmat.primitives import hashes
48-
from cryptography.hazmat.primitives.asymmetric import ec
46+
CRYPTO = True
47+
NO_CRYPTO_MSG = "ECDSA key support requires the cryptography library"
48+
try:
49+
from cryptography.hazmat.backends import default_backend
50+
from cryptography.hazmat.primitives import hashes
51+
from cryptography.hazmat.primitives.asymmetric import ec
4952

50-
from cryptography.hazmat.primitives import serialization
51-
from cryptography.hazmat.backends.interfaces import PEMSerializationBackend
53+
from cryptography.hazmat.primitives import serialization
54+
from cryptography.hazmat.backends.interfaces import PEMSerializationBackend
5255

53-
from cryptography.hazmat.primitives.serialization import load_pem_public_key
54-
from cryptography.hazmat.primitives.serialization import load_pem_private_key
56+
from cryptography.hazmat.primitives.serialization import load_pem_public_key
57+
from cryptography.hazmat.primitives.serialization import load_pem_private_key
5558

56-
import cryptography.exceptions
59+
import cryptography.exceptions
60+
except ImportError:
61+
CRYPTO = False
5762

5863
# Perform object format-checking and add ability to handle/raise exceptions.
5964
import securesystemslib.formats
@@ -112,6 +117,9 @@ def generate_public_and_private(scheme='ecdsa-sha2-nistp256'):
112117
securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is an
113118
unsupported algorithm.
114119
120+
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
121+
module is not available.
122+
115123
<Side Effects>
116124
None.
117125
@@ -121,6 +129,9 @@ def generate_public_and_private(scheme='ecdsa-sha2-nistp256'):
121129
'securesystemslib.formats.PEMECDSA_SCHEMA', respectively.
122130
"""
123131

132+
if not CRYPTO: # pragma: no cover
133+
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)
134+
124135
# Does 'scheme' have the correct format?
125136
# Verify that 'scheme' is of the correct type, and that it's one of the
126137
# supported ECDSA . It must conform to
@@ -195,6 +206,9 @@ def create_signature(public_key, private_key, data, scheme='ecdsa-sha2-nistp256'
195206
securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is not
196207
one of the supported signature schemes.
197208
209+
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
210+
module is not available.
211+
198212
<Side Effects>
199213
None.
200214
@@ -204,6 +218,9 @@ def create_signature(public_key, private_key, data, scheme='ecdsa-sha2-nistp256'
204218
however, the hexlified signature is stored in the dictionary returned.
205219
"""
206220

221+
if not CRYPTO: # pragma: no cover
222+
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)
223+
207224
# Do 'public_key' and 'private_key' have the correct format?
208225
# This check will ensure that the arguments conform to
209226
# 'securesystemslib.formats.PEMECDSA_SCHEMA'. Raise
@@ -281,6 +298,9 @@ def verify_signature(public_key, scheme, signature, data):
281298
securesystemslib.exceptions.UnsupportedAlgorithmError, if 'scheme' is
282299
not one of the supported signature schemes.
283300
301+
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
302+
module is not available.
303+
284304
<Side Effects>
285305
None.
286306
@@ -289,6 +309,9 @@ def verify_signature(public_key, scheme, signature, data):
289309
the private key associated with 'public_key'.
290310
"""
291311

312+
if not CRYPTO: # pragma: no cover
313+
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)
314+
292315
# Are the arguments properly formatted?
293316
# If not, raise 'securesystemslib.exceptions.FormatError'.
294317
securesystemslib.formats.PEMECDSA_SCHEMA.check_match(public_key)
@@ -358,6 +381,9 @@ def create_ecdsa_public_and_private_from_pem(pem, password=None):
358381
securesystemslib.exceptions.UnsupportedAlgorithmError, if the ECDSA key
359382
pair could not be extracted, possibly due to an unsupported algorithm.
360383
384+
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
385+
module is not available.
386+
361387
<Side Effects>
362388
None.
363389
@@ -366,6 +392,9 @@ def create_ecdsa_public_and_private_from_pem(pem, password=None):
366392
Conforms to 'securesystemslib.formats.ECDSAKEY_SCHEMA'.
367393
"""
368394

395+
if not CRYPTO: # pragma: no cover
396+
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)
397+
369398
# Does 'pem' have the correct format?
370399
# This check will ensure 'pem' conforms to
371400
# 'securesystemslib.formats.ECDSARSA_SCHEMA'.
@@ -439,6 +468,9 @@ def create_ecdsa_encrypted_pem(private_pem, passphrase):
439468
securesystemslib.exceptions.CryptoError, if an ECDSA key in encrypted PEM
440469
format cannot be created.
441470
471+
securesystemslib.exceptions.UnsupportedLibraryError, if the cryptography
472+
module is not available.
473+
442474
<Side Effects>
443475
None.
444476
@@ -447,6 +479,9 @@ def create_ecdsa_encrypted_pem(private_pem, passphrase):
447479
Conforms to 'securesystemslib.formats.PEMECDSA_SCHEMA'.
448480
"""
449481

482+
if not CRYPTO: # pragma: no cover
483+
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_CRYPTO_MSG)
484+
450485
# Does 'private_key' have the correct format?
451486
# Raise 'securesystemslib.exceptions.FormatError' if the check fails.
452487
securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_pem)

securesystemslib/ed25519_keys.py

Lines changed: 25 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,13 @@
8181
# Import the PyNaCl library, if available. It is recommended this library be
8282
# used over the pure python implementation of Ed25519, due to its speedier
8383
# routines and side-channel protections available in the libsodium library.
84-
#
85-
# TODO: Version 0.2.3 of 'pynacl' prints: "UserWarning: reimporting '...' might
86-
# overwrite older definitions." when importing 'nacl.signing'. Suppress user
87-
# warnings temporarily (at least until this issue is fixed by PyNaCl).
88-
#
89-
# Note: A 'pragma: no cover' comment is intended for test 'coverage'. Lines
90-
# or code blocks with this comment should not be flagged as uncovered.
91-
# pynacl will always be install prior to running the unit tests.
92-
with warnings.catch_warnings():
93-
warnings.simplefilter('ignore')
94-
try:
95-
import nacl.signing
96-
import nacl.encoding
97-
98-
# PyNaCl's 'cffi' dependency may raise an 'IOError' exception when importing
99-
# 'nacl.signing'.
100-
except (ImportError, IOError): # pragma: no cover
101-
pass
84+
NACL = True
85+
NO_NACL_MSG = "ed25519 key support requires the nacl library"
86+
try:
87+
import nacl.signing
88+
import nacl.encoding
89+
except ImportError:
90+
NACL = False
10291

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

147+
if not NACL: # pragma: no cover
148+
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_NACL_MSG)
149+
158150
# Generate ed25519's seed key by calling os.urandom(). The random bytes
159151
# returned should be suitable for cryptographic use and is OS-specific.
160152
# Raise 'NotImplementedError' if a randomness source is not found.
@@ -165,21 +157,13 @@ def generate_public_and_private():
165157

166158
# Generate the public key. PyNaCl (i.e., 'nacl' module) performs the actual
167159
# key generation.
168-
try:
169-
nacl_key = nacl.signing.SigningKey(seed)
170-
public = nacl_key.verify_key.encode(encoder=nacl.encoding.RawEncoder())
171-
172-
except NameError: # pragma: no cover
173-
raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl'
174-
' library and/or its dependencies unavailable.')
175-
160+
nacl_key = nacl.signing.SigningKey(seed)
161+
public = nacl_key.verify_key.encode(encoder=nacl.encoding.RawEncoder())
176162

177163
return public, seed
178164

179165

180166

181-
182-
183167
def create_signature(public_key, private_key, data, scheme):
184168
"""
185169
<Purpose>
@@ -227,6 +211,9 @@ def create_signature(public_key, private_key, data, scheme):
227211
228212
securesystemslib.exceptions.CryptoError, if a signature cannot be created.
229213
214+
securesystemslib.exceptions.UnsupportedLibraryError, if the PyNaCl ('nacl')
215+
module is unavailable.
216+
230217
<Side Effects>
231218
nacl.signing.SigningKey.sign() called to generate the actual signature.
232219
@@ -237,6 +224,9 @@ def create_signature(public_key, private_key, data, scheme):
237224
returned.
238225
"""
239226

227+
if not NACL: # pragma: no cover
228+
raise securesystemslib.exceptions.UnsupportedLibraryError(NO_NACL_MSG)
229+
240230
# Does 'public_key' have the correct format?
241231
# This check will ensure 'public_key' conforms to
242232
# 'securesystemslib.formats.ED25519PUBLIC_SCHEMA', which must have length 32
@@ -265,11 +255,6 @@ def create_signature(public_key, private_key, data, scheme):
265255
nacl_sig = nacl_key.sign(data)
266256
signature = nacl_sig.signature
267257

268-
# The unit tests expect required libraries to be installed.
269-
except NameError: # pragma: no cover
270-
raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl'
271-
' library and/or its dependencies unavailable.')
272-
273258
except (ValueError, TypeError, nacl.exceptions.CryptoError) as e:
274259
raise securesystemslib.exceptions.CryptoError('An "ed25519" signature'
275260
' could not be created with PyNaCl.' + str(e))
@@ -286,7 +271,7 @@ def create_signature(public_key, private_key, data, scheme):
286271

287272

288273

289-
def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
274+
def verify_signature(public_key, scheme, signature, data):
290275
"""
291276
<Purpose>
292277
Determine whether the private key corresponding to 'public_key' produced
@@ -298,14 +283,12 @@ def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
298283
>>> scheme = 'ed25519'
299284
>>> signature, scheme = \
300285
create_signature(public, private, data, scheme)
301-
>>> verify_signature(public, scheme, signature, data, use_pynacl=False)
302-
True
303-
>>> verify_signature(public, scheme, signature, data, use_pynacl=True)
286+
>>> verify_signature(public, scheme, signature, data)
304287
True
305288
>>> bad_data = b'The sly brown fox jumps over the lazy dog'
306289
>>> bad_signature, scheme = \
307290
create_signature(public, private, bad_data, scheme)
308-
>>> verify_signature(public, scheme, bad_signature, data, use_pynacl=False)
291+
>>> verify_signature(public, scheme, bad_signature, data)
309292
False
310293
311294
<Arguments>
@@ -323,11 +306,6 @@ def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
323306
Data object used by securesystemslib.ed25519_keys.create_signature() to
324307
generate 'signature'. 'data' is needed here to verify the signature.
325308
326-
use_pynacl:
327-
True, if the ed25519 signature should be verified by PyNaCl. False,
328-
if the signature should be verified with the pure Python implementation
329-
of ed25519 (slower).
330-
331309
<Exceptions>
332310
securesystemslib.exceptions.UnsupportedAlgorithmError. Raised if the
333311
signature scheme 'scheme' is not one supported by
@@ -337,9 +315,9 @@ def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
337315
improperly formatted.
338316
339317
<Side Effects>
318+
nacl.signing.VerifyKey.verify() called if available, otherwise
340319
securesystemslib._vendor.ed25519.ed25519.checkvalid() called to do the
341-
actual verification. nacl.signing.VerifyKey.verify() called if
342-
'use_pynacl' is True.
320+
verification.
343321
344322
<Returns>
345323
Boolean. True if the signature is valid, False otherwise.
@@ -357,28 +335,18 @@ def verify_signature(public_key, scheme, signature, data, use_pynacl=False):
357335
# Is 'signature' properly formatted?
358336
securesystemslib.formats.ED25519SIGNATURE_SCHEMA.check_match(signature)
359337

360-
# Is 'use_pynacl' properly formatted?
361-
securesystemslib.formats.BOOLEAN_SCHEMA.check_match(use_pynacl)
362-
363338
# Verify 'signature'. Before returning the Boolean result, ensure 'ed25519'
364-
# was used as the signature scheme. Raise
365-
# 'securesystemslib.exceptions.UnsupportedLibraryError' if 'use_pynacl' is
366-
# True but 'nacl' is unavailable.
339+
# was used as the signature scheme.
367340
public = public_key
368341
valid_signature = False
369342

370343
if scheme in _SUPPORTED_ED25519_SIGNING_SCHEMES:
371-
if use_pynacl:
344+
if NACL:
372345
try:
373346
nacl_verify_key = nacl.signing.VerifyKey(public)
374347
nacl_message = nacl_verify_key.verify(data, signature)
375348
valid_signature = True
376349

377-
# The unit tests expect PyNaCl to be installed.
378-
except NameError: # pragma: no cover
379-
raise securesystemslib.exceptions.UnsupportedLibraryError('The PyNaCl'
380-
' library and/or its dependencies unavailable.')
381-
382350
except nacl.exceptions.BadSignatureError:
383351
pass
384352

securesystemslib/hash.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class PycaDiggestWrapper(object):
8080
<Properties>
8181
algorithm:
8282
Specific for `cryptography.hazmat.primitives.hashes.Hash` object, but
83-
needed for `pyca_crypto_keys.py`
83+
needed for `rsa_keys.py`
8484
8585
digest_size:
8686
Returns original's object digest size.

0 commit comments

Comments
 (0)