function sign_request($token = '', $timestamp = 0, $nonce = '', $body_hash = '', $method = '', $url = '', $body = null, $verify_body_hash = true) { if (!$this->secret) { return new Jetpack_Error('invalid_secret', 'Invalid secret'); } if (!$this->token) { return new Jetpack_Error('invalid_token', 'Invalid token'); } list($token) = explode('.', $token); if (0 !== strpos($token, "{$this->token}:")) { return new Jetpack_Error('token_mismatch', 'Incorrect token'); } $required_parameters = array('token', 'timestamp', 'nonce', 'method', 'url'); if (!is_null($body)) { $required_parameters[] = 'body_hash'; if (!is_string($body)) { return new Jetpack_Error('invalid_body', 'Body is malformed.'); } } foreach ($required_parameters as $required) { if (!is_scalar(${$required})) { return new Jetpack_Error('invalid_signature', sprintf('The required "%s" parameter is malformed.', str_replace('_', '-', $required))); } if (!strlen(${$required})) { return new Jetpack_Error('invalid_signature', sprintf('The required "%s" parameter is missing.', str_replace('_', '-', $required))); } } if (is_null($body)) { if ($body_hash) { return new Jetpack_Error('invalid_body_hash', 'The body hash does not match.'); } } else { if ($verify_body_hash && jetpack_sha1_base64($body) !== $body_hash) { return new Jetpack_Error('invalid_body_hash', 'The body hash does not match.'); } } $parsed = parse_url($url); if (!isset($parsed['host'])) { return new Jetpack_Error('invalid_signature', sprintf('The required "%s" parameter is malformed.', 'url')); } if (!empty($parsed['port'])) { $port = $parsed['port']; } else { if ('http' == $parsed['scheme']) { $port = 80; } else { if ('https' == $parsed['scheme']) { $port = 443; } else { return new Jetpack_Error('unknown_scheme_port', "The scheme's port is unknown"); } } } if (!ctype_digit("{$timestamp}") || 10 < strlen($timestamp)) { // If Jetpack is around in 275 years, you can blame mdawaffe for the bug. return new Jetpack_Error('invalid_signature', sprintf('The required "%s" parameter is malformed.', 'timestamp')); } $local_time = $timestamp - $this->time_diff; if ($local_time < time() - 600 || $local_time > time() + 300) { return new Jetpack_Error('invalid_signature', 'The timestamp is too old.'); } if (12 < strlen($nonce) || preg_match('/[^a-zA-Z0-9]/', $nonce)) { return new Jetpack_Error('invalid_signature', sprintf('The required "%s" parameter is malformed.', 'nonce')); } $normalized_request_pieces = array($token, $timestamp, $nonce, $body_hash, strtoupper($method), strtolower($parsed['host']), $port, $parsed['path']); $normalized_request_pieces = array_merge($normalized_request_pieces, $this->normalized_query_parameters(isset($parsed['query']) ? $parsed['query'] : '')); $normalized_request_string = join("\n", $normalized_request_pieces) . "\n"; return base64_encode(hash_hmac('sha1', $normalized_request_string, $this->secret, true)); }
/** * Makes an authorized remote request using Jetpack_Signature * * @static * @return array|WP_Error WP HTTP response on success */ function remote_request($args, $body = null) { $defaults = array('url' => '', 'user_id' => 0, 'blog_id' => 0, 'auth_location' => JETPACK_CLIENT__AUTH_LOCATION, 'method' => 'POST', 'timeout' => 10, 'redirection' => 0); $args = wp_parse_args($args, $defaults); $args['user_id'] = (int) $args['user_id']; $args['blog_id'] = (int) $args['blog_id']; if ('header' != $args['auth_location']) { $args['auth_location'] = 'query_string'; } $token = Jetpack_Data::get_access_token($args); if (!$token) { return new Jetpack_Error('missing_token'); } $method = strtoupper($args['method']); $timeout = intval($args['timeout']); $redirection = $args['redirection']; $request = compact('method', 'body', 'timeout', 'redirection'); @(list($token_key, $secret) = explode('.', $token->secret)); if (empty($token) || empty($secret)) { return new Jetpack_Error('malformed_token'); } $token_key = sprintf('%s:%d:%d', $token_key, JETPACK__API_VERSION, $token->external_user_id); require_once dirname(__FILE__) . '/class.jetpack-signature.php'; $time_diff = (int) Jetpack::get_option('time_diff'); $jetpack_signature =& new Jetpack_Signature($token->secret, $time_diff); $timestamp = time() + $time_diff; $nonce = wp_generate_password(10, false); // Kind of annoying. Maybe refactor Jetpack_Signature to handle body-hashing if (is_null($body)) { $body_hash = ''; } else { if (!is_string($body)) { return new Jetpack_Error('invalid_body', 'Body is malformed.'); } $body_hash = jetpack_sha1_base64($body); } $auth = array('token' => $token_key, 'timestamp' => $timestamp, 'nonce' => $nonce, 'body-hash' => $body_hash); if (false !== strpos($args['url'], 'xmlrpc.php')) { $url_args = array('for' => 'jetpack'); } else { $url_args = array(); } if ('header' != $args['auth_location']) { $url_args += $auth; } $url = add_query_arg(urlencode_deep($url_args), $args['url']); $url = Jetpack::fix_url_for_bad_hosts($url, $request); $signature = $jetpack_signature->sign_request($token_key, $timestamp, $nonce, $body_hash, $method, $url, $body, false); if (!$signature || is_wp_error($signature)) { return $signature; } // Send an Authorization header so various caches/proxies do the right thing $auth['signature'] = $signature; $auth['version'] = JETPACK__VERSION; $header_pieces = array(); foreach ($auth as $key => $value) { $header_pieces[] = sprintf('%s="%s"', $key, $value); } $request['headers'] = array('Authorization' => "X_JETPACK " . join(' ', $header_pieces)); if ('header' != $args['auth_location']) { $url = add_query_arg('signature', urlencode($signature), $url); } return Jetpack_Client::_wp_remote_request($url, $request); }
/** * Makes an authorized remote request using Jetpack_Signature * * @return array|WP_Error WP HTTP response on success */ public static function remote_request($args, $body = null) { $defaults = array('url' => '', 'user_id' => 0, 'blog_id' => 0, 'auth_location' => JETPACK_CLIENT__AUTH_LOCATION, 'method' => 'POST', 'timeout' => 10, 'redirection' => 0, 'headers' => array(), 'stream' => false, 'filename' => null); $args = wp_parse_args($args, $defaults); $args['blog_id'] = (int) $args['blog_id']; if ('header' != $args['auth_location']) { $args['auth_location'] = 'query_string'; } $token = Jetpack_Data::get_access_token($args['user_id']); if (!$token) { return new Jetpack_Error('missing_token'); } $method = strtoupper($args['method']); $timeout = intval($args['timeout']); $redirection = $args['redirection']; $stream = $args['stream']; $filename = $args['filename']; $request = compact('method', 'body', 'timeout', 'redirection', 'stream', 'filename'); @(list($token_key, $secret) = explode('.', $token->secret)); if (empty($token) || empty($secret)) { return new Jetpack_Error('malformed_token'); } $token_key = sprintf('%s:%d:%d', $token_key, JETPACK__API_VERSION, $token->external_user_id); require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php'; $time_diff = (int) Jetpack_Options::get_option('time_diff'); $jetpack_signature = new Jetpack_Signature($token->secret, $time_diff); $timestamp = time() + $time_diff; if (function_exists('wp_generate_password')) { $nonce = wp_generate_password(10, false); } else { $nonce = substr(sha1(rand(0, 1000000)), 0, 10); } // Kind of annoying. Maybe refactor Jetpack_Signature to handle body-hashing if (is_null($body)) { $body_hash = ''; } else { // Allow arrays to be used in passing data. $body_to_hash = $body; if (is_array($body)) { // We cast this to a new variable, because the array form of $body needs to be // maintained so it can be passed into the request later on in the code. if (count($body) > 0) { $body_to_hash = json_encode(self::_stringify_data($body)); } else { $body_to_hash = ''; } } if (!is_string($body_to_hash)) { return new Jetpack_Error('invalid_body', 'Body is malformed.'); } $body_hash = jetpack_sha1_base64($body_to_hash); } $auth = array('token' => $token_key, 'timestamp' => $timestamp, 'nonce' => $nonce, 'body-hash' => $body_hash); if (false !== strpos($args['url'], 'xmlrpc.php')) { $url_args = array('for' => 'jetpack', 'wpcom_blog_id' => Jetpack_Options::get_option('id')); } else { $url_args = array(); } if ('header' != $args['auth_location']) { $url_args += $auth; } $url = add_query_arg(urlencode_deep($url_args), $args['url']); $url = Jetpack::fix_url_for_bad_hosts($url); $signature = $jetpack_signature->sign_request($token_key, $timestamp, $nonce, $body_hash, $method, $url, $body, false); if (!$signature || is_wp_error($signature)) { return $signature; } // Send an Authorization header so various caches/proxies do the right thing $auth['signature'] = $signature; $auth['version'] = JETPACK__VERSION; $header_pieces = array(); foreach ($auth as $key => $value) { $header_pieces[] = sprintf('%s="%s"', $key, $value); } $request['headers'] = array_merge($args['headers'], array('Authorization' => "X_JETPACK " . join(' ', $header_pieces))); // Make sure we keep the host when we do JETPACK__WPCOM_JSON_API_HOST requests. $host = parse_url($url, PHP_URL_HOST); if ($host === JETPACK__WPCOM_JSON_API_HOST) { $request['headers']['Host'] = 'public-api.wordpress.com'; } if ('header' != $args['auth_location']) { $url = add_query_arg('signature', urlencode($signature), $url); } return Jetpack_Client::_wp_remote_request($url, $request); }