diff options
Diffstat (limited to 'vendor/paragonie/sodium_compat/src/Core/Util.php')
-rw-r--r-- | vendor/paragonie/sodium_compat/src/Core/Util.php | 1889 |
1 files changed, 947 insertions, 942 deletions
diff --git a/vendor/paragonie/sodium_compat/src/Core/Util.php b/vendor/paragonie/sodium_compat/src/Core/Util.php index 865f544..73e463f 100644 --- a/vendor/paragonie/sodium_compat/src/Core/Util.php +++ b/vendor/paragonie/sodium_compat/src/Core/Util.php @@ -1,942 +1,947 @@ -<?php
-
-if (class_exists('ParagonIE_Sodium_Core_Util', false)) {
- return;
-}
-
-/**
- * Class ParagonIE_Sodium_Core_Util
- */
-abstract class ParagonIE_Sodium_Core_Util
-{
- /**
- * @param int $integer
- * @param int $size (16, 32, 64)
- * @return int
- */
- public static function abs($integer, $size = 0)
- {
- /** @var int $realSize */
- $realSize = (PHP_INT_SIZE << 3) - 1;
- if ($size) {
- --$size;
- } else {
- /** @var int $size */
- $size = $realSize;
- }
-
- $negative = -(($integer >> $size) & 1);
- return (int) (
- ($integer ^ $negative)
- +
- (($negative >> $realSize) & 1)
- );
- }
-
- /**
- * Convert a binary string into a hexadecimal string without cache-timing
- * leaks
- *
- * @internal You should not use this directly from another application
- *
- * @param string $binaryString (raw binary)
- * @return string
- * @throws TypeError
- */
- public static function bin2hex($binaryString)
- {
- /* Type checks: */
- if (!is_string($binaryString)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($binaryString) . ' given.');
- }
-
- $hex = '';
- $len = self::strlen($binaryString);
- for ($i = 0; $i < $len; ++$i) {
- /** @var array<int, int> $chunk */
- $chunk = unpack('C', $binaryString[$i]);
- /** @var int $c */
- $c = $chunk[1] & 0xf;
- /** @var int $b */
- $b = $chunk[1] >> 4;
- $hex .= pack(
- 'CC',
- (87 + $b + ((($b - 10) >> 8) & ~38)),
- (87 + $c + ((($c - 10) >> 8) & ~38))
- );
- }
- return $hex;
- }
-
- /**
- * Convert a binary string into a hexadecimal string without cache-timing
- * leaks, returning uppercase letters (as per RFC 4648)
- *
- * @internal You should not use this directly from another application
- *
- * @param string $bin_string (raw binary)
- * @return string
- * @throws TypeError
- */
- public static function bin2hexUpper($bin_string)
- {
- $hex = '';
- $len = self::strlen($bin_string);
- for ($i = 0; $i < $len; ++$i) {
- /** @var array<int, int> $chunk */
- $chunk = unpack('C', $bin_string[$i]);
- /**
- * Lower 16 bits
- *
- * @var int $c
- */
- $c = $chunk[1] & 0xf;
-
- /**
- * Upper 16 bits
- * @var int $b
- */
- $b = $chunk[1] >> 4;
-
- /**
- * Use pack() and binary operators to turn the two integers
- * into hexadecimal characters. We don't use chr() here, because
- * it uses a lookup table internally and we want to avoid
- * cache-timing side-channels.
- */
- $hex .= pack(
- 'CC',
- (55 + $b + ((($b - 10) >> 8) & ~6)),
- (55 + $c + ((($c - 10) >> 8) & ~6))
- );
- }
- return $hex;
- }
-
- /**
- * Cache-timing-safe variant of ord()
- *
- * @internal You should not use this directly from another application
- *
- * @param string $chr
- * @return int
- * @throws SodiumException
- * @throws TypeError
- */
- public static function chrToInt($chr)
- {
- /* Type checks: */
- if (!is_string($chr)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($chr) . ' given.');
- }
- if (self::strlen($chr) !== 1) {
- throw new SodiumException('chrToInt() expects a string that is exactly 1 character long');
- }
- /** @var array<int, int> $chunk */
- $chunk = unpack('C', $chr);
- return (int) ($chunk[1]);
- }
-
- /**
- * Compares two strings.
- *
- * @internal You should not use this directly from another application
- *
- * @param string $left
- * @param string $right
- * @param int $len
- * @return int
- * @throws SodiumException
- * @throws TypeError
- */
- public static function compare($left, $right, $len = null)
- {
- $leftLen = self::strlen($left);
- $rightLen = self::strlen($right);
- if ($len === null) {
- $len = max($leftLen, $rightLen);
- $left = str_pad($left, $len, "\x00", STR_PAD_RIGHT);
- $right = str_pad($right, $len, "\x00", STR_PAD_RIGHT);
- }
-
- $gt = 0;
- $eq = 1;
- $i = $len;
- while ($i !== 0) {
- --$i;
- $gt |= ((self::chrToInt($right[$i]) - self::chrToInt($left[$i])) >> 8) & $eq;
- $eq &= ((self::chrToInt($right[$i]) ^ self::chrToInt($left[$i])) - 1) >> 8;
- }
- return ($gt + $gt + $eq) - 1;
- }
-
- /**
- * If a variable does not match a given type, throw a TypeError.
- *
- * @param mixed $mixedVar
- * @param string $type
- * @param int $argumentIndex
- * @throws TypeError
- * @throws SodiumException
- * @return void
- */
- public static function declareScalarType(&$mixedVar = null, $type = 'void', $argumentIndex = 0)
- {
- if (func_num_args() === 0) {
- /* Tautology, by default */
- return;
- }
- if (func_num_args() === 1) {
- throw new TypeError('Declared void, but passed a variable');
- }
- $realType = strtolower(gettype($mixedVar));
- $type = strtolower($type);
- switch ($type) {
- case 'null':
- if ($mixedVar !== null) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be null, ' . $realType . ' given.');
- }
- break;
- case 'integer':
- case 'int':
- $allow = array('int', 'integer');
- if (!in_array($type, $allow)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be an integer, ' . $realType . ' given.');
- }
- $mixedVar = (int) $mixedVar;
- break;
- case 'boolean':
- case 'bool':
- $allow = array('bool', 'boolean');
- if (!in_array($type, $allow)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be a boolean, ' . $realType . ' given.');
- }
- $mixedVar = (bool) $mixedVar;
- break;
- case 'string':
- if (!is_string($mixedVar)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be a string, ' . $realType . ' given.');
- }
- $mixedVar = (string) $mixedVar;
- break;
- case 'decimal':
- case 'double':
- case 'float':
- $allow = array('decimal', 'double', 'float');
- if (!in_array($type, $allow)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be a float, ' . $realType . ' given.');
- }
- $mixedVar = (float) $mixedVar;
- break;
- case 'object':
- if (!is_object($mixedVar)) {
- throw new TypeError('Argument ' . $argumentIndex . ' must be an object, ' . $realType . ' given.');
- }
- break;
- case 'array':
- if (!is_array($mixedVar)) {
- if (is_object($mixedVar)) {
- if ($mixedVar instanceof ArrayAccess) {
- return;
- }
- }
- throw new TypeError('Argument ' . $argumentIndex . ' must be an array, ' . $realType . ' given.');
- }
- break;
- default:
- throw new SodiumException('Unknown type (' . $realType .') does not match expect type (' . $type . ')');
- }
- }
-
- /**
- * Evaluate whether or not two strings are equal (in constant-time)
- *
- * @param string $left
- * @param string $right
- * @return bool
- * @throws SodiumException
- * @throws TypeError
- */
- public static function hashEquals($left, $right)
- {
- /* Type checks: */
- if (!is_string($left)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($left) . ' given.');
- }
- if (!is_string($right)) {
- throw new TypeError('Argument 2 must be a string, ' . gettype($right) . ' given.');
- }
-
- if (is_callable('hash_equals')) {
- return hash_equals($left, $right);
- }
- $d = 0;
- /** @var int $len */
- $len = self::strlen($left);
- if ($len !== self::strlen($right)) {
- return false;
- }
- for ($i = 0; $i < $len; ++$i) {
- $d |= self::chrToInt($left[$i]) ^ self::chrToInt($right[$i]);
- }
-
- if ($d !== 0) {
- return false;
- }
- return $left === $right;
- }
-
- /**
- * Catch hash_update() failures and throw instead of silently proceding
- *
- * @param HashContext|resource &$hs
- * @param string $data
- * @return void
- * @throws SodiumException
- * @psalm-suppress PossiblyInvalidArgument
- */
- protected static function hash_update(&$hs, $data)
- {
- if (!hash_update($hs, $data)) {
- throw new SodiumException('hash_update() failed');
- }
- }
-
- /**
- * Convert a hexadecimal string into a binary string without cache-timing
- * leaks
- *
- * @internal You should not use this directly from another application
- *
- * @param string $hexString
- * @param bool $strictPadding
- * @return string (raw binary)
- * @throws RangeException
- * @throws TypeError
- */
- public static function hex2bin($hexString, $strictPadding = false)
- {
- /* Type checks: */
- if (!is_string($hexString)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($hexString) . ' given.');
- }
-
- /** @var int $hex_pos */
- $hex_pos = 0;
- /** @var string $bin */
- $bin = '';
- /** @var int $c_acc */
- $c_acc = 0;
- /** @var int $hex_len */
- $hex_len = self::strlen($hexString);
- /** @var int $state */
- $state = 0;
- if (($hex_len & 1) !== 0) {
- if ($strictPadding) {
- throw new RangeException(
- 'Expected an even number of hexadecimal characters'
- );
- } else {
- $hexString = '0' . $hexString;
- ++$hex_len;
- }
- }
-
- $chunk = unpack('C*', $hexString);
- while ($hex_pos < $hex_len) {
- ++$hex_pos;
- /** @var int $c */
- $c = $chunk[$hex_pos];
- /** @var int $c_num */
- $c_num = $c ^ 48;
- /** @var int $c_num0 */
- $c_num0 = ($c_num - 10) >> 8;
- /** @var int $c_alpha */
- $c_alpha = ($c & ~32) - 55;
- /** @var int $c_alpha0 */
- $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
- if (($c_num0 | $c_alpha0) === 0) {
- throw new RangeException(
- 'hex2bin() only expects hexadecimal characters'
- );
- }
- /** @var int $c_val */
- $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
- if ($state === 0) {
- $c_acc = $c_val * 16;
- } else {
- $bin .= pack('C', $c_acc | $c_val);
- }
- $state ^= 1;
- }
- return $bin;
- }
-
- /**
- * Turn an array of integers into a string
- *
- * @internal You should not use this directly from another application
- *
- * @param array<int, int> $ints
- * @return string
- */
- public static function intArrayToString(array $ints)
- {
- /** @var array<int, int> $args */
- $args = $ints;
- foreach ($args as $i => $v) {
- $args[$i] = (int) ($v & 0xff);
- }
- array_unshift($args, str_repeat('C', count($ints)));
- return (string) (call_user_func_array('pack', $args));
- }
-
- /**
- * Cache-timing-safe variant of ord()
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function intToChr($int)
- {
- return pack('C', $int);
- }
-
- /**
- * Load a 3 character substring into an integer
- *
- * @internal You should not use this directly from another application
- *
- * @param string $string
- * @return int
- * @throws RangeException
- * @throws TypeError
- */
- public static function load_3($string)
- {
- /* Type checks: */
- if (!is_string($string)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
- }
-
- /* Input validation: */
- if (self::strlen($string) < 3) {
- throw new RangeException(
- 'String must be 3 bytes or more; ' . self::strlen($string) . ' given.'
- );
- }
- /** @var array<int, int> $unpacked */
- $unpacked = unpack('V', $string . "\0");
- return (int) ($unpacked[1] & 0xffffff);
- }
-
- /**
- * Load a 4 character substring into an integer
- *
- * @internal You should not use this directly from another application
- *
- * @param string $string
- * @return int
- * @throws RangeException
- * @throws TypeError
- */
- public static function load_4($string)
- {
- /* Type checks: */
- if (!is_string($string)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
- }
-
- /* Input validation: */
- if (self::strlen($string) < 4) {
- throw new RangeException(
- 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
- );
- }
- /** @var array<int, int> $unpacked */
- $unpacked = unpack('V', $string);
- return (int) ($unpacked[1] & 0xffffffff);
- }
-
- /**
- * Load a 8 character substring into an integer
- *
- * @internal You should not use this directly from another application
- *
- * @param string $string
- * @return int
- * @throws RangeException
- * @throws SodiumException
- * @throws TypeError
- */
- public static function load64_le($string)
- {
- /* Type checks: */
- if (!is_string($string)) {
- throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
- }
-
- /* Input validation: */
- if (self::strlen($string) < 4) {
- throw new RangeException(
- 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
- );
- }
- if (PHP_VERSION_ID >= 50603 && PHP_INT_SIZE === 8) {
- /** @var array<int, int> $unpacked */
- $unpacked = unpack('P', $string);
- return (int) $unpacked[1];
- }
-
- /** @var int $result */
- $result = (self::chrToInt($string[0]) & 0xff);
- $result |= (self::chrToInt($string[1]) & 0xff) << 8;
- $result |= (self::chrToInt($string[2]) & 0xff) << 16;
- $result |= (self::chrToInt($string[3]) & 0xff) << 24;
- $result |= (self::chrToInt($string[4]) & 0xff) << 32;
- $result |= (self::chrToInt($string[5]) & 0xff) << 40;
- $result |= (self::chrToInt($string[6]) & 0xff) << 48;
- $result |= (self::chrToInt($string[7]) & 0xff) << 56;
- return (int) $result;
- }
-
- /**
- * @internal You should not use this directly from another application
- *
- * @param string $left
- * @param string $right
- * @return int
- * @throws SodiumException
- * @throws TypeError
- */
- public static function memcmp($left, $right)
- {
- if (self::hashEquals($left, $right)) {
- return 0;
- }
- return -1;
- }
-
- /**
- * Multiply two integers in constant-time
- *
- * Micro-architecture timing side-channels caused by how your CPU
- * implements multiplication are best prevented by never using the
- * multiplication operators and ensuring that our code always takes
- * the same number of operations to complete, regardless of the values
- * of $a and $b.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $a
- * @param int $b
- * @param int $size Limits the number of operations (useful for small,
- * constant operands)
- * @return int
- */
- public static function mul($a, $b, $size = 0)
- {
- if (ParagonIE_Sodium_Compat::$fastMult) {
- return (int) ($a * $b);
- }
-
- static $defaultSize = null;
- /** @var int $defaultSize */
- if (!$defaultSize) {
- /** @var int $defaultSize */
- $defaultSize = (PHP_INT_SIZE << 3) - 1;
- }
- if ($size < 1) {
- /** @var int $size */
- $size = $defaultSize;
- }
- /** @var int $size */
-
- $c = 0;
-
- /**
- * Mask is either -1 or 0.
- *
- * -1 in binary looks like 0x1111 ... 1111
- * 0 in binary looks like 0x0000 ... 0000
- *
- * @var int
- */
- $mask = -(($b >> ((int) $defaultSize)) & 1);
-
- /**
- * Ensure $b is a positive integer, without creating
- * a branching side-channel
- *
- * @var int $b
- */
- $b = ($b & ~$mask) | ($mask & -$b);
-
- /**
- * Unless $size is provided:
- *
- * This loop always runs 32 times when PHP_INT_SIZE is 4.
- * This loop always runs 64 times when PHP_INT_SIZE is 8.
- */
- for ($i = $size; $i >= 0; --$i) {
- $c += (int) ($a & -($b & 1));
- $a <<= 1;
- $b >>= 1;
- }
-
- /**
- * If $b was negative, we then apply the same value to $c here.
- * It doesn't matter much if $a was negative; the $c += above would
- * have produced a negative integer to begin with. But a negative $b
- * makes $b >>= 1 never return 0, so we would end up with incorrect
- * results.
- *
- * The end result is what we'd expect from integer multiplication.
- */
- return (int) (($c & ~$mask) | ($mask & -$c));
- }
-
- /**
- * Convert any arbitrary numbers into two 32-bit integers that represent
- * a 64-bit integer.
- *
- * @internal You should not use this directly from another application
- *
- * @param int|float $num
- * @return array<int, int>
- */
- public static function numericTo64BitInteger($num)
- {
- $high = 0;
- /** @var int $low */
- $low = $num & 0xffffffff;
-
- if ((+(abs($num))) >= 1) {
- if ($num > 0) {
- /** @var int $high */
- $high = min((+(floor($num/4294967296))), 4294967295);
- } else {
- /** @var int $high */
- $high = ~~((+(ceil(($num - (+((~~($num)))))/4294967296))));
- }
- }
- return array((int) $high, (int) $low);
- }
-
- /**
- * Store a 24-bit integer into a string, treating it as big-endian.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function store_3($int)
- {
- /* Type checks: */
- if (!is_int($int)) {
- if (is_numeric($int)) {
- $int = (int) $int;
- } else {
- throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
- }
- }
- /** @var string $packed */
- $packed = pack('N', $int);
- return self::substr($packed, 1, 3);
- }
-
- /**
- * Store a 32-bit integer into a string, treating it as little-endian.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function store32_le($int)
- {
- /* Type checks: */
- if (!is_int($int)) {
- if (is_numeric($int)) {
- $int = (int) $int;
- } else {
- throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
- }
- }
-
- /** @var string $packed */
- $packed = pack('V', $int);
- return $packed;
- }
-
- /**
- * Store a 32-bit integer into a string, treating it as big-endian.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function store_4($int)
- {
- /* Type checks: */
- if (!is_int($int)) {
- if (is_numeric($int)) {
- $int = (int) $int;
- } else {
- throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
- }
- }
-
- /** @var string $packed */
- $packed = pack('N', $int);
- return $packed;
- }
-
- /**
- * Stores a 64-bit integer as an string, treating it as little-endian.
- *
- * @internal You should not use this directly from another application
- *
- * @param int $int
- * @return string
- * @throws TypeError
- */
- public static function store64_le($int)
- {
- /* Type checks: */
- if (!is_int($int)) {
- if (is_numeric($int)) {
- $int = (int) $int;
- } else {
- throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
- }
- }
-
- if (PHP_INT_SIZE === 8) {
- if (PHP_VERSION_ID >= 50603) {
- /** @var string $packed */
- $packed = pack('P', $int);
- return $packed;
- }
- return self::intToChr($int & 0xff) .
- self::intToChr(($int >> 8) & 0xff) .
- self::intToChr(($int >> 16) & 0xff) .
- self::intToChr(($int >> 24) & 0xff) .
- self::intToChr(($int >> 32) & 0xff) .
- self::intToChr(($int >> 40) & 0xff) .
- self::intToChr(($int >> 48) & 0xff) .
- self::intToChr(($int >> 56) & 0xff);
- }
- if ($int > PHP_INT_MAX) {
- list($hiB, $int) = self::numericTo64BitInteger($int);
- } else {
- $hiB = 0;
- }
- return
- self::intToChr(($int ) & 0xff) .
- self::intToChr(($int >> 8) & 0xff) .
- self::intToChr(($int >> 16) & 0xff) .
- self::intToChr(($int >> 24) & 0xff) .
- self::intToChr($hiB & 0xff) .
- self::intToChr(($hiB >> 8) & 0xff) .
- self::intToChr(($hiB >> 16) & 0xff) .
- self::intToChr(($hiB >> 24) & 0xff);
- }
-
- /**
- * Safe string length
- *
- * @internal You should not use this directly from another application
- *
- * @ref mbstring.func_overload
- *
- * @param string $str
- * @return int
- * @throws TypeError
- */
- public static function strlen($str)
- {
- /* Type checks: */
- if (!is_string($str)) {
- throw new TypeError('String expected');
- }
-
- return (int) (
- self::isMbStringOverride()
- ? mb_strlen($str, '8bit')
- : strlen($str)
- );
- }
-
- /**
- * Turn a string into an array of integers
- *
- * @internal You should not use this directly from another application
- *
- * @param string $string
- * @return array<int, int>
- * @throws TypeError
- */
- public static function stringToIntArray($string)
- {
- if (!is_string($string)) {
- throw new TypeError('String expected');
- }
- /**
- * @var array<int, int>
- */
- $values = array_values(
- unpack('C*', $string)
- );
- return $values;
- }
-
- /**
- * Safe substring
- *
- * @internal You should not use this directly from another application
- *
- * @ref mbstring.func_overload
- *
- * @param string $str
- * @param int $start
- * @param int $length
- * @return string
- * @throws TypeError
- */
- public static function substr($str, $start = 0, $length = null)
- {
- /* Type checks: */
- if (!is_string($str)) {
- throw new TypeError('String expected');
- }
-
- if ($length === 0) {
- return '';
- }
-
- if (self::isMbStringOverride()) {
- if (PHP_VERSION_ID < 50400 && $length === null) {
- $length = self::strlen($str);
- }
- $sub = (string) mb_substr($str, $start, $length, '8bit');
- } elseif ($length === null) {
- $sub = (string) substr($str, $start);
- } else {
- $sub = (string) substr($str, $start, $length);
- }
- if ($sub !== '') {
- return $sub;
- }
- return '';
- }
-
- /**
- * Compare a 16-character byte string in constant time.
- *
- * @internal You should not use this directly from another application
- *
- * @param string $a
- * @param string $b
- * @return bool
- * @throws SodiumException
- * @throws TypeError
- */
- public static function verify_16($a, $b)
- {
- /* Type checks: */
- if (!is_string($a)) {
- throw new TypeError('String expected');
- }
- if (!is_string($b)) {
- throw new TypeError('String expected');
- }
- return self::hashEquals(
- self::substr($a, 0, 16),
- self::substr($b, 0, 16)
- );
- }
-
- /**
- * Compare a 32-character byte string in constant time.
- *
- * @internal You should not use this directly from another application
- *
- * @param string $a
- * @param string $b
- * @return bool
- * @throws SodiumException
- * @throws TypeError
- */
- public static function verify_32($a, $b)
- {
- /* Type checks: */
- if (!is_string($a)) {
- throw new TypeError('String expected');
- }
- if (!is_string($b)) {
- throw new TypeError('String expected');
- }
- return self::hashEquals(
- self::substr($a, 0, 32),
- self::substr($b, 0, 32)
- );
- }
-
- /**
- * Calculate $a ^ $b for two strings.
- *
- * @internal You should not use this directly from another application
- *
- * @param string $a
- * @param string $b
- * @return string
- * @throws TypeError
- */
- public static function xorStrings($a, $b)
- {
- /* Type checks: */
- if (!is_string($a)) {
- throw new TypeError('Argument 1 must be a string');
- }
- if (!is_string($b)) {
- throw new TypeError('Argument 2 must be a string');
- }
-
- return (string) ($a ^ $b);
- }
-
- /**
- * Returns whether or not mbstring.func_overload is in effect.
- *
- * @internal You should not use this directly from another application
- *
- * Note: MB_OVERLOAD_STRING === 2, but we don't reference the constant
- * (for nuisance-free PHP 8 support)
- *
- * @return bool
- */
- protected static function isMbStringOverride()
- {
- static $mbstring = null;
-
- if ($mbstring === null) {
- $mbstring = extension_loaded('mbstring')
- && defined('MB_OVERLOAD_STRING')
- &&
- ((int) (ini_get('mbstring.func_overload')) & 2);
- // MB_OVERLOAD_STRING === 2
- }
- /** @var bool $mbstring */
-
- return $mbstring;
- }
-}
+<?php + +if (class_exists('ParagonIE_Sodium_Core_Util', false)) { + return; +} + +/** + * Class ParagonIE_Sodium_Core_Util + */ +abstract class ParagonIE_Sodium_Core_Util +{ + /** + * @param int $integer + * @param int $size (16, 32, 64) + * @return int + */ + public static function abs($integer, $size = 0) + { + /** @var int $realSize */ + $realSize = (PHP_INT_SIZE << 3) - 1; + if ($size) { + --$size; + } else { + /** @var int $size */ + $size = $realSize; + } + + $negative = -(($integer >> $size) & 1); + return (int) ( + ($integer ^ $negative) + + + (($negative >> $realSize) & 1) + ); + } + + /** + * Convert a binary string into a hexadecimal string without cache-timing + * leaks + * + * @internal You should not use this directly from another application + * + * @param string $binaryString (raw binary) + * @return string + * @throws TypeError + */ + public static function bin2hex($binaryString) + { + /* Type checks: */ + if (!is_string($binaryString)) { + throw new TypeError('Argument 1 must be a string, ' . gettype($binaryString) . ' given.'); + } + + $hex = ''; + $len = self::strlen($binaryString); + for ($i = 0; $i < $len; ++$i) { + /** @var array<int, int> $chunk */ + $chunk = unpack('C', $binaryString[$i]); + /** @var int $c */ + $c = $chunk[1] & 0xf; + /** @var int $b */ + $b = $chunk[1] >> 4; + $hex .= pack( + 'CC', + (87 + $b + ((($b - 10) >> 8) & ~38)), + (87 + $c + ((($c - 10) >> 8) & ~38)) + ); + } + return $hex; + } + + /** + * Convert a binary string into a hexadecimal string without cache-timing + * leaks, returning uppercase letters (as per RFC 4648) + * + * @internal You should not use this directly from another application + * + * @param string $bin_string (raw binary) + * @return string + * @throws TypeError + */ + public static function bin2hexUpper($bin_string) + { + $hex = ''; + $len = self::strlen($bin_string); + for ($i = 0; $i < $len; ++$i) { + /** @var array<int, int> $chunk */ + $chunk = unpack('C', $bin_string[$i]); + /** + * Lower 16 bits + * + * @var int $c + */ + $c = $chunk[1] & 0xf; + + /** + * Upper 16 bits + * @var int $b + */ + $b = $chunk[1] >> 4; + + /** + * Use pack() and binary operators to turn the two integers + * into hexadecimal characters. We don't use chr() here, because + * it uses a lookup table internally and we want to avoid + * cache-timing side-channels. + */ + $hex .= pack( + 'CC', + (55 + $b + ((($b - 10) >> 8) & ~6)), + (55 + $c + ((($c - 10) >> 8) & ~6)) + ); + } + return $hex; + } + + /** + * Cache-timing-safe variant of ord() + * + * @internal You should not use this directly from another application + * + * @param string $chr + * @return int + * @throws SodiumException + * @throws TypeError + */ + public static function chrToInt($chr) + { + /* Type checks: */ + if (!is_string($chr)) { + throw new TypeError('Argument 1 must be a string, ' . gettype($chr) . ' given.'); + } + if (self::strlen($chr) !== 1) { + throw new SodiumException('chrToInt() expects a string that is exactly 1 character long'); + } + /** @var array<int, int> $chunk */ + $chunk = unpack('C', $chr); + return (int) ($chunk[1]); + } + + /** + * Compares two strings. + * + * @internal You should not use this directly from another application + * + * @param string $left + * @param string $right + * @param int $len + * @return int + * @throws SodiumException + * @throws TypeError + */ + public static function compare($left, $right, $len = null) + { + $leftLen = self::strlen($left); + $rightLen = self::strlen($right); + if ($len === null) { + $len = max($leftLen, $rightLen); + $left = str_pad($left, $len, "\x00", STR_PAD_RIGHT); + $right = str_pad($right, $len, "\x00", STR_PAD_RIGHT); + } + + $gt = 0; + $eq = 1; + $i = $len; + while ($i !== 0) { + --$i; + $gt |= ((self::chrToInt($right[$i]) - self::chrToInt($left[$i])) >> 8) & $eq; + $eq &= ((self::chrToInt($right[$i]) ^ self::chrToInt($left[$i])) - 1) >> 8; + } + return ($gt + $gt + $eq) - 1; + } + + /** + * If a variable does not match a given type, throw a TypeError. + * + * @param mixed $mixedVar + * @param string $type + * @param int $argumentIndex + * @throws TypeError + * @throws SodiumException + * @return void + */ + public static function declareScalarType(&$mixedVar = null, $type = 'void', $argumentIndex = 0) + { + if (func_num_args() === 0) { + /* Tautology, by default */ + return; + } + if (func_num_args() === 1) { + throw new TypeError('Declared void, but passed a variable'); + } + $realType = strtolower(gettype($mixedVar)); + $type = strtolower($type); + switch ($type) { + case 'null': + if ($mixedVar !== null) { + throw new TypeError('Argument ' . $argumentIndex . ' must be null, ' . $realType . ' given.'); + } + break; + case 'integer': + case 'int': + $allow = array('int', 'integer'); + if (!in_array($type, $allow)) { + throw new TypeError('Argument ' . $argumentIndex . ' must be an integer, ' . $realType . ' given.'); + } + $mixedVar = (int) $mixedVar; + break; + case 'boolean': + case 'bool': + $allow = array('bool', 'boolean'); + if (!in_array($type, $allow)) { + throw new TypeError('Argument ' . $argumentIndex . ' must be a boolean, ' . $realType . ' given.'); + } + $mixedVar = (bool) $mixedVar; + break; + case 'string': + if (!is_string($mixedVar)) { + throw new TypeError('Argument ' . $argumentIndex . ' must be a string, ' . $realType . ' given.'); + } + $mixedVar = (string) $mixedVar; + break; + case 'decimal': + case 'double': + case 'float': + $allow = array('decimal', 'double', 'float'); + if (!in_array($type, $allow)) { + throw new TypeError('Argument ' . $argumentIndex . ' must be a float, ' . $realType . ' given.'); + } + $mixedVar = (float) $mixedVar; + break; + case 'object': + if (!is_object($mixedVar)) { + throw new TypeError('Argument ' . $argumentIndex . ' must be an object, ' . $realType . ' given.'); + } + break; + case 'array': + if (!is_array($mixedVar)) { + if (is_object($mixedVar)) { + if ($mixedVar instanceof ArrayAccess) { + return; + } + } + throw new TypeError('Argument ' . $argumentIndex . ' must be an array, ' . $realType . ' given.'); + } + break; + default: + throw new SodiumException('Unknown type (' . $realType .') does not match expect type (' . $type . ')'); + } + } + + /** + * Evaluate whether or not two strings are equal (in constant-time) + * + * @param string $left + * @param string $right + * @return bool + * @throws SodiumException + * @throws TypeError + */ + public static function hashEquals($left, $right) + { + /* Type checks: */ + if (!is_string($left)) { + throw new TypeError('Argument 1 must be a string, ' . gettype($left) . ' given.'); + } + if (!is_string($right)) { + throw new TypeError('Argument 2 must be a string, ' . gettype($right) . ' given.'); + } + + if (is_callable('hash_equals')) { + return hash_equals($left, $right); + } + $d = 0; + /** @var int $len */ + $len = self::strlen($left); + if ($len !== self::strlen($right)) { + return false; + } + for ($i = 0; $i < $len; ++$i) { + $d |= self::chrToInt($left[$i]) ^ self::chrToInt($right[$i]); + } + + if ($d !== 0) { + return false; + } + return $left === $right; + } + + /** + * Catch hash_update() failures and throw instead of silently proceeding + * + * @param HashContext|resource &$hs + * @param string $data + * @return void + * @throws SodiumException + * @psalm-suppress PossiblyInvalidArgument + */ + protected static function hash_update(&$hs, $data) + { + if (!hash_update($hs, $data)) { + throw new SodiumException('hash_update() failed'); + } + } + + /** + * Convert a hexadecimal string into a binary string without cache-timing + * leaks + * + * @internal You should not use this directly from another application + * + * @param string $hexString + * @param string $ignore + * @param bool $strictPadding + * @return string (raw binary) + * @throws RangeException + * @throws TypeError + */ + public static function hex2bin($hexString, $ignore = '', $strictPadding = false) + { + /* Type checks: */ + if (!is_string($hexString)) { + throw new TypeError('Argument 1 must be a string, ' . gettype($hexString) . ' given.'); + } + if (!is_string($ignore)) { + throw new TypeError('Argument 2 must be a string, ' . gettype($hexString) . ' given.'); + } + + $hex_pos = 0; + $bin = ''; + $c_acc = 0; + $hex_len = self::strlen($hexString); + $state = 0; + if (($hex_len & 1) !== 0) { + if ($strictPadding) { + throw new RangeException( + 'Expected an even number of hexadecimal characters' + ); + } else { + $hexString = '0' . $hexString; + ++$hex_len; + } + } + + $chunk = unpack('C*', $hexString); + while ($hex_pos < $hex_len) { + ++$hex_pos; + /** @var int $c */ + $c = $chunk[$hex_pos]; + $c_num = $c ^ 48; + $c_num0 = ($c_num - 10) >> 8; + $c_alpha = ($c & ~32) - 55; + $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8; + if (($c_num0 | $c_alpha0) === 0) { + if ($ignore && $state === 0 && strpos($ignore, self::intToChr($c)) !== false) { + continue; + } + throw new RangeException( + 'hex2bin() only expects hexadecimal characters' + ); + } + $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0); + if ($state === 0) { + $c_acc = $c_val * 16; + } else { + $bin .= pack('C', $c_acc | $c_val); + } + $state ^= 1; + } + return $bin; + } + + /** + * Turn an array of integers into a string + * + * @internal You should not use this directly from another application + * + * @param array<int, int> $ints + * @return string + */ + public static function intArrayToString(array $ints) + { + $args = $ints; + foreach ($args as $i => $v) { + $args[$i] = (int) ($v & 0xff); + } + array_unshift($args, str_repeat('C', count($ints))); + return (string) (call_user_func_array('pack', $args)); + } + + /** + * Cache-timing-safe variant of ord() + * + * @internal You should not use this directly from another application + * + * @param int $int + * @return string + * @throws TypeError + */ + public static function intToChr($int) + { + return pack('C', $int); + } + + /** + * Load a 3 character substring into an integer + * + * @internal You should not use this directly from another application + * + * @param string $string + * @return int + * @throws RangeException + * @throws TypeError + */ + public static function load_3($string) + { + /* Type checks: */ + if (!is_string($string)) { + throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.'); + } + + /* Input validation: */ + if (self::strlen($string) < 3) { + throw new RangeException( + 'String must be 3 bytes or more; ' . self::strlen($string) . ' given.' + ); + } + /** @var array<int, int> $unpacked */ + $unpacked = unpack('V', $string . "\0"); + return (int) ($unpacked[1] & 0xffffff); + } + + /** + * Load a 4 character substring into an integer + * + * @internal You should not use this directly from another application + * + * @param string $string + * @return int + * @throws RangeException + * @throws TypeError + */ + public static function load_4($string) + { + /* Type checks: */ + if (!is_string($string)) { + throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.'); + } + + /* Input validation: */ + if (self::strlen($string) < 4) { + throw new RangeException( + 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.' + ); + } + /** @var array<int, int> $unpacked */ + $unpacked = unpack('V', $string); + return (int) $unpacked[1]; + } + + /** + * Load a 8 character substring into an integer + * + * @internal You should not use this directly from another application + * + * @param string $string + * @return int + * @throws RangeException + * @throws SodiumException + * @throws TypeError + */ + public static function load64_le($string) + { + /* Type checks: */ + if (!is_string($string)) { + throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.'); + } + + /* Input validation: */ + if (self::strlen($string) < 4) { + throw new RangeException( + 'String must be 4 bytes or more; ' . self::strlen($string) . ' given.' + ); + } + if (PHP_VERSION_ID >= 50603 && PHP_INT_SIZE === 8) { + /** @var array<int, int> $unpacked */ + $unpacked = unpack('P', $string); + return (int) $unpacked[1]; + } + + /** @var int $result */ + $result = (self::chrToInt($string[0]) & 0xff); + $result |= (self::chrToInt($string[1]) & 0xff) << 8; + $result |= (self::chrToInt($string[2]) & 0xff) << 16; + $result |= (self::chrToInt($string[3]) & 0xff) << 24; + $result |= (self::chrToInt($string[4]) & 0xff) << 32; + $result |= (self::chrToInt($string[5]) & 0xff) << 40; + $result |= (self::chrToInt($string[6]) & 0xff) << 48; + $result |= (self::chrToInt($string[7]) & 0xff) << 56; + return (int) $result; + } + + /** + * @internal You should not use this directly from another application + * + * @param string $left + * @param string $right + * @return int + * @throws SodiumException + * @throws TypeError + */ + public static function memcmp($left, $right) + { + if (self::hashEquals($left, $right)) { + return 0; + } + return -1; + } + + /** + * Multiply two integers in constant-time + * + * Micro-architecture timing side-channels caused by how your CPU + * implements multiplication are best prevented by never using the + * multiplication operators and ensuring that our code always takes + * the same number of operations to complete, regardless of the values + * of $a and $b. + * + * @internal You should not use this directly from another application + * + * @param int $a + * @param int $b + * @param int $size Limits the number of operations (useful for small, + * constant operands) + * @return int + */ + public static function mul($a, $b, $size = 0) + { + if (ParagonIE_Sodium_Compat::$fastMult) { + return (int) ($a * $b); + } + + static $defaultSize = null; + /** @var int $defaultSize */ + if (!$defaultSize) { + /** @var int $defaultSize */ + $defaultSize = (PHP_INT_SIZE << 3) - 1; + } + if ($size < 1) { + /** @var int $size */ + $size = $defaultSize; + } + /** @var int $size */ + + $c = 0; + + /** + * Mask is either -1 or 0. + * + * -1 in binary looks like 0x1111 ... 1111 + * 0 in binary looks like 0x0000 ... 0000 + * + * @var int + */ + $mask = -(($b >> ((int) $defaultSize)) & 1); + + /** + * Ensure $b is a positive integer, without creating + * a branching side-channel + * + * @var int $b + */ + $b = ($b & ~$mask) | ($mask & -$b); + + /** + * Unless $size is provided: + * + * This loop always runs 32 times when PHP_INT_SIZE is 4. + * This loop always runs 64 times when PHP_INT_SIZE is 8. + */ + for ($i = $size; $i >= 0; --$i) { + $c += (int) ($a & -($b & 1)); + $a <<= 1; + $b >>= 1; + } + $c = (int) @($c & -1); + + /** + * If $b was negative, we then apply the same value to $c here. + * It doesn't matter much if $a was negative; the $c += above would + * have produced a negative integer to begin with. But a negative $b + * makes $b >>= 1 never return 0, so we would end up with incorrect + * results. + * + * The end result is what we'd expect from integer multiplication. + */ + return (int) (($c & ~$mask) | ($mask & -$c)); + } + + /** + * Convert any arbitrary numbers into two 32-bit integers that represent + * a 64-bit integer. + * + * @internal You should not use this directly from another application + * + * @param int|float $num + * @return array<int, int> + */ + public static function numericTo64BitInteger($num) + { + $high = 0; + /** @var int $low */ + if (PHP_INT_SIZE === 4) { + $low = (int) $num; + } else { + $low = $num & 0xffffffff; + } + + if ((+(abs($num))) >= 1) { + if ($num > 0) { + /** @var int $high */ + $high = min((+(floor($num/4294967296))), 4294967295); + } else { + /** @var int $high */ + $high = ~~((+(ceil(($num - (+((~~($num)))))/4294967296)))); + } + } + return array((int) $high, (int) $low); + } + + /** + * Store a 24-bit integer into a string, treating it as big-endian. + * + * @internal You should not use this directly from another application + * + * @param int $int + * @return string + * @throws TypeError + */ + public static function store_3($int) + { + /* Type checks: */ + if (!is_int($int)) { + if (is_numeric($int)) { + $int = (int) $int; + } else { + throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.'); + } + } + /** @var string $packed */ + $packed = pack('N', $int); + return self::substr($packed, 1, 3); + } + + /** + * Store a 32-bit integer into a string, treating it as little-endian. + * + * @internal You should not use this directly from another application + * + * @param int $int + * @return string + * @throws TypeError + */ + public static function store32_le($int) + { + /* Type checks: */ + if (!is_int($int)) { + if (is_numeric($int)) { + $int = (int) $int; + } else { + throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.'); + } + } + + /** @var string $packed */ + $packed = pack('V', $int); + return $packed; + } + + /** + * Store a 32-bit integer into a string, treating it as big-endian. + * + * @internal You should not use this directly from another application + * + * @param int $int + * @return string + * @throws TypeError + */ + public static function store_4($int) + { + /* Type checks: */ + if (!is_int($int)) { + if (is_numeric($int)) { + $int = (int) $int; + } else { + throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.'); + } + } + + /** @var string $packed */ + $packed = pack('N', $int); + return $packed; + } + + /** + * Stores a 64-bit integer as an string, treating it as little-endian. + * + * @internal You should not use this directly from another application + * + * @param int $int + * @return string + * @throws TypeError + */ + public static function store64_le($int) + { + /* Type checks: */ + if (!is_int($int)) { + if (is_numeric($int)) { + $int = (int) $int; + } else { + throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.'); + } + } + + if (PHP_INT_SIZE === 8) { + if (PHP_VERSION_ID >= 50603) { + /** @var string $packed */ + $packed = pack('P', $int); + return $packed; + } + return self::intToChr($int & 0xff) . + self::intToChr(($int >> 8) & 0xff) . + self::intToChr(($int >> 16) & 0xff) . + self::intToChr(($int >> 24) & 0xff) . + self::intToChr(($int >> 32) & 0xff) . + self::intToChr(($int >> 40) & 0xff) . + self::intToChr(($int >> 48) & 0xff) . + self::intToChr(($int >> 56) & 0xff); + } + if ($int > PHP_INT_MAX) { + list($hiB, $int) = self::numericTo64BitInteger($int); + } else { + $hiB = 0; + } + return + self::intToChr(($int ) & 0xff) . + self::intToChr(($int >> 8) & 0xff) . + self::intToChr(($int >> 16) & 0xff) . + self::intToChr(($int >> 24) & 0xff) . + self::intToChr($hiB & 0xff) . + self::intToChr(($hiB >> 8) & 0xff) . + self::intToChr(($hiB >> 16) & 0xff) . + self::intToChr(($hiB >> 24) & 0xff); + } + + /** + * Safe string length + * + * @internal You should not use this directly from another application + * + * @ref mbstring.func_overload + * + * @param string $str + * @return int + * @throws TypeError + */ + public static function strlen($str) + { + /* Type checks: */ + if (!is_string($str)) { + throw new TypeError('String expected'); + } + + return (int) ( + self::isMbStringOverride() + ? mb_strlen($str, '8bit') + : strlen($str) + ); + } + + /** + * Turn a string into an array of integers + * + * @internal You should not use this directly from another application + * + * @param string $string + * @return array<int, int> + * @throws TypeError + */ + public static function stringToIntArray($string) + { + if (!is_string($string)) { + throw new TypeError('String expected'); + } + /** + * @var array<int, int> + */ + $values = array_values( + unpack('C*', $string) + ); + return $values; + } + + /** + * Safe substring + * + * @internal You should not use this directly from another application + * + * @ref mbstring.func_overload + * + * @param string $str + * @param int $start + * @param int $length + * @return string + * @throws TypeError + */ + public static function substr($str, $start = 0, $length = null) + { + /* Type checks: */ + if (!is_string($str)) { + throw new TypeError('String expected'); + } + + if ($length === 0) { + return ''; + } + + if (self::isMbStringOverride()) { + if (PHP_VERSION_ID < 50400 && $length === null) { + $length = self::strlen($str); + } + $sub = (string) mb_substr($str, $start, $length, '8bit'); + } elseif ($length === null) { + $sub = (string) substr($str, $start); + } else { + $sub = (string) substr($str, $start, $length); + } + if ($sub !== '') { + return $sub; + } + return ''; + } + + /** + * Compare a 16-character byte string in constant time. + * + * @internal You should not use this directly from another application + * + * @param string $a + * @param string $b + * @return bool + * @throws SodiumException + * @throws TypeError + */ + public static function verify_16($a, $b) + { + /* Type checks: */ + if (!is_string($a)) { + throw new TypeError('String expected'); + } + if (!is_string($b)) { + throw new TypeError('String expected'); + } + return self::hashEquals( + self::substr($a, 0, 16), + self::substr($b, 0, 16) + ); + } + + /** + * Compare a 32-character byte string in constant time. + * + * @internal You should not use this directly from another application + * + * @param string $a + * @param string $b + * @return bool + * @throws SodiumException + * @throws TypeError + */ + public static function verify_32($a, $b) + { + /* Type checks: */ + if (!is_string($a)) { + throw new TypeError('String expected'); + } + if (!is_string($b)) { + throw new TypeError('String expected'); + } + return self::hashEquals( + self::substr($a, 0, 32), + self::substr($b, 0, 32) + ); + } + + /** + * Calculate $a ^ $b for two strings. + * + * @internal You should not use this directly from another application + * + * @param string $a + * @param string $b + * @return string + * @throws TypeError + */ + public static function xorStrings($a, $b) + { + /* Type checks: */ + if (!is_string($a)) { + throw new TypeError('Argument 1 must be a string'); + } + if (!is_string($b)) { + throw new TypeError('Argument 2 must be a string'); + } + + return (string) ($a ^ $b); + } + + /** + * Returns whether or not mbstring.func_overload is in effect. + * + * @internal You should not use this directly from another application + * + * Note: MB_OVERLOAD_STRING === 2, but we don't reference the constant + * (for nuisance-free PHP 8 support) + * + * @return bool + */ + protected static function isMbStringOverride() + { + static $mbstring = null; + + if ($mbstring === null) { + if (!defined('MB_OVERLOAD_STRING')) { + $mbstring = false; + return $mbstring; + } + $mbstring = extension_loaded('mbstring') + && defined('MB_OVERLOAD_STRING') + && + ((int) (ini_get('mbstring.func_overload')) & 2); + // MB_OVERLOAD_STRING === 2 + } + /** @var bool $mbstring */ + + return $mbstring; + } +} |