Skip to content

Commit 9d46f22

Browse files
authored
Fix trust path denormalization for x5c data validation (#694)
Ensure the 'x5c' key contains an array before creating a CertificateTrustPath. This prevents potential errors caused by invalid 'x5c' data structures during denormalization.
1 parent 3ceacab commit 9d46f22

File tree

5 files changed

+58
-4
lines changed

5 files changed

+58
-4
lines changed

phpstan-baseline.neon

+1-1
Original file line numberDiff line numberDiff line change
@@ -2011,7 +2011,7 @@ parameters:
20112011
path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php
20122012

20132013
-
2014-
message: '#^Parameter \#1 \$certificates of static method Webauthn\\TrustPath\\CertificateTrustPath\:\:create\(\) expects array\<string\>, array given\.$#'
2014+
message: '#^Parameter \#1 \$certificates of static method Webauthn\\TrustPath\\CertificateTrustPath\:\:create\(\) expects array\<string\>, array\<mixed, mixed\> given\.$#'
20152015
identifier: argument.type
20162016
count: 1
20172017
path: src/webauthn/src/Denormalizer/TrustPathDenormalizer.php

src/webauthn/src/Denormalizer/TrustPathDenormalizer.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
use Webauthn\TrustPath\TrustPath;
1313
use function array_key_exists;
1414
use function assert;
15+
use function is_array;
1516

1617
final class TrustPathDenormalizer implements DenormalizerInterface, NormalizerInterface
1718
{
1819
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
1920
{
2021
return match (true) {
21-
array_key_exists('x5c', $data) => CertificateTrustPath::create($data),
22+
array_key_exists('x5c', $data) && is_array($data['x5c']) => CertificateTrustPath::create($data['x5c']),
2223
$data === [], isset($data['type']) && $data['type'] === EmptyTrustPath::class => EmptyTrustPath::create(),
2324
default => throw new InvalidTrustPathException('Unsupported trust path type'),
2425
};

tests/framework/ComposerJsonTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public function packageDependenciesEqualRootDependencies(): void
5656
'Dependencies declared in root composer.json, which are not declared in any sub-package: %s',
5757
implode('', $unusedDependencies)
5858
);
59-
static::assertCount(0, $unusedDependencies, $message);
59+
static::assertEmpty($unusedDependencies, $message);
6060
}
6161

6262
#[Test]

tests/library/Unit/SerializerTest.php

+53
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Webauthn\Tests\Unit;
66

7+
use PHPUnit\Framework\Attributes\DataProvider;
78
use PHPUnit\Framework\Attributes\Test;
89
use Symfony\Component\Serializer\Encoder\JsonEncode;
910
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
@@ -13,13 +14,65 @@
1314
use Webauthn\PublicKeyCredentialRpEntity;
1415
use Webauthn\PublicKeyCredentialUserEntity;
1516
use Webauthn\Tests\AbstractTestCase;
17+
use Webauthn\TrustPath\CertificateTrustPath;
18+
use Webauthn\TrustPath\EmptyTrustPath;
19+
use Webauthn\TrustPath\TrustPath;
1620
use const JSON_THROW_ON_ERROR;
1721

1822
/**
1923
* @internal
2024
*/
2125
final class SerializerTest extends AbstractTestCase
2226
{
27+
public static function provideTrustPath(): iterable
28+
{
29+
yield [
30+
CertificateTrustPath::create(['X509_KEY_1', 'X509_KEY_2', 'X509_KEY_3']),
31+
'{"x5c":["X509_KEY_1","X509_KEY_2","X509_KEY_3"]}',
32+
];
33+
yield [EmptyTrustPath::create(), '[]'];
34+
}
35+
36+
#[Test]
37+
#[DataProvider('provideTrustPath')]
38+
public function theTrustPathCanBeSerialized(TrustPath $trustPath, string $expected): void
39+
{
40+
//When
41+
$json = $this->getSerializer()
42+
->serialize(
43+
$trustPath,
44+
'json',
45+
[
46+
JsonEncode::OPTIONS => JSON_THROW_ON_ERROR,
47+
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
48+
AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => true,
49+
],
50+
);
51+
52+
//Then
53+
static::assertJsonStringEqualsJsonString($expected, $json);
54+
}
55+
56+
#[Test]
57+
#[DataProvider('provideTrustPath')]
58+
public function theTrustPathCanBeDeserialized(TrustPath $trustPath, string $expected): void
59+
{
60+
//When
61+
$deserialized = $this->getSerializer()
62+
->deserialize(
63+
$expected,
64+
TrustPath::class,
65+
'json',
66+
[
67+
AbstractObjectNormalizer::SKIP_NULL_VALUES => true,
68+
AbstractObjectNormalizer::SKIP_UNINITIALIZED_VALUES => true,
69+
],
70+
);
71+
72+
//Then
73+
static::assertEquals($trustPath, $deserialized);
74+
}
75+
2376
#[Test]
2477
public function theCredentialCanBeDeserialized(): void
2578
{

tests/symfony/functional/Attestation/AttestationTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ public function aPublicKeyCredentialCreationOptionsCanBeCreatedFromProfile(): vo
247247
);
248248
static::assertSame(32, strlen($options->challenge));
249249
static::assertSame([], $options->excludeCredentials);
250-
static::assertCount(0, $options->pubKeyCredParams);
250+
static::assertEmpty($options->pubKeyCredParams);
251251
static::assertSame('none', $options->attestation);
252252
static::assertNull($options->timeout);
253253
}

0 commit comments

Comments
 (0)