예제 #1
0
def convert_signed_der_to_dersigned_json(der_data, datatype):
  """
  Convert the given der_data to a Python dictionary representation consistent
  with Uptane's typical JSON encoding.

  The 'signed' portion will be a JSON-compatible Python dict translation
  of der_data's 'signed' portion. Likewise for the 'signatures'
  portion. The result will be a dict containing a 'signatures' section that has
  signatures over not what is in the 'signed' section, but rather over a
  different format and encoding of what is in the 'signed' section. Please take
  care.

  <Arguments>
    der_data:
      # TODO: FILL IN

    datatype:
      String chosen from SUPPORTED_ASN1_METADATA_MODULES.
      Specifies the type of data provided in der_data, whether a Time
      Attestation, ECU Manifest, or Vehicle Manifest. This is used to determine
      the module to use for the conversion.

      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.

  <Returns>
    A JSON-compatible Python dictionary representing the data from der_data,
    including signatures that are still over the DER data.

  <Exceptions>
    tuf.FormatError
      If der_data does not seem to be valid DER data (regardless of the type).

    uptane.Error
      If datatype is not a data type that Uptane supports converting into
      ASN.1/DER.

    uptane.ASN1DERDecodingError
      If der_data cannot be decoded as the given datatype (if pyasn1 raises an
      error in the decode process).
  """

  if not PYASN1_EXISTS:
    # This error message is provided in order to be helpful; behavior is not
    # prescribed when a dependency is missing, so this clause is not tested
    # (which would entail tests running after a separate installation with
    # missing dependencies), so this clause is not included in coverage
    # metrics.
    raise uptane.Error( # pragma: no cover
        'Request was made to load a DER file, but the required '
        'pyasn1 library failed to import.')

  uptane.formats.DER_DATA_SCHEMA.check_match(der_data)

  # Make sure it's a supported type of metadata for ASN.1 to Python dict
  # translation. (Throw an exception if not.)
  ensure_valid_metadata_type_for_asn1(datatype)


  # "_signed" here refers to the portion of the metadata that will be signed.
  # The metadata is divided into "signed" and "signature" portions. The
  # signatures are signatures over the "signed" portion. "json_signed" below
  # is actually not signed - it is simply the portion that will be put into
  # the "signed" section - the portion to be signed. The nomenclature is
  # unfortunate....
  # Note that decode() returns a tuple: (pyasn1_object, remaining_input)
  # We don't expect any remaining input (TODO: Consider testing it?) and
  # are only interested in the pyasn1 object decoded from the DER.

  # TODO: Determine type of metadata here first, so that you can choose the
  # correct class from asn1_spec.
  # This object will be used by the decoder for its structure, to determine
  # how to decode the DER object.
  # I can't seem to figure out why I need to do this this way.
  # Why can't I just use Metadata() by adding TokensAndTimestamp as an optional
  # component of SignedBody()? Anyway, this seems to work.......
  # Handle for the corresponding module.
  relevant_asn_module = SUPPORTED_ASN1_METADATA_MODULES[datatype]
  if datatype == DATATYPE_TIME_ATTESTATION:
    exemplar_object = asn1_spec.TokensAndTimestampSignable()
  elif datatype == DATATYPE_ECU_MANIFEST:
    exemplar_object = asn1_spec.ECUVersionManifest()
  elif datatype == DATATYPE_VEHICLE_MANIFEST:
    exemplar_object = asn1_spec.VehicleVersionManifest()

  # TODO: Determine if there are any other error types to add to the except
  # clause below to cover whatever errors we expect pyasn1 to raise when trying
  # to convert data. That error class covers ValueConstraintError and
  # SubstrateUnderrunError, but I'm not sure if pyasn1 wouldn't raise other
  # errors....
  try:
    asn_metadata = p_der_decoder.decode(der_data, asn1Spec=exemplar_object)[0]
  except pyasn1.error.PyAsn1Error as e:
    raise uptane.FailedToDecodeASN1DER('Unable to decode the provided '
        'der_data as datatype ' + repr(datatype) + '. The pyasn1-raised error '
        'follows: ' + repr(e))

  # asn_metadata here now has three components, indexed by integer 0, 1, 2.
  # 0 is the signed component (Signed())
  # 1 i the numberOfSignatures component (Length())
  # 2 is the signatures component (Signatures())

  asn_signed_metadata = asn_metadata[0]

  # TODO: The 'signed' component here should probably already be DER, since
  # that is what the signature is over. Because this would entail some changes
  # changes to the ASN.1 data specifications in metadataverificationmodule.py,
  # I'm not doing this yet (though I expect to).
  # So, for the time being, if we wanted to check the signature, we'd have to
  # encode this thing into DER again.
  # der_signed_metadata = p_der_encoder.encode(asn_signed)


  # Now we have to figure out what type of metadata the ASN.1 metadata is
  # so that we can use the appropriate spec to convert it back to JSON.

  # # (Even though this takes asn_metadata, it only uses asn_metadata[0],
  # # asn_signed_metadata....)
  # asn_type_data = asn_signed_metadata[0] # This is the RoleType info, a class.

  # # This is how we'd extract the name of the type from the enumeration that is
  # # in the class (namedValues), indexed by the underlying "value" of
  # # asn_type_data.
  # # We call lower() on it because I don't care about the casing, which has
  # # varied somewhat in TUF history, and I don't want casing to ruin this
  # # detection.
  # metadata_type = asn_type_data.namedValues[asn_type_data._value][0].lower()


  # Convert into the basic Python dict we use in the JSON encoding.
  json_signed = relevant_asn_module.get_json_signed(asn_metadata)

  # Extract the signatures from the ASN.1 representation.
  asn_signatures = asn_metadata[2]
  json_signatures = convert_signatures_to_json(asn_signatures)

  return {'signatures': json_signatures, 'signed': json_signed}
