| Total Complexity | 28 |
| Total Lines | 178 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
| 1 | <?php |
||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 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
|
|||
| 99 | return FALSE; |
||
| 100 | } |
||
| 101 | $pemBegin = strpos($cadata, "-----BEGIN CERTIFICATE-----"); |
||
| 102 | if ($pemBegin !== FALSE) { |
||
|
1 ignored issue
–
show
|
|||
| 103 | $pemEnd = strpos($cadata, "-----END CERTIFICATE-----") + 25; |
||
| 104 | if ($pemEnd !== FALSE) { |
||
|
1 ignored issue
–
show
|
|||
| 105 | $cadata = substr($cadata, $pemBegin, $pemEnd - $pemBegin); |
||
| 106 | if ($cadata === FALSE) { |
||
|
1 ignored issue
–
show
|
|||
| 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
|
|||
| 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) { |
||
| 212 | } |
||
| 213 | |||
| 214 | } |
||
| 215 |