diff --git a/wp-includes/sodium_compat/LICENSE b/wp-includes/sodium_compat/LICENSE index 532a3cf5270..ce41e508c0b 100644 --- a/wp-includes/sodium_compat/LICENSE +++ b/wp-includes/sodium_compat/LICENSE @@ -1,10 +1,10 @@ /* * ISC License * - * Copyright (c) 2016-2018 + * Copyright (c) 2016-2019 * Paragon Initiative Enterprises * - * Copyright (c) 2013-2018 + * Copyright (c) 2013-2019 * Frank Denis * * Permission to use, copy, modify, and/or distribute this software for any diff --git a/wp-includes/sodium_compat/autoload.php b/wp-includes/sodium_compat/autoload.php index de2683daee5..aa6ef8535b2 100644 --- a/wp-includes/sodium_compat/autoload.php +++ b/wp-includes/sodium_compat/autoload.php @@ -43,7 +43,17 @@ function sodiumCompatAutoloader($class) // unless PHP >= 5.3.0 require_once dirname(__FILE__) . '/lib/namespaced.php'; require_once dirname(__FILE__) . '/lib/sodium_compat.php'; +} else { + require_once dirname(__FILE__) . '/src/PHP52/SplFixedArray.php'; } if (PHP_VERSION_ID < 70200 || !extension_loaded('sodium')) { - require_once dirname(__FILE__) . '/lib/php72compat.php'; + if (PHP_VERSION_ID >= 50300 && !defined('SODIUM_CRYPTO_SCALARMULT_BYTES')) { + require_once dirname(__FILE__) . '/lib/php72compat_const.php'; + } + if (PHP_VERSION_ID >= 70000) { + assert(class_exists('ParagonIE_Sodium_Compat'), 'Possible filesystem/autoloader bug?'); + } else { + assert(class_exists('ParagonIE_Sodium_Compat')); + } + require_once (dirname(__FILE__) . '/lib/php72compat.php'); } diff --git a/wp-includes/sodium_compat/composer.json b/wp-includes/sodium_compat/composer.json index b69808d9228..160d94490be 100644 --- a/wp-includes/sodium_compat/composer.json +++ b/wp-includes/sodium_compat/composer.json @@ -54,7 +54,7 @@ "paragonie/random_compat": ">=1" }, "require-dev": { - "phpunit/phpunit": "^3|^4|^5" + "phpunit/phpunit": "^3|^4|^5|^6|^7" }, "suggest": { "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", diff --git a/wp-includes/sodium_compat/lib/constants.php b/wp-includes/sodium_compat/lib/constants.php index 8ddb6a4879c..b6a120e4a4b 100644 --- a/wp-includes/sodium_compat/lib/constants.php +++ b/wp-includes/sodium_compat/lib/constants.php @@ -1,6 +1,8 @@ + * @throws SodiumException + */ + function sodium_crypto_secretstream_xchacha20poly1305_init_push($key) + { + return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_init_push($key); + } +} +if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_push')) { + /** + * @param string $state + * @param string $msg + * @param string $aad + * @param int $tag + * @return string + * @throws SodiumException + */ + function sodium_crypto_secretstream_xchacha20poly1305_push(&$state, $msg, $aad = '', $tag = 0) + { + return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_push($state, $msg, $aad, $tag); + } +} +if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_init_pull')) { + /** + * @param string $header + * @param string $key + * @return string + * @throws Exception + */ + function sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $key) + { + return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_init_pull($header, $key); + } +} +if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_pull')) { + /** + * @param string $state + * @param string $cipher + * @param string $aad + * @return bool|array{0: string, 1: int} + * @throws SodiumException + */ + function sodium_crypto_secretstream_xchacha20poly1305_pull(&$state, $cipher, $aad = '') + { + return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_pull($state, $cipher, $aad); + } +} +if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_rekey')) { + /** + * @param string $state + * @return void + * @throws SodiumException + */ + function sodium_crypto_secretstream_xchacha20poly1305_rekey(&$state) + { + ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_rekey($state); + } +} +if (!is_callable('sodium_crypto_secretstream_xchacha20poly1305_keygen')) { + /** + * @return string + * @throws Exception + */ + function sodium_crypto_secretstream_xchacha20poly1305_keygen() + { + return ParagonIE_Sodium_Compat::crypto_secretstream_xchacha20poly1305_keygen(); + } +} if (!is_callable('sodium_crypto_shorthash')) { /** * @see ParagonIE_Sodium_Compat::crypto_shorthash() @@ -739,6 +991,7 @@ function sodium_crypto_shorthash($message, $key = '') /** * @see ParagonIE_Sodium_Compat::crypto_shorthash_keygen() * @return string + * @throws Exception */ function sodium_crypto_shorthash_keygen() { @@ -773,6 +1026,20 @@ function sodium_crypto_sign_detached($message, $sk) return ParagonIE_Sodium_Compat::crypto_sign_detached($message, $sk); } } +if (!is_callable('sodium_crypto_sign_keypair_from_secretkey_and_publickey')) { + /** + * @see ParagonIE_Sodium_Compat::crypto_sign_keypair_from_secretkey_and_publickey() + * @param string $sk + * @param string $pk + * @return string + * @throws SodiumException + * @throws TypeError + */ + function sodium_crypto_sign_keypair_from_secretkey_and_publickey($sk, $pk) + { + return ParagonIE_Sodium_Compat::crypto_sign_keypair_from_secretkey_and_publickey($sk, $pk); + } +} if (!is_callable('sodium_crypto_sign_keypair')) { /** * @see ParagonIE_Sodium_Compat::crypto_sign_keypair() @@ -915,6 +1182,7 @@ function sodium_crypto_stream($len, $nonce, $key) /** * @see ParagonIE_Sodium_Compat::crypto_stream_keygen() * @return string + * @throws Exception */ function sodium_crypto_stream_keygen() { @@ -1019,6 +1287,34 @@ function sodium_memzero(&$str) ParagonIE_Sodium_Compat::memzero($str); } } +if (!is_callable('sodium_pad')) { + /** + * @see ParagonIE_Sodium_Compat::pad() + * @param string $unpadded + * @param int $blockSize + * @return int + * @throws SodiumException + * @throws TypeError + */ + function sodium_pad($unpadded, $blockSize) + { + return ParagonIE_Sodium_Compat::pad($unpadded, $blockSize, true); + } +} +if (!is_callable('sodium_unpad')) { + /** + * @see ParagonIE_Sodium_Compat::pad() + * @param string $padded + * @param int $blockSize + * @return int + * @throws SodiumException + * @throws TypeError + */ + function sodium_unpad($padded, $blockSize) + { + return ParagonIE_Sodium_Compat::unpad($padded, $blockSize, true); + } +} if (!is_callable('sodium_randombytes_buf')) { /** * @see ParagonIE_Sodium_Compat::randombytes_buf() @@ -1049,6 +1345,7 @@ function sodium_randombytes_uniform($upperLimit) /** * @see ParagonIE_Sodium_Compat::randombytes_random16() * @return int + * @throws Exception */ function sodium_randombytes_random16() { diff --git a/wp-includes/sodium_compat/lib/php72compat_const.php b/wp-includes/sodium_compat/lib/php72compat_const.php new file mode 100644 index 00000000000..6a4247aa97e --- /dev/null +++ b/wp-includes/sodium_compat/lib/php72compat_const.php @@ -0,0 +1,90 @@ +>= 8; + } + $val = ParagonIE_Sodium_Core_Util::intArrayToString($A); + } + + /** + * @param string $encoded + * @param int $variant + * @param string $ignore + * @return string + * @throws SodiumException + */ + public static function base642bin($encoded, $variant, $ignore = '') + { + /* Type checks: */ + ParagonIE_Sodium_Core_Util::declareScalarType($encoded, 'string', 1); + + /** @var string $encoded */ + $encoded = (string) $encoded; + if (ParagonIE_Sodium_Core_Util::strlen($encoded) === 0) { + return ''; + } + + // Just strip before decoding + if (!empty($ignore)) { + $encoded = str_replace($ignore, '', $encoded); + } + + try { + switch ($variant) { + case self::BASE64_VARIANT_ORIGINAL: + return ParagonIE_Sodium_Core_Base64_Original::decode($encoded, true); + case self::BASE64_VARIANT_ORIGINAL_NO_PADDING: + return ParagonIE_Sodium_Core_Base64_Original::decode($encoded, false); + case self::BASE64_VARIANT_URLSAFE: + return ParagonIE_Sodium_Core_Base64_UrlSafe::decode($encoded, true); + case self::BASE64_VARIANT_URLSAFE_NO_PADDING: + return ParagonIE_Sodium_Core_Base64_UrlSafe::decode($encoded, false); + default: + throw new SodiumException('invalid base64 variant identifier'); + } + } catch (Exception $ex) { + if ($ex instanceof SodiumException) { + throw $ex; + } + throw new SodiumException('invalid base64 string'); + } + } + + /** + * @param string $decoded + * @param int $variant + * @return string + * @throws SodiumException + */ + public static function bin2base64($decoded, $variant) + { + /* Type checks: */ + ParagonIE_Sodium_Core_Util::declareScalarType($decoded, 'string', 1); + /** @var string $decoded */ + $decoded = (string) $decoded; + if (ParagonIE_Sodium_Core_Util::strlen($decoded) === 0) { + return ''; + } + + switch ($variant) { + case self::BASE64_VARIANT_ORIGINAL: + return ParagonIE_Sodium_Core_Base64_Original::encode($decoded); + case self::BASE64_VARIANT_ORIGINAL_NO_PADDING: + return ParagonIE_Sodium_Core_Base64_Original::encodeUnpadded($decoded); + case self::BASE64_VARIANT_URLSAFE: + return ParagonIE_Sodium_Core_Base64_UrlSafe::encode($decoded); + case self::BASE64_VARIANT_URLSAFE_NO_PADDING: + return ParagonIE_Sodium_Core_Base64_UrlSafe::encodeUnpadded($decoded); + default: + throw new SodiumException('invalid base64 variant identifier'); + } + } + /** * Cache-timing-safe implementation of bin2hex(). * @@ -1310,6 +1433,7 @@ public static function crypto_generichash($message, $key = '', $length = self::C * @throws TypeError * @psalm-suppress MixedArgument * @psalm-suppress ReferenceConstraintViolation + * @psalm-suppress ConflictingReferenceConstraint */ public static function crypto_generichash_final(&$ctx, $length = self::CRYPTO_GENERICHASH_BYTES) { @@ -1324,6 +1448,14 @@ public static function crypto_generichash_final(&$ctx, $length = self::CRYPTO_GE $func = '\\Sodium\\crypto_generichash_final'; return (string) $func($ctx, $length); } + if ($length < 1) { + try { + self::memzero($ctx); + } catch (SodiumException $ex) { + unset($ctx); + } + return ''; + } if (PHP_INT_SIZE === 4) { $result = ParagonIE_Sodium_Crypto32::generichash_final($ctx, $length); } else { @@ -1379,6 +1511,53 @@ public static function crypto_generichash_init($key = '', $length = self::CRYPTO return ParagonIE_Sodium_Crypto::generichash_init($key, $length); } + /** + * Initialize a BLAKE2b hashing context, for use in a streaming interface. + * + * @param string|null $key If specified must be a string between 16 and 64 bytes + * @param int $length The size of the desired hash output + * @param string $salt Salt (up to 16 bytes) + * @param string $personal Personalization string (up to 16 bytes) + * @return string A BLAKE2 hashing context, encoded as a string + * (To be 100% compatible with ext/libsodium) + * @throws SodiumException + * @throws TypeError + * @psalm-suppress MixedArgument + */ + public static function crypto_generichash_init_salt_personal( + $key = '', + $length = self::CRYPTO_GENERICHASH_BYTES, + $salt = '', + $personal = '' + ) { + /* Type checks: */ + if (is_null($key)) { + $key = ''; + } + ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($length, 'int', 2); + ParagonIE_Sodium_Core_Util::declareScalarType($salt, 'string', 3); + ParagonIE_Sodium_Core_Util::declareScalarType($personal, 'string', 4); + $salt = str_pad($salt, 16, "\0", STR_PAD_RIGHT); + $personal = str_pad($personal, 16, "\0", STR_PAD_RIGHT); + + /* Input validation: */ + if (!empty($key)) { + /* + if (ParagonIE_Sodium_Core_Util::strlen($key) < self::CRYPTO_GENERICHASH_KEYBYTES_MIN) { + throw new SodiumException('Unsupported key size. Must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes long.'); + } + */ + if (ParagonIE_Sodium_Core_Util::strlen($key) > self::CRYPTO_GENERICHASH_KEYBYTES_MAX) { + throw new SodiumException('Unsupported key size. Must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes long.'); + } + } + if (PHP_INT_SIZE === 4) { + return ParagonIE_Sodium_Crypto32::generichash_init_salt_personal($key, $length, $salt, $personal); + } + return ParagonIE_Sodium_Crypto::generichash_init_salt_personal($key, $length, $salt, $personal); + } + /** * Update a BLAKE2b hashing context with additional data. * @@ -1424,6 +1603,65 @@ public static function crypto_generichash_keygen() return random_bytes(self::CRYPTO_GENERICHASH_KEYBYTES); } + /** + * @param int $subkey_len + * @param int $subkey_id + * @param string $context + * @param string $key + * @return string + * @throws SodiumException + */ + public static function crypto_kdf_derive_from_key( + $subkey_len, + $subkey_id, + $context, + $key + ) { + ParagonIE_Sodium_Core_Util::declareScalarType($subkey_len, 'int', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($subkey_id, 'int', 2); + ParagonIE_Sodium_Core_Util::declareScalarType($context, 'string', 3); + ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4); + $subkey_id = (int) $subkey_id; + $subkey_len = (int) $subkey_len; + $context = (string) $context; + $key = (string) $key; + + if ($subkey_len < self::CRYPTO_KDF_BYTES_MIN) { + throw new SodiumException('subkey cannot be smaller than SODIUM_CRYPTO_KDF_BYTES_MIN'); + } + if ($subkey_len > self::CRYPTO_KDF_BYTES_MAX) { + throw new SodiumException('subkey cannot be larger than SODIUM_CRYPTO_KDF_BYTES_MAX'); + } + if ($subkey_id < 0) { + throw new SodiumException('subkey_id cannot be negative'); + } + if (ParagonIE_Sodium_Core_Util::strlen($context) !== self::CRYPTO_KDF_CONTEXTBYTES) { + throw new SodiumException('context should be SODIUM_CRYPTO_KDF_CONTEXTBYTES bytes'); + } + if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_KDF_KEYBYTES) { + throw new SodiumException('key should be SODIUM_CRYPTO_KDF_KEYBYTES bytes'); + } + + $salt = ParagonIE_Sodium_Core_Util::store64_le($subkey_id); + $state = self::crypto_generichash_init_salt_personal( + $key, + $subkey_len, + $salt, + $context + ); + return self::crypto_generichash_final($state, $subkey_len); + } + + /** + * @return string + * @throws Exception + * @throws Error + */ + public static function crypto_kdf_keygen() + { + return random_bytes(self::CRYPTO_KDF_KEYBYTES); + } + /** * Perform a key exchange, between a designated client and a server. * @@ -1510,6 +1748,149 @@ public static function crypto_kx($my_secret, $their_public, $client_public, $ser ); } + /** + * @param string $seed + * @return string + * @throws SodiumException + */ + public static function crypto_kx_seed_keypair($seed) + { + ParagonIE_Sodium_Core_Util::declareScalarType($seed, 'string', 1); + + $seed = (string) $seed; + + if (ParagonIE_Sodium_Core_Util::strlen($seed) !== self::CRYPTO_KX_SEEDBYTES) { + throw new SodiumException('seed must be SODIUM_CRYPTO_KX_SEEDBYTES bytes'); + } + + $sk = self::crypto_generichash($seed, '', self::CRYPTO_KX_SECRETKEYBYTES); + $pk = self::crypto_scalarmult_base($sk); + return $sk . $pk; + } + + /** + * @return string + * @throws Exception + */ + public static function crypto_kx_keypair() + { + $sk = self::randombytes_buf(self::CRYPTO_KX_SECRETKEYBYTES); + $pk = self::crypto_scalarmult_base($sk); + return $sk . $pk; + } + + /** + * @param string $keypair + * @param string $serverPublicKey + * @return array{0: string, 1: string} + * @throws SodiumException + */ + public static function crypto_kx_client_session_keys($keypair, $serverPublicKey) + { + ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($serverPublicKey, 'string', 2); + + $keypair = (string) $keypair; + $serverPublicKey = (string) $serverPublicKey; + + if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_KX_KEYPAIRBYTES) { + throw new SodiumException('keypair should be SODIUM_CRYPTO_KX_KEYPAIRBYTES bytes'); + } + if (ParagonIE_Sodium_Core_Util::strlen($serverPublicKey) !== self::CRYPTO_KX_PUBLICKEYBYTES) { + throw new SodiumException('public keys must be SODIUM_CRYPTO_KX_PUBLICKEYBYTES bytes'); + } + + $sk = self::crypto_kx_secretkey($keypair); + $pk = self::crypto_kx_publickey($keypair); + $h = self::crypto_generichash_init(null, self::CRYPTO_KX_SESSIONKEYBYTES * 2); + self::crypto_generichash_update($h, self::crypto_scalarmult($sk, $serverPublicKey)); + self::crypto_generichash_update($h, $pk); + self::crypto_generichash_update($h, $serverPublicKey); + $sessionKeys = self::crypto_generichash_final($h, self::CRYPTO_KX_SESSIONKEYBYTES * 2); + return array( + ParagonIE_Sodium_Core_Util::substr( + $sessionKeys, + 0, + self::CRYPTO_KX_SESSIONKEYBYTES + ), + ParagonIE_Sodium_Core_Util::substr( + $sessionKeys, + self::CRYPTO_KX_SESSIONKEYBYTES, + self::CRYPTO_KX_SESSIONKEYBYTES + ) + ); + } + + /** + * @param string $keypair + * @param string $clientPublicKey + * @return array{0: string, 1: string} + * @throws SodiumException + */ + public static function crypto_kx_server_session_keys($keypair, $clientPublicKey) + { + ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($clientPublicKey, 'string', 2); + + $keypair = (string) $keypair; + $clientPublicKey = (string) $clientPublicKey; + + if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_KX_KEYPAIRBYTES) { + throw new SodiumException('keypair should be SODIUM_CRYPTO_KX_KEYPAIRBYTES bytes'); + } + if (ParagonIE_Sodium_Core_Util::strlen($clientPublicKey) !== self::CRYPTO_KX_PUBLICKEYBYTES) { + throw new SodiumException('public keys must be SODIUM_CRYPTO_KX_PUBLICKEYBYTES bytes'); + } + + $sk = self::crypto_kx_secretkey($keypair); + $pk = self::crypto_kx_publickey($keypair); + $h = self::crypto_generichash_init(null, self::CRYPTO_KX_SESSIONKEYBYTES * 2); + self::crypto_generichash_update($h, self::crypto_scalarmult($sk, $clientPublicKey)); + self::crypto_generichash_update($h, $clientPublicKey); + self::crypto_generichash_update($h, $pk); + $sessionKeys = self::crypto_generichash_final($h, self::CRYPTO_KX_SESSIONKEYBYTES * 2); + return array( + ParagonIE_Sodium_Core_Util::substr( + $sessionKeys, + self::CRYPTO_KX_SESSIONKEYBYTES, + self::CRYPTO_KX_SESSIONKEYBYTES + ), + ParagonIE_Sodium_Core_Util::substr( + $sessionKeys, + 0, + self::CRYPTO_KX_SESSIONKEYBYTES + ) + ); + } + + /** + * @param string $kp + * @return string + * @throws SodiumException + */ + public static function crypto_kx_secretkey($kp) + { + return ParagonIE_Sodium_Core_Util::substr( + $kp, + 0, + self::CRYPTO_KX_SECRETKEYBYTES + ); + } + + /** + * @param string $kp + * @return string + * @throws SodiumException + */ + public static function crypto_kx_publickey($kp) + { + return ParagonIE_Sodium_Core_Util::substr( + $kp, + self::CRYPTO_KX_SECRETKEYBYTES, + self::CRYPTO_KX_PUBLICKEYBYTES + ); + } + /** * @param int $outlen * @param string $passwd @@ -1592,6 +1973,36 @@ public static function crypto_pwhash_str($passwd, $opslimit, $memlimit) ); } + /** + * Do we need to rehash this password? + * + * @param string $hash + * @param int $opslimit + * @param int $memlimit + * @return bool + * @throws SodiumException + */ + public static function crypto_pwhash_str_needs_rehash($hash, $opslimit, $memlimit) + { + ParagonIE_Sodium_Core_Util::declareScalarType($hash, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 2); + ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 3); + + // Just grab the first 4 pieces. + $pieces = explode('$', (string) $hash); + $prefix = implode('$', array_slice($pieces, 0, 4)); + + // Rebuild the expected header. + /** @var int $ops */ + $ops = (int) $opslimit; + /** @var int $mem */ + $mem = (int) $memlimit >> 10; + $encoded = self::CRYPTO_PWHASH_STRPREFIX . 'v=19$m=' . $mem . ',t=' . $ops . ',p=1'; + + // Do they match? If so, we don't need to rehash, so return false. + return !ParagonIE_Sodium_Core_Util::hashEquals($encoded, $prefix); + } + /** * @param string $passwd * @param string $hash @@ -1987,6 +2398,111 @@ public static function crypto_secretbox_xchacha20poly1305_open($ciphertext, $non return ParagonIE_Sodium_Crypto::secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key); } + /** + * @param string $key + * @return array Returns a state and a header. + * @throws Exception + * @throws SodiumException + */ + public static function crypto_secretstream_xchacha20poly1305_init_push($key) + { + if (PHP_INT_SIZE === 4) { + return ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_init_push($key); + } + return ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_init_push($key); + } + + /** + * @param string $header + * @param string $key + * @return string Returns a state. + * @throws Exception + */ + public static function crypto_secretstream_xchacha20poly1305_init_pull($header, $key) + { + if (ParagonIE_Sodium_Core_Util::strlen($header) < self::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES) { + throw new SodiumException( + 'header size should be SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES bytes' + ); + } + if (PHP_INT_SIZE === 4) { + return ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_init_pull($key, $header); + } + return ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_init_pull($key, $header); + } + + /** + * @param string $state + * @param string $msg + * @param string $aad + * @param int $tag + * @return string + * @throws SodiumException + */ + public static function crypto_secretstream_xchacha20poly1305_push(&$state, $msg, $aad = '', $tag = 0) + { + if (PHP_INT_SIZE === 4) { + return ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_push( + $state, + $msg, + $aad, + $tag + ); + } + return ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_push( + $state, + $msg, + $aad, + $tag + ); + } + + /** + * @param string $state + * @param string $msg + * @param string $aad + * @return bool|array{0: string, 1: int} + * @throws SodiumException + */ + public static function crypto_secretstream_xchacha20poly1305_pull(&$state, $msg, $aad = '') + { + if (PHP_INT_SIZE === 4) { + return ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_pull( + $state, + $msg, + $aad + ); + } + return ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_pull( + $state, + $msg, + $aad + ); + } + + /** + * @return string + * @throws Exception + */ + public static function crypto_secretstream_xchacha20poly1305_keygen() + { + return random_bytes(self::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES); + } + + /** + * @param string $state + * @return void + * @throws SodiumException + */ + public static function crypto_secretstream_xchacha20poly1305_rekey(&$state) + { + if (PHP_INT_SIZE === 4) { + ParagonIE_Sodium_Crypto32::secretstream_xchacha20poly1305_rekey($state); + } else { + ParagonIE_Sodium_Crypto::secretstream_xchacha20poly1305_rekey($state); + } + } + /** * Calculates a SipHash-2-4 hash of a message for a given key. * @@ -2136,6 +2652,32 @@ public static function crypto_sign_keypair() return ParagonIE_Sodium_Core_Ed25519::keypair(); } + /** + * @param string $sk + * @param string $pk + * @return string + * @throws SodiumException + */ + public static function crypto_sign_keypair_from_secretkey_and_publickey($sk, $pk) + { + ParagonIE_Sodium_Core_Util::declareScalarType($sk, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($pk, 'string', 1); + $sk = (string) $sk; + $pk = (string) $pk; + + if (ParagonIE_Sodium_Core_Util::strlen($sk) !== self::CRYPTO_SIGN_SECRETKEYBYTES) { + throw new SodiumException('secretkey should be SODIUM_CRYPTO_SIGN_SECRETKEYBYTES bytes'); + } + if (ParagonIE_Sodium_Core_Util::strlen($pk) !== self::CRYPTO_SIGN_PUBLICKEYBYTES) { + throw new SodiumException('publickey should be SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES bytes'); + } + + if (self::useNewSodiumAPI()) { + return sodium_crypto_sign_keypair_from_secretkey_and_publickey($sk, $pk); + } + return $sk . $pk; + } + /** * Generate an Ed25519 keypair from a seed. * @@ -2624,6 +3166,9 @@ public static function memcmp($left, $right) ParagonIE_Sodium_Core_Util::declareScalarType($left, 'string', 1); ParagonIE_Sodium_Core_Util::declareScalarType($right, 'string', 2); + if (self::useNewSodiumAPI()) { + return sodium_memcmp($left, $right); + } if (self::use_fallback('memcmp')) { return (int) call_user_func('\\Sodium\\memcmp', $left, $right); } @@ -2668,6 +3213,158 @@ public static function memzero(&$var) ); } + /** + * @param string $unpadded + * @param int $blockSize + * @param bool $dontFallback + * @return string + * @throws SodiumException + */ + public static function pad($unpadded, $blockSize, $dontFallback = false) + { + /* Type checks: */ + ParagonIE_Sodium_Core_Util::declareScalarType($unpadded, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($blockSize, 'int', 2); + + $unpadded = (string) $unpadded; + $blockSize = (int) $blockSize; + + if (self::useNewSodiumAPI() && !$dontFallback) { + return (string) sodium_pad($unpadded, $blockSize); + } + + if ($blockSize <= 0) { + throw new SodiumException( + 'block size cannot be less than 1' + ); + } + $unpadded_len = ParagonIE_Sodium_Core_Util::strlen($unpadded); + $xpadlen = ($blockSize - 1); + if (($blockSize & ($blockSize - 1)) === 0) { + $xpadlen -= $unpadded_len & ($blockSize - 1); + } else { + $xpadlen -= $unpadded_len % $blockSize; + } + + $xpadded_len = $unpadded_len + $xpadlen; + $padded = str_repeat("\0", $xpadded_len - 1); + if ($unpadded_len > 0) { + $st = 1; + $i = 0; + $k = $unpadded_len; + for ($j = 0; $j <= $xpadded_len; ++$j) { + $i = (int) $i; + $k = (int) $k; + $st = (int) $st; + if ($j >= $unpadded_len) { + $padded[$j] = "\0"; + } else { + $padded[$j] = $unpadded[$j]; + } + /** @var int $k */ + $k -= $st; + $st = (int) (~( + ( + ( + ($k >> 48) + | + ($k >> 32) + | + ($k >> 16) + | + $k + ) - 1 + ) >> 16 + ) + ) & 1; + $i += $st; + } + } + + $mask = 0; + $tail = $xpadded_len; + for ($i = 0; $i < $blockSize; ++$i) { + # barrier_mask = (unsigned char) + # (((i ^ xpadlen) - 1U) >> ((sizeof(size_t) - 1U) * CHAR_BIT)); + $barrier_mask = (($i ^ $xpadlen) -1) >> ((PHP_INT_SIZE << 3) - 1); + # tail[-i] = (tail[-i] & mask) | (0x80 & barrier_mask); + $padded[$tail - $i] = ParagonIE_Sodium_Core_Util::intToChr( + (ParagonIE_Sodium_Core_Util::chrToInt($padded[$tail - $i]) & $mask) + | + (0x80 & $barrier_mask) + ); + # mask |= barrier_mask; + $mask |= $barrier_mask; + } + return $padded; + } + + /** + * @param string $padded + * @param int $blockSize + * @param bool $dontFallback + * @return string + * @throws SodiumException + */ + public static function unpad($padded, $blockSize, $dontFallback = false) + { + /* Type checks: */ + ParagonIE_Sodium_Core_Util::declareScalarType($padded, 'string', 1); + ParagonIE_Sodium_Core_Util::declareScalarType($blockSize, 'int', 2); + + $padded = (string) $padded; + $blockSize = (int) $blockSize; + + if (self::useNewSodiumAPI() && !$dontFallback) { + return (string) sodium_unpad($padded, $blockSize); + } + if ($blockSize <= 0) { + throw new SodiumException('block size cannot be less than 1'); + } + $padded_len = ParagonIE_Sodium_Core_Util::strlen($padded); + if ($padded_len < $blockSize) { + throw new SodiumException('invalid padding'); + } + + # tail = &padded[padded_len - 1U]; + $tail = $padded_len - 1; + + $acc = 0; + $valid = 0; + $pad_len = 0; + + $found = 0; + for ($i = 0; $i < $blockSize; ++$i) { + # c = tail[-i]; + $c = ParagonIE_Sodium_Core_Util::chrToInt($padded[$tail - $i]); + + # is_barrier = + # (( (acc - 1U) & (pad_len - 1U) & ((c ^ 0x80) - 1U) ) >> 8) & 1U; + $is_barrier = ( + ( + ($acc - 1) & ($pad_len - 1) & (($c ^ 80) - 1) + ) >> 7 + ) & 1; + $is_barrier &= ~$found; + $found |= $is_barrier; + + # acc |= c; + $acc |= $c; + + # pad_len |= i & (1U + ~is_barrier); + $pad_len |= $i & (1 + ~$is_barrier); + + # valid |= (unsigned char) is_barrier; + $valid |= ($is_barrier & 0xff); + } + # unpadded_len = padded_len - 1U - pad_len; + $unpadded_len = $padded_len - 1 - $pad_len; + if ($valid !== 1) { + throw new SodiumException('invalid padding'); + } + return ParagonIE_Sodium_Core_Util::substr($padded, 0, $unpadded_len); + } + /** * Will sodium_compat run fast on the current hardware and PHP configuration? * diff --git a/wp-includes/sodium_compat/src/Core/BLAKE2b.php b/wp-includes/sodium_compat/src/Core/BLAKE2b.php index 5d295ce615e..930a0ede3dc 100644 --- a/wp-includes/sodium_compat/src/Core/BLAKE2b.php +++ b/wp-includes/sodium_compat/src/Core/BLAKE2b.php @@ -88,10 +88,10 @@ protected static function add64($x, $y) { $l = ($x[1] + $y[1]) & 0xffffffff; return self::new64( - $x[0] + $y[0] + ( + (int) ($x[0] + $y[0] + ( ($l < $x[1]) ? 1 : 0 - ), - $l + )), + (int) $l ); } @@ -132,8 +132,8 @@ protected static function xor64(SplFixedArray $x, SplFixedArray $y) throw new SodiumException('y[1] is not an integer'); } return self::new64( - (int) ($x[0] ^ $y[0]), - (int) ($x[1] ^ $y[1]) + (int) (($x[0] ^ $y[0]) & 0xffffffff), + (int) (($x[1] ^ $y[1]) & 0xffffffff) ); } @@ -299,12 +299,13 @@ public static function pseudoConstructor() */ protected static function context() { - $ctx = new SplFixedArray(5); + $ctx = new SplFixedArray(6); $ctx[0] = new SplFixedArray(8); // h $ctx[1] = new SplFixedArray(2); // t $ctx[2] = new SplFixedArray(2); // f $ctx[3] = new SplFixedArray(256); // buf $ctx[4] = 0; // buflen + $ctx[5] = 0; // last_node (uint8_t) for ($i = 8; $i--;) { $ctx[0][$i] = self::$iv[$i]; @@ -550,6 +551,8 @@ public static function finish(SplFixedArray $ctx, SplFixedArray $out) * * @param SplFixedArray|null $key * @param int $outlen + * @param SplFixedArray|null $salt + * @param SplFixedArray|null $personal * @return SplFixedArray * @throws SodiumException * @throws TypeError @@ -559,8 +562,12 @@ public static function finish(SplFixedArray $ctx, SplFixedArray $out) * @psalm-suppress MixedArrayAssignment * @psalm-suppress MixedArrayOffset */ - public static function init($key = null, $outlen = 64) - { + public static function init( + $key = null, + $outlen = 64, + $salt = null, + $personal = null + ) { self::pseudoConstructor(); $klen = 0; @@ -578,6 +585,7 @@ public static function init($key = null, $outlen = 64) $ctx = self::context(); $p = new SplFixedArray(64); + // Zero our param buffer... for ($i = 64; --$i;) { $p[$i] = 0; } @@ -587,10 +595,32 @@ public static function init($key = null, $outlen = 64) $p[2] = 1; // fanout $p[3] = 1; // depth + if ($salt instanceof SplFixedArray) { + // salt: [32] through [47] + for ($i = 0; $i < 16; ++$i) { + $p[32 + $i] = (int) $salt[$i]; + } + } + if ($personal instanceof SplFixedArray) { + // personal: [48] through [63] + for ($i = 0; $i < 16; ++$i) { + $p[48 + $i] = (int) $personal[$i]; + } + } + $ctx[0][0] = self::xor64( $ctx[0][0], self::load64($p, 0) ); + if ($salt instanceof SplFixedArray || $personal instanceof SplFixedArray) { + // We need to do what blake2b_init_param() does: + for ($i = 1; $i < 8; ++$i) { + $ctx[0][$i] = self::xor64( + $ctx[0][$i], + self::load64($p, $i << 3) + ); + } + } if ($klen > 0 && $key instanceof SplFixedArray) { $block = new SplFixedArray(128); @@ -601,6 +631,7 @@ public static function init($key = null, $outlen = 64) $block[$i] = $key[$i]; } self::update($ctx, $block, 128); + $ctx[4] = 128; } return $ctx; @@ -693,7 +724,7 @@ public static function contextToString(SplFixedArray $ctx) self::intToChr(($ctx4 >> 56) & 0xff) )); # uint8_t last_node; - return $str . "\x00"; + return $str . self::intToChr($ctx[5]) . str_repeat("\x00", 23); } /** @@ -746,7 +777,6 @@ public static function stringToContext($string) # uint8_t buf[2 * 128]; $ctx[3] = self::stringToSplFixedArray(self::substr($string, 96, 256)); - # uint8_t buf[2 * 128]; $int = 0; for ($i = 0; $i < 8; ++$i) { diff --git a/wp-includes/sodium_compat/src/Core/Base64/Common.php b/wp-includes/sodium_compat/src/Core/Base64/Common.php new file mode 100644 index 00000000000..94b2e8f33ab --- /dev/null +++ b/wp-includes/sodium_compat/src/Core/Base64/Common.php @@ -0,0 +1,213 @@ + $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 3)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + + $dest .= + self::encode6Bits( $b0 >> 2 ) . + self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + self::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . + self::encode6Bits( $b2 & 63); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= + self::encode6Bits($b0 >> 2) . + self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + self::encode6Bits(($b1 << 2) & 63); + if ($pad) { + $dest .= '='; + } + } else { + $dest .= + self::encode6Bits( $b0 >> 2) . + self::encode6Bits(($b0 << 4) & 63); + if ($pad) { + $dest .= '=='; + } + } + } + return $dest; + } + + /** + * decode from base64 into binary + * + * Base64 character set "./[A-Z][a-z][0-9]" + * + * @param string $src + * @param bool $strictPadding + * @return string + * @throws RangeException + * @throws TypeError + * @psalm-suppress RedundantCondition + */ + public static function decode($src, $strictPadding = false) + { + // Remove padding + $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); + if ($srcLen === 0) { + return ''; + } + + if ($strictPadding) { + if (($srcLen & 3) === 0) { + if ($src[$srcLen - 1] === '=') { + $srcLen--; + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + } + } + if (($srcLen & 3) === 1) { + throw new RangeException( + 'Incorrect padding' + ); + } + if ($src[$srcLen - 1] === '=') { + throw new RangeException( + 'Incorrect padding' + ); + } + } else { + $src = rtrim($src, '='); + $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); + } + + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 4 <= $srcLen; $i += 4) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 4)); + $c0 = self::decode6Bits($chunk[1]); + $c1 = self::decode6Bits($chunk[2]); + $c2 = self::decode6Bits($chunk[3]); + $c3 = self::decode6Bits($chunk[4]); + + $dest .= pack( + 'CCC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff), + ((($c2 << 6) | $c3 ) & 0xff) + ); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); + $c0 = self::decode6Bits($chunk[1]); + + if ($i + 2 < $srcLen) { + $c1 = self::decode6Bits($chunk[2]); + $c2 = self::decode6Bits($chunk[3]); + $dest .= pack( + 'CC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff) + ); + $err |= ($c0 | $c1 | $c2) >> 8; + } elseif ($i + 1 < $srcLen) { + $c1 = self::decode6Bits($chunk[2]); + $dest .= pack( + 'C', + ((($c0 << 2) | ($c1 >> 4)) & 0xff) + ); + $err |= ($c0 | $c1) >> 8; + } elseif ($i < $srcLen && $strictPadding) { + $err |= 1; + } + } + /** @var bool $check */ + $check = ($err === 0); + if (!$check) { + throw new RangeException( + 'Base64::decode() only expects characters in the correct base64 alphabet' + ); + } + return $dest; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 6-bit integers + * into 8-bit integers. + * + * Base64 character set: + * [A-Z] [a-z] [0-9] + / + * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f + * + * @param int $src + * @return int + */ + abstract protected static function decode6Bits($src); + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + abstract protected static function encode6Bits($src); +} diff --git a/wp-includes/sodium_compat/src/Core/Base64/Original.php b/wp-includes/sodium_compat/src/Core/Base64/Original.php new file mode 100644 index 00000000000..dc939eeafb6 --- /dev/null +++ b/wp-includes/sodium_compat/src/Core/Base64/Original.php @@ -0,0 +1,248 @@ + $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 3)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + + $dest .= + self::encode6Bits( $b0 >> 2 ) . + self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + self::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . + self::encode6Bits( $b2 & 63); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= + self::encode6Bits($b0 >> 2) . + self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + self::encode6Bits(($b1 << 2) & 63); + if ($pad) { + $dest .= '='; + } + } else { + $dest .= + self::encode6Bits( $b0 >> 2) . + self::encode6Bits(($b0 << 4) & 63); + if ($pad) { + $dest .= '=='; + } + } + } + return $dest; + } + + /** + * decode from base64 into binary + * + * Base64 character set "./[A-Z][a-z][0-9]" + * + * @param string $src + * @param bool $strictPadding + * @return string + * @throws RangeException + * @throws TypeError + * @psalm-suppress RedundantCondition + */ + public static function decode($src, $strictPadding = false) + { + // Remove padding + $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); + if ($srcLen === 0) { + return ''; + } + + if ($strictPadding) { + if (($srcLen & 3) === 0) { + if ($src[$srcLen - 1] === '=') { + $srcLen--; + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + } + } + if (($srcLen & 3) === 1) { + throw new RangeException( + 'Incorrect padding' + ); + } + if ($src[$srcLen - 1] === '=') { + throw new RangeException( + 'Incorrect padding' + ); + } + } else { + $src = rtrim($src, '='); + $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); + } + + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 4 <= $srcLen; $i += 4) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 4)); + $c0 = self::decode6Bits($chunk[1]); + $c1 = self::decode6Bits($chunk[2]); + $c2 = self::decode6Bits($chunk[3]); + $c3 = self::decode6Bits($chunk[4]); + + $dest .= pack( + 'CCC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff), + ((($c2 << 6) | $c3) & 0xff) + ); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); + $c0 = self::decode6Bits($chunk[1]); + + if ($i + 2 < $srcLen) { + $c1 = self::decode6Bits($chunk[2]); + $c2 = self::decode6Bits($chunk[3]); + $dest .= pack( + 'CC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff) + ); + $err |= ($c0 | $c1 | $c2) >> 8; + } elseif ($i + 1 < $srcLen) { + $c1 = self::decode6Bits($chunk[2]); + $dest .= pack( + 'C', + ((($c0 << 2) | ($c1 >> 4)) & 0xff) + ); + $err |= ($c0 | $c1) >> 8; + } elseif ($i < $srcLen && $strictPadding) { + $err |= 1; + } + } + /** @var bool $check */ + $check = ($err === 0); + if (!$check) { + throw new RangeException( + 'Base64::decode() only expects characters in the correct base64 alphabet' + ); + } + return $dest; + } + // COPY ParagonIE_Sodium_Core_Base64_Common ENDING HERE + + /** + * Uses bitwise operators instead of table-lookups to turn 6-bit integers + * into 8-bit integers. + * + * Base64 character set: + * [A-Z] [a-z] [0-9] + / + * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f + * + * @param int $src + * @return int + */ + protected static function decode6Bits($src) + { + $ret = -1; + + // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); + + // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); + + // if ($src == 0x2b) $ret += 62 + 1; + $ret += (((0x2a - $src) & ($src - 0x2c)) >> 8) & 63; + + // if ($src == 0x2f) ret += 63 + 1; + $ret += (((0x2e - $src) & ($src - 0x30)) >> 8) & 64; + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits($src) + { + $diff = 0x41; + + // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 + $diff += ((25 - $src) >> 8) & 6; + + // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 + $diff -= ((51 - $src) >> 8) & 75; + + // if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15 + $diff -= ((61 - $src) >> 8) & 15; + + // if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3 + $diff += ((62 - $src) >> 8) & 3; + + return pack('C', $src + $diff); + } +} diff --git a/wp-includes/sodium_compat/src/Core/Base64/UrlSafe.php b/wp-includes/sodium_compat/src/Core/Base64/UrlSafe.php new file mode 100644 index 00000000000..64bf53b8583 --- /dev/null +++ b/wp-includes/sodium_compat/src/Core/Base64/UrlSafe.php @@ -0,0 +1,247 @@ + $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 3)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + + $dest .= + self::encode6Bits( $b0 >> 2 ) . + self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + self::encode6Bits((($b1 << 2) | ($b2 >> 6)) & 63) . + self::encode6Bits( $b2 & 63); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= + self::encode6Bits($b0 >> 2) . + self::encode6Bits((($b0 << 4) | ($b1 >> 4)) & 63) . + self::encode6Bits(($b1 << 2) & 63); + if ($pad) { + $dest .= '='; + } + } else { + $dest .= + self::encode6Bits( $b0 >> 2) . + self::encode6Bits(($b0 << 4) & 63); + if ($pad) { + $dest .= '=='; + } + } + } + return $dest; + } + + /** + * decode from base64 into binary + * + * Base64 character set "./[A-Z][a-z][0-9]" + * + * @param string $src + * @param bool $strictPadding + * @return string + * @throws RangeException + * @throws TypeError + * @psalm-suppress RedundantCondition + */ + public static function decode($src, $strictPadding = false) + { + // Remove padding + $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); + if ($srcLen === 0) { + return ''; + } + + if ($strictPadding) { + if (($srcLen & 3) === 0) { + if ($src[$srcLen - 1] === '=') { + $srcLen--; + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } + } + } + if (($srcLen & 3) === 1) { + throw new RangeException( + 'Incorrect padding' + ); + } + if ($src[$srcLen - 1] === '=') { + throw new RangeException( + 'Incorrect padding' + ); + } + } else { + $src = rtrim($src, '='); + $srcLen = ParagonIE_Sodium_Core_Util::strlen($src); + } + + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 4 <= $srcLen; $i += 4) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, 4)); + $c0 = self::decode6Bits($chunk[1]); + $c1 = self::decode6Bits($chunk[2]); + $c2 = self::decode6Bits($chunk[3]); + $c3 = self::decode6Bits($chunk[4]); + + $dest .= pack( + 'CCC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff), + ((($c2 << 6) | $c3) & 0xff) + ); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', ParagonIE_Sodium_Core_Util::substr($src, $i, $srcLen - $i)); + $c0 = self::decode6Bits($chunk[1]); + + if ($i + 2 < $srcLen) { + $c1 = self::decode6Bits($chunk[2]); + $c2 = self::decode6Bits($chunk[3]); + $dest .= pack( + 'CC', + ((($c0 << 2) | ($c1 >> 4)) & 0xff), + ((($c1 << 4) | ($c2 >> 2)) & 0xff) + ); + $err |= ($c0 | $c1 | $c2) >> 8; + } elseif ($i + 1 < $srcLen) { + $c1 = self::decode6Bits($chunk[2]); + $dest .= pack( + 'C', + ((($c0 << 2) | ($c1 >> 4)) & 0xff) + ); + $err |= ($c0 | $c1) >> 8; + } elseif ($i < $srcLen && $strictPadding) { + $err |= 1; + } + } + /** @var bool $check */ + $check = ($err === 0); + if (!$check) { + throw new RangeException( + 'Base64::decode() only expects characters in the correct base64 alphabet' + ); + } + return $dest; + } + // COPY ParagonIE_Sodium_Core_Base64_Common ENDING HERE + /** + * Uses bitwise operators instead of table-lookups to turn 6-bit integers + * into 8-bit integers. + * + * Base64 character set: + * [A-Z] [a-z] [0-9] + / + * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f + * + * @param int $src + * @return int + */ + protected static function decode6Bits($src) + { + $ret = -1; + + // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 + $ret += (((0x40 - $src) & ($src - 0x5b)) >> 8) & ($src - 64); + + // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 + $ret += (((0x60 - $src) & ($src - 0x7b)) >> 8) & ($src - 70); + + // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 + $ret += (((0x2f - $src) & ($src - 0x3a)) >> 8) & ($src + 5); + + // if ($src == 0x2c) $ret += 62 + 1; + $ret += (((0x2c - $src) & ($src - 0x2e)) >> 8) & 63; + + // if ($src == 0x5f) ret += 63 + 1; + $ret += (((0x5e - $src) & ($src - 0x60)) >> 8) & 64; + + return $ret; + } + + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits($src) + { + $diff = 0x41; + + // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 + $diff += ((25 - $src) >> 8) & 6; + + // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 + $diff -= ((51 - $src) >> 8) & 75; + + // if ($src > 61) $diff += 0x2d - 0x30 - 10; // -13 + $diff -= ((61 - $src) >> 8) & 13; + + // if ($src > 62) $diff += 0x5f - 0x2b - 1; // 3 + $diff += ((62 - $src) >> 8) & 49; + + return pack('C', $src + $diff); + } +} diff --git a/wp-includes/sodium_compat/src/Core/Ed25519.php b/wp-includes/sodium_compat/src/Core/Ed25519.php index 68e193b7892..72286441de9 100644 --- a/wp-includes/sodium_compat/src/Core/Ed25519.php +++ b/wp-includes/sodium_compat/src/Core/Ed25519.php @@ -276,7 +276,7 @@ public static function verify_detached($sig, $message, $pk) if (self::strlen($sig) < 64) { throw new SodiumException('Signature is too short'); } - if (self::check_S_lt_L(self::substr($sig, 32, 32))) { + if ((self::chrToInt($sig[63]) & 240) && self::check_S_lt_L(self::substr($sig, 32, 32))) { throw new SodiumException('S < L - Invalid signature'); } if (self::small_order($sig)) { diff --git a/wp-includes/sodium_compat/src/Core/Poly1305/State.php b/wp-includes/sodium_compat/src/Core/Poly1305/State.php index 9954bad3a84..4b64e04078c 100644 --- a/wp-includes/sodium_compat/src/Core/Poly1305/State.php +++ b/wp-includes/sodium_compat/src/Core/Poly1305/State.php @@ -79,6 +79,29 @@ public function __construct($key = '') $this->final = false; } + /** + * Zero internal buffer upon destruction + */ + public function __destruct() + { + $this->r[0] ^= $this->r[0]; + $this->r[1] ^= $this->r[1]; + $this->r[2] ^= $this->r[2]; + $this->r[3] ^= $this->r[3]; + $this->r[4] ^= $this->r[4]; + $this->h[0] ^= $this->h[0]; + $this->h[1] ^= $this->h[1]; + $this->h[2] ^= $this->h[2]; + $this->h[3] ^= $this->h[3]; + $this->h[4] ^= $this->h[4]; + $this->pad[0] ^= $this->pad[0]; + $this->pad[1] ^= $this->pad[1]; + $this->pad[2] ^= $this->pad[2]; + $this->pad[3] ^= $this->pad[3]; + $this->leftover = 0; + $this->final = true; + } + /** * @internal You should not use this directly from another application * @@ -90,6 +113,9 @@ public function __construct($key = '') public function update($message = '') { $bytes = self::strlen($message); + if ($bytes < 1) { + return $this; + } /* handle leftover */ if ($this->leftover) { @@ -111,7 +137,7 @@ public function update($message = '') } $this->blocks( - static::intArrayToString($this->buffer), + self::intArrayToString($this->buffer), ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE ); $this->leftover = 0; @@ -296,7 +322,7 @@ public function finish() $this->final = true; $this->blocks( self::substr( - static::intArrayToString($this->buffer), + self::intArrayToString($this->buffer), 0, ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE ), diff --git a/wp-includes/sodium_compat/src/Core/SecretStream/State.php b/wp-includes/sodium_compat/src/Core/SecretStream/State.php new file mode 100644 index 00000000000..2412f6549e8 --- /dev/null +++ b/wp-includes/sodium_compat/src/Core/SecretStream/State.php @@ -0,0 +1,163 @@ +key = $key; + $this->counter = 1; + if (is_null($nonce)) { + $nonce = str_repeat("\0", 12); + } + $this->nonce = str_pad($nonce, 12, "\0", STR_PAD_RIGHT);; + $this->_pad = str_repeat("\0", 4); + } + + /** + * @return self + */ + public function counterReset() + { + $this->counter = 1; + $this->_pad = str_repeat("\0", 4); + return $this; + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string + */ + public function getCounter() + { + return ParagonIE_Sodium_Core_Util::store32_le($this->counter); + } + + /** + * @return string + */ + public function getNonce() + { + if (!is_string($this->nonce)) { + $this->nonce = str_repeat("\0", 12); + } + if (ParagonIE_Sodium_Core_Util::strlen($this->nonce) !== 12) { + $this->nonce = str_pad($this->nonce, 12, "\0", STR_PAD_RIGHT); + } + return $this->nonce; + } + + /** + * @return string + */ + public function getCombinedNonce() + { + return $this->getCounter() . + ParagonIE_Sodium_Core_Util::substr($this->getNonce(), 0, 8); + } + + /** + * @return self + */ + public function incrementCounter() + { + ++$this->counter; + return $this; + } + + /** + * @return bool + */ + public function needsRekey() + { + return ($this->counter & 0xffff) === 0; + } + + /** + * @param string $newKeyAndNonce + * @return self + */ + public function rekey($newKeyAndNonce) + { + $this->key = ParagonIE_Sodium_Core_Util::substr($newKeyAndNonce, 0, 32); + $this->nonce = str_pad( + ParagonIE_Sodium_Core_Util::substr($newKeyAndNonce, 32), + 12, + "\0", + STR_PAD_RIGHT + ); + return $this; + } + + /** + * @param string $str + * @return self + */ + public function xorNonce($str) + { + $this->nonce = ParagonIE_Sodium_Core_Util::xorStrings( + $this->getNonce(), + str_pad( + ParagonIE_Sodium_Core_Util::substr($str, 0, 8), + 12, + "\0", + STR_PAD_RIGHT + ) + ); + return $this; + } + + /** + * @param string $string + * @return self + */ + public static function fromString($string) + { + $state = new ParagonIE_Sodium_Core_SecretStream_State( + ParagonIE_Sodium_Core_Util::substr($string, 0, 32) + ); + $state->counter = ParagonIE_Sodium_Core_Util::load_4( + ParagonIE_Sodium_Core_Util::substr($string, 32, 4) + ); + $state->nonce = ParagonIE_Sodium_Core_Util::substr($string, 36, 12); + $state->_pad = ParagonIE_Sodium_Core_Util::substr($string, 48, 8); + return $state; + } + + /** + * @return string + */ + public function toString() + { + return $this->key . + $this->getCounter() . + $this->nonce . + $this->_pad; + } +} diff --git a/wp-includes/sodium_compat/src/Core/XChaCha20.php b/wp-includes/sodium_compat/src/Core/XChaCha20.php index a9b203ce577..39e717b79a7 100644 --- a/wp-includes/sodium_compat/src/Core/XChaCha20.php +++ b/wp-includes/sodium_compat/src/Core/XChaCha20.php @@ -36,6 +36,33 @@ public static function stream($len = 64, $nonce = '', $key = '') ); } + /** + * @internal You should not use this directly from another application + * + * @param int $len + * @param string $nonce + * @param string $key + * @return string + * @throws SodiumException + * @throws TypeError + */ + public static function ietfStream($len = 64, $nonce = '', $key = '') + { + if (self::strlen($nonce) !== 24) { + throw new SodiumException('Nonce must be 24 bytes long'); + } + return self::encryptBytes( + new ParagonIE_Sodium_Core_ChaCha20_IetfCtx( + self::hChaCha20( + self::substr($nonce, 0, 16), + $key + ), + "\x00\x00\x00\x00" . self::substr($nonce, 16, 8) + ), + str_repeat("\x00", $len) + ); + } + /** * @internal You should not use this directly from another application * @@ -61,4 +88,30 @@ public static function streamXorIc($message, $nonce = '', $key = '', $ic = '') $message ); } + + /** + * @internal You should not use this directly from another application + * + * @param string $message + * @param string $nonce + * @param string $key + * @param string $ic + * @return string + * @throws SodiumException + * @throws TypeError + */ + public static function ietfStreamXorIc($message, $nonce = '', $key = '', $ic = '') + { + if (self::strlen($nonce) !== 24) { + throw new SodiumException('Nonce must be 24 bytes long'); + } + return self::encryptBytes( + new ParagonIE_Sodium_Core_ChaCha20_IetfCtx( + self::hChaCha20(self::substr($nonce, 0, 16), $key), + "\x00\x00\x00\x00" . self::substr($nonce, 16, 8), + $ic + ), + $message + ); + } } diff --git a/wp-includes/sodium_compat/src/Core32/BLAKE2b.php b/wp-includes/sodium_compat/src/Core32/BLAKE2b.php index f33b7b626e5..5b30ade0602 100644 --- a/wp-includes/sodium_compat/src/Core32/BLAKE2b.php +++ b/wp-includes/sodium_compat/src/Core32/BLAKE2b.php @@ -223,12 +223,13 @@ public static function pseudoConstructor() */ protected static function context() { - $ctx = new SplFixedArray(5); + $ctx = new SplFixedArray(6); $ctx[0] = new SplFixedArray(8); // h $ctx[1] = new SplFixedArray(2); // t $ctx[2] = new SplFixedArray(2); // f $ctx[3] = new SplFixedArray(256); // buf $ctx[4] = 0; // buflen + $ctx[5] = 0; // last_node (uint8_t) for ($i = 8; $i--;) { $ctx[0][$i] = self::$iv[$i]; @@ -482,6 +483,8 @@ public static function finish(SplFixedArray $ctx, SplFixedArray $out) * * @param SplFixedArray|null $key * @param int $outlen + * @param SplFixedArray|null $salt + * @param SplFixedArray|null $personal * @return SplFixedArray * @throws SodiumException * @throws TypeError @@ -491,8 +494,12 @@ public static function finish(SplFixedArray $ctx, SplFixedArray $out) * @psalm-suppress MixedArrayAssignment * @psalm-suppress MixedMethodCall */ - public static function init($key = null, $outlen = 64) - { + public static function init( + $key = null, + $outlen = 64, + $salt = null, + $personal = null + ) { self::pseudoConstructor(); $klen = 0; @@ -510,6 +517,7 @@ public static function init($key = null, $outlen = 64) $ctx = self::context(); $p = new SplFixedArray(64); + // Zero our param buffer... for ($i = 64; --$i;) { $p[$i] = 0; } @@ -519,11 +527,34 @@ public static function init($key = null, $outlen = 64) $p[2] = 1; // fanout $p[3] = 1; // depth + if ($salt instanceof SplFixedArray) { + // salt: [32] through [47] + for ($i = 0; $i < 16; ++$i) { + $p[32 + $i] = (int) $salt[$i]; + } + } + if ($personal instanceof SplFixedArray) { + // personal: [48] through [63] + for ($i = 0; $i < 16; ++$i) { + $p[48 + $i] = (int) $personal[$i]; + } + } + $ctx[0][0] = self::xor64( $ctx[0][0], self::load64($p, 0) ); + if ($salt instanceof SplFixedArray || $personal instanceof SplFixedArray) { + // We need to do what blake2b_init_param() does: + for ($i = 1; $i < 8; ++$i) { + $ctx[0][$i] = self::xor64( + $ctx[0][$i], + self::load64($p, $i << 3) + ); + } + } + if ($klen > 0 && $key instanceof SplFixedArray) { $block = new SplFixedArray(128); for ($i = 128; $i--;) { @@ -533,6 +564,7 @@ public static function init($key = null, $outlen = 64) $block[$i] = $key[$i]; } self::update($ctx, $block, 128); + $ctx[4] = 128; } return $ctx; @@ -595,7 +627,7 @@ public static function contextToString(SplFixedArray $ctx) } /** @var ParagonIE_Sodium_Core32_Int64 $ctxAi */ $ctxAi = $ctxA[$i]; - $str .= $ctxAi->toString(); + $str .= $ctxAi->toReverseString(); } # uint64_t t[2]; @@ -608,8 +640,8 @@ public static function contextToString(SplFixedArray $ctx) /** @var ParagonIE_Sodium_Core32_Int64 $ctxA2 */ $ctxA2 = $ctxA[1]; - $str .= $ctxA1->toString(); - $str .= $ctxA2->toString(); + $str .= $ctxA1->toReverseString(); + $str .= $ctxA2->toReverseString(); } # uint8_t buf[2 * 128]; @@ -624,13 +656,16 @@ public static function contextToString(SplFixedArray $ctx) self::intToChr(($ctx4 >> 8) & 0xff), self::intToChr(($ctx4 >> 16) & 0xff), self::intToChr(($ctx4 >> 24) & 0xff), + "\x00\x00\x00\x00" + /* self::intToChr(($ctx4 >> 32) & 0xff), self::intToChr(($ctx4 >> 40) & 0xff), self::intToChr(($ctx4 >> 48) & 0xff), self::intToChr(($ctx4 >> 56) & 0xff) + */ )); # uint8_t last_node; - return $str . "\x00"; + return $str . self::intToChr($ctx[5]) . str_repeat("\x00", 23); } /** @@ -652,7 +687,7 @@ public static function stringToContext($string) # uint64_t h[8]; for ($i = 0; $i < 8; ++$i) { - $ctx[0][$i] = ParagonIE_Sodium_Core32_Int64::fromString( + $ctx[0][$i] = ParagonIE_Sodium_Core32_Int64::fromReverseString( self::substr($string, (($i << 3) + 0), 8) ); } @@ -660,10 +695,10 @@ public static function stringToContext($string) # uint64_t t[2]; # uint64_t f[2]; for ($i = 1; $i < 3; ++$i) { - $ctx[$i][1] = ParagonIE_Sodium_Core32_Int64::fromString( + $ctx[$i][1] = ParagonIE_Sodium_Core32_Int64::fromReverseString( self::substr($string, 72 + (($i - 1) << 4), 8) ); - $ctx[$i][0] = ParagonIE_Sodium_Core32_Int64::fromString( + $ctx[$i][0] = ParagonIE_Sodium_Core32_Int64::fromReverseString( self::substr($string, 64 + (($i - 1) << 4), 8) ); } @@ -671,7 +706,6 @@ public static function stringToContext($string) # uint8_t buf[2 * 128]; $ctx[3] = self::stringToSplFixedArray(self::substr($string, 96, 256)); - # uint8_t buf[2 * 128]; $int = 0; for ($i = 0; $i < 8; ++$i) { diff --git a/wp-includes/sodium_compat/src/Core32/Ed25519.php b/wp-includes/sodium_compat/src/Core32/Ed25519.php index 9bb31abdf25..c4ecc557578 100644 --- a/wp-includes/sodium_compat/src/Core32/Ed25519.php +++ b/wp-includes/sodium_compat/src/Core32/Ed25519.php @@ -278,7 +278,7 @@ public static function verify_detached($sig, $message, $pk) if (self::strlen($sig) < 64) { throw new SodiumException('Signature is too short'); } - if (self::check_S_lt_L(self::substr($sig, 32, 32))) { + if ((self::chrToInt($sig[63]) & 240) && self::check_S_lt_L(self::substr($sig, 32, 32))) { throw new SodiumException('S < L - Invalid signature'); } if (self::small_order($sig)) { diff --git a/wp-includes/sodium_compat/src/Core32/Poly1305/State.php b/wp-includes/sodium_compat/src/Core32/Poly1305/State.php index e87acf1a3a3..d80e1ff9812 100644 --- a/wp-includes/sodium_compat/src/Core32/Poly1305/State.php +++ b/wp-includes/sodium_compat/src/Core32/Poly1305/State.php @@ -142,7 +142,7 @@ public function update($message = '') } $this->blocks( - static::intArrayToString($this->buffer), + self::intArrayToString($this->buffer), ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE ); $this->leftover = 0; @@ -346,7 +346,7 @@ public function finish() $this->final = true; $this->blocks( self::substr( - static::intArrayToString($this->buffer), + self::intArrayToString($this->buffer), 0, ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE ), diff --git a/wp-includes/sodium_compat/src/Core32/SecretStream/State.php b/wp-includes/sodium_compat/src/Core32/SecretStream/State.php new file mode 100644 index 00000000000..6f20486297b --- /dev/null +++ b/wp-includes/sodium_compat/src/Core32/SecretStream/State.php @@ -0,0 +1,163 @@ +key = $key; + $this->counter = 1; + if (is_null($nonce)) { + $nonce = str_repeat("\0", 12); + } + $this->nonce = str_pad($nonce, 12, "\0", STR_PAD_RIGHT);; + $this->_pad = str_repeat("\0", 4); + } + + /** + * @return self + */ + public function counterReset() + { + $this->counter = 1; + $this->_pad = str_repeat("\0", 4); + return $this; + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string + */ + public function getCounter() + { + return ParagonIE_Sodium_Core32_Util::store32_le($this->counter); + } + + /** + * @return string + */ + public function getNonce() + { + if (!is_string($this->nonce)) { + $this->nonce = str_repeat("\0", 12); + } + if (ParagonIE_Sodium_Core32_Util::strlen($this->nonce) !== 12) { + $this->nonce = str_pad($this->nonce, 12, "\0", STR_PAD_RIGHT); + } + return $this->nonce; + } + + /** + * @return string + */ + public function getCombinedNonce() + { + return $this->getCounter() . + ParagonIE_Sodium_Core32_Util::substr($this->getNonce(), 0, 8); + } + + /** + * @return self + */ + public function incrementCounter() + { + ++$this->counter; + return $this; + } + + /** + * @return bool + */ + public function needsRekey() + { + return ($this->counter & 0xffff) === 0; + } + + /** + * @param string $newKeyAndNonce + * @return self + */ + public function rekey($newKeyAndNonce) + { + $this->key = ParagonIE_Sodium_Core32_Util::substr($newKeyAndNonce, 0, 32); + $this->nonce = str_pad( + ParagonIE_Sodium_Core32_Util::substr($newKeyAndNonce, 32), + 12, + "\0", + STR_PAD_RIGHT + ); + return $this; + } + + /** + * @param string $str + * @return self + */ + public function xorNonce($str) + { + $this->nonce = ParagonIE_Sodium_Core32_Util::xorStrings( + $this->getNonce(), + str_pad( + ParagonIE_Sodium_Core32_Util::substr($str, 0, 8), + 12, + "\0", + STR_PAD_RIGHT + ) + ); + return $this; + } + + /** + * @param string $string + * @return self + */ + public static function fromString($string) + { + $state = new ParagonIE_Sodium_Core32_SecretStream_State( + ParagonIE_Sodium_Core32_Util::substr($string, 0, 32) + ); + $state->counter = ParagonIE_Sodium_Core32_Util::load_4( + ParagonIE_Sodium_Core32_Util::substr($string, 32, 4) + ); + $state->nonce = ParagonIE_Sodium_Core32_Util::substr($string, 36, 12); + $state->_pad = ParagonIE_Sodium_Core32_Util::substr($string, 48, 8); + return $state; + } + + /** + * @return string + */ + public function toString() + { + return $this->key . + $this->getCounter() . + $this->nonce . + $this->_pad; + } +} diff --git a/wp-includes/sodium_compat/src/Core32/X25519.php b/wp-includes/sodium_compat/src/Core32/X25519.php index 3eb96900c52..2cc000d9fa7 100644 --- a/wp-includes/sodium_compat/src/Core32/X25519.php +++ b/wp-includes/sodium_compat/src/Core32/X25519.php @@ -151,8 +151,9 @@ public static function fe_mul121666(ParagonIE_Sodium_Core32_Curve25519_Fe $f) for ($i = 0; $i < 10; ++$i) { $h[$i] = $h[$i]->toInt32(); } - /** @var array $h */ - return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray($h); + /** @var array $h2 */ + $h2 = $h; + return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray($h2); } /** diff --git a/wp-includes/sodium_compat/src/Crypto.php b/wp-includes/sodium_compat/src/Crypto.php index 2e3383ff6f7..7e980f4f7c5 100644 --- a/wp-includes/sodium_compat/src/Crypto.php +++ b/wp-includes/sodium_compat/src/Crypto.php @@ -777,6 +777,53 @@ public static function generichash_init($key = '', $outputLength = 32) return ParagonIE_Sodium_Core_BLAKE2b::contextToString($ctx); } + /** + * Initialize a hashing context for BLAKE2b. + * + * @internal Do not use this directly. Use ParagonIE_Sodium_Compat. + * + * @param string $key + * @param int $outputLength + * @param string $salt + * @param string $personal + * @return string + * @throws RangeException + * @throws SodiumException + * @throws TypeError + */ + public static function generichash_init_salt_personal( + $key = '', + $outputLength = 32, + $salt = '', + $personal = '' + ) { + // This ensures that ParagonIE_Sodium_Core_BLAKE2b::$iv is initialized + ParagonIE_Sodium_Core_BLAKE2b::pseudoConstructor(); + + $k = null; + if (!empty($key)) { + $k = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($key); + if ($k->count() > ParagonIE_Sodium_Core_BLAKE2b::KEYBYTES) { + throw new RangeException('Invalid key size'); + } + } + if (!empty($salt)) { + $s = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($salt); + } else { + $s = null; + } + if (!empty($salt)) { + $p = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($personal); + } else { + $p = null; + } + + /** @var SplFixedArray $ctx */ + $ctx = ParagonIE_Sodium_Core_BLAKE2b::init($k, $outputLength, $s, $p); + + return ParagonIE_Sodium_Core_BLAKE2b::contextToString($ctx); + } + /** * Update a hashing context for BLAKE2b with $message * @@ -1185,6 +1232,362 @@ public static function secretbox_xchacha20poly1305_open($ciphertext, $nonce, $ke return $m; } + /** + * @param string $key + * @return array Returns a state and a header. + * @throws Exception + * @throws SodiumException + */ + public static function secretstream_xchacha20poly1305_init_push($key) + { + # randombytes_buf(out, crypto_secretstream_xchacha20poly1305_HEADERBYTES); + $out = random_bytes(24); + + # crypto_core_hchacha20(state->k, out, k, NULL); + $subkey = ParagonIE_Sodium_Core_HChaCha20::hChaCha20($out, $key); + $state = new ParagonIE_Sodium_Core_SecretStream_State( + $subkey, + ParagonIE_Sodium_Core_Util::substr($out, 16, 8) . str_repeat("\0", 4) + ); + + # _crypto_secretstream_xchacha20poly1305_counter_reset(state); + $state->counterReset(); + + # memcpy(STATE_INONCE(state), out + crypto_core_hchacha20_INPUTBYTES, + # crypto_secretstream_xchacha20poly1305_INONCEBYTES); + # memset(state->_pad, 0, sizeof state->_pad); + return array( + $state->toString(), + $out + ); + } + + /** + * @param string $key + * @param string $header + * @return string Returns a state. + * @throws Exception + */ + public static function secretstream_xchacha20poly1305_init_pull($key, $header) + { + # crypto_core_hchacha20(state->k, in, k, NULL); + $subkey = ParagonIE_Sodium_Core_HChaCha20::hChaCha20( + ParagonIE_Sodium_Core_Util::substr($header, 0, 16), + $key + ); + $state = new ParagonIE_Sodium_Core_SecretStream_State( + $subkey, + ParagonIE_Sodium_Core_Util::substr($header, 16) + ); + $state->counterReset(); + # memcpy(STATE_INONCE(state), in + crypto_core_hchacha20_INPUTBYTES, + # crypto_secretstream_xchacha20poly1305_INONCEBYTES); + # memset(state->_pad, 0, sizeof state->_pad); + # return 0; + return $state->toString(); + } + + /** + * @param string $state + * @param string $msg + * @param string $aad + * @param int $tag + * @return string + * @throws SodiumException + */ + public static function secretstream_xchacha20poly1305_push(&$state, $msg, $aad = '', $tag = 0) + { + $st = ParagonIE_Sodium_Core_SecretStream_State::fromString($state); + # crypto_onetimeauth_poly1305_state poly1305_state; + # unsigned char block[64U]; + # unsigned char slen[8U]; + # unsigned char *c; + # unsigned char *mac; + + $msglen = ParagonIE_Sodium_Core_Util::strlen($msg); + $aadlen = ParagonIE_Sodium_Core_Util::strlen($aad); + + if ((($msglen + 63) >> 6) > 0xfffffffe) { + throw new SodiumException( + 'message cannot be larger than SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX bytes' + ); + } + + # if (outlen_p != NULL) { + # *outlen_p = 0U; + # } + # if (mlen > crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX) { + # sodium_misuse(); + # } + + # crypto_stream_chacha20_ietf(block, sizeof block, state->nonce, state->k); + # crypto_onetimeauth_poly1305_init(&poly1305_state, block); + # sodium_memzero(block, sizeof block); + $auth = new ParagonIE_Sodium_Core_Poly1305_State( + ParagonIE_Sodium_Core_ChaCha20::ietfStream(32, $st->getCombinedNonce(), $st->getKey()) + ); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, ad, adlen); + $auth->update($aad); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, _pad0, + # (0x10 - adlen) & 0xf); + $auth->update(str_repeat("\0", ((0x10 - $aadlen) & 0xf))); + + # memset(block, 0, sizeof block); + # block[0] = tag; + # crypto_stream_chacha20_ietf_xor_ic(block, block, sizeof block, + # state->nonce, 1U, state->k); + $block = ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc( + ParagonIE_Sodium_Core_Util::intToChr($tag) . str_repeat("\0", 63), + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core_Util::store64_le(1) + ); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, block, sizeof block); + $auth->update($block); + + # out[0] = block[0]; + $out = $block[0]; + # c = out + (sizeof tag); + # crypto_stream_chacha20_ietf_xor_ic(c, m, mlen, state->nonce, 2U, state->k); + $cipher = ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc( + $msg, + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core_Util::store64_le(2) + ); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, c, mlen); + $auth->update($cipher); + + $out .= $cipher; + unset($cipher); + + # crypto_onetimeauth_poly1305_update + # (&poly1305_state, _pad0, (0x10 - (sizeof block) + mlen) & 0xf); + $auth->update(str_repeat("\0", ((0x10 - 64 + $msglen) & 0xf))); + + # STORE64_LE(slen, (uint64_t) adlen); + $slen = ParagonIE_Sodium_Core_Util::store64_le($aadlen); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen); + $auth->update($slen); + + # STORE64_LE(slen, (sizeof block) + mlen); + $slen = ParagonIE_Sodium_Core_Util::store64_le(64 + $msglen); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen); + $auth->update($slen); + + # mac = c + mlen; + # crypto_onetimeauth_poly1305_final(&poly1305_state, mac); + $mac = $auth->finish(); + $out .= $mac; + + # sodium_memzero(&poly1305_state, sizeof poly1305_state); + unset($auth); + + + # XOR_BUF(STATE_INONCE(state), mac, + # crypto_secretstream_xchacha20poly1305_INONCEBYTES); + $st->xorNonce($mac); + + # sodium_increment(STATE_COUNTER(state), + # crypto_secretstream_xchacha20poly1305_COUNTERBYTES); + $st->incrementCounter(); + // Overwrite by reference: + $state = $st->toString(); + + /** @var bool $rekey */ + $rekey = ($tag & ParagonIE_Sodium_Compat::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY) !== 0; + # if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) != 0 || + # sodium_is_zero(STATE_COUNTER(state), + # crypto_secretstream_xchacha20poly1305_COUNTERBYTES)) { + # crypto_secretstream_xchacha20poly1305_rekey(state); + # } + if ($rekey || $st->needsRekey()) { + // DO REKEY + self::secretstream_xchacha20poly1305_rekey($state); + } + # if (outlen_p != NULL) { + # *outlen_p = crypto_secretstream_xchacha20poly1305_ABYTES + mlen; + # } + return $out; + } + + /** + * @param string $state + * @param string $cipher + * @param string $aad + * @return bool|array{0: string, 1: int} + * @throws SodiumException + */ + public static function secretstream_xchacha20poly1305_pull(&$state, $cipher, $aad = '') + { + $st = ParagonIE_Sodium_Core_SecretStream_State::fromString($state); + + $cipherlen = ParagonIE_Sodium_Core_Util::strlen($cipher); + # mlen = inlen - crypto_secretstream_xchacha20poly1305_ABYTES; + $msglen = $cipherlen - ParagonIE_Sodium_Compat::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES; + $aadlen = ParagonIE_Sodium_Core_Util::strlen($aad); + + # if (mlen > crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX) { + # sodium_misuse(); + # } + if ((($msglen + 63) >> 6) > 0xfffffffe) { + throw new SodiumException( + 'message cannot be larger than SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX bytes' + ); + } + + # crypto_stream_chacha20_ietf(block, sizeof block, state->nonce, state->k); + # crypto_onetimeauth_poly1305_init(&poly1305_state, block); + # sodium_memzero(block, sizeof block); + $auth = new ParagonIE_Sodium_Core_Poly1305_State( + ParagonIE_Sodium_Core_ChaCha20::ietfStream(32, $st->getCombinedNonce(), $st->getKey()) + ); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, ad, adlen); + $auth->update($aad); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, _pad0, + # (0x10 - adlen) & 0xf); + $auth->update(str_repeat("\0", ((0x10 - $aadlen) & 0xf))); + + + # memset(block, 0, sizeof block); + # block[0] = in[0]; + # crypto_stream_chacha20_ietf_xor_ic(block, block, sizeof block, + # state->nonce, 1U, state->k); + $block = ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc( + $cipher[0] . str_repeat("\0", 63), + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core_Util::store64_le(1) + ); + # tag = block[0]; + # block[0] = in[0]; + # crypto_onetimeauth_poly1305_update(&poly1305_state, block, sizeof block); + $tag = ParagonIE_Sodium_Core_Util::chrToInt($block[0]); + $block[0] = $cipher[0]; + $auth->update($block); + + + # c = in + (sizeof tag); + # crypto_onetimeauth_poly1305_update(&poly1305_state, c, mlen); + $auth->update(ParagonIE_Sodium_Core_Util::substr($cipher, 1, $msglen)); + + # crypto_onetimeauth_poly1305_update + # (&poly1305_state, _pad0, (0x10 - (sizeof block) + mlen) & 0xf); + $auth->update(str_repeat("\0", ((0x10 - 64 + $msglen) & 0xf))); + + # STORE64_LE(slen, (uint64_t) adlen); + # crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen); + $slen = ParagonIE_Sodium_Core_Util::store64_le($aadlen); + $auth->update($slen); + + # STORE64_LE(slen, (sizeof block) + mlen); + # crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen); + $slen = ParagonIE_Sodium_Core_Util::store64_le(64 + $msglen); + $auth->update($slen); + + # crypto_onetimeauth_poly1305_final(&poly1305_state, mac); + # sodium_memzero(&poly1305_state, sizeof poly1305_state); + $mac = $auth->finish(); + + # stored_mac = c + mlen; + # if (sodium_memcmp(mac, stored_mac, sizeof mac) != 0) { + # sodium_memzero(mac, sizeof mac); + # return -1; + # } + + $stored = ParagonIE_Sodium_Core_Util::substr($cipher, $msglen + 1, 16); + if (!ParagonIE_Sodium_Core_Util::hashEquals($mac, $stored)) { + return false; + } + + # crypto_stream_chacha20_ietf_xor_ic(m, c, mlen, state->nonce, 2U, state->k); + $out = ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc( + ParagonIE_Sodium_Core_Util::substr($cipher, 1, $msglen), + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core_Util::store64_le(2) + ); + + # XOR_BUF(STATE_INONCE(state), mac, + # crypto_secretstream_xchacha20poly1305_INONCEBYTES); + $st->xorNonce($mac); + + # sodium_increment(STATE_COUNTER(state), + # crypto_secretstream_xchacha20poly1305_COUNTERBYTES); + $st->incrementCounter(); + + # if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) != 0 || + # sodium_is_zero(STATE_COUNTER(state), + # crypto_secretstream_xchacha20poly1305_COUNTERBYTES)) { + # crypto_secretstream_xchacha20poly1305_rekey(state); + # } + + // Overwrite by reference: + $state = $st->toString(); + + /** @var bool $rekey */ + $rekey = ($tag & ParagonIE_Sodium_Compat::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY) !== 0; + if ($rekey || $st->needsRekey()) { + // DO REKEY + self::secretstream_xchacha20poly1305_rekey($state); + } + return array($out, $tag); + } + + /** + * @param string $state + * @return void + * @throws SodiumException + */ + public static function secretstream_xchacha20poly1305_rekey(&$state) + { + $st = ParagonIE_Sodium_Core_SecretStream_State::fromString($state); + # unsigned char new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + + # crypto_secretstream_xchacha20poly1305_INONCEBYTES]; + # size_t i; + # for (i = 0U; i < crypto_stream_chacha20_ietf_KEYBYTES; i++) { + # new_key_and_inonce[i] = state->k[i]; + # } + $new_key_and_inonce = $st->getKey(); + + # for (i = 0U; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) { + # new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + i] = + # STATE_INONCE(state)[i]; + # } + $new_key_and_inonce .= ParagonIE_Sodium_Core_Util::substR($st->getNonce(), 0, 8); + + # crypto_stream_chacha20_ietf_xor(new_key_and_inonce, new_key_and_inonce, + # sizeof new_key_and_inonce, + # state->nonce, state->k); + + $st->rekey(ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc( + $new_key_and_inonce, + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core_Util::store64_le(0) + )); + + # for (i = 0U; i < crypto_stream_chacha20_ietf_KEYBYTES; i++) { + # state->k[i] = new_key_and_inonce[i]; + # } + # for (i = 0U; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) { + # STATE_INONCE(state)[i] = + # new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + i]; + # } + # _crypto_secretstream_xchacha20poly1305_counter_reset(state); + $st->counterReset(); + + $state = $st->toString(); + } + /** * Detached Ed25519 signature. * diff --git a/wp-includes/sodium_compat/src/Crypto32.php b/wp-includes/sodium_compat/src/Crypto32.php index e76803dd8fa..34c0d7a3949 100644 --- a/wp-includes/sodium_compat/src/Crypto32.php +++ b/wp-includes/sodium_compat/src/Crypto32.php @@ -776,6 +776,53 @@ public static function generichash_init($key = '', $outputLength = 32) return ParagonIE_Sodium_Core32_BLAKE2b::contextToString($ctx); } + /** + * Initialize a hashing context for BLAKE2b. + * + * @internal Do not use this directly. Use ParagonIE_Sodium_Compat. + * + * @param string $key + * @param int $outputLength + * @param string $salt + * @param string $personal + * @return string + * @throws RangeException + * @throws SodiumException + * @throws TypeError + */ + public static function generichash_init_salt_personal( + $key = '', + $outputLength = 32, + $salt = '', + $personal = '' + ) { + // This ensures that ParagonIE_Sodium_Core32_BLAKE2b::$iv is initialized + ParagonIE_Sodium_Core32_BLAKE2b::pseudoConstructor(); + + $k = null; + if (!empty($key)) { + $k = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($key); + if ($k->count() > ParagonIE_Sodium_Core32_BLAKE2b::KEYBYTES) { + throw new RangeException('Invalid key size'); + } + } + if (!empty($salt)) { + $s = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($salt); + } else { + $s = null; + } + if (!empty($salt)) { + $p = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($personal); + } else { + $p = null; + } + + /** @var SplFixedArray $ctx */ + $ctx = ParagonIE_Sodium_Core32_BLAKE2b::init($k, $outputLength, $s, $p); + + return ParagonIE_Sodium_Core32_BLAKE2b::contextToString($ctx); + } + /** * Update a hashing context for BLAKE2b with $message * @@ -1184,6 +1231,362 @@ public static function secretbox_xchacha20poly1305_open($ciphertext, $nonce, $ke return $m; } + /** + * @param string $key + * @return array Returns a state and a header. + * @throws Exception + * @throws SodiumException + */ + public static function secretstream_xchacha20poly1305_init_push($key) + { + # randombytes_buf(out, crypto_secretstream_xchacha20poly1305_HEADERBYTES); + $out = random_bytes(24); + + # crypto_core_hchacha20(state->k, out, k, NULL); + $subkey = ParagonIE_Sodium_Core32_HChaCha20::hChaCha20($out, $key); + $state = new ParagonIE_Sodium_Core32_SecretStream_State( + $subkey, + ParagonIE_Sodium_Core32_Util::substr($out, 16, 8) . str_repeat("\0", 4) + ); + + # _crypto_secretstream_xchacha20poly1305_counter_reset(state); + $state->counterReset(); + + # memcpy(STATE_INONCE(state), out + crypto_core_hchacha20_INPUTBYTES, + # crypto_secretstream_xchacha20poly1305_INONCEBYTES); + # memset(state->_pad, 0, sizeof state->_pad); + return array( + $state->toString(), + $out + ); + } + + /** + * @param string $key + * @param string $header + * @return string Returns a state. + * @throws Exception + */ + public static function secretstream_xchacha20poly1305_init_pull($key, $header) + { + # crypto_core_hchacha20(state->k, in, k, NULL); + $subkey = ParagonIE_Sodium_Core32_HChaCha20::hChaCha20( + ParagonIE_Sodium_Core32_Util::substr($header, 0, 16), + $key + ); + $state = new ParagonIE_Sodium_Core32_SecretStream_State( + $subkey, + ParagonIE_Sodium_Core32_Util::substr($header, 16) + ); + $state->counterReset(); + # memcpy(STATE_INONCE(state), in + crypto_core_hchacha20_INPUTBYTES, + # crypto_secretstream_xchacha20poly1305_INONCEBYTES); + # memset(state->_pad, 0, sizeof state->_pad); + # return 0; + return $state->toString(); + } + + /** + * @param string $state + * @param string $msg + * @param string $aad + * @param int $tag + * @return string + * @throws SodiumException + */ + public static function secretstream_xchacha20poly1305_push(&$state, $msg, $aad = '', $tag = 0) + { + $st = ParagonIE_Sodium_Core32_SecretStream_State::fromString($state); + # crypto_onetimeauth_poly1305_state poly1305_state; + # unsigned char block[64U]; + # unsigned char slen[8U]; + # unsigned char *c; + # unsigned char *mac; + + $msglen = ParagonIE_Sodium_Core32_Util::strlen($msg); + $aadlen = ParagonIE_Sodium_Core32_Util::strlen($aad); + + if ((($msglen + 63) >> 6) > 0xfffffffe) { + throw new SodiumException( + 'message cannot be larger than SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX bytes' + ); + } + + # if (outlen_p != NULL) { + # *outlen_p = 0U; + # } + # if (mlen > crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX) { + # sodium_misuse(); + # } + + # crypto_stream_chacha20_ietf(block, sizeof block, state->nonce, state->k); + # crypto_onetimeauth_poly1305_init(&poly1305_state, block); + # sodium_memzero(block, sizeof block); + $auth = new ParagonIE_Sodium_Core32_Poly1305_State( + ParagonIE_Sodium_Core32_ChaCha20::ietfStream(32, $st->getCombinedNonce(), $st->getKey()) + ); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, ad, adlen); + $auth->update($aad); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, _pad0, + # (0x10 - adlen) & 0xf); + $auth->update(str_repeat("\0", ((0x10 - $aadlen) & 0xf))); + + # memset(block, 0, sizeof block); + # block[0] = tag; + # crypto_stream_chacha20_ietf_xor_ic(block, block, sizeof block, + # state->nonce, 1U, state->k); + $block = ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc( + ParagonIE_Sodium_Core32_Util::intToChr($tag) . str_repeat("\0", 63), + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core32_Util::store64_le(1) + ); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, block, sizeof block); + $auth->update($block); + + # out[0] = block[0]; + $out = $block[0]; + # c = out + (sizeof tag); + # crypto_stream_chacha20_ietf_xor_ic(c, m, mlen, state->nonce, 2U, state->k); + $cipher = ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc( + $msg, + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core32_Util::store64_le(2) + ); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, c, mlen); + $auth->update($cipher); + + $out .= $cipher; + unset($cipher); + + # crypto_onetimeauth_poly1305_update + # (&poly1305_state, _pad0, (0x10 - (sizeof block) + mlen) & 0xf); + $auth->update(str_repeat("\0", ((0x10 - 64 + $msglen) & 0xf))); + + # STORE64_LE(slen, (uint64_t) adlen); + $slen = ParagonIE_Sodium_Core32_Util::store64_le($aadlen); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen); + $auth->update($slen); + + # STORE64_LE(slen, (sizeof block) + mlen); + $slen = ParagonIE_Sodium_Core32_Util::store64_le(64 + $msglen); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen); + $auth->update($slen); + + # mac = c + mlen; + # crypto_onetimeauth_poly1305_final(&poly1305_state, mac); + $mac = $auth->finish(); + $out .= $mac; + + # sodium_memzero(&poly1305_state, sizeof poly1305_state); + unset($auth); + + + # XOR_BUF(STATE_INONCE(state), mac, + # crypto_secretstream_xchacha20poly1305_INONCEBYTES); + $st->xorNonce($mac); + + # sodium_increment(STATE_COUNTER(state), + # crypto_secretstream_xchacha20poly1305_COUNTERBYTES); + $st->incrementCounter(); + // Overwrite by reference: + $state = $st->toString(); + + /** @var bool $rekey */ + $rekey = ($tag & ParagonIE_Sodium_Compat::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY) !== 0; + # if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) != 0 || + # sodium_is_zero(STATE_COUNTER(state), + # crypto_secretstream_xchacha20poly1305_COUNTERBYTES)) { + # crypto_secretstream_xchacha20poly1305_rekey(state); + # } + if ($rekey || $st->needsRekey()) { + // DO REKEY + self::secretstream_xchacha20poly1305_rekey($state); + } + # if (outlen_p != NULL) { + # *outlen_p = crypto_secretstream_xchacha20poly1305_ABYTES + mlen; + # } + return $out; + } + + /** + * @param string $state + * @param string $cipher + * @param string $aad + * @return bool|array{0: string, 1: int} + * @throws SodiumException + */ + public static function secretstream_xchacha20poly1305_pull(&$state, $cipher, $aad = '') + { + $st = ParagonIE_Sodium_Core32_SecretStream_State::fromString($state); + + $cipherlen = ParagonIE_Sodium_Core32_Util::strlen($cipher); + # mlen = inlen - crypto_secretstream_xchacha20poly1305_ABYTES; + $msglen = $cipherlen - ParagonIE_Sodium_Compat::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES; + $aadlen = ParagonIE_Sodium_Core32_Util::strlen($aad); + + # if (mlen > crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX) { + # sodium_misuse(); + # } + if ((($msglen + 63) >> 6) > 0xfffffffe) { + throw new SodiumException( + 'message cannot be larger than SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX bytes' + ); + } + + # crypto_stream_chacha20_ietf(block, sizeof block, state->nonce, state->k); + # crypto_onetimeauth_poly1305_init(&poly1305_state, block); + # sodium_memzero(block, sizeof block); + $auth = new ParagonIE_Sodium_Core32_Poly1305_State( + ParagonIE_Sodium_Core32_ChaCha20::ietfStream(32, $st->getCombinedNonce(), $st->getKey()) + ); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, ad, adlen); + $auth->update($aad); + + # crypto_onetimeauth_poly1305_update(&poly1305_state, _pad0, + # (0x10 - adlen) & 0xf); + $auth->update(str_repeat("\0", ((0x10 - $aadlen) & 0xf))); + + + # memset(block, 0, sizeof block); + # block[0] = in[0]; + # crypto_stream_chacha20_ietf_xor_ic(block, block, sizeof block, + # state->nonce, 1U, state->k); + $block = ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc( + $cipher[0] . str_repeat("\0", 63), + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core32_Util::store64_le(1) + ); + # tag = block[0]; + # block[0] = in[0]; + # crypto_onetimeauth_poly1305_update(&poly1305_state, block, sizeof block); + $tag = ParagonIE_Sodium_Core32_Util::chrToInt($block[0]); + $block[0] = $cipher[0]; + $auth->update($block); + + + # c = in + (sizeof tag); + # crypto_onetimeauth_poly1305_update(&poly1305_state, c, mlen); + $auth->update(ParagonIE_Sodium_Core32_Util::substr($cipher, 1, $msglen)); + + # crypto_onetimeauth_poly1305_update + # (&poly1305_state, _pad0, (0x10 - (sizeof block) + mlen) & 0xf); + $auth->update(str_repeat("\0", ((0x10 - 64 + $msglen) & 0xf))); + + # STORE64_LE(slen, (uint64_t) adlen); + # crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen); + $slen = ParagonIE_Sodium_Core32_Util::store64_le($aadlen); + $auth->update($slen); + + # STORE64_LE(slen, (sizeof block) + mlen); + # crypto_onetimeauth_poly1305_update(&poly1305_state, slen, sizeof slen); + $slen = ParagonIE_Sodium_Core32_Util::store64_le(64 + $msglen); + $auth->update($slen); + + # crypto_onetimeauth_poly1305_final(&poly1305_state, mac); + # sodium_memzero(&poly1305_state, sizeof poly1305_state); + $mac = $auth->finish(); + + # stored_mac = c + mlen; + # if (sodium_memcmp(mac, stored_mac, sizeof mac) != 0) { + # sodium_memzero(mac, sizeof mac); + # return -1; + # } + + $stored = ParagonIE_Sodium_Core32_Util::substr($cipher, $msglen + 1, 16); + if (!ParagonIE_Sodium_Core32_Util::hashEquals($mac, $stored)) { + return false; + } + + # crypto_stream_chacha20_ietf_xor_ic(m, c, mlen, state->nonce, 2U, state->k); + $out = ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc( + ParagonIE_Sodium_Core32_Util::substr($cipher, 1, $msglen), + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core32_Util::store64_le(2) + ); + + # XOR_BUF(STATE_INONCE(state), mac, + # crypto_secretstream_xchacha20poly1305_INONCEBYTES); + $st->xorNonce($mac); + + # sodium_increment(STATE_COUNTER(state), + # crypto_secretstream_xchacha20poly1305_COUNTERBYTES); + $st->incrementCounter(); + + # if ((tag & crypto_secretstream_xchacha20poly1305_TAG_REKEY) != 0 || + # sodium_is_zero(STATE_COUNTER(state), + # crypto_secretstream_xchacha20poly1305_COUNTERBYTES)) { + # crypto_secretstream_xchacha20poly1305_rekey(state); + # } + + // Overwrite by reference: + $state = $st->toString(); + + /** @var bool $rekey */ + $rekey = ($tag & ParagonIE_Sodium_Compat::CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY) !== 0; + if ($rekey || $st->needsRekey()) { + // DO REKEY + self::secretstream_xchacha20poly1305_rekey($state); + } + return array($out, $tag); + } + + /** + * @param string $state + * @return void + * @throws SodiumException + */ + public static function secretstream_xchacha20poly1305_rekey(&$state) + { + $st = ParagonIE_Sodium_Core32_SecretStream_State::fromString($state); + # unsigned char new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + + # crypto_secretstream_xchacha20poly1305_INONCEBYTES]; + # size_t i; + # for (i = 0U; i < crypto_stream_chacha20_ietf_KEYBYTES; i++) { + # new_key_and_inonce[i] = state->k[i]; + # } + $new_key_and_inonce = $st->getKey(); + + # for (i = 0U; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) { + # new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + i] = + # STATE_INONCE(state)[i]; + # } + $new_key_and_inonce .= ParagonIE_Sodium_Core32_Util::substR($st->getNonce(), 0, 8); + + # crypto_stream_chacha20_ietf_xor(new_key_and_inonce, new_key_and_inonce, + # sizeof new_key_and_inonce, + # state->nonce, state->k); + + $st->rekey(ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc( + $new_key_and_inonce, + $st->getCombinedNonce(), + $st->getKey(), + ParagonIE_Sodium_Core32_Util::store64_le(0) + )); + + # for (i = 0U; i < crypto_stream_chacha20_ietf_KEYBYTES; i++) { + # state->k[i] = new_key_and_inonce[i]; + # } + # for (i = 0U; i < crypto_secretstream_xchacha20poly1305_INONCEBYTES; i++) { + # STATE_INONCE(state)[i] = + # new_key_and_inonce[crypto_stream_chacha20_ietf_KEYBYTES + i]; + # } + # _crypto_secretstream_xchacha20poly1305_counter_reset(state); + $st->counterReset(); + + $state = $st->toString(); + } + /** * Detached Ed25519 signature. * diff --git a/wp-includes/sodium_compat/src/File.php b/wp-includes/sodium_compat/src/File.php index da6366b6753..a28df9ffa96 100644 --- a/wp-includes/sodium_compat/src/File.php +++ b/wp-includes/sodium_compat/src/File.php @@ -679,7 +679,11 @@ public static function verify($sig, $filePath, $publicKey) } /* Security checks */ - if (ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) { + if ( + (ParagonIE_Sodium_Core_Ed25519::chrToInt($sig[63]) & 240) + && + ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32)) + ) { throw new SodiumException('S < L - Invalid signature'); } if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) { @@ -841,7 +845,7 @@ protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key) if (!is_string($plaintext)) { throw new SodiumException('Could not read input file'); } - $first32 = ftell($ifp); + $first32 = self::ftell($ifp); /** @var string $subkey */ $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key); @@ -875,7 +879,7 @@ protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key) ); // Pre-write 16 blank bytes for the Poly1305 tag - $start = ftell($ofp); + $start = self::ftell($ofp); fwrite($ofp, str_repeat("\x00", 16)); /** @var string $c */ @@ -926,7 +930,7 @@ protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key) $block0 = null; $subkey = null; } - $end = ftell($ofp); + $end = self::ftell($ofp); /* * Write the Poly1305 authentication tag that provides integrity @@ -1043,7 +1047,7 @@ protected static function onetimeauth_verify( $mlen = 0 ) { /** @var int $pos */ - $pos = ftell($ifp); + $pos = self::ftell($ifp); /** @var int $iter */ $iter = 1; @@ -1106,7 +1110,7 @@ public static function updateHashWithFile($hash, $fp, $size = 0) } /** @var int $originalPosition */ - $originalPosition = ftell($fp); + $originalPosition = self::ftell($fp); // Move file pointer to beginning of file fseek($fp, 0, SEEK_SET); @@ -1314,7 +1318,7 @@ protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $k if (!is_string($plaintext)) { throw new SodiumException('Could not read input file'); } - $first32 = ftell($ifp); + $first32 = self::ftell($ifp); /** @var string $subkey */ $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key); @@ -1348,7 +1352,7 @@ protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $k ); // Pre-write 16 blank bytes for the Poly1305 tag - $start = ftell($ofp); + $start = self::ftell($ofp); fwrite($ofp, str_repeat("\x00", 16)); /** @var string $c */ @@ -1399,7 +1403,7 @@ protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $k $block0 = null; $subkey = null; } - $end = ftell($ofp); + $end = self::ftell($ofp); /* * Write the Poly1305 authentication tag that provides integrity @@ -1515,7 +1519,7 @@ protected static function onetimeauth_verify_core32( $mlen = 0 ) { /** @var int $pos */ - $pos = ftell($ifp); + $pos = self::ftell($ifp); /** @var int $iter */ $iter = 1; @@ -1540,4 +1544,18 @@ protected static function onetimeauth_verify_core32( fseek($ifp, $pos, SEEK_SET); return $res; } + + /** + * @param resource $resource + * @return int + * @throws SodiumException + */ + private static function ftell($resource) + { + $return = ftell($resource); + if (!is_int($return)) { + throw new SodiumException('ftell() returned false'); + } + return (int) $return; + } } diff --git a/wp-includes/sodium_compat/src/PHP52/SplFixedArray.php b/wp-includes/sodium_compat/src/PHP52/SplFixedArray.php new file mode 100644 index 00000000000..9279f60c1e5 --- /dev/null +++ b/wp-includes/sodium_compat/src/PHP52/SplFixedArray.php @@ -0,0 +1,187 @@ + */ + private $internalArray = array(); + + /** @var int $size */ + private $size = 0; + + /** + * SplFixedArray constructor. + * @param int $size + */ + public function __construct($size = 0) + { + $this->size = $size; + $this->internalArray = array(); + } + + /** + * @return int + */ + public function count() + { + return count($this->internalArray); + } + + /** + * @return array + */ + public function toArray() + { + ksort($this->internalArray); + return (array) $this->internalArray; + } + + /** + * @param array $array + * @param bool $save_indexes + * @return SplFixedArray + * @psalm-suppress MixedAssignment + */ + public static function fromArray(array $array, $save_indexes = true) + { + $self = new SplFixedArray(count($array)); + if($save_indexes) { + foreach($array as $key => $value) { + $self[(int) $key] = $value; + } + } else { + $i = 0; + foreach (array_values($array) as $value) { + $self[$i] = $value; + $i++; + } + } + return $self; + } + + /** + * @return int + */ + public function getSize() + { + return $this->size; + } + + /** + * @param int $size + * @return bool + */ + public function setSize($size) + { + $this->size = $size; + return true; + } + + /** + * @param string|int $index + * @return bool + */ + public function offsetExists($index) + { + return array_key_exists((int) $index, $this->internalArray); + } + + /** + * @param string|int $index + * @return mixed + */ + public function offsetGet($index) + { + return $this->internalArray[(int) $index]; + } + + /** + * @param string|int $index + * @param mixed $newval + * @psalm-suppress MixedAssignment + */ + public function offsetSet($index, $newval) + { + $this->internalArray[(int) $index] = $newval; + } + + /** + * @param string|int $index + */ + public function offsetUnset($index) + { + unset($this->internalArray[(int) $index]); + } + + /** + * Rewind iterator back to the start + * @link https://php.net/manual/en/splfixedarray.rewind.php + * @return void + * @since 5.3.0 + */ + public function rewind() + { + reset($this->internalArray); + } + + /** + * Return current array entry + * @link https://php.net/manual/en/splfixedarray.current.php + * @return mixed The current element value. + * @since 5.3.0 + */ + public function current() + { + return current($this->internalArray); + } + + /** + * Return current array index + * @return int The current array index. + */ + public function key() + { + return key($this->internalArray); + } + + /** + * @return void + */ + public function next() + { + next($this->internalArray); + } + + /** + * Check whether the array contains more elements + * @link https://php.net/manual/en/splfixedarray.valid.php + * @return bool true if the array contains any more elements, false otherwise. + */ + public function valid() + { + if (empty($this->internalArray)) { + return false; + } + $result = next($this->internalArray) !== false; + prev($this->internalArray); + return $result; + } + + /** + * Do nothing. + */ + public function __wakeup() + { + // NOP + } +} \ No newline at end of file diff --git a/wp-includes/version.php b/wp-includes/version.php index 35f6ebf4444..064ec79cdb4 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -13,7 +13,7 @@ * * @global string $wp_version */ -$wp_version = '5.3.1-alpha-46857'; +$wp_version = '5.3.1-alpha-46859'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.