From 1f396143ab6f012c39cea5aa54d6e05af2342659 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Wed, 12 Nov 2025 16:47:54 +0100 Subject: [PATCH] Enhance key operations validation in UsageAnalyzer and add unit tests (#641) --- .../KeyManagement/Analyzer/UsageAnalyzer.php | 35 ++-- .../KeyManagement/UsageAnalyzerTest.php | 166 ++++++++++++++++++ 2 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 tests/Component/KeyManagement/UsageAnalyzerTest.php diff --git a/src/Library/KeyManagement/Analyzer/UsageAnalyzer.php b/src/Library/KeyManagement/Analyzer/UsageAnalyzer.php index 771d9a8f..6af0d4f7 100644 --- a/src/Library/KeyManagement/Analyzer/UsageAnalyzer.php +++ b/src/Library/KeyManagement/Analyzer/UsageAnalyzer.php @@ -6,7 +6,9 @@ use Jose\Component\Core\JWK; use Override; +use function array_diff; use function in_array; +use function is_array; use function sprintf; final readonly class UsageAnalyzer implements KeyAnalyzer @@ -24,18 +26,27 @@ public function analyze(JWK $jwk, MessageBag $bag): void )) ); } - if ($jwk->has('key_ops') && ! in_array( - $jwk->get('key_ops'), - ['sign', 'verify', 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey'], - true - )) { - $bag->add( - Message::high(sprintf( - 'The parameter "key_ops" has an unsupported value "%s". Please use one of the following values: %s.', - $jwk->get('key_ops'), - implode(', ', ['verify', 'sign', 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey']) - )) - ); + if ($jwk->has('key_ops')) { + $key_ops = $jwk->get('key_ops'); + if (! is_array($key_ops)) { + $bag->add( + Message::high( + 'The parameter "key_ops" must be an array of key operation values.' + ) + ); + } else { + $allowedOps = ['sign', 'verify', 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey', 'deriveKey', 'deriveBits']; + $unsupportedOps = array_diff($key_ops, $allowedOps); + if ($unsupportedOps !== []) { + $bag->add( + Message::high(sprintf( + 'The parameter "key_ops" contains unsupported values: "%s". Please use only the following values: %s.', + implode('", "', $unsupportedOps), + implode(', ', $allowedOps) + )) + ); + } + } } } } diff --git a/tests/Component/KeyManagement/UsageAnalyzerTest.php b/tests/Component/KeyManagement/UsageAnalyzerTest.php new file mode 100644 index 00000000..675de22a --- /dev/null +++ b/tests/Component/KeyManagement/UsageAnalyzerTest.php @@ -0,0 +1,166 @@ +analyzer = new UsageAnalyzer(); + } + + #[Test] + public function keyWithoutUseShouldGetMediumMessage(): void + { + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => 'GawgguFyGrWKav7AX4VKUg', + ]); + + $bag = new MessageBag(); + $this->analyzer->analyze($jwk, $bag); + + $messages = $bag->all(); + static::assertCount(1, $messages); + static::assertSame(Message::SEVERITY_MEDIUM, $messages[0]->getSeverity()); + static::assertSame('The parameter "use" should be added.', $messages[0]->getMessage()); + } + + #[Test] + public function keyWithValidUseShouldHaveNoMessages(): void + { + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => 'GawgguFyGrWKav7AX4VKUg', + 'use' => 'sig', + ]); + + $bag = new MessageBag(); + $this->analyzer->analyze($jwk, $bag); + + static::assertEmpty($bag->all()); + } + + #[Test] + public function keyWithInvalidUseShouldGetHighMessage(): void + { + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => 'GawgguFyGrWKav7AX4VKUg', + 'use' => 'invalid', + ]); + + $bag = new MessageBag(); + $this->analyzer->analyze($jwk, $bag); + + $messages = $bag->all(); + static::assertCount(1, $messages); + static::assertSame(Message::SEVERITY_HIGH, $messages[0]->getSeverity()); + static::assertStringContainsString('unsupported value "invalid"', $messages[0]->getMessage()); + } + + #[Test] + public function keyWithValidKeyOpsArrayShouldHaveNoMessages(): void + { + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => 'GawgguFyGrWKav7AX4VKUg', + 'use' => 'sig', + 'key_ops' => ['sign', 'verify'], + ]); + + $bag = new MessageBag(); + $this->analyzer->analyze($jwk, $bag); + + static::assertEmpty($bag->all()); + } + + #[Test] + public function keyWithKeyOpsAsStringShouldGetHighMessage(): void + { + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => 'GawgguFyGrWKav7AX4VKUg', + 'use' => 'sig', + 'key_ops' => 'sign', + ]); + + $bag = new MessageBag(); + $this->analyzer->analyze($jwk, $bag); + + $messages = $bag->all(); + static::assertCount(1, $messages); + static::assertSame(Message::SEVERITY_HIGH, $messages[0]->getSeverity()); + static::assertSame( + 'The parameter "key_ops" must be an array of key operation values.', + $messages[0]->getMessage() + ); + } + + #[Test] + public function keyWithInvalidKeyOpsValuesShouldGetHighMessage(): void + { + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => 'GawgguFyGrWKav7AX4VKUg', + 'use' => 'sig', + 'key_ops' => ['sign', 'invalid', 'unknownOp'], + ]); + + $bag = new MessageBag(); + $this->analyzer->analyze($jwk, $bag); + + $messages = $bag->all(); + static::assertCount(1, $messages); + static::assertSame(Message::SEVERITY_HIGH, $messages[0]->getSeverity()); + static::assertStringContainsString('unsupported values', $messages[0]->getMessage()); + static::assertStringContainsString('invalid', $messages[0]->getMessage()); + static::assertStringContainsString('unknownOp', $messages[0]->getMessage()); + } + + #[Test] + public function keyWithAllValidKeyOpsValuesShouldHaveNoMessages(): void + { + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => 'GawgguFyGrWKav7AX4VKUg', + 'use' => 'enc', + 'key_ops' => ['sign', 'verify', 'encrypt', 'decrypt', 'wrapKey', 'unwrapKey', 'deriveKey', 'deriveBits'], + ]); + + $bag = new MessageBag(); + $this->analyzer->analyze($jwk, $bag); + + static::assertEmpty($bag->all()); + } + + #[Test] + public function keyWithEmptyKeyOpsArrayShouldHaveNoMessages(): void + { + $jwk = new JWK([ + 'kty' => 'oct', + 'k' => 'GawgguFyGrWKav7AX4VKUg', + 'use' => 'sig', + 'key_ops' => [], + ]); + + $bag = new MessageBag(); + $this->analyzer->analyze($jwk, $bag); + + static::assertEmpty($bag->all()); + } +}