Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Preparing 5.0.0 #520

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/integrate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ jobs:
operating-system:
- "ubuntu-latest"
php-version:
- "8.1"
- "8.2"
- "8.3"
dependencies:
Expand Down
58 changes: 29 additions & 29 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,10 @@
}
},
"require": {
"php": ">=8.1",
"php": ">=8.2",
"ext-json": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"lcobucci/clock": "^2.2|^3.0",
"nyholm/psr7": "^1.5",
"paragonie/constant_time_encoding": "^2.6",
"psr/clock": "^1.0",
"psr/event-dispatcher": "^1.0",
Expand All @@ -56,20 +54,20 @@
"psr/log": "^1.0|^2.0|^3.0",
"spomky-labs/cbor-php": "^3.0",
"spomky-labs/pki-framework": "^1.0",
"symfony/config": "^6.1",
"symfony/dependency-injection": "^6.1",
"symfony/config": "^6.4|^7.0",
"symfony/clock": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/deprecation-contracts": "^3.2",
"symfony/framework-bundle": "^6.1",
"symfony/http-client": "^6.1",
"symfony/psr-http-message-bridge": "^2.1",
"symfony/security-bundle": "^6.1",
"symfony/security-core": "^6.1",
"symfony/security-http": "^6.1",
"symfony/serializer": "^6.1",
"symfony/uid": "^6.1",
"symfony/validator": "^6.1",
"symfony/framework-bundle": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/security-bundle": "^6.4|^7.0",
"symfony/security-core": "^6.4|^7.0",
"symfony/security-http": "^6.4|^7.0",
"symfony/serializer": "^6.4|^7.0",
"symfony/uid": "^6.4|^7.0",
"symfony/validator": "^6.4|^7.0",
"web-auth/cose-lib": "^4.2.3",
"web-token/jwt-signature": "^3.1"
"web-token/jwt-signature": "^3.2.8"
},
"replace": {
"web-auth/webauthn-lib": "self.version",
Expand All @@ -86,7 +84,7 @@
}
},
"suggest": {
"psr/clock-implementation": "As of 4.5.x, the PSR Clock implementation will replace lcobucci/clock",
"psr/clock-implementation": "For datetime dependency injection",
"psr/log-implementation": "Recommended to receive logs from the library",
"symfony/security-bundle": "Symfony firewall using a JSON API (perfect for script applications)",
"web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources",
Expand All @@ -96,11 +94,13 @@
},
"require-dev": {
"doctrine/dbal": "^3.4",
"doctrine/doctrine-bundle": "^2.7",
"doctrine/orm": "^2.13",
"doctrine/doctrine-bundle": "^2.8",
"doctrine/orm": "^2.14",
"doctrine/persistence": "^3.1",
"ekino/phpstan-banned-code": "^1.0",
"infection/infection": "^0.27",
"matthiasnoback/symfony-dependency-injection-test": "^4.3|^5.0",
"nyholm/psr7": "^1.8",
"php-http/curl-client": "^2.2",
"php-http/mock-client": "^1.5",
"php-parallel-lint/php-parallel-lint": "^1.3",
Expand All @@ -114,20 +114,20 @@
"qossmic/deptrac-shim": "^1.0",
"rector/rector": "^0.18",
"roave/security-advisories": "dev-latest",
"symfony/asset": "^6.3",
"symfony/asset-mapper": "^6.3",
"symfony/browser-kit": "^6.1",
"symfony/filesystem": "^6.1",
"symfony/finder": "^6.1",
"symfony/asset": "^6.4|^7.0",
"symfony/asset-mapper": "^6.4|^7.0",
"symfony/browser-kit": "^6.4|^7.0",
"symfony/filesystem": "^6.4|^7.0",
"symfony/finder": "^6.4|^7.0",
"symfony/monolog-bundle": "^3.8",
"symfony/phpunit-bridge": "^6.3",
"symfony/var-dumper": "^6.1",
"symfony/yaml": "^6.1",
"symfony/phpunit-bridge": "^6.4|^7.0",
"symfony/var-dumper": "^6.4|^7.0",
"symfony/yaml": "^6.4|^7.0",
"symplify/easy-coding-standard": "^12.0",
"web-token/jwt-key-mgmt": "^3.1",
"web-token/jwt-signature-algorithm-ecdsa": "^3.1",
"web-token/jwt-signature-algorithm-eddsa": "^3.1",
"web-token/jwt-signature-algorithm-rsa": "^3.1"
"web-token/jwt-signature-algorithm-ecdsa": "^3.2.8",
"web-token/jwt-signature-algorithm-eddsa": "^3.2.8",
"web-token/jwt-signature-algorithm-rsa": "^3.2.8"
},
"extra": {
"thanks": {
Expand Down
634 changes: 83 additions & 551 deletions phpstan-baseline.neon

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/metadata-service/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@
}
],
"require": {
"php": ">=8.1",
"php": ">=8.2",
"ext-json": "*",
"lcobucci/clock": "^2.2|^3.0",
"paragonie/constant_time_encoding": "^2.6",
"psr/clock": "^1.0",
"psr/event-dispatcher": "^1.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"psr/log": "^1.0|^2.0|^3.0",
"spomky-labs/pki-framework": "^1.0",
"symfony/deprecation-contracts": "^3.2"
"symfony/deprecation-contracts": "^3.2",
"symfony/clock": "^6.4|^7.0"
},
"autoload": {
"psr-4": {
Expand Down
17 changes: 0 additions & 17 deletions src/metadata-service/src/CertificateChain/CertificateToolbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace Webauthn\MetadataService\CertificateChain;

use ParagonIE\ConstantTime\Base64;
use function preg_replace;
use const PHP_EOL;

class CertificateToolbox
Expand Down Expand Up @@ -35,22 +33,7 @@
return $pem . (self::PEM_FOOTER . $type . '-----' . PHP_EOL);
}

/**
* @deprecated since 4.7.0 and will be removed in 5.0.0. No replacement as not used internally.
* @infection-ignore-all
*/
public static function convertPEMToDER(string $data): string
{
if (! str_contains($data, self::PEM_HEADER)) {
return $data;
}
$data = preg_replace('/\-{5}.*\-{5}[\r\n]*/', '', $data);
$data = preg_replace("/[\r\n]*/", '', $data);

return Base64::decode(trim($data), true);
}

public static function convertDERToPEM(string $data, string $type = 'CERTIFICATE'): string

Check warning on line 36 in src/metadata-service/src/CertificateChain/CertificateToolbox.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "PublicVisibility": --- Original +++ New @@ @@ $pem .= chunk_split($data, 64, PHP_EOL); return $pem . (self::PEM_FOOTER . $type . '-----' . PHP_EOL); } - public static function convertDERToPEM(string $data, string $type = 'CERTIFICATE') : string + protected static function convertDERToPEM(string $data, string $type = 'CERTIFICATE') : string { if (str_contains($data, self::PEM_HEADER)) { return $data;
{
if (str_contains($data, self::PEM_HEADER)) {
return $data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
<?php

Check failure on line 1 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static Analysis

Ignored error pattern #^Access to an undefined property Webauthn\\MetadataService\\CertificateChain\\PhpCertificateChainValidator\:\:\$requestFactory\.$# in path /home/runner/work/webauthn-framework/webauthn-framework/src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php was not matched in reported errors.

Check failure on line 1 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static Analysis

Ignored error pattern #^Call to an undefined method Symfony\\Contracts\\HttpClient\\HttpClientInterface\:\:sendRequest\(\)\.$# in path /home/runner/work/webauthn-framework/webauthn-framework/src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php was not matched in reported errors.

Check failure on line 1 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 3️⃣ Static Analysis

Ignored error pattern #^Instanceof between Symfony\\Contracts\\HttpClient\\HttpClientInterface and Symfony\\Contracts\\HttpClient\\HttpClientInterface will always evaluate to true\.$# in path /home/runner/work/webauthn-framework/webauthn-framework/src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php was not matched in reported errors.

declare(strict_types=1);

namespace Webauthn\MetadataService\CertificateChain;

use DateTimeZone;
use Lcobucci\Clock\Clock;
use Lcobucci\Clock\SystemClock;
use Psr\Clock\ClockInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use SpomkyLabs\Pki\ASN1\Type\UnspecifiedType;
use SpomkyLabs\Pki\CryptoEncoding\PEM;
use SpomkyLabs\Pki\X509\Certificate\Certificate;
Expand All @@ -36,41 +31,22 @@
{
private const MAX_VALIDATION_LENGTH = 5;

private readonly Clock|ClockInterface $clock;

private EventDispatcherInterface $dispatcher;

public function __construct(
private readonly ClientInterface|HttpClientInterface $client,
private readonly ?RequestFactoryInterface $requestFactory = null,
null|Clock|ClockInterface $clock = null,
private readonly HttpClientInterface $client,
private readonly ClockInterface $clock,
private readonly bool $allowFailures = true
) {
if ($clock === null) {
trigger_deprecation(
'web-auth/metadata-service',
'4.5.0',
'The parameter "$clock" will become mandatory in 5.0.0. Please set a valid PSR Clock implementation instead of "null".'
);
$clock = new SystemClock(new DateTimeZone('UTC'));
}
if ($requestFactory !== null && ! $client instanceof HttpClientInterface) {
trigger_deprecation(
'web-auth/metadata-service',
'4.7.0',
'The parameter "$requestFactory" will be removed in 5.0.0. Please set it to null and set an Symfony\Contracts\HttpClient\HttpClientInterface as "$client" argument.'
);
}
$this->clock = $clock;
$this->dispatcher = new NullEventDispatcher();
}

public static function create(
HttpClientInterface $client,
null|Clock|ClockInterface $clock = null,
ClockInterface $clock,
bool $allowFailures = true
): self {
return new self($client, null, $clock, $allowFailures);
return new self($client, $clock, $allowFailures);
}

public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
Expand All @@ -90,7 +66,7 @@
);
try {
if ($this->validateChain($untrustedCertificates, $trustedCertificate)) {
$this->dispatcher->dispatch(

Check warning on line 69 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ $this->dispatcher->dispatch(BeforeCertificateChainValidation::create($untrustedCertificates, $trustedCertificate)); try { if ($this->validateChain($untrustedCertificates, $trustedCertificate)) { - $this->dispatcher->dispatch(CertificateChainValidationSucceeded::create($untrustedCertificates, $trustedCertificate)); + return; } } catch (Throwable $exception) {
CertificateChainValidationSucceeded::create($untrustedCertificates, $trustedCertificate)
);
return;
Expand Down Expand Up @@ -140,9 +116,9 @@
return false;
}

$certificates = [$trustedCertificate, ...$untrustedCertificates];

Check warning on line 119 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "SpreadOneItem": --- Original +++ New @@ @@ if (!$this->validateCertificates($trustedCertificate, ...$untrustedCertificates)) { return false; } - $certificates = [$trustedCertificate, ...$untrustedCertificates]; + $certificates = [$trustedCertificate, [...$untrustedCertificates][0]]; $numCerts = count($certificates); for ($i = 1; $i < $numCerts; $i++) { if ($this->isRevoked($certificates[$i])) {

Check warning on line 119 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ if (!$this->validateCertificates($trustedCertificate, ...$untrustedCertificates)) { return false; } - $certificates = [$trustedCertificate, ...$untrustedCertificates]; + $certificates = [...$untrustedCertificates]; $numCerts = count($certificates); for ($i = 1; $i < $numCerts; $i++) { if ($this->isRevoked($certificates[$i])) {
$numCerts = count($certificates);
for ($i = 1; $i < $numCerts; $i++) {

Check warning on line 121 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ } $certificates = [$trustedCertificate, ...$untrustedCertificates]; $numCerts = count($certificates); - for ($i = 1; $i < $numCerts; $i++) { + for ($i = 2; $i < $numCerts; $i++) { if ($this->isRevoked($certificates[$i])) { throw CertificateChainException::create($untrustedCertificates, [$trustedCertificate], 'Unable to validate the certificate chain. Revoked certificate found.'); }

Check warning on line 121 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "LessThanNegotiation": --- Original +++ New @@ @@ } $certificates = [$trustedCertificate, ...$untrustedCertificates]; $numCerts = count($certificates); - for ($i = 1; $i < $numCerts; $i++) { + for ($i = 1; $i >= $numCerts; $i++) { if ($this->isRevoked($certificates[$i])) { throw CertificateChainException::create($untrustedCertificates, [$trustedCertificate], 'Unable to validate the certificate chain. Revoked certificate found.'); }

Check warning on line 121 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "For_": --- Original +++ New @@ @@ } $certificates = [$trustedCertificate, ...$untrustedCertificates]; $numCerts = count($certificates); - for ($i = 1; $i < $numCerts; $i++) { + for ($i = 1; false; $i++) { if ($this->isRevoked($certificates[$i])) { throw CertificateChainException::create($untrustedCertificates, [$trustedCertificate], 'Unable to validate the certificate chain. Revoked certificate found.'); }
if ($this->isRevoked($certificates[$i])) {
throw CertificateChainException::create(
$untrustedCertificates,
Expand Down Expand Up @@ -183,7 +159,7 @@
);
}

foreach ($urls as $url) {

Check warning on line 162 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "Foreach_": --- Original +++ New @@ @@ } throw InvalidCertificateException::create($subject->toPEM()->string(), 'Failed to get CRL distribution points: ' . $e->getMessage(), $e); } - foreach ($urls as $url) { + foreach (array() as $url) { try { $revokedCertificates = $this->retrieveRevokedSerialNumbers($url); if (in_array($csn, $revokedCertificates, true)) {
try {
$revokedCertificates = $this->retrieveRevokedSerialNumbers($url);

Expand Down Expand Up @@ -221,12 +197,8 @@
private function retrieveRevokedSerialNumbers(string $url): array
{
try {
if ($this->client instanceof HttpClientInterface) {
$crlData = $this->client->request('GET', $url)
->getContent();
} else {
$crlData = $this->sendPsrRequest($url);
}
$crlData = $this->client->request('GET', $url)
->getContent();
$crl = UnspecifiedType::fromDER($crlData)->asSequence();
count($crl) === 3 || throw CertificateRevocationListException::create($url);
$tbsCertList = $crl->at(0)
Expand All @@ -243,7 +215,7 @@
->number();
}, $list->elements());
} catch (Throwable $e) {
throw CertificateRevocationListException::create($url, 'Failed to download the CRL', $e);

Check warning on line 218 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "Throw_": --- Original +++ New @@ @@ return $sequence->at(0)->asInteger()->number(); }, $list->elements()); } catch (Throwable $e) { - throw CertificateRevocationListException::create($url, 'Failed to download the CRL', $e); + CertificateRevocationListException::create($url, 'Failed to download the CRL', $e); } } /**
}
}

Expand All @@ -257,7 +229,7 @@

$extensions = $subject->tbsCertificate()
->extensions();
if ($extensions->hasCRLDistributionPoints()) {

Check warning on line 232 in src/metadata-service/src/CertificateChain/PhpCertificateChainValidator.php

View workflow job for this annotation

GitHub Actions / 5️⃣ Mutation Testing

Escaped Mutant for Mutator "IfNegation": --- Original +++ New @@ @@ try { $urls = []; $extensions = $subject->tbsCertificate()->extensions(); - if ($extensions->hasCRLDistributionPoints()) { + if (!$extensions->hasCRLDistributionPoints()) { $crlDists = $extensions->crlDistributionPoints(); foreach ($crlDists->distributionPoints() as $dist) { $url = $dist->fullName()->names()->firstURI();
$crlDists = $extensions->crlDistributionPoints();
foreach ($crlDists->distributionPoints() as $dist) {
$url = $dist->fullName()
Expand All @@ -280,16 +252,4 @@
);
}
}

private function sendPsrRequest(string $url): string
{
$request = $this->requestFactory->createRequest('GET', $url);
$response = $this->client->sendRequest($request);
if ($response->getStatusCode() !== 200) {
throw CertificateRevocationListException::create($url, 'Failed to download the CRL');
}

return $response->getBody()
->getContents();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class ExtensionDescriptorDenormalizer implements DenormalizerInterface, De

private const ALREADY_CALLED = 'EXTENSION_DESCRIPTOR_PREPROCESS_ALREADY_CALLED';

public function denormalize(mixed $data, string $type, string $format = null, array $context = [])
public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
{
if ($this->denormalizer === null) {
throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

use ParagonIE\ConstantTime\Base64;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Webauthn\MetadataService\Denormalizer\MetadataStatementSerializerFactory;
Expand All @@ -25,26 +23,18 @@ final class DistantResourceMetadataService implements MetadataService, CanDispat

private EventDispatcherInterface $dispatcher;

private readonly ?SerializerInterface $serializer;
private readonly SerializerInterface $serializer;

/**
* @param array<string, string> $additionalHeaderParameters
*/
public function __construct(
private readonly ?RequestFactoryInterface $requestFactory,
private readonly ClientInterface|HttpClientInterface $httpClient,
private readonly HttpClientInterface $httpClient,
private readonly string $uri,
private readonly bool $isBase64Encoded = false,
private readonly array $additionalHeaderParameters = [],
?SerializerInterface $serializer = null,
) {
if ($requestFactory !== null && ! $httpClient instanceof HttpClientInterface) {
trigger_deprecation(
'web-auth/metadata-service',
'4.7.0',
'The parameter "$requestFactory" will be removed in 5.0.0. Please set it to null and set an Symfony\Contracts\HttpClient\HttpClientInterface as "$httpClient" argument.'
);
}
$this->serializer = $serializer ?? MetadataStatementSerializerFactory::create();
$this->dispatcher = new NullEventDispatcher();
}
Expand All @@ -58,14 +48,13 @@ public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): v
* @param array<string, mixed> $additionalHeaderParameters
*/
public static function create(
?RequestFactoryInterface $requestFactory,
ClientInterface|HttpClientInterface $httpClient,
HttpClientInterface $httpClient,
string $uri,
bool $isBase64Encoded = false,
array $additionalHeaderParameters = [],
?SerializerInterface $serializer = null
): self {
return new self($requestFactory, $httpClient, $uri, $isBase64Encoded, $additionalHeaderParameters, $serializer);
return new self($httpClient, $uri, $isBase64Encoded, $additionalHeaderParameters, $serializer);
}

public function list(): iterable
Expand Down Expand Up @@ -112,46 +101,19 @@ private function loadData(): void
if ($this->isBase64Encoded) {
$content = Base64::decode($content, true);
}
if ($this->serializer !== null) {
$this->statement = $this->serializer->deserialize($content, MetadataStatement::class, 'json');
return;
}

$this->statement = MetadataStatement::createFromString($content);
$this->statement = $this->serializer->deserialize($content, MetadataStatement::class, 'json');
}

private function fetch(): string
{
if ($this->httpClient instanceof HttpClientInterface) {
$content = $this->sendSymfonyRequest();
} else {
$content = $this->sendPsrRequest();
}
$content = $this->sendSymfonyRequest();
$content !== '' || throw MetadataStatementLoadingException::create(
'Unable to contact the server. The response has no content'
);

return $content;
}

private function sendPsrRequest(): string
{
$request = $this->requestFactory->createRequest('GET', $this->uri);
foreach ($this->additionalHeaderParameters as $k => $v) {
$request = $request->withHeader($k, $v);
}
$response = $this->httpClient->sendRequest($request);
$response->getStatusCode() === 200 || throw MetadataStatementLoadingException::create(sprintf(
'Unable to contact the server. Response code is %d',
$response->getStatusCode()
));
$response->getBody()
->rewind();

return $response->getBody()
->getContents();
}

private function sendSymfonyRequest(): string
{
$response = $this->httpClient->request('GET', $this->uri, [
Expand Down
Loading
Loading