Skip to content

Commit

Permalink
Merge pull request #173 from openlawlibrary/danixeee/rsa-pkcs1v15-scheme
Browse files Browse the repository at this point in the history
Support for RSA-PKCS1v15-HASH_ID scheme
  • Loading branch information
lukpueh authored Aug 14, 2019
2 parents 6ef6c61 + 3116ee2 commit d6227b4
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 146 deletions.
4 changes: 3 additions & 1 deletion securesystemslib/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,9 @@
ANYKEYLIST_SCHEMA = SCHEMA.ListOf(ANYKEY_SCHEMA)

# RSA signature schemes.
RSA_SCHEME_SCHEMA = SCHEMA.OneOf([SCHEMA.String('rsassa-pss-sha256')])
RSA_SCHEME_SCHEMA = SCHEMA.OneOf([
SCHEMA.RegularExpression(r'rsassa-pss-(md5|sha1|sha224|sha256|sha384|sha512)'),
SCHEMA.RegularExpression(r'rsa-pkcs1v15-(md5|sha1|sha224|sha256|sha384|sha512)')])

# An RSA TUF key.
RSAKEY_SCHEMA = SCHEMA.Object(
Expand Down
160 changes: 154 additions & 6 deletions securesystemslib/hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,88 @@
SUPPORTED_LIBRARIES = ['hashlib']


# If `pyca_crypto` is installed, add it to supported libraries
try:
import cryptography.exceptions
import cryptography.hazmat.backends
import cryptography.hazmat.primitives.hashes as _pyca_hashes
import binascii

# Dictionary of `pyca/cryptography` supported hash algorithms.
PYCA_DIGEST_OBJECTS_CACHE = {
"md5": _pyca_hashes.MD5,
"sha1": _pyca_hashes.SHA1,
"sha224": _pyca_hashes.SHA224,
"sha256": _pyca_hashes.SHA256,
"sha384": _pyca_hashes.SHA384,
"sha512": _pyca_hashes.SHA512
}

SUPPORTED_LIBRARIES.append('pyca_crypto')

class PycaDiggestWrapper(object):
"""
<Purpose>
A wrapper around `cryptography.hazmat.primitives.hashes.Hash` which adds
additional methods to meet expected interface for digest objects:
digest_object.digest_size
digest_object.hexdigest()
digest_object.update('data')
digest_object.digest()
<Properties>
algorithm:
Specific for `cryptography.hazmat.primitives.hashes.Hash` object, but
needed for `pyca_crypto_keys.py`
digest_size:
Returns original's object digest size.
<Methods>
digest(self) -> bytes:
Calls original's object `finalize` method and returns digest as bytes.
NOTE: `cryptography.hazmat.primitives.hashes.Hash` allows calling
`finalize` method just once on the same instance, so everytime `digest`
methods is called, we replace internal object (`_digest_obj`).
hexdigest(self) -> str:
Returns a string hex representation of digest.
update(self, data) -> None:
Updates digest object data by calling the original's object `update`
method.
"""

def __init__(self, digest_obj):
self._digest_obj = digest_obj

@property
def algorithm(self):
return self._digest_obj.algorithm

@property
def digest_size(self):
return self._digest_obj.algorithm.digest_size

def digest(self):
digest_obj_copy = self._digest_obj.copy()
digest = self._digest_obj.finalize()
self._digest_obj = digest_obj_copy
return digest

def hexdigest(self):
return binascii.hexlify(self.digest()).decode('utf-8')

def update(self, data):
self._digest_obj.update(data)

except ImportError: #pragma: no cover
pass




def digest(algorithm=DEFAULT_HASH_ALGORITHM, hash_library=DEFAULT_HASH_LIBRARY):
"""
<Purpose>
Expand Down Expand Up @@ -93,7 +175,11 @@ def digest(algorithm=DEFAULT_HASH_ALGORITHM, hash_library=DEFAULT_HASH_LIBRARY):
None.
<Returns>
Digest object (e.g., hashlib.new(algorithm)).
Digest object
e.g.
hashlib.new(algorithm) or
PycaDiggestWrapper object
"""

# Are the arguments properly formatted? If not, raise
Expand All @@ -111,9 +197,15 @@ def digest(algorithm=DEFAULT_HASH_ALGORITHM, hash_library=DEFAULT_HASH_LIBRARY):
raise securesystemslib.exceptions.UnsupportedAlgorithmError(algorithm)

# Was a pyca_crypto digest object requested and is it supported?
elif hash_library == 'pyca_crypto' and hash_library in SUPPORTED_LIBRARIES: #pragma: no cover
# TODO: Add support for pyca/cryptography's hashing routines.
pass
elif hash_library == 'pyca_crypto' and hash_library in SUPPORTED_LIBRARIES:
try:
hash_algorithm = PYCA_DIGEST_OBJECTS_CACHE[algorithm]()
return PycaDiggestWrapper(
cryptography.hazmat.primitives.hashes.Hash(hash_algorithm,
cryptography.hazmat.backends.default_backend()))

except KeyError:
raise securesystemslib.exceptions.UnsupportedAlgorithmError(algorithm)

# The requested hash library is not supported.
else:
Expand Down Expand Up @@ -166,7 +258,11 @@ def digest_fileobject(file_object, algorithm=DEFAULT_HASH_ALGORITHM,
None.
<Returns>
Digest object (e.g., hashlib.new(algorithm)).
Digest object
e.g.
hashlib.new(algorithm) or
PycaDiggestWrapper object
"""

# Are the arguments properly formatted? If not, raise
Expand Down Expand Up @@ -254,7 +350,11 @@ def digest_filename(filename, algorithm=DEFAULT_HASH_ALGORITHM,
None.
<Returns>
Digest object (e.g., hashlib.new(algorithm)).
Digest object
e.g.
hashlib.new(algorithm) or
PycaDiggestWrapper object
"""
# Are the arguments properly formatted? If not, raise
# 'securesystemslib.exceptions.FormatError'.
Expand All @@ -274,3 +374,51 @@ def digest_filename(filename, algorithm=DEFAULT_HASH_ALGORITHM,
file_object, algorithm, hash_library, normalize_line_endings)

return digest_object





def digest_from_rsa_scheme(scheme, hash_library=DEFAULT_HASH_LIBRARY):
"""
<Purpose>
Get digest object from RSA scheme.
<Arguments>
scheme:
A string that indicates the signature scheme used to generate
'signature'. Currently supported RSA schemes are defined in
`securesystemslib.keys.RSA_SIGNATURE_SCHEMES`
hash_library:
The crypto library to use for the given hash algorithm (e.g., 'hashlib').
<Exceptions>
securesystemslib.exceptions.FormatError, if the arguments are
improperly formatted.
securesystemslib.exceptions.UnsupportedAlgorithmError, if an unsupported
hashing algorithm is specified, or digest could not be generated with given
the algorithm.
securesystemslib.exceptions.UnsupportedLibraryError, if an unsupported
library was requested via 'hash_library'.
<Side Effects>
None.
<Returns>
Digest object
e.g.
hashlib.new(algorithm) or
PycaDiggestWrapper object
"""
# Are the arguments properly formatted? If not, raise
# 'securesystemslib.exceptions.FormatError'.
securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme)

# Get hash algorithm from rsa scheme (hash algorithm id is specified after
# the last dash; e.g. rsassa-pss-sha256 -> sha256)
hash_algorithm = scheme.split('-')[-1]
return digest(hash_algorithm, hash_library)
35 changes: 26 additions & 9 deletions securesystemslib/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,22 @@
# size 3072 provide security through 2031 and beyond.
_DEFAULT_RSA_KEY_BITS = 3072


RSA_SIGNATURE_SCHEMES = [
'rsassa-pss-md5',
'rsassa-pss-sha1',
'rsassa-pss-sha224',
'rsassa-pss-sha256',
'rsassa-pss-sha384',
'rsassa-pss-sha512',
'rsa-pkcs1v15-md5',
'rsa-pkcs1v15-sha1',
'rsa-pkcs1v15-sha224',
'rsa-pkcs1v15-sha256',
'rsa-pkcs1v15-sha384',
'rsa-pkcs1v15-sha512',
]

logger = logging.getLogger('securesystemslib_keys')


Expand Down Expand Up @@ -165,8 +181,8 @@ def generate_rsa_key(bits=_DEFAULT_RSA_KEY_BITS, scheme='rsassa-pss-sha256'):
greater, and a multiple of 256.
scheme:
The signature scheme used by the key. It must be one of
['rsassa-pss-sha256'].
The signature scheme used by the key. It must be one from the list
`securesystemslib.keys.RSA_SIGNATURE_SCHEMES`.
<Exceptions>
securesystemslib.exceptions.FormatError, if 'bits' is improperly or invalid
Expand Down Expand Up @@ -692,11 +708,12 @@ def create_signature(key_dict, data):
# The key type of 'key_dict' must be either 'rsa' or 'ed25519'.
securesystemslib.formats.ANYKEY_SCHEMA.check_match(key_dict)

# Signing the 'data' object requires a private key. 'rsassa-pss-sha256',
# 'ed25519', and 'ecdsa-sha2-nistp256' are the only signing schemes currently
# supported. RSASSA-PSS keys and signatures can be generated and verified by
# pyca_crypto_keys.py, and Ed25519 keys by PyNaCl and PyCA's optimized, pure
# python implementation of Ed25519.
# Signing the 'data' object requires a private key. Signing schemes that are
# currently supported are: 'ed25519', 'ecdsa-sha2-nistp256', and rsa schemes
# defined in `securesystemslib.keys.RSA_SIGNATURE_SCHEMES`.
# RSASSA-PSS and RSA-PKCS1v15 keys and signatures can be generated and
# verified by pyca_crypto_keys.py, and Ed25519 keys by PyNaCl and PyCA's
# optimized, pure python implementation of Ed25519.
signature = {}
keytype = key_dict['keytype']
scheme = key_dict['scheme']
Expand All @@ -706,7 +723,7 @@ def create_signature(key_dict, data):
sig = None

if keytype == 'rsa':
if scheme == 'rsassa-pss-sha256':
if scheme in RSA_SIGNATURE_SCHEMES:
private = private.replace('\r\n', '\n')
sig, scheme = securesystemslib.pyca_crypto_keys.create_rsa_signature(
private, data, scheme)
Expand Down Expand Up @@ -847,7 +864,7 @@ def verify_signature(key_dict, signature, data):


if keytype == 'rsa':
if scheme == 'rsassa-pss-sha256':
if scheme in RSA_SIGNATURE_SCHEMES:
valid_signature = securesystemslib.pyca_crypto_keys.verify_rsa_signature(sig,
scheme, public, data)

Expand Down
Loading

0 comments on commit d6227b4

Please sign in to comment.