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'))
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
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)
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)
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