Ocsp::decodeBasicSingleResponse()   F
last analyzed

Complexity

Conditions 21
Paths 522

Size

Total Lines 59
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 42.6034

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 43
c 1
b 0
f 0
dl 0
loc 59
ccs 26
cts 41
cp 0.6341
rs 0.6637
cc 21
nc 522
nop 1
crap 42.6034

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
The method getRawEncodedValue() 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\RawPrimitive. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

185
        switch ($responseStatus->/** @scrutinizer ignore-call */ getRawEncodedValue()) {
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
Bug introduced by
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 ignore-call  annotation

218
            switch ($responseType->/** @scrutinizer ignore-call */ getIdentifier()) {
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
Bug introduced by
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 ignore-call  annotation

220
                    return $this->decodeBasicResponse($response->/** @scrutinizer ignore-call */ getValue());
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