diff --git a/README.md b/README.md index c5c1330..515f2b4 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,10 @@ use AltchaOrg\Altcha\Altcha; $hmacKey = 'secret hmac key'; // Create a new challenge -$options = new ChallengeOptions([ - 'hmacKey' => $hmacKey, - 'maxNumber' => 50000, // the maximum random number +$options = new ChallengeOptions( + $hmacKey, + ChallengeOptions::DEFAULT_ALGORITHM, + 50000, // the maximum random number ]); $challenge = Altcha::createChallenge($options); @@ -65,46 +66,37 @@ if ($ok) { ## API -### `Altcha::createChallenge(array $options): array` +### `Altcha::createChallenge(ChallengeOptions $options): Challenge` Creates a new challenge for ALTCHA. -**Parameters:** +**Returns:** `Challenge` -- `options array`: - - `algorithm string`: Hashing algorithm to use (`SHA-1`, `SHA-256`, `SHA-512`, default: `SHA-256`). - - `maxNumber int`: Maximum number for the random number generator (default: 1,000,000). - - `saltLength int`: Length of the random salt (default: 12 bytes). - - `hmacKey string`: Required HMAC key. - - `salt string`: Optional salt string. If not provided, a random salt will be generated. - - `number int`: Optional specific number to use. If not provided, a random number will be generated. - - `expires \DateTime`: Optional expiration time for the challenge. - - `params array`: Optional URL-encoded query parameters. +#### `ChallengeOptions` -**Returns:** `array` +```php +$options = new ChallengeOptions( + $hmacKey, + ChallengeOptions::DEFAULT_ALGORITHM, + ChallengeOptions::DEFAULT_MAX_NUMBER, + (new \DateTimeImmutable())->add(new \DateInterval('PT10S')), + ['query_param' => '123'], + ChallengeOptions::DEFAULT_SALT_LENGTH +]); +``` -### `Altcha::verifySolution(array $payload, string $hmacKey, bool $checkExpires): bool` +### `Altcha::verifySolution(array|string $payload, string $hmacKey, bool $checkExpires): bool` Verifies an ALTCHA solution. **Parameters:** -- `payload array`: The solution payload to verify. +- `data array|string`: The solution payload to verify. - `hmacKey string`: The HMAC key used for verification. - `checkExpires bool`: Whether to check if the challenge has expired. **Returns:** `bool` -### `Altcha::extractParams(array $payload): array` - -Extracts URL parameters from the payload's salt. - -**Parameters:** - -- `payload array`: The payload containing the salt. - -**Returns:** `array` - ### `Altcha::verifyFieldsHash(array $formData, array $fields, string $fieldsHash, string $algorithm): bool` Verifies the hash of form fields. @@ -118,18 +110,18 @@ Verifies the hash of form fields. **Returns:** `bool` -### `Altcha::verifyServerSignature($payload, string $hmacKey): array` +### `Altcha::verifyServerSignature(array|string $payload, string $hmacKey): ServerSignatureVerification` Verifies the server signature. **Parameters:** -- `payload mixed`: The payload to verify (string or `ServerSignaturePayload` array). +- `data array|string`: The payload to verify (string or `ServerSignaturePayload` array). - `hmacKey string`: The HMAC key used for verification. -**Returns:** `array` +**Returns:** `ServerSignatureVerification` -### `Altcha::solveChallenge(string $challenge, string $salt, string $algorithm, int $max, int $start, $stopChan = null): array` +### `Altcha::solveChallenge(string $challenge, string $salt, string $algorithm, int $max, int $start = 0): array` Finds a solution to the given challenge. @@ -141,7 +133,7 @@ Finds a solution to the given challenge. - `max int`: Maximum number to iterate to. - `start int`: Starting number. -**Returns:** `array` +**Returns:** `null|Solution` ## Tests @@ -152,4 +144,4 @@ vendor/bin/phpunit --bootstrap src/Altcha.php tests/AltchaTest.php ## License -MIT \ No newline at end of file +MIT diff --git a/composer.json b/composer.json index cc65c4b..0c0fec1 100644 --- a/composer.json +++ b/composer.json @@ -14,9 +14,18 @@ } ], "require": { - "php": ">=7.4" + "php": ">=7.4", + "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^11.5" + "phpunit/phpunit": "^11.5", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/extension-installer": "^1.4" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } } } diff --git a/composer.lock b/composer.lock index b283f3b..01d8c35 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d73ea3536cd869eb6ae47e1638b6076d", + "content-hash": "4215398bfd95a5256d87cf47ad091cb8", "packages": [], "packages-dev": [ { @@ -243,6 +243,163 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpstan/extension-installer", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPStan\\ExtensionInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" + }, + "time": "2024-09-04T20:21:43+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.6", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-02-19T15:46:42+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "d09e152f403c843998d7a52b5d87040c937525dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/d09e152f403c843998d7a52b5d87040c937525dd", + "reference": "d09e152f403c843998d7a52b5d87040c937525dd", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0.4" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.4" + }, + "time": "2025-01-22T13:07:38+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "11.0.9", @@ -1702,7 +1859,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.4" + "php": ">=7.4", + "ext-json": "*" }, "platform-dev": {}, "plugin-api-version": "2.6.0" diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..d3d85e5 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,7 @@ +parameters: + ignoreErrors: + - + message: '#^Dead catch \- ValueError is never thrown in the try block\.$#' + identifier: catch.neverThrown + count: 1 + path: src/Altcha.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..3a36a39 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: max + paths: + - src + - tests diff --git a/src/Algorithm.php b/src/Algorithm.php index bda7a19..0c1f3ce 100644 --- a/src/Algorithm.php +++ b/src/Algorithm.php @@ -1,10 +1,12 @@ + */ + private static function decodePayload(string $payload): ?array { - $decoded = base64_decode($payload); + $decoded = base64_decode($payload, true); if (!$decoded) { return null; @@ -79,63 +72,97 @@ private static function decodePayload($payload) return $data; } - public static function createChallenge($options) + /** + * @param string|array $data + */ + private static function verifyAndBuildSolutionPayload($data): ?Payload { - if (is_array($options)) { - $options = new ChallengeOptions($options); + if (is_string($data)) { + $data = self::decodePayload($data); } - $algorithm = $options->algorithm ?: self::DEFAULT_ALGORITHM; - $maxNumber = $options->maxNumber ?: self::DEFAULT_MAX_NUMBER; - $saltLength = $options->saltLength ?: self::DEFAULT_SALT_LENGTH; - - $params = $options->params; - if ($options->expires) { - $params['expires'] = $options->expires->getTimestamp(); + if ($data === null + || !isset($data['algorithm'], $data['challenge'], $data['number'], $data['salt'], $data['signature']) + || !is_string($data['algorithm']) + || !is_string($data['challenge']) + || !is_int($data['number']) + || !is_string($data['salt']) + || !is_string($data['signature']) + ) { + return null; } - $salt = $options->salt ?: bin2hex(self::randomBytes($saltLength)); - if (!empty($params)) { - $salt .= '?' . http_build_query($params); + return new Payload($data['algorithm'], $data['challenge'], $data['number'], $data['salt'], $data['signature']); + } + + /** + * @param string|array $data + */ + private static function verifyAndBuildServerSignaturePayload($data): ?ServerSignaturePayload + { + if (is_string($data)) { + $data = self::decodePayload($data); } - $number = $options->number ?: self::randomInt($maxNumber); + if ($data === null + || !isset($data['algorithm'], $data['verificationData'], $data['signature'], $data['verified']) + || !is_string($data['algorithm']) + || !is_string($data['verificationData']) + || !is_string($data['signature']) + || !is_bool($data['verified']) + ) { + return null; + } - $challenge = self::hashHex($algorithm, $salt . $number); + return new ServerSignaturePayload($data['algorithm'], $data['verificationData'], $data['signature'], $data['verified']); + } - $signature = self::hmacHex($algorithm, $challenge, $options->hmacKey); + /** + * Creates a new challenge for ALTCHA. + * + * @param BaseChallengeOptions $options + * + * @return Challenge The challenge data to be passed to ALTCHA. + */ + public static function createChallenge(BaseChallengeOptions $options): Challenge + { + $challenge = self::hashHex($options->algorithm, $options->salt . $options->number); + $signature = self::hmacHex($options->algorithm, $challenge, $options->hmacKey); - return new Challenge($algorithm, $challenge, $maxNumber, $salt, $signature); + return new Challenge($options->algorithm, $challenge, $options->maxNumber, $options->salt, $signature); } - public static function verifySolution($payload, $hmacKey, $checkExpires = true) + /** + * Verifies an ALTCHA solution. + * + * @param string|array $data The solution payload to verify. + * @param string $hmacKey The HMAC key used for verification. + * @param bool $checkExpires Whether to check if the challenge has expired. + * + * @return bool True if the solution is valid. + */ + public static function verifySolution($data, string $hmacKey, bool $checkExpires = true): bool { - if (is_string($payload)) { - $payload = self::decodePayload($payload); - } + $payload = self::verifyAndBuildSolutionPayload($data); - if ($payload === null - || !isset($payload['algorithm'], $payload['challenge'], $payload['number'], $payload['salt'], $payload['signature']) - ) { + if (!$payload) { return false; } - $payload = new Payload($payload['algorithm'], $payload['challenge'], $payload['number'], $payload['salt'], $payload['signature']); - $params = self::extractParams($payload); - if ($checkExpires && isset($params['expires'])) { + if ($checkExpires && isset($params['expires']) && is_numeric($params['expires'])) { $expireTime = (int)$params['expires']; if (time() > $expireTime) { return false; } } - $challengeOptions = new ChallengeOptions([ - 'algorithm' => $payload->algorithm, - 'hmacKey' => $hmacKey, - 'number' => $payload->number, - 'salt' => $payload->salt, - ]); + $challengeOptions = new CheckChallengeOptions( + $hmacKey, + $payload->algorithm, + $payload->salt, + $payload->number + ); $expectedChallenge = self::createChallenge($challengeOptions); @@ -143,7 +170,10 @@ public static function verifySolution($payload, $hmacKey, $checkExpires = true) $expectedChallenge->signature === $payload->signature; } - private static function extractParams($payload) + /** + * @return array|string> + */ + private static function extractParams(Payload $payload): array { $saltParts = explode('?', $payload->salt); if (count($saltParts) > 1) { @@ -153,7 +183,15 @@ private static function extractParams($payload) return []; } - public static function verifyFieldsHash($formData, $fields, $fieldsHash, $algorithm) + /** + * Verifies the hash of form fields. + * + * @param array $formData The form data to hash. + * @param array $fields The fields to include in the hash. + * @param string $fieldsHash The expected hash value. + * @param string $algorithm Hashing algorithm (`SHA-1`, `SHA-256`, `SHA-512`). + */ + public static function verifyFieldsHash(array $formData, array $fields, string $fieldsHash, string $algorithm): bool { $lines = []; foreach ($fields as $field) { @@ -164,47 +202,70 @@ public static function verifyFieldsHash($formData, $fields, $fieldsHash, $algori return $computedHash === $fieldsHash; } - public static function verifyServerSignature($payload, $hmacKey) + + /** + * Verifies the server signature. + * + * @param string|array $data The payload to verify (string or `ServerSignaturePayload` array). + * @param string $hmacKey The HMAC key used for verification. + */ + public static function verifyServerSignature($data, string $hmacKey): ServerSignatureVerification { - if (is_string($payload)) { - $payload = self::decodePayload($payload); - } + $payload = self::verifyAndBuildServerSignaturePayload($data); - if ($payload === null - || !isset($payload['algorithm'], $payload['verificationData'], $payload['signature'], $payload['verified']) - ) { - return false; + if (!$payload) { + return new ServerSignatureVerification(false, null); } - $payload = new ServerSignaturePayload($payload['algorithm'], $payload['verificationData'], $payload['signature'], $payload['verified']); - $hash = self::hash($payload->algorithm, $payload->verificationData); $expectedSignature = self::hmacHex($payload->algorithm, $hash, $hmacKey); parse_str($payload->verificationData, $params); - $verificationData = new ServerSignatureVerificationData(); - $verificationData->classification = $params['classification'] ?? ''; - $verificationData->country = $params['country'] ?? ''; - $verificationData->detectedLanguage = $params['detectedLanguage'] ?? ''; - $verificationData->email = $params['email'] ?? ''; - $verificationData->expire = (int)($params['expire'] ?? 0); - $verificationData->fields = explode(',', $params['fields'] ?? ''); - $verificationData->fieldsHash = $params['fieldsHash'] ?? ''; - $verificationData->reasons = explode(',', $params['reasons'] ?? ''); - $verificationData->score = (float)($params['score'] ?? 0); - $verificationData->time = (int)($params['time'] ?? 0); - $verificationData->verified = ($params['verified'] ?? 'false') === 'true'; + $classification = isset($params['classification']) && is_string($params['classification']) ? $params['classification'] : ''; + $country = isset($params['country']) && is_string($params['country']) ? $params['country'] : ''; + $detectedLanguage = isset($params['detectedLanguage']) && is_string($params['detectedLanguage']) ? $params['detectedLanguage'] : ''; + $email = isset($params['email']) && is_string($params['email']) ? $params['email'] : ''; + $expire = isset($params['expire']) && is_numeric($params['expire']) ? (int) $params['expire'] : 0; + $fields = isset($params['fields']) && is_array($params['fields']) ? $params['fields'] : []; + $fieldsHash = isset($params['fieldsHash']) && is_string($params['fieldsHash']) ? $params['fieldsHash'] : ''; + $reasons = isset($params['reasons']) && is_array($params['reasons']) ? $params['reasons'] : []; + $score = isset($params['score']) && is_numeric($params['score']) ? (float) $params['score'] : 0.0; + $time = isset($params['time']) && is_numeric($params['time']) ? (int) $params['time'] : 0; + $verified = isset($params['verified']) && $params['verified']; + + $verificationData = new ServerSignatureVerificationData( + $classification, + $country, + $detectedLanguage, + $email, + $expire, + $fields, + $fieldsHash, + $reasons, + $score, + $time, + $verified, + ); $now = time(); $isVerified = $payload->verified && $verificationData->verified && $verificationData->expire > $now && $payload->signature === $expectedSignature; - return [$isVerified, $verificationData]; + return new ServerSignatureVerification($isVerified, $verificationData); } - public static function solveChallenge($challenge, $salt, $algorithm, $max = 1000000, $start = 0) + /** + * Finds a solution to the given challenge. + * + * @param string $challenge The challenge hash. + * @param string $salt The challenge salt. + * @param string $algorithm Hashing algorithm (`SHA-1`, `SHA-256`, `SHA-512`). + * @param int $max Maximum number to iterate to. + * @param int $start Starting number. + */ + public static function solveChallenge(string $challenge, string $salt, string $algorithm, int $max, int $start = 0): ?Solution { $startTime = microtime(true); diff --git a/src/BaseChallengeOptions.php b/src/BaseChallengeOptions.php new file mode 100644 index 0000000..0dfc1cc --- /dev/null +++ b/src/BaseChallengeOptions.php @@ -0,0 +1,55 @@ + + */ +class BaseChallengeOptions +{ + public const DEFAULT_MAX_NUMBER = 1000000; + + public string $algorithm; + public int $maxNumber; + public string $hmacKey; + public string $salt; + public int $number; + public ?\DateTimeInterface $expires; + + /** @var array */ + public array $params; + + /** + * Options for creation of a new challenge. + * @see ChallengeOptions for options with sane defaults. + * + * @param ChallengeParams $params + */ + public function __construct( + string $algorithm, + string $hmacKey, + int $maxNumber, + ?\DateTimeInterface $expires, + string $salt, + int $number, + array $params + ) { + $this->algorithm = $algorithm; + $this->hmacKey = $hmacKey; + $this->maxNumber = $maxNumber; + $this->expires = $expires; + $this->salt = $salt; + $this->number = $number; + $this->params = $params; + + if ($expires) { + $params['expires'] = $expires->getTimestamp(); + } + + if (!empty($params)) { + $this->salt .= '?' . http_build_query($params); + } + } +} diff --git a/src/Challenge.php b/src/Challenge.php index 1f514fe..4d9984d 100644 --- a/src/Challenge.php +++ b/src/Challenge.php @@ -1,16 +1,18 @@ algorithm = $algorithm; $this->challenge = $challenge; diff --git a/src/ChallengeOptions.php b/src/ChallengeOptions.php index ed2ef96..448727f 100644 --- a/src/ChallengeOptions.php +++ b/src/ChallengeOptions.php @@ -1,27 +1,58 @@ $saltLength Length of the random salt (default: 12 bytes). + */ + public function __construct( + string $hmacKey, + string $algorithm = self::DEFAULT_ALGORITHM, + int $maxNumber = self::DEFAULT_MAX_NUMBER, + ?\DateTimeInterface $expires = null, + array $params = [], + int $saltLength = self::DEFAULT_SALT_LENGTH + ) { + parent::__construct( + $algorithm, + $hmacKey, + $maxNumber, + $expires, + bin2hex(self::randomBytes($saltLength)), + self::randomInt($maxNumber), + $params + ); + } + + private static function randomInt(int $max): int + { + return random_int(0, $max); + } - public function __construct($options = []) + /** + * @param int<1, max> $length + */ + private static function randomBytes(int $length): string { - $this->algorithm = $options['algorithm'] ?? Altcha::DEFAULT_ALGORITHM; - $this->maxNumber = $options['maxNumber'] ?? Altcha::DEFAULT_MAX_NUMBER; - $this->saltLength = $options['saltLength'] ?? Altcha::DEFAULT_SALT_LENGTH; - $this->hmacKey = $options['hmacKey'] ?? ''; - $this->salt = $options['salt'] ?? ''; - $this->number = $options['number'] ?? 0; - $this->expires = $options['expires'] ?? null; - $this->params = $options['params'] ?? []; + return random_bytes($length); } } diff --git a/src/CheckChallengeOptions.php b/src/CheckChallengeOptions.php new file mode 100644 index 0000000..4c1c9ea --- /dev/null +++ b/src/CheckChallengeOptions.php @@ -0,0 +1,17 @@ +algorithm = $algorithm; $this->challenge = $challenge; diff --git a/src/ServerSignaturePayload.php b/src/ServerSignaturePayload.php index d7c2c45..fdd6eee 100644 --- a/src/ServerSignaturePayload.php +++ b/src/ServerSignaturePayload.php @@ -1,15 +1,17 @@ algorithm = $algorithm; $this->verificationData = $verificationData; diff --git a/src/ServerSignatureVerification.php b/src/ServerSignatureVerification.php new file mode 100644 index 0000000..ca638b1 --- /dev/null +++ b/src/ServerSignatureVerification.php @@ -0,0 +1,17 @@ +verified = $verified; + $this->data = $data; + } +} diff --git a/src/ServerSignatureVerificationData.php b/src/ServerSignatureVerificationData.php index 862c23e..b123c59 100644 --- a/src/ServerSignatureVerificationData.php +++ b/src/ServerSignatureVerificationData.php @@ -1,19 +1,52 @@ */ + public array $fields; + public string $fieldsHash; + /** @var array */ + public array $reasons; + public float $score; + public int $time; + public bool $verified; + + /** + * @param array $fields + * @param array $reasons + */ + public function __construct( + string $classification, + string $country, + string $detectedLanguage, + string $email, + int $expire, + array $fields, + string $fieldsHash, + array $reasons, + float $score, + int $time, + bool $verified + ) { + $this->classification = $classification; + $this->country = $country; + $this->detectedLanguage = $detectedLanguage; + $this->email = $email; + $this->expire = $expire; + $this->fields = $fields; + $this->fieldsHash = $fieldsHash; + $this->reasons = $reasons; + $this->score = $score; + $this->time = $time; + $this->verified = $verified; + } } diff --git a/src/Solution.php b/src/Solution.php index 74a04ea..0f0899a 100644 --- a/src/Solution.php +++ b/src/Solution.php @@ -1,13 +1,15 @@ number = $number; $this->took = $took; diff --git a/tests/AltchaTest.php b/tests/AltchaTest.php index 2089555..63ef4ce 100644 --- a/tests/AltchaTest.php +++ b/tests/AltchaTest.php @@ -1,54 +1,34 @@ Algorithm::SHA256, - 'hmacKey' => 'test-key' - ]); - - $challenge = Altcha::createChallenge($options); - - $this->assertInstanceOf(Challenge::class, $challenge); - $this->assertEquals(Algorithm::SHA256, $challenge->algorithm); - $this->assertNotEmpty($challenge->challenge); - $this->assertEquals(1e6, $challenge->maxnumber); - $this->assertNotEmpty($challenge->salt); - $this->assertNotEmpty($challenge->signature); + // build a default challenge for all tests (for performance reasons) + $options = new ChallengeOptions('test-key'); + self::$challenge = Altcha::createChallenge($options); } - public function testVerifySolution() + public function testCreateChallenge(): void { - $options = new ChallengeOptions([ - 'algorithm' => Algorithm::SHA256, - 'number' => 10, - 'hmacKey' => 'test-key' - ]); - - $challenge = Altcha::createChallenge($options); - $payload = [ - 'algorithm' => $challenge->algorithm, - 'challenge' => $challenge->challenge, - 'number' => 10, - 'salt' => $challenge->salt, - 'signature' => $challenge->signature, - ]; - - $isValid = Altcha::verifySolution($payload, 'test-key'); - - $this->assertTrue($isValid); + self::assertEquals(Algorithm::SHA256, self::$challenge->algorithm); + self::assertNotEmpty(self::$challenge->challenge); + self::assertEquals(BaseChallengeOptions::DEFAULT_MAX_NUMBER, self::$challenge->maxnumber); + self::assertNotEmpty(self::$challenge->salt); + self::assertNotEmpty(self::$challenge->signature); } - public function testVerifyFieldsHash() + public function testVerifyFieldsHash(): void { $formData = [ 'field1' => 'value1', @@ -60,32 +40,53 @@ public function testVerifyFieldsHash() $isValid = Altcha::verifyFieldsHash($formData, $fields, $fieldsHash, Algorithm::SHA256); - $this->assertTrue($isValid); + self::assertTrue($isValid); + } + + public function testSolveChallenge(): void + { + $solution = Altcha::solveChallenge( + self::$challenge->challenge, + self::$challenge->salt, + self::$challenge->algorithm, + self::$challenge->maxnumber + ); + + self::assertInstanceOf(Solution::class, $solution); + self::assertEquals($solution->number, $solution->number); + self::assertGreaterThan(0, $solution->took); } - public function testSolveChallenge() + public function testVerifySolution(): void { - $options = new ChallengeOptions([ - 'algorithm' => Algorithm::SHA256, - 'hmacKey' => 'test-key', - 'maxNumber' => 100 - ]); + $solution = Altcha::solveChallenge( + self::$challenge->challenge, + self::$challenge->salt, + self::$challenge->algorithm, + self::$challenge->maxnumber + ); - $challenge = Altcha::createChallenge($options); + self::assertInstanceOf(Solution::class, $solution); - $solution = Altcha::solveChallenge($challenge->challenge, $challenge->salt, $challenge->algorithm, $challenge->maxnumber); + $payload = [ + 'algorithm' => self::$challenge->algorithm, + 'challenge' => self::$challenge->challenge, + 'salt' => self::$challenge->salt, + 'signature' => self::$challenge->signature, + 'number' => $solution->number, + ]; + + $isValid = Altcha::verifySolution($payload, 'test-key'); - $this->assertInstanceOf(Solution::class, $solution); - $this->assertEquals($solution->number, $solution->number); - $this->assertGreaterThan(0, $solution->took); + self::assertTrue($isValid); } - public function testInvalidPayload() + public function testInvalidPayload(): void { $isValid = Altcha::verifySolution('I am invalid', 'key'); - $this->assertFalse($isValid); + self::assertFalse($isValid); - $isValid = Altcha::verifyServerSignature('I am invalid', 'key'); - $this->assertFalse($isValid); + $verification = Altcha::verifyServerSignature('I am invalid', 'key'); + self::assertFalse($verification->verified); } }