diff --git a/tuf/formats.py b/tuf/formats.py index 8ecc483a..c1977216 100755 --- a/tuf/formats.py +++ b/tuf/formats.py @@ -227,6 +227,11 @@ # An ED25519 raw signature, which must be 64 bytes. ED25519SIGNATURE_SCHEMA = SCHEMA.LengthString(64) +# Required installation libraries expected by the repository tools and other +# cryptography modules. +REQUIRED_LIBRARIES_SCHEMA = SCHEMA.ListOf(SCHEMA.OneOf( + [SCHEMA.String('general'), SCHEMA.String('ed25519'), SCHEMA.String('rsa')])) + # An ed25519 TUF key. ED25519KEY_SCHEMA = SCHEMA.Object( object_name = 'ED25519KEY_SCHEMA', diff --git a/tuf/keys.py b/tuf/keys.py index 3b32b8bb..0e7ba84f 100755 --- a/tuf/keys.py +++ b/tuf/keys.py @@ -197,9 +197,8 @@ def generate_rsa_key(bits=_DEFAULT_RSA_KEY_BITS): tuf.formats.RSAKEYBITS_SCHEMA.check_match(bits) # Raise 'tuf.UnsupportedLibraryError' if the following libraries, specified in - # 'tuf.conf', are unsupported or unavailable: - # 'tuf.conf.RSA_CRYPTO_LIBRARY' and 'tuf.conf.ED25519_CRYPTO_LIBRARY'. - _check_crypto_libraries() + # 'tuf.conf', are unsupported or unavailable: 'tuf.conf.RSA_CRYPTO_LIBRARY'. + check_crypto_libraries(['rsa']) # Begin building the RSA key dictionary. rsakey_dict = {} @@ -281,8 +280,8 @@ def generate_ed25519_key(): # Raise 'tuf.UnsupportedLibraryError' if the following libraries, specified # in 'tuf.conf', are unsupported or unavailable: - # 'tuf.conf.RSA_CRYPTO_LIBRARY' and 'tuf.conf.ED25519_CRYPTO_LIBRARY'. - _check_crypto_libraries() + # 'tuf.conf.ED25519_CRYPTO_LIBRARY'. + check_crypto_libraries(['ed25519']) # Begin building the ED25519 key dictionary. ed25519_key = {} @@ -503,46 +502,78 @@ def _get_keyid(keytype, key_value): -def _check_crypto_libraries(): - """ Ensure all the crypto libraries specified in tuf.conf are available. """ +def check_crypto_libraries(required_libraries): + """ + + Public function that ensures the cryptography libraries specified in + 'tuf.conf' are supported and available for each 'required_libraries'. + + + required_libraries: + A list of library strings to validate. One, or multiple, strings from + ['rsa', 'ed25519', 'general'] can be specified. + + + tuf.UnsupportedLibraryError, if the 'required_libraries' and the libraries + specified in 'tuf.conf' are not supported or unavailable. + + + Validates the libraries set in 'tuf.conf'. + + + None. + """ + + # Does 'required_libraries' have the correct format? + # This check will ensure 'required_libraries' 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.REQUIRED_LIBRARIES_SCHEMA.check_match(required_libraries) - # The checks below all raise 'tuf.UnsupportedLibraryError' if the RSA and - # ED25519 crypto libraries specified in 'tuf.conf.py' are not supported or - # unavailable. The appropriate error message is added to the exception. - # The funcions of this module that depend on user-installed crypto libraries - # should call this private function to ensure the called routine does not fail - # with unpredictable exceptions in the event of a missing library. - # The supported and available lists checked are populated when 'tuf.keys.py' - # is imported. - if _RSA_CRYPTO_LIBRARY not in _SUPPORTED_RSA_CRYPTO_LIBRARIES: + # The checks below all raise 'tuf.UnsupportedLibraryError' if the general, + # RSA, and ED25519 crypto libraries specified in 'tuf.conf.py' are not + # supported or unavailable. The appropriate error message is added to the + # exception. The funcions of this module that depend on user-installed + # crypto libraries should call this private function to ensure the called + # routine does not fail with unpredictable exceptions in the event of a + # missing library. The supported and available lists checked are populated + # when 'tuf.keys.py' is imported. + + if 'rsa' in required_libraries and _RSA_CRYPTO_LIBRARY not in \ + _SUPPORTED_RSA_CRYPTO_LIBRARIES: message = 'The '+repr(_RSA_CRYPTO_LIBRARY)+' crypto library specified'+ \ ' in "tuf.conf.RSA_CRYPTO_LIBRARY" is not supported.\n'+ \ 'Supported crypto libraries: '+repr(_SUPPORTED_RSA_CRYPTO_LIBRARIES)+'.' raise tuf.UnsupportedLibraryError(message) - if _ED25519_CRYPTO_LIBRARY not in _SUPPORTED_ED25519_CRYPTO_LIBRARIES: + if 'ed25519' in required_libraries and _ED25519_CRYPTO_LIBRARY not in \ + _SUPPORTED_ED25519_CRYPTO_LIBRARIES: message = 'The '+repr(_ED25519_CRYPTO_LIBRARY)+' crypto library specified'+\ ' in "tuf.conf.ED25519_CRYPTO_LIBRARY" is not supported.\n'+ \ 'Supported crypto libraries: '+repr(_SUPPORTED_ED25519_CRYPTO_LIBRARIES)+'.' raise tuf.UnsupportedLibraryError(message) - if _GENERAL_CRYPTO_LIBRARY not in _SUPPORTED_GENERAL_CRYPTO_LIBRARIES: + if 'general' in required_libraries and _GENERAL_CRYPTO_LIBRARY not in \ + _SUPPORTED_GENERAL_CRYPTO_LIBRARIES: message = 'The '+repr(_GENERAL_CRYPTO_LIBRARY)+' crypto library specified'+\ ' in "tuf.conf.GENERAL_CRYPTO_LIBRARY" is not supported.\n'+ \ 'Supported crypto libraries: '+repr(_SUPPORTED_GENERAL_CRYPTO_LIBRARIES)+'.' raise tuf.UnsupportedLibraryError(message) - if _RSA_CRYPTO_LIBRARY not in _available_crypto_libraries: + if 'rsa' in required_libraries and _RSA_CRYPTO_LIBRARY not in \ + _available_crypto_libraries: message = 'The '+repr(_RSA_CRYPTO_LIBRARY)+' crypto library specified'+ \ ' in "tuf.conf.RSA_CRYPTO_LIBRARY" could not be imported.' raise tuf.UnsupportedLibraryError(message) - if _ED25519_CRYPTO_LIBRARY not in _available_crypto_libraries: + if 'ed25519' in required_libraries and _ED25519_CRYPTO_LIBRARY not in \ + _available_crypto_libraries: message = 'The '+repr(_ED25519_CRYPTO_LIBRARY)+' crypto library specified'+\ ' in "tuf.conf.ED25519_CRYPTO_LIBRARY" could not be imported.' raise tuf.UnsupportedLibraryError(message) - if _GENERAL_CRYPTO_LIBRARY not in _available_crypto_libraries: + if 'general' in required_libraries and _GENERAL_CRYPTO_LIBRARY not in \ + _available_crypto_libraries: message = 'The '+repr(_GENERAL_CRYPTO_LIBRARY)+' crypto library specified'+\ ' in "tuf.conf.GENERAL_CRYPTO_LIBRARY" could not be imported.' raise tuf.UnsupportedLibraryError(message) @@ -628,8 +659,8 @@ def create_signature(key_dict, data): # Raise 'tuf.UnsupportedLibraryError' if the following libraries, specified # in 'tuf.conf', are unsupported or unavailable: - # 'tuf.conf.RSA_CRYPTO_LIBRARY' and 'tuf.conf.ED25519_CRYPTO_LIBRARY'. - _check_crypto_libraries() + # 'tuf.conf.RSA_CRYPTO_LIBRARY' or 'tuf.conf.ED25519_CRYPTO_LIBRARY'. + check_crypto_libraries([key_dict['keytype']]) # Signing the 'data' object requires a private key. # The 'RSASSA-PSS' (i.e., PyCrypto module) and 'ed25519' (i.e., PyNaCl and the @@ -885,8 +916,8 @@ def import_rsakey_from_encrypted_pem(encrypted_pem, password): # Raise 'tuf.UnsupportedLibraryError' if the following libraries, specified in # 'tuf.conf', are unsupported or unavailable: - # 'tuf.conf.RSA_CRYPTO_LIBRARY' and 'tuf.conf.ED25519_CRYPTO_LIBRARY'. - _check_crypto_libraries() + # 'tuf.conf.RSA_CRYPTO_LIBRARY' and 'tuf.conf.GENERAL_CRYPTO_LIBRARY'. + check_crypto_libraries(['rsa', 'general']) # Begin building the RSA key dictionary. rsakey_dict = {} @@ -1068,7 +1099,7 @@ def encrypt_key(key_object, password): # Raise 'tuf.UnsupportedLibraryError' if the following libraries, specified in # 'tuf.conf', are unsupported or unavailable: # 'tuf.conf.GENERAL_CRYPTO_LIBRARY'. - _check_crypto_libraries() + check_crypto_libraries(['general']) # Encrypted string of 'key_object'. The encrypted string may be safely saved # to a file and stored offline. @@ -1164,7 +1195,7 @@ def decrypt_key(encrypted_key, passphrase): # Raise 'tuf.UnsupportedLibraryError' if the following libraries, specified in # 'tuf.conf', are unsupported or unavailable: # 'tuf.conf.GENERAL_CRYPTO_LIBRARY'. - _check_crypto_libraries() + check_crypto_libraries(['general']) # Store and return the decrypted key object. key_object = None @@ -1247,8 +1278,8 @@ def create_rsa_encrypted_pem(private_key, passphrase): # Raise 'tuf.UnsupportedLibraryError' if the following libraries, specified in # 'tuf.conf', are unsupported or unavailable: - # 'tuf.conf.RSA_CRYPTO_LIBRARY'. - _check_crypto_libraries() + # 'tuf.conf.RSA_CRYPTO_LIBRARY' and 'tuf.conf.GENERAL_CRYPTO_LIBRARY'. + check_crypto_libraries(['rsa', 'general']) encrypted_pem = None diff --git a/tuf/log.py b/tuf/log.py index 6fcf9553..ebbe65d6 100755 --- a/tuf/log.py +++ b/tuf/log.py @@ -258,6 +258,7 @@ def set_console_log_level(log_level=_DEFAULT_CONSOLE_LOG_LEVEL): if console_handler is not None: console_handler.setLevel(log_level) + else: message = 'The console handler has not been set with add_console_handler().' raise tuf.Error(message) @@ -298,14 +299,18 @@ def add_console_handler(log_level=_DEFAULT_CONSOLE_LOG_LEVEL): # Set the console handler for the logger. The built-in console handler will # log messages to 'sys.stderr' and capture 'log_level' messages. console_handler = logging.StreamHandler() + # Get our filter for the console handler. console_filter = ConsoleFilter() + console_format_string = '%(message)s' + formatter = logging.Formatter(console_format_string) console_handler.setLevel(log_level) console_handler.setFormatter(formatter) console_handler.addFilter(console_filter) logger.addHandler(console_handler) logger.debug('Added a console handler.') + else: logger.warn('We already have a console handler.') @@ -339,5 +344,6 @@ def remove_console_handler(): logger.removeHandler(console_handler) console_handler = None logger.debug('Removed a console handler.') + else: logger.warn('We do not have a console handler.') diff --git a/tuf/repository_tool.py b/tuf/repository_tool.py index b60f7db2..5c93cab8 100755 --- a/tuf/repository_tool.py +++ b/tuf/repository_tool.py @@ -117,6 +117,15 @@ TIMESTAMP_EXPIRES_WARN_SECONDS = 86400 +try: + tuf.keys.check_crypto_libraries(['rsa', 'ed25519', 'general']) + +except tuf.UnsupportedLibraryError as e: + message = 'Warning: The repository and developer tools require additional' + \ + ' libraries and can be installed as follows:\n $ pip install tuf[tools]' + logger.warn(message) + + class Repository(object): """ @@ -430,23 +439,23 @@ def status(self): except tuf.UnsignedMetadataError, e: insufficient_signatures.append(delegated_role) - # Print the verification results of the delegated roles and return + # Log the verification results of the delegated roles and return # immediately after each invalid case. if len(insufficient_keys): message = \ 'Delegated roles with insufficient keys:\n'+repr(insufficient_keys) - print(message) + logger.info(message) return if len(insufficient_signatures): message = \ 'Delegated roles with insufficient signatures:\n'+\ repr(insufficient_signatures) - print(message) + logger.info(message) return - # Verify the top-level roles and print the results. - _print_status_of_top_level_roles(targets_directory, metadata_directory) + # Verify the top-level roles and log the results. + _log_status_of_top_level_roles(targets_directory, metadata_directory) finally: shutil.rmtree(temp_repository_directory, ignore_errors=True) @@ -2579,13 +2588,13 @@ def _generate_and_write_metadata(rolename, metadata_filename, write_partial, -def _print_status_of_top_level_roles(targets_directory, metadata_directory): +def _log_status_of_top_level_roles(targets_directory, metadata_directory): """ - Non-public function that prints whether any of the top-level roles contain an + Non-public function that logs whether any of the top-level roles contain an invalid number of public and private keys, or an insufficient threshold of signatures. Considering that the top-level metadata have to be verified in the expected root -> targets -> snapshot -> timestamp order, this function - prints the error message and returns as soon as a required metadata file is + logs the error message and returns as soon as a required metadata file is found to be invalid. It is assumed here that the delegated roles have been written and verified. Example output: @@ -2614,7 +2623,7 @@ def _print_status_of_top_level_roles(targets_directory, metadata_directory): _check_role_keys(rolename) except tuf.InsufficientKeysError, e: - print(str(e)) + logger.info(str(e)) return # Do the top-level roles contain a valid threshold of signatures? Top-level @@ -2624,13 +2633,13 @@ def _print_status_of_top_level_roles(targets_directory, metadata_directory): signable, root_filename = \ _generate_and_write_metadata('root', root_filename, False, targets_directory, metadata_directory) - _print_status('root', signable) + _log_status('root', signable) # 'tuf.UnsignedMetadataError' raised if metadata contains an invalid threshold - # of signatures. Print the valid/threshold message, where valid < threshold. + # of signatures. log the valid/threshold message, where valid < threshold. except tuf.UnsignedMetadataError, e: signable = e[1] - _print_status('root', signable) + _log_status('root', signable) return # Verify the metadata of the Targets role. @@ -2638,11 +2647,11 @@ def _print_status_of_top_level_roles(targets_directory, metadata_directory): signable, targets_filename = \ _generate_and_write_metadata('targets', targets_filename, False, targets_directory, metadata_directory) - _print_status('targets', signable) + _log_status('targets', signable) except tuf.UnsignedMetadataError, e: signable = e[1] - _print_status('targets', signable) + _log_status('targets', signable) return # Verify the metadata of the snapshot role. @@ -2652,11 +2661,11 @@ def _print_status_of_top_level_roles(targets_directory, metadata_directory): _generate_and_write_metadata('snapshot', snapshot_filename, False, targets_directory, metadata_directory, False, filenames) - _print_status('snapshot', signable) + _log_status('snapshot', signable) except tuf.UnsignedMetadataError, e: signable = e[1] - _print_status('snapshot', signable) + _log_status('snapshot', signable) return # Verify the metadata of the Timestamp role. @@ -2666,19 +2675,19 @@ def _print_status_of_top_level_roles(targets_directory, metadata_directory): _generate_and_write_metadata('timestamp', snapshot_filename, False, targets_directory, metadata_directory, False, filenames) - _print_status('timestamp', signable) + _log_status('timestamp', signable) except tuf.UnsignedMetadataError, e: signable = e[1] - _print_status('timestamp', signable) + _log_status('timestamp', signable) return -def _print_status(rolename, signable): +def _log_status(rolename, signable): """ - Non-public function prints the number of (good/threshold) signatures of + Non-public function logs the number of (good/threshold) signatures of 'rolename'. """ @@ -2686,7 +2695,7 @@ def _print_status(rolename, signable): message = repr(rolename)+' role contains '+ repr(len(status['good_sigs']))+\ ' / '+repr(status['threshold'])+' signatures.' - print(message) + logger.info(message) @@ -2694,7 +2703,7 @@ def _print_status(rolename, signable): def _prompt(message, result_type=str): """ - Non-public function that prompts the user for input by printing 'message', + Non-public function that prompts the user for input by loging 'message', converting the input to 'result_type', and returning the value to the caller. """