diff --git a/.gitattributes b/.gitattributes index b2c6ccce..b6cbd7eb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,6 +9,7 @@ /.gitignore export-ignore /.gitsplit.yml export-ignore /CODE_OF_CONDUCT.md export-ignore +/deptrac.yaml export-ignore /ecs.php export-ignore /infection.json export-ignore /Makefile export-ignore diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml index c1580d47..3e4db0e7 100644 --- a/.github/workflows/integrate.yml +++ b/.github/workflows/integrate.yml @@ -2,214 +2,218 @@ name: "Integrate" -on: [push, pull_request] +on: + push: + branches: + - "*.x" + pull_request: null jobs: - byte_level: - name: "0️⃣ Byte-level" - runs-on: "ubuntu-latest" - steps: - - name: "Checkout code" - uses: "actions/checkout@v3" - - - name: "Check file permissions" - run: | - test "$(find . -type f -not -path './.git/*' -executable)" == "" - - - name: "Find non-printable ASCII characters" - run: | - ! LC_ALL=C.UTF-8 find . -type f -name "*.php" -print0 | xargs -0 -- grep -PHn "[^ -~]" - - syntax_errors: - name: "1️⃣ Syntax errors" - runs-on: "ubuntu-latest" - steps: - - name: "Set up PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "8.1" - - - name: "Checkout code" - uses: "actions/checkout@v3" - - - name: "Install dependencies" - uses: "ramsey/composer-install@v2" - with: - dependency-versions: "highest" - - - name: "Check source code for syntax errors" - run: "composer exec -- parallel-lint src/ tests/" - - unit_tests: - name: "2️⃣ Unit and functional tests" - needs: - - "byte_level" - - "syntax_errors" - strategy: - matrix: - operating-system: - - "ubuntu-latest" - php-version: - - "8.1" - dependencies: - - "lowest" - - "highest" - runs-on: ${{ matrix.operating-system }} - steps: - - name: "Set up PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "${{ matrix.php-version }}" - extensions: "json, mbstring, openssl, sqlite3, curl, uuid" - coverage: "xdebug" - - - name: "Checkout code" - uses: "actions/checkout@v3" - - - name: "Install dependencies" - uses: "ramsey/composer-install@v2" - with: - dependency-versions: "${{ matrix.dependencies }}" - composer-options: "--optimize-autoloader" - - - name: "Execute unit tests" - run: "make ci-cc" - - # - name: Send coverage to Coveralls - # if: "matrix.php-version == '8.1' && matrix.dependencies == 'highest'" - # env: - # COVERALLS_REPO_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - # run: | - # wget "https://github.com/php-coveralls/php-coveralls/releases/download/v2.5.2/php-coveralls.phar" - # php ./php-coveralls.phar -v - - static_analysis: - name: "3️⃣ Static Analysis" - needs: - - "byte_level" - - "syntax_errors" - runs-on: "ubuntu-latest" - steps: - - name: "Set up PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "8.1" - extensions: "json, mbstring, openssl, sqlite3, curl, uuid" - - - name: "Checkout code" - uses: "actions/checkout@v3" - - - name: "Validate Composer configuration" - run: "composer validate --strict" - - - name: "Install dependencies" - uses: "ramsey/composer-install@v2" - with: - dependency-versions: "highest" - composer-options: "--optimize-autoloader" - - - name: "Execute static analysis" - run: "make st" - - coding_standards: - name: "4️⃣ Coding Standards" - needs: - - "byte_level" - - "syntax_errors" - runs-on: "ubuntu-latest" - steps: - - name: "Set up PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "8.1" - extensions: "json, mbstring, openssl, sqlite3, curl, uuid" - - - name: "Checkout code" - uses: "actions/checkout@v3" - - - name: "Check adherence to EditorConfig" - uses: "greut/eclint-action@v0" - - - name: "Install dependencies" - uses: "ramsey/composer-install@v2" - with: - dependency-versions: "highest" - composer-options: "--optimize-autoloader" - - - name: "Check coding style" - run: "make ci-cs" - - mutation_testing: - name: "5️⃣ Mutation Testing" - needs: - - "byte_level" - - "syntax_errors" - runs-on: "ubuntu-latest" - steps: - - name: "Set up PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "8.1" - extensions: "json, mbstring, openssl, sqlite3, curl, uuid" - - - name: "Checkout code" - uses: "actions/checkout@v3" - - - name: "Fetch Git base reference" - run: "git fetch --depth=1 origin ${GITHUB_BASE_REF}" - - - name: "Install dependencies" - uses: "ramsey/composer-install@v2" - with: - dependency-versions: "highest" - composer-options: "--optimize-autoloader" - - - name: "Execute Infection" - run: "make ci-mu" - - rector_checkstyle: - name: "6️⃣ Rector Checkstyle" - needs: - - "byte_level" - - "syntax_errors" - runs-on: "ubuntu-latest" - steps: - - name: "Set up PHP" - uses: "shivammathur/setup-php@v2" - with: - php-version: "8.1" - extensions: "json, mbstring, openssl, sqlite3, curl, uuid" - coverage: "xdebug" - - - name: "Checkout code" - uses: "actions/checkout@v3" - - - name: "Fetch Git base reference" - run: "git fetch --depth=1 origin ${GITHUB_BASE_REF}" - - - name: "Install dependencies" - uses: "ramsey/composer-install@v2" - with: - dependency-versions: "highest" - composer-options: "--optimize-autoloader" - - - name: "Execute Rector" - run: "make rector" - - exported_files: - name: "7️⃣ Exported files" - needs: - - "byte_level" - - "syntax_errors" - runs-on: "ubuntu-20.04" - steps: - - name: "Checkout code" - uses: "actions/checkout@v3" - - - name: "Check exported files" - run: | - EXPECTED="LICENSE,README.md,SECURITY.md,composer.json" - CURRENT="$(git archive HEAD | tar --list --exclude="src" --exclude="src/*" | paste -s -d ",")" - echo "CURRENT =${CURRENT}" - echo "EXPECTED=${EXPECTED}" - test "${CURRENT}" == "${EXPECTED}" + byte_level: + name: "0️⃣ Byte-level" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Check file permissions" + run: | + test "$(find . -type f -not -path './.git/*' -executable)" == "" + + - name: "Find non-printable ASCII characters" + run: | + ! LC_ALL=C.UTF-8 find . -type f -name "*.php" -print0 | xargs -0 -- grep -PHn "[^ -~]" + + syntax_errors: + name: "1️⃣ Syntax errors" + runs-on: "ubuntu-latest" + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.1" + + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "highest" + + - name: "Check source code for syntax errors" + run: "composer exec -- parallel-lint src/ tests/" + + unit_tests: + name: "2️⃣ Unit and functional tests" + needs: + - "byte_level" + - "syntax_errors" + strategy: + matrix: + operating-system: + - "ubuntu-latest" + php-version: + - "8.1" + dependencies: + - "lowest" + - "highest" + runs-on: ${{ matrix.operating-system }} + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + extensions: "json, mbstring, openssl, sqlite3, curl, uuid" + coverage: "xdebug" + + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "${{ matrix.dependencies }}" + composer-options: "--optimize-autoloader" + + - name: "Execute unit tests" + run: "make ci-cc" + + # - name: Send coverage to Coveralls + # if: "matrix.php-version == '8.1' && matrix.dependencies == 'highest'" + # env: + # COVERALLS_REPO_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + # run: | + # wget "https://github.com/php-coveralls/php-coveralls/releases/download/v2.5.2/php-coveralls.phar" + # php ./php-coveralls.phar -v + + static_analysis: + name: "3️⃣ Static Analysis" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-latest" + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.1" + extensions: "json, mbstring, openssl, sqlite3, curl, uuid" + + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Validate Composer configuration" + run: "composer validate --strict" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "highest" + composer-options: "--optimize-autoloader" + + - name: "Execute static analysis" + run: "make st" + + coding_standards: + name: "4️⃣ Coding Standards" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-latest" + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.1" + extensions: "json, mbstring, openssl, sqlite3, curl, uuid" + + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Check adherence to EditorConfig" + uses: "greut/eclint-action@v0" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "highest" + composer-options: "--optimize-autoloader" + + - name: "Check coding style" + run: "make ci-cs" + + mutation_testing: + name: "5️⃣ Mutation Testing" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-latest" + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.1" + extensions: "json, mbstring, openssl, sqlite3, curl, uuid" + + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Fetch Git base reference" + run: "git fetch --depth=1 origin ${GITHUB_BASE_REF}" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "highest" + composer-options: "--optimize-autoloader" + + - name: "Execute Infection" + run: "make ci-mu" + + rector_checkstyle: + name: "6️⃣ Rector Checkstyle" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-latest" + steps: + - name: "Set up PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "8.1" + extensions: "json, mbstring, openssl, sqlite3, curl, uuid" + coverage: "xdebug" + + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Fetch Git base reference" + run: "git fetch --depth=1 origin ${GITHUB_BASE_REF}" + + - name: "Install dependencies" + uses: "ramsey/composer-install@v2" + with: + dependency-versions: "highest" + composer-options: "--optimize-autoloader" + + - name: "Execute Rector" + run: "make rector" + + exported_files: + name: "7️⃣ Exported files" + needs: + - "byte_level" + - "syntax_errors" + runs-on: "ubuntu-20.04" + steps: + - name: "Checkout code" + uses: "actions/checkout@v3" + + - name: "Check exported files" + run: | + EXPECTED="LICENSE,README.md,SECURITY.md,composer.json" + CURRENT="$(git archive HEAD | tar --list --exclude="src" --exclude="src/*" | paste -s -d ",")" + echo "CURRENT =${CURRENT}" + echo "EXPECTED=${EXPECTED}" + test "${CURRENT}" == "${EXPECTED}" diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index e02d5d39..c82fb729 100644 --- a/.github/workflows/release-on-milestone-closed.yml +++ b/.github/workflows/release-on-milestone-closed.yml @@ -17,7 +17,7 @@ jobs: uses: "actions/checkout@v3" - name: "Release" - uses: "laminas/automatic-releases@1.16.0" + uses: "laminas/automatic-releases@1" with: command-name: "laminas:automatic-releases:release" env: @@ -28,7 +28,7 @@ jobs: "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} - name: "Create Merge-Up Pull Request" - uses: "laminas/automatic-releases@1.16.0" + uses: "laminas/automatic-releases@1" with: command-name: "laminas:automatic-releases:create-merge-up-pull-request" env: @@ -39,7 +39,7 @@ jobs: "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} - name: "Create and/or Switch to new Release Branch" - uses: "laminas/automatic-releases@1.16.0" + uses: "laminas/automatic-releases@1" with: command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor" env: @@ -50,7 +50,7 @@ jobs: "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} - name: "Bump Changelog Version On Originating Release Branch" - uses: "laminas/automatic-releases@1.16.0" + uses: "laminas/automatic-releases@1" with: command-name: "laminas:automatic-releases:bump-changelog" env: @@ -61,7 +61,7 @@ jobs: "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} - name: "Create new milestones" - uses: "laminas/automatic-releases@1.16.0" + uses: "laminas/automatic-releases@1" with: command-name: "laminas:automatic-releases:create-milestones" env: diff --git a/.github/workflows/tweet.yml b/.github/workflows/tweet.yml new file mode 100644 index 00000000..5958b57a --- /dev/null +++ b/.github/workflows/tweet.yml @@ -0,0 +1,23 @@ +name: tweet +on: + push: + tags: + - '*' + release: + types: [ published ] + +jobs: + tweet: + runs-on: ubuntu-latest + steps: + - name: Tweet + uses: snow-actions/tweet@v1 + with: + status: | + We are proud to announce that ${{ github.repository }} · ${{ github.event.release.name }} + ${{ github.event.release.html_url }} is now released 🚀. #jwt #jwe #jws #jwk #jwa #php #symfony + env: + CONSUMER_API_KEY: ${{ secrets.TWITTER_CONSUMER_API_KEY }} + CONSUMER_API_SECRET_KEY: ${{ secrets.TWITTER_CONSUMER_API_SECRET }} + ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} + ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} diff --git a/.gitignore b/.gitignore index 888ade4c..287a6d9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,10 @@ .phpbench -.phpunit.result.cache jose.phar jose.phar.pubkey jose.phar.version report.md composer.lock -.php_cs -.php_cs.cache +.*.cache vendor/ src/Bundle/JoseFramework/var/ infection.txt diff --git a/composer.json b/composer.json index edc08fb7..42d01391 100644 --- a/composer.json +++ b/composer.json @@ -58,12 +58,12 @@ "ext-openssl": "*", "ext-sodium": "*", "brick/math": "^0.9|^0.10", - "fgrosse/phpasn1": "^2.0", "paragonie/constant_time_encoding": "^2.4", "psr/event-dispatcher": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", "spomky-labs/aes-key-wrap": "^7.0", + "spomky-labs/pki-framework": "^1.0", "symfony/config": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", @@ -89,6 +89,7 @@ "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.4", "phpunit/phpunit": "^9.5.23", + "qossmic/deptrac-shim": "^1.0", "rector/rector": "^0.14", "roave/security-advisories": "dev-latest", "symfony/browser-kit": "^6.1.3", diff --git a/deptrac.yaml b/deptrac.yaml new file mode 100644 index 00000000..f0df0272 --- /dev/null +++ b/deptrac.yaml @@ -0,0 +1,73 @@ +parameters: + paths: + - './src' + layers: + - name: 'Core' + collectors: + - { type: className, regex: '^Jose\\Component\\Core\\' } + - name: 'Checker' + collectors: + - { type: className, regex: '^Jose\\Component\\Checker\\' } + - name: 'Console' + collectors: + - { type: className, regex: '^Jose\\Component\\Console\\' } + - name: 'KeyManagement' + collectors: + - { type: className, regex: '^Jose\\Component\\KeyManagement\\' } + - name: 'NestedToken' + collectors: + - { type: className, regex: '^Jose\\Component\\NestedToken\\' } + - name: 'Encryption' + collectors: + - { type: className, regex: '^Jose\\Component\\Encryption\\' } + - name: 'Signature' + collectors: + - { type: className, regex: '^Jose\\Component\\Signature\\' } + - name: 'Bundle' + collectors: + - { type: className, regex: '^Jose\\Bundle\\JoseFramework\\' } + - name: 'Vendors' + collectors: + - { type: className, regex: '^Symfony\\' } + - { type: className, regex: '^SpomkyLabs\\Pki\\' } + - { type: className, regex: '^ParagonIE\\' } + - { type: className, regex: '^Psr\\EventDispatcher\\' } + - { type: className, regex: '^Psr\\Http\\' } + - { type: className, regex: '^Brick\\Math\\' } + - { type: className, regex: '^AESKW\\' } + - { type: className, regex: '^ZxcvbnPhp\\' } + ruleset: + Core: + - 'Vendors' + Checker: + - 'Core' + - 'Vendors' + Console: + - 'Core' + - 'KeyManagement' + - 'Vendors' + KeyManagement: + - 'Core' + - 'Vendors' + Encryption: + - 'Core' + - 'Checker' + - 'Vendors' + Signature: + - 'Core' + - 'Checker' + - 'Vendors' + NestedToken: + - 'Core' + - 'Encryption' + - 'Signature' + - 'Vendors' + Bundle: + - 'Core' + - 'Checker' + - 'Console' + - 'KeyManagement' + - 'Encryption' + - 'Signature' + - 'NestedToken' + - 'Vendors' diff --git a/ecs.php b/ecs.php index f1841703..ad413171 100644 --- a/ecs.php +++ b/ecs.php @@ -30,9 +30,8 @@ use Symplify\EasyCodingStandard\Config\ECSConfig; use Symplify\EasyCodingStandard\ValueObject\Set\SetList; -$header = ''; - -return static function (ECSConfig $config) use ($header): void { +return static function (ECSConfig $config): void { + $header = ''; $config->import(SetList::PSR_12); $config->import(SetList::CLEAN_CODE); $config->import(SetList::DOCTRINE_ANNOTATIONS); diff --git a/phpstan.neon b/phpstan.neon index 3a4ef9de..8f568b76 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,10 +5,10 @@ parameters: checkMissingIterableValueType: false checkGenericClassInNonGenericObjectType: false treatPhpDocTypesAsCertain: false + checkUninitializedProperties: true ignoreErrors: - '#Variable static method call on Jose\\Component\\Core\\Util\\Hash\.#' - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children\(\)#' - - '#Parameter \#1 \$value of class FG\\ASN1\\Universal\\Integer constructor expects int\, string given\.#' - '#Invalid type object to throw\.#' - '#Strict comparison using === between .* and .* will always evaluate to false\.#' - '#Binary operation "\^" between string and 1 results in an error\.#' @@ -19,3 +19,10 @@ parameters: - '#Cannot cast mixed to int\.#' - '#Parameter .* of (static )?method .* expects Jose\\Component\\Core\\JWK, mixed given\.#' - '#Cannot call method (get|has)\(\) on mixed\.#' +includes: + - vendor/phpstan/phpstan/conf/bleedingEdge.neon + - vendor/phpstan/phpstan-deprecation-rules/rules.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-strict-rules/rules.neon + - vendor/ekino/phpstan-banned-code/extension.neon diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSetSource/JWKSetSource.php b/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSetSource/JWKSetSource.php index 2a06bf42..b7c95220 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSetSource/JWKSetSource.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSetSource/JWKSetSource.php @@ -15,7 +15,7 @@ interface JWKSetSource * @param ContainerBuilder $container A ContainerBuilder instance * @param string $type The type of the service * @param string $id The id of the service - * @param array $config An array of configuration + * @param array $config An array of configuration */ public function create(ContainerBuilder $container, string $type, string $id, array $config): void; diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSource/JWKSource.php b/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSource/JWKSource.php index b9ec8c6c..10fbf5ec 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSource/JWKSource.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSource/JWKSource.php @@ -15,7 +15,7 @@ interface JWKSource * @param ContainerBuilder $container A ContainerBuilder instance * @param string $type The type of the service * @param string $id The id of the service - * @param array $config An array of configuration + * @param array $config An array of configuration */ public function create(ContainerBuilder $container, string $type, string $id, array $config): void; diff --git a/src/Component/Console/AddKeyIntoKeysetCommand.php b/src/Component/Console/AddKeyIntoKeysetCommand.php index 49e3a16f..2604b4bc 100644 --- a/src/Component/Console/AddKeyIntoKeysetCommand.php +++ b/src/Component/Console/AddKeyIntoKeysetCommand.php @@ -10,19 +10,20 @@ use Jose\Component\Core\JWK; use Jose\Component\Core\JWKSet; use Jose\Component\Core\Util\JsonConverter; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:add:key', description: 'Add a key into a key set.')] final class AddKeyIntoKeysetCommand extends ObjectOutputCommand { + protected static $defaultName = 'keyset:add:key'; + + protected static $defaultDescription = 'Add a key into a key set.'; + protected function configure(): void { parent::configure(); - $this - ->setHelp('This command adds a key at the end of a key set.') + $this->setHelp('This command adds a key at the end of a key set.') ->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object') ->addArgument('jwk', InputArgument::REQUIRED, 'The new JWK object'); } @@ -34,7 +35,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwkset = $jwkset->with($jwk); $this->prepareJsonOutput($input, $output, $jwkset); - return 0; + return self::SUCCESS; } private function getKeyset(InputInterface $input): JWKSet diff --git a/src/Component/Console/EcKeyGeneratorCommand.php b/src/Component/Console/EcKeyGeneratorCommand.php index ba6c381a..71eab830 100644 --- a/src/Component/Console/EcKeyGeneratorCommand.php +++ b/src/Component/Console/EcKeyGeneratorCommand.php @@ -7,20 +7,20 @@ use InvalidArgumentException; use function is_string; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:generate:ec', description: 'Generate an EC key (JWK format)')] final class EcKeyGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'key:generate:ec'; + + protected static $defaultDescription = 'Generate an EC key (JWK format)'; + protected function configure(): void { parent::configure(); - $this->setDescription('Generate an EC key (JWK format)') - ->addArgument('curve', InputArgument::REQUIRED, 'Curve of the key.') - ; + $this->addArgument('curve', InputArgument::REQUIRED, 'Curve of the key.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -34,6 +34,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createECKey($curve, $args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/EcKeysetGeneratorCommand.php b/src/Component/Console/EcKeysetGeneratorCommand.php index 366d2174..0bb058f7 100644 --- a/src/Component/Console/EcKeysetGeneratorCommand.php +++ b/src/Component/Console/EcKeysetGeneratorCommand.php @@ -8,19 +8,20 @@ use function is_string; use Jose\Component\Core\JWKSet; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:generate:ec', description: 'Generate an EC key set (JWKSet format)')] final class EcKeysetGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'keyset:generate:ec'; + + protected static $defaultDescription = 'Generate an EC key set (JWKSet format)'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of keys in the key set.') + $this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of keys in the key set.') ->addArgument('curve', InputArgument::REQUIRED, 'Curve of the keys.'); } @@ -42,6 +43,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->prepareJsonOutput($input, $output, $keyset); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/GetThumbprintCommand.php b/src/Component/Console/GetThumbprintCommand.php index d8075c31..a8d8eb64 100644 --- a/src/Component/Console/GetThumbprintCommand.php +++ b/src/Component/Console/GetThumbprintCommand.php @@ -9,20 +9,21 @@ use function is_string; use Jose\Component\Core\JWK; use Jose\Component\Core\Util\JsonConverter; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:thumbprint', description: 'Get the thumbprint of a JWK key.')] final class GetThumbprintCommand extends ObjectOutputCommand { + protected static $defaultName = 'key:thumbprint'; + + protected static $defaultDescription = 'Get the thumbprint of a JWK key.'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('jwk', InputArgument::REQUIRED, 'The JWK key.') + $this->addArgument('jwk', InputArgument::REQUIRED, 'The JWK key.') ->addOption('hash', null, InputOption::VALUE_OPTIONAL, 'The hashing algorithm.', 'sha256'); } @@ -43,6 +44,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $key = new JWK($json); $output->write($key->thumbprint($hash)); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/JKULoaderCommand.php b/src/Component/Console/JKULoaderCommand.php index 47b4f1a6..a2303db2 100644 --- a/src/Component/Console/JKULoaderCommand.php +++ b/src/Component/Console/JKULoaderCommand.php @@ -7,27 +7,28 @@ use InvalidArgumentException; use function is_string; use Jose\Component\KeyManagement\JKUFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:load:jku', description: 'Loads a key set from an url.')] final class JKULoaderCommand extends ObjectOutputCommand { + protected static $defaultName = 'keyset:load:jku'; + + protected static $defaultDescription = 'Loads a key set from an url.'; + public function __construct( private readonly JKUFactory $jkuFactory, + ?string $name = null ) { - parent::__construct(); + parent::__construct($name); } protected function configure(): void { parent::configure(); - $this - ->setHelp('This command will try to get a key set from an URL. The distant key set is a JWKSet.') - ->addArgument('url', InputArgument::REQUIRED, 'The URL') - ; + $this->setHelp('This command will try to get a key set from an URL. The distant key set is a JWKSet.') + ->addArgument('url', InputArgument::REQUIRED, 'The URL'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -39,6 +40,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $result = $this->jkuFactory->loadFromUrl($url); $this->prepareJsonOutput($input, $output, $result); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/KeyAnalyzerCommand.php b/src/Component/Console/KeyAnalyzerCommand.php index 04b88370..9c4bed0a 100644 --- a/src/Component/Console/KeyAnalyzerCommand.php +++ b/src/Component/Console/KeyAnalyzerCommand.php @@ -10,27 +10,29 @@ use Jose\Component\Core\JWK; use Jose\Component\Core\Util\JsonConverter; use Jose\Component\KeyManagement\Analyzer\KeyAnalyzerManager; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:analyze', description: 'JWK quality analyzer.')] final class KeyAnalyzerCommand extends Command { + protected static $defaultName = 'key:analyze'; + + protected static $defaultDescription = 'JWK quality analyzer.'; + public function __construct( private readonly KeyAnalyzerManager $analyzerManager, + string $name = null ) { - parent::__construct(); + parent::__construct($name); } protected function configure(): void { parent::configure(); - $this - ->setHelp('This command will analyze a JWK object and find security issues.') + $this->setHelp('This command will analyze a JWK object and find security issues.') ->addArgument('jwk', InputArgument::REQUIRED, 'The JWK object'); } @@ -57,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } - return 0; + return self::SUCCESS; } private function getKey(InputInterface $input): JWK diff --git a/src/Component/Console/KeyFileLoaderCommand.php b/src/Component/Console/KeyFileLoaderCommand.php index 2598c314..8c1a23ba 100644 --- a/src/Component/Console/KeyFileLoaderCommand.php +++ b/src/Component/Console/KeyFileLoaderCommand.php @@ -7,20 +7,21 @@ use InvalidArgumentException; use function is_string; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:load:key', description: 'Loads a key from a key file (JWK format)')] final class KeyFileLoaderCommand extends GeneratorCommand { + protected static $defaultName = 'key:load:key'; + + protected static $defaultDescription = 'Loads a key from a key file (JWK format)'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('file', InputArgument::REQUIRED, 'Filename of the key.') + $this->addArgument('file', InputArgument::REQUIRED, 'Filename of the key.') ->addOption('secret', 's', InputOption::VALUE_OPTIONAL, 'Secret if the key is encrypted.', null); } @@ -39,6 +40,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createFromKeyFile($file, $password, $args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/KeysetAnalyzerCommand.php b/src/Component/Console/KeysetAnalyzerCommand.php index e26778bb..d33e8ce4 100644 --- a/src/Component/Console/KeysetAnalyzerCommand.php +++ b/src/Component/Console/KeysetAnalyzerCommand.php @@ -12,28 +12,30 @@ use Jose\Component\KeyManagement\Analyzer\KeyAnalyzerManager; use Jose\Component\KeyManagement\Analyzer\KeysetAnalyzerManager; use Jose\Component\KeyManagement\Analyzer\MessageBag; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:analyze', description: 'JWKSet quality analyzer.')] final class KeysetAnalyzerCommand extends Command { + protected static $defaultName = 'keyset:analyze'; + + protected static $defaultDescription = 'JWKSet quality analyzer.'; + public function __construct( private readonly KeysetAnalyzerManager $keysetAnalyzerManager, - private readonly KeyAnalyzerManager $keyAnalyzerManager + private readonly KeyAnalyzerManager $keyAnalyzerManager, + string $name = null ) { - parent::__construct(); + parent::__construct($name); } protected function configure(): void { parent::configure(); - $this - ->setHelp('This command will analyze a JWKSet object and find security issues.') + $this->setHelp('This command will analyze a JWKSet object and find security issues.') ->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object'); } @@ -58,7 +60,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->showMessages($messages, $output); } - return 0; + return self::SUCCESS; } private function showMessages(MessageBag $messages, OutputInterface $output): void diff --git a/src/Component/Console/MergeKeysetCommand.php b/src/Component/Console/MergeKeysetCommand.php index 25dd2906..4e692703 100644 --- a/src/Component/Console/MergeKeysetCommand.php +++ b/src/Component/Console/MergeKeysetCommand.php @@ -8,21 +8,22 @@ use function is_array; use Jose\Component\Core\JWKSet; use Jose\Component\Core\Util\JsonConverter; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:merge', description: 'Merge several key sets into one.')] final class MergeKeysetCommand extends ObjectOutputCommand { + protected static $defaultName = 'keyset:merge'; + + protected static $defaultDescription = 'Merge several key sets into one.'; + protected function configure(): void { parent::configure(); - $this - ->setHelp( - 'This command merges several key sets into one. It is very useful when you generate e.g. RSA, EC and OKP keys and you want only one key set to rule them all.' - ) + $this->setHelp( + 'This command merges several key sets into one. It is very useful when you generate e.g. RSA, EC and OKP keys and you want only one key set to rule them all.' + ) ->addArgument('jwksets', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'The JWKSet objects'); } @@ -43,6 +44,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->prepareJsonOutput($input, $output, $newJwkset); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/NoneKeyGeneratorCommand.php b/src/Component/Console/NoneKeyGeneratorCommand.php index 5f23c0b8..0b00c776 100644 --- a/src/Component/Console/NoneKeyGeneratorCommand.php +++ b/src/Component/Console/NoneKeyGeneratorCommand.php @@ -5,16 +5,20 @@ namespace Jose\Component\Console; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand( - name: 'key:generate:none', - description: 'Generate a none key (JWK format). This key type is only supposed to be used with the "none" algorithm.', -)] final class NoneKeyGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'key:generate:none'; + + protected static $defaultDescription = 'Generate a none key (JWK format). This key type is only supposed to be used with the "none" algorithm.'; + + protected function configure(): void + { + parent::configure(); + } + protected function execute(InputInterface $input, OutputInterface $output): int { $args = $this->getOptions($input); @@ -22,6 +26,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createNoneKey($args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/OctKeyGeneratorCommand.php b/src/Component/Console/OctKeyGeneratorCommand.php index ccef3772..499bb561 100644 --- a/src/Component/Console/OctKeyGeneratorCommand.php +++ b/src/Component/Console/OctKeyGeneratorCommand.php @@ -6,19 +6,20 @@ use InvalidArgumentException; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:generate:oct', description: 'Generate an octet key (JWK format)')] final class OctKeyGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'key:generate:oct'; + + protected static $defaultDescription = 'Generate an octet key (JWK format)'; + protected function configure(): void { parent::configure(); - $this->setDescription('Generate an octet key (JWK format)') - ->addArgument('size', InputArgument::REQUIRED, 'Key size.'); + $this->addArgument('size', InputArgument::REQUIRED, 'Key size.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -32,6 +33,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createOctKey($size, $args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/OctKeysetGeneratorCommand.php b/src/Component/Console/OctKeysetGeneratorCommand.php index ed74ce23..61156528 100644 --- a/src/Component/Console/OctKeysetGeneratorCommand.php +++ b/src/Component/Console/OctKeysetGeneratorCommand.php @@ -7,19 +7,20 @@ use InvalidArgumentException; use Jose\Component\Core\JWKSet; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:generate:oct', description: 'Generate a key set with octet keys (JWK format)')] final class OctKeysetGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'keyset:generate:oct'; + + protected static $defaultDescription = 'Generate a key set with octet keys (JWK format)'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of keys in the key set.') + $this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of keys in the key set.') ->addArgument('size', InputArgument::REQUIRED, 'Key size.'); } @@ -41,6 +42,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->prepareJsonOutput($input, $output, $keyset); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/OkpKeyGeneratorCommand.php b/src/Component/Console/OkpKeyGeneratorCommand.php index bfcc9a8c..2dc947ba 100644 --- a/src/Component/Console/OkpKeyGeneratorCommand.php +++ b/src/Component/Console/OkpKeyGeneratorCommand.php @@ -7,19 +7,20 @@ use InvalidArgumentException; use function is_string; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:generate:okp', description: 'Generate an Octet Key Pair key (JWK format)')] final class OkpKeyGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'key:generate:okp'; + + protected static $defaultDescription = 'Generate an Octet Key Pair key (JWK format)'; + protected function configure(): void { parent::configure(); - $this->setDescription('Generate an Octet Key Pair key (JWK format)') - ->addArgument('curve', InputArgument::REQUIRED, 'Curve of the key.'); + $this->addArgument('curve', InputArgument::REQUIRED, 'Curve of the key.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -33,6 +34,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createOKPKey($curve, $args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/OkpKeysetGeneratorCommand.php b/src/Component/Console/OkpKeysetGeneratorCommand.php index b45b2658..4d78fe24 100644 --- a/src/Component/Console/OkpKeysetGeneratorCommand.php +++ b/src/Component/Console/OkpKeysetGeneratorCommand.php @@ -8,22 +8,20 @@ use function is_string; use Jose\Component\Core\JWKSet; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand( - name: 'keyset:generate:okp', - description: 'Generate a key set with Octet Key Pairs keys (JWKSet format)', -)] final class OkpKeysetGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'keyset:generate:okp'; + + protected static $defaultDescription = 'Generate a key set with Octet Key Pairs keys (JWKSet format)'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of keys in the key set.') + $this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of keys in the key set.') ->addArgument('curve', InputArgument::REQUIRED, 'Curve of the keys.'); } @@ -45,6 +43,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->prepareJsonOutput($input, $output, $keyset); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/OptimizeRsaKeyCommand.php b/src/Component/Console/OptimizeRsaKeyCommand.php index 0210f393..bd2a9da6 100644 --- a/src/Component/Console/OptimizeRsaKeyCommand.php +++ b/src/Component/Console/OptimizeRsaKeyCommand.php @@ -10,20 +10,20 @@ use Jose\Component\Core\JWK; use Jose\Component\Core\Util\JsonConverter; use Jose\Component\KeyManagement\KeyConverter\RSAKey; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:optimize', description: 'Optimize a RSA key by calculating additional primes (CRT).')] final class OptimizeRsaKeyCommand extends ObjectOutputCommand { + protected static $defaultName = 'key:optimize'; + + protected static $defaultDescription = 'Optimize a RSA key by calculating additional primes (CRT).'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('jwk', InputArgument::REQUIRED, 'The RSA key.') - ; + $this->addArgument('jwk', InputArgument::REQUIRED, 'The RSA key.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -40,6 +40,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $key->optimize(); $this->prepareJsonOutput($input, $output, $key->toJwk()); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/P12CertificateLoaderCommand.php b/src/Component/Console/P12CertificateLoaderCommand.php index 3c9ab4f5..065c3a4a 100644 --- a/src/Component/Console/P12CertificateLoaderCommand.php +++ b/src/Component/Console/P12CertificateLoaderCommand.php @@ -7,20 +7,21 @@ use InvalidArgumentException; use function is_string; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:load:p12', description: 'Load a key from a P12 certificate file.')] final class P12CertificateLoaderCommand extends GeneratorCommand { + protected static $defaultName = 'key:load:p12'; + + protected static $defaultDescription = 'Load a key from a P12 certificate file.'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('file', InputArgument::REQUIRED, 'Filename of the P12 certificate.') + $this->addArgument('file', InputArgument::REQUIRED, 'Filename of the P12 certificate.') ->addOption('secret', 's', InputOption::VALUE_OPTIONAL, 'Secret if the key is encrypted.', null); } @@ -38,6 +39,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createFromPKCS12CertificateFile($file, $password, $args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/PemConverterCommand.php b/src/Component/Console/PemConverterCommand.php index 0ad02bda..f73b5071 100644 --- a/src/Component/Console/PemConverterCommand.php +++ b/src/Component/Console/PemConverterCommand.php @@ -11,20 +11,20 @@ use Jose\Component\Core\Util\ECKey; use Jose\Component\Core\Util\JsonConverter; use Jose\Component\Core\Util\RSAKey; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:convert:pkcs1', description: 'Converts a RSA or EC key into PKCS#1 key.')] final class PemConverterCommand extends ObjectOutputCommand { + protected static $defaultName = 'key:convert:pkcs1'; + + protected static $defaultDescription = 'Converts a RSA or EC key into PKCS#1 key.'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('jwk', InputArgument::REQUIRED, 'The key') - ; + $this->addArgument('jwk', InputArgument::REQUIRED, 'The key'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -46,6 +46,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int }; $output->write($pem); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/PublicKeyCommand.php b/src/Component/Console/PublicKeyCommand.php index 6d395f13..8d593ddc 100644 --- a/src/Component/Console/PublicKeyCommand.php +++ b/src/Component/Console/PublicKeyCommand.php @@ -9,22 +9,20 @@ use function is_string; use Jose\Component\Core\JWK; use Jose\Component\Core\Util\JsonConverter; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand( - name: 'key:convert:public', - description: 'Convert a private key into public key. Symmetric keys (shared keys) are not changed.', -)] final class PublicKeyCommand extends ObjectOutputCommand { + protected static $defaultName = 'key:convert:public'; + + protected static $defaultDescription = 'Convert a private key into public key. Symmetric keys (shared keys) are not changed.'; + protected function configure(): void { parent::configure(); - $this - ->setHelp('This command converts a private key into a public key.') + $this->setHelp('This command converts a private key into a public key.') ->addArgument('jwk', InputArgument::REQUIRED, 'The JWK object'); } @@ -35,7 +33,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } private function getKey(InputInterface $input): JWK diff --git a/src/Component/Console/PublicKeysetCommand.php b/src/Component/Console/PublicKeysetCommand.php index 0c89a672..1b2a537a 100644 --- a/src/Component/Console/PublicKeysetCommand.php +++ b/src/Component/Console/PublicKeysetCommand.php @@ -9,22 +9,20 @@ use function is_string; use Jose\Component\Core\JWKSet; use Jose\Component\Core\Util\JsonConverter; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand( - name: 'keyset:convert:public', - description: 'Convert private keys in a key set into public keys. Symmetric keys (shared keys) are not changed.', -)] final class PublicKeysetCommand extends ObjectOutputCommand { + protected static $defaultName = 'keyset:convert:public'; + + protected static $defaultDescription = 'Convert private keys in a key set into public keys. Symmetric keys (shared keys) are not changed.'; + protected function configure(): void { parent::configure(); - $this - ->setHelp('This command converts private keys in a key set into public keys.') + $this->setHelp('This command converts private keys in a key set into public keys.') ->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object'); } @@ -38,7 +36,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->prepareJsonOutput($input, $output, $newJwkset); - return 0; + return self::SUCCESS; } private function getKeyset(InputInterface $input): JWKSet diff --git a/src/Component/Console/RotateKeysetCommand.php b/src/Component/Console/RotateKeysetCommand.php index 966e39a9..d5e9c4f4 100644 --- a/src/Component/Console/RotateKeysetCommand.php +++ b/src/Component/Console/RotateKeysetCommand.php @@ -11,19 +11,20 @@ use Jose\Component\Core\JWK; use Jose\Component\Core\JWKSet; use Jose\Component\Core\Util\JsonConverter; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:rotate', description: 'Rotate a key set.')] final class RotateKeysetCommand extends ObjectOutputCommand { + protected static $defaultName = 'keyset:rotate'; + + protected static $defaultDescription = 'Rotate a key set.'; + protected function configure(): void { parent::configure(); - $this - ->setHelp('This command removes the last key in a key set a place a new one at the beginning.') + $this->setHelp('This command removes the last key in a key set a place a new one at the beginning.') ->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object') ->addArgument('jwk', InputArgument::REQUIRED, 'The new JWK object'); } diff --git a/src/Component/Console/RsaKeyGeneratorCommand.php b/src/Component/Console/RsaKeyGeneratorCommand.php index b8646442..2dcebdce 100644 --- a/src/Component/Console/RsaKeyGeneratorCommand.php +++ b/src/Component/Console/RsaKeyGeneratorCommand.php @@ -6,20 +6,20 @@ use InvalidArgumentException; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:generate:rsa', description: 'Generate a RSA key (JWK format)')] final class RsaKeyGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'key:generate:rsa'; + + protected static $defaultDescription = 'Generate a RSA key (JWK format)'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('size', InputArgument::REQUIRED, 'Key size.') - ; + $this->addArgument('size', InputArgument::REQUIRED, 'Key size.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -33,6 +33,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createRSAKey($size, $args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/RsaKeysetGeneratorCommand.php b/src/Component/Console/RsaKeysetGeneratorCommand.php index a36d069f..612bfcb5 100644 --- a/src/Component/Console/RsaKeysetGeneratorCommand.php +++ b/src/Component/Console/RsaKeysetGeneratorCommand.php @@ -7,19 +7,20 @@ use InvalidArgumentException; use Jose\Component\Core\JWKSet; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:generate:rsa', description: 'Generate a key set with RSA keys (JWK format)')] final class RsaKeysetGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'keyset:generate:rsa'; + + protected static $defaultDescription = 'Generate a key set with RSA keys (JWK format)'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of keys in the key set.') + $this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of keys in the key set.') ->addArgument('size', InputArgument::REQUIRED, 'Key size.'); } @@ -41,6 +42,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->prepareJsonOutput($input, $output, $keyset); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/SecretKeyGeneratorCommand.php b/src/Component/Console/SecretKeyGeneratorCommand.php index 17d16c83..3f50630e 100644 --- a/src/Component/Console/SecretKeyGeneratorCommand.php +++ b/src/Component/Console/SecretKeyGeneratorCommand.php @@ -8,23 +8,21 @@ use function is_bool; use function is_string; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand( - name: 'key:generate:from_secret', - description: 'Generate an octet key (JWK format) using an existing secret', -)] final class SecretKeyGeneratorCommand extends GeneratorCommand { + protected static $defaultName = 'key:generate:from_secret'; + + protected static $defaultDescription = 'Generate an octet key (JWK format) using an existing secret'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('secret', InputArgument::REQUIRED, 'The secret') + $this->addArgument('secret', InputArgument::REQUIRED, 'The secret') ->addOption( 'is_b64', 'b', @@ -54,6 +52,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createFromSecret($secret, $args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/X509CertificateLoaderCommand.php b/src/Component/Console/X509CertificateLoaderCommand.php index e2570def..afeaad56 100644 --- a/src/Component/Console/X509CertificateLoaderCommand.php +++ b/src/Component/Console/X509CertificateLoaderCommand.php @@ -7,20 +7,20 @@ use InvalidArgumentException; use function is_string; use Jose\Component\KeyManagement\JWKFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'key:load:x509', description: 'Load a key from a X.509 certificate file.')] final class X509CertificateLoaderCommand extends GeneratorCommand { + protected static $defaultName = 'key:load:x509'; + + protected static $defaultDescription = 'Load a key from a X.509 certificate file.'; + protected function configure(): void { parent::configure(); - $this - ->addArgument('file', InputArgument::REQUIRED, 'Filename of the X.509 certificate.') - ; + $this->addArgument('file', InputArgument::REQUIRED, 'Filename of the X.509 certificate.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -40,6 +40,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jwk = JWKFactory::createFromCertificateFile($file, $args); $this->prepareJsonOutput($input, $output, $jwk); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Console/X5ULoaderCommand.php b/src/Component/Console/X5ULoaderCommand.php index 2971beab..2c689760 100644 --- a/src/Component/Console/X5ULoaderCommand.php +++ b/src/Component/Console/X5ULoaderCommand.php @@ -7,27 +7,29 @@ use InvalidArgumentException; use function is_string; use Jose\Component\KeyManagement\X5UFactory; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'keyset:load:x5u', description: 'Loads a key set from an url.')] final class X5ULoaderCommand extends ObjectOutputCommand { + protected static $defaultName = 'keyset:load:x5u'; + + protected static $defaultDescription = 'Loads a key set from an url.'; + public function __construct( - private readonly X5UFactory $x5uFactory + private readonly X5UFactory $x5uFactory, + ?string $name = null ) { - parent::__construct(); + parent::__construct($name); } protected function configure(): void { parent::configure(); - $this - ->setHelp( - 'This command will try to get a key set from an URL. The distant key set is list of X.509 certificates.' - ) + $this->setHelp( + 'This command will try to get a key set from an URL. The distant key set is list of X.509 certificates.' + ) ->addArgument('url', InputArgument::REQUIRED, 'The URL'); } @@ -40,6 +42,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int $result = $this->x5uFactory->loadFromUrl($url); $this->prepareJsonOutput($input, $output, $result); - return 0; + return self::SUCCESS; } } diff --git a/src/Component/Core/JWKSet.php b/src/Component/Core/JWKSet.php index 3261c9c2..b9d3472c 100644 --- a/src/Component/Core/JWKSet.php +++ b/src/Component/Core/JWKSet.php @@ -166,7 +166,7 @@ public function count($mode = COUNT_NORMAL): int * * @param string $type Must be 'sig' (signature) or 'enc' (encryption) * @param Algorithm|null $algorithm Specifies the algorithm to be used - * @param array $restrictions More restrictions such as 'kid' or 'kty' + * @param array $restrictions More restrictions such as 'kid' or 'kty' */ public function selectKey(string $type, ?Algorithm $algorithm = null, array $restrictions = []): ?JWK { diff --git a/src/Component/Core/Util/RSAKey.php b/src/Component/Core/Util/RSAKey.php index bc717f16..d207e3d6 100644 --- a/src/Component/Core/Util/RSAKey.php +++ b/src/Component/Core/Util/RSAKey.php @@ -4,29 +4,30 @@ namespace Jose\Component\Core\Util; +use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence; +use SpomkyLabs\Pki\ASN1\Type\Primitive\BitString; +use SpomkyLabs\Pki\ASN1\Type\Primitive\Integer; +use SpomkyLabs\Pki\ASN1\Type\Primitive\NullType; +use SpomkyLabs\Pki\ASN1\Type\Primitive\ObjectIdentifier; +use SpomkyLabs\Pki\ASN1\Type\Primitive\OctetString; +use SpomkyLabs\Pki\CryptoEncoding\PEM; +use SpomkyLabs\Pki\CryptoTypes\AlgorithmIdentifier\Asymmetric\RSAEncryptionAlgorithmIdentifier; use function array_key_exists; use function count; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\NullObject; -use FG\ASN1\Universal\ObjectIdentifier; -use FG\ASN1\Universal\OctetString; -use FG\ASN1\Universal\Sequence; use InvalidArgumentException; use function is_array; use Jose\Component\Core\JWK; use ParagonIE\ConstantTime\Base64UrlSafe; -use const PHP_EOL; use RuntimeException; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\RSA\RSAPrivateKey; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\RSA\RSAPublicKey; /** * @internal */ final class RSAKey { - private Sequence $sequence; - - private readonly bool $private; + private null|Sequence $sequence = null; private readonly array $values; @@ -52,10 +53,8 @@ final class RSAKey private function __construct(JWK $data) { - $this->sequence = new Sequence(); $this->values = $data->all(); $this->populateBigIntegers(); - $this->private = array_key_exists('d', $this->values); } public static function createFromJWK(JWK $jwk): self @@ -139,16 +138,39 @@ public function toArray(): array public function toPEM(): string { - $this->sequence = new Sequence(); if (array_key_exists('d', $this->values)) { - $this->initPrivateKey(); - } else { - $this->initPublicKey(); + $this->sequence = Sequence::create( + Integer::create(0), + RSAEncryptionAlgorithmIdentifier::create()->toASN1(), + OctetString::create( + RSAPrivateKey::create( + $this->fromBase64ToInteger($this->values['n']), + $this->fromBase64ToInteger($this->values['e']), + $this->fromBase64ToInteger($this->values['d']), + isset($this->values['p']) ? $this->fromBase64ToInteger($this->values['p']) : '0', + isset($this->values['q']) ? $this->fromBase64ToInteger($this->values['q']) : '0', + isset($this->values['dp']) ? $this->fromBase64ToInteger($this->values['dp']) : '0', + isset($this->values['dq']) ? $this->fromBase64ToInteger($this->values['dq']) : '0', + isset($this->values['qi']) ? $this->fromBase64ToInteger($this->values['qi']) : '0', + )->toDER() + ) + ); + + return PEM::create(PEM::TYPE_RSA_PRIVATE_KEY, $this->sequence->toDER()) + ->string(); } - $result = '-----BEGIN ' . ($this->private ? 'RSA PRIVATE' : 'PUBLIC') . ' KEY-----' . PHP_EOL; - $result .= chunk_split(base64_encode($this->sequence->getBinary()), 64, PHP_EOL); + $this->sequence = Sequence::create( + RSAEncryptionAlgorithmIdentifier::create()->toASN1(), + BitString::create( + RSAPublicKey::create( + $this->fromBase64ToInteger($this->values['n']), + $this->fromBase64ToInteger($this->values['e']) + )->toDER() + ) + ); - return $result . ('-----END ' . ($this->private ? 'RSA PRIVATE' : 'PUBLIC') . ' KEY-----' . PHP_EOL); + return PEM::create(PEM::TYPE_RSA_PUBLIC_KEY, $this->sequence->toDER()) + ->string(); } /** @@ -160,8 +182,8 @@ public static function exponentiate(self $key, BigInteger $c): BigInteger throw new RuntimeException(); } if ($key->isPublic() || $key->getCoefficient() === null || count($key->getPrimes()) === 0 || count( - $key->getExponents() - ) === 0) { + $key->getExponents() + ) === 0) { return $c->modPow($key->getExponent(), $key->getModulus()); } @@ -194,9 +216,9 @@ private function populateBigIntegers(): void $this->convertBase64StringToBigInteger($this->values['q']), ]; if (array_key_exists('dp', $this->values) && array_key_exists('dq', $this->values) && array_key_exists( - 'qi', - $this->values - )) { + 'qi', + $this->values + )) { $this->exponents = [ $this->convertBase64StringToBigInteger($this->values['dp']), $this->convertBase64StringToBigInteger($this->values['dq']), @@ -212,57 +234,6 @@ private function convertBase64StringToBigInteger(string $value): BigInteger return BigInteger::createFromBinaryString(Base64UrlSafe::decode($value)); } - private function initPublicKey(): void - { - $oid_sequence = new Sequence(); - $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1')); - $oid_sequence->addChild(new NullObject()); - $this->sequence->addChild($oid_sequence); - $n = new Integer($this->fromBase64ToInteger($this->values['n'])); - $e = new Integer($this->fromBase64ToInteger($this->values['e'])); - $key_sequence = new Sequence(); - $key_sequence->addChild($n); - $key_sequence->addChild($e); - $key_bit_string = new BitString(bin2hex($key_sequence->getBinary())); - $this->sequence->addChild($key_bit_string); - } - - private function initPrivateKey(): void - { - $this->sequence->addChild(new Integer(0)); - $oid_sequence = new Sequence(); - $oid_sequence->addChild(new ObjectIdentifier('1.2.840.113549.1.1.1')); - $oid_sequence->addChild(new NullObject()); - $this->sequence->addChild($oid_sequence); - $v = new Integer(0); - $n = new Integer($this->fromBase64ToInteger($this->values['n'])); - $e = new Integer($this->fromBase64ToInteger($this->values['e'])); - $d = new Integer($this->fromBase64ToInteger($this->values['d'])); - $p = new Integer($this->fromBase64ToInteger($this->values['p'])); - $q = new Integer($this->fromBase64ToInteger($this->values['q'])); - $dp = array_key_exists('dp', $this->values) ? new Integer($this->fromBase64ToInteger( - $this->values['dp'] - )) : new Integer(0); - $dq = array_key_exists('dq', $this->values) ? new Integer($this->fromBase64ToInteger( - $this->values['dq'] - )) : new Integer(0); - $qi = array_key_exists('qi', $this->values) ? new Integer($this->fromBase64ToInteger( - $this->values['qi'] - )) : new Integer(0); - $key_sequence = new Sequence(); - $key_sequence->addChild($v); - $key_sequence->addChild($n); - $key_sequence->addChild($e); - $key_sequence->addChild($d); - $key_sequence->addChild($p); - $key_sequence->addChild($q); - $key_sequence->addChild($dp); - $key_sequence->addChild($dq); - $key_sequence->addChild($qi); - $key_octet_string = new OctetString(bin2hex($key_sequence->getBinary())); - $this->sequence->addChild($key_octet_string); - } - private function fromBase64ToInteger(string $value): string { $unpacked = unpack('H*', Base64UrlSafe::decode($value)); diff --git a/src/Component/Core/composer.json b/src/Component/Core/composer.json index 0867fe52..4911aaa0 100644 --- a/src/Component/Core/composer.json +++ b/src/Component/Core/composer.json @@ -42,8 +42,8 @@ "ext-json": "*", "ext-mbstring": "*", "brick/math": "^0.9|^0.10", - "fgrosse/phpasn1": "^2.0", - "paragonie/constant_time_encoding": "^2.4" + "paragonie/constant_time_encoding": "^2.4", + "spomky-labs/pki-framework": "^1.0" }, "conflict": { "spomky-labs/jose": "*" diff --git a/src/Component/Encryption/Algorithm/KeyEncryption/KeyAgreement.php b/src/Component/Encryption/Algorithm/KeyEncryption/KeyAgreement.php index 49c27bda..270ffa66 100644 --- a/src/Component/Encryption/Algorithm/KeyEncryption/KeyAgreement.php +++ b/src/Component/Encryption/Algorithm/KeyEncryption/KeyAgreement.php @@ -11,6 +11,9 @@ interface KeyAgreement extends KeyEncryptionAlgorithm { /** * Computes the agreement key. + * + * @param array $completeHeader + * @param array $additionalHeaderValues */ public function getAgreementKey( int $encryptionKeyLength, diff --git a/src/Component/Encryption/Algorithm/KeyEncryption/KeyAgreementWithKeyWrapping.php b/src/Component/Encryption/Algorithm/KeyEncryption/KeyAgreementWithKeyWrapping.php index 0dfe050a..d6908edc 100644 --- a/src/Component/Encryption/Algorithm/KeyEncryption/KeyAgreementWithKeyWrapping.php +++ b/src/Component/Encryption/Algorithm/KeyEncryption/KeyAgreementWithKeyWrapping.php @@ -15,8 +15,8 @@ interface KeyAgreementWithKeyWrapping extends KeyEncryptionAlgorithm * @param JWK $recipientKey The receiver's key * @param string $cek The CEK to wrap * @param int $encryption_key_length Size of the key expected for the algorithm used for data encryption - * @param array $complete_header The complete header of the JWT - * @param array $additional_header_values Set additional header values if needed + * @param array $complete_header The complete header of the JWT + * @param array $additional_header_values Set additional header values if needed */ public function wrapAgreementKey( JWK $recipientKey, @@ -33,7 +33,7 @@ public function wrapAgreementKey( * @param JWK $recipientKey The receiver's key * @param string $encrypted_cek The encrypted CEK * @param int $encryption_key_length Size of the key expected for the algorithm used for data encryption - * @param array $complete_header The complete header of the JWT + * @param array $complete_header The complete header of the JWT * * @return string The decrypted CEK */ diff --git a/src/Component/Encryption/Algorithm/KeyEncryption/KeyEncryption.php b/src/Component/Encryption/Algorithm/KeyEncryption/KeyEncryption.php index 0b21ef1d..f27f7fca 100644 --- a/src/Component/Encryption/Algorithm/KeyEncryption/KeyEncryption.php +++ b/src/Component/Encryption/Algorithm/KeyEncryption/KeyEncryption.php @@ -14,8 +14,8 @@ interface KeyEncryption extends KeyEncryptionAlgorithm * * @param JWK $key The key used to wrap the CEK * @param string $cek The CEK to encrypt - * @param array $completeHeader The complete header of the JWT - * @param array $additionalHeader Additional header + * @param array $completeHeader The complete header of the JWT + * @param array $additionalHeader Additional header */ public function encryptKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string; @@ -24,7 +24,7 @@ public function encryptKey(JWK $key, string $cek, array $completeHeader, array & * * @param JWK $key The key used to wrap the CEK * @param string $encrypted_cek The CEK to decrypt - * @param array $header The complete header of the JWT + * @param array $header The complete header of the JWT */ public function decryptKey(JWK $key, string $encrypted_cek, array $header): string; } diff --git a/src/Component/Encryption/Algorithm/KeyEncryption/KeyWrapping.php b/src/Component/Encryption/Algorithm/KeyEncryption/KeyWrapping.php index d4f42bf5..e5b37825 100644 --- a/src/Component/Encryption/Algorithm/KeyEncryption/KeyWrapping.php +++ b/src/Component/Encryption/Algorithm/KeyEncryption/KeyWrapping.php @@ -14,8 +14,8 @@ interface KeyWrapping extends KeyEncryptionAlgorithm * * @param JWK $key The key used to wrap the CEK * @param string $cek The CEK to encrypt - * @param array $completeHeader The complete header of the JWT - * @param array $additionalHeader The complete header of the JWT + * @param array $completeHeader The complete header of the JWT + * @param array $additionalHeader The complete header of the JWT */ public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string; @@ -24,7 +24,7 @@ public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$ad * * @param JWK $key The key used to wrap the CEK * @param string $encrypted_cek The CEK to decrypt - * @param array $completeHeader The complete header of the JWT + * @param array $completeHeader The complete header of the JWT */ public function unwrapKey(JWK $key, string $encrypted_cek, array $completeHeader): string; } diff --git a/src/Component/KeyManagement/JWKFactory.php b/src/Component/KeyManagement/JWKFactory.php index 88e483d5..01d9468e 100644 --- a/src/Component/KeyManagement/JWKFactory.php +++ b/src/Component/KeyManagement/JWKFactory.php @@ -30,7 +30,7 @@ class JWKFactory * Creates a RSA key with the given key size and additional values. * * @param int $size The key size in bits - * @param array $values values to configure the key + * @param array $values values to configure the key */ public static function createRSAKey(int $size, array $values = []): JWK { @@ -62,7 +62,7 @@ public static function createRSAKey(int $size, array $values = []): JWK * Creates a EC key with the given curve and additional values. * * @param string $curve The curve - * @param array $values values to configure the key + * @param array $values values to configure the key */ public static function createECKey(string $curve, array $values = []): JWK { @@ -73,7 +73,7 @@ public static function createECKey(string $curve, array $values = []): JWK * Creates a octet key with the given key size and additional values. * * @param int $size The key size in bits - * @param array $values values to configure the key + * @param array $values values to configure the key */ public static function createOctKey(int $size, array $values = []): JWK { @@ -95,7 +95,7 @@ public static function createOctKey(int $size, array $values = []): JWK * Creates a OKP key with the given curve and additional values. * * @param string $curve The curve - * @param array $values values to configure the key + * @param array $values values to configure the key */ public static function createOKPKey(string $curve, array $values = []): JWK { @@ -141,7 +141,7 @@ public static function createOKPKey(string $curve, array $values = []): JWK * Creates a none key with the given additional values. Please note that this key type is not pat of any * specification. It is used to prevent the use of the "none" algorithm with other key types. * - * @param array $values values to configure the key + * @param array $values values to configure the key */ public static function createNoneKey(array $values = []): JWK { @@ -228,14 +228,14 @@ public static function createFromPKCS12CertificateFile( throw new RuntimeException('Unable to read the file.'); } openssl_pkcs12_read($content, $certs, $secret); + if (! is_array($certs) || ! array_key_exists('pkey', $certs)) { + throw new RuntimeException('Unable to load the certificates.'); + } + + return self::createFromKey($certs['pkey'], null, $additional_values); } catch (Throwable $throwable) { throw new RuntimeException('Unable to load the certificates.', $throwable->getCode(), $throwable); } - if (! is_array($certs) || ! array_key_exists('pkey', $certs)) { - throw new RuntimeException('Unable to load the certificates.'); - } - - return self::createFromKey($certs['pkey'], null, $additional_values); } /** diff --git a/src/Component/KeyManagement/KeyConverter/ECKey.php b/src/Component/KeyManagement/KeyConverter/ECKey.php index 3c80d034..cac0832c 100644 --- a/src/Component/KeyManagement/KeyConverter/ECKey.php +++ b/src/Component/KeyManagement/KeyConverter/ECKey.php @@ -5,18 +5,13 @@ namespace Jose\Component\KeyManagement\KeyConverter; use function array_key_exists; -use function count; -use FG\ASN1\ASNObject; -use FG\ASN1\ExplicitlyTaggedObject; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\ObjectIdentifier; -use FG\ASN1\Universal\OctetString; -use FG\ASN1\Universal\Sequence; use InvalidArgumentException; -use function is_array; use function is_string; use ParagonIE\ConstantTime\Base64UrlSafe; +use SpomkyLabs\Pki\CryptoEncoding\PEM; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\EC\ECPrivateKey; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\EC\ECPublicKey; +use Throwable; /** * @internal @@ -57,171 +52,32 @@ public function toArray() private static function loadPEM(string $data): array { - $data = base64_decode(preg_replace('#-.*-|\r|\n#', '', $data) ?? '', true); - $asnObject = ASNObject::fromBinary($data); - if (! $asnObject instanceof Sequence) { - throw new InvalidArgumentException('Unable to load the key.'); + $pem = PEM::fromString($data); + try { + $key = ECPrivateKey::fromPEM($pem); + + return [ + 'kty' => 'EC', + 'crv' => self::getCurve($key->namedCurve()), + 'd' => Base64UrlSafe::encodeUnpadded($key->privateKeyOctets()), + 'x' => Base64UrlSafe::encodeUnpadded($key->publicKey()->curvePointOctets()[0]), + 'y' => Base64UrlSafe::encodeUnpadded($key->publicKey()->curvePointOctets()[1]), + ]; + } catch (Throwable) { + } + try { + $key = ECPublicKey::fromPEM($pem); + return [ + 'kty' => 'EC', + 'crv' => self::getCurve($key->namedCurve()), + 'x' => Base64UrlSafe::encodeUnpadded($key->curvePointOctets()[0]), + 'y' => Base64UrlSafe::encodeUnpadded($key->curvePointOctets()[1]), + ]; + } catch (Throwable) { } - $children = $asnObject->getChildren(); - if (self::isPKCS8($children)) { - return self::loadPKCS8($children); - } - - if (count($children) === 4) { - return self::loadPrivatePEM($children); - } - if (count($children) === 2) { - return self::loadPublicPEM($children); - } - throw new InvalidArgumentException('Unable to load the key.'); } - /** - * @param ASNObject[] $children - */ - private static function loadPKCS8(array $children): array - { - $oidList = $children[1]->getContent(); - if (! is_array($oidList) || count($oidList) !== 2) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $oid = $oidList[1]; - if (! $oid instanceof ObjectIdentifier) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $oid = $oid->getContent(); - if (! is_string($oid)) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - $data = $children[2]->getContent(); - if (! is_string($data)) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $binary = hex2bin($data); - $asnObject = ASNObject::fromBinary($binary); - if (! $asnObject instanceof Sequence) { - throw new InvalidArgumentException('Unable to load the key.'); - } - if ($asnObject->count() < 2) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $version = $asnObject->getChildren()[0]; - if (! $version instanceof Integer && $version->getContent() !== '1') { - throw new InvalidArgumentException('Unable to load the key.'); - } - $privateKey = $asnObject->getChildren()[1]; - if (! $privateKey instanceof OctetString) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $privateKey = $privateKey->getContent(); - if (! is_string($privateKey)) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $dBin = hex2bin($privateKey); - if (! is_string($dBin)) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - $attributes = $asnObject->getChildren(); - $publicKeys = array_reduce($attributes, static function (array $carry, mixed $attribute): array { - if (! $attribute instanceof ExplicitlyTaggedObject) { - return $carry; - } - $attribute = $attribute->getContent(); - if (! is_array($attribute) || count($attribute) === 0) { - return $carry; - } - $value = $attribute[0]; - if ($value instanceof BitString) { - $carry[] = $value; - } - return $carry; - }, []); - - if (count($publicKeys) !== 1) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $publicKey = $publicKeys[0]; - - if (! $publicKey instanceof BitString) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $bits = $publicKey->getContent(); - if (! is_string($bits)) { - throw new InvalidArgumentException('Unsupported key type'); - } - $bits_length = mb_strlen($bits, '8bit'); - if (mb_strpos($bits, '04', 0, '8bit') !== 0) { - throw new InvalidArgumentException('Unsupported key type'); - } - - $xBin = hex2bin(mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit')); - $yBin = hex2bin(mb_substr($bits, (int) (($bits_length - 2) / 2 + 2), ($bits_length - 2) / 2, '8bit')); - if (! is_string($xBin) || ! is_string($yBin)) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - return [ - 'kty' => 'EC', - 'crv' => self::getCurve($oid), - 'x' => Base64UrlSafe::encodeUnpadded($xBin), - 'y' => Base64UrlSafe::encodeUnpadded($yBin), - 'd' => Base64UrlSafe::encodeUnpadded($dBin), - ]; - } - - private static function loadPublicPEM(array $children): array - { - if (! $children[0] instanceof Sequence) { - throw new InvalidArgumentException('Unsupported key type.'); - } - - $sub = $children[0]->getChildren(); - if (! $sub[0] instanceof ObjectIdentifier) { - throw new InvalidArgumentException('Unsupported key type.'); - } - if ($sub[0]->getContent() !== '1.2.840.10045.2.1') { - throw new InvalidArgumentException('Unsupported key type.'); - } - if (! $sub[1] instanceof ObjectIdentifier) { - throw new InvalidArgumentException('Unsupported key type.'); - } - if (! $children[1] instanceof BitString) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - $bits = $children[1]->getContent(); - if (! is_string($bits)) { - throw new InvalidArgumentException('Unsupported key type'); - } - $bits_length = mb_strlen($bits, '8bit'); - if (mb_strpos($bits, '04', 0, '8bit') !== 0) { - throw new InvalidArgumentException('Unsupported key type'); - } - - $values = [ - 'kty' => 'EC', - ]; - $oid = $sub[1]->getContent(); - if (! is_string($oid)) { - throw new InvalidArgumentException('Unsupported key type'); - } - $values['crv'] = self::getCurve($oid); - - $xBin = hex2bin(mb_substr($bits, 2, ($bits_length - 2) / 2, '8bit')); - $yBin = hex2bin(mb_substr($bits, (int) (($bits_length - 2) / 2 + 2), ($bits_length - 2) / 2, '8bit')); - if (! is_string($xBin) || ! is_string($yBin)) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - $values['x'] = Base64UrlSafe::encodeUnpadded($xBin); - $values['y'] = Base64UrlSafe::encodeUnpadded($yBin); - - return $values; - } - private static function getCurve(string $oid): string { $curves = self::getSupportedCurves(); @@ -242,107 +98,6 @@ private static function getSupportedCurves(): array ]; } - private static function verifyVersion(ASNObject $children): void - { - if (! $children instanceof Integer || $children->getContent() !== '1') { - throw new InvalidArgumentException('Unable to load the key.'); - } - } - - private static function getXAndY(ASNObject $children, string &$x, string &$y): void - { - if (! $children instanceof ExplicitlyTaggedObject || ! is_array($children->getContent())) { - throw new InvalidArgumentException('Unable to load the key.'); - } - if (! $children->getContent()[0] instanceof BitString) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - $bits = $children->getContent()[0] - ->getContent(); - if (! is_string($bits)) { - throw new InvalidArgumentException('Unsupported key type'); - } - $bits_length = mb_strlen($bits, '8bit'); - - if (mb_strpos($bits, '04', 0, '8bit') !== 0) { - throw new InvalidArgumentException('Unsupported key type'); - } - - $x = mb_substr($bits, 2, (int) (($bits_length - 2) / 2), '8bit'); - $y = mb_substr($bits, (int) (($bits_length - 2) / 2 + 2), (int) (($bits_length - 2) / 2), '8bit'); - } - - private static function getD(ASNObject $children): string - { - if (! $children instanceof OctetString) { - throw new InvalidArgumentException('Unable to load the key.'); - } - $data = $children->getContent(); - if (! is_string($data)) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - return $data; - } - - private static function loadPrivatePEM(array $children): array - { - self::verifyVersion($children[0]); - $x = ''; - $y = ''; - $d = self::getD($children[1]); - self::getXAndY($children[3], $x, $y); - - if (! $children[2] instanceof ExplicitlyTaggedObject || ! is_array($children[2]->getContent())) { - throw new InvalidArgumentException('Unable to load the key.'); - } - if (! $children[2]->getContent()[0] instanceof ObjectIdentifier) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - $curve = $children[2]->getContent()[0]->getContent(); - $dBin = hex2bin($d); - $xBin = hex2bin((string) $x); - $yBin = hex2bin((string) $y); - if (! is_string($curve) || ! is_string($dBin) || ! is_string($xBin) || ! is_string($yBin)) { - throw new InvalidArgumentException('Unable to load the key.'); - } - - $values = [ - 'kty' => 'EC', - ]; - $values['crv'] = self::getCurve($curve); - $values['d'] = Base64UrlSafe::encodeUnpadded($dBin); - $values['x'] = Base64UrlSafe::encodeUnpadded($xBin); - $values['y'] = Base64UrlSafe::encodeUnpadded($yBin); - - return $values; - } - - /** - * @param ASNObject[] $children - */ - private static function isPKCS8(array $children): bool - { - if (count($children) !== 3) { - return false; - } - - $classes = [ - 0 => Integer::class, - 1 => Sequence::class, - 2 => OctetString::class, - ]; - foreach ($classes as $k => $class) { - if (! $children[$k] instanceof $class) { - return false; - } - } - - return true; - } - private function loadJWK(array $jwk): void { $keys = [ diff --git a/src/Component/KeyManagement/KeyConverter/KeyConverter.php b/src/Component/KeyManagement/KeyConverter/KeyConverter.php index a8f9961e..8da0e471 100644 --- a/src/Component/KeyManagement/KeyConverter/KeyConverter.php +++ b/src/Component/KeyManagement/KeyConverter/KeyConverter.php @@ -4,14 +4,11 @@ namespace Jose\Component\KeyManagement\KeyConverter; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\EC\ECPublicKey; use function array_key_exists; use function count; use function extension_loaded; -use FG\ASN1\Universal\BitString; -use FG\ASN1\Universal\Integer; -use FG\ASN1\Universal\ObjectIdentifier; -use FG\ASN1\Universal\OctetString; -use FG\ASN1\Universal\Sequence; +use function in_array; use InvalidArgumentException; use function is_array; use function is_string; @@ -19,11 +16,13 @@ use const OPENSSL_KEYTYPE_RSA; use const OPENSSL_RAW_DATA; use OpenSSLCertificate; -use ParagonIE\ConstantTime\Base64; use ParagonIE\ConstantTime\Base64UrlSafe; use const PHP_EOL; use const PREG_PATTERN_ORDER; use RuntimeException; +use SpomkyLabs\Pki\CryptoEncoding\PEM; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\PrivateKey; +use SpomkyLabs\Pki\CryptoTypes\Asymmetric\PublicKey; use Throwable; /** @@ -82,7 +81,7 @@ public static function loadKeyFromX509Resource(OpenSSLCertificate $res): array if (isset($details['key'])) { $values = self::loadKeyFromPEM($details['key']); openssl_x509_export($res, $out); - $x5c = preg_replace('#-.*-#', '', $out); + $x5c = preg_replace('#-.*-#', '', (string) $out); $x5c = preg_replace('~\R~', '', $x5c); if (! is_string($x5c)) { throw new InvalidArgumentException('Unable to load the certificate'); @@ -135,10 +134,10 @@ public static function loadFromX5C(array $x5c): array } foreach ($x5c as $id => $cert) { $x5c[$id] = '-----BEGIN CERTIFICATE-----' . PHP_EOL . chunk_split( - (string) $cert, - 64, - PHP_EOL - ) . '-----END CERTIFICATE-----'; + (string) $cert, + 64, + PHP_EOL + ) . '-----END CERTIFICATE-----'; $x509 = openssl_x509_read($x5c[$id]); if ($x509 === false) { throw new InvalidArgumentException('Unable to load the certificate chain'); @@ -192,7 +191,7 @@ private static function loadKeyFromPEM(string $pem, ?string $password = null): a } return match ($details['type']) { - OPENSSL_KEYTYPE_EC => ECKey::createFromPEM($pem)->toArray(), + OPENSSL_KEYTYPE_EC => self::tryToLoadECKey($pem), OPENSSL_KEYTYPE_RSA => RSAKey::createFromPEM($pem)->toArray(), -1 => self::tryToLoadOtherKeyTypes($pem), default => throw new InvalidArgumentException('Unsupported key type'), @@ -202,104 +201,68 @@ private static function loadKeyFromPEM(string $pem, ?string $password = null): a /** * This method tries to load Ed448, X488, Ed25519 and X25519 keys. */ - private static function tryToLoadOtherKeyTypes(string $pem): array + private static function tryToLoadECKey(string $input): array { try { - preg_match_all('#(-.*-)#', $pem, $matches, PREG_PATTERN_ORDER); - $data = preg_replace('#-.*-|\r|\n| #', '', $pem); - if (! is_string($data)) { - throw new InvalidArgumentException('Unsupported key type'); - } - $der = Base64::decode($data); - $sequence = Sequence::fromBinary($der); - if (! $sequence instanceof Sequence) { - throw new InvalidArgumentException('Unsupported key type'); - } - - return match ($sequence->count()) { - 2 => self::tryToLoadPublicKeyTypes($sequence), - 3 => self::tryToLoadPrivateKeyTypes($sequence), - default => throw new InvalidArgumentException('Unsupported key type'), - }; - } catch (Throwable $e) { - throw new InvalidArgumentException('Unsupported key type', 0, $e); + return ECKey::createFromPEM($input)->toArray(); + } catch (Throwable) { + } + try { + return self::tryToLoadOtherKeyTypes($input); + } catch (Throwable) { } + throw new InvalidArgumentException('Unable to load the key.'); } /** - * This method tries to load Ed448 or Ed25519 keys. + * This method tries to load Ed448, X488, Ed25519 and X25519 keys. */ - private static function tryToLoadPublicKeyTypes(Sequence $sequence): array + private static function tryToLoadOtherKeyTypes(string $input): array { - [$curveId, $x] = $sequence; - if (! $curveId instanceof Sequence || $curveId->count() === 0) { - throw new InvalidArgumentException('Unsupported key type'); - } - if (! $x instanceof BitString) { - throw new InvalidArgumentException('Unsupported key type'); - } - $oid = $curveId[0]; - if (! $oid instanceof ObjectIdentifier) { - throw new InvalidArgumentException('Unsupported key type'); + $pem = PEM::fromString($input); + try { + $key = PrivateKey::fromPEM($pem); + $curve = self::getCurve($key->algorithmIdentifier()->oid()); + $values = [ + 'kty' => 'OKP', + 'crv' => $curve, + 'd' => Base64UrlSafe::encodeUnpadded($key->privateKeyData()), + ]; + return self::populatePoints($key, $values); + } catch (Throwable) { } - $curve = $oid->getContent(); - if (! is_string($curve)) { - throw new InvalidArgumentException('Unsupported key type'); + try { + $key = PublicKey::fromPEM($pem); + $curve = self::getCurve($key->algorithmIdentifier()->oid()); + self::checkType($curve); + return [ + 'kty' => 'OKP', + 'crv' => $curve, + 'x' => Base64UrlSafe::encodeUnpadded((string) $key->subjectPublicKey()), + ]; + } catch (Throwable) { } - - return [ - 'kty' => 'OKP', - 'crv' => self::getCurve($curve), - 'x' => Base64UrlSafe::encodeUnpadded($x->getBinaryContent()), - ]; + throw new InvalidArgumentException('Unsupported key type'); } /** - * This method tries to load X448 or X25519 keys. + * @param array $values + * @return array */ - private static function tryToLoadPrivateKeyTypes(Sequence $sequence): array + private static function populatePoints(PrivateKey $key, array $values): array { - [$version, $curveId, $octetD] = $sequence; - if ($version instanceof Integer && $version->getContent() !== '0') { - throw new InvalidArgumentException('Unsupported key type'); - } - if (! $curveId instanceof Sequence || $curveId->count() === 0) { - throw new InvalidArgumentException('Unsupported key type'); - } - if (! $octetD instanceof OctetString) { - throw new InvalidArgumentException('Unsupported key type'); - } - $oid = $curveId[0]; - if (! $oid instanceof ObjectIdentifier) { - throw new InvalidArgumentException('Unsupported key type'); - } - $curve = $oid->getContent(); - if (! is_string($curve)) { - throw new InvalidArgumentException('Unsupported key type'); + if (($values['crv'] === 'Ed25519' || $values['crv'] === 'X25519') && extension_loaded('sodium')) { + $x = sodium_crypto_scalarmult_base($key->privateKeyData()); + $values['x'] = Base64UrlSafe::encodeUnpadded($x); } - $crv = self::getCurve($curve); - $binOctetdD = $octetD->getBinaryContent(); - $d = OctetString::fromBinary($binOctetdD); - $d = $d->getContent(); - if (! is_string($d)) { - throw new InvalidArgumentException('Unsupported key type'); - } - $dBin = hex2bin($d); - if (! is_string($dBin)) { - throw new InvalidArgumentException('Unsupported key type'); - } - - $data = [ - 'kty' => 'OKP', - 'crv' => $crv, - 'd' => Base64UrlSafe::encodeUnpadded($dBin), - ]; - if (($crv === 'Ed25519' || $crv === 'X25519') && extension_loaded('sodium')) { - $data['x'] = Base64UrlSafe::encodeUnpadded(sodium_crypto_sign_publickey_from_secretkey($d)); - } + return $values; + } - return $data; + private static function checkType(string $curve): void + { + $curves = ['Ed448ph', 'Ed25519ph', 'Ed448', 'Ed25519', 'X448', 'X25519']; + in_array($curve, $curves, true) || throw new InvalidArgumentException('Unsupported key type.'); } /** diff --git a/src/Component/Signature/Algorithm/SignatureAlgorithm.php b/src/Component/Signature/Algorithm/SignatureAlgorithm.php index 3dd0b1cd..a283d1dd 100644 --- a/src/Component/Signature/Algorithm/SignatureAlgorithm.php +++ b/src/Component/Signature/Algorithm/SignatureAlgorithm.php @@ -21,8 +21,8 @@ public function sign(JWK $key, string $input): string; * Verify the signature of data. * * @param JWK $key The private key used to sign the data - * @param string $input The input - * @param string $signature The signature to verify + * @param non-empty-string $input The input + * @param non-empty-string $signature The signature to verify */ public function verify(JWK $key, string $input, string $signature): bool; } diff --git a/src/Component/Signature/JWSVerifier.php b/src/Component/Signature/JWSVerifier.php index 905bc7cb..d750a507 100644 --- a/src/Component/Signature/JWSVerifier.php +++ b/src/Component/Signature/JWSVerifier.php @@ -140,7 +140,7 @@ private function checkPayload(JWS $jws, ?string $detachedPayload = null): void */ private function getAlgorithm(Signature $signature): Algorithm { - $completeHeader = array_merge($signature->getProtectedHeader(), $signature->getHeader()); + $completeHeader = [...$signature->getProtectedHeader(), ...$signature->getHeader()]; if (! isset($completeHeader['alg'])) { throw new InvalidArgumentException('No "alg" parameter set in the header.'); } diff --git a/src/Component/Signature/Serializer/JSONGeneralSerializer.php b/src/Component/Signature/Serializer/JSONGeneralSerializer.php index 6f703098..0212bf4f 100644 --- a/src/Component/Signature/Serializer/JSONGeneralSerializer.php +++ b/src/Component/Signature/Serializer/JSONGeneralSerializer.php @@ -103,6 +103,9 @@ public function unserialize(string $input): JWS return $jws; } + /** + * @param array $protectedHeader + */ private function processIsPayloadEncoded(?bool $isPayloadEncoded, array $protectedHeader): bool { if ($isPayloadEncoded === null) { @@ -115,6 +118,10 @@ private function processIsPayloadEncoded(?bool $isPayloadEncoded, array $protect return $isPayloadEncoded; } + /** + * @param array $signature + * @return array + */ private function processHeaders(array $signature): array { $encodedProtectedHeader = $signature['protected'] ?? null; diff --git a/src/Component/Signature/Serializer/Serializer.php b/src/Component/Signature/Serializer/Serializer.php index 05b35587..541d3e2e 100644 --- a/src/Component/Signature/Serializer/Serializer.php +++ b/src/Component/Signature/Serializer/Serializer.php @@ -8,6 +8,9 @@ abstract class Serializer implements JWSSerializer { + /** + * @param array $protectedHeader + */ protected function isPayloadEncoded(array $protectedHeader): bool { return ! array_key_exists('b64', $protectedHeader) || $protectedHeader['b64'] === true; diff --git a/src/Component/Signature/Signature.php b/src/Component/Signature/Signature.php index 148e5d6a..a385d44c 100644 --- a/src/Component/Signature/Signature.php +++ b/src/Component/Signature/Signature.php @@ -14,8 +14,15 @@ class Signature { private readonly ?string $encodedProtectedHeader; + /** + * @var array + */ private readonly array $protectedHeader; + /** + * @param array $protectedHeader + * @param array $header + */ public function __construct( private readonly string $signature, array $protectedHeader, @@ -28,6 +35,8 @@ public function __construct( /** * The protected header associated with the signature. + * + * @return array */ public function getProtectedHeader(): array { @@ -36,6 +45,8 @@ public function getProtectedHeader(): array /** * The unprotected header associated with the signature. + * + * @return array */ public function getHeader(): array { diff --git a/src/EncryptionAlgorithm/Experimental/KeyEncryption/AESCTR.php b/src/EncryptionAlgorithm/Experimental/KeyEncryption/AESCTR.php index dd6e5d4c..3dab4849 100644 --- a/src/EncryptionAlgorithm/Experimental/KeyEncryption/AESCTR.php +++ b/src/EncryptionAlgorithm/Experimental/KeyEncryption/AESCTR.php @@ -19,6 +19,10 @@ public function allowedKeyTypes(): array return ['oct']; } + /** + * @param array $completeHeader + * @param array $additionalHeader + */ public function encryptKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string { $k = $this->getKey($key); @@ -35,10 +39,14 @@ public function encryptKey(JWK $key, string $cek, array $completeHeader, array & return $result; } + /** + * @param array $header + */ public function decryptKey(JWK $key, string $encrypted_cek, array $header): string { $k = $this->getKey($key); - $this->checkHeaderAdditionalParameters($header); + isset($header['iv']) ||throw new InvalidArgumentException('The header parameter "iv" is missing.'); + is_string($header['iv']) ||throw new InvalidArgumentException('The header parameter "iv" is not valid.'); $iv = Base64UrlSafe::decode($header['iv']); $result = openssl_decrypt($encrypted_cek, $this->getMode(), $k, OPENSSL_RAW_DATA, $iv); @@ -71,14 +79,4 @@ private function getKey(JWK $key): string return Base64UrlSafe::decode($k); } - - private function checkHeaderAdditionalParameters(array $header): void - { - if (! isset($header['iv'])) { - throw new InvalidArgumentException('The header parameter "iv" is missing.'); - } - if (! is_string($header['iv'])) { - throw new InvalidArgumentException('The header parameter "iv" is not valid.'); - } - } } diff --git a/src/EncryptionAlgorithm/Experimental/KeyEncryption/Chacha20Poly1305.php b/src/EncryptionAlgorithm/Experimental/KeyEncryption/Chacha20Poly1305.php index 6ee72bf2..d4c9a80d 100644 --- a/src/EncryptionAlgorithm/Experimental/KeyEncryption/Chacha20Poly1305.php +++ b/src/EncryptionAlgorithm/Experimental/KeyEncryption/Chacha20Poly1305.php @@ -32,6 +32,10 @@ public function name(): string return 'chacha20-poly1305'; } + /** + * @param array $completeHeader + * @param array $additionalHeader + */ public function encryptKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string { $k = $this->getKey($key); @@ -40,18 +44,23 @@ public function encryptKey(JWK $key, string $cek, array $completeHeader, array & // We set header parameters $additionalHeader['nonce'] = Base64UrlSafe::encodeUnpadded($nonce); - $result = openssl_encrypt($cek, 'chacha20-poly1305', $k, OPENSSL_RAW_DATA, $nonce); - if ($result === false) { + $tag = null; + $result = openssl_encrypt($cek, 'chacha20-poly1305', $k, OPENSSL_RAW_DATA, $nonce, $tag); + if ($result === false || ! is_string($tag)) { throw new RuntimeException('Unable to encrypt the CEK'); } return $result; } + /** + * @param array $header + */ public function decryptKey(JWK $key, string $encrypted_cek, array $header): string { $k = $this->getKey($key); - $this->checkHeaderAdditionalParameters($header); + isset($header['nonce']) ||throw new InvalidArgumentException('The header parameter "nonce" is missing.'); + is_string($header['nonce']) ||throw new InvalidArgumentException('The header parameter "nonce" is not valid.'); $nonce = Base64UrlSafe::decode($header['nonce']); if (mb_strlen($nonce, '8bit') !== 12) { throw new InvalidArgumentException('The header parameter "nonce" is not valid.'); @@ -85,14 +94,4 @@ private function getKey(JWK $key): string return Base64UrlSafe::decode($k); } - - private function checkHeaderAdditionalParameters(array $header): void - { - if (! isset($header['nonce'])) { - throw new InvalidArgumentException('The header parameter "nonce" is missing.'); - } - if (! is_string($header['nonce'])) { - throw new InvalidArgumentException('The header parameter "nonce" is not valid.'); - } - } } diff --git a/src/EncryptionAlgorithm/KeyEncryption/AESGCMKW/AESGCMKW.php b/src/EncryptionAlgorithm/KeyEncryption/AESGCMKW/AESGCMKW.php index 92b97059..92e0d688 100644 --- a/src/EncryptionAlgorithm/KeyEncryption/AESGCMKW/AESGCMKW.php +++ b/src/EncryptionAlgorithm/KeyEncryption/AESGCMKW/AESGCMKW.php @@ -19,6 +19,10 @@ public function allowedKeyTypes(): array return ['oct']; } + /** + * @param array $completeHeader + * @param array $additionalHeader + */ public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string { $kek = $this->getKey($key); @@ -36,10 +40,14 @@ public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$ad return $encrypted_cek; } + /** + * @param array $completeHeader + */ public function unwrapKey(JWK $key, string $encrypted_cek, array $completeHeader): string { $kek = $this->getKey($key); - $this->checkAdditionalParameters($completeHeader); + (isset($completeHeader['iv']) && is_string($completeHeader['iv'])) || throw new InvalidArgumentException('Parameter "iv" is missing.'); + (isset($completeHeader['tag']) && is_string($completeHeader['tag'])) || throw new InvalidArgumentException('Parameter "tag" is missing.'); $tag = Base64UrlSafe::decode($completeHeader['tag']); $iv = Base64UrlSafe::decode($completeHeader['iv']); @@ -74,14 +82,5 @@ protected function getKey(JWK $key): string return Base64UrlSafe::decode($k); } - protected function checkAdditionalParameters(array $header): void - { - foreach (['iv', 'tag'] as $k) { - if (! isset($header[$k])) { - throw new InvalidArgumentException(sprintf('Parameter "%s" is missing.', $k)); - } - } - } - abstract protected function getKeySize(): int; } diff --git a/src/EncryptionAlgorithm/KeyEncryption/AESKW/AESKW.php b/src/EncryptionAlgorithm/KeyEncryption/AESKW/AESKW.php index 1e27500c..f72b6762 100644 --- a/src/EncryptionAlgorithm/KeyEncryption/AESKW/AESKW.php +++ b/src/EncryptionAlgorithm/KeyEncryption/AESKW/AESKW.php @@ -18,6 +18,10 @@ public function allowedKeyTypes(): array return ['oct']; } + /** + * @param array $completeHeader + * @param array $additionalHeader + */ public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string { $k = $this->getKey($key); @@ -26,6 +30,9 @@ public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$ad return $wrapper::wrap($k, $cek); } + /** + * @param array $completeHeader + */ public function unwrapKey(JWK $key, string $encrypted_cek, array $completeHeader): string { $k = $this->getKey($key); diff --git a/src/EncryptionAlgorithm/KeyEncryption/ECDHES/AbstractECDH.php b/src/EncryptionAlgorithm/KeyEncryption/ECDHES/AbstractECDH.php index 105048c6..9fab2069 100644 --- a/src/EncryptionAlgorithm/KeyEncryption/ECDHES/AbstractECDH.php +++ b/src/EncryptionAlgorithm/KeyEncryption/ECDHES/AbstractECDH.php @@ -30,6 +30,10 @@ public function allowedKeyTypes(): array return ['EC', 'OKP']; } + /** + * @param array $complete_header + * @param array $additional_header_values + */ public function getAgreementKey( int $encryptionKeyLength, string $algorithm, @@ -51,7 +55,9 @@ public function getAgreementKey( $agreed_key = $this->calculateAgreementKey($private_key, $public_key); $apu = array_key_exists('apu', $complete_header) ? $complete_header['apu'] : ''; + is_string($apu) || throw new InvalidArgumentException('Invalid APU.'); $apv = array_key_exists('apv', $complete_header) ? $complete_header['apv'] : ''; + is_string($apv) || throw new InvalidArgumentException('Invalid APU.'); return ConcatKDF::generate($agreed_key, $algorithm, $encryptionKeyLength, $apu, $apv); } @@ -135,6 +141,7 @@ protected function calculateAgreementKey(JWK $private_key, JWK $public_key): str /** * @return JWK[] + * @param array $additional_header_values */ protected function getKeysFromPublicKey( JWK $recipient_key, @@ -173,6 +180,7 @@ protected function getKeysFromPublicKey( } /** + * @param array $complete_header * @return JWK[] */ protected function getKeysFromPrivateKeyAndHeader(JWK $recipient_key, array $complete_header): array @@ -187,6 +195,9 @@ protected function getKeysFromPrivateKeyAndHeader(JWK $recipient_key, array $com return [$public_key, $private_key]; } + /** + * @param array $complete_header + */ private function getPublicKey(array $complete_header): JWK { if (! isset($complete_header['epk'])) { @@ -250,7 +261,7 @@ private function getCurve(string $crv): Curve private function convertBase64ToBigInteger(string $value): BigInteger { $data = unpack('H*', Base64UrlSafe::decode($value)); - if (! is_array($data) || ! isset($data[1])) { + if (! is_array($data) || ! isset($data[1]) || ! is_string($data[1])) { throw new InvalidArgumentException('Unable to convert base64 to integer'); } diff --git a/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHESAESKW.php b/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHESAESKW.php index ff78500b..1a40ca29 100644 --- a/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHESAESKW.php +++ b/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHESAESKW.php @@ -8,6 +8,10 @@ abstract class ECDHESAESKW extends AbstractECDHAESKW { + /** + * @param array $complete_header + * @param array $additional_header_values + */ public function wrapAgreementKey( JWK $recipientKey, ?JWK $senderKey, @@ -30,6 +34,9 @@ public function wrapAgreementKey( return $wrapper::wrap($agreement_key, $cek); } + /** + * @param array $complete_header + */ public function unwrapAgreementKey( JWK $recipientKey, ?JWK $senderKey, diff --git a/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHSS.php b/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHSS.php index 342c35a7..1af2e6d0 100644 --- a/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHSS.php +++ b/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHSS.php @@ -9,6 +9,10 @@ final class ECDHSS extends AbstractECDH { + /** + * @param array $complete_header + * @param array $additional_header_values + */ public function getAgreementKey( int $encryptionKeyLength, string $algorithm, diff --git a/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHSSAESKW.php b/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHSSAESKW.php index 08c69346..2cdb5617 100644 --- a/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHSSAESKW.php +++ b/src/EncryptionAlgorithm/KeyEncryption/ECDHES/ECDHSSAESKW.php @@ -8,6 +8,10 @@ abstract class ECDHSSAESKW extends AbstractECDHAESKW { + /** + * @param array $complete_header + * @param array $additional_header_values + */ public function wrapAgreementKey( JWK $recipientKey, ?JWK $senderKey, @@ -30,6 +34,9 @@ public function wrapAgreementKey( return $wrapper::wrap($agreement_key, $cek); } + /** + * @param array $complete_header + */ public function unwrapAgreementKey( JWK $recipientKey, ?JWK $senderKey, diff --git a/src/EncryptionAlgorithm/KeyEncryption/PBES2/PBES2AESKW.php b/src/EncryptionAlgorithm/KeyEncryption/PBES2/PBES2AESKW.php index 106711c5..1891bbb0 100644 --- a/src/EncryptionAlgorithm/KeyEncryption/PBES2/PBES2AESKW.php +++ b/src/EncryptionAlgorithm/KeyEncryption/PBES2/PBES2AESKW.php @@ -27,6 +27,10 @@ public function allowedKeyTypes(): array return ['oct']; } + /** + * @param array $completeHeader + * @param array $additionalHeader + */ public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string { $password = $this->getKey($key); @@ -52,6 +56,9 @@ public function wrapKey(JWK $key, string $cek, array $completeHeader, array &$ad return $wrapper::wrap($derived_key, $cek); } + /** + * @param array $completeHeader + */ public function unwrapKey(JWK $key, string $encrypted_cek, array $completeHeader): string { $password = $this->getKey($key); @@ -60,8 +67,11 @@ public function unwrapKey(JWK $key, string $encrypted_cek, array $completeHeader $wrapper = $this->getWrapper(); $hash_algorithm = $this->getHashAlgorithm(); $key_size = $this->getKeySize(); - $salt = $completeHeader['alg'] . "\x00" . Base64UrlSafe::decode($completeHeader['p2s']); + $p2s = $completeHeader['p2s']; + is_string($p2s) || throw new InvalidArgumentException('Invalid salt.'); + $salt = $completeHeader['alg'] . "\x00" . Base64UrlSafe::decode($p2s); $count = $completeHeader['p2c']; + is_int($count) || throw new InvalidArgumentException('Invalid counter.'); $derived_key = hash_pbkdf2($hash_algorithm, $password, $salt, $count, $key_size, true); @@ -89,6 +99,9 @@ protected function getKey(JWK $key): string return Base64UrlSafe::decode($k); } + /** + * @param array $header + */ protected function checkHeaderAlgorithm(array $header): void { if (! isset($header['alg'])) { @@ -99,6 +112,9 @@ protected function checkHeaderAlgorithm(array $header): void } } + /** + * @param array $header + */ protected function checkHeaderAdditionalParameters(array $header): void { if (! isset($header['p2s'])) { diff --git a/src/EncryptionAlgorithm/KeyEncryption/RSA/RSA.php b/src/EncryptionAlgorithm/KeyEncryption/RSA/RSA.php index 456d2f5e..57c48f43 100644 --- a/src/EncryptionAlgorithm/KeyEncryption/RSA/RSA.php +++ b/src/EncryptionAlgorithm/KeyEncryption/RSA/RSA.php @@ -17,6 +17,10 @@ public function allowedKeyTypes(): array return ['RSA']; } + /** + * @param array $completeHeader + * @param array $additionalHeader + */ public function encryptKey(JWK $key, string $cek, array $completeHeader, array &$additionalHeader): string { $this->checkKey($key); @@ -25,6 +29,9 @@ public function encryptKey(JWK $key, string $cek, array $completeHeader, array & return RSACrypt::encrypt($pub, $cek, $this->getEncryptionMode(), $this->getHashAlgorithm()); } + /** + * @param array $header + */ public function decryptKey(JWK $key, string $encrypted_cek, array $header): string { $this->checkKey($key); diff --git a/src/SignatureAlgorithm/EdDSA/EdDSA.php b/src/SignatureAlgorithm/EdDSA/EdDSA.php index f3c7a7fc..dcc57c57 100644 --- a/src/SignatureAlgorithm/EdDSA/EdDSA.php +++ b/src/SignatureAlgorithm/EdDSA/EdDSA.php @@ -26,6 +26,9 @@ public function allowedKeyTypes(): array return ['OKP']; } + /** + * @return non-empty-string + */ public function sign(JWK $key, string $input): string { $this->checkKey($key); @@ -33,7 +36,7 @@ public function sign(JWK $key, string $input): string throw new InvalidArgumentException('The EC key is not private'); } $d = $key->get('d'); - if (! is_string($d)) { + if (! is_string($d) || $d === '') { throw new InvalidArgumentException('Invalid "d" parameter.'); } if (! $key->has('x')) { @@ -41,10 +44,12 @@ public function sign(JWK $key, string $input): string } else { $x = $key->get('x'); } - if (! is_string($x)) { + if (! is_string($x) || $x === '') { throw new InvalidArgumentException('Invalid "x" parameter.'); } + /** @var non-empty-string $x */ $x = Base64UrlSafe::decode($x); + /** @var non-empty-string $d */ $d = Base64UrlSafe::decode($d); $secret = $d . $x; @@ -54,6 +59,9 @@ public function sign(JWK $key, string $input): string }; } + /** + * @param non-empty-string $signature + */ public function verify(JWK $key, string $input, string $signature): bool { $this->checkKey($key); @@ -62,6 +70,7 @@ public function verify(JWK $key, string $input, string $signature): bool throw new InvalidArgumentException('Invalid "x" parameter.'); } + /** @var non-empty-string $public */ $public = Base64UrlSafe::decode($x); return match ($key->get('crv')) { diff --git a/src/SignatureAlgorithm/RSA/RSAPSS.php b/src/SignatureAlgorithm/RSA/RSAPSS.php index 982cd25a..19f36abd 100644 --- a/src/SignatureAlgorithm/RSA/RSAPSS.php +++ b/src/SignatureAlgorithm/RSA/RSAPSS.php @@ -25,6 +25,9 @@ public function verify(JWK $key, string $input, string $signature): bool return JoseRSA::verify($pub, $input, $signature, $this->getAlgorithm(), JoseRSA::SIGNATURE_PSS); } + /** + * @return non-empty-string + */ public function sign(JWK $key, string $input): string { $this->checkKey($key); diff --git a/src/SignatureAlgorithm/RSA/Util/RSA.php b/src/SignatureAlgorithm/RSA/Util/RSA.php index f8458eee..901badcf 100644 --- a/src/SignatureAlgorithm/RSA/Util/RSA.php +++ b/src/SignatureAlgorithm/RSA/Util/RSA.php @@ -28,6 +28,9 @@ final class RSA */ public const SIGNATURE_PKCS1 = 2; + /** + * @return non-empty-string + */ public static function sign(RSAKey $key, string $message, string $hash, int $mode): string { switch ($mode) { @@ -49,14 +52,20 @@ public static function sign(RSAKey $key, string $message, string $hash, int $mod /** * Create a signature. + * + * @return non-empty-string */ public static function signWithPSS(RSAKey $key, string $message, string $hash): string { $em = self::encodeEMSAPSS($message, 8 * $key->getModulusLength() - 1, Hash::$hash()); $message = BigInteger::createFromBinaryString($em); $signature = RSAKey::exponentiate($key, $message); + $result = self::convertIntegerToOctetString($signature, $key->getModulusLength()); + if ($result === '') { + throw new InvalidArgumentException('Invalid signature.'); + } - return self::convertIntegerToOctetString($signature, $key->getModulusLength()); + return $result; } public static function verify(RSAKey $key, string $message, string $signature, string $hash, int $mode): bool diff --git a/tests/Bundle/JoseFramework/Functional/KeyManagement/JWKSetLoaderTest.php b/tests/Bundle/JoseFramework/Functional/KeyManagement/JWKSetLoaderTest.php index 587bce4b..2093b61d 100644 --- a/tests/Bundle/JoseFramework/Functional/KeyManagement/JWKSetLoaderTest.php +++ b/tests/Bundle/JoseFramework/Functional/KeyManagement/JWKSetLoaderTest.php @@ -51,7 +51,7 @@ public function aJWKSetCanBeSharedInTheConfiguration(): void /** @var Response $response */ $response = $client->getResponse(); - static::assertSame(200, $response->getStatusCode()); + static::assertResponseIsSuccessful(); static::assertSame( '{"keys":[{"kty":"oct","k":"dzI6nbW4OcNF-AtfxGAmuyz7IpHRudBI0WgGjZWgaRJt6prBn3DARXgUR8NVwKhfL43QBIU2Un3AvCGCHRgY4TbEqhOi8-i98xxmCggNjde4oaW6wkJ2NgM3Ss9SOX9zS3lcVzdCMdum-RwVJ301kbin4UtGztuzJBeg5oVN00MGxjC2xWwyI0tgXVs-zJs5WlafCuGfX1HrVkIf5bvpE0MQCSjdJpSeVao6-RSTYDajZf7T88a2eVjeW31mMAg-jzAWfUrii61T_bYPJFOXW8kkRWoa1InLRdG6bKB9wQs9-VdXZP60Q4Yuj_WZ-lO7qV9AEFrUkkjpaDgZT86w2g"},{"kty":"oct","k":"bwIAv5Nn-fo8p4LCEvM4IR9eLXgzJRs8jXCLb3xR0tDJGiZ46KheO4ip6htFKyN2aqJqlNi9-7hB6I1aLLy1IRT9-vcBoCSGu977cNAUuRLkRp7vo8s6MsxhB8WvQBDRZghV7jIYaune-3vbE7iDU2AESr8BUtorckLoO9uW__fIabaa3hJMMQIHCzYQbJKZvlCRCKWMk2H_zuS4JeDFTvyZH1skJYF_TET1DrCZHMPicw-Yk3_m2P-ilC-yidPPoVzeU8Jj3tQ6gtX3975qiQW7pt2qbgjKAuq2wsz_9hxLBtMB5rQPafFoxop7O4BklvZ9-ECcK6dfI2CAx9_tjQ"}]}', $response->getContent() @@ -84,7 +84,7 @@ public function aJWKSetCanBeSharedFromAnotherBundle(): void /** @var Response $response */ $response = $client->getResponse(); - static::assertSame(200, $response->getStatusCode()); + static::assertResponseIsSuccessful(); static::assertSame( '{"keys":[{"kty":"oct","k":"dzI6nbW4OcNF-AtfxGAmuyz7IpHRudBI0WgGjZWgaRJt6prBn3DARXgUR8NVwKhfL43QBIU2Un3AvCGCHRgY4TbEqhOi8-i98xxmCggNjde4oaW6wkJ2NgM3Ss9SOX9zS3lcVzdCMdum-RwVJ301kbin4UtGztuzJBeg5oVN00MGxjC2xWwyI0tgXVs-zJs5WlafCuGfX1HrVkIf5bvpE0MQCSjdJpSeVao6-RSTYDajZf7T88a2eVjeW31mMAg-jzAWfUrii61T_bYPJFOXW8kkRWoa1InLRdG6bKB9wQs9-VdXZP60Q4Yuj_WZ-lO7qV9AEFrUkkjpaDgZT86w2g"},{"kty":"oct","k":"bwIAv5Nn-fo8p4LCEvM4IR9eLXgzJRs8jXCLb3xR0tDJGiZ46KheO4ip6htFKyN2aqJqlNi9-7hB6I1aLLy1IRT9-vcBoCSGu977cNAUuRLkRp7vo8s6MsxhB8WvQBDRZghV7jIYaune-3vbE7iDU2AESr8BUtorckLoO9uW__fIabaa3hJMMQIHCzYQbJKZvlCRCKWMk2H_zuS4JeDFTvyZH1skJYF_TET1DrCZHMPicw-Yk3_m2P-ilC-yidPPoVzeU8Jj3tQ6gtX3975qiQW7pt2qbgjKAuq2wsz_9hxLBtMB5rQPafFoxop7O4BklvZ9-ECcK6dfI2CAx9_tjQ"}]}', $response->getContent() diff --git a/tests/Component/Console/KeyConversionCommandTest.php b/tests/Component/Console/KeyConversionCommandTest.php index 880dfa06..a78beb0e 100644 --- a/tests/Component/Console/KeyConversionCommandTest.php +++ b/tests/Component/Console/KeyConversionCommandTest.php @@ -61,8 +61,9 @@ public function iCanLoadAnEncryptedKeyFile(): void * @test * @doesNotPerformAssertions */ - public function iCanLoadAPKCS12CertificateFile(): void + public function iCanLoadAPKCS12CertificateFile(): never { + static::markTestIncomplete('Unable to run this test using the last OpenSSL versions'); $input = new ArrayInput([ 'file' => __DIR__ . '/Sample/CertRSA.p12', '--secret' => 'certRSA', diff --git a/tests/Component/KeyManagement/CertificateTest.php b/tests/Component/KeyManagement/CertificateTest.php index ebe6301c..6a259a45 100644 --- a/tests/Component/KeyManagement/CertificateTest.php +++ b/tests/Component/KeyManagement/CertificateTest.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use Jose\Component\Core\JWK; +use Jose\Component\Core\Util\RSAKey; use Jose\Component\KeyManagement\JWKFactory; use Jose\Component\KeyManagement\KeyConverter\KeyConverter; use PHPUnit\Framework\TestCase; @@ -41,9 +42,12 @@ public function fileNotValid(): void /** * @test */ - public function certificateConversion(): void + public function rsaEncryptedPrivateKeyConversion(): void { + // When $details = KeyConverter::loadFromKeyFile(__DIR__ . '/Keys/RSA/private.encrypted.key', 'tests'); + + // Then static::assertEqualsCanonicalizing($details, [ 'kty' => 'RSA', 'n' => 'tpS1ZmfVKVP5KofIhMBP0tSWc4qlh6fm2lrZSkuKxUjEaWjzZSzs72gEIGxraWusMdoRuV54xsWRyf5KeZT0S-I5Prle3Idi3gICiO4NwvMk6JwSBcJWwmSLFEKyUSnB2CtfiGc0_5rQCpcEt_Dn5iM-BNn7fqpoLIbks8rXKUIj8-qMVqkTXsEKeKinE23t1ykMldsNaaOH-hvGti5Jt2DMnH1JjoXdDXfxvSP_0gjUYb0ektudYFXoA6wekmQyJeImvgx4Myz1I4iHtkY_Cp7J4Mn1ejZ6HNmyvoTE_4OuY1uCeYv4UyXFc1s1uUyYtj4z57qsHGsS4dQ3A2MJsw', @@ -55,13 +59,27 @@ public function certificateConversion(): void 'dq' => 'Swz1-m_vmTFN_pu1bK7vF7S5nNVrL4A0OFiEsGliCmuJWzOKdL14DiYxctvnw3H6qT2dKZZfV2tbse5N9-JecdldUjfuqAoLIe7dD7dKi42YOlTC9QXmqvTh1ohnJu8pmRFXEZQGUm_BVhoIb2_WPkjav6YSkguCUHt4HRd2YwE', 'qi' => 'BocuCOEOq-oyLDALwzMXU8gOf3IL1Q1_BWwsdoANoh6i179psxgE4JXToWcpXZQQqub8ngwE6uR9fpd3m6N_PL4T55vbDDyjPKmrL2ttC2gOtx9KrpPh-Z7LQRo4BE48nHJJrystKHfFlaH2G7JxHNgMBYVADyttN09qEoav8Os', ]); + } - $details = KeyConverter::loadFromKeyFile(__DIR__ . '/Keys/RSA/public.key'); - static::assertSame($details, [ + /** + * @test + */ + public function rsaPublicKeyConversion(): void + { + // When + $filename = __DIR__ . '/Keys/RSA/public.key'; + $content = trim(file_get_contents($filename)); + $details = KeyConverter::loadFromKeyFile($filename); + $values = [ 'kty' => 'RSA', 'n' => 'tpS1ZmfVKVP5KofIhMBP0tSWc4qlh6fm2lrZSkuKxUjEaWjzZSzs72gEIGxraWusMdoRuV54xsWRyf5KeZT0S-I5Prle3Idi3gICiO4NwvMk6JwSBcJWwmSLFEKyUSnB2CtfiGc0_5rQCpcEt_Dn5iM-BNn7fqpoLIbks8rXKUIj8-qMVqkTXsEKeKinE23t1ykMldsNaaOH-hvGti5Jt2DMnH1JjoXdDXfxvSP_0gjUYb0ektudYFXoA6wekmQyJeImvgx4Myz1I4iHtkY_Cp7J4Mn1ejZ6HNmyvoTE_4OuY1uCeYv4UyXFc1s1uUyYtj4z57qsHGsS4dQ3A2MJsw', 'e' => 'AQAB', - ]); + ]; + + // Then + static::assertSame($details, $values); + $rsaKey = RSAKey::createFromJWK(new JWK($values)); + static::assertSame($content, $rsaKey->toPEM()); } /** diff --git a/tests/Component/KeyManagement/JWKFactoryTest.php b/tests/Component/KeyManagement/JWKFactoryTest.php index 9ad2dbba..818a3353 100644 --- a/tests/Component/KeyManagement/JWKFactoryTest.php +++ b/tests/Component/KeyManagement/JWKFactoryTest.php @@ -4,6 +4,7 @@ namespace Jose\Tests\Component\KeyManagement; +use Jose\Component\Core\Util\ECKey; use Jose\Component\KeyManagement\JWKFactory; use const JSON_THROW_ON_ERROR; use ParagonIE\ConstantTime\Base64UrlSafe; @@ -17,9 +18,10 @@ final class JWKFactoryTest extends TestCase /** * @test */ - public function iCanLoadAP12CertificateThatContainsARSAKey(): void + public function iCanLoadAP12CertificateThatContainsARSAKey(): never { - $result = JWKFactory::createFromPKCS12CertificateFile(__DIR__ . '/P12/CertRSA.p12', 'certRSA'); + static::markTestIncomplete('Unable to run this test using the last OpenSSL versions'); + $result = JWKFactory::createFromPKCS12CertificateFile(__DIR__ . '/P12/CertRSA.p12', 'cert'); static::assertSame( [ @@ -218,42 +220,39 @@ public function createFromPrivateEC512KeyFileEncrypted(): void } /** + * @dataProvider publicKeysAndPem * @test */ - public function createFromPublicEC256KeyFile(): void + public function createFromPublicEC512KeyFile(string $filename, string $expectedJWK): void { - $result = JWKFactory::createFromKeyFile(__DIR__ . '/Keys/EC/public.es256.key'); + // Given + $content = file_get_contents($filename); - static::assertSame( - '{"kty":"EC","crv":"P-256","x":"vuYsP-QnrqAbM7Iyhzjt08hFSuzapyojCB_gFsBt65U","y":"oq-E2K-X0kPeqGuKnhlXkxc5fnxomRSC6KLby7Ij8AE"}', - json_encode($result, JSON_THROW_ON_ERROR) - ); - } - - /** - * @test - */ - public function createFromPublicEC384KeyFile(): void - { - $result = JWKFactory::createFromKeyFile(__DIR__ . '/Keys/EC/public.es384.key'); + // When + $jwk = JWKFactory::createFromKeyFile($filename); + // Then static::assertSame( - '{"kty":"EC","crv":"P-384","x":"6f-XZsg2Tvn0EoEapQ-ylMYNtsm8CPf0cb8HI2EkfY9Bqpt3QMzwlM7mVsFRmaMZ","y":"b8nOnRwmpmEnvA2U8ydS-dbnPv7bwYl-q1qNeh8Wpjor3VO-RTt4ce0Pn25oGGWU"}', - json_encode($result, JSON_THROW_ON_ERROR) + $expectedJWK, + json_encode($jwk, JSON_THROW_ON_ERROR) ); + static::assertSame($content, ECKey::convertPublicKeyToPEM($jwk)); } - /** - * @test - */ - public function createFromPublicEC512KeyFile(): void + public function publicKeysAndPem(): iterable { - $result = JWKFactory::createFromKeyFile(__DIR__ . '/Keys/EC/public.es512.key'); - - static::assertSame( + yield [ + __DIR__ . '/Keys/EC/public.es256.key', + '{"kty":"EC","crv":"P-256","x":"vuYsP-QnrqAbM7Iyhzjt08hFSuzapyojCB_gFsBt65U","y":"oq-E2K-X0kPeqGuKnhlXkxc5fnxomRSC6KLby7Ij8AE"}', + ]; + yield [ + __DIR__ . '/Keys/EC/public.es384.key', + '{"kty":"EC","crv":"P-384","x":"6f-XZsg2Tvn0EoEapQ-ylMYNtsm8CPf0cb8HI2EkfY9Bqpt3QMzwlM7mVsFRmaMZ","y":"b8nOnRwmpmEnvA2U8ydS-dbnPv7bwYl-q1qNeh8Wpjor3VO-RTt4ce0Pn25oGGWU"}', + ]; + yield [ + __DIR__ . '/Keys/EC/public.es512.key', '{"kty":"EC","crv":"P-521","x":"AVpvo7TGpQk5P7ZLo0qkBpaT-fFDv6HQrWElBKMxcrJd_mRNapweATsVv83YON4lTIIRXzgGkmWeqbDr6RQO-1cS","y":"AIs-MoRmLaiPyG2xmPwQCHX2CGX_uCZiT3iOxTAJEZuUbeSA828K4WfAA4ODdGiB87YVShhPOkiQswV3LpbpPGhC"}', - json_encode($result, JSON_THROW_ON_ERROR) - ); + ]; } /** @@ -277,9 +276,9 @@ public function createFromValues(): void /** * @test - * @dataProvider dataEd25519Keys + * @dataProvider dataKeys */ - public function loadEd25519KeyPEMEncoded(string $filename, array $expectedValues): void + public function loadKeyPEMEncoded(string $filename, array $expectedValues): void { $jwk = JWKFactory::createFromKeyFile($filename); @@ -289,7 +288,7 @@ public function loadEd25519KeyPEMEncoded(string $filename, array $expectedValues /** * @return array> */ - public function dataEd25519Keys(): array + public function dataKeys(): array { return [ [ @@ -297,7 +296,7 @@ public function dataEd25519Keys(): array 'values' => [ 'kty' => 'OKP', 'crv' => 'Ed448', - 'x' => 'AMMByg1e7OHwYZhUk82KK5Wk6BlzXLGu0mpGXXpE_7HsQ-RDY-ZVj-vyl_f7vvsP0EpvNKzTqHY9AA', + 'x' => 'wwHKDV7s4fBhmFSTzYorlaToGXNcsa7SakZdekT_sexD5ENj5lWP6_KX9_u--w_QSm80rNOodj0A', ], ], [ @@ -305,7 +304,7 @@ public function dataEd25519Keys(): array 'values' => [ 'kty' => 'OKP', 'crv' => 'Ed25519', - 'x' => 'AMKyN9wBI9eShx2KZbnlBOXJySrWzPKFRxX-sBp8NqDh', + 'x' => 'wrI33AEj15KHHYplueUE5cnJKtbM8oVHFf6wGnw2oOE', ], ], [ @@ -313,7 +312,7 @@ public function dataEd25519Keys(): array 'values' => [ 'kty' => 'OKP', 'crv' => 'X448', - 'x' => 'AFKDw-9zUAAgvAPqLQ1Fbp-CKzLJO--UoTEX-E4Q66uMWNngCPqTiFbo67wV13fYPIFDcAU9H5p3', + 'x' => 'UoPD73NQACC8A-otDUVun4IrMsk775ShMRf4ThDrq4xY2eAI-pOIVujrvBXXd9g8gUNwBT0fmnc', ], ], [ @@ -321,7 +320,7 @@ public function dataEd25519Keys(): array 'values' => [ 'kty' => 'OKP', 'crv' => 'X25519', - 'x' => 'ANziS4n35jgkBrYpdt0Bsp9J5PRASqGcej_uqPhJ7AQe', + 'x' => '3OJLiffmOCQGtil23QGyn0nk9EBKoZx6P-6o-EnsBB4', ], ], [ @@ -346,7 +345,7 @@ public function dataEd25519Keys(): array 'kty' => 'OKP', 'crv' => 'Ed25519', 'd' => 'Pr9AxZivB-zSq95wLrZfYa7DQ3TUPqZTkP_0w33r3rc', - 'x' => 'QUVDMzQzNzRENDNFQTY1MzkwRkZGNEMzN0RFQkRFQjc', + 'x' => 'uRhai1TsvrSB43HD-36TQ2hMQfV8ruJz7F8o0wIe1VI', ], ], [ @@ -354,9 +353,9 @@ public function dataEd25519Keys(): array 'values' => [ 'kty' => 'EC', 'crv' => 'P-384', + 'd' => '31taDOPQnlNl2aBC_EaGTqVGjGN_qg6iuLwP6cVTmhKMQ5PTL67wS6mmyKi8GdVP', 'x' => 'j0w1Y3bRXLNKVhIp0i5VtZwh7gWIKEcKIFXZa8N_7idIdW7_o6djgDHedTI_BeLy', 'y' => 'x-IGyHQ2pZRM-OAWfRGe2E9y0rcbukq9GdIgFcPmXU_P8B0tvtgxz3KH0WKwkX5K', - 'd' => '31taDOPQnlNl2aBC_EaGTqVGjGN_qg6iuLwP6cVTmhKMQ5PTL67wS6mmyKi8GdVP', ], ], [ @@ -365,7 +364,7 @@ public function dataEd25519Keys(): array 'kty' => 'OKP', 'crv' => 'X25519', 'd' => 'mG-fgDwkr58hwIeqCQKZbR8HKeY4yg_AzvU6zyNaVUE', - 'x' => 'MUYwNzI5RTYzOENBMEZDMENFRjUzQUNGMjM1QTU1NDE', + 'x' => '3OJLiffmOCQGtil23QGyn0nk9EBKoZx6P-6o-EnsBB4', ], ], ]; diff --git a/tests/Component/KeyManagement/JWKTest.php b/tests/Component/KeyManagement/JWKTest.php index 9948800a..460bcd83 100644 --- a/tests/Component/KeyManagement/JWKTest.php +++ b/tests/Component/KeyManagement/JWKTest.php @@ -7,6 +7,7 @@ use InvalidArgumentException; use Jose\Component\Core\JWK; use Jose\Component\Core\JWKSet; +use Jose\Component\Core\Util\RSAKey; use Jose\Component\KeyManagement\JWKFactory; use const JSON_THROW_ON_ERROR; use ParagonIE\ConstantTime\Base64UrlSafe; @@ -239,4 +240,39 @@ public function loadCertificateChain(): void $key->all() ); } + + /** + * @test + */ + public function theRSAKeyIsCorrectlyConvertedIntoPEM(): void + { + // Given + $key = new JWK([ + 'kty' => 'RSA', + 'n' => 'z62tHQzm4fDHipqlcrNhC1gUdn0N38pmlcQbVlLvtZf1aRm1OO43cB9YQyWr1MsTrYH4nyWZDMPIGY_BsIfYw1lp9fo2D1tpG2vtCaKRETVimu-N9DySQ9vYs6n8lG0vXy_spK7sGrOLFooijDSt0LYrYrZY9UI3OkyEAKUbZLJhxi7nT3CPtMCYDUMIIt1LgWdR6-ha5fQQrWF7YbyiMNmITg64DZ9yof4-OfouNE2dFXGl3Nr92HaugXbMZF_pILpcB61NT215aql1ifVXvEyGAsyPBnxIcjadfcgQ0UUtepN2BJRj_pq55jfQR2Nl0e11JeKEIPR3ypqvKeDI10Cl-qr9GpU0rFfw2vcp8IHTNrAeam4nTRDVCmXGwiMaLifAKbvfGwxaA2mHbO5i4669KiPf_lXAQz9FzAZZRwpdM1FTB9BlB5R-JgvtBabP5ZGhqlUOgkJM_4UfrpcIkS8Ub4Y60QvPkInCGBMHNdUqpJUkLoA5Mddl8hVW-cMjC2qCckgT1KgZxIsZTgOJXCARX1IObFJNoinxYJ5SNX9bCSRtgefuBKE7BSNukAkHyBPf---kEi9GbYXzlJr-yCMAIsA0UoiEx264hkAF9zF-N1yRhS_QmrhzU5hpj1IE8WRCqyIZV8f_IbSGXBue7MmgknLVRWHuGqehkTSfiNE', + 'e' => 'AQAB', + ]); + + // When + $pem = RSAKey::createFromJWK($key)->toPEM(); + + // Then + static::assertSame( + '-----BEGIN RSA PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAz62tHQzm4fDHipqlcrNh +C1gUdn0N38pmlcQbVlLvtZf1aRm1OO43cB9YQyWr1MsTrYH4nyWZDMPIGY/BsIfY +w1lp9fo2D1tpG2vtCaKRETVimu+N9DySQ9vYs6n8lG0vXy/spK7sGrOLFooijDSt +0LYrYrZY9UI3OkyEAKUbZLJhxi7nT3CPtMCYDUMIIt1LgWdR6+ha5fQQrWF7Ybyi +MNmITg64DZ9yof4+OfouNE2dFXGl3Nr92HaugXbMZF/pILpcB61NT215aql1ifVX +vEyGAsyPBnxIcjadfcgQ0UUtepN2BJRj/pq55jfQR2Nl0e11JeKEIPR3ypqvKeDI +10Cl+qr9GpU0rFfw2vcp8IHTNrAeam4nTRDVCmXGwiMaLifAKbvfGwxaA2mHbO5i +4669KiPf/lXAQz9FzAZZRwpdM1FTB9BlB5R+JgvtBabP5ZGhqlUOgkJM/4UfrpcI +kS8Ub4Y60QvPkInCGBMHNdUqpJUkLoA5Mddl8hVW+cMjC2qCckgT1KgZxIsZTgOJ +XCARX1IObFJNoinxYJ5SNX9bCSRtgefuBKE7BSNukAkHyBPf+++kEi9GbYXzlJr+ +yCMAIsA0UoiEx264hkAF9zF+N1yRhS/QmrhzU5hpj1IE8WRCqyIZV8f/IbSGXBue +7MmgknLVRWHuGqehkTSfiNECAwEAAQ== +-----END RSA PUBLIC KEY-----', + $pem + ); + } } diff --git a/tests/Component/KeyManagement/Keys/ECKeysTest.php b/tests/Component/KeyManagement/Keys/ECKeysTest.php index ba8ce8de..ce5ca639 100644 --- a/tests/Component/KeyManagement/Keys/ECKeysTest.php +++ b/tests/Component/KeyManagement/Keys/ECKeysTest.php @@ -4,6 +4,7 @@ namespace Jose\Tests\Component\KeyManagement\Keys; +use Jose\Component\Core\Util\ECKey; use const DIRECTORY_SEPARATOR; use InvalidArgumentException; use Jose\Component\Core\JWK; @@ -43,9 +44,9 @@ public function loadPrivateEC256KeyGenerateByAPN(): void static::assertSame($details, [ 'kty' => 'EC', 'crv' => 'P-256', + 'd' => '13n3isfsEktzl-CtH5ECpRrKk-40prVuCbldkP77gak', 'x' => 'YcIMUkalwbeeAVkUF6FP3aBVlCzlqxEd7i0uN_4roA0', 'y' => 'bU8wOWJBkTNZ61gB1_4xp-r8-uVsQB8D6Xsl-aKMCy8', - 'd' => '13n3isfsEktzl-CtH5ECpRrKk-40prVuCbldkP77gak', ]); } @@ -71,17 +72,25 @@ public function loadPublicEC256Key(): void */ public function loadPrivateEC256Key(): void { + // Given $private_pem = file_get_contents( 'file://' . __DIR__ . DIRECTORY_SEPARATOR . 'EC' . DIRECTORY_SEPARATOR . 'private.es256.key' ); - $details = KeyConverter::loadFromKey($private_pem); - static::assertSame($details, [ + $expectedValues = [ 'kty' => 'EC', 'crv' => 'P-256', 'd' => 'q_VkzNnxTG39jHB0qkwA_SeVXud7yCHT7kb7kZv-0xQ', 'x' => 'vuYsP-QnrqAbM7Iyhzjt08hFSuzapyojCB_gFsBt65U', 'y' => 'oq-E2K-X0kPeqGuKnhlXkxc5fnxomRSC6KLby7Ij8AE', - ]); + ]; + + // Whent + $details = KeyConverter::loadFromKey($private_pem, 'test'); + + //Then + static::assertSame($details, $expectedValues); + $ecKey = ECKey::convertPrivateKeyToPEM(new JWK($expectedValues)); + static::assertSame($private_pem, $ecKey); } /** diff --git a/tests/Component/KeyManagement/Keys/RSA/private.key b/tests/Component/KeyManagement/Keys/RSA/private.key index 5eb9c347..d3cb4f26 100644 --- a/tests/Component/KeyManagement/Keys/RSA/private.key +++ b/tests/Component/KeyManagement/Keys/RSA/private.key @@ -1,4 +1,4 @@ ------BEGIN PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAN91kQxBuaze3WjI CNjeR/HD8E3kDzp89+Lhtn3tMish4yQxhNl6BEkabuS3pUj3WDP6+AFjBVqA1j3f u8Wqu7hRJDPHOs2kCII+LhIqvqQTLx/nvNOUhW2DimKn0HuHnlwJODq0MHFJEq5R @@ -13,4 +13,4 @@ RVdVjA/gFqJp1Q+VWdS1tvYRIqmadkECQCVdqQuwgedEHmcewtNod42crjmwvWBx BKMTl6/WT4zwVb41eUujVWo0LHRLuCoK//GDqmloIh6L3MU8MqnIGb0CQFWcpD4/ roCkMblk0hPoQPpyapJexc438x7XuEGFEhyxxauqC5R4YFKCf+KBS2gZgr4GSwBU Qww+qZ3eRYM7faM= ------END PRIVATE KEY----- +-----END RSA PRIVATE KEY----- diff --git a/tests/Component/KeyManagement/Keys/RSA/public.key b/tests/Component/KeyManagement/Keys/RSA/public.key index 50291024..73e0de9b 100644 --- a/tests/Component/KeyManagement/Keys/RSA/public.key +++ b/tests/Component/KeyManagement/Keys/RSA/public.key @@ -1,4 +1,4 @@ ------BEGIN PUBLIC KEY----- +-----BEGIN RSA PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtpS1ZmfVKVP5KofIhMBP 0tSWc4qlh6fm2lrZSkuKxUjEaWjzZSzs72gEIGxraWusMdoRuV54xsWRyf5KeZT0 S+I5Prle3Idi3gICiO4NwvMk6JwSBcJWwmSLFEKyUSnB2CtfiGc0/5rQCpcEt/Dn @@ -6,4 +6,4 @@ S+I5Prle3Idi3gICiO4NwvMk6JwSBcJWwmSLFEKyUSnB2CtfiGc0/5rQCpcEt/Dn t2DMnH1JjoXdDXfxvSP/0gjUYb0ektudYFXoA6wekmQyJeImvgx4Myz1I4iHtkY/ Cp7J4Mn1ejZ6HNmyvoTE/4OuY1uCeYv4UyXFc1s1uUyYtj4z57qsHGsS4dQ3A2MJ swIDAQAB ------END PUBLIC KEY----- +-----END RSA PUBLIC KEY----- diff --git a/tests/Component/KeyManagement/Keys/RSAKeysTest.php b/tests/Component/KeyManagement/Keys/RSAKeysTest.php index 2d958510..1cefdfbe 100644 --- a/tests/Component/KeyManagement/Keys/RSAKeysTest.php +++ b/tests/Component/KeyManagement/Keys/RSAKeysTest.php @@ -4,6 +4,9 @@ namespace Jose\Tests\Component\KeyManagement\Keys; +use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence; +use SpomkyLabs\Pki\ASN1\Type\Primitive\RelativeOID; +use SpomkyLabs\Pki\CryptoEncoding\PEM; use const DIRECTORY_SEPARATOR; use InvalidArgumentException; use Jose\Component\Core\JWK; @@ -134,9 +137,14 @@ public function loadPublicRSAKeyFromValues(): void */ public function loadPrivateRSAKey(): void { - $file = 'file://' . __DIR__ . DIRECTORY_SEPARATOR . 'RSA' . DIRECTORY_SEPARATOR . 'private.key'; - $rsa_key = RSAKey::createFromPEM($file); + // Given + $file = __DIR__ . '/RSA/private.key'; + $content = trim(file_get_contents($file)); + + // When + $rsaKey = RSAKey::createFromPEM('file://'.$file); + // Then static::assertEqualsCanonicalizing([ 'kty' => 'RSA', 'n' => '33WRDEG5rN7daMgI2N5H8cPwTeQPOnz34uG2fe0yKyHjJDGE2XoESRpu5LelSPdYM_r4AWMFWoDWPd-7xaq7uFEkM8c6zaQIgj4uEiq-pBMvH-e805SFbYOKYqfQe4eeXAk4OrQwcUkSrlGskf6YUaw_3IwbPgzEDTgTZFVtQlE', @@ -147,16 +155,17 @@ public function loadPrivateRSAKey(): void 'dp' => '5m79fpE1Jz0YE1ijT7ivOMAws-fnTCnR08eiB8-W36GBWplbHaXejrJFV1WMD-AWomnVD5VZ1LW29hEiqZp2QQ', 'dq' => 'JV2pC7CB50QeZx7C02h3jZyuObC9YHEEoxOXr9ZPjPBVvjV5S6NVajQsdEu4Kgr_8YOqaWgiHovcxTwyqcgZvQ', 'qi' => 'VZykPj-ugKQxuWTSE-hA-nJqkl7FzjfzHte4QYUSHLHFq6oLlHhgUoJ_4oFLaBmCvgZLAFRDDD6pnd5Fgzt9ow', - ], $rsa_key->toArray()); - static::assertFalse($rsa_key->isPublic()); + ], $rsaKey->toArray()); + static::assertFalse($rsaKey->isPublic()); - $public_key = RSAKey::toPublic($rsa_key); + $public_key = RSAKey::toPublic($rsaKey); static::assertSame([ 'kty' => 'RSA', 'n' => '33WRDEG5rN7daMgI2N5H8cPwTeQPOnz34uG2fe0yKyHjJDGE2XoESRpu5LelSPdYM_r4AWMFWoDWPd-7xaq7uFEkM8c6zaQIgj4uEiq-pBMvH-e805SFbYOKYqfQe4eeXAk4OrQwcUkSrlGskf6YUaw_3IwbPgzEDTgTZFVtQlE', 'e' => 'AQAB', ], $public_key->toArray()); static::assertTrue($public_key->isPublic()); + static::assertSame($content, \Jose\Component\Core\Util\RSAKey::createFromJWK($rsaKey->toJwk())->toPEM()); } /**