From adee06a2ac510c7972811bfcc991c7e1b9fc0aab Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Thu, 16 May 2019 12:35:25 -0400 Subject: [PATCH 01/40] Refactor: Add Client as parent class for Primary and Secondary Client class aim to implement the common methods used by by both the Primary and Secondary. They would inherit the Client class and would implement additional methods as per their requirements. --- uptane/clients/client.py | 172 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 uptane/clients/client.py diff --git a/uptane/clients/client.py b/uptane/clients/client.py new file mode 100644 index 0000000..6bd3f15 --- /dev/null +++ b/uptane/clients/client.py @@ -0,0 +1,172 @@ +""" + + client.py + + + Provides common core functionality for Uptane clients: + - Primary and Secondary clients will inherit client class + and further implement additional functions as required + by the clients + +""" +from __future__ import print_function +from __future__ import unicode_literals + +import uptane # Import before TUF modules; may change tuf.conf values. + +import os # For paths and makedirs +import shutil # For copyfile +import random # for nonces +import zipfile # to expand the metadata archive retrieved from the Primary +import hashlib +import iso8601 + +import tuf.formats +import tuf.conf +import tuf.keys +import tuf.client.updater +import tuf.repository_tool as rt + +import uptane.formats +import uptane.common +import uptane.services.director as director +import uptane.services.timeserver as timeserver +import uptane.encoding.asn1_codec as asn1_codec + +from uptane.encoding.asn1_codec import DATATYPE_TIME_ATTESTATION +from uptane.encoding.asn1_codec import DATATYPE_ECU_MANIFEST +from uptane.encoding.asn1_codec import DATATYPE_VEHICLE_MANIFEST + + +class Client(object): + + """ + + This class contains the necessary code to perform Uptane validation of + images and metadata which is required by both Primary and Secondary clients. + An implementation of Uptane should use code like this + to perform full validation of images and metadata. + + + + self.vin + A unique identifier for the vehicle that contains the ECU. + In this reference implementation, this conforms to + uptane.formats.VIN_SCHEMA. There is no need to use the vehicle's VIN in + particular; we simply need a unique identifier for the vehicle, known + to the Director. + + self.ecu_serial + A unique identifier for the ECU. In this reference implementation, + this conforms to uptane.formats.ECU_SERIAL_SCHEMA. + (In other implementations, the important point is that this should be + unique.) The Director should be aware of this identifier. + + self.ecu_key: + The signing key for the ECU. This key will be used to sign + Vehicle Manifests(by Primary ECU) or ECU Manifests(by Secondary ECU) + that will then be sent along to the Primary (and subsequently + to the Director). The Director should be aware of the corresponding + public key, so that it can validate these ECU Manifests. + Conforms to tuf.formats.ANYKEY_SCHEMA. + + self.updater: + A tuf.client.updater.Updater object used to retrieve metadata and + target files from the Director and Image repositories. + + self.full_client_dir: + The full path of the directory where all client data is stored for the + ECU. This includes verified and unverified metadata and images and + any temp files. Conforms to tuf.formats.PATH_SCHEMA. + + self.director_repo_name + The name of the Director repository (e.g. 'director'), as listed in the + map (or pinning) file (pinned.json). This value must appear in that file. + Used to distinguish between the Image Repository and the Director + Repository. Conforms to tuf.formats.REPOSITORY_NAME_SCHEMA. + + self.timeserver_public_key: + The public key of the Timeserver, which will be used to validate signed + time attestations from the Timeserver. + Conforms to tuf.formats.ANYKEY_SCHEMA. + + self.all_valid_timeserver_times: + A list of all times extracted from all Timeserver attestations that have + been verified by update_time. + Items are appended to the end. + + Methods, as called: ("self" arguments excluded): + + __init__(...) + + """ + + def __init__( + self, + full_client_dir, + director_repo_name, + vin, + ecu_serial, + ecu_key, + time, + timeserver_public_key): + """ + + Constructor for class Client + + + + full_client_dir See class docstring above. + + director_repo_name See class docstring above. + + vin See class docstring above. + + ecu_serial See class docstring above. + + primary_key See class docstring above. + + timeserver_public_key See class docstring above. + + time + An initial time to set the Primary's "clock" to, conforming to + tuf.formats.ISO8601_DATETIME_SCHEMA. + + + + + tuf.FormatError + if the arguments are not correctly formatted + + uptane.Error + if director_repo_name is not a known repository based on the + map/pinning file (pinned.json) + + + None. + """ + + # Check arguments: + tuf.formats.PATH_SCHEMA.check_match(full_client_dir) + 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) + 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) + + self.director_repo_name = director_repo_name + self.ecu_key = ecu_key + self.vin = vin + self.ecu_serial = ecu_serial + self.full_client_dir = full_client_dir + self.timeserver_public_key = timeserver_public_key + + # Create a TAP-4-compliant updater object. This will read pinned.json + # and create single-repository updaters within it to handle connections to + # each repository. + self.updater = tuf.client.updater.Updater('updater') + + if director_repo_name not in self.updater.pinned_metadata['repositories']: + raise uptane.Error('Given name for the Director repository is not a ' + 'known repository, according to the pinned metadata from pinned.json') From d239b38bba78a56e1853916b865f7ff995fcde10 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Thu, 16 May 2019 13:01:14 -0400 Subject: [PATCH 02/40] Add duplicate functions from Primary and Secondary Add two duplicate functions - refresh_toplevel_metadata - get_validated_target_info --- uptane/clients/client.py | 133 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 6bd3f15..dc14db3 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -170,3 +170,136 @@ def __init__( if director_repo_name not in self.updater.pinned_metadata['repositories']: raise uptane.Error('Given name for the Director repository is not a ' 'known repository, according to the pinned metadata from pinned.json') + + + + + + def refresh_toplevel_metadata(self): + """ + Refreshes client's metadata for the top-level roles: + root, targets, snapshot, and timestamp + + See tuf.client.updater.Updater.refresh() for details, or the + Uptane Standard, section 5.4.4.2 (Full Verification). + + This can raise TUF update exceptions like + - tuf.ExpiredMetadataError: + if after attempts to update the Root metadata succeeded or failed, + whatever currently trusted Root metadata we ended up with was expired. + - tuf.NoWorkingMirrorError: + if we could not obtain and verify all necessary metadata + """ + + # Refresh the Director first, per the Uptane Standard. + self.updater.refresh(repo_name=self.director_repo_name) + + # Now that we've dealt with the Director repository, deal with any and all + # other repositories, presumably Image Repositories. + for repository_name in self.updater.repositories: + if repository_name == self.director_repo_name: + continue + + self.updater.refresh(repo_name=repository_name) + + + + + + def get_validated_target_info(self, target_filepath): + """ + (Could be called: get Director's version of the fully validated target info) + + + + Returns trustworthy target information for the given target file + (specified by its file path), from the Director, validated against the + Image Repository (or whichever repositories are required per the + pinned.json file). + + The returned information has been cleared according to the trust + requirements of the pinning file (pinned.json) that this client is + equipped with. Assuming typical pinned.json configuration for Uptane, + this means that there is a multi-repository delegation to [the Director + Repository plus the Image Repository]. The target file info received + within this method is that from all repositories in the multi-repository + delegation, and each is guaranteed to be identical to the others in all + respects (e.g. crytographic hash and length) except for the "custom" + metadata field, since the Director includes an additional piece of + information in the fileinfo: the ECU Serial to which the target file is + assigned. + + This method returns only the Director's version of this target file info, + which includes that "custom" field with ECU Serial assignments. + + + Target file info compliant with tuf.formats.TARGETFILE_INFO_SCHEMA, + + + + + tuf.UnknownTargetError + if a given filepath is not listed by the consensus of Director and + Image Repository (or through whichever trusted path is specified by + this client's pinned.json file.) If info is returned, it will match + tuf.formats.TARGETFILE_SCHEMA and will have been validated by all + required parties. + + tuf.NoWorkingMirrorError + will be raised by the updater.target() call here if we are unable to + validate reliable target info for the target file specified (if the + repositories do not agree, or we could not reach them, or so on). + + uptane.Error + if the Director targets file has not provided information about the + given target_filepath, but target_filepath has nevertheless been + validated. This could happen if the map/pinning file for some reason + incorrectly set to not require metadata from the Director. + + """ + tuf.formats.RELPATH_SCHEMA.check_match(target_filepath) + + validated_target_info = self.updater.target( + target_filepath, multi_custom=True) + + # validated_target_info will now look something like this: + # { + # 'Director': { + # filepath: 'django/django.1.9.3.tgz', + # fileinfo: {hashes: ..., length: ..., custom: {'ecu_serial': 'ECU1010101'} } }, + # 'ImageRepo': { + # filepath: 'django/django.1.9.3.tgz', + # fileinfo: {hashes: ..., length: ... } } } + # } + + # We expect there to be an entry in the dict with key name equal to the + # name of the Director repository (specified in pinned.json). + + if self.director_repo_name not in validated_target_info: + # TODO: Consider a different exception class. This seems more like an + # assert statement, though.... If this happens, something is wrong in + # code, or pinned.json is misconfigured (to allow target validation + # whereby the Director is not specified in some multi-repository + # delegations) or the value of director_repo_name passed to the + # initialization of this object was wrong. Those are the edge cases I can + # come up with that could cause this. + + # If the Director repo specified as self.director_repo_name is not in + # pinned.json at all, we'd have thrown an error during __init__. If the + # repos couldn't provide validated target file info, we'd have caught an + # error earlier instead. + + raise uptane.Error('Unexpected behavior: did not receive target info from' + ' Director repository (' + repr(self.director_repo_name) + ') for ' + 'a target (' + repr( + target_filepath) + '). Is pinned.json configured ' + '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?') + + # Defensive coding: this should already have been checked. + tuf.formats.TARGETFILE_SCHEMA.check_match( + validated_target_info[self.director_repo_name]) + + return validated_target_info[self.director_repo_name] + \ No newline at end of file From b056121ec3218d2937146393e076f1d9657609c0 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Thu, 16 May 2019 15:01:37 -0400 Subject: [PATCH 03/40] Add method to obtain the targets list from the director Method get_target_list_from_director gets the list of latest updates to be install in the vehicle --- uptane/clients/client.py | 42 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index dc14db3..41271df 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -38,6 +38,12 @@ from uptane.encoding.asn1_codec import DATATYPE_VEHICLE_MANIFEST +log = uptane.logging.getLogger('client') +log.addHandler(uptane.file_handler) +log.addHandler(uptane.console_handler) +log.setLevel(uptane.logging.DEBUG) + + class Client(object): """ @@ -161,6 +167,7 @@ def __init__( self.ecu_serial = ecu_serial self.full_client_dir = full_client_dir self.timeserver_public_key = timeserver_public_key + self.validated_targets = [] # Create a TAP-4-compliant updater object. This will read pinned.json # and create single-repository updaters within it to handle connections to @@ -302,4 +309,37 @@ def get_validated_target_info(self, target_filepath): validated_target_info[self.director_repo_name]) return validated_target_info[self.director_repo_name] - \ No newline at end of file + + + + + + def get_target_list_from_director(self): + """ + This method extracts the Director's instructions from the targets role in + the Director repository's metadata. These must still be validated against + the Image Repository in further calls. + """ + # TODO: This will have to be changed (along with the functions that depend + # on this function's output) once multi-role delegations can yield multiple + # targetfile_info objects. (Currently, we only yield more than one at the + # multi-repository delegation level.) + + # Refresh the top-level metadata first (all repositories). + log.debug('Refreshing top level metadata from all repositories.') + + # Refresh the top-level metadata first (all repositories). + self.refresh_toplevel_metadata() + + directed_targets = self.updater.targets_of_role( + rolename='targets', repo_name=self.director_repo_name) + + if not directed_targets: + log.info('A correctly signed statement from the Director indicates that ' + 'this vehicle has NO updates to install.') + else: + log.info('A correctly signed statement from the Director indicates that ' + 'this vehicle has updates to install:' + + repr([targ['filepath'] for targ in directed_targets])) + + return directed_targets \ No newline at end of file From ed10eb3c27416f174f5ee6099443f873d8e76222 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Thu, 16 May 2019 17:01:58 -0400 Subject: [PATCH 04/40] Add method to verify the timeserver signature This method would be used to verify timeserver signature on the timeserver_attestation before we proced to update the time of the client. --- uptane/clients/client.py | 43 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 41271df..18076c5 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -342,4 +342,45 @@ def get_target_list_from_director(self): 'this vehicle has updates to install:' + repr([targ['filepath'] for targ in directed_targets])) - return directed_targets \ No newline at end of file + return directed_targets + + + + + + def verify_timeserver_signature(self, timeserver_attestation): + """ + The response from the Timeserver should then be provided to this function. + This function attempts to verify the given attestation, + if timeserver_attestation is correctly signed by the expected Timeserver + + If the client is using ASN.1/DER metadata, then timeserver_attestation is + expected to be in that format, as a byte string. + Otherwise, we're using simple Python dictionaries and timeserver_attestation + conforms to uptane.formats.SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA. + """ + # If we're using ASN.1/DER format, convert the attestation into something + # comprehensible (JSON-compatible dictionary) instead. + if tuf.conf.METADATA_FORMAT == 'der': + timeserver_attestation = asn1_codec.convert_signed_der_to_dersigned_json( + timeserver_attestation, DATATYPE_TIME_ATTESTATION) + + # Check format. + uptane.formats.SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA.check_match( + timeserver_attestation) + + # Assume there's only one signature. This assumption is made for simplicity + # in this reference implementation. If the Timeserver needs to sign with + # multiple keys for some reason, that can be accomodated. + assert len(timeserver_attestation['signatures']) == 1 + + verified = uptane.common.verify_signature_over_metadata( + self.timeserver_public_key, + timeserver_attestation['signatures'][0], + timeserver_attestation['signed'], + DATATYPE_TIME_ATTESTATION) + + if not verified: + raise tuf.BadSignatureError('Timeserver returned an invalid signature. ' + 'Time is questionable, so not saved. If you see this persistently, ' + 'it is possible that there is a Man in the Middle attack underway.') From 9877c4470f0f155d7ecd4f0b181023ccff3816d7 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Thu, 16 May 2019 17:09:32 -0400 Subject: [PATCH 05/40] Add function to update time after verification of timeserver_attestation This method would be used to update the time of the client after the timeserver signature is verified and the list of nounce is verified by the Primary or Secondary. --- uptane/clients/client.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 18076c5..aba721b 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -384,3 +384,36 @@ def verify_timeserver_signature(self, timeserver_attestation): raise tuf.BadSignatureError('Timeserver returned an invalid signature. ' 'Time is questionable, so not saved. If you see this persistently, ' 'it is possible that there is a Man in the Middle attack underway.') + + + + + + def update_verified_time(self, timeserver_attestation): + """ + This method extracts the time from provided timeserver_attestation, + which must be verified first, and updates the time of the client. + This method must only be called if timeserver_attestation is correctly + signed by the expected Timeserver key and is verified by the Primary + or Secondary for the list of nounces. + The new time will be used by this client (via TUF) in place of + system time when checking metadata for expiration. + """ + # Extract actual time from the timeserver's signed attestation. + new_timeserver_time = timeserver_attestation['signed']['time'] + + # Make sure the format is understandable to us before saving the + # attestation and time. Convert to a UNIX timestamp. + new_timeserver_time_unix = int(tuf.formats.datetime_to_unix_timestamp( + iso8601.parse_date(new_timeserver_time))) + tuf.formats.UNIX_TIMESTAMP_SCHEMA.check_match(new_timeserver_time_unix) + + # Save validated time. + self.all_valid_timeserver_times.append(new_timeserver_time) + + # Save the attestation itself as well, to provide to Secondaries (who need + # not trust us). + self.all_valid_timeserver_attestations.append(timeserver_attestation) + + # Set the client's clock. This will be used instead of system time by TUF. + tuf.conf.CLOCK_OVERRIDE = new_timeserver_time_unix \ No newline at end of file From f60df1188db9725f1fcc60c27a13300461a9985f Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 14:20:59 -0400 Subject: [PATCH 06/40] Inherit Primary form Client class Two important changes were introduced 1. Primary inherited from the Client class and upate the constructor of the primary class 2. Earlier the key of Primary was refered to as primary_key, to improve consistency in Client, Primary and Secondary, change it to ecu_key --- uptane/clients/primary.py | 42 +++++++++++++-------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index f26c271..a19eb24 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -20,6 +20,8 @@ import uptane # Import before TUF modules; may change tuf.conf values. +from client.py import Client + import os # For paths and makedirs import shutil # For copyfile import random # for nonces @@ -60,7 +62,7 @@ -class Primary(object): # Consider inheriting from Secondary and refactoring. +class Primary(Client): # Inheriting from client class """ This class contains the necessary code to perform Uptane validation of @@ -84,7 +86,7 @@ class Primary(object): # Consider inheriting from Secondary and refactoring. (In other implementations, the important point is that this should be unique.) The Director should be aware of this identifier. - self.primary_key + self.ecu_key The signing key for this Primary ECU. This key will be used to sign Vehicle Manifests that will then be sent to the Director). The Director should be aware of the corresponding public key, so that it can validate @@ -227,7 +229,7 @@ def __init__( director_repo_name, # e.g. 'director'; value must appear in pinning file vin, # 'vin11111' ecu_serial, # 'ecu00000' - primary_key, + ecu_key, time, timeserver_public_key, my_secondaries=None): @@ -246,7 +248,7 @@ def __init__( ecu_serial See class docstring above. - primary_key See class docstring above. + ecu_key See class docstring above. timeserver_public_key See class docstring above. @@ -277,23 +279,17 @@ def __init__( uptane.formats.VIN_SCHEMA.check_match(vin) uptane.formats.ECU_SERIAL_SCHEMA.check_match(ecu_serial) 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 + tuf.formats.ANYKEY_SCHEMA.check_match(ecu_key) + # TODO: Should also check that ecu_key is a private key, not a # public key. - self.vin = vin - self.ecu_serial = ecu_serial - self.full_client_dir = full_client_dir - # TODO: Consider removing time from [time] here and starting with an empty - # list, or setting time to 0 to start by default. - self.all_valid_timeserver_times = [time] - self.all_valid_timeserver_attestations = [] - self.timeserver_public_key = timeserver_public_key - self.primary_key = primary_key + super().__init__(full_client_dir, director_repo_name, vin, + ecu_serial, ecu_key, time, timeserver_public_key) + + self.my_secondaries = my_secondaries if self.my_secondaries is None: self.my_secondaries = [] # (because must not use mutable as default value) - self.director_repo_name = director_repo_name self.temp_full_metadata_archive_fname = os.path.join( full_client_dir, 'metadata', 'temp_full_metadata_archive.zip') @@ -319,16 +315,6 @@ def __init__( self.ecu_manifests = {} - # Create a TUF-TAP-4-compliant updater object. This will read pinning.json - # and create single-repository updaters within it to handle connections to - # each repository. - self.updater = tuf.client.updater.Updater('updater') - - if director_repo_name not in self.updater.pinned_metadata['repositories']: - raise uptane.Error('Given name for the Director repository is not a ' - 'known repository, according to the pinned metadata from pinned.json') - - @@ -895,13 +881,13 @@ def generate_signed_vehicle_manifest(self): # Convert to DER and sign, replacing the Python dictionary. signable_vehicle_manifest = asn1_codec.convert_signed_metadata_to_der( signable_vehicle_manifest, DATATYPE_VEHICLE_MANIFEST, - private_key=self.primary_key, resign=True) + private_key=self.ecu_key, resign=True) else: # If we're not using ASN.1, sign the Python dictionary in a JSON encoding. uptane.common.sign_signable( signable_vehicle_manifest, - [self.primary_key], + [self.ecu_key], DATATYPE_VEHICLE_MANIFEST) uptane.formats.SIGNABLE_VEHICLE_VERSION_MANIFEST_SCHEMA.check_match( From 8950ac2929e31ad3063b147e61b3800c3e3a3741 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 14:28:42 -0400 Subject: [PATCH 07/40] Add time variables to the constructor --- uptane/clients/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index aba721b..99edbed 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -168,6 +168,10 @@ def __init__( self.full_client_dir = full_client_dir self.timeserver_public_key = timeserver_public_key self.validated_targets = [] + # TODO: Consider removing time from [time] here and starting with an empty + # list, or setting time to 0 to start by default. + self.all_valid_timeserver_times = [time] + self.all_valid_timeserver_attestations = [] # Create a TAP-4-compliant updater object. This will read pinned.json # and create single-repository updaters within it to handle connections to From 3f4587a6cca053dae1c41da41e3091c63fd7e873 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 14:30:48 -0400 Subject: [PATCH 08/40] Fix Client import error --- uptane/clients/primary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index a19eb24..a0f880d 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -20,7 +20,7 @@ import uptane # Import before TUF modules; may change tuf.conf values. -from client.py import Client +from uptane.clients.client import Client import os # For paths and makedirs import shutil # For copyfile From b76248328f9993aca526dc5b477cc401d5d9a06b Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 14:41:24 -0400 Subject: [PATCH 09/40] Remove function implemented in Client Remove 1. refresh_toplevel_metadata 2. get_validated_target_info --- uptane/clients/primary.py | 134 -------------------------------------- 1 file changed, 134 deletions(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index a0f880d..06244b2 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -318,40 +318,6 @@ def __init__( - def refresh_toplevel_metadata(self): - """ - Refreshes client's metadata for the top-level roles: - root, targets, snapshot, and timestamp - - See tuf.client.updater.Updater.refresh() for details, or the - Uptane Standard, section 5.4.4.2 (Full Verification). - - # TODO: This function is duplicated in primary.py and secondary.py. It must - # be moved to a general client.py as part of a fix to issue #14 - # (github.com/uptane/uptane/issues/14). - This can raise TUF update exceptions like - - tuf.ExpiredMetadataError: - if after attempts to update the Root metadata succeeded or failed, - whatever currently trusted Root metadata we ended up with was expired. - - tuf.NoWorkingMirrorError: - if we could not obtain and verify all necessary metadata - """ - - # Refresh the Director first, per the Uptane Standard. - self.updater.refresh(repo_name=self.director_repo_name) - - # Now that we've dealt with the Director repository, deal with any and all - # other repositories, presumably Image Repositories. - for repository_name in self.updater.repositories: - if repository_name == self.director_repo_name: - continue - - self.updater.refresh(repo_name=repository_name) - - - - - def get_target_list_from_director(self): """ This method extracts the Director's instructions from the targets role in @@ -371,106 +337,6 @@ def get_target_list_from_director(self): - def get_validated_target_info(self, target_filepath): - """ - (Could be called: get Director's version of the fully validated target info) - - - - Returns trustworthy target information for the given target file - (specified by its file path), from the Director, validated against the - Image Repository (or whichever repositories are required per the - pinned.json file). - - The returned information has been cleared according to the trust - requirements of the pinning file (pinned.json) that this client is - equipped with. Assuming typical pinned.json configuration for Uptane, - this means that there is a multi-repository delegation to [the Director - Repository plus the Image Repository]. The target file info received - within this method is that from all repositories in the multi-repository - delegation, and each is guaranteed to be identical to the others in all - respects (e.g. crytographic hash and length) except for the "custom" - metadata field, since the Director includes an additional piece of - information in the fileinfo: the ECU Serial to which the target file is - assigned. - - This method returns only the Director's version of this target file info, - which includes that "custom" field with ECU Serial assignments. - - - Target file info compliant with tuf.formats.TARGETFILE_INFO_SCHEMA, - - - - - tuf.UnknownTargetError - if a given filepath is not listed by the consensus of Director and - Image Repository (or through whichever trusted path is specified by - this client's pinned.json file.) If info is returned, it will match - tuf.formats.TARGETFILE_SCHEMA and will have been validated by all - required parties. - - tuf.NoWorkingMirrorError - will be raised by the updater.target() call here if we are unable to - validate reliable target info for the target file specified (if the - repositories do not agree, or we could not reach them, or so on). - - uptane.Error - if the Director targets file has not provided information about the - given target_filepath, but target_filepath has nevertheless been - validated. This could happen if the map/pinning file for some reason - incorrectly set to not require metadata from the Director. - - """ - tuf.formats.RELPATH_SCHEMA.check_match(target_filepath) - - validated_target_info = self.updater.target( - target_filepath, multi_custom=True) - - # validated_target_info will now look something like this: - # { - # 'Director': { - # filepath: 'django/django.1.9.3.tgz', - # fileinfo: {hashes: ..., length: ..., custom: {'ecu_serial': 'ECU1010101'} } }, - # 'ImageRepo': { - # filepath: 'django/django.1.9.3.tgz', - # fileinfo: {hashes: ..., length: ... } } } - # } - - # We expect there to be an entry in the dict with key name equal to the - # name of the Director repository (specified in pinned.json). - - if self.director_repo_name not in validated_target_info: - # TODO: Consider a different exception class. This seems more like an - # assert statement, though.... If this happens, something is wrong in - # code, or pinned.json is misconfigured (to allow target validation - # whereby the Director is not specified in some multi-repository - # delegations) or the value of director_repo_name passed to the - # initialization of this object was wrong. Those are the edge cases I can - # come up with that could cause this. - - # If the Director repo specified as self.director_repo_name is not in - # pinned.json at all, we'd have thrown an error during __init__. If the - # repos couldn't provide validated target file info, we'd have caught an - # error earlier instead. - - raise uptane.Error('Unexpected behavior: did not receive target info from' - ' Director repository (' + repr(self.director_repo_name) + ') for ' - 'a target (' + repr(target_filepath) + '). Is pinned.json configured ' - '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?') - - # Defensive coding: this should already have been checked. - tuf.formats.TARGETFILE_SCHEMA.check_match( - validated_target_info[self.director_repo_name]) - - return validated_target_info[self.director_repo_name] - - - - - def primary_update_cycle(self): """ Download fresh metadata and images for this vehicle, as instructed by the From f6e4d32d351374df48133d266fdb4bef60818195 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 14:47:22 -0400 Subject: [PATCH 10/40] Update primary_update_cycle --- uptane/clients/primary.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index 06244b2..eff2ce9 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -362,23 +362,10 @@ def primary_update_cycle(self): are deposited by TUF that does not have an extension that befits a file of type tuf.conf.METADATA_FORMAT. """ - log.debug('Refreshing top level metadata from all repositories.') - self.refresh_toplevel_metadata() - # Get the list of targets the director expects us to download and update to. - # Note that at this line, this target info is not yet validated with the - # Image Repository: that is done a few lines down. + # Get list of targets from the Director directed_targets = self.get_target_list_from_director() - if not directed_targets: - log.info('A correctly signed statement from the Director indicates that ' - 'this vehicle has NO updates to install.') - else: - log.info('A correctly signed statement from the Director indicates that ' - 'this vehicle has updates to install:' + - repr([targ['filepath'] for targ in directed_targets])) - - log.debug('Retrieving validated image file metadata from Image and ' 'Director Repositories.') From 0d266826016d09f395e6f52b204208d8c4f36bae Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 15:39:39 -0400 Subject: [PATCH 11/40] Update update_time method Modify update_time to use method verify_timeserver_signature and update_verified_time from Client to verify signature of the timeserver and to update the time after verification process respectively. --- uptane/clients/primary.py | 50 ++++----------------------------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index eff2ce9..4686176 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -974,32 +974,9 @@ def update_time(self, timeserver_attestation): conforms to uptane.formats.SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA. """ - # If we're using DER format, convert the attestation into something - # comprehensible instead. - if tuf.conf.METADATA_FORMAT == 'der': - timeserver_attestation = asn1_codec.convert_signed_der_to_dersigned_json( - timeserver_attestation, DATATYPE_TIME_ATTESTATION) - - # Check format. - uptane.formats.SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA.check_match( - timeserver_attestation) - - - # Assume there's only one signature. This assumption is made for simplicity - # in this reference implementation. If the Timeserver needs to sign with - # multiple keys for some reason, that can be accomodated. - assert len(timeserver_attestation['signatures']) == 1 - - valid = uptane.common.verify_signature_over_metadata( - self.timeserver_public_key, - timeserver_attestation['signatures'][0], - timeserver_attestation['signed'], - DATATYPE_TIME_ATTESTATION) - - if not valid: - raise tuf.BadSignatureError('Timeserver returned an invalid signature. ' - 'Time is questionable, so not saved. If you see this persistently, ' - 'it is possible that there is a Man in the Middle attack underway.') + # Verify the signature of the timeserver on the attestation. If not verified, + # it raises a BadSignatureError + self.verify_timeserver_signature(timeserver_attestation) for nonce in self.nonces_sent: if nonce not in timeserver_attestation['signed']['nonces']: @@ -1013,25 +990,8 @@ def update_time(self, timeserver_attestation): 'persistently, it is possible that there is a Man in the Middle ' 'attack underway.') - - # Extract actual time from the timeserver's signed attestation. - new_timeserver_time = timeserver_attestation['signed']['time'] - - # Make sure the format is understandable to us before saving the - # attestation and time. Convert to a UNIX timestamp. - new_timeserver_time_unix = int(tuf.formats.datetime_to_unix_timestamp( - iso8601.parse_date(new_timeserver_time))) - tuf.formats.UNIX_TIMESTAMP_SCHEMA.check_match(new_timeserver_time_unix) - - # Save validated time. - self.all_valid_timeserver_times.append(new_timeserver_time) - - # Save the attestation itself as well, to provide to Secondaries (who need - # not trust us). - self.all_valid_timeserver_attestations.append(timeserver_attestation) - - # Set the client's clock. This will be used instead of system time by TUF. - tuf.conf.CLOCK_OVERRIDE = new_timeserver_time_unix + # Update the time of Primary with the time in attestation + self.update_verified_time(timeserver_attestation) From 4c0dc3cfd2c49124347f2a882e50379eb60bf1dc Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 16:26:43 -0400 Subject: [PATCH 12/40] Fix verify_timeserver_signature to return time_attestation as dictionary --- uptane/clients/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 99edbed..3afe81e 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -389,6 +389,9 @@ def verify_timeserver_signature(self, timeserver_attestation): 'Time is questionable, so not saved. If you see this persistently, ' 'it is possible that there is a Man in the Middle attack underway.') + # Return timeserver_attestation in JSON-compatible dictionary + return timeserver_attestation + From c2f801e8c8af9ae77c5910bf927480a1b3da0124 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 16:29:01 -0400 Subject: [PATCH 13/40] Change primary_key to ecu_key Done to accomodate for changes made in Primary. In Primary, we now refere private key of Primary as ecu_key insted of primary_key --- demo/demo_primary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/demo_primary.py b/demo/demo_primary.py index 7293856..3f6574f 100644 --- a/demo/demo_primary.py +++ b/demo/demo_primary.py @@ -138,7 +138,7 @@ def clean_slate( director_repo_name=demo.DIRECTOR_REPO_NAME, vin=_vin, ecu_serial=_ecu_serial, - primary_key=ecu_key, + ecu_key=ecu_key, time=clock, timeserver_public_key=key_timeserver_pub) @@ -381,7 +381,7 @@ def register_self_with_director(): print('Registering Primary ECU Serial and Key with Director.') server.register_ecu_serial( primary_ecu.ecu_serial, - uptane.common.public_key_from_canonical(primary_ecu.primary_key), + uptane.common.public_key_from_canonical(primary_ecu.ecu_key), _vin, True) print(GREEN + 'Primary has been registered with the Director.' + ENDCOLORS) From 7a59f5c2b387ea7cc0e6e23476a6d9460cff78b3 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 16:33:23 -0400 Subject: [PATCH 14/40] Remove get_targetlist_from_director from Primary There was a copy of previous implemented version of this method in Primary, which needed to be removed so that Primary can use Client class implemtation of the method, by default. --- uptane/clients/primary.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index 4686176..62ece41 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -318,25 +318,6 @@ def __init__( - def get_target_list_from_director(self): - """ - This method extracts the Director's instructions from the targets role in - the Director repository's metadata. These must still be validated against - the Image Repository in further calls. - """ - # TODO: This will have to be changed (along with the functions that depend - # on this function's output) once multi-role delegations can yield multiple - # targetfile_info objects. (Currently, we only yield more than one at the - # multi-repository delegation level.) - directed_targets = self.updater.targets_of_role( - rolename='targets', repo_name=self.director_repo_name) - - return directed_targets - - - - - def primary_update_cycle(self): """ Download fresh metadata and images for this vehicle, as instructed by the From bac471fa4618d1d0dd052ed1373db9d5d1c5f3a7 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 16:35:13 -0400 Subject: [PATCH 15/40] Fix update_time to use dictionary format of attestation --- uptane/clients/primary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index 62ece41..8ef60ec 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -957,7 +957,7 @@ def update_time(self, timeserver_attestation): # Verify the signature of the timeserver on the attestation. If not verified, # it raises a BadSignatureError - self.verify_timeserver_signature(timeserver_attestation) + timeserver_attestation = self.verify_timeserver_signature(timeserver_attestation) for nonce in self.nonces_sent: if nonce not in timeserver_attestation['signed']['nonces']: From d257a22842f836da7546cbaa69f5c5d5b5adaaaa Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 17 May 2019 17:01:05 -0400 Subject: [PATCH 16/40] Inherit secondary from Client --- uptane/clients/secondary.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/uptane/clients/secondary.py b/uptane/clients/secondary.py index 88ea8fe..7d0dbf3 100644 --- a/uptane/clients/secondary.py +++ b/uptane/clients/secondary.py @@ -24,6 +24,8 @@ import uptane # Import before TUF modules; may change tuf.conf values. +from uptane.clients.client import Client + import os # For paths and makedirs import shutil # For copyfile import random # for nonces @@ -54,7 +56,7 @@ -class Secondary(object): +class Secondary(Client): """ @@ -240,13 +242,10 @@ def __init__( if director_public_key is not None: tuf.formats.ANYKEY_SCHEMA.check_match(director_public_key) - self.director_repo_name = director_repo_name - self.ecu_key = ecu_key - self.vin = vin - self.ecu_serial = ecu_serial - self.full_client_dir = full_client_dir + super().__init__(full_client_dir, director_repo_name, vin, + ecu_serial, ecu_key, time, timeserver_public_key) + self.director_proxy = None - self.timeserver_public_key = timeserver_public_key self.director_public_key = director_public_key self.partial_verifying = partial_verifying self.firmware_fileinfo = firmware_fileinfo @@ -262,16 +261,8 @@ def __init__( 'only the ') - # Create a TAP-4-compliant updater object. This will read pinned.json - # and create single-repository updaters within it to handle connections to - # each repository. - self.updater = tuf.client.updater.Updater('updater') - - if director_repo_name not in self.updater.pinned_metadata['repositories']: - raise uptane.Error('Given name for the Director repository is not a ' - 'known repository, according to the pinned metadata from pinned.json') - # We load the given time twice for simplicity in later code. + # TODO: Check if this is necessary. self.all_valid_timeserver_times = [time, time] self.last_nonce_sent = None From de463d8568dcd93647e5643e1ad88f0ef7b4c55b Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 21 May 2019 20:16:19 -0400 Subject: [PATCH 17/40] Update to be compatible with partial verification --- demo/demo_secondary.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/demo/demo_secondary.py b/demo/demo_secondary.py index e3f50b2..d2ef6c0 100644 --- a/demo/demo_secondary.py +++ b/demo/demo_secondary.py @@ -63,6 +63,7 @@ _ecu_serial = 'TCUdemocar' _primary_host = demo.PRIMARY_SERVER_HOST _primary_port = demo.PRIMARY_SERVER_DEFAULT_PORT +_partial_verifying = False firmware_filename = 'secondary_firmware.txt' current_firmware_fileinfo = {} secondary_ecu = None @@ -79,7 +80,8 @@ def clean_slate( vin=_vin, ecu_serial=_ecu_serial, primary_host=None, - primary_port=None): + primary_port=None, + partial_verifying=_partial_verifying): """ """ @@ -88,12 +90,14 @@ def clean_slate( global _ecu_serial global _primary_host global _primary_port + global _partial_verifying global nonce global CLIENT_DIRECTORY global attacks_detected _vin = vin _ecu_serial = ecu_serial + _partial_verifying = partial_verifying if primary_host is not None: _primary_host = primary_host @@ -104,6 +108,12 @@ def clean_slate( CLIENT_DIRECTORY = os.path.join( uptane.WORKING_DIR, CLIENT_DIRECTORY_PREFIX + demo.get_random_string(5)) + # If secondary is partial verification then it would need director public key + if _partial_verifying: + key_director_pub = demo.import_public_key('director') + else: + key_director_pub = None + # Load the public timeserver key. key_timeserver_pub = demo.import_public_key('timeserver') @@ -154,7 +164,9 @@ def clean_slate( ecu_key=ecu_key, time=clock, firmware_fileinfo=factory_firmware_fileinfo, - timeserver_public_key=key_timeserver_pub) + timeserver_public_key=key_timeserver_pub, + director_public_key=key_director_pub, + partial_verifying=_partial_verifying) @@ -302,7 +314,8 @@ def update_cycle(): # Download the metadata from the Primary in the form of an archive. This # returns the binary data that we need to write to file. - metadata_archive = pserver.get_metadata(secondary_ecu.ecu_serial) + metadata_from_primary = pserver.get_metadata( + secondary_ecu.ecu_serial, secondary_ecu.partial_verifying) # Verify the time attestation and internalize the time (if verified, the time # will be used in place of system time to perform future metadata expiration @@ -322,15 +335,19 @@ def update_cycle(): # print(GREEN + 'Official time has been updated successfully.' + ENDCOLORS) # Dump the archive file to disk. - archive_fname = os.path.join( - secondary_ecu.full_client_dir, 'metadata_archive.zip') + if secondary_ecu.partial_verifying: + metadata_fname = os.path.join( + secondary_ecu.full_client_dir, 'directed_targets.' + tuf.conf.METADATA_FORMAT) + else: + metadata_fname = os.path.join( + secondary_ecu.full_client_dir, 'metadata_archive.zip') - with open(archive_fname, 'wb') as fobj: - fobj.write(metadata_archive.data) + with open(metadata_fname, 'wb') as fobj: + fobj.write(metadata_from_primary.data) # 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) + secondary_ecu.process_metadata(metadata_fname) # As part of the process_metadata call, the secondary will have saved From 20fbc0a3dd79248c4be08573b3f2c6f45e86d24e Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Wed, 22 May 2019 16:40:16 -0400 Subject: [PATCH 18/40] Update metadata distributed to partial verification secondaries Now metadata distributed to the partial verification secondaries would be in the form of a archive with the structure almost similar to that of the distributed archive for full verification. Here only director repo would be present and it would only have the targets metadata. This is done so that the secondary could obtain, save and expand the metadata recieved from primary in a similar fashion as that of full verification. It would further help in simplifying the partial verification process --- demo/demo_primary.py | 2 +- uptane/clients/primary.py | 62 +++++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/demo/demo_primary.py b/demo/demo_primary.py index 7293856..5cabd00 100644 --- a/demo/demo_primary.py +++ b/demo/demo_primary.py @@ -494,7 +494,7 @@ def get_metadata_for_ecu(ecu_serial, force_partial_verification=False): fname = None if force_partial_verification: - fname = primary_ecu.get_partial_metadata_fname() + fname = primary_ecu.get_partial_metadata_archive_fname() else: # Note that in Python 2.7.4 and later, unzipping should prevent files from diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index f26c271..4709e81 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -159,7 +159,7 @@ class Primary(object): # Consider inheriting from Secondary and refactoring. moved into place (renamed) after it has been fully written, to avoid race conditions. - self.distributable_partial_metadata_fname: + self.distributable_partial_metadata_archive_fname: The filename at which the Director's targets metadata file is stored after each update cycle, once it is safe to use. This is atomically moved into place (renamed) after it has been fully written, to avoid race conditions. @@ -187,7 +187,7 @@ class Primary(object): # Consider inheriting from Secondary and refactoring. update_exists_for_ecu(ecu_serial) get_image_fname_for_ecu(ecu_serial) get_full_metadata_archive_fname() - get_partial_metadata_fname() + get_partial_metadata_archive_fname() register_new_secondary(ecu_serial) Private methods: @@ -301,12 +301,10 @@ def __init__( full_client_dir, 'metadata', 'full_metadata_archive.zip') # TODO: Some of these assumptions are unseemly. Reconsider. - self.temp_partial_metadata_fname = os.path.join( - full_client_dir, 'metadata', 'temp_director_targets.' + - tuf.conf.METADATA_FORMAT) - self.distributable_partial_metadata_fname = os.path.join( - full_client_dir, 'metadata', 'director_targets.' + - tuf.conf.METADATA_FORMAT) + self.temp_partial_metadata_archive_fname = os.path.join( + full_client_dir, 'metadata', 'temp_partial_metadata_archive.zip') + self.distributable_partial_metadata_archive_fname = os.path.join( + full_client_dir, 'metadata', 'partial_metadata_archive.zip') # Initializations not directly related to arguments. self.nonces_to_send = [] @@ -770,7 +768,7 @@ def get_full_metadata_archive_fname(self): - def get_partial_metadata_fname(self): + def get_partial_metadata_archive_fname(self): """ Returns the absolute-path filename of the Director's targets.json metadata file, necessary for performing partial validation of target files (as a @@ -781,7 +779,7 @@ def get_partial_metadata_fname(self): file is completely written. If this Primary has never completed an update cycle, it will not exist yet. """ - return self.distributable_partial_metadata_fname + return self.distributable_partial_metadata_archive_fname @@ -1207,7 +1205,7 @@ def save_distributable_metadata_files(self): a zip archive of all the metadata files, from all repositories, validated by this Primary, for use by Full Verification Secondaries. - - self.distributable_partial_metadata_fname + - self.distributable_partial_metadata_archive_fname the Director Targets role file alone, for use by Partial Verification Secondaries @@ -1267,23 +1265,43 @@ def save_distributable_metadata_files(self): # Copy the Director's targets file to a temp location for partial-verifying # Secondaries. - director_targets_file = os.path.join( - self.full_client_dir, - 'metadata', - self.director_repo_name, - 'current', - 'targets.' + tuf.conf.METADATA_FORMAT) - if os.path.exists(self.temp_partial_metadata_fname): - os.remove(self.temp_partial_metadata_fname) - shutil.copyfile(director_targets_file, self.temp_partial_metadata_fname) + with zipfile.ZipFile(self.temp_partial_metadata_archive_fname, 'w') \ + as archive: + # Need 'target' metadata from only director repo + repo_name = self.director_repo_name + # Construct path to "current" metadata directory for that repository in + # the client metadata directory, relative to Uptane working directory. + abs_repo_dir = os.path.join(metadata_base_dir, repo_name, 'current') + + # Archive only 'targets' metadat file for partial verification + role_fname = 'targets.' + tuf.conf.METADATA_FORMAT + # Reconstruct file path relative to Uptane working directory. + role_abs_fname = os.path.join(abs_repo_dir, role_fname) + + # Make sure it's the right type of file. Should be a file, not a + # directory. Symlinks are OK. Should end in an extension matching + # tuf.conf.METADATA_FORMAT (presumably .json or .der, depending on + # that setting). + if not os.path.isfile(role_abs_fname) or not role_abs_fname.endswith( + '.' + tuf.conf.METADATA_FORMAT): + # Consider special error type. + raise uptane.Error('Unexpected file type in a metadata ' + 'directory: ' + repr(role_abs_fname) + ' Expecting only ' + + tuf.conf.METADATA_FORMAT + 'files.') + + # Write the file to the archive, adjusting the path in the archive so + # that when expanded, it resembles repository structure rather than + # a client directory structure. + archive.write(role_abs_fname, + os.path.join(repo_dir, 'metadata', role_fname)) # Now move both Full and Partial metadata files into place. For each file, # this happens atomically on POSIX-compliant systems and replaces any # existing file. os.rename( - self.temp_partial_metadata_fname, - self.distributable_partial_metadata_fname) + self.temp_partial_metadata_archive_fname, + self.distributable_partial_metadata_archive_fname) os.rename( self.temp_full_metadata_archive_fname, self.distributable_full_metadata_archive_fname) From 20ce12647e3b5375e3c51c18866f517aca1820ee Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Wed, 22 May 2019 16:51:45 -0400 Subject: [PATCH 19/40] Update to reciver the metadata for partial verification Now primary is sending archive for partial verification metadata as well. --- demo/demo_secondary.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/demo/demo_secondary.py b/demo/demo_secondary.py index d2ef6c0..5c45b4e 100644 --- a/demo/demo_secondary.py +++ b/demo/demo_secondary.py @@ -335,19 +335,15 @@ def update_cycle(): # print(GREEN + 'Official time has been updated successfully.' + ENDCOLORS) # Dump the archive file to disk. - if secondary_ecu.partial_verifying: - metadata_fname = os.path.join( - secondary_ecu.full_client_dir, 'directed_targets.' + tuf.conf.METADATA_FORMAT) - else: - metadata_fname = os.path.join( - secondary_ecu.full_client_dir, 'metadata_archive.zip') + archive_fname = os.path.join( + secondary_ecu.full_client_dir, 'metadata_archive.zip') - with open(metadata_fname, 'wb') as fobj: + with open(archive_fname, 'wb') as fobj: fobj.write(metadata_from_primary.data) # Now tell the Secondary reference implementation code where the archive file # is and let it expand and validate the metadata. - secondary_ecu.process_metadata(metadata_fname) + secondary_ecu.process_metadata(archive_fname) # As part of the process_metadata call, the secondary will have saved From 3398f3716ee91aab91d2ed2372fc154fb47f41c2 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Thu, 23 May 2019 15:39:18 -0400 Subject: [PATCH 20/40] Add function for Partial Verification of Secondary Used various tuf low level functions to verify the targets metadata supplied by the director repository. It complies with the latest Uptane standard for design. --- uptane/clients/secondary.py | 162 ++++++++++++++++++++++++++++++++++-- 1 file changed, 155 insertions(+), 7 deletions(-) diff --git a/uptane/clients/secondary.py b/uptane/clients/secondary.py index 88ea8fe..dec6446 100644 --- a/uptane/clients/secondary.py +++ b/uptane/clients/secondary.py @@ -30,9 +30,11 @@ import zipfile # to expand the metadata archive retrieved from the Primary import hashlib import iso8601 +import six import tuf.formats import tuf.keys +import tuf.keydb as key_database import tuf.client.updater import tuf.repository_tool as rt @@ -571,6 +573,114 @@ def fully_validate_metadata(self): + def partial_validate_metadata(self): + """ + + Given the filename of a file containing the Director's Targets role + metadata, validates and processes that metadata, determining what firmware + the Director has instructed this partial-verification Secondary ECU to + install. + The given metadata replaces this client's current Director metadata if + the given metadata is valid -- i.e. if the metadata: + - is signed by a key matching self.director_public_key + - and is not expired (current date is before metadata's expiration date) + - and does not have an older version number than this client has + previously seen -- i.e. is not a rollback) + Otherwise, an exception is raised indicating that the metadata is not + valid. + Further, if the metadata is valid, this function then updates + self.validated_target_for_this_ecu if the metadata also lists a target + for this ECU (i.e. includes a target with field "ecu_serial" set to this + ECU's serial number) + + + + director_targets_metadata_fname + Filename of the Director's Targets role metadata, in either JSON or + ASN.1/DER format. + + + + None + + + + uptane.Error + if director_targets_metadata_fname does not specify a file that exists + or if tuf.conf.METADATA_FORMAT is somehow an unsupported format (i.e. + not 'json' or 'der') + tuf.BadSignatureError + if the signature over the Targets metadata is not a valid + signature by the key corresponding to self.director_public_key, or if + the key type listed in the signature does not match the key type listed + in the public key + tuf.ExpiredMetadataError + if the Targets metadata is expired + tuf.ReplayedMetadataError + if the Targets metadata has a lower version number than + the last Targets metadata this client deemed valid (rollback) + + + + May update this client's metadata (Director Targets); see + May update self.validated_targets_for_this_ecu; see + """ + + + validated_targets_for_this_ecu = [] + + upperbound_filelength = tuf.conf.DEFAULT_TARGETS_REQUIRED_LENGTH + + # Add director key in the key_database for metadata verification + try: + key_database.add_key( + self.director_public_key, repository_name=self.director_repo_name) + except tuf.KeyAlreadyExistsError: + log.debug('Key already present in the key database') + + director_obj = self.updater.repositories['director'] + + # _update_metadata carries out the verification of metadata + # and then updates the metadata of the repository + director_obj._update_metadata('targets', upperbound_filelength) + + # TODO: If this Targets metadata file indicates that + # the Timeserver key should be rotated then reset the + # clock used to determine the expiration of metadata + # to a minimal value. It will be updated in the next cycle. + + # Is the metadata is not expired? + director_obj._ensure_not_expired(director_obj.metadata['current']['targets'], 'targets') + + validated_targets_from_director = [] + # Do we have metadata for 'targets'? + if 'targets' not in director_obj.metadata['current']: + log.debug('No metadata for \'targtes\'. Unable to determine targets.') + validated_targets_from_director = [] + + # Get the targets specified by the role itself. + for filepath, fileinfo in six.iteritems(director_obj.metadata['current']['targets']['targets']): + new_target = {} + new_target['filepath'] = filepath + new_target['fileinfo'] = fileinfo + + validated_targets_from_director.append(new_target) + + for target in validated_targets_from_director: + # 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 \ + self.ecu_serial != target['fileinfo']['custom']['ecu_serial']: + continue + + validated_targets_for_this_ecu.append(target) + + self.validated_targets_for_this_ecu = validated_targets_for_this_ecu + + + + + def get_validated_target_info(self, target_filepath): """ COPIED EXACTLY, MINUS COMMENTS, from primary.py. @@ -602,18 +712,56 @@ def get_validated_target_info(self, target_filepath): def process_metadata(self, metadata_archive_fname): """ - Expand the metadata archive using _expand_metadata_archive() - Validate metadata files using fully_validate_metadata() - Select the Director targets.json file - Pick out the target file(s) with our ECU serial listed - Fully validate the metadata for the target file(s) + Runs either partial or full metadata verification, based on the + value of self.partial_verifying. + Note that in both cases, the use of files and archives is not key. Keep an + eye on the procedure without regard to them. The central idea is to take + the metadata pointed at by the argument here as untrusted and verify it + using the full verification or partial verification algorithms from the + Uptane Implementation Specification. It's generally expected that this + metadata comes to the Secondary from the Primary, originally from the + Director and Image repositories, but the way it gets here does not matter + as long as it checks out as trustworthy. + + Full: + The given filename, metadata_fname, should point to an archive of all + metadata necessary to perform full verification, such as is produced by + primary.save_distributable_metadata_files(). + process_metadata expands this archive to a local directory where + repository files are expected to be found (the 'unverified' directory in + directory self.full_client_dir). + Then, these expanded metadata files are treated as repository metadata by + the call to fully_validate_metadata(). The Director targets.json file is + selected. The target file(s) with this Secondary's ECU serial listed is + fully validated, using whatever provided metadata is necessary, by the + underlying TUF code. + + Partial: + The given filename, metadata_fname, should point to a single metadata + role file, the Director's Targets role. The signature on the Targets role + file is validated against the Director's public key + (self.director_public_key). If the signature is valid, the new Targets + role file is trusted, else it is discarded and we TUF raises a + tuf.BadSignatureError. + (Additional protections come from the Primary + having vetted the file for us using full verification, as long as the + Primary is trustworthy.) + From the trusted Targets role file, the target with this Secondary's + ECU identifier/serial listed is chosen, and the metadata describing that + target (hash, length, etc.) is extracted from the metadata file and + taken as the trustworthy description of the targets file to be installed + on this Secondary. """ + tuf.formats.RELPATH_SCHEMA.check_match(metadata_archive_fname) self._expand_metadata_archive(metadata_archive_fname) - # This entails using the local metadata files as a repository. - self.fully_validate_metadata() + # This verification entails using the local metadata files as a repository. + if self.partial_verifying: + self.partial_validate_metadata() + else: + self.fully_validate_metadata() From f0b4611e0d3d2348bebc3a4846f27ee72aa6df5d Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Thu, 23 May 2019 19:30:43 -0400 Subject: [PATCH 21/40] Update to store last validated targets until new found Earlier even if no target_info was provided the validated_targets_for_this_ecu would be emptied. Now if new target info is present, then the list validated_targets_for_this_ecu would be updated --- uptane/clients/secondary.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/uptane/clients/secondary.py b/uptane/clients/secondary.py index dec6446..835f004 100644 --- a/uptane/clients/secondary.py +++ b/uptane/clients/secondary.py @@ -566,8 +566,8 @@ def fully_validate_metadata(self): ENDCOLORS) continue - - self.validated_targets_for_this_ecu = validated_targets_for_this_ecu + if validated_targets_for_this_ecu: + self.validated_targets_for_this_ecu = validated_targets_for_this_ecu @@ -675,7 +675,8 @@ def partial_validate_metadata(self): validated_targets_for_this_ecu.append(target) - self.validated_targets_for_this_ecu = validated_targets_for_this_ecu + if validated_targets_from_director: + self.validated_targets_for_this_ecu = validated_targets_for_this_ecu From c0f890278b11834e883325f33b1fdf91bca4a55b Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Thu, 23 May 2019 19:35:14 -0400 Subject: [PATCH 22/40] Restructure the test data in the test_secondary Earlier the test data was stores in seperate lists like TEMP_CLIENT_DIRS, vins, ecu_serials and secondary_instances Now all the data corresponding to a particular secondary is stored in a dictionary and all the dictionaries are further stored in a list TEST_INSTANCES --- tests/test_secondary.py | 203 ++++++++++++++++++++++++---------------- 1 file changed, 123 insertions(+), 80 deletions(-) diff --git a/tests/test_secondary.py b/tests/test_secondary.py index cdd92bd..f39f773 100644 --- a/tests/test_secondary.py +++ b/tests/test_secondary.py @@ -57,15 +57,42 @@ os.path.join(TEST_DATA_DIR, 'temp_test_secondary1'), os.path.join(TEST_DATA_DIR, 'temp_test_secondary2')] -# I'll initialize these in the __init__ test, and use this for the simple -# non-damaging tests so as to avoid creating objects all over again. -secondary_instances = [None, None, None] -# Changing these values would require producing new signed test data from the -# Timeserver (in the case of nonce) or a Secondary (in the case of the others). +# For each Secondary instance we'll use in testing, a dictionary of the +# client directory, whether or not the instance is partial-verifying, the +# vehicle's ID, the Secondary's ID, and a reference to the instance. +# Also note the nonce we'll use when validating sample time attestation data. +# Changing the nonce or would require producing new signed test data +# from the Timeserver (in the case of nonce) or a Secondary (in the case of the +# others). + nonce = 5 -vins = ['democar', 'democar', '000'] -ecu_serials = ['TCUdemocar', '00000', '00000'] + +TEST_INSTANCES = [ + { + 'client_dir': os.path.join(TEST_DATA_DIR, 'temp_secondary0'), + 'partial_verifying': False, + 'vin': 'democar', + 'ecu_serial': 'TCUdemocar', + 'instance': None}, + { + 'client_dir': os.path.join(TEST_DATA_DIR, 'temp_secondary1'), + 'partial_verifying': False, + 'vin': 'democar', + 'ecu_serial': '00000', + 'instance': None}, + { + 'client_dir': os.path.join(TEST_DATA_DIR, 'temp_secondary2'), + 'partial_verifying': False, + 'vin': '000', + 'ecu_serial': '00000', + 'instance': None}, + { + 'client_dir': os.path.join(TEST_DATA_DIR, 'temp_partial_secondary0'), + 'partial_verifying': True, + 'vin': 'vehicle_w_pv_bcu', + 'ecu_serial': 'pv_bcu', + 'instance': None}] # Set starting firmware fileinfo (that this ECU had coming from the factory) # It will serve as the initial firmware state for the Secondary clients. @@ -89,9 +116,9 @@ def destroy_temp_dir(): # Clean up anything that may currently exist in the temp test directories. - for client_dir in TEMP_CLIENT_DIRS: - if os.path.exists(client_dir): - shutil.rmtree(client_dir) + for instance_data in TEST_INSTANCES: + if os.path.exists(instance_data['client_dir']): + shutil.rmtree(instance_data['client_dir']) @@ -152,9 +179,9 @@ def setUpClass(cls): # We're going to cheat in this test module for the purpose of testing # and update tuf.conf.repository_directories before each Secondary is # created, to refer to the client we're creating. - for client_dir in TEMP_CLIENT_DIRS: + for instance_data in TEST_INSTANCES: uptane.common.create_directory_structure_for_client( - client_dir, + instance_data['client_dir'], TEST_PINNING_FNAME, {'imagerepo': TEST_IMAGE_REPO_ROOT_FNAME, 'director': TEST_DIRECTOR_ROOT_FNAME}) @@ -191,8 +218,8 @@ def test_01_init(self): secondary.Secondary( full_client_dir=42, director_repo_name=demo.DIRECTOR_REPO_NAME, - vin=vins[0], - ecu_serial=ecu_serials[0], + vin= TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -208,10 +235,10 @@ def test_01_init(self): # Invalid director_repo_name with self.assertRaises(tuf.FormatError): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=42, - vin=vins[0], - ecu_serial=ecu_serials[0], + vin=TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -222,10 +249,10 @@ def test_01_init(self): # Unknown director_repo_name with self.assertRaises(uptane.Error): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name='string_that_is_not_a_known_repo_name', - vin=vins[0], - ecu_serial=ecu_serials[0], + vin=TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -236,10 +263,10 @@ def test_01_init(self): # Invalid VIN: with self.assertRaises(tuf.FormatError): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=demo.DIRECTOR_REPO_NAME, vin=5, - ecu_serial=ecu_serials[0], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -250,9 +277,9 @@ def test_01_init(self): # Invalid ECU Serial with self.assertRaises(tuf.FormatError): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=demo.DIRECTOR_REPO_NAME, - vin=vins[0], + vin=TEST_INSTANCES[0]['vin'], ecu_serial=500, ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, @@ -264,10 +291,10 @@ def test_01_init(self): # Invalid ECU Key with self.assertRaises(tuf.FormatError): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=demo.DIRECTOR_REPO_NAME, - vin=vins[0], - ecu_serial=ecu_serials[0], + vin=TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key={''}, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -278,10 +305,10 @@ def test_01_init(self): # Invalid initial time: with self.assertRaises(tuf.FormatError): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=demo.DIRECTOR_REPO_NAME, - vin=vins[0], - ecu_serial=ecu_serials[0], + vin=TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time='potato', timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -292,10 +319,10 @@ def test_01_init(self): # Invalid director_public_key: with self.assertRaises(tuf.FormatError): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=demo.DIRECTOR_REPO_NAME, - vin=vins[0], - ecu_serial=ecu_serials[0], + vin=TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -310,10 +337,10 @@ def test_01_init(self): # for full verification are determined based on the root metadata file. with self.assertRaises(uptane.Error): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=demo.DIRECTOR_REPO_NAME, - vin=vins[0], - ecu_serial=ecu_serials[0], + vin=TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -322,10 +349,10 @@ def test_01_init(self): partial_verifying=False) with self.assertRaises(uptane.Error): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=demo.DIRECTOR_REPO_NAME, - vin=vins[0], - ecu_serial=ecu_serials[0], + vin=TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, @@ -337,10 +364,10 @@ def test_01_init(self): # Invalid timeserver key with self.assertRaises(tuf.FormatError): secondary.Secondary( - full_client_dir=TEMP_CLIENT_DIRS[0], + full_client_dir=TEST_INSTANCES[0]['client_dir'], director_repo_name=demo.DIRECTOR_REPO_NAME, - vin=vins[0], - ecu_serial=ecu_serials[0], + vin=TEST_INSTANCES[0]['vin'], + ecu_serial=TEST_INSTANCES[0]['ecu_serial'], ecu_key=TestSecondary.secondary_ecu_key, time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.initial_time, # INVALID @@ -362,16 +389,23 @@ def test_01_init(self): # Initialize three clients and perform checks on each of them. - for i in range(0, len(TEMP_CLIENT_DIRS)): - client_dir = TEMP_CLIENT_DIRS[i] - ecu_serial = ecu_serials[i] - vin = vins[i] + for instance_data in TEST_INSTANCES: + client_dir = instance_data['client_dir'] + ecu_serial = instance_data['ecu_serial'] + vin = instance_data['vin'] + + # Partial verification Secondaries need to be initialized with the + # Director's public key. + if instance_data['partial_verifying']: + director_public_key_for_ecu = self.key_directortargets_pub + else: + director_public_key_for_ecu = None # Try initializing each of three secondaries, expecting these calls to - # work. Save the instances for future tests as elements in a module list - # variable(secondary_instances) to save time and code. + # work. Save the instances for future tests as elements in a module + # variable (TEST_INSTANCES) to save time and code. tuf.conf.repository_directory = client_dir - secondary_instances[i] = secondary.Secondary( + instance_data['instance'] = secondary.Secondary( full_client_dir=client_dir, director_repo_name=demo.DIRECTOR_REPO_NAME, vin=vin, @@ -380,11 +414,9 @@ def test_01_init(self): time=TestSecondary.initial_time, timeserver_public_key=TestSecondary.key_timeserver_pub, firmware_fileinfo=factory_firmware_fileinfo, - director_public_key=None, - partial_verifying=False) - - instance = secondary_instances[i] - + director_public_key=director_public_key_for_ecu, + partial_verifying=instance_data['partial_verifying']) + instance = instance_data['instance'] # Check the fields initialized in the instance to make sure they're correct. # Fields initialized from parameters @@ -399,11 +431,9 @@ def test_01_init(self): TestSecondary.initial_time, instance.all_valid_timeserver_times[1]) self.assertEqual( TestSecondary.key_timeserver_pub, instance.timeserver_public_key) - self.assertTrue(None is instance.director_public_key) - self.assertFalse(instance.partial_verifying) # Fields initialized, but not directly with parameters - self.assertTrue(None is instance.last_nonce_sent) + self.assertIsNone(instance.last_nonce_sent) self.assertTrue(instance.nonce_next) # Random value self.assertIsInstance( instance.updater, tuf.client.updater.Updater) @@ -448,7 +478,7 @@ def test_10_nonce_rotation(self): """ # We'll just test one of the three client instances, since it shouldn't # make a difference. - instance = secondary_instances[0] + instance = TEST_INSTANCES[0]['instance'] old_nonce = instance.nonce_next @@ -471,7 +501,7 @@ def test_20_update_time(self): # We'll just test one of the three client instances, since it shouldn't # make a difference. - instance = secondary_instances[0] + instance = TEST_INSTANCES[0]['instance'] # Try a good time attestation first, signed by an expected timeserver key, # with an expected nonce (previously "received" from a Secondary) @@ -560,6 +590,14 @@ def test_20_update_time(self): with self.assertRaises(uptane.BadTimeAttestation): instance.update_time(time_attestation__wrongnonce) + # Conduct one test with a different secondary instance: + # Expect that if a time attestation is submitted to be validated by a + # Secondary that hasn't ever sent a nonce, the validation function will + # reject the time attestation. (Because it doesn't matter, we'll use the + # same sensible time attestation previously generated in this test func.) + with self.assertRaises(uptane.BadTimeAttestation): + TEST_INSTANCES[1]['instance'].validate_time_attestation(time_attestation) + # TODO: Consider other tests here. @@ -574,7 +612,7 @@ def test_25_generate_signed_ecu_manifest(self): # We'll just test one of the three client instances, since it shouldn't # make a difference. - ecu_manifest = secondary_instances[0].generate_signed_ecu_manifest() + ecu_manifest = TEST_INSTANCES[0]['instance'].generate_signed_ecu_manifest() # If the ECU Manifest is in DER format, check its format and then # convert back to JSON so that we can inspect it further. @@ -612,9 +650,9 @@ def test_40_process_metadata(self): Tests uptane.clients.secondary.Secondary::process_metadata() Tests three clients: - - secondary_instances[0]: an update is provided in Director metadata - - secondary_instances[1]: no update is provided in Director metadata - - secondary_instances[2]: no Director metadata can be retrieved + - TEST_INSTANCES[0]: an update is provided in Director metadata + - TEST_INSTANCES[1]: no update is provided in Director metadata + - TEST_INSTANCES[2]: no Director metadata can be retrieved """ # --- Test this test module's setup (defensive) @@ -623,12 +661,12 @@ def test_40_process_metadata(self): # client directories when the directories were created by the # create_directory_structure_for_client() calls in setUpClass above, and # only the root metadata file. - for client_dir in TEMP_CLIENT_DIRS: + for instance_data in TEST_INSTANCES: for repo in ['director', 'imagerepo']: self.assertEqual( ['root.' + tuf.conf.METADATA_FORMAT], sorted(os.listdir(os.path.join( - client_dir, 'metadata', repo, 'current')))) + instance_data['client_dir'], 'metadata', repo, 'current')))) # --- Set up this test @@ -643,9 +681,14 @@ def test_40_process_metadata(self): # Continue set-up followed by the test, per client. - for i in range(0, len(TEMP_CLIENT_DIRS)): - client_dir = TEMP_CLIENT_DIRS[i] - instance = secondary_instances[i] + # Only tests the full verification secondaries + for instance_data in TEST_INSTANCES: + + if instance_data['partial_verifying']: + continue + + client_dir = instance_data['client_dir'] + instance = instance_data['instance'] # Make sure TUF uses the right client directory. # Hack to allow multiple clients to run in the same Python process. @@ -663,7 +706,7 @@ def test_40_process_metadata(self): # Process this sample metadata. - if instance is secondary_instances[2]: + if instance_data is TEST_INSTANCES[2]: # Expect the update to fail for the third Secondary client. with self.assertRaises(tuf.NoWorkingMirrorError): instance.process_metadata(archive_fname) @@ -685,15 +728,15 @@ def test_40_process_metadata(self): # For clients 0 and 1, we expect root, snapshot, targets, and timestamp for # both director and image repo. - for client_dir in [TEMP_CLIENT_DIRS[0], TEMP_CLIENT_DIRS[1]]: + for instance_data in TEST_INSTANCES[0:2]: for repo in ['director', 'imagerepo']: self.assertEqual([ 'root.' + tuf.conf.METADATA_FORMAT, 'snapshot.' + tuf.conf.METADATA_FORMAT, 'targets.' + tuf.conf.METADATA_FORMAT, 'timestamp.' + tuf.conf.METADATA_FORMAT], - sorted(os.listdir(os.path.join(client_dir, 'metadata', repo, - 'current')))) + sorted(os.listdir(os.path.join(instance_data['client_dir'], + 'metadata', repo, 'current')))) # For client 2, we are certain that Director metadata will have failed to # update. Image Repository metadata may or may not have updated before the @@ -711,15 +754,15 @@ def test_40_process_metadata(self): # Client 0 should have validated expected_updated_fileinfo. self.assertEqual( expected_updated_fileinfo, - secondary_instances[0].validated_targets_for_this_ecu[0]) + TEST_INSTANCES[0]['instance'].validated_targets_for_this_ecu[0]) # Clients 1 and 2 should have no validated targets. - self.assertFalse(secondary_instances[1].validated_targets_for_this_ecu) - self.assertFalse(secondary_instances[2].validated_targets_for_this_ecu) + self.assertFalse(TEST_INSTANCES[1]['instance'].validated_targets_for_this_ecu) + self.assertFalse(TEST_INSTANCES[2]['instance'].validated_targets_for_this_ecu) # Finally, test behavior if the file we indicate does not exist. - instance = secondary_instances[0] + instance = TEST_INSTANCES[0]['instance'] with self.assertRaises(uptane.Error): instance.process_metadata('some_file_that_does_not_actually_exist.xyz') @@ -731,7 +774,7 @@ def test_50_validate_image(self): image_fname = 'TCU1.1.txt' sample_image_location = os.path.join(demo.DEMO_DIR, 'images') - client_unverified_targets_dir = TEMP_CLIENT_DIRS[0] + '/unverified_targets' + client_unverified_targets_dir = TEST_INSTANCES[0]['client_dir'] + '/unverified_targets' if os.path.exists(client_unverified_targets_dir): shutil.rmtree(client_unverified_targets_dir) @@ -741,12 +784,12 @@ def test_50_validate_image(self): os.path.join(sample_image_location, image_fname), client_unverified_targets_dir) - secondary_instances[0].validate_image(image_fname) + TEST_INSTANCES[0]['instance'].validate_image(image_fname) with self.assertRaises(uptane.Error): - secondary_instances[1].validate_image(image_fname) + TEST_INSTANCES[1]['instance'].validate_image(image_fname) with self.assertRaises(uptane.Error): - secondary_instances[2].validate_image(image_fname) + TEST_INSTANCES[2]['instance'].validate_image(image_fname) From 95df574adc82e066ddbf46cd459aa61dffaa88dc Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 24 May 2019 14:47:24 -0400 Subject: [PATCH 23/40] Update the test verifying the parital_metadata generaton for secondary Update the function get_partial_metadata_fname to get_partial_metadata_archive_fname --- tests/test_primary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_primary.py b/tests/test_primary.py index f7087a9..a9db3fc 100644 --- a/tests/test_primary.py +++ b/tests/test_primary.py @@ -873,11 +873,11 @@ def test_61_get_full_metadata_archive_fname(self): - def test_62_get_partial_metadata_fname(self): + def test_62_get_partial_metadata_archive_fname(self): # TODO: More thorough tests. - fname = TestPrimary.instance.get_partial_metadata_fname() + fname = TestPrimary.instance.get_partial_metadata_archive_fname() self.assertTrue(fname) From 8084fdaebe30edad203fcbaa09bfe7b2d62df120 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Fri, 24 May 2019 15:02:52 -0400 Subject: [PATCH 24/40] Update samples to accomodate the changes in partial metadata Earlier the partial metadata supplied by the primary was just director_targets file but now the primary provides a archive with the structure similar to that of full metadata --- .../partial_metadata_archive.zip | Bin 0 -> 1506 bytes .../director/metadata/targets.der} | Bin .../director/metadata/targets.json} | 0 .../partial_metadata_archive.zip | Bin 0 -> 1859 bytes .../director/metadata/targets.der} | Bin .../director/metadata/targets.json} | 0 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive.zip rename samples/metadata_samples_long_expiry/initial_w_no_update/{director_targets.der => partial_metadata_archive/director/metadata/targets.der} (100%) rename samples/metadata_samples_long_expiry/initial_w_no_update/{director_targets.json => partial_metadata_archive/director/metadata/targets.json} (100%) create mode 100644 samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive.zip rename samples/metadata_samples_long_expiry/update_to_one_ecu/{director_targets.der => partial_metadata_archive/director/metadata/targets.der} (100%) rename samples/metadata_samples_long_expiry/update_to_one_ecu/{director_targets.json => partial_metadata_archive/director/metadata/targets.json} (100%) diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive.zip b/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive.zip new file mode 100644 index 0000000000000000000000000000000000000000..11a4680e9cfedc25f252a14ac9aec817b7712b0c GIT binary patch literal 1506 zcmWIWW@h1H0D+|P9e!X2l#pbQVJJu}D#=XDiO)?fNlZyBNsLb{O3uhEOVtkz;bdU` zZucVQ9}t&Ta5FHnykKTv022XlBU90gRKjOuN@h`Na!Gy>#O%L7a~bXNn4O1awl+~_ z!#o19gdb>u0Cr16fT6&_@U3EtpJLe6eR7Np4BLR%oHR>H5{uGPON#Zfiu3cpA;$3g zQ49wVqlK7hfM?!e1A*PYMcdA4Dx6=W`u@a9*MJPkrEZs1Hr!q0IAMQyT-6NbPRU7! z-7l}&Xdip%i|vUO-`+-F-qvrm_Scl^`Pz$lUWMEUbmVWba=QN0;YH*HX@$aN8w!%P z9J$5zx@OG=h2x9%o^yLEwpV|q-Ma1tDGBfX91H(Flco7!O8UeZ$sQ@PQj={x{ALAt zoYBlod~~9wF7oVch&Pkg-Yeua%&FGEeTBJFV z;X=TxIoDRTRhqpm+-|B=d}Sx+SG&9UM(#E(4^=MjcoG(Qu81qRes9n1M?p84=Dkio z{B=)R)ts%1{Ot31&%Y-O!WMg#`11bz!;DBz zpy>E!e)miVFmgcHjJ)VbNiD(`8wQQ73q%_j85>!u)faL%F)}V@U&z|P$gq&1k&$sx zra?N3LNd?Smd}%%xfLasSjJf<`L%t0deq#NeR=1;n9tE4CS2oUWMpypv2E|N&B05p zjNSG)#Mazg<`5CDqP4c>`xN1YQSyt$M48M@ugNCN=;=DvU8AgU?`)Q*S>4MsEqYd^ z4%T08U*!n!W@M6M#+5lGpxIP_;jbfzi6^VFLb57aqC++wGovCKAB@jPY|q literal 0 HcmV?d00001 diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/director_targets.der b/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.der similarity index 100% rename from samples/metadata_samples_long_expiry/initial_w_no_update/director_targets.der rename to samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.der diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/director_targets.json b/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.json similarity index 100% rename from samples/metadata_samples_long_expiry/initial_w_no_update/director_targets.json rename to samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.json diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive.zip b/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive.zip new file mode 100644 index 0000000000000000000000000000000000000000..fa646a70a63854757e59b2bf06aeeba76cf05dec GIT binary patch literal 1859 zcmWIWW@h1H00Euy9e!X2l#pbQVJJu}D#=XDiO)?fNlZyBNsJFJO3uhEOVtkz;bdT* zZ1*B&IuMsua5FHnykKTv022XlBaP6ERKjOuN@h`Na!Gy>#Owt?bI;;4+Y!xdZKBME zc?4p~F5H%g07HR;;akNPzn!(OWFIp!Fo*&DXHJ?WC5c7psU^jFS;hHz;1FZ@{U~N7 zrmsSS4*Dq@^6dSt?N-cot?ZG?vs3y9Qo}hcJD0EB7H~x}OyNrX{WCs}yW3^{$k^}S zGyS~p_5R;)yZ!RjrGMXkcYWDwyW1~+-T$_6J@3orW}|{n&BCAfj1;@7)N}lpD!mgE z9v{-HYFlCGnfLU81;0XobN$v2wzs!f>$QY+NzA+feY|M*`mzmc{eC|_U9odV#5R%V zUsIPqPW<|%@LAllH-Ag_OWhUTQNdf}`%>=Zwf5*q^R>U0-2CkN`|-bbjQJnZs^;(8 zuueb!aY@a;b<(Ak)lzS_TtB?%>$`=wK5u-nGXB@uV!;x38 zFtxRC!yKjJl&LdbP0&#-I638(x^&PgrenU1St>pcqR*#`2x_S=GpOIbDEE-T2K^u} z*%-CFcwW`i@R|}2iL7t`_y0}Mznyu#BympT=W?z3HzwN@qT9`Q?f$bOG6g72t>g~> zq5@1)AZ$inno3D6!k45Bniv%pG_G%8WNc)qR$s{7#K^R`alyjIxj>PHjnfSprz~h} zZs67raSk=qGt?`oC~0IA1S!fhP~~D|WKjsael~1HoXHbjhB*1k121DU&94S^9oWox z`&n=J1FINC16M9aW)_Dj*BxiKu?g6lSuL!8-5y%Hvf_SU2Xo(|mj4|$`GWmc6-lr# zw``m_=}bl1*EcJeAEx@uIs182z%|Kk8+Sv++${@vnz(>&NJ-7jPfjdqWMo{FX^_sM zkj(S7AP_4{Sz16pRMQsejBw*Sw_>3T8c{zdm)&Gj3P z@C0}>GRZOHD*Ys&<)8q=Uq=uVPg%$cDGM>mL4@&`B_Xo$!T5|vDi?wFfXYQY_F$HZ z$o8x!${wUL5@;c)jKpdot`ZVrAuwSxY-tn$QaHm6?lHK9sO2Tla!`4R)pAIrVP++m iZ$Xx401Ip~EC**STz0dv0sY0m3WSDC3=Dr*Ks*3uGmO9h literal 0 HcmV?d00001 diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/director_targets.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.der similarity index 100% rename from samples/metadata_samples_long_expiry/update_to_one_ecu/director_targets.der rename to samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.der diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/director_targets.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.json similarity index 100% rename from samples/metadata_samples_long_expiry/update_to_one_ecu/director_targets.json rename to samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.json From 71e37fb6b213f04eb69cdf3c5fd659d067af1dee Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 28 May 2019 12:28:45 -0400 Subject: [PATCH 25/40] Update metadata files to include update for the partial verifying ECU Modified metadata files to include a possible update for the newly included partial verifying secondary ECU. --- .../full_metadata_archive.zip | Bin 13900 -> 10893 bytes .../director/metadata/root.der | Bin 631 -> 631 bytes .../director/metadata/root.json | 4 ++-- .../director/metadata/snapshot.der | Bin 217 -> 219 bytes .../director/metadata/snapshot.json | 6 +++--- .../director/metadata/targets.der | Bin 136 -> 136 bytes .../director/metadata/targets.json | 4 ++-- .../director/metadata/timestamp.der | Bin 196 -> 197 bytes .../director/metadata/timestamp.json | 6 +++--- .../imagerepo/metadata/root.der | Bin 631 -> 631 bytes .../imagerepo/metadata/root.json | 4 ++-- .../imagerepo/metadata/snapshot.der | Bin 217 -> 219 bytes .../imagerepo/metadata/snapshot.json | 6 +++--- .../imagerepo/metadata/targets.der | Bin 1105 -> 1105 bytes .../imagerepo/metadata/targets.json | 4 ++-- .../imagerepo/metadata/timestamp.der | Bin 196 -> 197 bytes .../imagerepo/metadata/timestamp.json | 6 +++--- .../partial_metadata_archive.zip | Bin 1506 -> 1129 bytes .../director/metadata/targets.der | Bin 136 -> 136 bytes .../director/metadata/targets.json | 4 ++-- .../full_metadata_archive.zip | Bin 14244 -> 11514 bytes .../director/metadata/root.der | Bin 631 -> 631 bytes .../director/metadata/root.json | 4 ++-- .../director/metadata/snapshot.der | 7 +++---- .../director/metadata/snapshot.json | 10 +++++----- .../director/metadata/targets.der | Bin 292 -> 447 bytes .../director/metadata/targets.json | 16 +++++++++++++--- .../director/metadata/timestamp.der | Bin 196 -> 197 bytes .../director/metadata/timestamp.json | 10 +++++----- .../imagerepo/metadata/root.der | Bin 631 -> 631 bytes .../imagerepo/metadata/root.json | 4 ++-- .../imagerepo/metadata/snapshot.der | Bin 217 -> 219 bytes .../imagerepo/metadata/snapshot.json | 6 +++--- .../imagerepo/metadata/targets.der | Bin 1105 -> 1105 bytes .../imagerepo/metadata/targets.json | 4 ++-- .../imagerepo/metadata/timestamp.der | Bin 196 -> 197 bytes .../imagerepo/metadata/timestamp.json | 6 +++--- .../partial_metadata_archive.zip | Bin 1859 -> 1748 bytes .../director/metadata/targets.der | Bin 292 -> 447 bytes .../director/metadata/targets.json | 16 +++++++++++++--- 40 files changed, 73 insertions(+), 54 deletions(-) diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive.zip b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive.zip index f2fb767f2e095df0985ee8510d918fbc2adbb24b..cb4c2752c6ea8f7db5cec52446ade7ae45a7e653 100644 GIT binary patch literal 10893 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Ξ``v~_-UN(75bQw4ZNEg)4dE|jz%i^zd;2)}9c!a2lG#_m1 z$r;L^;-e#*wgXQsQt9>dnE_!^sfib{NfK|DO2P2z?E8f!7(TrwOa9(l-9LVCz^t>f z*t)!SzU(`0cYUF(l$fA$?PO+qhU6TX!Liuf?H?WrC?;zDS}JeZCqrN!>pt;XSTD3< zgs5yqsVrO}CtfRWMpB03O;y&3Y+U~AG>TvEORbsQ#iRCQvFE8NayV~t(`5q0YsCh} zWGc{zR*g;zRCwe_a z%USJDRz}H(SNXPLRa-C0DNQ&Nqq5o3&!UzwI){lwy`8Jc9f5{$)~dg__EhagwljBf zzBbv~w@4e8-8+kRTMREcOfkpI5>ORXgxv_$)!H;JQK&$&l`g<2sgOfaY+;bAJU4dq z3ZH76jtFbwhAcu5!kO|BFU}Sr zeMzENmd%7foI5&3^PRk;(Z{CjJTX{;Y;3|2!tRmetAQLf;~0Y@?GM{58KC;4Pt|Pj+*q7gi|v&qkKe zq!VV{DVKc4Rq82WNzikVPAh=I~n^TdAUNprYVbd4cF3M=*^T%)&`v8RLrWqZsK z=A+82QgNfP6GJ{oH5u`eLZUXlV$Pj_V1ckVIf5AEFb+(s+OTJwaS4u!Ow8&QDY6C0 z4dOHxRpRV5V1yP96X4rV_RG*}(8G%PTqw1v4NqSP0YLP-J*cw0L!lF%v%?6GysYb& zZZ77eUtqIb$bJ8;@$GAxd`ojY5DP^k3hkCAIzC1skG{$2gkgJfF@*kb0elup6DSMX z^G(&}k@|+iUDJ>{B>SOf#aZN-@bYqLOL8KGn%3ABir^P!MFLt_EE8W@MOY0F>R?rk zxCy(7hVsOEC(WoLTowWF!Od0$$X0a3qfnw;)KGY2(Ae9bSt^8FW9D~@f*`g(SjFvr z^HJqla}8~H-e{$H<#CD>Hv5hoR_CkiC|%txw!ne6H%bi(maFgUh0!Dow*l+y^s+J2 zZ<(lQ0R9Wmzp;g$_!eK3(LXADgc^?t`{O`Og#Y(>Zh2GK!dkfwascq zD~TmAs=CS|8j_HtEy~QMBh|_p9w#;$LHa8XxDsh@I8EDj$rYU5d^mRqJtdx1Ufefx z0JtoR-hllkAcBEY#T?H}@qg)=KQ!^bce4LZof7{8KNIvn^D{7*WhHPRZd5jym?t0X zx(8>)hh<4N3wQ;H2qV+FXF7fn7$&_jA#QTHei>`;XjEl4%|>BMBklabT65r5tWV`hc(}>t<_!tCH$awlK6a4g(1|UUYaUY# z&}Qb9P9!+nQxT;QdT|i}`+K*zHP3;n*jx|rEKMA^W84xL|FB#9i*aamHTIMTS{$lf zf(wJws`OQqevITR!!WT?fs9l|kFp*?`WwMu#$r_*#m8~O9l;(7ycWR3IEzJ(MqS0o zG^eI1BDq{qh+*!|XK_=%o-!1vABOcD_jo20;#3i;R~@l0Uq0jynWyB}Lk2%RjaAX_ zFjviBN`W?>A&~fLPdCM^@+g zcmz9Bzd+~fyeqP7Zdg2cBrYqj=`vO7iK(vvn9D1Ur~T(g{TKF5pzS8L&?{f&HS@nt zUqP$~t*w7~Gf7H!t#Xch^p@j0Zeq2Y;U7n>@<#m;>edYKh&g4`g z8fjIrVLMDpwei+$f2ocgPOgB$$8|(}GI+C?@Agz`>HCQ*Mv@vN6Sh^0 zIs;GfGBDl9RB}Brey{4#%42S7C^ybUd07w+a|(^FjBfY4{dwhC^CDy`Awp|jP2kY@ zOs#peW@B>HsFj}N^9#chN;DD&^Uv!?Z3w+$kt3H9D`Qouod#y##vHWW-A-)!dsr3= zrm2_O7#XAGGeQ$7gK5-{`;5-4g}p_)^se$C$ZegkOW14Fk>&RH+Wl2ZU+v;TaS9NC zb-*+40_@E5n(f=m)+m#=*C&?yE#fT#+pU{!9&_xs_+m0nyRf8Q?S)fL_o+4|79|EP z%4-EtpY=wLXL(kg*O57%lgXdGZxZ)d&d9KM-MRG@ux4qYEVy%)peYXJe+IK2vT}NJ zw)X{Y6mNj%rPpRq!G=z=OiMJ|H!ZKwe70R0hT%(nv2mAPzw*Q*4~_yK@lPz8U6$<= zMuuR=+A=U3s^y*$d0#5##v7;0Gnbv)h3nMnrFox{^^jxfh_FZoA0PEXuXtv$1Nxk1 zQA~x4rrKu2sI6hLx%~>5mQK7?{Z9Nj0O+ii9H+Uv$K&UO6ei)6By8-C`{@17g~{-l z+yOWVO9s>P)&dNYMAy-WeIsF;iBn-3FPuy(0Qq~^H=iAL+PhS>+xF^cmA_qEThG>K zCa{OIAN%qu#2k;55Fo?{UYlS6lm=&mdvHO*O+p`JF~r7W3z9v5fXpFK0|jS zZ#{jkZ#BxXBE7x*YBgWwGJGzLM2o8X>r5oZ9b)FTMwld#uiO%xx6duZ{XmWQ#e@7y zk)U$n{ZBboYl1`1^-G4dayh}X#hRWT0BTDx4i_NdPn!T&)4R(RQ8xr8#d8o*%H;J)ZLqI}Wg@ z-sd^_ALcou%$qiwgB$!11bgNjZHDS3=M8ouNclIEa+={HY)~*AVj5aII+Ay_gm*>` zP9!7a`t)28KmC+n+ET`5%X=@-W5#565k_!6-qz2I0&@`)C1lvR?-3o`cY+d&hn zv9xGad(Vq{h3idy05Yo1v>i#@vw5Q|f($t4FVC-!>t>K^ZR7)Ec>2FjS2wf@l^#rv z>#4c@H z5;ozDb#*+E=mVD)>0lLG_7hwFgbgR%2P-W?GUC#O1!UN@FjJAyM&Vhg`CX*<%9KL- z!Ua{GGUuWO&0}Q&eHMU5G!DuBFW13E->Y0pON5_AI<6?z<$G5(uEMbj&(+l2Y-o)* z?)9)4hyc?G6zs*LKV5V|{}1bl2y(@F`}5AV7l@zNUjBCF< zs^6OYsLDW)+jGYU{qY;*{(;?HkP#rE@Bqkv6!}+(KOLIgg(wEDX<5fH!JnuWk z9ryd=F2*zV!x(G*)?DlP&AH~BYsyPOK%zkaK+VM;Q|9*fM}q;q=;+7@3d<>~GRpt4 z8_KWU3}4t888}$m{p=47z%_a-^%;AnRRq-UCFsGww?BgpgNUMzB50_QWhgT&fEXS! z(RaOZ^bL>oM<@;_l2`s^Utlt=N%V;k;0=2iw69me+$_;h(A8ff@|&v;NFLHT2}?BV z*+=Zf^=YQV|5;qzd#ATFa?mr>bI|)mZZ^Ze%T4j`1OL|+fcmQ(FD&&;jO>hTtbY-l z^tTNsxPQR^n!tGX&wtOBbNsgDl>cGm|Fb20(R?fgn)_$FRfRuL8zL*GbqEpwfFuF{ zZdKuDx1W4IP?g)P z?<+3S>nq#siy0|N7KC}Tzh4yC8(ObZeKIzBb2D+4ntZv9Wq_5&$-`Qpppp|PRfj@O z-g$a;@vut%&1bJGY}TYvw}Qe%*H9^Lksu^uAwOtj58e;MTT$)yhhjlUtNagF2vhv* zbg5=wu`Tf9Ac+Bk)tMLacu3-hZ^)fDV_IU+S`fNFfMwHmBSCbh8@`uiv7y9nWc5qM z=zb5OHv~mP04&$Cp9z)Eeb( z4bJH=54|S~D{Alclzc*-^{a_Yw@Y_*gU9 zr|jUJcXTHw z7*mX;ldUGQY(>vXbSa9=gb);+)JO)oa9+hsSSc?18IC&GH9VR&&P zKVg+ItsICw!Axh;HR-dd_F^Gz{z)li)}hG2pI`u{7qL19OT= zH#V6?ck*2_iJW6SOKUT>p4B)*V2%^yjuTThH4UCN^{xNl^9{?(8_T;2D-tFtC^;#R zjfE-7KkzZEYDk@GB{sEB=kk z^BZImkuQPX)q_fQOk-{XUIX)wGcuk#WNnLKf|2om)B7@@1u3%a>F`Z#oBtAUcdTN8 z4w;|k5%lsm(|3<)zdcRO06~_mpuUh;0Kn}r{L@ta?0;u76PXdZnb3qgvW3YLBEsnK zyl}IM)4brAY#um8j5@Co=a_B7UgZ(q6x+63_ab{ApSFs?&z-4Fa@i;MoR?y^6a=>G zg)#RhS;m@h>>%zfgfV+UZ}&*J#Y5B7+j%1yvC00lNqNdCavjI-aZs)FU?RfiG%hNr~ADaXX*!9Dd*%@U)-2nptoWTJAoO>5-XKn4kU}$9LAEhC# z4u%(a-cSRFUcy21)F7!;zpfwK0M2>c!b;VEqx$VEe0=z{5yV_bW%anhITNjKNC=x$ zivqH!+FN04Jqd+z{AMI?#CU(Fn#<@Dk7a&7j}FAX7lUSL51%zs2($RGS#fz`1U_L$ zSa>GD#`Rca4*I&3zhtwix@vGQzfsn7QihME8a-q!v_WvGOvu!+tYQ$CRD40YVs8~^qT+GRm`%(fC*on%y}v*bC+#e zm`l-zP~$?=W_Fs7(hj)vksoUp#k!G4T@DZnwDt*6_Nm;bco~RA1}fw%tutWu8QZO3 z=FD;W_MN}=OMYJB-z=!YvdtFrSL}`I;KU4eY;_yPQ7l%Z4kD#!)%BvXbqQ;rs~7f&Z(vpbHHp7V|Nuq4sqx|X~kazF|)U}>Qf#H!DEJR zJ$laOF56jeF}FMh%}wqnnvkDPe4^is`iba@mq8s z<96*0ne6^zQPz;?Vy_7&iAa6%wbp8;BD5}#Y|uo9_{`7AA3`@9A>DTjzeVU_VLY-_ z1Q^LPG!e(8%MZ$SR03Vk!`853#G=9;wLyT9VFJT;Q-|t`1<1mKnW-WD-)Z7M8cu9k z4fj^Zj()J)-TQ^f^YI|~ao;glMV1)fpq9q!FXF0xg@uJA_zLke!5EFk*01wAi=;wy3D2e7!+P4u#*sbp9$QR5az0zaM5g zL&acVeqG@f19|&fd-A>JlMJU7M%@01mimQHcGW6xV@g=@=d7Udm`!LRh$J1fYmJVR zo6Id`o5&}^iRl7-J1wWol!pi~Q5{({$jV;_=MD*0;Rq%Vmv!YfAqT@Ck3hz2>`Tsb z;o2f;RG!6tudMLw`A0^ zPP1lVDDh%sCQ&^H=1@`{SGmE*q~qk?P!{ZVn`ummLL9~yH})tNIR?56^qAd~lEJcU zdZ&Kqu6ms$3~G21(K+PFrO9xr@Gi&KFx}{s4aRex+alSb2=5hbin#QOyNvyN3)iSy z^vklg#z~o=w&}KLqC#w-cqqW$Xn8?hOf~ z%!cy;1Ity-QSkx;%SK^B@`7{FvokSru)npl-e6!kPrRUim|f7nKH%WazF=T^luYEX zU|^6$=F#(SySQE*rn$qC^qxo?JoK{GQ)=4l%ZjMxaf1UYFGCO$Z_xw}Z)gg!>qQq0)7UkJ5Oc?Y1>59B;dOP& z&}s_4SvS=gBjW z$sqjqO}RB0T2UibxXkc=M;@?(+XYb~(J@yz2SuU^@+dowo)$eV^f+i9=cY5G#N1{g z2p!2cyawCrtGz>+CBshE?T2C5Im~B;j|ng3OEFC6(Xt@2%pkj|!ZoGWX1AA-;X(v3 zArY0NEb__o;#hg<2R&x5Jcd}(IfZw?-X;d}jaKNBh-sk+msD*(L(w0jVaVNrj|MBU zJ&R|$q-tWy&CMF7hTv8v+*Ot2&ay)$q@{IG@xkB?d`MfP2m{cxgW7sJ&a7uO9G^A| zX1|bZ4Pj<3qB2O*=eDC<;7|*kj zQwaI`wa2zM*CQ|7T$RJpUuJMbUINoHxQHwZq}!efh2-|lhx8QZ62vU0$_cuo>cVro zoT@=2P<!jdFEBth;3w4# z;7|QdVuay{vGcgKx%FiOPB;5FMWG6V+ci%#K1NOdMYS*WZ3Q)o`8=#oTLrY-<_ZK< zb;xMS+|J`rv(*W6G0?#|*{AV!lfpv!L)}P;mPgDaI6QWS{TO&01$aL%OhNyhYQ!p2 z?F1mI)q|e<_3y_LZkhHZN=mjz07>MW&!63#B>a*!GmVs1-fwFHl}-p{?jbVqN_rK( zs)~e`^0h~bO8E5-O2L@m(>iONl=?E`g<|KX^Cq4*S+f54=${2%kvgO!`ei(514A)^ zg`<2bRccu|IzR(wM>oG`F*%WPPCT*T_$Us3INkG0odl%F;Qkn(Sh_SL(PyrxRNMEFee6A0UCZnjBt>2dEUNT$h7$*_1r`sQ%N!a zTO3beU9rJd!INnPT4c2X2C~ULhmJv(W2Ass>Gfs}Bkmz;>Fd*K-Al3h5SL4f#thDa z0dinl%#lWiw@TLA!`c;ZUWZD`8mOCAQMt&B4zg479@Ws|x9pA^SAQw-on>=tcxTrO zBE{`Eijgm7?SyV zIe;8XG&lSgwLZ-S%3K`PpfjF~S?N#3%wraGeOr8_$C#P0WOS6F(blct(Vi;ZLn~&M zrtGQQk*g!jhAfBq@|6^q`m8ZL=Yzjfiv{wqNFPKi8juwux?ekfZonD}4X1LbsA3|PoCt!Jas-)gwC7mW42x*jK78C=(X$!4vfDa6FQGr= za(ec;bm6}F z>Zj4o63=QAVnilF)Y)B8+r^*JUl=zW>aKYMDSxRheE zGSg6E88UVzZHZghE@0Uv+^MNmpFeJHiEHl(_S29K~jVoP1mKL?sSnF%fysKoY!u3!s-pS>^NDJ z_HeS&hE_m=^S3Xg2Fk8cEH&;r$xdm#=X|WlAH#w-t#!m@`k5g_3dkb4NMz>lY}JHP zaAQbY*@={2&4cp>&V$oubLF$Xr!nnC7D^8xh!bYs@Iob)r7nD1 zdjCxUF{mf=l`0f2aCDLcsK1lz->bJ*QozC6?{jLp96m3%KtB6|B6!b!UvFI+=l(PxsH2Q z{lfvhjr$crasMS@q{3D|c-e=+fK^|~!Z0;)wCssp!MIInLX0(|Q3um;OTPuR9A9H? z8b|Vs9k4FkpatKV9UHCyo(e^@bZ4}J)HSq8?kDT5ndgy_D@Q#x8>HT(M1i!hN*GUH z+Imsoox$KaZF-s6Lieq`jao6zFz1rkC&@k6#LZvAYf$HmeTz(qs&6Pn>?Ds2@Cv}I zFS=1ZP}?vakW`RU?{;b-L&A-6jhVo6@zpuB&7q2RkXFo>w!E2Z?eQc-;ej=QIAvd% z3@VDRJC)cFfpE)7$xdCpMn6s`crtX>Tbb&4x)XA(OLca!uxlR^jt@hn5Gs{-CKng_ zh|s|WXumaH7iS)ne$z?Ue#*@b*D!e@QW3(sHMdh-$qScn)aK)6r!pKSkCeC1Ag#v&TGfmq;2!{w~4KJ|e$1y4P z2&M(#*jM3gttv%gGOtd>pvdXy8yahQTgtEy;7?jGo-wqzI9uBn;4MridI(Cb(EBM2 zO|g^7!LFEOTFLU$jpr$emU=!xtsMW&k4MLyiF^+t$8FgE2er71`{M^ACI#RFjvkA~ zv5MC`!PtV7cOeqRsm01yGCg@SGvL>2J-OaqozYCZ zc%yBmmBuo$3 z?JqQM&mC?#Ns)JbJvURa?R0H7>uN3oqdxeoihXqVnXIz=$p>6P`xcay{GC0mmWlaZSft(P{aj|Ew;@wTrSZacV>5@onY%JNBGc(~UWF{p= zFpA)Uk3GKGM2oJSoVaLnr-Ms zd#0>IvQxskl{}Ahb^qqIQAakRZ|G)n$ryP1$5n=|^nt_$_vz5h&@$$%McOGZX2!=! zA(N2Ih9jblUo!9;G^L358Egu+ngzht^~CYrr9Rj7o%5zRb-dDlF)}L9HC=eXepwlJ zR@X@1$N8AQ)-k*_x95B);P_KL{#h$eapn1Rz;uaJzRLl9%QCUP;?6jJFHL#L!!v5p zk*25UlCr}JQ45l`e3FShdeG!jZJKzTYP%MDp5z} z7a?ocgA+0!%TZO2Z^6u_V(vno*$ES9+V1@hKei+uK1tNmxSMz`fNBI*n%-`FqbBJ( zg{?+{#H5yMAWlQHv@DkOM)0*NuW9+mo8&loVBJMMJycK*RlJZqiuLFWmU5zzNrH7$ zO8-Nm&Np)lNZB&u1{7ziO<^-9dRl@DK1~e3!vP_}=T0Z3*8sicxwD0I#G&TXNS;$u zwQsVoM;_i#60>R%8h(@A#^kZ$nCjS>B{WLed3oU*lj`}%Q{mGR)6qur)m44xHf3hV zUuos<&hbZ3*BJe1DDmvu^&c;{MBEx0Kk8+C7yNA2+8yTgo_BoPMzk;SctonsJtdpKQ^Wsp*1gjl*#3dgLdt|D^(pk@IDGad+39}4zYiJ9S&RW^X$hgNDsXP$o(TV8{*;#lcK#EKh6?Bq#oUm%3k zZ94{7{rolDJ5I1|CrEZef=F`3-N|1>V3-6lSRI1QvF^pY=2mw8RL4B4&k9w2V5J7b zvjKX~t=!}kj14|-UfDPt(;}CeMv6H2+v$63UIaqmuJAG+6VBKkpJKLN^hbr?cym$k zh6ZcKF+6>JTuOtXGb9jzdGzW%!uyJphnXia(eP2(O)JhK2{FzlUTqYZ{JU(EfL zsB7nShh-Sqn+NnbD6Ybe9-}$$;m5j{Ov=}1TL`vCdCs$Ue2OiY`iYvjB7El9D*P8X z+oT|f`_gT=x@{mW+%pWfRA(a}P?D z(>xh%Q&9#>i=yVMlrjZMA7ZBR2)J6e@CLQ;=T9tN6t@H8s+s&$=JmXofEu-xZS(8y z#$!lZ53oASb!usJB)~ONmu8N9UG1uu$)5KIH#~jpHd*>9$hZx8zS)sL^HoVI=qgBGETcs8el4SMmxBs}DSHy{I~yT8Ka?CppQolkswGCDRtCbNE zg|5{=4#ErGb6Ab(na>Jg9iJ4@+tC>+v*K99E}1<};9?l&@wufI_6mpHIwCCZMpzA< z<-CF7>q_kM&}6ddMxiUd<6bNB$~5quhbrmk)btyzE-ZFCeF-fk`TWhW2SK*50Rct^ zD_wcb=Utw^+yUNR@yN+dh}VGZ!5dH=$sbO%A2#9EbjPbSl`AvD?_a<=m}5}dqNJjb zk>QwTfe{;0auBIh!T5b~-m>uy5FKwV+_R4`h&1WHa9ds5IhYt=JXpHqJU(+cSo4L$ zWF&ccytbp}=Gu(RUG`?w-Bqi9Sw)87iCs%z3kJvhLT zq1X{$TSRVe%h{G~EAX=Ha3W&+cy{@T`{^gI14_dc4>xY-v#qHU)&j)H#g+Du$coGi zZ$4$S6PBG#8fU4t{&n@}%k9ad_RJH~gQ-2v!+2VU&7}cnetk?ix&4D!>)_E2h0mxZ z?s&-)Fs2BiGtBPL(Z>6$d4P#vm71jiva-J1#%HH$W%YF~!-t!XlkDGU??%U@;pu!; z=Vo3FKel864^woZHjb44a>mZ?7#EEuQ>2G6NV|Ke&6|v=0@m^k8?=Jcn`?-9`}HvD^}@O&Sa{bi%^f48Y7;N zP{;CoXy`l!44IBt>a|7766bUGrM~=Rt@}=m(otRS5qE8G8k; zcx{rD)h29|A8RHm_zsU}cW4CMfTsXbnl9y1>5(2%$k#kIuWaYbN78CiLvKH}D@J0d z1OtuH&`)5{YUv>i1QbdXy0eZ+R4nk^4w6#ycXczXQ-dIs81R(4Yzfd`)0z^B*Bxh) za+rpH<&DXj{3ge=)a-sTdh?0_wl$EnWvY~kIcb!N2WBkMC6u|=KH79)!WfLND;1Ci zl!!UQP1}-u9fJp7qzuz$HD4f6_Kt?vvUMVu zAIgi3ua%AK%fAT%*Alm7yTVN}1Xq#`NUrTI@G;(w@j){Ve+bW~!lF9y933?c_W2H$ zxbOCH))WkY;WhCBgjGA^tVpr>CS#@*+N8)(vj%JbG6m8AMMNH!?wqOgolktQ$-e0719KVd*^gE5IwIKksk-!73#+HN z8-WT88D(w@Z#)ert{xP4au$9jPZICSHif1!t9%Y+Qu)rHPz`4tw#t0LPh2Ach52}x zrIYI7k+gYvLATa95qzxz&&(TJk>RrY$y8$0`LvYo38Ho}-+>h{s#>41@!*aL}F?u(v+Y zod=H}+{*SNz5NGY^^f`O?=Jj*li!{Xj@?*46V@KmbSr{T^6Xm1D6cadg=8VMo%7f; zlXLv(ty_FKwI(7TQIE&_6{3K9G(}X6fyFCTxhTN z9^H9?w-(<(?JJos$Xmb7ZxjE)3;cV6J8CEbj~JBTzD5snqyYEr`s_SRqkx8OQ9vWX z&!!yA67qFLSoxgJc(9L$ul)w0pW$REd1j@ec{caLmC4wze>U?g$B?+it_&<@mRGM( zcuhhQxb8qHJy39lCLt}kS5UZImTl8dt0gqzpfKI}on!O90J^~E&FIKe4e?>VX(pRx z$4_BTguG&^$-oc1s@9_bAJJ0*BA>MJ9|{)Go$zp~rAe`U9Q;4TK^DYHxv z2a+N(+7|=+r>+2x4wEZ}9Mt%4R2jC})a&~J{!3wQQ*GbdtuW#s_58WOJ7MnK zs{4wQza!mBOZu zrlkj^B!5nO?_S(j&38Ay*1hBJ5#;^~xjUd|AbO*7vnw$A`dv49o@AsN_QZW5(+&!cQrRVOsCcB%M1awvUUznQTug&fxP`n@D z9#OvQ*Y8(RcSsW0_loi-1^fNV;tt3RbO7D6rk@uWziQZhDc(CIKTyu>9;AO$vEMVJ zcVH=?VEf;|{>~U^z{Q=@J*BxH?BCSv gcL((jO`P^WpnVta!Nc1j#GpSpkd@x1yZ!Hf03{=l5&!@I diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/root.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/root.der index 1e9e82aa3a929e48a6cde8d1878bf9cd199869ee..c26630a783c9e1c1e75a8929fbbf0ee69279f684 100644 GIT binary patch delta 91 zcmV-h0Hpu-1os3HFoFVepn?JcfB^u31bpMdJh2fb0zjyr^GS!vglF9+GWa~1MjM-1 x1*V-Qo=9U;keM0L)Be%1GEg*sIB8Ir@Z+guoKz}+S&Wk}MZvj#)ClUYv diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/root.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/root.json index 790a3a4..90b7c7b 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/root.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/root.json @@ -3,7 +3,7 @@ { "keyid": "fdba7eaa358fa5a8113a789f60c4a6ce29c4478d8d8eff3e27d1d77416696ab2", "method": "ed25519", - "sig": "e9855e5171934d56a78033cead3dc217d6df3730f9c668742346a4e66f0d1141fe7283a21964e0c35163e76b6103e36a04d44f1b0799fe34af45c65f32f38b09" + "sig": "a89ff34987c98467dd2732f83c99461b9b5905a69d269e486353909919d1d3fed1b13250347f38695098f0e3a9649c542a837fecb49630e28aa7c97ba789270c" } ], "signed": { @@ -12,7 +12,7 @@ "gz" ], "consistent_snapshot": false, - "expires": "2037-09-28T12:46:29Z", + "expires": "2036-05-25T04:06:20Z", "keys": { "630cf584f392430b2119a4395e39624e86f5e5c5374507a789be5cf35bf090d6": { "keyid_hash_algorithms": [ diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/snapshot.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/snapshot.der index ff0488e11e983c8a048d0f487755000a57211609..a627306e4f29271e6a7102b90bb2a00fd456087f 100644 GIT binary patch delta 200 zcmcb~c$=}_pz+3nj0Q%gMwV*s@)=ExjEkcdMKmxnE)+HpY~U$LEJ{x;Db~v>&d+OP zWL#v|z*&@^UjpMdF>!b_GcqnwH&EqbWMonJwEvcJ$hJ+>A4e<`n-l7H+A7*UTfBXL z`?|>Z;_GGpg&-p`4boW@e%k!`Y-=3<#cX1&jKh@Gu2PpH(-;=-^*-&w(GV xM8+25?Y%j>+>Af9eu_W9_EO&E-MvJ^KZ5T*Kc4)^r6{d0>%jhu$j&SuJ^(~^QFZ_T delta 180 zcmcc3c#|>Ppz+#*v<60|MwV*zh1^YyjEf@{g*7lTE)+5lXy7hMEJ{x;Db`C#Eox+B zTx8q8QIwxw0_8R_F_kwnGA>a!P~~D|WKqbTcDI(Wm(rU#MR8Ty7YqF|D)y`-AI5e`tL tcqB_37qqU?xjQ6%&tZtM$-py)BHh$^Mgf2RDKs)r?)d;${BmV#Z diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/targets.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/targets.json index 8614ec3..60315a5 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/targets.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/targets.json @@ -3,7 +3,7 @@ { "keyid": "630cf584f392430b2119a4395e39624e86f5e5c5374507a789be5cf35bf090d6", "method": "ed25519", - "sig": "263a6d873455bac478366d92edc04cac55cc0e545b5f4249cf696254263d58de6a225446ce96725bccb83ffdaab548fcd89cb790020039ef336fb422364c1800" + "sig": "9e4b5d3f5bfe986739ce554825f2a6230f72110e50a578244b1b17b4aed1b93b247dcf6188b1c9d18686f58119c0aa3aa166180ec20cc4973d49c20249f4990e" } ], "signed": { @@ -12,7 +12,7 @@ "keys": {}, "roles": [] }, - "expires": "2037-09-28T12:46:29Z", + "expires": "2035-08-25T05:45:10Z", "targets": {}, "version": 1 } diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.der index e1e1df6c43c727752ee1ca2462ec51d3c3fb707c..7f4e52802496727bb0f80bccc429842be6c90e17 100644 GIT binary patch delta 160 zcmV;R0AK&a0mT7VFoD9LQh)&ifdqR4j+lZ00i#5tLVyi(Zeeh9Xm4~bYIARHfdK)6 z0s>Nl0Rf~ZFeM5B0R$i>Qef(n#P>7!Yw>;b08rpE5w%!L_D3+sYrS@0vs-A9RxUt$ z-d0w0)tI>o=D}d6-%I(3HV@>F)v2;S52yXKeKo03+^<1S#@C9O#D}V{I*#>UBBo#5 O*=YsS-=IR@o!$-<0!T&x delta 159 zcmV;Q0AT;c0mK1TFoD6KQGfvhfdqRep$mcm0i#2sL4XW%Zeeh9Xm4~bWMy)J0Re&n z0NH~90i-7|B?U)LhCqQ9wwy{{US8<9R=-eS(o!EoK=5SzoTD6G!ai1 Nc&JM_TG(a`HwBzULqz}p diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.json index b08013e..f2911d6 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.json @@ -3,16 +3,16 @@ { "keyid": "da9c65c96c5c4072f6984f7aa81216d776aca6664d49cb4dfafbc7119320d9cc", "method": "ed25519", - "sig": "3500dd4af7002c2a7d36c1a4b546b87979805a5c31f6038b39ca19d5d61868b5d3cc8c96c93eb44c3ed862859f45739e926626b73b7beb2d37ceeb83b32c8b0c" + "sig": "7bde565674d598b90ae6c160a7df4bf988360fe48fd5a9b23f0fa7fdb47d35a951dcaf414ec6d78a99c487aaaf3a8ef55f22a65fdcd96905d3dfa042df9dde0e" } ], "signed": { "_type": "Timestamp", - "expires": "2037-09-28T12:46:29Z", + "expires": "2035-05-26T22:18:00Z", "meta": { "snapshot.json": { "hashes": { - "sha256": "114817f21566942dbcdafe97e3d1aca1660db86d8920a872c65b76d9890409de" + "sha256": "265260ea93c4f733f86bf17df40050e03211b5584bf64730c76bbd7660b35b68" }, "length": 594, "version": 1 diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/root.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/root.der index 6fea55fcce628c6a57f51fe4856f29e0aae645a0..383234ba9b9b17490a17e0e70475e7828c1628d0 100644 GIT binary patch delta 91 zcmV-h0Hpu-1os3HFoFVepn?JcfB^u31bpMdG_esT0zfQWVo`cuJqj5^d8O&_f3awU x+YKq4mp2jU`DM(j_++BjAUhj8dI~>j?GE)}EmO}ii8bu$-}>oV-xq0aI|*>eColj2 delta 91 zcmV-h0Hpu-1os3HFoFVepn?JcfB^u31bZi-|FID!0zf+}lWK+iyiYA;nQqqNYR)$l xr{917-t{&b7ZB3y=+8m}6y1Hwpu(Xdw4YMZS72uQ<&d+OP zWL#v|z*&@^UjpMdF>!b_GcqnwH&EqbWMolz;i9_pmf4JRw|o7T87iMX|L02m2lcDA z2Cbd1XN3P)Qvos}(;%HiVbzEDURM8xjQta9WgH^U@3eOP<5j|N@?>`t!{Nq5$Ch

