def parse_base64(base64_string):
    """
  <Purpose>
    Parse a base64 encoding with whitespace and '=' signs omitted.
  
  <Arguments>
    base64_string:
      A string holding a base64 value.

  <Exceptions>
    ssl_crypto.FormatError, if 'base64_string' cannot be parsed due to
    an invalid base64 encoding.

  <Side Effects>
    None.

  <Returns>
    A byte string representing the parsed based64 encoding of
    'base64_string'.
  """

    if not isinstance(base64_string, six.string_types):
        message = 'Invalid argument: ' + repr(base64_string)
        raise ssl_crypto.FormatError(message)

    extra = len(base64_string) % 4
    if extra:
        padding = '=' * (4 - extra)
        base64_string = base64_string + padding

    try:
        return binascii.a2b_base64(base64_string.encode('utf-8'))

    except (TypeError, binascii.Error) as e:
        raise ssl_crypto.FormatError('Invalid base64 encoding: ' + str(e))
Exemple #2
0
    def check_match(self, object):
        if not isinstance(object, dict):
            message = 'Wanted a ' + repr(self._object_name) + '.'
            raise ssl_crypto.FormatError(message)

        # (key, schema) = (a, AnyString()) = (a=AnyString())
        for key, schema in self._required:
            # Check if 'object' has all the required dict keys.
            # If not one of the required keys, check if it is an Optional().
            try:
                item = object[key]
            except KeyError:
                # If not an Optional schema, raise an exception.
                if not isinstance(schema, Optional):
                    message = 'Missing key ' + repr(key) + ' in ' + repr(
                        self._object_name)
                    raise ssl_crypto.FormatError(message)
            # Check that 'object's schema matches Object()'s schema for this
            # particular 'key'.
            else:
                try:
                    schema.check_match(item)
                except ssl_crypto.FormatError as e:
                    raise ssl_crypto.FormatError(
                        str(e) + ' in ' + self._object_name + '.' + key)
Exemple #3
0
    def __init__(self,
                 sub_schemas,
                 optional_schemas=[],
                 allow_more=False,
                 struct_name='list'):
        """
    <Purpose> 
      Create a new Struct schema.

    <Arguments>
      sub_schemas: The sub-schemas recognized.
      optional_schemas: The optional list of schemas.
      allow_more: Specifies that an optional list of types is allowed.
      struct_name: A string identifier for the Struct object.
    """

        # Ensure each item of the list contains the expected object type.
        if not isinstance(sub_schemas, (list, tuple)):
            message = 'Expected Schema but got ' + repr(sub_schemas)
            raise ssl_crypto.FormatError(message)
        for schema in sub_schemas:
            if not isinstance(schema, Schema):
                raise ssl_crypto.FormatError('Expected Schema but got ' +
                                             repr(schema))

        self._sub_schemas = sub_schemas + optional_schemas
        self._min = len(sub_schemas)
        self._allow_more = allow_more
        self._struct_name = struct_name
Exemple #4
0
    def check_match(self, object):
        if not isinstance(object, six.binary_type):
            raise ssl_crypto.FormatError('Expected a byte but got ' +
                                         repr(object))

        if len(object) != self._bytes_length:
            raise ssl_crypto.FormatError('Expected a byte of length ' + \
                                  repr(self._bytes_length))
Exemple #5
0
    def check_match(self, object):
        if not isinstance(object, six.string_types):
            raise ssl_crypto.FormatError('Expected a string but got ' +
                                         repr(object))

        if len(object) != self._string_length:
            raise ssl_crypto.FormatError('Expected a string of length ' + \
                                  repr(self._string_length))
Exemple #6
0
    def __init__(self, alternatives):
        # Ensure each item of the list contains the expected object type.
        if not isinstance(alternatives, list):
            raise ssl_crypto.FormatError('Expected a list but got ' +
                                         repr(alternatives))
        for alternative in alternatives:
            if not isinstance(alternative, Schema):
                raise ssl_crypto.FormatError('List contains an invalid item ' +
                                             repr(alternative))

        self._alternatives = alternatives
