This is lame, but many, many, many hosts have misconfigured SSL.
When Jetpack is registered, the jetpack_fallback_no_verify_ssl_certs option is set to the current time if:
1. a certificate error is found AND
2. not verifying the certificate works around the problem.
The option is checked on each request.
public static _wp_remote_request ( $url, $args, $set_fallback = false ) : array | WP_Error | ||
return | array | WP_Error | WP HTTP response on success |
/** * @return bool|WP_Error */ public static function register() { Jetpack_Options::update_option('register', wp_generate_password(32, false) . ':' . wp_generate_password(32, false) . ':' . (time() + 600)); @(list($secret_1, $secret_2, $secret_eol) = explode(':', Jetpack_Options::get_option('register'))); if (empty($secret_1) || empty($secret_2) || empty($secret_eol) || $secret_eol < time()) { return new Jetpack_Error('missing_secrets'); } $timeout = (int) ini_get('max_execution_time'); if (!$timeout) { $timeout = 30; } $timeout = intval($timeout / 2); $gmt_offset = get_option('gmt_offset'); if (!$gmt_offset) { $gmt_offset = 0; } $stats_options = get_option('stats_options'); $stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null; $args = array('method' => 'POST', 'body' => array('siteurl' => site_url(), 'home' => home_url(), 'gmt_offset' => $gmt_offset, 'timezone_string' => (string) get_option('timezone_string'), 'site_name' => (string) get_option('blogname'), 'secret_1' => $secret_1, 'secret_2' => $secret_2, 'site_lang' => get_locale(), 'timeout' => $timeout, 'stats_id' => $stats_id), 'headers' => array('Accept' => 'application/json'), 'timeout' => $timeout); $response = Jetpack_Client::_wp_remote_request(Jetpack::fix_url_for_bad_hosts(Jetpack::api_url('register')), $args, true); if (is_wp_error($response)) { return new Jetpack_Error('register_http_request_failed', $response->get_error_message()); } $code = wp_remote_retrieve_response_code($response); $entity = wp_remote_retrieve_body($response); if ($entity) { $json = json_decode($entity); } else { $json = false; } $code_type = intval($code / 100); if (5 == $code_type) { return new Jetpack_Error('wpcom_5??', sprintf(__('Error Details: %s', 'jetpack'), $code), $code); } elseif (408 == $code) { return new Jetpack_Error('wpcom_408', sprintf(__('Error Details: %s', 'jetpack'), $code), $code); } elseif (!empty($json->error)) { $error_description = isset($json->error_description) ? sprintf(__('Error Details: %s', 'jetpack'), (string) $json->error_description) : ''; return new Jetpack_Error((string) $json->error, $error_description, $code); } elseif (200 != $code) { return new Jetpack_Error('wpcom_bad_response', sprintf(__('Error Details: %s', 'jetpack'), $code), $code); } // Jetpack ID error block if (empty($json->jetpack_id)) { return new Jetpack_Error('jetpack_id', sprintf(__('Error Details: Jetpack ID is empty. Do not publicly post this error message! %s', 'jetpack'), $entity), $entity); } elseif (!is_scalar($json->jetpack_id)) { return new Jetpack_Error('jetpack_id', sprintf(__('Error Details: Jetpack ID is not a scalar. Do not publicly post this error message! %s', 'jetpack'), $entity), $entity); } elseif (preg_match('/[^0-9]/', $json->jetpack_id)) { return new Jetpack_Error('jetpack_id', sprintf(__('Error Details: Jetpack ID begins with a numeral. Do not publicly post this error message! %s', 'jetpack'), $entity), $entity); } if (empty($json->jetpack_secret) || !is_string($json->jetpack_secret)) { return new Jetpack_Error('jetpack_secret', '', $code); } if (isset($json->jetpack_public)) { $jetpack_public = (int) $json->jetpack_public; } else { $jetpack_public = false; } Jetpack_Options::update_options(array('id' => (int) $json->jetpack_id, 'blog_token' => (string) $json->jetpack_secret, 'public' => $jetpack_public)); return true; }
/** * @return bool|WP_Error */ public static function register() { add_action('pre_update_jetpack_option_register', array('Jetpack_Options', 'delete_option')); $secrets = Jetpack::init()->generate_secrets(); Jetpack_Options::update_option('register', $secrets[0] . ':' . $secrets[1] . ':' . $secrets[2]); @(list($secret_1, $secret_2, $secret_eol) = explode(':', Jetpack_Options::get_option('register'))); if (empty($secret_1) || empty($secret_2) || empty($secret_eol) || $secret_eol < time()) { return new Jetpack_Error('missing_secrets'); } $timeout = Jetpack::init()->get_remote_query_timeout_limit(); $gmt_offset = get_option('gmt_offset'); if (!$gmt_offset) { $gmt_offset = 0; } $stats_options = get_option('stats_options'); $stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null; $args = array('method' => 'POST', 'body' => array('siteurl' => site_url(), 'home' => home_url(), 'gmt_offset' => $gmt_offset, 'timezone_string' => (string) get_option('timezone_string'), 'site_name' => (string) get_option('blogname'), 'secret_1' => $secret_1, 'secret_2' => $secret_2, 'site_lang' => get_locale(), 'timeout' => $timeout, 'stats_id' => $stats_id), 'headers' => array('Accept' => 'application/json'), 'timeout' => $timeout); $response = Jetpack_Client::_wp_remote_request(Jetpack::fix_url_for_bad_hosts(Jetpack::api_url('register')), $args, true); // Make sure the response is valid and does not contain any Jetpack errors $valid_response = Jetpack::init()->validate_remote_register_response($response); if (is_wp_error($valid_response) || !$valid_response) { return $valid_response; } // Grab the response values to work with $code = wp_remote_retrieve_response_code($response); $entity = wp_remote_retrieve_body($response); if ($entity) { $json = json_decode($entity); } else { $json = false; } if (empty($json->jetpack_secret) || !is_string($json->jetpack_secret)) { return new Jetpack_Error('jetpack_secret', '', $code); } if (isset($json->jetpack_public)) { $jetpack_public = (int) $json->jetpack_public; } else { $jetpack_public = false; } Jetpack_Options::update_options(array('id' => (int) $json->jetpack_id, 'blog_token' => (string) $json->jetpack_secret, 'public' => $jetpack_public)); return true; }
/** * @return bool|WP_Error */ public static function register() { add_action('pre_update_jetpack_option_register', array('Jetpack_Options', 'delete_option')); $secrets = Jetpack::init()->generate_secrets('register'); @(list($secret_1, $secret_2, $secret_eol) = explode(':', $secrets)); if (empty($secret_1) || empty($secret_2) || empty($secret_eol) || $secret_eol < time()) { return new Jetpack_Error('missing_secrets'); } $timeout = Jetpack::init()->get_remote_query_timeout_limit(); $gmt_offset = get_option('gmt_offset'); if (!$gmt_offset) { $gmt_offset = 0; } $stats_options = get_option('stats_options'); $stats_id = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null; $args = array('method' => 'POST', 'body' => array('siteurl' => site_url(), 'home' => home_url(), 'gmt_offset' => $gmt_offset, 'timezone_string' => (string) get_option('timezone_string'), 'site_name' => (string) get_option('blogname'), 'secret_1' => $secret_1, 'secret_2' => $secret_2, 'site_lang' => get_locale(), 'timeout' => $timeout, 'stats_id' => $stats_id, 'state' => get_current_user_id()), 'headers' => array('Accept' => 'application/json'), 'timeout' => $timeout); $response = Jetpack_Client::_wp_remote_request(Jetpack::fix_url_for_bad_hosts(Jetpack::api_url('register')), $args, true); // Make sure the response is valid and does not contain any Jetpack errors $valid_response = Jetpack::init()->validate_remote_register_response($response); if (is_wp_error($valid_response) || !$valid_response) { return $valid_response; } // Grab the response values to work with $code = wp_remote_retrieve_response_code($response); $entity = wp_remote_retrieve_body($response); if ($entity) { $json = json_decode($entity); } else { $json = false; } if (empty($json->jetpack_secret) || !is_string($json->jetpack_secret)) { return new Jetpack_Error('jetpack_secret', '', $code); } if (isset($json->jetpack_public)) { $jetpack_public = (int) $json->jetpack_public; } else { $jetpack_public = false; } Jetpack_Options::update_options(array('id' => (int) $json->jetpack_id, 'blog_token' => (string) $json->jetpack_secret, 'public' => $jetpack_public)); /** * Fires when a site is registered on WordPress.com. * * @since 3.7.0 * * @param int $json->jetpack_id Jetpack Blog ID. * @param string $json->jetpack_secret Jetpack Blog Token. * @param int|bool $jetpack_public Is the site public. */ do_action('jetpack_site_registered', $json->jetpack_id, $json->jetpack_secret, $jetpack_public); // Initialize Jump Start for the first and only time. if (!Jetpack_Options::get_option('jumpstart')) { Jetpack_Options::update_option('jumpstart', 'new_connection'); $jetpack = Jetpack::init(); $jetpack->stat('jumpstart', 'unique-views'); $jetpack->do_stats('server_side'); } return true; }
/** * @return object|WP_Error */ function get_token($data) { $jetpack = Jetpack::init(); $role = $jetpack->translate_current_user_to_role(); if (!$role) { return new Jetpack_Error('role', __('An administrator for this blog must set up the Jetpack connection.', 'jetpack')); } $client_secret = Jetpack_Data::get_access_token(0); if (!$client_secret) { return new Jetpack_Error('client_secret', __('You need to register your Jetpack before connecting it.', 'jetpack')); } $body = array('client_id' => Jetpack::get_option('id'), 'client_secret' => $client_secret->secret, 'grant_type' => 'authorization_code', 'code' => $data['code'], 'redirect_uri' => add_query_arg(array('action' => 'authorize', '_wpnonce' => wp_create_nonce("jetpack-authorize_{$role}")), menu_page_url('jetpack', false))); $args = array('method' => 'POST', 'body' => $body, 'headers' => array('Accept' => 'application/json')); $response = Jetpack_Client::_wp_remote_request(Jetpack::fix_url_for_bad_hosts(Jetpack::api_url('token'), $args), $args); if (is_wp_error($response)) { return new Jetpack_Error('token_http_request_failed', $response->get_error_message()); } $code = wp_remote_retrieve_response_code($response); $entity = wp_remote_retrieve_body($response); if ($entity) { $json = json_decode($entity); } else { $json = false; } if (200 != $code || !empty($json->error)) { if (empty($json->error)) { return new Jetpack_Error('unknown', '', $code); } $error_description = isset($json->error_description) ? sprintf(__('Error Details: %s', 'jetpack'), (string) $json->error_description) : ''; return new Jetpack_Error((string) $json->error, $error_description, $code); } if (empty($json->access_token) || !is_scalar($json->access_token)) { return new Jetpack_Error('access_token', '', $code); } if (empty($json->token_type) || 'X_JETPACK' != strtoupper($json->token_type)) { return new Jetpack_Error('token_type', '', $code); } if (empty($json->scope)) { return new Jetpack_Error('scope', 'No Scope', $code); } @(list($role, $hmac) = explode(':', $json->scope)); if (empty($role) || empty($hmac)) { return new Jetpack_Error('scope', 'Malformed Scope', $code); } if ($jetpack->sign_role($role) !== $json->scope) { return new Jetpack_Error('scope', 'Invalid Scope', $code); } if (!($cap = $jetpack->translate_role_to_cap($role))) { return new Jetpack_Error('scope', 'No Cap', $code); } if (!current_user_can($cap)) { return new Jetpack_Error('scope', 'current_user_cannot', $code); } return (string) $json->access_token; }
/** * 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); $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']; $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_Options::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', '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('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); }
/** * Registers a subsite with the Jetpack servers * * @since 2.9 * @todo Break apart into easier to manage chunks that can be unit tested * @see Jetpack_Network::jetpack_sites_list(); */ public function do_subsiteregister($site_id = null) { if (!current_user_can('jetpack_disconnect')) { return; } $jp = Jetpack::init(); // Figure out what site we are working on $site_id = is_null($site_id) ? $_GET['site_id'] : $site_id; // Build secrets to sent to wpcom for verification $secrets = $jp->generate_secrets(); // Remote query timeout limit $timeout = $jp->get_remote_query_timeout_limit(); // The blog id on WordPress.com of the primary network site $network_wpcom_blog_id = Jetpack_Options::get_option('id'); /* * Here we need to switch to the subsite * For the registration process we really only hijack how it * works for an individual site and pass in some extra data here */ switch_to_blog($site_id); // Save the secrets in the subsite so when the wpcom server does a pingback it // will be able to validate the connection Jetpack_Options::update_option('register', $secrets[0] . ':' . $secrets[1] . ':' . $secrets[2]); // Gra info for gmt offset $gmt_offset = get_option('gmt_offset'); if (!$gmt_offset) { $gmt_offset = 0; } /* * Get the stats_option option from the db. * It looks like the server strips this out so maybe it is not necessary? * Does it match the Jetpack site with the old stats plugin id? * * @todo Find out if sending the stats_id is necessary */ $stat_options = get_option('stats_options'); $stat_id = $stat_options = isset($stats_options['blog_id']) ? $stats_options['blog_id'] : null; $args = array('method' => 'POST', 'body' => array('network_url' => $this->get_url('network_admin_page'), 'network_wpcom_blog_id' => $network_wpcom_blog_id, 'siteurl' => site_url(), 'home' => home_url(), 'gmt_offset' => $gmt_offset, 'timezone_string' => (string) get_option('timezone_string'), 'site_name' => (string) get_option('blogname'), 'secret_1' => $secrets[0], 'secret_2' => $secrets[1], 'site_lang' => get_locale(), 'timeout' => $timeout, 'stats_id' => $stat_id, 'user_id' => get_current_user_id()), 'headers' => array('Accept' => 'application/json'), 'timeout' => $timeout); // Attempt to retrieve shadow blog details $response = Jetpack_Client::_wp_remote_request(Jetpack::fix_url_for_bad_hosts(Jetpack::api_url('subsiteregister')), $args, true); /* * $response should either be invalid or contain: * - jetpack_id => id * - jetpack_secret => blog_token * - jetpack_public * * Store the wpcom site details */ $valid_response = $jp->validate_remote_register_response($response); if (is_wp_error($valid_response) || !$valid_response) { restore_current_blog(); return $valid_response; } // Grab the response values to work with $code = wp_remote_retrieve_response_code($response); $entity = wp_remote_retrieve_body($response); if ($entity) { $json = json_decode($entity); } else { $json = false; } if (empty($json->jetpack_secret) || !is_string($json->jetpack_secret)) { restore_current_blog(); return new Jetpack_Error('jetpack_secret', '', $code); } if (isset($json->jetpack_public)) { $jetpack_public = (int) $json->jetpack_public; } else { $jetpack_public = false; } Jetpack_Options::update_options(array('id' => (int) $json->jetpack_id, 'blog_token' => (string) $json->jetpack_secret, 'public' => $jetpack_public)); /* * Update the subsiteregister method on wpcom so that it also sends back the * token in this same request */ $is_master_user = !Jetpack::is_active(); Jetpack::update_user_token(get_current_user_id(), sprintf('%s.%d', $json->token->secret, get_current_user_id()), $is_master_user); Jetpack::activate_default_modules(); restore_current_blog(); }
/** * 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); }