PageRenderTime 162ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Twilio/Jwt/JWT.php

http://github.com/twilio/twilio-php
PHP | 172 lines | 97 code | 14 blank | 61 comment | 19 complexity | 06537e5d399ab8a7a06e4545f5fcd64d MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. namespace Twilio\Jwt;
  3. /**
  4. * JSON Web Token implementation
  5. *
  6. * Minimum implementation used by Realtime auth, based on this spec:
  7. * http://self-issued.info/docs/draft-jones-json-web-token-01.html.
  8. *
  9. * @author Neuman Vong <neuman@twilio.com>
  10. */
  11. class JWT {
  12. /**
  13. * @param string $jwt The JWT
  14. * @param string|null $key The secret key
  15. * @param bool $verify Don't skip verification process
  16. * @return object The JWT's payload as a PHP object
  17. * @throws \DomainException
  18. * @throws \UnexpectedValueException
  19. */
  20. public static function decode(string $jwt, string $key = null, bool $verify = true) {
  21. $tks = \explode('.', $jwt);
  22. if (\count($tks) !== 3) {
  23. throw new \UnexpectedValueException('Wrong number of segments');
  24. }
  25. list($headb64, $payloadb64, $cryptob64) = $tks;
  26. if (null === ($header = self::jsonDecode(self::urlsafeB64Decode($headb64)))
  27. ) {
  28. throw new \UnexpectedValueException('Invalid segment encoding');
  29. }
  30. if (null === $payload = self::jsonDecode(self::urlsafeB64Decode($payloadb64))
  31. ) {
  32. throw new \UnexpectedValueException('Invalid segment encoding');
  33. }
  34. $sig = self::urlsafeB64Decode($cryptob64);
  35. if ($verify) {
  36. if (empty($header->alg)) {
  37. throw new \DomainException('Empty algorithm');
  38. }
  39. if (!hash_equals($sig, self::sign("$headb64.$payloadb64", $key, $header->alg))) {
  40. throw new \UnexpectedValueException('Signature verification failed');
  41. }
  42. }
  43. return $payload;
  44. }
  45. /**
  46. * @param string $jwt The JWT
  47. * @return object The JWT's header as a PHP object
  48. * @throws \UnexpectedValueException
  49. */
  50. public static function getHeader(string $jwt) {
  51. $tks = \explode('.', $jwt);
  52. if (\count($tks) !== 3) {
  53. throw new \UnexpectedValueException('Wrong number of segments');
  54. }
  55. list($headb64) = $tks;
  56. if (null === ($header = self::jsonDecode(self::urlsafeB64Decode($headb64)))
  57. ) {
  58. throw new \UnexpectedValueException('Invalid segment encoding');
  59. }
  60. return $header;
  61. }
  62. /**
  63. * @param object|array $payload PHP object or array
  64. * @param string $key The secret key
  65. * @param string $algo The signing algorithm
  66. * @param array $additionalHeaders Additional keys/values to add to the header
  67. *
  68. * @return string A JWT
  69. */
  70. public static function encode($payload, string $key, string $algo = 'HS256', array $additionalHeaders = []): string {
  71. $header = ['typ' => 'JWT', 'alg' => $algo];
  72. $header += $additionalHeaders;
  73. $segments = [];
  74. $segments[] = self::urlsafeB64Encode(self::jsonEncode($header));
  75. $segments[] = self::urlsafeB64Encode(self::jsonEncode($payload));
  76. $signing_input = \implode('.', $segments);
  77. $signature = self::sign($signing_input, $key, $algo);
  78. $segments[] = self::urlsafeB64Encode($signature);
  79. return \implode('.', $segments);
  80. }
  81. /**
  82. * @param string $msg The message to sign
  83. * @param string $key The secret key
  84. * @param string $method The signing algorithm
  85. * @return string An encrypted message
  86. * @throws \DomainException
  87. */
  88. public static function sign(string $msg, string $key, string $method = 'HS256'): string {
  89. $methods = [
  90. 'HS256' => 'sha256',
  91. 'HS384' => 'sha384',
  92. 'HS512' => 'sha512',
  93. ];
  94. if (empty($methods[$method])) {
  95. throw new \DomainException('Algorithm not supported');
  96. }
  97. return \hash_hmac($methods[$method], $msg, $key, true);
  98. }
  99. /**
  100. * @param string $input JSON string
  101. * @return object Object representation of JSON string
  102. * @throws \DomainException
  103. */
  104. public static function jsonDecode(string $input) {
  105. $obj = \json_decode($input);
  106. if (\function_exists('json_last_error') && $errno = \json_last_error()) {
  107. self::handleJsonError($errno);
  108. } else if ($obj === null && $input !== 'null') {
  109. throw new \DomainException('Null result with non-null input');
  110. }
  111. return $obj;
  112. }
  113. /**
  114. * @param object|array $input A PHP object or array
  115. * @return string JSON representation of the PHP object or array
  116. * @throws \DomainException
  117. */
  118. public static function jsonEncode($input): string {
  119. $json = \json_encode($input);
  120. if (\function_exists('json_last_error') && $errno = \json_last_error()) {
  121. self::handleJsonError($errno);
  122. } else if ($json === 'null' && $input !== null) {
  123. throw new \DomainException('Null result with non-null input');
  124. }
  125. return $json;
  126. }
  127. /**
  128. * @param string $input A base64 encoded string
  129. *
  130. * @return string A decoded string
  131. */
  132. public static function urlsafeB64Decode(string $input): string {
  133. $padLen = 4 - \strlen($input) % 4;
  134. $input .= \str_repeat('=', $padLen);
  135. return \base64_decode(\strtr($input, '-_', '+/'));
  136. }
  137. /**
  138. * @param string $input Anything really
  139. *
  140. * @return string The base64 encode of what you passed in
  141. */
  142. public static function urlsafeB64Encode(string $input): string {
  143. return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
  144. }
  145. /**
  146. * @param int $errno An error number from json_last_error()
  147. *
  148. * @throws \DomainException
  149. */
  150. private static function handleJsonError(int $errno): void {
  151. $messages = [
  152. JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
  153. JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
  154. JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON'
  155. ];
  156. throw new \DomainException($messages[$errno] ?? 'Unknown JSON error: ' . $errno);
  157. }
  158. }