Exemple #1
0
    def test_06_encode_and_validate_resigned_time_attestation(self):
        """
    Test timeserver attestation encoding and decoding, with signing over DER
    ('re-sign' functionality in asn1_codec) and signature validation.
    """

        signable_attestation = {
            str('signatures'): [{
                str('keyid'):
                str('79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'
                    ),
                str('sig'):
                str('a5ea6a3b685ad64f96c8c12145beda4efafddfac60bcdb45def35fe43c7d1150a182a1b50a1463bfffb0ef8d30b6203aa8b5365b0b7176312e1e9d7e355e550e'
                    ),
                str('method'):
                str('ed25519')
            }],
            str('signed'): {
                str('nonces'): [1],
                str('time'): str('2017-03-08T17:09:56Z')
            }
        }

        # Load the timeserver's private key to sign a time attestation, and public
        # key to verify that signature.
        timeserver_key = demo.import_private_key('timeserver')
        timeserver_key_pub = demo.import_public_key('timeserver')
        tuf.formats.ANYKEY_SCHEMA.check_match(timeserver_key)
        tuf.formats.ANYKEY_SCHEMA.check_match(timeserver_key_pub)

        # First, calculate what we'll be verifying at the end of this test.
        # The re-signing in the previous line produces a signature over the SHA256
        # hash of the DER encoding of the ASN.1 format of the 'signed' portion of
        # signable_attestation. We produce it here so that we can check it against
        # the result of encoding, resigning, and decoding.
        der_signed = asn1_codec.convert_signed_metadata_to_der(
            signable_attestation, only_signed=True)
        der_signed_hash = hashlib.sha256(der_signed).digest()

        # Now perform the actual conversion to ASN.1/DER of the full
        # signable_attestation, replacing the signature (which was given as
        # signatures over the Python 'signed' dictionary) with a signature over
        # the hash of the DER encoding of the 'signed' ASN.1 data.
        # This is the final product to be distributed back to a Primary client.
        der_attestation = asn1_codec.convert_signed_metadata_to_der(
            signable_attestation, private_key=timeserver_key, resign=True)

        # Now, in order to test the final product, decode it back from DER into
        # pyasn1 ASN.1, and convert back into Uptane's standard Python dictionary
        # form.
        pydict_again = asn1_codec.convert_signed_der_to_dersigned_json(
            der_attestation)

        # Check the extracted signature against the hash we produced earlier.
        self.assertTrue(
            tuf.keys.verify_signature(timeserver_key_pub,
                                      pydict_again['signatures'][0],
                                      der_signed_hash))
Exemple #2
0
def derify_sample_time_attestation(json_fname, key):

    # Read the named JSON file.
    with open(json_fname) as fobj:
        json_data = json.load(fobj)

    # Check assumptions:
    #  - only one signature on the Time Attestation
    #  - the existing signature on the Time Attestation is by the same key as the
    #    one provided for re-signing
    assert len(json_data['signatures']) == 1, 'Sample conversion not written ' \
        'to handle more than one signature on a sample Time Attestation.'
    if json_data['signatures'][0]['keyid'] != key['keyid']:
        print('Existing metadata signed by keyid ' +
              json_data['signatures'][0]['keyid'] +
              '; provided key has keyid ' + key['keyid'])
        raise Exception('Wrong key! Not re-signing the Time Attestation.')

    # Re-sign (in memory) the Time Attestation using the given key.
    # Error out if any existing signature is ostensibly using a different key
    # than the given key.
    der_data = asn1_codec.convert_signed_metadata_to_der(
        json_data, key, True, False, 'time_attestation')

    # Write the new DER sample file.
    der_fname = json_fname[:-4] + 'der'
    with open(der_fname, 'wb') as fobj:
        fobj.write(der_data)
Exemple #3
0
    def test_11_ecu_manifest_der_conversion(self):

        conversion_tester(SAMPLE_ECU_MANIFEST_SIGNABLE, 'ecu_manifest', self)

        # Redundant tests. The above call should cover all of the following tests.
        der_ecu_manifest = asn1_codec.convert_signed_metadata_to_der(
            SAMPLE_ECU_MANIFEST_SIGNABLE,
            only_signed=True,
            datatype='ecu_manifest')

        der_ecu_manifest = asn1_codec.convert_signed_metadata_to_der(
            SAMPLE_ECU_MANIFEST_SIGNABLE, datatype='ecu_manifest')

        pydict_ecu_manifest = asn1_codec.convert_signed_der_to_dersigned_json(
            der_ecu_manifest, datatype='ecu_manifest')

        self.assertEqual(pydict_ecu_manifest, SAMPLE_ECU_MANIFEST_SIGNABLE)
Exemple #4
0
    def test_04_encode_full_signable_attestation(self):
        """
    Tests the conversion of signable time attestations from Python dictionaries
    to ASN.1/DER objects, and back.

    Similar to test 03 above, except that it encodes the signable dictionary
    (signed and signatures) instead of what is effectively just the 'signed'
    portion.

    Employing the asn1_codec code instead of using
     - the lower level code from pyasn1.der, asn1_spec, and
       timeserver_asn1_coder.
     - using a signable version of the time attestation (encapsulated in
       'signed', with 'signatures')

    This test doesn't re-sign the attestation over the hash of the DER encoding.
    One of the other tests below does that. The signatures here remain over
    the human-readable internal representation of the time attestation.

    """
        # Using str() here because in Python2, I'll get u'' if I don't, and the
        # self.assertEqual(signable_attestation, attestation_again) will fail
        # because they won't both have u' prefixes.
        signable_attestation = {
            str('signatures'): [{
                str('keyid'):
                str('79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'
                    ),
                str('sig'):
                str('a5ea6a3b685ad64f96c8c12145beda4efafddfac60bcdb45def35fe43c7d1150a182a1b50a1463bfffb0ef8d30b6203aa8b5365b0b7176312e1e9d7e355e550e'
                    ),
                str('method'):
                str('ed25519')
            }],
            str('signed'): {
                str('nonces'): [1],
                str('time'): str('2017-03-08T17:09:56Z')
            }
        }
        # str('signed'): {str('nonces'): [834845858], str('time'): str('2017-03-01T19:30:45Z')},
        # str('signatures'): [{str('keyid'): str('12'), str('method'): str('ed25519'), str('sig'): str('123495')}]}
        # u'signed': {u'nonces': [834845858], u'time': u'2017-03-01T19:30:45Z'},
        # u'signatures': [{u'keyid': u'12', u'method': u'ed25519', u'sig': u'12345'}]}

        uptane.formats.SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA.check_match(
            signable_attestation)

        # Converts it, without re-signing (default for this method).
        der_attestation = asn1_codec.convert_signed_metadata_to_der(
            signable_attestation, DATATYPE_TIME_ATTESTATION)

        self.assertTrue(is_valid_nonempty_der(der_attestation))

        attestation_again = asn1_codec.convert_signed_der_to_dersigned_json(
            der_attestation, DATATYPE_TIME_ATTESTATION)

        self.assertEqual(attestation_again, signable_attestation)
