diff -r 3d4e9c994f10 -r a86126ab1dd4 wp/wp-includes/sodium_compat/src/Compat.php --- a/wp/wp-includes/sodium_compat/src/Compat.php Tue Oct 22 16:11:46 2019 +0200 +++ b/wp/wp-includes/sodium_compat/src/Compat.php Tue Dec 15 13:49:49 2020 +0100 @@ -49,6 +49,10 @@ const VERSION_STRING = 'polyfill-1.0.8'; // From libsodium + const BASE64_VARIANT_ORIGINAL = 1; + const BASE64_VARIANT_ORIGINAL_NO_PADDING = 3; + const BASE64_VARIANT_URLSAFE = 5; + const BASE64_VARIANT_URLSAFE_NO_PADDING = 7; const CRYPTO_AEAD_AES256GCM_KEYBYTES = 32; const CRYPTO_AEAD_AES256GCM_NSECBYTES = 0; const CRYPTO_AEAD_AES256GCM_NPUBBYTES = 12; @@ -74,10 +78,17 @@ const CRYPTO_BOX_MACBYTES = 16; const CRYPTO_BOX_NONCEBYTES = 24; const CRYPTO_BOX_SEEDBYTES = 32; + const CRYPTO_KDF_BYTES_MIN = 16; + const CRYPTO_KDF_BYTES_MAX = 64; + const CRYPTO_KDF_CONTEXTBYTES = 8; + const CRYPTO_KDF_KEYBYTES = 32; const CRYPTO_KX_BYTES = 32; + const CRYPTO_KX_PRIMITIVE = 'x25519blake2b'; const CRYPTO_KX_SEEDBYTES = 32; + const CRYPTO_KX_KEYPAIRBYTES = 64; const CRYPTO_KX_PUBLICKEYBYTES = 32; const CRYPTO_KX_SECRETKEYBYTES = 32; + const CRYPTO_KX_SESSIONKEYBYTES = 32; const CRYPTO_GENERICHASH_BYTES = 32; const CRYPTO_GENERICHASH_BYTES_MIN = 16; const CRYPTO_GENERICHASH_BYTES_MAX = 64; @@ -85,7 +96,7 @@ const CRYPTO_GENERICHASH_KEYBYTES_MIN = 16; const CRYPTO_GENERICHASH_KEYBYTES_MAX = 64; const CRYPTO_PWHASH_SALTBYTES = 16; - const CRYPTO_PWHASH_STRPREFIX = '$argon2i$'; + const CRYPTO_PWHASH_STRPREFIX = '$argon2id$'; const CRYPTO_PWHASH_ALG_ARGON2I13 = 1; const CRYPTO_PWHASH_ALG_ARGON2ID13 = 2; const CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE = 33554432; @@ -107,6 +118,14 @@ const CRYPTO_SECRETBOX_KEYBYTES = 32; const CRYPTO_SECRETBOX_MACBYTES = 16; const CRYPTO_SECRETBOX_NONCEBYTES = 24; + const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES = 17; + const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES = 24; + const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES = 32; + const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PUSH = 0; + const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_PULL = 1; + const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_REKEY = 2; + const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL = 3; + const CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_MESSAGEBYTES_MAX = 0x3fffffff80; const CRYPTO_SIGN_BYTES = 64; const CRYPTO_SIGN_SEEDBYTES = 32; const CRYPTO_SIGN_PUBLICKEYBYTES = 32; @@ -116,6 +135,110 @@ const CRYPTO_STREAM_NONCEBYTES = 24; /** + * Add two numbers (little-endian unsigned), storing the value in the first + * parameter. + * + * This mutates $val. + * + * @param string $val + * @param string $addv + * @return void + * @throws SodiumException + */ + public static function add(&$val, $addv) + { + $val_len = ParagonIE_Sodium_Core_Util::strlen($val); + $addv_len = ParagonIE_Sodium_Core_Util::strlen($addv); + if ($val_len !== $addv_len) { + throw new SodiumException('values must have the same length'); + } + $A = ParagonIE_Sodium_Core_Util::stringToIntArray($val); + $B = ParagonIE_Sodium_Core_Util::stringToIntArray($addv); + + $c = 0; + for ($i = 0; $i < $val_len; $i++) { + $c += ($A[$i] + $B[$i]); + $A[$i] = ($c & 0xff); + $c >>= 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(). * * @param string $string A string (probably raw binary) @@ -1310,6 +1433,7 @@ * @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 @@ $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 { @@ -1380,6 +1512,53 @@ } /** + * 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. * * @param string $ctx BLAKE2 hashing context. Generated by crypto_generichash_init(). @@ -1425,6 +1604,65 @@ } /** + * @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. * * Typically, you would designate one machine to be the client and the @@ -1511,6 +1749,149 @@ } /** + * @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 * @param string $salt @@ -1593,6 +1974,36 @@ } /** + * 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 * @return bool @@ -1988,6 +2399,111 @@ } /** + * @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. * * @param string $message Input message @@ -2137,6 +2653,32 @@ } /** + * @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. * * @param string $seed Input seed @@ -2624,6 +3166,9 @@ 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); } @@ -2669,6 +3214,158 @@ } /** + * @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? * * @return bool