diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 18f3e09..dcb7450 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -12,7 +12,7 @@ jobs: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} PROJECTNAME: 'web-eid/web-eid-authtoken-validation-php' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Download Coverity Build Tool run: | curl --silent --data "token=$TOKEN&project=$PROJECTNAME" -o cov-analysis-linux64.tar.gz https://scan.coverity.com/download/linux64 diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 353dbc8..96f37a9 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: shivammathur/setup-php@v2 with: @@ -26,7 +26,7 @@ jobs: - name: Cache Composer packages id: composer-cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: vendor key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} diff --git a/src/certificate/CertificateValidator.php b/src/certificate/CertificateValidator.php index 26d96c4..110aa36 100644 --- a/src/certificate/CertificateValidator.php +++ b/src/certificate/CertificateValidator.php @@ -79,7 +79,7 @@ public static function validateIsValidAndSignedByTrustedCA( if ($certificate->validateSignature()) { $chain = $certificate->getChain(); - $trustedCACert = end($chain); + $trustedCACert = next($chain); // Verify that the trusted CA cert is presently valid before returning the result. self::certificateIsValidOnDate($trustedCACert, $now, "Trusted CA"); diff --git a/tests/certificate/CertificateValidatorTest.php b/tests/certificate/CertificateValidatorTest.php index 9c7c262..53f0cae 100644 --- a/tests/certificate/CertificateValidatorTest.php +++ b/tests/certificate/CertificateValidatorTest.php @@ -25,14 +25,23 @@ namespace web_eid\web_eid_authtoken_validation_php\certificate; use DateTime; +use phpseclib3\File\X509; use web_eid\web_eid_authtoken_validation_php\testutil\Certificates; +use web_eid\web_eid_authtoken_validation_php\testutil\Dates; +use web_eid\web_eid_authtoken_validation_php\util\TrustedCertificates; use PHPUnit\Framework\TestCase; use web_eid\web_eid_authtoken_validation_php\exceptions\CertificateExpiredException; +use web_eid\web_eid_authtoken_validation_php\exceptions\CertificateNotTrustedException; use web_eid\web_eid_authtoken_validation_php\exceptions\CertificateNotYetValidException; class CertificateValidatorTest extends TestCase { + protected function tearDown(): void + { + Dates::resetMockedCertificateValidatorDate(); + } + public function testWhenCertificateDateValid(): void { $cert = Certificates::getJaakKristjanEsteid2018Cert(); @@ -56,4 +65,65 @@ public function testWhenCertificateExpired(): void $cert = Certificates::getJaakKristjanEsteid2018Cert(); $this->assertNull(CertificateValidator::certificateIsValidOnDate($cert, new DateTime("20.01.2050 16:00:00"), "User")); } + + public function testWhenCertSignedByDirectIssuerThenReturnsIssuerCert(): void + { + Dates::setMockedCertificateValidatorDate(new DateTime("2022-01-20 16:00:00")); + + $issuerCA = Certificates::getTestEsteid2018CA(); + + $result = CertificateValidator::validateIsValidAndSignedByTrustedCA( + $this->freshJaakKristjanCert(), + new TrustedCertificates([$issuerCA]) + ); + + $this->assertEquals( + $issuerCA->saveX509($issuerCA->getCurrentCert(), X509::FORMAT_PEM), + $result->saveX509($result->getCurrentCert(), X509::FORMAT_PEM) + ); + } + + public function testWhenCertWithThreeLevelChainThenReturnsIssuerNotRootCert(): void + { + Dates::setMockedCertificateValidatorDate(new DateTime("2022-01-20 16:00:00")); + + $issuerCA = Certificates::getTestEsteid2018CA(); + $rootCA = Certificates::getTestEsteid2018CAGov(); + + $result = CertificateValidator::validateIsValidAndSignedByTrustedCA( + $this->freshJaakKristjanCert(), + new TrustedCertificates([$issuerCA, $rootCA]) + ); + + // The intermediate issuing CA must be returned, not the root CA + $this->assertEquals( + $issuerCA->saveX509($issuerCA->getCurrentCert(), X509::FORMAT_PEM), + $result->saveX509($result->getCurrentCert(), X509::FORMAT_PEM) + ); + $this->assertNotEquals( + $rootCA->saveX509($rootCA->getCurrentCert(), X509::FORMAT_PEM), + $result->saveX509($result->getCurrentCert(), X509::FORMAT_PEM) + ); + } + + public function testWhenCertNotTrustedThenThrows(): void + { + Dates::setMockedCertificateValidatorDate(new DateTime("2022-01-20 16:00:00")); + + $this->expectException(CertificateNotTrustedException::class); + + CertificateValidator::validateIsValidAndSignedByTrustedCA( + $this->freshJaakKristjanCert(), + new TrustedCertificates([Certificates::getTestEsteid2015CA()]) + ); + } + + private function freshJaakKristjanCert(): X509 + { + // Load a fresh instance so that loadCA() calls from previous tests don't accumulate + $template = Certificates::getJaakKristjanEsteid2018Cert(); + $fresh = new X509(); + $fresh->loadX509($template->saveX509($template->getCurrentCert(), X509::FORMAT_PEM)); + return $fresh; + } }