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
RemoteConfigFetcher.php
162 lines
| 1 | <?php |
| 2 | namespace NitroPack\SDK\Api; |
| 3 | |
| 4 | // Responsible for fetching the config from the API |
| 5 | class RemoteConfigFetcher extends Base { |
| 6 | |
| 7 | private $siteSecret; |
| 8 | |
| 9 | private static $algorithm = 'sha512'; // which algorithm to use for calculating challenge responses - needs to be one of the algos reported by hash_algos(); |
| 10 | private static $rounds = 5; // how many times to apply the algorithm |
| 11 | |
| 12 | public function __construct($siteId, $siteSecret) { |
| 13 | parent::__construct($siteId); |
| 14 | $this->siteSecret = $siteSecret; |
| 15 | } |
| 16 | |
| 17 | public function get() { |
| 18 | // initiate the config request, expecting to receive a challenge |
| 19 | $initiation = $this->initiateConfigRequest(); |
| 20 | if ($initiation->getStatus() != ResponseStatus::OK) { |
| 21 | throw new \NitroPack\SDK\ConfigFetcherException('Error while initializing remote config fetch request. Response code: ' . $initiation->getStatus()); // TODO better error message and logging |
| 22 | } |
| 23 | |
| 24 | try { |
| 25 | /* Expecting a json response like the following: |
| 26 | * { "cid": "...", "sc0": "...", "sc1": "...", "resp": "..." } |
| 27 | * where cid is the challenge id, sc0/1 are the two challenge strings, and resp is the challenge response for sc0 to verify the server knows the secret */ |
| 28 | $challenge = json_decode($initiation->getBody(), true); |
| 29 | } catch (\Exception $e) { |
| 30 | throw new \NitroPack\SDK\ChallengeProcessingException('Error while processing remote config challenge'); // TODO better error message and logging |
| 31 | } |
| 32 | |
| 33 | // verify that the challenge came from the API server |
| 34 | $expectedResponse = $this->calculateChallengeResponse($challenge['sc0']); |
| 35 | |
| 36 | if (!$this->internal_hash_equals($expectedResponse, $challenge['resp'])) { |
| 37 | throw new \NitroPack\SDK\ChallengeVerificationException('API server failed challenge verification when fetching the remote config'); // TODO better error message and logging |
| 38 | } |
| 39 | |
| 40 | $challengeResponse = $this->respondToChallenge($challenge['sc1'], $challenge['cid']); |
| 41 | |
| 42 | if ($challengeResponse->getStatus() != ResponseStatus::OK) { |
| 43 | throw new \NitroPack\SDK\ConfigFetcherException('Error while receiving remote config. Response code: ' . $challengeResponse->getStatus()); // TODO better error message and logging |
| 44 | } |
| 45 | |
| 46 | return $challengeResponse->getBody(); |
| 47 | } |
| 48 | |
| 49 | private function initiateConfigRequest() { |
| 50 | // start the config fetching process by sending the siteId to the server |
| 51 | $path = 'config/getchallenge/' . $this->siteId; |
| 52 | $httpResponse = $this->makeRequest($path, array(), array()); // path, headers, cookies |
| 53 | $status = ResponseStatus::getStatus($httpResponse->getStatusCode()); |
| 54 | $body = $httpResponse->getBody(); |
| 55 | return new Response($status, $body); |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * Calculates the challenge response and sends it to the API server |
| 60 | * $sc1 - the challenge sent by the server, whose challenge response was not provided |
| 61 | * $cId - the id of the challenge |
| 62 | */ |
| 63 | private function respondToChallenge($sc1, $cId) { |
| 64 | $path = 'config/get/' . $this->siteId; |
| 65 | $headers = array( |
| 66 | 'X-Challenge-Id' => $cId, |
| 67 | 'X-Challenge-Response' => $this->calculateChallengeResponse($sc1) |
| 68 | ); |
| 69 | $httpResponse = $this->makeRequest($path, $headers, array()); // path, headers, cookies |
| 70 | $status = ResponseStatus::getStatus($httpResponse->getStatusCode()); |
| 71 | $body = $httpResponse->getBody(); |
| 72 | return new Response($status, $body); |
| 73 | } |
| 74 | |
| 75 | private function calculateChallengeResponse($challenge) { |
| 76 | // We cannot use the following algorithms, even if hash_algos reports them as available, since then we cannot offer the same algo across all php versions: |
| 77 | /** |
| 78 | * 5.3: Added md2, ripemd256, ripemd320, salsa10, salsa20, snefru256, sha224 |
| 79 | * 5.4: Added joaat, fnv132, fnv164 |
| 80 | * Removed Salsa10, Salsa 20 |
| 81 | * 5.6: Added gost-crypto |
| 82 | * 7.1: Added sha512/224, sha512/256, sha3-224, sha3-256, sha3-384, sha3-512 |
| 83 | */ |
| 84 | $str = $this->siteSecret . $challenge; |
| 85 | for ($i = 0; $i < RemoteConfigFetcher::$rounds; ++$i) { |
| 86 | $str = hash(RemoteConfigFetcher::$algorithm, $str); |
| 87 | } |
| 88 | return $str; |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * internal_hash_equals is an implementation of the PHP 5.6+ hash_equals function |
| 93 | * Implementation taken from the MIT Licensed hash_equals library at https://github.com/realityking/hash_equals |
| 94 | * Copyright notice included below |
| 95 | * |
| 96 | * This [function] is part of the hash_equals library |
| 97 | * |
| 98 | * For the full copyright and license information, please view the LICENSE |
| 99 | * file that was distributed with this source code. |
| 100 | * |
| 101 | * @copyright Copyright (c) 2013-2014 Rouven Weßling <http://rouvenwessling.de> |
| 102 | * @license http://opensource.org/licenses/MIT MIT |
| 103 | * |
| 104 | * License text acquired from https://github.com/realityking/hash_equals/blob/master/LICENSE February 21, 2018 |
| 105 | * |
| 106 | * The MIT License (MIT) |
| 107 | * |
| 108 | * Copyright (c) 2014 Rouven Weßling |
| 109 | * |
| 110 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 111 | * of this software and associated documentation files (the "Software"), to deal |
| 112 | * in the Software without restriction, including without limitation the rights |
| 113 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 114 | * copies of the Software, and to permit persons to whom the Software is |
| 115 | * furnished to do so, subject to the following conditions: |
| 116 | * |
| 117 | * The above copyright notice and this permission notice shall be included in all |
| 118 | * copies or substantial portions of the Software. |
| 119 | * |
| 120 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 121 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 122 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 123 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 124 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 125 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 126 | * SOFTWARE. |
| 127 | */ |
| 128 | protected static function internal_hash_equals($known_string, $user_string) { |
| 129 | // iSenseLabs modification to use the PHP function if available |
| 130 | if (function_exists('hash_equals')) { |
| 131 | return hash_equals($known_string, $user_string); |
| 132 | } |
| 133 | // We jump trough some hoops to match the internals errors as closely as possible |
| 134 | $argc = func_num_args(); |
| 135 | |
| 136 | if ($argc < 2) { |
| 137 | trigger_error("hash_equals() expects at least 2 parameters, {$argc} given", E_USER_WARNING); |
| 138 | return null; |
| 139 | } |
| 140 | |
| 141 | if (!is_string($known_string)) { |
| 142 | trigger_error("hash_equals(): Expected known_string to be a string, " . gettype($known_string) . " given", E_USER_WARNING); |
| 143 | return false; |
| 144 | } |
| 145 | if (!is_string($user_string)) { |
| 146 | trigger_error("hash_equals(): Expected user_string to be a string, " . gettype($user_string) . " given", E_USER_WARNING); |
| 147 | return false; |
| 148 | } |
| 149 | |
| 150 | if (strlen($known_string) !== strlen($user_string)) { |
| 151 | return false; |
| 152 | } |
| 153 | $len = strlen($known_string); |
| 154 | $result = 0; |
| 155 | for ($i = 0; $i < $len; $i++) { |
| 156 | $result |= (ord($known_string[$i]) ^ ord($user_string[$i])); |
| 157 | } |
| 158 | // They are only identical strings if $result is exactly 0... |
| 159 | return 0 === $result; |
| 160 | } |
| 161 | } |
| 162 |