diff --git a/README.REDIST.BINS b/README.REDIST.BINS index 14a93d5e29479..0f6e4fdd7fc6c 100644 --- a/README.REDIST.BINS +++ b/README.REDIST.BINS @@ -19,6 +19,7 @@ 19. xxHash (ext/hash/xxhash) 20. Lexbor (ext/dom/lexbor/lexbor) see ext/dom/lexbor/LICENSE 21. Portions of libcperciva (ext/hash/hash_sha_{ni,sse2}.c) see the header in the source file +22. yescrypt (ext/standard/yescrypt) see the header in the source files 3. pcre2lib (ext/pcre) diff --git a/ext/standard/config.m4 b/ext/standard/config.m4 index ef6b3c5a01018..caa5d095b4394 100644 --- a/ext/standard/config.m4 +++ b/ext/standard/config.m4 @@ -89,6 +89,9 @@ AS_VAR_IF([PHP_EXTERNAL_LIBCRYPT], [no], [ crypt_sha256.c crypt_sha512.c php_crypt_r.c + yescrypt/yescrypt-opt.c + yescrypt/yescrypt-common.c + yescrypt/sha256.c "]) ], [ AC_SEARCH_LIBS([crypt], [crypt], @@ -206,6 +209,33 @@ int main(void) { [ac_cv_crypt_blowfish=no], [ac_cv_crypt_blowfish=no])]) +AC_CACHE_CHECK([for Yescrypt crypt], [ac_cv_crypt_yescrypt], + [AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_CRYPT_H +#include <crypt.h> +#endif + +#include <stdlib.h> +#include <string.h> + +int main(void) { + char answer[128]; + char *encrypted; + char salt[] = "\$y\$j9T\$fFqB7ZKMpdoOep2IXlKMuBnGplYOF/\$"; + + strcpy(answer, salt); + strcpy(&answer[sizeof(salt) - 1], "YUbFz9cPA2OISKzl1FhXHQP556fm3v7K1PBuIcVwyL/"); + encrypted = crypt("rasmuslerdorf", salt); + return !encrypted || strcmp(encrypted, answer); +}]])], + [ac_cv_crypt_yescrypt=yes], + [ac_cv_crypt_yescrypt=no], + [ac_cv_crypt_yescrypt=no])]) + AC_CACHE_CHECK([for SHA512 crypt], [ac_cv_crypt_sha512], [AC_RUN_IFELSE([AC_LANG_SOURCE([[ #ifdef HAVE_UNISTD_H @@ -260,7 +290,7 @@ int main(void) { [ac_cv_crypt_sha256=no], [ac_cv_crypt_sha256=no])]) - if test "$ac_cv_crypt_blowfish" = "no" || test "$ac_cv_crypt_des" = "no" || test "$ac_cv_crypt_ext_des" = "no" || test "$ac_cv_crypt_md5" = "no" || test "$ac_cv_crypt_sha512" = "no" || test "$ac_cv_crypt_sha256" = "no"; then + if test "$ac_cv_crypt_blowfish" = "no" || test "$ac_cv_crypt_yescrypt" = "no" || test "$ac_cv_crypt_des" = "no" || test "$ac_cv_crypt_ext_des" = "no" || test "$ac_cv_crypt_md5" = "no" || test "$ac_cv_crypt_sha512" = "no" || test "$ac_cv_crypt_sha256" = "no"; then AC_MSG_FAILURE([Cannot use external libcrypt as some algo are missing.]) fi @@ -396,6 +426,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([ crc32.c credits.c crypt.c + yescrypt/yescrypt-config.c css.c datetime.c dir.c @@ -456,6 +487,7 @@ PHP_NEW_EXTENSION([standard], m4_normalize([ [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1]) PHP_ADD_BUILD_DIR([$ext_builddir/libavifinfo]) +PHP_ADD_BUILD_DIR([$ext_builddir/yescrypt]) PHP_ADD_MAKEFILE_FRAGMENT PHP_INSTALL_HEADERS([ext/standard/]) diff --git a/ext/standard/config.w32 b/ext/standard/config.w32 index c7c14b8705ca2..8d07c55d3a19f 100644 --- a/ext/standard/config.w32 +++ b/ext/standard/config.w32 @@ -38,6 +38,7 @@ EXTENSION("standard", "array.c base64.c basic_functions.c browscap.c \ streamsfuncs.c http.c flock_compat.c hrtime.c", false /* never shared */, '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); ADD_SOURCES("ext/standard/libavifinfo", "avifinfo.c", "standard"); +ADD_SOURCES("ext/standard/yescrypt", "yescrypt-opt.c yescrypt-common.c yescrypt-config.c sha256.c", "standard"); PHP_STANDARD = "yes"; ADD_MAKEFILE_FRAGMENT(); PHP_INSTALL_HEADERS("", "ext/standard"); diff --git a/ext/standard/crypt.c b/ext/standard/crypt.c index 54687f6cdf307..47195dade1273 100644 --- a/ext/standard/crypt.c +++ b/ext/standard/crypt.c @@ -27,6 +27,7 @@ #if PHP_USE_PHP_CRYPT_R # include "php_crypt_r.h" # include "crypt_freesec.h" +# include "yescrypt/yescrypt.h" #else # ifdef HAVE_CRYPT_H # if defined(CRYPT_R_GNU_SOURCE) && !defined(_GNU_SOURCE) @@ -76,6 +77,19 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch return NULL; } + if (salt[0] == '$' && (salt[1] == 'y' || salt[1] == '7') && salt[2] == '$') { + /* Reference yescrypt can handle NUL bytes in the password, but sytem crypt cannot. + * Return NULL for both cases for consistency. */ + if (zend_char_has_nul_byte(password, (size_t) pass_len)) { + return NULL; + } + + /* Neither reference yescrypt nor system crypt can handle NUL bytes in the salt. */ + if (zend_char_has_nul_byte(salt, (size_t) salt_len)) { + return NULL; + } + } + /* Windows (win32/crypt) has a stripped down version of libxcrypt and a CryptoApi md5_crypt implementation */ #if PHP_USE_PHP_CRYPT_R @@ -138,6 +152,33 @@ PHPAPI zend_string *php_crypt(const char *password, const int pass_len, const ch ZEND_SECURE_ZERO(output, PHP_MAX_SALT_LEN + 1); return result; } + } else if (salt[0] == '$' && (salt[1] == 'y' || salt[1] == '7') && salt[2] == '$') { + yescrypt_local_t local; + uint8_t buf[PREFIX_LEN + 1 + HASH_LEN + 1]; /* prefix, '$', hash, NUL */ + + if (yescrypt_init_local(&local)) { + return NULL; + } + + uint8_t *hash = yescrypt_r( + NULL, + &local, + (const uint8_t *) password, + (size_t) pass_len, + (const uint8_t *) salt, + NULL /* no key */, + buf, + sizeof(buf) + ); + + if (yescrypt_free_local(&local) || !hash) { + ZEND_SECURE_ZERO(buf, sizeof(buf)); + return NULL; + } + + result = zend_string_init((const char *) hash, strlen((const char *) hash), false); + ZEND_SECURE_ZERO(buf, sizeof(buf)); + return result; } else if (salt[0] == '_' || (IS_VALID_SALT_CHARACTER(salt[0]) && IS_VALID_SALT_CHARACTER(salt[1]))) { /* DES Fallback */ diff --git a/ext/standard/password.c b/ext/standard/password.c index 1e647bb301c3b..7713654731ab3 100644 --- a/ext/standard/password.c +++ b/ext/standard/password.c @@ -30,6 +30,7 @@ #ifdef HAVE_ARGON2LIB #include "argon2.h" #endif +#include "yescrypt/yescrypt.h" #ifdef PHP_WIN32 #include "win32/winutil.h" @@ -151,7 +152,8 @@ static bool php_password_bcrypt_needs_rehash(const zend_string *hash, zend_array return old_cost != new_cost; } -static bool php_password_bcrypt_verify(const zend_string *password, const zend_string *hash) { +/* Password verification using the crypt() API, works for both bcrypt and yescrypt. */ +static bool php_password_crypt_verify(const zend_string *password, const zend_string *hash) { int status = 0; zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1); @@ -224,12 +226,215 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a const php_password_algo php_password_algo_bcrypt = { "bcrypt", php_password_bcrypt_hash, - php_password_bcrypt_verify, + php_password_crypt_verify, php_password_bcrypt_needs_rehash, php_password_bcrypt_get_info, php_password_bcrypt_valid, }; +/* yescrypt implementation */ + +static void php_password_yescrypt_expect_long(const char *parameter_name) { + if (!EG(exception)) { + zend_value_error("Parameter \"%s\" cannot be converted to int", parameter_name); + } +} + +static zend_string *php_password_yescrypt_hash(const zend_string *password, zend_array *options) { + zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT; + zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE; + zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM; + zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME; + + if (UNEXPECTED(ZEND_LONG_INT_OVFL(ZSTR_LEN(password)))) { + zend_value_error("Password is too long"); + return NULL; + } + + if (options) { + bool failed; + const zval *option; + + option = zend_hash_str_find(options, ZEND_STRL("block_count")); + if (option) { + block_count = zval_try_get_long(option, &failed); + if (UNEXPECTED(failed)) { + php_password_yescrypt_expect_long("block_count"); + return NULL; + } + + if (block_count < 4 || block_count > UINT32_MAX) { + zend_value_error("Parameter \"block_count\" must be between 4 and %u", UINT32_MAX); + return NULL; + } + } + + option = zend_hash_str_find(options, ZEND_STRL("block_size")); + if (option) { + block_size = zval_try_get_long(option, &failed); + if (UNEXPECTED(failed)) { + php_password_yescrypt_expect_long("block_size"); + return NULL; + } + + if (block_size < 1) { + zend_value_error("Parameter \"block_size\" must be greater than 0"); + return NULL; + } + } + + option = zend_hash_str_find(options, ZEND_STRL("parallelism")); + if (option) { + parallelism = zval_try_get_long(option, &failed); + if (UNEXPECTED(failed)) { + php_password_yescrypt_expect_long("parallelism"); + return NULL; + } + + if (parallelism < 1) { + zend_value_error("Parameter \"parallelism\" must be greater than 0"); + return NULL; + } + } + + option = zend_hash_str_find(options, ZEND_STRL("time")); + if (option) { + time = zval_try_get_long(option, &failed); + if (UNEXPECTED(failed)) { + php_password_yescrypt_expect_long("time"); + return NULL; + } + + if (time < 0) { + zend_value_error("Parameter \"time\" must be greater than or equal to 0"); + return NULL; + } + } + + if ((uint64_t) block_size * (uint64_t) parallelism >= (1U << 30)) { + zend_value_error("Parameter \"block_size\" * parameter \"parallelism\" must be less than 2**30"); + return NULL; + } + } + + zend_string *salt = php_password_get_salt(NULL, Z_UL(16), options); + if (UNEXPECTED(!salt)) { + return NULL; + } + ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0; + + uint8_t prefix_buffer[PREFIX_LEN + 1]; + yescrypt_params_t params = { + .flags = YESCRYPT_DEFAULTS, + .N = block_count, .r = block_size, .p = parallelism, .t = time, + .g = 0, .NROM = 0 + }; + uint8_t *prefix = yescrypt_encode_params_r( + ¶ms, + (const uint8_t *) ZSTR_VAL(salt), + ZSTR_LEN(salt), + prefix_buffer, + sizeof(prefix_buffer) + ); + + zend_string_release_ex(salt, false); + + if (UNEXPECTED(prefix == NULL)) { + return NULL; + } + + return php_crypt( + ZSTR_VAL(password), + /* This cast is safe because we check that the password length fits in an int at the start. */ + (int) ZSTR_LEN(password), + (const char *) prefix_buffer, + /* The following cast is safe because the prefix buffer size is always below INT_MAX. */ + (int) strlen((const char *) prefix_buffer), + true + ); +} + +static bool php_password_yescrypt_valid(const zend_string *hash) { + const char *h = ZSTR_VAL(hash); + /* Note: $7$-style is longer */ + return (ZSTR_LEN(hash) >= 3 /* "$y$" */ + 3 /* 3 parameters that must be encoded */ + 2 /* $salt$ */ + HASH_LEN + && ZSTR_LEN(hash) <= PREFIX_LEN + 1 + HASH_LEN) + && (h[0] == '$') && h[1] == 'y' && (h[2] == '$'); +} + +static bool php_password_yescrypt_needs_rehash(const zend_string *hash, zend_array *options) { + zend_long block_count = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT; + zend_long block_size = PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE; + zend_long parallelism = PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM; + zend_long time = PHP_PASSWORD_YESCRYPT_DEFAULT_TIME; + + if (!php_password_yescrypt_valid(hash)) { + /* Should never get called this way. */ + return true; + } + + yescrypt_params_t params = { .p = 1 }; + const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), ¶ms, NULL); + if (!src) { + return true; + } + + if (options) { + const zval *option; + + option = zend_hash_str_find(options, ZEND_STRL("block_count")); + if (option) { + block_count = zval_get_long(option); + } + + option = zend_hash_str_find(options, ZEND_STRL("block_size")); + if (option) { + block_size = zval_get_long(option); + } + + option = zend_hash_str_find(options, ZEND_STRL("parallelism")); + if (option) { + parallelism = zval_get_long(option); + } + + option = zend_hash_str_find(options, ZEND_STRL("time")); + if (option) { + time = zval_get_long(option); + } + } + + return block_count != params.N || block_size != params.r || parallelism != params.p || time != params.t; +} + +static int php_password_yescrypt_get_info(zval *return_value, const zend_string *hash) { + if (!php_password_yescrypt_valid(hash)) { + /* Should never get called this way. */ + return FAILURE; + } + + yescrypt_params_t params = { .p = 1 }; + const uint8_t *src = yescrypt_parse_settings((const uint8_t *) ZSTR_VAL(hash), ¶ms, NULL); + if (!src) { + return FAILURE; + } + + add_assoc_long(return_value, "block_count", (zend_long) params.N); + add_assoc_long(return_value, "block_size", (zend_long) params.r); + add_assoc_long(return_value, "parallelism", (zend_long) params.p); + add_assoc_long(return_value, "time", (zend_long) params.t); + + return SUCCESS; +} + +const php_password_algo php_password_algo_yescrypt = { + "yescrypt", + php_password_yescrypt_hash, + php_password_crypt_verify, + php_password_yescrypt_needs_rehash, + php_password_yescrypt_get_info, + php_password_yescrypt_valid, +}; + #ifdef HAVE_ARGON2LIB /* argon2i/argon2id shared implementation */ @@ -427,6 +632,10 @@ PHP_MINIT_FUNCTION(password) /* {{{ */ return FAILURE; } + if (FAILURE == php_password_algo_register("y", &php_password_algo_yescrypt)) { + return FAILURE; + } + #ifdef HAVE_ARGON2LIB if (FAILURE == php_password_algo_register("argon2i", &php_password_algo_argon2i)) { return FAILURE; diff --git a/ext/standard/password.stub.php b/ext/standard/password.stub.php index c3c99117d514c..7cb796b6c8ccf 100644 --- a/ext/standard/password.stub.php +++ b/ext/standard/password.stub.php @@ -10,12 +10,37 @@ * @var string */ const PASSWORD_BCRYPT = "2y"; +/** + * @var string + */ +const PASSWORD_YESCRYPT = "y"; /** * @var int * @cvalue PHP_PASSWORD_BCRYPT_COST */ const PASSWORD_BCRYPT_DEFAULT_COST = UNKNOWN; +/** + * @var int + * @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT + */ +const PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT = UNKNOWN; +/** + * @var int + * @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE + */ +const PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE = UNKNOWN; +/** + * @var int + * @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM + */ +const PASSWORD_YESCRYPT_DEFAULT_PARALLELISM = UNKNOWN; +/** + * @var int + * @cvalue PHP_PASSWORD_YESCRYPT_DEFAULT_TIME + */ +const PASSWORD_YESCRYPT_DEFAULT_TIME = UNKNOWN; + #ifdef HAVE_ARGON2LIB /** * @var string diff --git a/ext/standard/password_arginfo.h b/ext/standard/password_arginfo.h index bf1b614273a4a..fd3d27705afec 100644 --- a/ext/standard/password_arginfo.h +++ b/ext/standard/password_arginfo.h @@ -1,11 +1,16 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: aab38646ace967e985c348b78251474693da95a7 */ + * Stub hash: ae870a71e45d4cd590c24498f7fd88afe213b3b5 */ static void register_password_symbols(int module_number) { REGISTER_STRING_CONSTANT("PASSWORD_DEFAULT", "2y", CONST_PERSISTENT); REGISTER_STRING_CONSTANT("PASSWORD_BCRYPT", "2y", CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("PASSWORD_YESCRYPT", "y", CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT", PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE", PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PASSWORD_YESCRYPT_DEFAULT_PARALLELISM", PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PASSWORD_YESCRYPT_DEFAULT_TIME", PHP_PASSWORD_YESCRYPT_DEFAULT_TIME, CONST_PERSISTENT); #if defined(HAVE_ARGON2LIB) REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_PERSISTENT); #endif diff --git a/ext/standard/php_password.h b/ext/standard/php_password.h index 5a6f3b2af5c9e..04ad0e9b2ad4a 100644 --- a/ext/standard/php_password.h +++ b/ext/standard/php_password.h @@ -24,6 +24,11 @@ PHP_MSHUTDOWN_FUNCTION(password); #define PHP_PASSWORD_DEFAULT PHP_PASSWORD_BCRYPT #define PHP_PASSWORD_BCRYPT_COST 12 +#define PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT 4096 +#define PHP_PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE 32 +#define PHP_PASSWORD_YESCRYPT_DEFAULT_PARALLELISM 1 +#define PHP_PASSWORD_YESCRYPT_DEFAULT_TIME 0 + #ifdef HAVE_ARGON2LIB /** * When updating these values, synchronize values in diff --git a/ext/standard/tests/crypt/yescrypt.phpt b/ext/standard/tests/crypt/yescrypt.phpt new file mode 100644 index 0000000000000..35f8498e590c4 --- /dev/null +++ b/ext/standard/tests/crypt/yescrypt.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test crypt() with yescrypt +--FILE-- +<?php + +var_dump(crypt("test", '$y$')); +var_dump(crypt("test", '$y$$')); +var_dump(crypt("test", '$y$j$')); +var_dump(crypt("test", '$y$j9$')); +var_dump(crypt("test\0x", '$y$j9T$salt'.chr(0))); +var_dump(crypt("test\0", '$y$j9T$salt')); +var_dump(crypt("test\0x", '$y$j9T$salt')); +var_dump(crypt("\0", '$y$j9T$salt')); + +var_dump(crypt("test", '$y$j9T$')); +var_dump(crypt("test", '$y$j9T$salt')); +var_dump(crypt("test", '$y$j9T$salt$')); +var_dump(crypt("", '$y$j9T$salt')); + +var_dump(crypt("", '$7$400.../....$')); +var_dump(crypt("", '$7$400.../....$salt$')); + +?> +--EXPECT-- +string(2) "*0" +string(2) "*0" +string(2) "*0" +string(2) "*0" +string(2) "*0" +string(2) "*0" +string(2) "*0" +string(2) "*0" +string(51) "$y$j9T$$6tN6tt5mmPHxQskcf5Oi7Sb.1nKYbi5cOZgTiMq7Qw4" +string(55) "$y$j9T$salt$a9CZafQyDF042zUCgPAhoF7Zd5phBweZqIIw6SMCTh." +string(55) "$y$j9T$salt$a9CZafQyDF042zUCgPAhoF7Zd5phBweZqIIw6SMCTh." +string(55) "$y$j9T$salt$sE5vvd.NbRw0CRzUgcEQ/PZMH4hmete7N5s3qN09F12" +string(58) "$7$400.../....$fsLd.toTUvgzSAYmoHbKwQGAmqLK6y.yIpW2WKuemOA" +string(63) "$7$400.../....$salt$3SJITk6BqtXkmuOQkPe7e.yClr8MVXc6twSB2ZBHPE3" diff --git a/ext/standard/tests/password/password_get_info_yescrypt.phpt b/ext/standard/tests/password/password_get_info_yescrypt.phpt new file mode 100644 index 0000000000000..00a1b392f2ce4 --- /dev/null +++ b/ext/standard/tests/password/password_get_info_yescrypt.phpt @@ -0,0 +1,76 @@ +--TEST-- +Test normal operation of password_get_info() with Yescrypt +--FILE-- +<?php + +var_dump(password_get_info('$y$jC5//$7NbMKtqBsR3PDV5JHBYPDtaRD3nRg/$FT4mgFH/6EJNHRAGvD6yvzGjCo01KpIhLwGbQW.Nxk1')); +var_dump(password_get_info('$y$jC50..$bBICnZqGiE5P5h4KjoYGpp4S773JB/$ZcD9FJW35.VG0kN0hh5C7oXa3o3dSBSXg/WDaTiWsA8')); +var_dump(password_get_info('$y$jA.0./$pU1KtJbSnMYKcNIQZZLPpAXFpZ4RB/$TeI5bGZQ5l589gWeUjaJzSlIPQZk7Wp2gLsnVG0gJH6')); + +var_dump(password_get_info('$y$jA.0./$')); + +echo "OK!"; + +?> +--EXPECT-- +array(3) { + ["algo"]=> + string(1) "y" + ["algoName"]=> + string(8) "yescrypt" + ["options"]=> + array(4) { + ["block_count"]=> + int(32768) + ["block_size"]=> + int(8) + ["parallelism"]=> + int(1) + ["time"]=> + int(2) + } +} +array(3) { + ["algo"]=> + string(1) "y" + ["algoName"]=> + string(8) "yescrypt" + ["options"]=> + array(4) { + ["block_count"]=> + int(32768) + ["block_size"]=> + int(8) + ["parallelism"]=> + int(2) + ["time"]=> + int(1) + } +} +array(3) { + ["algo"]=> + string(1) "y" + ["algoName"]=> + string(8) "yescrypt" + ["options"]=> + array(4) { + ["block_count"]=> + int(8192) + ["block_size"]=> + int(1) + ["parallelism"]=> + int(2) + ["time"]=> + int(2) + } +} +array(3) { + ["algo"]=> + NULL + ["algoName"]=> + string(7) "unknown" + ["options"]=> + array(0) { + } +} +OK! diff --git a/ext/standard/tests/password/password_hash.phpt b/ext/standard/tests/password/password_hash.phpt index 6eb786887ba72..cc22b71fb4a6d 100644 --- a/ext/standard/tests/password/password_hash.phpt +++ b/ext/standard/tests/password/password_hash.phpt @@ -8,8 +8,11 @@ Test normal operation of password_hash() var_dump(password_hash("foo", PASSWORD_BCRYPT)); +var_dump(password_hash("foo", PASSWORD_YESCRYPT)); + $algos = [ PASSWORD_BCRYPT, + PASSWORD_YESCRYPT, '2y', 1, ]; @@ -23,6 +26,8 @@ echo "OK!"; ?> --EXPECTF-- string(60) "$2y$12$%s" +string(73) "$y$j9T$%s" +bool(true) bool(true) bool(true) bool(true) diff --git a/ext/standard/tests/password/password_hash_error_yescrypt.phpt b/ext/standard/tests/password/password_hash_error_yescrypt.phpt new file mode 100644 index 0000000000000..244c37fe6d4e9 --- /dev/null +++ b/ext/standard/tests/password/password_hash_error_yescrypt.phpt @@ -0,0 +1,69 @@ +--TEST-- +Test error operation of password_hash() with Yescrypt +--FILE-- +<?php +try { + password_hash('test', PASSWORD_YESCRYPT, ['block_count' => 3]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + password_hash('test', PASSWORD_YESCRYPT, ['block_count' => -1]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + password_hash('test', PASSWORD_YESCRYPT, ['block_count' => []]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + password_hash('test', PASSWORD_YESCRYPT, ['block_size' => 0]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + password_hash('test', PASSWORD_YESCRYPT, ['block_size' => []]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + password_hash('test', PASSWORD_YESCRYPT, ['parallelism' => 0]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + password_hash('test', PASSWORD_YESCRYPT, ['parallelism' => []]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + password_hash('test', PASSWORD_YESCRYPT, ['time' => -1]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +try { + password_hash('test', PASSWORD_YESCRYPT, ['time' => []]); +} catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; +} + +?> +--EXPECT-- +Parameter "block_count" must be between 4 and 4294967295 +Parameter "block_count" must be between 4 and 4294967295 +Parameter "block_count" cannot be converted to int +Parameter "block_size" must be greater than 0 +Parameter "block_size" cannot be converted to int +Parameter "parallelism" must be greater than 0 +Parameter "parallelism" cannot be converted to int +Parameter "time" must be greater than or equal to 0 +Parameter "time" cannot be converted to int diff --git a/ext/standard/tests/password/password_hash_yescrypt.phpt b/ext/standard/tests/password/password_hash_yescrypt.phpt new file mode 100644 index 0000000000000..a4833b56f1fcf --- /dev/null +++ b/ext/standard/tests/password/password_hash_yescrypt.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test normal operation of password_hash() with Yescrypt +--FILE-- +<?php + +$password = "the password for testing 12345!"; + +$hash = password_hash($password, PASSWORD_YESCRYPT); +var_dump(password_verify($password, $hash)); +var_dump(password_get_info($hash)['algo']); + +echo "OK!"; +?> +--EXPECT-- +bool(true) +string(1) "y" +OK! diff --git a/ext/standard/tests/password/password_needs_rehash_yescrypt.phpt b/ext/standard/tests/password/password_needs_rehash_yescrypt.phpt new file mode 100644 index 0000000000000..0ef78c7a79677 --- /dev/null +++ b/ext/standard/tests/password/password_needs_rehash_yescrypt.phpt @@ -0,0 +1,21 @@ +--TEST-- +Test normal operation of password_needs_rehash() with Yescrypt +--FILE-- +<?php + +$hash = password_hash('test', PASSWORD_YESCRYPT); +var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT)); +var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT, ['block_size' => PASSWORD_YESCRYPT_DEFAULT_BLOCK_SIZE * 2])); +var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT, ['block_count' => PASSWORD_YESCRYPT_DEFAULT_BLOCK_COUNT * 2])); +var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT, ['parallelism' => PASSWORD_YESCRYPT_DEFAULT_PARALLELISM + 1])); +var_dump(password_needs_rehash($hash, PASSWORD_YESCRYPT, ['time' => PASSWORD_YESCRYPT_DEFAULT_TIME + 1])); + +echo "OK!"; +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +OK! diff --git a/ext/standard/tests/password/password_verify.phpt b/ext/standard/tests/password/password_verify.phpt index 5f2ff1230cc63..99ad58f40ba93 100644 --- a/ext/standard/tests/password/password_verify.phpt +++ b/ext/standard/tests/password/password_verify.phpt @@ -1,5 +1,5 @@ --TEST-- -Test normal operation of password_verify) +Test normal operation of password_verify() --FILE-- <?php //-=-=-=- diff --git a/ext/standard/tests/password/password_verify_yescrypt.phpt b/ext/standard/tests/password/password_verify_yescrypt.phpt new file mode 100644 index 0000000000000..72abfa00548d0 --- /dev/null +++ b/ext/standard/tests/password/password_verify_yescrypt.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test normal operation of password_verify() with Yescrypt +--FILE-- +<?php + +// TODO: what to do with \0 ??? + +var_dump(password_verify('letmein', '$y$jA.0./$nE5IrNnOpcaIL7JKLRrJANbOMhoOg/$Gdobttkzk5ona2UNoBZfepu2Gl3w3NQXloEfWz4KSq3')); +var_dump(password_verify('test', '$y$jA.0./$nE5IrNnOpcaIL7JKLRrJANbOMhoOg/$Gdobttkzk5ona2UNoBZfepu2Gl3w3NQXloEfWz4KSq3')); +var_dump(password_verify('letmei', '$y$jA.0./$nE5IrNnOpcaIL7JKLRrJANbOMhoOg/$Gdobttkzk5ona2UNoBZfepu2Gl3w3NQXloEfWz4KSq3')); +var_dump(password_verify('letmein', '$y$jA.1./$nE5IrNnOpcaIL7JKLRrJANbOMhoOg/$Gdobttkzk5ona2UNoBZfepu2Gl3w3NQXloEfWz4KSq3')); +var_dump(password_verify('letmein', '$y$jA.1./$'.chr(0))); + +var_dump(password_verify('this is yescrypt', '$y$j9T$oZ1CpcJNi71CnQpOHlY9q.$7KlWq26Kv1pBblGfbg3HQn7j84oZv4dSYIAlZ2YzOh6')); +var_dump(password_verify('test', '$y$j9T$oZ1CpcJNi71CnQpOHlY9q.$7KlWq26Kv1pBblGfbg3HQn7j84oZv4dSYIAlZ2YzOh6')); +var_dump(password_verify('letmein', '$y$j9T$oZ1CpcJNi71CnQpOHlY9q.$7KlWq26Kv1pBblGfbg3HQn7j84oZv4dSYIAlZ2YzOh6')); + +echo "OK!"; +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(false) +bool(false) +OK! diff --git a/ext/standard/yescrypt/insecure_memzero.h b/ext/standard/yescrypt/insecure_memzero.h new file mode 100644 index 0000000000000..f46b44f1b6482 --- /dev/null +++ b/ext/standard/yescrypt/insecure_memzero.h @@ -0,0 +1,22 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef INSECURE_MEMZERO_H +#define INSECURE_MEMZERO_H + +#include "php.h" +#include "zend_portability.h" +#define insecure_memzero ZEND_SECURE_ZERO + +#endif diff --git a/ext/standard/yescrypt/sha256.c b/ext/standard/yescrypt/sha256.c new file mode 100644 index 0000000000000..614475e988b5b --- /dev/null +++ b/ext/standard/yescrypt/sha256.c @@ -0,0 +1,653 @@ +/*- + * Copyright 2005-2016 Colin Percival + * Copyright 2016-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#include "insecure_memzero.h" +#include "sysendian.h" + +#include "sha256.h" + +#ifdef __ICC +/* Miscompile with icc 14.0.0 (at least), so don't use restrict there */ +#define restrict +#elif __STDC_VERSION__ >= 199901L +/* Have restrict */ +#elif defined(__GNUC__) +#define restrict __restrict +#else +#define restrict +#endif + +/* PHP change: Windows compatibility */ +#ifdef _MSC_VER +# define PHP_STATIC_RESTRICT +#else +# define PHP_STATIC_RESTRICT static restrict +#endif + +/* + * Encode a length len*2 vector of (uint32_t) into a length len*8 vector of + * (uint8_t) in big-endian form. + */ +static void +be32enc_vect(uint8_t * dst, const uint32_t * src, size_t len) +{ + + /* Encode vector, two words at a time. */ + do { + be32enc(&dst[0], src[0]); + be32enc(&dst[4], src[1]); + src += 2; + dst += 8; + } while (--len); +} + +/* + * Decode a big-endian length len*8 vector of (uint8_t) into a length + * len*2 vector of (uint32_t). + */ +static void +be32dec_vect(uint32_t * dst, const uint8_t * src, size_t len) +{ + + /* Decode vector, two words at a time. */ + do { + dst[0] = be32dec(&src[0]); + dst[1] = be32dec(&src[4]); + src += 8; + dst += 2; + } while (--len); +} + +/* SHA256 round constants. */ +static const uint32_t Krnd[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/* Elementary functions used by SHA256 */ +#define Ch(x, y, z) ((x & (y ^ z)) ^ z) +#define Maj(x, y, z) ((x & (y | z)) | (y & z)) +#define SHR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << (32 - n))) +#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +/* SHA256 round function */ +#define RND(a, b, c, d, e, f, g, h, k) \ + h += S1(e) + Ch(e, f, g) + k; \ + d += h; \ + h += S0(a) + Maj(a, b, c); + +/* Adjusted round function for rotating state */ +#define RNDr(S, W, i, ii) \ + RND(S[(64 - i) % 8], S[(65 - i) % 8], \ + S[(66 - i) % 8], S[(67 - i) % 8], \ + S[(68 - i) % 8], S[(69 - i) % 8], \ + S[(70 - i) % 8], S[(71 - i) % 8], \ + W[i + ii] + Krnd[i + ii]) + +/* Message schedule computation */ +#define MSCH(W, ii, i) \ + W[i + ii + 16] = s1(W[i + ii + 14]) + W[i + ii + 9] + s0(W[i + ii + 1]) + W[i + ii] + +/* + * SHA256 block compression function. The 256-bit state is transformed via + * the 512-bit input block to produce a new state. + */ +static void +SHA256_Transform(uint32_t state[PHP_STATIC_RESTRICT 8], + const uint8_t block[PHP_STATIC_RESTRICT 64], + uint32_t W[PHP_STATIC_RESTRICT 64], uint32_t S[PHP_STATIC_RESTRICT 8]) +{ + int i; + + /* 1. Prepare the first part of the message schedule W. */ + be32dec_vect(W, block, 8); + + /* 2. Initialize working variables. */ + memcpy(S, state, 32); + + /* 3. Mix. */ + for (i = 0; i < 64; i += 16) { + RNDr(S, W, 0, i); + RNDr(S, W, 1, i); + RNDr(S, W, 2, i); + RNDr(S, W, 3, i); + RNDr(S, W, 4, i); + RNDr(S, W, 5, i); + RNDr(S, W, 6, i); + RNDr(S, W, 7, i); + RNDr(S, W, 8, i); + RNDr(S, W, 9, i); + RNDr(S, W, 10, i); + RNDr(S, W, 11, i); + RNDr(S, W, 12, i); + RNDr(S, W, 13, i); + RNDr(S, W, 14, i); + RNDr(S, W, 15, i); + + if (i == 48) + break; + MSCH(W, 0, i); + MSCH(W, 1, i); + MSCH(W, 2, i); + MSCH(W, 3, i); + MSCH(W, 4, i); + MSCH(W, 5, i); + MSCH(W, 6, i); + MSCH(W, 7, i); + MSCH(W, 8, i); + MSCH(W, 9, i); + MSCH(W, 10, i); + MSCH(W, 11, i); + MSCH(W, 12, i); + MSCH(W, 13, i); + MSCH(W, 14, i); + MSCH(W, 15, i); + } + + /* 4. Mix local working variables into global state. */ + state[0] += S[0]; + state[1] += S[1]; + state[2] += S[2]; + state[3] += S[3]; + state[4] += S[4]; + state[5] += S[5]; + state[6] += S[6]; + state[7] += S[7]; +} + +static const uint8_t PAD[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* Add padding and terminating bit-count. */ +static void +SHA256_Pad(SHA256_CTX * ctx, uint32_t tmp32[PHP_STATIC_RESTRICT 72]) +{ + size_t r; + + /* Figure out how many bytes we have buffered. */ + r = (ctx->count >> 3) & 0x3f; + + /* Pad to 56 mod 64, transforming if we finish a block en route. */ + if (r < 56) { + /* Pad to 56 mod 64. */ + memcpy(&ctx->buf[r], PAD, 56 - r); + } else { + /* Finish the current block and mix. */ + memcpy(&ctx->buf[r], PAD, 64 - r); + SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); + + /* The start of the final block is all zeroes. */ + memset(&ctx->buf[0], 0, 56); + } + + /* Add the terminating bit-count. */ + be64enc(&ctx->buf[56], ctx->count); + + /* Mix in the final block. */ + SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); +} + +/* Magic initialization constants. */ +static const uint32_t initial_state[8] = { + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 +}; + +/** + * SHA256_Init(ctx): + * Initialize the SHA256 context ${ctx}. + */ +void +SHA256_Init(SHA256_CTX * ctx) +{ + + /* Zero bits processed so far. */ + ctx->count = 0; + + /* Initialize state. */ + memcpy(ctx->state, initial_state, sizeof(initial_state)); +} + +/** + * SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the SHA256 context ${ctx}. + */ +static void +_SHA256_Update(SHA256_CTX * ctx, const void * in, size_t len, + uint32_t tmp32[PHP_STATIC_RESTRICT 72]) +{ + uint32_t r; + const uint8_t * src = in; + + /* Return immediately if we have nothing to do. */ + if (len == 0) + return; + + /* Number of bytes left in the buffer from previous updates. */ + r = (ctx->count >> 3) & 0x3f; + + /* Update number of bits. */ + ctx->count += (uint64_t)(len) << 3; + + /* Handle the case where we don't need to perform any transforms. */ + if (len < 64 - r) { + memcpy(&ctx->buf[r], src, len); + return; + } + + /* Finish the current block. */ + memcpy(&ctx->buf[r], src, 64 - r); + SHA256_Transform(ctx->state, ctx->buf, &tmp32[0], &tmp32[64]); + src += 64 - r; + len -= 64 - r; + + /* Perform complete blocks. */ + while (len >= 64) { + SHA256_Transform(ctx->state, src, &tmp32[0], &tmp32[64]); + src += 64; + len -= 64; + } + + /* Copy left over data into buffer. */ + memcpy(ctx->buf, src, len); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +SHA256_Update(SHA256_CTX * ctx, const void * in, size_t len) +{ + uint32_t tmp32[72]; + + /* Call the real function. */ + _SHA256_Update(ctx, in, len, tmp32); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); +} + +/** + * SHA256_Final(digest, ctx): + * Output the SHA256 hash of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +static void +_SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx, + uint32_t tmp32[PHP_STATIC_RESTRICT 72]) +{ + + /* Add padding. */ + SHA256_Pad(ctx, tmp32); + + /* Write the hash. */ + be32enc_vect(digest, ctx->state, 4); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx) +{ + uint32_t tmp32[72]; + + /* Call the real function. */ + _SHA256_Final(digest, ctx, tmp32); + + /* Clear the context state. */ + insecure_memzero(ctx, sizeof(SHA256_CTX)); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); +} + +/** + * SHA256_Buf(in, len, digest): + * Compute the SHA256 hash of ${len} bytes from ${in} and write it to ${digest}. + */ +void +SHA256_Buf(const void * in, size_t len, uint8_t digest[32]) +{ + SHA256_CTX ctx; + uint32_t tmp32[72]; + + SHA256_Init(&ctx); + _SHA256_Update(&ctx, in, len, tmp32); + _SHA256_Final(digest, &ctx, tmp32); + + /* Clean the stack. */ + insecure_memzero(&ctx, sizeof(SHA256_CTX)); + insecure_memzero(tmp32, 288); +} + +/** + * HMAC_SHA256_Init(ctx, K, Klen): + * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from + * ${K}. + */ +static void +_HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen, + uint32_t tmp32[PHP_STATIC_RESTRICT 72], uint8_t pad[PHP_STATIC_RESTRICT 64], + uint8_t khash[PHP_STATIC_RESTRICT 32]) +{ + const uint8_t * K = _K; + size_t i; + + /* If Klen > 64, the key is really SHA256(K). */ + if (Klen > 64) { + SHA256_Init(&ctx->ictx); + _SHA256_Update(&ctx->ictx, K, Klen, tmp32); + _SHA256_Final(khash, &ctx->ictx, tmp32); + K = khash; + Klen = 32; + } + + /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ + SHA256_Init(&ctx->ictx); + memset(pad, 0x36, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + _SHA256_Update(&ctx->ictx, pad, 64, tmp32); + + /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ + SHA256_Init(&ctx->octx); + memset(pad, 0x5c, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + _SHA256_Update(&ctx->octx, pad, 64, tmp32); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen) +{ + uint32_t tmp32[72]; + uint8_t pad[64]; + uint8_t khash[32]; + + /* Call the real function. */ + _HMAC_SHA256_Init(ctx, _K, Klen, tmp32, pad, khash); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); + insecure_memzero(khash, 32); + insecure_memzero(pad, 64); +} + +/** + * HMAC_SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}. + */ +static void +_HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void * in, size_t len, + uint32_t tmp32[PHP_STATIC_RESTRICT 72]) +{ + + /* Feed data to the inner SHA256 operation. */ + _SHA256_Update(&ctx->ictx, in, len, tmp32); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void * in, size_t len) +{ + uint32_t tmp32[72]; + + /* Call the real function. */ + _HMAC_SHA256_Update(ctx, in, len, tmp32); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); +} + +/** + * HMAC_SHA256_Final(digest, ctx): + * Output the HMAC-SHA256 of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +static void +_HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx, + uint32_t tmp32[PHP_STATIC_RESTRICT 72], uint8_t ihash[PHP_STATIC_RESTRICT 32]) +{ + + /* Finish the inner SHA256 operation. */ + _SHA256_Final(ihash, &ctx->ictx, tmp32); + + /* Feed the inner hash to the outer SHA256 operation. */ + _SHA256_Update(&ctx->octx, ihash, 32, tmp32); + + /* Finish the outer SHA256 operation. */ + _SHA256_Final(digest, &ctx->octx, tmp32); +} + +/* Wrapper function for intermediate-values sanitization. */ +void +HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx) +{ + uint32_t tmp32[72]; + uint8_t ihash[32]; + + /* Call the real function. */ + _HMAC_SHA256_Final(digest, ctx, tmp32, ihash); + + /* Clean the stack. */ + insecure_memzero(tmp32, 288); + insecure_memzero(ihash, 32); +} + +/** + * HMAC_SHA256_Buf(K, Klen, in, len, digest): + * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of + * length ${Klen}, and write the result to ${digest}. + */ +void +HMAC_SHA256_Buf(const void * K, size_t Klen, const void * in, size_t len, + uint8_t digest[32]) +{ + HMAC_SHA256_CTX ctx; + uint32_t tmp32[72]; + uint8_t tmp8[96]; + + _HMAC_SHA256_Init(&ctx, K, Klen, tmp32, &tmp8[0], &tmp8[64]); + _HMAC_SHA256_Update(&ctx, in, len, tmp32); + _HMAC_SHA256_Final(digest, &ctx, tmp32, &tmp8[0]); + + /* Clean the stack. */ + insecure_memzero(&ctx, sizeof(HMAC_SHA256_CTX)); + insecure_memzero(tmp32, 288); + insecure_memzero(tmp8, 96); +} + +/* Add padding and terminating bit-count, but don't invoke Transform yet. */ +static int +SHA256_Pad_Almost(SHA256_CTX * ctx, uint8_t len[PHP_STATIC_RESTRICT 8], + uint32_t tmp32[PHP_STATIC_RESTRICT 72]) +{ + uint32_t r; + + r = (ctx->count >> 3) & 0x3f; + if (r >= 56) + return -1; + + /* + * Convert length to a vector of bytes -- we do this now rather + * than later because the length will change after we pad. + */ + be64enc(len, ctx->count); + + /* Add 1--56 bytes so that the resulting length is 56 mod 64. */ + _SHA256_Update(ctx, PAD, 56 - r, tmp32); + + /* Add the terminating bit-count. */ + ctx->buf[63] = len[7]; + _SHA256_Update(ctx, len, 7, tmp32); + + return 0; +} + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void +PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, + size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen) +{ + HMAC_SHA256_CTX Phctx, PShctx, hctx; + uint32_t tmp32[72]; + union { + uint8_t tmp8[96]; + uint32_t state[8]; + } u; + size_t i; + uint8_t ivec[4]; + uint8_t U[32]; + uint8_t T[32]; + uint64_t j; + int k; + size_t clen; + + /* Sanity-check. */ + assert(dkLen <= 32 * (size_t)(UINT32_MAX)); + + if (c == 1 && (dkLen & 31) == 0 && (saltlen & 63) <= 51) { + uint32_t oldcount; + uint8_t * ivecp; + + /* Compute HMAC state after processing P and S. */ + _HMAC_SHA256_Init(&hctx, passwd, passwdlen, + tmp32, &u.tmp8[0], &u.tmp8[64]); + _HMAC_SHA256_Update(&hctx, salt, saltlen, tmp32); + + /* Prepare ictx padding. */ + oldcount = hctx.ictx.count & (0x3f << 3); + _HMAC_SHA256_Update(&hctx, "\0\0\0", 4, tmp32); + if ((hctx.ictx.count & (0x3f << 3)) < oldcount || + SHA256_Pad_Almost(&hctx.ictx, u.tmp8, tmp32)) + goto generic; /* Can't happen due to saltlen check */ + ivecp = hctx.ictx.buf + (oldcount >> 3); + + /* Prepare octx padding. */ + hctx.octx.count += 32 << 3; + SHA256_Pad_Almost(&hctx.octx, u.tmp8, tmp32); + + /* Iterate through the blocks. */ + for (i = 0; i * 32 < dkLen; i++) { + /* Generate INT(i + 1). */ + be32enc(ivecp, (uint32_t)(i + 1)); + + /* Compute U_1 = PRF(P, S || INT(i)). */ + memcpy(u.state, hctx.ictx.state, sizeof(u.state)); + SHA256_Transform(u.state, hctx.ictx.buf, + &tmp32[0], &tmp32[64]); + be32enc_vect(hctx.octx.buf, u.state, 4); + memcpy(u.state, hctx.octx.state, sizeof(u.state)); + SHA256_Transform(u.state, hctx.octx.buf, + &tmp32[0], &tmp32[64]); + be32enc_vect(&buf[i * 32], u.state, 4); + } + + goto cleanup; + } + +generic: + /* Compute HMAC state after processing P. */ + _HMAC_SHA256_Init(&Phctx, passwd, passwdlen, + tmp32, &u.tmp8[0], &u.tmp8[64]); + + /* Compute HMAC state after processing P and S. */ + memcpy(&PShctx, &Phctx, sizeof(HMAC_SHA256_CTX)); + _HMAC_SHA256_Update(&PShctx, salt, saltlen, tmp32); + + /* Iterate through the blocks. */ + for (i = 0; i * 32 < dkLen; i++) { + /* Generate INT(i + 1). */ + be32enc(ivec, (uint32_t)(i + 1)); + + /* Compute U_1 = PRF(P, S || INT(i)). */ + memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); + _HMAC_SHA256_Update(&hctx, ivec, 4, tmp32); + _HMAC_SHA256_Final(T, &hctx, tmp32, u.tmp8); + + if (c > 1) { + /* T_i = U_1 ... */ + memcpy(U, T, 32); + + for (j = 2; j <= c; j++) { + /* Compute U_j. */ + memcpy(&hctx, &Phctx, sizeof(HMAC_SHA256_CTX)); + _HMAC_SHA256_Update(&hctx, U, 32, tmp32); + _HMAC_SHA256_Final(U, &hctx, tmp32, u.tmp8); + + /* ... xor U_j ... */ + for (k = 0; k < 32; k++) + T[k] ^= U[k]; + } + } + + /* Copy as many bytes as necessary into buf. */ + clen = dkLen - i * 32; + if (clen > 32) + clen = 32; + memcpy(&buf[i * 32], T, clen); + } + + /* Clean the stack. */ + insecure_memzero(&Phctx, sizeof(HMAC_SHA256_CTX)); + insecure_memzero(&PShctx, sizeof(HMAC_SHA256_CTX)); + insecure_memzero(U, 32); + insecure_memzero(T, 32); + +cleanup: + insecure_memzero(&hctx, sizeof(HMAC_SHA256_CTX)); + insecure_memzero(tmp32, 288); + insecure_memzero(&u, sizeof(u)); +} diff --git a/ext/standard/yescrypt/sha256.h b/ext/standard/yescrypt/sha256.h new file mode 100644 index 0000000000000..6210502ff1b44 --- /dev/null +++ b/ext/standard/yescrypt/sha256.h @@ -0,0 +1,129 @@ +/*- + * Copyright 2005-2016 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _SHA256_H_ +#define _SHA256_H_ + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Use #defines in order to avoid namespace collisions with anyone else's + * SHA256 code (e.g., the code in OpenSSL). + */ +#define SHA256_Init libcperciva_SHA256_Init +#define SHA256_Update libcperciva_SHA256_Update +#define SHA256_Final libcperciva_SHA256_Final +#define SHA256_Buf libcperciva_SHA256_Buf +#define SHA256_CTX libcperciva_SHA256_CTX +#define HMAC_SHA256_Init libcperciva_HMAC_SHA256_Init +#define HMAC_SHA256_Update libcperciva_HMAC_SHA256_Update +#define HMAC_SHA256_Final libcperciva_HMAC_SHA256_Final +#define HMAC_SHA256_Buf libcperciva_HMAC_SHA256_Buf +#define HMAC_SHA256_CTX libcperciva_HMAC_SHA256_CTX + +/* Context structure for SHA256 operations. */ +typedef struct { + uint32_t state[8]; + uint64_t count; + uint8_t buf[64]; +} SHA256_CTX; + +/** + * SHA256_Init(ctx): + * Initialize the SHA256 context ${ctx}. + */ +void SHA256_Init(SHA256_CTX *); + +/** + * SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the SHA256 context ${ctx}. + */ +void SHA256_Update(SHA256_CTX *, const void *, size_t); + +/** + * SHA256_Final(digest, ctx): + * Output the SHA256 hash of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +void SHA256_Final(uint8_t[32], SHA256_CTX *); + +/** + * SHA256_Buf(in, len, digest): + * Compute the SHA256 hash of ${len} bytes from ${in} and write it to ${digest}. + */ +void SHA256_Buf(const void *, size_t, uint8_t[32]); + +/* Context structure for HMAC-SHA256 operations. */ +typedef struct { + SHA256_CTX ictx; + SHA256_CTX octx; +} HMAC_SHA256_CTX; + +/** + * HMAC_SHA256_Init(ctx, K, Klen): + * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from + * ${K}. + */ +void HMAC_SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t); + +/** + * HMAC_SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}. + */ +void HMAC_SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t); + +/** + * HMAC_SHA256_Final(digest, ctx): + * Output the HMAC-SHA256 of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +void HMAC_SHA256_Final(uint8_t[32], HMAC_SHA256_CTX *); + +/** + * HMAC_SHA256_Buf(K, Klen, in, len, digest): + * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of + * length ${Klen}, and write the result to ${digest}. + */ +void HMAC_SHA256_Buf(const void *, size_t, const void *, size_t, uint8_t[32]); + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, + uint64_t, uint8_t *, size_t); + +#ifdef __cplusplus +} +#endif + +#endif /* !_SHA256_H_ */ diff --git a/ext/standard/yescrypt/sysendian.h b/ext/standard/yescrypt/sysendian.h new file mode 100644 index 0000000000000..c51730d1cd5d5 --- /dev/null +++ b/ext/standard/yescrypt/sysendian.h @@ -0,0 +1,122 @@ +/*- + * Copyright 2007-2014 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _SYSENDIAN_H_ +#define _SYSENDIAN_H_ + +#include <stdint.h> + +/* Avoid namespace collisions with BSD <sys/endian.h>. */ +#define be32dec libcperciva_be32dec +#define be32enc libcperciva_be32enc +#define be64enc libcperciva_be64enc +#define le32dec libcperciva_le32dec +#define le32enc libcperciva_le32enc +#define le64dec libcperciva_le64dec +#define le64enc libcperciva_le64enc + +static inline uint32_t +be32dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) + + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24)); +} + +static inline void +be32enc(void * pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[3] = x & 0xff; + p[2] = (x >> 8) & 0xff; + p[1] = (x >> 16) & 0xff; + p[0] = (x >> 24) & 0xff; +} + +static inline void +be64enc(void * pp, uint64_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[7] = x & 0xff; + p[6] = (x >> 8) & 0xff; + p[5] = (x >> 16) & 0xff; + p[4] = (x >> 24) & 0xff; + p[3] = (x >> 32) & 0xff; + p[2] = (x >> 40) & 0xff; + p[1] = (x >> 48) & 0xff; + p[0] = (x >> 56) & 0xff; +} + +static inline uint32_t +le32dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) + + ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24)); +} + +static inline void +le32enc(void * pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; +} + +static inline uint64_t +le64dec(const void * pp) +{ + const uint8_t * p = (uint8_t const *)pp; + + return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) + + ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) + + ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) + + ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56)); +} + +static inline void +le64enc(void * pp, uint64_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; + p[4] = (x >> 32) & 0xff; + p[5] = (x >> 40) & 0xff; + p[6] = (x >> 48) & 0xff; + p[7] = (x >> 56) & 0xff; +} + +#endif /* !_SYSENDIAN_H_ */ diff --git a/ext/standard/yescrypt/yescrypt-common.c b/ext/standard/yescrypt/yescrypt-common.c new file mode 100644 index 0000000000000..459bdc4416a23 --- /dev/null +++ b/ext/standard/yescrypt/yescrypt-common.c @@ -0,0 +1,133 @@ +/*- + * Copyright 2013-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <string.h> + +#include "insecure_memzero.h" + +#define YESCRYPT_INTERNAL +#include "yescrypt.h" + +static uint8_t *encode64(uint8_t *dst, size_t dstlen, + const uint8_t *src, size_t srclen) +{ + size_t i; + + for (i = 0; i < srclen; ) { + uint8_t *dnext; + uint32_t value = 0, bits = 0; + do { + value |= (uint32_t)src[i++] << bits; + bits += 8; + } while (bits < 24 && i < srclen); + dnext = yescrypt_encode64_uint32_fixed(dst, dstlen, value, bits); + if (!dnext) + return NULL; + dstlen -= dnext - dst; + dst = dnext; + } + + if (dstlen < 1) + return NULL; + + *dst = 0; /* NUL terminate just in case */ + + return dst; +} + +uint8_t *yescrypt_r(const yescrypt_shared_t *shared, yescrypt_local_t *local, + const uint8_t *passwd, size_t passwdlen, + const uint8_t *setting, + const yescrypt_binary_t *key, + uint8_t *buf, size_t buflen) +{ + unsigned char saltbin[64], hashbin[32]; + const uint8_t *src, *saltstr, *salt; + uint8_t *dst; + size_t need, prefixlen, saltstrlen, saltlen; + yescrypt_params_t params = { .p = 1 }; + + /* PHP change: extracted the settings parsing */ + src = yescrypt_parse_settings(setting, ¶ms, key); + if (!src) { + return NULL; + } + + prefixlen = src - setting; + + saltstr = src; + src = (uint8_t *)strrchr((char *)saltstr, '$'); + if (src) + saltstrlen = src - saltstr; + else + saltstrlen = strlen((char *)saltstr); + + if (setting[1] == '7') { + salt = saltstr; + saltlen = saltstrlen; + } else { + const uint8_t *saltend; + + saltlen = sizeof(saltbin); + saltend = yescrypt_decode64(saltbin, &saltlen, saltstr, saltstrlen); + + if (!saltend || (size_t)(saltend - saltstr) != saltstrlen) + goto fail; + + salt = saltbin; + + if (key) { + /* PHP change: removed so we don't carry the encrypt implementation */ + ZEND_UNREACHABLE(); + } + } + + need = prefixlen + saltstrlen + 1 + HASH_LEN + 1; + if (need > buflen || need < saltstrlen) + goto fail; + + if (yescrypt_kdf(shared, local, passwd, passwdlen, salt, saltlen, + ¶ms, hashbin, sizeof(hashbin))) + goto fail; + + if (key) { + /* PHP change: removed so we don't carry the encrypt implementation */ + ZEND_UNREACHABLE(); + } + + dst = buf; + memcpy(dst, setting, prefixlen + saltstrlen); + dst += prefixlen + saltstrlen; + *dst++ = '$'; + + dst = encode64(dst, buflen - (dst - buf), hashbin, sizeof(hashbin)); + insecure_memzero(hashbin, sizeof(hashbin)); + if (!dst || dst >= buf + buflen) + return NULL; + + *dst = 0; /* NUL termination */ + + return buf; + +fail: + insecure_memzero(saltbin, sizeof(saltbin)); + insecure_memzero(hashbin, sizeof(hashbin)); + return NULL; +} diff --git a/ext/standard/yescrypt/yescrypt-config.c b/ext/standard/yescrypt/yescrypt-config.c new file mode 100644 index 0000000000000..50bbc721ce28d --- /dev/null +++ b/ext/standard/yescrypt/yescrypt-config.c @@ -0,0 +1,442 @@ +/*- + * Copyright 2013-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stdint.h> +#include <string.h> + +#define YESCRYPT_INTERNAL +#include "yescrypt.h" + +static const char * const itoa64 = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static const uint8_t atoi64_partial[77] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 64, 64, 64, 64, 64, 64, 64, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 64, 64, 64, 64, 64, 64, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 +}; + +static uint8_t *encode64_uint32(uint8_t *dst, size_t dstlen, + uint32_t src, uint32_t min) +{ + uint32_t start = 0, end = 47, chars = 1, bits = 0; + + if (src < min) + return NULL; + src -= min; + + do { + uint32_t count = (end + 1 - start) << bits; + if (src < count) + break; + if (start >= 63) + return NULL; + start = end + 1; + end = start + (62 - end) / 2; + src -= count; + chars++; + bits += 6; + } while (1); + + if (dstlen <= chars) /* require room for a NUL terminator */ + return NULL; + + *dst++ = itoa64[start + (src >> bits)]; + + while (--chars) { + bits -= 6; + *dst++ = itoa64[(src >> bits) & 0x3f]; + } + + *dst = 0; /* NUL terminate just in case */ + + return dst; +} + +static inline uint32_t atoi64(uint8_t src) +{ + if (src >= '.' && src <= 'z') + return atoi64_partial[src - '.']; + + return 64; +} + +static const uint8_t *decode64_uint32(uint32_t *dst, + const uint8_t *src, uint32_t min) +{ + uint32_t start = 0, end = 47, chars = 1, bits = 0; + uint32_t c; + + c = atoi64(*src++); + if (c > 63) + goto fail; + + *dst = min; + while (c > end) { + *dst += (end + 1 - start) << bits; + start = end + 1; + end = start + (62 - end) / 2; + chars++; + bits += 6; + } + + *dst += (c - start) << bits; + + while (--chars) { + c = atoi64(*src++); + if (c > 63) + goto fail; + bits -= 6; + *dst += c << bits; + } + + return src; + +fail: + *dst = 0; + return NULL; +} + +uint8_t *yescrypt_encode64_uint32_fixed(uint8_t *dst, size_t dstlen, + uint32_t src, uint32_t srcbits) +{ + uint32_t bits; + + for (bits = 0; bits < srcbits; bits += 6) { + if (dstlen < 2) + return NULL; + *dst++ = itoa64[src & 0x3f]; + dstlen--; + src >>= 6; + } + + if (src || dstlen < 1) + return NULL; + + *dst = 0; /* NUL terminate just in case */ + + return dst; +} + +static uint8_t *encode64(uint8_t *dst, size_t dstlen, + const uint8_t *src, size_t srclen) +{ + size_t i; + + for (i = 0; i < srclen; ) { + uint8_t *dnext; + uint32_t value = 0, bits = 0; + do { + value |= (uint32_t)src[i++] << bits; + bits += 8; + } while (bits < 24 && i < srclen); + dnext = yescrypt_encode64_uint32_fixed(dst, dstlen, value, bits); + if (!dnext) + return NULL; + dstlen -= dnext - dst; + dst = dnext; + } + + if (dstlen < 1) + return NULL; + + *dst = 0; /* NUL terminate just in case */ + + return dst; +} + +static const uint8_t *decode64_uint32_fixed(uint32_t *dst, uint32_t dstbits, + const uint8_t *src) +{ + uint32_t bits; + + *dst = 0; + for (bits = 0; bits < dstbits; bits += 6) { + uint32_t c = atoi64(*src++); + if (c > 63) { + *dst = 0; + return NULL; + } + *dst |= c << bits; + } + + return src; +} + +const uint8_t *yescrypt_decode64(uint8_t *dst, size_t *dstlen, + const uint8_t *src, size_t srclen) +{ + size_t dstpos = 0; + + while (dstpos <= *dstlen && srclen) { + uint32_t value = 0, bits = 0; + while (srclen--) { + uint32_t c = atoi64(*src); + if (c > 63) { + srclen = 0; + break; + } + src++; + value |= c << bits; + bits += 6; + if (bits >= 24) + break; + } + if (!bits) + break; + if (bits < 12) /* must have at least one full byte */ + goto fail; + while (dstpos++ < *dstlen) { + *dst++ = value; + value >>= 8; + bits -= 8; + if (bits < 8) { /* 2 or 4 */ + if (value) /* must be 0 */ + goto fail; + bits = 0; + break; + } + } + if (bits) + goto fail; + } + + if (!srclen && dstpos <= *dstlen) { + *dstlen = dstpos; + return src; + } + + fail: + *dstlen = 0; + return NULL; +} + +/* PHP change: code to parse the settings extracted from yescrypt_r */ +const uint8_t *yescrypt_parse_settings(const uint8_t *setting, yescrypt_params_t *params, const yescrypt_binary_t *key) +{ + if (setting[0] != '$' || + (setting[1] != '7' && setting[1] != 'y') || + setting[2] != '$') + return NULL; + const uint8_t *src = setting + 3; + + if (setting[1] == '7') { + uint32_t N_log2 = atoi64(*src++); + if (N_log2 < 1 || N_log2 > 63) + return NULL; + params->N = (uint64_t)1 << N_log2; + + src = decode64_uint32_fixed(¶ms->r, 30, src); + if (!src) + return NULL; + + src = decode64_uint32_fixed(¶ms->p, 30, src); + if (!src) + return NULL; + + if (key) + return NULL; + } else { + uint32_t flavor, N_log2; + + src = decode64_uint32(&flavor, src, 0); + if (!src) + return NULL; + + if (flavor < YESCRYPT_RW) { + params->flags = flavor; + } else if (flavor <= YESCRYPT_RW + (YESCRYPT_RW_FLAVOR_MASK >> 2)) { + params->flags = YESCRYPT_RW + ((flavor - YESCRYPT_RW) << 2); + } else { + return NULL; + } + + src = decode64_uint32(&N_log2, src, 1); + if (!src || N_log2 > 63) + return NULL; + params->N = (uint64_t)1 << N_log2; + + src = decode64_uint32(¶ms->r, src, 1); + if (!src) + return NULL; + + if (*src != '$') { + uint32_t have; + + src = decode64_uint32(&have, src, 1); + if (!src) + return NULL; + + if (have & 1) { + src = decode64_uint32(¶ms->p, src, 2); + if (!src) + return NULL; + } + + if (have & 2) { + src = decode64_uint32(¶ms->t, src, 1); + if (!src) + return NULL; + } + + if (have & 4) { + src = decode64_uint32(¶ms->g, src, 1); + if (!src) + return NULL; + } + + if (have & 8) { + uint32_t NROM_log2; + src = decode64_uint32(&NROM_log2, src, 1); + if (!src || NROM_log2 > 63) + return NULL; + params->NROM = (uint64_t)1 << NROM_log2; + } + } + + if (*src++ != '$') + return NULL; + } + + return src; +} + +static uint32_t N2log2(uint64_t N) +{ + uint32_t N_log2; + + if (N < 2) + return 0; + + N_log2 = 2; + while (N >> N_log2 != 0) + N_log2++; + N_log2--; + + if (N >> N_log2 != 1) + return 0; + + return N_log2; +} + +uint8_t *yescrypt_encode_params_r(const yescrypt_params_t *params, + const uint8_t *src, size_t srclen, + uint8_t *buf, size_t buflen) +{ + uint32_t flavor, N_log2, NROM_log2, have; + uint8_t *dst; + + if (srclen > SIZE_MAX / 16) + return NULL; + + if (params->flags < YESCRYPT_RW) { + flavor = params->flags; + } else if ((params->flags & YESCRYPT_MODE_MASK) == YESCRYPT_RW && + params->flags <= (YESCRYPT_RW | YESCRYPT_RW_FLAVOR_MASK)) { + flavor = YESCRYPT_RW + (params->flags >> 2); + } else { + return NULL; + } + + N_log2 = N2log2(params->N); + if (!N_log2) + return NULL; + + NROM_log2 = N2log2(params->NROM); + if (params->NROM && !NROM_log2) + return NULL; + + if ((uint64_t)params->r * (uint64_t)params->p >= (1U << 30)) + return NULL; + + dst = buf; + *dst++ = '$'; + *dst++ = 'y'; + *dst++ = '$'; + + dst = encode64_uint32(dst, buflen - (dst - buf), flavor, 0); + if (!dst) + return NULL; + + dst = encode64_uint32(dst, buflen - (dst - buf), N_log2, 1); + if (!dst) + return NULL; + + dst = encode64_uint32(dst, buflen - (dst - buf), params->r, 1); + if (!dst) + return NULL; + + have = 0; + if (params->p != 1) + have |= 1; + if (params->t) + have |= 2; + if (params->g) + have |= 4; + if (NROM_log2) + have |= 8; + + if (have) { + dst = encode64_uint32(dst, buflen - (dst - buf), have, 1); + if (!dst) + return NULL; + } + + if (params->p != 1) { + dst = encode64_uint32(dst, buflen - (dst - buf), params->p, 2); + if (!dst) + return NULL; + } + + if (params->t) { + dst = encode64_uint32(dst, buflen - (dst - buf), params->t, 1); + if (!dst) + return NULL; + } + + if (params->g) { + dst = encode64_uint32(dst, buflen - (dst - buf), params->g, 1); + if (!dst) + return NULL; + } + + if (NROM_log2) { + dst = encode64_uint32(dst, buflen - (dst - buf), NROM_log2, 1); + if (!dst) + return NULL; + } + + if (dst >= buf + buflen) + return NULL; + + *dst++ = '$'; + + dst = encode64(dst, buflen - (dst - buf), src, srclen); + if (!dst || dst >= buf + buflen) + return NULL; + + *dst = 0; /* NUL termination */ + + return buf; +} diff --git a/ext/standard/yescrypt/yescrypt-opt.c b/ext/standard/yescrypt/yescrypt-opt.c new file mode 100644 index 0000000000000..7e960940d1b9a --- /dev/null +++ b/ext/standard/yescrypt/yescrypt-opt.c @@ -0,0 +1,1532 @@ +/*- + * Copyright 2009 Colin Percival + * Copyright 2012-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ + +/* PHP change: Commented out the warnings */ +#if 0 +/* + * AVX and especially XOP speed up Salsa20 a lot, but this mostly matters for + * classic scrypt and for YESCRYPT_WORM (which use 8 rounds of Salsa20 per + * sub-block), and much less so for YESCRYPT_RW (which uses 2 rounds of Salsa20 + * per block except during pwxform S-box initialization). + */ +#ifdef __XOP__ +#warning "Note: XOP is enabled. That's great." +#elif defined(__AVX__) +#warning "Note: AVX is enabled, which is great for classic scrypt and YESCRYPT_WORM, but is sometimes slightly slower than plain SSE2 for YESCRYPT_RW" +#elif defined(__SSE2__) +#warning "Note: AVX and XOP are not enabled, which is great for YESCRYPT_RW, but they would substantially improve performance at classic scrypt and YESCRYPT_WORM" +#elif defined(__x86_64__) || defined(__i386__) +#warning "SSE2 not enabled. Expect poor performance." +#else +#warning "Note: building generic code for non-x86. That's OK." +#endif +#endif + +/* + * The SSE4 code version has fewer instructions than the generic SSE2 version, + * but all of the instructions are SIMD, thereby wasting the scalar execution + * units. Thus, the generic SSE2 version below actually runs faster on some + * CPUs due to its balanced mix of SIMD and scalar instructions. + */ +#undef USE_SSE4_FOR_32BIT + +#ifdef __SSE2__ +/* + * GCC before 4.9 would by default unnecessarily use store/load (without + * SSE4.1) or (V)PEXTR (with SSE4.1 or AVX) instead of simply (V)MOV. + * This was tracked as GCC bug 54349. + * "-mtune=corei7" works around this, but is only supported for GCC 4.6+. + * We use inline asm for pre-4.6 GCC, further down this file. + */ +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && __GNUC_MINOR__ < 9 && \ + !defined(__clang__) && !defined(__ICC) +#pragma GCC target ("tune=corei7") +#endif +#include <emmintrin.h> +#ifdef __XOP__ +#include <x86intrin.h> +#endif +#elif defined(__SSE__) +#include <xmmintrin.h> +#endif + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "insecure_memzero.h" +#include "sha256.h" +#include "sysendian.h" + +#define YESCRYPT_INTERNAL +#include "yescrypt.h" + +#include "yescrypt-platform.c" + +#if __STDC_VERSION__ >= 199901L +/* Have restrict */ +#elif defined(__GNUC__) +#define restrict __restrict +#else +#define restrict +#endif + +#ifdef __GNUC__ +#define unlikely(exp) __builtin_expect(exp, 0) +#else +#define unlikely(exp) (exp) +#endif + +#ifdef __SSE__ +#define PREFETCH(x, hint) _mm_prefetch((const char *)(x), (hint)); +#else +#undef PREFETCH +#endif + +typedef union { + uint32_t w[16]; + uint64_t d[8]; +#ifdef __SSE2__ + __m128i q[4]; +#endif +} salsa20_blk_t; + +static inline void salsa20_simd_shuffle(const salsa20_blk_t *Bin, + salsa20_blk_t *Bout) +{ +#define COMBINE(out, in1, in2) \ + Bout->d[out] = Bin->w[in1 * 2] | ((uint64_t)Bin->w[in2 * 2 + 1] << 32); + COMBINE(0, 0, 2) + COMBINE(1, 5, 7) + COMBINE(2, 2, 4) + COMBINE(3, 7, 1) + COMBINE(4, 4, 6) + COMBINE(5, 1, 3) + COMBINE(6, 6, 0) + COMBINE(7, 3, 5) +#undef COMBINE +} + +static inline void salsa20_simd_unshuffle(const salsa20_blk_t *Bin, + salsa20_blk_t *Bout) +{ +#define UNCOMBINE(out, in1, in2) \ + Bout->w[out * 2] = Bin->d[in1]; \ + Bout->w[out * 2 + 1] = Bin->d[in2] >> 32; + UNCOMBINE(0, 0, 6) + UNCOMBINE(1, 5, 3) + UNCOMBINE(2, 2, 0) + UNCOMBINE(3, 7, 5) + UNCOMBINE(4, 4, 2) + UNCOMBINE(5, 1, 7) + UNCOMBINE(6, 6, 4) + UNCOMBINE(7, 3, 1) +#undef UNCOMBINE +} + +#ifdef __SSE2__ +#define DECL_X \ + __m128i X0, X1, X2, X3; +#define DECL_Y \ + __m128i Y0, Y1, Y2, Y3; +#define READ_X(in) \ + X0 = (in).q[0]; X1 = (in).q[1]; X2 = (in).q[2]; X3 = (in).q[3]; +#define WRITE_X(out) \ + (out).q[0] = X0; (out).q[1] = X1; (out).q[2] = X2; (out).q[3] = X3; + +#ifdef __XOP__ +#define ARX(out, in1, in2, s) \ + out = _mm_xor_si128(out, _mm_roti_epi32(_mm_add_epi32(in1, in2), s)); +#else +#define ARX(out, in1, in2, s) { \ + __m128i tmp = _mm_add_epi32(in1, in2); \ + out = _mm_xor_si128(out, _mm_slli_epi32(tmp, s)); \ + out = _mm_xor_si128(out, _mm_srli_epi32(tmp, 32 - s)); \ +} +#endif + +#define SALSA20_2ROUNDS \ + /* Operate on "columns" */ \ + ARX(X1, X0, X3, 7) \ + ARX(X2, X1, X0, 9) \ + ARX(X3, X2, X1, 13) \ + ARX(X0, X3, X2, 18) \ + /* Rearrange data */ \ + X1 = _mm_shuffle_epi32(X1, 0x93); \ + X2 = _mm_shuffle_epi32(X2, 0x4E); \ + X3 = _mm_shuffle_epi32(X3, 0x39); \ + /* Operate on "rows" */ \ + ARX(X3, X0, X1, 7) \ + ARX(X2, X3, X0, 9) \ + ARX(X1, X2, X3, 13) \ + ARX(X0, X1, X2, 18) \ + /* Rearrange data */ \ + X1 = _mm_shuffle_epi32(X1, 0x39); \ + X2 = _mm_shuffle_epi32(X2, 0x4E); \ + X3 = _mm_shuffle_epi32(X3, 0x93); + +/** + * Apply the Salsa20 core to the block provided in (X0 ... X3). + */ +#define SALSA20_wrapper(out, rounds) { \ + __m128i Z0 = X0, Z1 = X1, Z2 = X2, Z3 = X3; \ + rounds \ + (out).q[0] = X0 = _mm_add_epi32(X0, Z0); \ + (out).q[1] = X1 = _mm_add_epi32(X1, Z1); \ + (out).q[2] = X2 = _mm_add_epi32(X2, Z2); \ + (out).q[3] = X3 = _mm_add_epi32(X3, Z3); \ +} + +/** + * Apply the Salsa20/2 core to the block provided in X. + */ +#define SALSA20_2(out) \ + SALSA20_wrapper(out, SALSA20_2ROUNDS) + +#define SALSA20_8ROUNDS \ + SALSA20_2ROUNDS SALSA20_2ROUNDS SALSA20_2ROUNDS SALSA20_2ROUNDS + +#define XOR_X(in) \ + X0 = _mm_xor_si128(X0, (in).q[0]); \ + X1 = _mm_xor_si128(X1, (in).q[1]); \ + X2 = _mm_xor_si128(X2, (in).q[2]); \ + X3 = _mm_xor_si128(X3, (in).q[3]); + +#define XOR_X_2(in1, in2) \ + X0 = _mm_xor_si128((in1).q[0], (in2).q[0]); \ + X1 = _mm_xor_si128((in1).q[1], (in2).q[1]); \ + X2 = _mm_xor_si128((in1).q[2], (in2).q[2]); \ + X3 = _mm_xor_si128((in1).q[3], (in2).q[3]); + +#define XOR_X_WRITE_XOR_Y_2(out, in) \ + (out).q[0] = Y0 = _mm_xor_si128((out).q[0], (in).q[0]); \ + (out).q[1] = Y1 = _mm_xor_si128((out).q[1], (in).q[1]); \ + (out).q[2] = Y2 = _mm_xor_si128((out).q[2], (in).q[2]); \ + (out).q[3] = Y3 = _mm_xor_si128((out).q[3], (in).q[3]); \ + X0 = _mm_xor_si128(X0, Y0); \ + X1 = _mm_xor_si128(X1, Y1); \ + X2 = _mm_xor_si128(X2, Y2); \ + X3 = _mm_xor_si128(X3, Y3); + +/** + * Apply the Salsa20/8 core to the block provided in X ^ in. + */ +#define SALSA20_8_XOR_MEM(in, out) \ + XOR_X(in) \ + SALSA20_wrapper(out, SALSA20_8ROUNDS) + +#define INTEGERIFY _mm_cvtsi128_si32(X0) + +#else /* !defined(__SSE2__) */ + +#define DECL_X \ + salsa20_blk_t X; +#define DECL_Y \ + salsa20_blk_t Y; + +#define COPY(out, in) \ + (out).d[0] = (in).d[0]; \ + (out).d[1] = (in).d[1]; \ + (out).d[2] = (in).d[2]; \ + (out).d[3] = (in).d[3]; \ + (out).d[4] = (in).d[4]; \ + (out).d[5] = (in).d[5]; \ + (out).d[6] = (in).d[6]; \ + (out).d[7] = (in).d[7]; + +#define READ_X(in) COPY(X, in) +#define WRITE_X(out) COPY(out, X) + +/** + * salsa20(B): + * Apply the Salsa20 core to the provided block. + */ +static inline void salsa20(salsa20_blk_t *restrict B, + salsa20_blk_t *restrict Bout, uint32_t doublerounds) +{ + salsa20_blk_t X; +#define x X.w + + salsa20_simd_unshuffle(B, &X); + + do { +#define R(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) + /* Operate on columns */ + x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); + x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); + + x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); + x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); + + x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); + x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); + + x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); + x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); + + /* Operate on rows */ + x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); + x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); + + x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); + x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); + + x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); + x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); + + x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); + x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); +#undef R + } while (--doublerounds); +#undef x + + { + uint32_t i; + salsa20_simd_shuffle(&X, Bout); + for (i = 0; i < 16; i += 4) { + B->w[i] = Bout->w[i] += B->w[i]; + B->w[i + 1] = Bout->w[i + 1] += B->w[i + 1]; + B->w[i + 2] = Bout->w[i + 2] += B->w[i + 2]; + B->w[i + 3] = Bout->w[i + 3] += B->w[i + 3]; + } + } + +#if 0 + /* Too expensive */ + insecure_memzero(&X, sizeof(X)); +#endif +} + +/** + * Apply the Salsa20/2 core to the block provided in X. + */ +#define SALSA20_2(out) \ + salsa20(&X, &out, 1); + +#define XOR(out, in1, in2) \ + (out).d[0] = (in1).d[0] ^ (in2).d[0]; \ + (out).d[1] = (in1).d[1] ^ (in2).d[1]; \ + (out).d[2] = (in1).d[2] ^ (in2).d[2]; \ + (out).d[3] = (in1).d[3] ^ (in2).d[3]; \ + (out).d[4] = (in1).d[4] ^ (in2).d[4]; \ + (out).d[5] = (in1).d[5] ^ (in2).d[5]; \ + (out).d[6] = (in1).d[6] ^ (in2).d[6]; \ + (out).d[7] = (in1).d[7] ^ (in2).d[7]; + +#define XOR_X(in) XOR(X, X, in) +#define XOR_X_2(in1, in2) XOR(X, in1, in2) +#define XOR_X_WRITE_XOR_Y_2(out, in) \ + XOR(Y, out, in) \ + COPY(out, Y) \ + XOR(X, X, Y) + +/** + * Apply the Salsa20/8 core to the block provided in X ^ in. + */ +#define SALSA20_8_XOR_MEM(in, out) \ + XOR_X(in); \ + salsa20(&X, &out, 4); + +#define INTEGERIFY (uint32_t)X.d[0] +#endif + +/** + * blockmix_salsa8(Bin, Bout, r): + * Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r + * bytes in length; the output Bout must also be the same size. + */ +static void blockmix_salsa8(const salsa20_blk_t *restrict Bin, + salsa20_blk_t *restrict Bout, size_t r) +{ + size_t i; + DECL_X + + READ_X(Bin[r * 2 - 1]) + for (i = 0; i < r; i++) { + SALSA20_8_XOR_MEM(Bin[i * 2], Bout[i]) + SALSA20_8_XOR_MEM(Bin[i * 2 + 1], Bout[r + i]) + } +} + +static uint32_t blockmix_salsa8_xor(const salsa20_blk_t *restrict Bin1, + const salsa20_blk_t *restrict Bin2, salsa20_blk_t *restrict Bout, + size_t r) +{ + size_t i; + DECL_X + +#ifdef PREFETCH + PREFETCH(&Bin2[r * 2 - 1], _MM_HINT_T0) + for (i = 0; i < r - 1; i++) { + PREFETCH(&Bin2[i * 2], _MM_HINT_T0) + PREFETCH(&Bin2[i * 2 + 1], _MM_HINT_T0) + } + PREFETCH(&Bin2[i * 2], _MM_HINT_T0) +#endif + + XOR_X_2(Bin1[r * 2 - 1], Bin2[r * 2 - 1]) + for (i = 0; i < r; i++) { + XOR_X(Bin1[i * 2]) + SALSA20_8_XOR_MEM(Bin2[i * 2], Bout[i]) + XOR_X(Bin1[i * 2 + 1]) + SALSA20_8_XOR_MEM(Bin2[i * 2 + 1], Bout[r + i]) + } + + return INTEGERIFY; +} + +/* This is tunable */ +#define Swidth 8 + +/* Not tunable in this implementation, hard-coded in a few places */ +#define PWXsimple 2 +#define PWXgather 4 + +/* Derived values. Not tunable except via Swidth above. */ +#define PWXbytes (PWXgather * PWXsimple * 8) +#define Sbytes (3 * (1 << Swidth) * PWXsimple * 8) +#define Smask (((1 << Swidth) - 1) * PWXsimple * 8) +#define Smask2 (((uint64_t)Smask << 32) | Smask) + +#define DECL_SMASK2REG /* empty */ +#define FORCE_REGALLOC_3 /* empty */ +#define MAYBE_MEMORY_BARRIER /* empty */ + +#ifdef __SSE2__ +/* + * (V)PSRLDQ and (V)PSHUFD have higher throughput than (V)PSRLQ on some CPUs + * starting with Sandy Bridge. Additionally, PSHUFD uses separate source and + * destination registers, whereas the shifts would require an extra move + * instruction for our code when building without AVX. Unfortunately, PSHUFD + * is much slower on Conroe (4 cycles latency vs. 1 cycle latency for PSRLQ) + * and somewhat slower on some non-Intel CPUs (luckily not including AMD + * Bulldozer and Piledriver). + */ +#ifdef __AVX__ +#define HI32(X) \ + _mm_srli_si128((X), 4) +#elif 1 /* As an option, check for __SSE4_1__ here not to hurt Conroe */ +#define HI32(X) \ + _mm_shuffle_epi32((X), _MM_SHUFFLE(2,3,0,1)) +#else +#define HI32(X) \ + _mm_srli_epi64((X), 32) +#endif + +#if defined(__x86_64__) && \ + __GNUC__ == 4 && __GNUC_MINOR__ < 6 && !defined(__ICC) +#ifdef __AVX__ +#define MOVQ "vmovq" +#else +/* "movq" would be more correct, but "movd" is supported by older binutils + * due to an error in AMD's spec for x86-64. */ +#define MOVQ "movd" +#endif +#define EXTRACT64(X) ({ \ + uint64_t result; \ + __asm__(MOVQ " %1, %0" : "=r" (result) : "x" (X)); \ + result; \ +}) +#elif defined(__x86_64__) && !defined(_MSC_VER) && !defined(__OPEN64__) +/* MSVC and Open64 had bugs */ +#define EXTRACT64(X) _mm_cvtsi128_si64(X) +#elif defined(__x86_64__) && defined(__SSE4_1__) +/* No known bugs for this intrinsic */ +#include <smmintrin.h> +#define EXTRACT64(X) _mm_extract_epi64((X), 0) +#elif defined(USE_SSE4_FOR_32BIT) && defined(__SSE4_1__) +/* 32-bit */ +#include <smmintrin.h> +#if 0 +/* This is currently unused by the code below, which instead uses these two + * intrinsics explicitly when (!defined(__x86_64__) && defined(__SSE4_1__)) */ +#define EXTRACT64(X) \ + ((uint64_t)(uint32_t)_mm_cvtsi128_si32(X) | \ + ((uint64_t)(uint32_t)_mm_extract_epi32((X), 1) << 32)) +#endif +#else +/* 32-bit or compilers with known past bugs in _mm_cvtsi128_si64() */ +#define EXTRACT64(X) \ + ((uint64_t)(uint32_t)_mm_cvtsi128_si32(X) | \ + ((uint64_t)(uint32_t)_mm_cvtsi128_si32(HI32(X)) << 32)) +#endif + +#if defined(__x86_64__) && (defined(__AVX__) || !defined(__GNUC__)) +/* 64-bit with AVX */ +/* Force use of 64-bit AND instead of two 32-bit ANDs */ +#undef DECL_SMASK2REG +#if defined(__GNUC__) && !defined(__ICC) +#define DECL_SMASK2REG uint64_t Smask2reg = Smask2; +/* Force use of lower-numbered registers to reduce number of prefixes, relying + * on out-of-order execution and register renaming. */ +#define FORCE_REGALLOC_1 \ + __asm__("" : "=a" (x), "+d" (Smask2reg), "+S" (S0), "+D" (S1)); +#define FORCE_REGALLOC_2 \ + __asm__("" : : "c" (lo)); +#else +static volatile uint64_t Smask2var = Smask2; +#define DECL_SMASK2REG uint64_t Smask2reg = Smask2var; +#define FORCE_REGALLOC_1 /* empty */ +#define FORCE_REGALLOC_2 /* empty */ +#endif +#define PWXFORM_SIMD(X) { \ + uint64_t x; \ + FORCE_REGALLOC_1 \ + uint32_t lo = x = EXTRACT64(X) & Smask2reg; \ + FORCE_REGALLOC_2 \ + uint32_t hi = x >> 32; \ + X = _mm_mul_epu32(HI32(X), X); \ + X = _mm_add_epi64(X, *(__m128i *)(S0 + lo)); \ + X = _mm_xor_si128(X, *(__m128i *)(S1 + hi)); \ +} +#elif defined(__x86_64__) +/* 64-bit without AVX. This relies on out-of-order execution and register + * renaming. It may actually be fastest on CPUs with AVX(2) as well - e.g., + * it runs great on Haswell. */ +/* PHP change: Commented out warning */ +/*#warning "Note: using x86-64 inline assembly for YESCRYPT_RW. That's great."*/ +/* We need a compiler memory barrier between sub-blocks to ensure that none of + * the writes into what was S2 during processing of the previous sub-block are + * postponed until after a read from S0 or S1 in the inline asm code below. */ +#undef MAYBE_MEMORY_BARRIER +#define MAYBE_MEMORY_BARRIER \ + __asm__("" : : : "memory"); +#ifdef __ILP32__ /* x32 */ +#define REGISTER_PREFIX "e" +#else +#define REGISTER_PREFIX "r" +#endif +#define PWXFORM_SIMD(X) { \ + __m128i H; \ + __asm__( \ + "movd %0, %%rax\n\t" \ + "pshufd $0xb1, %0, %1\n\t" \ + "andq %2, %%rax\n\t" \ + "pmuludq %1, %0\n\t" \ + "movl %%eax, %%ecx\n\t" \ + "shrq $0x20, %%rax\n\t" \ + "paddq (%3,%%" REGISTER_PREFIX "cx), %0\n\t" \ + "pxor (%4,%%" REGISTER_PREFIX "ax), %0\n\t" \ + : "+x" (X), "=x" (H) \ + : "d" (Smask2), "S" (S0), "D" (S1) \ + : "cc", "ax", "cx"); \ +} +#elif defined(USE_SSE4_FOR_32BIT) && defined(__SSE4_1__) +/* 32-bit with SSE4.1 */ +#define PWXFORM_SIMD(X) { \ + __m128i x = _mm_and_si128(X, _mm_set1_epi64x(Smask2)); \ + __m128i s0 = *(__m128i *)(S0 + (uint32_t)_mm_cvtsi128_si32(x)); \ + __m128i s1 = *(__m128i *)(S1 + (uint32_t)_mm_extract_epi32(x, 1)); \ + X = _mm_mul_epu32(HI32(X), X); \ + X = _mm_add_epi64(X, s0); \ + X = _mm_xor_si128(X, s1); \ +} +#else +/* 32-bit without SSE4.1 */ +#define PWXFORM_SIMD(X) { \ + uint64_t x = EXTRACT64(X) & Smask2; \ + __m128i s0 = *(__m128i *)(S0 + (uint32_t)x); \ + __m128i s1 = *(__m128i *)(S1 + (x >> 32)); \ + X = _mm_mul_epu32(HI32(X), X); \ + X = _mm_add_epi64(X, s0); \ + X = _mm_xor_si128(X, s1); \ +} +#endif + +#define PWXFORM_ROUND \ + PWXFORM_SIMD(X0) \ + PWXFORM_SIMD(X1) \ + PWXFORM_SIMD(X2) \ + PWXFORM_SIMD(X3) + +#if defined(__x86_64__) && defined(__GNUC__) && !defined(__ICC) +#undef FORCE_REGALLOC_3 +#define FORCE_REGALLOC_3 __asm__("" : : "b" (Sw)); +#endif + +#else /* !defined(__SSE2__) */ + +#define PWXFORM_SIMD(x0, x1) { \ + uint64_t x = x0 & Smask2; \ + uint64_t *p0 = (uint64_t *)(S0 + (uint32_t)x); \ + uint64_t *p1 = (uint64_t *)(S1 + (x >> 32)); \ + x0 = ((x0 >> 32) * (uint32_t)x0 + p0[0]) ^ p1[0]; \ + x1 = ((x1 >> 32) * (uint32_t)x1 + p0[1]) ^ p1[1]; \ +} + +#define PWXFORM_ROUND \ + PWXFORM_SIMD(X.d[0], X.d[1]) \ + PWXFORM_SIMD(X.d[2], X.d[3]) \ + PWXFORM_SIMD(X.d[4], X.d[5]) \ + PWXFORM_SIMD(X.d[6], X.d[7]) +#endif + +/* + * This offset helps address the 256-byte write block via the single-byte + * displacements encodable in x86(-64) instructions. It is needed because the + * displacements are signed. Without it, we'd get 4-byte displacements for + * half of the writes. Setting it to 0x80 instead of 0x7c would avoid needing + * a displacement for one of the writes, but then the LEA instruction would + * need a 4-byte displacement. + */ +#define PWXFORM_WRITE_OFFSET 0x7c + +#define PWXFORM_WRITE \ + WRITE_X(*(salsa20_blk_t *)(Sw - PWXFORM_WRITE_OFFSET)) \ + Sw += 64; + +#define PWXFORM { \ + uint8_t *Sw = S2 + w + PWXFORM_WRITE_OFFSET; \ + FORCE_REGALLOC_3 \ + MAYBE_MEMORY_BARRIER \ + PWXFORM_ROUND \ + PWXFORM_ROUND PWXFORM_WRITE \ + PWXFORM_ROUND PWXFORM_WRITE \ + PWXFORM_ROUND PWXFORM_WRITE \ + PWXFORM_ROUND PWXFORM_WRITE \ + PWXFORM_ROUND \ + w = (w + 64 * 4) & Smask2; \ + { \ + uint8_t *Stmp = S2; \ + S2 = S1; \ + S1 = S0; \ + S0 = Stmp; \ + } \ +} + +typedef struct { + uint8_t *S0, *S1, *S2; + size_t w; +} pwxform_ctx_t; + +#define Salloc (Sbytes + ((sizeof(pwxform_ctx_t) + 63) & ~63U)) + +/** + * blockmix_pwxform(Bin, Bout, r, S): + * Compute Bout = BlockMix_pwxform{salsa20/2, r, S}(Bin). The input Bin must + * be 128r bytes in length; the output Bout must also be the same size. + */ +static void blockmix(const salsa20_blk_t *restrict Bin, + salsa20_blk_t *restrict Bout, size_t r, pwxform_ctx_t *restrict ctx) +{ + uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2; + size_t w = ctx->w; + size_t i; + DECL_X + + /* Convert count of 128-byte blocks to max index of 64-byte block */ + r = r * 2 - 1; + + READ_X(Bin[r]) + + DECL_SMASK2REG + + i = 0; + do { + XOR_X(Bin[i]) + PWXFORM + if (unlikely(i >= r)) + break; + WRITE_X(Bout[i]) + i++; + } while (1); + + ctx->S0 = S0; ctx->S1 = S1; ctx->S2 = S2; + ctx->w = w; + + SALSA20_2(Bout[i]) +} + +static uint32_t blockmix_xor(const salsa20_blk_t *Bin1, + const salsa20_blk_t *restrict Bin2, salsa20_blk_t *Bout, + size_t r, int Bin2_in_ROM, pwxform_ctx_t *restrict ctx) +{ + uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2; + size_t w = ctx->w; + size_t i; + DECL_X + + /* Convert count of 128-byte blocks to max index of 64-byte block */ + r = r * 2 - 1; + +#ifdef PREFETCH + if (Bin2_in_ROM) { + PREFETCH(&Bin2[r], _MM_HINT_NTA) + for (i = 0; i < r; i++) { + PREFETCH(&Bin2[i], _MM_HINT_NTA) + } + } else { + PREFETCH(&Bin2[r], _MM_HINT_T0) + for (i = 0; i < r; i++) { + PREFETCH(&Bin2[i], _MM_HINT_T0) + } + } +#else + (void)Bin2_in_ROM; /* unused */ +#endif + + XOR_X_2(Bin1[r], Bin2[r]) + + DECL_SMASK2REG + + i = 0; + r--; + do { + XOR_X(Bin1[i]) + XOR_X(Bin2[i]) + PWXFORM + WRITE_X(Bout[i]) + + XOR_X(Bin1[i + 1]) + XOR_X(Bin2[i + 1]) + PWXFORM + + if (unlikely(i >= r)) + break; + + WRITE_X(Bout[i + 1]) + + i += 2; + } while (1); + i++; + + ctx->S0 = S0; ctx->S1 = S1; ctx->S2 = S2; + ctx->w = w; + + SALSA20_2(Bout[i]) + + return INTEGERIFY; +} + +static uint32_t blockmix_xor_save(salsa20_blk_t *restrict Bin1out, + salsa20_blk_t *restrict Bin2, + size_t r, pwxform_ctx_t *restrict ctx) +{ + uint8_t *S0 = ctx->S0, *S1 = ctx->S1, *S2 = ctx->S2; + size_t w = ctx->w; + size_t i; + DECL_X + DECL_Y + + /* Convert count of 128-byte blocks to max index of 64-byte block */ + r = r * 2 - 1; + +#ifdef PREFETCH + PREFETCH(&Bin2[r], _MM_HINT_T0) + for (i = 0; i < r; i++) { + PREFETCH(&Bin2[i], _MM_HINT_T0) + } +#endif + + XOR_X_2(Bin1out[r], Bin2[r]) + + DECL_SMASK2REG + + i = 0; + r--; + do { + XOR_X_WRITE_XOR_Y_2(Bin2[i], Bin1out[i]) + PWXFORM + WRITE_X(Bin1out[i]) + + XOR_X_WRITE_XOR_Y_2(Bin2[i + 1], Bin1out[i + 1]) + PWXFORM + + if (unlikely(i >= r)) + break; + + WRITE_X(Bin1out[i + 1]) + + i += 2; + } while (1); + i++; + + ctx->S0 = S0; ctx->S1 = S1; ctx->S2 = S2; + ctx->w = w; + + SALSA20_2(Bin1out[i]) + + return INTEGERIFY; +} + +/** + * integerify(B, r): + * Return the result of parsing B_{2r-1} as a little-endian integer. + */ +static inline uint32_t integerify(const salsa20_blk_t *B, size_t r) +{ +/* + * Our 64-bit words are in host byte order, which is why we don't just read + * w[0] here (would be wrong on big-endian). Also, our 32-bit words are + * SIMD-shuffled (so the next 32 bits would be part of d[6]), but currently + * this does not matter as we only care about the least significant 32 bits. + */ + return (uint32_t)B[2 * r - 1].d[0]; +} + +/** + * smix1(B, r, N, flags, V, NROM, VROM, XY, ctx): + * Compute first loop of B = SMix_r(B, N). The input B must be 128r bytes in + * length; the temporary storage V must be 128rN bytes in length; the temporary + * storage XY must be 128r+64 bytes in length. N must be even and at least 4. + * The array V must be aligned to a multiple of 64 bytes, and arrays B and XY + * to a multiple of at least 16 bytes. + */ +static void smix1(uint8_t *B, size_t r, uint32_t N, yescrypt_flags_t flags, + salsa20_blk_t *V, uint32_t NROM, const salsa20_blk_t *VROM, + salsa20_blk_t *XY, pwxform_ctx_t *ctx) +{ + size_t s = 2 * r; + salsa20_blk_t *X = V, *Y = &V[s]; + uint32_t i, j; + + for (i = 0; i < 2 * r; i++) { + const salsa20_blk_t *src = (salsa20_blk_t *)&B[i * 64]; + salsa20_blk_t *tmp = Y; + salsa20_blk_t *dst = &X[i]; + size_t k; + for (k = 0; k < 16; k++) + tmp->w[k] = le32dec(&src->w[k]); + salsa20_simd_shuffle(tmp, dst); + } + + if (VROM) { + uint32_t n; + const salsa20_blk_t *V_j; + + V_j = &VROM[(NROM - 1) * s]; + j = blockmix_xor(X, V_j, Y, r, 1, ctx) & (NROM - 1); + V_j = &VROM[j * s]; + X = Y + s; + j = blockmix_xor(Y, V_j, X, r, 1, ctx); + + for (n = 2; n < N; n <<= 1) { + uint32_t m = (n < N / 2) ? n : (N - 1 - n); + for (i = 1; i < m; i += 2) { + j &= n - 1; + j += i - 1; + V_j = &V[j * s]; + Y = X + s; + j = blockmix_xor(X, V_j, Y, r, 0, ctx) & (NROM - 1); + V_j = &VROM[j * s]; + X = Y + s; + j = blockmix_xor(Y, V_j, X, r, 1, ctx); + } + } + n >>= 1; + + j &= n - 1; + j += N - 2 - n; + V_j = &V[j * s]; + Y = X + s; + j = blockmix_xor(X, V_j, Y, r, 0, ctx) & (NROM - 1); + V_j = &VROM[j * s]; + blockmix_xor(Y, V_j, XY, r, 1, ctx); + } else if (flags & YESCRYPT_RW) { + uint32_t n; + salsa20_blk_t *V_j; + + blockmix(X, Y, r, ctx); + X = Y + s; + blockmix(Y, X, r, ctx); + j = integerify(X, r); + + for (n = 2; n < N; n <<= 1) { + uint32_t m = (n < N / 2) ? n : (N - 1 - n); + for (i = 1; i < m; i += 2) { + Y = X + s; + j &= n - 1; + j += i - 1; + V_j = &V[j * s]; + j = blockmix_xor(X, V_j, Y, r, 0, ctx); + j &= n - 1; + j += i; + V_j = &V[j * s]; + X = Y + s; + j = blockmix_xor(Y, V_j, X, r, 0, ctx); + } + } + n >>= 1; + + j &= n - 1; + j += N - 2 - n; + V_j = &V[j * s]; + Y = X + s; + j = blockmix_xor(X, V_j, Y, r, 0, ctx); + j &= n - 1; + j += N - 1 - n; + V_j = &V[j * s]; + blockmix_xor(Y, V_j, XY, r, 0, ctx); + } else { + N -= 2; + do { + blockmix_salsa8(X, Y, r); + X = Y + s; + blockmix_salsa8(Y, X, r); + Y = X + s; + } while ((N -= 2)); + + blockmix_salsa8(X, Y, r); + blockmix_salsa8(Y, XY, r); + } + + for (i = 0; i < 2 * r; i++) { + const salsa20_blk_t *src = &XY[i]; + salsa20_blk_t *tmp = &XY[s]; + salsa20_blk_t *dst = (salsa20_blk_t *)&B[i * 64]; + size_t k; + for (k = 0; k < 16; k++) + le32enc(&tmp->w[k], src->w[k]); + salsa20_simd_unshuffle(tmp, dst); + } +} + +/** + * smix2(B, r, N, Nloop, flags, V, NROM, VROM, XY, ctx): + * Compute second loop of B = SMix_r(B, N). The input B must be 128r bytes in + * length; the temporary storage V must be 128rN bytes in length; the temporary + * storage XY must be 256r bytes in length. N must be a power of 2 and at + * least 2. Nloop must be even. The array V must be aligned to a multiple of + * 64 bytes, and arrays B and XY to a multiple of at least 16 bytes. + */ +static void smix2(uint8_t *B, size_t r, uint32_t N, uint64_t Nloop, + yescrypt_flags_t flags, salsa20_blk_t *V, uint32_t NROM, + const salsa20_blk_t *VROM, salsa20_blk_t *XY, pwxform_ctx_t *ctx) +{ + size_t s = 2 * r; + salsa20_blk_t *X = XY, *Y = &XY[s]; + uint32_t i, j; + + if (Nloop == 0) + return; + + for (i = 0; i < 2 * r; i++) { + const salsa20_blk_t *src = (salsa20_blk_t *)&B[i * 64]; + salsa20_blk_t *tmp = Y; + salsa20_blk_t *dst = &X[i]; + size_t k; + for (k = 0; k < 16; k++) + tmp->w[k] = le32dec(&src->w[k]); + salsa20_simd_shuffle(tmp, dst); + } + + j = integerify(X, r) & (N - 1); + +/* + * Normally, VROM implies YESCRYPT_RW, but we check for these separately + * because our SMix resets YESCRYPT_RW for the smix2() calls operating on the + * entire V when p > 1. + */ + if (VROM && (flags & YESCRYPT_RW)) { + do { + salsa20_blk_t *V_j = &V[j * s]; + const salsa20_blk_t *VROM_j; + j = blockmix_xor_save(X, V_j, r, ctx) & (NROM - 1); + VROM_j = &VROM[j * s]; + j = blockmix_xor(X, VROM_j, X, r, 1, ctx) & (N - 1); + } while (Nloop -= 2); + } else if (VROM) { + do { + const salsa20_blk_t *V_j = &V[j * s]; + j = blockmix_xor(X, V_j, X, r, 0, ctx) & (NROM - 1); + V_j = &VROM[j * s]; + j = blockmix_xor(X, V_j, X, r, 1, ctx) & (N - 1); + } while (Nloop -= 2); + } else if (flags & YESCRYPT_RW) { + do { + salsa20_blk_t *V_j = &V[j * s]; + j = blockmix_xor_save(X, V_j, r, ctx) & (N - 1); + V_j = &V[j * s]; + j = blockmix_xor_save(X, V_j, r, ctx) & (N - 1); + } while (Nloop -= 2); + } else if (ctx) { + do { + const salsa20_blk_t *V_j = &V[j * s]; + j = blockmix_xor(X, V_j, X, r, 0, ctx) & (N - 1); + V_j = &V[j * s]; + j = blockmix_xor(X, V_j, X, r, 0, ctx) & (N - 1); + } while (Nloop -= 2); + } else { + do { + const salsa20_blk_t *V_j = &V[j * s]; + j = blockmix_salsa8_xor(X, V_j, Y, r) & (N - 1); + V_j = &V[j * s]; + j = blockmix_salsa8_xor(Y, V_j, X, r) & (N - 1); + } while (Nloop -= 2); + } + + for (i = 0; i < 2 * r; i++) { + const salsa20_blk_t *src = &X[i]; + salsa20_blk_t *tmp = Y; + salsa20_blk_t *dst = (salsa20_blk_t *)&B[i * 64]; + size_t k; + for (k = 0; k < 16; k++) + le32enc(&tmp->w[k], src->w[k]); + salsa20_simd_unshuffle(tmp, dst); + } +} + +/** + * p2floor(x): + * Largest power of 2 not greater than argument. + */ +static uint64_t p2floor(uint64_t x) +{ + uint64_t y; + while ((y = x & (x - 1))) + x = y; + return x; +} + +/** + * smix(B, r, N, p, t, flags, V, NROM, VROM, XY, S, passwd): + * Compute B = SMix_r(B, N). The input B must be 128rp bytes in length; the + * temporary storage V must be 128rN bytes in length; the temporary storage + * XY must be 256r or 256rp bytes in length (the larger size is required with + * OpenMP-enabled builds). N must be a power of 2 and at least 4. The array V + * must be aligned to a multiple of 64 bytes, and arrays B and XY to a multiple + * of at least 16 bytes (aligning them to 64 bytes as well saves cache lines + * and helps avoid false sharing in OpenMP-enabled builds when p > 1, but it + * might also result in cache bank conflicts). + */ +static void smix(uint8_t *B, size_t r, uint32_t N, uint32_t p, uint32_t t, + yescrypt_flags_t flags, + salsa20_blk_t *V, uint32_t NROM, const salsa20_blk_t *VROM, + salsa20_blk_t *XY, uint8_t *S, uint8_t *passwd) +{ + size_t s = 2 * r; + uint32_t Nchunk; + uint64_t Nloop_all, Nloop_rw; + uint32_t i; + + Nchunk = N / p; + Nloop_all = Nchunk; + if (flags & YESCRYPT_RW) { + if (t <= 1) { + if (t) + Nloop_all *= 2; /* 2/3 */ + Nloop_all = (Nloop_all + 2) / 3; /* 1/3, round up */ + } else { + Nloop_all *= t - 1; + } + } else if (t) { + if (t == 1) + Nloop_all += (Nloop_all + 1) / 2; /* 1.5, round up */ + Nloop_all *= t; + } + + Nloop_rw = 0; + if (flags & YESCRYPT_INIT_SHARED) + Nloop_rw = Nloop_all; + else if (flags & YESCRYPT_RW) + Nloop_rw = Nloop_all / p; + + Nchunk &= ~(uint32_t)1; /* round down to even */ + Nloop_all++; Nloop_all &= ~(uint64_t)1; /* round up to even */ + Nloop_rw++; Nloop_rw &= ~(uint64_t)1; /* round up to even */ + +#ifdef _OPENMP +#pragma omp parallel if (p > 1) default(none) private(i) shared(B, r, N, p, flags, V, NROM, VROM, XY, S, passwd, s, Nchunk, Nloop_all, Nloop_rw) + { +#pragma omp for +#endif + for (i = 0; i < p; i++) { + uint32_t Vchunk = i * Nchunk; + uint32_t Np = (i < p - 1) ? Nchunk : (N - Vchunk); + uint8_t *Bp = &B[128 * r * i]; + salsa20_blk_t *Vp = &V[Vchunk * s]; +#ifdef _OPENMP + salsa20_blk_t *XYp = &XY[i * (2 * s)]; +#else + salsa20_blk_t *XYp = XY; +#endif + pwxform_ctx_t *ctx_i = NULL; + if (flags & YESCRYPT_RW) { + uint8_t *Si = S + i * Salloc; + smix1(Bp, 1, Sbytes / 128, 0 /* no flags */, + (salsa20_blk_t *)Si, 0, NULL, XYp, NULL); + ctx_i = (pwxform_ctx_t *)(Si + Sbytes); + ctx_i->S2 = Si; + ctx_i->S1 = Si + Sbytes / 3; + ctx_i->S0 = Si + Sbytes / 3 * 2; + ctx_i->w = 0; + if (i == 0) + HMAC_SHA256_Buf(Bp + (128 * r - 64), 64, + passwd, 32, passwd); + } + smix1(Bp, r, Np, flags, Vp, NROM, VROM, XYp, ctx_i); + smix2(Bp, r, p2floor(Np), Nloop_rw, flags, Vp, + NROM, VROM, XYp, ctx_i); + } + + if (Nloop_all > Nloop_rw) { +#ifdef _OPENMP +#pragma omp for +#endif + for (i = 0; i < p; i++) { + uint8_t *Bp = &B[128 * r * i]; +#ifdef _OPENMP + salsa20_blk_t *XYp = &XY[i * (2 * s)]; +#else + salsa20_blk_t *XYp = XY; +#endif + pwxform_ctx_t *ctx_i = NULL; + if (flags & YESCRYPT_RW) { + uint8_t *Si = S + i * Salloc; + ctx_i = (pwxform_ctx_t *)(Si + Sbytes); + } + smix2(Bp, r, N, Nloop_all - Nloop_rw, + flags & ~YESCRYPT_RW, V, NROM, VROM, XYp, ctx_i); + } + } +#ifdef _OPENMP + } +#endif +} + +/** + * yescrypt_kdf_body(shared, local, passwd, passwdlen, salt, saltlen, + * flags, N, r, p, t, NROM, buf, buflen): + * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, + * p, buflen), or a revision of scrypt as requested by flags and shared, and + * write the result into buf. + * + * shared and flags may request special modes as described in yescrypt.h. + * + * local is the thread-local data structure, allowing to preserve and reuse a + * memory allocation across calls, thereby reducing its overhead. + * + * t controls computation time while not affecting peak memory usage. + * + * Return 0 on success; or -1 on error. + * + * This optimized implementation currently limits N to the range from 4 to + * 2^31, but other implementations might not. + */ +static int yescrypt_kdf_body(const yescrypt_shared_t *shared, + yescrypt_local_t *local, + const uint8_t *passwd, size_t passwdlen, + const uint8_t *salt, size_t saltlen, + yescrypt_flags_t flags, uint64_t N, uint32_t r, uint32_t p, uint32_t t, + uint64_t NROM, + uint8_t *buf, size_t buflen) +{ + yescrypt_region_t tmp; + const salsa20_blk_t *VROM; + size_t B_size, V_size, XY_size, need; + uint8_t *B, *S; + salsa20_blk_t *V, *XY; + uint8_t sha256[32]; + uint8_t dk[sizeof(sha256)], *dkp = buf; + + /* Sanity-check parameters */ + switch (flags & YESCRYPT_MODE_MASK) { + case 0: /* classic scrypt - can't have anything non-standard */ + if (flags || t || NROM) + goto out_EINVAL; + break; + case YESCRYPT_WORM: + if (flags != YESCRYPT_WORM || NROM) + goto out_EINVAL; + break; + case YESCRYPT_RW: + if (flags != (flags & YESCRYPT_KNOWN_FLAGS)) + goto out_EINVAL; +#if PWXsimple == 2 && PWXgather == 4 && Sbytes == 12288 + if ((flags & YESCRYPT_RW_FLAVOR_MASK) == + (YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | + YESCRYPT_SIMPLE_2 | YESCRYPT_SBOX_12K)) + break; +#else +#error "Unsupported pwxform settings" +#endif + /* FALLTHRU */ + default: + goto out_EINVAL; + } +#if SIZE_MAX > UINT32_MAX + if (buflen > (((uint64_t)1 << 32) - 1) * 32) + goto out_EINVAL; +#endif + if ((uint64_t)r * (uint64_t)p >= 1 << 30) + goto out_EINVAL; + if (N > UINT32_MAX) + goto out_EINVAL; + if ((N & (N - 1)) != 0 || N <= 3 || r < 1 || p < 1) + goto out_EINVAL; + if (r > SIZE_MAX / 256 / p || + N > SIZE_MAX / 128 / r) + goto out_EINVAL; + if (flags & YESCRYPT_RW) { + /* PHP change: fix compile warning */ +#if SIZEOF_SIZE_T == 8 + if (N / p <= 3) + goto out_EINVAL; +#else + if (N / p <= 3 || p > SIZE_MAX / Salloc) + goto out_EINVAL; +#endif + } +#ifdef _OPENMP + else if (N > SIZE_MAX / 128 / (r * p)) { + goto out_EINVAL; + } +#endif + + VROM = NULL; + if (shared) { + uint64_t expected_size = (size_t)128 * r * NROM; + if ((NROM & (NROM - 1)) != 0 || + NROM <= 1 || NROM > UINT32_MAX || + shared->aligned_size < expected_size) + goto out_EINVAL; + if (!(flags & YESCRYPT_INIT_SHARED)) { + uint64_t *tag = (uint64_t *) + ((uint8_t *)shared->aligned + expected_size - 48); + if (tag[0] != YESCRYPT_ROM_TAG1 || tag[1] != YESCRYPT_ROM_TAG2) + goto out_EINVAL; + } + VROM = shared->aligned; + } else { + if (NROM) + goto out_EINVAL; + } + + /* Allocate memory */ + V = NULL; + V_size = (size_t)128 * r * N; +#ifdef _OPENMP + if (!(flags & YESCRYPT_RW)) + V_size *= p; +#endif + need = V_size; + if (flags & YESCRYPT_INIT_SHARED) { + if (local->aligned_size < need) { + if (local->base || local->aligned || + local->base_size || local->aligned_size) + goto out_EINVAL; + if (!alloc_region(local, need)) + return -1; + } + if (flags & YESCRYPT_ALLOC_ONLY) + return -2; /* expected "failure" */ + V = (salsa20_blk_t *)local->aligned; + need = 0; + } + B_size = (size_t)128 * r * p; + need += B_size; + if (need < B_size) + goto out_EINVAL; + XY_size = (size_t)256 * r; +#ifdef _OPENMP + XY_size *= p; +#endif + need += XY_size; + if (need < XY_size) + goto out_EINVAL; + if (flags & YESCRYPT_RW) { + size_t S_size = (size_t)Salloc * p; + need += S_size; + if (need < S_size) + goto out_EINVAL; + } + if (flags & YESCRYPT_INIT_SHARED) { + if (!alloc_region(&tmp, need)) + return -1; + B = (uint8_t *)tmp.aligned; + XY = (salsa20_blk_t *)((uint8_t *)B + B_size); + } else { + init_region(&tmp); + if (local->aligned_size < need) { + if (free_region(local)) + return -1; + if (!alloc_region(local, need)) + return -1; + } + if (flags & YESCRYPT_ALLOC_ONLY) + return -3; /* expected "failure" */ + B = (uint8_t *)local->aligned; + V = (salsa20_blk_t *)((uint8_t *)B + B_size); + XY = (salsa20_blk_t *)((uint8_t *)V + V_size); + } + S = NULL; + if (flags & YESCRYPT_RW) + S = (uint8_t *)XY + XY_size; + + if (flags) { + HMAC_SHA256_Buf("yescrypt-prehash", + (flags & YESCRYPT_PREHASH) ? 16 : 8, + passwd, passwdlen, sha256); + passwd = sha256; + passwdlen = sizeof(sha256); + } + + PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, B_size); + + if (flags) + memcpy(sha256, B, sizeof(sha256)); + + if (p == 1 || (flags & YESCRYPT_RW)) { + smix(B, r, N, p, t, flags, V, NROM, VROM, XY, S, sha256); + } else { + uint32_t i; +#ifdef _OPENMP +#pragma omp parallel for default(none) private(i) shared(B, r, N, p, t, flags, V, NROM, VROM, XY, S) +#endif + for (i = 0; i < p; i++) { +#ifdef _OPENMP + smix(&B[(size_t)128 * r * i], r, N, 1, t, flags, + &V[(size_t)2 * r * i * N], + NROM, VROM, + &XY[(size_t)4 * r * i], NULL, NULL); +#else + smix(&B[(size_t)128 * r * i], r, N, 1, t, flags, V, + NROM, VROM, XY, NULL, NULL); +#endif + } + } + + dkp = buf; + if (flags && buflen < sizeof(dk)) { + PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, dk, sizeof(dk)); + dkp = dk; + } + + PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, buf, buflen); + + /* + * Except when computing classic scrypt, allow all computation so far + * to be performed on the client. The final steps below match those of + * SCRAM (RFC 5802), so that an extension of SCRAM (with the steps so + * far in place of SCRAM's use of PBKDF2 and with SHA-256 in place of + * SCRAM's use of SHA-1) would be usable with yescrypt hashes. + */ + if (flags && !(flags & YESCRYPT_PREHASH)) { + /* Compute ClientKey */ + HMAC_SHA256_Buf(dkp, sizeof(dk), "Client Key", 10, sha256); + /* Compute StoredKey */ + { + size_t clen = buflen; + if (clen > sizeof(dk)) + clen = sizeof(dk); + SHA256_Buf(sha256, sizeof(sha256), dk); + memcpy(buf, dk, clen); + } + } + + if (flags) { + insecure_memzero(sha256, sizeof(sha256)); + insecure_memzero(dk, sizeof(dk)); + } + + if (free_region(&tmp)) { + insecure_memzero(buf, buflen); /* must preserve errno */ + return -1; + } + + /* Success! */ + return 0; + +out_EINVAL: + errno = EINVAL; + return -1; +} + +/** + * yescrypt_kdf(shared, local, passwd, passwdlen, salt, saltlen, params, + * buf, buflen): + * Compute scrypt or its revision as requested by the parameters. The inputs + * to this function are the same as those for yescrypt_kdf_body() above, with + * the addition of g, which controls hash upgrades (0 for no upgrades so far). + */ +int yescrypt_kdf(const yescrypt_shared_t *shared, yescrypt_local_t *local, + const uint8_t *passwd, size_t passwdlen, + const uint8_t *salt, size_t saltlen, + const yescrypt_params_t *params, + uint8_t *buf, size_t buflen) +{ + yescrypt_flags_t flags = params->flags; + uint64_t N = params->N; + uint32_t r = params->r; + uint32_t p = params->p; + uint32_t t = params->t; + uint32_t g = params->g; + uint64_t NROM = params->NROM; + uint8_t dk[32]; + int retval; + + /* Support for hash upgrades has been temporarily removed */ + if (g) { + errno = EINVAL; + return -1; + } + + if ((flags & (YESCRYPT_RW | YESCRYPT_INIT_SHARED)) == YESCRYPT_RW && + p >= 1 && N / p >= 0x100 && N / p * r >= 0x20000) { + if (yescrypt_kdf_body(shared, local, + passwd, passwdlen, salt, saltlen, + flags | YESCRYPT_ALLOC_ONLY, N, r, p, t, NROM, + buf, buflen) != -3) { + errno = EINVAL; + return -1; + } + if ((retval = yescrypt_kdf_body(shared, local, + passwd, passwdlen, salt, saltlen, + flags | YESCRYPT_PREHASH, N >> 6, r, p, 0, NROM, + dk, sizeof(dk)))) + return retval; + passwd = dk; + passwdlen = sizeof(dk); + } + + retval = yescrypt_kdf_body(shared, local, + passwd, passwdlen, salt, saltlen, + flags, N, r, p, t, NROM, buf, buflen); +#ifndef SKIP_MEMZERO + if (passwd == dk) + insecure_memzero(dk, sizeof(dk)); +#endif + return retval; +} + +int yescrypt_init_shared(yescrypt_shared_t *shared, + const uint8_t *seed, size_t seedlen, + const yescrypt_params_t *params) +{ + yescrypt_params_t subparams; + yescrypt_shared_t half1, half2; + uint8_t salt[32]; + uint64_t *tag; + + subparams = *params; + subparams.flags |= YESCRYPT_INIT_SHARED; + subparams.N = params->NROM; + subparams.NROM = 0; + + if (!(params->flags & YESCRYPT_RW) || params->N || params->g) + return -1; + + if (params->flags & YESCRYPT_SHARED_PREALLOCATED) { + if (!shared->aligned || !shared->aligned_size) + return -1; + +/* Overwrite a possible old ROM tag before we overwrite the rest */ + tag = (uint64_t *) + ((uint8_t *)shared->aligned + shared->aligned_size - 48); + memset(tag, 0, 48); + } else { + init_region(shared); + + subparams.flags |= YESCRYPT_ALLOC_ONLY; + if (yescrypt_kdf(NULL, shared, NULL, 0, NULL, 0, &subparams, + NULL, 0) != -2 || !shared->aligned) + return -1; + subparams.flags -= YESCRYPT_ALLOC_ONLY; + } + + subparams.N /= 2; + + half1 = *shared; + half1.aligned_size /= 2; + half2 = half1; + half2.aligned = (uint8_t *)half2.aligned + half1.aligned_size; + + if (yescrypt_kdf(NULL, &half1, + seed, seedlen, (const uint8_t *)"yescrypt-ROMhash", 16, &subparams, + salt, sizeof(salt))) + goto fail; + + subparams.NROM = subparams.N; + + if (yescrypt_kdf(&half1, &half2, + seed, seedlen, salt, sizeof(salt), &subparams, salt, sizeof(salt))) + goto fail; + + if (yescrypt_kdf(&half2, &half1, + seed, seedlen, salt, sizeof(salt), &subparams, salt, sizeof(salt))) + goto fail; + + tag = (uint64_t *) + ((uint8_t *)shared->aligned + shared->aligned_size - 48); + tag[0] = YESCRYPT_ROM_TAG1; + tag[1] = YESCRYPT_ROM_TAG2; + tag[2] = le64dec(salt); + tag[3] = le64dec(salt + 8); + tag[4] = le64dec(salt + 16); + tag[5] = le64dec(salt + 24); + + insecure_memzero(salt, sizeof(salt)); + return 0; + +fail: + insecure_memzero(salt, sizeof(salt)); + if (!(params->flags & YESCRYPT_SHARED_PREALLOCATED)) + free_region(shared); + return -1; +} + +yescrypt_binary_t *yescrypt_digest_shared(yescrypt_shared_t *shared) +{ + static yescrypt_binary_t digest; + uint64_t *tag; + + if (shared->aligned_size < 48) + return NULL; + + tag = (uint64_t *) + ((uint8_t *)shared->aligned + shared->aligned_size - 48); + + if (tag[0] != YESCRYPT_ROM_TAG1 || tag[1] != YESCRYPT_ROM_TAG2) + return NULL; + + le64enc(digest.uc, tag[2]); + le64enc(digest.uc + 8, tag[3]); + le64enc(digest.uc + 16, tag[4]); + le64enc(digest.uc + 24, tag[5]); + + return &digest; +} + +int yescrypt_free_shared(yescrypt_shared_t *shared) +{ + return free_region(shared); +} + +int yescrypt_init_local(yescrypt_local_t *local) +{ + init_region(local); + return 0; +} + +int yescrypt_free_local(yescrypt_local_t *local) +{ + return free_region(local); +} diff --git a/ext/standard/yescrypt/yescrypt-platform.c b/ext/standard/yescrypt/yescrypt-platform.c new file mode 100644 index 0000000000000..4fdcf272fd229 --- /dev/null +++ b/ext/standard/yescrypt/yescrypt-platform.c @@ -0,0 +1,107 @@ +/*- + * Copyright 2013-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef __unix__ +#include <sys/mman.h> +#endif + +#define HUGEPAGE_THRESHOLD (32 * 1024 * 1024) + +#ifdef __x86_64__ +#define HUGEPAGE_SIZE (2 * 1024 * 1024) +#else +#undef HUGEPAGE_SIZE +#endif + +static void *alloc_region(yescrypt_region_t *region, size_t size) +{ + size_t base_size = size; + uint8_t *base, *aligned; +#ifdef MAP_ANON + int flags = +#ifdef MAP_NOCORE + MAP_NOCORE | +#endif + MAP_ANON | MAP_PRIVATE; +#if defined(MAP_HUGETLB) && defined(HUGEPAGE_SIZE) + size_t new_size = size; + const size_t hugepage_mask = (size_t)HUGEPAGE_SIZE - 1; + if (size >= HUGEPAGE_THRESHOLD && size + hugepage_mask >= size) { + flags |= MAP_HUGETLB; +/* + * Linux's munmap() fails on MAP_HUGETLB mappings if size is not a multiple of + * huge page size, so let's round up to huge page size here. + */ + new_size = size + hugepage_mask; + new_size &= ~hugepage_mask; + } + base = mmap(NULL, new_size, PROT_READ | PROT_WRITE, flags, -1, 0); + if (base != MAP_FAILED) { + base_size = new_size; + } else if (flags & MAP_HUGETLB) { + flags &= ~MAP_HUGETLB; + base = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); + } + +#else + base = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0); +#endif + if (base == MAP_FAILED) + base = NULL; + aligned = base; +#elif defined(HAVE_POSIX_MEMALIGN) + if ((errno = posix_memalign((void **)&base, 64, size)) != 0) + base = NULL; + aligned = base; +#else + base = aligned = NULL; + if (size + 63 < size) { + errno = ENOMEM; + } else if ((base = malloc(size + 63)) != NULL) { + aligned = base + 63; + aligned -= (uintptr_t)aligned & 63; + } +#endif + region->base = base; + region->aligned = aligned; + region->base_size = base ? base_size : 0; + region->aligned_size = base ? size : 0; + return aligned; +} + +static inline void init_region(yescrypt_region_t *region) +{ + region->base = region->aligned = NULL; + region->base_size = region->aligned_size = 0; +} + +static int free_region(yescrypt_region_t *region) +{ + if (region->base) { +#ifdef MAP_ANON + if (munmap(region->base, region->base_size)) + return -1; +#else + free(region->base); +#endif + } + init_region(region); + return 0; +} diff --git a/ext/standard/yescrypt/yescrypt.h b/ext/standard/yescrypt/yescrypt.h new file mode 100644 index 0000000000000..9313420047277 --- /dev/null +++ b/ext/standard/yescrypt/yescrypt.h @@ -0,0 +1,367 @@ +/*- + * Copyright 2009 Colin Percival + * Copyright 2013-2018 Alexander Peslyak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file was originally written by Colin Percival as part of the Tarsnap + * online backup system. + */ +#ifndef _YESCRYPT_H_ +#define _YESCRYPT_H_ + +#include <stdint.h> +#include <stdlib.h> /* for size_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen): + * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, + * p, buflen) and write the result into buf. The parameters r, p, and buflen + * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32. The parameter N + * must be a power of 2 greater than 1. + * + * Return 0 on success; or -1 on error. + * + * MT-safe as long as buf is local to the thread. + */ +extern int crypto_scrypt(const uint8_t *passwd, size_t passwdlen, + const uint8_t *salt, size_t saltlen, + uint64_t N, uint32_t r, uint32_t p, uint8_t *buf, size_t buflen); + +/** + * Internal type used by the memory allocator. Please do not use it directly. + * Use yescrypt_shared_t and yescrypt_local_t as appropriate instead, since + * they might differ from each other in a future version. + */ +typedef struct { + void *base, *aligned; + size_t base_size, aligned_size; +} yescrypt_region_t; + +/** + * Types for shared (ROM) and thread-local (RAM) data structures. + */ +typedef yescrypt_region_t yescrypt_shared_t; +typedef yescrypt_region_t yescrypt_local_t; + +/** + * Two 64-bit tags placed 48 bytes to the end of a ROM in host byte endianness + * (and followed by 32 bytes of the ROM digest). + */ +#define YESCRYPT_ROM_TAG1 0x7470797263736579ULL /* "yescrypt" */ +#define YESCRYPT_ROM_TAG2 0x687361684d4f522dULL /* "-ROMhash" */ + +/** + * Type and possible values for the flags argument of yescrypt_kdf(), + * yescrypt_encode_params_r(), yescrypt_encode_params(). Most of these may be + * OR'ed together, except that YESCRYPT_WORM stands on its own. + * Please refer to the description of yescrypt_kdf() below for the meaning of + * these flags. + */ +typedef uint32_t yescrypt_flags_t; +/* Public */ +#define YESCRYPT_WORM 1 +#define YESCRYPT_RW 0x002 +#define YESCRYPT_ROUNDS_3 0x000 +#define YESCRYPT_ROUNDS_6 0x004 +#define YESCRYPT_GATHER_1 0x000 +#define YESCRYPT_GATHER_2 0x008 +#define YESCRYPT_GATHER_4 0x010 +#define YESCRYPT_GATHER_8 0x018 +#define YESCRYPT_SIMPLE_1 0x000 +#define YESCRYPT_SIMPLE_2 0x020 +#define YESCRYPT_SIMPLE_4 0x040 +#define YESCRYPT_SIMPLE_8 0x060 +#define YESCRYPT_SBOX_6K 0x000 +#define YESCRYPT_SBOX_12K 0x080 +#define YESCRYPT_SBOX_24K 0x100 +#define YESCRYPT_SBOX_48K 0x180 +#define YESCRYPT_SBOX_96K 0x200 +#define YESCRYPT_SBOX_192K 0x280 +#define YESCRYPT_SBOX_384K 0x300 +#define YESCRYPT_SBOX_768K 0x380 +/* Only valid for yescrypt_init_shared() */ +#define YESCRYPT_SHARED_PREALLOCATED 0x10000 +#ifdef YESCRYPT_INTERNAL +/* Private */ +#define YESCRYPT_MODE_MASK 0x003 +#define YESCRYPT_RW_FLAVOR_MASK 0x3fc +#define YESCRYPT_INIT_SHARED 0x01000000 +#define YESCRYPT_ALLOC_ONLY 0x08000000 +#define YESCRYPT_PREHASH 0x10000000 +#endif + +#define YESCRYPT_RW_DEFAULTS \ + (YESCRYPT_RW | \ + YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | YESCRYPT_SIMPLE_2 | \ + YESCRYPT_SBOX_12K) + +#define YESCRYPT_DEFAULTS YESCRYPT_RW_DEFAULTS + +#ifdef YESCRYPT_INTERNAL +#define YESCRYPT_KNOWN_FLAGS \ + (YESCRYPT_MODE_MASK | YESCRYPT_RW_FLAVOR_MASK | \ + YESCRYPT_SHARED_PREALLOCATED | \ + YESCRYPT_INIT_SHARED | YESCRYPT_ALLOC_ONLY | YESCRYPT_PREHASH) +#endif + +/** + * yescrypt parameters combined into one struct. N, r, p are the same as in + * classic scrypt, except that the meaning of p changes when YESCRYPT_RW is + * set. flags, t, g, NROM are special to yescrypt. + */ +typedef struct { + yescrypt_flags_t flags; + uint64_t N; + uint32_t r, p, t, g; + uint64_t NROM; +} yescrypt_params_t; + +/** + * A 256-bit yescrypt hash, or a hash encryption key (which may itself have + * been derived as a yescrypt hash of a human-specified key string). + */ +typedef union { + unsigned char uc[32]; + uint64_t u64[4]; +} yescrypt_binary_t; + +/** + * yescrypt_init_shared(shared, seed, seedlen, params): + * Optionally allocate memory for and initialize the shared (ROM) data + * structure. The parameters flags, NROM, r, p, and t specify how the ROM is + * to be initialized, and seed and seedlen specify the initial seed affecting + * the data with which the ROM is filled. + * + * Return 0 on success; or -1 on error. + * + * If bit YESCRYPT_SHARED_PREALLOCATED in flags is set, then memory for the + * ROM is assumed to have been preallocated by the caller, with shared->aligned + * being the start address of the ROM and shared->aligned_size being its size + * (which must be sufficient for NROM, r, p). This may be used e.g. when the + * ROM is to be placed in a SysV shared memory segment allocated by the caller. + * + * MT-safe as long as shared is local to the thread. + */ +extern int yescrypt_init_shared(yescrypt_shared_t *shared, + const uint8_t *seed, size_t seedlen, const yescrypt_params_t *params); + +/** + * yescrypt_digest_shared(shared): + * Extract the previously stored message digest of the provided yescrypt ROM. + * + * Return pointer to the message digest on success; or NULL on error. + * + * MT-unsafe. + */ +extern yescrypt_binary_t *yescrypt_digest_shared(yescrypt_shared_t *shared); + +/** + * yescrypt_free_shared(shared): + * Free memory that had been allocated with yescrypt_init_shared(). + * + * Return 0 on success; or -1 on error. + * + * MT-safe as long as shared is local to the thread. + */ +extern int yescrypt_free_shared(yescrypt_shared_t *shared); + +/** + * yescrypt_init_local(local): + * Initialize the thread-local (RAM) data structure. Actual memory allocation + * is currently fully postponed until a call to yescrypt_kdf() or yescrypt_r(). + * + * Return 0 on success; or -1 on error. + * + * MT-safe as long as local is local to the thread. + */ +extern int yescrypt_init_local(yescrypt_local_t *local); + +/** + * yescrypt_free_local(local): + * Free memory that may have been allocated for an initialized thread-local + * (RAM) data structure. + * + * Return 0 on success; or -1 on error. + * + * MT-safe as long as local is local to the thread. + */ +extern int yescrypt_free_local(yescrypt_local_t *local); + +/** + * yescrypt_kdf(shared, local, passwd, passwdlen, salt, saltlen, params, + * buf, buflen): + * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, + * p, buflen), or a revision of scrypt as requested by flags and shared, and + * write the result into buf. The parameters N, r, p, and buflen must satisfy + * the same conditions as with crypto_scrypt(). t controls computation time + * while not affecting peak memory usage (t = 0 is optimal unless higher N*r + * is not affordable while higher t is). g controls hash upgrades (g = 0 for + * no upgrades so far). shared and flags may request special modes. local is + * the thread-local data structure, allowing to preserve and reuse a memory + * allocation across calls, thereby reducing processing overhead. + * + * Return 0 on success; or -1 on error. + * + * Classic scrypt is available by setting shared = NULL, flags = 0, and t = 0. + * + * Setting YESCRYPT_WORM enables only minimal deviations from classic scrypt: + * support for the t parameter, and pre- and post-hashing. + * + * Setting YESCRYPT_RW fully enables yescrypt. As a side effect of differences + * between the algorithms, it also prevents p > 1 from growing the threads' + * combined processing time and memory allocation (like it did with classic + * scrypt and YESCRYPT_WORM), treating p as a divider rather than a multiplier. + * + * Passing a shared structure, with ROM contents previously computed by + * yescrypt_init_shared(), enables the use of ROM and requires YESCRYPT_RW. + * + * In order to allow for initialization of the ROM to be split into a separate + * program (or separate invocation of the same program), the shared->aligned + * and shared->aligned_size fields may optionally be set by the caller directly + * (e.g., to a mapped SysV shm segment), without using yescrypt_init_shared(). + * + * local must be initialized with yescrypt_init_local(). + * + * MT-safe as long as local and buf are local to the thread. + */ +extern int yescrypt_kdf(const yescrypt_shared_t *shared, + yescrypt_local_t *local, + const uint8_t *passwd, size_t passwdlen, + const uint8_t *salt, size_t saltlen, + const yescrypt_params_t *params, + uint8_t *buf, size_t buflen); + +/** + * yescrypt_r(shared, local, passwd, passwdlen, setting, key, buf, buflen): + * Compute and encode an scrypt or enhanced scrypt hash of passwd given the + * parameters and salt value encoded in setting. If shared is not NULL, a ROM + * is used and YESCRYPT_RW is required. Otherwise, whether to compute classic + * scrypt, YESCRYPT_WORM (a slight deviation from classic scrypt), or + * YESCRYPT_RW (time-memory tradeoff discouraging modification) is determined + * by the setting string. shared (if not NULL) and local must be initialized + * as described above for yescrypt_kdf(). buf must be large enough (as + * indicated by buflen) to hold the encoded hash string. + * + * Return the encoded hash string on success; or NULL on error. + * + * MT-safe as long as local and buf are local to the thread. + */ +extern uint8_t *yescrypt_r(const yescrypt_shared_t *shared, + yescrypt_local_t *local, + const uint8_t *passwd, size_t passwdlen, + const uint8_t *setting, + const yescrypt_binary_t *key, + uint8_t *buf, size_t buflen); + +/** + * yescrypt(passwd, setting): + * Compute and encode an scrypt or enhanced scrypt hash of passwd given the + * parameters and salt value encoded in setting. Whether to compute classic + * scrypt, YESCRYPT_WORM (a slight deviation from classic scrypt), or + * YESCRYPT_RW (time-memory tradeoff discouraging modification) is determined + * by the setting string. + * + * Return the encoded hash string on success; or NULL on error. + * + * This is a crypt(3)-like interface, which is simpler to use than + * yescrypt_r(), but it is not MT-safe, it does not allow for the use of a ROM, + * and it is slower than yescrypt_r() for repeated calls because it allocates + * and frees memory on each call. + * + * MT-unsafe. + */ +extern uint8_t *yescrypt(const uint8_t *passwd, const uint8_t *setting); + +/** + * yescrypt_reencrypt(hash, from_key, to_key): + * Re-encrypt a yescrypt hash from one key to another. Either key may be NULL + * to indicate unencrypted hash. The encoded hash string is modified in-place. + * + * Return the hash pointer on success; or NULL on error (in which case the hash + * string is left unmodified). + * + * MT-safe as long as hash is local to the thread. + */ +extern uint8_t *yescrypt_reencrypt(uint8_t *hash, + const yescrypt_binary_t *from_key, + const yescrypt_binary_t *to_key); + +/** + * yescrypt_encode_params_r(params, src, srclen, buf, buflen): + * Generate a setting string for use with yescrypt_r() and yescrypt() by + * encoding into it the parameters flags, N, r, p, t, g, and a salt given by + * src (of srclen bytes). buf must be large enough (as indicated by buflen) + * to hold the setting string. + * + * Return the setting string on success; or NULL on error. + * + * MT-safe as long as buf is local to the thread. + */ +extern uint8_t *yescrypt_encode_params_r(const yescrypt_params_t *params, + const uint8_t *src, size_t srclen, + uint8_t *buf, size_t buflen); + +/** + * yescrypt_encode_params(params, src, srclen): + * Generate a setting string for use with yescrypt_r() and yescrypt(). This + * function is the same as yescrypt_encode_params_r() except that it uses a + * static buffer and thus is not MT-safe. + * + * Return the setting string on success; or NULL on error. + * + * MT-unsafe. + */ +extern uint8_t *yescrypt_encode_params(const yescrypt_params_t *params, + const uint8_t *src, size_t srclen); + +/* PHP changes: expose some macros and functions */ +const uint8_t *yescrypt_parse_settings(const uint8_t *setting, yescrypt_params_t *params, const yescrypt_binary_t *key); + +const uint8_t *yescrypt_decode64(uint8_t *dst, size_t *dstlen, + const uint8_t *src, size_t srclen); + +uint8_t *yescrypt_encode64_uint32_fixed(uint8_t *dst, size_t dstlen, + uint32_t src, uint32_t srcbits); + +#define BYTES2CHARS(bytes) ((((bytes) * 8) + 5) / 6) + +#define HASH_SIZE sizeof(yescrypt_binary_t) /* bytes */ +#define HASH_LEN BYTES2CHARS(HASH_SIZE) /* base-64 chars */ + +/* + * "$y$", up to 8 params of up to 6 chars each, '$', salt + * Alternatively, but that's smaller: + * "$7$", 3 params encoded as 1+5+5 chars, salt + */ +#define PREFIX_LEN (3 + 8 * 6 + 1 + BYTES2CHARS(32)) + +#ifdef __cplusplus +} +#endif + +#endif /* !_YESCRYPT_H_ */