primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_array;
use function is_string;
#[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.')
->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object')
->addArgument('jwk', InputArgument::REQUIRED, 'The new JWK object');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$jwkset = $this->getKeyset($input);
$jwk = $this->getKey($input);
$jwkset = $jwkset->with($jwk);
$this->prepareJsonOutput($input, $output, $jwkset);
return self::SUCCESS;
}
private function getKeyset(InputInterface $input): JWKSet
{
$jwkset = $input->getArgument('jwkset');
if (! is_string($jwkset)) {
throw new InvalidArgumentException('The argument must be a valid JWKSet.');
}
$json = JsonConverter::decode($jwkset);
if (! is_array($json)) {
throw new InvalidArgumentException('The argument must be a valid JWKSet.');
}
return JWKSet::createFromKeyData($json);
}
private function getKey(InputInterface $input): JWK
{
$jwk = $input->getArgument('jwk');
if (! is_string($jwk)) {
throw new InvalidArgumentException('The argument must be a valid JWK.');
}
$json = JsonConverter::decode($jwk);
if (! is_array($json)) {
throw new InvalidArgumentException('The argument must be a valid JWK.');
}
return new JWK($json);
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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;
use function is_string;
#[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->addArgument('curve', InputArgument::REQUIRED, 'Curve of the key.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$curve = $input->getArgument('curve');
if (! is_string($curve)) {
throw new InvalidArgumentException('Invalid curve');
}
$args = $this->getOptions($input);
$jwk = JWKFactory::createECKey($curve, $args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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;
use function is_string;
#[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.')
->addArgument('curve', InputArgument::REQUIRED, 'Curve of the keys.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$quantity = (int) $input->getArgument('quantity');
if ($quantity < 1) {
throw new InvalidArgumentException('Invalid quantity');
}
$curve = $input->getArgument('curve');
if (! is_string($curve)) {
throw new InvalidArgumentException('Invalid curve');
}
$keyset = new JWKSet([]);
for ($i = 0; $i < $quantity; ++$i) {
$args = $this->getOptions($input);
$keyset = $keyset->with(JWKFactory::createECKey($curve, $args));
}
$this->prepareJsonOutput($input, $output, $keyset);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
use Jose\Component\Core\Util\Base64UrlSafe;
use Jose\Component\KeyManagement\JWKFactory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use function is_bool;
abstract class GeneratorCommand extends ObjectOutputCommand
{
public function isEnabled(): bool
{
return class_exists(JWKFactory::class);
}
protected function configure(): void
{
parent::configure();
$this
->addOption('use', 'u', InputOption::VALUE_OPTIONAL, 'Usage of the key. Must be either "sig" or "enc".')
->addOption('alg', 'a', InputOption::VALUE_OPTIONAL, 'Algorithm for the key.')
->addOption(
'random_id',
null,
InputOption::VALUE_NONE,
'If this option is set, a random key ID (kid) will be generated.'
);
}
protected function getOptions(InputInterface $input): array
{
$args = [];
$useRandomId = $input->getOption('random_id');
if (! is_bool($useRandomId)) {
throw new InvalidArgumentException('Invalid value for option "random_id"');
}
if ($useRandomId) {
$args['kid'] = $this->generateKeyID();
}
foreach (['use', 'alg'] as $key) {
$value = $input->getOption($key);
if ($value !== null) {
$args[$key] = $value;
}
}
return $args;
}
private function generateKeyID(): string
{
return Base64UrlSafe::encode(random_bytes(32));
}
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_array;
use function is_string;
#[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.')
->addOption('hash', null, InputOption::VALUE_OPTIONAL, 'The hashing algorithm.', 'sha256');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$jwk = $input->getArgument('jwk');
if (! is_string($jwk)) {
throw new InvalidArgumentException('Invalid JWK');
}
$hash = $input->getOption('hash');
if (! is_string($hash)) {
throw new InvalidArgumentException('Invalid hash algorithm');
}
$json = JsonConverter::decode($jwk);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid input.');
}
$key = new JWK($json);
$output->write($key->thumbprint($hash));
return self::SUCCESS;
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_string;
#[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($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');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$url = $input->getArgument('url');
if (! is_string($url)) {
throw new InvalidArgumentException('Invalid URL');
}
$result = $this->jkuFactory->loadFromUrl($url);
$this->prepareJsonOutput($input, $output, $result);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_array;
use function is_string;
#[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($name);
}
protected function configure(): void
{
parent::configure();
$this->setHelp('This command will analyze a JWK object and find security issues.')
->addArgument('jwk', InputArgument::REQUIRED, 'The JWK object');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->getFormatter()
->setStyle('success', new OutputFormatterStyle('white', 'green'));
$output->getFormatter()
->setStyle('high', new OutputFormatterStyle('white', 'red', ['bold']));
$output->getFormatter()
->setStyle('medium', new OutputFormatterStyle('yellow'));
$output->getFormatter()
->setStyle('low', new OutputFormatterStyle('blue'));
$jwk = $this->getKey($input);
$result = $this->analyzerManager->analyze($jwk);
if ($result->count() === 0) {
$output->writeln('<success>All good! No issue found.</success>');
} else {
foreach ($result->all() as $message) {
$output->writeln(
'<' . $message->getSeverity() . '>* ' . $message->getMessage() . '</' . $message->getSeverity() . '>'
);
}
}
return self::SUCCESS;
}
private function getKey(InputInterface $input): JWK
{
$jwk = $input->getArgument('jwk');
if (! is_string($jwk)) {
throw new InvalidArgumentException('Invalid JWK');
}
$json = JsonConverter::decode($jwk);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid JWK.');
}
return new JWK($json);
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function is_string;
#[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.')
->addOption('secret', 's', InputOption::VALUE_OPTIONAL, 'Secret if the key is encrypted.', null);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$file = $input->getArgument('file');
$password = $input->getOption('secret');
if (! is_string($file)) {
throw new InvalidArgumentException('Invalid file');
}
if ($password !== null && ! is_string($password)) {
throw new InvalidArgumentException('Invalid secret');
}
$args = $this->getOptions($input);
$jwk = JWKFactory::createFromKeyFile($file, $password, $args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
use Jose\Component\Core\JWKSet;
use Jose\Component\Core\Util\JsonConverter;
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;
use function is_array;
use function is_string;
#[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,
?string $name = null
) {
parent::__construct($name);
}
protected function configure(): void
{
parent::configure();
$this->setHelp('This command will analyze a JWKSet object and find security issues.')
->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->getFormatter()
->setStyle('success', new OutputFormatterStyle('white', 'green'));
$output->getFormatter()
->setStyle('high', new OutputFormatterStyle('white', 'red', ['bold']));
$output->getFormatter()
->setStyle('medium', new OutputFormatterStyle('yellow'));
$output->getFormatter()
->setStyle('low', new OutputFormatterStyle('blue'));
$jwkset = $this->getKeyset($input);
$messages = $this->keysetAnalyzerManager->analyze($jwkset);
$this->showMessages($messages, $output);
foreach ($jwkset as $kid => $jwk) {
$output->writeln(sprintf('Analysing key with index/kid "%s"', $kid));
$messages = $this->keyAnalyzerManager->analyze($jwk);
$this->showMessages($messages, $output);
}
return self::SUCCESS;
}
private function showMessages(MessageBag $messages, OutputInterface $output): void
{
if ($messages->count() === 0) {
$output->writeln(' <success>All good! No issue found.</success>');
} else {
foreach ($messages->all() as $message) {
$output->writeln(
' <' . $message->getSeverity() . '>* ' . $message->getMessage() . '</' . $message->getSeverity() . '>'
);
}
}
}
private function getKeyset(InputInterface $input): JWKSet
{
$jwkset = $input->getArgument('jwkset');
if (! is_string($jwkset)) {
throw new InvalidArgumentException('Invalid JWKSet');
}
$json = JsonConverter::decode($jwkset);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid JWKSet');
}
return JWKSet::createFromKeyData($json);
}
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_array;
#[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.'
)
->addArgument('jwksets', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'The JWKSet objects');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var string[] $keySets */
$keySets = $input->getArgument('jwksets');
$newJwkset = new JWKSet([]);
foreach ($keySets as $keySet) {
$json = JsonConverter::decode($keySet);
if (! is_array($json)) {
throw new InvalidArgumentException('The argument must be a valid JWKSet.');
}
$jwkset = JWKSet::createFromKeyData($json);
foreach ($jwkset->all() as $jwk) {
$newJwkset = $newJwkset->with($jwk);
}
}
$this->prepareJsonOutput($input, $output, $newJwkset);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
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);
$jwk = JWKFactory::createNoneKey($args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use Jose\Component\Core\Util\JsonConverter;
use JsonSerializable;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
abstract class ObjectOutputCommand extends Command
{
protected function prepareJsonOutput(InputInterface $input, OutputInterface $output, JsonSerializable $json): void
{
$data = JsonConverter::encode($json);
$output->write($data);
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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->addArgument('size', InputArgument::REQUIRED, 'Key size.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$size = (int) $input->getArgument('size');
if ($size < 1) {
throw new InvalidArgumentException('Invalid size');
}
$args = $this->getOptions($input);
$jwk = JWKFactory::createOctKey($size, $args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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.')
->addArgument('size', InputArgument::REQUIRED, 'Key size.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$quantity = (int) $input->getArgument('quantity');
$size = (int) $input->getArgument('size');
if ($quantity < 1) {
throw new InvalidArgumentException('Invalid quantity');
}
if ($size < 1) {
throw new InvalidArgumentException('Invalid size');
}
$keyset = new JWKSet([]);
for ($i = 0; $i < $quantity; ++$i) {
$args = $this->getOptions($input);
$keyset = $keyset->with(JWKFactory::createOctKey($size, $args));
}
$this->prepareJsonOutput($input, $output, $keyset);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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;
use function is_string;
#[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->addArgument('curve', InputArgument::REQUIRED, 'Curve of the key.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$curve = $input->getArgument('curve');
if (! is_string($curve)) {
throw new InvalidArgumentException('Invalid curve');
}
$args = $this->getOptions($input);
$jwk = JWKFactory::createOKPKey($curve, $args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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;
use function is_string;
#[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.')
->addArgument('curve', InputArgument::REQUIRED, 'Curve of the keys.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$quantity = (int) $input->getArgument('quantity');
$curve = $input->getArgument('curve');
if ($quantity < 1) {
throw new InvalidArgumentException('Invalid quantity');
}
if (! is_string($curve)) {
throw new InvalidArgumentException('Invalid curve');
}
$keyset = new JWKSet([]);
for ($i = 0; $i < $quantity; ++$i) {
$args = $this->getOptions($input);
$keyset = $keyset->with(JWKFactory::createOKPKey($curve, $args));
}
$this->prepareJsonOutput($input, $output, $keyset);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_array;
use function is_string;
#[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.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$jwk = $input->getArgument('jwk');
if (! is_string($jwk)) {
throw new InvalidArgumentException('Invalid JWK');
}
$json = JsonConverter::decode($jwk);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid JWK');
}
$key = RSAKey::createFromJWK(new JWK($json));
$key->optimize();
$this->prepareJsonOutput($input, $output, $key->toJwk());
return self::SUCCESS;
}
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function is_string;
#[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.')
->addOption('secret', 's', InputOption::VALUE_OPTIONAL, 'Secret if the key is encrypted.', null);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$file = $input->getArgument('file');
$password = $input->getOption('secret');
if (! is_string($file)) {
throw new InvalidArgumentException('Invalid file');
}
if (! is_string($password)) {
throw new InvalidArgumentException('Invalid secret');
}
$args = $this->getOptions($input);
$jwk = JWKFactory::createFromPKCS12CertificateFile($file, $password, $args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
use Jose\Component\Core\JWK;
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;
use function is_array;
use function is_string;
#[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');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$jwk = $input->getArgument('jwk');
if (! is_string($jwk)) {
throw new InvalidArgumentException('Invalid JWK');
}
$json = JsonConverter::decode($jwk);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid JWK.');
}
$key = new JWK($json);
$pem = match ($key->get('kty')) {
'RSA' => RSAKey::createFromJWK($key)->toPEM(),
'EC' => ECKey::convertToPEM($key),
default => throw new InvalidArgumentException('Not a RSA or EC key.'),
};
$output->write($pem);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_array;
use function is_string;
#[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.')
->addArgument('jwk', InputArgument::REQUIRED, 'The JWK object');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$jwk = $this->getKey($input);
$jwk = $jwk->toPublic();
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
private function getKey(InputInterface $input): JWK
{
$jwk = $input->getArgument('jwk');
if (! is_string($jwk)) {
throw new InvalidArgumentException('Invalid JWK');
}
$json = JsonConverter::decode($jwk);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid JWK');
}
return new JWK($json);
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_array;
use function is_string;
#[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.')
->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$jwkset = $this->getKeyset($input);
$newJwkset = new JWKSet([]);
foreach ($jwkset->all() as $jwk) {
$newJwkset = $newJwkset->with($jwk->toPublic());
}
$this->prepareJsonOutput($input, $output, $newJwkset);
return self::SUCCESS;
}
private function getKeyset(InputInterface $input): JWKSet
{
$jwkset = $input->getArgument('jwkset');
if (! is_string($jwkset)) {
throw new InvalidArgumentException('Invalid JWKSet');
}
$json = JsonConverter::decode($jwkset);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid JWKSet');
}
return JWKSet::createFromKeyData($json);
}
}

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function count;
use function is_array;
use function is_string;
#[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.')
->addArgument('jwkset', InputArgument::REQUIRED, 'The JWKSet object')
->addArgument('jwk', InputArgument::REQUIRED, 'The new JWK object');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$jwkset = $this->getKeyset($input)
->all();
$jwk = $this->getKey($input);
if (count($jwkset) !== 0) {
array_pop($jwkset);
}
array_unshift($jwkset, $jwk);
$this->prepareJsonOutput($input, $output, new JWKSet($jwkset));
return self::SUCCESS;
}
private function getKeyset(InputInterface $input): JWKSet
{
$jwkset = $input->getArgument('jwkset');
if (! is_string($jwkset)) {
throw new InvalidArgumentException('Invalid JWKSet');
}
$json = JsonConverter::decode($jwkset);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid JWKSet');
}
return JWKSet::createFromKeyData($json);
}
private function getKey(InputInterface $input): JWK
{
$jwk = $input->getArgument('jwk');
if (! is_string($jwk)) {
throw new InvalidArgumentException('Invalid JWK');
}
$json = JsonConverter::decode($jwk);
if (! is_array($json)) {
throw new InvalidArgumentException('Invalid JWK');
}
return new JWK($json);
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$size = (int) $input->getArgument('size');
$args = $this->getOptions($input);
if ($size < 1) {
throw new InvalidArgumentException('Invalid size');
}
$jwk = JWKFactory::createRSAKey($size, $args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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.')
->addArgument('size', InputArgument::REQUIRED, 'Key size.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$quantity = (int) $input->getArgument('quantity');
$size = (int) $input->getArgument('size');
if ($quantity < 1) {
throw new InvalidArgumentException('Invalid quantity');
}
if ($size < 1) {
throw new InvalidArgumentException('Invalid size');
}
$keyset = new JWKSet([]);
for ($i = 0; $i < $quantity; ++$i) {
$args = $this->getOptions($input);
$keyset = $keyset->with(JWKFactory::createRSAKey($size, $args));
}
$this->prepareJsonOutput($input, $output, $keyset);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function is_bool;
use function is_string;
#[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')
->addOption(
'is_b64',
'b',
InputOption::VALUE_NONE,
'Indicates if the secret is Base64 encoded (useful for binary secrets)'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$secret = $input->getArgument('secret');
if (! is_string($secret)) {
throw new InvalidArgumentException('Invalid secret');
}
$isBsae64Encoded = $input->getOption('is_b64');
if (! is_bool($isBsae64Encoded)) {
throw new InvalidArgumentException('Invalid option value for "is_b64"');
}
if ($isBsae64Encoded) {
$secret = base64_decode($secret, true);
}
if (! is_string($secret)) {
throw new InvalidArgumentException('Invalid secret');
}
$args = $this->getOptions($input);
$jwk = JWKFactory::createFromSecret($secret, $args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
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;
use function is_string;
#[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.');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$file = $input->getArgument('file');
if (! is_string($file)) {
throw new InvalidArgumentException('Invalid file');
}
$args = [];
foreach (['use', 'alg'] as $key) {
$value = $input->getOption($key);
if ($value !== null) {
$args[$key] = $value;
}
}
$jwk = JWKFactory::createFromCertificateFile($file, $args);
$this->prepareJsonOutput($input, $output, $jwk);
return self::SUCCESS;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Jose\Component\Console;
use InvalidArgumentException;
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;
use function is_string;
#[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,
?string $name = null
) {
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.'
)
->addArgument('url', InputArgument::REQUIRED, 'The URL');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$url = $input->getArgument('url');
if (! is_string($url)) {
throw new InvalidArgumentException('Invalid URL');
}
$result = $this->x5uFactory->loadFromUrl($url);
$this->prepareJsonOutput($input, $output, $result);
return self::SUCCESS;
}
}