Exemple #5
0
def derify_sample_vehicle_manifest(json_fname, ecu_key, vehicle_key):

    # Read the named JSON file.
    with open(json_fname) as fobj:
        json_data = json.load(fobj)

    # Check assumptions:
    #  - only one signature on the Vehicle Manifest (itself)
    #  - the existing signature on the Vehicle Manifest is by the same key as the
    #    one provided for re-signing
    assert len(json_data['signatures']) == 1, 'Sample conversion not written ' \
        'to handle more than one signature on the Vehicle Manifest itself.'
    if json_data['signatures'][0]['keyid'] != vehicle_key['keyid']:
        print('Existing metadata signed by keyid ' +
              json_data['signatures'][0]['keyid'] +
              '; provided key has keyid ' + key['keyid'])
        raise Exception('Wrong key! Not re-signing the Vehicle Manifest.')

    # Minor debugging info.
    n_ecus = len(json_data['signed']['ecu_version_manifests'])
    n_manifests = sum([
        len(json_data['signed']['ecu_version_manifests'][vin])
        for vin in json_data['signed']['ecu_version_manifests']
    ])
    print('vm ' + json_fname + ' has ' + str(n_ecus) +
          ' ECUs with a total of ' + str(n_manifests) + ' manifests.')

    # Re-sign (in memory, in place) every individual ECU Manifest with the given
    # key. Error out if any existing signature is ostensibly using a different
    # key than the given key.
    for ecu in json_data['signed']['ecu_version_manifests']:
        for em in json_data['signed']['ecu_version_manifests'][ecu]:
            for i in range(0, len(em['signatures'])):
                print('Re-signing manifest #' + str(i) + ' for ecu ' + ecu)
                keyid_used_in_json = em['signatures'][i]['keyid']
                if ecu_key['keyid'] != keyid_used_in_json:
                    print('Existing ECU Manifest signed by keyid ' +
                          em['signatures'][i]['keyid'] +
                          '; provided key has keyid ' + ecu_key['keyid'])
                    raise Exception(
                        'Wrong key! Not re-signing the ECU Manifest.')
                em['signatures'][i] = common.sign_over_metadata(
                    ecu_key,
                    em['signed'],
                    'ecu_manifest',
                    metadata_format='der')

    # Convert the full Vehicle Manifest (which now has all the ECU Manifests
    # in it re-signed) and re-sign the Vehicle Manifest.
    der_data = asn1_codec.convert_signed_metadata_to_der(
        json_data, vehicle_key, True, False, 'vehicle_manifest')

    # Write the new DER sample file.
    der_fname = json_fname[:-4] + 'der'
    with open(der_fname, 'wb') as fobj:
        fobj.write(der_data)
Exemple #6
0
    def generate_signed_ecu_manifest(self, description_of_attacks_observed=''):
        """
    Returns a signed ECU manifest indicating self.firmware_fileinfo.

    If the optional description_of_attacks_observed argument is provided,
    the ECU Manifest will include that in the ECU Manifest (attacks_detected).
    """

        uptane.formats.DESCRIPTION_OF_ATTACKS_SCHEMA.check_match(
            description_of_attacks_observed)

        # We'll construct a signed signable_ecu_manifest_SCHEMA from the
        # targetinfo.
        # First, construct and check an ECU_VERSION_MANIFEST_SCHEMA.
        ecu_manifest = {
            'ecu_serial': self.ecu_serial,
            'installed_image': self.firmware_fileinfo,
            'timeserver_time': self.all_valid_timeserver_times[-1],
            'previous_timeserver_time': self.all_valid_timeserver_times[-2],
            'attacks_detected': description_of_attacks_observed
        }
        uptane.formats.ECU_VERSION_MANIFEST_SCHEMA.check_match(ecu_manifest)

        # Now we'll convert it into a signable object and sign it with a key we
        # generate.

        # Wrap the ECU version manifest object into an
        # uptane.formats.SIGNABLE_ECU_VERSION_MANIFEST_SCHEMA and check the format.
        # {
        #     'signed': ecu_version_manifest,
        #     'signatures': []
        # }
        signable_ecu_manifest = tuf.formats.make_signable(ecu_manifest)
        uptane.formats.SIGNABLE_ECU_VERSION_MANIFEST_SCHEMA.check_match(
            signable_ecu_manifest)

        if tuf.conf.METADATA_FORMAT == 'der':
            der_signed_ecu_manifest = asn1_codec.convert_signed_metadata_to_der(
                signable_ecu_manifest,
                DATATYPE_ECU_MANIFEST,
                resign=True,
                private_key=self.ecu_key)
            # TODO: Consider verification of output here.
            return der_signed_ecu_manifest

        # Else use standard Python dictionary format specified in uptane.formats.

        # Now sign with that key.
        uptane.common.sign_signable(signable_ecu_manifest, [self.ecu_key],
                                    DATATYPE_ECU_MANIFEST)
        uptane.formats.SIGNABLE_ECU_VERSION_MANIFEST_SCHEMA.check_match(
            signable_ecu_manifest)

        return signable_ecu_manifest
Exemple #7
0
def get_signed_time_der(nonces):
  """
  Same as get_signed_time, but converts the resulting Python dictionary into
  an ASN.1 representation, encodes it as DER (Distinguished Encoding Rules),
  replaces the signature with a signature over the hash of the DER encoding of
  the 'signed' portion of the data (the time and nonces).
  """
  if not PYASN1_EXISTS:
    raise uptane.Error('This Timeserver does not support DER: pyasn1 is not '
        'installed.')
  time_attestation = get_time(nonces)

  signable_time_attestation = tuf.formats.make_signable(time_attestation)
  uptane.formats.SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA.check_match(
      signable_time_attestation)

  # Convert it, re-signing over the hash of the DER encoding of the attestation.
  der_attestation = asn1_codec.convert_signed_metadata_to_der(
      signable_time_attestation, DATATYPE_TIME_ATTESTATION,
      private_key=timeserver_key, resign=True)


  return der_attestation
Exemple #8
0
    def test_20_validate_time_attestation(self):
        """
    Tests uptane.clients.secondary.Secondary::validate_time_attestation()
    """

        # We'll just test one of the three client instances, since it shouldn't
        # make a difference.
        instance = secondary_instances[0]

        # Try a valid time attestation first, signed by an expected timeserver key,
        # with an expected nonce (previously "received" from a Secondary)
        original_time_attestation = time_attestation = {
            'signed': {
                'nonces': [nonce],
                'time': '2016-11-02T21:06:05Z'
            },
            'signatures': [{
                'method':
                'ed25519',
                'sig':
                'aabffcebaa57f1d6397bdc5647764261fd23516d2996446c3c40b3f30efb2a4a8d80cd2c21a453e78bf99dafb9d0f5e56c4e072db365499fa5f2f304afec100e',
                'keyid':
                '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'
            }]
        }

        # Make sure that the Secondary thinks that it sent the nonce listed in the
        # sample data above.
        instance.last_nonce_sent = nonce

        if tuf.conf.METADATA_FORMAT == 'der':
            # Convert this time attestation to the expected ASN.1/DER format.
            time_attestation = asn1_codec.convert_signed_metadata_to_der(
                original_time_attestation,
                DATATYPE_TIME_ATTESTATION,
                private_key=TestSecondary.key_timeserver_pri,
                resign=True)

        # If the time_attestation is not deemed valid, an exception will be raised.
        instance.validate_time_attestation(time_attestation)

        # Prepare to try again with a bad signature.
        # This test we will conduct differently depending on TUF's current format:
        if tuf.conf.METADATA_FORMAT == 'der':
            # Fail to re-sign the DER, so that the signature is over JSON instead,
            # which results in a bad signature.
            time_attestation__badsig = asn1_codec.convert_signed_metadata_to_der(
                original_time_attestation,
                DATATYPE_TIME_ATTESTATION,
                resign=False)

        else:  # 'json' format
            # Rewrite the first 9 digits of the signature ('sig') to something
            # invalid.
            time_attestation__badsig = {
                'signed': {
                    'nonces': [nonce],
                    'time': '2016-11-02T21:06:05Z'
                },
                'signatures': [{
                    'method':
                    'ed25519',
                    'sig':
                    '987654321a57f1d6397bdc5647764261fd23516d2996446c3c40b3f30efb2a4a8d80cd2c21a453e78bf99dafb9d0f5e56c4e072db365499fa5f2f304afec100e',
                    'keyid':
                    '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'
                }]
            }

        # Now actually perform the bad signature test.
        with self.assertRaises(tuf.BadSignatureError):
            instance.validate_time_attestation(time_attestation__badsig)

        self.assertNotEqual(500,
                            nonce,
                            msg='Programming error: bad and good '
                            'test nonces are equal.')

        time_attestation__wrongnonce = {
            'signed': {
                'nonces': [500],
                'time': '2016-11-02T21:15:00Z'
            },
            'signatures': [{
                'method':
                'ed25519',
                'sig':
                '4d01df35ca829fd7ead1408c250950c444db8ac51fa929a7f0288578fbf81016f0e81ed35789689481aee6b7af28ab311306397ef38572732854fb6cf2072604',
                'keyid':
                '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'
            }]
        }

        if tuf.conf.METADATA_FORMAT == 'der':
            # Convert this time attestation to the expected ASN.1/DER format.
            time_attestation__wrongnonce = asn1_codec.convert_signed_metadata_to_der(
                time_attestation__wrongnonce,
                DATATYPE_TIME_ATTESTATION,
                private_key=TestSecondary.key_timeserver_pri,
                resign=True)

        with self.assertRaises(uptane.BadTimeAttestation):
            instance.validate_time_attestation(time_attestation__wrongnonce)
