Passed
Push — master ( 39accf...7eb5c8 )
by Stefan
03:42
created

X509::processCertificate()   D

Complexity

Conditions 17
Paths 200

Size

Total Lines 75
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 75
rs 4.9162
c 0
b 0
f 0
cc 17
eloc 50
nc 200
nop 1

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
/*
4
 * ******************************************************************************
5
 * Copyright 2011-2017 DANTE Ltd. and GÉANT on behalf of the GN3, GN3+, GN4-1 
6
 * and GN4-2 consortia
7
 *
8
 * License: see the web/copyright.php file in the file structure
9
 * ******************************************************************************
10
 */
11
12
/** This file contains the X509 class.
13
 *
14
 * @author Stefan Winter <[email protected]>
15
 * @author Tomasz Wolniewicz <[email protected]>
16
 *
17
 * @package Developer
18
 */
19
20
namespace core\common;
21
22
use Exception;
23
24
/**
25
 * This class contains handling functions for X.509 certificates
26
 *
27
 * @author Stefan Winter <[email protected]>
28
 * @author Tomasz Wolniewicz <[email protected]>
29
 *
30
 * @license see LICENSE file in root directory
31
 *
32
 * @package Developer
33
 */
34
class X509 {
35
36
    const KNOWN_PUBLIC_KEY_ALGORITHMS = [0 => "rsaEncryption", 1 => "id-ecPublicKey"];
37
38
    /**
39
     * transform PEM formatted certificate to DER format
40
     *
41
     *  @param string $pemData blob of data, which is hopefully a PEM certificate
42
     *  @return string the DER representation of the certificate
43
     *
44
     *  @author http://php.net/manual/en/ref.openssl.php (comment from 29-Mar-2007)
45
     */
46
    public function pem2der(string $pemData) {
47
        $begin = "CERTIFICATE-----";
48
        $end = "-----END";
49
        $pemDataTemp = substr($pemData, strpos($pemData, $begin) + strlen($begin));
50
        if ($pemDataTemp === FALSE) { // this is not allowed to happen, we always have clean input here
1 ignored issue
show
introduced by
The condition $pemDataTemp === FALSE can never be true.
Loading history...
51
            throw new Exception("No BEGIN marker found in guaranteed PEM data!");
52
        }
53
        $markerPosition = strpos($pemDataTemp, $end);
54
        if ($markerPosition === FALSE) {
1 ignored issue
show
introduced by
The condition $markerPosition === FALSE can never be true.
Loading history...
55
            throw new Exception("No END marker found in guaranteed PEM data!");
56
        }
57
        $pemDataTemp2 = substr($pemDataTemp, 0, $markerPosition);
58
        if ($pemDataTemp2 === FALSE) { // this is not allowed to happen, we always have clean input here
1 ignored issue
show
introduced by
The condition $pemDataTemp2 === FALSE can never be true.
Loading history...
59
            throw new Exception("Impossible: END marker cutting resulted in an empty string or error?!");
60
        }
61
        $der = base64_decode($pemDataTemp2);
62
        if ($der === FALSE) {
1 ignored issue
show
introduced by
The condition $der === FALSE can never be true.
Loading history...
63
            throw new Exception("Invalid DER data after extracting guaranteed PEM data!");
64
        }
65
        return $der;
66
    }
67
68
    /**
69
     * transform DER formatted certificate to PEM format
70
     * 
71
     * @param string $derData blob of DER data
72
     * @return string the PEM representation of the certificate
73
     */
74
    public function der2pem($derData) {
75
        $pem = chunk_split(base64_encode($derData), 64, "\n");
76
        $pem = "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n";
77
        return $pem;
78
    }
79
80
    /**
81
     * prepare PEM and DER formats, MD5 and SHA1 fingerprints and subject of the certificate
82
     *
83
     * returns an array with the following fields:
84
     * <pre> uuid
85
     * pem	certificate in PEM format
86
     * der	certificate in DER format
87
     * md5	MD5 fingerprint
88
     * sha1	SHA1 fingerprint
89
     * name	certificate subject
90
     * root value 1 if root certificate 0 otherwise
91
     * ca   value 1 if CA certificate 0 otherwise
92
     *
93
     * </pre>
94
     * @param string $cadata certificate in ether PEM or DER format
95
     * @return array|false
96
     */
97
    public function processCertificate($cadata) {
98
        if ($cadata === FALSE) { // we are expecting a string anyway
1 ignored issue
show
introduced by
The condition $cadata === FALSE can never be true.
Loading history...
99
            return FALSE;
100
        }
101
        $pemBegin = strpos($cadata, "-----BEGIN CERTIFICATE-----");
102
        if ($pemBegin !== FALSE) {
1 ignored issue
show
introduced by
The condition $pemBegin !== FALSE can never be false.
Loading history...
103
            $pemEnd = strpos($cadata, "-----END CERTIFICATE-----") + 25;
104
            if ($pemEnd !== FALSE) {
1 ignored issue
show
introduced by
The condition $pemEnd !== FALSE can never be false.
Loading history...
105
                $cadata = substr($cadata, $pemBegin, $pemEnd - $pemBegin);
106
                if ($cadata === FALSE) {
1 ignored issue
show
introduced by
The condition $cadata === FALSE can never be true.
Loading history...
107
                    throw new Exception("Impossible: despite having found BEGIN and END markers, unable to cut out substring!");
108
                }
109
            }
110
            $authorityDer = $this->pem2der($cadata);
111
            $authorityPem = $this->der2pem($authorityDer);
112
        } else {
113
            $authorityDer = $cadata;
114
            $authorityPem = $this->der2pem($cadata);
115
        }
116
117
        // check that the certificate is OK
118
        $myca = openssl_x509_read($authorityPem);
119
        if ($myca == FALSE) {
1 ignored issue
show
introduced by
The condition $myca == FALSE can never be true.
Loading history...
120
            return FALSE;
121
        }
122
        $mydetails = openssl_x509_parse($myca);
123
        if (!isset($mydetails['subject'])) {
124
            return FALSE;
125
        }
126
        $md5 = openssl_digest($authorityDer, 'MD5');
127
        $sha1 = openssl_digest($authorityDer, 'SHA1');
128
        $out = ["pem" => $authorityPem, "der" => $authorityDer, "md5" => $md5, "sha1" => $sha1, "name" => $mydetails['name']];
129
130
        $out['root'] = 0; // default, unless concinved otherwise below
131
        if ($mydetails['issuer'] === $mydetails['subject']) {
132
            $out['root'] = 1;
133
            $mydetails['type'] = 'root';
134
        }
135
        // again default: not a CA unless convinced otherwise
136
        $out['ca'] = 0; // we need to resolve this ambiguity
137
        $out['basicconstraints_set'] = 0;
138
        // if no basicContraints are set at all, this is a problem in itself
139
        // is this a CA? or not? Treat as server, but add a warning...
140
        if (isset($mydetails['extensions']['basicConstraints'])) {
141
            $out['ca'] = preg_match('/^CA:TRUE/', $mydetails['extensions']['basicConstraints']);
142
            $out['basicconstraints_set'] = 1;
143
        }
144
145
        if ($out['ca'] > 0 && $out['root'] == 0) {
146
            $mydetails['type'] = 'interm_ca';
147
        }
148
        if ($out['ca'] == 0 && $out['root'] == 0) {
149
            $mydetails['type'] = 'server';
150
        }
151
        $mydetails['sha1'] = $sha1;
152
        // the signature algorithm is available in PHP7 with the property "signatureTypeSN", example "RSA-SHA512"
153
        $out['full_details'] = $mydetails;
154
155
        $algoMatch = [];
156
        $keyLengthMatch = [];
157
        // we are also interested in the type and length of public key,
158
        // which ..._parse doesn't tell us :-(
159
        openssl_x509_export($myca, $output, FALSE);
160
        if (preg_match('/^\s+Public Key Algorithm:\s*(.*)\s*$/m', $output, $algoMatch) && in_array($algoMatch[1], X509::KNOWN_PUBLIC_KEY_ALGORITHMS)) {
161
            $out['full_details']['public_key_algorithm'] = $algoMatch[1];
162
        } else {
163
            $out['full_details']['public_key_algorithm'] = "UNKNOWN";
164
        }
165
        
166
        if ((preg_match('/^\s+Public-Key:\s*\((.*) bit\)\s*$/m', $output, $keyLengthMatch)) && is_numeric($keyLengthMatch[1])) {
167
            $out['full_details']['public_key_length'] = $keyLengthMatch[1];
168
        } else {
169
            $out['full_details']['public_key_length'] = 0; // if we don't know, assume an unsafe key length -> will trigger warning
170
        }
171
        return $out;
172
    }
173
174
    /**
175
     * split a certificate file into components 
176
     *
177
     * returns an array containing the PEM format of the certificate (s)
178
     * if the file contains multiple certificates it gets split into components
179
     *
180
     * @param string $cadata certificate in ether PEM or DER format
181
     * @return array
182
     */
183
    public function splitCertificate($cadata) {
184
        $returnarray = [];
185
        // maybe we got no real cert data at all? The code is hardened, but will
186
        // produce ugly WARNING level output in the logfiles, so let's avoid at least
187
        // the trivial case: if the file is empty, there's no cert in it
188
        if ($cadata == "") {
189
            return $returnarray;
190
        }
191
        $startPem = strpos($cadata, "-----BEGIN CERTIFICATE-----");
192
        if ($startPem !== FALSE) {
1 ignored issue
show
introduced by
The condition $startPem !== FALSE can never be false.
Loading history...
193
            $cadata = substr($cadata, $startPem);
194
            if ($cadata === FALSE) {
1 ignored issue
show
introduced by
The condition $cadata === FALSE can never be true.
Loading history...
195
                throw new Exception("Impossible: despite having found BEGIN marker, unable to cut out substring!");
196
            }
197
            $endPem = strpos($cadata, "-----END CERTIFICATE-----") + 25;
198
            $nextPem = strpos($cadata, "-----BEGIN CERTIFICATE-----", 30);
199
            while ($nextPem !== FALSE) {
200
                $returnarray[] = substr($cadata, 0, $endPem);
201
                $cadata = substr($cadata, $nextPem);
202
                $endPem = strpos($cadata, "-----END CERTIFICATE-----") + 25;
203
                $nextPem = strpos($cadata, "-----BEGIN CERTIFICATE-----", 30);
204
            }
205
            $returnarray[] = substr($cadata, 0, $endPem);
206
        } else {
207
            // we hand it over to der2pem (no user content coming in from any caller
208
            // so we know we work with valid cert data in the first place
209
            $returnarray[] = $this->der2pem($cadata);
210
        }
211
        return array_unique($returnarray);
212
    }
213
214
}
215