From f35de219cc530212c130059a83fa3db1c568128d Mon Sep 17 00:00:00 2001 From: Shikhar Sakhuja Date: Fri, 26 Jan 2018 21:25:17 -0500 Subject: [PATCH 1/4] Working on cleaning code. Trying to solve bug 'connection refused'. Branching off. --- demo/demo_director.py | 71 +++++++++++++++++++++++- demo/demo_image_repo.py | 105 +++++++++++++++++++++++++++++++---- demo/demo_primary.py | 25 ++++++++- demo/demo_secondary.py | 24 +++++++- tests/test_primary.py | 6 +- uptane/__init__.py | 15 ++++- uptane/clients/primary.py | 108 +++++++++++++++++++++++++++++++++++- uptane/clients/secondary.py | 76 ++++++++++++++++++++++++- uptane/formats.py | 4 ++ uptane/services/director.py | 11 +++- 10 files changed, 423 insertions(+), 22 deletions(-) diff --git a/demo/demo_director.py b/demo/demo_director.py index 4a45c91..e61e619 100644 --- a/demo/demo_director.py +++ b/demo/demo_director.py @@ -144,6 +144,21 @@ def clean_slate(use_new_keys=False): print(LOG_PREFIX + 'Signing and hosting initial repository metadata') + add_target_to_director(uptane.WORKING_DIR + '/demo/images/INFO1.0.txt', + 'INFO1.0.txt', vin, 'INFO' + vin, hardware_id='info', release_counter=0) + add_target_to_director(uptane.WORKING_DIR + '/demo/images/TCU1.0.txt', + 'TCU1.0.txt', vin, 'TCU' + vin, hardware_id='tcu', release_counter=0) + add_target_to_director(uptane.WORKING_DIR + '/demo/images/TCU1.1.txt', + 'TCU1.1.txt', vin, 'TCU' + vin, hardware_id='tcu', release_counter=1) + add_target_to_director(uptane.WORKING_DIR + '/demo/images/TCU1.2.txt', + 'TCU1.2.txt', vin, 'TCU' + vin, hardware_id='tcu', release_counter=2) + add_target_to_director(uptane.WORKING_DIR + '/demo/images/BCU1.0.txt', + 'BCU1.0.txt', vin, 'BCU' + vin, hardware_id='bcu', release_counter=0) + add_target_to_director(uptane.WORKING_DIR + '/demo/images/BCU1.1.txt', + 'BCU1.1.txt', vin, 'BCU' + vin, hardware_id='bcu', release_counter=0) + add_target_to_director(uptane.WORKING_DIR + '/demo/images/BCU1.2.txt', + 'BCU1.2.txt', vin, 'BCU' + vin, hardware_id='bcu', release_counter=0) + write_to_live() host() @@ -590,7 +605,8 @@ def undo_sign_with_compromised_keys_attack(vin=None): -def add_target_to_director(target_fname, filepath_in_repo, vin, ecu_serial): +def add_target_to_director(target_fname, filepath_in_repo, vin, ecu_serial, + hardware_id, release_counter): """ For use in attacks and more specific demonstration. @@ -615,6 +631,12 @@ def add_target_to_director(target_fname, filepath_in_repo, vin, ecu_serial): The ECU to assign this target to in the targets metadata. Complies with uptane.formats.ECU_SERIAL_SCHEMA + hardware_id + #TODO + + release_counter + #TODO + """ uptane.formats.VIN_SCHEMA.check_match(vin) uptane.formats.ECU_SERIAL_SCHEMA.check_match(ecu_serial) @@ -640,7 +662,8 @@ def add_target_to_director(target_fname, filepath_in_repo, vin, ecu_serial): # This calls the appropriate vehicle repository. director_service_instance.add_target_for_ecu( - vin, ecu_serial, destination_filepath) + vin, ecu_serial, destination_filepath, hardware_id=hardware_id, + release_counter=release_counter) @@ -1234,3 +1257,47 @@ def kill_server(): str(repo_server_process.pid)) repo_server_process.kill() repo_server_process = None + + + + +def image_rollback_attack(firmware_fname, ecu_serial = '22222', + vin = '111', release_counter = 0, hardware_id = "SecondaryECU101"): + """ + Tries to install an image with a lower release counter on the ecu. Should be stopped. + Default release counter of our ECUs is set to 1. + """ + print("ATTACK: IMAGE ROLLBACK ATTACK, an attempt to install a firmware with lower release counter than that of the ECU") + filepath_in_repo = firmware_fname + add_target_to_director(firmware_fname, filepath_in_repo, vin, ecu_serial, hardware_id, release_counter) + write_to_live(vin_to_update = vin) + + + + + +def confused_bundle_attack(firmware_fname, ecu_serial = '22222', vin = '111', release_counter = 0, hardware_id = "SecondaryECU101"): + """ + Assumes a compromised director. + Tries to install images with release counters that don't match the other image repositories. + """ + print("ATTACK: confused_bundle_attack, " + "an attempt to install a compromised image with a " + "release_counter that doesn't match that of other repositories.") + filepath_in_repo = firmware_fname + add_target_to_director(firmware_fname, filepath_in_repo, vin, ecu_serial, hardware_id, release_counter) + write_to_live(vin_to_update = vin) + + + + + +def sneaky_director_attack(firmware_fname, ecu_serial = '22222', vin = '111', release_counter = 3, hardware_id = "NotARealSecondaryECU101"): + """ + Assumes a compromised director. + Tries to install an image on an ECU that is not meant for that particular ECU through leveraging the ECU serial. + """ + print("ATTACK: SNEAKY DIRECTOR ATTACK. Tries to install an image on an ECU that is not meant for that particular ECU through leveraging the ECU serial.") + filepath_in_repo = firmware_fname + add_target_to_director(firmware_fname, filepath_in_repo, vin, ecu_serial, hardware_id, release_counter) + write_to_live(vin_to_update = vin) \ No newline at end of file diff --git a/demo/demo_image_repo.py b/demo/demo_image_repo.py index 53c3195..80f15cd 100644 --- a/demo/demo_image_repo.py +++ b/demo/demo_image_repo.py @@ -131,14 +131,20 @@ def clean_slate(use_new_keys=False): # Add some starting image files, primarily for use with the web frontend. - add_target_to_imagerepo('demo/images/INFO1.0.txt', 'INFO1.0.txt') - add_target_to_imagerepo('demo/images/TCU1.0.txt', 'TCU1.0.txt') - add_target_to_imagerepo('demo/images/TCU1.1.txt', 'TCU1.1.txt') - add_target_to_imagerepo('demo/images/TCU1.2.txt', 'TCU1.2.txt') - add_target_to_imagerepo('demo/images/BCU1.0.txt', 'BCU1.0.txt') - add_target_to_imagerepo('demo/images/BCU1.1.txt', 'BCU1.1.txt') - add_target_to_imagerepo('demo/images/BCU1.2.txt', 'BCU1.2.txt') - + add_target_to_imagerepo('demo/images/INFO1.0.txt', 'INFO1.0.txt', + hardware_id='info', release_counter=0) + add_target_to_imagerepo('demo/images/TCU1.0.txt', 'TCU1.0.txt', + hardware_id='tcu', release_counter=0) + add_target_to_imagerepo('demo/images/TCU1.1.txt', 'TCU1.1.txt', + hardware_id='tcu', release_counter=1) + add_target_to_imagerepo('demo/images/TCU1.2.txt', 'TCU1.2.txt', + hardware_id='tcu', release_counter=2) + add_target_to_imagerepo('demo/images/BCU1.0.txt', 'BCU1.0.txt', + hardware_id='bcu', release_counter=0) + add_target_to_imagerepo('demo/images/BCU1.1.txt', 'BCU1.1.txt', + hardware_id='bcu', release_counter=0) + add_target_to_imagerepo('demo/images/BCU1.2.txt', 'BCU1.2.txt', + hardware_id='bcu', release_counter=0) print(LOG_PREFIX + 'Signing and hosting initial repository metadata') @@ -172,7 +178,8 @@ def write_to_live(): -def add_target_to_imagerepo(target_fname, filepath_in_repo): +def add_target_to_imagerepo(target_fname, filepath_in_repo, hardware_id, + release_counter): """ For use in attacks and more specific demonstration. @@ -191,7 +198,21 @@ def add_target_to_imagerepo(target_fname, filepath_in_repo): This is the name that will identify the file in the repository, and the filepath it will have relative to the root of the repository's targets directory. - """ + + hardware_id + A unique identifier for an ECU through it's hardware ID. + Conforms to uptane.formats.HARDWARE_ID_SCHEMA. + This is used to prevent a compromised director + from causing an ECU to download an image not intended for it. + + release_counter + An integer to track the version number of the image installed. + Conforms to uptane.formats.RELEASE_COUNTER_SCHEMA. + This is used to prevent a compromised director from + causing an ECU to download an outdated image or an older + one with known vulnerabilities. + + """ global repo tuf.formats.RELPATH_SCHEMA.check_match(target_fname) @@ -203,6 +224,14 @@ def add_target_to_imagerepo(target_fname, filepath_in_repo): shutil.copy(target_fname, destination_filepath) + custom = {} + custom['hardware_id'] = hardware_id + custom['release_counter'] = release_counter + + # If custom is empty, pass None, which is what TUF expects instead of {}. + repo.targets.add_target( + destination_filepath, custom=custom if custom else None) + repo.targets.add_target(destination_filepath) @@ -472,6 +501,62 @@ def undo_keyed_arbitrary_package_attack(target_filepath): +def image_rollback_attack(firmware_fname, release_counter = 0, + hardware_id = "SecondaryPotato101"): + """ + Assumes a compromised director. + Tries to install an image with a lower release counter on the ecu. Should be stopped. + Default release counter of our ECUs is set to 1. + """ + print("ATTACK: IMAGE ROLLBACK ATTACK, an attempt to install a firmware " + "with lower release counter than that of the ECU") + filepath_in_repo = firmware_fname + open(firmware_fname, 'w').write('Fresh firmware image') + add_target_to_imagerepo(firmware_fname, filepath_in_repo, release_counter, hardware_id) + write_to_live() + + + + + +def confused_bundle_attack(firmware_fname, release_counter = 3, + hardware_id = "SecondaryPotato101"): + """ + Assumes a compromised director. + Tries to install images with release counters that don't match the other + image repositories. + """ + print("ATTACK: confused_bundle_attack, an attempt to install a compromised " + "image with a release_counter that doesn't match that of other " + "repositories.") + filepath_in_repo = firmware_fname + open(firmware_fname, 'w').write('Fresh firmware image') + add_target_to_imagerepo(firmware_fname, filepath_in_repo, release_counter, hardware_id) + write_to_live() + + + + + +def sneaky_director_attack(firmware_fname, release_counter = 3, + hardware_id = "SecondaryPotato101"): + """ + Assumes a compromised director. + Tries to install an image on an ECU that is not meant + for that particular ECU through its ECU. + """ + print("ATTACK: SNEAKY DIRECTOR ATTACK. Tries to install an image on an ECU " + "that is not meant for that particular ECU through " + "leveraging the ECU serial.") + filepath_in_repo = firmware_fname + open(firmware_fname, 'w').write('Fresh firmware image') + add_target_to_imagerepo(firmware_fname, filepath_in_repo, release_counter, hardware_id) + write_to_live() + + + + + def add_target_and_write_to_live(filename, file_content): """ High-level version of add_target_to_imagerepo() that creates the target diff --git a/demo/demo_primary.py b/demo/demo_primary.py index 1ae9158..bb6031e 100644 --- a/demo/demo_primary.py +++ b/demo/demo_primary.py @@ -63,6 +63,8 @@ #_client_directory_name = 'temp_primary' # name for this Primary's directory _vin = 'democar' _ecu_serial = 'INFOdemocar' +_hardware_id = 'Infotainment101' +_release_counter = 0 # firmware_filename = 'infotainment_firmware.txt' @@ -80,16 +82,22 @@ def clean_slate( use_new_keys=False, # client_directory_name=None, vin=_vin, - ecu_serial=_ecu_serial): + ecu_serial=_ecu_serial, + hardware_id=_hardware_id, + release_counter =_release_counter): """ """ global primary_ecu global CLIENT_DIRECTORY global _vin global _ecu_serial + global _hardware_id + global _release_counter global listener_thread _vin = vin _ecu_serial = ecu_serial + _release_counter = release_counter + _hardware_id = hardware_id # if client_directory_name is not None: # CLIENT_DIRECTORY = client_directory_name @@ -139,6 +147,8 @@ def clean_slate( vin=_vin, ecu_serial=_ecu_serial, primary_key=ecu_key, + hardware_id = _hardware_id, + release_counter = _release_counter, time=clock, timeserver_public_key=key_timeserver_pub) @@ -306,6 +316,19 @@ def update_cycle(): else: raise + except uptane.ImageRollBack: + print_banner(BANNER_DEFENDED, color=WHITE+DARK_BLUE_BG, + text='The Director has instructed us to download an image' + ' that has a bad release counter and does not match with ' + ' other repositories. This image has' + ' been rejected.', sound=TADA) + except uptane.HardwareIDMismatch: + print_banner(BANNER_DEFENDED, color=WHITE+DARK_BLUE_BG, + text='The Director has instructed us to download an image' + ' that is not meant for the stated ECU. HardwareIDdoes not' + ' match with other repositorie. This image has' + ' been rejected.', sound=TADA) + # All targets have now been downloaded. diff --git a/demo/demo_secondary.py b/demo/demo_secondary.py index bba2027..073a4ec 100644 --- a/demo/demo_secondary.py +++ b/demo/demo_secondary.py @@ -61,6 +61,8 @@ CLIENT_DIRECTORY = None _vin = 'democar' _ecu_serial = 'TCUdemocar' +_hardware_id = 'SecondaryInfotainment111' +_release_counter = 0 _primary_host = demo.PRIMARY_SERVER_HOST _primary_port = demo.PRIMARY_SERVER_DEFAULT_PORT firmware_filename = 'secondary_firmware.txt' @@ -79,7 +81,9 @@ def clean_slate( vin=_vin, ecu_serial=_ecu_serial, primary_host=None, - primary_port=None): + primary_port=None, + hardware_id=_hardware_id, + release_counter=_release_counter): """ """ @@ -91,6 +95,8 @@ def clean_slate( global nonce global CLIENT_DIRECTORY global attacks_detected + global _hardware_id + global _release_counter _vin = vin _ecu_serial = ecu_serial @@ -152,6 +158,8 @@ def clean_slate( vin=_vin, ecu_serial=_ecu_serial, ecu_key=ecu_key, + hardware_id = _hardware_id, + release_counter= _release_counter, time=clock, firmware_fileinfo=factory_firmware_fileinfo, timeserver_public_key=key_timeserver_pub) @@ -329,8 +337,16 @@ def update_cycle(): # Now tell the Secondary reference implementation code where the archive file # is and let it expand and validate the metadata. - secondary_ecu.process_metadata(archive_fname) - + try: + secondary_ecu.process_metadata(archive_fname) + except uptane.ImageRollBack: + print_banner(BANNER_DEFENDED, color=WHITE+DARK_BLUE_BG, + text='The Director has instructed us to download an image' + ' that has a lower release counter. This image has' + ' been rejected.', sound=TADA) + generate_signed_ecu_manifest() + submit_ecu_manifest_to_primary() + return # As part of the process_metadata call, the secondary will have saved # validated target info for targets intended for it in @@ -483,6 +499,8 @@ def update_cycle(): # 2. Set the fileinfo in the secondary_ecu object to the target info for the # new firmware. secondary_ecu.firmware_fileinfo = expected_target_info + secondary_ecu.update_release_counter( + expected_target_info['fileinfo']['custom']['release_counter']) with open(current_firmware_filepath, 'rb') as file_object: diff --git a/tests/test_primary.py b/tests/test_primary.py index de8161e..78f6485 100644 --- a/tests/test_primary.py +++ b/tests/test_primary.py @@ -11,6 +11,7 @@ from __future__ import unicode_literals import uptane # Import before TUF modules; may change tuf.conf values. +from io import open # To add support for Python 2 import unittest import os.path @@ -59,7 +60,8 @@ NONCE = 5 VIN = 'democar' PRIMARY_ECU_SERIAL = '00000' - +PRIMARY_RELEASE_COUNTER = 1 +PRIMARY_HARDWARE_ID = 'Infotainment101' def destroy_temp_dir(): @@ -115,6 +117,8 @@ def setUpClass(cls): int(time.time())).isoformat() + 'Z' tuf.formats.ISO8601_DATETIME_SCHEMA.check_match(cls.initial_time) + + diff --git a/uptane/__init__.py b/uptane/__init__.py index 176e4bf..a907dc4 100644 --- a/uptane/__init__.py +++ b/uptane/__init__.py @@ -12,7 +12,7 @@ # Configure TUF to use DER format instead of Python dictionaries / JSON. import tuf.conf -tuf.conf.METADATA_FORMAT = 'der' +tuf.conf.METADATA_FORMAT = 'json' # FIXME: I actually think other modules rely on the `os` imported here and # not just for getcwd @@ -75,6 +75,19 @@ class FailedToEncodeASN1DER(Error): """ pass +class HardwareIDMismatch(Error): + """ + Received an image to install by director that doesn't match HardwareID of the + ECU. + """ + pass + +class ImageRollBack(Error): + """ + Received an image to download with the Release Counter value lower than + the one already installed. + """ + pass # Logging configuration diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index 9a3d9ec..218d053 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -131,6 +131,14 @@ class Primary(object): # Consider inheriting from Secondary and refactoring. The list of nonces sent to us from Secondaries and not yet sent to the Timeserver. + self.primary_exceptions + A dictionary to track errors ECU encounter while trying to install + images. The mapping is in the following format: + Image : ECU : Error + Example ==> + {'/BCU1.1.txt': {'BCUdemocar': }, + '/BCU1.2.txt': {'BCUdemocar': }} + self.nonces_sent: The list of nonces sent to the Timeserver by our Secondaries, which we have already sent to the Timeserver. Will be checked against the @@ -223,6 +231,8 @@ def __init__( primary_key, time, timeserver_public_key, + hardware_id, + release_counter, my_secondaries=[]): """ @@ -269,6 +279,8 @@ def __init__( tuf.formats.ISO8601_DATETIME_SCHEMA.check_match(time) uptane.formats.VIN_SCHEMA.check_match(vin) uptane.formats.ECU_SERIAL_SCHEMA.check_match(ecu_serial) + uptane.formats.HARDWARE_ID_SCHEMA.check_match(hardware_id) + uptane.formats.RELEASE_COUNTER_SCHEMA.check_match(release_counter) tuf.formats.ANYKEY_SCHEMA.check_match(timeserver_public_key) tuf.formats.ANYKEY_SCHEMA.check_match(primary_key) # TODO: Should also check that primary_key is a private key, not a @@ -283,6 +295,7 @@ def __init__( self.primary_key = primary_key self.my_secondaries = my_secondaries self.director_repo_name = director_repo_name + self.primary_exceptions = dict() self.temp_full_metadata_archive_fname = os.path.join( full_client_dir, 'metadata', 'temp_full_metadata_archive.zip') @@ -444,6 +457,53 @@ def get_validated_target_info(self, target_filepath): 'to allow some targets to validate without Director approval, or is' 'the wrong repository specified as the Director repository in the ' 'initialization of this primary object?') + director_target = validated_target_info[self.director_repo_name] + temp_release_counter = None + temp_hardware_id = None + + for repository_name in validated_target_info.keys(): + current_target = validated_target_info[repository_name] + + if 'custom' not in validated_target_info[repository_name]['fileinfo']: + raise uptane.Error('{} repo failed to include the custom field in a \ + target. \nTarget metadata was: {}'.format(repository_name, \ + repr(current_target))) + continue + + custom_target_metadata = \ + validated_target_info[repository_name]['fileinfo']['custom'] + current_target_hardware_id = custom_target_metadata['hardware_id'] + current_target_release_counter = custom_target_metadata['release_counter'] + if 'hardware_id' in custom_target_metadata: + if temp_hardware_id == None: + temp_hardware_id = current_target_hardware_id + elif temp_hardware_id != current_target_hardware_id: + raise uptane.HardwareIDMismatch('Bad value for the field \ + hardware_ID in the that did not correspond the value in \ + the other repos. Value did not match between the director \ + and the other repos. The value of director target is {}'.format( + repr(director_target))) + continue + else: + raise uptane.Error('{} repo failed to include the hardware ID field \ + in the custom field of the target. \nTarget metadata was:\ + {}'.format(repository_name, repr(current_target))) + continue + + if 'release_counter' in custom_target_metadata: + if temp_release_counter == None: + temp_release_counter = current_target_release_counter + elif temp_release_counter != current_target_release_counter: + raise uptane.ImageRollBack('Bad value for the field release_counter \ + that did not correspond the value in the other repos. Value \ + did not match between the director and the other repos. \ + The value of director target is {}'.format(repr(director_target))) + continue + else: + raise uptane.Error('{} repo failed to include the release counter \ + field in the custom field of the target. \nTarget metadata was:\ + {}'.format(repository_name, repr(current_target))) + continue # Defensive coding: this should already have been checked. tuf.formats.TARGETFILE_SCHEMA.check_match( @@ -515,7 +575,11 @@ def primary_update_cycle(self): # This will contain a list of tuf.formats.TARGETFILE_SCHEMA objects. verified_targets = [] for targetinfo in directed_targets: - target_filepath = targetinfo['filepath'] + target_filepath = targetinfo['filepath'].encode( + 'ascii', 'ignore') + target_ecu = \ + ['fileinfo']['custom']['ecu_serial'].encode( + 'ascii', 'ignore') try: # targetinfos = self.get_validated_target_info(target_filepath) # for repo in targetinfos: @@ -532,6 +596,9 @@ def primary_update_cycle(self): 'untrustworthy Image Repository, or the Director and Image ' 'Repository may be out of sync.' + ENDCOLORS) + self.log_primary_exceptions( + target_filepath, target_ecu, tuf.UnknownTargetError) + # If running the demo, display splash banner indicating the rejection. # This clause should be pulled out of the reference implementation when # possible. @@ -542,6 +609,33 @@ def primary_update_cycle(self): 'File: ' + repr(target_filepath), sound=TADA) time.sleep(3) + except uptane.HardwareIDMismatch: + log.warning(RED + 'Dorector has instructed us to download a target (' + + target_filepath + ') that is not validated by the combination of ' + 'Image + Director Repositories. That update IS BEING SKIPPED. It ' + 'the hardware IDs do not match between image and director ' + 'repositories. Try again, but if this happens often, you may be ' + 'connecting to an untrustworthy Director, or there may be an ' + 'untrustworthy Image Repository, or the Director and Image ' + 'Repository may be out of sync.' + ENDCOLORS) + + self.log_primary_exceptions( + target_filepath, target_ecu, uptane.HardwareIDMismatch) + + except uptane.ImageRollBack: + log.warning(RED + 'Dorector has instructed us to download a target (' + + target_filepath + ') that is not validated by the combination of ' + 'Image + Director Repositories. That update IS BEING SKIPPED. It ' + 'the release counters do not match between image and director ' + 'repositories. Try again, but if this happens often, you may be ' + 'connecting to an untrustworthy Director, or there may be an ' + 'untrustworthy Image Repository, or the Director and Image ' + 'Repository may be out of sync.' + ENDCOLORS) + + self.log_primary_exceptions( + target_filepath, target_ecu, uptane.ImageRollBack) + + # # Grab a filepath from each of the dicts of target file infos. (Each dict # # corresponds to one file, and the filepaths in all the infos in that dict @@ -575,6 +669,8 @@ def primary_update_cycle(self): # Get the ECU Serial listed in the custom file data. assigned_ecu_serial = target['fileinfo']['custom']['ecu_serial'] + # Checking the formats + uptane.formats.ECU_SERIAL_SCHEMA.check_match(assigned_ecu_serial) # Make sure it's actually an ECU we know about. if assigned_ecu_serial not in self.my_secondaries: @@ -695,6 +791,16 @@ def primary_update_cycle(self): + def log_primary_exceptions(self, image_name, ecu_serial, error): + """ + Adds errors and exceptions handled by the primary. + """ + self.primary_exceptions[image_name] = {ecu_serial:error} + + + + + def get_image_fname_for_ecu(self, ecu_serial): """ Given an ECU serial, returns: diff --git a/uptane/clients/secondary.py b/uptane/clients/secondary.py index 534df1f..bf56310 100644 --- a/uptane/clients/secondary.py +++ b/uptane/clients/secondary.py @@ -80,6 +80,23 @@ class Secondary(object): corresponding public key, so that it can validate these ECU Manifests. Conforms to tuf.formats.ANYKEY_SCHEMA. + self.hardware_id + An identifier for a group of ECUs that helps ensure the installation + of the right firmware is installed. + Verified for installation if the value matches both in the + Image Repository and Director Repository. + Conforms to uptane.formats.HARDWARE_ID_SCHEMA. + This is used to prevent a compromised director from causing an + ECU to download an image not intended for it. + + self.release_counter + An integer value to track the version number of the images installed. + Ensures that an older image than the one currently installed is + not installed. + Conforms to uptane.formats.RELEASE_COUNTER_SCHEMA. + This is used to prevent a compromised director from causing an ECU + to download an outdated image or an older one with known vulnerabilities. + self.updater: A tuf.client.updater.Updater object used to retrieve metadata and target files from the Director and Image repositories. @@ -173,6 +190,8 @@ def __init__( ecu_key, time, timeserver_public_key, + hardware_id, + release_counter=0, firmware_fileinfo=None, director_public_key=None, partial_verifying=False): @@ -230,6 +249,8 @@ def __init__( tuf.formats.PATH_SCHEMA.check_match(director_repo_name) uptane.formats.VIN_SCHEMA.check_match(vin) uptane.formats.ECU_SERIAL_SCHEMA.check_match(ecu_serial) + uptane.formats.HARDWARE_ID_SCHEMA.check_match(hardware_id) + uptane.formats.RELEASE_COUNTER_SCHEMA.check_match(release_counter) tuf.formats.ISO8601_DATETIME_SCHEMA.check_match(time) tuf.formats.ANYKEY_SCHEMA.check_match(timeserver_public_key) tuf.formats.ANYKEY_SCHEMA.check_match(ecu_key) @@ -246,6 +267,8 @@ def __init__( self.director_public_key = director_public_key self.partial_verifying = partial_verifying self.firmware_fileinfo = firmware_fileinfo + self.hardware_id = hardware_id + self.release_counter = release_counter if not self.partial_verifying and self.director_public_key is not None: raise uptane.Error('Secondary not set as partial verifying, but a director ' # TODO: Choose error class. @@ -338,6 +361,8 @@ def generate_signed_ecu_manifest(self, description_of_attacks_observed=''): # First, construct and check an ECU_VERSION_MANIFEST_SCHEMA. ecu_manifest = { 'ecu_serial': self.ecu_serial, + 'hardware_id': self.hardware_id, + 'release_counter': self.release_counter, 'installed_image': self.firmware_fileinfo, 'timeserver_time': self.all_valid_timeserver_times[-1], 'previous_timeserver_time': self.all_valid_timeserver_times[-2], @@ -494,9 +519,44 @@ def fully_validate_metadata(self): # Ignore target info not marked as being for this ECU. if 'custom' not in target['fileinfo'] or \ 'ecu_serial' not in target['fileinfo']['custom'] or \ + 'hardware_id' not in target['fileinfo']['custom'] or \ + 'release_counter' not in target['fileinfo']['custom'] or \ self.ecu_serial != target['fileinfo']['custom']['ecu_serial']: continue + elif self.hardware_id != \ + target['fileinfo']['custom']['hardware_id']: + + log.warning(RED + 'Received a target from the Director with \ + instructions to install an Image on self with ECU_Serial {} \ + with mismatching hardwareID. Diregarding/not downloading \ + target for saving. The target is {}'.format(self.ecu_serial, \ + repr(target))+ ENDCOLORS) + + raise uptane.HardwareIDMismatch("The director has instructed the ECU \ + to download an image the hardware_id of which does not match that \ + of the ecu. ImageRepo Value was {}. Director value is {} \ + Image rejected".format(self.hardware_id, \ + target['fileinfo']['custom']['hardware_id'])) + continue + + elif self.release_counter > \ + target['fileinfo']['custom']['release_counter']: + + log.warning(RED + 'Received a target from the Director \ + with instructions to install an Image {} on self with \ + ECU_Serial {} with lower value of release counter than current. \ + Diregarding/not downloading target for saving. \ + The target is {}'.format(target['filepath'], self.ecu_serial,\ + repr(target))+ ENDCOLORS) + + raise uptane.ImageRollBack("The director has instructed the ECU \ + to download an image that has a lower relase counter than \ + the current. Original Value was {}. New value is {} \ + Image rejected".format(self.release_counter, \ + target['fileinfo']['custom']['release_counter'])) + continue + # Fully validate the target info for our target(s). try: validated_targets_for_this_ecu.append( @@ -515,6 +575,13 @@ def fully_validate_metadata(self): + def update_release_counter(self, new_release_counter_val): + self.release_counter = new_release_counter_val + + + + + def get_validated_target_info(self, target_filepath): """ COPIED EXACTLY, MINUS COMMENTS, from primary.py. @@ -557,7 +624,14 @@ def process_metadata(self, metadata_archive_fname): self._expand_metadata_archive(metadata_archive_fname) # This entails using the local metadata files as a repository. - self.fully_validate_metadata() + try: + self.fully_validate_metadata() + except uptane.ImageRollBack: + log.warning(RED + "Image rollback attack detected. Conitnuing" + ENDCOLORS) + except uptane.HardwareIDMismatch: + log.warning(RED + "HardwareID Mismatch attack detected. Conitnuing" + ENDCOLORS) + + diff --git a/uptane/formats.py b/uptane/formats.py index 73fd8cb..76cd71b 100644 --- a/uptane/formats.py +++ b/uptane/formats.py @@ -35,6 +35,10 @@ # vin = VIN_SCHEMA) ECU_SERIAL_SCHEMA = SCHEMA.AnyString() # Instead, for now, we'll go with an ecu serial number. +HARDWARE_ID_SCHEMA = SCHEMA.AnyString() + +RELEASE_COUNTER_SCHEMA = SCHEMA.Integer(lo=0) + # Information specifying the target(s) installed on a given ECU. # This object corresponds to not "ECUVersionManifest" in the Uptane diff --git a/uptane/services/director.py b/uptane/services/director.py index 9a1e609..42ba7d5 100644 --- a/uptane/services/director.py +++ b/uptane/services/director.py @@ -522,7 +522,8 @@ def create_director_repo_for_vehicle(self, vin): - def add_target_for_ecu(self, vin, ecu_serial, target_filepath): + def add_target_for_ecu(self, vin, ecu_serial, target_filepath, + hardware_id=None, release_counter=None): """ Add a target to the repository for a vehicle, marked as being for a specific ECU. @@ -546,5 +547,11 @@ def add_target_for_ecu(self, vin, ecu_serial, target_filepath): # raise uptane.UnknownECU('The ECU Serial provided, ' + repr(ecu_serial) + # ' is not that of an ECU known to this Director.') + custom = {'ecu_serial': ecu_serial} + if hardware_id is not None: + custom['hardware_id'] = hardware_id + if release_counter is not None: + custom['release_counter'] = release_counter + self.vehicle_repositories[vin].targets.add_target( - target_filepath, custom={'ecu_serial': ecu_serial}) + target_filepath, custom=custom) From fcf25d911874e4e1005f66abfc3d98a2779e2866 Mon Sep 17 00:00:00 2001 From: Shikhar Sakhuja Date: Fri, 26 Jan 2018 21:52:32 -0500 Subject: [PATCH 2/4] Fixed typo that caused bug in primary --- uptane/clients/primary.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index 218d053..db421e6 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -577,8 +577,7 @@ def primary_update_cycle(self): for targetinfo in directed_targets: target_filepath = targetinfo['filepath'].encode( 'ascii', 'ignore') - target_ecu = \ - ['fileinfo']['custom']['ecu_serial'].encode( + target_ecu = targetinfo['fileinfo']['custom']['ecu_serial'].encode( 'ascii', 'ignore') try: # targetinfos = self.get_validated_target_info(target_filepath) From ca0828365d6b645fa88c55e6075a79783fde56b6 Mon Sep 17 00:00:00 2001 From: Shikhar Sakhuja Date: Sat, 27 Jan 2018 01:28:39 -0500 Subject: [PATCH 3/4] Added samples --- sample_ecu_manifest.json | 32 +++++++++++++++++++ sample_timeserver_attestation.der | Bin 0 -> 140 bytes sample_timeserver_attestation.json | 15 +++++++++ sample_vehicle_manifest.json | 49 +++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 sample_ecu_manifest.json create mode 100644 sample_timeserver_attestation.der create mode 100644 sample_timeserver_attestation.json create mode 100644 sample_vehicle_manifest.json diff --git a/sample_ecu_manifest.json b/sample_ecu_manifest.json new file mode 100644 index 0000000..934ef47 --- /dev/null +++ b/sample_ecu_manifest.json @@ -0,0 +1,32 @@ +{ + "signatures": [ + { + "keyid": "49309f114b857e4b29bfbff1c1c75df59f154fbc45539b2eb30c8a867843b2cb", + "method": "ed25519", + "sig": "15c5d2aa2dd6160711d8113f295d8f49710fe5678f67dccb6b275d5d6655b1758df577089b94dfeea93e77b487848502dabc7cf05eaad2aaa312cb301526ff0c" + } + ], + "signed": { + "attacks_detected": "", + "ecu_serial": "TCUdemocar", + "hardware_id": "SecondaryInfotainment111", + "installed_image": { + "fileinfo": { + "custom": { + "ecu_serial": "TCUdemocar", + "hardware_id": "SecondaryInfotainment111", + "release_counter": 2 + }, + "hashes": { + "sha256": "daeec2555599b8e7a82b6f1339d5f419346b57a0eb7a39ee6334b8f205595752", + "sha512": "1570937a84e9e74e35f5e56a8f8518c91e18258cffc8ace249d9acf173d1845d82002583b1b53373b79edf52494d524f2619f5f1896f3085038deca92c950486" + }, + "length": 20 + }, + "filepath": "/firmware0111.img" + }, + "previous_timeserver_time": "2018-01-27T06:16:16Z", + "release_counter": 2, + "timeserver_time": "2018-01-27T06:16:16Z" + } +} \ No newline at end of file diff --git a/sample_timeserver_attestation.der b/sample_timeserver_attestation.der new file mode 100644 index 0000000000000000000000000000000000000000..d717f505729a6f31021fd9b60f5b28d2ba6d3932 GIT binary patch literal 140 zcmV;70CWE^fr+3MfB^%c3IYKF0s#pE0sxnS1X^qpoq+)XqHQp1pdx`FdB>L5=yQqD z>#YRs&Pn?3VI32mgn9|FZla`G4pl$$hS3hY0 literal 0 HcmV?d00001 diff --git a/sample_timeserver_attestation.json b/sample_timeserver_attestation.json new file mode 100644 index 0000000..d1daaa0 --- /dev/null +++ b/sample_timeserver_attestation.json @@ -0,0 +1,15 @@ +{ + "signatures": [ + { + "keyid": "79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e", + "method": "ed25519", + "sig": "634d5fbe1a30f9e00990c58b599f5290b0c88e30c069c22eb783d1cbfdc75e8276dc4505e5980715db6a02cbfd71716953c9c205e385c3f4ddb26f770f5b4d05" + } + ], + "signed": { + "nonces": [ + 2055631847 + ], + "time": "2018-01-27T06:15:29Z" + } +} \ No newline at end of file diff --git a/sample_vehicle_manifest.json b/sample_vehicle_manifest.json new file mode 100644 index 0000000..0f407c8 --- /dev/null +++ b/sample_vehicle_manifest.json @@ -0,0 +1,49 @@ +{ + "signatures": [ + { + "keyid": "9a406d99e362e7c93e7acfe1e4d6585221315be817f350c026bbee84ada260da", + "method": "ed25519", + "sig": "5224f791dd198952c7df5ae35647a2bcef1db498197795668d6420d62f0029b7f49d84c6eb2abd3da1321a33e32f1222f54815872fc92f0202e9c59cc3c07400" + } + ], + "signed": { + "ecu_version_manifests": { + "TCUdemocar": [ + { + "signatures": [ + { + "keyid": "49309f114b857e4b29bfbff1c1c75df59f154fbc45539b2eb30c8a867843b2cb", + "method": "ed25519", + "sig": "6532a2ef1f7fa83927dc41acbceb153592d005c2d9b6fcf33d87bca15e949c037070ba75635e0ab30385a3538b1d2beaddf400033f32b89d1dcc0f9b02744c0d" + } + ], + "signed": { + "attacks_detected": "", + "ecu_serial": "TCUdemocar", + "hardware_id": "SecondaryInfotainment111", + "installed_image": { + "fileinfo": { + "custom": { + "ecu_serial": "TCUdemocar", + "hardware_id": "SecondaryInfotainment111", + "release_counter": 2 + }, + "hashes": { + "sha256": "daeec2555599b8e7a82b6f1339d5f419346b57a0eb7a39ee6334b8f205595752", + "sha512": "1570937a84e9e74e35f5e56a8f8518c91e18258cffc8ace249d9acf173d1845d82002583b1b53373b79edf52494d524f2619f5f1896f3085038deca92c950486" + }, + "length": 20 + }, + "filepath": "/firmware011.img" + }, + "previous_timeserver_time": "2018-01-27T05:52:57Z", + "release_counter": 2, + "timeserver_time": "2018-01-27T05:57:49Z" + } + } + ] + }, + "primary_ecu_serial": "INFOdemocar", + "vin": "democar" + } +} \ No newline at end of file From 45247acee9314da0eb6fb7086e2f95d9cfde0d1a Mon Sep 17 00:00:00 2001 From: Shikhar Sakhuja Date: Sat, 27 Jan 2018 03:16:01 -0500 Subject: [PATCH 4/4] Changed test_director for new vehicle version manifest sample to correspond to new ECU names. --- sample_ecu_manifest.json | 32 --------- sample_timeserver_attestation.der | Bin 140 -> 0 bytes sample_timeserver_attestation.json | 15 ----- sample_vehicle_manifest.json | 49 -------------- samples/sample_ecu_manifest.json | 21 ++++-- samples/sample_timeserver_attestation.der | Bin 136 -> 140 bytes samples/sample_timeserver_attestation.json | 7 +- samples/sample_vehicle_manifest.json | 23 ++++--- tests/test_director.py | 74 +++++++++++++-------- 9 files changed, 80 insertions(+), 141 deletions(-) delete mode 100644 sample_ecu_manifest.json delete mode 100644 sample_timeserver_attestation.der delete mode 100644 sample_timeserver_attestation.json delete mode 100644 sample_vehicle_manifest.json diff --git a/sample_ecu_manifest.json b/sample_ecu_manifest.json deleted file mode 100644 index 934ef47..0000000 --- a/sample_ecu_manifest.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "signatures": [ - { - "keyid": "49309f114b857e4b29bfbff1c1c75df59f154fbc45539b2eb30c8a867843b2cb", - "method": "ed25519", - "sig": "15c5d2aa2dd6160711d8113f295d8f49710fe5678f67dccb6b275d5d6655b1758df577089b94dfeea93e77b487848502dabc7cf05eaad2aaa312cb301526ff0c" - } - ], - "signed": { - "attacks_detected": "", - "ecu_serial": "TCUdemocar", - "hardware_id": "SecondaryInfotainment111", - "installed_image": { - "fileinfo": { - "custom": { - "ecu_serial": "TCUdemocar", - "hardware_id": "SecondaryInfotainment111", - "release_counter": 2 - }, - "hashes": { - "sha256": "daeec2555599b8e7a82b6f1339d5f419346b57a0eb7a39ee6334b8f205595752", - "sha512": "1570937a84e9e74e35f5e56a8f8518c91e18258cffc8ace249d9acf173d1845d82002583b1b53373b79edf52494d524f2619f5f1896f3085038deca92c950486" - }, - "length": 20 - }, - "filepath": "/firmware0111.img" - }, - "previous_timeserver_time": "2018-01-27T06:16:16Z", - "release_counter": 2, - "timeserver_time": "2018-01-27T06:16:16Z" - } -} \ No newline at end of file diff --git a/sample_timeserver_attestation.der b/sample_timeserver_attestation.der deleted file mode 100644 index d717f505729a6f31021fd9b60f5b28d2ba6d3932..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140 zcmV;70CWE^fr+3MfB^%c3IYKF0s#pE0sxnS1X^qpoq+)XqHQp1pdx`FdB>L5=yQqD z>#YRs&Pn?3VI32mgn9|FZla`G4pl$$hS3hY0 diff --git a/sample_timeserver_attestation.json b/sample_timeserver_attestation.json deleted file mode 100644 index d1daaa0..0000000 --- a/sample_timeserver_attestation.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "signatures": [ - { - "keyid": "79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e", - "method": "ed25519", - "sig": "634d5fbe1a30f9e00990c58b599f5290b0c88e30c069c22eb783d1cbfdc75e8276dc4505e5980715db6a02cbfd71716953c9c205e385c3f4ddb26f770f5b4d05" - } - ], - "signed": { - "nonces": [ - 2055631847 - ], - "time": "2018-01-27T06:15:29Z" - } -} \ No newline at end of file diff --git a/sample_vehicle_manifest.json b/sample_vehicle_manifest.json deleted file mode 100644 index 0f407c8..0000000 --- a/sample_vehicle_manifest.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "signatures": [ - { - "keyid": "9a406d99e362e7c93e7acfe1e4d6585221315be817f350c026bbee84ada260da", - "method": "ed25519", - "sig": "5224f791dd198952c7df5ae35647a2bcef1db498197795668d6420d62f0029b7f49d84c6eb2abd3da1321a33e32f1222f54815872fc92f0202e9c59cc3c07400" - } - ], - "signed": { - "ecu_version_manifests": { - "TCUdemocar": [ - { - "signatures": [ - { - "keyid": "49309f114b857e4b29bfbff1c1c75df59f154fbc45539b2eb30c8a867843b2cb", - "method": "ed25519", - "sig": "6532a2ef1f7fa83927dc41acbceb153592d005c2d9b6fcf33d87bca15e949c037070ba75635e0ab30385a3538b1d2beaddf400033f32b89d1dcc0f9b02744c0d" - } - ], - "signed": { - "attacks_detected": "", - "ecu_serial": "TCUdemocar", - "hardware_id": "SecondaryInfotainment111", - "installed_image": { - "fileinfo": { - "custom": { - "ecu_serial": "TCUdemocar", - "hardware_id": "SecondaryInfotainment111", - "release_counter": 2 - }, - "hashes": { - "sha256": "daeec2555599b8e7a82b6f1339d5f419346b57a0eb7a39ee6334b8f205595752", - "sha512": "1570937a84e9e74e35f5e56a8f8518c91e18258cffc8ace249d9acf173d1845d82002583b1b53373b79edf52494d524f2619f5f1896f3085038deca92c950486" - }, - "length": 20 - }, - "filepath": "/firmware011.img" - }, - "previous_timeserver_time": "2018-01-27T05:52:57Z", - "release_counter": 2, - "timeserver_time": "2018-01-27T05:57:49Z" - } - } - ] - }, - "primary_ecu_serial": "INFOdemocar", - "vin": "democar" - } -} \ No newline at end of file diff --git a/samples/sample_ecu_manifest.json b/samples/sample_ecu_manifest.json index cf67675..934ef47 100644 --- a/samples/sample_ecu_manifest.json +++ b/samples/sample_ecu_manifest.json @@ -3,23 +3,30 @@ { "keyid": "49309f114b857e4b29bfbff1c1c75df59f154fbc45539b2eb30c8a867843b2cb", "method": "ed25519", - "sig": "fd04c1edb0ddf1089f0d3fc1cd460af584e548b230d9c290deabfaf29ce5636b6b897eaa97feb64147ac2214c176bbb1d0fa8bb9c623011a0e48d258eb3f9108" + "sig": "15c5d2aa2dd6160711d8113f295d8f49710fe5678f67dccb6b275d5d6655b1758df577089b94dfeea93e77b487848502dabc7cf05eaad2aaa312cb301526ff0c" } ], "signed": { "attacks_detected": "", "ecu_serial": "TCUdemocar", + "hardware_id": "SecondaryInfotainment111", "installed_image": { "fileinfo": { + "custom": { + "ecu_serial": "TCUdemocar", + "hardware_id": "SecondaryInfotainment111", + "release_counter": 2 + }, "hashes": { - "sha256": "6b9f987226610bfed08b824c93bf8b2f59521fce9a2adef80c495f363c1c9c44", - "sha512": "706c283972c5ae69864b199e1cdd9b4b8babc14f5a454d0fd4d3b35396a04ca0b40af731671b74020a738b5108a78deb032332c36d6ae9f31fae2f8a70f7e1ce" + "sha256": "daeec2555599b8e7a82b6f1339d5f419346b57a0eb7a39ee6334b8f205595752", + "sha512": "1570937a84e9e74e35f5e56a8f8518c91e18258cffc8ace249d9acf173d1845d82002583b1b53373b79edf52494d524f2619f5f1896f3085038deca92c950486" }, - "length": 37 + "length": 20 }, - "filepath": "/secondary_firmware.txt" + "filepath": "/firmware0111.img" }, - "previous_timeserver_time": "2017-05-18T16:37:46Z", - "timeserver_time": "2017-05-18T16:37:48Z" + "previous_timeserver_time": "2018-01-27T06:16:16Z", + "release_counter": 2, + "timeserver_time": "2018-01-27T06:16:16Z" } } \ No newline at end of file diff --git a/samples/sample_timeserver_attestation.der b/samples/sample_timeserver_attestation.der index 7f05e136683fddfe44f149e0cefedef6d9ba5f5d..d717f505729a6f31021fd9b60f5b28d2ba6d3932 100644 GIT binary patch delta 99 zcmV-p0G$7b0gM3}FoB7n6@UQ)p$Y;40s;XE0s;V+f&^M@6P=M3GC*Jw0tX^aefaU9 zOPS<{KXvS?r}oLuvV$!hKi=*@sRrOGiWpHx!;s;Cy3;9m2G%%|?nt$m(#w{UmW=nH FZUC%GDTDw3 delta 95 zcmV-l0HFVj0f+$>FoA`j5r6>!p#}m3PTX=Zf&^I|%C?ajGC-`-qf)`GhaK9Mejd|Z z(2DLZJb4|afscrN5!s3+(t|9I;ix-