diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/AbstractSource.php b/src/Bundle/JoseFramework/DependencyInjection/Source/AbstractSource.php index 6f8c6086..e2b8f680 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/AbstractSource.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/AbstractSource.php @@ -60,7 +60,7 @@ public function addConfiguration(NodeDefinition $node) ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('variable')->end() + ->variablePrototype()->end() ->end() ->end(); } diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Checker/ClaimChecker.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Checker/ClaimChecker.php index 97b73b75..3622f7a4 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/Checker/ClaimChecker.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Checker/ClaimChecker.php @@ -81,7 +81,7 @@ public function getNodeDefinition(NodeDefinition $node) ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('variable') + ->variablePrototype() ->end() ->end() ->end() diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Checker/HeaderChecker.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Checker/HeaderChecker.php index 1ddfa6ba..f60579d7 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/Checker/HeaderChecker.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Checker/HeaderChecker.php @@ -81,7 +81,7 @@ public function getNodeDefinition(NodeDefinition $node) ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('variable') + ->variablePrototype() ->end() ->end() ->end() diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/AbstractEncryptionSource.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/AbstractEncryptionSource.php index 718dab12..92972785 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/AbstractEncryptionSource.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/AbstractEncryptionSource.php @@ -52,7 +52,6 @@ public function getNodeDefinition(NodeDefinition $node) ->info('A list of supported compression methods.') ->useAttributeAsKey('name') ->defaultValue(['DEF']) - ->requiresAtLeastOneElement() ->scalarPrototype()->end() ->end() ->arrayNode('tags') @@ -60,7 +59,7 @@ public function getNodeDefinition(NodeDefinition $node) ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('variable')->end() + ->variablePrototype()->end() ->end() ->end() ->end() diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/JWELoader.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/JWELoader.php index 9ed2c6a7..8570f871 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/JWELoader.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/JWELoader.php @@ -65,7 +65,7 @@ public function getNodeDefinition(NodeDefinition $node) ->arrayNode($this->name()) ->requiresAtLeastOneElement() ->useAttributeAsKey('name') - ->prototype('array') + ->arrayPrototype() ->children() ->booleanNode('is_public') ->info('If true, the service will be public, else private.') @@ -75,39 +75,39 @@ public function getNodeDefinition(NodeDefinition $node) ->info('A list of key encryption algorithm aliases.') ->useAttributeAsKey('name') ->isRequired() - ->prototype('scalar')->end() + ->scalarPrototype()->end() ->end() ->arrayNode('content_encryption_algorithms') ->info('A list of key encryption algorithm aliases.') ->useAttributeAsKey('name') ->isRequired() - ->prototype('scalar')->end() + ->scalarPrototype()->end() ->end() ->arrayNode('compression_methods') ->info('A list of compression method aliases.') ->useAttributeAsKey('name') - ->isRequired() - ->prototype('scalar')->end() + ->defaultValue(['DEF']) + ->scalarPrototype()->end() ->end() ->arrayNode('serializers') ->info('A list of signature serializer aliases.') ->useAttributeAsKey('name') ->requiresAtLeastOneElement() - ->prototype('scalar')->end() + ->scalarPrototype()->end() ->end() ->arrayNode('header_checkers') ->info('A list of header checker aliases.') ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('scalar')->end() + ->scalarPrototype()->end() ->end() ->arrayNode('tags') ->info('A list of tags to be associated to the service.') ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('variable')->end() + ->variablePrototype()->end() ->end() ->end() ->end() diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedToken.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedToken.php new file mode 100644 index 00000000..c4f5693d --- /dev/null +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedToken.php @@ -0,0 +1,112 @@ +sources = [ + new NestedTokenLoader(), + new NestedTokenBuilder(), + ]; + } + + /** + * {@inheritdoc} + */ + public function name(): string + { + return 'nested_token'; + } + + /** + * {@inheritdoc} + */ + public function load(array $configs, ContainerBuilder $container) + { + if (!$this->isEnabled()) { + return; + } + $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../../../Resources/config')); + $loader->load('nested_token.yml'); + + if (array_key_exists('nested_token', $configs)) { + foreach ($this->sources as $source) { + $source->load($configs['nested_token'], $container); + } + } + } + + public function getNodeDefinition(NodeDefinition $node) + { + if (!$this->isEnabled()) { + return; + } + $childNode = $node->children() + ->arrayNode($this->name()) + ->treatNullLike([]) + ->treatFalseLike([]); + + foreach ($this->sources as $source) { + $source->getNodeDefinition($childNode); + } + } + + /** + * {@inheritdoc} + */ + public function prepend(ContainerBuilder $container, array $config): array + { + if (!$this->isEnabled()) { + return []; + } + $result = []; + foreach ($this->sources as $source) { + $prepend = $source->prepend($container, $config); + if (!empty($prepend)) { + $result[$source->name()] = $prepend; + } + } + + return $result; + } + + /** + * @return bool + */ + private function isEnabled(): bool + { + return class_exists(JWEDecrypterFactory::class) + && class_exists(JWSVerifierFactory::class) + && class_exists(HeaderCheckerManagerFactory::class); + } +} diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedTokenBuilder.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedTokenBuilder.php new file mode 100644 index 00000000..797b615d --- /dev/null +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedTokenBuilder.php @@ -0,0 +1,134 @@ +name()] as $name => $itemConfig) { + $service_id = sprintf('jose.nested_token_builder.%s', $name); + $definition = new Definition(self::class); + $definition + ->setFactory([new Reference(NestedTokenBuilderFactory::class), 'create']) + ->setArguments([ + $itemConfig['jwe_serializers'], + $itemConfig['key_encryption_algorithms'], + $itemConfig['content_encryption_algorithms'], + $itemConfig['compression_methods'], + $itemConfig['jws_serializers'], + $itemConfig['signature_algorithms'], + ]) + ->addTag('jose.nested_token_builder') + ->setPublic($itemConfig['is_public']); + foreach ($itemConfig['tags'] as $id => $attributes) { + $definition->addTag($id, $attributes); + } + $container->setDefinition($service_id, $definition); + } + } + + public function getNodeDefinition(NodeDefinition $node) + { + $node->children() + ->arrayNode($this->name()) + ->treatNullLike([]) + ->treatFalseLike([]) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->booleanNode('is_public') + ->info('If true, the service will be public, else private.') + ->defaultTrue() + ->end() + ->arrayNode('signature_algorithms') + ->info('A list of signature algorithm aliases.') + ->useAttributeAsKey('name') + ->isRequired() + ->scalarPrototype()->end() + ->end() + ->arrayNode('key_encryption_algorithms') + ->info('A list of key encryption algorithm aliases.') + ->useAttributeAsKey('name') + ->isRequired() + ->scalarPrototype()->end() + ->end() + ->arrayNode('content_encryption_algorithms') + ->info('A list of key encryption algorithm aliases.') + ->useAttributeAsKey('name') + ->isRequired() + ->scalarPrototype()->end() + ->end() + ->arrayNode('compression_methods') + ->info('A list of compression method aliases.') + ->useAttributeAsKey('name') + ->defaultValue(['DEF']) + ->scalarPrototype()->end() + ->end() + ->arrayNode('jws_serializers') + ->info('A list of JWS serializer aliases.') + ->useAttributeAsKey('name') + ->treatNullLike([]) + ->treatFalseLike([]) + ->isRequired() + ->requiresAtLeastOneElement() + ->scalarPrototype()->end() + ->end() + ->arrayNode('jwe_serializers') + ->info('A list of JWE serializer aliases.') + ->useAttributeAsKey('name') + ->treatNullLike([]) + ->treatFalseLike([]) + ->isRequired() + ->requiresAtLeastOneElement() + ->scalarPrototype()->end() + ->end() + ->arrayNode('tags') + ->info('A list of tags to be associated to the service.') + ->useAttributeAsKey('name') + ->treatNullLike([]) + ->treatFalseLike([]) + ->variablePrototype()->end() + ->end() + ->end() + ->end() + ->end(); + } + + /** + * {@inheritdoc} + */ + public function prepend(ContainerBuilder $container, array $config): array + { + return []; + } +} diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedTokenLoader.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedTokenLoader.php new file mode 100644 index 00000000..f24585be --- /dev/null +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Encryption/NestedTokenLoader.php @@ -0,0 +1,150 @@ +name()] as $name => $itemConfig) { + $service_id = sprintf('jose.nested_token_loader.%s', $name); + $definition = new Definition(self::class); + $definition + ->setFactory([new Reference(NestedTokenLoaderFactory::class), 'create']) + ->setArguments([ + $itemConfig['jwe_serializers'], + $itemConfig['key_encryption_algorithms'], + $itemConfig['content_encryption_algorithms'], + $itemConfig['compression_methods'], + $itemConfig['jwe_header_checkers'], + $itemConfig['jws_serializers'], + $itemConfig['signature_algorithms'], + $itemConfig['jws_header_checkers'], + ]) + ->addTag('jose.nested_token_loader') + ->setPublic($itemConfig['is_public']); + foreach ($itemConfig['tags'] as $id => $attributes) { + $definition->addTag($id, $attributes); + } + $container->setDefinition($service_id, $definition); + } + } + + public function getNodeDefinition(NodeDefinition $node) + { + $node->children() + ->arrayNode($this->name()) + ->treatNullLike([]) + ->treatFalseLike([]) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->booleanNode('is_public') + ->info('If true, the service will be public, else private.') + ->defaultTrue() + ->end() + ->arrayNode('signature_algorithms') + ->info('A list of signature algorithm aliases.') + ->useAttributeAsKey('name') + ->isRequired() + ->scalarPrototype()->end() + ->end() + ->arrayNode('key_encryption_algorithms') + ->info('A list of key encryption algorithm aliases.') + ->useAttributeAsKey('name') + ->isRequired() + ->scalarPrototype()->end() + ->end() + ->arrayNode('content_encryption_algorithms') + ->info('A list of key encryption algorithm aliases.') + ->useAttributeAsKey('name') + ->isRequired() + ->scalarPrototype()->end() + ->end() + ->arrayNode('compression_methods') + ->info('A list of compression method aliases.') + ->useAttributeAsKey('name') + ->defaultValue(['DEF']) + ->scalarPrototype()->end() + ->end() + ->arrayNode('jws_serializers') + ->info('A list of JWS serializer aliases.') + ->useAttributeAsKey('name') + ->treatNullLike([]) + ->treatFalseLike([]) + ->isRequired() + ->requiresAtLeastOneElement() + ->scalarPrototype()->end() + ->end() + ->arrayNode('jwe_serializers') + ->info('A list of JWE serializer aliases.') + ->useAttributeAsKey('name') + ->treatNullLike([]) + ->treatFalseLike([]) + ->isRequired() + ->requiresAtLeastOneElement() + ->scalarPrototype()->end() + ->end() + ->arrayNode('jws_header_checkers') + ->info('A list of header checker aliases.') + ->useAttributeAsKey('name') + ->treatNullLike([]) + ->treatFalseLike([]) + ->scalarPrototype()->end() + ->end() + ->arrayNode('jwe_header_checkers') + ->info('A list of header checker aliases.') + ->useAttributeAsKey('name') + ->treatNullLike([]) + ->treatFalseLike([]) + ->scalarPrototype()->end() + ->end() + ->arrayNode('tags') + ->info('A list of tags to be associated to the service.') + ->useAttributeAsKey('name') + ->treatNullLike([]) + ->treatFalseLike([]) + ->variablePrototype()->end() + ->end() + ->end() + ->end() + ->end(); + } + + /** + * {@inheritdoc} + */ + public function prepend(ContainerBuilder $container, array $config): array + { + return []; + } +} diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSource/Secret.php b/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSource/Secret.php index 0b6be909..a6425ddd 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSource/Secret.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKSource/Secret.php @@ -65,7 +65,7 @@ public function addConfiguration(NodeDefinition $node) ->info('Additional values to be added to the key.') ->defaultValue([]) ->useAttributeAsKey('key') - ->prototype('variable')->end() + ->variablePrototype()->end() ->end() ->end(); } diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKUriSource.php b/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKUriSource.php index b6ec3a11..b94f8ad8 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKUriSource.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/KeyManagement/JWKUriSource.php @@ -81,7 +81,7 @@ public function getNodeDefinition(NodeDefinition $node) ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('variable')->end() + ->variablePrototype()->end() ->end() ->booleanNode('is_public') ->info('If true, the service will be public, else private.') diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Signature/AbstractSignatureSource.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Signature/AbstractSignatureSource.php index 89054d97..6e0ca9c4 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/Signature/AbstractSignatureSource.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Signature/AbstractSignatureSource.php @@ -46,7 +46,7 @@ public function getNodeDefinition(NodeDefinition $node) ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('variable')->end() + ->variablePrototype()->end() ->end() ->end() ->end() diff --git a/src/Bundle/JoseFramework/DependencyInjection/Source/Signature/JWSLoader.php b/src/Bundle/JoseFramework/DependencyInjection/Source/Signature/JWSLoader.php index 24a31bc4..ee8e43b1 100644 --- a/src/Bundle/JoseFramework/DependencyInjection/Source/Signature/JWSLoader.php +++ b/src/Bundle/JoseFramework/DependencyInjection/Source/Signature/JWSLoader.php @@ -63,7 +63,7 @@ public function getNodeDefinition(NodeDefinition $node) ->arrayNode($this->name()) ->requiresAtLeastOneElement() ->useAttributeAsKey('name') - ->prototype('array') + ->arrayPrototype() ->children() ->booleanNode('is_public') ->info('If true, the service will be public, else private.') @@ -73,27 +73,27 @@ public function getNodeDefinition(NodeDefinition $node) ->info('A list of signature algorithm aliases.') ->useAttributeAsKey('name') ->isRequired() - ->prototype('scalar')->end() + ->scalarPrototype()->end() ->end() ->arrayNode('serializers') ->info('A list of signature serializer aliases.') ->useAttributeAsKey('name') ->requiresAtLeastOneElement() - ->prototype('scalar')->end() + ->scalarPrototype()->end() ->end() ->arrayNode('header_checkers') ->info('A list of header checker aliases.') ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('scalar')->end() + ->scalarPrototype()->end() ->end() ->arrayNode('tags') ->info('A list of tags to be associated to the service.') ->useAttributeAsKey('name') ->treatNullLike([]) ->treatFalseLike([]) - ->prototype('variable')->end() + ->variablePrototype()->end() ->end() ->end() ->end() diff --git a/src/Bundle/JoseFramework/Helper/ConfigurationHelper.php b/src/Bundle/JoseFramework/Helper/ConfigurationHelper.php index a7342301..14d5bb62 100644 --- a/src/Bundle/JoseFramework/Helper/ConfigurationHelper.php +++ b/src/Bundle/JoseFramework/Helper/ConfigurationHelper.php @@ -126,6 +126,82 @@ public static function addJWSLoader(ContainerBuilder $container, string $name, a self::updateJoseConfiguration($container, $config, 'jws'); } + /** + * @param ContainerBuilder $container + * @param string $name + * @param string[] $jwe_serializers + * @param string[] $key_encryption_algorithms + * @param string[] $content_encryption_algorithms + * @param string[] $compression_methods + * @param string[] $jwe_header_checkers + * @param string[] $jws_serializers + * @param string[] $signature_algorithms + * @param string[] $jws_header_checkers + * @param bool $is_public + * @param array $tags + */ + public static function addNestedTokenLoader(ContainerBuilder $container, string $name, array $jwe_serializers, array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods, array $jwe_header_checkers, array $jws_serializers, array $signature_algorithms, array $jws_header_checkers, bool $is_public = true, array $tags = []) + { + $config = [ + self::BUNDLE_ALIAS => [ + 'nested_token' => [ + 'loaders' => [ + $name => [ + 'is_public' => $is_public, + 'jwe_serializers' => $jwe_serializers, + 'key_encryption_algorithms' => $key_encryption_algorithms, + 'content_encryption_algorithms' => $content_encryption_algorithms, + 'compression_methods' => $compression_methods, + 'jwe_header_checkers' => $jwe_header_checkers, + 'jws_serializers' => $jws_serializers, + 'signature_algorithms' => $signature_algorithms, + 'jws_header_checkers' => $jws_header_checkers, + 'tags' => $tags, + ], + ], + ], + ], + ]; + + self::updateJoseConfiguration($container, $config, 'nested_token'); + } + + /** + * @param ContainerBuilder $container + * @param string $name + * @param string[] $jwe_serializers + * @param string[] $key_encryption_algorithms + * @param string[] $content_encryption_algorithms + * @param string[] $compression_methods + * @param string[] $jws_serializers + * @param string[] $signature_algorithms + * @param bool $is_public + * @param array $tags + */ + public static function addNestedTokenBuilder(ContainerBuilder $container, string $name, array $jwe_serializers, array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods, array $jws_serializers, array $signature_algorithms, bool $is_public = true, array $tags = []) + { + $config = [ + self::BUNDLE_ALIAS => [ + 'nested_token' => [ + 'builders' => [ + $name => [ + 'is_public' => $is_public, + 'jwe_serializers' => $jwe_serializers, + 'key_encryption_algorithms' => $key_encryption_algorithms, + 'content_encryption_algorithms' => $content_encryption_algorithms, + 'compression_methods' => $compression_methods, + 'jws_serializers' => $jws_serializers, + 'signature_algorithms' => $signature_algorithms, + 'tags' => $tags, + ], + ], + ], + ], + ]; + + self::updateJoseConfiguration($container, $config, 'nested_token'); + } + /** * @param ContainerBuilder $container * @param string $name diff --git a/src/Bundle/JoseFramework/JoseFrameworkBundle.php b/src/Bundle/JoseFramework/JoseFrameworkBundle.php index 88a97743..44023785 100644 --- a/src/Bundle/JoseFramework/JoseFrameworkBundle.php +++ b/src/Bundle/JoseFramework/JoseFrameworkBundle.php @@ -73,6 +73,7 @@ private function getSources(): array new Source\Console\ConsoleSource(), new Source\Signature\SignatureSource(), new Source\Encryption\EncryptionSource(), + new Source\Encryption\NestedToken(), new Source\KeyManagement\KeyManagementSource(), ]; } diff --git a/src/Bundle/JoseFramework/Resources/config/nested_token.yml b/src/Bundle/JoseFramework/Resources/config/nested_token.yml new file mode 100644 index 00000000..76b3d1f0 --- /dev/null +++ b/src/Bundle/JoseFramework/Resources/config/nested_token.yml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: true + + Jose\Component\Encryption\NestedTokenLoaderFactory: ~ + Jose\Component\Encryption\NestedTokenBuilderFactory: ~ diff --git a/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenBuilderConfigurationTest.php b/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenBuilderConfigurationTest.php new file mode 100644 index 00000000..ddb18fd2 --- /dev/null +++ b/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenBuilderConfigurationTest.php @@ -0,0 +1,249 @@ +markTestSkipped('The component "web-token/jwt-encryption" is not installed.'); + } + if (!class_exists(JWSBuilderFactory::class)) { + $this->markTestSkipped('The component "web-token/jwt-signature" is not installed.'); + } + if (!class_exists(HeaderCheckerManagerFactory::class)) { + $this->markTestSkipped('The component "web-token/jwt-checker" is not installed.'); + } + } + + /** + * {@inheritdoc} + */ + protected function getConfiguration() + { + return new Configuration('jose', [ + new Source\Core\CoreSource(), + new Source\Checker\CheckerSource(), + new Source\Signature\SignatureSource(), + new Source\Encryption\EncryptionSource(), + new Source\Encryption\NestedToken(), + ]); + } + + /** + * @test + */ + public function theConfigurationIsValidIfNoConfigurationIsSet() + { + $this->assertConfigurationIsValid( + [] + ); + } + + /** + * @test + */ + public function theConfigurationIsValidIfConfigurationIsFalse() + { + $this->assertConfigurationIsValid( + [ + [ + 'nested_token' => false, + ], + ] + ); + } + + /** + * @test + */ + public function theConfigurationIsValidIfConfigurationIsEmpty() + { + $this->assertConfigurationIsValid( + [ + [ + 'nested_token' => [], + ], + ] + ); + } + + /** + * @test + */ + public function theConfigurationIsValidIfNoBuilderIsSet() + { + $this->assertConfigurationIsValid( + [ + [ + 'nested_token' => [ + 'builders' => [], + ], + ], + ] + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoSignatureAlgorithmIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'builders' => [ + 'foo' => [], + ], + ], + ], + ], + 'The child node "signature_algorithms" at path "jose.nested_token.builders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoKeyEncryptionAlgorithmIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'builders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + ], + ], + ], + ], + ], + 'The child node "key_encryption_algorithms" at path "jose.nested_token.builders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoContentEncryptionAlgorithmIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'builders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + 'key_encryption_algorithms' => ['RSA-OAEP'], + ], + ], + ], + ], + ], + 'The child node "content_encryption_algorithms" at path "jose.nested_token.builders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoJwsSerializerIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'builders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + 'key_encryption_algorithms' => ['RSA-OAEP'], + 'content_encryption_algorithms' => ['A128GCM'], + ], + ], + ], + ], + ], + 'The child node "jws_serializers" at path "jose.nested_token.builders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoJweSerializerIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'builders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + 'key_encryption_algorithms' => ['RSA-OAEP'], + 'content_encryption_algorithms' => ['A128GCM'], + 'jws_serializers' => ['jws_compact'], + ], + ], + ], + ], + ], + 'The child node "jwe_serializers" at path "jose.nested_token.builders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsValid() + { + $this->assertConfigurationIsValid( + [ + [ + 'nested_token' => [ + 'builders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + 'key_encryption_algorithms' => ['RSA-OAEP'], + 'content_encryption_algorithms' => ['A128GCM'], + 'jws_serializers' => ['jws_compact'], + 'jwe_serializers' => ['jwe_compact'], + ], + ], + ], + ], + ] + ); + } +} diff --git a/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenBuilderTest.php b/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenBuilderTest.php new file mode 100644 index 00000000..c210738d --- /dev/null +++ b/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenBuilderTest.php @@ -0,0 +1,200 @@ +markTestSkipped('The component "web-token/jwt-encryption" is not installed.'); + } + if (!class_exists(JWSBuilderFactory::class)) { + $this->markTestSkipped('The component "web-token/jwt-signature" is not installed.'); + } + if (!class_exists(HeaderCheckerManagerFactory::class)) { + $this->markTestSkipped('The component "web-token/jwt-checker" is not installed.'); + } + } + + /** + * @test + */ + public function theNestedTokenBuilderFactoryIsAvailable() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + self::assertTrue($container->has(NestedTokenBuilderFactory::class)); + } + + /** + * @test + */ + public function theNestedTokenBuilderFromTheConfigurationIsAvailable() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + self::assertTrue($container->has('jose.nested_token_builder.nested_token_builder_1')); + } + + /** + * @test + */ + public function theNestedTokenBuilderFromTheConfigurationHelperIsAvailable() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + self::assertTrue($container->has('jose.nested_token_builder.nested_token_builder_2')); + } + + /** + * @test + */ + public function aNestedTokenCanBeSignedAndEncryptedUsingTheServiceCreatedFromTheConfiguration() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + + /** @var NestedTokenBuilder $builder */ + $builder = $container->get('jose.nested_token_builder.nested_token_builder_1'); + + $encryption_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'samwise.gamgee@hobbiton.example', + 'use' => 'enc', + 'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE', + 'e' => 'AQAB', + 'alg' => 'RSA-OAEP', + 'd' => 'n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE', + 'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ', + 'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q', + 'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ', + 'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ', + 'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA', + ]); + + $signature_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'hobbiton.example', + 'use' => 'sig', + 'n' => 'kNrPIBDXMU6fcyv5i-QHQAQ-K8gsC3HJb7FYhYaw8hXbNJa-t8q0lDKwLZgQXYV-ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6Iy6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s-hwx1IU5AT-AIelNqBgcF2vE5W25_SGGBoaROVdUYxqETDggM1z5cKV4ZjDZ8-lh4oVB07bkac6LQdHpJUUySH_Er20DXx30Kyi97PciXKTS-QKXnmm8ivyRCmux22ZoPUind2BKC5OiG4MwALhaL2Z2k8CsRdfy-7dg7z41Rp6D0ZeEvtaUp4bX4aKraL4rTfw', + 'e' => 'AQAB', + 'd' => 'ZLe_TIxpE9-W_n2VBa-HWvuYPtjvxwVXClJFOpJsdea8g9RMx34qEOEtnoYc2un3CZ3LtJi-mju5RAT8YSc76YJds3ZVw0UiO8mMBeG6-iOnvgobobNx7K57-xjTJZU72EjOr9kB7z6ZKwDDq7HFyCDhUEcYcHFVc7iL_6TibVhAhOFONWlqlJgEgwVYd0rybNGKifdnpEbwyHoMwY6HM1qvnEFgP7iZ0YzHUT535x6jj4VKcdA7ZduFkhUauysySEW7mxZM6fj1vdjJIy9LD1fIz30Xv4ckoqhKF5GONU6tNmMmNgAD6gIViyEle1PrIxl1tBhCI14bRW-zrpHgAQ', + 'p' => 'yKWYoNIAqwMRQlgIBOdT1NIcbDNUUs2Rh-pBaxD_mIkweMt4Mg-0-B2iSYvMrs8horhonV7vxCQagcBAATGW-hAafUehWjxWSH-3KccRM8toL4e0q7M-idRDOBXSoe7Z2-CV2x_ZCY3RP8qp642R13WgXqGDIM4MbUkZSjcY9-c', + 'q' => 'uND4o15V30KDzf8vFJw589p1vlQVQ3NEilrinRUPHkkxaAzDzccGgrWMWpGxGFFnNL3w5CqPLeU76-5IVYQq0HwYVl0hVXQHr7sgaGu-483Ad3ENcL23FrOnF45m7_2ooAstJDe49MeLTTQKrSIBl_SKvqpYvfSPTczPcZkh9Kk', + 'dp' => 'jmTnEoq2qqa8ouaymjhJSCnsveUXnMQC2gAneQJRQkFqQu-zV2PKPKNbPvKVyiF5b2-L3tM3OW2d2iNDyRUWXlT7V5l0KwPTABSTOnTqAmYChGi8kXXdlhcrtSvXldBakC6saxwI_TzGGY2MVXzc2ZnCvCXHV4qjSxOrfP3pHFU', + 'dq' => 'R9FUvU88OVzEkTkXl3-5-WusE4DjHmndeZIlu3rifBdfLpq_P-iWPBbGaq9wzQ1c-J7SzCdJqkEJDv5yd2C7rnZ6kpzwBh_nmL8zscAk1qsunnt9CJGAYz7-sGWy1JGShFazfP52ThB4rlCJ0YuEaQMrIzpY77_oLAhpmDA0hLk', + 'qi' => 'S8tC7ZknW6hPITkjcwttQOPLVmRfwirRlFAViuDb8NW9CrV_7F2OqUZCqmzHTYAumwGFHI1WVRep7anleWaJjxC_1b3fq_al4qH3Pe-EKiHg6IMazuRtZLUROcThrExDbF5dYbsciDnfRUWLErZ4N1Be0bnxYuPqxwKd9QZwMo0', + ]); + + $payload = '{"iss":"hobbiton.example","exp":1300819380,"http://example.com/is_root":true}'; + + $builder->create( + $payload, + [[ + 'key' => $signature_key, + 'protected_header' => ['alg' => 'PS256'], + ]], + 'jws_compact', + ['alg' => 'RSA-OAEP', 'enc' => 'A128GCM'], + [], + [[ + 'key' => $encryption_key, + ]], + 'jwe_compact' + ); + + self::assertTrue(true); + } + + /** + * @test + */ + public function aNestedTokenCanBeSignedAndEncryptedUsingTheServiceCreatedFromTheConfigurationHelper() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + + /** @var NestedTokenBuilder $builder */ + $builder = $container->get('jose.nested_token_builder.nested_token_builder_2'); + + $encryption_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'samwise.gamgee@hobbiton.example', + 'use' => 'enc', + 'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE', + 'e' => 'AQAB', + 'alg' => 'RSA-OAEP', + 'd' => 'n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE', + 'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ', + 'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q', + 'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ', + 'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ', + 'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA', + ]); + + $signature_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'hobbiton.example', + 'use' => 'sig', + 'n' => 'kNrPIBDXMU6fcyv5i-QHQAQ-K8gsC3HJb7FYhYaw8hXbNJa-t8q0lDKwLZgQXYV-ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6Iy6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s-hwx1IU5AT-AIelNqBgcF2vE5W25_SGGBoaROVdUYxqETDggM1z5cKV4ZjDZ8-lh4oVB07bkac6LQdHpJUUySH_Er20DXx30Kyi97PciXKTS-QKXnmm8ivyRCmux22ZoPUind2BKC5OiG4MwALhaL2Z2k8CsRdfy-7dg7z41Rp6D0ZeEvtaUp4bX4aKraL4rTfw', + 'e' => 'AQAB', + 'd' => 'ZLe_TIxpE9-W_n2VBa-HWvuYPtjvxwVXClJFOpJsdea8g9RMx34qEOEtnoYc2un3CZ3LtJi-mju5RAT8YSc76YJds3ZVw0UiO8mMBeG6-iOnvgobobNx7K57-xjTJZU72EjOr9kB7z6ZKwDDq7HFyCDhUEcYcHFVc7iL_6TibVhAhOFONWlqlJgEgwVYd0rybNGKifdnpEbwyHoMwY6HM1qvnEFgP7iZ0YzHUT535x6jj4VKcdA7ZduFkhUauysySEW7mxZM6fj1vdjJIy9LD1fIz30Xv4ckoqhKF5GONU6tNmMmNgAD6gIViyEle1PrIxl1tBhCI14bRW-zrpHgAQ', + 'p' => 'yKWYoNIAqwMRQlgIBOdT1NIcbDNUUs2Rh-pBaxD_mIkweMt4Mg-0-B2iSYvMrs8horhonV7vxCQagcBAATGW-hAafUehWjxWSH-3KccRM8toL4e0q7M-idRDOBXSoe7Z2-CV2x_ZCY3RP8qp642R13WgXqGDIM4MbUkZSjcY9-c', + 'q' => 'uND4o15V30KDzf8vFJw589p1vlQVQ3NEilrinRUPHkkxaAzDzccGgrWMWpGxGFFnNL3w5CqPLeU76-5IVYQq0HwYVl0hVXQHr7sgaGu-483Ad3ENcL23FrOnF45m7_2ooAstJDe49MeLTTQKrSIBl_SKvqpYvfSPTczPcZkh9Kk', + 'dp' => 'jmTnEoq2qqa8ouaymjhJSCnsveUXnMQC2gAneQJRQkFqQu-zV2PKPKNbPvKVyiF5b2-L3tM3OW2d2iNDyRUWXlT7V5l0KwPTABSTOnTqAmYChGi8kXXdlhcrtSvXldBakC6saxwI_TzGGY2MVXzc2ZnCvCXHV4qjSxOrfP3pHFU', + 'dq' => 'R9FUvU88OVzEkTkXl3-5-WusE4DjHmndeZIlu3rifBdfLpq_P-iWPBbGaq9wzQ1c-J7SzCdJqkEJDv5yd2C7rnZ6kpzwBh_nmL8zscAk1qsunnt9CJGAYz7-sGWy1JGShFazfP52ThB4rlCJ0YuEaQMrIzpY77_oLAhpmDA0hLk', + 'qi' => 'S8tC7ZknW6hPITkjcwttQOPLVmRfwirRlFAViuDb8NW9CrV_7F2OqUZCqmzHTYAumwGFHI1WVRep7anleWaJjxC_1b3fq_al4qH3Pe-EKiHg6IMazuRtZLUROcThrExDbF5dYbsciDnfRUWLErZ4N1Be0bnxYuPqxwKd9QZwMo0', + ]); + + $payload = '{"iss":"hobbiton.example","exp":1300819380,"http://example.com/is_root":true}'; + + $builder->create( + $payload, + [[ + 'key' => $signature_key, + 'protected_header' => ['alg' => 'PS256'], + ]], + 'jws_compact', + ['alg' => 'RSA-OAEP', 'enc' => 'A128GCM'], + [], + [[ + 'key' => $encryption_key, + ]], + 'jwe_compact' + ); + + self::assertTrue(true); + } +} diff --git a/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenLoaderConfigurationTest.php b/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenLoaderConfigurationTest.php new file mode 100644 index 00000000..ecf5290d --- /dev/null +++ b/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenLoaderConfigurationTest.php @@ -0,0 +1,249 @@ +markTestSkipped('The component "web-token/jwt-encryption" is not installed.'); + } + if (!class_exists(JWSLoaderFactory::class)) { + $this->markTestSkipped('The component "web-token/jwt-signature" is not installed.'); + } + if (!class_exists(HeaderCheckerManagerFactory::class)) { + $this->markTestSkipped('The component "web-token/jwt-checker" is not installed.'); + } + } + + /** + * {@inheritdoc} + */ + protected function getConfiguration() + { + return new Configuration('jose', [ + new Source\Core\CoreSource(), + new Source\Checker\CheckerSource(), + new Source\Signature\SignatureSource(), + new Source\Encryption\EncryptionSource(), + new Source\Encryption\NestedToken(), + ]); + } + + /** + * @test + */ + public function theConfigurationIsValidIfNoConfigurationIsSet() + { + $this->assertConfigurationIsValid( + [] + ); + } + + /** + * @test + */ + public function theConfigurationIsValidIfConfigurationIsFalse() + { + $this->assertConfigurationIsValid( + [ + [ + 'nested_token' => false, + ], + ] + ); + } + + /** + * @test + */ + public function theConfigurationIsValidIfConfigurationIsEmpty() + { + $this->assertConfigurationIsValid( + [ + [ + 'nested_token' => [], + ], + ] + ); + } + + /** + * @test + */ + public function theConfigurationIsValidIfNoLoaderIsSet() + { + $this->assertConfigurationIsValid( + [ + [ + 'nested_token' => [ + 'loaders' => [], + ], + ], + ] + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoSignatureAlgorithmIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'loaders' => [ + 'foo' => [], + ], + ], + ], + ], + 'The child node "signature_algorithms" at path "jose.nested_token.loaders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoKeyEncryptionAlgorithmIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'loaders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + ], + ], + ], + ], + ], + 'The child node "key_encryption_algorithms" at path "jose.nested_token.loaders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoContentEncryptionAlgorithmIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'loaders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + 'key_encryption_algorithms' => ['RSA-OAEP'], + ], + ], + ], + ], + ], + 'The child node "content_encryption_algorithms" at path "jose.nested_token.loaders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoJwsSerializerIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'loaders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + 'key_encryption_algorithms' => ['RSA-OAEP'], + 'content_encryption_algorithms' => ['A128GCM'], + ], + ], + ], + ], + ], + 'The child node "jws_serializers" at path "jose.nested_token.loaders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsInvalidIfNoJweSerializerIsSet() + { + $this->assertConfigurationIsInvalid( + [ + [ + 'nested_token' => [ + 'loaders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + 'key_encryption_algorithms' => ['RSA-OAEP'], + 'content_encryption_algorithms' => ['A128GCM'], + 'jws_serializers' => ['jws_compact'], + ], + ], + ], + ], + ], + 'The child node "jwe_serializers" at path "jose.nested_token.loaders.foo" must be configured.' + ); + } + + /** + * @test + */ + public function theConfigurationIsValid() + { + $this->assertConfigurationIsValid( + [ + [ + 'nested_token' => [ + 'loaders' => [ + 'foo' => [ + 'signature_algorithms' => ['RS256'], + 'key_encryption_algorithms' => ['RSA-OAEP'], + 'content_encryption_algorithms' => ['A128GCM'], + 'jws_serializers' => ['jws_compact'], + 'jwe_serializers' => ['jwe_compact'], + ], + ], + ], + ], + ] + ); + } +} diff --git a/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenLoaderTest.php b/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenLoaderTest.php new file mode 100644 index 00000000..0128f68d --- /dev/null +++ b/src/Bundle/JoseFramework/Tests/Functional/Encryption/NestedTokenLoaderTest.php @@ -0,0 +1,185 @@ +markTestSkipped('The component "web-token/jwt-encryption" is not installed.'); + } + if (!class_exists(JWSLoaderFactory::class)) { + $this->markTestSkipped('The component "web-token/jwt-signature" is not installed.'); + } + if (!class_exists(HeaderCheckerManagerFactory::class)) { + $this->markTestSkipped('The component "web-token/jwt-checker" is not installed.'); + } + } + + /** + * @test + */ + public function theNestedTokenLoaderFactoryIsAvailable() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + self::assertTrue($container->has(NestedTokenLoaderFactory::class)); + } + + /** + * @test + */ + public function theNestedTokenLoaderFromTheConfigurationIsAvailable() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + self::assertTrue($container->has('jose.nested_token_loader.nested_token_loader_1')); + } + + /** + * @test + */ + public function theNestedTokenLoaderFromTheConfigurationHelperIsAvailable() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + self::assertTrue($container->has('jose.nested_token_loader.nested_token_loader_2')); + } + + /** + * @test + */ + public function aNestedTokenCanBeDecryptedAndVerifiedUsingTheServiceCreatedFromTheConfiguration() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + + /** @var NestedTokenLoader $loader */ + $loader = $container->get('jose.nested_token_loader.nested_token_loader_1'); + + $encryption_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'samwise.gamgee@hobbiton.example', + 'use' => 'enc', + 'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE', + 'e' => 'AQAB', + 'alg' => 'RSA-OAEP', + 'd' => 'n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE', + 'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ', + 'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q', + 'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ', + 'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ', + 'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA', + ]); + $encryption_key_set = JWKSet::createFromKeys([$encryption_key]); + + $signature_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'hobbiton.example', + 'use' => 'sig', + 'n' => 'kNrPIBDXMU6fcyv5i-QHQAQ-K8gsC3HJb7FYhYaw8hXbNJa-t8q0lDKwLZgQXYV-ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6Iy6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s-hwx1IU5AT-AIelNqBgcF2vE5W25_SGGBoaROVdUYxqETDggM1z5cKV4ZjDZ8-lh4oVB07bkac6LQdHpJUUySH_Er20DXx30Kyi97PciXKTS-QKXnmm8ivyRCmux22ZoPUind2BKC5OiG4MwALhaL2Z2k8CsRdfy-7dg7z41Rp6D0ZeEvtaUp4bX4aKraL4rTfw', + 'e' => 'AQAB', + 'd' => 'ZLe_TIxpE9-W_n2VBa-HWvuYPtjvxwVXClJFOpJsdea8g9RMx34qEOEtnoYc2un3CZ3LtJi-mju5RAT8YSc76YJds3ZVw0UiO8mMBeG6-iOnvgobobNx7K57-xjTJZU72EjOr9kB7z6ZKwDDq7HFyCDhUEcYcHFVc7iL_6TibVhAhOFONWlqlJgEgwVYd0rybNGKifdnpEbwyHoMwY6HM1qvnEFgP7iZ0YzHUT535x6jj4VKcdA7ZduFkhUauysySEW7mxZM6fj1vdjJIy9LD1fIz30Xv4ckoqhKF5GONU6tNmMmNgAD6gIViyEle1PrIxl1tBhCI14bRW-zrpHgAQ', + 'p' => 'yKWYoNIAqwMRQlgIBOdT1NIcbDNUUs2Rh-pBaxD_mIkweMt4Mg-0-B2iSYvMrs8horhonV7vxCQagcBAATGW-hAafUehWjxWSH-3KccRM8toL4e0q7M-idRDOBXSoe7Z2-CV2x_ZCY3RP8qp642R13WgXqGDIM4MbUkZSjcY9-c', + 'q' => 'uND4o15V30KDzf8vFJw589p1vlQVQ3NEilrinRUPHkkxaAzDzccGgrWMWpGxGFFnNL3w5CqPLeU76-5IVYQq0HwYVl0hVXQHr7sgaGu-483Ad3ENcL23FrOnF45m7_2ooAstJDe49MeLTTQKrSIBl_SKvqpYvfSPTczPcZkh9Kk', + 'dp' => 'jmTnEoq2qqa8ouaymjhJSCnsveUXnMQC2gAneQJRQkFqQu-zV2PKPKNbPvKVyiF5b2-L3tM3OW2d2iNDyRUWXlT7V5l0KwPTABSTOnTqAmYChGi8kXXdlhcrtSvXldBakC6saxwI_TzGGY2MVXzc2ZnCvCXHV4qjSxOrfP3pHFU', + 'dq' => 'R9FUvU88OVzEkTkXl3-5-WusE4DjHmndeZIlu3rifBdfLpq_P-iWPBbGaq9wzQ1c-J7SzCdJqkEJDv5yd2C7rnZ6kpzwBh_nmL8zscAk1qsunnt9CJGAYz7-sGWy1JGShFazfP52ThB4rlCJ0YuEaQMrIzpY77_oLAhpmDA0hLk', + 'qi' => 'S8tC7ZknW6hPITkjcwttQOPLVmRfwirRlFAViuDb8NW9CrV_7F2OqUZCqmzHTYAumwGFHI1WVRep7anleWaJjxC_1b3fq_al4qH3Pe-EKiHg6IMazuRtZLUROcThrExDbF5dYbsciDnfRUWLErZ4N1Be0bnxYuPqxwKd9QZwMo0', + ]); + $signature_key_set = JWKSet::createFromKeys([ + $signature_key, + ]); + + $payload = '{"iss":"hobbiton.example","exp":1300819380,"http://example.com/is_root":true}'; + $token = 'eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYyI6IkExMjhHQ00ifQ.a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVhjurCyrBg3I7YhCRYjphDOOS4E7rXbr2Fn6NyQq-A-gqT0FXqNjVOGrG-bi13mwy7RoYhjTkBEC6P7sMYMXXx4gzMedpiJHQVeyI-zkZV7A9matpgevAJWrXzOUysYGTtwoSN6gtUVtlLaivjvb21O0ul4YxSHV-ByK1kyeetRp_fuYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e5IR7nany-25_UmC2urosNkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5o6yV64x6yzDUF_5JCIdl-Qv6H5dMVIY7q1eKpXcV1lWO_2FefEBqXxXvIjLeZivjNkzogCq3-IapSjVFnMjBxjpYLT8muaawo1yy1XXMuinIpNcOY3n4KKrXLrCcteX85m4IIHMZa38s1Hpr56fPPseMA-Jltmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3kJusAamBKOYwfk7JhLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15qJIEXNJtqnblpymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TXuPC8yDDhi1smxS_X_xpkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX2Fo_GnVrNwlK7Lgxw6FSQvDO0.GbX1i9kXz0sxXPmA.SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBNgCe2OFMf66cSJ8k2QkxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAvVLsAXB0_UTCBGyBg3C2bWLXqZlfJAAoJRUPRk-BimYZY81zVBuIhc7HsQePCpu33SzMsFHjn4lP_idrJz_glZTNgKDt8zdnUPauKTKDNOH1DD4fuzvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9ChMPqW1QNhzuX_Zul3bvrJwr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEaulV18l4Fg9tLejdkAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2zI3Q_1sYjKUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUrlx4gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gYjLfhn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5XmnwZMyNc9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMxtlMizR88NKU1WkBsiXMCjy1Noue7MD-ShDp5dmM.KnIKEhN8U-3C9s4gtSpjSw'; + + $jws = $loader->load($token, $encryption_key_set, $signature_key_set, $signature); + self::assertEquals($payload, $jws->getPayload()); + self::assertEquals(0, $signature); + } + + /** + * @test + */ + public function aNestedTokenCanBeDecryptedAndVerifiedUsingTheServiceCreatedFromTheConfigurationHelper() + { + $client = static::createClient(); + $container = $client->getContainer(); + self::assertNotNull($container); + + /** @var NestedTokenLoader $loader */ + $loader = $container->get('jose.nested_token_loader.nested_token_loader_2'); + + $encryption_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'samwise.gamgee@hobbiton.example', + 'use' => 'enc', + 'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE', + 'e' => 'AQAB', + 'alg' => 'RSA-OAEP', + 'd' => 'n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE', + 'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ', + 'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q', + 'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ', + 'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ', + 'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA', + ]); + $encryption_key_set = JWKSet::createFromKeys([$encryption_key]); + + $signature_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'hobbiton.example', + 'use' => 'sig', + 'n' => 'kNrPIBDXMU6fcyv5i-QHQAQ-K8gsC3HJb7FYhYaw8hXbNJa-t8q0lDKwLZgQXYV-ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6Iy6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s-hwx1IU5AT-AIelNqBgcF2vE5W25_SGGBoaROVdUYxqETDggM1z5cKV4ZjDZ8-lh4oVB07bkac6LQdHpJUUySH_Er20DXx30Kyi97PciXKTS-QKXnmm8ivyRCmux22ZoPUind2BKC5OiG4MwALhaL2Z2k8CsRdfy-7dg7z41Rp6D0ZeEvtaUp4bX4aKraL4rTfw', + 'e' => 'AQAB', + 'd' => 'ZLe_TIxpE9-W_n2VBa-HWvuYPtjvxwVXClJFOpJsdea8g9RMx34qEOEtnoYc2un3CZ3LtJi-mju5RAT8YSc76YJds3ZVw0UiO8mMBeG6-iOnvgobobNx7K57-xjTJZU72EjOr9kB7z6ZKwDDq7HFyCDhUEcYcHFVc7iL_6TibVhAhOFONWlqlJgEgwVYd0rybNGKifdnpEbwyHoMwY6HM1qvnEFgP7iZ0YzHUT535x6jj4VKcdA7ZduFkhUauysySEW7mxZM6fj1vdjJIy9LD1fIz30Xv4ckoqhKF5GONU6tNmMmNgAD6gIViyEle1PrIxl1tBhCI14bRW-zrpHgAQ', + 'p' => 'yKWYoNIAqwMRQlgIBOdT1NIcbDNUUs2Rh-pBaxD_mIkweMt4Mg-0-B2iSYvMrs8horhonV7vxCQagcBAATGW-hAafUehWjxWSH-3KccRM8toL4e0q7M-idRDOBXSoe7Z2-CV2x_ZCY3RP8qp642R13WgXqGDIM4MbUkZSjcY9-c', + 'q' => 'uND4o15V30KDzf8vFJw589p1vlQVQ3NEilrinRUPHkkxaAzDzccGgrWMWpGxGFFnNL3w5CqPLeU76-5IVYQq0HwYVl0hVXQHr7sgaGu-483Ad3ENcL23FrOnF45m7_2ooAstJDe49MeLTTQKrSIBl_SKvqpYvfSPTczPcZkh9Kk', + 'dp' => 'jmTnEoq2qqa8ouaymjhJSCnsveUXnMQC2gAneQJRQkFqQu-zV2PKPKNbPvKVyiF5b2-L3tM3OW2d2iNDyRUWXlT7V5l0KwPTABSTOnTqAmYChGi8kXXdlhcrtSvXldBakC6saxwI_TzGGY2MVXzc2ZnCvCXHV4qjSxOrfP3pHFU', + 'dq' => 'R9FUvU88OVzEkTkXl3-5-WusE4DjHmndeZIlu3rifBdfLpq_P-iWPBbGaq9wzQ1c-J7SzCdJqkEJDv5yd2C7rnZ6kpzwBh_nmL8zscAk1qsunnt9CJGAYz7-sGWy1JGShFazfP52ThB4rlCJ0YuEaQMrIzpY77_oLAhpmDA0hLk', + 'qi' => 'S8tC7ZknW6hPITkjcwttQOPLVmRfwirRlFAViuDb8NW9CrV_7F2OqUZCqmzHTYAumwGFHI1WVRep7anleWaJjxC_1b3fq_al4qH3Pe-EKiHg6IMazuRtZLUROcThrExDbF5dYbsciDnfRUWLErZ4N1Be0bnxYuPqxwKd9QZwMo0', + ]); + $signature_key_set = JWKSet::createFromKeys([ + $signature_key, + ]); + + $payload = '{"iss":"hobbiton.example","exp":1300819380,"http://example.com/is_root":true}'; + $token = 'eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYyI6IkExMjhHQ00ifQ.a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVhjurCyrBg3I7YhCRYjphDOOS4E7rXbr2Fn6NyQq-A-gqT0FXqNjVOGrG-bi13mwy7RoYhjTkBEC6P7sMYMXXx4gzMedpiJHQVeyI-zkZV7A9matpgevAJWrXzOUysYGTtwoSN6gtUVtlLaivjvb21O0ul4YxSHV-ByK1kyeetRp_fuYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e5IR7nany-25_UmC2urosNkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5o6yV64x6yzDUF_5JCIdl-Qv6H5dMVIY7q1eKpXcV1lWO_2FefEBqXxXvIjLeZivjNkzogCq3-IapSjVFnMjBxjpYLT8muaawo1yy1XXMuinIpNcOY3n4KKrXLrCcteX85m4IIHMZa38s1Hpr56fPPseMA-Jltmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3kJusAamBKOYwfk7JhLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15qJIEXNJtqnblpymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TXuPC8yDDhi1smxS_X_xpkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX2Fo_GnVrNwlK7Lgxw6FSQvDO0.GbX1i9kXz0sxXPmA.SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBNgCe2OFMf66cSJ8k2QkxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAvVLsAXB0_UTCBGyBg3C2bWLXqZlfJAAoJRUPRk-BimYZY81zVBuIhc7HsQePCpu33SzMsFHjn4lP_idrJz_glZTNgKDt8zdnUPauKTKDNOH1DD4fuzvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9ChMPqW1QNhzuX_Zul3bvrJwr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEaulV18l4Fg9tLejdkAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2zI3Q_1sYjKUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUrlx4gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gYjLfhn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5XmnwZMyNc9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMxtlMizR88NKU1WkBsiXMCjy1Noue7MD-ShDp5dmM.KnIKEhN8U-3C9s4gtSpjSw'; + + $jws = $loader->load($token, $encryption_key_set, $signature_key_set, $signature); + self::assertEquals($payload, $jws->getPayload()); + self::assertEquals(0, $signature); + } +} diff --git a/src/Bundle/JoseFramework/Tests/TestBundle/DependencyInjection/TestExtension.php b/src/Bundle/JoseFramework/Tests/TestBundle/DependencyInjection/TestExtension.php index 316c1e09..b16cdf22 100644 --- a/src/Bundle/JoseFramework/Tests/TestBundle/DependencyInjection/TestExtension.php +++ b/src/Bundle/JoseFramework/Tests/TestBundle/DependencyInjection/TestExtension.php @@ -62,5 +62,7 @@ public function prepend(ContainerBuilder $container) ConfigurationHelper::addJWEDecrypter($container, 'loader2', ['RSA-OAEP-256'], ['A128GCM'], ['DEF'], true); ConfigurationHelper::addJWESerializer($container, 'jwe_serializer2', ['jwe_compact', 'jwe_json_flattened', 'jwe_json_general'], true); ConfigurationHelper::addJWELoader($container, 'jwe_loader2', ['jwe_compact'], ['RSA-OAEP-256'], ['A128GCM'], ['DEF'], [], true); + ConfigurationHelper::addNestedTokenLoader($container, 'nested_token_loader_2', ['jwe_compact'], ['RSA-OAEP'], ['A128GCM'], ['DEF'], [], ['jws_compact'], ['PS256'], [], true, []); + ConfigurationHelper::addNestedTokenBuilder($container, 'nested_token_builder_2', ['jwe_compact'], ['RSA-OAEP'], ['A128GCM'], ['DEF'], ['jws_compact'], ['PS256'], true, []); } } diff --git a/src/Bundle/JoseFramework/Tests/config/config_test.yml b/src/Bundle/JoseFramework/Tests/config/config_test.yml index c36c8fb9..1fb22ecf 100644 --- a/src/Bundle/JoseFramework/Tests/config/config_test.yml +++ b/src/Bundle/JoseFramework/Tests/config/config_test.yml @@ -74,6 +74,23 @@ jose: content_encryption_algorithms: ['A256CBC-HS512'] compression_methods: [] is_public: true + nested_token: + loaders: + nested_token_loader_1: + signature_algorithms: ['PS256'] + key_encryption_algorithms: ['RSA-OAEP'] + content_encryption_algorithms: ['A128GCM'] + jws_serializers: ['jws_compact'] + jwe_serializers: ['jwe_compact'] + is_public: true + builders: + nested_token_builder_1: + signature_algorithms: ['PS256'] + key_encryption_algorithms: ['RSA-OAEP'] + content_encryption_algorithms: ['A128GCM'] + jws_serializers: ['jws_compact'] + jwe_serializers: ['jwe_compact'] + is_public: true keys: jwk1: jwk: diff --git a/src/Component/Encryption/NestedTokenBuilder.php b/src/Component/Encryption/NestedTokenBuilder.php new file mode 100644 index 00000000..85e5354d --- /dev/null +++ b/src/Component/Encryption/NestedTokenBuilder.php @@ -0,0 +1,106 @@ +jweBuilder = $jweBuilder; + $this->jwsSerializerManager = $jwsSerializerManager; + $this->jwsBuilder = $jwsBuilder; + $this->jweSerializerManager = $jweSerializerManager; + } + + /** + * @param string $payload + * @param array[] $signatures + * @param string $jws_serialization_mode + * @param array $jweSharedProtectedHeader + * @param array $jweSharedHeader + * @param array[] $recipients + * @param string $jwe_serialization_mode + * @param string|null $aad + * + * @throws \Exception + * + * @return string + */ + public function create(string $payload, array $signatures, string $jws_serialization_mode, array $jweSharedProtectedHeader, array $jweSharedHeader, array $recipients, string $jwe_serialization_mode, ?string $aad = null): string + { + $jws = $this->jwsBuilder->create()->withPayload($payload); + foreach ($signatures as $signature) { + if (!is_array($signature) || !array_key_exists('key', $signature)) { + throw new \InvalidArgumentException('The signatures must be an array of arrays containing a key, a protected header and a header'); + } + $signature['protected_header'] = array_key_exists('protected_header', $signature) ? $signature['protected_header'] : []; + $signature['header'] = array_key_exists('header', $signature) ? $signature['header'] : []; + $jws = $jws->addSignature($signature['key'], $signature['protected_header'], $signature['header']); + } + $jws = $jws->build(); + $token = $this->jwsSerializerManager->serialize($jws_serialization_mode, $jws); + + $jweSharedProtectedHeader['cty'] = 'JWT'; + + $jwe = $this->jweBuilder + ->create() + ->withPayload($token) + ->withSharedProtectedHeader($jweSharedProtectedHeader) + ->withSharedHeader($jweSharedHeader) + ->withAAD($aad); + foreach ($recipients as $recipient) { + if (!is_array($recipient) || !array_key_exists('key', $recipient)) { + throw new \InvalidArgumentException('The recipients must be an array of arrays containing a key and a header'); + } + $recipient['header'] = array_key_exists('header', $recipient) ? $recipient['header'] : []; + $jwe = $jwe->addRecipient($recipient['key'], $recipient['header']); + } + $jwe = $jwe->build(); + $token = $this->jweSerializerManager->serialize($jwe_serialization_mode, $jwe); + + return $token; + } +} diff --git a/src/Component/Encryption/NestedTokenBuilderFactory.php b/src/Component/Encryption/NestedTokenBuilderFactory.php new file mode 100644 index 00000000..e18d3333 --- /dev/null +++ b/src/Component/Encryption/NestedTokenBuilderFactory.php @@ -0,0 +1,77 @@ +jweBuilderFactory = $jweBuilderFactory; + $this->jweSerializerManagerFactory = $jweSerializerManagerFactory; + $this->jwsBuilderFactory = $jwsBuilderFactory; + $this->jwsSerializerManagerFactory = $jwsSerializerManagerFactory; + } + + /** + * @param array $jwe_serializers + * @param array $keyEncryptionAlgorithms + * @param array $contentEncryptionAlgorithms + * @param array $compressionMethods + * @param array $jws_serializers + * @param array $signatureAlgorithms + * + * @return NestedTokenBuilder + */ + public function create(array $jwe_serializers, array $keyEncryptionAlgorithms, array $contentEncryptionAlgorithms, array $compressionMethods, array $jws_serializers, array $signatureAlgorithms): NestedTokenBuilder + { + $jweBuilder = $this->jweBuilderFactory->create($keyEncryptionAlgorithms, $contentEncryptionAlgorithms, $compressionMethods); + $jweSerializerManager = $this->jweSerializerManagerFactory->create($jwe_serializers); + $jwsBuilder = $this->jwsBuilderFactory->create($signatureAlgorithms); + $jwsSerializerManager = $this->jwsSerializerManagerFactory->create($jws_serializers); + + return new NestedTokenBuilder($jweBuilder, $jweSerializerManager, $jwsBuilder, $jwsSerializerManager); + } +} diff --git a/src/Component/Encryption/NestedTokenLoader.php b/src/Component/Encryption/NestedTokenLoader.php new file mode 100644 index 00000000..d95102d0 --- /dev/null +++ b/src/Component/Encryption/NestedTokenLoader.php @@ -0,0 +1,92 @@ +jweLoader = $jweLoader; + $this->jwsLoader = $jwsLoader; + } + + /** + * @param string $token + * @param JWKSet $encryptionKeySet + * @param JWKSet $signatureKeySet + * @param int|null $signature + * + * @throws \Exception + * + * @return JWS + */ + public function load(string $token, JWKSet $encryptionKeySet, JWKSet $signatureKeySet, ?int &$signature = null): JWS + { + $recipient = null; + $jwe = $this->jweLoader->loadAndDecryptWithKeySet($token, $encryptionKeySet, $recipient); + $this->checkContentTypeHeader($jwe, $recipient); + if (null === $jwe->getPayload()) { + throw new \InvalidArgumentException('The token has no payload.'); + } + + return $this->jwsLoader->loadAndVerifyWithKeySet($jwe->getPayload(), $signatureKeySet, $signature); + } + + /** + * @param JWE $jwe + * @param int $recipient + * + * @throws \InvalidArgumentException + */ + private function checkContentTypeHeader(JWE $jwe, int $recipient) + { + switch (true) { + case $jwe->hasSharedProtectedHeaderParameter('cty'): + $cty = $jwe->getSharedProtectedHeaderParameter('cty'); + break; + case $jwe->hasSharedHeaderParameter('cty'): + $cty = $jwe->getSharedHeaderParameter('cty'); + break; + case $jwe->getRecipient($recipient)->hasHeaderParameter('cty'): + $cty = $jwe->getRecipient($recipient)->getHeaderParameter('cty'); + break; + default: + throw new \InvalidArgumentException('The token is not a nested token.'); + } + + if (0 !== strcasecmp($cty, 'jwt')) { + throw new \InvalidArgumentException('The token is not a nested token.'); + } + } +} diff --git a/src/Component/Encryption/NestedTokenLoaderFactory.php b/src/Component/Encryption/NestedTokenLoaderFactory.php new file mode 100644 index 00000000..14d48267 --- /dev/null +++ b/src/Component/Encryption/NestedTokenLoaderFactory.php @@ -0,0 +1,61 @@ +jweLoaderFactory = $jweLoaderFactory; + $this->jwsLoaderFactory = $jwsLoaderFactory; + } + + /** + * @param array $jweSerializers + * @param array $keyEncryptionAlgorithms + * @param array $contentEncryptionAlgorithms + * @param array $compressionMethods + * @param array $jweHeaderCheckers + * @param array $jwsSerializers + * @param array $signatureAlgorithms + * @param array $jwsHeaderCheckers + * + * @return NestedTokenLoader + */ + public function create(array $jweSerializers, array $keyEncryptionAlgorithms, array $contentEncryptionAlgorithms, array $compressionMethods, array $jweHeaderCheckers, array $jwsSerializers, array $signatureAlgorithms, array $jwsHeaderCheckers): NestedTokenLoader + { + $jweLoader = $this->jweLoaderFactory->create($jweSerializers, $keyEncryptionAlgorithms, $contentEncryptionAlgorithms, $compressionMethods, $jweHeaderCheckers); + $jwsLoader = $this->jwsLoaderFactory->create($jwsSerializers, $signatureAlgorithms, $jwsHeaderCheckers); + + return new NestedTokenLoader($jweLoader, $jwsLoader); + } +} diff --git a/src/Component/Encryption/Tests/RFC7520/NestingTokenBuilderTest.php b/src/Component/Encryption/Tests/RFC7520/NestingTokenBuilderTest.php new file mode 100644 index 00000000..98c8c2f4 --- /dev/null +++ b/src/Component/Encryption/Tests/RFC7520/NestingTokenBuilderTest.php @@ -0,0 +1,250 @@ +markTestSkipped('The component "web-token/jwt-checker" is not installed.'); + } + if (!class_exists(JWSBuilder::class)) { + $this->markTestSkipped('The component "web-token/jwt-signature" is not installed.'); + } + } + + public function testDecryption() + { + $payload = '{"iss":"hobbiton.example","exp":1300819380,"http://example.com/is_root":true}'; + + $encryption_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'samwise.gamgee@hobbiton.example', + 'use' => 'enc', + 'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE', + 'e' => 'AQAB', + 'alg' => 'RSA-OAEP', + 'd' => 'n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE', + 'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ', + 'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q', + 'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ', + 'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ', + 'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA', + ]); + + $signature_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'hobbiton.example', + 'use' => 'sig', + 'n' => 'kNrPIBDXMU6fcyv5i-QHQAQ-K8gsC3HJb7FYhYaw8hXbNJa-t8q0lDKwLZgQXYV-ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6Iy6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s-hwx1IU5AT-AIelNqBgcF2vE5W25_SGGBoaROVdUYxqETDggM1z5cKV4ZjDZ8-lh4oVB07bkac6LQdHpJUUySH_Er20DXx30Kyi97PciXKTS-QKXnmm8ivyRCmux22ZoPUind2BKC5OiG4MwALhaL2Z2k8CsRdfy-7dg7z41Rp6D0ZeEvtaUp4bX4aKraL4rTfw', + 'e' => 'AQAB', + 'd' => 'ZLe_TIxpE9-W_n2VBa-HWvuYPtjvxwVXClJFOpJsdea8g9RMx34qEOEtnoYc2un3CZ3LtJi-mju5RAT8YSc76YJds3ZVw0UiO8mMBeG6-iOnvgobobNx7K57-xjTJZU72EjOr9kB7z6ZKwDDq7HFyCDhUEcYcHFVc7iL_6TibVhAhOFONWlqlJgEgwVYd0rybNGKifdnpEbwyHoMwY6HM1qvnEFgP7iZ0YzHUT535x6jj4VKcdA7ZduFkhUauysySEW7mxZM6fj1vdjJIy9LD1fIz30Xv4ckoqhKF5GONU6tNmMmNgAD6gIViyEle1PrIxl1tBhCI14bRW-zrpHgAQ', + 'p' => 'yKWYoNIAqwMRQlgIBOdT1NIcbDNUUs2Rh-pBaxD_mIkweMt4Mg-0-B2iSYvMrs8horhonV7vxCQagcBAATGW-hAafUehWjxWSH-3KccRM8toL4e0q7M-idRDOBXSoe7Z2-CV2x_ZCY3RP8qp642R13WgXqGDIM4MbUkZSjcY9-c', + 'q' => 'uND4o15V30KDzf8vFJw589p1vlQVQ3NEilrinRUPHkkxaAzDzccGgrWMWpGxGFFnNL3w5CqPLeU76-5IVYQq0HwYVl0hVXQHr7sgaGu-483Ad3ENcL23FrOnF45m7_2ooAstJDe49MeLTTQKrSIBl_SKvqpYvfSPTczPcZkh9Kk', + 'dp' => 'jmTnEoq2qqa8ouaymjhJSCnsveUXnMQC2gAneQJRQkFqQu-zV2PKPKNbPvKVyiF5b2-L3tM3OW2d2iNDyRUWXlT7V5l0KwPTABSTOnTqAmYChGi8kXXdlhcrtSvXldBakC6saxwI_TzGGY2MVXzc2ZnCvCXHV4qjSxOrfP3pHFU', + 'dq' => 'R9FUvU88OVzEkTkXl3-5-WusE4DjHmndeZIlu3rifBdfLpq_P-iWPBbGaq9wzQ1c-J7SzCdJqkEJDv5yd2C7rnZ6kpzwBh_nmL8zscAk1qsunnt9CJGAYz7-sGWy1JGShFazfP52ThB4rlCJ0YuEaQMrIzpY77_oLAhpmDA0hLk', + 'qi' => 'S8tC7ZknW6hPITkjcwttQOPLVmRfwirRlFAViuDb8NW9CrV_7F2OqUZCqmzHTYAumwGFHI1WVRep7anleWaJjxC_1b3fq_al4qH3Pe-EKiHg6IMazuRtZLUROcThrExDbF5dYbsciDnfRUWLErZ4N1Be0bnxYuPqxwKd9QZwMo0', + ]); + + $nestedTokenBuilder = $this->getNestedTokenBuilderFactory()->create( + ['jwe_compact'], + ['RSA-OAEP'], + ['A128GCM'], + ['DEF'], + ['jws_compact'], + ['PS256'] + ); + + $nestedTokenBuilder->create( + $payload, + [ + ['key' => $signature_key, 'protected_header' => ['alg' => 'PS256']], + ], + 'jws_compact', + ['alg' => 'RSA-OAEP', 'enc' => 'A128GCM'], + [], + [ + ['key' => $encryption_key], + ], + 'jwe_compact' + ); + + self::assertTrue(true); + } + + /** + * @var JWSBuilderFactory + */ + private $jwsBuilderFactory; + + /** + * @return JWSBuilderFactory + */ + protected function getJWSBuilderFactory(): JWSBuilderFactory + { + if (null === $this->jwsBuilderFactory) { + $this->jwsBuilderFactory = new JWSBuilderFactory( + new StandardConverter(), + $this->getAlgorithmManagerFactory() + ); + } + + return $this->jwsBuilderFactory; + } + + /** + * @var JWEBuilderFactory + */ + private $jweBuilderFactory; + + /** + * @return JWEBuilderFactory + */ + protected function getJWEBuilderFactory(): JWEBuilderFactory + { + if (null === $this->jweBuilderFactory) { + $this->jweBuilderFactory = new JWEBuilderFactory( + new StandardConverter(), + $this->getAlgorithmManagerFactory(), + $this->getCompressionMethodManagerFactory() + ); + } + + return $this->jweBuilderFactory; + } + + /** + * @var NestedTokenBuilderFactory + */ + private $nestedTokenBuilderFactory; + + /** + * @return NestedTokenBuilderFactory + */ + private function getNestedTokenBuilderFactory(): NestedTokenBuilderFactory + { + if (null === $this->nestedTokenBuilderFactory) { + $this->nestedTokenBuilderFactory = new NestedTokenBuilderFactory( + $this->getJWEBuilderFactory(), + $this->getJWESerializerManagerFactory(), + $this->getJWSBuilderFactory(), + $this->getJWSSerializerManagerFactory() + ); + } + + return $this->nestedTokenBuilderFactory; + } + + /** + * @return JwsSerializer\JWSSerializerManagerFactory + */ + private function getJWSSerializerManagerFactory(): JwsSerializer\JWSSerializerManagerFactory + { + $jwsSerializerManagerFactory = new JwsSerializer\JWSSerializerManagerFactory(); + $jwsSerializerManagerFactory->add(new JwsSerializer\CompactSerializer(new StandardConverter())); + $jwsSerializerManagerFactory->add(new JwsSerializer\JSONFlattenedSerializer(new StandardConverter())); + $jwsSerializerManagerFactory->add(new JwsSerializer\JSONGeneralSerializer(new StandardConverter())); + + return $jwsSerializerManagerFactory; + } + + /** + * @var AlgorithmManagerFactory + */ + private $algorithmManagerFactory; + + /** + * @return AlgorithmManagerFactory + */ + private function getAlgorithmManagerFactory(): AlgorithmManagerFactory + { + if (null === $this->algorithmManagerFactory) { + $this->algorithmManagerFactory = new AlgorithmManagerFactory(); + $this->algorithmManagerFactory + ->add('A128GCM', new A128GCM()) + ->add('RSA-OAEP', new RSAOAEP()) + ->add('PS256', new PS256()); + } + + return $this->algorithmManagerFactory; + } + + /** + * @var CompressionMethodManagerFactory + */ + private $compressionMethodManagerFactory; + + /** + * @return CompressionMethodManagerFactory + */ + private function getCompressionMethodManagerFactory(): CompressionMethodManagerFactory + { + if (null === $this->compressionMethodManagerFactory) { + $this->compressionMethodManagerFactory = new CompressionMethodManagerFactory(); + $this->compressionMethodManagerFactory + ->add('DEF', new Compression\Deflate()) + ->add('ZLIB', new Compression\ZLib()) + ->add('GZ', new Compression\GZip()); + } + + return $this->compressionMethodManagerFactory; + } + + /** + * @var null|JweSerializer\JWESerializerManagerFactory + */ + private $jwsSerializerManagerFactory = null; + + /** + * @return JweSerializer\JWESerializerManagerFactory + */ + private function getJWESerializerManagerFactory(): JweSerializer\JWESerializerManagerFactory + { + if (null === $this->jwsSerializerManagerFactory) { + $this->jwsSerializerManagerFactory = new JweSerializer\JWESerializerManagerFactory(); + $this->jwsSerializerManagerFactory->add(new JweSerializer\CompactSerializer(new StandardConverter())); + $this->jwsSerializerManagerFactory->add(new JweSerializer\JSONFlattenedSerializer(new StandardConverter())); + $this->jwsSerializerManagerFactory->add(new JweSerializer\JSONGeneralSerializer(new StandardConverter())); + } + + return $this->jwsSerializerManagerFactory; + } +} diff --git a/src/Component/Encryption/Tests/RFC7520/NestingTokenUsingNestedTokenLoaderTest.php b/src/Component/Encryption/Tests/RFC7520/NestingTokenUsingNestedTokenLoaderTest.php new file mode 100644 index 00000000..ec4a77c1 --- /dev/null +++ b/src/Component/Encryption/Tests/RFC7520/NestingTokenUsingNestedTokenLoaderTest.php @@ -0,0 +1,304 @@ +markTestSkipped('The component "web-token/jwt-checker" is not installed.'); + } + if (!class_exists(JWSLoader::class)) { + $this->markTestSkipped('The component "web-token/jwt-signature" is not installed.'); + } + } + + public function testDecryption() + { + $payload = '{"iss":"hobbiton.example","exp":1300819380,"http://example.com/is_root":true}'; + + $encryption_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'samwise.gamgee@hobbiton.example', + 'use' => 'enc', + 'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE', + 'e' => 'AQAB', + 'alg' => 'RSA-OAEP', + 'd' => 'n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE', + 'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ', + 'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q', + 'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ', + 'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ', + 'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA', + ]); + $encryption_key_set = JWKSet::createFromKeys([$encryption_key]); + + $nestedTokenLoader = $this->getNestedTokenLoaderFactory()->create( + ['jwe_compact', 'jwe_json_flattened', 'jwe_json_general'], + ['RSA-OAEP'], + ['A128GCM'], + ['DEF'], + [], + ['jws_compact', 'jws_json_flattened', 'jws_json_general'], + ['PS256'], + [] + ); + + $signature_key = JWK::create([ + 'kty' => 'RSA', + 'kid' => 'hobbiton.example', + 'use' => 'sig', + 'n' => 'kNrPIBDXMU6fcyv5i-QHQAQ-K8gsC3HJb7FYhYaw8hXbNJa-t8q0lDKwLZgQXYV-ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6Iy6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s-hwx1IU5AT-AIelNqBgcF2vE5W25_SGGBoaROVdUYxqETDggM1z5cKV4ZjDZ8-lh4oVB07bkac6LQdHpJUUySH_Er20DXx30Kyi97PciXKTS-QKXnmm8ivyRCmux22ZoPUind2BKC5OiG4MwALhaL2Z2k8CsRdfy-7dg7z41Rp6D0ZeEvtaUp4bX4aKraL4rTfw', + 'e' => 'AQAB', + 'd' => 'ZLe_TIxpE9-W_n2VBa-HWvuYPtjvxwVXClJFOpJsdea8g9RMx34qEOEtnoYc2un3CZ3LtJi-mju5RAT8YSc76YJds3ZVw0UiO8mMBeG6-iOnvgobobNx7K57-xjTJZU72EjOr9kB7z6ZKwDDq7HFyCDhUEcYcHFVc7iL_6TibVhAhOFONWlqlJgEgwVYd0rybNGKifdnpEbwyHoMwY6HM1qvnEFgP7iZ0YzHUT535x6jj4VKcdA7ZduFkhUauysySEW7mxZM6fj1vdjJIy9LD1fIz30Xv4ckoqhKF5GONU6tNmMmNgAD6gIViyEle1PrIxl1tBhCI14bRW-zrpHgAQ', + 'p' => 'yKWYoNIAqwMRQlgIBOdT1NIcbDNUUs2Rh-pBaxD_mIkweMt4Mg-0-B2iSYvMrs8horhonV7vxCQagcBAATGW-hAafUehWjxWSH-3KccRM8toL4e0q7M-idRDOBXSoe7Z2-CV2x_ZCY3RP8qp642R13WgXqGDIM4MbUkZSjcY9-c', + 'q' => 'uND4o15V30KDzf8vFJw589p1vlQVQ3NEilrinRUPHkkxaAzDzccGgrWMWpGxGFFnNL3w5CqPLeU76-5IVYQq0HwYVl0hVXQHr7sgaGu-483Ad3ENcL23FrOnF45m7_2ooAstJDe49MeLTTQKrSIBl_SKvqpYvfSPTczPcZkh9Kk', + 'dp' => 'jmTnEoq2qqa8ouaymjhJSCnsveUXnMQC2gAneQJRQkFqQu-zV2PKPKNbPvKVyiF5b2-L3tM3OW2d2iNDyRUWXlT7V5l0KwPTABSTOnTqAmYChGi8kXXdlhcrtSvXldBakC6saxwI_TzGGY2MVXzc2ZnCvCXHV4qjSxOrfP3pHFU', + 'dq' => 'R9FUvU88OVzEkTkXl3-5-WusE4DjHmndeZIlu3rifBdfLpq_P-iWPBbGaq9wzQ1c-J7SzCdJqkEJDv5yd2C7rnZ6kpzwBh_nmL8zscAk1qsunnt9CJGAYz7-sGWy1JGShFazfP52ThB4rlCJ0YuEaQMrIzpY77_oLAhpmDA0hLk', + 'qi' => 'S8tC7ZknW6hPITkjcwttQOPLVmRfwirRlFAViuDb8NW9CrV_7F2OqUZCqmzHTYAumwGFHI1WVRep7anleWaJjxC_1b3fq_al4qH3Pe-EKiHg6IMazuRtZLUROcThrExDbF5dYbsciDnfRUWLErZ4N1Be0bnxYuPqxwKd9QZwMo0', + ]); + $signature_key_set = JWKSet::createFromKeys([ + $signature_key, + ]); + + $json_compact = 'eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYyI6IkExMjhHQ00ifQ.a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVhjurCyrBg3I7YhCRYjphDOOS4E7rXbr2Fn6NyQq-A-gqT0FXqNjVOGrG-bi13mwy7RoYhjTkBEC6P7sMYMXXx4gzMedpiJHQVeyI-zkZV7A9matpgevAJWrXzOUysYGTtwoSN6gtUVtlLaivjvb21O0ul4YxSHV-ByK1kyeetRp_fuYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e5IR7nany-25_UmC2urosNkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5o6yV64x6yzDUF_5JCIdl-Qv6H5dMVIY7q1eKpXcV1lWO_2FefEBqXxXvIjLeZivjNkzogCq3-IapSjVFnMjBxjpYLT8muaawo1yy1XXMuinIpNcOY3n4KKrXLrCcteX85m4IIHMZa38s1Hpr56fPPseMA-Jltmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3kJusAamBKOYwfk7JhLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15qJIEXNJtqnblpymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TXuPC8yDDhi1smxS_X_xpkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX2Fo_GnVrNwlK7Lgxw6FSQvDO0.GbX1i9kXz0sxXPmA.SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBNgCe2OFMf66cSJ8k2QkxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAvVLsAXB0_UTCBGyBg3C2bWLXqZlfJAAoJRUPRk-BimYZY81zVBuIhc7HsQePCpu33SzMsFHjn4lP_idrJz_glZTNgKDt8zdnUPauKTKDNOH1DD4fuzvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9ChMPqW1QNhzuX_Zul3bvrJwr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEaulV18l4Fg9tLejdkAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2zI3Q_1sYjKUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUrlx4gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gYjLfhn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5XmnwZMyNc9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMxtlMizR88NKU1WkBsiXMCjy1Noue7MD-ShDp5dmM.KnIKEhN8U-3C9s4gtSpjSw'; + $json_flattened = '{"encrypted_key": "a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVhjurCyrBg3I7YhCRYjphDOOS4E7rXbr2Fn6NyQq-A-gqT0FXqNjVOGrG-bi13mwy7RoYhjTkBEC6P7sMYMXXx4gzMedpiJHQVeyI-zkZV7A9matpgevAJWrXzOUysYGTtwoSN6gtUVtlLaivjvb21O0ul4YxSHV-ByK1kyeetRp_fuYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e5IR7nany-25_UmC2urosNkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5o6yV64x6yzDUF_5JCIdl-Qv6H5dMVIY7q1eKpXcV1lWO_2FefEBqXxXvIjLeZivjNkzogCq3-IapSjVFnMjBxjpYLT8muaawo1yy1XXMuinIpNcOY3n4KKrXLrCcteX85m4IIHMZa38s1Hpr56fPPseMA-Jltmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3kJusAamBKOYwfk7JhLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15qJIEXNJtqnblpymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TXuPC8yDDhi1smxS_X_xpkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX2Fo_GnVrNwlK7Lgxw6FSQvDO0","protected": "eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYyI6IkExMjhHQ00ifQ","iv": "GbX1i9kXz0sxXPmA","ciphertext": "SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBNgCe2OFMf66cSJ8k2QkxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAvVLsAXB0_UTCBGyBg3C2bWLXqZlfJAAoJRUPRk-BimYZY81zVBuIhc7HsQePCpu33SzMsFHjn4lP_idrJz_glZTNgKDt8zdnUPauKTKDNOH1DD4fuzvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9ChMPqW1QNhzuX_Zul3bvrJwr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEaulV18l4Fg9tLejdkAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2zI3Q_1sYjKUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUrlx4gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gYjLfhn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5XmnwZMyNc9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMxtlMizR88NKU1WkBsiXMCjy1Noue7MD-ShDp5dmM","tag": "KnIKEhN8U-3C9s4gtSpjSw"}'; + $json_general = '{"recipients": [{"encrypted_key": "a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVhjurCyrBg3I7YhCRYjphDOOS4E7rXbr2Fn6NyQq-A-gqT0FXqNjVOGrG-bi13mwy7RoYhjTkBEC6P7sMYMXXx4gzMedpiJHQVeyI-zkZV7A9matpgevAJWrXzOUysYGTtwoSN6gtUVtlLaivjvb21O0ul4YxSHV-ByK1kyeetRp_fuYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e5IR7nany-25_UmC2urosNkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5o6yV64x6yzDUF_5JCIdl-Qv6H5dMVIY7q1eKpXcV1lWO_2FefEBqXxXvIjLeZivjNkzogCq3-IapSjVFnMjBxjpYLT8muaawo1yy1XXMuinIpNcOY3n4KKrXLrCcteX85m4IIHMZa38s1Hpr56fPPseMA-Jltmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3kJusAamBKOYwfk7JhLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15qJIEXNJtqnblpymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TXuPC8yDDhi1smxS_X_xpkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX2Fo_GnVrNwlK7Lgxw6FSQvDO0"}],"protected": "eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYyI6IkExMjhHQ00ifQ","iv": "GbX1i9kXz0sxXPmA","ciphertext": "SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBNgCe2OFMf66cSJ8k2QkxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAvVLsAXB0_UTCBGyBg3C2bWLXqZlfJAAoJRUPRk-BimYZY81zVBuIhc7HsQePCpu33SzMsFHjn4lP_idrJz_glZTNgKDt8zdnUPauKTKDNOH1DD4fuzvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9ChMPqW1QNhzuX_Zul3bvrJwr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEaulV18l4Fg9tLejdkAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2zI3Q_1sYjKUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUrlx4gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gYjLfhn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5XmnwZMyNc9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMxtlMizR88NKU1WkBsiXMCjy1Noue7MD-ShDp5dmM","tag": "KnIKEhN8U-3C9s4gtSpjSw"}'; + + $loaded_compact_json = $nestedTokenLoader->load($json_compact, $encryption_key_set, $signature_key_set, $json_compact_signature); + self::assertEquals($payload, $loaded_compact_json->getPayload()); + self::assertEquals(0, $json_compact_signature); + + $loaded_flattened_json = $nestedTokenLoader->load($json_flattened, $encryption_key_set, $signature_key_set, $json_flattened_signature); + self::assertEquals($payload, $loaded_flattened_json->getPayload()); + self::assertEquals(0, $json_flattened_signature); + + $loaded_json = $nestedTokenLoader->load($json_general, $encryption_key_set, $signature_key_set, $json_general_signature); + self::assertEquals($payload, $loaded_json->getPayload()); + self::assertEquals(0, $json_general_signature); + } + + /** + * @var JWSLoaderFactory + */ + private $jwsLoaderFactory; + + /** + * @return JWSLoaderFactory + */ + protected function getJWSLoaderFactory(): JWSLoaderFactory + { + if (null === $this->jwsLoaderFactory) { + $this->jwsLoaderFactory = new JWSLoaderFactory( + $this->getJWSSerializerManagerFactory(), + $this->getJWSVerifierFactory(), + $this->getHeaderCheckerManagerFactory() + ); + } + + return $this->jwsLoaderFactory; + } + + /** + * @var JWELoaderFactory + */ + private $jweLoaderFactory; + + /** + * @return JWELoaderFactory + */ + protected function getJWELoaderFactory(): JWELoaderFactory + { + if (null === $this->jweLoaderFactory) { + $this->jweLoaderFactory = new JWELoaderFactory( + $this->getJWESerializerManagerFactory(), + $this->getJWEDecrypterFactory(), + $this->getHeaderCheckerManagerFactory() + ); + } + + return $this->jweLoaderFactory; + } + + /** + * @var NestedTokenLoaderFactory + */ + private $nestedTokenLoaderFactory; + + /** + * @return NestedTokenLoaderFactory + */ + private function getNestedTokenLoaderFactory(): NestedTokenLoaderFactory + { + if (null === $this->nestedTokenLoaderFactory) { + $this->nestedTokenLoaderFactory = new NestedTokenLoaderFactory( + $this->getJWELoaderFactory(), + $this->getJWSLoaderFactory() + ); + } + + return $this->nestedTokenLoaderFactory; + } + + /** + * @return HeaderCheckerManagerFactory + */ + private function getHeaderCheckerManagerFactory(): HeaderCheckerManagerFactory + { + $headerCheckerManagerFactory = new HeaderCheckerManagerFactory(); + $headerCheckerManagerFactory->addTokenTypeSupport(new JWETokenSupport()); + $headerCheckerManagerFactory->addTokenTypeSupport(new JWSTokenSupport()); + + return $headerCheckerManagerFactory; + } + + /** + * @return JwsSerializer\JWSSerializerManagerFactory + */ + private function getJWSSerializerManagerFactory(): JwsSerializer\JWSSerializerManagerFactory + { + $jwsSerializerManagerFactory = new JwsSerializer\JWSSerializerManagerFactory(); + $jwsSerializerManagerFactory->add(new JwsSerializer\CompactSerializer(new StandardConverter())); + $jwsSerializerManagerFactory->add(new JwsSerializer\JSONFlattenedSerializer(new StandardConverter())); + $jwsSerializerManagerFactory->add(new JwsSerializer\JSONGeneralSerializer(new StandardConverter())); + + return $jwsSerializerManagerFactory; + } + + /** + * @return JWSVerifierFactory + */ + private function getJWSVerifierFactory(): JWSVerifierFactory + { + $jwsVerifierFactory = new JWSVerifierFactory( + $this->getAlgorithmManagerFactory() + ); + + return $jwsVerifierFactory; + } + + /** + * @var AlgorithmManagerFactory + */ + private $algorithmManagerFactory; + + /** + * @return AlgorithmManagerFactory + */ + private function getAlgorithmManagerFactory(): AlgorithmManagerFactory + { + if (null === $this->algorithmManagerFactory) { + $this->algorithmManagerFactory = new AlgorithmManagerFactory(); + $this->algorithmManagerFactory + ->add('A128GCM', new A128GCM()) + ->add('RSA-OAEP', new RSAOAEP()) + ->add('PS256', new PS256()); + } + + return $this->algorithmManagerFactory; + } + + /** + * @var CompressionMethodManagerFactory + */ + private $compressionMethodManagerFactory; + + /** + * @return CompressionMethodManagerFactory + */ + private function getCompressionMethodManagerFactory(): CompressionMethodManagerFactory + { + if (null === $this->compressionMethodManagerFactory) { + $this->compressionMethodManagerFactory = new CompressionMethodManagerFactory(); + $this->compressionMethodManagerFactory + ->add('DEF', new Compression\Deflate()) + ->add('ZLIB', new Compression\ZLib()) + ->add('GZ', new Compression\GZip()); + } + + return $this->compressionMethodManagerFactory; + } + + /** + * @var JWEDecrypterFactory + */ + private $jweDecrypterFactory; + + /** + * @return JWEDecrypterFactory + */ + private function getJWEDecrypterFactory(): JWEDecrypterFactory + { + if (null === $this->jweDecrypterFactory) { + $this->jweDecrypterFactory = new JWEDecrypterFactory( + $this->getAlgorithmManagerFactory(), + $this->getCompressionMethodManagerFactory() + ); + } + + return $this->jweDecrypterFactory; + } + + /** + * @var null|JweSerializer\JWESerializerManagerFactory + */ + private $jwsSerializerManagerFactory = null; + + /** + * @return JweSerializer\JWESerializerManagerFactory + */ + private function getJWESerializerManagerFactory(): JweSerializer\JWESerializerManagerFactory + { + if (null === $this->jwsSerializerManagerFactory) { + $this->jwsSerializerManagerFactory = new JweSerializer\JWESerializerManagerFactory(); + $this->jwsSerializerManagerFactory->add(new JweSerializer\CompactSerializer(new StandardConverter())); + $this->jwsSerializerManagerFactory->add(new JweSerializer\JSONFlattenedSerializer(new StandardConverter())); + $this->jwsSerializerManagerFactory->add(new JweSerializer\JSONGeneralSerializer(new StandardConverter())); + } + + return $this->jwsSerializerManagerFactory; + } +}