Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
355 changes: 203 additions & 152 deletions skysparkClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
* Techledge | SkySparkAuth Class
*
* Conducts SCRAM Authentication for SS3.
*****************************************************/
*****************************************************
* Updated: 2025-07-22
* Updated by: Shaun R Hawk(shaun@shaunhawk.com) || Huckleberry Services, FSM Controls, LLC.
* SkySpark Class is used to authenticate to SkySpark and send messages to the server which now using zinc format with a POST request.
******************************************************
*/

//using .env plugin with composer, that's why I'm using this autoload file
//this is used for getting the uri, username, and password for skyspark login.
require_once '../../vendor/autoload.php';

class SkySpark {

Expand All @@ -32,10 +35,10 @@ class SkySpark {
private $password;
private $serverUrl;

function __construct() {
$this->uri = getenv('SKYSPARK_URI'); //ex: "projUri/api/projName/eval?expr="
$this->username = getenv('SKYSPARK_USERNAME'); //ex: username
$this->password = getenv('SKYSPARK_PASS'); //ex: password
function __construct($uri=null, $username=null, $password=null) {
$this->uri = $uri ?? getenv('SKYSPARK_URI'); //ex: "projUri/api/projName/eval"
$this->username = $username ?? getenv('SKYSPARK_USERNAME'); //ex: username
$this->password = $password ?? getenv('SKYSPARK_PASS'); //ex: password
}


Expand All @@ -45,151 +48,199 @@ function __construct() {
* *
*******************************************/

/************************
* Main SCRAM's function *
*************************/

function scram() {

// the size in bytes of a SHA-256 hash
$dklen = 32;

//SCRAM Autherntication Parameters
$serverUrl = 'https://skyspark.urlProjName.com/ui';

//Send url and username for first introduction in message 1
$handshakeToken = $this->sendMsg1($serverUrl, $this->username);

//Parse hanshakeToken from Server Response 1.
$handshakeToken = $this->get_string_between($handshakeToken, '=', ',');

//Create a random but strong id.
$random = md5(uniqid(mt_rand(), true));

$clientNonce = $random;

$clientFirstMsg = "n=".$this->username.",r=".$clientNonce;

//Send url, Client's First Message, and the hansshakeToken in message 2
$serverFirstMsg = $this->sendMsg2($serverUrl, $clientFirstMsg,$handshakeToken);

//Parse Server Nonce, Server Salt, and Server Iterations from Server Response 2
$serverNonce = $this->get_string_between($serverFirstMsg, 'r=', ',');
$serverSalt = $this->get_string_between($serverFirstMsg, 's=', ',');
$serverIterations = substr($serverFirstMsg, strpos($serverFirstMsg, "i=") + 2);

//PBKDf2 for the SHA-256 hashing algorithm
$saltedPassword = hash_pbkdf2("sha256", $this->password, base64_decode($serverSalt), intval($serverIterations), $dklen, true);

$gs2Header = base64_encode("n,,");
$clientFinalNoPf = 'c='.$gs2Header.',r='.$serverNonce;
$authMessage = $clientFirstMsg.','.$serverFirstMsg.','.$clientFinalNoPf;

//HMAC for SHA-256 hashing for the Client Key
$clientKey = hash_hmac('sha256',"Client Key", $saltedPassword, true);

//hash the Stored Key
$storedKey = hash('sha256', $clientKey, true);

//HMAC for SHA-256 hashing for the Client Signature
$clientSignature = hash_hmac('sha256', $authMessage, $storedKey, true);

//Xor Client Key with Client Signature
$clientProof = ($clientKey ^ $clientSignature);

$clientFinalMsg = $clientFinalNoPf.",p=".base64_encode($clientProof);

//Send url, Client's Final Message, and the hansshakeToken in message 3
$serverSecondMsg = $this->sendMsg3($serverUrl, $clientFinalMsg,$handshakeToken);

return $serverSecondMsg;

}

/***********************
* Message 1 Using cURL *
************************/

function sendMsg1($serverUrl, $msg) {


$authMsg = "HELLO username=".rtrim(strtr(base64_encode($msg), '+/', '-_'), '=');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $serverUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER , true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Authorization: ". $authMsg,
"WWW-Authenticate: SCRAM"
));
$serverMsg = curl_exec($ch);

curl_close($ch);

return $serverMsg;
}

/***********************
* Message 2 Using cURL *
************************/

function sendMsg2($serverUrl, $msg, $handshakeToken) {

$authMsg = "SCRAM handshakeToken=".$handshakeToken.", data=".rtrim(strtr(base64_encode($msg), '+/', '-_'), '=');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $serverUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER , true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Authorization: ". $authMsg,
"WWW-Authenticate: SCRAM"
));
$serverMsg = curl_exec($ch);
$serverMsg = base64_decode($this->get_string_between($serverMsg,"data=",","));

curl_close($ch);

return $serverMsg;

}

