Subversion Repositories php_utils

Rev

Rev 2 | Rev 10 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
4
 * X.509 Utilities for PHP
5
 * Copyright 2011-2014 Daniel Marschall, ViaThinkSoft
6
 * Version 2014-11-17
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
 
21
# define('OPENSSL_EXEC', 'openssl');
22
# define('OPENSSL_EXEC', 'torify openssl');
23
define('OPENSSL_EXEC', 'vtor -cr 1 -- openssl');
24
 
25
# ToDo: For every function 2 modes: certFile, certPEM
26
 
27
function x_509_matching_issuer($cert, $issuer) {
28
        exec(OPENSSL_EXEC.' verify -purpose any -CApath /dev/null -CAfile '.escapeshellarg($issuer).' '.escapeshellarg($cert), $out, $code);
29
        $out = implode("\n", $out);
30
# Ab 1.0 wird hier ein Errorcode zurückgeliefert
31
#       if ($code != 0) return false;
32
 
33
# TODO
34
#                                  error 20 at 0 depth lookup:unable to get local issuer certificate
35
        $chain0_ok = strpos($out, "error 2 at 1 depth lookup:unable to get issuer certificate") !== false;
36
        $all_ok = substr($out, -2) == 'OK';
37
 
38
        $ok = $chain0_ok | $all_ok;
39
 
40
        return $ok;
41
}
42
 
43
function x_509_is_crl_file($infile) { # Only PEM files
44
        $cx = file($infile);
45
        return trim($cx[0]) == '-----BEGIN X509 CRL-----';
46
}
47
 
48
function x_509_chain($infile, $CApath) {
49
        $chain = array();
50
        $chain[] = $infile;
51
 
52
        while (true) {
53
                $out = array();
54
                exec(OPENSSL_EXEC.' x509 -issuer_hash -in '.escapeshellarg($infile).' -noout', $out, $code);
55
                if ($code != 0) return false;
56
                $hash = $out[0];
57
                unset($out);
58
 
59
#               $ary = glob($CApath . $hash . '.*');
60
#               $aryr = glob($CApath . $hash . '.r*');
61
 
62
                $ary = array();
63
                $aryr = array();
64
                $all_trusted = glob($CApath . '*.pem');
65
                foreach ($all_trusted as &$a) {
66
                        if (x_509_is_crl_file($a)) {
67
                                $out = array();
68
                                exec(OPENSSL_EXEC.' crl -hash -noout -in '.escapeshellarg($a), $out, $code);
69
                                if ($code != 0) return false;
70
                                $this_hash = trim($out[0]);
71
                                unset($out);
72
# echo "CRL $a : $this_hash == $hash<br>\n";
73
                                if ($this_hash == $hash) {
74
                                        $aryr[] = $a;
75
                                }
76
                                if ($code != 0) return false;
77
                        } else {
78
                                $out = array();
79
                                exec(OPENSSL_EXEC.' x509 -subject_hash -noout -in '.escapeshellarg($a), $out, $code);
80
                                if ($code != 0) return false;
81
                                $this_hash = trim($out[0]);
82
                                unset($out);
83
# echo "CERT $a : $this_hash == $hash<br>\n";
84
                                if ($this_hash == $hash) {
85
                                        $ary[] = $a;
86
                                }
87
                        }
88
                }
89
 
90
                $found = false;
91
# echo "Searching issuer for $infile... (Hash = $hash)<br>\n";
92
                foreach ($ary as &$a) {
93
                        if (in_array($a, $aryr)) continue;
94
 
95
# echo "Check $a...<br>\n";
96
                        if (x_509_matching_issuer($infile, $a)) {
97
# echo "Found! New file is $a<br>\n";
98
                                $found = true;
99
                                $infile = $a;
100
 
101
                                if (in_array($a, $chain)) {
102
                                        # echo "Finished.\n";
103
                                        return $chain;
104
                                }
105
 
106
                                $chain[] = $a;
107
                                break;
108
                        }
109
                }
110
                if (!$found) {
111
                        # echo "No issuer found!\n";
112
                        return false;
113
                }
114
        }
115
}
116
 
