mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
425 lines
13 KiB
Python
Executable file
425 lines
13 KiB
Python
Executable file
"""
|
|
<Program Name>
|
|
rsa_key.py
|
|
|
|
<Author>
|
|
Vladimir Diaz <vladimir.v.diaz@gmail.com>
|
|
|
|
<Started>
|
|
March 9, 2012. Based on a previous version of this module by Geremy Condra.
|
|
|
|
<Copyright>
|
|
See LICENSE for licensing information.
|
|
|
|
<Purpose>
|
|
The goal of this module is to support public-key cryptography using
|
|
the RSA algorithm. The RSA-related functions provided include
|
|
generate(), create_signature(), and verify_signature(). The 'evpy' package
|
|
used by 'rsa_key.py' generates the actual RSA keys and the functions listed
|
|
above can be viewed as an easy-to-use public interface. Additional functions
|
|
contained here include create_in_metadata_format() and
|
|
create_from_metadata_format(). These last two functions produce or use RSA
|
|
keys compatible with the key structures listed in TUF Metadata files.
|
|
The generate() function returns a dictionary containing all the information
|
|
needed of RSA keys, such as public and private keys, keyIDs, and an iden-
|
|
fier. create_signature() and verify_signature() are supplemental functions
|
|
used for generating RSA signatures and verifying them.
|
|
|
|
Key IDs are used as identifiers for keys (e.g., RSA key). They are the
|
|
hexadecimal representation of the hash of key object (specifically, the key
|
|
object containing only the public key). See 'rsa_key.py' and the
|
|
'_get_keyid()' function to see precisely how keyids are generated. One may
|
|
get the keyid of a key object by simply accessing the dictionary's 'keyid'
|
|
key (i.e., rsakey['keyid']).
|
|
|
|
"""
|
|
|
|
|
|
# Required for hexadecimal conversions.
|
|
import binascii
|
|
|
|
# Needed to generate, sign, and verify RSA keys.
|
|
import evpy.signature
|
|
import evpy.envelope
|
|
|
|
# Digest objects needed to generate hashes.
|
|
import tuf.hash
|
|
|
|
# Perform object format-checking.
|
|
import tuf.formats
|
|
|
|
|
|
_KEY_ID_HASH_ALGORITHM = 'sha256'
|
|
|
|
# Recommended RSA key sizes: http://www.rsa.com/rsalabs/node.asp?id=2004
|
|
# According to the document above, revised May 6, 2003, RSA keys of
|
|
# size 3072 provide security through 2031 and beyond.
|
|
_DEFAULT_RSA_KEY_BITS = 3072
|
|
|
|
|
|
def generate(bits=_DEFAULT_RSA_KEY_BITS):
|
|
"""
|
|
<Purpose>
|
|
Generate public and private RSA keys, with modulus length 'bits'.
|
|
In addition, a keyid used as an identifier for RSA keys is generated.
|
|
The object returned conforms to tuf.formats.RSAKEY_SCHEMA and as the form:
|
|
{'keytype': 'rsa',
|
|
'keyid': keyid,
|
|
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
|
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
|
|
|
The public and private keys are in PEM format and stored as strings.
|
|
|
|
<Arguments>
|
|
bits:
|
|
The key size, or key length, of the RSA key.
|
|
|
|
<Exceptions>
|
|
tuf.CryptoError, if an exception occurs after calling evpy.envelope.keygen().
|
|
|
|
tuf.FormatError, if 'bits' does not contain the correct format.
|
|
|
|
<Side Effects>
|
|
The RSA keys are generated by calling evpy.envelope.keygen().
|
|
|
|
<Returns>
|
|
A dictionary containing the RSA keys and other identifying information.
|
|
|
|
"""
|
|
|
|
|
|
# Does 'bits' have the correct format?
|
|
# This check will ensure 'bits' conforms to 'tuf.formats.RSAKEYBITS_SCHEMA'.
|
|
# 'bits' must be an integer object, with a minimum value of 2048.
|
|
# Raise 'tuf.FormatError' if the check fails.
|
|
tuf.formats.RSAKEYBITS_SCHEMA.check_match(bits)
|
|
|
|
# Begin building the RSA key dictionary.
|
|
rsakey_dict = {}
|
|
keytype = 'rsa'
|
|
|
|
# Generate the public and private keys. 'public_key' and 'private_key'
|
|
# will both be strings containing RSA keys in PEM format.
|
|
# The evpy.envelope module performs the actual key generation. The
|
|
# evpy.envelope.keygen() function returns a (public, private) tuple.
|
|
|
|
try:
|
|
public_key, private_key = evpy.envelope.keygen(bits, pem=True)
|
|
except (EnvelopeError, KeygenError, MemoryError), e:
|
|
raise tuf.CryptoError(e)
|
|
|
|
# Generate the keyid for the RSA key. 'key_value' corresponds to the
|
|
# 'keyval' entry of the RSAKEY_SCHEMA dictionary.
|
|
key_value = {'public': public_key,
|
|
'private': ''}
|
|
|
|
keyid = _get_keyid(key_value)
|
|
|
|
# Build the 'rsakey_dict' dictionary.
|
|
# Update 'key_value' with the RSA private key prior to adding
|
|
# 'key_value' to 'rsakey_dict'.
|
|
key_value['private'] = private_key
|
|
|
|
rsakey_dict['keytype'] = keytype
|
|
rsakey_dict['keyid'] = keyid
|
|
rsakey_dict['keyval'] = key_value
|
|
|
|
return rsakey_dict
|
|
|
|
|
|
|
|
|
|
|
|
def create_in_metadata_format(key_value, private=False):
|
|
"""
|
|
<Purpose>
|
|
Return a dictionary conformant to tuf.formats.KEY_SCHEMA.
|
|
If 'private' is True, include the private key. The dictionary
|
|
returned has the form:
|
|
{'keytype': 'rsa',
|
|
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
|
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
|
or
|
|
|
|
{'keytype': 'rsa',
|
|
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
|
'private': ''}} if 'private' is False.
|
|
|
|
The private and public keys are in PEM format.
|
|
|
|
RSA keys are stored in Metadata files (e.g., root.txt) in the format
|
|
returned by this function.
|
|
|
|
<Arguments>
|
|
key_value:
|
|
A dictionary containing a private and public RSA key.
|
|
'key_value' is of the form:
|
|
|
|
{'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
|
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}},
|
|
conformat to tuf.formats.KEYVAL_SCHEMA.
|
|
|
|
private:
|
|
Indicates if the private key should be included in the
|
|
returned dictionary.
|
|
|
|
<Exceptions>
|
|
tuf.FormatError, if 'key_value' does not conform to
|
|
tuf.formats.KEYVAL_SCHEMA.
|
|
|
|
<Side Effects>
|
|
None.
|
|
|
|
<Returns>
|
|
An KEY_SCHEMA dictionary.
|
|
|
|
"""
|
|
|
|
|
|
# Does 'key_value' have the correct format?
|
|
# This check will ensure 'key_value' has the appropriate number
|
|
# of objects and object types, and that all dict keys are properly named.
|
|
# Raise 'tuf.FormatError' if the check fails.
|
|
tuf.formats.KEYVAL_SCHEMA.check_match(key_value)
|
|
|
|
if private and key_value['private']:
|
|
return {'keytype': 'rsa', 'keyval': key_value}
|
|
else:
|
|
public_key_value = {'public': key_value['public'], 'private': ''}
|
|
return {'keytype': 'rsa', 'keyval': public_key_value}
|
|
|
|
|
|
|
|
|
|
|
|
def create_from_metadata_format(key_metadata):
|
|
"""
|
|
<Purpose>
|
|
Construct an RSA key dictionary (i.e., tuf.formats.RSAKEY_SCHEMA)
|
|
from 'key_metadata'. The dict returned by this function has the exact
|
|
format as the dict returned by generate(). It is of the form:
|
|
|
|
{'keytype': 'rsa',
|
|
'keyid': keyid,
|
|
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
|
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
|
|
|
The public and private keys are in PEM format and stored as strings.
|
|
|
|
RSA key dictionaries in RSAKEY_SCHEMA format should be used by
|
|
modules storing a collection of keys, such as a keydb and keystore.
|
|
RSA keys as stored in metadata files use a different format, so this
|
|
function should be called if an RSA key is extracted from one of these
|
|
metadata files and needs converting. Generate() creates an entirely
|
|
new key and returns it in the format appropriate for keydb and keystore.
|
|
|
|
<Arguments>
|
|
key_metadata:
|
|
The RSA key dictionary as stored in Metadata files, conforming to
|
|
tuf.formats.KEY_SCHEMA.
|
|
|
|
<Exceptions>
|
|
tuf.FormatError, if 'key_metadata' does not conform to
|
|
tuf.formats.KEY_SCHEMA.
|
|
|
|
<Side Effects>
|
|
None.
|
|
|
|
<Returns>
|
|
A dictionary containing the RSA keys and other identifying information.
|
|
|
|
"""
|
|
|
|
|
|
# Does 'key_metadata' have the correct format?
|
|
# This check will ensure 'key_metadata' has the appropriate number
|
|
# of objects and object types, and that all dict keys are properly named.
|
|
# Raise 'tuf.FormatError' if the check fails.
|
|
tuf.formats.KEY_SCHEMA.check_match(key_metadata)
|
|
|
|
# Construct the dictionary to be returned.
|
|
rsakey_dict = {}
|
|
keytype = 'rsa'
|
|
key_value = key_metadata['keyval']
|
|
|
|
keyid = _get_keyid(key_value)
|
|
|
|
# We now have all the required key values.
|
|
# Build 'rsakey_dict'.
|
|
rsakey_dict['keytype'] = keytype
|
|
rsakey_dict['keyid'] = keyid
|
|
rsakey_dict['keyval'] = key_value
|
|
|
|
return rsakey_dict
|
|
|
|
|
|
|
|
|
|
|
|
def _get_keyid(key_value):
|
|
"""Return the keyid for 'key_value'."""
|
|
|
|
# 'keyid' will be generated from an object conformant to KEY_SCHEMA,
|
|
# which is the format Metadata files (e.g., root.txt) store keys.
|
|
# 'create_in_metadata_format()' returns the object needed by _get_keyid().
|
|
rsakey_meta = create_in_metadata_format(key_value, private=False)
|
|
|
|
# Convert the RSA key to JSON Canonical format suitable for adding
|
|
# to digest objects.
|
|
rsakey_update_data = tuf.formats.encode_canonical(rsakey_meta)
|
|
|
|
# Create a digest object and call update(), using the JSON
|
|
# canonical format of 'rskey_meta' as the update data.
|
|
digest_object = tuf.hash.digest(_KEY_ID_HASH_ALGORITHM)
|
|
digest_object.update(rsakey_update_data)
|
|
|
|
# 'keyid' becomes the hexadecimal representation of the hash.
|
|
keyid = digest_object.hexdigest()
|
|
|
|
return keyid
|
|
|
|
|
|
|
|
|
|
|
|
def create_signature(rsakey_dict, data):
|
|
"""
|
|
<Purpose>
|
|
Return a signature dictionary of the form:
|
|
{'keyid': keyid, 'method': 'evp', 'sig': sig}.
|
|
|
|
The signing process will use the private key
|
|
rsakey_dict['keyval']['private'] and 'data' to generate the signature.
|
|
|
|
<Arguments>
|
|
rsakey_dict:
|
|
A dictionary containing the RSA keys and other identifying information.
|
|
'rsakey_dict' has the form:
|
|
|
|
{'keytype': 'rsa',
|
|
'keyid': keyid,
|
|
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
|
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
|
|
|
The public and private keys are in PEM format and stored as strings.
|
|
|
|
data:
|
|
Data object used by create_signature() to generate the signature.
|
|
|
|
<Exceptions>
|
|
TypeError, if a private key is not defined for 'rsakey_dict'.
|
|
|
|
tuf.FormatError, if an incorrect format is found for the
|
|
'rsakey_dict' object.
|
|
|
|
<Side Effects>
|
|
evpy.signature.sign() called to perform the actual signing.
|
|
|
|
<Returns>
|
|
A signature dictionary conformat to tuf.format.SIGNATURE_SCHEMA.
|
|
|
|
"""
|
|
|
|
|
|
# Does 'rsakey_dict' have the correct format?
|
|
# This check will ensure 'rsakey_dict' has the appropriate number
|
|
# of objects and object types, and that all dict keys are properly named.
|
|
# Raise 'tuf.FormatError' if the check fails.
|
|
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
|
|
|
|
# Signing the 'data' object requires a private key.
|
|
# The 'evp' (i.e., evpy) signing method is the only method
|
|
# currently supported.
|
|
signature = {}
|
|
private_key = rsakey_dict['keyval']['private']
|
|
keyid = rsakey_dict['keyid']
|
|
method = 'evp'
|
|
|
|
if private_key:
|
|
sig = evpy.signature.sign(data, key=private_key)
|
|
else:
|
|
raise TypeError('The required private key is not defined for rsakey_dict.')
|
|
|
|
# Build the signature dictionary to be returned.
|
|
# The hexadecimal representation of 'sig' is stored in the signature.
|
|
signature['keyid'] = keyid
|
|
signature['method'] = method
|
|
signature['sig'] = binascii.hexlify(sig)
|
|
|
|
return signature
|
|
|
|
|
|
|
|
|
|
|
|
def verify_signature(rsakey_dict, signature, data):
|
|
"""
|
|
<Purpose>
|
|
Determine whether the private key belonging to 'rsakey_dict' produced
|
|
'signature'. verify_signature() will use the public key found in
|
|
'rsakey_dict', the 'method' and 'sig' objects contained in 'signature',
|
|
and 'data' to complete the verification. Type-checking performed on both
|
|
'rsakey_dict' and 'signature'.
|
|
|
|
<Arguments>
|
|
rsakey_dict:
|
|
A dictionary containing the RSA keys and other identifying information.
|
|
'rsakey_dict' has the form:
|
|
|
|
{'keytype': 'rsa',
|
|
'keyid': keyid,
|
|
'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
|
|
'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
|
|
|
|
The public and private keys are in PEM format and stored as strings.
|
|
|
|
signature:
|
|
The signature dictionary produced by tuf.rsa_key.create_signature().
|
|
'signature' has the form:
|
|
{'keyid': keyid, 'method': 'method', 'sig': sig}. Conformant to
|
|
tuf.formats.SIGNATURE_SCHEMA.
|
|
|
|
data:
|
|
Data object used by tuf.rsa_key.create_signature() to generate
|
|
'signature'. 'data' is needed here to verify the signature.
|
|
|
|
<Exceptions>
|
|
tuf.UnknownMethodError. Raised if the signing method used by
|
|
'signature' is not one supported by tuf.rsa_key.create_signature().
|
|
|
|
tuf.FormatError. Raised if either 'rsakey_dict'
|
|
or 'signature' do not match their respective tuf.formats schema.
|
|
'rsakey_dict' must conform to tuf.formats.RSAKEY_SCHEMA.
|
|
'signature' must conform to tuf.formats.SIGNATURE_SCHEMA.
|
|
|
|
<Side Effects>
|
|
evpy.signature_verify() called to do the actual verification.
|
|
|
|
<Returns>
|
|
Boolean.
|
|
|
|
"""
|
|
|
|
|
|
# Does 'rsakey_dict' have the correct format?
|
|
# This check will ensure 'rsakey_dict' has the appropriate number
|
|
# of objects and object types, and that all dict keys are properly named.
|
|
# Raise 'tuf.FormatError' if the check fails.
|
|
tuf.formats.RSAKEY_SCHEMA.check_match(rsakey_dict)
|
|
|
|
# Does 'signature' have the correct format?
|
|
tuf.formats.SIGNATURE_SCHEMA.check_match(signature)
|
|
|
|
# Using the public key belonging to 'rsakey_dict'
|
|
# (i.e., rsakey_dict['keyval']['public']), verify whether 'signature'
|
|
# was produced by rsakey_dict's corresponding private key
|
|
# rsakey_dict['keyval']['private']. Before returning the Boolean result,
|
|
# ensure 'evp' was used as the signing method.
|
|
|
|
method = signature['method']
|
|
sig = signature['sig']
|
|
public_key = rsakey_dict['keyval']['public']
|
|
|
|
if method != 'evp':
|
|
raise tuf.UnknownMethodError(method)
|
|
return evpy.signature.verify(data, binascii.unhexlify(sig), key=public_key)
|