Exemple #9
0
  def test_20_update_time(self):

    # First, confirm that we've never verified a timeserver attestation, and/or
    # that that results in get_last_timeserver_attestation returning None.
    self.assertIsNone(TestPrimary.instance.get_last_timeserver_attestation())


    # Try a good time attestation first, signed by an expected timeserver key,
    # with an expected nonce (previously "received" from a Secondary)
    original_time_attestation = time_attestation = {
        'signed': {'nonces': [NONCE], 'time': '2016-11-02T21:06:05Z'},
        'signatures': [{
          'method': 'ed25519',
          'sig': 'aabffcebaa57f1d6397bdc5647764261fd23516d2996446c3c40b3f30efb2a4a8d80cd2c21a453e78bf99dafb9d0f5e56c4e072db365499fa5f2f304afec100e',
          'keyid': '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'}]}

    if tuf.conf.METADATA_FORMAT == 'der':
      # Convert this time attestation to the expected ASN.1/DER format.
      time_attestation = asn1_codec.convert_signed_metadata_to_der(
          original_time_attestation, DATATYPE_TIME_ATTESTATION,
          private_key=TestPrimary.key_timeserver_pri, resign=True)

    # Check expected base conditions before updating time:
    # The only timeserver times registered should be one added during
    # initialization.  Because the clock override is a module variable in TUF,
    # its value (whether None or already set) depends on whether or not other
    # tests resulting in time attestation verification have occurred (e.g.
    # those for the Primary).
    self.assertEqual(1, len(TestPrimary.instance.all_valid_timeserver_times))
    initial_clock_override = tuf.conf.CLOCK_OVERRIDE

    # In the previous functions, we added a variety of nonces in the nonce
    # rotation. Verification of a time attestation confirms that the time
    # attestation contains the nonces we've most recently sent to the
    # timeserver. The sample attestation we have here does not have the nonces
    # we've indicated to the Primary that we've sent, so this verification
    # should fail:
    with self.assertRaises(uptane.BadTimeAttestation):
      TestPrimary.instance.update_time(time_attestation)

    # Check results.  The bad attestation should change none of these.
    self.assertEqual(1, len(TestPrimary.instance.all_valid_timeserver_times))
    self.assertEqual(initial_clock_override, tuf.conf.CLOCK_OVERRIDE)

    # Now we adjust the Primary's notion of what nonces we sent to the
    # timeserver most recently, and then try the verification again, expecting
    # it to succeed.
    TestPrimary.instance.get_nonces_to_send_and_rotate()
    TestPrimary.instance.nonces_to_send = [NONCE]
    TestPrimary.instance.get_nonces_to_send_and_rotate()
    TestPrimary.instance.update_time(time_attestation)

    # Check results.  Among other things, since the verification succeeded,
    # get_last_timeserver_attestation should return the attestation we just
    # provided.
    self.assertEqual(
        time_attestation,
        TestPrimary.instance.get_last_timeserver_attestation())
    self.assertEqual(2, len(TestPrimary.instance.all_valid_timeserver_times))
    self.assertEqual(
        int(tuf.formats.datetime_to_unix_timestamp(iso8601.parse_date(
        '2016-11-02T21:06:05Z'))), tuf.conf.CLOCK_OVERRIDE)


    # Prepare to try again with a bad signature.
    # This test we will conduct differently depending on TUF's current format:
    if tuf.conf.METADATA_FORMAT == 'der':
      # Fail to re-sign the DER, so that the signature is over JSON instead,
      # which results in a bad signature.
      time_attestation__badsig = asn1_codec.convert_signed_metadata_to_der(
          original_time_attestation, DATATYPE_TIME_ATTESTATION, resign=False)

    else: # 'json' format
      # Rewrite the first 9 digits of the signature ('sig') to something
      # invalid.
      time_attestation__badsig = {
          'signed': {'nonces': [NONCE], 'time': '2016-11-02T21:06:05Z'},
          'signatures': [{
            'method': 'ed25519',
            'sig': '987654321a57f1d6397bdc5647764261fd23516d2996446c3c40b3f30efb2a4a8d80cd2c21a453e78bf99dafb9d0f5e56c4e072db365499fa5f2f304afec100e',
            'keyid': '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'}]}

    # Now actually perform the bad signature test.
    with self.assertRaises(tuf.BadSignatureError):
      TestPrimary.instance.update_time(time_attestation__badsig)


    assert 500 not in original_time_attestation['signed']['nonces'], \
        'Programming error: bad and good test nonces are equal.'

    time_attestation__wrongnonce = {
        'signed': {'nonces': [500], 'time': '2016-11-02T21:15:00Z'},
        'signatures': [{
          'method': 'ed25519',
          'sig': '4d01df35ca829fd7ead1408c250950c444db8ac51fa929a7f0288578fbf81016f0e81ed35789689481aee6b7af28ab311306397ef38572732854fb6cf2072604',
          'keyid': '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'}]}

    if tuf.conf.METADATA_FORMAT == 'der':
      # Convert this time attestation to the expected ASN.1/DER format.
      time_attestation__wrongnonce = asn1_codec.convert_signed_metadata_to_der(
          time_attestation__wrongnonce, DATATYPE_TIME_ATTESTATION,
          private_key=TestPrimary.key_timeserver_pri, resign=True)

    with self.assertRaises(uptane.BadTimeAttestation):
      TestPrimary.instance.update_time(
          time_attestation__wrongnonce)
