/class.jetpack-signature.php

  1. <?php 
  2.  
  3. defined( 'JETPACK_SIGNATURE__HTTP_PORT' ) or define( 'JETPACK_SIGNATURE__HTTP_PORT' , 80 ); 
  4. defined( 'JETPACK_SIGNATURE__HTTPS_PORT' ) or define( 'JETPACK_SIGNATURE__HTTPS_PORT', 443 ); 
  5.  
  6. class Jetpack_Signature { 
  7. public $token; 
  8. public $secret; 
  9.  
  10. function __construct( $access_token, $time_diff = 0 ) { 
  11. $secret = explode( '.', $access_token ); 
  12. if ( 2 != count( $secret ) ) 
  13. return; 
  14.  
  15. $this->token = $secret[0]; 
  16. $this->secret = $secret[1]; 
  17. $this->time_diff = $time_diff; 
  18.  
  19. function sign_current_request( $override = array() ) { 
  20. if ( isset( $override['scheme'] ) ) { 
  21. $scheme = $override['scheme']; 
  22. if ( !in_array( $scheme, array( 'http', 'https' ) ) ) { 
  23. return new Jetpack_Error( 'invalid_sheme', 'Invalid URL scheme' ); 
  24. } else { 
  25. if ( is_ssl() ) { 
  26. $scheme = 'https'; 
  27. } else { 
  28. $scheme = 'http'; 
  29.  
  30. if ( is_ssl() ) { 
  31. $port = JETPACK_SIGNATURE__HTTPS_PORT == $_SERVER['SERVER_PORT'] ? '' : $_SERVER['SERVER_PORT']; 
  32. } else { 
  33. $port = JETPACK_SIGNATURE__HTTP_PORT == $_SERVER['SERVER_PORT'] ? '' : $_SERVER['SERVER_PORT']; 
  34.  
  35. $url = "{$scheme}://{$_SERVER['HTTP_HOST']}:{$port}" . stripslashes( $_SERVER['REQUEST_URI'] ); 
  36.  
  37. if ( array_key_exists( 'body', $override ) && !is_null( $override['body'] ) ) { 
  38. $body = $override['body']; 
  39. } else if ( 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] ) ) { 
  40. $body = isset( $GLOBALS['HTTP_RAW_POST_DATA'] ) ? $GLOBALS['HTTP_RAW_POST_DATA'] : null; 
  41. } else { 
  42. $body = null; 
  43.  
  44. $a = array(); 
  45. foreach ( array( 'token', 'timestamp', 'nonce', 'body-hash' ) as $parameter ) { 
  46. if ( isset( $override[$parameter] ) ) { 
  47. $a[$parameter] = $override[$parameter]; 
  48. } else { 
  49. $a[$parameter] = isset( $_GET[$parameter] ) ? stripslashes( $_GET[$parameter] ) : ''; 
  50.  
  51. $method = isset( $override['method'] ) ? $override['method'] : $_SERVER['REQUEST_METHOD']; 
  52. return $this->sign_request( $a['token'], $a['timestamp'], $a['nonce'], $a['body-hash'], $method, $url, $body, true ); 
  53.  
  54. // body_hash v. body-hash is annoying. Refactor to accept an array? 
  55. function sign_request( $token = '', $timestamp = 0, $nonce = '', $body_hash = '', $method = '', $url = '', $body = null, $verify_body_hash = true ) { 
  56. if ( !$this->secret ) { 
  57. return new Jetpack_Error( 'invalid_secret', 'Invalid secret' ); 
  58.  
  59. if ( !$this->token ) { 
  60. return new Jetpack_Error( 'invalid_token', 'Invalid token' ); 
  61.  
  62. list( $token ) = explode( '.', $token ); 
  63.  
  64. if ( 0 !== strpos( $token, "$this->token:" ) ) { 
  65. return new Jetpack_Error( 'token_mismatch', 'Incorrect token' ); 
  66.  
  67. $required_parameters = array( 'token', 'timestamp', 'nonce', 'method', 'url' ); 
  68. if ( !is_null( $body ) ) { 
  69. $required_parameters[] = 'body_hash'; 
  70. if ( !is_string( $body ) ) { 
  71. return new Jetpack_Error( 'invalid_body', 'Body is malformed.' ); 
  72.  
  73. foreach ( $required_parameters as $required ) { 
  74. if ( !is_scalar( $$required ) ) { 
  75. return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', str_replace( '_', '-', $required ) ) ); 
  76.  
  77. if ( !strlen( $$required ) ) { 
  78. return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is missing.', str_replace( '_', '-', $required ) ) ); 
  79.  
  80. if ( is_null( $body ) ) { 
  81. if ( $body_hash ) { 
  82. return new Jetpack_Error( 'invalid_body_hash', 'The body hash does not match.' ); 
  83. } else { 
  84. if ( $verify_body_hash && jetpack_sha1_base64( $body ) !== $body_hash ) { 
  85. return new Jetpack_Error( 'invalid_body_hash', 'The body hash does not match.' ); 
  86.  
  87. $parsed = parse_url( $url ); 
  88. if ( !isset( $parsed['host'] ) ) { 
  89. return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'url' ) ); 
  90.  
  91. if ( !empty( $parsed['port'] ) ) { 
  92. $port = $parsed['port']; 
  93. } else { 
  94. if ( 'http' == $parsed['scheme'] ) { 
  95. $port = 80; 
  96. } else if ( 'https' == $parsed['scheme'] ) { 
  97. $port = 443; 
  98. } else { 
  99. return new Jetpack_Error( 'unknown_scheme_port', "The scheme's port is unknown" ); 
  100.  
  101. if ( !ctype_digit( "$timestamp" ) || 10 < strlen( $timestamp ) ) { // If Jetpack is around in 275 years, you can blame mdawaffe for the bug. 
  102. return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'timestamp' ) ); 
  103.  
  104. $local_time = $timestamp - $this->time_diff; 
  105. if ( $local_time < time() - 600 || $local_time > time() + 300 ) { 
  106. return new Jetpack_Error( 'invalid_signature', 'The timestamp is too old.' ); 
  107.  
  108. if ( 12 < strlen( $nonce ) || preg_match( '/[^a-zA-Z0-9]/', $nonce ) ) { 
  109. return new Jetpack_Error( 'invalid_signature', sprintf( 'The required "%s" parameter is malformed.', 'nonce' ) ); 
  110.  
  111. $normalized_request_pieces = array( 
  112. $token,  
  113. $timestamp,  
  114. $nonce,  
  115. $body_hash,  
  116. strtoupper( $method ),  
  117. strtolower( $parsed['host'] ),  
  118. $port,  
  119. $parsed['path'],  
  120. // Normalized Query String 
  121. ); 
  122.  
  123. $normalized_request_pieces = array_merge( $normalized_request_pieces, $this->normalized_query_parameters( isset( $parsed['query'] ) ? $parsed['query'] : '' ) ); 
  124.  
  125. $normalized_request_string = join( "\n", $normalized_request_pieces ) . "\n"; 
  126.  
  127. return base64_encode( hash_hmac( 'sha1', $normalized_request_string, $this->secret, true ) ); 
  128.  
  129. function normalized_query_parameters( $query_string ) { 
  130. parse_str( $query_string, $array ); 
  131. if ( get_magic_quotes_gpc() ) 
  132. $array = stripslashes_deep( $array ); 
  133.  
  134. unset( $array['signature'] ); 
  135.  
  136. $names = array_keys( $array ); 
  137. $values = array_values( $array ); 
  138.  
  139. $names = array_map( array( $this, 'encode_3986' ), $names ); 
  140. $values = array_map( array( $this, 'encode_3986' ), $values ); 
  141.  
  142. $pairs = array_map( array( $this, 'join_with_equal_sign' ), $names, $values ); 
  143.  
  144. sort( $pairs ); 
  145.  
  146. return $pairs; 
  147.  
  148. function encode_3986( $string ) { 
  149. $string = rawurlencode( $string ); 
  150. return str_replace( '%7E', '~', $string ); // prior to PHP 5.3, rawurlencode was RFC 1738 
  151.  
  152. function join_with_equal_sign( $name, $value ) { 
  153. return "{$name}={$value}"; 
  154.  
  155. function jetpack_sha1_base64( $text ) { 
  156. return base64_encode( sha1( $text, true ) ); 
.