API: fix JWT signature verification

Fixes https://github.com/shaarli/Shaarli/issues/737

Added:
- Base64Url utilities

Fixed:
- use URL-safe Base64 encoding/decoding functions
- use byte representations for HMAC digests
- all JWT parts are Base64Url-encoded

See:
- https://en.wikipedia.org/wiki/JSON_Web_Token
- https://tools.ietf.org/html/rfc7519
- https://scotch.io/tutorials/the-anatomy-of-a-json-web-token
- https://jwt.io/introduction/
- https://en.wikipedia.org/wiki/Base64#URL_applications
- https://secure.php.net/manual/en/function.base64-encode.php#103849

Signed-off-by: VirtualTam <virtualtam@flibidi.net>
This commit is contained in:
VirtualTam 2017-01-04 11:41:05 +01:00
parent fc11ab2f29
commit 7a9daac56d
4 changed files with 49 additions and 13 deletions

34
application/Base64Url.php Normal file
View file

@ -0,0 +1,34 @@
<?php
namespace Shaarli;
/**
* URL-safe Base64 operations
*
* @see https://en.wikipedia.org/wiki/Base64#URL_applications
*/
class Base64Url
{
/**
* Base64Url-encodes data
*
* @param string $data Data to encode
*
* @return string Base64Url-encoded data
*/
public static function encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
/**
* Decodes Base64Url-encoded data
*
* @param string $data Data to decode
*
* @return string Decoded data
*/
public static function decode($data) {
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}
}

View file

@ -1,13 +1,11 @@
<?php <?php
namespace Shaarli\Api; namespace Shaarli\Api;
use Shaarli\Base64Url;
use Shaarli\Api\Exceptions\ApiAuthorizationException; use Shaarli\Api\Exceptions\ApiAuthorizationException;
/** /**
* Class ApiUtils * REST API utilities
*
* Utility functions for the API.
*/ */
class ApiUtils class ApiUtils
{ {
@ -26,17 +24,17 @@ public static function validateJwtToken($token, $secret)
throw new ApiAuthorizationException('Malformed JWT token'); throw new ApiAuthorizationException('Malformed JWT token');
} }
$genSign = hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret); $genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret, true));
if ($parts[2] != $genSign) { if ($parts[2] != $genSign) {
throw new ApiAuthorizationException('Invalid JWT signature'); throw new ApiAuthorizationException('Invalid JWT signature');
} }
$header = json_decode(base64_decode($parts[0])); $header = json_decode(Base64Url::decode($parts[0]));
if ($header === null) { if ($header === null) {
throw new ApiAuthorizationException('Invalid JWT header'); throw new ApiAuthorizationException('Invalid JWT header');
} }
$payload = json_decode(base64_decode($parts[1])); $payload = json_decode(Base64Url::decode($parts[1]));
if ($payload === null) { if ($payload === null) {
throw new ApiAuthorizationException('Invalid JWT payload'); throw new ApiAuthorizationException('Invalid JWT payload');
} }

View file

@ -24,6 +24,7 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Shaarli\\": "application",
"Shaarli\\Api\\": "application/api/", "Shaarli\\Api\\": "application/api/",
"Shaarli\\Api\\Controllers\\": "application/api/controllers", "Shaarli\\Api\\Controllers\\": "application/api/controllers",
"Shaarli\\Api\\Exceptions\\": "application/api/exceptions" "Shaarli\\Api\\Exceptions\\": "application/api/exceptions"

View file

@ -2,6 +2,9 @@
namespace Shaarli\Api; namespace Shaarli\Api;
use Shaarli\Base64Url;
/** /**
* Class ApiUtilsTest * Class ApiUtilsTest
*/ */
@ -24,14 +27,14 @@ public static function setUpBeforeClass()
*/ */
public static function generateValidJwtToken($secret) public static function generateValidJwtToken($secret)
{ {
$header = base64_encode('{ $header = Base64Url::encode('{
"typ": "JWT", "typ": "JWT",
"alg": "HS512" "alg": "HS512"
}'); }');
$payload = base64_encode('{ $payload = Base64Url::encode('{
"iat": '. time() .' "iat": '. time() .'
}'); }');
$signature = hash_hmac('sha512', $header .'.'. $payload , $secret); $signature = Base64Url::encode(hash_hmac('sha512', $header .'.'. $payload , $secret, true));
return $header .'.'. $payload .'.'. $signature; return $header .'.'. $payload .'.'. $signature;
} }
@ -46,9 +49,9 @@ public static function generateValidJwtToken($secret)
*/ */
public static function generateCustomJwtToken($header, $payload, $secret) public static function generateCustomJwtToken($header, $payload, $secret)
{ {
$header = base64_encode($header); $header = Base64Url::encode($header);
$payload = base64_encode($payload); $payload = Base64Url::encode($payload);
$signature = hash_hmac('sha512', $header . '.' . $payload, $secret); $signature = Base64Url::encode(hash_hmac('sha512', $header . '.' . $payload, $secret, true));
return $header . '.' . $payload . '.' . $signature; return $header . '.' . $payload . '.' . $signature;
} }