AdditionalDomains.php
1 year ago
Base.php
8 months ago
Cache.php
1 year ago
ExcludedUrls.php
1 year ago
Excludes.php
1 year ago
Integration.php
1 year ago
RemoteConfigFetcher.php
1 year ago
RequestMaker.php
1 year ago
Response.php
1 year ago
ResponseStatus.php
1 year ago
SafeMode.php
1 year ago
SecureRequestMaker.php
1 year ago
SignedBase.php
1 year ago
Stats.php
1 year ago
Tagger.php
1 year ago
Url.php
1 year ago
VariationCookie.php
1 year ago
Varnish.php
1 year ago
Warmup.php
1 year ago
Webhook.php
1 year ago
SignedBase.php
136 lines
| 1 | <?php |
| 2 | namespace NitroPack\SDK\Api; |
| 3 | use \NitroPack\Url\Url; |
| 4 | |
| 5 | // Src: http://php.net/manual/en/function.hash-equals.php |
| 6 | if(!function_exists('hash_equals')) { |
| 7 | function hash_equals($str1, $str2) { |
| 8 | if(strlen($str1) != strlen($str2)) { |
| 9 | return false; |
| 10 | } else { |
| 11 | $res = $str1 ^ $str2; |
| 12 | $ret = 0; |
| 13 | for($i = strlen($res) - 1; $i >= 0; $i--) $ret |= ord($res[$i]); |
| 14 | return !$ret; |
| 15 | } |
| 16 | } |
| 17 | } |
| 18 | |
| 19 | class SignedBase extends Base { |
| 20 | |
| 21 | private static $signatureHeader = 'X-Nitro-Signature'; |
| 22 | private static $algorithm = 'sha512'; |
| 23 | |
| 24 | protected $secret; |
| 25 | |
| 26 | public function __construct($siteId, $siteSecret) { |
| 27 | parent::__construct($siteId); |
| 28 | $this->secret = $siteSecret; |
| 29 | } |
| 30 | |
| 31 | protected function addToBacklog($entry) { |
| 32 | $entry["siteSecret"] = $this->secret; |
| 33 | parent::addToBacklog($entry); |
| 34 | } |
| 35 | |
| 36 | protected function makeRequest($path, $headers = array(), $cookies = array(), $type = 'GET', $bodyData=array(), $async = false, $verifySSL = false) { |
| 37 | // calculate request signature |
| 38 | $url = new Url($this->baseUrl . $path); |
| 39 | $noParamsPath = $url->getPath(); |
| 40 | $params = array(); |
| 41 | if(!is_null($url->getQuery())) { |
| 42 | parse_str($url->getQuery(), $params); |
| 43 | } |
| 44 | $params = $params + $bodyData; |
| 45 | $headers[SignedBase::$signatureHeader] = static::calculateRequestSignature($noParamsPath, $params, static::getSignatureHeaders($headers), $this->secret); |
| 46 | |
| 47 | // make the request |
| 48 | $http = parent::makeRequest($path, $headers, $cookies, $type, $bodyData, $async, $verifySSL); |
| 49 | |
| 50 | if ($async) { |
| 51 | $http->setOnCompleteCallback(array($this, 'verifySignature')); |
| 52 | } else { |
| 53 | $this->verifySignature($http); |
| 54 | } |
| 55 | |
| 56 | return $http; |
| 57 | } |
| 58 | |
| 59 | protected function verifySignature($http) { |
| 60 | if ($http->getStatusCode() === ResponseStatus::OK) { |
| 61 | $responseHeaders = $http->getHeaders(); |
| 62 | if (!isset($responseHeaders[strtolower(SignedBase::$signatureHeader)])) { |
| 63 | throw new \RuntimeException('Server did not include a signature for a request that was expected to be signed'); |
| 64 | } |
| 65 | $signature = static::calculateResponseSignature($http->getBody(), static::getSignatureHeaders($responseHeaders), $this->secret); |
| 66 | if (!hash_equals($signature, $responseHeaders[strtolower(SignedBase::$signatureHeader)])) { // TODO move RemoteConfigFetcher::internal_hash_equals to a place where its accessible everywhere and use it here |
| 67 | throw new \RuntimeException('Server signature is wrong'); // TODO better message |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | /** Signature data: |
| 73 | * UrlPath|header1:value,header2:value|param1:value,param2:value |
| 74 | * if not using headers, we still expect their delimiter UrlPath||param1:value,param2:value |
| 75 | */ |
| 76 | protected static function calculateRequestSignature($urlPath, $parameters, $headers, $secret) { |
| 77 | // do not include the header that specifies the signature |
| 78 | if (isset($headers[SignedBase::$signatureHeader])) { |
| 79 | unset($headers[SignedBase::$signatureHeader]); |
| 80 | } |
| 81 | |
| 82 | $data = $urlPath; |
| 83 | |
| 84 | ksort($headers); |
| 85 | $headersTemp = []; |
| 86 | foreach ($headers as $header => $value) { |
| 87 | $headersTemp[] = str_replace('-', '_', strtolower($header)) . ':' . $value; |
| 88 | } |
| 89 | |
| 90 | $data .= '|' . implode(',', $headersTemp); |
| 91 | $headersTemp = null; |
| 92 | |
| 93 | ksort($parameters); |
| 94 | $paramsTemp = []; |
| 95 | foreach ($parameters as $param => $value) { |
| 96 | if (is_array($value)) { |
| 97 | $paramsTemp[] = $param . ':' . json_encode($value); |
| 98 | } else { |
| 99 | $paramsTemp[] = $param . ':' . $value; |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | $data .= '|' . implode(',', $paramsTemp); |
| 104 | $paramsTemp = null; |
| 105 | return SignedBase::hmac($data, $secret); |
| 106 | } |
| 107 | |
| 108 | protected static function getSignatureHeaders($headers) { |
| 109 | $signatureHeaders = []; |
| 110 | foreach ($headers as $header => $value) { |
| 111 | if (stripos($header, 'x-nitro-') !== false && strtolower($header) != strtolower(SignedBase::$signatureHeader)) { |
| 112 | $signatureHeaders[$header] = $value; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | return $signatureHeaders; |
| 117 | } |
| 118 | |
| 119 | protected static function calculateResponseSignature($data, $headers, $secret) { |
| 120 | ksort($headers); |
| 121 | $headersTemp = []; |
| 122 | foreach ($headers as $header => $value) { |
| 123 | $headersTemp[] = str_replace('-', '_', strtolower($header)) . ':' . $value; |
| 124 | } |
| 125 | |
| 126 | $data = implode(',', $headersTemp) . '|' . $data; |
| 127 | $headersTemp = null; |
| 128 | |
| 129 | return SignedBase::hmac($data, $secret); |
| 130 | } |
| 131 | |
| 132 | private static function hmac($data, $secret) { |
| 133 | return hash_hmac(SignedBase::$algorithm, $data, $secret); |
| 134 | } |
| 135 | |
| 136 | } |