RFC6614Tests   F
last analyzed

Complexity

Total Complexity 65

Size/Duplication

Total Lines 404
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 65
eloc 186
dl 0
loc 404
rs 3.2
c 2
b 1
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
B checkServerName() 0 28 7
A allChecks() 0 5 2
A __construct() 0 34 3
A cApathCheck() 0 11 3
A getCertificateIssuer() 0 13 4
B opensslCAResult() 0 54 11
C tlsClientSideCheck() 0 49 17
A getCertificatePropertyField() 0 6 3
B opensslClientsResult() 0 35 9
A execOpensslClient() 0 16 1
A propertyCheckPolicy() 0 11 5

How to fix   Complexity   

Complex Class

Complex classes like RFC6614Tests often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RFC6614Tests, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
namespace core\diag;
24
25
use \Exception;
0 ignored issues
show
Bug introduced by
The type \Exception was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
27
/**
28
 * Test suite to verify that a given NAI realm has NAPTR records according to
29
 * consortium-agreed criteria
30
 * Can only be used if \config\Diagnostics::RADIUSTESTS is configured.
31
 *
32
 * @author Stefan Winter <[email protected]>
33
 * @author Tomasz Wolniewicz <[email protected]>
34
 * @author Maja Gorecka-Wolniewicz <[email protected]>
35
 *
36
 * @license see LICENSE file in root directory
37
 *
38
 * @package Developer
39
 */