Exemple #7
0
    def __init__(self, required_schemas):
        # Ensure each item of the list contains the expected object type.
        if not isinstance(required_schemas, list):
            raise ssl_crypto.FormatError('Expected a list but got' +
                                         repr(required_schemas))
        for schema in required_schemas:
            if not isinstance(schema, Schema):
                raise ssl_crypto.FormatError('List contains an invalid item ' +
                                             repr(schema))

        self._required_schemas = required_schemas[:]
Exemple #8
0
    def check_match(self, object):
        if isinstance(object,
                      bool) or not isinstance(object, six.integer_types):
            # We need to check for bool as a special case, since bool
            # is for historical reasons a subtype of int.
            raise ssl_crypto.FormatError('Got ' + repr(object) +
                                         ' instead of an integer.')

        elif not (self._lo <= object <= self._hi):
            int_range = '[' + repr(self._lo) + ', ' + repr(self._hi) + '].'
            raise ssl_crypto.FormatError(
                repr(object) + ' not in range ' + int_range)
def check_signable_object_format(object):
    """
  <Purpose>
    Ensure 'object' is properly formatted, conformant to
    'ssl_crypto.formats.SIGNABLE_SCHEMA'.  Return the signing role on success.
    Note: The 'signed' field of a 'SIGNABLE_SCHEMA' is checked against
    ssl_crypto.schema.Any().  The 'signed' field, however, should actually
    hold one of the supported role schemas (e.g., 'ROOT_SCHEMA',
    'TARGETS_SCHEMA').  The role schemas all differ in their format, so this
    function determines exactly which schema is listed in the 'signed'
    field.

  <Arguments>
    object:
     The object compare against 'SIGNABLE.SCHEMA'. 

  <Exceptions>
    ssl_crypto.FormatError, if 'object' does not have the correct format.

  <Side Effects>
    None.

  <Returns>
    A string representing the signing role (e.g., 'root', 'targets').
    The role string is returned with characters all lower case.
  """

    # Does 'object' have the correct type?
    # This check ensures 'object' conforms to
    # 'ssl_crypto.formats.SIGNABLE_SCHEMA'.
    SIGNABLE_SCHEMA.check_match(object)

    try:
        role_type = object['signed']['_type']

    except (KeyError, TypeError):
        raise ssl_crypto.FormatError('Untyped object')

    try:
        schema = SCHEMAS_BY_TYPE[role_type]

    except KeyError:
        raise ssl_crypto.FormatError('Unrecognized type ' + repr(role_type))

    # 'ssl_crypto.FormatError' raised if 'object' does not have a properly
    # formatted role schema.
    schema.check_match(object['signed'])

    return role_type.lower()
Exemple #10
0
def format_base64(data):
    """
  <Purpose>
    Return the base64 encoding of 'data' with whitespace
    and '=' signs omitted.

  <Arguments>
    data:
      Binary or buffer of data to convert.

  <Exceptions>
    ssl_crypto.FormatError, if the base64 encoding fails or the argument
    is invalid.

  <Side Effects>
    None.

  <Returns>
    A base64-encoded string.
  """

    try:
        return binascii.b2a_base64(data).decode('utf-8').rstrip('=\n ')

    except (TypeError, binascii.Error) as e:
        raise ssl_crypto.FormatError('Invalid base64 encoding: ' + str(e))