Exemple #10
0
    def validate_primary_certification_in_vehicle_manifest(
            self, vin, primary_ecu_serial, vehicle_manifest):
        """
    Check the Primary's signature on the Vehicle Manifest and any other data
    the Primary is certifying, without diving into the individual ECU Manifests
    in the Vehicle Manifest.

    Raises an exception if there is an issue with the Primary's signature.
    No return value.
    """
        # If args don't match expectations, error out here.
        log.info(
            'Beginning validate_primary_certification_in_vehicle_manifest')
        uptane.formats.VIN_SCHEMA.check_match(vin)
        uptane.formats.ECU_SERIAL_SCHEMA.check_match(primary_ecu_serial)
        uptane.formats.SIGNABLE_VEHICLE_VERSION_MANIFEST_SCHEMA.check_match(
            vehicle_manifest)

        if primary_ecu_serial != vehicle_manifest['signed'][
                'primary_ecu_serial']:
            raise uptane.Spoofing(
                'Received a spoofed or mistaken vehicle manifest: '
                'the supposed origin Primary ECU (' +
                repr(primary_ecu_serial) + ') '
                'is not the same as what is signed in the vehicle manifest itself '
                + '(' +
                repr(vehicle_manifest['signed']['primary_ecu_serial']) + ').')

        # # TODO: Consider mechanism for fetching keys from inventorydb itself,
        # # rather than always registering them after Director svc starts up.
        # if primary_ecu_serial not in inventory.ecu_public_keys:
        #   log.debug(
        #       'Rejecting a vehicle manifest from a Primary ECU whose '
        #       'key is not registered.')
        #   raise uptane.UnknownECU('The Director is not aware of the given Primary '
        #       'ECU Serial (' + repr(primary_ecu_serial) + '. Manifest rejected. If '
        #       'the ECU is new, Register the new ECU with its key in order to be '
        #       'able to submit its manifests.')

        ecu_public_key = inventory.get_ecu_public_key(primary_ecu_serial)

        # Here, we check to see if the key that signed the Vehicle Manifest is the
        # same key as ecu_public_key (the one the director expects), so that we can
        # generate a more informative error, allowing user/debugger to distinguish
        # between a bad signature ostensibly from the right key and a signature
        # from the wrong key.
        # TODO: Fix(?) assumption that one signature is used below.
        keyid_used_in_signature = vehicle_manifest['signatures'][0]['keyid']
        # Note, though, that there could be some edge cases here that the TUF code
        # might actually resolve: for example, if the keyid hash algorithm used
        # in the signature is not the same one as the one used in the key listing,
        # this check would provide a false failure. So we don't raise an error here,
        # and instead just log this difference and let the final arbiter of the
        # validity of the signature be the dedicated code in tuf.keys.
        if keyid_used_in_signature != ecu_public_key['keyid']:
            log.info(
                'Key used to sign Vehicle Manifest has a different keyid from that '
                'listed in the inventory DB. Expect signature validation to fail, '
                'unless the key is the same but the keyid differently hashed. '
                'Expected keyid: ' + repr(ecu_public_key['keyid']) +
                '; keyid used '
                'in signature: ' + repr(keyid_used_in_signature))

        if tuf.conf.METADATA_FORMAT == 'der':
            # To check the signature, we have to make sure to encode the data as it
            # was when the signature was made. If we're using ASN.1/DER as the
            # data format/encoding, then we convert the 'signed' portion of the data
            # back to ASN.1/DER to check it.
            # Further, since for ASN.1/DER, a SHA256 hash is taken of the data and
            # *that* is what is signed, we perform that hashing as well and retrieve
            # the raw binary digest.
            data_to_check = asn1_codec.convert_signed_metadata_to_der(
                vehicle_manifest, DATATYPE_VEHICLE_MANIFEST, only_signed=True)
            data_to_check = hashlib.sha256(data_to_check).digest()

        else:
            data_to_check = vehicle_manifest['signed']

        valid = uptane.common.verify_signature_over_metadata(
            ecu_public_key,
            vehicle_manifest['signatures'][0],  # TODO: Fix assumptions.
            vehicle_manifest['signed'],
            DATATYPE_VEHICLE_MANIFEST)

        if not valid:
            log.debug(
                'Rejecting a vehicle manifest because the Primary signature on it is '
                'not valid. It must be correctly signed by the expected Primary ECU '
                'key.')
            raise tuf.BadSignatureError(
                'Sender supplied an invalid signature. '
                'Vehicle Manifest is questionable; discarding. If you see this '
                'persistently, it is possible that there is a man in the middle '
                'attack or misconfiguration.')
Exemple #11
0
    def test_20_validate_time_attestation(self):

        # First, confirm that we've never validated a timeserver attestation, and/or
        # that that results in get_last_timeserver_attestation returning None.
        self.assertIsNone(
            TestPrimary.instance.get_last_timeserver_attestation())

        # Try a valid time attestation first, signed by an expected timeserver key,
        # with an expected nonce (previously "received" from a Secondary)
        original_time_attestation = time_attestation = {
            'signed': {
                'nonces': [NONCE],
                'time': '2016-11-02T21:06:05Z'
            },
            'signatures': [{
                'method':
                'ed25519',
                'sig':
                'aabffcebaa57f1d6397bdc5647764261fd23516d2996446c3c40b3f30efb2a4a8d80cd2c21a453e78bf99dafb9d0f5e56c4e072db365499fa5f2f304afec100e',
                'keyid':
                '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'
            }]
        }

        if tuf.conf.METADATA_FORMAT == 'der':
            # Convert this time attestation to the expected ASN.1/DER format.
            time_attestation = asn1_codec.convert_signed_metadata_to_der(
                original_time_attestation,
                private_key=TestPrimary.key_timeserver_pri,
                resign=True)

        # In the previous functions, we added a variety of nonces in the nonce
        # rotation. Validation of a time attestation confirms that the time
        # attestation contains the nonces we've most recently sent to the
        # timeserver. The sample attestation we have here does not have the nonces
        # we've indicated to the Primary that we've sent, so this validation
        # should fail:
        with self.assertRaises(uptane.BadTimeAttestation):
            TestPrimary.instance.validate_time_attestation(time_attestation)

        # Now we adjust the Primary's notion of what nonces we sent to the
        # timeserver most recently, and then try the validation again, expecting
        # it to succeed.
        TestPrimary.instance.get_nonces_to_send_and_rotate()
        TestPrimary.instance.nonces_to_send = [NONCE]
        TestPrimary.instance.get_nonces_to_send_and_rotate()
        TestPrimary.instance.validate_time_attestation(time_attestation)

        # Since the validation succeeded, get_last_timeserver_attestation should
        # return the attestation we just provided.
        self.assertEqual(
            time_attestation,
            TestPrimary.instance.get_last_timeserver_attestation())

        # Prepare to try again with a bad signature.
        # This test we will conduct differently depending on TUF's current format:
        if tuf.conf.METADATA_FORMAT == 'der':
            # Fail to re-sign the DER, so that the signature is over JSON instead,
            # which results in a bad signature.
            time_attestation__badsig = asn1_codec.convert_signed_metadata_to_der(
                original_time_attestation,
                resign=False,
                datatype='time_attestation')

        else:  # 'json' format
            # Rewrite the first 9 digits of the signature ('sig') to something
            # invalid.
            time_attestation__badsig = {
                'signed': {
                    'nonces': [NONCE],
                    'time': '2016-11-02T21:06:05Z'
                },
                'signatures': [{
                    'method':
                    'ed25519',
                    'sig':
                    '987654321a57f1d6397bdc5647764261fd23516d2996446c3c40b3f30efb2a4a8d80cd2c21a453e78bf99dafb9d0f5e56c4e072db365499fa5f2f304afec100e',
                    'keyid':
                    '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'
                }]
            }

        # Now actually perform the bad signature test.
        with self.assertRaises(tuf.BadSignatureError):
            TestPrimary.instance.validate_time_attestation(
                time_attestation__badsig)


        assert 500 not in original_time_attestation['signed']['nonces'], \
            'Programming error: bad and good test nonces are equal.'

        time_attestation__wrongnonce = {
            'signed': {
                'nonces': [500],
                'time': '2016-11-02T21:15:00Z'
            },
            'signatures': [{
                'method':
                'ed25519',
                'sig':
                '4d01df35ca829fd7ead1408c250950c444db8ac51fa929a7f0288578fbf81016f0e81ed35789689481aee6b7af28ab311306397ef38572732854fb6cf2072604',
                'keyid':
                '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'
            }]
        }

        if tuf.conf.METADATA_FORMAT == 'der':
            # Convert this time attestation to the expected ASN.1/DER format.
            time_attestation__wrongnonce = asn1_codec.convert_signed_metadata_to_der(
                time_attestation__wrongnonce,
                private_key=TestPrimary.key_timeserver_pri,
                resign=True)

        with self.assertRaises(uptane.BadTimeAttestation):
            TestPrimary.instance.validate_time_attestation(
                time_attestation__wrongnonce)