40
class RFC6614Tests extends AbstractTest
41
{
42
43
    /**
44
     * dictionary of translatable texts around the certificates we check
45
     * 
46
     * @var array
47
     */
48
    private $TLS_certkeys = [];
49
50
    /**
51
     * list of IP addresses which are candidates for dynamic discovery targets
52
     * 
53
     * @var array
54
     */
55
    private $candidateIPs;
56
57
    /**
58
     * the hostname which should show up in the certificate when establishing
59
     * a connection to the RADIUS/TLS server (hostname is an intermediary result
60
     * of the RFC7585 DNS resolution algorithm, in SRV response)
61
     * 
62
     * @var string
63
     */
64
    private $expectedName;
65
66
    /**
67
     * associative array holding the server-side cert test results for a given IP (IP is the key)
68
     * 
69
     * @var array
70
     */
71
    public $TLS_CA_checks_result;
72
73
    /**
74
     * associative array holding the client-side cert test results for a given IP (IP is the key)
75
     * 
76
     * @var array
77
     */
78
    public $TLS_clients_checks_result;
79
80
    /**
81
     * which consortium are we testing against?
82
     * 
83
     * @var string
84
     */
85
    private $consortium;
86
    /**
87
     * Sets up the instance for testing of a number of candidate IPs
88
     * 
89
     * @param array  $listOfIPs    candidates to test
90
     * @param string $expectedName expected server name to test against
91
     * @param string $consortium   which consortium to test against
92
     */
93
    public function __construct($listOfIPs, $expectedName, $consortium = "eduroam")
94
    {
95
        parent::__construct();
96
        \core\common\Entity::intoThePotatoes();
97
        $this->TLS_certkeys = [
98
            'eduPKI' => _('eduPKI'),
99
            'NCU' => _('Nicolaus Copernicus University'),
100
            'ACCREDITED' => _('accredited'),
101
            'NONACCREDITED' => _('non-accredited'),
102
            'CORRECT' => _('correct certificate'),
103
            'WRONGPOLICY' => _('certificate with wrong policy OID'),
104
            'EXPIRED' => _('expired certificate'),
105
            'REVOKED' => _('revoked certificate'),
106
            'PASS' => _('pass'),
107
            'FAIL' => _('fail'),
108
            'non-eduPKI-accredited' => _("eduroam-accredited CA (now only for tests)"),
109
        ];
110
        $this->TLS_CA_checks_result = [];
111
        $this->TLS_clients_checks_result = [];
112
113
        $this->candidateIPs = $listOfIPs;
114
        $this->expectedName = $expectedName;
115
116
        switch ($consortium) {
117
            case "eduroam":
118
                // fall-through intended
119
            case "openroaming":
120
                $this->consortium = $consortium;
121
                break;
122
            default:
123
                throw new Exception("Certificate checks against unknown consortium identifier requested!");
124
        }
125
126
        \core\common\Entity::outOfThePotatoes();
127
    }
128
129
    /**
130
     * run all checks on all candidates
131
     * 
132
     * @return void
133
     */
134
    public function allChecks()
135
    {
136
        foreach ($this->candidateIPs as $oneIP) {
137
            $this->cApathCheck($oneIP);
138
            $this->tlsClientSideCheck($oneIP, '', '');
139
        }
140
    }
141
142
    /**
143
     * This function executes openssl s_clientends command to check if a server accepts a CA
144
     * 
145
     * @param string $host IP:port
146
     * @return int returncode
147
     */
148
    public function cApathCheck(string $host)
149
    {
150
        if (!isset($this->TLS_CA_checks_result[$host])) {
151
            $this->TLS_CA_checks_result[$host] = [];
152
        }
153
        $opensslbabble = $this->execOpensslClient($host, '', $this->TLS_CA_checks_result[$host]);
154
        $overallRetval = $this->opensslCAResult($host, $opensslbabble);
155
        if ($overallRetval == AbstractTest::RETVAL_OK) {
156
            $this->checkServerName($host);
157
        } 
158
        return $overallRetval;
159
    }
160
161
    /**
162
     * checks whether the received servername matches the expected server name
163
     * 
164
     * @param string $host IP:port
165
     * @return bool yes or no
166
     */
167
    private function checkServerName($host)
168
    {
169
        // it could match CN or sAN:DNS, we don't care which
170
        if (isset($this->TLS_CA_checks_result[$host]['certdata']['subject'])) {
171
            $this->loggerInstance->debug(4, "Checking expected server name " . $this->expectedName . 
172
                    " against Subject: " . $this->TLS_CA_checks_result[$host]['certdata']['subject']);
173
            // we are checking against accidental misconfig, not attacks, so loosely checking against end of string is appropriate
174
            if (preg_match("/CN=" . $this->expectedName . "/", $this->TLS_CA_checks_result[$host]['certdata']['subject']) === 1) {
175
                return TRUE;
176
            }
177
        }
178
        if (isset($this->TLS_CA_checks_result[$host]['certdata']['extensions']['subjectaltname'])) {
179
            $this->loggerInstance->debug(4, "Checking expected server name " . $this->expectedName . " against sANs: ");
180
            $this->loggerInstance->debug(4, $this->TLS_CA_checks_result[$host]['certdata']['extensions']['subjectaltname']);
181
            $testNames = $this->TLS_CA_checks_result[$host]['certdata']['extensions']['subjectaltname'];
182
            if (!is_array($testNames)) {
183
                $testNames = [$testNames];
184
            }
185
            foreach ($testNames as $oneName) {
186
                if (preg_match("/" . $this->expectedName . "/", $oneName) === 1) {
187
                    return TRUE;
188
                }
189
            }
190
        }
191
        $this->loggerInstance->debug(3, "Tried to check expected server name " . $this->expectedName . " but neither CN nor sANs matched.");
192
193
        $this->TLS_CA_checks_result[$host]['cert_oddity'] = RADIUSTests::CERTPROB_DYN_SERVER_NAME_MISMATCH;
194
        return FALSE;
195
    }
196
197
    /**
198
     * This function executes openssl s_client command to check if a server accepts a client certificate
199
     * 
200
     * @param string $host       IP:port
201
     * @return int returncode
202
     */
203
    public function tlsClientSideCheck(string $host, string $ename, string $realm)
0 ignored issues
show
Unused Code introduced by
The parameter $ename is not used and could be removed. ( Ignorable by Annotation )

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

203
    public function tlsClientSideCheck(string $host, /** @scrutinizer ignore-unused */ string $ename, string $realm)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $realm is not used and could be removed. ( Ignorable by Annotation )

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

203
    public function tlsClientSideCheck(string $host, string $ename, /** @scrutinizer ignore-unused */ string $realm)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
204
    {
205
        $res = RADIUSTests::RETVAL_OK;
206
        if (!is_array(\config\Diagnostics::RADIUSTESTS['TLS-clientcerts']) || count(\config\Diagnostics::RADIUSTESTS['TLS-clientcerts']) == 0) {
207
            return RADIUSTests::RETVAL_SKIPPED;
208
        }
209
        foreach (\config\Diagnostics::RADIUSTESTS['TLS-clientcerts'] as $type => $tlsclient) {
210
            $this->TLS_clients_checks_result[$host]['ca'][$type]['clientcertinfo']['from'] = $type;
211
            $this->TLS_clients_checks_result[$host]['ca'][$type]['clientcertinfo']['status'] = $tlsclient['status'];
212
            $this->TLS_clients_checks_result[$host]['ca'][$type]['clientcertinfo']['message'] = $this->TLS_certkeys[$tlsclient['status']];
213
            $this->TLS_clients_checks_result[$host]['ca'][$type]['clientcertinfo']['issuer'] = $tlsclient['issuerCA'];
214
            foreach ($tlsclient['certificates'] as $k => $cert) {
215
                $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['status'] = $cert['status'];
216
                $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['message'] = $this->TLS_certkeys[$cert['status']];
217
                $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['expected'] = $cert['expected'];
218
                $add = ' -cert ' . ROOT . '/config/cli-certs/' . $cert['public'] . ' -key ' . ROOT . '/config/cli-certs/' . $cert['private'];
219
                if (!file_exists(ROOT . '/config/cli-certs/' . $cert['public']) ||!file_exists(ROOT . 
220
                        '/config/cli-certs/' . $cert['private'])) {
221
                    $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['finalerror'] = 2;
222
                    continue;
223
                }
224
                if (!isset($this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k])) {
225
                    $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k] = [];
226
                }
227
                // tls1_3 connections have a problem in strdout/stderr buffering 
228
                $add .= ' ' . "-no_ssl3 -no_tls1_3";
229
                $opensslbabble = $this->execOpensslClient($host, $add, $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]);
230
                $res = $this->opensslClientsResult($host, $opensslbabble, $this->TLS_clients_checks_result, $type, $k);
231
                if ($cert['expected'] == 'PASS') {
232
                    if (!$this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['connected']) {
233
                        if (($tlsclient['status'] == 'ACCREDITED') && ($cert['status'] == 'CORRECT')) {
234
                            $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['returncode'] = RADIUSTests::CERTPROB_NOT_ACCEPTED;
235
                            $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['finalerror'] = 1;
236
                            break;
237
                        }
238
                    }
239
                } else {
240
                    if ($this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['connected']) {
241
                        $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['returncode'] = RADIUSTests::CERTPROB_WRONGLY_ACCEPTED;
242
                    }
243
244
                    if (isset($this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['reason']) && ($this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['reason'] == RADIUSTests::CERTPROB_UNKNOWN_CA) && ($tlsclient['status'] == 'ACCREDITED') && ($cert['status'] == 'CORRECT')) {
245
                        $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['finalerror'] = 1;
246
                        break;
247
                    }
248
                }
249
            }
