Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security] Support RSA algorithm signature for OIDC tokens #53682

Merged
merged 1 commit into from Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions composer.json
Expand Up @@ -158,8 +158,7 @@
"twig/cssinliner-extra": "^2.12|^3",
"twig/inky-extra": "^2.12|^3",
"twig/markdown-extra": "^2.12|^3",
"web-token/jwt-checker": "^3.1",
"web-token/jwt-signature-algorithm-ecdsa": "^3.1"
"web-token/jwt-library": "^3.3.2"
},
"conflict": {
"ext-psr": "<1.1|>=2",
Expand Down
Expand Up @@ -82,6 +82,7 @@ class UnusedTagsPass implements CompilerPassInterface
'routing.route_loader',
'scheduler.schedule_provider',
'scheduler.task',
'security.access_token_handler.oidc.signature_algorithm',
'security.authenticator.login_linker',
'security.expression_language_provider',
'security.remember_me_handler',
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/CHANGELOG.md
Expand Up @@ -5,6 +5,8 @@ CHANGELOG
---

* Mark class `ExpressionCacheWarmer` as `final`
* Support multiple signature algorithms for OIDC Token
* Support JWK or JWKSet for OIDC Token

7.0
---
Expand Down
Expand Up @@ -13,10 +13,10 @@

use Jose\Component\Core\Algorithm;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;

/**
* Configures a token handler for decoding and validating an OIDC token.
Expand All @@ -31,22 +31,15 @@
->replaceArgument(4, $config['claim'])
);

if (!ContainerBuilder::willBeAvailable('web-token/jwt-core', Algorithm::class, ['symfony/security-bundle'])) {
throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-core" is not installed. Try running "composer require web-token/jwt-core".');
if (!ContainerBuilder::willBeAvailable('web-token/jwt-library', Algorithm::class, ['symfony/security-bundle'])) {
throw new LogicException('You cannot use the "oidc" token handler since "web-token/jwt-library" is not installed. Try running "composer require web-token/jwt-library".');
}

// @see Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory
// for supported algorithms
if (\in_array($config['algorithm'], ['ES256', 'ES384', 'ES512'], true)) {
$tokenHandlerDefinition->replaceArgument(0, new Reference('security.access_token_handler.oidc.signature.'.$config['algorithm']));
} else {
$tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))
->replaceArgument(0, $config['algorithm'])
);
}
$tokenHandlerDefinition->replaceArgument(0, (new ChildDefinition('security.access_token_handler.oidc.signature'))
->replaceArgument(0, $config['algorithms']));

$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwk'))
->replaceArgument(0, $config['key'])
$tokenHandlerDefinition->replaceArgument(1, (new ChildDefinition('security.access_token_handler.oidc.jwkset'))
->replaceArgument(0, $config['keyset'])
chalasr marked this conversation as resolved.
Show resolved Hide resolved
);
}

Expand All @@ -60,7 +53,38 @@
$node
->arrayNode($this->getKey())
->fixXmlConfig($this->getKey())
->validate()
->ifTrue(static fn ($v) => !isset($v['algorithm']) && !isset($v['algorithms']))
->thenInvalid('You must set either "algorithm" or "algorithms".')
->end()
->validate()
->ifTrue(static fn ($v) => !isset($v['key']) && !isset($v['keyset']))
->thenInvalid('You must set either "key" or "keyset".')
->end()
->beforeNormalization()
->ifTrue(static fn ($v) => isset($v['algorithm']) && \is_string($v['algorithm']))
->then(static function ($v) {
if (isset($v['algorithms'])) {
throw new InvalidConfigurationException('You cannot use both "algorithm" and "algorithms" at the same time.');
}
$v['algorithms'] = [$v['algorithm']];
unset($v['algorithm']);

return $v;
})
->end()
->beforeNormalization()
->ifTrue(static fn ($v) => isset($v['key']) && \is_string($v['key']))
->then(static function ($v) {
if (isset($v['keyset'])) {
throw new InvalidConfigurationException('You cannot use both "key" and "keyset" at the same time.');
}
$v['keyset'] = sprintf('{"keys":[%s]}', $v['key']);

return $v;
})
->end()
->children()

Check failure on line 87 in src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php:87:19: UndefinedMethod: Method Symfony\Component\Config\Definition\Builder\NodeDefinition::children does not exist (see https://psalm.dev/022)

Check failure on line 87 in src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php

View workflow job for this annotation

GitHub Actions / Psalm

UndefinedMethod

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/AccessToken/OidcTokenHandlerFactory.php:87:19: UndefinedMethod: Method Symfony\Component\Config\Definition\Builder\NodeDefinition::children does not exist (see https://psalm.dev/022)
->scalarNode('claim')
->info('Claim which contains the user identifier (e.g.: sub, email..).')
->defaultValue('sub')
Expand All @@ -72,14 +96,23 @@
->arrayNode('issuers')
->info('Issuers allowed to generate the token, for validation purpose.')
->isRequired()
->prototype('scalar')->end()
->scalarPrototype()->end()
->end()
->scalarNode('algorithm')
chalasr marked this conversation as resolved.
Show resolved Hide resolved
->arrayNode('algorithm')
->info('Algorithm used to sign the token.')
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "algorithms" option instead.')
->end()
->arrayNode('algorithms')
->info('Algorithms used to sign the token.')
->isRequired()
->scalarPrototype()->end()
->end()
->scalarNode('key')
->info('JSON-encoded JWK used to sign the token (must contain a "kty" key).')
->setDeprecated('symfony/security-bundle', '7.1', 'The "%node%" option is deprecated and will be removed in 8.0. Use the "keyset" option instead.')
->end()
->scalarNode('keyset')
->info('JSON-encoded JWKSet used to sign the token (must contain a list of valid keys).')
->isRequired()
->end()
->end()
Expand Down
@@ -1,43 +0,0 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;

use Jose\Component\Core\Algorithm as AlgorithmInterface;
use Jose\Component\Signature\Algorithm;
use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
use Symfony\Component\Security\Http\AccessToken\Oidc\OidcTokenHandler;

/**
* Creates a signature algorithm for {@see OidcTokenHandler}.
*
* @internal
*/
final class SignatureAlgorithmFactory
{
public static function create(string $algorithm): AlgorithmInterface
{
switch ($algorithm) {
case 'ES256':
case 'ES384':
case 'ES512':
if (!class_exists(Algorithm::class.'\\'.$algorithm)) {
throw new \LogicException(sprintf('You cannot use the "%s" signature algorithm since "web-token/jwt-signature-algorithm-ecdsa" is not installed. Try running "composer require web-token/jwt-signature-algorithm-ecdsa".', $algorithm));
}

$algorithm = Algorithm::class.'\\'.$algorithm;

return new $algorithm();
}

throw new InvalidArgumentException(sprintf('Unsupported signature algorithm "%s". Only ES* algorithms are supported. If you want to use another algorithm, create your TokenHandler as a service.', $algorithm));
}
}
Expand Up @@ -11,12 +11,19 @@

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Jose\Component\Core\Algorithm;
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\AlgorithmManagerFactory;
use Jose\Component\Core\JWK;
use Jose\Component\Core\JWKSet;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\Algorithm\ES384;
use Jose\Component\Signature\Algorithm\ES512;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SignatureAlgorithmFactory;
use Jose\Component\Signature\Algorithm\PS256;
use Jose\Component\Signature\Algorithm\PS384;
use Jose\Component\Signature\Algorithm\PS512;
use Jose\Component\Signature\Algorithm\RS256;
use Jose\Component\Signature\Algorithm\RS384;
use Jose\Component\Signature\Algorithm\RS512;
use Symfony\Component\Security\Http\AccessToken\ChainAccessTokenExtractor;
use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor;
use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor;
Expand Down Expand Up @@ -77,28 +84,56 @@

->set('security.access_token_handler.oidc.jwk', JWK::class)
->abstract()
->deprecate('symfony/security-http', '7.1', 'The "%service_id%" service is deprecated. Please use "security.access_token_handler.oidc.jwkset" instead')
->factory([JWK::class, 'createFromJson'])
->args([
abstract_arg('signature key'),
])

->set('security.access_token_handler.oidc.signature', Algorithm::class)
->set('security.access_token_handler.oidc.jwkset', JWKSet::class)
->abstract()
->factory([SignatureAlgorithmFactory::class, 'create'])
->factory([JWKSet::class, 'createFromJson'])
->args([
abstract_arg('signature algorithm'),
abstract_arg('signature keyset'),
])

->set('security.access_token_handler.oidc.algorithm_manager_factory', AlgorithmManagerFactory::class)
->args([
tagged_iterator('security.access_token_handler.oidc.signature_algorithm'),
])

->set('security.access_token_handler.oidc.signature', AlgorithmManager::class)
->abstract()
->factory([service('security.access_token_handler.oidc.algorithm_manager_factory'), 'create'])
->args([
abstract_arg('signature algorithms'),
])

->set('security.access_token_handler.oidc.signature.ES256', ES256::class)
->parent('security.access_token_handler.oidc.signature')
->args(['index_0' => 'ES256'])
->tag('security.access_token_handler.oidc.signature_algorithm')

->set('security.access_token_handler.oidc.signature.ES384', ES384::class)
->parent('security.access_token_handler.oidc.signature')
->args(['index_0' => 'ES384'])
->tag('security.access_token_handler.oidc.signature_algorithm')

->set('security.access_token_handler.oidc.signature.ES512', ES512::class)
->parent('security.access_token_handler.oidc.signature')
->args(['index_0' => 'ES512'])
->tag('security.access_token_handler.oidc.signature_algorithm')

->set('security.access_token_handler.oidc.signature.RS256', RS256::class)
->tag('security.access_token_handler.oidc.signature_algorithm')

->set('security.access_token_handler.oidc.signature.RS384', RS384::class)
->tag('security.access_token_handler.oidc.signature_algorithm')

->set('security.access_token_handler.oidc.signature.RS512', RS512::class)
->tag('security.access_token_handler.oidc.signature_algorithm')

->set('security.access_token_handler.oidc.signature.PS256', PS256::class)
->tag('security.access_token_handler.oidc.signature_algorithm')

->set('security.access_token_handler.oidc.signature.PS384', PS384::class)
->tag('security.access_token_handler.oidc.signature_algorithm')

->set('security.access_token_handler.oidc.signature.PS512', PS512::class)
->tag('security.access_token_handler.oidc.signature_algorithm')
;
};