~fkooman/php-jwt

14afbe0767fdd6421e9bba1f86afe861dd5c2c98 — François Kooman 10 months ago 1a40423
major cleanups, drop paragonie/constant_time_encoding dependency
12 files changed, 134 insertions(+), 84 deletions(-)

M .gitignore
R .php_cs.dist => .php-cs-fixer.dist.php
A Makefile
M composer.json
D phpunit.xml.dist
A src/Base64UrlSafe.php
M src/Jwt.php
M src/Keys/EdDSA/PublicKey.php
M src/Keys/EdDSA/SecretKey.php
M src/Keys/HS256/SecretKey.php
M src/Keys/RS256/PrivateKey.php
M tests/EdDSATest.php
M .gitignore => .gitignore +1 -0
@@ 1,2 1,3 @@
/vendor
/composer.lock
/.php-cs-fixer.cache

R .php_cs.dist => .php-cs-fixer.dist.php +40 -39
@@ 1,44 1,45 @@
<?php

return PhpCsFixer\Config::create()
    ->setRiskyAllowed(true)
    ->setRules(
        [
            '@Symfony' => true,
            '@Symfony:risky' => true,
            'ordered_imports' => true,
            'ordered_class_elements' => true,
            'array_syntax' => ['syntax' => 'short'],
            'native_function_invocation' => true,
            'phpdoc_order' => true,
            'phpdoc_no_empty_return' => false,
            'phpdoc_add_missing_param_annotation' => true,
            'strict_comparison' => true,
            'strict_param' => true,
            'php_unit_strict' => true,
            'header_comment' => [
                'header' => <<< 'EOD'
Copyright (c) 2019-2020 François Kooman <fkooman@tuxed.net>
$config = new PhpCsFixer\Config();

return $config->setRules(
    [
        '@PSR12' => true,
        'visibility_required' => ['elements' => ['method', 'property']],
        'ordered_imports' => true,
        'ordered_class_elements' => true,
        'array_syntax' => ['syntax' => 'short'],
        'phpdoc_order' => true,
        'phpdoc_no_empty_return' => false,
        'phpdoc_add_missing_param_annotation' => true,
        'strict_comparison' => true,
        'strict_param' => true,
        'php_unit_strict' => true,
        'header_comment' => [
            'header' => <<< 'EOD'
                Copyright (c) 2019-2020 François Kooman <fkooman@tuxed.net>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
                Permission is hereby granted, free of charge, to any person obtaining a copy
                of this software and associated documentation files (the "Software"), to deal
                in the Software without restriction, including without limitation the rights
                to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
                copies of the Software, and to permit persons to whom the Software is
                furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
                The above copyright notice and this permission notice shall be included in all
                copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
EOD
             ],
        ]
    )
    ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__));
                THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
                IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
                FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
                AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
                LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
                OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
                SOFTWARE.
                EOD,
        ],
    ]
)
    ->setRiskyAllowed(true)
    ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__))
;

A Makefile => Makefile +18 -0
@@ 0,0 1,18 @@
.PHONY: all test fmt psalm phpstan sloc

all:	test fmt psalm phpstan

test:
	vendor/bin/put

fmt:
	php-cs-fixer fix

psalm:
	psalm

phpstan:
	phpstan

sloc:
	phploc src