Exemple #12
0
def conversion_tester(signable_pydict, datatype, cls):  # cls: clunky
    """
  Tests each of the different kinds of conversions into ASN.1/DER, and tests
  converting back. In one type of conversion, compares to make sure the data
  has not changed.

  This function takes as a third parameter the unittest.TestCase object whose
  functions (assertTrue etc) it can use. This is awkward and inappropriate. :P
  Find a different means of providing modularity instead of this one.
  (Can't just have this method in the class above because it would be run as
  a test. Could have default parameters and do that, but that's clunky, too.)
  Does unittest allow/test private functions in UnitTest classes?
  """

    # Test type 1: only-signed
    # Convert and return only the 'signed' portion, the metadata payload itself,
    # without including any signatures.
    signed_der = asn1_codec.convert_signed_metadata_to_der(signable_pydict,
                                                           datatype,
                                                           only_signed=True)

    cls.assertTrue(is_valid_nonempty_der(signed_der))

    # TODO: Add function to asn1_codec that will convert signed-only DER back to
    # Python dictionary. Might be useful, and is useful for testing only_signed
    # in any case.

    # Test type 2: full conversion
    # Convert the full signable ('signed' and 'signatures'), maintaining the
    # existing signature in a new format and encoding.
    signable_der = asn1_codec.convert_signed_metadata_to_der(
        signable_pydict, datatype)
    cls.assertTrue(is_valid_nonempty_der(signable_der))

    # Convert it back.
    signable_reverted = asn1_codec.convert_signed_der_to_dersigned_json(
        signable_der, datatype)

    # Ensure the original is equal to what is converted back.
    cls.assertEqual(signable_pydict, signable_reverted)

    # Test type 3: full conversion with re-signing
    # Convert the full signable ('signed' and 'signatures'), but discarding the
    # original signatures and re-signing over, instead, the hash of the converted,
    # ASN.1/DER 'signed' element.
    resigned_der = asn1_codec.convert_signed_metadata_to_der(
        signable_pydict, datatype, resign=True, private_key=test_signing_key)
    cls.assertTrue(is_valid_nonempty_der(resigned_der))

    # Convert the re-signed DER manifest back in order to split it up.
    resigned_reverted = asn1_codec.convert_signed_der_to_dersigned_json(
        resigned_der, datatype)
    resigned_signature = resigned_reverted['signatures'][0]

    # Check the signature on the re-signed DER manifest:
    cls.assertTrue(
        uptane.common.verify_signature_over_metadata(
            test_signing_key,
            resigned_signature,
            resigned_reverted['signed'],
            datatype,
            metadata_format='der'))

    # The signatures will not match, because a new signature was made, but the
    # 'signed' elements should match when converted back.
    cls.assertEqual(signable_pydict['signed'], resigned_reverted['signed'])
Exemple #13
0
def verify_signature_over_metadata(key_dict,
                                   signature,
                                   data,
                                   datatype,
                                   metadata_format=tuf.conf.METADATA_FORMAT):
    """
  <Purpose>
    Determine whether the private key belonging to 'key_dict' produced
    'signature'. tuf.keys.verify_signature() will use the public key found in
    'key_dict', the 'method' and 'sig' objects contained in 'signature',
    and 'data' to complete the verification.

    Higher level function that wraps tuf.keys.verify_signature, and works
    specifically with Time Attestations, ECU Manifsts, and Vehicle Manifests
    that will be in JSON or ASN.1/DER format.

    Almost exactly identical to the function simultaneously added to TUF,
    tuf.sig.verify_signature_over_metadata(). Requires datatype.
    Must differ in Uptane simply because it is not possible to convert
    Uptane-specific metadata (Time Attestations, ECU Manifests, and Vehicle
    Manifests) to or from ASN.1/DER without knowing which of those three
    types of metadata you're dealign with, and this conversion is required for
    signing and verifying signatures.

    See tuf.keys.verify_signature for lower level details.

  <Arguments>
    key_dict:
      A dictionary containing the TUF keys and other identifying information.
      If 'key_dict' is an RSA key, it has the form:

      {'keytype': 'rsa',
       'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...',
       'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
                  'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}

      The public and private keys are strings in PEM format.

    signature:
      The signature dictionary produced by one of the key generation functions.
      'signature' has the form:

      {'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...',
       'method': 'method',
       'sig': sig}.

      Conformant to 'tuf.formats.SIGNATURE_SCHEMA'.

    data:
      Data object over which the validity of the provided signature will be
      checked by verify_signature().

      Acceptable format depends somewhat on tuf.conf.METADATA_FORMAT, or, if
      the optional argument is provided, metadata_format.

      This will be converted into a bytes object and passed down to
      tuf.keys.verify_signature().

      In 'der' mode:
        'data' is expected to be a dictionary compliant with
        uptane.formats.ANY_SIGNABLE_UPTANE_METADATA_SCHEMA. ASN.1/DER
        conversion requires strictly defined formats.

      In 'json' mode:
        'data' can be any data that can be processed by
        tuf.formats.encode_canonical(data). This function is generally intended
        to verify signatures over Uptane metadata
        (uptane.formats.ANY_SIGNABLE_UPTANE_METADATA_SCHEMA), but can be used
        more broadly when in 'json' mode.

    metadata_format: (optional; default based on tuf.conf.METADATA_FORMAT)

      If 'json', treats data as a JSON-friendly Python dictionary to be turned
      into a canonical JSON string and then encoded as utf-8 before checking
      against the signature. When operating TUF with DER metadata but checking
      the signature on some piece of JSON for some reason, this should be
      manually set to 'json'. The purpose of this canonicalization is to
      produce repeatable signatures across different platforms and Python key
      dictionaries (avoiding things like different signatures over the same
      dictionary).

      If 'der', the data will be converted into ASN.1, encoded as DER,
      and hashed. The signature is then checked against that hash.

  <Exceptions>
    tuf.FormatError, raised if either 'key_dict' or 'signature' are improperly
    formatted.

    tuf.UnsupportedLibraryError, if an unsupported or unavailable library is
    detected.

    tuf.UnknownMethodError.  Raised if the signing method used by
    'signature' is not one supported.

    uptane.Error, if tuf.conf.METADATA_FORMAT is neither 'json' nor 'der'.

  <Side Effects>
    The cryptography library specified in 'tuf.conf' is called to do the actual
    verification. When in 'der' mode, argument data is converted into ASN.1/DER
    in order to verify it. (Argument object is unchanged.)

  <Returns>
    Boolean.  True if the signature is valid, False otherwise.
  """

    tuf.formats.ANYKEY_SCHEMA.check_match(key_dict)
    tuf.formats.SIGNATURE_SCHEMA.check_match(signature)
    # TODO: Check format of data, based on metadata_format.
    # TODO: Consider checking metadata_format redundantly. It's checked below.

    if metadata_format == 'json':
        data = tuf.formats.encode_canonical(data).encode('utf-8')

    elif metadata_format == 'der':

        # TODO: Have convert_signed_metadata_to_der take just the 'signed' element
        # so we don't have to do this silly wrapping in an empty signable.
        data = asn1_codec.convert_signed_metadata_to_der(
            {
                'signed': data,
                'signatures': []
            }, datatype, only_signed=True)
        data = hashlib.sha256(data).digest()

    else:  # pragma: no cover
        raise uptane.Error('Unsupported metadata format: ' +
                           repr(metadata_format) +
                           '; the supported formats are: "der" and "json".')

    return tuf.keys.verify_signature(key_dict, signature, data)