/***********************
* Message 3 Using cURL *
************************/

function sendMsg3($serverUrl, $msg,$handshakeToken) {

$authMsg = "SCRAM handshakeToken=".$handshakeToken.", data=".rtrim(strtr(base64_encode($msg), '+/', '-_'), '=');

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $serverUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER , true);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Authorization: " .$authMsg
));
$serverMsg = curl_exec($ch);
$serverMsg = $this->get_string_between($serverMsg,"authToken=",",");

curl_close($ch);

return $serverMsg;
}

/******************
* Parse function *
*******************/

function get_string_between($string, $start, $end) {

$string = ' ' . $string;
$ini = strpos($string, $start);
if ($ini == 0) return '';
$ini += strlen($start);
$len = strpos($string, $end, $ini) - $ini;
return substr($string, $ini, $len);
}
protected function scram() {
// the size in bytes of a SHA-256 hash
$dklen = 32;
//SCRAM Autherntication Parameters
if($this->uri == null){
throw new Exception("Skyspark Client not found");
}
$serverUrl = explode('/api', $this->uri)[0];
//Send url and username for first introduction in message 1
$handshakeToken = $this->sendMsg1($serverUrl, $this->username);
//Parse hanshakeToken from Server Response 1.
$handshakeToken = $this->get_string_between($handshakeToken, '=', ',');
//Create a random but strong id.
$random = md5(uniqid(mt_rand(), true));
$clientNonce = $random;
$clientFirstMsg = "n=".$this->username.",r=".$clientNonce;
//Send url, Client's First Message, and the hansshakeToken in message 2
$serverFirstMsg = $this->sendMsg2($serverUrl, $clientFirstMsg,$handshakeToken);
//Parse Server Nonce, Server Salt, and Server Iterations from Server Response 2
$serverNonce = $this->get_string_between($serverFirstMsg, 'r=', ',');
$serverSalt = $this->get_string_between($serverFirstMsg, 's=', ',');
$serverIterations = substr($serverFirstMsg, strpos($serverFirstMsg, "i=") + 2);
//PBKDf2 for the SHA-256 hashing algorithm
$saltedPassword = hash_pbkdf2("sha256", $this->password, base64_decode($serverSalt), intval($serverIterations), $dklen, true);
$gs2Header = base64_encode("n,,");
$clientFinalNoPf = 'c='.$gs2Header.',r='.$serverNonce;
$authMessage = $clientFirstMsg.','.$serverFirstMsg.','.$clientFinalNoPf;
//HMAC for SHA-256 hashing for the Client Key
$clientKey = hash_hmac('sha256',"Client Key", $saltedPassword, true);
//hash the Stored Key
$storedKey = hash('sha256', $clientKey, true);
//HMAC for SHA-256 hashing for the Client Signature
$clientSignature = hash_hmac('sha256', $authMessage, $storedKey, true);
//Xor Client Key with Client Signature
$clientProof = ($clientKey ^ $clientSignature);
$clientFinalMsg = $clientFinalNoPf.",p=".base64_encode($clientProof);
//Send url, Client's Final Message, and the hansshakeToken in message 3
$serverSecondMsg = $this->sendMsg3($serverUrl, $clientFinalMsg,$handshakeToken);
return $serverSecondMsg;
}

