mlocati /
ocsp
| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace Ocsp; |
||||
| 4 | |||||
| 5 | use Ocsp\Asn1\Der\Decoder as DerDecoder; |
||||
| 6 | use Ocsp\Asn1\Der\Encoder as DerEncoder; |
||||
| 7 | use Ocsp\Asn1\Element; |
||||
| 8 | use Ocsp\Asn1\Element\AbstractList; |
||||
| 9 | use Ocsp\Asn1\Element\GeneralizedTime; |
||||
| 10 | use Ocsp\Asn1\Element\Integer; |
||||
| 11 | use Ocsp\Asn1\Element\ObjectIdentifier; |
||||
| 12 | use Ocsp\Asn1\Element\OctetString; |
||||
| 13 | use Ocsp\Asn1\Element\RawPrimitive; |
||||
| 14 | use Ocsp\Asn1\Element\Sequence; |
||||
| 15 | use Ocsp\Asn1\Tag; |
||||
| 16 | use Ocsp\Asn1\TaggableElement; |
||||
| 17 | use Ocsp\Asn1\UniversalTagID; |
||||
| 18 | use Ocsp\Exception\Asn1DecodingException; |
||||
| 19 | use Ocsp\Exception\ResponseException; |
||||
| 20 | |||||
| 21 | class Ocsp |
||||
| 22 | { |
||||
| 23 | /** |
||||
| 24 | * The media type (Content-Type header) to be used when sending the request to the OCSP Responder URL. |
||||
| 25 | * |
||||
| 26 | * @var string |
||||
| 27 | */ |
||||
| 28 | const OCSP_REQUEST_MEDIATYPE = 'application/ocsp-request'; |
||||
| 29 | |||||
| 30 | /** |
||||
| 31 | * The media type (Content-Type header) that should be included in responses from the OCSP Responder URL. |
||||
| 32 | * |
||||
| 33 | * @var string |
||||
| 34 | */ |
||||
| 35 | const OCSP_RESPONSE_MEDIATYPE = 'application/ocsp-response'; |
||||
| 36 | |||||
| 37 | /** |
||||
| 38 | * The decoder to be used to decode DER-encoded data. |
||||
| 39 | * |
||||
| 40 | * @var \Ocsp\Asn1\Der\Decoder |
||||
| 41 | */ |
||||
| 42 | private $derDecoder; |
||||
| 43 | |||||
| 44 | /** |
||||
| 45 | * The encoder to be used to encode data to DER. |
||||
| 46 | * |
||||
| 47 | * @var \Ocsp\Asn1\Der\Encoder |
||||
| 48 | */ |
||||
| 49 | private $derEncoder; |
||||
| 50 | |||||
| 51 | /** |
||||
| 52 | * Initialize the instance. |
||||
| 53 | */ |
||||
| 54 | 2 | public function __construct() |
|||
| 55 | { |
||||
| 56 | 2 | $this->derDecoder = new DerDecoder(); |
|||
| 57 | 2 | $this->derEncoder = new DerEncoder(); |
|||
| 58 | } |
||||
| 59 | |||||
| 60 | /** |
||||
| 61 | * Build the raw body to be sent to the OCSP Responder URL with one request. |
||||
| 62 | * |
||||
| 63 | * @param \Ocsp\Request $request request to be included in the body |
||||
| 64 | * |
||||
| 65 | * @return string |
||||
| 66 | * |
||||
| 67 | * @see https://tools.ietf.org/html/rfc6960#section-4.1.1 for OCSPRequest |
||||
| 68 | */ |
||||
| 69 | 2 | public function buildOcspRequestBodySingle(Request $request) |
|||
| 70 | { |
||||
| 71 | 2 | return $this->buildOcspRequestBody(RequestList::create([$request])); |
|||
| 72 | } |
||||
| 73 | |||||
| 74 | /** |
||||
| 75 | * Build the raw body to be sent to the OCSP Responder URL with a variable number of requests. |
||||
| 76 | * |
||||
| 77 | * @param \Ocsp\RequestList $requests the list of requests to be included in the body |
||||
| 78 | * |
||||
| 79 | * @return string |
||||
| 80 | * |
||||
| 81 | * @see https://tools.ietf.org/html/rfc6960#section-4.1.1 for OCSPRequest |
||||
| 82 | */ |
||||
| 83 | 2 | public function buildOcspRequestBody(RequestList $requests) |
|||
| 84 | { |
||||
| 85 | 2 | $hashAlgorithm = Sequence::create([ |
|||
| 86 | // OBJECT IDENTIFIER [algorithm] |
||||
| 87 | 2 | ObjectIdentifier::create('1.3.14.3.2.26'), // SHA1 |
|||
| 88 | 2 | ]); |
|||
| 89 | 2 | $requestList = new Sequence(); |
|||
| 90 | 2 | foreach ($requests->getRequests() as $request) { |
|||
| 91 | 2 | $requestList->addElement( |
|||
| 92 | // Request |
||||
| 93 | 2 | Sequence::create([ |
|||
| 94 | // CertID [reqCert] |
||||
| 95 | 2 | Sequence::create([ |
|||
| 96 | // AlgorithmIdentifier [hashAlgorithm] |
||||
| 97 | 2 | $hashAlgorithm, |
|||
| 98 | // OCTET STRING [issuerNameHash] |
||||
| 99 | 2 | OctetString::create(sha1($request->getIssuerNameDer(), true)), |
|||
| 100 | // OCTET STRING [issuerKeyHash] |
||||
| 101 | 2 | OctetString::create(sha1($request->getIssuerPublicKeyBytes(), true)), |
|||
| 102 | // CertificateSerialNumber [serialNumber] |
||||
| 103 | 2 | Integer::create($request->getCertificateSerialNumber()), |
|||
| 104 | 2 | ]), |
|||
| 105 | 2 | ]) |
|||
| 106 | 2 | ); |
|||
| 107 | } |
||||
| 108 | |||||
| 109 | 2 | return $this->derEncoder->encodeElement( |
|||
| 110 | // OCSPRequest |
||||
| 111 | 2 | Sequence::create([ |
|||
| 112 | // TBSRequest [tbsRequest] |
||||
| 113 | 2 | Sequence::create([ |
|||
| 114 | 2 | $requestList, |
|||
| 115 | 2 | ]), |
|||
| 116 | 2 | ]) |
|||
| 117 | 2 | ); |
|||
| 118 | } |
||||
| 119 | |||||
| 120 | /** |
||||
| 121 | * Parse the response received from the OCSP Responder when you expect just one certificate revocation status. |
||||
| 122 | * |
||||
| 123 | * @param string $rawResponseBody the raw response from the responder |
||||
| 124 | * |
||||
| 125 | * @throws \Ocsp\Exception\Asn1DecodingException if $rawBody is not a valid response from the OCSP responder |
||||
| 126 | * @throws \Ocsp\Exception\ResponseException:: if the request was not successfull |
||||
| 127 | * |
||||
| 128 | * @return \Ocsp\Response |
||||
| 129 | * |
||||
| 130 | * @see https://tools.ietf.org/html/rfc6960#section-4.2.1 |
||||
| 131 | */ |
||||
| 132 | 2 | public function decodeOcspResponseSingle($rawResponseBody) |
|||
| 133 | { |
||||
| 134 | 2 | $responses = $this->decodeOcspResponse($rawResponseBody)->getResponses(); |
|||
| 135 | 2 | if (count($responses) !== 1) { |
|||
| 136 | throw ResponseException\MultipleResponsesException::create(); |
||||
| 137 | } |
||||
| 138 | |||||
| 139 | 2 | return $responses[0]; |
|||
| 140 | } |
||||
| 141 | |||||
| 142 | /** |
||||
| 143 | * Parse the response received from the OCSP Responder when you expect a variable number of certificate revocation statuses. |
||||
| 144 | * |
||||
| 145 | * @param string $rawResponseBody the raw response from the responder |
||||
| 146 | * |
||||
| 147 | * @throws \Ocsp\Exception\Asn1DecodingException if $rawBody is not a valid response from the OCSP responder |
||||
| 148 | * @throws \Ocsp\Exception\ResponseException:: if the request was not successfull |
||||
| 149 | * |
||||
| 150 | * @return \Ocsp\ResponseList |
||||
| 151 | * |
||||
| 152 | * @see https://tools.ietf.org/html/rfc6960#section-4.2.1 |
||||
| 153 | */ |
||||
| 154 | 2 | public function decodeOcspResponse($rawResponseBody) |
|||
| 155 | { |
||||
| 156 | 2 | $ocspResponse = $this->derDecoder->decodeElement($rawResponseBody); |
|||
| 157 | 2 | if (!$ocspResponse instanceof Sequence) { |
|||
| 158 | throw Asn1DecodingException::create('Invalid response type'); |
||||
| 159 | } |
||||
| 160 | 2 | $this->checkResponseStatus($ocspResponse); |
|||
| 161 | 2 | $responseBytes = $ocspResponse->getFirstChildOfType(0, Element::CLASS_CONTEXTSPECIFIC, Tag::ENVIRONMENT_EXPLICIT); |
|||
| 162 | 2 | if (!$responseBytes instanceof Sequence) { |
|||
| 163 | throw ResponseException\MissingResponseBytesException::create(); |
||||
| 164 | } |
||||
| 165 | |||||
| 166 | 2 | return $this->decodeResponseBytes($responseBytes); |
|||
| 167 | } |
||||
| 168 | |||||
| 169 | /** |
||||
| 170 | * Check the OCSP response status. |
||||
| 171 | * |
||||
| 172 | * @param \Ocsp\Asn1\Element\Sequence $ocspResponse |
||||
| 173 | * |
||||
| 174 | * @throws \Ocsp\Exception\ResponseException:: if the request was not successfull |
||||
| 175 | * @throws \Ocsp\Exception\Asn1DecodingException if the response contains invalid data |
||||
| 176 | * |
||||
| 177 | * @see https://tools.ietf.org/html/rfc6960#section-4.2.1 |
||||
| 178 | */ |
||||
| 179 | 2 | protected function checkResponseStatus(Sequence $ocspResponse) |
|||
| 180 | { |
||||
| 181 | 2 | $responseStatus = $ocspResponse->getFirstChildOfType(UniversalTagID::ENUMERATED); |
|||
| 182 | 2 | if ($responseStatus === null) { |
|||
| 183 | throw Asn1DecodingException::create('Invalid response type'); |
||||
| 184 | } |
||||
| 185 | 2 | switch ($responseStatus->getRawEncodedValue()) { |
|||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 186 | 2 | case "\x00": // successful (Response has valid confirmations) |
|||
| 187 | 2 | break; |
|||
| 188 | case "\x01": // malformedRequest (Illegal confirmation request) |
||||
| 189 | throw ResponseException\MalformedRequestException::create(); |
||||
| 190 | case "\x02": // internalError (Internal error in issuer) |
||||
| 191 | throw ResponseException\InternalErrorException::create(); |
||||
| 192 | case "\x03": // tryLater (Try again later) |
||||
| 193 | throw ResponseException\TryLaterException::create(); |
||||
| 194 | case "\x05": // sigRequired (Must sign the request) |
||||
| 195 | throw ResponseException\SigRequiredException::create(); |
||||
| 196 | case "\x06": // unauthorized (Request unauthorized) |
||||
| 197 | throw ResponseException\UnauthorizedException::create(); |
||||
| 198 | default: |
||||
| 199 | throw Asn1DecodingException::create('Invalid response data'); |
||||
| 200 | } |
||||
| 201 | } |
||||
| 202 | |||||
| 203 | /** |
||||
| 204 | * Parse "responseBytes" element of a response received from the OCSP Responder. |
||||
| 205 | * |
||||
| 206 | * @param \Ocsp\Asn1\Element\Sequence $responseBytes |
||||
| 207 | * |
||||
| 208 | * @throws \Ocsp\Exception\Asn1DecodingException |
||||
| 209 | * @throws \Ocsp\Exception\ResponseException |
||||
| 210 | * |
||||
| 211 | * @see https://tools.ietf.org/html/rfc6960#section-4.2.1 |
||||
| 212 | */ |
||||
| 213 | 2 | protected function decodeResponseBytes(Sequence $responseBytes) |
|||
| 214 | { |
||||
| 215 | 2 | $responseType = $responseBytes->getFirstChildOfType(UniversalTagID::OBJECT_IDENTIFIER); |
|||
| 216 | 2 | $response = $responseBytes->getFirstChildOfType(UniversalTagID::OCTET_STRING); |
|||
| 217 | 2 | if ($responseType !== null && $response !== null) { |
|||
| 218 | 2 | switch ($responseType->getIdentifier()) { |
|||
|
0 ignored issues
–
show
The method
getIdentifier() does not exist on Ocsp\Asn1\Element. It seems like you code against a sub-type of Ocsp\Asn1\Element such as Ocsp\Asn1\Element\ObjectIdentifier.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 219 | 2 | case '1.3.6.1.5.5.7.48.1.1': |
|||
| 220 | 2 | return $this->decodeBasicResponse($response->getValue()); |
|||
|
0 ignored issues
–
show
The method
getValue() does not exist on Ocsp\Asn1\Element. It seems like you code against a sub-type of Ocsp\Asn1\Element such as Ocsp\Asn1\Element\PrintableString or Ocsp\Asn1\Element\Integer or Ocsp\Asn1\Element\GeneralizedTime or Ocsp\Asn1\Element\OctetString.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 221 | } |
||||
| 222 | } |
||||
| 223 | |||||
| 224 | throw ResponseException\MissingResponseBytesException::create(); |
||||
| 225 | } |
||||
| 226 | |||||
| 227 | /** |
||||
| 228 | * Parse the "responseBytes" element of a response received from the OCSP Responder. |
||||
| 229 | * |
||||
| 230 | * @param string $responseBytes |
||||
| 231 | * |
||||
| 232 | * @throws \Ocsp\Exception\Asn1DecodingException |
||||
| 233 | * @throws \Ocsp\Exception\ResponseException |
||||
| 234 | * |
||||
| 235 | * @see https://tools.ietf.org/html/rfc6960#section-4.2.1 |
||||
| 236 | * |
||||
| 237 | * @return \Ocsp\ResponseList |
||||
| 238 | */ |
||||
| 239 | 2 | protected function decodeBasicResponse($responseBytes) |
|||
| 240 | { |
||||
| 241 | 2 | $basicOCSPResponse = $this->derDecoder->decodeElement($responseBytes); |
|||
| 242 | 2 | if (!$basicOCSPResponse instanceof Sequence) { |
|||
| 243 | throw Asn1DecodingException::create(); |
||||
| 244 | } |
||||
| 245 | 2 | $tbsResponseData = $basicOCSPResponse->getFirstChildOfType(UniversalTagID::SEQUENCE); |
|||
| 246 | 2 | if (!$tbsResponseData instanceof Sequence) { |
|||
| 247 | throw Asn1DecodingException::create(); |
||||
| 248 | } |
||||
| 249 | 2 | $responses = $tbsResponseData->getFirstChildOfType(UniversalTagID::SEQUENCE); |
|||
| 250 | 2 | if (!$responses instanceof Sequence) { |
|||
| 251 | throw Asn1DecodingException::create(); |
||||
| 252 | } |
||||
| 253 | 2 | $responseList = ResponseList::create(); |
|||
| 254 | 2 | foreach ($responses->getElements() as $singleResponse) { |
|||
| 255 | 2 | if ($singleResponse instanceof Sequence && $singleResponse->getTag() === null) { |
|||
| 256 | 2 | $responseList->addResponse($this->decodeBasicSingleResponse($singleResponse)); |
|||
| 257 | } |
||||
| 258 | } |
||||
| 259 | 2 | if ($responseList->getResponses() === []) { |
|||
| 260 | throw ResponseException\MissingResponseBytesException::create(); |
||||
| 261 | } |
||||
| 262 | |||||
| 263 | 2 | return $responseList; |
|||
| 264 | } |
||||
| 265 | |||||
| 266 | /** |
||||
| 267 | * Parse a "SingleResponse" element of a BasicOCSPResponse. |
||||
| 268 | * |
||||
| 269 | * @param \Ocsp\Asn1\Element\Sequence $singleResponse |
||||
| 270 | * |
||||
| 271 | * @throws \Ocsp\Exception\Asn1DecodingException |
||||
| 272 | * @throws \Ocsp\Exception\ResponseException |
||||
| 273 | * |
||||
| 274 | * @return \Ocsp\Response |
||||
| 275 | * |
||||
| 276 | * @see https://tools.ietf.org/html/rfc6960#section-4.2.1 |
||||
| 277 | */ |
||||
| 278 | 2 | protected function decodeBasicSingleResponse(Sequence $singleResponse) |
|||
| 279 | { |
||||
| 280 | 2 | $elements = $singleResponse->getElements(); |
|||
| 281 | 2 | $certID = isset($elements[0]) ? $elements[0] : null; |
|||
| 282 | 2 | if (!$certID instanceof Sequence) { |
|||
| 283 | throw Asn1DecodingException::create(); |
||||
| 284 | } |
||||
| 285 | |||||
| 286 | 2 | $certificateSerialNumber = (string) $certID->getFirstChildOfType(UniversalTagID::INTEGER, Element::CLASS_UNIVERSAL)->getValue(); |
|||
| 287 | 2 | $thisUpdate = $singleResponse->getFirstChildOfType(UniversalTagID::GENERALIZEDTIME, Element::CLASS_UNIVERSAL)->getValue(); |
|||
| 288 | 2 | $nextUpdate = $singleResponse->getFirstChildOfType(0, Element::CLASS_CONTEXTSPECIFIC, Tag::ENVIRONMENT_EXPLICIT); |
|||
| 289 | 2 | if ($nextUpdate !== null) { |
|||
| 290 | 2 | $nextUpdate = $nextUpdate->getValue(); |
|||
| 291 | } |
||||
| 292 | |||||
| 293 | 2 | $certStatus = isset($elements[1]) ? $elements[1] : null; |
|||
| 294 | 2 | if ($certStatus === null) { |
|||
| 295 | throw Asn1DecodingException::create(); |
||||
| 296 | } |
||||
| 297 | 2 | $certStatusTag = $certStatus instanceof TaggableElement ? $certStatus->getTag() : null; |
|||
| 298 | 2 | if ($certStatusTag === null) { |
|||
| 299 | 1 | if ($certStatus->getClass() !== Element::CLASS_CONTEXTSPECIFIC) { |
|||
| 300 | throw Asn1DecodingException::create(); |
||||
| 301 | } |
||||
| 302 | 1 | $tagID = $certStatus->getTypeID(); |
|||
| 303 | } else { |
||||
| 304 | 1 | if ($certStatusTag->getClass() !== Element::CLASS_CONTEXTSPECIFIC) { |
|||
| 305 | throw Asn1DecodingException::create(); |
||||
| 306 | } |
||||
| 307 | 1 | $tagID = $certStatusTag->getTagID(); |
|||
| 308 | } |
||||
| 309 | switch ($tagID) { |
||||
| 310 | 2 | case 0: |
|||
| 311 | 1 | return Response::good($thisUpdate, $certificateSerialNumber, $nextUpdate); |
|||
| 312 | 1 | case 1: |
|||
| 313 | 1 | $revokedOn = null; |
|||
| 314 | 1 | $revocationReason = Response::REVOCATIONREASON_UNSPECIFIED; |
|||
| 315 | 1 | if ($certStatus instanceof GeneralizedTime) { |
|||
| 316 | 1 | $revokedOn = $certStatus->getValue(); |
|||
| 317 | } elseif ($certStatus instanceof AbstractList) { |
||||
| 318 | $certStatusChildren = $certStatus->getElements(); |
||||
| 319 | if (isset($certStatusChildren[0]) && $certStatusChildren[0] instanceof GeneralizedTime) { |
||||
| 320 | $revokedOn = $certStatusChildren[0]->getValue(); |
||||
| 321 | if (isset($certStatusChildren[1]) && $certStatusChildren[1] instanceof RawPrimitive) { |
||||
| 322 | $bitString = $certStatusChildren[1]->getRawEncodedValue(); |
||||
| 323 | if (strlen($bitString) === 1) { |
||||
| 324 | $revocationReason = ord($bitString[0]); |
||||
| 325 | } |
||||
| 326 | } |
||||
| 327 | } |
||||
| 328 | } |
||||
| 329 | 1 | if ($revokedOn === null) { |
|||
| 330 | throw Asn1DecodingException::create('Failed to find the revocation date/time'); |
||||
| 331 | } |
||||
| 332 | |||||
| 333 | 1 | return Response::revoked($thisUpdate, $certificateSerialNumber, $revokedOn, $revocationReason, $nextUpdate); |
|||
| 334 | case 2: |
||||
| 335 | default: |
||||
| 336 | return Response::unknown($thisUpdate, $certificateSerialNumber, $nextUpdate); |
||||
| 337 | } |
||||
| 338 | } |
||||
| 339 | } |
||||
| 340 |