aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorVirtualTam <virtualtam@flibidi.net>2017-01-04 11:41:05 +0100
committerVirtualTam <virtualtam@flibidi.net>2017-01-04 16:59:47 +0100
commit7a9daac56dc64ec1ddb12adece3e1a8f71778cc7 (patch)
treeb92c37792e7af48e1da36686f1d722aaffb90a06
parentfc11ab2f290a3712b766d78fdbcd354625a35d0a (diff)
downloadShaarli-7a9daac56dc64ec1ddb12adece3e1a8f71778cc7.tar.gz
Shaarli-7a9daac56dc64ec1ddb12adece3e1a8f71778cc7.tar.zst
Shaarli-7a9daac56dc64ec1ddb12adece3e1a8f71778cc7.zip
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>
-rw-r--r--application/Base64Url.php34
-rw-r--r--application/api/ApiUtils.php12
-rw-r--r--composer.json1
-rw-r--r--tests/api/ApiUtilsTest.php15
4 files changed, 49 insertions, 13 deletions
diff --git a/application/Base64Url.php b/application/Base64Url.php
new file mode 100644
index 00000000..61590e43
--- /dev/null
+++ b/application/Base64Url.php
@@ -0,0 +1,34 @@
1<?php
2
3namespace Shaarli;
4
5
6/**
7 * URL-safe Base64 operations
8 *
9 * @see https://en.wikipedia.org/wiki/Base64#URL_applications
10 */
11class Base64Url
12{
13 /**
14 * Base64Url-encodes data
15 *
16 * @param string $data Data to encode
17 *
18 * @return string Base64Url-encoded data
19 */
20 public static function encode($data) {
21 return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
22 }
23
24 /**
25 * Decodes Base64Url-encoded data
26 *
27 * @param string $data Data to decode
28 *
29 * @return string Decoded data
30 */
31 public static function decode($data) {
32 return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
33 }
34}
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php
index fbb1e72f..a419c396 100644
--- a/application/api/ApiUtils.php
+++ b/application/api/ApiUtils.php
@@ -1,13 +1,11 @@
1<?php 1<?php
2
3namespace Shaarli\Api; 2namespace Shaarli\Api;
4 3
4use Shaarli\Base64Url;
5use Shaarli\Api\Exceptions\ApiAuthorizationException; 5use Shaarli\Api\Exceptions\ApiAuthorizationException;
6 6
7/** 7/**
8 * Class ApiUtils 8 * REST API utilities
9 *
10 * Utility functions for the API.
11 */ 9 */
12class ApiUtils 10class ApiUtils
13{ 11{
@@ -26,17 +24,17 @@ class ApiUtils
26 throw new ApiAuthorizationException('Malformed JWT token'); 24 throw new ApiAuthorizationException('Malformed JWT token');
27 } 25 }
28 26
29 $genSign = hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret); 27 $genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret, true));
30 if ($parts[2] != $genSign) { 28 if ($parts[2] != $genSign) {
31 throw new ApiAuthorizationException('Invalid JWT signature'); 29 throw new ApiAuthorizationException('Invalid JWT signature');
32 } 30 }
33 31
34 $header = json_decode(base64_decode($parts[0])); 32 $header = json_decode(Base64Url::decode($parts[0]));
35 if ($header === null) { 33 if ($header === null) {
36 throw new ApiAuthorizationException('Invalid JWT header'); 34 throw new ApiAuthorizationException('Invalid JWT header');
37 } 35 }
38 36
39 $payload = json_decode(base64_decode($parts[1])); 37 $payload = json_decode(Base64Url::decode($parts[1]));
40 if ($payload === null) { 38 if ($payload === null) {
41 throw new ApiAuthorizationException('Invalid JWT payload'); 39 throw new ApiAuthorizationException('Invalid JWT payload');
42 } 40 }
diff --git a/composer.json b/composer.json
index cfbde1a0..2fed0df7 100644
--- a/composer.json
+++ b/composer.json
@@ -24,6 +24,7 @@
24 }, 24 },
25 "autoload": { 25 "autoload": {
26 "psr-4": { 26 "psr-4": {
27 "Shaarli\\": "application",
27 "Shaarli\\Api\\": "application/api/", 28 "Shaarli\\Api\\": "application/api/",
28 "Shaarli\\Api\\Controllers\\": "application/api/controllers", 29 "Shaarli\\Api\\Controllers\\": "application/api/controllers",
29 "Shaarli\\Api\\Exceptions\\": "application/api/exceptions" 30 "Shaarli\\Api\\Exceptions\\": "application/api/exceptions"
diff --git a/tests/api/ApiUtilsTest.php b/tests/api/ApiUtilsTest.php
index 10da1459..4b2fa3b2 100644
--- a/tests/api/ApiUtilsTest.php
+++ b/tests/api/ApiUtilsTest.php
@@ -2,6 +2,9 @@
2 2
3namespace Shaarli\Api; 3namespace Shaarli\Api;
4 4
5use Shaarli\Base64Url;
6
7
5/** 8/**
6 * Class ApiUtilsTest 9 * Class ApiUtilsTest
7 */ 10 */
@@ -24,14 +27,14 @@ class ApiUtilsTest extends \PHPUnit_Framework_TestCase
24 */ 27 */
25 public static function generateValidJwtToken($secret) 28 public static function generateValidJwtToken($secret)
26 { 29 {
27 $header = base64_encode('{ 30 $header = Base64Url::encode('{
28 "typ": "JWT", 31 "typ": "JWT",
29 "alg": "HS512" 32 "alg": "HS512"
30 }'); 33 }');
31 $payload = base64_encode('{ 34 $payload = Base64Url::encode('{
32 "iat": '. time() .' 35 "iat": '. time() .'
33 }'); 36 }');
34 $signature = hash_hmac('sha512', $header .'.'. $payload , $secret); 37 $signature = Base64Url::encode(hash_hmac('sha512', $header .'.'. $payload , $secret, true));
35 return $header .'.'. $payload .'.'. $signature; 38 return $header .'.'. $payload .'.'. $signature;
36 } 39 }
37 40
@@ -46,9 +49,9 @@ class ApiUtilsTest extends \PHPUnit_Framework_TestCase
46 */ 49 */
47 public static function generateCustomJwtToken($header, $payload, $secret) 50 public static function generateCustomJwtToken($header, $payload, $secret)
48 { 51 {
49 $header = base64_encode($header); 52 $header = Base64Url::encode($header);
50 $payload = base64_encode($payload); 53 $payload = Base64Url::encode($payload);
51 $signature = hash_hmac('sha512', $header . '.' . $payload, $secret); 54 $signature = Base64Url::encode(hash_hmac('sha512', $header . '.' . $payload, $secret, true));
52 return $header . '.' . $payload . '.' . $signature; 55 return $header . '.' . $payload . '.' . $signature;
53 } 56 }
54 57