Ppz+#*v<60|MwV*z1^=5E85c(^3Tt3wTqtB9(7;`iSd^YxQmmJfTGYtM zxX8AFqbNVW1j=nC+6xocvS8`anEAKp63;;`jwaEOagtls{dSAVaM>?{QHw9wG-YYM|LLe gULZ18^MbL0YVZ<$!TLoE!FN}Fiuah=zk3S@0Bw;>nlndD1H4-)9smFU delta 90 zcmV-g0Hy!Y2+;@;FoFb4pn?P2fB^x41bZi-|Dh2CK=MsN7_iVkDyxJl^TUJwP`*dyj!(w6d33d#TARTWfDh}(qkjVd_Yhk#b)4c{OstN;K2 diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/targets.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/targets.json index 9275521..9a945d8 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/targets.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/targets.json @@ -3,7 +3,7 @@ { "keyid": "c24b457b2ca4b3c2f415efdbbebb914a0d05c5345b9889bda044362589d6f596", "method": "ed25519", - "sig": "aa5c4bd41dbb8bcf5fdc6fd44ec6b42998488fb1849cc9634e58e514dc5f00f7a59e75216db325f07bfdaba72558a9a4dcfe694f76c8e9af2980477a2893b208" + "sig": "50370a71b676fb3fa32e73c10e0da41261205973c1c5df1546df6ce8e4feb5a4b9bef2d937f97ec73bd56b7afe03aa477764cfd5d1fa6f8a211a309a33474d03" } ], "signed": { @@ -12,7 +12,7 @@ "keys": {}, "roles": [] }, - "expires": "2037-09-28T12:46:18Z", + "expires": "2035-08-25T05:45:02Z", "targets": { "/BCU1.0.txt": { "hashes": { diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.der index 78e17e843069c81f71a370b9e5d0cea60d5a4fbd..0f168bb32d772be153c4157ca1d49fe47de2dd78 100644 GIT binary patch delta 160 zcmV;R0AK&a0mT7VFoD9LQh)&ifdqR4j*x-@0i#5tLVyi(Zeeh9Xm4~bYIARHfdK)6 z0s>Nl0Rf~ZFeM5B0R$im8n(0z0D~}i8XEUwddtz?QPWo_?65^jCMB1p%iYM4RxUs+ zwWRM=DHmG(58^cvQYKH2JGtcNBfM*dgIBGt(oBWG2DQltd{aMXo3mKM)?bchIYm{U OB>C}PPcBRz`<4u>`#`<` delta 159 zcmV;Q0AT;c0mK1TFoD6KQGfvhfdqRep#Opa0i#2sL4XW%Zeeh9Xm4~bWMy)J0Re&n z0NH~90i-7|B?CpjQ(>1?vN*X$8 NBPNnigg6w)>kS2LM6mz> diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.json index 1a34ad6..53962b2 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.json @@ -3,16 +3,16 @@ { "keyid": "6fcd9a928358ad8ca7e946325f57ec71d50cb5977a8d02c5ab0de6765fef040a", "method": "ed25519", - "sig": "8aa14b371d10f81a5f4232967d682ef254f5a92221b5cd224811e053d5a8bd99cc1298984c284580684a958eec2f40a6eaf0d8756bd950e5b3714736befda805" + "sig": "2cb5a4ef5529175afd0fe2351252264f8f3bb9e4e723bc6b868357adaed24c85c006b5c9077c533f679bb358c3d65f8e663945559f24f9f15e4f2e4c1efb960c" } ], "signed": { "_type": "Timestamp", - "expires": "2037-09-28T12:46:18Z", + "expires": "2035-05-26T22:17:52Z", "meta": { "snapshot.json": { "hashes": { - "sha256": "d37a3f9b41bd7ad4c87bc978f0341cbeb081bdee0891f087f33d34963c565e16" + "sha256": "0b1ab6b40d008330781a1af7637acbd1de51d35728ecb0454a262597a5cbddc8" }, "length": 594, "version": 1 diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive.zip b/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive.zip index 11a4680e9cfedc25f252a14ac9aec817b7712b0c..731278c6f46983654b28704287866aea8c606c42 100644 GIT binary patch literal 1129 zcmWIWW@h1H0D;u<9e!X2l;C8LVMxg=N=+`wFVYVU;bdU`Yxg2%MN?)=X$3a}Bg+eB z1_m$@05{_M)SW0s2q7Dhn_7~Xl30=mF-!2+OI&7&03E==;4o=t-l`zY2l9*z4BLQM z4y#!uiACwDCB=GK#rb()H%iztRYw5LMsuU-1n)d%MFE%Zc1Jt5Dw|&5-E#h6sFL=h z%NAiWw`a|C*{E~h_KZ5W%C3f0?zU!grTmV2%j~a~yt1zPcFEnvYyE0Z{SBG5XijU4 zX2RB-3r^bvbhLPM`nvy7J{0gUa*kpL{Qi>I!4i+3QeYxP|?e;#-qFde(wZ0<7qc&9ZD3?r$k52hxG2*gokby;=WEO7NzUAgl1nV(ER+1&zCJx_ z?#jNrb6?Eo=noUFaWOKoILz~owU7QcBi-^`sE6vOWy<_Tf_wo>D^$Ft#kZ`xxYJst z_IzT;#*-J@+P*eQ9$00yFinE*5YLh6ww{NWJipB33-D%Sl4Hh|%q5@+UV!1RBZ!HV z=vg6&9xX8;8;6$z-k91 vC@|w1=5~-BY)nW&0e3sh4sgr^tpLS5h83)AK=(1Q0AU``y-$G#F)#oC6PjYt literal 1506 zcmWIWW@h1H0D+|P9e!X2l#pbQVJJu}D#=XDiO)?fNlZyBNsLb{O3uhEOVtkz;bdU` zZucVQ9}t&Ta5FHnykKTv022XlBU90gRKjOuN@h`Na!Gy>#O%L7a~bXNn4O1awl+~_ z!#o19gdb>u0Cr16fT6&_@U3EtpJLe6eR7Np4BLR%oHR>H5{uGPON#Zfiu3cpA;$3g zQ49wVqlK7hfM?!e1A*PYMcdA4Dx6=W`u@a9*MJPkrEZs1Hr!q0IAMQyT-6NbPRU7! z-7l}&Xdip%i|vUO-`+-F-qvrm_Scl^`Pz$lUWMEUbmVWba=QN0;YH*HX@$aN8w!%P z9J$5zx@OG=h2x9%o^yLEwpV|q-Ma1tDGBfX91H(Flco7!O8UeZ$sQ@PQj={x{ALAt zoYBlod~~9wF7oVch&Pkg-Yeua%&FGEeTBJFV z;X=TxIoDRTRhqpm+-|B=d}Sx+SG&9UM(#E(4^=MjcoG(Qu81qRes9n1M?p84=Dkio z{B=)R)ts%1{Ot31&%Y-O!WMg#`11bz!;DBz zpy>E!e)miVFmgcHjJ)VbNiD(`8wQQ73q%_j85>!u)faL%F)}V@U&z|P$gq&1k&$sx zra?N3LNd?Smd}%%xfLasSjJf<`L%t0deq#NeR=1;n9tE4CS2oUWMpypv2E|N&B05p zjNSG)#Mazg<`5CDqP4c>`xN1YQSyt$M48M@ugNCN=;=DvU8AgU?`)Q*S>4MsEqYd^ z4%T08U*!n!W@M6M#+5lGpxIP_;jbfzi6^VFLb57aqC++wGovCKAB@jPY|q diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.der b/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.der index d836c76b810055b6692d375312a06177282c9e4b..2afb83d3c0e7ba3db237d0e3f3787c7259f9dd71 100644 GIT binary patch delta 87 zcmV-d0I2_n0f+$(FoA`j6@UQ&fdqSco@S8_IzXOFT|Zm?m}fc8RY)cBrXvq>5e`tL tcqB_37qqU?xjQ6%&tZtM$-py)BHh$^Mgf2RDKs)r?)d;${BmV#Z diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.json b/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.json index 8614ec3..60315a5 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive/director/metadata/targets.json @@ -3,7 +3,7 @@ { "keyid": "630cf584f392430b2119a4395e39624e86f5e5c5374507a789be5cf35bf090d6", "method": "ed25519", - "sig": "263a6d873455bac478366d92edc04cac55cc0e545b5f4249cf696254263d58de6a225446ce96725bccb83ffdaab548fcd89cb790020039ef336fb422364c1800" + "sig": "9e4b5d3f5bfe986739ce554825f2a6230f72110e50a578244b1b17b4aed1b93b247dcf6188b1c9d18686f58119c0aa3aa166180ec20cc4973d49c20249f4990e" } ], "signed": { @@ -12,7 +12,7 @@ "keys": {}, "roles": [] }, - "expires": "2037-09-28T12:46:29Z", + "expires": "2035-08-25T05:45:10Z", "targets": {}, "version": 1 } diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive.zip b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive.zip index acc2e4b4e30ba3843023f3a6076879bf063caabd..203a5b876e14789139158142514ca1885f09f0fa 100644 GIT binary patch literal 11514 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 z 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 QAm051Jp%xcB)tFje_>>GiU0rr literal 14244 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 diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/root.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/root.json index 790a3a4..90b7c7b 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/root.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/root.json @@ -3,7 +3,7 @@ { "keyid": "fdba7eaa358fa5a8113a789f60c4a6ce29c4478d8d8eff3e27d1d77416696ab2", "method": "ed25519", - "sig": "e9855e5171934d56a78033cead3dc217d6df3730f9c668742346a4e66f0d1141fe7283a21964e0c35163e76b6103e36a04d44f1b0799fe34af45c65f32f38b09" + "sig": "a89ff34987c98467dd2732f83c99461b9b5905a69d269e486353909919d1d3fed1b13250347f38695098f0e3a9649c542a837fecb49630e28aa7c97ba789270c" } ], "signed": { @@ -12,7 +12,7 @@ "gz" ], "consistent_snapshot": false, - "expires": "2037-09-28T12:46:29Z", + "expires": "2036-05-25T04:06:20Z", "keys": { "630cf584f392430b2119a4395e39624e86f5e5c5374507a789be5cf35bf090d6": { "keyid_hash_algorithms": [ diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/snapshot.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/snapshot.der index c5a2945..8e19068 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/snapshot.der +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/snapshot.der @@ -1,4 +1,3 @@ -0Ö f€{'¡ ‚£X¢V€¡0€ targets.der¢=€root.der‚wƒ¤'0% - k‘ÚÑÁ8¨îœ} -"ÄóLc~r´I9˜A¹¹»9¢i0g ù<üó=3_ô6Tì`Gà¡ÕY^ãÞSk”ÉÇVxŠ— -@dKÛ-D5¿rmóÓÕ†²¶ËÔ‹ä…þ “Õ²f–çÑAtÍ«äEí@¨š°Œ¤6–¨öyÁ%û -!•^É‚ \ No newline at end of file +0Ø h€{ w˜‚£Z¢X€¡0€ targets.json¢>€ root.json‚Hƒ¤'0% + ò¿Ú#T¶²—ãX¦œUNË:[?k‡¿‡®Y_s×vOq¢i0g ù<üó=3_ô6Tì`Gà¡ÕY^ãÞSk”ÉÇVxŠ— +@µØRWA¾`¨% ‹§)É#ˆ0>1-)FoFTQpn?HOfB^x41bcd(W`Y3&qk;iBp@IQ5fB^!bf&nftfs~+u zgMbS!LPJ$CE-@~2cyxgQ5`qB&qi!%I3IPEGARe~*Tx+%l#WHPf@c#w%{O{gQP{-Qp zQwewp2Y9oK^e{yV0Rsd;mnQ{`E2t>_()?QBFH*q{M?gY5gZVA&94KV(I)%m3N?MH{JH&c61PqKg-|*=XmJW~2_+M~4K>`);jOdc^Sft!Aw0^+dBy KQvZCV-zN(_-E~9& delta 104 zcmV-u0GI#21Ec~UFoFRfpnNl0Rf~ZFeM5B0R$kto{b^Xk>z`!LM>jIFr;SYpE+)~ikr1{y$8(&>wM0URxdwl zGXRRdc5=;YXn6m-gh_c1{oI>7@{=_V;Qg%w9ga-o95nsPNqi}$$#QX*pHLem53eT* NS?d1379-0fR0Ur|LL2}9 delta 158 zcmV;P0Ac^d0mK1TFoD6KQGfvhfdqRep$mcm0;5BtL4XW%Zeeh9Xm4~bWMy)J0Rn;o z0NH~90i-7|B?j?GE)}EmO}ii8bu$-}>oV-xq0aI|*>eColj2 delta 91 zcmV-h0Hpu-1os3HFoFVepn?JcfB^u31bZi-|FID!0zf+}lWK+iyiYA;nQqqNYR)$l xr{917-t{&b7ZB3y=+8m}6y1Hwpu(Xdw4YMZS72uQ<&d+OP zWL#v|z*&@^UjpMdF>!b_GcqnwH&EqbWMolz;i9_pmf4JRw|o7T87iMX|L02m2lcDA z2Cbd1XN3P)Qvos}(;%HiVbzEDURM8xjQta9WgH^U@3eOP<5j|N@?>`t!{Nq5$Ch