Exemple #11
0
def safe_download(url, required_length):
    """
  <Purpose>
    Given the 'url' and 'required_length' of the desired file, open a connection
    to 'url', download it, and return the contents of the file.  Also ensure
    the length of the downloaded file matches 'required_length' exactly.
    ssl_crypto.download.unsafe_download() may be called if an upper download limit is
    preferred.
 
    'ssl_crypto.util.TempFile', the file-like object returned, is used instead of
    regular tempfile object because of additional functionality provided, such
    as handling compressed metadata and automatically closing files after
    moving to final destination.
  
  <Arguments>
    url:
      A URL string that represents the location of the file.  The URI scheme
      component must be one of 'ssl_crypto.conf.SUPPORTED_URI_SCHEMES'.
  
    required_length:
      An integer value representing the length of the file.  This is an exact
      limit.

  <Side Effects>
    A 'ssl_crypto.util.TempFile' object is created on disk to store the contents of
    'url'.
 
  <Exceptions>
    ssl_crypto.DownloadLengthMismatchError, if there was a mismatch of observed vs
    expected lengths while downloading the file.
 
    ssl_crypto.FormatError, if any of the arguments are improperly formatted.

    Any other unforeseen runtime exception.
 
  <Returns>
    A 'ssl_crypto.util.TempFile' file-like object that points to the contents of 'url'.
  """

    # Do all of the arguments have the appropriate format?
    # Raise 'ssl_crypto.FormatError' if there is a mismatch.
    ssl_crypto.formats.URL_SCHEMA.check_match(url)
    ssl_crypto.formats.LENGTH_SCHEMA.check_match(required_length)

    # Ensure 'url' specifies one of the URI schemes in
    # 'ssl_crypto.conf.SUPPORTED_URI_SCHEMES'.  Be default, ['http', 'https'] is
    # supported.  If the URI scheme of 'url' is empty or "file", files on the
    # local system can be accessed.  Unexpected files may be accessed by
    # compromised metadata (unlikely to happen if targets.json metadata is signed
    # with offline keys).
    parsed_url = six.moves.urllib.parse.urlparse(url)

    if parsed_url.scheme not in ssl_crypto.conf.SUPPORTED_URI_SCHEMES:
        message = \
          repr(url) + ' specifies an unsupported URI scheme.  Supported ' + \
          ' URI Schemes: ' + repr(ssl_crypto.conf.SUPPORTED_URI_SCHEMES)
        raise ssl_crypto.FormatError(message)

    return _download_file(url, required_length, STRICT_REQUIRED_LENGTH=True)
Exemple #12
0
 def check_match(self, object):
     # Simply return as soon as we find a match.
     # Raise 'ssl_crypto.FormatError' if no matches are found.
     for alternative in self._alternatives:
         if alternative.matches(object):
             return
     raise ssl_crypto.FormatError(
         'Object did not match a recognized alternative.')
Exemple #13
0
    def check_match(self, object):
        if not isinstance(object, dict):
            raise ssl_crypto.FormatError('Expected a dict but got ' +
                                         repr(object))

        for key, value in six.iteritems(object):
            self._key_schema.check_match(key)
            self._value_schema.check_match(value)
Exemple #14
0
    def __init__(self, length):
        if isinstance(length,
                      bool) or not isinstance(length, six.integer_types):
            # We need to check for bool as a special case, since bool
            # is for historical reasons a subtype of int.
            raise ssl_crypto.FormatError('Got ' + repr(length) +
                                         ' instead of an integer.')

        self._bytes_length = length
Exemple #15
0
    def __init__(self, key_schema, value_schema):
        """
    <Purpose> 
      Create a new DictOf schema.

    <Arguments>
      key_schema:  The dictionary's key.
      value_schema: The dictionary's value.
    """

        if not isinstance(key_schema, Schema):
            raise ssl_crypto.FormatError('Expected Schema but got ' +
                                         repr(key_schema))

        if not isinstance(value_schema, Schema):
            raise ssl_crypto.FormatError('Expected Schema but got ' +
                                         repr(value_schema))

        self._key_schema = key_schema
        self._value_schema = value_schema
Exemple #16
0
    def check_match(self, object):
        if not isinstance(object, (list, tuple)):
            message = 'Expected ' + repr(
                self._list_name) + ' but got ' + repr(object)
            raise ssl_crypto.FormatError(message)

        # Check if all the items in the 'object' list
        # match 'schema'.
        for item in object:
            try:
                self._schema.check_match(item)
            except ssl_crypto.FormatError as e:
                raise ssl_crypto.FormatError(
                    str(e) + ' in ' + repr(self._list_name))

        # Raise exception if the number of items in the list is
        # not within the expected range.
        if not (self._min_count <= len(object) <= self._max_count):
            raise ssl_crypto.FormatError('Length of ' + repr(self._list_name) +
                                         ' out of range')