250
        }
251
        return $res;
252
    }
253
254
    /**
255
     * This function executes openssl s_client command
256
     * 
257
     * @param string $host        IP address
258
     * @param string $consortium  which consortium to check against
259
     * @param string $arg         arguments to add to the openssl command 
260
     * @param array  $testresults by-reference: the testresults array we are writing into
261
     * @return array result of openssl s_client ...
262
     */
263
    private function execOpensslClient($host, $arg, &$testresults)
264
    {
265
// we got the IP address either from DNS (guaranteeing well-formedness)
266
// or from filter_var'ed user input. So it is always safe as an argument
267
// but code analysers want this more explicit, so here is this extra
268
// call to escapeshellarg()
269
        $escapedHost = escapeshellarg($host);
270
        $this->loggerInstance->debug(4, \config\Master::PATHS['openssl'] . " s_client -connect " . $escapedHost . " -CApath " . ROOT . "/config/ca-certs/$this->consortium/ $arg 2>&1\n");
271
        $time_start = microtime(true);
272
        $opensslbabble = [];
273
        $result = 999; // likely to become zero by openssl; don't want to initialise to zero, could cover up exec failures
274
        exec(\config\Master::PATHS['openssl'] . " s_client -connect " . $escapedHost . " -CApath " . ROOT . "/config/ca-certs/$this->consortium/ $arg 2>&1", $opensslbabble, $result);
275
        $time_stop = microtime(true);
276
        $testresults['time_millisec'] = floor(($time_stop - $time_start) * 1000);
277
        $testresults['returncode'] = $result;
278
        return $opensslbabble;
279
    }
280
281
    /**
282
     * This function parses openssl s_client result
283
     * 
284
     * @param string $host          IP:port
285
     * @param array  $opensslbabble openssl command output
286
     * @return int return code
287
     */
288
    private function opensslCAResult($host, $opensslbabble)
289
    {
290
        if (preg_match('/connect: Connection refused/', implode($opensslbabble))) {
291
            $this->TLS_CA_checks_result[$host]['status'] = RADIUSTests::RETVAL_CONNECTION_REFUSED;
292
            return RADIUSTests::RETVAL_INVALID;
293
        }
294
        if (preg_match('/no peer certificate available/', implode($opensslbabble))) {
295
            $this->TLS_CA_checks_result[$host]['status'] = RADIUSTests::RETVAL_SERVER_UNFINISHED_COMM;
296
            return RADIUSTests::RETVAL_INVALID;
297
        }
298
        /*if (preg_match('/verify error:num=19/', implode($opensslbabble))) {
299
            $this->TLS_CA_checks_result[$host]['cert_oddity'] = RADIUSTests::CERTPROB_UNKNOWN_CA;
300
            $this->TLS_CA_checks_result[$host]['status'] = RADIUSTests::RETVAL_INVALID;
301
            return RADIUSTests::RETVAL_INVALID;
302
        }*/
303
        if (preg_match('/Verification error: self-signed certificate in certificate chain/', implode($opensslbabble))) {
304
            $this->TLS_CA_checks_result[$host]['cert_oddity'] = RADIUSTests::CERTPROB_UNKNOWN_CA;
305
            $this->TLS_CA_checks_result[$host]['status'] = RADIUSTests::RETVAL_INVALID;
306
            return RADIUSTests::RETVAL_INVALID;
307
        }
308
        if (preg_match('/Cipher is (NONE)/', implode($opensslbabble))) {
309
            $this->TLS_CA_checks_result[$host]['status'] = RADIUSTests::RETVAL_SERVER_UNFINISHED_COMM;
310
            return RADIUSTests::RETVAL_INVALID;
311
        }
312
        /*if (preg_match('/verify return:1/', implode($opensslbabble))) {*/
313
        if (preg_match('/Verification: OK/', implode($opensslbabble))) {
314
            $this->TLS_CA_checks_result[$host]['status'] = RADIUSTests::RETVAL_OK;
315
            $servercertStage1 = implode("\n", $opensslbabble);
316
            $servercert = preg_replace("/.*(-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----\n).*/s", "$1", $servercertStage1);
317
            $data = openssl_x509_parse($servercert);
318
            $this->TLS_CA_checks_result[$host]['certdata']['subject'] = $data['name'];
319
            $this->TLS_CA_checks_result[$host]['certdata']['validTo'] = $data['validTo'];
320
            $this->TLS_CA_checks_result[$host]['certdata']['issuer'] = $this->getCertificateIssuer($data);
321
            if (($altname = $this->getCertificatePropertyField($data, 'subjectAltName'))) {
322
                $this->TLS_CA_checks_result[$host]['certdata']['extensions']['subjectaltname'] = $altname;
323
            }
324
325
            $oids = $this->propertyCheckPolicy($data);
326
            if (!empty($oids)) {
327
                foreach ($oids as $resultArrayKey => $o) {
328
                    $this->TLS_CA_checks_result[$host]['certdata']['extensions']['policyoid'][] = " $o ($resultArrayKey)";
329
                }
330
            }
331
            if (($crl = $this->getCertificatePropertyField($data, 'crlDistributionPoints'))) {
332
                $this->TLS_CA_checks_result[$host]['certdata']['extensions']['crlDistributionPoint'] = $crl;
333
            }
334
            if (($ocsp = $this->getCertificatePropertyField($data, 'authorityInfoAccess'))) {
335
                $this->TLS_CA_checks_result[$host]['certdata']['extensions']['authorityInfoAccess'] = $ocsp;
336
            }
337
            return RADIUSTests::RETVAL_OK;
338
        }
339
        // we should have been caught somewhere along the way. If we got here,
340
        // something seriously unexpected happened. Let's talk about it.
341
        return RADIUSTests::RETVAL_INVALID;
342
    }