Ppz+#*v<60|MwV*z1^=5E85c(^3Tt3wTqtB9(7;`iSd^YxQmmJfTGYtM zxX8AFqbNVW1j=nC+6xocvS8`anEAKp63;;`jwaEOagtls{dSAVaM>?{QHw9wG-YYM|LLe gULZ18^MbL0YVZ<$!TLoE!FN}Fiuah=zk3S@0Bw;>nlndD1H4-)9smFU delta 90 zcmV-g0Hy!Y2+;@;FoFb4pn?P2fB^x41bZi-|Dh2CK=MsN7_iVkDyxJl^TUJwP`*dyj!(w6d33d#TARTWfDh}(qkjVd_Yhk#b)4c{OstN;K2 diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/targets.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/targets.json index 9275521..9a945d8 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/targets.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/targets.json @@ -3,7 +3,7 @@ { "keyid": "c24b457b2ca4b3c2f415efdbbebb914a0d05c5345b9889bda044362589d6f596", "method": "ed25519", - "sig": "aa5c4bd41dbb8bcf5fdc6fd44ec6b42998488fb1849cc9634e58e514dc5f00f7a59e75216db325f07bfdaba72558a9a4dcfe694f76c8e9af2980477a2893b208" + "sig": "50370a71b676fb3fa32e73c10e0da41261205973c1c5df1546df6ce8e4feb5a4b9bef2d937f97ec73bd56b7afe03aa477764cfd5d1fa6f8a211a309a33474d03" } ], "signed": { @@ -12,7 +12,7 @@ "keys": {}, "roles": [] }, - "expires": "2037-09-28T12:46:18Z", + "expires": "2035-08-25T05:45:02Z", "targets": { "/BCU1.0.txt": { "hashes": { diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.der index 78e17e843069c81f71a370b9e5d0cea60d5a4fbd..0f168bb32d772be153c4157ca1d49fe47de2dd78 100644 GIT binary patch delta 160 zcmV;R0AK&a0mT7VFoD9LQh)&ifdqR4j*x-@0i#5tLVyi(Zeeh9Xm4~bYIARHfdK)6 z0s>Nl0Rf~ZFeM5B0R$im8n(0z0D~}i8XEUwddtz?QPWo_?65^jCMB1p%iYM4RxUs+ zwWRM=DHmG(58^cvQYKH2JGtcNBfM*dgIBGt(oBWG2DQltd{aMXo3mKM)?bchIYm{U OB>C}PPcBRz`<4u>`#`<` delta 159 zcmV;Q0AT;c0mK1TFoD6KQGfvhfdqRep#Opa0i#2sL4XW%Zeeh9Xm4~bWMy)J0Re&n z0NH~90i-7|B?CpjQ(>1?vN*X$8 NBPNnigg6w)>kS2LM6mz> diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.json index 1a34ad6..53962b2 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.json @@ -3,16 +3,16 @@ { "keyid": "6fcd9a928358ad8ca7e946325f57ec71d50cb5977a8d02c5ab0de6765fef040a", "method": "ed25519", - "sig": "8aa14b371d10f81a5f4232967d682ef254f5a92221b5cd224811e053d5a8bd99cc1298984c284580684a958eec2f40a6eaf0d8756bd950e5b3714736befda805" + "sig": "2cb5a4ef5529175afd0fe2351252264f8f3bb9e4e723bc6b868357adaed24c85c006b5c9077c533f679bb358c3d65f8e663945559f24f9f15e4f2e4c1efb960c" } ], "signed": { "_type": "Timestamp", - "expires": "2037-09-28T12:46:18Z", + "expires": "2035-05-26T22:17:52Z", "meta": { "snapshot.json": { "hashes": { - "sha256": "d37a3f9b41bd7ad4c87bc978f0341cbeb081bdee0891f087f33d34963c565e16" + "sha256": "0b1ab6b40d008330781a1af7637acbd1de51d35728ecb0454a262597a5cbddc8" }, "length": 594, "version": 1 diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive.zip b/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive.zip index fa646a70a63854757e59b2bf06aeeba76cf05dec..55c94bc13b44407fe34a4b8e631dee98e8d4f2e1 100644 GIT binary patch literal 1748 zcmWIWW@h1H00E=&9e!X2l;C8LVMxg=N=+`wFVYVU;bdT5VD}B1DMi@GRY}GlaiLI6a;tm6fHMw)SX1VZx#w2cNB__oEKwU2r({<=K+* z<)_~j2!(WZEVMoP_ou1G8qF-vY0^$APbRE+(j)6;J~_=c`Nf?Ap0`;`Q~S2EzfxHn zvUHl)LmsUuD}|mYUhCeZAk?3eF!g9YPi~v#_bH-_C3)R8dR$P~e^$8p@26*-9qX2^ zQ(5-<>2vc~?>&zm_v(FK`Ofo8SboJ#`FGEkcdMr?Kl}J+`tieWXTF>s`g_V!`+aZE zFIdjOdEcp4`Xko|qsOTY{^>8DzMPwWv-`WeU0eCXyPuLP`*N3mG|+F~J$=dI`d_n2KSvvG~4W3}P2z$*QKJLe;ote9-<;4GcB z$d8w8XLt{%RH*rk72iU3Kixb(-IJ$qYR;34jeR$E9?Md?$hOJJq(`|S?bL*1-)oC1 z4llWub@{}hOo>d_JeP|RswIu*CY@QjBr0%9!`{G`q1jzZJ3~&zYJR&`B%fE+$y5LT zZ`k|%X>+fOzIvu=6C1O4-n*->UnU7kHMOoj?|Y4hsX?me7DKMaQq@msy;Ir0usH6T zrsctP(zf$qe930>>Wsa+Vi_kboiu}a<~Of<-?aB?J!{ZfvdT{H=9;j~1usMmf_X$Y z&3`Hvm0ifQDpmH`!FdZ8CdlRfP*^H=k=@dyyeh>@u}#VLf4|<`7yG{aDYrdmr&c$~ zOs?(^FDN%aQg9kDHMJDXB%^1pNQV%NQ&P*f=BU&fW!`7dJOf z+&FRRuB^ByVdc+{)Z9+D`t~g2?eTH&I=`UsRMW9%Y+AOK-~VKl+5c5*#^$EUdy|U4i!+DoxuuD< zzkIdE_LjEEzqnt*UmfgUD9qp89Q@h;?)ihp*79p(Yxb=xP2Kr#S9kT!>$l`@-JUP5 zy=!yMaqh*t^WNS&zcsx|eyvy3h6&3%xnHzRxpL>vlOswyHakt2quTLi&4$<@Jn(mA6mo$hB9Jn`Gtvs};GSK1YytGg#s;JfMevAY3=<=iJC z6${c$jg0h8nRuiJdOZ7i>r>?<m;e9( literal 1859 zcmWIWW@h1H00Euy9e!X2l#pbQVJJu}D#=XDiO)?fNlZyBNsJFJO3uhEOVtkz;bdT* zZ1*B&IuMsua5FHnykKTv022XlBaP6ERKjOuN@h`Na!Gy>#Owt?bI;;4+Y!xdZKBME zc?4p~F5H%g07HR;;akNPzn!(OWFIp!Fo*&DXHJ?WC5c7psU^jFS;hHz;1FZ@{U~N7 zrmsSS4*Dq@^6dSt?N-cot?ZG?vs3y9Qo}hcJD0EB7H~x}OyNrX{WCs}yW3^{$k^}S zGyS~p_5R;)yZ!RjrGMXkcYWDwyW1~+-T$_6J@3orW}|{n&BCAfj1;@7)N}lpD!mgE z9v{-HYFlCGnfLU81;0XobN$v2wzs!f>$QY+NzA+feY|M*`mzmc{eC|_U9odV#5R%V zUsIPqPW<|%@LAllH-Ag_OWhUTQNdf}`%>=Zwf5*q^R>U0-2CkN`|-bbjQJnZs^;(8 zuueb!aY@a;b<(Ak)lzS_TtB?%>$`=wK5u-nGXB@uV!;x38 zFtxRC!yKjJl&LdbP0&#-I638(x^&PgrenU1St>pcqR*#`2x_S=GpOIbDEE-T2K^u} z*%-CFcwW`i@R|}2iL7t`_y0}Mznyu#BympT=W?z3HzwN@qT9`Q?f$bOG6g72t>g~> zq5@1)AZ$inno3D6!k45Bniv%pG_G%8WNc)qR$s{7#K^R`alyjIxj>PHjnfSprz~h} zZs67raSk=qGt?`oC~0IA1S!fhP~~D|WKjsael~1HoXHbjhB*1k121DU&94S^9oWox z`&n=J1FINC16M9aW)_Dj*BxiKu?g6lSuL!8-5y%Hvf_SU2Xo(|mj4|$`GWmc6-lr# zw``m_=}bl1*EcJeAEx@uIs182z%|Kk8+Sv++${@vnz(>&NJ-7jPfjdqWMo{FX^_sM zkj(S7AP_4{Sz16pRMQsejBw*Sw_>3T8c{zdm)&Gj3P z@C0}>GRZOHD*Ys&<)8q=Uq=uVPg%$cDGM>mL4@&`B_Xo$!T5|vDi?wFfXYQY_F$HZ z$o8x!${wUL5@;c)jKpdot`ZVrAuwSxY-tn$QaHm6?lHK9sO2Tla!`4R)pAIrVP++m iZ$Xx401Ip~EC**STz0dv0sY0m3WSDC3=Dr*Ks*3uGmO9h diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.der index 09869c3ee50cdeb5a880d5f1f87a3d71cc4ba34c..3ee0e0936350373178b8a83ac0dfcc168c4129df 100644 GIT binary patch delta 260 zcmV+f0sH=>0>1-)FoFTQpn?HOfB^x41bcd(W`Y3&qk;iBp@IQ5fB^!bf&nftfs~+u zgMbS!LPJ$CE-@~2cyxgQ5`qB&qi!%I3IPEGARe~*Tx+%l#WHPf@c#w%{O{gQP{-Qp zQwewp2Y9oK^e{yV0Rsd;mnQ{`E2t>_()?QBFH*q{M?gY5gZVA&94KV(I)%m3N?MH{JH&c61PqKg-|*=XmJW~2_+M~4K>`);jOdc^Sft!Aw0^+dBy KQvZCV-zN(_-E~9& delta 104 zcmV-u0GI#21Ec~UFoFRfpn Date: Tue, 28 May 2019 12:40:22 -0400 Subject: [PATCH 26/40] Add two more TEST_INSTANCES for the partial verifying secondary ECUs --- tests/test_secondary.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_secondary.py b/tests/test_secondary.py index f39f773..8fa2ee6 100644 --- a/tests/test_secondary.py +++ b/tests/test_secondary.py @@ -90,8 +90,20 @@ { 'client_dir': os.path.join(TEST_DATA_DIR, 'temp_partial_secondary0'), 'partial_verifying': True, - 'vin': 'vehicle_w_pv_bcu', - 'ecu_serial': 'pv_bcu', + 'vin': 'democar', + 'ecu_serial': 'BCUdemocar', + 'instance': None}, + { + 'client_dir': os.path.join(TEST_DATA_DIR, 'temp_partial_secondary1'), + 'partial_verifying': True, + 'vin': 'democar', + 'ecu_serial': 'pv_00000', + 'instance': None}, + { + 'client_dir': os.path.join(TEST_DATA_DIR, 'temp_partial_secondary2'), + 'partial_verifying': True, + 'vin': '000', + 'ecu_serial': 'pv_00000', 'instance': None}] # Set starting firmware fileinfo (that this ECU had coming from the factory) From ce5880193cd8672c4e4ef6a06511532447a44e16 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 28 May 2019 12:45:44 -0400 Subject: [PATCH 27/40] Add pv__expected_updated_fileinfo to make partial verifying ECU accept the update This included fileinfo corresponds to the info of the update that the partial verifying ECU would donwnload as per the new metadata. --- tests/test_secondary.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_secondary.py b/tests/test_secondary.py index 8fa2ee6..4399158 100644 --- a/tests/test_secondary.py +++ b/tests/test_secondary.py @@ -116,7 +116,7 @@ 'sha256': '6b9f987226610bfed08b824c93bf8b2f59521fce9a2adef80c495f363c1c9c44'}, 'length': 37}} -expected_updated_fileinfo = { +fv_expected_updated_fileinfo = { 'filepath': '/TCU1.1.txt', 'fileinfo': { 'custom': {'ecu_serial': 'TCUdemocar'}, @@ -125,6 +125,15 @@ 'sha256': '56d7cd56a85e34e40d005e1f79c0e95d6937d5528ac0b301dbe68d57e03a5c21'}, 'length': 17}} +pv_expected_updated_fileinfo = { + 'filepath': "/BCU1.1.txt", + 'fileinfo': { + "custom": {"ecu_serial": "BCUdemocar"}, + "hashes": { + "sha256": "1eb6fa5c6bb606c5326d6ef0ff05f5fcefde4e50c7daea530978090778b38bf4", + "sha512": "9727058c2ba828fdd2fc5ae02f52c10e47404283f92df3539989e2ada3cf7e85a9772faed1bd0bad3fc2bd8f6e5d15b976b8e832dd46874be72b994bc57a62a0"}, + "length": 18}} + def destroy_temp_dir(): # Clean up anything that may currently exist in the temp test directories. From b38d4deec70a73b2a7549ba9dfe7d6a861078273 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 28 May 2019 12:50:44 -0400 Subject: [PATCH 28/40] Modify to include the tests for partial verifying secondary ECU The tests for partial verification ECU are carried out in the similar fashion as that of the full verification ECU. --- tests/test_secondary.py | 100 +++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/tests/test_secondary.py b/tests/test_secondary.py index 4399158..e6bd2bf 100644 --- a/tests/test_secondary.py +++ b/tests/test_secondary.py @@ -683,7 +683,11 @@ def test_40_process_metadata(self): # create_directory_structure_for_client() calls in setUpClass above, and # only the root metadata file. for instance_data in TEST_INSTANCES: - for repo in ['director', 'imagerepo']: + if instance_data['partial_verifying']: + repo_list = ['director'] + else: + repo_list = ['director', 'imagerepo'] + for repo in repo_list: self.assertEqual( ['root.' + tuf.conf.METADATA_FORMAT], sorted(os.listdir(os.path.join( @@ -692,22 +696,26 @@ def test_40_process_metadata(self): # --- Set up this test # Location of the sample Primary-produced metadata archive - sample_archive_fname = os.path.join( + sample_fv_archive_fname = os.path.join( uptane.WORKING_DIR, 'samples', 'metadata_samples_long_expiry', 'update_to_one_ecu', 'full_metadata_archive.zip') + sample_pv_archive_fname = os.path.join( + uptane.WORKING_DIR, 'samples', 'metadata_samples_long_expiry', + 'update_to_one_ecu', 'partial_metadata_archive.zip') + - assert os.path.exists(sample_archive_fname), 'Cannot test ' \ + assert os.path.exists(sample_fv_archive_fname), 'Cannot test ' \ 'process_metadata; unable to find expected sample metadata archive' + \ - ' at ' + repr(sample_archive_fname) + ' at ' + repr(sample_fv_archive_fname) + assert os.path.exists(sample_pv_archive_fname), 'Cannot test ' \ + 'process_metadata; unable to find expected sample metadata archive' + \ + ' at ' + repr(sample_pv_archive_fname) # Continue set-up followed by the test, per client. # Only tests the full verification secondaries for instance_data in TEST_INSTANCES: - if instance_data['partial_verifying']: - continue - client_dir = instance_data['client_dir'] instance = instance_data['instance'] @@ -716,19 +724,22 @@ def test_40_process_metadata(self): # See comments in SetUpClass() method. tuf.conf.repository_directory = client_dir - # Location in the client directory to which we'll copy the archive. - archive_fname = os.path.join(client_dir, 'full_metadata_archive.zip') - - # Copy the sample archive into place in the client directory. - shutil.copy(sample_archive_fname, archive_fname) + # Getting the location in the client directory to which we'll copy the archive + # and then copy the sample archive into place in the client directory. + if instance_data['partial_verifying']: + archive_fname = os.path.join(client_dir, 'partial_metadata_archive.zip') + shutil.copy(sample_pv_archive_fname, archive_fname) + else: + archive_fname = os.path.join(client_dir, 'full_metadata_archive.zip') + shutil.copy(sample_fv_archive_fname, archive_fname) # --- Perform the test - # Process this sample metadata. + # Process this sample metadata - if instance_data is TEST_INSTANCES[2]: - # Expect the update to fail for the third Secondary client. + if instance_data in [TEST_INSTANCES[2], TEST_INSTANCES[5]]: + # Expect the update to fail for the third and fifth Secondary client. with self.assertRaises(tuf.NoWorkingMirrorError): instance.process_metadata(archive_fname) continue @@ -737,8 +748,14 @@ def test_40_process_metadata(self): instance.process_metadata(archive_fname) # Make sure the archive of unverified metadata was expanded - for repo in ['director', 'imagerepo']: - for role in ['root', 'snapshot', 'targets', 'timestamp']: + if instance_data['partial_verifying']: + repo_list = ['director'] + roles_list = ['targets'] + else: + repo_list = ['director', 'imagerepo'] + roles_list = ['root', 'snapshot', 'targets', 'timestamp'] + for repo in repo_list: + for role in roles_list: self.assertTrue(os.path.exists(client_dir + '/unverified/' + repo + '/metadata/' + role + '.' + tuf.conf.METADATA_FORMAT)) @@ -749,37 +766,56 @@ def test_40_process_metadata(self): # For clients 0 and 1, we expect root, snapshot, targets, and timestamp for # both director and image repo. - for instance_data in TEST_INSTANCES[0:2]: - for repo in ['director', 'imagerepo']: - self.assertEqual([ - 'root.' + tuf.conf.METADATA_FORMAT, - 'snapshot.' + tuf.conf.METADATA_FORMAT, - 'targets.' + tuf.conf.METADATA_FORMAT, - 'timestamp.' + tuf.conf.METADATA_FORMAT], + # For clients 3 and 4, we expect root and targets for director repo + for instance_data in (TEST_INSTANCES[0:2] + TEST_INSTANCES[3:5]): + if instance_data['partial_verifying']: + repo_list = ['director'] + # Both root and targets as there is root file needed to establish a root + # of trust while shipping the ECU + roles_list = ['root','targets'] + else: + repo_list = ['director', 'imagerepo'] + roles_list = ['root', 'snapshot', 'targets', 'timestamp'] + roles_in_repo_directory = [] + + for role in roles_list: + roles_in_repo_directory.append( + role + '.' + tuf.conf.METADATA_FORMAT) + + for repo in repo_list: + self.assertEqual(roles_in_repo_directory, sorted(os.listdir(os.path.join(instance_data['client_dir'], 'metadata', repo, 'current')))) - # For client 2, we are certain that Director metadata will have failed to + # For client 2 and 5, we are certain that Director metadata will have failed to # update. Image Repository metadata may or may not have updated before the # Director repository update failure, so we don't check that. Client 2 # started with root metadata for the Director repository, so that is all # we expect to find. - self.assertEqual( - ['root.' + tuf.conf.METADATA_FORMAT], - sorted(os.listdir(os.path.join(TEMP_CLIENT_DIRS[2], 'metadata', - 'director', 'current')))) + for instance_data in [TEST_INSTANCES[2], TEST_INSTANCES[5]]: + self.assertEqual( + ['root.' + tuf.conf.METADATA_FORMAT], + sorted(os.listdir(os.path.join(instance_data['client_dir'], + 'metadata', 'director', 'current')))) # Second: Check targets each Secondary client has been instructed to # install (and has in turn validated). - # Client 0 should have validated expected_updated_fileinfo. + # Client 0 should have validated fv_expected_updated_fileinfo. self.assertEqual( - expected_updated_fileinfo, + fv_expected_updated_fileinfo, TEST_INSTANCES[0]['instance'].validated_targets_for_this_ecu[0]) - # Clients 1 and 2 should have no validated targets. + # Client 3 should have validated pv_expected_updated_fileinfo. + self.assertEqual( + pv_expected_updated_fileinfo, + TEST_INSTANCES[3]['instance'].validated_targets_for_this_ecu[0]) + + # Clients 1, 2, 4 and should have no validated targets. self.assertFalse(TEST_INSTANCES[1]['instance'].validated_targets_for_this_ecu) self.assertFalse(TEST_INSTANCES[2]['instance'].validated_targets_for_this_ecu) + self.assertFalse(TEST_INSTANCES[4]['instance'].validated_targets_for_this_ecu) + self.assertFalse(TEST_INSTANCES[5]['instance'].validated_targets_for_this_ecu) # Finally, test behavior if the file we indicate does not exist. From dcb6995ba97701d50a5eaee97bdb992b8b36b677 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 28 May 2019 16:22:42 -0400 Subject: [PATCH 29/40] FIX error in test_20_update_time due to wrong function call --- tests/test_secondary.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_secondary.py b/tests/test_secondary.py index e6bd2bf..de2a98b 100644 --- a/tests/test_secondary.py +++ b/tests/test_secondary.py @@ -611,14 +611,6 @@ def test_20_update_time(self): with self.assertRaises(uptane.BadTimeAttestation): instance.update_time(time_attestation__wrongnonce) - # Conduct one test with a different secondary instance: - # Expect that if a time attestation is submitted to be validated by a - # Secondary that hasn't ever sent a nonce, the validation function will - # reject the time attestation. (Because it doesn't matter, we'll use the - # same sensible time attestation previously generated in this test func.) - with self.assertRaises(uptane.BadTimeAttestation): - TEST_INSTANCES[1]['instance'].validate_time_attestation(time_attestation) - # TODO: Consider other tests here. From 229a2c7aa55eddd255a29c151a7e08fb26502203 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 28 May 2019 16:28:33 -0400 Subject: [PATCH 30/40] Add new sample metadata files for tests to work with der format Due to some discrepency in json and der metadata the tests failed while working with der formats --- .../full_metadata_archive.zip | Bin 10893 -> 10887 bytes .../director/metadata/root.der | Bin 631 -> 631 bytes .../director/metadata/root.json | 4 ++-- .../director/metadata/snapshot.der | Bin 219 -> 217 bytes .../director/metadata/snapshot.json | 6 +++--- .../director/metadata/targets.der | Bin 136 -> 136 bytes .../director/metadata/targets.json | 4 ++-- .../director/metadata/timestamp.der | Bin 197 -> 196 bytes .../director/metadata/timestamp.json | 6 +++--- .../imagerepo/metadata/root.der | Bin 631 -> 631 bytes .../imagerepo/metadata/root.json | 4 ++-- .../imagerepo/metadata/snapshot.der | Bin 219 -> 217 bytes .../imagerepo/metadata/snapshot.json | 6 +++--- .../imagerepo/metadata/targets.der | Bin 1105 -> 1105 bytes .../imagerepo/metadata/targets.json | 4 ++-- .../imagerepo/metadata/timestamp.der | Bin 197 -> 196 bytes .../imagerepo/metadata/timestamp.json | 6 +++--- .../partial_metadata_archive.zip | Bin 1129 -> 1129 bytes .../director/metadata/targets.der | Bin 136 -> 136 bytes .../director/metadata/targets.json | 4 ++-- .../full_metadata_archive.zip | Bin 11514 -> 11507 bytes .../director/metadata/root.der | Bin 631 -> 631 bytes .../director/metadata/root.json | 4 ++-- .../director/metadata/snapshot.der | Bin 219 -> 217 bytes .../director/metadata/snapshot.json | 10 +++++----- .../director/metadata/targets.der | Bin 447 -> 447 bytes .../director/metadata/targets.json | 6 +++--- .../director/metadata/timestamp.der | Bin 197 -> 196 bytes .../director/metadata/timestamp.json | 10 +++++----- .../imagerepo/metadata/root.der | Bin 631 -> 631 bytes .../imagerepo/metadata/root.json | 4 ++-- .../imagerepo/metadata/snapshot.der | Bin 219 -> 217 bytes .../imagerepo/metadata/snapshot.json | 6 +++--- .../imagerepo/metadata/targets.der | Bin 1105 -> 1105 bytes .../imagerepo/metadata/targets.json | 4 ++-- .../imagerepo/metadata/timestamp.der | Bin 197 -> 196 bytes .../imagerepo/metadata/timestamp.json | 6 +++--- .../partial_metadata_archive.zip | Bin 1748 -> 1746 bytes .../director/metadata/targets.der | Bin 447 -> 447 bytes .../director/metadata/targets.json | 6 +++--- 40 files changed, 50 insertions(+), 50 deletions(-) diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive.zip b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive.zip index cb4c2752c6ea8f7db5cec52446ade7ae45a7e653..67734461e2f6800baeb44c5d39444ad2d46e7d37 100644 GIT binary patch literal 10887 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Ξ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 delta 91 zcmV-h0Hpu-1os3HFoFVepn?JcfB^u31bpMdJh2fb0zjyr^GS!vglF9+GWa~1MjM-1 x1*V-Qo=9U;keM0L)Be%1GEg*sIB8Ir@Z+guoKz}Ppz+#*v<60|Mwa@2>pGek85c(^3Tt3wTqtB9(7;`iSd^YxQmmJfTGYtM zxX8AFqbNVW1j=n&d+OP zWL#v|z*&@^UjpMdF>!b_GcqnwH&EqbWMonJwEvcJ$hJ+>A4e<`n-l7H+A7*UTfBXL z`?|>Z;_GGpg&-p`4boW@e%k!`Y-=3<#cX1&jKh@Gu2PpH(-;=-^*-&w(GV xM8+25?Y%j>+>Af9eu_W9_EO&E-MvJ^KZ5T*Kc4)^r6{d0>%jhu$j&SuJ^(~^QFZ_T diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/snapshot.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/snapshot.json index 1db9b05..b6040f0 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/snapshot.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/snapshot.json @@ -3,16 +3,16 @@ { "keyid": "f93cfcf33d335ff43654ec6047e0a18dd5595ee3de53136b94c9c756788a0f97", "method": "ed25519", - "sig": "94ab451ad3596600a3bd4bcb440880531cce0c51bc91018433b78d6cba4633f285f25fc006e91f44eede6131fc11eef3e393e24472668e6ac0bf6859896a4c0e" + "sig": "dbf828452d952b1423b6797bceb6f1eea0f049cf3da73f42af85c032b3a4b3265d9eb164616ea94afe8bcf0d2c972a0abdf11a7a0a0107711bbf47eb9df7ff04" } ], "signed": { "_type": "Snapshot", - "expires": "2035-06-01T22:18:00Z", + "expires": "2038-01-18T03:14:15Z", "meta": { "root.json": { "hashes": { - "sha256": "f2bfda2354b6b297e358a6169c554ecb3a5b3f6b1787bf87ae595f73d7764f71" + "sha256": "983203b28c67ea490db40bc82801aa771c9522ccbbc33bcf3239e37eef95523d" }, "length": 2120, "version": 1 diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/targets.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/targets.der index 2afb83d3c0e7ba3db237d0e3f3787c7259f9dd71..1a9de0dc95b9a8717ca2ffa55b86996321a56429 100644 GIT binary patch delta 87 zcmV-d0I2_n0f+$(FoA`j6@UQ&fdqg4u85HiIzU@Rk=sFgpplm*MHI0Wnp~sI*+;;T t^KZyHCI=6Hexq2KmE>b8Xlj}R76oSI+)NNT!p>2c)c-PNa{K$nFAf+(CR6|b delta 87 zcmV-d0I2_n0f+$(FoA`j6@UQ&fdqSco@S8_IzXOFT|Zm?m}fc8RY)cBrXvq>5e`tL tcqB_37qqU?xjQ6%&tZtM$dOrti+sm?imje8ZLrMSu delta 160 zcmV;R0AK&a0mT7VFoD9LQh)&ifdqR4j+lZ00i#5tLVyi(Zeeh9Xm4~bYIARHfdK)6 z0s>Nl0Rf~ZFeM5B0R$i>Qef(n#P>7!Yw>;b08rpE5w%!L_D3+sYrS@0vs-A9RxUt$ z-d0w0)tI>o=D}d6-%I(3HV@>F)v2;S52yXKeKo03+^<1S#@C9O#D}V{I*#>UBBo#5 O*=YsS-=IR@o!$-<0!T&x diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.json index f2911d6..82d839b 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/director/metadata/timestamp.json @@ -3,16 +3,16 @@ { "keyid": "da9c65c96c5c4072f6984f7aa81216d776aca6664d49cb4dfafbc7119320d9cc", "method": "ed25519", - "sig": "7bde565674d598b90ae6c160a7df4bf988360fe48fd5a9b23f0fa7fdb47d35a951dcaf414ec6d78a99c487aaaf3a8ef55f22a65fdcd96905d3dfa042df9dde0e" + "sig": "4d29caea1f135790c99f5112c89833c363e23a72f082dee707bf9723a9c45bda47f8bfd8711a646139e16d892c9a0ea99b86c85f021ce9db28ee70ae3d5c6e07" } ], "signed": { "_type": "Timestamp", - "expires": "2035-05-26T22:18:00Z", + "expires": "2038-01-18T03:14:15Z", "meta": { "snapshot.json": { "hashes": { - "sha256": "265260ea93c4f733f86bf17df40050e03211b5584bf64730c76bbd7660b35b68" + "sha256": "9d98d3a1a89a277eb23fb3237709318fbea54249c539dbf5b53eb8318c75847a" }, "length": 594, "version": 1 diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/root.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/root.der index 383234ba9b9b17490a17e0e70475e7828c1628d0..5c932fb18debbe442545aea5f71502aac26e0eee 100644 GIT binary patch delta 91 zcmV-h0Hpu-1os3HFoFVepn?JcfB^u31b_anf3XoJ0zhM7htlajzZKQ%PzIYj?GE)}EmO}ii8bu$-}>oV-xq0aI|*>eColj2 diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/root.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/root.json index f39cb1c..8375b5d 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/root.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/root.json @@ -3,7 +3,7 @@ { "keyid": "94c836f0c45168f0a437eef0e487b910f58db4d462ae457b5730a4487130f290", "method": "ed25519", - "sig": "2c5c62517a5f3d0a194379a5e9f07fb16884db0d299c973711e8f965ccabf864a2d7203b1b3c7a0a3f69ed0ef5612d53cf318935ece9dffae95adf17696e3b09" + "sig": "f08d8dfe263e55facaaa16eda78a8e51fbbaa2e36ae47b93a8103ca0982dc3deb988f7a1aee5c805a322232b01299c5919b3f8cb6bf04cca7a68e9ac3903d309" } ], "signed": { @@ -12,7 +12,7 @@ "gz" ], "consistent_snapshot": false, - "expires": "2036-05-25T04:06:12Z", + "expires": "2038-01-18T03:14:07Z", "keys": { "6fcd9a928358ad8ca7e946325f57ec71d50cb5977a8d02c5ab0de6765fef040a": { "keyid_hash_algorithms": [ diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/snapshot.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/snapshot.der index b48917780fadd1f3678e1e9cbef4a3c871741f62..be72d96bcd6efdcf9e13255a435c0db8ddd4c651 100644 GIT binary patch delta 180 zcmcc3c#|>Ppz+#*v<60|Mwa@2>*|{r85c(^3Tt3wTqtB9(7;`iSd^YxQmmJfTGYtM zxX8AFqbNVW1j=n&d+OP zWL#v|z*&@^UjpMdF>!b_GcqnwH&EqbWMolz;i9_pmf4JRw|o7T87iMX|L02m2lcDA z2Cbd1XN3P)Qvos}(;%HiVbzEDURM8xjQta9WgH^U@3eOP<5j|N@?>`t!{Nq5$Ch