예제 #2
0
def convert_signed_metadata_to_der(signed_metadata, datatype,
    private_key=None, resign=False, only_signed=False):
  """
  Normal behavior ("resign" (re-sign) parameter being False) converts the
  basic Python dictionary format of signed_metadata provided into ASN.1 and
  encodes it as DER, returning the resulting DER encoding of the given metadata.

  "_signed" here refers to the portion of the metadata that will be signed.
  The metadata is divided into "signed" and "signature" portions. The
  signatures are signatures over the "signed" portion. "json_signed" below
  is actually not signed - it is simply the portion that will be put into
  the "signed" section - the portion to be signed. The nomenclature is
  unfortunate....
  TODO: Better variable and function naming.

  <Arguments>
    signed_metadata
      Metadata (time attestation or ecu manifest, for example), and signature(s)
      over it.
      A dictionary with keys 'signed' and 'signatures'.
      signed_metadata must conform to one of the following:
          SIGNABLE_TIMESERVER_ATTESTATION_SCHEMA
          SIGNABLE_VEHICLE_VERSION_MANIFEST_SCHEMA
          SIGNABLE_ECU_VERSION_MANIFEST_SCHEMA

      Each of the above also conforms to tuf.formats.SIGNABLE_SCHEMA.

    datatype:
      String chosen from SUPPORTED_ASN1_METADATA_MODULES.
      Specifies the type of data provided in der_data, whether a Time
      Attestation, ECU Manifest, or Vehicle Manifest. This is used to determine
      the module to use for the conversion.

    resign
      ("re-sign"). Normally False, resulting in the signatures in
      signed_metadata being formatted as ASN.1 and encoded as DER, but otherwise
      preserved (for example, they may still be signatures over JSON - the
      signature values themselves are unchanged).
      If resign is instead True, any signatures provided are
      discarded, and a new signature is generated. This new signature will be
      over the DER encoding of the data provided in signed_metadata['signed'].
      In other words, 'signed' will first be converted into ASN.1 and then
      encoded as DER, and a signature will be made using the given private_key,
      over that DER encoding.
      If the given signatures are already over DER encoding before reaching
      this point (as may happen in the current design), then you will not
      need this to be True.
      NOTE that if given a vehicle manifest and told to re-sign, this function
      will only re-sign the vehicle manifest itself - it will not try to re-sign
      every ECU Manifest contained in it. (Those would presumably be signed
      by other keys.)

    private_key
      This should be left out (None) unless resign is True, in which case
      private_key must conform to tuf.formats.ANYKEY_SCHEMA, containing a
      private key, specifically. It will be used to re-sign the metadata
      provided in signed_metadata['signed'].
      Such a key can be imported, for example, through the
      tuf.repository_tool.import_*_private_key() functions.

    only_signed
      Default False. If this is set to True, instead of returning the DER
      encoding of the full {'signed': {"abc..."}, 'signatures': [{"xyz..."}]}
      object, the DER encoding of only the 'signed' entry will be returned
      {"abc..."}.

  <Returns>
    By default (only_signed=False, resign=False), the returned value is the DER
    encoding of the full signed_metadata dictionary.

    If only_signed is True, the returned value is the DER encoding of only the
    'signed' entry in the signed_metadata dictionary.

    Otherwise, if resign is True, the returned value is the DER encoding of the
    full signed_metadata dictionary, but with the 'signatures' entry
    discarded and rebuilt anew with a new signature over the DER ENCODING of the
    'signed' entry in the signed_metadata dictionary.

  """
  # Make sure that if and only if the re-sign ('resign') parameter is True, a
  # private_key has been provided.
  tuf.formats.BOOLEAN_SCHEMA.check_match(resign)
  if resign != (private_key is not None):
    raise uptane.Error('Inconsistent arguments: a private key should be '
        'provided to convert_signed_json_to_signed_der if and only if the '
        'resign argument is True.')

  if only_signed and resign:
    raise uptane.Error('Inconsistent arguments: request to re-sign metadata '
        'in a new encoding and then throw those same new signatures away.')


  if private_key is not None:
    tuf.formats.ANYKEY_SCHEMA.check_match(private_key)
    # TODO: Note that this does not confirm that it is specifically a private key.
    # Consider checking that. (Best way is to have an additional SCHEMA in
    # tuf.formats and use that.)

  tuf.formats.SIGNABLE_SCHEMA.check_match(signed_metadata)
  uptane.formats.ANY_SIGNABLE_UPTANE_METADATA_SCHEMA.check_match(
      signed_metadata)

  json_signed = signed_metadata['signed']

  # # Force lowercase for metadata type because some TUF versions have been
  # # inconsistent in the casing of metadata types ('targets' vs 'Targets').
  # metadata_type = json_signed['_type'].lower()

  # Ensure that the type is one of the supported metadata types, for which
  # a module exists that translates it to and from an ASN.1 format.
  ensure_valid_metadata_type_for_asn1(datatype)

  # Handle for the corresponding module.
  relevant_asn_module = SUPPORTED_ASN1_METADATA_MODULES[datatype]

  asn_signed = relevant_asn_module.get_asn_signed(json_signed)

  if only_signed:
    # If the caller doesn't want any signatures included in the returned
    # DER object, then we need go no further and may encode what we already
    # have, which is the 'signed' component, the core metadata itself.
    der_signed = p_der_encoder.encode(asn_signed)
    return der_signed

  # Otherwise, we're to produce the full signable object (signed + signatures).
  # Either we will be retaining existing signatures or re-signing.


  if resign:

    # Encode the ASN.1 as DER first using pyasn1.
    # TODO: Determine if there are any other error types to add to the except
    # clause below to cover whatever errors we expect pyasn1 to raise when
    # trying to encode data. That error class covers ValueConstraintError and
    # SubstrateUnderrunError, but I'm not sure if pyasn1 wouldn't raise other
    # errors....
    try:
      der_signed = p_der_encoder.encode(asn_signed)
    except pyasn1.error.PyAsn1Error as e:
      raise uptane.FailedToEncodeASN1DER('Unable to encode the provided '
          'der_data as datatype ' + repr(datatype) + '. The pyasn1-raised '
          'error follows: ' + repr(e))


    # This hashing is redundant and temporary. Eventually, the hash will
    # consistently be performed in securesystemslib/keys.py in the
    # create_signature() function, so we shouldn't be taking a hash here.
    # For the time being, I do this so that it always uses a hash even for ed25519
    # and also so that the canonicalization that is currently called by
    # create_signature() doesn't choke on the DER I want to sign.
    hash_of_der = hashlib.sha256(der_signed).digest()

    # Now sign the metadata. (This signs a cryptographic hash of the metadata.)
    # The returned value is a basic Python dict writable into JSON.
    # This is a signature over the hash of the DER encoding.
    # Tell keys.create_signature that the data we're providing is not JSON so
    # that it doesn't try to canonicalize it (and wrap the hash in double
    # quotes).
    pydict_signatures = [tuf.keys.create_signature(private_key, hash_of_der)]

  else:
    pydict_signatures = signed_metadata['signatures']

  asn_signatures_list = convert_signatures_to_asn(pydict_signatures)


  # Now construct an ASN.1 representation of the signed/signatures-encapsulated
  # metadata, populating it.
  if datatype == DATATYPE_TIME_ATTESTATION:
    metadata = asn1_spec.TokensAndTimestampSignable()
  elif datatype == DATATYPE_ECU_MANIFEST:
    metadata = asn1_spec.ECUVersionManifest()
  elif datatype == DATATYPE_VEHICLE_MANIFEST:
    metadata = asn1_spec.VehicleVersionManifest()
  metadata['signed'] = asn_signed #considering using der_signed instead - requires changes
  metadata['signatures'] = asn_signatures_list # TODO: Support multiple sigs, or integrate with TUF.
  metadata['numberOfSignatures'] = len(asn_signatures_list)

  # Encode our new (py)ASN.1 object as DER (Distinguished Encoding Rules).
  return p_der_encoder.encode(metadata)