Exemple #17
0
    def check_match(self, object):
        if not isinstance(object, (list, tuple)):
            raise ssl_crypto.FormatError('Expected ' +
                                         repr(self._struct_name) + '; got ' +
                                         repr(object))
        elif len(object) < self._min:
            raise ssl_crypto.FormatError('Too few fields in ' +
                                         self._struct_name)
        elif len(object) > len(self._sub_schemas) and not self._allow_more:
            raise ssl_crypto.FormatError('Too many fields in ' +
                                         self._struct_name)

        # Iterate through the items of 'object', checking against each schema
        # in the list of schemas allowed (i.e., the sub-schemas and also
        # any optional schemas.  The lenth of 'object' must be less than
        # the length of the required schemas + the optional schemas.  However,
        # 'object' is allowed to be only as large as the length of the required
        # schemas.  In the while loop below, we check against these two cases.
        index = 0
        while index < len(object) and index < len(self._sub_schemas):
            item = object[index]
            schema = self._sub_schemas[index]
            schema.check_match(item)
            index = index + 1
Exemple #18
0
    def __init__(self,
                 pattern=None,
                 modifiers=0,
                 re_object=None,
                 re_name=None):
        """
    <Purpose> 
      Create a new regular expression schema.

    <Arguments>
      pattern:  The pattern to match, or None if re_object is provided.
      modifiers:  Flags to use when compiling the pattern.
      re_object:  A compiled regular expression object.
      re_name: Identifier for the regular expression object.
    """

        if not isinstance(pattern, six.string_types):
            if pattern is not None:
                raise ssl_crypto.FormatError(
                    repr(pattern) + ' is not a string.')

        if re_object is None:
            if pattern is None:
                error = 'Cannot compare against an unset regular expression'
                raise ssl_crypto.FormatError(error)
            if not pattern.endswith('$'):
                pattern += '$'
            re_object = re.compile(pattern, modifiers)
        self._re_object = re_object

        if re_name is None:
            if pattern is not None:
                re_name = 'pattern /' + pattern + '/'
            else:
                re_name = 'pattern'
        self._re_name = re_name
Exemple #19
0
    def __init__(self, object_name='object', **required):
        """
    <Purpose> 
      Create a new Object schema.

    <Arguments>
      object_name: A string identifier for the object argument.
      
      A variable number of keyword arguments is accepted.
    """

        # Ensure valid arguments.
        for key, schema in six.iteritems(required):
            if not isinstance(schema, Schema):
                raise ssl_crypto.FormatError('Expected Schema but got ' +
                                             repr(schema))

        self._object_name = object_name
        self._required = list(required.items())
Exemple #20
0
def get_role_class(expected_rolename):
    """
  <Purpose>
    Return the role class corresponding to
    'expected_rolename'.  The role name returned
    by expected_meta_rolename() should be the name
    passed as an argument to this function.  If
    'expected_rolename' is 'Root', the class
    RootFile is returned.

  <Arguments>
    expected_rolename:
      The role name used to determine which role class
      to return.

  <Exceptions>
    ssl_crypto.FormatError, if 'expected_rolename' is not a
    supported role.

  <Side Effects>
    None.

  <Returns>
    The class corresponding to 'expected_rolename'.
    E.g., 'Snapshot' as an argument to this function causes
    SnapshotFile' to be returned. 
  """

    # Does 'expected_rolename' have the correct type?
    # This check ensures 'expected_rolename' conforms to
    # 'ssl_crypto.formats.NAME_SCHEMA'.
    # Raise 'ssl_crypto.FormatError' if there is a mismatch.
    NAME_SCHEMA.check_match(expected_rolename)

    try:
        role_class = ROLE_CLASSES_BY_TYPE[expected_rolename]

    except KeyError:
        raise ssl_crypto.FormatError(
            repr(expected_rolename) + ' not supported.')

    else:
        return role_class
Exemple #21
0
def _encode_canonical(object, output_function):
    # Helper for encode_canonical.  Older versions of json.encoder don't
    # even let us replace the separators.

    if isinstance(object, six.string_types):
        output_function(_canonical_string_encoder(object))
    elif object is True:
        output_function("true")
    elif object is False:
        output_function("false")
    elif object is None:
        output_function("null")
    elif isinstance(object, six.integer_types):
        output_function(str(object))
    elif isinstance(object, (tuple, list)):
        output_function("[")
        if len(object):
            for item in object[:-1]:
                _encode_canonical(item, output_function)
                output_function(",")
            _encode_canonical(object[-1], output_function)
        output_function("]")
    elif isinstance(object, dict):
        output_function("{")
        if len(object):
            items = sorted(six.iteritems(object))
            for key, value in items[:-1]:
                output_function(_canonical_string_encoder(key))
                output_function(":")
                _encode_canonical(value, output_function)
                output_function(",")
            key, value = items[-1]
            output_function(_canonical_string_encoder(key))
            output_function(":")
            _encode_canonical(value, output_function)
        output_function("}")
    else:
        raise ssl_crypto.FormatError('I cannot encode ' + repr(object))
