""" keydb.py Vladimir Diaz March 21, 2012. Based on a previous version of this module by Geremy Condra. See LICENSE for licensing information. Represent a collection of keys and their organization. This module ensures the layout of the collection remain consistent and easily verifiable. Provided are functions to add and delete keys from the database, retrieve a single key, and assemble a collection from keys stored in TUF 'Root' Metadata files. The Update Framework process maintains a single keydb. RSA keys are currently supported and a collection of keys is organized as a dictionary indexed by key ID. Key IDs are used as identifiers for keys (e.g., RSA key). They are the hexadecimal representations of the hash of key objects (specifically, the key object containing only the public key). See 'rsa_key.py' and the '_get_keyid()' function to learn 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']). """ # Help with Python 3 compatibility, where the print statement is a function, an # implicit relative import is invalid, and the '/' operator performs true # division. Example: print 'hello world' raises a 'SyntaxError' exception. from __future__ import print_function from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import logging import copy import tuf import tuf.formats import tuf.keys import six # List of strings representing the key types supported by TUF. _SUPPORTED_KEY_TYPES = ['rsa', 'ed25519'] # See 'log.py' to learn how logging is handled in TUF. logger = logging.getLogger('tuf.keydb') # The key database. _keydb_dict = {} def create_keydb_from_root_metadata(root_metadata): """ Populate the key database with the unique keys found in 'root_metadata'. The database dictionary will conform to 'tuf.formats.KEYDB_SCHEMA' and have the form: {keyid: key, ...}. The 'keyid' conforms to 'tuf.formats.KEYID_SCHEMA' and 'key' to its respective type. In the case of RSA keys, this object would match 'RSAKEY_SCHEMA'. root_metadata: A dictionary conformant to 'tuf.formats.ROOT_SCHEMA'. The keys found in the 'keys' field of 'root_metadata' are needed by this function. tuf.FormatError, if 'root_metadata' does not have the correct format. A function to add the key to the database is called. In the case of RSA keys, this function is add_key(). The old keydb key database is replaced. None. """ # Does 'root_metadata' have the correct format? # This check will ensure 'root_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.ROOT_SCHEMA.check_match(root_metadata) # Clear the key database. _keydb_dict.clear() # Iterate the keys found in 'root_metadata' by converting them to # 'RSAKEY_SCHEMA' if their type is 'rsa', and then adding them to the # database. for keyid, key_metadata in six.iteritems(root_metadata['keys']): if key_metadata['keytype'] in _SUPPORTED_KEY_TYPES: # 'key_metadata' is stored in 'KEY_SCHEMA' format. Call # create_from_metadata_format() to get the key in 'RSAKEY_SCHEMA' # format, which is the format expected by 'add_key()'. key_dict = tuf.keys.format_metadata_to_key(key_metadata) try: add_key(key_dict, keyid) # Although keyid duplicates should *not* occur (unique dict keys), log a # warning and continue. except tuf.KeyAlreadyExistsError as e: # pragma: no cover logger.warning(e) continue # 'tuf.Error' raised if keyid does not match the keyid for 'rsakey_dict'. except tuf.Error as e: logger.error(e) continue else: logger.warning('Root Metadata file contains a key with an invalid keytype.') def add_key(key_dict, keyid=None): """ Add 'rsakey_dict' to the key database while avoiding duplicates. If keyid is provided, verify it is the correct keyid for 'rsakey_dict' and raise an exception if it is not. key_dict: A dictionary conformant to 'tuf.formats.ANYKEY_SCHEMA'. It has the form: {'keytype': 'rsa', 'keyid': keyid, 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} keyid: An object conformant to 'KEYID_SCHEMA'. It is used as an identifier for RSA keys. tuf.FormatError, if 'rsakey_dict' or 'keyid' does not have the correct format. tuf.Error, if 'keyid' does not match the keyid for 'rsakey_dict'. tuf.KeyAlreadyExistsError, if 'rsakey_dict' is found in the key database. The keydb key database is modified. None. """ # 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.ANYKEY_SCHEMA.check_match(key_dict) # Does 'keyid' have the correct format? if keyid is not None: # Raise 'tuf.FormatError' if the check fails. tuf.formats.KEYID_SCHEMA.check_match(keyid) # Check if the keyid found in 'key_dict' matches 'keyid'. if keyid != key_dict['keyid']: raise tuf.Error('Incorrect keyid ' + key_dict['keyid'] + ' expected ' + keyid) # Check if the keyid belonging to 'rsakey_dict' is not already # available in the key database before returning. keyid = key_dict['keyid'] if keyid in _keydb_dict: raise tuf.KeyAlreadyExistsError('Key: '+keyid) _keydb_dict[keyid] = copy.deepcopy(key_dict) def get_key(keyid): """ Return the key belonging to 'keyid'. keyid: An object conformant to 'tuf.formats.KEYID_SCHEMA'. It is used as an identifier for keys. tuf.FormatError, if 'keyid' does not have the correct format. tuf.UnknownKeyError, if 'keyid' is not found in the keydb database. None. The key matching 'keyid'. In the case of RSA keys, a dictionary conformant to 'tuf.formats.RSAKEY_SCHEMA' is returned. """ # Does 'keyid' have the correct format? # This check will ensure 'keyid' has the appropriate number of objects # and object types, and that all dict keys are properly named. # Raise 'tuf.FormatError' is the match fails. tuf.formats.KEYID_SCHEMA.check_match(keyid) # Return the key belonging to 'keyid', if found in the key database. try: return copy.deepcopy(_keydb_dict[keyid]) except KeyError: raise tuf.UnknownKeyError('Key: '+keyid) def remove_key(keyid): """ Remove the key belonging to 'keyid'. keyid: An object conformant to 'tuf.formats.KEYID_SCHEMA'. It is used as an identifier for keys. tuf.FormatError, if 'keyid' does not have the correct format. tuf.UnknownKeyError, if 'keyid' is not found in key database. The key, identified by 'keyid', is deleted from the key database. None. """ # Does 'keyid' have the correct format? # This check will ensure 'keyid' has the appropriate number of objects # and object types, and that all dict keys are properly named. # Raise 'tuf.FormatError' is the match fails. tuf.formats.KEYID_SCHEMA.check_match(keyid) # Remove the key belonging to 'keyid' if found in the key database. if keyid in _keydb_dict: del _keydb_dict[keyid] else: raise tuf.UnknownKeyError('Key: '+keyid) def clear_keydb(): """ Clear the keydb key database. None. None. The keydb key database is reset. None. """ _keydb_dict.clear()