M composer.json => composer.json +14 -4
@@ 2,7 2,7 @@
    "authors": [
        {
            "email": "fkooman@tuxed.net",
            "name": "Fran\u00e7ois Kooman"
            "name": "François Kooman"
        }
    ],
    "autoload": {


@@ 15,23 15,33 @@
            "fkooman\\Jwt\\Tests\\": "tests/"
        }
    },
    "config": {
        "platform": {
            "php": "5.4.8"
        }
    },
    "description": "Small JSON Web Signature implementation",
    "license": "MIT",
    "name": "fkooman/jwt",
    "repositories": [
        {
            "type": "vcs",
            "url": "https://git.sr.ht/~fkooman/put"
        }
    ],
    "require": {
        "ext-date": "*",
        "ext-hash": "*",
        "ext-json": "*",
        "ext-openssl": "*",
        "ext-spl": "*",
        "paragonie/constant_time_encoding": "^1.0.3|^2.2.0",
        "paragonie/random_compat": ">=1",
        "paragonie/random_compat": "^2",
        "paragonie/sodium_compat": "^1",
        "php": ">= 5.4.8",
        "symfony/polyfill-php56": "^1"
    },
    "require-dev": {
        "phpunit/phpunit": "^4|^5|^6|^7"
        "fkooman/put": "^0"
    },
    "support": {
        "email": "fkooman@tuxed.net",

D phpunit.xml.dist => phpunit.xml.dist +0 -21
@@ 1,21 0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.3/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         forceCoversAnnotation="false"
         beStrictAboutCoversAnnotation="true"
         beStrictAboutOutputDuringTests="true"
         beStrictAboutTodoAnnotatedTests="true"
         verbose="true">
    <testsuites>
        <testsuite name="default">
            <directory suffix="Test.php">tests</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">src</directory>
        </whitelist>
    </filter>
</phpunit>

A src/Base64UrlSafe.php => src/Base64UrlSafe.php +46 -0
@@ 0,0 1,46 @@
<?php

/*
 * Copyright (c) 2019-2020 François Kooman <fkooman@tuxed.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

namespace fkooman\Jwt;

class Base64UrlSafe
{
    /**
     * @param string $inputStr
     * @return string
     */
    public static function encodeUnpadded($inputStr)
    {
        return sodium_bin2base64($inputStr, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
    }

    /**
     * @param string $inputStr
     * @return string
     */
    public static function decode($inputStr)
    {
        return sodium_base642bin($inputStr, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
    }
}

M src/Jwt.php => src/Jwt.php +0 -1
@@ 26,7 26,6 @@ namespace fkooman\Jwt;

use DateTime;
use fkooman\Jwt\Exception\JwtException;
use ParagonIE\ConstantTime\Base64UrlSafe;

/**
 * The base class that MUST be extended by the classes that actually implement

M src/Keys/EdDSA/PublicKey.php => src/Keys/EdDSA/PublicKey.php +2 -3
@@ 24,9 24,8 @@

namespace fkooman\Jwt\Keys\EdDSA;

use fkooman\Jwt\Base64UrlSafe;
use LengthException;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\ConstantTime\Binary;

class PublicKey
{


@@ 38,7 37,7 @@ class PublicKey
     */
    public function __construct($publicKey)
    {
        if (SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== Binary::safeStrlen($publicKey)) {
        if (SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES !== strlen($publicKey)) {
            throw new LengthException('invalid public key length');
        }
        $this->publicKey = $publicKey;

M src/Keys/EdDSA/SecretKey.php => src/Keys/EdDSA/SecretKey.php +4 -5
@@ 24,8 24,7 @@

namespace fkooman\Jwt\Keys\EdDSA;

use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\ConstantTime\Binary;
use fkooman\Jwt\Base64UrlSafe;

class SecretKey
{


@@ 37,15 36,15 @@ class SecretKey
     */
    public function __construct($secretKey)
    {
        switch (Binary::safeStrlen($secretKey)) {
        switch (strlen($secretKey)) {
            case SODIUM_CRYPTO_SIGN_SECRETKEYBYTES:
                $this->secretKey = $secretKey;
                break;
            case SODIUM_CRYPTO_SIGN_SEEDBYTES:
                $this->secretKey = Binary::safeSubstr(\sodium_crypto_sign_seed_keypair($secretKey), 0, 64);
                $this->secretKey = substr(\sodium_crypto_sign_seed_keypair($secretKey), 0, 64);
                break;
            case SODIUM_CRYPTO_SIGN_KEYPAIRBYTES:
                $this->secretKey = Binary::safeSubstr($secretKey, 0, 64);
                $this->secretKey = substr($secretKey, 0, 64);
                break;
            default:
                throw new \LengthException('invalid secret key length');

M src/Keys/HS256/SecretKey.php => src/Keys/HS256/SecretKey.php +3 -3
@@ 24,9 24,9 @@

namespace fkooman\Jwt\Keys\HS256;

use fkooman\Jwt\Base64UrlSafe;

use fkooman\Jwt\Exception\KeyException;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\ConstantTime\Binary;

class SecretKey
{


@@ 41,7 41,7 @@ class SecretKey
     */
    public function __construct($secretKey)
    {
        if (32 !== Binary::safeStrlen($secretKey)) {
        if (32 !== strlen($secretKey)) {
            throw new KeyException('invalid key length');
        }
        $this->secretKey = $secretKey;

M src/Keys/RS256/PrivateKey.php => src/Keys/RS256/PrivateKey.php +1 -2
@@ 25,7 25,6 @@
namespace fkooman\Jwt\Keys\RS256;

use fkooman\Jwt\Exception\KeyException;
use ParagonIE\ConstantTime\Binary;
use RuntimeException;

class PrivateKey


@@ 52,7 51,7 @@ class PrivateKey
        $rsaInfo = $keyInfo['rsa'];
        // RSA key MUST be at least 2048 bits
        // @see https://tools.ietf.org/html/rfc7518#section-4.2
        if (256 > Binary::safeStrlen($rsaInfo['n'])) {
        if (256 > strlen($rsaInfo['n'])) {
            throw new KeyException('invalid RSA key, must be >= 2048 bits');
        }
        $this->privateKey = $privateKey;

M tests/EdDSATest.php => tests/EdDSATest.php +5 -6
@@ 24,11 24,10 @@

namespace fkooman\Jwt\Tests;

use fkooman\Jwt\Base64UrlSafe;
use fkooman\Jwt\EdDSA;
use fkooman\Jwt\Keys\EdDSA\PublicKey;
use fkooman\Jwt\Keys\EdDSA\SecretKey;
use ParagonIE\ConstantTime\Base64UrlSafe;
use ParagonIE\ConstantTime\Hex;
use PHPUnit\Framework\TestCase;

class EdDSATest extends TestCase


@@ 71,14 70,14 @@ class EdDSATest extends TestCase

    public function testKeys()
    {
        $secretKey = new SecretKey(Hex::decode('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'));
        $publicKey = new PublicKey(Hex::decode('d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'));
        $secretKey = new SecretKey(hex2bin('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'));
        $publicKey = new PublicKey(hex2bin('d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'));
        $this->assertSame($secretKey->getPublicKey()->encode(), $publicKey->encode());
    }

    public function testTestVectorSign()
    {
        $secretKey = new SecretKey(Hex::decode('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'));
        $secretKey = new SecretKey(hex2bin('9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60'));
        $dsa = new TestEdDSA(
            $secretKey->getPublicKey(),
            $secretKey


@@ 88,7 87,7 @@ class EdDSATest extends TestCase

    public function testTestVectorVerify()
    {
        $publicKey = new PublicKey(Hex::decode('d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'));
        $publicKey = new PublicKey(hex2bin('d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'));
        $dsa = new TestEdDSA($publicKey);
        $this->assertTrue(
            $dsa->verify(