Exemple #22
0
    def __init__(self,
                 schema,
                 min_count=0,
                 max_count=sys.maxsize,
                 list_name='list'):
        """
    <Purpose> 
      Create a new ListOf schema.

    <Arguments>
      schema:  The pattern to match.
      min_count: The minimum number of sub-schema in 'schema'.
      max_count: The maximum number of sub-schema in 'schema'.
      list_name: A string identifier for the ListOf object.
    """

        if not isinstance(schema, Schema):
            message = 'Expected Schema type but got ' + repr(schema)
            raise ssl_crypto.FormatError(message)

        self._schema = schema
        self._min_count = min_count
        self._max_count = max_count
        self._list_name = list_name
Exemple #23
0
def datetime_to_unix_timestamp(datetime_object):
    """
  <Purpose>
    Convert 'datetime_object' (in datetime.datetime()) format) to a Unix/POSIX
    timestamp.  For example, Python's time.time() returns a Unix timestamp, and
    includes the number of microseconds.  'datetime_object' is converted to UTC.

    >>> datetime_object = datetime.datetime(1985, 10, 26, 1, 22)
    >>> timestamp = datetime_to_unix_timestamp(datetime_object)
    >>> timestamp 
    499137720

  <Arguments>
    datetime_object:
      The datetime.datetime() object to convert to a Unix timestamp.

  <Exceptions>
    ssl_crypto.FormatError, if 'datetime_object' is not a datetime.datetime() object.

  <Side Effects>
    None.

  <Returns>
    A unix (posix) timestamp (e.g., 499137660).
  """

    # Is 'datetime_object' a datetime.datetime() object?
    # Raise 'ssl_crypto.FormatError' if not.
    if not isinstance(datetime_object, datetime.datetime):
        message = repr(
            datetime_object) + ' is not a datetime.datetime() object.'
        raise ssl_crypto.FormatError(message)

    unix_timestamp = calendar.timegm(datetime_object.timetuple())

    return unix_timestamp
Exemple #24
0
def encrypt_key(key_object, password):
    """
  <Purpose>
    Return a string containing 'key_object' in encrypted form. Encrypted
    strings may be safely saved to a file.  The corresponding decrypt_key()
    function can be applied to the encrypted string to restore the original key
    object.  'key_object' is a TUF key (e.g., RSAKEY_SCHEMA,
    ED25519KEY_SCHEMA).  This function calls the pyca/cryptography library to
    perform the encryption and derive a suitable encryption key.
    
    Whereas an encrypted PEM file uses the Triple Data Encryption Algorithm
    (3DES), the Cipher-block chaining (CBC) mode of operation, and the Password
    Based Key Derivation Function 1 (PBKF1) + MD5 to strengthen 'password',
    encrypted TUF keys use AES-256-CTR-Mode and passwords strengthened with
    PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in
    'ssl_crypto.conf.PBKDF2_ITERATIONS' by the user).

    http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
    http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29
    https://en.wikipedia.org/wiki/PBKDF2

    >>> ed25519_key = {'keytype': 'ed25519', \
                       'keyid': \
          'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \
                       'keyval': {'public': \
          '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \
                                  'private': \
          '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
    >>> passphrase = 'secret'
    >>> encrypted_key = encrypt_key(ed25519_key, passphrase)
    >>> ssl_crypto.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8'))
    True

  <Arguments>
    key_object:
      The TUF key object that should contain the private portion of the ED25519
      key.

    password:
      The password, or passphrase, to encrypt the private part of the RSA
      key.  'password' is not used directly as the encryption key, a stronger
      encryption key is derived from it. 

  <Exceptions>
    ssl_crypto.FormatError, if any of the arguments are improperly formatted or 
    'key_object' does not contain the private portion of the key.

    ssl_crypto.CryptoError, if an ED25519 key in encrypted TUF format cannot be
    created.

  <Side Effects>
    pyca/Cryptography cryptographic operations called to perform the actual
    encryption of 'key_object'.  'password' used to derive a suitable
    encryption key.

  <Returns>
    An encrypted string in 'ssl_crypto.formats.ENCRYPTEDKEY_SCHEMA' format.
  """

    # Do the arguments have the correct format?
    # Ensure the arguments have the appropriate number of objects and object
    # types, and that all dict keys are properly named.
    # Raise 'ssl_crypto.FormatError' if the check fails.
    ssl_crypto.formats.ANYKEY_SCHEMA.check_match(key_object)

    # Does 'password' have the correct format?
    ssl_crypto.formats.PASSWORD_SCHEMA.check_match(password)

    # Ensure the private portion of the key is included in 'key_object'.
    if not key_object['keyval']['private']:
        raise ssl_crypto.FormatError(
            'Key object does not contain a private part.')

    # Derive a key (i.e., an appropriate encryption key and not the
    # user's password) from the given 'password'.  Strengthen 'password' with
    # PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in
    # 'ssl_crypto.conf.PBKDF2_ITERATIONS' by the user).
    salt, iterations, derived_key = _generate_derived_key(password)

    # Store the derived key info in a dictionary, the object expected
    # by the non-public _encrypt() routine.
    derived_key_information = {
        'salt': salt,
        'iterations': iterations,
        'derived_key': derived_key
    }

    # Convert the key object to json string format and encrypt it with the
    # derived key.
    encrypted_key = _encrypt(json.dumps(key_object), derived_key_information)

    return encrypted_key
