Skip to content

Commit

Permalink
Restructure pyspx conditional import
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lukpueh committed Jun 18, 2019
1 parent d653691 commit 9beace8
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 63 deletions.
17 changes: 0 additions & 17 deletions securesystemslib/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()

Expand Down
3 changes: 2 additions & 1 deletion securesystemslib/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
68 changes: 26 additions & 42 deletions securesystemslib/spx_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
"""
<Purpose>
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
<Arguments>
Expand All @@ -91,8 +90,8 @@ def generate_public_and_private():
<Returns>
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
Expand Down Expand Up @@ -120,20 +119,20 @@ def create_signature(public_key, private_key, data, scheme):
<Purpose>
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
Expand Down Expand Up @@ -163,17 +162,9 @@ def create_signature(public_key, private_key, data, scheme):
<Returns>
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.
Expand Down Expand Up @@ -233,13 +224,13 @@ def verify_signature(public_key, scheme, signature, data):
<Arguments>
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
Expand All @@ -259,18 +250,11 @@ def verify_signature(public_key, scheme, signature, data):
<Returns>
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
Expand Down
6 changes: 3 additions & 3 deletions tests/test_spx_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))



Expand All @@ -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)
Expand Down

0 comments on commit 9beace8

Please sign in to comment.