CertificateInfo   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 216
Duplicated Lines 0 %

Test Coverage

Coverage 74.7%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
eloc 78
c 1
b 0
f 0
dl 0
loc 216
ccs 62
cts 83
cp 0.747
rs 9.52

8 Methods

Rating   Name   Duplication   Size   Complexity  
A extractRequestInfo() 0 6 1
A extractSubjectPublicKeyBytes() 0 16 4
B extractIssuerCertificateUrl() 0 20 7
A extractIssuerDer() 0 12 3
B getAuthorityInfoAccessExtension() 0 32 10
A extractSerialNumber() 0 12 3
B extractOcspResponderUrl() 0 20 7
A __construct() 0 4 1
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\RawPrimitive;
9
use Ocsp\Asn1\Element\Sequence;
10
use Ocsp\Asn1\Tag;
11
use Ocsp\Asn1\UniversalTagID;
12
use Ocsp\Exception\Asn1DecodingException;
13
14
class CertificateInfo
15
{
16
    /**
17
     * The decoder to be used to decode DER-encoded data.
18
     *
19
     * @var \Ocsp\Asn1\Der\Decoder
20
     */
21
    private $derDecoder;
22
23
    /**
24
     * The encoder to be used to encode data to DER.
25
     *
26
     * @var \Ocsp\Asn1\Der\Encoder
27
     */
28
    private $derEncoder;
29
30
    /**
31
     * Initialize the instance.
32
     */
33 3
    public function __construct()
34
    {
35 3
        $this->derDecoder = new DerDecoder();
36 3
        $this->derEncoder = new DerEncoder();
37
    }
38
39
    /**
40
     * Extract the OCSP Responder URL that *may* be included in a certificate.
41
     *
42
     * @param \Ocsp\Asn1\Element\Sequence $certificate the certificate (loaded with the CertificateLoader class)
43
     *
44
     * @return string empty string if not found
45
     */
46 3
    public function extractOcspResponderUrl(Sequence $certificate)
47
    {
48 3
        $authorityInfoAccess = $this->getAuthorityInfoAccessExtension($certificate);
49 3
        if ($authorityInfoAccess === null) {
50
            return '';
51
        }
52 3
        foreach ($authorityInfoAccess->getElements() as $accessDescription) {
53 3
            $accessMethod = $accessDescription instanceof Sequence ? $accessDescription->getFirstChildOfType(UniversalTagID::OBJECT_IDENTIFIER) : null;
54 3
            if ($accessMethod === null || $accessMethod->getIdentifier() !== '1.3.6.1.5.5.7.48.1') {
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

54
            if ($accessMethod === null || $accessMethod->/** @scrutinizer ignore-call */ getIdentifier() !== '1.3.6.1.5.5.7.48.1') {
Loading history...
55
                continue;
56
            }
57 3
            $accessLocation = $accessDescription->getFirstChildOfType(6, Element::CLASS_CONTEXTSPECIFIC);
0 ignored issues
show
Bug introduced by
The method getFirstChildOfType() 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\AbstractList. ( Ignorable by Annotation )

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

57
            /** @scrutinizer ignore-call */ 
58
            $accessLocation = $accessDescription->getFirstChildOfType(6, Element::CLASS_CONTEXTSPECIFIC);
Loading history...
58 3
            if (!$accessLocation instanceof RawPrimitive) {
59
                return '';
60
            }
61
            // It's a IA5String, that is US-ASCII
62 3
            return $accessLocation->getRawEncodedValue();
63
        }
64
65
        return '';
66
    }
67
68
    /**
69
     * Extract the URL where the issuer certificate can be retrieved from (if present).
70
     *
71
     * @param \Ocsp\Asn1\Element\Sequence $certificate the certificate (loaded with the CertificateLoader class)
72
     *
73
     * @return string empty string if not found
74
     */
75 1
    public function extractIssuerCertificateUrl(Sequence $certificate)
76
    {
77 1
        $authorityInfoAccess = $this->getAuthorityInfoAccessExtension($certificate);
78 1
        if ($authorityInfoAccess === null) {
79
            return '';
80
        }
81 1
        foreach ($authorityInfoAccess->getElements() as $accessDescription) {
82 1
            $accessMethod = $accessDescription instanceof Sequence ? $accessDescription->getFirstChildOfType(UniversalTagID::OBJECT_IDENTIFIER) : null;
83 1
            if ($accessMethod === null || $accessMethod->getIdentifier() !== '1.3.6.1.5.5.7.48.2') {
84 1
                continue;
85
            }
86 1
            $accessLocation = $accessDescription->getFirstChildOfType(6, Element::CLASS_CONTEXTSPECIFIC);
87 1
            if (!$accessLocation instanceof RawPrimitive) {
88
                return '';
89
            }
90
            // It's a IA5String, that is US-ASCII
91 1
            return $accessLocation->getRawEncodedValue();
92
        }
93
94
        return '';
95
    }
96
97
    /**
98
     * Extract the data to be sent to the OCSP Responder url from a certificate and the issuer certifiacte.
99
     *
100
     * @param \Ocsp\Asn1\Element\Sequence $certificate the certificate (loaded with the CertificateLoader class)
101
     * @param \Ocsp\Asn1\Element\Sequence $issuerCertificate the issuer certificate (loaded with the CertificateLoader class; its URL can be retrieved with the extractOcspResponderUrl method)
102
     *
103
     * @throws \Ocsp\Exception\RequestException when some required data is missing in the certificate/issuer certificate
104
     *
105
     * @return \Ocsp\Request
106
     */
107 2
    public function extractRequestInfo(Sequence $certificate, Sequence $issuerCertificate)
108
    {
109 2
        return Request::create(
110 2
            $this->extractSerialNumber($certificate),
111 2
            $this->extractIssuerDer($certificate),
112 2
            $this->extractSubjectPublicKeyBytes($issuerCertificate)
113 2
        );
114
    }
115
116
    /**
117
     * Get the AuthorityInfoAccess extension included in a certificate.
118
     *
119
     * @param \Ocsp\Asn1\Element\Sequence $certificate
120
     *
121
     * @return \Ocsp\Asn1\Element\Sequence|null
122
     *
123
     * @see https://tools.ietf.org/html/rfc2459#section-4.1 for Certificate
124
     * @see https://tools.ietf.org/html/rfc2459#section-4.2.2.1 for AuthorityInfoAccessSyntax
125
     */
126 3
    protected function getAuthorityInfoAccessExtension(Sequence $certificate)
127
    {
128 3
        $tbsCertificate = $certificate->getFirstChildOfType(UniversalTagID::SEQUENCE, Element::CLASS_UNIVERSAL);
129 3
        if (!$tbsCertificate instanceof Sequence) {
130
            return null;
131
        }
132 3
        $extensions = $tbsCertificate->getFirstChildOfType(3, Element::CLASS_CONTEXTSPECIFIC, Tag::ENVIRONMENT_EXPLICIT);
133 3
        if (!$extensions instanceof Sequence) {
134
            return null;
135
        }
136 3
        foreach ($extensions->getElements() as $extension) {
137 3
            if (!$extension instanceof Sequence) {
138
                continue;
139
            }
140 3
            $extnID = $extension->getFirstChildOfType(UniversalTagID::OBJECT_IDENTIFIER);
141 3
            if ($extnID === null || $extnID->getIdentifier() !== '1.3.6.1.5.5.7.1.1') {
142 3
                continue;
143
            }
144 3
            $extnValue = $extension->getFirstChildOfType(UniversalTagID::OCTET_STRING);
145 3
            if ($extnValue === null) {
146
                return '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '' returns the type string which is incompatible with the documented return type Ocsp\Asn1\Element\Sequence|null.
Loading history...
147
            }
148
            try {
149 3
                $authorityInfoAccess = $this->derDecoder->decodeElement($extnValue->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

149
                $authorityInfoAccess = $this->derDecoder->decodeElement($extnValue->/** @scrutinizer ignore-call */ getValue());
Loading history...
150
            } catch (Asn1DecodingException $foo) {
151
                $authorityInfoAccess = null;
152
            }
153
154 3
            return $authorityInfoAccess instanceof Sequence ? $authorityInfoAccess : null;
155
        }
156
157
        return null;
158
    }
159
160
    /**
161
     * Extract the serial number from a certificate.
162
     *
163
     * @param \Ocsp\Asn1\Element\Sequence $certificate
164
     *
165
     * @return string Empty string if not found
166
     *
167
     * @see https://tools.ietf.org/html/rfc2459#section-4.1 for Certificate
168
     * @see https://tools.ietf.org/html/rfc5912#section-14 for CertificateSerialNumber
169
     */
170 2
    protected function extractSerialNumber(Sequence $certificate)
171
    {
172 2
        $tbsCertificate = $certificate->getFirstChildOfType(UniversalTagID::SEQUENCE);
173 2
        if ($tbsCertificate === null) {
174
            return '';
175
        }
176 2
        $serialNumber = $tbsCertificate->getFirstChildOfType(UniversalTagID::INTEGER);
177 2
        if ($serialNumber === null) {
178
            return '';
179
        }
180
181 2
        return (string) $serialNumber->getValue();
182
    }
183
184
    /**
185
     * Extract the DER-encoded data of the issuer.
186
     *
187
     * @param \Ocsp\Asn1\Element\Sequence $certificate
188
     *
189
     * @return string Empty string if not found
190
     *
191
     * @see https://tools.ietf.org/html/rfc2459#section-4.1 for Certificate
192
     */
193 2
    protected function extractIssuerDer(Sequence $certificate)
194
    {
195 2
        $tbsCertificate = $certificate->getFirstChildOfType(UniversalTagID::SEQUENCE);
196 2
        if ($tbsCertificate === null) {
197
            return '';
198
        }
199 2
        $issuer = $tbsCertificate->getNthChildOfType(2, UniversalTagID::SEQUENCE);
0 ignored issues
show
Bug introduced by
The method getNthChildOfType() 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\AbstractList. ( Ignorable by Annotation )

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

199
        /** @scrutinizer ignore-call */ 
200
        $issuer = $tbsCertificate->getNthChildOfType(2, UniversalTagID::SEQUENCE);
Loading history...
200 2
        if ($issuer === null) {
201
            return '';
202
        }
203
204 2
        return $this->derEncoder->encodeElement($issuer);
205
    }
206
207
    /**
208
     * Extract the bytes of the public key of the subject included in the certificate.
209
     *
210
     * @param \Ocsp\Asn1\Element\Sequence $certificate
211
     *
212
     * @return string Empty string if not found
213
     */
214 2
    protected function extractSubjectPublicKeyBytes(Sequence $certificate)
215
    {
216 2
        $tbsCertificate = $certificate->getFirstChildOfType(UniversalTagID::SEQUENCE);
217 2
        if ($tbsCertificate === null) {
218
            return '';
219
        }
220 2
        $subjectPublicKeyInfo = $tbsCertificate->getNthChildOfType(5, UniversalTagID::SEQUENCE);
221 2
        if ($subjectPublicKeyInfo === null) {
222
            return '';
223
        }
224 2
        $subjectPublicKey = $subjectPublicKeyInfo->getFirstChildOfType(UniversalTagID::BIT_STRING);
225 2
        if ($subjectPublicKey === null) {
226
            return '';
227
        }
228
229 2
        return $subjectPublicKey->getBytes();
230
    }
231
}
232