Пример #1
0
    def _get_root_secret(self, conf):
        """
        This keymaster requires its ``encryption_root_secret`` option to be
        set. This must be set before first use to a value that is a base64
        encoding of at least 32 bytes. The encryption root secret is stored
        in either proxy-server.conf, or in an external file referenced from
        proxy-server.conf using ``keymaster_config_path``.

        :param conf: the keymaster config section from proxy-server.conf
        :type conf: dict

        :return: the encryption root secret binary bytes
        :rtype: bytearray
        """
        if self.keymaster_config_path:
            keymaster_opts = ['encryption_root_secret']
            if any(opt in conf for opt in keymaster_opts):
                raise ValueError('keymaster_config_path is set, but there '
                                 'are other config options specified: %s' %
                                 ", ".join(list(
                                     set(keymaster_opts).intersection(conf))))
            conf = readconf(self.keymaster_config_path, 'keymaster')
        b64_root_secret = conf.get('encryption_root_secret')
        try:
            binary_root_secret = strict_b64decode(b64_root_secret,
                                                  allow_line_breaks=True)
            if len(binary_root_secret) < 32:
                raise ValueError
            return binary_root_secret
        except ValueError:
            raise ValueError(
                'encryption_root_secret option in %s must be a base64 '
                'encoding of at least 32 raw bytes' % (
                    self.keymaster_config_path or 'proxy-server.conf'))
Пример #2
0
 def _decode_root_secret(self, b64_root_secret):
     binary_root_secret = strict_b64decode(b64_root_secret,
                                           allow_line_breaks=True)
     if len(binary_root_secret) < 32:
         raise ValueError
     return binary_root_secret
Пример #3
0
    def __call__(self, env, start_response):
        """
        Main hook into the WSGI paste.deploy filter/app pipeline.

        :param env: The WSGI environment dict.
        :param start_response: The WSGI start_response hook.
        :returns: Response as per WSGI.
        """
        if env['REQUEST_METHOD'] == 'OPTIONS':
            return self.app(env, start_response)
        info = self._get_temp_url_info(env)
        temp_url_sig, temp_url_expires, temp_url_prefix, filename,\
            inline_disposition = info
        if temp_url_sig is None and temp_url_expires is None:
            return self.app(env, start_response)
        if not temp_url_sig or not temp_url_expires:
            return self._invalid(env, start_response)

        if ':' in temp_url_sig:
            hash_algorithm, temp_url_sig = temp_url_sig.split(':', 1)
            if ('-' in temp_url_sig or '_' in temp_url_sig) and not (
                    '+' in temp_url_sig or '/' in temp_url_sig):
                temp_url_sig = temp_url_sig.replace('-', '+').replace('_', '/')
            try:
                temp_url_sig = binascii.hexlify(strict_b64decode(
                    temp_url_sig + '=='))
            except ValueError:
                return self._invalid(env, start_response)
        elif len(temp_url_sig) == 40:
            hash_algorithm = 'sha1'
        elif len(temp_url_sig) == 64:
            hash_algorithm = 'sha256'
        else:
            return self._invalid(env, start_response)
        if hash_algorithm not in self.allowed_digests:
            return self._invalid(env, start_response)

        account, container, obj = self._get_path_parts(env)
        if not account:
            return self._invalid(env, start_response)
        keys = self._get_keys(env)
        if not keys:
            return self._invalid(env, start_response)
        if temp_url_prefix is None:
            path = '/v1/%s/%s/%s' % (account, container, obj)
        else:
            if not obj.startswith(temp_url_prefix):
                return self._invalid(env, start_response)
            path = 'prefix:/v1/%s/%s/%s' % (account, container,
                                            temp_url_prefix)
        if env['REQUEST_METHOD'] == 'HEAD':
            hmac_vals = [
                hmac for method in ('HEAD', 'GET', 'POST', 'PUT')
                for hmac in self._get_hmacs(
                    env, temp_url_expires, path, keys, hash_algorithm,
                    request_method=method)]
        else:
            hmac_vals = self._get_hmacs(
                env, temp_url_expires, path, keys, hash_algorithm)

        is_valid_hmac = False
        hmac_scope = None
        for hmac, scope in hmac_vals:
            # While it's true that we short-circuit, this doesn't affect the
            # timing-attack resistance since the only way this will
            # short-circuit is when a valid signature is passed in.
            if streq_const_time(temp_url_sig, hmac):
                is_valid_hmac = True
                hmac_scope = scope
                break
        if not is_valid_hmac:
            return self._invalid(env, start_response)
        # disallowed headers prevent accidentally allowing upload of a pointer
        # to data that the PUT tempurl would not otherwise allow access for.
        # It should be safe to provide a GET tempurl for data that an
        # untrusted client just uploaded with a PUT tempurl.
        resp = self._clean_disallowed_headers(env, start_response)
        if resp:
            return resp
        self._clean_incoming_headers(env)

        if hmac_scope == ACCOUNT_SCOPE:
            env['swift.authorize'] = authorize_same_account(account)
        else:
            env['swift.authorize'] = authorize_same_container(account,
                                                              container)
        env['swift.authorize_override'] = True
        env['REMOTE_USER'] = '******'
        qs = {'temp_url_sig': temp_url_sig,
              'temp_url_expires': temp_url_expires}
        if temp_url_prefix is not None:
            qs['temp_url_prefix'] = temp_url_prefix
        if filename:
            qs['filename'] = filename
        env['QUERY_STRING'] = urlencode(qs)

        def _start_response(status, headers, exc_info=None):
            headers = self._clean_outgoing_headers(headers)
            if env['REQUEST_METHOD'] in ('GET', 'HEAD') and status[0] == '2':
                # figure out the right value for content-disposition
                # 1) use the value from the query string
                # 2) use the value from the object metadata
                # 3) use the object name (default)
                out_headers = []
                existing_disposition = None
                for h, v in headers:
                    if h.lower() != 'content-disposition':
                        out_headers.append((h, v))
                    else:
                        existing_disposition = v
                if inline_disposition:
                    if filename:
                        disposition_value = disposition_format('inline',
                                                               filename)
                    else:
                        disposition_value = 'inline'
                elif filename:
                    disposition_value = disposition_format('attachment',
                                                           filename)
                elif existing_disposition:
                    disposition_value = existing_disposition
                else:
                    name = basename(env['PATH_INFO'].rstrip('/'))
                    disposition_value = disposition_format('attachment',
                                                           name)
                # this is probably just paranoia, I couldn't actually get a
                # newline into existing_disposition
                value = disposition_value.replace('\n', '%0A')
                out_headers.append(('Content-Disposition', value))

                # include Expires header for better cache-control
                out_headers.append(('Expires', strftime(
                    "%a, %d %b %Y %H:%M:%S GMT",
                    gmtime(temp_url_expires))))
                headers = out_headers
            return start_response(status, headers, exc_info)

        return self.app(env, _start_response)
