[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/sodium_compat/src/ -> File.php (source)

   1  <?php
   2  
   3  if (class_exists('ParagonIE_Sodium_File', false)) {
   4      return;
   5  }
   6  /**
   7   * Class ParagonIE_Sodium_File
   8   */
   9  class ParagonIE_Sodium_File extends ParagonIE_Sodium_Core_Util
  10  {
  11      /* PHP's default buffer size is 8192 for fread()/fwrite(). */
  12      const BUFFER_SIZE = 8192;
  13  
  14      /**
  15       * Box a file (rather than a string). Uses less memory than
  16       * ParagonIE_Sodium_Compat::crypto_box(), but produces
  17       * the same result.
  18       *
  19       * @param string $inputFile  Absolute path to a file on the filesystem
  20       * @param string $outputFile Absolute path to a file on the filesystem
  21       * @param string $nonce      Number to be used only once
  22       * @param string $keyPair    ECDH secret key and ECDH public key concatenated
  23       *
  24       * @return bool
  25       * @throws SodiumException
  26       * @throws TypeError
  27       */
  28      public static function box($inputFile, $outputFile, $nonce, $keyPair)
  29      {
  30          /* Type checks: */
  31          if (!is_string($inputFile)) {
  32              throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
  33          }
  34          if (!is_string($outputFile)) {
  35              throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
  36          }
  37          if (!is_string($nonce)) {
  38              throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
  39          }
  40  
  41          /* Input validation: */
  42          if (!is_string($keyPair)) {
  43              throw new TypeError('Argument 4 must be a string, ' . gettype($keyPair) . ' given.');
  44          }
  45          if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
  46              throw new TypeError('Argument 3 must be CRYPTO_BOX_NONCEBYTES bytes');
  47          }
  48          if (self::strlen($keyPair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
  49              throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
  50          }
  51  
  52          /** @var int $size */
  53          $size = filesize($inputFile);
  54          if (!is_int($size)) {
  55              throw new SodiumException('Could not obtain the file size');
  56          }
  57  
  58          /** @var resource $ifp */
  59          $ifp = fopen($inputFile, 'rb');
  60          if (!is_resource($ifp)) {
  61              throw new SodiumException('Could not open input file for reading');
  62          }
  63  
  64          /** @var resource $ofp */
  65          $ofp = fopen($outputFile, 'wb');
  66          if (!is_resource($ofp)) {
  67              fclose($ifp);
  68              throw new SodiumException('Could not open output file for writing');
  69          }
  70  
  71          $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $keyPair);
  72          fclose($ifp);
  73          fclose($ofp);
  74          return $res;
  75      }
  76  
  77      /**
  78       * Open a boxed file (rather than a string). Uses less memory than
  79       * ParagonIE_Sodium_Compat::crypto_box_open(), but produces
  80       * the same result.
  81       *
  82       * Warning: Does not protect against TOCTOU attacks. You should
  83       * just load the file into memory and use crypto_box_open() if
  84       * you are worried about those.
  85       *
  86       * @param string $inputFile
  87       * @param string $outputFile
  88       * @param string $nonce
  89       * @param string $keypair
  90       * @return bool
  91       * @throws SodiumException
  92       * @throws TypeError
  93       */
  94      public static function box_open($inputFile, $outputFile, $nonce, $keypair)
  95      {
  96          /* Type checks: */
  97          if (!is_string($inputFile)) {
  98              throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
  99          }
 100          if (!is_string($outputFile)) {
 101              throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
 102          }
 103          if (!is_string($nonce)) {
 104              throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
 105          }
 106          if (!is_string($keypair)) {
 107              throw new TypeError('Argument 4 must be a string, ' . gettype($keypair) . ' given.');
 108          }
 109  
 110          /* Input validation: */
 111          if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
 112              throw new TypeError('Argument 4 must be CRYPTO_BOX_NONCEBYTES bytes');
 113          }
 114          if (self::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
 115              throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
 116          }
 117  
 118          /** @var int $size */
 119          $size = filesize($inputFile);
 120          if (!is_int($size)) {
 121              throw new SodiumException('Could not obtain the file size');
 122          }
 123  
 124          /** @var resource $ifp */
 125          $ifp = fopen($inputFile, 'rb');
 126          if (!is_resource($ifp)) {
 127              throw new SodiumException('Could not open input file for reading');
 128          }
 129  
 130          /** @var resource $ofp */
 131          $ofp = fopen($outputFile, 'wb');
 132          if (!is_resource($ofp)) {
 133              fclose($ifp);
 134              throw new SodiumException('Could not open output file for writing');
 135          }
 136  
 137          $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $keypair);
 138          fclose($ifp);
 139          fclose($ofp);
 140          try {
 141              ParagonIE_Sodium_Compat::memzero($nonce);
 142              ParagonIE_Sodium_Compat::memzero($ephKeypair);
 143          } catch (SodiumException $ex) {
 144              unset($ephKeypair);
 145          }
 146          return $res;
 147      }
 148  
 149      /**
 150       * Seal a file (rather than a string). Uses less memory than
 151       * ParagonIE_Sodium_Compat::crypto_box_seal(), but produces
 152       * the same result.
 153       *
 154       * @param string $inputFile  Absolute path to a file on the filesystem
 155       * @param string $outputFile Absolute path to a file on the filesystem
 156       * @param string $publicKey  ECDH public key
 157       *
 158       * @return bool
 159       * @throws SodiumException
 160       * @throws TypeError
 161       */
 162      public static function box_seal($inputFile, $outputFile, $publicKey)
 163      {
 164          /* Type checks: */
 165          if (!is_string($inputFile)) {
 166              throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
 167          }
 168          if (!is_string($outputFile)) {
 169              throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
 170          }
 171          if (!is_string($publicKey)) {
 172              throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
 173          }
 174  
 175          /* Input validation: */
 176          if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
 177              throw new TypeError('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES bytes');
 178          }
 179  
 180          /** @var int $size */
 181          $size = filesize($inputFile);
 182          if (!is_int($size)) {
 183              throw new SodiumException('Could not obtain the file size');
 184          }
 185  
 186          /** @var resource $ifp */
 187          $ifp = fopen($inputFile, 'rb');
 188          if (!is_resource($ifp)) {
 189              throw new SodiumException('Could not open input file for reading');
 190          }
 191  
 192          /** @var resource $ofp */
 193          $ofp = fopen($outputFile, 'wb');
 194          if (!is_resource($ofp)) {
 195              fclose($ifp);
 196              throw new SodiumException('Could not open output file for writing');
 197          }
 198  
 199          /** @var string $ephKeypair */
 200          $ephKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair();
 201  
 202          /** @var string $msgKeypair */
 203          $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
 204              ParagonIE_Sodium_Compat::crypto_box_secretkey($ephKeypair),
 205              $publicKey
 206          );
 207  
 208          /** @var string $ephemeralPK */
 209          $ephemeralPK = ParagonIE_Sodium_Compat::crypto_box_publickey($ephKeypair);
 210  
 211          /** @var string $nonce */
 212          $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
 213              $ephemeralPK . $publicKey,
 214              '',
 215              24
 216          );
 217  
 218          /** @var int $firstWrite */
 219          $firstWrite = fwrite(
 220              $ofp,
 221              $ephemeralPK,
 222              ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES
 223          );
 224          if (!is_int($firstWrite)) {
 225              fclose($ifp);
 226              fclose($ofp);
 227              ParagonIE_Sodium_Compat::memzero($ephKeypair);
 228              throw new SodiumException('Could not write to output file');
 229          }
 230          if ($firstWrite !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
 231              ParagonIE_Sodium_Compat::memzero($ephKeypair);
 232              fclose($ifp);
 233              fclose($ofp);
 234              throw new SodiumException('Error writing public key to output file');
 235          }
 236  
 237          $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
 238          fclose($ifp);
 239          fclose($ofp);
 240          try {
 241              ParagonIE_Sodium_Compat::memzero($nonce);
 242              ParagonIE_Sodium_Compat::memzero($ephKeypair);
 243          } catch (SodiumException $ex) {
 244              unset($ephKeypair);
 245          }
 246          return $res;
 247      }
 248  
 249      /**
 250       * Open a sealed file (rather than a string). Uses less memory than
 251       * ParagonIE_Sodium_Compat::crypto_box_seal_open(), but produces
 252       * the same result.
 253       *
 254       * Warning: Does not protect against TOCTOU attacks. You should
 255       * just load the file into memory and use crypto_box_seal_open() if
 256       * you are worried about those.
 257       *
 258       * @param string $inputFile
 259       * @param string $outputFile
 260       * @param string $ecdhKeypair
 261       * @return bool
 262       * @throws SodiumException
 263       * @throws TypeError
 264       */
 265      public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair)
 266      {
 267          /* Type checks: */
 268          if (!is_string($inputFile)) {
 269              throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
 270          }
 271          if (!is_string($outputFile)) {
 272              throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
 273          }
 274          if (!is_string($ecdhKeypair)) {
 275              throw new TypeError('Argument 3 must be a string, ' . gettype($ecdhKeypair) . ' given.');
 276          }
 277  
 278          /* Input validation: */
 279          if (self::strlen($ecdhKeypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
 280              throw new TypeError('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
 281          }
 282  
 283          $publicKey = ParagonIE_Sodium_Compat::crypto_box_publickey($ecdhKeypair);
 284  
 285          /** @var int $size */
 286          $size = filesize($inputFile);
 287          if (!is_int($size)) {
 288              throw new SodiumException('Could not obtain the file size');
 289          }
 290  
 291          /** @var resource $ifp */
 292          $ifp = fopen($inputFile, 'rb');
 293          if (!is_resource($ifp)) {
 294              throw new SodiumException('Could not open input file for reading');
 295          }
 296  
 297          /** @var resource $ofp */
 298          $ofp = fopen($outputFile, 'wb');
 299          if (!is_resource($ofp)) {
 300              fclose($ifp);
 301              throw new SodiumException('Could not open output file for writing');
 302          }
 303  
 304          $ephemeralPK = fread($ifp, ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES);
 305          if (!is_string($ephemeralPK)) {
 306              throw new SodiumException('Could not read input file');
 307          }
 308          if (self::strlen($ephemeralPK) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
 309              fclose($ifp);
 310              fclose($ofp);
 311              throw new SodiumException('Could not read public key from sealed file');
 312          }
 313  
 314          $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
 315              $ephemeralPK . $publicKey,
 316              '',
 317              24
 318          );
 319          $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
 320              ParagonIE_Sodium_Compat::crypto_box_secretkey($ecdhKeypair),
 321              $ephemeralPK
 322          );
 323  
 324          $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
 325          fclose($ifp);
 326          fclose($ofp);
 327          try {
 328              ParagonIE_Sodium_Compat::memzero($nonce);
 329              ParagonIE_Sodium_Compat::memzero($ephKeypair);
 330          } catch (SodiumException $ex) {
 331              unset($ephKeypair);
 332          }
 333          return $res;
 334      }
 335  
 336      /**
 337       * Calculate the BLAKE2b hash of a file.
 338       *
 339       * @param string      $filePath     Absolute path to a file on the filesystem
 340       * @param string|null $key          BLAKE2b key
 341       * @param int         $outputLength Length of hash output
 342       *
 343       * @return string                   BLAKE2b hash
 344       * @throws SodiumException
 345       * @throws TypeError
 346       * @psalm-suppress FailedTypeResolution
 347       */
 348      public static function generichash($filePath, $key = '', $outputLength = 32)
 349      {
 350          /* Type checks: */
 351          if (!is_string($filePath)) {
 352              throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
 353          }
 354          if (!is_string($key)) {
 355              if (is_null($key)) {
 356                  $key = '';
 357              } else {
 358                  throw new TypeError('Argument 2 must be a string, ' . gettype($key) . ' given.');
 359              }
 360          }
 361          if (!is_int($outputLength)) {
 362              if (!is_numeric($outputLength)) {
 363                  throw new TypeError('Argument 3 must be an integer, ' . gettype($outputLength) . ' given.');
 364              }
 365              $outputLength = (int) $outputLength;
 366          }
 367  
 368          /* Input validation: */
 369          if (!empty($key)) {
 370              if (self::strlen($key) < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
 371                  throw new TypeError('Argument 2 must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes');
 372              }
 373              if (self::strlen($key) > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
 374                  throw new TypeError('Argument 2 must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes');
 375              }
 376          }
 377          if ($outputLength < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MIN) {
 378              throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MIN');
 379          }
 380          if ($outputLength > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MAX) {
 381              throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MAX');
 382          }
 383  
 384          /** @var int $size */
 385          $size = filesize($filePath);
 386          if (!is_int($size)) {
 387              throw new SodiumException('Could not obtain the file size');
 388          }
 389  
 390          /** @var resource $fp */
 391          $fp = fopen($filePath, 'rb');
 392          if (!is_resource($fp)) {
 393              throw new SodiumException('Could not open input file for reading');
 394          }
 395          $ctx = ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outputLength);
 396          while ($size > 0) {
 397              $blockSize = $size > 64
 398                  ? 64
 399                  : $size;
 400              $read = fread($fp, $blockSize);
 401              if (!is_string($read)) {
 402                  throw new SodiumException('Could not read input file');
 403              }
 404              ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $read);
 405              $size -= $blockSize;
 406          }
 407  
 408          fclose($fp);
 409          return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
 410      }
 411  
 412      /**
 413       * Encrypt a file (rather than a string). Uses less memory than
 414       * ParagonIE_Sodium_Compat::crypto_secretbox(), but produces
 415       * the same result.
 416       *
 417       * @param string $inputFile  Absolute path to a file on the filesystem
 418       * @param string $outputFile Absolute path to a file on the filesystem
 419       * @param string $nonce      Number to be used only once
 420       * @param string $key        Encryption key
 421       *
 422       * @return bool
 423       * @throws SodiumException
 424       * @throws TypeError
 425       */
 426      public static function secretbox($inputFile, $outputFile, $nonce, $key)
 427      {
 428          /* Type checks: */
 429          if (!is_string($inputFile)) {
 430              throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given..');
 431          }
 432          if (!is_string($outputFile)) {
 433              throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
 434          }
 435          if (!is_string($nonce)) {
 436              throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
 437          }
 438  
 439          /* Input validation: */
 440          if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
 441              throw new TypeError('Argument 3 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
 442          }
 443          if (!is_string($key)) {
 444              throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
 445          }
 446          if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
 447              throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_KEYBYTES bytes');
 448          }
 449  
 450          /** @var int $size */
 451          $size = filesize($inputFile);
 452          if (!is_int($size)) {
 453              throw new SodiumException('Could not obtain the file size');
 454          }
 455  
 456          /** @var resource $ifp */
 457          $ifp = fopen($inputFile, 'rb');
 458          if (!is_resource($ifp)) {
 459              throw new SodiumException('Could not open input file for reading');
 460          }
 461  
 462          /** @var resource $ofp */
 463          $ofp = fopen($outputFile, 'wb');
 464          if (!is_resource($ofp)) {
 465              fclose($ifp);
 466              throw new SodiumException('Could not open output file for writing');
 467          }
 468  
 469          $res = self::secretbox_encrypt($ifp, $ofp, $size, $nonce, $key);
 470          fclose($ifp);
 471          fclose($ofp);
 472          return $res;
 473      }
 474      /**
 475       * Seal a file (rather than a string). Uses less memory than
 476       * ParagonIE_Sodium_Compat::crypto_secretbox_open(), but produces
 477       * the same result.
 478       *
 479       * Warning: Does not protect against TOCTOU attacks. You should
 480       * just load the file into memory and use crypto_secretbox_open() if
 481       * you are worried about those.
 482       *
 483       * @param string $inputFile
 484       * @param string $outputFile
 485       * @param string $nonce
 486       * @param string $key
 487       * @return bool
 488       * @throws SodiumException
 489       * @throws TypeError
 490       */
 491      public static function secretbox_open($inputFile, $outputFile, $nonce, $key)
 492      {
 493          /* Type checks: */
 494          if (!is_string($inputFile)) {
 495              throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
 496          }
 497          if (!is_string($outputFile)) {
 498              throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
 499          }
 500          if (!is_string($nonce)) {
 501              throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
 502          }
 503          if (!is_string($key)) {
 504              throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
 505          }
 506  
 507          /* Input validation: */
 508          if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
 509              throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
 510          }
 511          if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
 512              throw new TypeError('Argument 4 must be CRYPTO_SECRETBOXBOX_KEYBYTES bytes');
 513          }
 514  
 515          /** @var int $size */
 516          $size = filesize($inputFile);
 517          if (!is_int($size)) {
 518              throw new SodiumException('Could not obtain the file size');
 519          }
 520  
 521          /** @var resource $ifp */
 522          $ifp = fopen($inputFile, 'rb');
 523          if (!is_resource($ifp)) {
 524              throw new SodiumException('Could not open input file for reading');
 525          }
 526  
 527          /** @var resource $ofp */
 528          $ofp = fopen($outputFile, 'wb');
 529          if (!is_resource($ofp)) {
 530              fclose($ifp);
 531              throw new SodiumException('Could not open output file for writing');
 532          }
 533  
 534          $res = self::secretbox_decrypt($ifp, $ofp, $size, $nonce, $key);
 535          fclose($ifp);
 536          fclose($ofp);
 537          try {
 538              ParagonIE_Sodium_Compat::memzero($key);
 539          } catch (SodiumException $ex) {
 540              unset($key);
 541          }
 542          return $res;
 543      }
 544  
 545      /**
 546       * Sign a file (rather than a string). Uses less memory than
 547       * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
 548       * the same result.
 549       *
 550       * @param string $filePath  Absolute path to a file on the filesystem
 551       * @param string $secretKey Secret signing key
 552       *
 553       * @return string           Ed25519 signature
 554       * @throws SodiumException
 555       * @throws TypeError
 556       */
 557      public static function sign($filePath, $secretKey)
 558      {
 559          /* Type checks: */
 560          if (!is_string($filePath)) {
 561              throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
 562          }
 563          if (!is_string($secretKey)) {
 564              throw new TypeError('Argument 2 must be a string, ' . gettype($secretKey) . ' given.');
 565          }
 566  
 567          /* Input validation: */
 568          if (self::strlen($secretKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_SECRETKEYBYTES) {
 569              throw new TypeError('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES bytes');
 570          }
 571          if (PHP_INT_SIZE === 4) {
 572              return self::sign_core32($filePath, $secretKey);
 573          }
 574  
 575          /** @var int $size */
 576          $size = filesize($filePath);
 577          if (!is_int($size)) {
 578              throw new SodiumException('Could not obtain the file size');
 579          }
 580  
 581          /** @var resource $fp */
 582          $fp = fopen($filePath, 'rb');
 583          if (!is_resource($fp)) {
 584              throw new SodiumException('Could not open input file for reading');
 585          }
 586  
 587          /** @var string $az */
 588          $az = hash('sha512', self::substr($secretKey, 0, 32), true);
 589  
 590          $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
 591          $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
 592  
 593          $hs = hash_init('sha512');
 594          hash_update($hs, self::substr($az, 32, 32));
 595          /** @var resource $hs */
 596          $hs = self::updateHashWithFile($hs, $fp, $size);
 597  
 598          /** @var string $nonceHash */
 599          $nonceHash = hash_final($hs, true);
 600  
 601          /** @var string $pk */
 602          $pk = self::substr($secretKey, 32, 32);
 603  
 604          /** @var string $nonce */
 605          $nonce = ParagonIE_Sodium_Core_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
 606  
 607          /** @var string $sig */
 608          $sig = ParagonIE_Sodium_Core_Ed25519::ge_p3_tobytes(
 609              ParagonIE_Sodium_Core_Ed25519::ge_scalarmult_base($nonce)
 610          );
 611  
 612          $hs = hash_init('sha512');
 613          hash_update($hs, self::substr($sig, 0, 32));
 614          hash_update($hs, self::substr($pk, 0, 32));
 615          /** @var resource $hs */
 616          $hs = self::updateHashWithFile($hs, $fp, $size);
 617  
 618          /** @var string $hramHash */
 619          $hramHash = hash_final($hs, true);
 620  
 621          /** @var string $hram */
 622          $hram = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hramHash);
 623  
 624          /** @var string $sigAfter */
 625          $sigAfter = ParagonIE_Sodium_Core_Ed25519::sc_muladd($hram, $az, $nonce);
 626  
 627          /** @var string $sig */
 628          $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
 629  
 630          try {
 631              ParagonIE_Sodium_Compat::memzero($az);
 632          } catch (SodiumException $ex) {
 633              $az = null;
 634          }
 635          fclose($fp);
 636          return $sig;
 637      }
 638  
 639      /**
 640       * Verify a file (rather than a string). Uses less memory than
 641       * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
 642       * produces the same result.
 643       *
 644       * @param string $sig       Ed25519 signature
 645       * @param string $filePath  Absolute path to a file on the filesystem
 646       * @param string $publicKey Signing public key
 647       *
 648       * @return bool
 649       * @throws SodiumException
 650       * @throws TypeError
 651       * @throws Exception
 652       */
 653      public static function verify($sig, $filePath, $publicKey)
 654      {
 655          /* Type checks: */
 656          if (!is_string($sig)) {
 657              throw new TypeError('Argument 1 must be a string, ' . gettype($sig) . ' given.');
 658          }
 659          if (!is_string($filePath)) {
 660              throw new TypeError('Argument 2 must be a string, ' . gettype($filePath) . ' given.');
 661          }
 662          if (!is_string($publicKey)) {
 663              throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
 664          }
 665  
 666          /* Input validation: */
 667          if (self::strlen($sig) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_BYTES) {
 668              throw new TypeError('Argument 1 must be CRYPTO_SIGN_BYTES bytes');
 669          }
 670          if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_PUBLICKEYBYTES) {
 671              throw new TypeError('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES bytes');
 672          }
 673          if (self::strlen($sig) < 64) {
 674              throw new SodiumException('Signature is too short');
 675          }
 676  
 677          if (PHP_INT_SIZE === 4) {
 678              return self::verify_core32($sig, $filePath, $publicKey);
 679          }
 680  
 681          /* Security checks */
 682          if (
 683              (ParagonIE_Sodium_Core_Ed25519::chrToInt($sig[63]) & 240)
 684                  &&
 685              ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))
 686          ) {
 687              throw new SodiumException('S < L - Invalid signature');
 688          }
 689          if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) {
 690              throw new SodiumException('Signature is on too small of an order');
 691          }
 692          if ((self::chrToInt($sig[63]) & 224) !== 0) {
 693              throw new SodiumException('Invalid signature');
 694          }
 695          $d = 0;
 696          for ($i = 0; $i < 32; ++$i) {
 697              $d |= self::chrToInt($publicKey[$i]);
 698          }
 699          if ($d === 0) {
 700              throw new SodiumException('All zero public key');
 701          }
 702  
 703          /** @var int $size */
 704          $size = filesize($filePath);
 705          if (!is_int($size)) {
 706              throw new SodiumException('Could not obtain the file size');
 707          }
 708  
 709          /** @var resource $fp */
 710          $fp = fopen($filePath, 'rb');
 711          if (!is_resource($fp)) {
 712              throw new SodiumException('Could not open input file for reading');
 713          }
 714  
 715          /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
 716          $orig = ParagonIE_Sodium_Compat::$fastMult;
 717  
 718          // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
 719          ParagonIE_Sodium_Compat::$fastMult = true;
 720  
 721          /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
 722          $A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);
 723  
 724          $hs = hash_init('sha512');
 725          hash_update($hs, self::substr($sig, 0, 32));
 726          hash_update($hs, self::substr($publicKey, 0, 32));
 727          /** @var resource $hs */
 728          $hs = self::updateHashWithFile($hs, $fp, $size);
 729          /** @var string $hDigest */
 730          $hDigest = hash_final($hs, true);
 731  
 732          /** @var string $h */
 733          $h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
 734  
 735          /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
 736          $R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
 737              $h,
 738              $A,
 739              self::substr($sig, 32)
 740          );
 741  
 742          /** @var string $rcheck */
 743          $rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);
 744  
 745          // Close the file handle
 746          fclose($fp);
 747  
 748          // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
 749          ParagonIE_Sodium_Compat::$fastMult = $orig;
 750          return self::verify_32($rcheck, self::substr($sig, 0, 32));
 751      }
 752  
 753      /**
 754       * @param resource $ifp
 755       * @param resource $ofp
 756       * @param int      $mlen
 757       * @param string   $nonce
 758       * @param string   $boxKeypair
 759       * @return bool
 760       * @throws SodiumException
 761       * @throws TypeError
 762       */
 763      protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
 764      {
 765          if (PHP_INT_SIZE === 4) {
 766              return self::secretbox_encrypt(
 767                  $ifp,
 768                  $ofp,
 769                  $mlen,
 770                  $nonce,
 771                  ParagonIE_Sodium_Crypto32::box_beforenm(
 772                      ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
 773                      ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
 774                  )
 775              );
 776          }
 777          return self::secretbox_encrypt(
 778              $ifp,
 779              $ofp,
 780              $mlen,
 781              $nonce,
 782              ParagonIE_Sodium_Crypto::box_beforenm(
 783                  ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
 784                  ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
 785              )
 786          );
 787      }
 788  
 789  
 790      /**
 791       * @param resource $ifp
 792       * @param resource $ofp
 793       * @param int      $mlen
 794       * @param string   $nonce
 795       * @param string   $boxKeypair
 796       * @return bool
 797       * @throws SodiumException
 798       * @throws TypeError
 799       */
 800      protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
 801      {
 802          if (PHP_INT_SIZE === 4) {
 803              return self::secretbox_decrypt(
 804                  $ifp,
 805                  $ofp,
 806                  $mlen,
 807                  $nonce,
 808                  ParagonIE_Sodium_Crypto32::box_beforenm(
 809                      ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
 810                      ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
 811                  )
 812              );
 813          }
 814          return self::secretbox_decrypt(
 815              $ifp,
 816              $ofp,
 817              $mlen,
 818              $nonce,
 819              ParagonIE_Sodium_Crypto::box_beforenm(
 820                  ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
 821                  ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
 822              )
 823          );
 824      }
 825  
 826      /**
 827       * Encrypt a file
 828       *
 829       * @param resource $ifp
 830       * @param resource $ofp
 831       * @param int $mlen
 832       * @param string $nonce
 833       * @param string $key
 834       * @return bool
 835       * @throws SodiumException
 836       * @throws TypeError
 837       */
 838      protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
 839      {
 840          if (PHP_INT_SIZE === 4) {
 841              return self::secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
 842          }
 843  
 844          $plaintext = fread($ifp, 32);
 845          if (!is_string($plaintext)) {
 846              throw new SodiumException('Could not read input file');
 847          }
 848          $first32 = self::ftell($ifp);
 849  
 850          /** @var string $subkey */
 851          $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
 852  
 853          /** @var string $realNonce */
 854          $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
 855  
 856          /** @var string $block0 */
 857          $block0 = str_repeat("\x00", 32);
 858  
 859          /** @var int $mlen - Length of the plaintext message */
 860          $mlen0 = $mlen;
 861          if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
 862              $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
 863          }
 864          $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
 865  
 866          /** @var string $block0 */
 867          $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
 868              $block0,
 869              $realNonce,
 870              $subkey
 871          );
 872  
 873          $state = new ParagonIE_Sodium_Core_Poly1305_State(
 874              ParagonIE_Sodium_Core_Util::substr(
 875                  $block0,
 876                  0,
 877                  ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
 878              )
 879          );
 880  
 881          // Pre-write 16 blank bytes for the Poly1305 tag
 882          $start = self::ftell($ofp);
 883          fwrite($ofp, str_repeat("\x00", 16));
 884  
 885          /** @var string $c */
 886          $cBlock = ParagonIE_Sodium_Core_Util::substr(
 887              $block0,
 888              ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
 889          );
 890          $state->update($cBlock);
 891          fwrite($ofp, $cBlock);
 892          $mlen -= 32;
 893  
 894          /** @var int $iter */
 895          $iter = 1;
 896  
 897          /** @var int $incr */
 898          $incr = self::BUFFER_SIZE >> 6;
 899  
 900          /*
 901           * Set the cursor to the end of the first half-block. All future bytes will
 902           * generated from salsa20_xor_ic, starting from 1 (second block).
 903           */
 904          fseek($ifp, $first32, SEEK_SET);
 905  
 906          while ($mlen > 0) {
 907              $blockSize = $mlen > self::BUFFER_SIZE
 908                  ? self::BUFFER_SIZE
 909                  : $mlen;
 910              $plaintext = fread($ifp, $blockSize);
 911              if (!is_string($plaintext)) {
 912                  throw new SodiumException('Could not read input file');
 913              }
 914              $cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
 915                  $plaintext,
 916                  $realNonce,
 917                  $iter,
 918                  $subkey
 919              );
 920              fwrite($ofp, $cBlock, $blockSize);
 921              $state->update($cBlock);
 922  
 923              $mlen -= $blockSize;
 924              $iter += $incr;
 925          }
 926          try {
 927              ParagonIE_Sodium_Compat::memzero($block0);
 928              ParagonIE_Sodium_Compat::memzero($subkey);
 929          } catch (SodiumException $ex) {
 930              $block0 = null;
 931              $subkey = null;
 932          }
 933          $end = self::ftell($ofp);
 934  
 935          /*
 936           * Write the Poly1305 authentication tag that provides integrity
 937           * over the ciphertext (encrypt-then-MAC)
 938           */
 939          fseek($ofp, $start, SEEK_SET);
 940          fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
 941          fseek($ofp, $end, SEEK_SET);
 942          unset($state);
 943  
 944          return true;
 945      }
 946  
 947      /**
 948       * Decrypt a file
 949       *
 950       * @param resource $ifp
 951       * @param resource $ofp
 952       * @param int $mlen
 953       * @param string $nonce
 954       * @param string $key
 955       * @return bool
 956       * @throws SodiumException
 957       * @throws TypeError
 958       */
 959      protected static function secretbox_decrypt($ifp, $ofp, $mlen, $nonce, $key)
 960      {
 961          if (PHP_INT_SIZE === 4) {
 962              return self::secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
 963          }
 964          $tag = fread($ifp, 16);
 965          if (!is_string($tag)) {
 966              throw new SodiumException('Could not read input file');
 967          }
 968  
 969          /** @var string $subkey */
 970          $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
 971  
 972          /** @var string $realNonce */
 973          $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
 974  
 975          /** @var string $block0 */
 976          $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
 977              64,
 978              ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
 979              $subkey
 980          );
 981  
 982          /* Verify the Poly1305 MAC -before- attempting to decrypt! */
 983          $state = new ParagonIE_Sodium_Core_Poly1305_State(self::substr($block0, 0, 32));
 984          if (!self::onetimeauth_verify($state, $ifp, $tag, $mlen)) {
 985              throw new SodiumException('Invalid MAC');
 986          }
 987  
 988          /*
 989           * Set the cursor to the end of the first half-block. All future bytes will
 990           * generated from salsa20_xor_ic, starting from 1 (second block).
 991           */
 992          $first32 = fread($ifp, 32);
 993          if (!is_string($first32)) {
 994              throw new SodiumException('Could not read input file');
 995          }
 996          $first32len = self::strlen($first32);
 997          fwrite(
 998              $ofp,
 999              self::xorStrings(
1000                  self::substr($block0, 32, $first32len),
1001                  self::substr($first32, 0, $first32len)
1002              )
1003          );
1004          $mlen -= 32;
1005  
1006          /** @var int $iter */
1007          $iter = 1;
1008  
1009          /** @var int $incr */
1010          $incr = self::BUFFER_SIZE >> 6;
1011  
1012          /* Decrypts ciphertext, writes to output file. */
1013          while ($mlen > 0) {
1014              $blockSize = $mlen > self::BUFFER_SIZE
1015                  ? self::BUFFER_SIZE
1016                  : $mlen;
1017              $ciphertext = fread($ifp, $blockSize);
1018              if (!is_string($ciphertext)) {
1019                  throw new SodiumException('Could not read input file');
1020              }
1021              $pBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
1022                  $ciphertext,
1023                  $realNonce,
1024                  $iter,
1025                  $subkey
1026              );
1027              fwrite($ofp, $pBlock, $blockSize);
1028              $mlen -= $blockSize;
1029              $iter += $incr;
1030          }
1031          return true;
1032      }
1033  
1034      /**
1035       * @param ParagonIE_Sodium_Core_Poly1305_State $state
1036       * @param resource $ifp
1037       * @param string $tag
1038       * @param int $mlen
1039       * @return bool
1040       * @throws SodiumException
1041       * @throws TypeError
1042       */
1043      protected static function onetimeauth_verify(
1044          ParagonIE_Sodium_Core_Poly1305_State $state,
1045          $ifp,
1046          $tag = '',
1047          $mlen = 0
1048      ) {
1049          /** @var int $pos */
1050          $pos = self::ftell($ifp);
1051  
1052          /** @var int $iter */
1053          $iter = 1;
1054  
1055          /** @var int $incr */
1056          $incr = self::BUFFER_SIZE >> 6;
1057  
1058          while ($mlen > 0) {
1059              $blockSize = $mlen > self::BUFFER_SIZE
1060                  ? self::BUFFER_SIZE
1061                  : $mlen;
1062              $ciphertext = fread($ifp, $blockSize);
1063              if (!is_string($ciphertext)) {
1064                  throw new SodiumException('Could not read input file');
1065              }
1066              $state->update($ciphertext);
1067              $mlen -= $blockSize;
1068              $iter += $incr;
1069          }
1070          $res = ParagonIE_Sodium_Core_Util::verify_16($tag, $state->finish());
1071  
1072          fseek($ifp, $pos, SEEK_SET);
1073          return $res;
1074      }
1075  
1076      /**
1077       * Update a hash context with the contents of a file, without
1078       * loading the entire file into memory.
1079       *
1080       * @param resource|object $hash
1081       * @param resource $fp
1082       * @param int $size
1083       * @return resource|object Resource on PHP < 7.2, HashContext object on PHP >= 7.2
1084       * @throws SodiumException
1085       * @throws TypeError
1086       * @psalm-suppress PossiblyInvalidArgument
1087       *                 PHP 7.2 changes from a resource to an object,
1088       *                 which causes Psalm to complain about an error.
1089       * @psalm-suppress TypeCoercion
1090       *                 Ditto.
1091       */
1092      public static function updateHashWithFile($hash, $fp, $size = 0)
1093      {
1094          /* Type checks: */
1095          if (PHP_VERSION_ID < 70200) {
1096              if (!is_resource($hash)) {
1097                  throw new TypeError('Argument 1 must be a resource, ' . gettype($hash) . ' given.');
1098              }
1099          } else {
1100              if (!is_object($hash)) {
1101                  throw new TypeError('Argument 1 must be an object (PHP 7.2+), ' . gettype($hash) . ' given.');
1102              }
1103          }
1104  
1105          if (!is_resource($fp)) {
1106              throw new TypeError('Argument 2 must be a resource, ' . gettype($fp) . ' given.');
1107          }
1108          if (!is_int($size)) {
1109              throw new TypeError('Argument 3 must be an integer, ' . gettype($size) . ' given.');
1110          }
1111  
1112          /** @var int $originalPosition */
1113          $originalPosition = self::ftell($fp);
1114  
1115          // Move file pointer to beginning of file
1116          fseek($fp, 0, SEEK_SET);
1117          for ($i = 0; $i < $size; $i += self::BUFFER_SIZE) {
1118              /** @var string|bool $message */
1119              $message = fread(
1120                  $fp,
1121                  ($size - $i) > self::BUFFER_SIZE
1122                      ? $size - $i
1123                      : self::BUFFER_SIZE
1124              );
1125              if (!is_string($message)) {
1126                  throw new SodiumException('Unexpected error reading from file.');
1127              }
1128              /** @var string $message */
1129              /** @psalm-suppress InvalidArgument */
1130              hash_update($hash, $message);
1131          }
1132          // Reset file pointer's position
1133          fseek($fp, $originalPosition, SEEK_SET);
1134          return $hash;
1135      }
1136  
1137      /**
1138       * Sign a file (rather than a string). Uses less memory than
1139       * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
1140       * the same result. (32-bit)
1141       *
1142       * @param string $filePath  Absolute path to a file on the filesystem
1143       * @param string $secretKey Secret signing key
1144       *
1145       * @return string           Ed25519 signature
1146       * @throws SodiumException
1147       * @throws TypeError
1148       */
1149      private static function sign_core32($filePath, $secretKey)
1150      {
1151          /** @var int|bool $size */
1152          $size = filesize($filePath);
1153          if (!is_int($size)) {
1154              throw new SodiumException('Could not obtain the file size');
1155          }
1156          /** @var int $size */
1157  
1158          /** @var resource|bool $fp */
1159          $fp = fopen($filePath, 'rb');
1160          if (!is_resource($fp)) {
1161              throw new SodiumException('Could not open input file for reading');
1162          }
1163          /** @var resource $fp */
1164  
1165          /** @var string $az */
1166          $az = hash('sha512', self::substr($secretKey, 0, 32), true);
1167  
1168          $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
1169          $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
1170  
1171          $hs = hash_init('sha512');
1172          hash_update($hs, self::substr($az, 32, 32));
1173          /** @var resource $hs */
1174          $hs = self::updateHashWithFile($hs, $fp, $size);
1175  
1176          /** @var string $nonceHash */
1177          $nonceHash = hash_final($hs, true);
1178  
1179          /** @var string $pk */
1180          $pk = self::substr($secretKey, 32, 32);
1181  
1182          /** @var string $nonce */
1183          $nonce = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
1184  
1185          /** @var string $sig */
1186          $sig = ParagonIE_Sodium_Core32_Ed25519::ge_p3_tobytes(
1187              ParagonIE_Sodium_Core32_Ed25519::ge_scalarmult_base($nonce)
1188          );
1189  
1190          $hs = hash_init('sha512');
1191          hash_update($hs, self::substr($sig, 0, 32));
1192          hash_update($hs, self::substr($pk, 0, 32));
1193          /** @var resource $hs */
1194          $hs = self::updateHashWithFile($hs, $fp, $size);
1195  
1196          /** @var string $hramHash */
1197          $hramHash = hash_final($hs, true);
1198  
1199          /** @var string $hram */
1200          $hram = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hramHash);
1201  
1202          /** @var string $sigAfter */
1203          $sigAfter = ParagonIE_Sodium_Core32_Ed25519::sc_muladd($hram, $az, $nonce);
1204  
1205          /** @var string $sig */
1206          $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
1207  
1208          try {
1209              ParagonIE_Sodium_Compat::memzero($az);
1210          } catch (SodiumException $ex) {
1211              $az = null;
1212          }
1213          fclose($fp);
1214          return $sig;
1215      }
1216  
1217      /**
1218       *
1219       * Verify a file (rather than a string). Uses less memory than
1220       * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
1221       * produces the same result. (32-bit)
1222       *
1223       * @param string $sig       Ed25519 signature
1224       * @param string $filePath  Absolute path to a file on the filesystem
1225       * @param string $publicKey Signing public key
1226       *
1227       * @return bool
1228       * @throws SodiumException
1229       * @throws Exception
1230       */
1231      public static function verify_core32($sig, $filePath, $publicKey)
1232      {
1233          /* Security checks */
1234          if (ParagonIE_Sodium_Core32_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
1235              throw new SodiumException('S < L - Invalid signature');
1236          }
1237          if (ParagonIE_Sodium_Core32_Ed25519::small_order($sig)) {
1238              throw new SodiumException('Signature is on too small of an order');
1239          }
1240          if ((self::chrToInt($sig[63]) & 224) !== 0) {
1241              throw new SodiumException('Invalid signature');
1242          }
1243          $d = 0;
1244          for ($i = 0; $i < 32; ++$i) {
1245              $d |= self::chrToInt($publicKey[$i]);
1246          }
1247          if ($d === 0) {
1248              throw new SodiumException('All zero public key');
1249          }
1250  
1251          /** @var int|bool $size */
1252          $size = filesize($filePath);
1253          if (!is_int($size)) {
1254              throw new SodiumException('Could not obtain the file size');
1255          }
1256          /** @var int $size */
1257  
1258          /** @var resource|bool $fp */
1259          $fp = fopen($filePath, 'rb');
1260          if (!is_resource($fp)) {
1261              throw new SodiumException('Could not open input file for reading');
1262          }
1263          /** @var resource $fp */
1264  
1265          /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
1266          $orig = ParagonIE_Sodium_Compat::$fastMult;
1267  
1268          // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
1269          ParagonIE_Sodium_Compat::$fastMult = true;
1270  
1271          /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
1272          $A = ParagonIE_Sodium_Core32_Ed25519::ge_frombytes_negate_vartime($publicKey);
1273  
1274          $hs = hash_init('sha512');
1275          hash_update($hs, self::substr($sig, 0, 32));
1276          hash_update($hs, self::substr($publicKey, 0, 32));
1277          /** @var resource $hs */
1278          $hs = self::updateHashWithFile($hs, $fp, $size);
1279          /** @var string $hDigest */
1280          $hDigest = hash_final($hs, true);
1281  
1282          /** @var string $h */
1283          $h = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
1284  
1285          /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
1286          $R = ParagonIE_Sodium_Core32_Ed25519::ge_double_scalarmult_vartime(
1287              $h,
1288              $A,
1289              self::substr($sig, 32)
1290          );
1291  
1292          /** @var string $rcheck */
1293          $rcheck = ParagonIE_Sodium_Core32_Ed25519::ge_tobytes($R);
1294  
1295          // Close the file handle
1296          fclose($fp);
1297  
1298          // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
1299          ParagonIE_Sodium_Compat::$fastMult = $orig;
1300          return self::verify_32($rcheck, self::substr($sig, 0, 32));
1301      }
1302  
1303      /**
1304       * Encrypt a file (32-bit)
1305       *
1306       * @param resource $ifp
1307       * @param resource $ofp
1308       * @param int $mlen
1309       * @param string $nonce
1310       * @param string $key
1311       * @return bool
1312       * @throws SodiumException
1313       * @throws TypeError
1314       */
1315      protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
1316      {
1317          $plaintext = fread($ifp, 32);
1318          if (!is_string($plaintext)) {
1319              throw new SodiumException('Could not read input file');
1320          }
1321          $first32 = self::ftell($ifp);
1322  
1323          /** @var string $subkey */
1324          $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
1325  
1326          /** @var string $realNonce */
1327          $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
1328  
1329          /** @var string $block0 */
1330          $block0 = str_repeat("\x00", 32);
1331  
1332          /** @var int $mlen - Length of the plaintext message */
1333          $mlen0 = $mlen;
1334          if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
1335              $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
1336          }
1337          $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);
1338  
1339          /** @var string $block0 */
1340          $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
1341              $block0,
1342              $realNonce,
1343              $subkey
1344          );
1345  
1346          $state = new ParagonIE_Sodium_Core32_Poly1305_State(
1347              ParagonIE_Sodium_Core32_Util::substr(
1348                  $block0,
1349                  0,
1350                  ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
1351              )
1352          );
1353  
1354          // Pre-write 16 blank bytes for the Poly1305 tag
1355          $start = self::ftell($ofp);
1356          fwrite($ofp, str_repeat("\x00", 16));
1357  
1358          /** @var string $c */
1359          $cBlock = ParagonIE_Sodium_Core32_Util::substr(
1360              $block0,
1361              ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
1362          );
1363          $state->update($cBlock);
1364          fwrite($ofp, $cBlock);
1365          $mlen -= 32;
1366  
1367          /** @var int $iter */
1368          $iter = 1;
1369  
1370          /** @var int $incr */
1371          $incr = self::BUFFER_SIZE >> 6;
1372  
1373          /*
1374           * Set the cursor to the end of the first half-block. All future bytes will
1375           * generated from salsa20_xor_ic, starting from 1 (second block).
1376           */
1377          fseek($ifp, $first32, SEEK_SET);
1378  
1379          while ($mlen > 0) {
1380              $blockSize = $mlen > self::BUFFER_SIZE
1381                  ? self::BUFFER_SIZE
1382                  : $mlen;
1383              $plaintext = fread($ifp, $blockSize);
1384              if (!is_string($plaintext)) {
1385                  throw new SodiumException('Could not read input file');
1386              }
1387              $cBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
1388                  $plaintext,
1389                  $realNonce,
1390                  $iter,
1391                  $subkey
1392              );
1393              fwrite($ofp, $cBlock, $blockSize);
1394              $state->update($cBlock);
1395  
1396              $mlen -= $blockSize;
1397              $iter += $incr;
1398          }
1399          try {
1400              ParagonIE_Sodium_Compat::memzero($block0);
1401              ParagonIE_Sodium_Compat::memzero($subkey);
1402          } catch (SodiumException $ex) {
1403              $block0 = null;
1404              $subkey = null;
1405          }
1406          $end = self::ftell($ofp);
1407  
1408          /*
1409           * Write the Poly1305 authentication tag that provides integrity
1410           * over the ciphertext (encrypt-then-MAC)
1411           */
1412          fseek($ofp, $start, SEEK_SET);
1413          fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
1414          fseek($ofp, $end, SEEK_SET);
1415          unset($state);
1416  
1417          return true;
1418      }
1419  
1420      /**
1421       * Decrypt a file (32-bit)
1422       *
1423       * @param resource $ifp
1424       * @param resource $ofp
1425       * @param int $mlen
1426       * @param string $nonce
1427       * @param string $key
1428       * @return bool
1429       * @throws SodiumException
1430       * @throws TypeError
1431       */
1432      protected static function secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
1433      {
1434          $tag = fread($ifp, 16);
1435          if (!is_string($tag)) {
1436              throw new SodiumException('Could not read input file');
1437          }
1438  
1439          /** @var string $subkey */
1440          $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
1441  
1442          /** @var string $realNonce */
1443          $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
1444  
1445          /** @var string $block0 */
1446          $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
1447              64,
1448              ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
1449              $subkey
1450          );
1451  
1452          /* Verify the Poly1305 MAC -before- attempting to decrypt! */
1453          $state = new ParagonIE_Sodium_Core32_Poly1305_State(self::substr($block0, 0, 32));
1454          if (!self::onetimeauth_verify_core32($state, $ifp, $tag, $mlen)) {
1455              throw new SodiumException('Invalid MAC');
1456          }
1457  
1458          /*
1459           * Set the cursor to the end of the first half-block. All future bytes will
1460           * generated from salsa20_xor_ic, starting from 1 (second block).
1461           */
1462          $first32 = fread($ifp, 32);
1463          if (!is_string($first32)) {
1464              throw new SodiumException('Could not read input file');
1465          }
1466          $first32len = self::strlen($first32);
1467          fwrite(
1468              $ofp,
1469              self::xorStrings(
1470                  self::substr($block0, 32, $first32len),
1471                  self::substr($first32, 0, $first32len)
1472              )
1473          );
1474          $mlen -= 32;
1475  
1476          /** @var int $iter */
1477          $iter = 1;
1478  
1479          /** @var int $incr */
1480          $incr = self::BUFFER_SIZE >> 6;
1481  
1482          /* Decrypts ciphertext, writes to output file. */
1483          while ($mlen > 0) {
1484              $blockSize = $mlen > self::BUFFER_SIZE
1485                  ? self::BUFFER_SIZE
1486                  : $mlen;
1487              $ciphertext = fread($ifp, $blockSize);
1488              if (!is_string($ciphertext)) {
1489                  throw new SodiumException('Could not read input file');
1490              }
1491              $pBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
1492                  $ciphertext,
1493                  $realNonce,
1494                  $iter,
1495                  $subkey
1496              );
1497              fwrite($ofp, $pBlock, $blockSize);
1498              $mlen -= $blockSize;
1499              $iter += $incr;
1500          }
1501          return true;
1502      }
1503  
1504      /**
1505       * One-time message authentication for 32-bit systems
1506       *
1507       * @param ParagonIE_Sodium_Core32_Poly1305_State $state
1508       * @param resource $ifp
1509       * @param string $tag
1510       * @param int $mlen
1511       * @return bool
1512       * @throws SodiumException
1513       * @throws TypeError
1514       */
1515      protected static function onetimeauth_verify_core32(
1516          ParagonIE_Sodium_Core32_Poly1305_State $state,
1517          $ifp,
1518          $tag = '',
1519          $mlen = 0
1520      ) {
1521          /** @var int $pos */
1522          $pos = self::ftell($ifp);
1523  
1524          /** @var int $iter */
1525          $iter = 1;
1526  
1527          /** @var int $incr */
1528          $incr = self::BUFFER_SIZE >> 6;
1529  
1530          while ($mlen > 0) {
1531              $blockSize = $mlen > self::BUFFER_SIZE
1532                  ? self::BUFFER_SIZE
1533                  : $mlen;
1534              $ciphertext = fread($ifp, $blockSize);
1535              if (!is_string($ciphertext)) {
1536                  throw new SodiumException('Could not read input file');
1537              }
1538              $state->update($ciphertext);
1539              $mlen -= $blockSize;
1540              $iter += $incr;
1541          }
1542          $res = ParagonIE_Sodium_Core32_Util::verify_16($tag, $state->finish());
1543  
1544          fseek($ifp, $pos, SEEK_SET);
1545          return $res;
1546      }
1547  
1548      /**
1549       * @param resource $resource
1550       * @return int
1551       * @throws SodiumException
1552       */
1553      private static function ftell($resource)
1554      {
1555          $return = ftell($resource);
1556          if (!is_int($return)) {
1557              throw new SodiumException('ftell() returned false');
1558          }
1559          return (int) $return;
1560      }
1561  }


Generated: Wed Dec 11 01:00:03 2019 Cross-referenced by PHPXref 0.7.1