/***********************
* Message 1 Using cURL *
*************************/
protected function sendMsg1($serverUrl, $msg) {
$authMsg = "HELLO username=".rtrim(strtr(base64_encode($msg), '+/', '-_'), '=');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $serverUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER , true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Authorization: ". $authMsg,"WWW-Authenticate: SCRAM"));
$serverMsg = curl_exec($ch);
curl_close($ch);
return $serverMsg;
}

/***********************
* Message 2 Using cURL *
*************************/
protected function sendMsg2($serverUrl, $msg, $handshakeToken) {
$authMsg = "SCRAM handshakeToken=".$handshakeToken.", data=".rtrim(strtr(base64_encode($msg), '+/', '-_'), '=');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $serverUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER , true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Authorization: ". $authMsg,"WWW-Authenticate: SCRAM"));
$serverMsg = curl_exec($ch);
$serverMsg = base64_decode($this->get_string_between($serverMsg,"data=",","));
curl_close($ch);
return $serverMsg;
}

/***********************
* Message 3 Using cURL *
*************************/
protected function sendMsg3($serverUrl, $msg,$handshakeToken) {
$authMsg = "SCRAM handshakeToken=".$handshakeToken.", data=".rtrim(strtr(base64_encode($msg), '+/', '-_'), '=');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $serverUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER , true);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Authorization: " .$authMsg));
$serverMsg = curl_exec($ch);
$serverMsg = $this->get_string_between($serverMsg,"authToken=",",");
curl_close($ch);
return $serverMsg;
}

/******************
* Parse function *
*******************/
protected function get_string_between($string, $start, $end) {
$string = ' ' . $string;
$ini = strpos($string, $start);
if ($ini == 0) return '';
$ini += strlen($start);
$len = strpos($string, $end, $ini) - $ini;
return substr($string, $ini, $len);
}

protected function convertToZinc($data) {
// Convert Grid data to Zinc format
$zinc = "ver:\"3.0\"\n";

if (isset($data['cols']) && isset($data['rows'])) {
// Grid format
$colNames = [];
foreach ($data['cols'] as $col) {
$colNames[] = $col['name'];
}
$zinc .= implode(',', $colNames) . "\n";

foreach ($data['rows'] as $row) {
$zincRow = [];
foreach ($row as $value) {
$zincRow[] = '"' . $value . '"';
}
$zinc .= implode(',', $zincRow) . "\n";
}
} else {
// Simple format
foreach ($data as $key => $value) {
if ($key !== 'ver') {
$zinc .= $key . "\n\"" . $value . "\"\n";
}
}
}

return $zinc;
}

/************************
* Curl function that can now handle both GET and POST requests for current and deprecated SkySpark versions*
* Backward compatible with old 3-parameter signature: curl($authToken, $url, $debug)
*************************/
protected function curl($authToken, $url, $debug=false, $method='GET', $postData=null) {
$ch = curl_init();
$addr = $this->uri . $url;
curl_setopt($ch, CURLOPT_URL, $addr);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER , true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // 30 second timeout
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // 10 second connection timeout

// Determine content type based on method and data
$contentType = "application/json"; // Default for GET requests
if ($method === 'POST' && $postData !== null) {
$contentType = "text/zinc"; // Use Zinc for POST with data
}

curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Authorization: BEARER authToken=".$authToken,
"Accept: application/json",
"Content-type: " . $contentType
));

// Set request method
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
if ($postData !== null) {
// Convert to Zinc format for SkySpark
$zincData = $this->convertToZinc($postData);
curl_setopt($ch, CURLOPT_POSTFIELDS, $zincData);
}
} elseif ($method !== 'GET') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($postData !== null) {
$jsonData = json_encode($postData);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);
}
}
// For GET requests, no additional curl options needed (default behavior)

$serverMsg = curl_exec($ch);

$str = strtok($serverMsg, "\n");
$Get_header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$yummy = array('":"r:', '":"s:', '":"n:','":"d:','":"t:');
$Clean = str_replace($yummy,'":"', $serverMsg);

$findAt = array('":"p:');
$Clean2 = str_replace($findAt,'":"@p:', $Clean);
$Get_body = substr($Clean2, $Get_header_size);
if($debug)
{
return $Get_body;
}

$Get_body_Array = json_decode($Get_body,true);
curl_close($ch);
return $Get_body_Array;
}
}

?>