diff --git a/_inc/lib/debugger/class-jetpack-cxn-tests.php b/_inc/lib/debugger/class-jetpack-cxn-tests.php index e6854370007e..b8dc0949a219 100644 --- a/_inc/lib/debugger/class-jetpack-cxn-tests.php +++ b/_inc/lib/debugger/class-jetpack-cxn-tests.php @@ -5,6 +5,7 @@ * @package Jetpack */ + /** * Class Jetpack_Cxn_Tests contains all of the actual tests. */ diff --git a/class.jetpack-client.php b/class.jetpack-client.php index cabb2e0374e8..14858652284f 100644 --- a/class.jetpack-client.php +++ b/class.jetpack-client.php @@ -54,8 +54,6 @@ public static function remote_request( $args, $body = null ) { $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 ); @@ -89,7 +87,7 @@ public static function remote_request( $args, $body = null ) { return new Jetpack_Error( 'invalid_body', 'Body is malformed.' ); } - $body_hash = jetpack_sha1_base64( $body_to_hash ); + $body_hash = Jetpack::connection()->sha1_base64( $body_to_hash ); } $auth = array( diff --git a/class.jetpack.php b/class.jetpack.php index 560dbd468d5b..4930c3b67dc2 100644 --- a/class.jetpack.php +++ b/class.jetpack.php @@ -5203,8 +5203,6 @@ function verify_xml_rpc_signature() { return false; } - require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php'; - $jetpack_signature = new Jetpack_Signature( $token->secret, (int) Jetpack_Options::get_option( 'time_diff' ) ); if ( isset( $_POST['_jetpack_is_multipart'] ) ) { $post_data = $_POST; @@ -5814,8 +5812,6 @@ function add_token_to_login_redirect_json_api_authorization( $redirect_to, $orig * @param null|array $environment */ function verify_json_api_authorization_request( $environment = null ) { - require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php'; - $environment = is_null( $environment ) ? $_REQUEST : $environment; diff --git a/composer.lock b/composer.lock index cb686138e673..5ed8edf71db6 100644 --- a/composer.lock +++ b/composer.lock @@ -8,12 +8,11 @@ "packages": [ { "name": "automattic/jetpack-asset-tools", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/asset-tools", - "reference": "d425ff2ef48f7e4c323507ca583e4065f7f107a6", - "shasum": null + "reference": "d425ff2ef48f7e4c323507ca583e4065f7f107a6" }, "require": { "automattic/jetpack-constants": "@dev" @@ -31,12 +30,11 @@ }, { "name": "automattic/jetpack-connection", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/connection", - "reference": "eb2ab1a317d710fbe651a20fa580d523e303d848", - "shasum": null + "reference": "0a0e293d73c17d59376590d56e5667b169d26877" }, "require": { "automattic/jetpack-constants": "@dev", @@ -50,7 +48,10 @@ "autoload": { "psr-4": { "Automattic\\Jetpack\\Connection\\": "src" - } + }, + "classmap": [ + "legacy" + ] }, "scripts": { "phpunit": [ @@ -65,12 +66,11 @@ }, { "name": "automattic/jetpack-constants", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/constants", - "reference": "a6ab6360f4b48962ec7d62b06b39d1470b1dbe95", - "shasum": null + "reference": "a6ab6360f4b48962ec7d62b06b39d1470b1dbe95" }, "require-dev": { "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5" @@ -94,12 +94,11 @@ }, { "name": "automattic/jetpack-jitm", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/jitm", - "reference": "38af4dd23b02753405d0f718f87df55259979591", - "shasum": null + "reference": "38af4dd23b02753405d0f718f87df55259979591" }, "require": { "automattic/jetpack-asset-tools": "@dev", @@ -132,12 +131,11 @@ }, { "name": "automattic/jetpack-logo", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/logo", - "reference": "d8a31dfd40166c4867fa2c526a03d9df481d5610", - "shasum": null + "reference": "d8a31dfd40166c4867fa2c526a03d9df481d5610" }, "require-dev": { "php-mock/php-mock": "^2.1", @@ -162,12 +160,11 @@ }, { "name": "automattic/jetpack-options", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/options", - "reference": "78220bf7d3c1a3a5ed4edb77462e84982b3c408f", - "shasum": null + "reference": "78220bf7d3c1a3a5ed4edb77462e84982b3c408f" }, "require": { "automattic/jetpack-constants": "@dev" @@ -189,12 +186,11 @@ }, { "name": "automattic/jetpack-sync", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/sync", - "reference": "b6c34729a2a37b5a485add5d357ff018654e3e35", - "shasum": null + "reference": "b6c34729a2a37b5a485add5d357ff018654e3e35" }, "require": { "automattic/jetpack-constants": "@dev", @@ -213,12 +209,11 @@ }, { "name": "automattic/jetpack-tracking", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/tracking", - "reference": "bf2c5cf51eec2e1ad21955c05c692ef2815875b9", - "shasum": null + "reference": "bf2c5cf51eec2e1ad21955c05c692ef2815875b9" }, "require": { "automattic/jetpack-options": "@dev" @@ -368,12 +363,11 @@ "packages-dev": [ { "name": "automattic/jetpack-autoloader", - "version": "dev-update/bye-constants-manager", + "version": "dev-add/jetpack-signature-to-connection", "dist": { "type": "path", "url": "./packages/autoloader", - "reference": "8c7dc6036bf6125c31a574c14c6b0521247410ed", - "shasum": null + "reference": "8c7dc6036bf6125c31a574c14c6b0521247410ed" }, "require": { "composer-plugin-api": "^1.1" diff --git a/functions.compat.php b/functions.compat.php index bc7e6fbd822e..4e1f47af4a14 100644 --- a/functions.compat.php +++ b/functions.compat.php @@ -1,5 +1,7 @@ sha1_base64( $text ); +} diff --git a/jetpack.php b/jetpack.php index f69b9a00f1c0..f882f9d7a7da 100644 --- a/jetpack.php +++ b/jetpack.php @@ -47,6 +47,12 @@ . '-----END PUBLIC KEY-----' . "\r\n" ); +// These constants can be set in wp-config.php to ensure sites behind proxies will still work. +// Setting these constants, though, is *not* the preferred method. It's better to configure +// the proxy to send the X-Forwarded-Port header. +defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) or define( 'JETPACK_SIGNATURE__HTTP_PORT' , 80 ); +defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) or define( 'JETPACK_SIGNATURE__HTTPS_PORT', 443 ); + /** * Returns the location of Jetpack's lib directory. This filter is applied * in require_lib(). diff --git a/packages/connection/composer.json b/packages/connection/composer.json index 1f75092bf68a..d2c1658f5b06 100644 --- a/packages/connection/composer.json +++ b/packages/connection/composer.json @@ -14,7 +14,10 @@ "autoload": { "psr-4": { "Automattic\\Jetpack\\Connection\\": "src" - } + }, + "classmap": [ + "legacy" + ] }, "scripts": { "phpunit": [ diff --git a/class.jetpack-signature.php b/packages/connection/legacy/class.jetpack-signature.php similarity index 56% rename from class.jetpack-signature.php rename to packages/connection/legacy/class.jetpack-signature.php index 8d925d7d074d..3a7e6fc39098 100644 --- a/class.jetpack-signature.php +++ b/packages/connection/legacy/class.jetpack-signature.php @@ -1,10 +1,6 @@ token = $secret[0]; - $this->secret = $secret[1]; + $this->token = $secret[0]; + $this->secret = $secret[1]; $this->time_diff = $time_diff; } function sign_current_request( $override = array() ) { if ( isset( $override['scheme'] ) ) { $scheme = $override['scheme']; - if ( !in_array( $scheme, array( 'http', 'https' ) ) ) { - return new Jetpack_Error( 'invalid_scheme', 'Invalid URL scheme' ); + if ( ! in_array( $scheme, array( 'http', 'https' ) ) ) { + return new WP_Error( 'invalid_scheme', 'Invalid URL scheme' ); } } else { if ( is_ssl() ) { @@ -36,6 +33,7 @@ function sign_current_request( $override = array() ) { $host_port = isset( $_SERVER['HTTP_X_FORWARDED_PORT'] ) ? $_SERVER['HTTP_X_FORWARDED_PORT'] : $_SERVER['SERVER_PORT']; + $connection = new Connection_Manager(); /** * Note: This port logic is tested in the Jetpack_Cxn_Tests->test__server_port_value() test. * Please update the test if any changes are made in this logic. @@ -43,28 +41,30 @@ function sign_current_request( $override = array() ) { if ( is_ssl() ) { // 443: Standard Port // 80: Assume we're behind a proxy without X-Forwarded-Port. Hardcoding "80" here means most sites - // with SSL termination proxies (self-served, Cloudflare, etc.) don't need to fiddle with - // the JETPACK_SIGNATURE__HTTPS_PORT constant. The code also implies we can't talk to a - // site at https://example.com:80/ (which would be a strange configuration). + // with SSL termination proxies (self-served, Cloudflare, etc.) don't need to fiddle with + // the JETPACK_SIGNATURE__HTTPS_PORT constant. The code also implies we can't talk to a + // site at https://example.com:80/ (which would be a strange configuration). // JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port - // if the site is behind a proxy running on port 443 without - // X-Forwarded-Port and the back end's port is *not* 80. It's better, - // though, to configure the proxy to send X-Forwarded-Port. - $port = in_array( $host_port, array( 443, 80, JETPACK_SIGNATURE__HTTPS_PORT ) ) ? '' : $host_port; + // if the site is behind a proxy running on port 443 without + // X-Forwarded-Port and the back end's port is *not* 80. It's better, + // though, to configure the proxy to send X-Forwarded-Port. + $https_port = defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) ? JETPACK_SIGNATURE__HTTPS_PORT : 443; + $port = in_array( $host_port, array( 443, 80, $https_port ) ) ? '' : $host_port; } else { // 80: Standard Port // JETPACK_SIGNATURE__HTTPS_PORT: Set this constant in wp-config.php to the back end webserver's port - // if the site is behind a proxy running on port 80 without - // X-Forwarded-Port. It's better, though, to configure the proxy to - // send X-Forwarded-Port. - $port = in_array( $host_port, array( 80, JETPACK_SIGNATURE__HTTP_PORT ) ) ? '' : $host_port; + // if the site is behind a proxy running on port 80 without + // X-Forwarded-Port. It's better, though, to configure the proxy to + // send X-Forwarded-Port. + $http_port = defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) ? JETPACK_SIGNATURE__HTTP_PORT : 80; + $port = in_array( $host_port, array( 80, $http_port ) ) ? '' : $host_port; } $url = "{$scheme}://{$_SERVER['HTTP_HOST']}:{$port}" . stripslashes( $_SERVER['REQUEST_URI'] ); if ( array_key_exists( 'body', $override ) && ! empty( $override['body'] ) ) { $body = $override['body']; - } else if ( 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { + } elseif ( 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { $body = isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null; // Convert the $_POST to the body, if the body was empty. This is how arrays are hashed @@ -74,7 +74,7 @@ function sign_current_request( $override = array() ) { $body = $_POST; } } - } else if ( 'PUT' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { + } elseif ( 'PUT' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { // This is a little strange-looking, but there doesn't seem to be another way to get the PUT body $raw_put_data = file_get_contents( 'php://input' ); parse_str( $raw_put_data, $body ); @@ -95,10 +95,10 @@ function sign_current_request( $override = array() ) { $a = array(); foreach ( array( 'token', 'timestamp', 'nonce', 'body-hash' ) as $parameter ) { - if ( isset( $override[$parameter] ) ) { - $a[$parameter] = $override[$parameter]; + if ( isset( $override[ $parameter ] ) ) { + $a[ $parameter ] = $override[ $parameter ]; } else { - $a[$parameter] = isset( $_GET[$parameter] ) ? stripslashes( $_GET[$parameter] ) : ''; + $a[ $parameter ] = isset( $_GET[ $parameter ] ) ? stripslashes( $_GET[ $parameter ] ) : ''; } } @@ -108,18 +108,18 @@ function sign_current_request( $override = array() ) { // body_hash v. body-hash is annoying. Refactor to accept an array? 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->secret ) { + return new WP_Error( 'invalid_secret', 'Invalid secret' ); } - if ( !$this->token ) { - return new Jetpack_Error( 'invalid_token', 'Invalid token' ); + if ( ! $this->token ) { + return new WP_Error( 'invalid_token', 'Invalid token' ); } list( $token ) = explode( '.', $token ); if ( 0 !== strpos( $token, "$this->token:" ) ) { - return new Jetpack_Error( 'token_mismatch', 'Incorrect token' ); + return new WP_Error( 'token_mismatch', 'Incorrect token' ); } // If we got an array at this point, let's encode it, so we can see what it looks like as a string. @@ -133,61 +133,62 @@ function sign_request( $token = '', $timestamp = 0, $nonce = '', $body_hash = '' } $required_parameters = array( 'token', 'timestamp', 'nonce', 'method', 'url' ); - if ( !is_null( $body ) ) { + if ( ! is_null( $body ) ) { $required_parameters[] = 'body_hash'; - if ( !is_string( $body ) ) { - return new Jetpack_Error( 'invalid_body', 'Body is malformed.' ); + if ( ! is_string( $body ) ) { + return new WP_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 ( ! is_scalar( $$required ) ) { + return new WP_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 ( ! strlen( $$required ) ) { + return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is missing.', str_replace( '_', '-', $required ) ) ); } } if ( empty( $body ) ) { if ( $body_hash ) { - return new Jetpack_Error( 'invalid_body_hash', 'The body hash does not match.' ); + return new WP_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.' ); + $connection = new Connection_Manager(); + if ( $verify_body_hash && $connection->sha1_base64( $body ) !== $body_hash ) { + return new WP_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 ( ! isset( $parsed['host'] ) ) { + return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'url' ) ); } - if ( !empty( $parsed['port'] ) ) { + if ( ! empty( $parsed['port'] ) ) { $port = $parsed['port']; } else { if ( 'http' == $parsed['scheme'] ) { $port = 80; - } else if ( 'https' == $parsed['scheme'] ) { + } elseif ( 'https' == $parsed['scheme'] ) { $port = 443; } else { - return new Jetpack_Error( 'unknown_scheme_port', "The scheme's port is unknown" ); + return new WP_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' ) ); + if ( ! ctype_digit( "$timestamp" ) || 10 < strlen( $timestamp ) ) { // If Jetpack is around in 275 years, you can blame mdawaffe for the bug. + return new WP_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.' ); + return new WP_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' ) ); + return new WP_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'nonce' ) ); } $normalized_request_pieces = array( @@ -202,9 +203,9 @@ function sign_request( $token = '', $timestamp = 0, $nonce = '', $body_hash = '' // Normalized Query String ); - $normalized_request_pieces = array_merge( $normalized_request_pieces, $this->normalized_query_parameters( isset( $parsed['query'] ) ? $parsed['query'] : '' ) ); + $normalized_request_pieces = array_merge( $normalized_request_pieces, $this->normalized_query_parameters( isset( $parsed['query'] ) ? $parsed['query'] : '' ) ); $flat_normalized_request_pieces = array(); - foreach ($normalized_request_pieces as $piece) { + foreach ( $normalized_request_pieces as $piece ) { if ( is_array( $piece ) ) { foreach ( $piece as $subpiece ) { $flat_normalized_request_pieces[] = $subpiece; @@ -222,18 +223,19 @@ function sign_request( $token = '', $timestamp = 0, $nonce = '', $body_hash = '' function normalized_query_parameters( $query_string ) { parse_str( $query_string, $array ); - if ( get_magic_quotes_gpc() ) + if ( get_magic_quotes_gpc() ) { $array = stripslashes_deep( $array ); + } unset( $array['signature'] ); $names = array_keys( $array ); $values = array_values( $array ); - $names = array_map( array( $this, 'encode_3986' ), $names ); + $names = array_map( array( $this, 'encode_3986' ), $names ); $values = array_map( array( $this, 'encode_3986' ), $values ); - $pairs = array_map( array( $this, 'join_with_equal_sign' ), $names, $values ); + $pairs = array_map( array( $this, 'join_with_equal_sign' ), $names, $values ); sort( $pairs ); @@ -260,7 +262,3 @@ function join_with_equal_sign( $name, $value ) { return "{$name}={$value}"; } } - -function jetpack_sha1_base64( $text ) { - return base64_encode( sha1( $text, true ) ); -} diff --git a/packages/connection/src/Manager.php b/packages/connection/src/Manager.php index 2702adc2d927..85d19400ea8c 100644 --- a/packages/connection/src/Manager.php +++ b/packages/connection/src/Manager.php @@ -266,6 +266,14 @@ public function disconnect_site() { } + /** + * @param $text + * @return string + */ + function sha1_base64( $text ) { + return base64_encode( sha1( $text, true ) ); + } + /** * This function mirrors Jetpack_Data::is_usable_domain() in the WPCOM codebase. * diff --git a/tests/php/_inc/lib/test_class.rest-api-authentication.php b/tests/php/_inc/lib/test_class.rest-api-authentication.php index a7d09aa44913..80b6ef8144cf 100644 --- a/tests/php/_inc/lib/test_class.rest-api-authentication.php +++ b/tests/php/_inc/lib/test_class.rest-api-authentication.php @@ -1,7 +1,5 @@ sha1_base64( '{"modules":[]}' ); $_GET['signature'] = 'abc'; $this->request = new WP_REST_Request( 'POST', '/jetpack/v4/module/all/active' ); $this->request->set_header( 'Content-Type', 'application/json' ); @@ -221,7 +219,7 @@ public function test_jetpack_rest_api_post_authentication_fail_bad_body_hash() { $_GET['token'], $_GET['timestamp'], $_GET['nonce'], - jetpack_sha1_base64( '{"modules":[]}' ), + Jetpack::connection()->sha1_base64( '{"modules":[]}' ), 'GET', 'example.org', '80', @@ -251,7 +249,7 @@ public function test_jetpack_rest_api_post_authentication_success() { $_GET['token'] = 'pretend_this_is_valid:1:' . self::$admin_id; $_GET['timestamp'] = (string) time(); $_GET['nonce'] = 'testing123'; - $_GET['body-hash'] = jetpack_sha1_base64( $body ); + $_GET['body-hash'] = Jetpack::connection()->sha1_base64( $body ); $_GET['signature'] = base64_encode( hash_hmac( 'sha1', implode( "\n", array( $_GET['token'], $_GET['timestamp'], @@ -291,7 +289,7 @@ public function test_jetpack_rest_api_post_urlencoded_authentication_success() { $_GET['token'] = 'pretend_this_is_valid:1:' . self::$admin_id; $_GET['timestamp'] = (string) time(); $_GET['nonce'] = 'testing123'; - $_GET['body-hash'] = jetpack_sha1_base64( $body ); + $_GET['body-hash'] = Jetpack::connection()->sha1_base64( $body ); $_GET['signature'] = base64_encode( hash_hmac( 'sha1', implode( "\n", array( $_GET['token'], $_GET['timestamp'], @@ -341,7 +339,7 @@ public function test_jetpack_rest_api_post_multipart_authentication_success() { $_GET['token'] = 'pretend_this_is_valid:1:' . self::$admin_id; $_GET['timestamp'] = (string) time(); $_GET['nonce'] = 'testing123'; - $_GET['body-hash'] = jetpack_sha1_base64( $body ); + $_GET['body-hash'] = Jetpack::connection()->sha1_base64( $body ); $_GET['signature'] = base64_encode( hash_hmac( 'sha1', implode( "\n", array( $_GET['token'], $_GET['timestamp'],