Exemple #25
0
def ensure_all_targets_allowed(rolename, list_of_targets, parent_delegations):
    """
  <Purpose>
    Ensure that the list of targets specified by 'rolename' are allowed; this is
    determined by inspecting the 'delegations' field of the parent role
    of 'rolename'.  If a target specified by 'rolename' is not found in the 
    delegations field of 'metadata_object_of_parent', raise an exception.  The
    top-level role 'targets' is allowed to list any target file, so this
    function does not raise an exception if 'rolename' is 'targets'.
 
    Targets allowed are either exlicitly listed under the 'paths' field, or
    implicitly exist under a subdirectory of a parent directory listed
    under 'paths'.  A parent role may delegate trust to all files under a 
    particular directory, including files in subdirectories, by simply
    listing the directory (e.g., '/packages/source/Django/', the equivalent
    of '/packages/source/Django/*').  Targets listed in hashed bins are
    also validated (i.e., its calculated path hash prefix must be delegated
    by the parent role).

    TODO: Should the TUF spec restrict the repository to one particular
    algorithm when calcutating path hash prefixes (currently restricted to 
    SHA256)?  Should we allow the repository to specify in the role dictionary
    the algorithm used for these generated hashed paths?

  <Arguments>
    rolename:
      The name of the role whose targets must be verified. This is a
      role name and should not end in '.json'.  Examples: 'root', 'targets',
      'targets/linux/x86'.

    list_of_targets:
      The targets of 'rolename', as listed in targets field of the 'rolename'
      metadata.  'list_of_targets' are target paths relative to the targets
      directory of the repository.  The delegations of the parent role are
      checked to verify that the targets of 'list_of_targets' are valid.
    
    parent_delegations:
      The parent delegations of 'rolename'.  The metadata object stores
      the allowed paths and path hash prefixes of child delegations in its 
      'delegations' attribute.

  <Exceptions>
    ssl_crypto.FormatError:
      If any of the arguments are improperly formatted.

    ssl_crypto.ForbiddenTargetError:
      If the targets of 'metadata_role' are not allowed according to
      the parent's metadata file.  The 'paths' and 'path_hash_prefixes'
      attributes are verified.

    ssl_crypto.RepositoryError:
      If the parent of 'rolename' has not made a delegation to 'rolename'.

  <Side Effects>
    None.

  <Returns>
    None.
  """

    # Do the arguments have the correct format?
    # Ensure the arguments have the appropriate number of objects and object
    # types, and that all dict keys are properly named.
    # Raise 'ssl_crypto.FormatError' if any are improperly formatted.
    ssl_crypto.formats.ROLENAME_SCHEMA.check_match(rolename)
    ssl_crypto.formats.RELPATHS_SCHEMA.check_match(list_of_targets)
    ssl_crypto.formats.DELEGATIONS_SCHEMA.check_match(parent_delegations)

    # Return if 'rolename' is 'targets'.  'targets' is not a delegated role.  Any
    # target file listed in 'targets' is allowed.
    if rolename == 'targets':
        return

    # The allowed targets of delegated roles are stored in the parent's metadata
    # file.  Iterate 'list_of_targets' and confirm they are trusted, or their root
    # parent directory exists in the role delegated paths, or path hash prefixes,
    # of the parent role.  First, locate 'rolename' in the 'roles' attribute of
    # 'parent_delegations'.
    roles = parent_delegations['roles']
    role_index = find_delegated_role(roles, rolename)

    # Ensure the delegated role exists prior to extracting trusted paths from
    # the parent's 'paths', or trusted path hash prefixes from the parent's
    # 'path_hash_prefixes'.
    if role_index is not None:
        role = roles[role_index]
        allowed_child_paths = role.get('paths')
        allowed_child_path_hash_prefixes = role.get('path_hash_prefixes')
        actual_child_targets = list_of_targets

        if allowed_child_path_hash_prefixes is not None:
            consistent = paths_are_consistent_with_hash_prefixes

            # 'actual_child_tarets' (i.e., 'list_of_targets') should have lenth
            # greater than zero due to the ssl_crypto.format check above.
            if not consistent(actual_child_targets,
                              allowed_child_path_hash_prefixes):
                message =  repr(rolename) + ' specifies a target that does not' + \
                  ' have a path hash prefix listed in its parent role.'
                raise ssl_crypto.ForbiddenTargetError(message)

        elif allowed_child_paths is not None:
            # Check that each delegated target is either explicitly listed or a parent
            # directory is found under role['paths'], otherwise raise an exception.
            # If the parent role explicitly lists target file paths in 'paths',
            # this loop will run in O(n^2), the worst-case.  The repository
            # maintainer will likely delegate entire directories, and opt for
            # explicit file paths if the targets in a directory are delegated to
            # different roles/developers.
            for child_target in actual_child_targets:
                for allowed_child_path in allowed_child_paths:
                    prefix = os.path.commonprefix(
                        [child_target, allowed_child_path])
                    if prefix == allowed_child_path:
                        break

                else:
                    raise ssl_crypto.ForbiddenTargetError('Role '+repr(rolename)+' specifies'+\
                                                   ' target '+repr(child_target)+','+\
                                                   ' which is not an allowed path'+\
                                                   ' according to the delegations set'+\
                                                   ' by its parent role.')

        else:
            # 'role' should have been validated when it was downloaded.
            # The 'paths' or 'path_hash_prefixes' attributes should not be missing,
            # so raise an error in case this clause is reached.
            raise ssl_crypto.FormatError(repr(role) + ' did not contain one of ' +\
                                  'the required fields ("paths" or ' +\
                                  '"path_hash_prefixes").')

    # Raise an exception if the parent has not delegated to the specified
    # 'rolename' child role.
    else:
        raise ssl_crypto.RepositoryError('The parent role has not delegated to '+\
                                  repr(rolename)+'.')
