PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.0
Defender Security – Malware Scanner, Login Security & Firewall v6.2.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / component / altcha / class-altcha.php

class-altcha.php in Defender Security – Malware Scanner, Login Security & Firewall 6.2.0, at src/component/altcha/class-altcha.php

182 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Altcha
4 *
5 * @package WP_Defender\Component\Altcha
6 */
7
8 namespace WP_Defender\Component\Altcha;
9
10 use WP_Defender\Component\Altcha\Hasher\Algorithm;
11 use WP_Defender\Component\Altcha\Hasher\Hasher;
12 use WP_Defender\Component\Altcha\Hasher\HasherInterface;
13
14 /**
15 * Class Altcha.
16 */
17 class Altcha {
18 /**
19 * The HMAC key used for signing.
20 *
21 * @var string
22 */
23 private string $hmac_key;
24
25 /**
26 * The hasher instance.
27 *
28 * @var HasherInterface
29 */
30 private HasherInterface $hasher;
31
32 /**
33 * Altcha constructor.
34 *
35 * @param string $hmac_key The HMAC key used for signing.
36 * @param HasherInterface $hasher The hasher instance to use.
37 */
38 public function __construct(
39 string $hmac_key,
40 ?HasherInterface $hasher = null
41 ) {
42 $this->hmac_key = $hmac_key;
43 $this->hasher = $hasher ?? new Hasher();
44 }
45
46 /**
47 * Decodes a base64-encoded JSON payload.
48 *
49 * @param string $payload The base64-encoded JSON payload.
50 *
51 * @return null|array
52 */
53 private function decode_payload( string $payload ): ?array {
54 $decoded = base64_decode( $payload, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
55
56 if ( ! $decoded ) {
57 return null;
58 }
59
60 try {
61 $data = json_decode( $decoded, true, 2, \JSON_THROW_ON_ERROR );
62 } catch ( \JsonException |\ValueError ) {
63 return null;
64 }
65
66 if ( ! is_array( $data ) || array() === $data ) {
67 return null;
68 }
69
70 return $data;
71 }
72
73 /**
74 * Verifies and builds a Payload object from the given data.
75 *
76 * @param array|string $data The data to verify and build the payload from.
77 */
78 private function verify_and_build_solution_payload( string|array $data ): ?Payload {
79 if ( is_string( $data ) ) {
80 $data = $this->decode_payload( $data );
81 }
82
83 if ( null === $data
84 || ! isset( $data['algorithm'], $data['challenge'], $data['number'], $data['salt'], $data['signature'] )
85 || ! is_string( $data['algorithm'] )
86 || ! is_string( $data['challenge'] )
87 || ! is_string( $data['salt'] )
88 || ! is_string( $data['signature'] )
89 || ! is_int( $data['number'] )
90 ) {
91 return null;
92 }
93
94 $algorithm = Algorithm::try_from( $data['algorithm'] ?? null );
95
96 if ( ! $algorithm ) {
97 return null;
98 }
99
100 return new Payload( $algorithm, $data['challenge'], $data['number'], $data['salt'], $data['signature'] );
101 }
102
103 /**
104 * Extracts parameters from the salt string of a Payload.
105 *
106 * @param Payload $payload The Payload object.
107 *
108 * @return array
109 */
110 private function extract_params( Payload $payload ): array {
111 $salt_parts = explode( '?', $payload->salt );
112 if ( count( $salt_parts ) > 1 ) {
113 parse_str( $salt_parts[1], $params );
114
115 return $params;
116 }
117
118 return array();
119 }
120
121 /**
122 * Creates a new challenge for ALTCHA.
123 *
124 * @param Base_Challenge_Options $options The options for creating the challenge.
125 *
126 * @return Challenge The challenge data to be passed to ALTCHA.
127 */
128 public function create_challenge( ?Base_Challenge_Options $options = null ): Challenge {
129 if ( null === $options ) {
130 $options = new Challenge_Options();
131 }
132
133 $challenge = $this->hasher->hash_hex( $options->algorithm, $options->salt . $options->number );
134 $signature = $this->hasher->hash_hmac_hex( $options->algorithm, $challenge, $this->hmac_key );
135
136 return new Challenge( $options->algorithm, $challenge, $options->max_number, $options->salt, $signature );
137 }
138
139 /**
140 * Verifies an ALTCHA solution.
141 *
142 * @param array|string $data The solution payload to verify.
143 * @param bool $check_expires Whether to check if the challenge has expired.
144 *
145 * @return bool True if the solution is valid.
146 */
147 public function verify_solution( string|array $data, bool $check_expires = true ): bool {
148 $payload = $this->verify_and_build_solution_payload( $data );
149 if ( ! $payload ) {
150 return false;
151 }
152
153 $params = $this->extract_params( $payload );
154 if ( $check_expires && isset( $params['expires'] ) && is_numeric( $params['expires'] ) ) {
155 $expire_time = (int) $params['expires'];
156 if ( time() > $expire_time ) {
157 return false;
158 }
159 }
160
161 $challenge_options = new Check_Challenge_Options(
162 $payload->algorithm,
163 $payload->salt,
164 $payload->number
165 );
166
167 $expected_challenge = $this->create_challenge( $challenge_options );
168
169 $challenge_ok = hash_equals(
170 $expected_challenge->challenge,
171 $payload->challenge
172 );
173
174 $signature_ok = hash_equals(
175 $expected_challenge->signature,
176 $payload->signature
177 );
178
179 return $challenge_ok && $signature_ok;
180 }
181 }
182