From 159c575a63a726043e4bcf46edd62b696e6b9898 Mon Sep 17 00:00:00 2001 From: Mihkel Kivisild Date: Wed, 25 Sep 2024 15:33:56 +0300 Subject: [PATCH] Merging ocsp-php library into web-eid-authtoken-validation-php libary WE2-920 Signed-off-by: Mihkel Kivisild --- .prettierrc | 3 + composer.json | 7 - composer.lock | 217 +++++--------- src/ocsp/Ocsp.php | 141 +++++++++ src/ocsp/OcspBasicResponse.php | 146 ++++++++++ src/ocsp/OcspRequest.php | 83 ++++++ src/ocsp/OcspResponse.php | 171 +++++++++++ src/ocsp/certificate/CertificateLoader.php | 129 +++++++++ .../exceptions/OcspCertificateException.php | 38 +++ src/ocsp/exceptions/OcspException.php | 43 +++ .../OcspResponseDecodeException.php | 38 +++ .../exceptions/OcspVerifyFailedException.php | 38 +++ src/ocsp/maps/OcspBasicResponseMap.php | 166 +++++++++++ src/ocsp/maps/OcspRequestMap.php | 122 ++++++++ src/ocsp/maps/OcspResponseMap.php | 63 ++++ src/util/AsnUtil.php | 58 ++++ .../SubjectCertificateNotRevokedValidator.php | 8 +- src/validator/ocsp/OcspClient.php | 2 +- src/validator/ocsp/OcspClientImpl.php | 2 +- src/validator/ocsp/OcspRequestBuilder.php | 2 +- src/validator/ocsp/OcspResponseValidator.php | 4 +- tests/_resources/invalid.crt | 21 ++ tests/_resources/revoked.crt | 31 ++ tests/_resources/revoked.issuer.crt | 27 ++ tests/ocsp/OcspRequestTest.php | 104 +++++++ tests/ocsp/OcspResponseTest.php | 270 ++++++++++++++++++ tests/ocsp/OcspTest.php | 60 ++++ .../certificate/CertificateLoaderTest.php | 98 +++++++ ...jectCertificateNotRevokedValidatorTest.php | 4 +- .../ocsp/OcspResponseValidatorTest.php | 2 +- 30 files changed, 1938 insertions(+), 160 deletions(-) create mode 100644 .prettierrc create mode 100644 src/ocsp/Ocsp.php create mode 100644 src/ocsp/OcspBasicResponse.php create mode 100644 src/ocsp/OcspRequest.php create mode 100644 src/ocsp/OcspResponse.php create mode 100644 src/ocsp/certificate/CertificateLoader.php create mode 100644 src/ocsp/exceptions/OcspCertificateException.php create mode 100644 src/ocsp/exceptions/OcspException.php create mode 100644 src/ocsp/exceptions/OcspResponseDecodeException.php create mode 100644 src/ocsp/exceptions/OcspVerifyFailedException.php create mode 100644 src/ocsp/maps/OcspBasicResponseMap.php create mode 100644 src/ocsp/maps/OcspRequestMap.php create mode 100644 src/ocsp/maps/OcspResponseMap.php create mode 100644 tests/_resources/invalid.crt create mode 100644 tests/_resources/revoked.crt create mode 100644 tests/_resources/revoked.issuer.crt create mode 100644 tests/ocsp/OcspRequestTest.php create mode 100644 tests/ocsp/OcspResponseTest.php create mode 100644 tests/ocsp/OcspTest.php create mode 100644 tests/ocsp/certificate/CertificateLoaderTest.php diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..61a98c3 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "plugins": ["@prettier/plugin-php"] +} \ No newline at end of file diff --git a/composer.json b/composer.json index 780582a..5b9ea3a 100644 --- a/composer.json +++ b/composer.json @@ -25,17 +25,10 @@ "web_eid\\web_eid_authtoken_validation_php\\": ["tests"] } }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/web-eid/ocsp-php.git" - } - ], "require": { "php": ">=8.1", "phpseclib/phpseclib": "3.0.*", "guzzlehttp/psr7": "2.6.*", - "web-eid/ocsp-php": "1.1.*", "psr/log": "^3.0" }, "scripts": { diff --git a/composer.lock b/composer.lock index a619a5e..0d8e123 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1f6887b9d84ca619b7724040b375d5eb", + "content-hash": "f74644f0b56d3fa3e96de21a1e6742cc", "packages": [ { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.6.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "6de29867b18790c0d2c846af4c13a24cc3ad56f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/6de29867b18790c0d2c846af4c13a24cc3ad56f3", + "reference": "6de29867b18790c0d2c846af4c13a24cc3ad56f3", "shasum": "" }, "require": { @@ -32,8 +32,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -104,7 +104,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.6.3" }, "funding": [ { @@ -120,7 +120,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T09:59:12+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -241,16 +241,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.39", + "version": "3.0.42", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485" + "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/211ebc399c6e73c225a018435fe5ae209d1d1485", - "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98", + "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98", "shasum": "" }, "require": { @@ -331,7 +331,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.39" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42" }, "funding": [ { @@ -347,7 +347,7 @@ "type": "tidelift" } ], - "time": "2024-06-24T06:27:33+00:00" + "time": "2024-09-16T03:06:04+00:00" }, { "name": "psr/http-factory", @@ -459,16 +459,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -503,9 +503,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "ralouphie/getallheaders", @@ -550,71 +550,6 @@ "source": "https://github.com/ralouphie/getallheaders/tree/develop" }, "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "web-eid/ocsp-php", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/web-eid/ocsp-php.git", - "reference": "6a2aa24e5c22bb1477421b118e563d679ddd69b7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web-eid/ocsp-php/zipball/6a2aa24e5c22bb1477421b118e563d679ddd69b7", - "reference": "6a2aa24e5c22bb1477421b118e563d679ddd69b7", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "phpseclib/phpseclib": "3.0.*" - }, - "require-dev": { - "phpunit/phpunit": "^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "web_eid\\ocsp_php\\": [ - "src" - ] - } - }, - "autoload-dev": { - "psr-4": { - "web_eid\\ocsp_php\\": [ - "tests" - ] - } - }, - "scripts": { - "fix-php": [ - "prettier src/**/* --write", - "prettier src/* --write" - ], - "test": [ - "phpunit --no-coverage --display-warnings" - ], - "test-coverage": [ - "@putenv XDEBUG_MODE=coverage", - "phpunit --coverage-html coverage" - ] - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guido Gröön", - "role": "developer" - } - ], - "description": "OCSP library for PHP", - "support": { - "source": "https://github.com/web-eid/ocsp-php/tree/1.1.2", - "issues": "https://github.com/web-eid/ocsp-php/issues" - }, - "time": "2024-03-15T11:51:14+00:00" } ], "packages-dev": [ @@ -680,16 +615,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", "shasum": "" }, "require": { @@ -732,9 +667,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-09-15T16:40:33+00:00" }, { "name": "phar-io/manifest", @@ -856,32 +791,32 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.15", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^10.1" @@ -893,7 +828,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -922,7 +857,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -930,7 +865,7 @@ "type": "github" } ], - "time": "2024-06-29T08:25:15+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1177,16 +1112,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.24", + "version": "10.5.34", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015" + "reference": "3c69d315bdf79080c8e115b69d1961c6905b0e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f124e3e3e561006047b532fd0431bf5bb6b9015", - "reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3c69d315bdf79080c8e115b69d1961c6905b0e18", + "reference": "3c69d315bdf79080c8e115b69d1961c6905b0e18", "shasum": "" }, "require": { @@ -1196,26 +1131,26 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.5", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-invoker": "^4.0", - "phpunit/php-text-template": "^3.0", - "phpunit/php-timer": "^6.0", - "sebastian/cli-parser": "^2.0", - "sebastian/code-unit": "^2.0", - "sebastian/comparator": "^5.0", - "sebastian/diff": "^5.0", - "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.1", - "sebastian/global-state": "^6.0.1", - "sebastian/object-enumerator": "^5.0", - "sebastian/recursion-context": "^5.0", - "sebastian/type": "^4.0", - "sebastian/version": "^4.0" + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.2", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -1258,7 +1193,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.24" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.34" }, "funding": [ { @@ -1274,7 +1209,7 @@ "type": "tidelift" } ], - "time": "2024-06-20T13:09:54+00:00" + "time": "2024-09-13T05:19:38+00:00" }, { "name": "sebastian/cli-parser", @@ -1446,16 +1381,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", "shasum": "" }, "require": { @@ -1466,7 +1401,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^10.4" }, "type": "library", "extra": { @@ -1511,7 +1446,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" }, "funding": [ { @@ -1519,7 +1454,7 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2024-08-12T06:03:08+00:00" }, { "name": "sebastian/complexity", @@ -2252,5 +2187,5 @@ "php": ">=8.1" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } diff --git a/src/ocsp/Ocsp.php b/src/ocsp/Ocsp.php new file mode 100644 index 0000000..2792dbd --- /dev/null +++ b/src/ocsp/Ocsp.php @@ -0,0 +1,141 @@ + [], + "issuerNameHash" => "", + "issuerKeyHash" => "", + "serialNumber" => [], + ]; + + if ( + !isset( + $certificate->getCurrentCert()["tbsCertificate"]["serialNumber"] + ) + ) { + // Serial number of subject certificate does not exist + throw new OcspCertificateException( + "Serial number of subject certificate does not exist" + ); + } + + $certificateId["serialNumber"] = clone $certificate->getCurrentCert()[ + "tbsCertificate" + ]["serialNumber"]; + + // issuer name + if ( + !isset( + $issuerCertificate->getCurrentCert()["tbsCertificate"][ + "subject" + ] + ) + ) { + // Serial number of issuer certificate does not exist + throw new OcspCertificateException( + "Serial number of issuer certificate does not exist" + ); + } + + $issuer = $issuerCertificate->getCurrentCert()["tbsCertificate"][ + "subject" + ]; + $issuerEncoded = ASN1::encodeDER($issuer, Name::MAP); + $certificateId["issuerNameHash"] = hash("sha1", $issuerEncoded, true); + + // issuer public key + if ( + !isset( + $issuerCertificate->getCurrentCert()["tbsCertificate"][ + "subjectPublicKeyInfo" + ]["subjectPublicKey"] + ) + ) { + // SubjectPublicKey of issuer certificate does not exist + throw new OcspCertificateException( + "SubjectPublicKey of issuer certificate does not exist" + ); + } + + $publicKey = $issuerCertificate->getCurrentCert()["tbsCertificate"][ + "subjectPublicKeyInfo" + ]["subjectPublicKey"]; + $certificateId["issuerKeyHash"] = hash( + "sha1", + AsnUtil::extractKeyData($publicKey), + true + ); + + $certificateId["hashAlgorithm"]["algorithm"] = Asn1::getOID("id-sha1"); + + return $certificateId; + } +} diff --git a/src/ocsp/OcspBasicResponse.php b/src/ocsp/OcspBasicResponse.php new file mode 100644 index 0000000..4fe8e1a --- /dev/null +++ b/src/ocsp/OcspBasicResponse.php @@ -0,0 +1,146 @@ +ocspBasicResponse = $ocspBasicResponse; + } + + public function getResponses(): array + { + return $this->ocspBasicResponse["tbsResponseData"]["responses"]; + } + + /** + * @copyright 2022 Petr Muzikant pmuzikant@email.cz + */ + public function getCertificates(): array + { + $certificatesArr = []; + if (isset($this->ocspBasicResponse["certs"])) { + foreach ($this->ocspBasicResponse["certs"] as $cert) { + $x509 = new X509(); + /* + We need to DER encode each responder certificate array as there exists some + more loading in X509->loadX509 method, which is not executed when loading just basic array. + For example without this the publicKey would not be in PEM format and X509->getPublicKey() + will throw error. It also maps out the extensions from BIT STRING + */ + $x509->loadX509(ASN1::encodeDER($cert, Certificate::MAP)); + $certificatesArr[] = $x509; + } + unset($x509); + } + + return $certificatesArr; + } + + public function getSignature(): string + { + $signature = $this->ocspBasicResponse["signature"]; + return pack("c*", ...array_slice(unpack("c*", $signature), 1)); + } + + public function getProducedAt(): DateTime + { + return new DateTime( + $this->ocspBasicResponse["tbsResponseData"]["producedAt"] + ); + } + + public function getThisUpdate(): DateTime + { + return new DateTime($this->getResponses()[0]["thisUpdate"]); + } + + public function getNextUpdate(): ?DateTime + { + if (isset($this->getResponses()[0]["nextUpdate"])) { + return new DateTime($this->getResponses()[0]["nextUpdate"]); + } + return null; + } + + /** + * @copyright 2022 Petr Muzikant pmuzikant@email.cz + */ + public function getSignatureAlgorithm(): string + { + $algorithm = strtolower( + $this->ocspBasicResponse["signatureAlgorithm"]["algorithm"] + ); + + if (false !== ($pos = strpos($algorithm, "sha3-"))) { + return substr($algorithm, $pos, 8); + } + if (false !== ($pos = strpos($algorithm, "sha"))) { + return substr($algorithm, $pos, 6); + } + + throw new OcspCertificateException( + "Signature algorithm " . $algorithm . " not implemented" + ); + } + + public function getNonceExtension(): ?string + { + return AsnUtil::decodeNonceExtension($this->ocspBasicResponse["tbsResponseData"]["responseExtensions"]); + } + + public function getCertID(): array + { + $certStatusResponse = $this->getResponses()[0]; + // Translate algorithm name to OID for correct equality check + $certStatusResponse["certID"]["hashAlgorithm"][ + "algorithm" + ] = ASN1::getOID( + $certStatusResponse["certID"]["hashAlgorithm"]["algorithm"] + ); + return $certStatusResponse["certID"]; + } + + public function getEncodedResponseData(): string + { + return ASN1::encodeDER( + $this->ocspBasicResponse["tbsResponseData"], + OcspBasicResponseMap::MAP["children"]["tbsResponseData"] + ); + } +} diff --git a/src/ocsp/OcspRequest.php b/src/ocsp/OcspRequest.php new file mode 100644 index 0000000..2d7f4ff --- /dev/null +++ b/src/ocsp/OcspRequest.php @@ -0,0 +1,83 @@ +ocspRequest = [ + "tbsRequest" => [ + "version" => "v1", + "requestList" => [], + "requestExtensions" => [], + ], + ]; + } + + public function addCertificateId(array $certificateId): void + { + $request = [ + "reqCert" => $certificateId, + ]; + $this->ocspRequest["tbsRequest"]["requestList"][] = $request; + } + + public function addNonceExtension(string $nonce): void + { + $nonceExtension = [ + "extnId" => AsnUtil::ID_PKIX_OCSP_NONCE, + "critical" => false, + "extnValue" => ASN1::encodeDER($nonce, ['type' => ASN1::TYPE_OCTET_STRING]), + ]; + $this->ocspRequest["tbsRequest"][ + "requestExtensions" + ][] = $nonceExtension; + } + + /** + * @copyright 2022 Petr Muzikant pmuzikant@email.cz + */ + public function getNonceExtension(): string + { + // TODO: the ?? '' is here only for v1.0 API compatibility. Remove this in version 1.2 and change the return type to ?string. + return AsnUtil::decodeNonceExtension($this->ocspRequest["tbsRequest"]["requestExtensions"]) ?? ''; + } + + public function getEncodeDer(): string + { + return ASN1::encodeDER($this->ocspRequest, OcspRequestMap::MAP); + } +} diff --git a/src/ocsp/OcspResponse.php b/src/ocsp/OcspResponse.php new file mode 100644 index 0000000..0d8a086 --- /dev/null +++ b/src/ocsp/OcspResponse.php @@ -0,0 +1,171 @@ +ocspResponse = ASN1::asn1map($decoded[0], OcspResponseMap::MAP, [ + "response" => function ($encoded) { + return ASN1::asn1map( + self::getDecoded($encoded)[0], + OcspBasicResponseMap::MAP + ); + }, + ]); + } + + public function getResponse(): array + { + return $this->ocspResponse; + } + + public function getBasicResponse(): OcspBasicResponse + { + if ( + Ocsp::ID_PKIX_OCSP_BASIC_STRING != + $this->ocspResponse["responseBytes"]["responseType"] + ) { + throw new UnexpectedValueException( + 'responseType is not "id-pkix-ocsp-basic" but is ' . + $this->ocspResponse["responseBytes"]["responseType"] + ); + } + + if (!$this->ocspResponse["responseBytes"]["response"]) { + throw new UnexpectedValueException( + "Could not decode OcspResponse->responseBytes->response" + ); + } + + return new OcspBasicResponse( + $this->ocspResponse["responseBytes"]["response"] + ); + } + + public function getStatus(): string + { + return $this->ocspResponse["responseStatus"]; + } + + public function getRevokeReason(): string + { + return $this->revokeReason; + } + + public function isRevoked() + { + $basicResponse = $this->getBasicResponse(); + $this->validateResponse($basicResponse); + + if (isset($basicResponse->getResponses()[0]["certStatus"]["good"])) { + return false; + } + if (isset($basicResponse->getResponses()[0]["certStatus"]["revoked"])) { + $revokedStatus = $basicResponse->getResponses()[0]["certStatus"][ + "revoked" + ]; + // Check revoke reason + if (isset($revokedStatus["revokedReason"])) { + $this->revokeReason = $revokedStatus["revokedReason"]; + } + return true; + } + return null; + } + + public function validateSignature(): void + { + $basicResponse = $this->getBasicResponse(); + $this->validateResponse($basicResponse); + + $responderCert = $basicResponse->getCertificates()[0]; + // get public key from responder certificate in order to verify signature on response + $publicKey = $responderCert + ->getPublicKey() + ->withHash($basicResponse->getSignatureAlgorithm()); + // verify response data + $encodedTbsResponseData = $basicResponse->getEncodedResponseData(); + $signature = $basicResponse->getSignature(); + + if (!$publicKey->verify($encodedTbsResponseData, $signature)) { + throw new OcspVerifyFailedException( + "OCSP response signature is not valid" + ); + } + } + + public function validateCertificateId(array $requestCertificateId): void + { + $basicResponse = $this->getBasicResponse(); + if ($requestCertificateId != $basicResponse->getCertID()) { + throw new OcspVerifyFailedException( + "OCSP responded with certificate ID that differs from the requested ID" + ); + } + } + + private function validateResponse(OcspBasicResponse $basicResponse): void + { + // Must be one response + if (count($basicResponse->getResponses()) != 1) { + throw new OcspVerifyFailedException( + "OCSP response must contain one response, received " . + count($basicResponse->getResponses()) . + " responses instead" + ); + } + + // At least on cert must exist in responder + if (count($basicResponse->getCertificates()) < 1) { + throw new OcspVerifyFailedException( + "OCSP response must contain the responder certificate, but none was provided" + ); + } + } + + private static function getDecoded(string $encodedBER) { + $decoded = ASN1::decodeBER($encodedBER); + if (!is_array($decoded)) { + throw new OcspResponseDecodeException(); + } + return $decoded; + } +} diff --git a/src/ocsp/certificate/CertificateLoader.php b/src/ocsp/certificate/CertificateLoader.php new file mode 100644 index 0000000..50ea2c3 --- /dev/null +++ b/src/ocsp/certificate/CertificateLoader.php @@ -0,0 +1,129 @@ +loadX509($fileContent); + if (!$loaded) { + throw new OcspCertificateException( + "Certificate decoding from Base64 or parsing failed for " . + $pathToFile + ); + } + $this->certificate = $certificate; + return $this; + } + + /** + * Loads the certificate from string and returns the certificate + * + * @param string certString - certificate as string + * @throws OcspCertificateException when the certificate decoding or parse fails + */ + public function fromString(string $certString) + { + $certificate = new X509(); + $loaded = false; + try { + $loaded = $certificate->loadX509($certString); + } catch (Exception $e) { + } + if (!$loaded) { + throw new OcspCertificateException( + "Certificate decoding from Base64 or parsing failed" + ); + } + $this->certificate = $certificate; + return $this; + } + + public function getIssuerCertificateUrl(): string + { + if (!$this->certificate) { + throw new OcspCertificateException("Certificate not loaded"); + } + + $url = ""; + $opts = $this->certificate->getExtension("id-pe-authorityInfoAccess"); + foreach ($opts as $opt) { + if ($opt["accessMethod"] == "id-ad-caIssuers") { + $url = $opt["accessLocation"]["uniformResourceIdentifier"]; + break; + } + } + return $url; + } + + public function getOcspResponderUrl(): string + { + if (!$this->certificate) { + throw new OcspCertificateException("Certificate not loaded"); + } + + $url = ""; + $opts = $this->certificate->getExtension("id-pe-authorityInfoAccess"); + foreach ($opts as $opt) { + if ($opt["accessMethod"] == "id-ad-ocsp" || $opt["accessMethod"] == "id-pkix-ocsp") { + $url = $opt["accessLocation"]["uniformResourceIdentifier"]; + break; + } + } + return $url; + } + + public function getCert(): X509 + { + if (!$this->certificate) { + throw new OcspCertificateException("Certificate not loaded"); + } + return $this->certificate; + } +} diff --git a/src/ocsp/exceptions/OcspCertificateException.php b/src/ocsp/exceptions/OcspCertificateException.php new file mode 100644 index 0000000..b8c332c --- /dev/null +++ b/src/ocsp/exceptions/OcspCertificateException.php @@ -0,0 +1,38 @@ + ASN1::TYPE_SEQUENCE, + "children" => [ + "tbsResponseData" => [ + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "version" => [ + "type" => ASN1::TYPE_INTEGER, + "constant" => 0, + "optional" => true, + "explicit" => true, + "mapping" => ["v1"], + "default" => "v1", + ], + "responderID" => [ + "type" => ASN1::TYPE_CHOICE, + "children" => [ + "byName" => + [ + "constant" => 1, + "explicit" => true, + ] + Name::MAP, + "byKey" => [ + "constant" => 2, + "explicit" => true, + "type" => ASN1::TYPE_OCTET_STRING, + ], + ], + ], + "producedAt" => ["type" => ASN1::TYPE_GENERALIZED_TIME], + "responses" => [ + "type" => ASN1::TYPE_SEQUENCE, + "min" => 0, + "max" => -1, + "children" => [ + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "certID" => [ + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "hashAlgorithm" => + AlgorithmIdentifier::MAP, + "issuerNameHash" => [ + "type" => ASN1::TYPE_OCTET_STRING, + ], + "issuerKeyHash" => [ + "type" => ASN1::TYPE_OCTET_STRING, + ], + "serialNumber" => + CertificateSerialNumber::MAP, + ], + ], + "certStatus" => [ + "type" => ASN1::TYPE_CHOICE, + "children" => [ + "good" => [ + "constant" => 0, + "implicit" => true, + "type" => ASN1::TYPE_NULL, + ], + "revoked" => [ + "constant" => 1, + "implicit" => true, + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "revokedTime" => [ + "type" => + ASN1::TYPE_GENERALIZED_TIME, + ], + "revokedReason" => + [ + "constant" => 0, + "explicit" => true, + "optional" => true, + ] + CRLReason::MAP, + ], + ], + "unknown" => [ + "constant" => 2, + "implicit" => true, + "type" => ASN1::TYPE_NULL, + ], + ], + ], + "thisUpdate" => [ + "type" => ASN1::TYPE_GENERALIZED_TIME, + ], + "nextUpdate" => [ + "type" => ASN1::TYPE_GENERALIZED_TIME, + "constant" => 0, + "explicit" => true, + "optional" => true, + ], + "singleExtensions" => + [ + "constant" => 1, + "explicit" => true, + "optional" => true, + ] + Extensions::MAP, + ], + ], + ], + "responseExtensions" => + [ + "constant" => 1, + "explicit" => true, + "optional" => true, + ] + Extensions::MAP, + ], + ], + "signatureAlgorithm" => AlgorithmIdentifier::MAP, + "signature" => ["type" => ASN1::TYPE_BIT_STRING], + "certs" => [ + "constant" => 0, + "explicit" => true, + "optional" => true, + "type" => ASN1::TYPE_SEQUENCE, + "min" => 0, + "max" => -1, + "children" => Certificate::MAP, + ], + ], + ]; +} diff --git a/src/ocsp/maps/OcspRequestMap.php b/src/ocsp/maps/OcspRequestMap.php new file mode 100644 index 0000000..02a181e --- /dev/null +++ b/src/ocsp/maps/OcspRequestMap.php @@ -0,0 +1,122 @@ + ASN1::TYPE_SEQUENCE, + "children" => [ + "tbsRequest" => [ + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "version" => [ + "constant" => 0, + "explicit" => true, + "optional" => true, + "mapping" => [0 => "v1"], + "default" => "v1", + "type" => ASN1::TYPE_INTEGER, + ], + "requestList" => [ + "type" => ASN1::TYPE_SEQUENCE, + "min" => 0, + "max" => -1, + "children" => [ + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "reqCert" => [ + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "hashAlgorithm" => + AlgorithmIdentifier::MAP, + "issuerNameHash" => [ + "type" => ASN1::TYPE_OCTET_STRING, + ], + "issuerKeyHash" => [ + "type" => ASN1::TYPE_OCTET_STRING, + ], + "serialNumber" => + CertificateSerialNumber::MAP, + ], + ], + "singleRequestExtensions" => + [ + "constant" => 0, + "explicit" => true, + "optional" => true, + ] + Extensions::MAP, + ], + ], + ], + "requestExtensions" => + [ + "constant" => 2, + "explicit" => true, + "optional" => true, + ] + Extensions::MAP, + "requestorName" => + [ + "constant" => 1, + "optional" => true, + "explicit" => true, + ] + GeneralName::MAP, + ], + ], + "optionalSignature" => [ + "constant" => 0, + "explicit" => true, + "optional" => true, + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "signatureAlgorithm" => AlgorithmIdentifier::MAP, + "signature" => ["type" => ASN1::TYPE_BIT_STRING], + "certs" => [ + "constant" => 0, + "explicit" => true, + "optional" => true, + "type" => ASN1::TYPE_SEQUENCE, + "min" => 0, + "max" => -1, + "children" => Certificate::MAP, + ], + ], + ], + ], + ]; +} diff --git a/src/ocsp/maps/OcspResponseMap.php b/src/ocsp/maps/OcspResponseMap.php new file mode 100644 index 0000000..37ecf88 --- /dev/null +++ b/src/ocsp/maps/OcspResponseMap.php @@ -0,0 +1,63 @@ + ASN1::TYPE_SEQUENCE, + "children" => [ + "responseStatus" => [ + "type" => ASN1::TYPE_ENUMERATED, + "mapping" => [ + 0 => "successful", + 1 => "malformedRequest", + 2 => "internalError", + 3 => "tryLater", + 5 => "sigRequired", + 6 => "unauthorized", + ], + ], + "responseBytes" => [ + "constant" => 0, + "explicit" => true, + "optional" => true, + "type" => ASN1::TYPE_SEQUENCE, + "children" => [ + "responseType" => ["type" => ASN1::TYPE_OBJECT_IDENTIFIER], + "response" => ["type" => ASN1::TYPE_OCTET_STRING], + ], + ], + ], + ]; +} diff --git a/src/util/AsnUtil.php b/src/util/AsnUtil.php index bb6e25c..c7b197a 100644 --- a/src/util/AsnUtil.php +++ b/src/util/AsnUtil.php @@ -25,9 +25,12 @@ namespace web_eid\web_eid_authtoken_validation_php\util; use BadFunctionCallException; +use phpseclib3\File\ASN1; +use phpseclib3\File\ASN1\Maps\SubjectPublicKeyInfo; final class AsnUtil { + public const ID_PKIX_OCSP_NONCE = "1.3.6.1.5.5.7.48.1.2"; public function __construct() { @@ -99,4 +102,59 @@ public static function transcodeSignatureToDER(string $p1363): string // 0x30 b1, then contents. return "\x30" . pack('C', $len) . $asn1; } + + public static function loadOIDs(): void + { + ASN1::loadOIDs([ + "id-pkix-ocsp-nonce" => self::ID_PKIX_OCSP_NONCE, + "id-sha1" => "1.3.14.3.2.26", + "sha256WithRSAEncryption" => "1.2.840.113549.1.1.11", + "qcStatements(3)" => "1.3.6.1.5.5.7.1.3", + "street" => "2.5.4.9", + "id-pkix-ocsp-basic" => "1.3.6.1.5.5.7.48.1.1", + "id-pkix-ocsp" => "1.3.6.1.5.5.7.48.1", + "secp384r1" => "1.3.132.0.34", + "id-pkix-ocsp-archive-cutoff" => "1.3.6.1.5.5.7.48.1.6", + "id-pkix-ocsp-nocheck" => "1.3.6.1.5.5.7.48.1.5", + ]); + } + + public static function extractKeyData(string $publicKey): string + { + $extractedBER = ASN1::extractBER($publicKey); + $decodedBER = ASN1::decodeBER($extractedBER); + $subjectPublicKey = ASN1::asn1map( + $decodedBER[0], + SubjectPublicKeyInfo::MAP + )["subjectPublicKey"]; + // Remove first byte + return pack("c*", ...array_slice(unpack("c*", $subjectPublicKey), 1)); + } + + public static function decodeNonceExtension(array $ocspExtensions): ?string + { + $nonceExtension = current( + array_filter( + $ocspExtensions, + function ($extension) { + return self::ID_PKIX_OCSP_NONCE == ASN1::getOID($extension["extnId"]); + } + ) + ); + if (!$nonceExtension || !isset($nonceExtension["extnValue"])) { + return null; + } + + $nonceValue = $nonceExtension["extnValue"]; + + $decoded = ASN1::decodeBER($nonceValue); + if (is_array($decoded)) { + // The value was DER-encoded, it is required to be an octet string. + $nonceString = ASN1::asn1map($decoded[0], ['type' => ASN1::TYPE_OCTET_STRING]); + return is_string($nonceString) ? $nonceString : null; + } + + // The value was not DER-encoded, return it as-is. + return $nonceValue; + } } diff --git a/src/validator/certvalidators/SubjectCertificateNotRevokedValidator.php b/src/validator/certvalidators/SubjectCertificateNotRevokedValidator.php index 900375a..ca7391d 100644 --- a/src/validator/certvalidators/SubjectCertificateNotRevokedValidator.php +++ b/src/validator/certvalidators/SubjectCertificateNotRevokedValidator.php @@ -30,10 +30,10 @@ use web_eid\web_eid_authtoken_validation_php\validator\ocsp\OcspRequestBuilder; use web_eid\web_eid_authtoken_validation_php\validator\ocsp\OcspResponseValidator; use Throwable; -use web_eid\ocsp_php\Ocsp; -use web_eid\ocsp_php\OcspBasicResponse; -use web_eid\ocsp_php\OcspRequest; -use web_eid\ocsp_php\OcspResponse; +use web_eid\web_eid_authtoken_validation_php\ocsp\Ocsp; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspBasicResponse; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspRequest; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspResponse; use web_eid\web_eid_authtoken_validation_php\exceptions\UserCertificateOCSPCheckFailedException; use web_eid\web_eid_authtoken_validation_php\validator\ocsp\service\OcspService; use Psr\Log\LoggerInterface; diff --git a/src/validator/ocsp/OcspClient.php b/src/validator/ocsp/OcspClient.php index d5a8604..f248ba0 100644 --- a/src/validator/ocsp/OcspClient.php +++ b/src/validator/ocsp/OcspClient.php @@ -25,7 +25,7 @@ namespace web_eid\web_eid_authtoken_validation_php\validator\ocsp; use GuzzleHttp\Psr7\Uri; -use web_eid\ocsp_php\OcspResponse; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspResponse; interface OcspClient { diff --git a/src/validator/ocsp/OcspClientImpl.php b/src/validator/ocsp/OcspClientImpl.php index d31cc34..503a0e5 100644 --- a/src/validator/ocsp/OcspClientImpl.php +++ b/src/validator/ocsp/OcspClientImpl.php @@ -26,7 +26,7 @@ use web_eid\web_eid_authtoken_validation_php\exceptions\UserCertificateOCSPCheckFailedException; use GuzzleHttp\Psr7\Uri; -use web_eid\ocsp_php\OcspResponse; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspResponse; use Psr\Log\LoggerInterface; class OcspClientImpl implements OcspClient diff --git a/src/validator/ocsp/OcspRequestBuilder.php b/src/validator/ocsp/OcspRequestBuilder.php index ef80651..1662f59 100644 --- a/src/validator/ocsp/OcspRequestBuilder.php +++ b/src/validator/ocsp/OcspRequestBuilder.php @@ -26,7 +26,7 @@ namespace web_eid\web_eid_authtoken_validation_php\validator\ocsp; -use web_eid\ocsp_php\OcspRequest; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspRequest; use web_eid\web_eid_authtoken_validation_php\util\SecureRandom; final class OcspRequestBuilder diff --git a/src/validator/ocsp/OcspResponseValidator.php b/src/validator/ocsp/OcspResponseValidator.php index 3c203d1..1ac3fd5 100644 --- a/src/validator/ocsp/OcspResponseValidator.php +++ b/src/validator/ocsp/OcspResponseValidator.php @@ -28,8 +28,8 @@ use DateInterval; use web_eid\web_eid_authtoken_validation_php\exceptions\OCSPCertificateException; use phpseclib3\File\X509; -use web_eid\ocsp_php\OcspBasicResponse; -use web_eid\ocsp_php\OcspResponse; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspBasicResponse; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspResponse; use web_eid\web_eid_authtoken_validation_php\exceptions\UserCertificateOCSPCheckFailedException; use web_eid\web_eid_authtoken_validation_php\exceptions\UserCertificateRevokedException; use web_eid\web_eid_authtoken_validation_php\util\DateAndTime; diff --git a/tests/_resources/invalid.crt b/tests/_resources/invalid.crt new file mode 100644 index 0000000..f0150e8 --- /dev/null +++ b/tests/_resources/invalid.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDfDCCAi+ertIBAgIEMTIzNDBCBgkqhkiG9w0BAQowNaANMAsGCWCGSAFlAwQV +AaEaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgGiAwIBIKMDAgEBMBAxDjAMBgNV +BAoMBXZhbGlkMB4XDTIyMDcwNTA4NDgyMloXDTIzMDgwNTA4NDgyMlowEDEOMAwG +A1UECgwFdmFsaWQwggFXMEIGCSqGSIb3DQEBCjA1oA0wCwYJYIZIAWUDBAIBoRow +GAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQCAaIDAgEgowMCAQEDggEPADCCAQoCggEB +ANPSaz/kyl+bnhFu68YzE6+D3K0bjs8XUn/ByQabqr3TmUcaMoko+FHb99r8MnlD +6mLHegdooIh3vzUY09y+NZfqPbV/ELDjmYCbnObyZDem6Qgz3oOMTzsS4Y+h4VDY +Ie7PCeGJYaDvXVrObEKk6iU3zuPz0UXHkmx+D2smBme/1N4q6XdJ++YlTCiQE/ik +pHYeuQxQiZ0nGB3ZcZ4749zpkymge5khh7hFF/FFy+mBW1QWck3eUwYPaiQW/x5U ++4qqCNeYpOlc0Db27L3KCpXNKvexy9ASgBPl3P2UX23KvaSQEoVXWUGYgna5cq8N +Dn5cyHvnkuwOkPLIVf4kPhECAwEAAaM/MD0wCwYDVR0PBAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFC+3punyNRL3eZuWEGaa05ms9z43MEIGCSqGSIb3 +DQEBCjA1oA0wCwYJYIZIAWUDBAIBoRowGAYJKoZIhvcNAQEIMAsGCWCGSAFlAwQC +AaIDAgEgowMCAQEDggEBALGQMatVnyNNOAgeHcRLHkgrvDNWEh7oJkdCLQwHVV/G +fhH0ErFdfA20OhQITGtng8d8i9HlHcR7W7zwYoj9xp3gLvtEJvQqh4tcJcHOFIpU +9+CC1DdusHuGvnWIzMnVXINP3Zr05ZRkzbvhF8bJPJZ8Gd818VazDvkvf8qQj123 +6TfWJgGZxxYscx2Y8jKWMEq1FoF4IorqYdcpc8Fk1/41IO9cfe5++XgwFnVvOPl9 +nFdv9/Lqqp5iJusdUWH45XPQp8StPwJoYKxAQzdzKVNRdArMNvB4B28psAgdJZyX +rYA0Vmb6MXUvpfWYlQpoxdeuD3JrbOtz1Fsj1pU8niw= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/_resources/revoked.crt b/tests/_resources/revoked.crt new file mode 100644 index 0000000..c638657 --- /dev/null +++ b/tests/_resources/revoked.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFXDCCBESgAwIBAgISA6hJEhJLhiCO5yUdoifWt9NdMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA5MTQwOTMyMDRaFw0x +OTEyMTMwOTMyMDRaMB0xGzAZBgNVBAMTEnJvYnltaWtpLmhvbWVwYy5pdDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJqrOWc0JhlhqRxT0AsQQN0mU2SG +J/vqyhAI8KpLhpmJ4G2ECrcTL86cX/AtFjsLdbd51jfKxNEpq3HUdI8aIM4ZD3c3 +85MLY7Rq4OkKnvEet9krT2VdXrcnj1NBVbcnU2QeBTuARL1HFNv3QhQ/QRA74JEy +IXpG0poE0hlr6BKDBb3k2hYq4HwVEfRZSdL/XvLfNoQh7PuQXjyJclBM1kYwcsKg +xv+jufRmn8C2/q/d6AFOKoDuftTn8dj4nRNDB1sqWD6pYVp1nRN2BDNWno4PCRaJ +1e8ed/STa3TBMB5plWJ8rCqceefhnzUabtDA5fGlaPuzoqt02+ivpIeYLGcCAwEA +AaOCAmcwggJjMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI +KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUVcDE8tdHrwKwQRcuIZcy +4SCHTbowHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUH +AQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5 +cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5 +cHQub3JnLzAdBgNVHREEFjAUghJyb2J5bWlraS5ob21lcGMuaXQwTAYDVR0gBEUw +QzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDov +L2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdwDi +aUuuJujpQAnohhu2O4PUPuf+dIj7pI8okwGd3fHb/gAAAW0vVG44AAAEAwBIMEYC +IQDwbH8oM4RGeDfXAjvAAe9nDw6Fl/F+YytfRT5lPEjudwIhAIUJdflZ7OgA8uWW +ztG+FMxczLy3vs3knJm3EodmA1YqAHUAKTxRllTIOWW6qlD8WAfUt2+/WHopctyk +wwz05UVH9HgAAAFtL1RsQQAABAMARjBEAiBHnEofTid0eDRJVel+Fc/CAri/1f+0 +jpMn4krFnnAPQgIgdE9nFd3yH6y/0dzbH8KBPypRpKUyGfKOdyb16mUA7bowDQYJ +KoZIhvcNAQELBQADggEBADO0FluEUwYXJTo2NK6/9EcPrViW9CoQg76Qp3vPpHiB +g664oZITsEfbJLD2j3sRsNjpfIBmoc6fwKk3Lx7vAJjzCL0KXJbiHhBNMi4rvO87 +wGOjDI9whguO5AleWqrvgyszYXruFXgovZgy/ddT0FK+mS3JPLXw2sKD4vxrZE+q +ejJiCOGmEJQc31PxVNzJbYfeuZt9kwkOwBNDlZ1mlXuUM2BGebA9sfqJqtS8o0fv +RLdRcHpwqhr6pxKSpfeEkrbOppNi07O2AyNW81+1N+ijxLzjuBH6RPNnDQvG4od1 +DE2JhTHj4oQqUlbP8BjShsiLNw3uJJaKMiMhQ3BsLR0= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/_resources/revoked.issuer.crt b/tests/_resources/revoked.issuer.crt new file mode 100644 index 0000000..edb593b --- /dev/null +++ b/tests/_resources/revoked.issuer.crt @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/ocsp/OcspRequestTest.php b/tests/ocsp/OcspRequestTest.php new file mode 100644 index 0000000..f37324a --- /dev/null +++ b/tests/ocsp/OcspRequestTest.php @@ -0,0 +1,104 @@ + [ + 'version' => 'v1', + 'requestList' => [], + 'requestExtensions' => [], + ], + ]; + } + + private function getNonce(): array + { + return [ + 'extnId' => AsnUtil::ID_PKIX_OCSP_NONCE, + 'critical' => false, + 'extnValue' => ASN1::encodeDER("nonce", ['type' => ASN1::TYPE_OCTET_STRING]), + ]; + } + + private function getExpectedRequestWithCertID(): array + { + $result = $this->getRequest(); + $result['tbsRequest']['requestList'][] = [ + 'reqCert' => [1] + ]; + return $result; + } + + private function getExpectedWithNonce(): array + { + $result = $this->getRequest(); + $result['tbsRequest']['requestExtensions'][] = $this->getNonce(); + return $result; + } + + public function testWhenAddCertificateIdSuccess(): void + { + $request = new OcspRequest(); + $request->addCertificateId([1]); + + $reflection = new ReflectionClass(get_class($request)); + $property = $reflection->getProperty('ocspRequest'); + $property->setAccessible(true); + + $this->assertEquals($this->getExpectedRequestWithCertID(), $property->getValue($request)); + } + + public function testWhenAddNonceExtensionSuccess(): void + { + $request = new OcspRequest(); + $request->addNonceExtension("nonce"); + + $reflection = new ReflectionClass(get_class($request)); + $property = $reflection->getProperty('ocspRequest'); + $property->setAccessible(true); + + $this->assertEquals($this->getExpectedWithNonce(), $property->getValue($request)); + } + + public function testWhenGetNonceExtensionSuccess(): void + { + $request = new OcspRequest(); + $request->addNonceExtension("nonce"); + + $this->assertEquals("nonce", $request->getNonceExtension()); + } +} diff --git a/tests/ocsp/OcspResponseTest.php b/tests/ocsp/OcspResponseTest.php new file mode 100644 index 0000000..1c3e108 --- /dev/null +++ b/tests/ocsp/OcspResponseTest.php @@ -0,0 +1,270 @@ +expectException(OcspResponseDecodeException::class); + $this->expectExceptionMessage("Could not decode OCSP response"); + new OcspResponse("1"); + } + + public function testWhenCertificateNotRevoked(): void + { + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + $basicResponse = $response->getBasicResponse(); + + $mockCertificateID = $basicResponse->getResponses()[0]['certID']; + $mockCertificateID['hashAlgorithm']['algorithm'] = ASN1::getOID('id-sha1'); + + $response->validateCertificateId($mockCertificateID); + $response->validateSignature(); + + $this->assertFalse($response->isRevoked()); + $this->assertEquals("successful", $response->getStatus()); + $this->assertEquals("2021-09-17 18:25:24", $basicResponse->getProducedAt()->format("Y-m-d H:i:s")); + $this->assertEquals("2021-09-17 18:25:24", $basicResponse->getThisUpdate()->format("Y-m-d H:i:s")); + $this->assertNull($basicResponse->getNextUpdate()); + $this->assertEquals([71, 255, 175, 201, 24, 17, 119, 14], array_values(unpack('C*', $basicResponse->getNonceExtension()))); + } + + public function testWhenCertificateIsRevoked(): void + { + $response = new OcspResponse(self::getOcspResponseBytesFromResources("ocsp_response_revoked.der")); + $this->assertTrue($response->isRevoked()); + $this->assertEquals("unspecified", $response->getRevokeReason()); + } + + public function testWhenCertificateRevokeStatusIsUnknown(): void + { + $response = new OcspResponse(self::getOcspResponseBytesFromResources("ocsp_response_unknown.der")); + $this->assertNull($response->isRevoked()); + } + + public function testWhenTwoResponsesThenThrows(): void + { + $response = new OcspResponse(self::getOcspResponseBytesFromResources("ocsp_response_with_2_responses.der")); + $basicResponse = $response->getBasicResponse(); + + $mockCertificateID = $basicResponse->getResponses()[0]['certID']; + $mockCertificateID['hashAlgorithm']['algorithm'] = ASN1::getOID('id-sha1'); + + $this->expectException(OcspVerifyFailedException::class); + $this->expectExceptionMessage("OCSP response must contain one response, received 2 responses instead"); + + $response->isRevoked(); + } + + public function testWhenCertificateIdsDoNotMatchThenThrows(): void + { + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + $basicResponse = $response->getBasicResponse(); + + $mockCertificateID = $basicResponse->getResponses()[0]['certID']; + $mockCertificateID['issuerNameHash'] = "1234"; + $mockCertificateID['hashAlgorithm']['algorithm'] = ASN1::getOID('id-sha1'); + + $this->expectException(OcspVerifyFailedException::class); + $this->expectExceptionMessage("OCSP responded with certificate ID that differs from the requested ID"); + + $response->validateCertificateId($mockCertificateID); + } + + public function testWhenResponseTypeNotBasicResponseThrows(): void + { + + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('responseType is not "id-pkix-ocsp-basic" but is responseType'); + + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + + $reflection = new ReflectionClass(get_class($response)); + $property = $reflection->getProperty('ocspResponse'); + $property->setAccessible(true); + $mockResponse = $property->getValue($response); + $mockResponse['responseBytes']['responseType'] = "responseType"; + $property->setValue($response, $mockResponse); + + $response->getBasicResponse(); + } + + public function testWhenMissingResponseThrows(): void + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Could not decode OcspResponse->responseBytes->response'); + + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + + $reflection = new ReflectionClass(get_class($response)); + $property = $reflection->getProperty('ocspResponse'); + $property->setAccessible(true); + $mockResponse = $property->getValue($response); + $mockResponse['responseBytes']['response'] = null; + $property->setValue($response, $mockResponse); + + $response->getBasicResponse(); + } + + public function testWhenNoCertificatesInResponseThrows(): void + { + $this->expectException(OcspVerifyFailedException::class); + $this->expectExceptionMessage('OCSP response must contain the responder certificate, but none was provided'); + + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + + $basicResponse = $response->getBasicResponse(); + $mockCertificateID = $basicResponse->getResponses()[0]['certID']; + $mockCertificateID['hashAlgorithm']['algorithm'] = ASN1::getOID('id-sha1'); + + $reflection = new ReflectionClass(get_class($response)); + $property = $reflection->getProperty('ocspResponse'); + $property->setAccessible(true); + $mockResponse = $property->getValue($response); + $mockResponse['responseBytes']['response']['certs'] = []; + + $property->setValue($response, $mockResponse); + + $response->isRevoked(); + } + + public function testWhenResponseSignatureIsNotValidThrows(): void + { + $this->expectException(OcspVerifyFailedException::class); + $this->expectExceptionMessage('OCSP response signature is not valid'); + + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + + $basicResponse = $response->getBasicResponse(); + $mockCertificateID = $basicResponse->getResponses()[0]['certID']; + $mockCertificateID['hashAlgorithm']['algorithm'] = ASN1::getOID('id-sha1'); + + $reflection = new ReflectionClass(get_class($response)); + $property = $reflection->getProperty('ocspResponse'); + $property->setAccessible(true); + $mockResponse = $property->getValue($response); + $mockResponse['responseBytes']['response']['signature'] = "somesignature"; + + $property->setValue($response, $mockResponse); + + $response->validateSignature(); + } + + public function testWhenSignatureAlgorithmIsSha3(): void + { + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + + $reflection = new ReflectionClass(get_class($response)); + $property = $reflection->getProperty('ocspResponse'); + $property->setAccessible(true); + $mockResponse = $property->getValue($response); + $mockResponse['responseBytes']['response']['signatureAlgorithm']['algorithm'] = "NNNsha3-256NNN"; + $property->setValue($response, $mockResponse); + + $basicResponse = $response->getBasicResponse(); + + $this->assertEquals("sha3-256", $basicResponse->getSignatureAlgorithm()); + } + + public function testWhenSignatureAlgorithmIsNotSupportedThenThrows(): void + { + + $this->expectException(OcspCertificateException::class); + $this->expectExceptionMessage('Signature algorithm somealgo not implemented'); + + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + + $reflection = new ReflectionClass(get_class($response)); + $property = $reflection->getProperty('ocspResponse'); + $property->setAccessible(true); + $mockResponse = $property->getValue($response); + $mockResponse['responseBytes']['response']['signatureAlgorithm']['algorithm'] = "someAlgo"; + $property->setValue($response, $mockResponse); + + $basicResponse = $response->getBasicResponse(); + $basicResponse->getSignatureAlgorithm(); + } + + public function testWhenNextUpdateInResponse(): void + { + + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + + $reflection = new ReflectionClass(get_class($response)); + $property = $reflection->getProperty('ocspResponse'); + $property->setAccessible(true); + $mockResponse = $property->getValue($response); + $mockResponse['responseBytes']['response']['tbsResponseData']['responses'][0]['nextUpdate'] = 'Fri, 17 Sep 2021 18:25:24 +0000'; + $property->setValue($response, $mockResponse); + + $basicResponse = $response->getBasicResponse(); + + $this->assertEquals(new DateTime("Fri, 17 Sep 2021 18:25:24 +0000"), $basicResponse->getNextUpdate()); + } + + public function testWhenNonceExtensionDoesNotExistNullShouldReturned(): void + { + + $response = new OcspResponse(self::getOcspResponseBytesFromResources()); + + $reflection = new ReflectionClass(get_class($response)); + $property = $reflection->getProperty('ocspResponse'); + $property->setAccessible(true); + $mockResponse = $property->getValue($response); + $mockResponse['responseBytes']['response']['tbsResponseData']['responseExtensions'][0]['extnId'] = "id-pkix-ocsp-nonce1"; + $property->setValue($response, $mockResponse); + + $basicResponse = $response->getBasicResponse(); + + $this->assertNull($basicResponse->getNonceExtension()); + } +} diff --git a/tests/ocsp/OcspTest.php b/tests/ocsp/OcspTest.php new file mode 100644 index 0000000..778bf43 --- /dev/null +++ b/tests/ocsp/OcspTest.php @@ -0,0 +1,60 @@ +generateCertificateId((new CertificateLoader)->fromFile(__DIR__ . '/../_resources/revoked.crt')->getCert(), (new CertificateLoader)->fromFile(__DIR__ . '/../_resources/revoked.issuer.crt')->getCert()); + + $this->assertEquals("1.3.14.3.2.26", $result['hashAlgorithm']['algorithm']); + $this->assertEquals([126, 230, 106, 231, 114, 154, 179, 252, 248, 162, 32, 100, 108, 22, 161, 45, 96, 113, 8, 93], array_values(unpack('C*', $result['issuerNameHash']))); + $this->assertEquals([168, 74, 106, 99, 4, 125, 221, 186, 230, 209, 57, 183, 166, 69, 101, 239, 243, 168, 236, 161], array_values(unpack('C*', $result['issuerKeyHash']))); + } + + public function testWhenMissingSerialNumberInSubjectCertificateThrow(): void + { + + $this->expectException(OcspCertificateException::class); + $this->expectExceptionMessage("Serial number of subject certificate does not exist"); + + $subject = new X509(); + $subject->setDNProp('id-at-organizationName', 'no serialnumber cert'); + + $issuer = new X509(); + $issuer->setDN($subject->getDN()); + + (new Ocsp)->generateCertificateId($subject, $issuer); + } +} diff --git a/tests/ocsp/certificate/CertificateLoaderTest.php b/tests/ocsp/certificate/CertificateLoaderTest.php new file mode 100644 index 0000000..c2b7b16 --- /dev/null +++ b/tests/ocsp/certificate/CertificateLoaderTest.php @@ -0,0 +1,98 @@ +fromFile(__DIR__.'/../../_resources/revoked.crt'); + + $this->assertEquals("318601422914101149693420017798940712227677", $loader->getCert()->getCurrentCert()['tbsCertificate']['serialNumber']); + $this->assertEquals("http://cert.int-x3.letsencrypt.org/", $loader->getIssuerCertificateUrl()); + $this->assertEquals("http://ocsp.int-x3.letsencrypt.org", $loader->getOcspResponderUrl()); + } + + public function testWhenCertificateLoaderFromStringSuccess(): void + { + $certData = file_get_contents(__DIR__.'/../../_resources/revoked.crt'); + $certificate = (new CertificateLoader)->fromString($certData)->getCert(); + $this->assertEquals("318601422914101149693420017798940712227677", $certificate->getCurrentCert()['tbsCertificate']['serialNumber']); + } + + public function testWhenCertificateFileDoNotExistThrows(): void + { + $this->expectException(OcspCertificateException::class); + $this->expectExceptionMessage('Certificate file not found or not readable: '.__DIR__.'/../../_resources/somecert.crt'); + + (new CertificateLoader)->fromFile(__DIR__.'/../../_resources/somecert.crt'); + + } + + public function testWhenCertificateIsInvalidThrows(): void + { + $this->expectException(OcspCertificateException::class); + $this->expectExceptionMessage('Certificate decoding from Base64 or parsing failed for '.__DIR__.'/../../_resources/invalid.crt'); + + (new CertificateLoader)->fromFile(__DIR__.'/../../_resources/invalid.crt'); + } + + public function testWhenCertificateStringIsNotValidThrows(): void + { + $this->expectException(OcspCertificateException::class); + $this->expectExceptionMessage('Certificate decoding from Base64 or parsing failed'); + + (new CertificateLoader)->fromString("certsource"); + } + + public function testWhenCertificateIsNotLoadedOnIssuerCertificateUrlThrows(): void + { + $this->expectException(OcspCertificateException::class); + $this->expectExceptionMessage('Certificate not loaded'); + + (new CertificateLoader)->getIssuerCertificateUrl(); + } + + public function testWhenCertificateIsNotLoadedOnOcspResponderUrlThrows(): void + { + $this->expectException(OcspCertificateException::class); + $this->expectExceptionMessage('Certificate not loaded'); + + (new CertificateLoader)->getOcspResponderUrl(); + } + + public function testWhenCertificateIsNotLoadedOnGetCertThrows(): void + { + $this->expectException(OcspCertificateException::class); + $this->expectExceptionMessage('Certificate not loaded'); + + (new CertificateLoader)->getCert(); + } + +} diff --git a/tests/validator/certvalidators/SubjectCertificateNotRevokedValidatorTest.php b/tests/validator/certvalidators/SubjectCertificateNotRevokedValidatorTest.php index 02bbd80..d4eebe0 100644 --- a/tests/validator/certvalidators/SubjectCertificateNotRevokedValidatorTest.php +++ b/tests/validator/certvalidators/SubjectCertificateNotRevokedValidatorTest.php @@ -28,8 +28,8 @@ use phpseclib3\File\X509; use PHPUnit\Framework\TestCase; use ReflectionProperty; -use web_eid\ocsp_php\OcspResponse; -use web_eid\ocsp_php\util\AsnUtil; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspResponse; +use web_eid\web_eid_authtoken_validation_php\util\AsnUtil; use web_eid\web_eid_authtoken_validation_php\exceptions\UserCertificateOCSPCheckFailedException; use web_eid\web_eid_authtoken_validation_php\testutil\Certificates; use web_eid\web_eid_authtoken_validation_php\testutil\Dates; diff --git a/tests/validator/ocsp/OcspResponseValidatorTest.php b/tests/validator/ocsp/OcspResponseValidatorTest.php index d0d650c..913a1f7 100644 --- a/tests/validator/ocsp/OcspResponseValidatorTest.php +++ b/tests/validator/ocsp/OcspResponseValidatorTest.php @@ -29,7 +29,7 @@ use DateTime; use DateInterval; use PHPUnit\Framework\TestCase; -use web_eid\ocsp_php\OcspBasicResponse; +use web_eid\web_eid_authtoken_validation_php\ocsp\OcspBasicResponse; use web_eid\web_eid_authtoken_validation_php\util\DateAndTime; use web_eid\web_eid_authtoken_validation_php\util\DefaultClock; use web_eid\web_eid_authtoken_validation_php\validator\AuthTokenValidationConfiguration;