Exemple #14
0
def sign_over_metadata(key_dict,
                       data,
                       datatype,
                       metadata_format=tuf.conf.METADATA_FORMAT):
    """
  <Purpose>
    Given a key and data, returns a signature over that data.

    Higher level function that wraps tuf.keys.create_signature, and works
    specifically with Time Attestations, ECU Manifsts, and Vehicle Manifests
    that will be in JSON or ASN.1/DER format.

    Almost exactly identical to the function simultaneously added to TUF,
    tuf.sig.sign_over_metadata(). Requires datatype, and operates on
    Uptane-specific metadata (see 'datatype' argument below)

    Must differ in Uptane simply because it is not possible to convert
    Uptane-specific metadata (Time Attestations, ECU Manifests, and Vehicle
    Manifests) to or from ASN.1/DER without knowing which of those three
    types of metadata you're dealign with, and this conversion is required for
    signing and verifying signatures.

    See tuf.keys.create_signature for lower level details.

  <Arguments>
    key_dict:
      A dictionary containing the TUF keys.  An example RSA key dict has the
      form:

      {'keytype': 'rsa',
       'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...',
       'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
                  'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}

      The public and private keys are strings in PEM format.

    data:
      Data object used by create_signature() to generate the signature.
      Acceptable format depends somewhat on tuf.conf.METADATA_FORMAT, or, if
      the optional argument is provided, metadata_format.

      This will be converted into a bytes object and passed down to
      tuf.keys.create_signature().

      In 'der' mode:
        'data' is expected to be a dictionary compliant with
        uptane.formats.ANY_UPTANE_METADATA_SCHEMA. ASN.1/DER
        conversion requires strictly defined formats.

      In 'json' mode:
        'data' can be any data that can be processed by
        tuf.formats.encode_canonical(data) can be signed. This function is
        generally intended to sign metadata (tuf.formats.ANYROLE_SCHEMA), but
        can be used more broadly.

    datatype:
      The type of data signable['signed'] represents.
      Must be in uptane.encoding.asn1_codec.SUPPORTED_ASN1_METADATA_MODULES.
      Specifies the type of data provided in der_data, whether a Time
      Attestation, ECU Manifest, or Vehicle Manifest.

      'datatype' is used to determine the module to use for the conversion to
      ASN.1/DER, if the metadata format is 'der'. When 'der' is the metadata
      format, we need to convert to ASN.1/DER first, and conversion to
      ASN.1/DER varies by type. 'datatype' doesn't matter if signing is
      occuring over JSON.

      If the metadata contained a metadata type indicator (the way that
      DER TUF metadata does), and if we could also capture this in an ASN.1
      specification that flexibly supports each possible metadata type (the
      way that the Metadata specification does in TUF ASN.1), then this would
      not be necessary....
      # TODO: Try to find some way to add the type to the metadata and cover
      # these requirements above.

    metadata_format: (optional; default based on tuf.conf.METADATA_FORMAT)

      If 'json', treats data as a JSON-friendly Python dictionary to be turned
      into a canonical JSON string and then encoded as utf-8 before signing.
      When operating TUF with DER metadata but checking the signature on some
      piece of JSON for some reason, this should be manually set to 'json'. The
      purpose of this canonicalization is to produce repeatable signatures
      across different platforms and Python key dictionaries (avoiding things
      like different signatures over the same dictionary).

      If 'der', the data will be converted into ASN.1, encoded as DER,
      and hashed. The signature is then checked against that hash.

  <Exceptions>
    tuf.FormatError, if 'key_dict' is improperly formatted.

    tuf.UnsupportedLibraryError, if an unsupported or unavailable cryptography
    library is chosen.

    uptane.Error, if the given metadata format is not 'json' or 'der' or if
    the given datatype is not one of the accepted Uptane data types for
    conversion (defined in constants asn1_codec.DATATYPE_*)

    TypeError, if 'key_dict' contains an invalid keytype.

  <Side Effects>
    The cryptography library specified in 'tuf.conf' is called to do the actual
    verification. When in 'der' mode, argument data is converted into ASN.1/DER
    in order to verify it. (Argument object is unchanged.)

  <Returns>
    A signature dictionary conformant to 'tuf.format.SIGNATURE_SCHEMA'. e.g.:
    {'keyid': 'f30a0870d026980100c0573bd557394f8c1bbd6...',
     'method': '...',
     'sig': '...'}.

  """

    tuf.formats.ANYKEY_SCHEMA.check_match(key_dict)

    if datatype not in asn1_codec.SUPPORTED_ASN1_METADATA_MODULES:
        raise uptane.Error('Datatype ' + repr(datatype) +
                           ' is not a supported '
                           'Uptane metadata type. The options are: ' +
                           repr(asn1_codec.SUPPORTED_ASN1_METADATA_MODULES))

    # TODO: Check format of data, based on metadata_format.
    # TODO: Consider checking metadata_format redundantly. It's checked below.

    if metadata_format == 'json':
        data = tuf.formats.encode_canonical(data).encode('utf-8')

    elif metadata_format == 'der':
        uptane.formats.ANY_UPTANE_METADATA_SCHEMA.check_match(data)

        data = asn1_codec.convert_signed_metadata_to_der(
            {
                'signed': data,
                'signatures': []
            }, datatype, only_signed=True)
        data = hashlib.sha256(data).digest()

    else:  # pragma: no cover
        raise uptane.Error('Unsupported metadata format: ' +
                           repr(metadata_format) +
                           '; the supported formats are: "der" and "json".')

    return tuf.keys.create_signature(key_dict, data)
Exemple #15
0
  def test_20_update_time(self):
    """
    Tests uptane.clients.secondary.Secondary::update_time()
    """

    # We'll just test one of the three client instances, since it shouldn't
    # make a difference.
    instance = secondary_instances[0]

    # Try a good time attestation first, signed by an expected timeserver key,
    # with an expected nonce (previously "received" from a Secondary)
    original_time_attestation = time_attestation = {
        'signed': {'nonces': [nonce], 'time': '2016-11-02T21:06:05Z'},
        'signatures': [{
          'method': 'ed25519',
          'sig': 'aabffcebaa57f1d6397bdc5647764261fd23516d2996446c3c40b3f30efb2a4a8d80cd2c21a453e78bf99dafb9d0f5e56c4e072db365499fa5f2f304afec100e',
          'keyid': '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'}]}

    # Make sure that the Secondary thinks that it sent the nonce listed in the
    # sample data above.
    instance.last_nonce_sent = nonce

    if tuf.conf.METADATA_FORMAT == 'der':
      # Convert this time attestation to the expected ASN.1/DER format.
      time_attestation = asn1_codec.convert_signed_metadata_to_der(
          original_time_attestation, DATATYPE_TIME_ATTESTATION,
          private_key=TestSecondary.key_timeserver_pri, resign=True)

    # Check expected base conditions before updating time:
    # The only timeserver times registered should be two "now"s added during
    # initialization.  Because the clock override is a module variable in TUF,
    # its value (whether None or already set) depends on whether or not other
    # tests resulting in time attestation verification have occurred (e.g.
    # those for the Primary).
    self.assertEqual(2, len(instance.all_valid_timeserver_times))

    # If the time_attestation is not deemed valid, an exception will be raised.
    instance.update_time(time_attestation)

    # Check results.
    self.assertEqual(3, len(instance.all_valid_timeserver_times))
    # self.assertIsNotNone(tuf.conf.CLOCK_OVERRIDE)
    self.assertEqual(
        int(tuf.formats.datetime_to_unix_timestamp(iso8601.parse_date(
        '2016-11-02T21:06:05Z'))), tuf.conf.CLOCK_OVERRIDE)


    # Prepare to try again with a bad signature.
    # This test we will conduct differently depending on TUF's current format:
    if tuf.conf.METADATA_FORMAT == 'der':
      # Fail to re-sign the DER, so that the signature is over JSON instead,
      # which results in a bad signature.
      time_attestation__badsig = asn1_codec.convert_signed_metadata_to_der(
          original_time_attestation, DATATYPE_TIME_ATTESTATION, resign=False)

    else: # 'json' format
      # Rewrite the first 9 digits of the signature ('sig') to something
      # invalid.
      time_attestation__badsig = {
          'signed': {'nonces': [nonce], 'time': '2016-11-02T21:06:05Z'},
          'signatures': [{
            'method': 'ed25519',
            'sig': '987654321a57f1d6397bdc5647764261fd23516d2996446c3c40b3f30efb2a4a8d80cd2c21a453e78bf99dafb9d0f5e56c4e072db365499fa5f2f304afec100e',
            'keyid': '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'}]}

    # Now actually perform the bad signature test.
    with self.assertRaises(tuf.BadSignatureError):
      instance.update_time(time_attestation__badsig)

    # Check results.  The bad attestation should change none of these.
    self.assertEqual(3, len(instance.all_valid_timeserver_times))
    # self.assertIsNotNone(tuf.conf.CLOCK_OVERRIDE)
    self.assertEqual(
        int(tuf.formats.datetime_to_unix_timestamp(iso8601.parse_date(
        '2016-11-02T21:06:05Z'))), tuf.conf.CLOCK_OVERRIDE)


    self.assertNotEqual(500, nonce, msg='Programming error: bad and good '
        'test nonces are equal.')

    time_attestation__wrongnonce = {
        'signed': {'nonces': [500], 'time': '2016-11-02T21:15:00Z'},
        'signatures': [{
          'method': 'ed25519',
          'sig': '4d01df35ca829fd7ead1408c250950c444db8ac51fa929a7f0288578fbf81016f0e81ed35789689481aee6b7af28ab311306397ef38572732854fb6cf2072604',
          'keyid': '79c796d7e87389d1ebad04edce49faef611d139ee41ea9fb1931732afbfaac2e'}]}

    if tuf.conf.METADATA_FORMAT == 'der':
      # Convert this time attestation to the expected ASN.1/DER format.
      time_attestation__wrongnonce = asn1_codec.convert_signed_metadata_to_der(
          time_attestation__wrongnonce, DATATYPE_TIME_ATTESTATION,
          private_key=TestSecondary.key_timeserver_pri, resign=True)

    with self.assertRaises(uptane.BadTimeAttestation):
      instance.update_time(time_attestation__wrongnonce)
