From 9beace8094b68bd46d73a9495c518587618f5180 Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Mon, 17 Jun 2019 15:23:53 +0200 Subject: [PATCH] Restructure pyspx conditional import Import third-party pyspx package in securesystemslib.spx_keys only and require whoever imports securesystemslib.spx_keys to handle ImportError (or IOError in case of missing C backend). See securesystemslib.keys for exemplary import handling. This commit also moves three spx-specific schema definitions from securesystemslib.formats to securesystemslib.spx_keys, so that the former does not have to deal with a conditional import of an optional library. --- securesystemslib/formats.py | 17 --------- securesystemslib/keys.py | 3 +- securesystemslib/spx_keys.py | 68 ++++++++++++++---------------------- tests/test_spx_keys.py | 6 ++-- 4 files changed, 31 insertions(+), 63 deletions(-) diff --git a/securesystemslib/formats.py b/securesystemslib/formats.py index 5855752c..c613e542 100755 --- a/securesystemslib/formats.py +++ b/securesystemslib/formats.py @@ -79,14 +79,6 @@ import time import six -# Try to import pyspx to get access to lengths of SPX signatures and keys -try: - import pyspx.shake256_192s as pyspx - -# pyspx's 'cffi' dependency may raise an 'IOError' exception when importing -except (ImportError, IOError): # pragma: no cover - pass - import securesystemslib.schema as SCHEMA import securesystemslib.exceptions @@ -300,15 +292,6 @@ # An ED25519 raw signature, which must be 64 bytes. ED25519SIGNATURE_SCHEMA = SCHEMA.LengthBytes(64) -# Lengths of SPX raw keys and signatures -try: - SPXPUBLIC_SCHEMA = SCHEMA.LengthBytes(pyspx.crypto_sign_PUBLICKEYBYTES) - SPXSEED_SCHEMA = SCHEMA.LengthBytes(pyspx.crypto_sign_SECRETKEYBYTES) - SPXSIGNATURE_SCHEMA = SCHEMA.LengthBytes(pyspx.crypto_sign_BYTES) - -except NameError: # pragma: no cover - pass # raised when pyspx was not available on import - # An ECDSA signature. ECDSASIGNATURE_SCHEMA = SCHEMA.AnyBytes() diff --git a/securesystemslib/keys.py b/securesystemslib/keys.py index 2de9d338..9effb5de 100755 --- a/securesystemslib/keys.py +++ b/securesystemslib/keys.py @@ -102,7 +102,8 @@ try: import securesystemslib.spx_keys -except ImportError: #pragma: no cover +# pyspx's 'cffi' dependency may raise an 'IOError' exception when importing +except (ImportError, IOError): #pragma: no cover pass diff --git a/securesystemslib/spx_keys.py b/securesystemslib/spx_keys.py index 81ec5c9c..66c02272 100755 --- a/securesystemslib/spx_keys.py +++ b/securesystemslib/spx_keys.py @@ -43,37 +43,36 @@ # Import the pyspx library, if available. This library is required to use # spx signatures. -# -# Note: A 'pragma: no cover' comment is intended for test 'coverage'. Lines -# or code blocks with this comment should not be flagged as uncovered. -try: - import pyspx.shake256_192s as pyspx - -# pyspx's 'cffi' dependency may raise an 'IOError' exception when importing -except (ImportError, IOError): # pragma: no cover - pass +import pyspx.shake256_192s as pyspx import securesystemslib.formats import securesystemslib.exceptions +import securesystemslib.schema as SCHEMA # Supported spx signing schemes: 'spx'. _SUPPORTED_SPX_SIGNING_SCHEMES = ['spx'] +# Define lengths of SPX keys and signature bytes +# NOTE: Define module scope schemas here to avoid conditional imports of +# optional 'pyspx' package in 'formats' module. ImportError and IOError should +# be handled by whoever imports this 'spx_keys' module. +SPX_PUBLIC_BYTES_SCHEMA = SCHEMA.LengthBytes(pyspx.crypto_sign_PUBLICKEYBYTES) +SPX_PRIVATE_BYTES_SCHEMA = SCHEMA.LengthBytes(pyspx.crypto_sign_SECRETKEYBYTES) +SPX_SIG_BYTES_SCHEMA = SCHEMA.LengthBytes(pyspx.crypto_sign_BYTES) def generate_public_and_private(): """ Generate a pair of spx public and private keys with pyspx. The public - and private keys returned conform to - 'securesystemslib.formats.SPXPUBLIC_SCHEMA' and - 'securesystemslib.formats.SPXSEED_SCHEMA', respectively. + and private keys returned conform to 'SPX_PUBLIC_BYTES_SCHEMA' and + 'SPX_PRIVATE_BYTES_SCHEMA', respectively. An spx seed key is a random 128-byte string. Public keys are 64 bytes. >>> public, private = generate_public_and_private() - >>> securesystemslib.formats.SPXPUBLIC_SCHEMA.matches(public) + >>> SPX_PUBLIC_BYTES_SCHEMA.matches(public) True - >>> securesystemslib.formats.SPXSEED_SCHEMA.matches(private) + >>> SPX_PRIVATE_BYTES_SCHEMA.matches(private) True @@ -91,8 +90,8 @@ def generate_public_and_private(): A (public, private) tuple that conform to - 'securesystemslib.formats.SPXPUBLIC_SCHEMA' and - 'securesystemslib.formats.SPXSEED_SCHEMA', respectively. + 'SPX_PUBLIC_BYTES_SCHEMA' and + 'SPX_PRIVATE_BYTES_SCHEMA', respectively. """ # Generate spx's seed key by calling os.urandom(). The random bytes @@ -120,20 +119,20 @@ def create_signature(public_key, private_key, data, scheme): Return a (signature, scheme) tuple, where the signature scheme is 'spx' and is always generated by pyspx. The signature returned - conforms to 'securesystemslib.formats.SPXSIGNATURE_SCHEMA'. + conforms to 'SPX_SIG_BYTES_SCHEMA'. >>> public, private = generate_public_and_private() >>> data = b'The quick brown fox jumps over the lazy dog' >>> scheme = 'spx' >>> signature, scheme = \ create_signature(public, private, data, scheme) - >>> securesystemslib.formats.SPXSIGNATURE_SCHEMA.matches(signature) + >>> SPX_SIG_BYTES_SCHEMA.matches(signature) True >>> scheme == 'spx' True >>> signature, scheme = \ create_signature(public, private, data, scheme) - >>> securesystemslib.formats.SPXSIGNATURE_SCHEMA.matches(signature) + >>> SPX_SIG_BYTES_SCHEMA.matches(signature) True >>> scheme == 'spx' True @@ -163,17 +162,9 @@ def create_signature(public_key, private_key, data, scheme): A signature dictionary conformat to 'securesystemslib.format.SIGNATURE_SCHEMA'. """ - - # Does 'public_key' have the correct format? - # This check will ensure 'public_key' conforms to - # 'securesystemslib.formats.SPXPUBLIC_SCHEMA', which must have length 32 - # bytes. Raise 'securesystemslib.exceptions.FormatError' if the check fails. - securesystemslib.formats.SPXPUBLIC_SCHEMA.check_match(public_key) - - # Is 'private_key' properly formatted? - securesystemslib.formats.SPXSEED_SCHEMA.check_match(private_key) - - # Is 'scheme' properly formatted? + # Validate arguments + SPX_PUBLIC_BYTES_SCHEMA.check_match(public_key) + SPX_PRIVATE_BYTES_SCHEMA.check_match(private_key) securesystemslib.formats.SPX_SIG_SCHEMA.check_match(scheme) # Signing the 'data' object requires a seed and public key. @@ -233,13 +224,13 @@ def verify_signature(public_key, scheme, signature, data): public_key: - The public key is a simple byte string of length spx.crypto_sign_PUBLICKEYBYTES + The public key is a simple byte string of length SPX_PUBLIC_BYTES_SCHEMA. scheme: 'spx' signature scheme signature: - The signature is a simple byte string of length spx.crypto_sign_BYTES + The signature is a simple byte string of length SPX_SIG_BYTES_SCHEMA. data: Data object used by securesystemslib.spx_keys.create_signature() to @@ -259,18 +250,11 @@ def verify_signature(public_key, scheme, signature, data): Boolean. True if the signature is valid, False otherwise. """ - - # Does 'public_key' have the correct format? - # This check will ensure 'public_key' conforms to - # 'securesystemslib.formats.SPXPUBLIC_SCHEMA', bytes. - # Raise 'securesystemslib.exceptions.FormatError' if the check fails. - securesystemslib.formats.SPXPUBLIC_SCHEMA.check_match(public_key) - - # Is 'scheme' properly formatted? + # Validate arguments + SPX_PUBLIC_BYTES_SCHEMA.check_match(public_key) + SPX_SIG_BYTES_SCHEMA.check_match(signature) securesystemslib.formats.SPX_SIG_SCHEMA.check_match(scheme) - # Is 'signature' properly formatted? - securesystemslib.formats.SPXSIGNATURE_SCHEMA.check_match(signature) # Verify 'signature'. Before returning the Boolean result, ensure 'spx' # was used as the signature scheme. Raise diff --git a/tests/test_spx_keys.py b/tests/test_spx_keys.py index 4377e35c..9f89111e 100755 --- a/tests/test_spx_keys.py +++ b/tests/test_spx_keys.py @@ -48,8 +48,8 @@ def test_generate_public_and_private(self): pub, priv = securesystemslib.spx_keys.generate_public_and_private() # Check format of 'pub' and 'priv'. - self.assertEqual(True, securesystemslib.formats.SPXPUBLIC_SCHEMA.matches(pub)) - self.assertEqual(True, securesystemslib.formats.SPXSEED_SCHEMA.matches(priv)) + self.assertEqual(True, securesystemslib.spx_keys.SPX_PUBLIC_BYTES_SCHEMA.matches(pub)) + self.assertEqual(True, securesystemslib.spx_keys.SPX_PRIVATE_BYTES_SCHEMA.matches(priv)) @@ -63,7 +63,7 @@ def test_create_signature(self): # Verify format of returned values. self.assertEqual(True, - securesystemslib.formats.SPXSIGNATURE_SCHEMA.matches(signature)) + securesystemslib.spx_keys.SPX_SIG_BYTES_SCHEMA.matches(signature)) self.assertEqual(True, securesystemslib.formats.SPX_SIG_SCHEMA.matches(scheme)) self.assertEqual('spx', scheme)