<?php
/*
* OIDplus 2.0
* Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use ViaThinkSoft\OIDplus\OIDplus;
use ViaThinkSoft\OIDplus\OIDplusException;
/**
* @param string $privKey
* @return bool
*/
function is_privatekey_encrypted(string $privKey): bool {
return strpos($privKey,'BEGIN ENCRYPTED PRIVATE KEY') !== false;
}
/**
* @param string $privKey
* @param string $pubKey
* @return bool
*/
function verify_private_public_key(string $privKey, string $pubKey): bool {
try {
if (empty($privKey)) return false;
if (empty($pubKey)) return false;
$data = generateRandomString(25);
$encrypted = '';
$decrypted = '';
return $decrypted == $data;
} catch (\Exception $e) {
return false;
}
}
/**
* @param string $privKeyOld
* @param string|null $passphrase_old
* @param string|null $passphrase_new
* @return false|string
*/
function change_private_key_passphrase(string $privKeyOld, string $passphrase_old=null, string $passphrase_new=null) {
//"digest_alg" => "sha512",
//"private_key_bits" => 2048,
//"private_key_type" => OPENSSL_KEYTYPE_RSA,
"config" => class_exists(OIDplus
::class) ? OIDplus
::getOpenSslCnf() : @getenv('OPENSSL_CONF')
);
if ($privKeyNew === false) return false;
if (!@openssl_pkey_export($privKeyNew, $privKeyNewExport, $passphrase_new, $pkey_config)) return false;
if ($privKeyNewExport === "") return false;
return "$privKeyNewExport";
}
/**
* @param string $privKey
* @param string $passphrase
* @return false|string
*/
function decrypt_private_key(string $privKey, string $passphrase) {
return change_private_key_passphrase($privKey, $passphrase, null);
}
/**
* @param string $privKey
* @param string $passphrase
* @return false|string
*/
function encrypt_private_key(string $privKey, string $passphrase) {
return change_private_key_passphrase($privKey, null, $passphrase);
}
/**
* @param string $data
* @return int
*/
function smallhash(string $data): int { // get 31 bits from SHA1. Values 0..2147483647
}
/**
* @param string $name
* @return array
*/
function split_firstname_lastname
(string
$name): array {
return array($first_name, $last_name);
}
/**
* @return void
*/
function originHeaders() {
// CORS
// Author: Till Wehowski
// TODO: add to class OIDplus
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Origin: ".strip_tags(((isset($_SERVER['HTTP_ORIGIN'])) ?
$_SERVER['HTTP_ORIGIN'] : "*")));
header("Access-Control-Allow-Headers: If-None-Match, X-Requested-With, Origin, X-Frdlweb-Bugs, Etag, X-Forgery-Protection-Token, X-CSRF-Token");
if (isset($_SERVER['HTTP_ORIGIN'])) {
header('X-Frame-Options: ALLOW-FROM '.$_SERVER['HTTP_ORIGIN']);
} else {
header_remove("X-Frame-Options");
}
$expose = array('Etag', 'X-CSRF-Token');
}
}
/**
* @param string $str
* @param int $width
* @param string $break
* @param bool $cut
* @return string
*/
function mb_wordwrap(string $str, int $width = 75, string $break = "\n", bool $cut = false): string {
// https://stackoverflow.com/a/4988494/488539
foreach ($lines as &$line) {
continue;
}
$line = '';
$actual = '';
foreach ($words as $word) {
$actual .= $word.' ';
} else {
if ($actual != '') {
$line .= rtrim($actual).$break;
}
$actual = $word;
if ($cut) {
$line .= mb_substr($actual, 0, $width).$break;
}
}
$actual .= ' ';
}
}
}
}
}
/**
* @param string $out
* @param string $contentType
* @param string $filename
* @return void
*/
function httpOutWithETag(string $out, string $contentType, string $filename='') {
header("Content-MD5: $etag"); // RFC 2616 clause 14.15
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && (trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag)) {
if (PHP_SAPI != 'cli') @http_response_code(304); // 304 Not Modified
} else {
header("Content-Type: $contentType");
header('Content-Disposition:inline; filename="'.$filename.'"');
}
echo $out;
}
}
/**
* @param string $str
* @param array $args
* @return string
*/
function my_vsprintf
(string
$str, array $args): string
{
$n = 1;
foreach ($args as $val) {
$n++;
}
}
/**
* @param string $str
* @param mixed ...$sprintfArgs
* @return string
* @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
* @throws \ViaThinkSoft\OIDplus\OIDplusException
*/
function _L(string $str, ...$sprintfArgs): string {
static
$translation_array = array();
static $translation_loaded = null;
return my_vsprintf($str, $sprintfArgs);
}
$lang = OIDplus::getCurrentLang();
$ta = OIDplus::getTranslationArray($lang);
$res = $ta[$lang][$str] ?? $str;
$res = str_replace('###', OIDplus
::baseConfig()->getValue('TABLENAME_PREFIX', ''), $res);
return my_vsprintf($res, $sprintfArgs);
}
/**
* @param array $params
* @param string $key
* @return void
* @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
* @throws \ViaThinkSoft\OIDplus\OIDplusException
*/
function _CheckParamExists
(array $params, string
$key) {
if (!isset($params[$key])) throw new OIDplusException
(_L
('Parameter %1 is missing', $key));
} else {
if (!isset($params[$key])) throw new Exception
(_L
('Parameter %1 is missing', $key));
}
}
/**
* @param string $cont
* @return array
*/
function extractHtmlContents
(string
$cont): array {
// make sure the program works even if the user provided HTML is not UTF-8
$cont = convert_to_utf8_no_bom($cont);
$out_js = '';
foreach ($m[1] as $x) {
$out_js = $x . "\n\n";
}
$out_css = '';
foreach ($m[1] as $x) {
$out_css = $x . "\n\n";
}
$out_html = $cont;
$out_html = preg_replace('@^(.+)<body[^>]*>@isU', '', $out_html);
$out_html = preg_replace('@<title>.+</title>@isU', '', $out_html);
$out_html = preg_replace('@<h1>.+</h1>@isU', '', $out_html, 1);
$out_html = preg_replace('@<script[^>]*>(.+)</script>@ismU', '', $out_html);
$out_html = preg_replace('@<style[^>]*>(.+)</style>@ismU', '', $out_html);
return array($out_html, $out_js, $out_css);
}
/**
* @param string $password
* @param bool $raw_output
* @return string
* @throws Exception
*/
function sha3_512(string $password, bool $raw_output=false): string {
if (hash_supported_natively('sha3-512')) {
return hash('sha3-512', $password, $raw_output);
} else {
return \bb\Sha3\Sha3
::hash($password, 512, $raw_output);
}
}
/**
* @param string $message
* @param string $key
* @param bool $raw_output
* @return string
*/
function sha3_512_hmac(string $message, string $key, bool $raw_output=false): string {
// RFC 2104 HMAC
if (hash_hmac_supported_natively('sha3-512')) {
return hash_hmac('sha3-512', $message, $key, $raw_output);
} else {
return \bb\Sha3\Sha3
::hash_hmac($message, $key, 512, $raw_output);
}
}
/**
* @param string $password
* @param string $salt
* @param int $iterations
* @param int $length
* @param bool $binary
* @return string
*/
function sha3_512_pbkdf2(string $password, string $salt, int $iterations, int $length=0, bool $binary=false): string {
if (hash_pbkdf2_supported_natively('sha3-512')) {
return hash_pbkdf2('sha3-512', $password, $salt, $iterations, $length, $binary);
} else {
return \bb\Sha3\Sha3::hash_pbkdf2($password, $salt, $iterations, 512, $length, $binary);
}
}
/**
* @param bool $require_ssl
* @param string|null $reason
* @return bool
* @throws OIDplusException
* @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
*/
function url_post_contents_available(bool $require_ssl=true, string &$reason=null): bool {
if (OIDplus::baseConfig()->getValue('OFFLINE_MODE', false)) {
$reason = _L('OIDplus is running in offline mode due to the base configuration setting %1.', 'OFFLINE_MODE');
return false;
}
}
return true;
} else {
$reason = _L('Please install the PHP extension %1, so that OIDplus can connect to the Internet.', '<code>php_curl</code>');
return false;
}
}
/**
* @param string $url
* @param array $params
* @param array $extraHeaders
* @param string $userAgent
* @return string|false
* @throws \ViaThinkSoft\OIDplus\OIDplusException
*/
function url_post_contents
(string
$url, array $params=array(), array $extraHeaders=array(), string
$userAgent='ViaThinkSoft-OIDplus/2.0') {
$require_ssl = str_starts_with
(strtolower($url),'https:');
if (!url_post_contents_available($require_ssl, $reason)) {
throw new OIDplusException(_L('This feature is not available, because OIDplus cannot connect to the Internet.').' '.$reason);
}
"User-Agent: $userAgent",
"Content-Length: ".strlen($postFields)
);
foreach ($extraHeaders as $name => $val) {
$headers[] = "$name: $val";
}
if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO
, OIDplus
::localpath() . 'vendor/cacert.pem');
}
if ($error_code >= 400) return false;
if ($res === false) return false;
} else {
$res = false;
}
return $res;
}
/**
* @param bool $require_ssl
* @param string|null $reason
* @return bool
* @throws OIDplusException
* @throws \ViaThinkSoft\OIDplus\OIDplusConfigInitializationException
*/
function url_get_contents_available(bool $require_ssl=true, string &$reason=null): bool {
if (OIDplus::baseConfig()->getValue('OFFLINE_MODE', false)) {
$reason = _L('OIDplus is running in offline mode due to the base configuration setting %1.', 'OFFLINE_MODE');
return false;
}
}
// Via cURL
return true;
} else {
// Via file_get_contents()
$reason = _L('Please install the PHP extension %1 and/or enable %2 in your PHP configuration, so that OIDplus can connect to the Internet.', '<code>php_curl</code>', '<code>allow_url_fopen</code>');
return false;
}
// Use extension_loaded() instead of function_exists(), because our supplement does not help...
$reason = _L('Please install the PHP extension %1 and/or %2, so that OIDplus can connect to the Internet.', '<code>php_curl</code>', '<code>php_openssl</code>');
return false;
}
return true;
}
}
/**
* @param string $url
* @param array $extraHeaders
* @param string $userAgent
* @return string|false
*/
function url_get_contents
(string
$url, array $extraHeaders=array(), string
$userAgent='ViaThinkSoft-OIDplus/2.0') {
$require_ssl = str_starts_with
(strtolower($url),'https:');
if (!url_get_contents_available($require_ssl, $reason)) {
throw new OIDplusException(_L('This feature is not available, because OIDplus cannot connect to the Internet.').' '.$reason);
}
$headers = array("User-Agent: $userAgent");
foreach ($extraHeaders as $name => $val) {
$headers[] = "$name: $val";
}
if (ini_get('curl.cainfo') == '') curl_setopt($ch, CURLOPT_CAINFO
, OIDplus
::localpath() . 'vendor/cacert.pem');
}
if ($error_code >= 400) return false;
if ($res === false) return false;
} else {
// Attention: HTTPS only works if OpenSSL extension is enabled.
// Our supplement does not help...
$opts = [
"http" => [
"method" => "GET",
"header" => implode("\r\n",$headers)."\r\n"
]
];
if ($res === false) return false;
}
return $res;
}
/**
* @param array &$rows
* @param string $fieldName
* @return void
*/
function natsort_field
(array &$rows, string
$fieldName) {
usort($rows, function($a,$b) use($fieldName) {
if ($a[$fieldName] == $b[$fieldName]) return 0; // equal
-1 => $a[$fieldName],
1 => $b[$fieldName]
);
return $keys[0];
});
}
/**
* @param array $ary
* @return \stdClass
*/
function array_to_stdobj
(array $ary): \stdClass
{
$obj = new \stdClass;
foreach ($ary as $name => $val) {
$obj->$name = $val;
}
return $obj;
}
/**
* @param \stdClass $obj
* @return array
*/
function stdobj_to_array
(\stdClass
$obj): array {
foreach ($obj as $name => $val) { /* @phpstan-ignore-line */
$ary[$name] = $val;
}
return $ary;
}
/**
* @return string|false
*/
function get_own_username() {
$current_user = exec('whoami');
try {
$current_user = @iconv("cp850", "UTF-8", $current_user);
}
} catch (\Exception $e) {}
}
}
if (!$current_user) {
// Windows on an IIS server:
// getenv('USERNAME') MARSCHALL$ (That is the "machine account", see https://docs.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities#accessing-the-network )
// get_current_user() DefaultAppPool
// exec('whoami') iis apppool\defaultapppool
// Windows with XAMPP:
// getenv('USERNAME') dmarschall
// get_current_user() dmarschall (even if script has a different NTFS owner!)
// exec('whoami') hickelsoft\dmarschall
if (!$current_user) {
$current_user = getenv('USERNAME');
}
} else {
// On Linux:
$current_user = exec('id -un');
if (!$current_user) {
// PHP'S get_current_user() will get the owner of the PHP script, not the process owner!
// We want the process owner, so we use posix_geteuid() preferably.
} else {
if ($temp_file !== false) {
if ($uid === false) $uid = -1;
} else {
$uid = -1;
}
}
if ($uid >= 0) {
$current_user = '#' . $uid;
$userinfo = posix_getpwuid($uid); // receive username from the UID (requires read access to /etc/passwd)
if ($userinfo !== false) $current_user = $userinfo['name'];
}
} else {
}
}
}
}
return $current_user ?: false;
}
/**
* @param string $path
* @return bool
*/
function isFileOrPathWritable(string $path): bool {
return false;
}
/**
* @param string $html
* @return string
*/
function html_to_text(string $html): string {
return $html;
}
/**
* Get header Authorization
* @see https://stackoverflow.com/questions/40582161/how-to-properly-use-bearer-tokens
**/
function getAuthorizationHeader(){
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER["Authorization"]);
}
else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { //Nginx or fast CGI
$headers = trim($_SERVER["HTTP_AUTHORIZATION"]);
// Server-side fix for bug in old Android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
//print_r($requestHeaders);
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
}
}
return $headers;
}
/**
* get access token from header
* @see https://stackoverflow.com/questions/40582161/how-to-properly-use-bearer-tokens
**/
function getBearerToken() {
$headers = getAuthorizationHeader();
// HEADER: Get the access token from the header
if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
return $matches[1];
}
}
return null;
}
/**
* @param array $struct
* @return string
*/
function array_to_html_ul_li
(array $struct): string
{
$res = '';
$res .= '<ul>';
foreach ($struct as $name => $val) {
$res .= '<li>';
$res .= $name . array_to_html_ul_li($val);
} else {
$res .= $val;
}
$res .= '</li>';
}
$res .= '</ul>';
return $res;
}
/**
* @param mixed $mixed
* @return bool
*/
function oidplus_is_true($mixed): bool {
return false;
return $mixed;
return $mixed != 0;
} else {
return (bool)$mixed; // let PHP decide...
}
}
/**
* @param string $data
* @param string $key
* @return string
* @throws \Exception
*/
function encrypt_str(string $data, string $key): string {
$iv = random_bytes(16); // AES block size in CBC mode
// Encryption
$ciphertext = openssl_encrypt(
$data,
'AES-256-CBC',
hash_pbkdf2('sha512', $key, '', 10000, 32/*256bit*/, true),
OPENSSL_RAW_DATA,
$iv
);
// Authentication
$hmac = sha3_512_hmac($iv . $ciphertext, $key, true);
return $hmac . $iv . $ciphertext;
} else {
// When OpenSSL is not available, then we just do a HMAC
$hmac = sha3_512_hmac($data, $key, true);
return $hmac . $data;
}
}
/**
* @param string $data
* @param string $key
* @return string
* @throws OIDplusException
*/
function decrypt_str(string $data, string $key): string {
$ciphertext = mb_substr($data, 80, null, '8bit');
// Authentication
$hmacNew = sha3_512_hmac($iv . $ciphertext, $key, true);
if (!hash_equals($hmac, $hmacNew)) {
throw new OIDplusException(_L('Authentication failed'));
}
// Decryption
$cleartext = openssl_decrypt(
$ciphertext,
'AES-256-CBC',
hash_pbkdf2('sha512', $key, '', 10000, 32/*256bit*/, true),
OPENSSL_RAW_DATA,
$iv
);
if ($cleartext === false) {
throw new OIDplusException(_L('Decryption failed'));
}
return $cleartext;
} else {
// When OpenSSL is not available, then we just do a HMAC
$cleartext = mb_substr($data, 64, null, '8bit');
$hmacNew = sha3_512_hmac($cleartext, $key, true);
if (!hash_equals($hmac, $hmacNew)) {
throw new OIDplusException(_L('Authentication failed'));
}
return $cleartext;
}
}