From d9caa116e2afd33668b9ecc5ed2a7cfa4cdc1421 Mon Sep 17 00:00:00 2001 From: "NTEN\\Nenninger" Date: Sat, 13 Aug 2022 17:27:26 +0200 Subject: [PATCH] V1.3 Senden von Nachrichten und Dateien funktioniert --- .gitignore | 2 + hermineConnect.php | 634 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 524 insertions(+), 112 deletions(-) diff --git a/.gitignore b/.gitignore index 2bd8749..790a3bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ _login response.txt +Dirk.py +test.txt diff --git a/hermineConnect.php b/hermineConnect.php index 20da2b4..7bd4d95 100644 --- a/hermineConnect.php +++ b/hermineConnect.php @@ -1,134 +1,544 @@ 8.1 hermine@THW PHPConnector inspiriert von https://gitlab.com/aeberhardt/stashcat-api-client + + Beispiel: + + $hermine = new hermineConnect('mailadresse','Accountpassword','Verschlüsselungskennwort'); + if($hermine->login(true) !== false){ + $array = $hermine->get_companies_list(); + print_r($array); + $array = $hermine->get_conversations_list(); + print_r($array); + $array = $hermine->get_channels_list(); + print_r($array); + + $file = file_get_contents('./ich.jpg'); + $hermine->send_message_with_file_to_channel(165562,'jpg per php',$file,'test.jpg','image/jpeg',1080,2068); + + send_message_to_conversation(id,message); + send_message_to_channel(id,message); + + }else{ + //login fehlgeschlagen + print_r($hermine->lasterror); + } + */ +class UUID { + public static function v3($namespace, $name) { + if(!self::is_valid($namespace)) return false; + + // Get hexadecimal components of namespace + $nhex = str_replace(array('-','{','}'), '', $namespace); + + // Binary Value + $nstr = ''; + + // Convert Namespace UUID to bits + for($i = 0; $i < strlen($nhex); $i+=2) { + $nstr .= chr(hexdec($nhex[$i].$nhex[$i+1])); + } + + // Calculate hash value + $hash = md5($nstr . $name); + + return sprintf('%08s-%04s-%04x-%04x-%12s', + + // 32 bits for "time_low" + substr($hash, 0, 8), + + // 16 bits for "time_mid" + substr($hash, 8, 4), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 3 + (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x3000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, + + // 48 bits for "node" + substr($hash, 20, 12) + ); + } + + public static function v4() { + return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + + // 32 bits for "time_low" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + + // 16 bits for "time_mid" + mt_rand(0, 0xffff), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0fff) | 0x4000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3fff) | 0x8000, + + // 48 bits for "node" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } + + public static function v5($namespace, $name) { + if(!self::is_valid($namespace)) return false; + + // Get hexadecimal components of namespace + $nhex = str_replace(array('-','{','}'), '', $namespace); + + // Binary Value + $nstr = ''; + + // Convert Namespace UUID to bits + for($i = 0; $i < strlen($nhex); $i+=2) { + $nstr .= chr(hexdec($nhex[$i].$nhex[$i+1])); + } + + // Calculate hash value + $hash = sha1($nstr . $name); + + return sprintf('%08s-%04s-%04x-%04x-%12s', + + // 32 bits for "time_low" + substr($hash, 0, 8), + + // 16 bits for "time_mid" + substr($hash, 8, 4), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 5 + (hexdec(substr($hash, 12, 4)) & 0x0fff) | 0x5000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + (hexdec(substr($hash, 16, 4)) & 0x3fff) | 0x8000, + + // 48 bits for "node" + substr($hash, 20, 12) + ); + } + + public static function is_valid($uuid) { + return preg_match('/^\{?[0-9a-f]{8}\-?[0-9a-f]{4}\-?[0-9a-f]{4}\-?'. + '[0-9a-f]{4}\-?[0-9a-f]{12}\}?$/i', $uuid) === 1; + } +} + class hermineConnect{ - private $hermineServer = 'https://api.thw-messenger.de'; - //private $hermineServer = 'https://thw.n-tools.de'; - private $connectorID = ""; - private $user = ""; - private $password = ""; - private $passphrase = ""; - private $cURL; - private $client_key = ""; - private $user_id = ""; - + private $VERSION = '1.3'; + private $hermineServer = 'https://api.thw-messenger.de'; + //private $hermineServer = 'https://thw.n-tools.de'; + private $connectorID = ""; + private $user = ""; + private $password = ""; + private $passphrase = ""; + private $cURL; + private $client_key = ""; + private $user_id = ""; + private $private_key; + private $key_cache_channels; + private $key_cache_conversations; + private $lasterror; + private $companies; + private $channels; + private $conversations; + + //private $debug = true; + + function build_data_files($boundary, $fields, $files){ + $data = ''; + $eol = "\r\n"; - - function __construct($user,$password,$passphrase) { - $this->connectorID = base64_encode('deadbeef'.sha1($_SERVER['SERVER_NAME'])); - $this->user = $user; - $this->password = $password; - $this->passphrase = $passphrase; - $this->cURL = curl_init(); - } - - function __destruct() { - curl_close($this->cURL); - } - - function request($_url,$_data){ - $_data['device_id'] = $this->connectorID; - if($this->client_key != '') - $_data['client_key'] = $this->client_key; - - print_r($_data); - curl_setopt($this->cURL, CURLOPT_URL, $_url); - //curl_setopt($this->cURL, CURLOPT_PORT , 443); - curl_setopt($this->cURL, CURLOPT_VERBOSE, 0); - //curl_setopt($this->cURL, CURLOPT_HEADER, 0); - //curl_setopt($this->cURL, CURLOPT_SSLVERSION, 3); - //curl_setopt($this->cURL, CURLOPT_SSLCERT, getcwd() . "/client.pem"); - //curl_setopt($this->cURL, CURLOPT_SSLKEY, getcwd() . "/keyout.pem"); - //curl_setopt($this->cURL, CURLOPT_CAINFO, getcwd() . "/ca.pem"); - - curl_setopt($this->cURL, CURLOPT_SSL_VERIFYPEER, 1); - curl_setopt($this->cURL, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($this->cURL, CURLOPT_POST, true); - //curl_setopt($this->cURL, CURLOPT_BINARYTRANSFER, true); - curl_setopt($this->cURL, CURLOPT_POSTFIELDS,$_data); - curl_setopt($this->cURL, CURLOPT_HTTPHEADER, array("Accept: application/json, text/plain, */*","Connection: keep-alive","Keep-Alive: timeout=5, max=100")); - - curl_setopt( $this->cURL, CURLOPT_COOKIESESSION, true ); - curl_setopt( $this->cURL, CURLOPT_COOKIEJAR, './hermineConnectCookie.txt' ); - curl_setopt( $this->cURL, CURLOPT_COOKIEFILE, './hermineConnectCookie.txt' ); - - $data = curl_exec($this->cURL); - if(!curl_errno($this->cURL)){ - $data = json_decode($data); - if($data->status->value != "OK") - print_r($data); - $info = curl_getinfo($this->cURL); - return $data->payload;// 'Took ' . $info['total_time'] . ' seconds to send a request to ' . $info['url']; - } else { - return false; //echo 'Curl error: ' . curl_error($cURL); - } - - - } - - function open_private_key(){ - $data = []; - $response = $this->request($this->hermineServer."/security/get_private_key",$data); - print_r($response->keys->private_key); - //$privkey_decoded = @openssl_pkey_get_private($private_key, $this->passphrase); - } - /*open_private_key(self, encryption_password): - data = self._post("security/get_private_key", data={}) - private_key_field = json.loads(data["keys"]["private_key"]) - # there might be an unescaping bug here.... - self.private_key = Crypto.PublicKey.RSA.import_key( - private_key_field["private"], passphrase=encryption_password - ) */ + $delimiter = '-------------' . $boundary; - - public function getID(){ - return $this->connectorID; - } - public function login(){ - $data = [ - "email" => $this->user, - "password" => $this->password, - "app_name" => 'hermine@thw-php:1.1-browser-0', - "encrypted" => true, - "callable" => true - ]; - - $response = $this->request($this->hermineServer."/auth/login",$data); - if($response !== false){ - $this->client_key = $response->client_key; - $this->user_id = $response->userinfo->id; - $this->open_private_key(); - return $response; - } + foreach ($fields as $name => $content) { + $data .= "--" . $delimiter . $eol + . 'Content-Disposition: form-data; name="' . $name . "\"".$eol.$eol + . $content . $eol; } - public function get_companies(){ - $data = [ - "no_cache" => true - ]; - $response = $this->request($this->hermineServer."/company/member",$data); - if($response !== false){ - return $response; - }else{ - return false; - } + foreach ($files as $name => $content) { + $data .= "--" . $delimiter . $eol + . 'Content-Disposition: form-data; name="file"; filename="[object Object]"' . $eol + . 'Content-Type: application/octet-stream'.$eol + //. 'Content-Transfer-Encoding: binary'.$eol + ; + + $data .= $eol; + $data .= $content . $eol; } + $data .= "--" . $delimiter . "--".$eol; + + + return $data; } + + function __construct($user,$password,$passphrase) { + function intArrayToString($ia){ + $ret = ''; + foreach($ia as $val){ + $ret .= chr($val); + } + return $ret; + } + $cID = array_map('hexdec', str_split('deadbeef'.sha1($_SERVER['SERVER_NAME']), 2)); + $cID = intArrayToString($cID); + $this->connectorID = base64_encode($cID); + $this->user = $user; + $this->password = $password; + $this->passphrase = $passphrase; + $this->cURL = curl_init(); + } + + function __destruct() { + curl_close($this->cURL); + } + + function request($_url,$_data,$_files=[]){ + $_data['device_id'] = $this->connectorID; + if($this->client_key != '') + $_data['client_key'] = $this->client_key; + + $boundary = uniqid(); + $delimiter = '-------------' . $boundary; + $_data = $this->build_data_files($boundary, $_data, $_files); + + + curl_setopt($this->cURL, CURLOPT_URL, $_url); + curl_setopt($this->cURL, CURLOPT_VERBOSE, 0); + curl_setopt($this->cURL, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($this->cURL, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($this->cURL, CURLOPT_POST, true); + curl_setopt($this->cURL, CURLOPT_POSTFIELDS,$_data); + curl_setopt($this->cURL, CURLOPT_HTTPHEADER, array("Accept: application/json, text/plain, */*", + "Keep-Alive: timeout=5, max=100", + "Connection: keep-alive", + "Content-Type: multipart/form-data; boundary=" . $delimiter, + "Content-Length: " . strlen($_data), + 'Expect:' + )); + + curl_setopt( $this->cURL, CURLOPT_COOKIESESSION, true ); + curl_setopt( $this->cURL, CURLOPT_COOKIEJAR, './hermineConnectCookie.txt' ); + curl_setopt( $this->cURL, CURLOPT_COOKIEFILE, './hermineConnectCookie.txt' ); + curl_setopt( $this->cURL, CURLINFO_HEADER_OUT, true); + + if(isset($this->debug)) + echo "\n\nsenddata->:\n".print_r($_data,true); + + $data = curl_exec($this->cURL); + if(!curl_errno($this->cURL)){ + if(isset($this->debug)){ + echo "\n\nretdata->:\n".print_r($data,true); + echo "\n\nSendheader->:\n".print_r(curl_getinfo($this->cURL, CURLINFO_HEADER_OUT),true); + } + $data = json_decode($data); + if($data->status->value === "OK"){ + $this->lasterror = ''; + return $data->payload; + }else{ + $this->lasterror = $data; + return false; + } + } else { + $this->lasterror = 'Curl error: ' . curl_error($cURL); + return false; + } + + + } + + function _open_private_key(){ + $data = []; + $response = $this->request($this->hermineServer."/security/get_private_key",$data); + $private_key = json_decode($response->keys->private_key); + $privkey_decoded = openssl_pkey_get_private($private_key->private, $this->passphrase); + if($privkey_decoded !== false){ + $this->private_key = $privkey_decoded; + return true; + }else{ + return false; + } + } + + function _get_conversation_key($_target){ + if($_target[0]=='conversation'){ + if(!isset($this->key_cache_conversations[$_target[1]])){ + $data = [ "conversation_id" => $_target[1] + ]; + $response = $this->request($this->hermineServer."/message/conversation",$data); + $this->key_cache_conversations[$_target[1]] = $response->conversation->key; + } + if(openssl_private_decrypt(base64_decode($this->key_cache_conversations[$_target[1]]),$ret,$this->private_key,OPENSSL_PKCS1_OAEP_PADDING)) + return $ret; + else + return false; + }else if($_target[0]=='channel'){ + if(!isset($this->key_cache_channels[$_target[1]])){ + $data = [ "channel_id" => $_target[1], + "without_members" => true + ]; + $response = $this->request($this->hermineServer."/channels/info",$data); + $this->key_cache_channels[$_target[1]] = $response->channels->key; + + } + if(openssl_private_decrypt(base64_decode($this->key_cache_channels[$_target[1]]),$ret,$this->private_key,OPENSSL_PKCS1_OAEP_PADDING)) + return $ret; + else + return false; + } + } + + function _encrypt_aes($_plain, $_key, $_iv){ + $ret = openssl_encrypt($_plain, "AES-256-CBC", $_key, 0, $_iv); + print_r($ret); + return $ret; + } -$test = new hermineConnect('xxx@n4h.de','xxx','xxxx'); -echo $test->getID(); -$test->login(); -print_r($test); -//print_r($test->get_companies()); + public function getLastError(){ + return $this->lasterror; + } + + public function getID(){ + return $this->connectorID; + } + + public function login($_saveLogin=false){ + if($_saveLogin){ + if(file_exists('./login.dat')){ + $savelogin = json_decode(file_get_contents('./login.dat')); + $this->client_key = $savelogin->ClientKey; + $this->connectorID = $savelogin->DeviceId; + $this->user_id = $savelogin->UserID; + if($this->_open_private_key()){ + $this->companies = $this->get_companies(); + $this->channels = $this->get_channels($this->companies->companies[0]->id); + $this->conversations = $this->get_conversations(); + return true; + } + } + } + $data = [ + "email" => $this->user, + "password" => $this->password, + "app_name" => 'hermine@thw-PHP:'.$this->VERSION, + "encrypted" => true, + "callable" => false + ]; + + $response = $this->request($this->hermineServer."/auth/login",$data); + if($response !== false){ + $this->client_key = $response->client_key; + $this->user_id = $response->userinfo->id; + if($this->_open_private_key()){ + $this->companies = $this->get_companies(); + $this->channels = $this->get_channels($this->companies->companies[0]->id); + $this->conversations = $this->get_conversations(); + if($_saveLogin){ + $savelogin['ClientKey'] = $this->client_key; + $savelogin['DeviceId'] = $this->connectorID; + $savelogin['UserID'] = $this->user_id; + file_put_contents('./login.dat',json_encode($savelogin)); + } + return true; + } + } + return false; + } + + function get_companies(){ + $data = [ + "no_cache" => true + ]; + + $response = $this->request($this->hermineServer."/company/member",$data); + if($response !== false){ + return $response; + }else{ + return false; + } + } + + function get_conversations($_limit = 99999, $_offset = 0){ + $data = [ + "limit" => $_limit, + "offset" => true, + "archive" => 0 + ]; + + $response = $this->request($this->hermineServer."/message/conversations",$data); + if($response !== false){ + return $response; + }else{ + return false; + } + } + + function get_channels($_company_id){ + $data = [ + "no_cache" => true, + "company" => $_company_id + ]; + + $response = $this->request($this->hermineServer."/channels/subscripted",$data); + if($response !== false){ + return $response; + }else{ + return false; + } + } + + public function get_companies_list(){ + $ret = []; + foreach($this->companies->companies as $company){ + $ret[$company->id] = $company->name; + } + return $ret; + } + + public function get_conversations_list(){ + $ret = []; + foreach($this->conversations->conversations as $conversation){ + $ret[$conversation->id] = $conversation->members[0]->last_name; + } + return $ret; + } + + public function get_channels_list(){ + $ret = []; + foreach($this->channels->channels as $channels){ + $ret[$channels->id] = $channels->name; + } + return $ret; + } + + function sendmsg($_target,$_message,$_files=[],$_location=false,$_encrypted=true){ + $iv = openssl_random_pseudo_bytes(16); + $conversation_key = $this->_get_conversation_key($_target); + $data = [ + "target" => $_target[0], + $_target[0]."_id" => $_target[1], + "text" => bin2hex(base64_decode($this->_encrypt_aes($_message, $conversation_key, $iv))), + "iv" => bin2hex($iv), + "files" => json_encode($_files), //Nummern vom Upload! + "url" => "[]", + "type" => "text", + "verification" => "", + "encrypted" => true + ]; + + if(!$_encrypted){ + $data['encrypted'] = false; + $data['text'] = $_message; + unset($data[$iv]); + } + + if($_location !== false){ + if(!$_encrypted){ + + } + } + print_r($data); + $response = $this->request($this->hermineServer."/message/send",$data); + if($response !== false){ + return $response; + }else{ + return false; + } + } + + //function uploadfile($_target,$_filerawdatat,$_filename,$_content_type="application/octet-stream",$_media_size=NULL,$_encrypted=false){ + function uploadfile($_target,$_fileraw,$_filename,$_filetype,$_mediawidth=0,$_mediaheight=0){ + $iv = openssl_random_pseudo_bytes(16); + $file_key = openssl_random_pseudo_bytes(32); + + //$file = file_get_contents($_filewithpath); + + $file_uuid = UUID::v4(); + + $chunk_size = 5 * 1024 * 1024; + $filesize = strlen($_fileraw); + + //$file = new \CURLStringFile($_fileraw, $_filename, $_filetype); + + $file_encryptet = base64_decode($this->_encrypt_aes($_fileraw, $file_key, $iv)); + + $file = ['dummy' => $file_encryptet]; + + $data = [ + "resumableChunkNumber" => 0, + "resumableChunkSize" => $chunk_size, + "resumableCurrentChunkSize" => strlen($file_encryptet), + "resumableTotalSize" => $filesize, + "resumableType" => $_filetype, + "resumableIdentifier" => $file_uuid, + "resumableFilename" => $_filename, + "resumableRelativePath" => $_filename, + "resumableTotalChunks" => 1, + "folder" => 0, + "media_width" => $_mediawidth, + "media_height" => $_mediaheight, + "iv" => bin2hex($iv), + "type" => $_target[0], + "type_id" => $_target[1], + "encrypted" => true + ]; + + $response = $this->request($this->hermineServer."/file/upload",$data,$file); + if($response !== false){ + $iv = openssl_random_pseudo_bytes(16); + $conversation_key = $this->_get_conversation_key($_target); + $fileid = $response->file->id; + $data = [ + "file_id" => $fileid, + "target" => $_target[0], + "target_id" => $_target[1], + "iv" => bin2hex($iv), + "key" => bin2hex(base64_decode($this->_encrypt_aes($file_key, $conversation_key, $iv))) + ]; + $response = $this->request($this->hermineServer."/security/set_file_access_key",$data); + return $fileid; + }else{ + return false; + } + } + + public function send_message_to_conversation($_conversation_id,$_message){ + return $this->sendmsg(array('conversation',$_conversation_id),$_message); + } + + public function send_message_with_file_to_conversation($_conversation_id,$_message,$_fileraw,$_filename,$_filetype,$_mediawidth=0,$_mediaheight=0){ + $ret = $this->uploadfile(array('conversation',$_conversation_id),$_fileraw,$_filename,$_filetype,$_mediawidth,$_mediaheight); + return $this->sendmsg(array('conversation',$_conversation_id),$_message,[$ret]); + } + + public function send_message_to_channel($_channel_id,$_message){ + return $this->sendmsg(array('channel',$_channel_id),$_message); + } + + public function send_message_with_file_to_channel($_channel_id,$_message,$_fileraw,$_filename,$_filetype,$_mediawidth=0,$_mediaheight=0){ + $ret = $this->uploadfile(array('channel',$_channel_id),$_fileraw,$_filename,$_filetype,$_mediawidth,$_mediaheight); + return $this->sendmsg(array('channel',$_channel_id),$_message,[$ret]); + } +} ?>