Exemple #26
0
 def __init__(self, schema):
     if not isinstance(schema, Schema):
         raise ssl_crypto.FormatError('Expected Schema, but got ' +
                                      repr(schema))
     self._schema = schema
Exemple #27
0
def encode_canonical(object, output_function=None):
    """
  <Purpose>
    Encode 'object' in canonical JSON form, as specified at
    http://wiki.laptop.org/go/Canonical_JSON .  It's a restricted
    dialect of JSON in which keys are always lexically sorted,
    there is no whitespace, floats aren't allowed, and only quote
    and backslash get escaped.  The result is encoded in UTF-8,
    and the resulting bits are passed to output_function (if provided),
    or joined into a string and returned.

    Note: This function should be called prior to computing the hash or
    signature of a JSON object in TUF.  For example, generating a signature
    of a signing role object such as 'ROOT_SCHEMA' is required to ensure
    repeatable hashes are generated across different json module versions
    and platforms.  Code elsewhere is free to dump JSON objects in any format
    they wish (e.g., utilizing indentation and single quotes around object
    keys).  These objects are only required to be in "canonical JSON" format
    when their hashes or signatures are needed.

    >>> encode_canonical("")
    '""'
    >>> encode_canonical([1, 2, 3])
    '[1,2,3]'
    >>> encode_canonical([])
    '[]'
    >>> encode_canonical({"A": [99]})
    '{"A":[99]}'
    >>> encode_canonical({"x" : 3, "y" : 2})
    '{"x":3,"y":2}'
  
  <Arguments>
    object:
      The object to be encoded.

    output_function:
      The result will be passed as arguments to 'output_function'
      (e.g., output_function('result')).

  <Exceptions>
    ssl_crypto.FormatError, if 'object' cannot be encoded or 'output_function'
    is not callable.

  <Side Effects>
    The results are fed to 'output_function()' if 'output_function' is set.  

  <Returns>
    A string representing the 'object' encoded in canonical JSON form.
  """

    result = None
    # If 'output_function' is unset, treat it as
    # appending to a list.
    if output_function is None:
        result = []
        output_function = result.append

    try:
        _encode_canonical(object, output_function)

    except (TypeError, ssl_crypto.FormatError) as e:
        message = 'Could not encode ' + repr(object) + ': ' + str(e)
        raise ssl_crypto.FormatError(message)

    # Return the encoded 'object' as a string.
    # Note: Implies 'output_function' is None,
    # otherwise results are sent to 'output_function'.
    if result is not None:
        return ''.join(result)
