|
24 | 24 |
|
25 | 25 | namespace web_eid\web_eid_authtoken_validation_php\validator\certvalidators; |
26 | 26 |
|
27 | | -use lyquidity\OCSP\CertificateInfo; |
28 | | -use lyquidity\OCSP\CertificateLoader; |
29 | | -use lyquidity\OCSP\Response; |
30 | 27 | use phpseclib3\File\X509; |
31 | 28 | use web_eid\web_eid_authtoken_validation_php\util\Log; |
32 | 29 | use web_eid\web_eid_authtoken_validation_php\validator\ocsp\OcspClient; |
33 | 30 | use web_eid\web_eid_authtoken_validation_php\validator\ocsp\OcspServiceProvider; |
34 | 31 | use web_eid\web_eid_authtoken_validation_php\validator\ocsp\OcspRequestBuilder; |
35 | 32 | use web_eid\web_eid_authtoken_validation_php\validator\ocsp\OcspResponseValidator; |
36 | 33 | use Throwable; |
| 34 | +use web_eid\ocsp_php\Ocsp; |
| 35 | +use web_eid\ocsp_php\OcspBasicResponse; |
| 36 | +use web_eid\ocsp_php\OcspRequest; |
| 37 | +use web_eid\ocsp_php\OcspResponse; |
37 | 38 | use web_eid\web_eid_authtoken_validation_php\exceptions\UserCertificateOCSPCheckFailedException; |
| 39 | +use web_eid\web_eid_authtoken_validation_php\validator\ocsp\service\OcspService; |
38 | 40 |
|
39 | 41 | final class SubjectCertificateNotRevokedValidator implements SubjectCertificateValidator |
40 | 42 | { |
@@ -62,45 +64,100 @@ public function validate(X509 $subjectCertificate): void |
62 | 64 | $this->logger->debug("Disabling OCSP nonce extension"); |
63 | 65 | } |
64 | 66 |
|
65 | | - $certificateLoader = new CertificateLoader(); |
| 67 | + $certificateId = (new Ocsp())->generateCertificateId($subjectCertificate, $this->trustValidator->getSubjectCertificateIssuerCertificate()); |
| 68 | + $request = (new OcspRequestBuilder())->withCertificateId($certificateId)->enableOcspNonce($ocspService->doesSupportNonce())->build(); |
66 | 69 |
|
67 | | - // PEM encoded |
68 | | - $certPem = $subjectCertificate->saveX509($subjectCertificate->getCurrentCert(), X509::FORMAT_PEM); |
69 | | - $iss = $this->trustValidator->getSubjectCertificateIssuerCertificate(); |
70 | | - $issuerPem = $iss->saveX509($iss->getCurrentCert(), X509::FORMAT_PEM); |
71 | | - |
72 | | - $certificate = $certificateLoader->fromString($certPem); |
73 | | - $issuerCertificate = $certificateLoader->fromString($issuerPem); |
74 | | - |
75 | | - // Extract the relevant data from the two certificates |
76 | | - $certificateInfo = new CertificateInfo(); |
77 | | - $requestInfo = $certificateInfo->extractRequestInfo($certificate, $issuerCertificate); |
78 | | - |
79 | | - $request = (new OcspRequestBuilder()) |
80 | | - ->withCertificateId($requestInfo) |
81 | | - ->enableOcspNonce($ocspService->doesSupportNonce()) |
82 | | - ->build(); |
83 | | - |
84 | | - |
85 | 70 | $this->logger->debug("Sending OCSP request"); |
86 | 71 |
|
87 | | - // TODO |
88 | | - // Replace faulty OCSP library |
89 | | - return; |
90 | | - |
91 | | - $response = $this->ocspClient->request($ocspService->getAccessLocation(), $request); |
92 | | - $this->verifyOcspResponse($response); |
93 | | - //$ocspResponderUrl = $certificateLoader->extractOcspResponderUrl($certificate); |
| 72 | + $response = $this->ocspClient->request($ocspService->getAccessLocation(), $request->getEncodeDer()); |
| 73 | + |
| 74 | + // When status is not successful |
| 75 | + if ($response->getStatus() != "successful") { |
| 76 | + throw new UserCertificateOCSPCheckFailedException("OCSP response status: " . $response->getStatus()); |
| 77 | + } |
| 78 | + |
| 79 | + $this->verifyOcspResponse($response, $ocspService, $certificateId); |
| 80 | + |
| 81 | + if ($ocspService->doesSupportNonce()) { |
| 82 | + $this->checkNonce($request, $response->getBasicResponse()); |
| 83 | + } |
94 | 84 |
|
95 | 85 | } catch (Throwable $e) { |
96 | 86 | throw new UserCertificateOCSPCheckFailedException("Exception: " . $e->getMessage(), $e); |
97 | 87 | } |
98 | 88 |
|
99 | 89 | } |
100 | 90 |
|
101 | | - private function verifyOcspResponse(Response $response): void |
| 91 | + // Todo, check ocspService |
| 92 | + private function verifyOcspResponse(OcspResponse $response, OcspService $ocspService, array $requestCertificateId): void |
102 | 93 | { |
| 94 | + $basicResponse = $response->getBasicResponse(); |
| 95 | + |
| 96 | + // The verification algorithm follows RFC 2560, https://www.ietf.org/rfc/rfc2560.txt. |
| 97 | + // |
| 98 | + // 3.2. Signed Response Acceptance Requirements |
| 99 | + // Prior to accepting a signed response for a particular certificate as |
| 100 | + // valid, OCSP clients SHALL confirm that: |
| 101 | + // |
| 102 | + // 1. The certificate identified in a received response corresponds to |
| 103 | + // the certificate that was identified in the corresponding request. |
| 104 | + |
| 105 | + // As we sent the request for only a single certificate, we expect only a single response. |
| 106 | + if (count($basicResponse->getResponses()) != 1) { |
| 107 | + throw new UserCertificateOCSPCheckFailedException("OCSP response must contain one response, received " . count($basicResponse->getResponses()) . " responses instead"); |
| 108 | + } |
| 109 | + |
| 110 | + if ($requestCertificateId != $basicResponse->getCertID()) { |
| 111 | + throw new UserCertificateOCSPCheckFailedException("OCSP responded with certificate ID that differs from the requested ID"); |
| 112 | + } |
| 113 | + |
| 114 | + // 2. The signature on the response is valid. |
| 115 | + |
| 116 | + // We assume that the responder includes its certificate in the certs field of the response |
| 117 | + // that helps us to verify it. According to RFC 2560 this field is optional, but including it |
| 118 | + // is standard practice. |
| 119 | + |
| 120 | + if (count($basicResponse->getCertificates()) < 1) { |
| 121 | + throw new UserCertificateOCSPCheckFailedException("OCSP response must contain the responder certificate, but none was provided"); |
| 122 | + } |
| 123 | + |
| 124 | + // The first certificate is the responder certificate, other certificates, if given, are the certificate's chain. |
| 125 | + $responderCert = $basicResponse->getCertificates()[0]; |
| 126 | + |
| 127 | + OcspResponseValidator::validateResponseSignature($basicResponse, $responderCert); |
| 128 | + |
| 129 | + // 3. The identity of the signer matches the intended recipient of the |
| 130 | + // request. |
| 131 | + // |
| 132 | + // 4. The signer is currently authorized to provide a response for the |
| 133 | + // certificate in question. |
| 134 | + |
| 135 | + $producedAt = $basicResponse->getProducedAt(); |
| 136 | + $ocspService->validateResponderCertificate($responderCert, $producedAt); |
| 137 | + |
| 138 | + // 5. The time at which the status being indicated is known to be |
| 139 | + // correct (thisUpdate) is sufficiently recent. |
| 140 | + // |
| 141 | + // 6. When available, the time at or before which newer information will |
| 142 | + // be available about the status of the certificate (nextUpdate) is |
| 143 | + // greater than the current time. |
| 144 | + |
| 145 | + OcspResponseValidator::validateCertificateStatusUpdateTime($basicResponse, $producedAt); |
| 146 | + |
| 147 | + // Now we can accept the signed response as valid and validate the certificate status. |
103 | 148 | OcspResponseValidator::validateSubjectCertificateStatus($response); |
| 149 | + $this->logger->debug("OCSP check result is GOOD"); |
| 150 | + |
| 151 | + } |
| 152 | + |
| 153 | + private static function checkNonce(OcspRequest $request, OcspBasicResponse $basicResponse): void |
| 154 | + { |
| 155 | + $requestNonce = $request->getNonceExtension(); |
| 156 | + $responseNonce = $basicResponse->getNonceExtension(); |
| 157 | + |
| 158 | + if ($requestNonce != $responseNonce) { |
| 159 | + throw new UserCertificateOCSPCheckFailedException("OCSP request and response nonces differ, possible replay attack"); |
| 160 | + } |
104 | 161 | } |
105 | 162 |
|
106 | 163 | } |
0 commit comments