>nlndD1H4-)9smFU diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/targets.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/targets.json index 9a945d8..ee1c00e 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/targets.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/targets.json @@ -3,7 +3,7 @@ { "keyid": "c24b457b2ca4b3c2f415efdbbebb914a0d05c5345b9889bda044362589d6f596", "method": "ed25519", - "sig": "50370a71b676fb3fa32e73c10e0da41261205973c1c5df1546df6ce8e4feb5a4b9bef2d937f97ec73bd56b7afe03aa477764cfd5d1fa6f8a211a309a33474d03" + "sig": "6c300b24ffb94f9d1d3465e3e9a877a359c5e5316e3edfd86154eeee08503e111fa23c8b7ca8af2af74747e701272999e61b4930b98a15a41e4a6d20e781490f" } ], "signed": { @@ -12,7 +12,7 @@ "keys": {}, "roles": [] }, - "expires": "2035-08-25T05:45:02Z", + "expires": "2038-01-18T03:14:07Z", "targets": { "/BCU1.0.txt": { "hashes": { diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.der b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.der index 0f168bb32d772be153c4157ca1d49fe47de2dd78..93282aba6be7ee35159657aec9c72044e82002e6 100644 GIT binary patch delta 159 zcmV;Q0AT;c0mK1TFoD6KQGfvhfdqg4u78370i#2sL4XW%Zeeh9Xm4~bWMy)J0Re&n z0NH~90i-7|B?LhSUvt*MLr4d*()lk8MrN=)J04t&>S#ZXq&A0%Z1iNc6Z$#$0ZTYht? NGer~HGO9>&-3;lDM6&<@ delta 160 zcmV;R0AK&a0mT7VFoD9LQh)&ifdqR4j*x-@0i#5tLVyi(Zeeh9Xm4~bYIARHfdK)6 z0s>Nl0Rf~ZFeM5B0R$im8n(0z0D~}i8XEUwddtz?QPWo_?65^jCMB1p%iYM4RxUs+ zwWRM=DHmG(58^cvQYKH2JGtcNBfM*dgIBGt(oBWG2DQltd{aMXo3mKM)?bchIYm{U OB>C}PPcBRz`<4u>`#`<` diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.json b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.json index 53962b2..3d490d5 100644 --- a/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.json +++ b/samples/metadata_samples_long_expiry/initial_w_no_update/full_metadata_archive/imagerepo/metadata/timestamp.json @@ -3,16 +3,16 @@ { "keyid": "6fcd9a928358ad8ca7e946325f57ec71d50cb5977a8d02c5ab0de6765fef040a", "method": "ed25519", - "sig": "2cb5a4ef5529175afd0fe2351252264f8f3bb9e4e723bc6b868357adaed24c85c006b5c9077c533f679bb358c3d65f8e663945559f24f9f15e4f2e4c1efb960c" + "sig": "0c2a95485dca56dadf4be8f8f015de79d92e015427c79a5ff134df63f6a81b19abd2bec2fd85840a7624c2409faaf694cbec2467d9012a42660e223000eb540d" } ], "signed": { "_type": "Timestamp", - "expires": "2035-05-26T22:17:52Z", + "expires": "2038-01-18T03:14:07Z", "meta": { "snapshot.json": { "hashes": { - "sha256": "0b1ab6b40d008330781a1af7637acbd1de51d35728ecb0454a262597a5cbddc8" + "sha256": "d637c37e4417c7649b275403eacf3bf2c0db913bc72ac2847e55968d07ea0716" }, "length": 594, "version": 1 diff --git a/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive.zip b/samples/metadata_samples_long_expiry/initial_w_no_update/partial_metadata_archive.zip index 731278c6f46983654b28704287866aea8c606c42..0d6adf4d52756651690c52e95e2c8edac1b4030e 100644 GIT binary patch delta 600 zcmaFK@sfi#z?+$civa{)mF=0xE3YEn@-{{fh)XND85mh!Ff%ZK352@F#yt}QG&qE& zy^U!G;)xBC9KH>E{DSr=FPXSdzy8wXw=u#%aWws=6VB!_EAq6w|1F|@Ze>aikHz_k z!K+s^v0e(jta9saSnr}e)i>wM)qLPMUvp-0=G7bX^%8%{eOmGDZS>`B{Z?y#o%wDT z>KfU!Zi9I%3$LTgT2%?5b1GuK-?kMz>pLmQCO&6+#C0bb?s^WD)n(oH!{^zp>rV7L zu_pC$_8*;PhqhUSdwDzxRyixN#5p|ZLx+IQ1m(9roidTuOp_vt7pw5Bb6HccZGyKz zuimv%K4vcuj!vt_n}TyI-bC0Wi{%Sjak!-LCNczxKD)W)_}yzB^8%l%UHM(I|Ec6h zfwtgz$6BM8)|^{z@b$Xa=^eH)TG7*<|JwYpD$d&W+`ZGG|Mq^(zR1=1hx>*2TYiV? z+;6ku!(V5f{#hPmUn!9tAX*ZvIKLhdz$byB>0uc&Z?XiF9{bKIZ)1Q#J=vE@ilzSF zx{k@&OjiEUt`l!NRxg-1UCmWwqv))d#b<81ADHkt|AdtqJAZxM;)t13pCoH#WX)n0 zV@-Q{$49{O(7C`FSNlKl0k|&;BIAMv0(#RgRoV0c@0Rlq zLzT22UA735xjk#9%SN61wrAA2RdzM3aRw@dCWUh7wT>Tk%b zMRQtXG!wSwTyQdqDt0-oGHtFS=ekJ&YOg0AaF@5(x-rU2v|ges`1Z~B-pBf1=V%-i z$c+0cX5XnZW5tbIah&YWb<-6OC9IhBqNnv$v;l{siabx!CC}e2fn9z%lO5xWO08O! z7>mo^IU*{R-6oOSb8D;fgdBzRlzi@@+tN?E&M2%pXk&giakrV0@T)5=Rr2r6X9|4Z zXn)G$%$-#yBKrz$V)eZaR~4?yk(+n={IA!QFTWU6o?rjuds$Rh7@N*shYG_j?6(x( zn(WcL_ITFk_e<)Q$VUYTe_7^e_n!d~z%5gE`ngCP6P+xb8Xlj}R76oSI+)NNT!p>2c)c-PNa{K$nFAf+(CR6|b delta 87 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 G-v0p|T?U!} delta 8406 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 F{{Zq45V-&V diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/root.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/root.der index c26630a783c9e1c1e75a8929fbbf0ee69279f684..8c25a3ba631c61acb742b8db0519b9ed5566a6b5 100644 GIT binary patch delta 91 zcmV-h0Hpu-1os3HFoFVepn?JcfB^u31b_anh_MkS0ziq{X%zRGWF!55O~dH2pjt_1 xM&!e{9e1ONZlc0r?wQ374g+~n|-u)D$wf|!bh%*=exoX${nFk;?Dv1C9 delta 91 zcmV-h0Hpu-1os3HFoFVepn?JcfB^u31bpMdJh2fb0zjyr^GS!vglF9+GWa~1MjM-1 x1*V-Qo=9U;keM0L)Be%1GEg*sIB8Ir@Z+guoKz}R)7Hkp%O3RE@WkLfdK)60s?n~0Rf~ZFeM5B0R$ky`xDyI1h^Z;#5-RIm1{0z zUuHkX47qH=y&P&A?!GvYZ7x7}`J~MAgv&R50Ald47`7-7-92km>BP11BZ0W=U0rkg ixAK?G^&P&_WcJ9k;xsF#pT`q_fA`_BZgsjT=|c#|Ur!7G delta 200 zcmcb~c$=}_pz+3nj0Q%gMwV*s@)=Ex%!{KIMKmxnE)+HpY~U$LEJ{x;Db~v>&d+OP zWL{*~z*&@^UjpSbHZgH{G&3?TQ8!TKVq|1d__Y6)a>%w#(;r7H6Ppw2ciJl2K3lwf zfBU+~_~Pqj{)LT{3DDj{N%)R;Vg;FPGCi zsoY_c-oB;u`P5sNuBV%N%g%5&FF&7`zIMi$r?(sgqw>nv`&9U^`yc)Jb6r0#01i4( A&j0`b diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/snapshot.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/snapshot.json index 7064427..ce09e30 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/snapshot.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/snapshot.json @@ -3,24 +3,24 @@ { "keyid": "f93cfcf33d335ff43654ec6047e0a18dd5595ee3de53136b94c9c756788a0f97", "method": "ed25519", - "sig": "b518d8071a5257410fbe60a825208ba71e29c923883c6787b475e795dad2d767354b1d98473777cf6e67ad98cce5da40115a6e77af4c784faeff5bf3f37e8f0d" + "sig": "df224bc6165f35638d37cbd207d613660abf62abfa9c6d90b59f8989ad26e1f1661118863589ecaa29a3b36bff090c3dfec05106cb906c5ff7c08303ea66bc06" } ], "signed": { "_type": "Snapshot", - "expires": "2035-06-01T22:18:00Z", + "expires": "2038-01-18T03:14:15Z", "meta": { "root.json": { "hashes": { - "sha256": "f2bfda2354b6b297e358a6169c554ecb3a5b3f6b1787bf87ae595f73d7764f71" + "sha256": "983203b28c67ea490db40bc82801aa771c9522ccbbc33bcf3239e37eef95523d" }, "length": 2120, "version": 1 }, "targets.json": { - "version": 3 + "version": 2 } }, - "version": 3 + "version": 2 } } \ No newline at end of file diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/targets.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/targets.der index 3ee0e0936350373178b8a83ac0dfcc168c4129df..ad83b248fe5634ac76a8a3f54279e7e4b71049f1 100644 GIT binary patch delta 94 zcmV-k0HOcC1HS_lFoFTQpn?HOfB^x41b_anh=KtEu@q|oKt1tx{pY}xjv#!9w(5OW zBy?cu1<7nVx}m8CGZ&|pHgHcOfeI0E_8r^nOw0z2F( A%>V!Z delta 94 zcmV-k0HOcC1HS_lFoFTQpn?HOfB^x41bcd(W`Y3&u@q|oK-%LjWAv2*b)i3GMX<~N z+51Hssa(#!_LQQF7q{7H=aXin4%bJA1kL+ytyOx&@c6A}tm*Ycvrba~e5Kzf3!&I9 AL;wH) diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/targets.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/targets.json index 75f48a1..763aac7 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/targets.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/targets.json @@ -3,7 +3,7 @@ { "keyid": "630cf584f392430b2119a4395e39624e86f5e5c5374507a789be5cf35bf090d6", "method": "ed25519", - "sig": "dae32e63f4950275a13f6445b0cbffd9fb451ba95ccebef694a28b17b7d968e79366a40ed7478704cdfb6ead557ac4f0f8ad66ace9f544b34e52ff7ca5df270b" + "sig": "f8ed79a8d122eb1eaf1132c980989480c36a4393ed1e6d63fcfa27623b996d395bc1db150692dd339a003a7338bdafacdae03204ebaeb9d64c10bb050715e003" } ], "signed": { @@ -12,7 +12,7 @@ "keys": {}, "roles": [] }, - "expires": "2035-08-25T05:45:10Z", + "expires": "2038-01-18T03:14:15Z", "targets": { "/BCU1.1.txt": { "custom": { @@ -35,6 +35,6 @@ "length": 17 } }, - "version": 3 + "version": 2 } } \ No newline at end of file diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/timestamp.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/director/metadata/timestamp.der index 7b5334fda650c009d87e9120689bbc690cf1227e..7277911e205761c841d838732d5306b36106fff6 100644 GIT binary patch delta 159 zcmV;Q0AT;c0mK1TFoD6KQGfvhfdqg4u84vG0;5BtL4XW%Zeeh9Xm4~bWMy)J0Rn;o z0NH~90i-7|B?FGrbZt1{`^vzNj}?(uEq}LOCiV{!>kVLmM)?2$ delta 160 zcmV;R0AK&a0mT7VFoD9LQh)&ifdqR4j+lZ01EWNvLVyi(Zeeh9Xm4~bYIARHfdK=8 z0s>Nl0Rf~ZFeM5B0R$kto{b^Xk>z`!LM>jIFr;SYpE+)~ikr1{y$8(&>wM0URxUuS zYBKj?GE)}EmO}ii8bu$-}>oV-xq0aI|*>eColj2 diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/root.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/root.json index f39cb1c..8375b5d 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/root.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/root.json @@ -3,7 +3,7 @@ { "keyid": "94c836f0c45168f0a437eef0e487b910f58db4d462ae457b5730a4487130f290", "method": "ed25519", - "sig": "2c5c62517a5f3d0a194379a5e9f07fb16884db0d299c973711e8f965ccabf864a2d7203b1b3c7a0a3f69ed0ef5612d53cf318935ece9dffae95adf17696e3b09" + "sig": "f08d8dfe263e55facaaa16eda78a8e51fbbaa2e36ae47b93a8103ca0982dc3deb988f7a1aee5c805a322232b01299c5919b3f8cb6bf04cca7a68e9ac3903d309" } ], "signed": { @@ -12,7 +12,7 @@ "gz" ], "consistent_snapshot": false, - "expires": "2036-05-25T04:06:12Z", + "expires": "2038-01-18T03:14:07Z", "keys": { "6fcd9a928358ad8ca7e946325f57ec71d50cb5977a8d02c5ab0de6765fef040a": { "keyid_hash_algorithms": [ diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/snapshot.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/snapshot.der index b48917780fadd1f3678e1e9cbef4a3c871741f62..be72d96bcd6efdcf9e13255a435c0db8ddd4c651 100644 GIT binary patch delta 180 zcmcc3c#|>Ppz+#*v<60|Mwa@2>*|{r85c(^3Tt3wTqtB9(7;`iSd^YxQmmJfTGYtM zxX8AFqbNVW1j=n&d+OP zWL#v|z*&@^UjpMdF>!b_GcqnwH&EqbWMolz;i9_pmf4JRw|o7T87iMX|L02m2lcDA z2Cbd1XN3P)Qvos}(;%HiVbzEDURM8xjQta9WgH^U@3eOP<5j|N@?>`t!{Nq5$Ch

>nlndD1H4-)9smFU diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/targets.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/targets.json index 9a945d8..ee1c00e 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/targets.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/targets.json @@ -3,7 +3,7 @@ { "keyid": "c24b457b2ca4b3c2f415efdbbebb914a0d05c5345b9889bda044362589d6f596", "method": "ed25519", - "sig": "50370a71b676fb3fa32e73c10e0da41261205973c1c5df1546df6ce8e4feb5a4b9bef2d937f97ec73bd56b7afe03aa477764cfd5d1fa6f8a211a309a33474d03" + "sig": "6c300b24ffb94f9d1d3465e3e9a877a359c5e5316e3edfd86154eeee08503e111fa23c8b7ca8af2af74747e701272999e61b4930b98a15a41e4a6d20e781490f" } ], "signed": { @@ -12,7 +12,7 @@ "keys": {}, "roles": [] }, - "expires": "2035-08-25T05:45:02Z", + "expires": "2038-01-18T03:14:07Z", "targets": { "/BCU1.0.txt": { "hashes": { diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.der b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.der index 0f168bb32d772be153c4157ca1d49fe47de2dd78..93282aba6be7ee35159657aec9c72044e82002e6 100644 GIT binary patch delta 159 zcmV;Q0AT;c0mK1TFoD6KQGfvhfdqg4u78370i#2sL4XW%Zeeh9Xm4~bWMy)J0Re&n z0NH~90i-7|B?LhSUvt*MLr4d*()lk8MrN=)J04t&>S#ZXq&A0%Z1iNc6Z$#$0ZTYht? NGer~HGO9>&-3;lDM6&<@ delta 160 zcmV;R0AK&a0mT7VFoD9LQh)&ifdqR4j*x-@0i#5tLVyi(Zeeh9Xm4~bYIARHfdK)6 z0s>Nl0Rf~ZFeM5B0R$im8n(0z0D~}i8XEUwddtz?QPWo_?65^jCMB1p%iYM4RxUs+ zwWRM=DHmG(58^cvQYKH2JGtcNBfM*dgIBGt(oBWG2DQltd{aMXo3mKM)?bchIYm{U OB>C}PPcBRz`<4u>`#`<` diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.json index 53962b2..3d490d5 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/full_metadata_archive/imagerepo/metadata/timestamp.json @@ -3,16 +3,16 @@ { "keyid": "6fcd9a928358ad8ca7e946325f57ec71d50cb5977a8d02c5ab0de6765fef040a", "method": "ed25519", - "sig": "2cb5a4ef5529175afd0fe2351252264f8f3bb9e4e723bc6b868357adaed24c85c006b5c9077c533f679bb358c3d65f8e663945559f24f9f15e4f2e4c1efb960c" + "sig": "0c2a95485dca56dadf4be8f8f015de79d92e015427c79a5ff134df63f6a81b19abd2bec2fd85840a7624c2409faaf694cbec2467d9012a42660e223000eb540d" } ], "signed": { "_type": "Timestamp", - "expires": "2035-05-26T22:17:52Z", + "expires": "2038-01-18T03:14:07Z", "meta": { "snapshot.json": { "hashes": { - "sha256": "0b1ab6b40d008330781a1af7637acbd1de51d35728ecb0454a262597a5cbddc8" + "sha256": "d637c37e4417c7649b275403eacf3bf2c0db913bc72ac2847e55968d07ea0716" }, "length": 594, "version": 1 diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive.zip b/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive.zip index 55c94bc13b44407fe34a4b8e631dee98e8d4f2e1..ee277c0e9cd53f5a154ee578c7cc956f40de1cea 100644 GIT binary patch delta 1023 zcmcb@dx@7fz?+$civa{)mF=0xE3YEn@-{{fh)XND85mh!Ff%ZK352@Ije8~rXmFTK zdmCc`#1k7N1zQ^S_}%={IwPBjfuV$jfkAF^4zuiJdp5RuP6lT4DQ{yyiqQ<5I{9Fp zh#^ng``;p~EtyN_SQJ*ZglmWkteSJ<<+_aB8R=aPE8h2|Z(r-sJe>$K?*u;FdPU5YLU1j?OZad|vP9 zHaE$W#g*&&v(thrc~>l`tSY{=dvj{J1?McKz!mqg`QK{i%;RUjot~|(v@I&u)93o>^ZDEAqxMXC+^hTfS}J!cl76&c;Bf)`UlseRFCP6c5|0bUtT>gRsUZ74~cuN z<){0f?%X!_*Wo=f@8YiM-IZULZvF0QS?G7=tnzlf<1e?=Z`(4r%;v?W|c6pw|e2hWLWEbz&6qdaf-U~UlR#*gZnYY_qvOK?8g*8Ss`ykU1 z+1Uo40*+oh*?hq9#UI_NES#2n=g&y1g`K|@5RxQ+bjFftP0lu-mtAnNEzLQlES~xTz#VaL;tz%|Rd6ef2T;)17x|l2h)RLe>uDXBtAMHXLhR zvF1xe?YbFzulihYuQ14FRW4TuGB8lN@2z_!YUB4qm9o$D>JB+AyORBWo{#ep2-pqTCbe{r;5Nqp76-LkUj^1}BI_D+|tX9gu;5nwXsVE6${tx8)Dr7xYF!mP_K zJQbWgC(mG()BSgUSD%{OvZKB|)3|3#_)1I6Y?w07@$nSa-m|=Se)*kS*jCk6r7Ei0 z+vB8Ay7?)yDr3lvI-TFY{~VZN*E*{&cYW63kb5VNg><-e6^FmssCDZ5_v*yYH-5Z5 zEGm9vdF5um*sKkIotiG28NZLT5%aOv*SD|xDJ{$p;LXS+#|+PgljB)+nSuFmazCpK zGcdbNUdL+249r46z8F8m5AYl}nTO2=l*N$b5dJG*6J-u!W|*AEtiZ;~zzp>2d}0j+&0Va zQ$!a_^15yGxS+28tZ?(+PtQ6#)-7G9vaJ5~)92=~-g_QB?$!Ie@}1|Gu>6Xf^6#E6 z?^aJ)e)jRt^y7!$&U`sN^!Jpd_WRzRU$C5m^S)E9^hd4_Mvqe){L^1PeK|M%X7_h_ zySDO&cRwXp_T?`BXrSM|d-`7Q{`S|A{nh(ouiCnPd1vs$V(z)~s=3Q$&s)#^?=ibv zX5$)7$Le~+V}Vur0e8+vELkzx*uhykYmpx>+s^PFPN`7y87sbp?0&j=e!3@5;nbWb z85{d<>^zpGbdhb7lSz+qL)xhc$-dVXRUBS&E$i}$Lzxnpu6ZsOBUDQo&rLeBbV*d; zl!m>5FGI7tly-)kiq-sftw=tvs*|Vw|KG6p`P1fJ7k%|iwcaK+X79XrS6{zO5|nCc zU47p78V^&0RL?DjT#cowpVE4#vVUQ5+%-+hgX^Si=fn7t&F0k^dw0b$PFgx?2J_5s zUiZFf@6~$NptWR`o!-qgVVMhFh#Ca*h;EwyR4yvJkY`n@?6ZUO7A{PX%l)CSRPG|X zrAc{JikD)WlI{O~y}2)x_I>$NZhOv7t!|Q;T-_gVN*4j9a}I{IsXP5jX3Z>EF*%i4 zm;L{dmodQ9Ie8|toc^w?xF})e&yUpHPPh8@EaUC*aqv37pzu`Fv1e>~wd&6UoNb-= zPU@QE=C)AHZQ0SyPncC1L+@0rnf@nLEBK|;Z;|V~%m3g1^HyuG(X%`I@`J@nyYu(Y z{8&?TOK9KgcW-sl>z}XXU2FB};qhBrO!fEg%K66qD}M8reOBfS0p5&Ea?J3oI60P8 zml>EBC-<_-FaxvPy?cu1<7nVx}m8CGZ&|pHgHcOfeI0E_8r^nOw0z2F( A%>V!Z delta 94 zcmV-k0HOcC1HS_lFoFTQpn?HOfB^x41bcd(W`Y3&u@q|oK-%LjWAv2*b)i3GMX<~N z+51Hssa(#!_LQQF7q{7H=aXin4%bJA1kL+ytyOx&@c6A}tm*Ycvrba~e5Kzf3!&I9 AL;wH) diff --git a/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.json b/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.json index 75f48a1..763aac7 100644 --- a/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.json +++ b/samples/metadata_samples_long_expiry/update_to_one_ecu/partial_metadata_archive/director/metadata/targets.json @@ -3,7 +3,7 @@ { "keyid": "630cf584f392430b2119a4395e39624e86f5e5c5374507a789be5cf35bf090d6", "method": "ed25519", - "sig": "dae32e63f4950275a13f6445b0cbffd9fb451ba95ccebef694a28b17b7d968e79366a40ed7478704cdfb6ead557ac4f0f8ad66ace9f544b34e52ff7ca5df270b" + "sig": "f8ed79a8d122eb1eaf1132c980989480c36a4393ed1e6d63fcfa27623b996d395bc1db150692dd339a003a7338bdafacdae03204ebaeb9d64c10bb050715e003" } ], "signed": { @@ -12,7 +12,7 @@ "keys": {}, "roles": [] }, - "expires": "2035-08-25T05:45:10Z", + "expires": "2038-01-18T03:14:15Z", "targets": { "/BCU1.1.txt": { "custom": { @@ -35,6 +35,6 @@ "length": 17 } }, - "version": 3 + "version": 2 } } \ No newline at end of file From 0dd0403cab7f6d7f804fc149a324b52f1e53c0b5 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 28 May 2019 16:32:55 -0400 Subject: [PATCH 31/40] Modify test to validate partial verification Secondary firmware also --- tests/test_secondary.py | 52 ++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/tests/test_secondary.py b/tests/test_secondary.py index de2a98b..58e9f6e 100644 --- a/tests/test_secondary.py +++ b/tests/test_secondary.py @@ -821,24 +821,54 @@ def test_40_process_metadata(self): def test_50_validate_image(self): - image_fname = 'TCU1.1.txt' + # In these tests, Secondary ECU were or were not given instructions to + # install a new update + # If instructed + # full verification Secondary will install 'TCU1.1.txt' + # partial verification Secondary will install 'BCU1.1.txt' + fv_image_fname = 'TCU1.1.txt' + pv_image_fname = 'BCU1.1.txt' + sample_image_location = os.path.join(demo.DEMO_DIR, 'images') - client_unverified_targets_dir = TEST_INSTANCES[0]['client_dir'] + '/unverified_targets' - if os.path.exists(client_unverified_targets_dir): - shutil.rmtree(client_unverified_targets_dir) - os.mkdir(client_unverified_targets_dir) + # Copy the firmware into the Secondary's unverified targets directory. + # (This is what the Secondary would do when receiving the file from + # the Primary.) + # Delete and recreate the unverified targets directory first. + for instance_data in TEST_INSTANCES: + client_unverified_targets_dir = os.path.join( + instance_data['client_dir'], 'unverified_targets') + + if os.path.exists(client_unverified_targets_dir): + shutil.rmtree(client_unverified_targets_dir) + os.mkdir(client_unverified_targets_dir) + + if instance_data['partial_verifying']: + image_fname = pv_image_fname + else: + image_fname = fv_image_fname - shutil.copy( - os.path.join(sample_image_location, image_fname), - client_unverified_targets_dir) + shutil.copy( + os.path.join(sample_image_location, image_fname), + client_unverified_targets_dir) + + # Validate the appropriate update image for each secondary + + # Secondaries 0-2 are running full verification + TEST_INSTANCES[0]['instance'].validate_image(fv_image_fname) + + with self.assertRaises(uptane.Error): + TEST_INSTANCES[1]['instance'].validate_image(fv_image_fname) + with self.assertRaises(uptane.Error): + TEST_INSTANCES[2]['instance'].validate_image(fv_image_fname) - TEST_INSTANCES[0]['instance'].validate_image(image_fname) + #Secondaries 3-5 are running partial verification + TEST_INSTANCES[3]['instance'].validate_image(pv_image_fname) with self.assertRaises(uptane.Error): - TEST_INSTANCES[1]['instance'].validate_image(image_fname) + TEST_INSTANCES[4]['instance'].validate_image(pv_image_fname) with self.assertRaises(uptane.Error): - TEST_INSTANCES[2]['instance'].validate_image(image_fname) + TEST_INSTANCES[5]['instance'].validate_image(pv_image_fname) From ee54328634e585bccc129d34ea9189fec30d83b3 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 28 May 2019 18:00:22 -0400 Subject: [PATCH 32/40] Modify Secondary client to use methods from parent class 1. Removed methods which are implemented in theclient class 2. Modify update_time and fully_validate_metadata methods to use function from the client class --- uptane/clients/secondary.py | 117 +++--------------------------------- 1 file changed, 8 insertions(+), 109 deletions(-) diff --git a/uptane/clients/secondary.py b/uptane/clients/secondary.py index 7d0dbf3..b715520 100644 --- a/uptane/clients/secondary.py +++ b/uptane/clients/secondary.py @@ -391,30 +391,9 @@ def update_time(self, timeserver_attestation): If verification is successful, switch to a new nonce for next time. """ - # If we're using ASN.1/DER format, convert the attestation into something - # comprehensible (JSON-compatible dictionary) instead. - if tuf.conf.METADATA_FORMAT == 'der': - timeserver_attestation = asn1_codec.convert_signed_der_to_dersigned_json( - timeserver_attestation, DATATYPE_TIME_ATTESTATION) - - # Check format. - uptane.formats.SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA.check_match( - timeserver_attestation) - - # Assume there's only one signature. - assert len(timeserver_attestation['signatures']) == 1 - - verified = uptane.common.verify_signature_over_metadata( - self.timeserver_public_key, - timeserver_attestation['signatures'][0], - timeserver_attestation['signed'], - DATATYPE_TIME_ATTESTATION) - - if not verified: - raise tuf.BadSignatureError('Timeserver returned an invalid signature. ' - 'Time is questionable, so not saved. If you see this persistently, ' - 'it is possible that there is a Man in the Middle attack underway.') - + # Verify the signature of the timeserver on the attestation. If not verified, + # it raises a BadSignatureError + timeserver_attestation = self.verify_timeserver_signature(timeserver_attestation) # If the most recent nonce we sent is not in the timeserver attestation, # then we don't trust the timeserver attestation. @@ -438,58 +417,9 @@ def update_time(self, timeserver_attestation): 'underway between the vehicle and the servers, or within the ' 'vehicle.') - # Extract actual time from the timeserver's signed attestation. - new_timeserver_time = timeserver_attestation['signed']['time'] - - # Make sure the format is understandable to us before saving the - # time. Convert to a UNIX timestamp. - new_timeserver_time_unix = int(tuf.formats.datetime_to_unix_timestamp( - iso8601.parse_date(new_timeserver_time))) - tuf.formats.UNIX_TIMESTAMP_SCHEMA.check_match(new_timeserver_time_unix) - - # Save verified time. - self.all_valid_timeserver_times.append(new_timeserver_time) - - # Set the client's clock. This will be used instead of system time by TUF. - tuf.conf.CLOCK_OVERRIDE = new_timeserver_time_unix + # Update the time of Primary with the time in attestation + self.update_verified_time(timeserver_attestation) - # Use a new nonce next time, since the nonce we were using has now been - # used to successfully verify a timeserver attestation. - self.change_nonce() - - - - - - def refresh_toplevel_metadata(self): - """ - Refreshes client's metadata for the top-level roles: - root, targets, snapshot, and timestamp - - See tuf.client.updater.Updater.refresh() for details, or the - Uptane Standard, section 5.4.4.2 (Full Verification). - - # TODO: This function is duplicated in primary.py and secondary.py. It must - # be moved to a general client.py as part of a fix to issue #14 - # (github.com/uptane/uptane/issues/14). - This can raise TUF update exceptions like - - tuf.ExpiredMetadataError: - if after attempts to update the Root metadata succeeded or failed, - whatever currently trusted Root metadata we ended up with was expired. - - tuf.NoWorkingMirrorError: - if we could not obtain and verify all necessary metadata - """ - - # Refresh the Director first, per the Uptane Standard. - self.updater.refresh(repo_name=self.director_repo_name) - - # Now that we've dealt with the Director repository, deal with any and all - # other repositories, presumably Image Repositories. - for repository_name in self.updater.repositories: - if repository_name == self.director_repo_name: - continue - - self.updater.refresh(repo_name=repository_name) @@ -528,16 +458,14 @@ def fully_validate_metadata(self): """ - # Refresh the top-level metadata first (all repositories). - self.refresh_toplevel_metadata() + # Get list of targets from the Director + directed_targets = self.get_target_list_from_director() validated_targets_for_this_ecu = [] # Comb through the Director's direct instructions, picking out only the # target(s) earmarked for this ECU (by ECU Serial) - for target in self.updater.targets_of_role( - rolename='targets', repo_name=self.director_repo_name): - + for target in directed_targets: # 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 \ @@ -562,35 +490,6 @@ def fully_validate_metadata(self): - def get_validated_target_info(self, target_filepath): - """ - COPIED EXACTLY, MINUS COMMENTS, from primary.py. - # TODO: Refactor later. - Throws tuf.UnknownTargetError if unable to find/validate a target. - """ - tuf.formats.RELPATH_SCHEMA.check_match(target_filepath) - - validated_target_info = self.updater.target( - target_filepath, multi_custom=True) - - if self.director_repo_name not in validated_target_info: - - raise tuf.Error('Unexpected behavior: did not receive target info from ' - 'Director repository (' + repr(self.director_repo_name) + ') for ' - 'a target (' + repr(target_filepath) + '). Is pinned.json configured ' - '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?') - - tuf.formats.TARGETFILE_SCHEMA.check_match( - validated_target_info[self.director_repo_name]) - - return validated_target_info[self.director_repo_name] - - - - - def process_metadata(self, metadata_archive_fname): """ Expand the metadata archive using _expand_metadata_archive() From cde525a5c733eed05c1cb27e887b4dd52e9a4fbf Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 28 May 2019 18:27:17 -0400 Subject: [PATCH 33/40] Modify tests to accomodate for change of name of primary ECU keys Earlier the private key of the primary ECU was used by the name 'primary_key'. Now it has been changed to 'ecu_key' --- tests/test_primary.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_primary.py b/tests/test_primary.py index f7087a9..2f1613b 100644 --- a/tests/test_primary.py +++ b/tests/test_primary.py @@ -176,7 +176,7 @@ def test_01_init(self): director_repo_name=demo.DIRECTOR_REPO_NAME, vin=5, # INVALID ecu_serial=PRIMARY_ECU_SERIAL, - primary_key=TestPrimary.ecu_key, + ecu_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, timeserver_public_key=TestPrimary.key_timeserver_pub, my_secondaries=[]) @@ -188,7 +188,7 @@ def test_01_init(self): director_repo_name=demo.DIRECTOR_REPO_NAME, vin=VIN, ecu_serial=500, # INVALID - primary_key=TestPrimary.ecu_key, + ecu_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, timeserver_public_key=TestPrimary.key_timeserver_pub, my_secondaries=[]) @@ -200,7 +200,7 @@ def test_01_init(self): director_repo_name=demo.DIRECTOR_REPO_NAME, vin=VIN, ecu_serial=PRIMARY_ECU_SERIAL, - primary_key={''}, # INVALID + ecu_key={''}, # INVALID time=TestPrimary.initial_time, timeserver_public_key=TestPrimary.key_timeserver_pub, my_secondaries=[]) @@ -212,7 +212,7 @@ def test_01_init(self): director_repo_name=demo.DIRECTOR_REPO_NAME, vin=VIN, ecu_serial=PRIMARY_ECU_SERIAL, - primary_key=TestPrimary.ecu_key, + ecu_key=TestPrimary.ecu_key, time='invalid because this is not a time', # INVALID timeserver_public_key=TestPrimary.key_timeserver_pub, my_secondaries=[]) @@ -224,7 +224,7 @@ def test_01_init(self): director_repo_name=demo.DIRECTOR_REPO_NAME, vin=VIN, ecu_serial=PRIMARY_ECU_SERIAL, - primary_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, + ecu_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, timeserver_public_key=TestPrimary.initial_time, # INVALID my_secondaries=[]) @@ -235,7 +235,7 @@ def test_01_init(self): director_repo_name=5, #INVALID vin=VIN, ecu_serial=PRIMARY_ECU_SERIAL, - primary_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, + ecu_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, timeserver_public_key = TestPrimary.key_timeserver_pub, my_secondaries=[]) @@ -246,7 +246,7 @@ def test_01_init(self): director_repo_name= "invalid", #INVALID vin=VIN, ecu_serial=PRIMARY_ECU_SERIAL, - primary_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, + ecu_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, timeserver_public_key = TestPrimary.key_timeserver_pub, my_secondaries=[]) @@ -261,7 +261,7 @@ def test_01_init(self): director_repo_name=demo.DIRECTOR_REPO_NAME, vin=VIN, ecu_serial=PRIMARY_ECU_SERIAL, - primary_key=TestPrimary.ecu_key, + ecu_key=TestPrimary.ecu_key, time=TestPrimary.initial_time, timeserver_public_key=TestPrimary.key_timeserver_pub) @@ -272,7 +272,7 @@ def test_01_init(self): self.assertEqual([], TestPrimary.instance.nonces_sent) self.assertEqual(VIN, TestPrimary.instance.vin) self.assertEqual(PRIMARY_ECU_SERIAL, TestPrimary.instance.ecu_serial) - self.assertEqual(TestPrimary.ecu_key, TestPrimary.instance.primary_key) + self.assertEqual(TestPrimary.ecu_key, TestPrimary.instance.ecu_key) self.assertEqual(dict(), TestPrimary.instance.ecu_manifests) self.assertEqual( TestPrimary.instance.full_client_dir, TEMP_CLIENT_DIR) From 78cb23b697e2fe998f2e7000efc750dbcffc1415 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Tue, 4 Jun 2019 12:36:57 -0400 Subject: [PATCH 34/40] FIX to work with python2 Syntax for initialization of parent from child class is different in python2. --- uptane/clients/primary.py | 2 +- uptane/clients/secondary.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index 8ef60ec..a3950b1 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -283,7 +283,7 @@ def __init__( # TODO: Should also check that ecu_key is a private key, not a # public key. - super().__init__(full_client_dir, director_repo_name, vin, + super(Primary, self).__init__(full_client_dir, director_repo_name, vin, ecu_serial, ecu_key, time, timeserver_public_key) diff --git a/uptane/clients/secondary.py b/uptane/clients/secondary.py index b715520..35e1667 100644 --- a/uptane/clients/secondary.py +++ b/uptane/clients/secondary.py @@ -242,7 +242,7 @@ def __init__( if director_public_key is not None: tuf.formats.ANYKEY_SCHEMA.check_match(director_public_key) - super().__init__(full_client_dir, director_repo_name, vin, + super(Secondary, self).__init__(full_client_dir, director_repo_name, vin, ecu_serial, ecu_key, time, timeserver_public_key) self.director_proxy = None From 3c0fad1f2d9f4ba46bb632f2130d32d913c8d8f4 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Mon, 8 Jul 2019 16:49:10 -0400 Subject: [PATCH 35/40] Remove unused imports --- uptane/clients/client.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 3afe81e..8fceecd 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -14,28 +14,18 @@ import uptane # Import before TUF modules; may change tuf.conf values. -import os # For paths and makedirs -import shutil # For copyfile -import random # for nonces -import zipfile # to expand the metadata archive retrieved from the Primary -import hashlib import iso8601 import tuf.formats import tuf.conf import tuf.keys import tuf.client.updater -import tuf.repository_tool as rt import uptane.formats import uptane.common -import uptane.services.director as director -import uptane.services.timeserver as timeserver import uptane.encoding.asn1_codec as asn1_codec from uptane.encoding.asn1_codec import DATATYPE_TIME_ATTESTATION -from uptane.encoding.asn1_codec import DATATYPE_ECU_MANIFEST -from uptane.encoding.asn1_codec import DATATYPE_VEHICLE_MANIFEST log = uptane.logging.getLogger('client') From fee6c12ba2724bb98a261e958e57963708a67f77 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Mon, 8 Jul 2019 16:50:03 -0400 Subject: [PATCH 36/40] Remove methods list in Client class --- uptane/clients/client.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 8fceecd..da4019a 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -91,10 +91,6 @@ class Client(object): been verified by update_time. Items are appended to the end. - Methods, as called: ("self" arguments excluded): - - __init__(...) - """ def __init__( From 45f938598b952f97c3aa5ade4ba281956c313760 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Mon, 8 Jul 2019 16:53:03 -0400 Subject: [PATCH 37/40] Add the TODO comment from Primary --- uptane/clients/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index da4019a..39e3039 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -146,6 +146,8 @@ def __init__( 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) + # TODO: Should also check that primary_key is a private key, not a + # public key. self.director_repo_name = director_repo_name self.ecu_key = ecu_key From 16ab335fd2c5d71a9b07dc3aa4453208b6878146 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Mon, 8 Jul 2019 17:00:42 -0400 Subject: [PATCH 38/40] Fix formatting issues --- uptane/clients/client.py | 37 ++++++++++++++++++------------------- uptane/clients/primary.py | 2 +- uptane/clients/secondary.py | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 39e3039..64d836c 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -168,7 +168,7 @@ def __init__( if director_repo_name not in self.updater.pinned_metadata['repositories']: raise uptane.Error('Given name for the Director repository is not a ' - 'known repository, according to the pinned metadata from pinned.json') + 'known repository, according to the pinned metadata from pinned.json') @@ -289,16 +289,15 @@ def get_validated_target_info(self, target_filepath): # error earlier instead. raise uptane.Error('Unexpected behavior: did not receive target info from' - ' Director repository (' + repr(self.director_repo_name) + ') for ' - 'a target (' + repr( - target_filepath) + '). Is pinned.json configured ' - '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 repository (' + repr(self.director_repo_name) + ') for ' + 'a target (' + repr(target_filepath) + '). Is pinned.json configured ' + '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?') # Defensive coding: this should already have been checked. tuf.formats.TARGETFILE_SCHEMA.check_match( - validated_target_info[self.director_repo_name]) + validated_target_info[self.director_repo_name]) return validated_target_info[self.director_repo_name] @@ -324,7 +323,7 @@ def get_target_list_from_director(self): self.refresh_toplevel_metadata() directed_targets = self.updater.targets_of_role( - rolename='targets', repo_name=self.director_repo_name) + rolename='targets', repo_name=self.director_repo_name) if not directed_targets: log.info('A correctly signed statement from the Director indicates that ' @@ -359,23 +358,23 @@ def verify_timeserver_signature(self, timeserver_attestation): # Check format. uptane.formats.SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA.check_match( - timeserver_attestation) + timeserver_attestation) # Assume there's only one signature. This assumption is made for simplicity # in this reference implementation. If the Timeserver needs to sign with - # multiple keys for some reason, that can be accomodated. + # multiple keys for some reason, that can be accommodated. assert len(timeserver_attestation['signatures']) == 1 verified = uptane.common.verify_signature_over_metadata( - self.timeserver_public_key, - timeserver_attestation['signatures'][0], - timeserver_attestation['signed'], - DATATYPE_TIME_ATTESTATION) + self.timeserver_public_key, + timeserver_attestation['signatures'][0], + timeserver_attestation['signed'], + DATATYPE_TIME_ATTESTATION) if not verified: raise tuf.BadSignatureError('Timeserver returned an invalid signature. ' - 'Time is questionable, so not saved. If you see this persistently, ' - 'it is possible that there is a Man in the Middle attack underway.') + 'Time is questionable, so not saved. If you see this persistently, ' + 'it is possible that there is a Man in the Middle attack underway.') # Return timeserver_attestation in JSON-compatible dictionary return timeserver_attestation @@ -400,7 +399,7 @@ def update_verified_time(self, timeserver_attestation): # Make sure the format is understandable to us before saving the # attestation and time. Convert to a UNIX timestamp. new_timeserver_time_unix = int(tuf.formats.datetime_to_unix_timestamp( - iso8601.parse_date(new_timeserver_time))) + iso8601.parse_date(new_timeserver_time))) tuf.formats.UNIX_TIMESTAMP_SCHEMA.check_match(new_timeserver_time_unix) # Save validated time. @@ -411,4 +410,4 @@ def update_verified_time(self, timeserver_attestation): self.all_valid_timeserver_attestations.append(timeserver_attestation) # Set the client's clock. This will be used instead of system time by TUF. - tuf.conf.CLOCK_OVERRIDE = new_timeserver_time_unix \ No newline at end of file + tuf.conf.CLOCK_OVERRIDE = new_timeserver_time_unix diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index a3950b1..372be15 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -62,7 +62,7 @@ -class Primary(Client): # Inheriting from client class +class Primary(Client): """ This class contains the necessary code to perform Uptane validation of diff --git a/uptane/clients/secondary.py b/uptane/clients/secondary.py index 35e1667..d786337 100644 --- a/uptane/clients/secondary.py +++ b/uptane/clients/secondary.py @@ -417,7 +417,7 @@ def update_time(self, timeserver_attestation): 'underway between the vehicle and the servers, or within the ' 'vehicle.') - # Update the time of Primary with the time in attestation + # Update the time of Secondary with the time in attestation self.update_verified_time(timeserver_attestation) From 3f99b2e699b3f0078658a092237e512507d1ba93 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Wed, 10 Jul 2019 14:16:45 -0400 Subject: [PATCH 39/40] Fix whitespace issues --- uptane/clients/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 64d836c..3758aa6 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -288,10 +288,10 @@ def get_validated_target_info(self, target_filepath): # repos couldn't provide validated target file info, we'd have caught an # error earlier instead. - raise uptane.Error('Unexpected behavior: did not receive target info from' - ' Director repository (' + repr(self.director_repo_name) + ') for ' + raise uptane.Error('Unexpected behavior: did not receive target info from ' + 'Director repository (' + repr(self.director_repo_name) + ') for ' 'a target (' + repr(target_filepath) + '). Is pinned.json configured ' - 'to allow some targets to validate without Director approval, or is' + '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?') From cd022ea885f2c124f4bbf7f77fbd343df5c86de3 Mon Sep 17 00:00:00 2001 From: Tanishq Jasoria Date: Wed, 10 Jul 2019 14:40:57 -0400 Subject: [PATCH 40/40] Fix docstrings for the classes --- uptane/clients/client.py | 9 ++++++++- uptane/clients/primary.py | 25 ++++--------------------- uptane/clients/secondary.py | 6 +----- 3 files changed, 13 insertions(+), 27 deletions(-) diff --git a/uptane/clients/client.py b/uptane/clients/client.py index 3758aa6..a1fec4d 100644 --- a/uptane/clients/client.py +++ b/uptane/clients/client.py @@ -86,9 +86,16 @@ class Client(object): time attestations from the Timeserver. Conforms to tuf.formats.ANYKEY_SCHEMA. + # TODO: Rename these two variables, valid -> verified, along with the + # verification functions. Do likewise in Secondary. + self.all_valid_timeserver_attestations: + A list of all attestations received from Timeservers that have been + verified by update_time(). + Items are appended to the end. + self.all_valid_timeserver_times: A list of all times extracted from all Timeserver attestations that have - been verified by update_time. + been verified by update_time(). Items are appended to the end. """ diff --git a/uptane/clients/primary.py b/uptane/clients/primary.py index 3a983dc..c04aaa8 100644 --- a/uptane/clients/primary.py +++ b/uptane/clients/primary.py @@ -92,10 +92,6 @@ class Primary(Client): should be aware of the corresponding public key, so that it can validate these Vehicle Manifests. Conforms to tuf.formats.ANYKEY_SCHEMA. - self.updater - A tuf.client.updater.Updater object used to retrieve metadata and - target files from the Director and Supplier repositories. - self.full_client_dir The full path of the directory where all client data is stored for this Primary. This includes verified and unverified metadata and images and @@ -143,18 +139,6 @@ class Primary(Client): have already sent to the Timeserver. Will be checked against the Timeserver's response. - # TODO: Rename these two variables, valid -> verified, along with the - # verification functions. Do likewise in Secondary. - self.all_valid_timeserver_attestations: - A list of all attestations received from Timeservers that have been - verified by update_time(). - Items are appended to the end. - - self.all_valid_timeserver_times: - A list of all times extracted from all Timeserver attestations that have - been verified by update_time(). - Items are appended to the end. - self.distributable_full_metadata_archive_fname: The filename at which the full metadata archive is stored after each update cycle. Path is relative to uptane.WORKING_DIR. This is atomically @@ -179,9 +163,8 @@ class Primary(Client): Lower-level methods called by primary_update_cycle() to perform retrieval and validation of metadata and data from central services: - refresh_toplevel_metadata() - get_target_list_from_director() - get_validated_target_info() + client->get_target_list_from_director() + client->get_validated_target_info() Components of the interface available to a Secondary client: register_ecu_manifest(vin, ecu_serial, nonce, signed_ecu_manifest) @@ -200,6 +183,7 @@ class Primary(Client): import uptane.clients.primary as primary p = primary.Primary( full_client_dir='/Users/s/w/uptane/temp_primarymetadata', + director_repo_name='director' vin='vin11111', ecu_serial='ecu00000', timeserver_public_key=) @@ -248,7 +232,7 @@ def __init__( ecu_serial See class docstring above. - ecu_key See class docstring above. + ecu_key See class docstring above. timeserver_public_key See class docstring above. @@ -258,7 +242,6 @@ def __init__( An initial time to set the Primary's "clock" to, conforming to tuf.formats.ISO8601_DATETIME_SCHEMA. - tuf.FormatError diff --git a/uptane/clients/secondary.py b/uptane/clients/secondary.py index a1a44e0..21ce956 100644 --- a/uptane/clients/secondary.py +++ b/uptane/clients/secondary.py @@ -88,10 +88,6 @@ class Secondary(Client): corresponding public key, so that it can validate these ECU Manifests. Conforms to tuf.formats.ANYKEY_SCHEMA. - self.updater: - A tuf.client.updater.Updater object used to retrieve metadata and - target files from the Director and Image repositories. - self.full_client_dir: The full path of the directory where all client data is stored for this secondary. This includes verified and unverified metadata and images and @@ -165,7 +161,7 @@ class Secondary(Client): process_metadata(metadata_archive_fname) _expand_metadata_archive(metadata_archive_fname) fully_validate_metadata() - get_validated_target_info(target_filepath) + client->get_validated_target_info(target_filepath) validate_image(image_fname)