Subversion Repositories oidplus

Rev

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

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. use ViaThinkSoft\OIDplus\OIDplus;
  21. use ViaThinkSoft\OIDplus\OIDplusException;
  22.  
  23. /**
  24.  * @param string $privKey
  25.  * @return bool
  26.  */
  27. function is_privatekey_encrypted(string $privKey): bool {
  28.         return strpos($privKey,'BEGIN ENCRYPTED PRIVATE KEY') !== false;
  29. }
  30.  
  31. /**
  32.  * @param string $privKey
  33.  * @param string $pubKey
  34.  * @return bool
  35.  */
  36. function verify_private_public_key(string $privKey, string $pubKey): bool {
  37.         if (!function_exists('openssl_public_encrypt')) return false;
  38.         try {
  39.                 if (empty($privKey)) return false;
  40.                 if (empty($pubKey)) return false;
  41.                 $data = generateRandomString(25);
  42.                 $encrypted = '';
  43.                 $decrypted = '';
  44.                 if (!@openssl_public_encrypt($data, $encrypted, $pubKey)) return false;
  45.                 if (!@openssl_private_decrypt($encrypted, $decrypted, $privKey)) return false;
  46.                 return $decrypted == $data;
  47.         } catch (\Exception $e) {
  48.                 return false;
  49.         }
  50. }
  51.  
  52. /**
  53.  * @param string $privKeyOld
  54.  * @param string|null $passphrase_old
  55.  * @param string|null $passphrase_new
  56.  * @return false|string
  57.  */
  58. function change_private_key_passphrase(string $privKeyOld, string $passphrase_old=null, string $passphrase_new=null) {
  59.         $pkey_config = array(
  60.             //"digest_alg" => "sha512",
  61.             //"private_key_bits" => 2048,
  62.             //"private_key_type" => OPENSSL_KEYTYPE_RSA,
  63.             "config" => class_exists("\\ViaThinkSoft\\OIDplus\\OIDplus") ? OIDplus::getOpenSslCnf() : @getenv('OPENSSL_CONF')
  64.         );
  65.         $privKeyNew = @openssl_pkey_get_private($privKeyOld, $passphrase_old);
  66.         if ($privKeyNew === false) return false;
  67.         if (!@openssl_pkey_export($privKeyNew, $privKeyNewExport, $passphrase_new, $pkey_config)) return false;
  68.         if ($privKeyNewExport === "") return false;
  69.         return "$privKeyNewExport";
  70. }
  71.  
  72. /**
  73.  * @param string $privKey
  74.  * @param string $passphrase
  75.  * @return false|string
  76.  */
  77. function decrypt_private_key(string $privKey, string $passphrase) {
  78.         return change_private_key_passphrase($privKey, $passphrase, null);
  79. }
  80.  
  81. /**
  82.  * @param string $privKey
  83.  * @param string $passphrase
  84.  * @return false|string
  85.  */
  86. function encrypt_private_key(string $privKey, string $passphrase) {
  87.         return change_private_key_passphrase($privKey, null, $passphrase);
  88. }
  89.  
  90. /**
  91.  * @param string $data
  92.  * @return int
  93.  */
  94. function smallhash(string $data): int { // get 31 bits from SHA1. Values 0..2147483647
  95.         return (hexdec(substr(sha1($data),-4*2)) & 0x7FFFFFFF);
  96. }
  97.  
  98. /**
  99.  * @param string $name
  100.  * @return array
  101.  */
  102. function split_firstname_lastname(string $name): array {
  103.         $ary = explode(' ', $name);
  104.         $last_name = array_pop($ary);
  105.         $first_name = implode(' ', $ary);
  106.         return array($first_name, $last_name);
  107. }
  108.  
  109. /**
  110.  * @return void
  111.  */
  112. function originHeaders() {
  113.         // CORS
  114.         // Author: Till Wehowski
  115.         // TODO: add to class OIDplus
  116.  
  117.         header("Access-Control-Allow-Credentials: true");
  118.         header("Access-Control-Allow-Origin: ".strip_tags(((isset($_SERVER['HTTP_ORIGIN'])) ? $_SERVER['HTTP_ORIGIN'] : "*")));
  119.  
  120.         header("Access-Control-Allow-Headers: If-None-Match, X-Requested-With, Origin, X-Frdlweb-Bugs, Etag, X-Forgery-Protection-Token, X-CSRF-Token");
  121.  
  122.         if (isset($_SERVER['HTTP_ORIGIN'])) {
  123.                 header('X-Frame-Options: ALLOW-FROM '.$_SERVER['HTTP_ORIGIN']);
  124.         } else {
  125.                 header_remove("X-Frame-Options");
  126.         }
  127.  
  128.         $expose = array('Etag', 'X-CSRF-Token');
  129.         foreach (headers_list() as $num => $header) {
  130.                 $h = explode(':', $header);
  131.                 $expose[] = trim($h[0]);
  132.         }
  133.         header("Access-Control-Expose-Headers: ".implode(',',$expose));
  134.  
  135.         header("Vary: Origin");
  136. }
  137.  
  138. if (!function_exists('mb_wordwrap')) {
  139.         /**
  140.          * @param string $str
  141.          * @param int $width
  142.          * @param string $break
  143.          * @param bool $cut
  144.          * @return string
  145.          */
  146.         function mb_wordwrap(string $str, int $width = 75, string $break = "\n", bool $cut = false): string {
  147.                 // https://stackoverflow.com/a/4988494/488539
  148.                 $lines = explode($break, $str);
  149.                 foreach ($lines as &$line) {
  150.                         $line = rtrim($line);
  151.                         if (mb_strlen($line) <= $width) {
  152.                                 continue;
  153.                         }
  154.                         $words = explode(' ', $line);
  155.                         $line = '';
  156.                         $actual = '';
  157.                         foreach ($words as $word) {
  158.                                 if (mb_strlen($actual.$word) <= $width) {
  159.                                         $actual .= $word.' ';
  160.                                 } else {
  161.                                         if ($actual != '') {
  162.                                                 $line .= rtrim($actual).$break;
  163.                                         }
  164.                                         $actual = $word;
  165.                                         if ($cut) {
  166.                                                 while (mb_strlen($actual) > $width) {
  167.                                                         $line .= mb_substr($actual, 0, $width).$break;
  168.                                                         $actual = mb_substr($actual, $width);
  169.                                                 }
  170.                                         }
  171.                                         $actual .= ' ';
  172.                                 }
  173.                         }
  174.                         $line .= trim($actual);
  175.                 }
  176.                 return implode($break, $lines);
  177.         }
  178. }
  179.  
  180. /**
  181.  * @param string $out
  182.  * @param string $contentType
  183.  * @param string $filename
  184.  * @return void
  185.  */
  186. function httpOutWithETag(string $out, string $contentType, string $filename='') {
  187.         $etag = md5($out);
  188.         header("Etag: $etag");
  189.         header("Content-MD5: $etag"); // RFC 2616 clause 14.15
  190.         if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && (trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag)) {
  191.                 if (PHP_SAPI != 'cli') @http_response_code(304); // 304 Not Modified
  192.         } else {
  193.                 header("Content-Type: $contentType");
  194.                 if (!empty($filename)) {
  195.                         header('Content-Disposition:inline; filename="'.$filename.'"');
  196.                 }
  197.                 echo $out;
  198.         }
  199.         die();
  200. }
  201.  
  202. /**
  203.  * @param string $str
  204.  * @param array $args
  205.  * @return string
  206.  */
  207. function my_vsprintf(string $str, array $args): string {
  208.         $n = 1;
  209.         foreach ($args as $val) {
  210.                 $str = str_replace("%$n", $val, $str);
  211.                 $n++;
  212.         }
  213.         return str_replace("%%", "%", $str);
  214. }
  215.  
  216. /**
  217.  * @param string $str
  218.  * @param mixed ...$sprintfArgs
  219.  * @return string
  220.  * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
  221.  * @throws \ViaThinkSoft\OIDplus\OIDplusException
  222.  */
  223. function _L(string $str, ...$sprintfArgs): string {
  224.         static $translation_array = array();
  225.         static $translation_loaded = null;
  226.  
  227.         $str = trim($str);
  228.  
  229.         if (!class_exists(OIDplus::class)) {
  230.                 return my_vsprintf($str, $sprintfArgs);
  231.         }
  232.  
  233.         $lang = OIDplus::getCurrentLang();
  234.         $ta = OIDplus::getTranslationArray($lang);
  235.         $res = (isset($ta[$lang]) && isset($ta[$lang][$str])) ? $ta[$lang][$str] : $str;
  236.  
  237.         $res = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $res);
  238.  
  239.         return my_vsprintf($res, $sprintfArgs);
  240. }
  241.  
  242. /**
  243.  * @param array $params
  244.  * @param string $key
  245.  * @return void
  246.  * @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
  247.  * @throws \ViaThinkSoft\OIDplus\OIDplusException
  248.  */
  249. function _CheckParamExists(array $params, string $key) {
  250.         if (class_exists(OIDplusException::class)) {
  251.                 if (!isset($params[$key])) throw new OIDplusException(_L('Parameter %1 is missing', $key));
  252.         } else {
  253.                 if (!isset($params[$key])) throw new Exception(_L('Parameter %1 is missing', $key));
  254.         }
  255. }
  256.  
  257. /**
  258.  * @param string $cont
  259.  * @return array
  260.  */
  261. function extractHtmlContents(string $cont): array {
  262.         // make sure the program works even if the user provided HTML is not UTF-8
  263.         $cont = convert_to_utf8_no_bom($cont);
  264.  
  265.         $out_js = '';
  266.         $m = array();
  267.         preg_match_all('@<script[^>]*>(.+)</script>@ismU', $cont, $m);
  268.         foreach ($m[1] as $x) {
  269.                 $out_js = $x . "\n\n";
  270.         }
  271.  
  272.         $out_css = '';
  273.         $m = array();
  274.         preg_match_all('@<style[^>]*>(.+)</style>@ismU', $cont, $m);
  275.         foreach ($m[1] as $x) {
  276.                 $out_css = $x . "\n\n";
  277.         }
  278.  
  279.         $out_html = $cont;
  280.         $out_html = preg_replace('@^(.+)<body[^>]*>@isU', '', $out_html);
  281.         $out_html = preg_replace('@</body>.+$@isU', '', $out_html);
  282.         $out_html = preg_replace('@<title>.+</title>@isU', '', $out_html);
  283.         $out_html = preg_replace('@<h1>.+</h1>@isU', '', $out_html, 1);
  284.         $out_html = preg_replace('@<script[^>]*>(.+)</script>@ismU', '', $out_html);
  285.         $out_html = preg_replace('@<style[^>]*>(.+)</style>@ismU', '', $out_html);
  286.  
  287.         return array($out_html, $out_js, $out_css);
  288. }
  289.  
  290. /**
  291.  * @param string $password
  292.  * @param bool $raw_output
  293.  * @return string
  294.  * @throws Exception
  295.  */
  296. function sha3_512(string $password, bool $raw_output=false): string {
  297.         if (hash_supported_natively('sha3-512')) {
  298.                 return hash('sha3-512', $password, $raw_output);
  299.         } else {
  300.                 return \bb\Sha3\Sha3::hash($password, 512, $raw_output);
  301.         }
  302. }
  303.  
  304. /**
  305.  * @param string $message
  306.  * @param string $key
  307.  * @param bool $raw_output
  308.  * @return string
  309.  */
  310. function sha3_512_hmac(string $message, string $key, bool $raw_output=false): string {
  311.         // RFC 2104 HMAC
  312.         if (hash_hmac_supported_natively('sha3-512')) {
  313.                 return hash_hmac('sha3-512', $message, $key, $raw_output);
  314.         } else {
  315.                 return \bb\Sha3\Sha3::hash_hmac($message, $key, 512, $raw_output);
  316.         }
  317. }
  318.  
  319. /**
  320.  * @param string $password
  321.  * @param string $salt
  322.  * @param int $iterations
  323.  * @param int $length
  324.  * @param bool $binary
  325.  * @return string
  326.  */
  327. function sha3_512_pbkdf2(string $password, string $salt, int $iterations, int $length=0, bool $binary=false): string {
  328.         if (hash_pbkdf2_supported_natively('sha3-512')) {
  329.                 return hash_pbkdf2('sha3-512', $password, $salt, $iterations, $length, $binary);
  330.         } else {
  331.                 return \bb\Sha3\Sha3::hash_pbkdf2($password, $salt, $iterations, 512, $length, $binary);
  332.         }
  333. }
  334.  
  335. /**
  336.  * @return bool
  337.  */
  338. function url_post_contents_available(): bool {
  339.         return function_exists('curl_init');
  340. }
  341.  
  342. /**
  343.  * @param string $url
  344.  * @param array $params
  345.  * @param array $extraHeaders
  346.  * @param string $userAgent
  347.  * @return string|false
  348.  * @throws \ViaThinkSoft\OIDplus\OIDplusException
  349.  */
  350. function url_post_contents(string $url, array $params=array(), array $extraHeaders=array(), string $userAgent='ViaThinkSoft-OIDplus/2.0') {
  351.         $postFields = http_build_query($params);
  352.  
  353.         $headers = array(
  354.                 "User-Agent: $userAgent",
  355.                 "Content-Length: ".strlen($postFields)
  356.         );
  357.  
  358.         foreach ($extraHeaders as $name => $val) {
  359.                 $headers[] = "$name: $val";
  360.         }
  361.  
  362.         if (function_exists('curl_init')) {
  363.                 $ch = curl_init();
  364.                 if (class_exists(OIDplus::class)) {
  365.                         if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO, OIDplus::localpath() . 'vendor/cacert.pem');
  366.                 }
  367.                 curl_setopt($ch, CURLOPT_URL, $url);
  368.                 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
  369.                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  370.                 curl_setopt($ch, CURLOPT_POST, true);
  371.                 curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
  372.                 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  373.                 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  374.                 curl_setopt($ch, CURLOPT_AUTOREFERER, true);
  375.                 $res = @curl_exec($ch);
  376.                 $error_code = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
  377.                 @curl_close($ch);
  378.                 if ($error_code >= 400) return false;
  379.                 if ($res === false) return false;
  380.         } else {
  381.                 throw new OIDplusException(_L('The "%1" PHP extension is not installed at your system. Please enable the PHP extension <code>%2</code>.','CURL','php_curl'));
  382.         }
  383.         return $res;
  384. }
  385.  
  386. /**
  387.  * @param string $url
  388.  * @param array $extraHeaders
  389.  * @param string $userAgent
  390.  * @return string|false
  391.  */
  392. function url_get_contents(string $url, array $extraHeaders=array(), string $userAgent='ViaThinkSoft-OIDplus/2.0') {
  393.         $headers = array("User-Agent: $userAgent");
  394.         foreach ($extraHeaders as $name => $val) {
  395.                 $headers[] = "$name: $val";
  396.         }
  397.         if (function_exists('curl_init')) {
  398.                 $ch = curl_init();
  399.                 if (class_exists(OIDplus::class)) {
  400.                         if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO, OIDplus::localpath() . 'vendor/cacert.pem');
  401.                 }
  402.                 curl_setopt($ch, CURLOPT_URL, $url);
  403.                 curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
  404.                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  405.                 curl_setopt($ch, CURLOPT_POST, false);
  406.                 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  407.                 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
  408.                 curl_setopt($ch, CURLOPT_AUTOREFERER, true);
  409.                 $res = @curl_exec($ch);
  410.                 $error_code = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
  411.                 @curl_close($ch);
  412.                 if ($error_code >= 400) return false;
  413.                 if ($res === false) return false;
  414.         } else {
  415.                 // Attention: HTTPS only works if OpenSSL extension is enabled.
  416.                 // Our supplement does not help...
  417.                 $opts = [
  418.                         "http" => [
  419.                                 "method" => "GET",
  420.                                 "header" => implode("\r\n",$headers)."\r\n"
  421.                         ]
  422.                 ];
  423.                 $context = stream_context_create($opts);
  424.                 $res = @file_get_contents($url, false, $context);
  425.                 if ($res === false) return false;
  426.         }
  427.         return $res;
  428. }
  429.  
  430. /**
  431.  * @return string
  432.  */
  433. function getSortedQuery(): string {
  434.         // https://stackoverflow.com/a/51777249/488539
  435.         $url = [];
  436.         parse_str($_SERVER['QUERY_STRING'], $url);
  437.         ksort($url);
  438.         return http_build_query($url);
  439. }
  440.  
  441. /**
  442. * @param array &$rows
  443. * @param string $fieldName
  444. * @return void
  445. */
  446. function natsort_field(&$rows, string $fieldName) {
  447.         usort($rows, function($a,$b) use($fieldName) {
  448.                 if ($a[$fieldName] == $b[$fieldName]) return 0; // equal
  449.                 $ary = array(
  450.                         -1 => $a[$fieldName],
  451.                         1 => $b[$fieldName]
  452.                 );
  453.                 natsort($ary);
  454.                 $keys = array_keys($ary);
  455.                 return $keys[0];
  456.         });
  457. }
  458.  
  459. /**
  460.  * @param array $ary
  461.  * @return \stdClass
  462.  */
  463. function array_to_stdobj(array $ary): \stdClass {
  464.         $obj = new \stdClass;
  465.         foreach ($ary as $name => $val) {
  466.                 $obj->$name = $val;
  467.         }
  468.         return $obj;
  469. }
  470.  
  471. /**
  472.  * @param \stdClass $obj
  473.  * @return array
  474.  */
  475. function stdobj_to_array(\stdClass $obj): array {
  476.         $ary = array();
  477.         foreach ($obj as $name => $val) {
  478.                 $ary[$name] = $val;
  479.         }
  480.         return $ary;
  481. }
  482.