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:
parent
fc11ab2f29
commit
7a9daac56d
4 changed files with 49 additions and 13 deletions
34
application/Base64Url.php
Normal file
34
application/Base64Url.php
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue