Test Failed
Branch release_2_0 (0a9376)
by Stefan
07:17
created

RFC6614Tests   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 47
eloc 137
dl 0
loc 291
rs 8.64
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
A getCertificateIssuer() 0 12 4
B opensslCAResult() 0 35 9
A allChecks() 0 4 2
C tlsClientSideCheck() 0 45 15
A getCertificatePropertyField() 0 5 2
B opensslClientsResult() 0 26 7
A propertyCheckPolicy() 0 10 4
A cApathCheck() 0 6 2
A execOpensslClient() 0 15 1

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
 * Contributions to this work were made on behalf of the GÉANT project, a 
5
 * project that has received funding from the European Union’s Framework 
6
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
7
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
8
 * 691567 (GN4-1) and No. 731122 (GN4-2).
9
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
10
 * of the copyright in all material which was developed by a member of the GÉANT
11
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
12
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
13
 * UK as a branch of GÉANT Vereniging.
14
 * 
15
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
16
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
17
 *
18
 * License: see the web/copyright.inc.php file in the file structure or
19
 *          <base_url>/copyright.php after deploying the software
20
 */
21
22
namespace core\diag;
23
24
use \Exception;
25
26
require_once dirname(dirname(__DIR__)) . "/config/_config.php";
27
28
/**
29
 * Test suite to verify that a given NAI realm has NAPTR records according to
30
 * consortium-agreed criteria
31
 * Can only be used if CONFIG_DIAGNOSTICS['RADIUSTESTS'] is configured.
32
 *
33
 * @author Stefan Winter <[email protected]>
34
 * @author Tomasz 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
     * dictionary of translatable texts around the certificates we check
44
     * 
45
     * @var array
46
     */
47
    private $TLS_certkeys = [];
48
    
49
    /**
50
     * list of IP addresses which are candidates for dynamic discovery targets
51
     * 
52
     * @var array
53
     */
54
    private $candidateIPs;
55
    
56
    /**
57
     * associative array holding the server-side cert test results for a given IP (IP is the key)
58
     * 
59
     * @var array
60
     */
61
    public $TLS_CA_checks_result;
62
    
63
    /**
64
     * associative array holding the client-side cert test results for a given IP (IP is the key)
65
     * 
66
     * @var array
67
     */
68
    public $TLS_clients_checks_result;
69
70
    /**
71
     * Sets up the instance for testing of a number of candidate IPs
72
     * 
73
     * @param array $listOfIPs candidates to test
74
     */
75
    public function __construct($listOfIPs) {
76
        parent::__construct();
77
        $this->TLS_certkeys = [
78
            'eduPKI' => _('eduPKI'),
79
            'NCU' => _('Nicolaus Copernicus University'),
80
            'ACCREDITED' => _('accredited'),
81
            'NONACCREDITED' => _('non-accredited'),
82
            'CORRECT' => _('correct certificate'),
83
            'WRONGPOLICY' => _('certificate with wrong policy OID'),
84
            'EXPIRED' => _('expired certificate'),
85
            'REVOKED' => _('revoked certificate'),
86
            'PASS' => _('pass'),
87
            'FAIL' => _('fail'),
88
            'non-eduPKI-accredited' => _("eduroam-accredited CA (now only for tests)"),
89
        ];
90
        $this->TLS_CA_checks_result = [];
91
        $this->TLS_clients_checks_result = [];
92
        
93
        $this->candidateIPs = $listOfIPs;
94
    }
95
96
    /**
97
     * run all checks on all candidates
98
     * 
99
     * @return void
100
     */
101
    public function allChecks() {
102
        foreach ($this->candidateIPs as $oneIP) {
103
            $this->cApathCheck($oneIP);
104
            $this->tlsClientSideCheck($oneIP);
105
        }
106
    }
107
    
108
    /**
109
     * This function executes openssl s_clientends command to check if a server accepts a CA
110
     * 
111
     * @param string $host IP:port
112
     * @return int returncode
113
     */
114
    public function cApathCheck(string $host) {
115
        if (!isset($this->TLS_CA_checks_result[$host])) {
116
            $this->TLS_CA_checks_result[$host] = [];
117
        }
118
        $opensslbabble = $this->execOpensslClient($host, '', $this->TLS_CA_checks_result[$host]);
119
        return $this->opensslCAResult($host, $opensslbabble, $this->TLS_CA_checks_result);
120
    }
121
122
    /**
123
     * This function executes openssl s_client command to check if a server accepts a client certificate
124
     * 
125
     * @param string $host IP:port
126
     * @return int returncode
127
     */
128
    public function tlsClientSideCheck(string $host) {
129
        $res = RADIUSTests::RETVAL_OK;
130
        if (!is_array(CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-clientcerts']) || count(CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-clientcerts']) == 0) {
131
            return RADIUSTests::RETVAL_SKIPPED;
132
        }
133
        if (preg_match("/\[/", $host)) {
134
            return RADIUSTests::RETVAL_INVALID;
135
        }
136
        foreach (CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-clientcerts'] as $type => $tlsclient) {
137
            $this->TLS_clients_checks_result[$host]['ca'][$type]['clientcertinfo']['from'] = $type;
138
            $this->TLS_clients_checks_result[$host]['ca'][$type]['clientcertinfo']['status'] = $tlsclient['status'];
139
            $this->TLS_clients_checks_result[$host]['ca'][$type]['clientcertinfo']['message'] = $this->TLS_certkeys[$tlsclient['status']];
140
            $this->TLS_clients_checks_result[$host]['ca'][$type]['clientcertinfo']['issuer'] = $tlsclient['issuerCA'];
141
            foreach ($tlsclient['certificates'] as $k => $cert) {
142
                $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['status'] = $cert['status'];
143
                $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['message'] = $this->TLS_certkeys[$cert['status']];
144
                $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['expected'] = $cert['expected'];
145
                $add = ' -cert ' . ROOT . '/config/cli-certs/' . $cert['public'] . ' -key ' . ROOT . '/config/cli-certs/' . $cert['private'];
146
                if (!isset($this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k])) {
147
                    $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k] = [];
148
                }
149
                $opensslbabble = $this->execOpensslClient($host, $add, $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]);
150
                $res = $this->opensslClientsResult($host, $opensslbabble, $this->TLS_clients_checks_result, $type, $k);
151
                if ($cert['expected'] == 'PASS') {
152
                    if (!$this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['connected']) {
153
                        if (($tlsclient['status'] == 'ACCREDITED') && ($cert['status'] == 'CORRECT')) {
154
                            $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['returncode'] = RADIUSTests::CERTPROB_NOT_ACCEPTED;
155
                            $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['finalerror'] = 1;
156
                            break;
157
                        }
158
                    }
159
                } else {
160
                    if ($this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['connected']) {
161
                        $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['returncode'] = RADIUSTests::CERTPROB_WRONGLY_ACCEPTED;
162
                    }
163
164
                    if (($this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['reason'] == RADIUSTests::CERTPROB_UNKNOWN_CA) && ($tlsclient['status'] == 'ACCREDITED') && ($cert['status'] == 'CORRECT')) {
165
                        $this->TLS_clients_checks_result[$host]['ca'][$type]['certificate'][$k]['finalerror'] = 1;
166
                        echo "koniec zabawy2<br>";
167
                        break;
168
                    }
169
                }
170
            }
171
        }
172
        return $res;
173
    }
174
175
    /**
176
     * This function executes openssl s_client command
177
     * 
178
     * @param string $host        IP address
179
     * @param string $arg         arguments to add to the openssl command 
180
     * @param array  $testresults by-reference: the testresults array we are writing into
181
     * @return array result of openssl s_client ...
182
     */
183
    private function execOpensslClient($host, $arg, &$testresults) {
184
// we got the IP address either from DNS (guaranteeing well-formedness)
185
// or from filter_var'ed user input. So it is always safe as an argument
186
// but code analysers want this more explicit, so here is this extra
187
// call to escapeshellarg()
188
        $escapedHost = escapeshellarg($host);
189
        $this->loggerInstance->debug(4, CONFIG['PATHS']['openssl'] . " s_client -connect " . $escapedHost . " -tls1 -CApath " . ROOT . "/config/ca-certs/ $arg 2>&1\n");
190
        $time_start = microtime(true);
191
        $opensslbabble = [];
192
        $result = 999; // likely to become zero by openssl; don't want to initialise to zero, could cover up exec failures
193
        exec(CONFIG['PATHS']['openssl'] . " s_client -connect " . $escapedHost . " -tls1 -CApath " . ROOT . "/config/ca-certs/ $arg 2>&1", $opensslbabble, $result);
194
        $time_stop = microtime(true);
195
        $testresults['time_millisec'] = floor(($time_stop - $time_start) * 1000);
196
        $testresults['returncode'] = $result;
197
        return $opensslbabble;
198
    }
199
200
    /**
201
     * This function parses openssl s_client result
202
     * 
203
     * @param string $host          IP:port
204
     * @param array  $opensslbabble openssl command output
205
     * @param array  $testresults   by-reference: pointer to results array we write into
206
     * @return int return code
207
     */
208
    private function opensslCAResult($host, $opensslbabble, &$testresults) {
209
        $res = RADIUSTests::RETVAL_OK;
210
        if (preg_match('/connect: Connection refused/', implode($opensslbabble))) {
211
            $testresults[$host]['status'] = RADIUSTests::RETVAL_CONNECTION_REFUSED;
212
            $res = RADIUSTests::RETVAL_INVALID;
213
        }
214
        if (preg_match('/verify error:num=19/', implode($opensslbabble))) {
215
            $testresults[$host]['cert_oddity'] = RADIUSTests::CERTPROB_UNKNOWN_CA;
216
            $testresults[$host]['status'] = RADIUSTests::RETVAL_INVALID;
217
            $res = RADIUSTests::RETVAL_INVALID;
218
        }
219
        if (preg_match('/verify return:1/', implode($opensslbabble))) {
220
            $testresults[$host]['status'] = RADIUSTests::RETVAL_OK;
221
            $servercertStage1 = implode("\n", $opensslbabble);
222
            $servercert = preg_replace("/.*(-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----\n).*/s", "$1", $servercertStage1);
223
            $data = openssl_x509_parse($servercert);
224
            $testresults[$host]['certdata']['subject'] = $data['name'];
225
            $testresults[$host]['certdata']['issuer'] = $this->getCertificateIssuer($data);
226
            if (($altname = $this->getCertificatePropertyField($data, 'subjectAltName'))) {
227
                $testresults[$host]['certdata']['extensions']['subjectaltname'] = $altname;
228
            }
229
            $oids = $this->propertyCheckPolicy($data);
230
            if (!empty($oids)) {
231
                foreach ($oids as $resultArrayKey => $o) {
232
                    $testresults[$host]['certdata']['extensions']['policyoid'][] = " $o ($resultArrayKey)";
233
                }
234
            }
235
            if (($crl = $this->getCertificatePropertyField($data, 'crlDistributionPoints'))) {
236
                $testresults[$host]['certdata']['extensions']['crlDistributionPoint'] = $crl;
237
            }
238
            if (($ocsp = $this->getCertificatePropertyField($data, 'authorityInfoAccess'))) {
239
                $testresults[$host]['certdata']['extensions']['authorityInfoAccess'] = $ocsp;
240
            }
241
        }
242
        return $res;
243
    }
244
245
    /**
246
     * This function parses openssl s_client result
247
     * 
248
     * @param string $host           IP:port
249
     * @param array  $opensslbabble  openssl command output
250
     * @param array  $testresults    by-reference: pointer to results array we write into
251
     * @param string $type           type of certificate
252
     * @param int    $resultArrayKey results array key
253
     * @return int return code
254
     */
255
    private function opensslClientsResult($host, $opensslbabble, &$testresults, $type = '', $resultArrayKey = 0) {
256
        $res = RADIUSTests::RETVAL_OK;
257
        $ret = $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['returncode'];
258
        $output = implode($opensslbabble);
259
        if ($ret == 0) {
260
            $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['connected'] = 1;
261
        } else {
262
            $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['connected'] = 0;
263
            if (preg_match('/connect: Connection refused/', implode($opensslbabble))) {
264
                $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['returncode'] = RADIUSTests::RETVAL_CONNECTION_REFUSED;
265
                $resComment = _("No TLS connection established: Connection refused");
266
            } elseif (preg_match('/sslv3 alert certificate expired/', $output)) {
267
                $resComment = _("certificate expired");
268
            } elseif (preg_match('/sslv3 alert certificate revoked/', $output)) {
269
                $resComment = _("certificate was revoked");
270
            } elseif (preg_match('/SSL alert number 46/', $output)) {
271
                $resComment = _("bad policy");
272
            } elseif (preg_match('/tlsv1 alert unknown ca/', $output)) {
273
                $resComment = _("unknown authority");
274
                $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['reason'] = RADIUSTests::CERTPROB_UNKNOWN_CA;
275
            } else {
276
                $resComment = _("unknown authority or no certificate policy or another problem");
277
            }
278
            $testresults[$host]['ca'][$type]['certificate'][$resultArrayKey]['resultcomment'] = $resComment;
279
        }
280
        return $res;
281
    }
282
283
        /**
284
     * This function parses a X.509 cert and returns all certificatePolicies OIDs
285
     * 
286
     * @param array $cert (returned from openssl_x509_parse) 
287
     * @return array of OIDs
288
     */
289
    private function propertyCheckPolicy($cert) {
290
        $oids = [];
291
        if ($cert['extensions']['certificatePolicies']) {
292
            foreach (CONFIG_DIAGNOSTICS['RADIUSTESTS']['TLS-acceptableOIDs'] as $key => $oid) {
293
                if (preg_match("/Policy: $oid/", $cert['extensions']['certificatePolicies'])) {
294
                    $oids[$key] = $oid;
295
                }
296
            }
297
        }
298
        return $oids;
299
    }
300
        /**
301
     * This function parses a X.509 cert and returns the value of $field
302
     * 
303
     * @param array $cert (returned from openssl_x509_parse) 
304
     * @return string value of the issuer field or ''
305
     */
306
    private function getCertificateIssuer($cert) {
307
        $issuer = '';
308
        foreach ($cert['issuer'] as $key => $val) {
309
            if (is_array($val)) {
310
                foreach ($val as $v) {
311
                    $issuer .= "/$key=$v";
312
                }
313
            } else {
314
                $issuer .= "/$key=$val";
315
            }
316
        }
317
        return $issuer;
318
    }
319
    /**
320
     * This function parses a X.509 cert and returns the value of $field
321
     * 
322
     * @param array  $cert  (returned from openssl_x509_parse) 
323
     * @param string $field the field to search for
324
     * @return string value of the extention named $field or ''
325
     */
326
    private function getCertificatePropertyField($cert, $field) {
327
        if ($cert['extensions'][$field]) {
328
            return $cert['extensions'][$field];
329
        }
330
        return '';
331
    }
332
333
}
334