diff -r dd6b3adde73b -r 338bcc78d431 server/src/app/Libraries/Handle/HandleClient.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/src/app/Libraries/Handle/HandleClient.php Fri Apr 22 11:20:17 2016 +0200 @@ -0,0 +1,279 @@ +session = null; + $this->cert = null; + $this->adminId = $adminId; + $this->privateKeyRes = null; + $this->privateKeyOrCert = $privateKeyOrCert; + $this->pkpass = $pkpass; + $this->handleHost = $handleHost; + $this->handlePort = $handlePort; + $this->httpClient = $httpClient; + $this->guzzleOptions = ['verify' => false,]; + } + + public function __destruct () { + $this->close(); + } + + public function close() { + $this->deleteSession(); + $this->freeResources(); + } + + private function getBaseUrl() { + return "https://$this->handleHost:$this->handlePort/api/"; + } + + private function getSessionAuthHeader() { + return "Handle sessionId=$this->session"; + } + + private function generateClientNonce() { + return openssl_random_pseudo_bytes(16); + } + + private function getPrivateKeyRes() { + if(is_null($this->privateKeyRes)) { + $this->privateKeyRes = openssl_pkey_get_private($this->privateKeyOrCert, $this->pkpass); + } + return $this->privateKeyRes; + } + + private function freeResources() { + if(!empty($this->privateKeyRes)) { + $privateKeyRes = $this->privateKeyRes; + $this->privateKeyRes = null; + openssl_free_key($privateKeyRes); + } + } + + private function signBytesDsa($str) { + + openssl_sign($str, $signature, $this->getPrivateKeyRes(), OPENSSL_ALGO_DSS1); + + return $signature; + + } + + private function signBytesRsa($str) { + + $rsa = new RSA(); + $rsa->setHash('sha256'); + if(!empty($this->pkpass)) { + $rsa->setPassword($this->pkpass); + } + $keyContent = $this->privateKeyOrCert; + if(is_file($keyContent)) { + $keyContent = file_get_contents($keyContent); + } + $rsa->loadKey($keyContent); + $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); + + return $rsa->sign($str); + + } + + + + private function createAuthorisationHeaderFromJson($jsonresp) { + # Unpick number once (nonce) and session id from server response (this is the challenge) + $serverNonce = base64_decode($jsonresp['nonce']); + $this->sessionId = $jsonresp['sessionId']; + + # Generate a client number once (cnonce) + $clientNonce = $this->generateClientNonce(); + $clientNonceStr = base64_encode($clientNonce); + + # Our response has to be the signature of server nonce + client nonce + $combinedNonce = $serverNonce . $clientNonce; + if($this->getPrivateKeyRes() === false) { + throw new \Exception("HandleClient: can not read private res"); + } + $keyDetails = openssl_pkey_get_details($this->getPrivateKeyRes()); + if($keyDetails === false) { + throw new \Exception("HandleClient: can not read private key"); + } + if($keyDetails['type']===OPENSSL_KEYTYPE_RSA) { + $signature = $this->signBytesRsa($combinedNonce); + $signAlg = 'SHA256'; + } elseif ($keyDetails['type']===OPENSSL_KEYTYPE_DSA) { + $signature = $this->signBytesDsa($combinedNonce); + $signAlg = 'SHA1'; + } else { + throw new \Exception("HandleClient: $keyDetails[type] Format unknown"); + } + $signatureStr = base64_encode($signature); + + $this->freeResources(); + # Build the authorisation header to send with the request + # Use SHA1 for DSA keys; SHA256 can be used for RSA keys + return $this->buildComplexAuthorizationString($signatureStr, $signAlg, $clientNonceStr); + + } + + private function buildComplexAuthorizationString($signatureString, $signAlg, $clientNonceString) { + return "Handle " . + "version=\"0\", " . + "sessionId=\"$this->sessionId\", " . + "cnonce=\"$clientNonceString\", " . + "id=\"$this->adminId\", " . + "type=\"HS_PUBKEY\", " . + "alg=\"$signAlg\", " . + "signature=\"$signatureString\""; + } + + public function initSession() { + + if(!empty($this->session) || !empty($this->cert)) { + return; + } + + $headers = key_exists('headers', $this->guzzleOptions)?$this->guzzleOptions['headers']:[]; + $headers = array_merge($headers, [ + 'Content-Type' => 'application/json;charset=UTF-8', + ]); + + $certContent = $this->privateKeyOrCert; + if(is_file($certContent)) { + $certContent = file_get_contents($certContent); + } + if(openssl_x509_parse($certContent) !== false) { + if(!empty($this->pkpass)) { + $this->cert = [$this->privateKeyOrCert, $this->pkpass]; + } else { + $this->cert = $this->privateKeyOrCert; + } + $headers['Authorization'] = "Handle clientCert=\"true\""; + } else { + $url = $this->getBaseUrl()."sessions/"; + $challengeRes = $this->httpClient->post($url, ['verify' => false]); + + $jsonResp = json_decode($challengeRes->getBody(), true); + $pkheaders = [ + 'Content-Type' => 'application/json;charset=UTF-8', + 'Authorization' => $this->createAuthorisationHeaderFromJson($jsonResp) + ]; + # Send the request again with a valid correctly signed Authorization header + $sessionResp = $this->httpClient->put($url.'this', ['headers' => $pkheaders, 'verify' => false]); + Log::debug('Create session with auth: '.$sessionResp->getStatusCode().' : '.$sessionResp->getReasonPhrase()); + + $jsonResp = json_decode($sessionResp->getBody(), true); + + $this->session = $jsonResp['authenticated']?$jsonResp['sessionId']:""; + $headers['Authorization'] = "Handle version=\"0\", sessionId=\"$this->session\""; + + } + + $this->guzzleOptions = array_merge($this->guzzleOptions, ['headers' => $headers, 'cert' => $this->cert]); + + } + + // will call a async method. Apart logging we do not really care in the result + public function deleteSession() { + if(empty($this->session)) { + return; + } + $headers = [ + 'Content-Type' => 'application/json;charset=UTF-8', + 'Authorization' => $this->getSessionAuthHeader() + ]; + $url = $this->getBaseUrl()."sessions/this"; + // Do not really care of the response... + $this->httpClient->deleteAsync($url, ['headers' => $headers, 'verify' => false]); + $this->session = null; + + } + + /** + * Paginate all handle as a paginator. + * + * @param int $perPage + * @param string $pageName + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + */ + public function paginateAll($prefix, $perPage = 15, $pageName = 'page', $page = null) { + + $this->initSession(); + + $url = $this->getBaseUrl()."handles"; + + $params = [ + 'prefix' => $prefix, + 'page' => is_null($page)?0:$page-1, + 'pageSize' => $perPage + ]; + + $paginateRes = $this->httpClient->get($url, array_merge($this->guzzleOptions, ['query' => $params])); + + + $paginateJson = json_decode($paginateRes->getBody(), true); + + $total = (int)$paginateJson['totalCount']; + + $results = $paginateJson['handles']; + + return new LengthAwarePaginator($results, $total, $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + + } + + public function deleteHandle($handle) { + $this->initSession(); + + $delUrl = $this->getBaseUrl()."handles/$handle"; + $delRes = $this->httpClient->delete($delUrl, $this->guzzleOptions); + + Log::debug('Delete Handle: '.$delRes->getStatusCode().': '.$delRes->getReasonPhrase()); + } + + public function createHandleUrlRecord($handle, $url) { + $this->initSession(); + $currentDate = gmstrftime('%Y-%m-%dT%H:%M:%SZ'); + $handleRecord = [ + 'values' => [ + ['index' => 1, 'ttl' => 86400, 'type' => 'URL', 'timestamp' => $currentDate, 'data' => ['value'=> $url, 'format'=> 'string']], + ['index' => 100, 'ttl' => 86400, 'type' => 'HS_ADMIN', 'timestamp' => $currentDate, 'data' => [ + 'value' => ['index' => 200, 'handle' => $this->adminId], + 'permissions' => '011111110011', + 'format' => 'admin' + ] + ] + ], + 'handle' => $handle, + ]; + + $submitUrl = $this->getBaseUrl()."handles/$handle"; + + $submitRes = $this->httpClient->put($submitUrl, array_merge($this->guzzleOptions, ['json' => $handleRecord, ])); + + Log::debug('Create Handle Url: '.$submitRes->getStatusCode().' : '.$submitRes->getReasonPhrase()); + } + +}