117
function x_509_get_ocsp_uris($infile) {
118
        exec(OPENSSL_EXEC.' x509 -ocsp_uri -in '.escapeshellarg($infile).' -noout', $out, $code);
119
        if ($code != 0) return false;
120
        return $out;
121
}
122
 
123
 
124
function x_509_ocsp_check_chain($infile, $CApath) {
125
        return '(Skipped)'; # TODO: we need caching, otherwise the page is too slow
126
 
127
        $x = x_509_chain($infile, $CApath);
128
 
129
        if ($x === false) {
130
                return 'Error: Could not complete chain!';
131
        }
132
 
133
        # echo 'Chain: ';
134
        # print_r($x);
135
 
136
        $found_ocsp = false;
137
        $diag_nonce_err = false;
138
        $diag_verify_err = false;
139
        $diag_revoked = false;
140
        $diag_unknown = false;
141
 
142
        foreach ($x as $n => &$y) {
143
                if (isset($x[$n+1])) {
144
                        $issuer = $x[$n+1];
145
                } else {
146
                        $issuer = $y; // Root
147
                }
148
 
149
                $uris = x_509_get_ocsp_uris($y);
150
 
151
                foreach ($uris as &$uri) {
152
                        $found_ocsp = true;
153
 
154
                        $out = array();
155
                        $xx = parse_url($uri);
156
                        $host = $xx['host'];
157
#                       $cmd = OPENSSL_EXEC." ocsp -issuer ".escapeshellarg($issuer)." -cert ".escapeshellarg($y)." -url ".escapeshellarg($uri)." -CApath ".escapeshellarg($CApath)." -VAfile ".escapeshellarg($issuer)." -nonce -header 'HOST' ".escapeshellarg($host)." -header 'User-Agent' 'Mozilla/5.0 (Windows NT 6.1; rv23.0) Gecko/20100101 Firefox/23.0' 2>&1" /* -text */;
158
# TODO: trusted.pem nicht hartcoden
159
                        $cmd = OPENSSL_EXEC." ocsp -issuer ".escapeshellarg($issuer)." -cert ".escapeshellarg($y)." -url ".escapeshellarg($uri)." -CAfile ".escapeshellarg($CApath.'/../trusted.pem')." -VAfile ".escapeshellarg($issuer)." -nonce -header 'HOST' ".escapeshellarg($host)." -header 'User-Agent' 'Mozilla/5.0 (Windows NT 6.1; rv23.0) Gecko/20100101 Firefox/23.0' 2>&1" /* -text */;
160
#echo $cmd;
161
                        exec($cmd, $out, $code);
162
                        if ($code != 0) {
163
                                if (($out[0] == 'Error querying OCSP responsder') ||
164
                                    ($out[0] == 'Error querying OCSP responder')) {
165
                                        # TODO: openssl has a typo 'Error querying OCSP responsder'
166
                                        # TODO: why does this error occour for comodo CA?
167
                                        return "Error querying OCSP responder (Code $code)";
168
                                }
169
                                # print_r($out);
170
                                return 'Error: OpenSSL-Exec failure ('.$code.')!';
171
                        }
172
 
173
                        $outc = implode("\n", $out);
174
                        if (strpos($outc, "Response verify OK") === false) $diag_verify_err = true;
175
                        if (strpos($outc, "WARNING: no nonce in response") !== false) $diag_nonce_err = true;
176
                        # We are currently not watching for other warnings (ToDo)
177
 
178
                        if (strpos($outc, "$y: unknown") !== false) {
179
                                $diag_unknown = true;
180
                        } else if (strpos($outc, "$y: revoked") !== false) {
181
                                $diag_revoked = true;
182
                        } else if (strpos($outc, "$y: good") === false) {
183
#echo "C = $outc<br>\n";
184
#Ã TODO:
185
# COMODO sagt
186
# C = Responder Error: unauthorized
187
# STARTCOM sagt
188
# C = Responder Error: malformedrequest
189
                                return "Error: Unexpected OCSP state! ($outc)";
190
                        }
191
 
192
                        # print_r($out);
193
                        unset($out);
194
                }
195
        }
196
 
197
        # echo "Found OCSP = ".($found_ocsp ? 1 : 0)."\n";
198
        # echo "Diag Nonce Error = ".($diag_nonce_err ? 1 : 0)."\n";
199
        # echo "Diag Verify Error = ".($diag_verify_err ? 1 : 0)."\n";
200
        # echo "Diag Revoked Error = ".($diag_revoked ? 1 : 0)."\n";
201
        # echo "Diag Unknown Error = ".($diag_unknown ? 1 : 0)."\n";
202
 
203
        if (!$found_ocsp) {
204
                return 'No OCSP responders found in chain.';
205
        }
206
 
207
        if ($diag_verify_err) {
208
                return 'Error: OCSP Verification failure!';
209
        }
210
 
211
        if ($diag_revoked) {
212
                return 'Error: Some certs are revoked!';
213
        }
214
 
215
        if ($diag_unknown) {
216
                return 'Warning: Some certs have unknown state!';
217
        }
218
 
219
        if ($diag_nonce_err) {
220
                return 'OK, but NONCE missing';
221
        }
222
 
223
        return 'OK';
224
}
225
 
226
function _opensslVerify($cert, $mode = 0, $crl_mode = 0) {
227
        # mode
228
        # 0 = cert is a file
229
        # 1 = cert is pem string
230
 
231
        # crl_mode
232
        # 0 = no crl check
233
        # 1 = 1 crl check
234
        # 2 = all crl check
235
 
236
        $params = '';
237
        if ($crl_mode == 0) {
238
                $params = '';
239
        } else if ($crl_mode == 1) {
240
                $params = '-crl_check ';
241
        } else if ($crl_mode == 2) {
242
                $params = '-crl_check_all ';
243
        } else {
244
                return false;
245
        }
246
 
247
        if ($mode == 0) {
248
#               $cmd = OPENSSL_EXEC.' verify '.$params.' -CApath '.escapeshellarg(__DIR__.'/../ca/trusted/').' '.escapeshellarg($cert);
249
                $cmd = OPENSSL_EXEC.' verify '.$params.' -CAfile '.escapeshellarg(__DIR__.'/../ca/trusted.pem').' '.escapeshellarg($cert);
250
        } else if ($mode == 1) {
251
#               $cmd = 'echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' verify '.$params.' -CApath '.escapeshellarg(__DIR__.'/../ca/trusted/');
252
                $cmd = 'echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' verify '.$params.' -CAfile '.escapeshellarg(__DIR__.'/../ca/trusted.pem');
253
        } else {
254
                return false;
255
        }
256
        $out = array();
257
        exec($cmd, $out, $code);
258
 
259
        if ($code != 0) return false;
260
 
261
        return $out;
262
}
263
 
264
function opensslVerify($cert, $mode = 0) {
265
        # 0 = cert is a file
266
        # 1 = cert is pem string
267
 
268
        $out = _opensslVerify($cert, $mode, 0);
269
        if ($out === false) return 'Internal error';
270
        $outtext = implode("\n", $out);
271
 
272
        $out_crl = _opensslVerify($cert, $mode, 2);
273
        if ($out_crl === false) return 'Internal error';
274
        $outtext_crl = implode("\n", $out_crl);
275
 
276
        if (strpos($outtext, "unable to get local issuer certificate") !== false) {
277
                return 'CA unknown';
278
        } else if (strpos($outtext, "certificate signature failure") !== false) {
279
                return 'Fraudulent!';
280
        }
281
 
282
        $stat_expired = (strpos($outtext, "certificate has expired") !== false);
283
        $stat_revoked = (strpos($outtext_crl, "certificate revoked") !== false);
284
 
285
        # (ToDo) We are currently not looking for warnings
286
        # $stat_crl_expired = (strpos($outtext_crl, "CRL has expired") !== false);
287
 
288
        if ($stat_expired && $stat_revoked) {
289
                return 'Expired & Revoked';
290
        } else if ($stat_revoked) {
291
                return 'Revoked';
292
        } else if ($stat_expired) {
293
                return 'Expired';
294
        }
295
 
296
        if (strpos($out[0], ': OK') !== false) {
297
                return 'Verified';
298
        }
299
 
300
        return 'Unknown error';
301
}
302
 
303
function getTextdump($cert, $mode = 0, $format = 0) {
304
        # mode
305
        # 0 = cert is a file
306
        # 1 = cert is pem string
307
 
308
        # format
309
        # 0 = normal
310
        # 1 = nameopt
311
 
312
        if ($format == 0) {
313
                $params = '';
314
        } else if ($format == 1) {
315
                $params = ' -nameopt "esc_ctrl, esc_msb, sep_multiline, space_eq, lname"';
316
        } else {
317
                return false;
318
        }
319
 
320
        if ($mode == 0) {
321
                exec(OPENSSL_EXEC.' x509 -noout -text'.$params.' -in '.escapeshellarg($cert), $out, $code);
322
        } else if ($mode == 1) {
323
                exec('echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' x509 -noout -text'.$params, $out, $code);
324
        } else {
325
                return false;
326
        }
327
 
328
        if ($code != 0) return false;
329
 
330
        $text = implode("\n", $out);
331
 
332
        $text = str_replace("\n\n", "\n", $text); # TODO: repeat until no \n\n exist anymore
333
 
334
        return $text;
335
}
336
 
337
function getAttributes($cert, $mode = 0, $issuer = false, $longnames = false) {
338
        # mode
339
        # 0 = cert is a file
340
        # 1 = cert is pem string
341
 
342
        if ($longnames) {
343
                $params = ' -nameopt "esc_ctrl, esc_msb, sep_multiline, space_eq, lname"';
344
        } else {
345
                $params = ' -nameopt "esc_ctrl, esc_msb, sep_multiline, space_eq"';
346
        }
347
 
348
        if ($issuer) {
349
                $params .= ' -issuer';
350
        } else {
351
                $params .= ' -subject';
352
        }
353
 
354
        if ($mode == 0) {
355
                exec(OPENSSL_EXEC.' x509 -noout'.$params.' -in '.escapeshellarg($cert), $out, $code);
356
        } else if ($mode == 1) {
357
                exec('echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' x509 -noout'.$params, $out, $code);
358
        } else {
359
                return false;
360
        }
361
 
362
        $attributes = array();
363
        foreach ($out as $n => &$o) {
364
                if ($n == 0) continue;
365
                preg_match("|    (.*) = (.*)$|ismU", $o, $m);
366
                if (!isset($attributes[$m[1]])) $attributes[$m[1]] = array();
367
                $attributes[$m[1]][] = $m[2];
368
        }
369
 
370
        return $attributes;
371
}
372
 
373
function openssl_get_sig_base64($cert, $mode = 0) {
374
        # mode
375
        # 0 = cert is a file
376
        # 1 = cert is pem string
377
 
378
        $out = array();
379
        if ($mode == 0) {
380
                exec(OPENSSL_EXEC.' x509 -noout'.$params.' -in '.escapeshellarg($cert), $out, $code);
381
        } else if ($mode == 1) {
382
                exec('echo '.escapeshellarg($cert).' | '.OPENSSL_EXEC.' x509 -noout'.$params, $out, $code);
383
        } else {
384
                return false;
385
        }
386
        $dump = implode("\n", $out);
387
 
388
/*
389
 
390
    Signature Algorithm: sha1WithRSAEncryption
391
        65:f0:6f:f0:1d:66:a4:fe:d1:38:85:6f:5e:06:7b:f3:a7:08:
392
        ...
393
        1a:13:37
394
 
395
*/
396
 
397
        $regex = "@\n {4}Signature Algorithm: (\S+)\n(( {8}([a-f0-9][a-f0-9]:){18}\n)* {8}([a-f0-9][a-f0-9](:[a-f0-9][a-f0-9]){0,17}\n))@sm";
398
        preg_match_all($regex, "$dump\n", $m);
399
        if (!isset($m[2][0])) return false;
400
        $x = preg_replace("@[^a-z0-9]@", "", $m[2][0]);
401
        $x = hex2bin($x);
402
        return base64_encode($x);
403
}