|  | 
| 30 | 30 | #ifdef HAVE_ARGON2LIB | 
| 31 | 31 | #include "argon2.h" | 
| 32 | 32 | #endif | 
|  | 33 | +#include "yescrypt/yescrypt.h" | 
| 33 | 34 | 
 | 
| 34 | 35 | #ifdef PHP_WIN32 | 
| 35 | 36 | #include "win32/winutil.h" | 
| @@ -151,7 +152,8 @@ static bool php_password_bcrypt_needs_rehash(const zend_string *hash, zend_array | 
| 151 | 152 | 	return old_cost != new_cost; | 
| 152 | 153 | } | 
| 153 | 154 | 
 | 
| 154 |  | -static bool php_password_bcrypt_verify(const zend_string *password, const zend_string *hash) { | 
|  | 155 | +/* Password verification using the crypt() API, works for both bcrypt and yescrypt. */ | 
|  | 156 | +static bool php_password_crypt_verify(const zend_string *password, const zend_string *hash) { | 
| 155 | 157 | 	int status = 0; | 
| 156 | 158 | 	zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1); | 
| 157 | 159 | 
 | 
| @@ -224,12 +226,215 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a | 
| 224 | 226 | const php_password_algo php_password_algo_bcrypt = { | 
| 225 | 227 | 	"bcrypt", | 
| 226 | 228 | 	php_password_bcrypt_hash, | 
| 227 |  | -	php_password_bcrypt_verify, | 
|  | 229 | +	php_password_crypt_verify, | 
| 228 | 230 | 	php_password_bcrypt_needs_rehash, | 
| 229 | 231 | 	php_password_bcrypt_get_info, | 
| 230 | 232 | 	php_password_bcrypt_valid, | 
| 231 | 233 | }; | 
| 232 | 234 | 
 | 
|  | 235 | +/* yescrypt implementation */ | 
|  | 236 | + | 
|  | 237 | +static void php_password_yescrypt_expect_long(const char *parameter_name) { | 
|  | 238 | +	if (!EG(exception)) { | 
|  | 239 | +		zend_value_error("Parameter \"%s\" cannot be converted to int", parameter_name); | 
|  | 240 | +	} | 
|  | 241 | +} | 
|  | 242 | + | 
|  | 243 | +static zend_string *php_password_yescrypt_hash(const zend_string *password, zend_array *options) { | 
|  | 244 | +	zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT; | 
|  | 245 | +	zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE; | 
|  | 246 | +	zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM; | 
|  | 247 | +	zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME; | 
|  | 248 | + | 
|  | 249 | +	if (UNEXPECTED(ZEND_LONG_INT_OVFL(ZSTR_LEN(password)))) { | 
|  | 250 | +		zend_value_error("Password is too long"); | 
|  | 251 | +		return NULL; | 
|  | 252 | +	} | 
|  | 253 | + | 
|  | 254 | +	if (options) { | 
|  | 255 | +		bool failed; | 
|  | 256 | +		const zval *option; | 
|  | 257 | + | 
|  | 258 | +		option = zend_hash_str_find(options, ZEND_STRL("block_count")); | 
|  | 259 | +		if (option) { | 
|  | 260 | +			block_count = zval_try_get_long(option, &failed); | 
|  | 261 | +			if (UNEXPECTED(failed)) { | 
|  | 262 | +				php_password_yescrypt_expect_long("block_count"); | 
|  | 263 | +				return NULL; | 
|  | 264 | +			} | 
|  | 265 | + | 
|  | 266 | +			if (block_count < 4 || block_count > UINT32_MAX) { | 
|  | 267 | +				zend_value_error("Parameter \"block_count\" must be between 4 and %u", UINT32_MAX); | 
|  | 268 | +				return NULL; | 
|  | 269 | +			} | 
|  | 270 | +		} | 
|  | 271 | + | 
|  | 272 | +		option = zend_hash_str_find(options, ZEND_STRL("block_size")); | 
|  | 273 | +		if (option) { | 
|  | 274 | +			block_size = zval_try_get_long(option, &failed); | 
|  | 275 | +			if (UNEXPECTED(failed)) { | 
|  | 276 | +				php_password_yescrypt_expect_long("block_size"); | 
|  | 277 | +				return NULL; | 
|  | 278 | +			} | 
|  | 279 | + | 
|  | 280 | +			if (block_size < 1) { | 
|  | 281 | +				zend_value_error("Parameter \"block_size\" must be greater than 0"); | 
|  | 282 | +				return NULL; | 
|  | 283 | +			} | 
|  | 284 | +		} | 
|  | 285 | + | 
|  | 286 | +		option = zend_hash_str_find(options, ZEND_STRL("parallelism")); | 
|  | 287 | +		if (option) { | 
|  | 288 | +			parallelism = zval_try_get_long(option, &failed); | 
|  | 289 | +			if (UNEXPECTED(failed)) { | 
|  | 290 | +				php_password_yescrypt_expect_long("parallelism"); | 
|  | 291 | +				return NULL; | 
|  | 292 | +			} | 
|  | 293 | + | 
|  | 294 | +			if (parallelism < 1) { | 
|  | 295 | +				zend_value_error("Parameter \"parallelism\" must be greater than 0"); | 
|  | 296 | +				return NULL; | 
|  | 297 | +			} | 
|  | 298 | +		} | 
|  | 299 | + | 
|  | 300 | +		option = zend_hash_str_find(options, ZEND_STRL("time")); | 
|  | 301 | +		if (option) { | 
|  | 302 | +			time = zval_try_get_long(option, &failed); | 
|  | 303 | +			if (UNEXPECTED(failed)) { | 
|  | 304 | +				php_password_yescrypt_expect_long("time"); | 
|  | 305 | +				return NULL; | 
|  | 306 | +			} | 
|  | 307 | + | 
|  | 308 | +			if (time < 0) { | 
|  | 309 | +				zend_value_error("Parameter \"time\" must be greater than or equal to 0"); | 
|  | 310 | +				return NULL; | 
|  | 311 | +			} | 
|  | 312 | +		} | 
|  | 313 | + | 
|  | 314 | +		if ((uint64_t) block_size * (uint64_t) parallelism >= (1U << 30)) { | 
|  | 315 | +			zend_value_error("Parameter \"block_size\" * parameter \"parallelism\" must be less than 2**30"); | 
|  | 316 | +			return NULL; | 
|  | 317 | +		} | 
|  | 318 | +	} | 
|  | 319 | + | 
|  | 320 | +	zend_string *salt = php_password_get_salt(NULL, Z_UL(16), options); | 
|  | 321 | +	if (UNEXPECTED(!salt)) { | 
|  | 322 | +		return NULL; | 
|  | 323 | +	} | 
|  | 324 | +	ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0; | 
|  | 325 | + | 
|  | 326 | +	uint8_t prefix_buffer[PREFIX_LEN + 1]; | 
|  | 327 | +	yescrypt_params_t params = { | 
|  | 328 | +		.flags = YESCRYPT_DEFAULTS, | 
|  | 329 | +		.N = block_count, .r = block_size, .p = parallelism, .t = time, | 
|  | 330 | +		.g = 0, .NROM = 0 | 
|  | 331 | +	}; | 
|  | 332 | +	uint8_t *prefix = yescrypt_encode_params_r( | 
|  | 333 | +		¶ms, | 
|  | 334 | +		(const uint8_t *) ZSTR_VAL(salt), | 
|  | 335 | +		ZSTR_LEN(salt), | 
|  | 336 | +		prefix_buffer, | 
|  | 337 | +		sizeof(prefix_buffer) | 
|  | 338 | +	); | 
|  | 339 | + | 
|  | 340 | +	zend_string_release_ex(salt, false); | 
|  | 341 | + | 
|  | 342 | +	if (UNEXPECTED(prefix == NULL)) { | 
|  | 343 | +		return NULL; | 
|  | 344 | +	} | 
|  | 345 | + | 
|  | 346 | +	return php_crypt( | 
|  | 347 | +		ZSTR_VAL(password), | 
|  | 348 | +		/* This cast is safe because we check that the password length fits in an int at the start. */ | 
|  | 349 | +		(int) ZSTR_LEN(password), | 
|  | 350 | +		(const char *) prefix_buffer, | 
|  | 351 | +		/* The following cast is safe because the prefix buffer size is always below INT_MAX. */ | 
|  | 352 | +		(int) strlen((const char *) prefix_buffer), | 
|  | 353 | +		true | 
|  | 354 | +	); | 
|  | 355 | +} | 
|  | 356 | + | 
|  | 357 | +static bool php_password_yescrypt_valid(const zend_string *hash) { | 
|  | 358 | +	const char *h = ZSTR_VAL(hash); | 
|  | 359 | +	/* Note: $7$-style is longer */ | 
|  | 360 | +	return (ZSTR_LEN(hash) >= 3 /* "$y$" */ + 3 /* 3 parameters that must be encoded */ + 2 /* $salt$ */ + HASH_LEN | 
|  | 361 | +			&& ZSTR_LEN(hash) <= PREFIX_LEN + 1 + HASH_LEN) | 
|  | 362 | +			&& (h[0] == '$') && h[1] == 'y' && (h[2] == '$'); | 
|  | 363 | +} | 
|  | 364 | + | 
|  | 365 | +static bool php_password_yescrypt_needs_rehash(const zend_string *hash, zend_array *options) { | 
|  | 366 | +	zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT; | 
|  | 367 | +	zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE; | 
|  | 368 | +	zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM; | 
|  | 369 | +	zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME; | 
|  | 370 | + | 
|  | 371 | +	if (!php_password_yescrypt_valid(hash)) { | 
|  | 372 | +		/* Should never get called this way. */ | 
|  | 373 | +		return true; | 
|  | 374 | +	} | 
|  | 375 | + | 
|  | 376 | +	yescrypt_params_t params = { .p = 1 }; | 
|  | 377 | +	const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), ¶ms, NULL); | 
|  | 378 | +	if (!src) { | 
|  | 379 | +		return true; | 
|  | 380 | +	} | 
|  | 381 | + | 
|  | 382 | +	if (options) { | 
|  | 383 | +		const zval *option; | 
|  | 384 | + | 
|  | 385 | +		option = zend_hash_str_find(options, ZEND_STRL("block_count")); | 
|  | 386 | +		if (option) { | 
|  | 387 | +			block_count = zval_get_long(option); | 
|  | 388 | +		} | 
|  | 389 | + | 
|  | 390 | +		option = zend_hash_str_find(options, ZEND_STRL("block_size")); | 
|  | 391 | +		if (option) { | 
|  | 392 | +			block_size = zval_get_long(option); | 
|  | 393 | +		} | 
|  | 394 | + | 
|  | 395 | +		option = zend_hash_str_find(options, ZEND_STRL("parallelism")); | 
|  | 396 | +		if (option) { | 
|  | 397 | +			parallelism = zval_get_long(option); | 
|  | 398 | +		} | 
|  | 399 | + | 
|  | 400 | +		option = zend_hash_str_find(options, ZEND_STRL("time")); | 
|  | 401 | +		if (option) { | 
|  | 402 | +			time = zval_get_long(option); | 
|  | 403 | +		} | 
|  | 404 | +	} | 
|  | 405 | + | 
|  | 406 | +	return block_count != params.N || block_size != params.r || parallelism != params.p || time != params.t; | 
|  | 407 | +} | 
|  | 408 | + | 
|  | 409 | +static int php_password_yescrypt_get_info(zval *return_value, const zend_string *hash) { | 
|  | 410 | +	if (!php_password_yescrypt_valid(hash)) { | 
|  | 411 | +		/* Should never get called this way. */ | 
|  | 412 | +		return FAILURE; | 
|  | 413 | +	} | 
|  | 414 | + | 
|  | 415 | +	yescrypt_params_t params = { .p = 1 }; | 
|  | 416 | +	const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), ¶ms, NULL); | 
|  | 417 | +	if (!src) { | 
|  | 418 | +		return FAILURE; | 
|  | 419 | +	} | 
|  | 420 | + | 
|  | 421 | +	add_assoc_long(return_value, "block_count", (zend_long) params.N); | 
|  | 422 | +	add_assoc_long(return_value, "block_size", (zend_long) params.r); | 
|  | 423 | +	add_assoc_long(return_value, "parallelism", (zend_long) params.p); | 
|  | 424 | +	add_assoc_long(return_value, "time", (zend_long) params.t); | 
|  | 425 | + | 
|  | 426 | +	return SUCCESS; | 
|  | 427 | +} | 
|  | 428 | + | 
|  | 429 | +const php_password_algo php_password_algo_yescrypt = { | 
|  | 430 | +	"yescrypt", | 
|  | 431 | +	php_password_yescrypt_hash, | 
|  | 432 | +	php_password_crypt_verify, | 
|  | 433 | +	php_password_yescrypt_needs_rehash, | 
|  | 434 | +	php_password_yescrypt_get_info, | 
|  | 435 | +	php_password_yescrypt_valid, | 
|  | 436 | +}; | 
|  | 437 | + | 
| 233 | 438 | 
 | 
| 234 | 439 | #ifdef HAVE_ARGON2LIB | 
| 235 | 440 | /* argon2i/argon2id shared implementation */ | 
| @@ -427,6 +632,10 @@ PHP_MINIT_FUNCTION(password) /* {{{ */ | 
| 427 | 632 | 		return FAILURE; | 
| 428 | 633 | 	} | 
| 429 | 634 | 
 | 
|  | 635 | +	if (FAILURE == php_password_algo_register("y", &php_password_algo_yescrypt)) { | 
|  | 636 | +		return FAILURE; | 
|  | 637 | +	} | 
|  | 638 | + | 
| 430 | 639 | #ifdef HAVE_ARGON2LIB | 
| 431 | 640 | 	if (FAILURE == php_password_algo_register("argon2i", &php_password_algo_argon2i)) { | 
| 432 | 641 | 		return FAILURE; | 
|  | 
0 commit comments