Subversion Repositories php_utils

Rev

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

  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. }
  404.