343
344
    /**
345
     * This function parses openssl s_client result
346
     * 
347
     * @param string $host           IP:port
348
     * @param array  $opensslbabble  openssl command output
349
     * @param array  $testresults    by-reference: pointer to results array we write into
350
     * @param string $type           type of certificate
351
     * @param int    $resultArrayKey results array key
352
     * @return int return code
353
     */
354
    private function opensslClientsResult($host, $opensslbabble, &$testresults, $type = '', $resultArrayKey = 0)
355
    {
356
        \core\common\Entity::intoThePotatoes();
357
        $res = RADIUSTests::RETVAL_OK;
358
        $ret = $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['returncode'];
359
        $jsondir = dirname(dirname(dirname(__FILE__)))."/var/json_cache/";
0 ignored issues
show
Unused Code introduced by
The assignment to $jsondir is dead and can be removed.
Loading history...
360
        $output = implode($opensslbabble);
361
        if ($ret == 0) {
362
            $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['connected'] = 1;
363
        } else {
364
            $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['connected'] = 0;
365
            if (preg_match('/connect: Connection refused/', $output)) {
366
                $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['returncode'] = RADIUSTests::RETVAL_CONNECTION_REFUSED;
367
                $resComment = _("No TLS connection established: Connection refused");
368
            } elseif (preg_match('/sslv3 alert certificate expired/', $output)) {
369
                $resComment = _("certificate expired");
370
            } elseif (preg_match('/sslv3 alert certificate revoked/', $output)) {
371
                $resComment = _("certificate was revoked");
372
            } elseif (preg_match('/SSL alert number 46/', $output) && 
373
                      preg_match('/sslv3 alert certificate unknown/', $output)) {
374
                $resComment = _("bad policy");
375
            } elseif (preg_match('/tlsv1 alert unknown ca/', $output)) {
376
                $resComment = _("unknown authority");
377
                $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['reason'] = RADIUSTests::CERTPROB_UNKNOWN_CA;
378
            } elseif (preg_match('/unexpected eof while reading/', $output)) {
379
                $resComment = _("unexpected eof while reading");
380
                $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['reason'] = RADIUSTests::CERTPROB_UNEXPECTED_EOF;
381
            } else {
382
                $resComment = _("unknown problem");
383
                $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['reason'] = RADIUSTests::CERTPROB_UNKNOWN_PROBLEM;
384
            }
385
            $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['resultcomment'] = $resComment;
386
        }
387
        \core\common\Entity::outOfThePotatoes();
388
        return $res;
389
    }
390
391
    /**
392
     * This function parses a X.509 cert and returns all certificatePolicies OIDs
393
     * 
394
     * @param array $cert (returned from openssl_x509_parse) 
395
     * @return array of OIDs
396
     */
397
    private function propertyCheckPolicy($cert)
398
    {
399
        $oids = [];
400
        if (isset($cert['extensions']['certificatePolicies']) &&  $cert['extensions']['certificatePolicies']) {
401
            foreach (\config\Diagnostics::RADIUSTESTS['TLS-acceptableOIDs'] as $key => $oid) {
402
                if (preg_match("/Policy: $oid/", $cert['extensions']['certificatePolicies'])) {
403
                    $oids[$key] = $oid;
404
                }
405
            }
406
        }
407
        return $oids;
408
    }
409
410
    /**
411
     * This function parses a X.509 cert and returns the value of $field
412
     * 
413
     * @param array $cert (returned from openssl_x509_parse) 
414
     * @return string value of the issuer field or ''
415
     */
416
    private function getCertificateIssuer($cert)
417
    {
418
        $issuer = '';
419
        foreach ($cert['issuer'] as $key => $val) {
420
            if (is_array($val)) {
421
                foreach ($val as $v) {
422
                    $issuer .= "/$key=$v";
423
                }
424
            } else {
425
                $issuer .= "/$key=$val";
426
            }
427
        }
428
        return $issuer;
429
    }
430
431
    /**
432
     * This function parses a X.509 cert and returns the value of $field
433
     * 
434
     * @param array  $cert  (returned from openssl_x509_parse) 
435
     * @param string $field the field to search for
436
     * @return string value of the extension named $field or ''
437
     */
438
    private function getCertificatePropertyField($cert, $field)
439
    {
440
        if (isset($cert['extensions'][$field]) && $cert['extensions'][$field]) {
441
            return $cert['extensions'][$field];
442
        }
443
        return '';
444
    }
445
}