Exemple #28
0
 def check_match(self, object):
     if not isinstance(object, bool):
         raise ssl_crypto.FormatError('Got ' + repr(object) +
                                      ' instead of a boolean.')
Exemple #29
0
def make_role_metadata(keyids,
                       threshold,
                       name=None,
                       paths=None,
                       path_hash_prefixes=None):
    """
  <Purpose>
    Create a dictionary conforming to 'ssl_crypto.formats.ROLE_SCHEMA',
    representing the role with 'keyids', 'threshold', and 'paths'
    as field values.  'paths' is optional (i.e., used only by the
    'Target' role).

  <Arguments>
    keyids: a list of key ids.

    threshold:
      An integer denoting the number of required keys
      for the signing role.

    name:
      A string that is the name of this role.

    paths:
      The 'Target' role stores the paths of target files
      in its metadata file.  'paths' is a list of
      file paths.

    path_hash_prefixes:
      The 'Target' role stores the paths of target files in its metadata file.
      'path_hash_prefixes' is a succint way to describe a set of paths to
      target files.

  <Exceptions>
    ssl_crypto.FormatError, if the returned role meta is
    formatted incorrectly.

  <Side Effects>
    If any of the arguments do not have a proper format, a 
    ssl_crypto.formats exception is raised when the 'ROLE_SCHEMA' dict
    is created.

  <Returns>
    A properly formatted role meta dict, conforming to
    'ROLE_SCHEMA'.
  """

    role_meta = {}
    role_meta['keyids'] = keyids
    role_meta['threshold'] = threshold

    if name is not None:
        role_meta['name'] = name

    # According to the specification, the 'paths' and 'path_hash_prefixes' must
    # be mutually exclusive. However, at the time of writing we do not always
    # ensure that this is the case with the schema checks (see #83). Therefore,
    # we must do it for ourselves.

    if paths is not None and path_hash_prefixes is not None:
        raise \
          ssl_crypto.FormatError('Both "paths" and "path_hash_prefixes" are specified.')

    if path_hash_prefixes is not None:
        role_meta['path_hash_prefixes'] = path_hash_prefixes
    elif paths is not None:
        role_meta['paths'] = paths

    # Does 'role_meta' have the correct type?
    # This check ensures 'role_meta' conforms to
    # ssl_crypto.formats.ROLE_SCHEMA.
    ROLE_SCHEMA.check_match(role_meta)

    return role_meta
Exemple #30
0
 def check_match(self, object):
     if not isinstance(
             object, six.string_types) or not self._re_object.match(object):
         raise ssl_crypto.FormatError(
             repr(object) + ' did not match ' + repr(self._re_name))