Пример #4
0
    def __call__(self, env, start_response):
        """
        Main hook into the WSGI paste.deploy filter/app pipeline.

        :param env: The WSGI environment dict.
        :param start_response: The WSGI start_response hook.
        :returns: Response as per WSGI.
        """
        if env['REQUEST_METHOD'] == 'OPTIONS':
            return self.app(env, start_response)
        info = self._get_temp_url_info(env)
        temp_url_sig, temp_url_expires, temp_url_prefix, filename,\
            inline_disposition, temp_url_ip_range = info
        if temp_url_sig is None and temp_url_expires is None:
            return self.app(env, start_response)
        if not temp_url_sig or not temp_url_expires:
            return self._invalid(env, start_response)

        if ':' in temp_url_sig:
            hash_algorithm, temp_url_sig = temp_url_sig.split(':', 1)
            if ('-' in temp_url_sig or '_' in temp_url_sig
                ) and not ('+' in temp_url_sig or '/' in temp_url_sig):
                temp_url_sig = temp_url_sig.replace('-', '+').replace('_', '/')
            try:
                temp_url_sig = binascii.hexlify(
                    strict_b64decode(temp_url_sig + '=='))
                if not six.PY2:
                    temp_url_sig = temp_url_sig.decode('ascii')
            except ValueError:
                return self._invalid(env, start_response)
        elif len(temp_url_sig) == 40:
            hash_algorithm = 'sha1'
        elif len(temp_url_sig) == 64:
            hash_algorithm = 'sha256'
        else:
            return self._invalid(env, start_response)
        if hash_algorithm not in self.allowed_digests:
            return self._invalid(env, start_response)

        account, container, obj = self._get_path_parts(env)
        if not account:
            return self._invalid(env, start_response)

        if temp_url_ip_range:
            client_address = env.get('REMOTE_ADDR')
            if client_address is None:
                return self._invalid(env, start_response)
            try:
                allowed_ip_ranges = ip_network(six.u(temp_url_ip_range))
                if ip_address(six.u(client_address)) not in allowed_ip_ranges:
                    return self._invalid(env, start_response)
            except ValueError:
                return self._invalid(env, start_response)

        keys = self._get_keys(env)
        if not keys:
            return self._invalid(env, start_response)
        if temp_url_prefix is None:
            path = '/v1/%s/%s/%s' % (account, container, obj)
        else:
            if not obj.startswith(temp_url_prefix):
                return self._invalid(env, start_response)
            path = 'prefix:/v1/%s/%s/%s' % (account, container,
                                            temp_url_prefix)
        if env['REQUEST_METHOD'] == 'HEAD':
            hmac_vals = [
                hmac for method in ('HEAD', 'GET', 'POST', 'PUT')
                for hmac in self._get_hmacs(env,
                                            temp_url_expires,
                                            path,
                                            keys,
                                            hash_algorithm,
                                            request_method=method,
                                            ip_range=temp_url_ip_range)
            ]
        else:
            hmac_vals = self._get_hmacs(env,
                                        temp_url_expires,
                                        path,
                                        keys,
                                        hash_algorithm,
                                        ip_range=temp_url_ip_range)

        is_valid_hmac = False
        hmac_scope = None
        for hmac, scope in hmac_vals:
            # While it's true that we short-circuit, this doesn't affect the
            # timing-attack resistance since the only way this will
            # short-circuit is when a valid signature is passed in.
            if streq_const_time(temp_url_sig, hmac):
                is_valid_hmac = True
                hmac_scope = scope
                break
        if not is_valid_hmac:
            return self._invalid(env, start_response)
        # disallowed headers prevent accidentally allowing upload of a pointer
        # to data that the PUT tempurl would not otherwise allow access for.
        # It should be safe to provide a GET tempurl for data that an
        # untrusted client just uploaded with a PUT tempurl.
        resp = self._clean_disallowed_headers(env, start_response)
        if resp:
            return resp
        self._clean_incoming_headers(env)

        if hmac_scope == ACCOUNT_SCOPE:
            env['swift.authorize'] = authorize_same_account(account)
        else:
            env['swift.authorize'] = authorize_same_container(
                account, container)
        env['swift.authorize_override'] = True
        env['REMOTE_USER'] = '******'
        qs = {
            'temp_url_sig': temp_url_sig,
            'temp_url_expires': temp_url_expires
        }
        if temp_url_prefix is not None:
            qs['temp_url_prefix'] = temp_url_prefix
        if filename:
            qs['filename'] = filename
        env['QUERY_STRING'] = urlencode(qs)

        def _start_response(status, headers, exc_info=None):
            headers = self._clean_outgoing_headers(headers)
            if env['REQUEST_METHOD'] in ('GET', 'HEAD') and status[0] == '2':
                # figure out the right value for content-disposition
                # 1) use the value from the query string
                # 2) use the value from the object metadata
                # 3) use the object name (default)
                out_headers = []
                existing_disposition = None
                for h, v in headers:
                    if h.lower() != 'content-disposition':
                        out_headers.append((h, v))
                    else:
                        existing_disposition = v
                if inline_disposition:
                    if filename:
                        disposition_value = disposition_format(
                            'inline', filename)
                    else:
                        disposition_value = 'inline'
                elif filename:
                    disposition_value = disposition_format(
                        'attachment', filename)
                elif existing_disposition:
                    disposition_value = existing_disposition
                else:
                    name = basename(wsgi_to_str(env['PATH_INFO']).rstrip('/'))
                    disposition_value = disposition_format('attachment', name)
                # this is probably just paranoia, I couldn't actually get a
                # newline into existing_disposition
                value = disposition_value.replace('\n', '%0A')
                out_headers.append(('Content-Disposition', value))

                # include Expires header for better cache-control
                out_headers.append(('Expires',
                                    strftime("%a, %d %b %Y %H:%M:%S GMT",
                                             gmtime(temp_url_expires))))
                headers = out_headers
            return start_response(status, headers, exc_info)

        return self.app(env, _start_response)
Пример #5
0
def decode_secret(b64_secret):
    """Decode and check a base64 encoded secret key."""
    binary_secret = strict_b64decode(b64_secret, allow_line_breaks=True)
    if len(binary_secret) != Crypto.key_length:
        raise ValueError
    return binary_secret
Пример #6
0
 def _decode_root_secret(self, b64_root_secret):
     binary_root_secret = strict_b64decode(b64_root_secret,
                                           allow_line_breaks=True)
     if len(binary_root_secret) < 32:
         raise ValueError
     return binary_root_secret
Пример #7
0
def decode_secret(b64_secret):
    """Decode and check a base64 encoded secret key."""
    binary_secret = strict_b64decode(b64_secret, allow_line_breaks=True)
    if len(binary_secret) != Crypto.key_length:
        raise ValueError
    return binary_secret