Exemple #16
0
    def test_15_register_vehicle_manifest(self):

        manifest_json = {
            "signatures": [{
                "keyid":
                "9a406d99e362e7c93e7acfe1e4d6585221315be817f350c026bbee84ada260da",
                "method":
                "ed25519",
                "sig":
                "335272f77357dc0e9f1b74d72eb500e4ff0f443f824b83405e2b21264778d1610e0a5f2663b90eda8ab05a28b5b64fc15514020985d8a93576fe33b287e1380f"
            }],
            "signed": {
                "primary_ecu_serial": "INFOdemocar",
                "vin": "democar",
                "ecu_version_manifests": {
                    "TCUdemocar": [{
                        "signatures": [{
                            "keyid":
                            "49309f114b857e4b29bfbff1c1c75df59f154fbc45539b2eb30c8a867843b2cb",
                            "method":
                            "ed25519",
                            "sig":
                            "fd04c1edb0ddf1089f0d3fc1cd460af584e548b230d9c290deabfaf29ce5636b6b897eaa97feb64147ac2214c176bbb1d0fa8bb9c623011a0e48d258eb3f9108"
                        }],
                        "signed": {
                            "attacks_detected": "",
                            "ecu_serial": "TCUdemocar",
                            "previous_timeserver_time": "2017-05-18T16:37:46Z",
                            "timeserver_time": "2017-05-18T16:37:48Z",
                            "installed_image": {
                                "filepath": "/secondary_firmware.txt",
                                "fileinfo": {
                                    "length": 37,
                                    "hashes": {
                                        "sha256":
                                        "6b9f987226610bfed08b824c93bf8b2f59521fce9a2adef80c495f363c1c9c44",
                                        "sha512":
                                        "706c283972c5ae69864b199e1cdd9b4b8babc14f5a454d0fd4d3b35396a04ca0b40af731671b74020a738b5108a78deb032332c36d6ae9f31fae2f8a70f7e1ce"
                                    }
                                }
                            }
                        }
                    }]
                }
            }
        }

        if tuf.conf.METADATA_FORMAT == 'json':
            manifest = manifest_json

        else:  # Use ASN.1/DER
            assert tuf.conf.METADATA_FORMAT == 'der'  # Or test code is broken/old.

            manifest_fname = os.path.join(SAMPLES_DIR,
                                          'sample_vehicle_manifest.der')
            with open(manifest_fname, 'rb') as fobj:
                manifest = fobj.read()

        # Make sure we're starting off with no registered ECU or vehicle manifests,
        # for any vehicles or ECUs, before the next tests.
        # (If you move ECU Manifest tests before Vehicle Manifest tests, of course,
        # the ECU Manifest check here will have to move elsewhere, as the dict
        # of ECU Manifests registered probably won't be empty.)
        for ecu_serial in inventory.ecu_manifests:
            self.assertFalse(inventory.ecu_manifests[ecu_serial])
        for vin in inventory.vehicle_manifests:
            self.assertFalse(inventory.vehicle_manifests[vin])

        # TODO: Register a vehicle manifest with NO ECU Manifests (unlike the one
        # above) and run these tests after it:
        # self.assertIn('democar', inventory.vehicle_manifests)
        # self.assertTrue(inventory.get_vehicle_manifests('democar'))
        # # No ECU Manifests have been registered yet, since the
        # self.assertIsNone(inventory.get_last_ecu_manifest('TCUdemocar'))
        # # This next one is a little subtle: even if there were no ECU Manifests
        # # submitted in any vehicle manifests yet, the dictionary of ECU Manifests
        # # provided should still not be totally empty if a Vehicle Manifest has been
        # # received: it will look something like this, listing an empty list of
        # # ECU Manifests for each ECU in the car:
        # # {'ecu1_in_car': [], 'ecu2_in_car': [], ...}
        # self.assertTrue(inventory.get_all_ecu_manifests_from_vehicle('democar'))

        # Try a normal vehicle manifest submission, expecting success.
        TestDirector.instance.register_vehicle_manifest(
            'democar', 'INFOdemocar', manifest)

        # Make sure that the vehicle manifest now shows up in the
        # inventorydb, that the various get functions return its data, and that
        # the ECU Manifest within now shows up in the inventorydb.
        self.assertIn('democar', inventory.vehicle_manifests)
        self.assertTrue(inventory.get_vehicle_manifests('democar'))

        # TODO: Check that the value of the Vehicle Manifest retrieved from the
        # inventory db is equivalent to the Vehicle Manifest submitted. This is
        # fairly easy for JSON, but a little trickier for ASN.1/DER, because it is
        # stored in the inventory db as JSON-compatible (not as DER, because there
        # is no longer a particularly good reason to store it as DER at that point).

        # Try reporting the wrong Primary ECU Serial, expecting a spoofing error.
        with self.assertRaises(uptane.Spoofing):
            TestDirector.instance.register_vehicle_manifest(
                'democar', 'TCUdemocar', manifest)
        with self.assertRaises(uptane.Spoofing):
            TestDirector.instance.register_vehicle_manifest(
                'democar', 'nonexistent_ecu_serial', manifest)

        # Try reporting an unknown VIN.
        with self.assertRaises(uptane.UnknownVehicle):
            TestDirector.instance.register_vehicle_manifest(
                'nonexistent_vin', 'INFOdemocar', manifest)

        # Send a partial or badly formatted manifest.
        if tuf.conf.METADATA_FORMAT == 'json':
            # Exclude the signatures portion.
            manifest_bad = copy.deepcopy(manifest['signed'])
            with self.assertRaises(tuf.FormatError):
                TestDirector.instance.register_vehicle_manifest(
                    'democar', 'INFOdemocar', manifest_bad)

        else:
            assert tuf.conf.METADATA_FORMAT == 'der'  # Or test code is broken/old

            # Send a corrupted manifest. Expect decoding error.
            manifest = b'\x99\x99\x99\x99\x99' + manifest[5:]
            with self.assertRaises(uptane.FailedToDecodeASN1DER):
                TestDirector.instance.register_vehicle_manifest(
                    'democar', 'INFOdemocar', manifest)

            # Send an empty manifest. Expect decoding error.
            manifest = bytes()
            with self.assertRaises(uptane.FailedToDecodeASN1DER):
                TestDirector.instance.register_vehicle_manifest(
                    'democar', 'INFOdemocar', manifest)

        # Prepare a manifest with a bad signature.

        if tuf.conf.METADATA_FORMAT == 'json':
            # If using JSON, just corrupt the signature value.
            manifest_bad = copy.deepcopy(manifest_json)
            manifest_bad['signatures'][0]['sig'] = \
                '1234567890abcdef9f1b74d72eb500e4ff0f443f824b83405e2b21264778d1610e0a5f2663b90eda8ab05a28b5b64fc15514020985d8a93576fe33b287e1380f'

            # Try registering the bad-signature manifest.
            with self.assertRaises(tuf.BadSignatureError):
                TestDirector.instance.register_vehicle_manifest(
                    'democar', 'INFOdemocar', manifest_bad)

        else:
            assert tuf.conf.METADATA_FORMAT == 'der'  # Or test code is broken/old.

            # TODO: Add a test using a bad signature. Note that there is already a
            # test below for the *wrong* signature, but it would be good to test
            # both for the wrong signature and for a corrupt signature.
            # So send a properly-encoded manifest with a signature that is produced
            # by the right key, but which does not match the data in the manifest.

            pass

        # Prepare a manifest with the *wrong* signature - a signature from the
        # wrong key that is otherwise correctly signed.
        if tuf.conf.METADATA_FORMAT == 'json':
            # If using JSON, just corrupt the key ID.
            manifest_bad = copy.deepcopy(manifest_json)
            manifest_bad['signatures'][0]['keyid'] = \
                '1234567890abcdef29bfbff1c1c75df59f154fbc45539b2eb30c8a867843b2cb'

        else:
            assert tuf.conf.METADATA_FORMAT == 'der'  # Or test code is broken/old.
            # When using DER, we can convert JSON to DER and re-sign with the wrong
            # key to achieve a similar test.
            manifest_bad = asn1_codec.convert_signed_metadata_to_der(
                manifest_json,
                resign=True,
                datatype='vehicle_manifest',
                private_key=demo.import_private_key('directortimestamp'))

        # Try registering the bad-signature manifest.
        with self.assertRaises(tuf.BadSignatureError):
            TestDirector.instance.register_vehicle_manifest(
                'democar', 'INFOdemocar', manifest_bad)

        # Send Vehicle Manifest containing an ECU Manifest with an unknown
        # ECU Serial. Expect no error, and expect the Vehicle Manifest to be
        # registered, but the particular ECU Manifest to not be registered.

        # First, make sure the ECU Serial in question is not registered.
        with self.assertRaises(uptane.UnknownECU):
            inventory.check_ecu_registered('unknown_ecu')

        if tuf.conf.METADATA_FORMAT == 'json':
            manifest_bad = json.load(
                open(
                    os.path.join(
                        TEST_DATA_DIR, 'flawed_manifests',
                        'vm2_contains_one_unknown_ecu_manifest.json')))
        else:
            assert tuf.conf.METADATA_FORMAT == 'der'  # Or test code is broken/old.
            manifest_bad = open(
                os.path.join(TEST_DATA_DIR, 'flawed_manifests',
                             'vm2_contains_one_unknown_ecu_manifest.der'),
                'rb').read()

        TestDirector.instance.register_vehicle_manifest(
            'democar', 'INFOdemocar', manifest_bad)

        # Now check to make sure the data for an unknown ECU wasn't saved as its
        # own ECU Manifest.
        self.assertNotIn(
            'unknown_ecu',
            inventory.get_all_ecu_manifests_from_vehicle('democar'))
        with self.assertRaises(uptane.UnknownECU):
            inventory.get_last_ecu_manifest('unknown_ecu')
        with self.assertRaises(uptane.UnknownECU):
            inventory.get_ecu_manifests('unknown_ecu')

        # Check to make sure the vehicle manifest itself was saved, though.
        self.assertIn(
            'unknown_ecu',
            inventory.get_last_vehicle_manifest('democar')['signed']
            ['ecu_version_manifests'])

        # Provide a vehicle manifest that is correctly signed by the Primary, but
        # which contains a single ECU Manifest, that ECU Manifest being signed by
        # the wrong key.
        # Ensure that the Vehicle Manifest is saved, but that the ECU Manifest it
        # contains is not saved on its own as a valid ECU Manifest.
        previous_vehicle_manifest = inventory.get_last_vehicle_manifest(
            'democar')
        previous_ecu_manifest = inventory.get_last_ecu_manifest('TCUdemocar')
        n_vms_before = len(inventory.get_vehicle_manifests('democar'))
        n_ems_before = len(inventory.get_ecu_manifests('TCUdemocar'))

        if tuf.conf.METADATA_FORMAT == 'json':
            manifest_bad = json.load(
                open(
                    os.path.join(
                        TEST_DATA_DIR, 'flawed_manifests',
                        'vm3_ecu_manifest_signed_with_wrong_key.json')))
        else:
            assert tuf.conf.METADATA_FORMAT == 'der'  # Or test code is broken/old.
            manifest_bad = open(
                os.path.join(TEST_DATA_DIR, 'flawed_manifests',
                             'vm3_ecu_manifest_signed_with_wrong_key.der'),
                'rb').read()

        TestDirector.instance.register_vehicle_manifest(
            'democar', 'INFOdemocar', manifest_bad)

        # If the latest vehicle manifest is no longer the same as the latest before
        # the test, then a vehicle manifest has been correctly saved.
        self.assertNotEqual(previous_vehicle_manifest,
                            inventory.get_last_vehicle_manifest('democar'))
        # But we must also be sure that the bad manifest has not been saved.
        self.assertEqual(previous_ecu_manifest,
                         inventory.get_last_ecu_manifest('TCUdemocar'))
        # Redundant test in case of code changes:
        self.assertEqual(n_vms_before + 1,
                         len(inventory.get_vehicle_manifests('democar')))
        self.assertEqual(n_ems_before,
                         len(inventory.get_ecu_manifests('TCUdemocar')))

        # TODO: Provide a vehicle manifest that is correctly signed by the
        # Primary, but which contains one untrustworthy ECU Manifest and one
        # trustworthy ECU Manifest. Expected behavior is to accept the Vehicle
        # Manifest and any valid ECU Manifests, and reject the untrustworthy
        # ECU Manifest. Call get functions to confirm.

        # Send a Vehicle Manifest containing an ECU Manifest that has an attack
        # detected report.
        previous_vehicle_manifest = inventory.get_last_vehicle_manifest(
            'democar')
        previous_ecu_manifest = inventory.get_last_ecu_manifest('TCUdemocar')
        n_vms_before = len(inventory.get_vehicle_manifests('democar'))
        n_ems_before = len(inventory.get_ecu_manifests('TCUdemocar'))

        if tuf.conf.METADATA_FORMAT == 'json':
            manifest = json.load(
                open(
                    os.path.join(TEST_DATA_DIR, 'flawed_manifests',
                                 'vm4_attack_detected_in_ecu_manifest.json')))
        else:
            assert tuf.conf.METADATA_FORMAT == 'der'  # Or test code is broken/old.
            manifest = open(
                os.path.join(TEST_DATA_DIR, 'flawed_manifests',
                             'vm4_attack_detected_in_ecu_manifest.der'),
                'rb').read()

        TestDirector.instance.register_vehicle_manifest(
            'democar', 'INFOdemocar', manifest)

        self.assertNotEqual(previous_vehicle_manifest,
                            inventory.get_last_vehicle_manifest('democar'))
        # But we must also be sure that the bad manifest has not been saved.
        self.assertNotEqual(previous_ecu_manifest,
                            inventory.get_last_ecu_manifest('TCUdemocar'))
        # Redundant test in case of code changes:
        self.assertEqual(n_vms_before + 1,
                         len(inventory.get_vehicle_manifests('democar')))
        self.assertEqual(n_ems_before + 1,
                         len(inventory.get_ecu_manifests('TCUdemocar')))

        # Expect the attack report string in the registered manifests.
        self.assertEqual(
            'some attack detected',
            inventory.get_last_vehicle_manifest(
                'democar')['signed']['ecu_version_manifests']['TCUdemocar'][0]
            ['signed']['attacks_detected'])

        self.assertEqual(
            'some attack detected',
            inventory.get_last_ecu_manifest('TCUdemocar')['signed']
            ['attacks_detected'])