[ 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 (ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
 683              throw new SodiumException('S < L - Invalid signature');
 684          }
 685          if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) {
 686              throw new SodiumException('Signature is on too small of an order');
 687          }
 688          if ((self::chrToInt($sig[63]) & 224) !== 0) {
 689              throw new SodiumException('Invalid signature');
 690          }
 691          $d = 0;
 692          for ($i = 0; $i < 32; ++$i) {
 693              $d |= self::chrToInt($publicKey[$i]);
 694          }
 695          if ($d === 0) {
 696              throw new SodiumException('All zero public key');
 697          }
 698  
 699          /** @var int $size */
 700          $size = filesize($filePath);
 701          if (!is_int($size)) {
 702              throw new SodiumException('Could not obtain the file size');
 703          }
 704  
 705          /** @var resource $fp */
 706          $fp = fopen($filePath, 'rb');
 707          if (!is_resource($fp)) {
 708              throw new SodiumException('Could not open input file for reading');
 709          }
 710  
 711          /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
 712          $orig = ParagonIE_Sodium_Compat::$fastMult;
 713  
 714          // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
 715          ParagonIE_Sodium_Compat::$fastMult = true;
 716  
 717          /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
 718          $A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);
 719  
 720          $hs = hash_init('sha512');
 721          hash_update($hs, self::substr($sig, 0, 32));
 722          hash_update($hs, self::substr($publicKey, 0, 32));
 723          /** @var resource $hs */
 724          $hs = self::updateHashWithFile($hs, $fp, $size);
 725          /** @var string $hDigest */
 726          $hDigest = hash_final($hs, true);
 727  
 728          /** @var string $h */
 729          $h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
 730  
 731          /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
 732          $R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
 733              $h,
 734              $A,
 735              self::substr($sig, 32)
 736          );
 737  
 738          /** @var string $rcheck */
 739          $rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);
 740  
 741          // Close the file handle
 742          fclose($fp);
 743  
 744          // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
 745          ParagonIE_Sodium_Compat::$fastMult = $orig;
 746          return self::verify_32($rcheck, self::substr($sig, 0, 32));
 747      }
 748  
 749      /**
 750       * @param resource $ifp
 751       * @param resource $ofp
 752       * @param int      $mlen
 753       * @param string   $nonce
 754       * @param string   $boxKeypair
 755       * @return bool
 756       * @throws SodiumException
 757       * @throws TypeError
 758       */
 759      protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
 760      {
 761          if (PHP_INT_SIZE === 4) {
 762              return self::secretbox_encrypt(
 763                  $ifp,
 764                  $ofp,
 765                  $mlen,
 766                  $nonce,
 767                  ParagonIE_Sodium_Crypto32::box_beforenm(
 768                      ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
 769                      ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
 770                  )
 771              );
 772          }
 773          return self::secretbox_encrypt(
 774              $ifp,
 775              $ofp,
 776              $mlen,
 777              $nonce,
 778              ParagonIE_Sodium_Crypto::box_beforenm(
 779                  ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
 780                  ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
 781              )
 782          );
 783      }
 784  
 785  
 786      /**
 787       * @param resource $ifp
 788       * @param resource $ofp
 789       * @param int      $mlen
 790       * @param string   $nonce
 791       * @param string   $boxKeypair
 792       * @return bool
 793       * @throws SodiumException
 794       * @throws TypeError
 795       */
 796      protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
 797      {
 798          if (PHP_INT_SIZE === 4) {
 799              return self::secretbox_decrypt(
 800                  $ifp,
 801                  $ofp,
 802                  $mlen,
 803                  $nonce,
 804                  ParagonIE_Sodium_Crypto32::box_beforenm(
 805                      ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
 806                      ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
 807                  )
 808              );
 809          }
 810          return self::secretbox_decrypt(
 811              $ifp,
 812              $ofp,
 813              $mlen,
 814              $nonce,
 815              ParagonIE_Sodium_Crypto::box_beforenm(
 816                  ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
 817                  ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
 818              )
 819          );
 820      }
 821  
 822      /**
 823       * Encrypt a file
 824       *
 825       * @param resource $ifp
 826       * @param resource $ofp
 827       * @param int $mlen
 828       * @param string $nonce
 829       * @param string $key
 830       * @return bool
 831       * @throws SodiumException
 832       * @throws TypeError
 833       */
 834      protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
 835      {
 836          if (PHP_INT_SIZE === 4) {
 837              return self::secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
 838          }
 839  
 840          $plaintext = fread($ifp, 32);
 841          if (!is_string($plaintext)) {
 842              throw new SodiumException('Could not read input file');
 843          }
 844          $first32 = ftell($ifp);
 845  
 846          /** @var string $subkey */
 847          $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
 848  
 849          /** @var string $realNonce */
 850          $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
 851  
 852          /** @var string $block0 */
 853          $block0 = str_repeat("\x00", 32);
 854  
 855          /** @var int $mlen - Length of the plaintext message */
 856          $mlen0 = $mlen;
 857          if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
 858              $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
 859          }
 860          $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
 861  
 862          /** @var string $block0 */
 863          $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
 864              $block0,
 865              $realNonce,
 866              $subkey
 867          );
 868  
 869          $state = new ParagonIE_Sodium_Core_Poly1305_State(
 870              ParagonIE_Sodium_Core_Util::substr(
 871                  $block0,
 872                  0,
 873                  ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
 874              )
 875          );
 876  
 877          // Pre-write 16 blank bytes for the Poly1305 tag
 878          $start = ftell($ofp);
 879          fwrite($ofp, str_repeat("\x00", 16));
 880  
 881          /** @var string $c */
 882          $cBlock = ParagonIE_Sodium_Core_Util::substr(
 883              $block0,
 884              ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
 885          );
 886          $state->update($cBlock);
 887          fwrite($ofp, $cBlock);
 888          $mlen -= 32;
 889  
 890          /** @var int $iter */
 891          $iter = 1;
 892  
 893          /** @var int $incr */
 894          $incr = self::BUFFER_SIZE >> 6;
 895  
 896          /*
 897           * Set the cursor to the end of the first half-block. All future bytes will
 898           * generated from salsa20_xor_ic, starting from 1 (second block).
 899           */
 900          fseek($ifp, $first32, SEEK_SET);
 901  
 902          while ($mlen > 0) {
 903              $blockSize = $mlen > self::BUFFER_SIZE
 904                  ? self::BUFFER_SIZE
 905                  : $mlen;
 906              $plaintext = fread($ifp, $blockSize);
 907              if (!is_string($plaintext)) {
 908                  throw new SodiumException('Could not read input file');
 909              }
 910              $cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
 911                  $plaintext,
 912                  $realNonce,
 913                  $iter,
 914                  $subkey
 915              );
 916              fwrite($ofp, $cBlock, $blockSize);
 917              $state->update($cBlock);
 918  
 919              $mlen -= $blockSize;
 920              $iter += $incr;
 921          }
 922          try {
 923              ParagonIE_Sodium_Compat::memzero($block0);
 924              ParagonIE_Sodium_Compat::memzero($subkey);
 925          } catch (SodiumException $ex) {
 926              $block0 = null;
 927              $subkey = null;
 928          }
 929          $end = ftell($ofp);
 930  
 931          /*
 932           * Write the Poly1305 authentication tag that provides integrity
 933           * over the ciphertext (encrypt-then-MAC)
 934           */
 935          fseek($ofp, $start, SEEK_SET);
 936          fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
 937          fseek($ofp, $end, SEEK_SET);
 938          unset($state);
 939  
 940          return true;
 941      }
 942  
 943      /**
 944       * Decrypt a file
 945       *
 946       * @param resource $ifp
 947       * @param resource $ofp
 948       * @param int $mlen
 949       * @param string $nonce
 950       * @param string $key
 951       * @return bool
 952       * @throws SodiumException
 953       * @throws TypeError
 954       */
 955      protected static function secretbox_decrypt($ifp, $ofp, $mlen, $nonce, $key)
 956      {
 957          if (PHP_INT_SIZE === 4) {
 958              return self::secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
 959          }
 960          $tag = fread($ifp, 16);
 961          if (!is_string($tag)) {
 962              throw new SodiumException('Could not read input file');
 963          }
 964  
 965          /** @var string $subkey */
 966          $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
 967  
 968          /** @var string $realNonce */
 969          $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
 970  
 971          /** @var string $block0 */
 972          $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
 973              64,
 974              ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
 975              $subkey
 976          );
 977  
 978          /* Verify the Poly1305 MAC -before- attempting to decrypt! */
 979          $state = new ParagonIE_Sodium_Core_Poly1305_State(self::substr($block0, 0, 32));
 980          if (!self::onetimeauth_verify($state, $ifp, $tag, $mlen)) {
 981              throw new SodiumException('Invalid MAC');
 982          }
 983  
 984          /*
 985           * Set the cursor to the end of the first half-block. All future bytes will
 986           * generated from salsa20_xor_ic, starting from 1 (second block).
 987           */
 988          $first32 = fread($ifp, 32);
 989          if (!is_string($first32)) {
 990              throw new SodiumException('Could not read input file');
 991          }
 992          $first32len = self::strlen($first32);
 993          fwrite(
 994              $ofp,
 995              self::xorStrings(
 996                  self::substr($block0, 32, $first32len),
 997                  self::substr($first32, 0, $first32len)
 998              )
 999          );
1000          $mlen -= 32;
1001  
1002          /** @var int $iter */
1003          $iter = 1;
1004  
1005          /** @var int $incr */
1006          $incr = self::BUFFER_SIZE >> 6;
1007  
1008          /* Decrypts ciphertext, writes to output file. */
1009          while ($mlen > 0) {
1010              $blockSize = $mlen > self::BUFFER_SIZE
1011                  ? self::BUFFER_SIZE
1012                  : $mlen;
1013              $ciphertext = fread($ifp, $blockSize);
1014              if (!is_string($ciphertext)) {
1015                  throw new SodiumException('Could not read input file');
1016              }
1017              $pBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
1018                  $ciphertext,
1019                  $realNonce,
1020                  $iter,
1021                  $subkey
1022              );
1023              fwrite($ofp, $pBlock, $blockSize);
1024              $mlen -= $blockSize;
1025              $iter += $incr;
1026          }
1027          return true;
1028      }
1029  
1030      /**
1031       * @param ParagonIE_Sodium_Core_Poly1305_State $state
1032       * @param resource $ifp
1033       * @param string $tag
1034       * @param int $mlen
1035       * @return bool
1036       * @throws SodiumException
1037       * @throws TypeError
1038       */
1039      protected static function onetimeauth_verify(
1040          ParagonIE_Sodium_Core_Poly1305_State $state,
1041          $ifp,
1042          $tag = '',
1043          $mlen = 0
1044      ) {
1045          /** @var int $pos */
1046          $pos = ftell($ifp);
1047  
1048          /** @var int $iter */
1049          $iter = 1;
1050  
1051          /** @var int $incr */
1052          $incr = self::BUFFER_SIZE >> 6;
1053  
1054          while ($mlen > 0) {
1055              $blockSize = $mlen > self::BUFFER_SIZE
1056                  ? self::BUFFER_SIZE
1057                  : $mlen;
1058              $ciphertext = fread($ifp, $blockSize);
1059              if (!is_string($ciphertext)) {
1060                  throw new SodiumException('Could not read input file');
1061              }
1062              $state->update($ciphertext);
1063              $mlen -= $blockSize;
1064              $iter += $incr;
1065          }
1066          $res = ParagonIE_Sodium_Core_Util::verify_16($tag, $state->finish());
1067  
1068          fseek($ifp, $pos, SEEK_SET);
1069          return $res;
1070      }
1071  
1072      /**
1073       * Update a hash context with the contents of a file, without
1074       * loading the entire file into memory.
1075       *
1076       * @param resource|object $hash
1077       * @param resource $fp
1078       * @param int $size
1079       * @return resource|object Resource on PHP < 7.2, HashContext object on PHP >= 7.2
1080       * @throws SodiumException
1081       * @throws TypeError
1082       * @psalm-suppress PossiblyInvalidArgument
1083       *                 PHP 7.2 changes from a resource to an object,
1084       *                 which causes Psalm to complain about an error.
1085       * @psalm-suppress TypeCoercion
1086       *                 Ditto.
1087       */
1088      public static function updateHashWithFile($hash, $fp, $size = 0)
1089      {
1090          /* Type checks: */
1091          if (PHP_VERSION_ID < 70200) {
1092              if (!is_resource($hash)) {
1093                  throw new TypeError('Argument 1 must be a resource, ' . gettype($hash) . ' given.');
1094              }
1095          } else {
1096              if (!is_object($hash)) {
1097                  throw new TypeError('Argument 1 must be an object (PHP 7.2+), ' . gettype($hash) . ' given.');
1098              }
1099          }
1100  
1101          if (!is_resource($fp)) {
1102              throw new TypeError('Argument 2 must be a resource, ' . gettype($fp) . ' given.');
1103          }
1104          if (!is_int($size)) {
1105              throw new TypeError('Argument 3 must be an integer, ' . gettype($size) . ' given.');
1106          }
1107  
1108          /** @var int $originalPosition */
1109          $originalPosition = ftell($fp);
1110  
1111          // Move file pointer to beginning of file
1112          fseek($fp, 0, SEEK_SET);
1113          for ($i = 0; $i < $size; $i += self::BUFFER_SIZE) {
1114              /** @var string|bool $message */
1115              $message = fread(
1116                  $fp,
1117                  ($size - $i) > self::BUFFER_SIZE
1118                      ? $size - $i
1119                      : self::BUFFER_SIZE
1120              );
1121              if (!is_string($message)) {
1122                  throw new SodiumException('Unexpected error reading from file.');
1123              }
1124              /** @var string $message */
1125              /** @psalm-suppress InvalidArgument */
1126              hash_update($hash, $message);
1127          }
1128          // Reset file pointer's position
1129          fseek($fp, $originalPosition, SEEK_SET);
1130          return $hash;
1131      }
1132  
1133      /**
1134       * Sign a file (rather than a string). Uses less memory than
1135       * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
1136       * the same result. (32-bit)
1137       *
1138       * @param string $filePath  Absolute path to a file on the filesystem
1139       * @param string $secretKey Secret signing key
1140       *
1141       * @return string           Ed25519 signature
1142       * @throws SodiumException
1143       * @throws TypeError
1144       */
1145      private static function sign_core32($filePath, $secretKey)
1146      {
1147          /** @var int|bool $size */
1148          $size = filesize($filePath);
1149          if (!is_int($size)) {
1150              throw new SodiumException('Could not obtain the file size');
1151          }
1152          /** @var int $size */
1153  
1154          /** @var resource|bool $fp */
1155          $fp = fopen($filePath, 'rb');
1156          if (!is_resource($fp)) {
1157              throw new SodiumException('Could not open input file for reading');
1158          }
1159          /** @var resource $fp */
1160  
1161          /** @var string $az */
1162          $az = hash('sha512', self::substr($secretKey, 0, 32), true);
1163  
1164          $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
1165          $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
1166  
1167          $hs = hash_init('sha512');
1168          hash_update($hs, self::substr($az, 32, 32));
1169          /** @var resource $hs */
1170          $hs = self::updateHashWithFile($hs, $fp, $size);
1171  
1172          /** @var string $nonceHash */
1173          $nonceHash = hash_final($hs, true);
1174  
1175          /** @var string $pk */
1176          $pk = self::substr($secretKey, 32, 32);
1177  
1178          /** @var string $nonce */
1179          $nonce = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
1180  
1181          /** @var string $sig */
1182          $sig = ParagonIE_Sodium_Core32_Ed25519::ge_p3_tobytes(
1183              ParagonIE_Sodium_Core32_Ed25519::ge_scalarmult_base($nonce)
1184          );
1185  
1186          $hs = hash_init('sha512');
1187          hash_update($hs, self::substr($sig, 0, 32));
1188          hash_update($hs, self::substr($pk, 0, 32));
1189          /** @var resource $hs */
1190          $hs = self::updateHashWithFile($hs, $fp, $size);
1191  
1192          /** @var string $hramHash */
1193          $hramHash = hash_final($hs, true);
1194  
1195          /** @var string $hram */
1196          $hram = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hramHash);
1197  
1198          /** @var string $sigAfter */
1199          $sigAfter = ParagonIE_Sodium_Core32_Ed25519::sc_muladd($hram, $az, $nonce);
1200  
1201          /** @var string $sig */
1202          $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
1203  
1204          try {
1205              ParagonIE_Sodium_Compat::memzero($az);
1206          } catch (SodiumException $ex) {
1207              $az = null;
1208          }
1209          fclose($fp);
1210          return $sig;
1211      }
1212  
1213      /**
1214       *
1215       * Verify a file (rather than a string). Uses less memory than
1216       * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
1217       * produces the same result. (32-bit)
1218       *
1219       * @param string $sig       Ed25519 signature
1220       * @param string $filePath  Absolute path to a file on the filesystem
1221       * @param string $publicKey Signing public key
1222       *
1223       * @return bool
1224       * @throws SodiumException
1225       * @throws Exception
1226       */
1227      public static function verify_core32($sig, $filePath, $publicKey)
1228      {
1229          /* Security checks */
1230          if (ParagonIE_Sodium_Core32_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
1231              throw new SodiumException('S < L - Invalid signature');
1232          }
1233          if (ParagonIE_Sodium_Core32_Ed25519::small_order($sig)) {
1234              throw new SodiumException('Signature is on too small of an order');
1235          }
1236          if ((self::chrToInt($sig[63]) & 224) !== 0) {
1237              throw new SodiumException('Invalid signature');
1238          }
1239          $d = 0;
1240          for ($i = 0; $i < 32; ++$i) {
1241              $d |= self::chrToInt($publicKey[$i]);
1242          }
1243          if ($d === 0) {
1244              throw new SodiumException('All zero public key');
1245          }
1246  
1247          /** @var int|bool $size */
1248          $size = filesize($filePath);
1249          if (!is_int($size)) {
1250              throw new SodiumException('Could not obtain the file size');
1251          }
1252          /** @var int $size */
1253  
1254          /** @var resource|bool $fp */
1255          $fp = fopen($filePath, 'rb');
1256          if (!is_resource($fp)) {
1257              throw new SodiumException('Could not open input file for reading');
1258          }
1259          /** @var resource $fp */
1260  
1261          /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
1262          $orig = ParagonIE_Sodium_Compat::$fastMult;
1263  
1264          // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
1265          ParagonIE_Sodium_Compat::$fastMult = true;
1266  
1267          /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
1268          $A = ParagonIE_Sodium_Core32_Ed25519::ge_frombytes_negate_vartime($publicKey);
1269  
1270          $hs = hash_init('sha512');
1271          hash_update($hs, self::substr($sig, 0, 32));
1272          hash_update($hs, self::substr($publicKey, 0, 32));
1273          /** @var resource $hs */
1274          $hs = self::updateHashWithFile($hs, $fp, $size);
1275          /** @var string $hDigest */
1276          $hDigest = hash_final($hs, true);
1277  
1278          /** @var string $h */
1279          $h = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
1280  
1281          /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
1282          $R = ParagonIE_Sodium_Core32_Ed25519::ge_double_scalarmult_vartime(
1283              $h,
1284              $A,
1285              self::substr($sig, 32)
1286          );
1287  
1288          /** @var string $rcheck */
1289          $rcheck = ParagonIE_Sodium_Core32_Ed25519::ge_tobytes($R);
1290  
1291          // Close the file handle
1292          fclose($fp);
1293  
1294          // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
1295          ParagonIE_Sodium_Compat::$fastMult = $orig;
1296          return self::verify_32($rcheck, self::substr($sig, 0, 32));
1297      }
1298  
1299      /**
1300       * Encrypt a file (32-bit)
1301       *
1302       * @param resource $ifp
1303       * @param resource $ofp
1304       * @param int $mlen
1305       * @param string $nonce
1306       * @param string $key
1307       * @return bool
1308       * @throws SodiumException
1309       * @throws TypeError
1310       */
1311      protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
1312      {
1313          $plaintext = fread($ifp, 32);
1314          if (!is_string($plaintext)) {
1315              throw new SodiumException('Could not read input file');
1316          }
1317          $first32 = ftell($ifp);
1318  
1319          /** @var string $subkey */
1320          $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
1321  
1322          /** @var string $realNonce */
1323          $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
1324  
1325          /** @var string $block0 */
1326          $block0 = str_repeat("\x00", 32);
1327  
1328          /** @var int $mlen - Length of the plaintext message */
1329          $mlen0 = $mlen;
1330          if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
1331              $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
1332          }
1333          $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);
1334  
1335          /** @var string $block0 */
1336          $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
1337              $block0,
1338              $realNonce,
1339              $subkey
1340          );
1341  
1342          $state = new ParagonIE_Sodium_Core32_Poly1305_State(
1343              ParagonIE_Sodium_Core32_Util::substr(
1344                  $block0,
1345                  0,
1346                  ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
1347              )
1348          );
1349  
1350          // Pre-write 16 blank bytes for the Poly1305 tag
1351          $start = ftell($ofp);
1352          fwrite($ofp, str_repeat("\x00", 16));
1353  
1354          /** @var string $c */
1355          $cBlock = ParagonIE_Sodium_Core32_Util::substr(
1356              $block0,
1357              ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
1358          );
1359          $state->update($cBlock);
1360          fwrite($ofp, $cBlock);
1361          $mlen -= 32;
1362  
1363          /** @var int $iter */
1364          $iter = 1;
1365  
1366          /** @var int $incr */
1367          $incr = self::BUFFER_SIZE >> 6;
1368  
1369          /*
1370           * Set the cursor to the end of the first half-block. All future bytes will
1371           * generated from salsa20_xor_ic, starting from 1 (second block).
1372           */
1373          fseek($ifp, $first32, SEEK_SET);
1374  
1375          while ($mlen > 0) {
1376              $blockSize = $mlen > self::BUFFER_SIZE
1377                  ? self::BUFFER_SIZE
1378                  : $mlen;
1379              $plaintext = fread($ifp, $blockSize);
1380              if (!is_string($plaintext)) {
1381                  throw new SodiumException('Could not read input file');
1382              }
1383              $cBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
1384                  $plaintext,
1385                  $realNonce,
1386                  $iter,
1387                  $subkey
1388              );
1389              fwrite($ofp, $cBlock, $blockSize);
1390              $state->update($cBlock);
1391  
1392              $mlen -= $blockSize;
1393              $iter += $incr;
1394          }
1395          try {
1396              ParagonIE_Sodium_Compat::memzero($block0);
1397              ParagonIE_Sodium_Compat::memzero($subkey);
1398          } catch (SodiumException $ex) {
1399              $block0 = null;
1400              $subkey = null;
1401          }
1402          $end = ftell($ofp);
1403  
1404          /*
1405           * Write the Poly1305 authentication tag that provides integrity
1406           * over the ciphertext (encrypt-then-MAC)
1407           */
1408          fseek($ofp, $start, SEEK_SET);
1409          fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
1410          fseek($ofp, $end, SEEK_SET);
1411          unset($state);
1412  
1413          return true;
1414      }
1415  
1416      /**
1417       * Decrypt a file (32-bit)
1418       *
1419       * @param resource $ifp
1420       * @param resource $ofp
1421       * @param int $mlen
1422       * @param string $nonce
1423       * @param string $key
1424       * @return bool
1425       * @throws SodiumException
1426       * @throws TypeError
1427       */
1428      protected static function secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
1429      {
1430          $tag = fread($ifp, 16);
1431          if (!is_string($tag)) {
1432              throw new SodiumException('Could not read input file');
1433          }
1434  
1435          /** @var string $subkey */
1436          $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
1437  
1438          /** @var string $realNonce */
1439          $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
1440  
1441          /** @var string $block0 */
1442          $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
1443              64,
1444              ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
1445              $subkey
1446          );
1447  
1448          /* Verify the Poly1305 MAC -before- attempting to decrypt! */
1449          $state = new ParagonIE_Sodium_Core32_Poly1305_State(self::substr($block0, 0, 32));
1450          if (!self::onetimeauth_verify_core32($state, $ifp, $tag, $mlen)) {
1451              throw new SodiumException('Invalid MAC');
1452          }
1453  
1454          /*
1455           * Set the cursor to the end of the first half-block. All future bytes will
1456           * generated from salsa20_xor_ic, starting from 1 (second block).
1457           */
1458          $first32 = fread($ifp, 32);
1459          if (!is_string($first32)) {
1460              throw new SodiumException('Could not read input file');
1461          }
1462          $first32len = self::strlen($first32);
1463          fwrite(
1464              $ofp,
1465              self::xorStrings(
1466                  self::substr($block0, 32, $first32len),
1467                  self::substr($first32, 0, $first32len)
1468              )
1469          );
1470          $mlen -= 32;
1471  
1472          /** @var int $iter */
1473          $iter = 1;
1474  
1475          /** @var int $incr */
1476          $incr = self::BUFFER_SIZE >> 6;
1477  
1478          /* Decrypts ciphertext, writes to output file. */
1479          while ($mlen > 0) {
1480              $blockSize = $mlen > self::BUFFER_SIZE
1481                  ? self::BUFFER_SIZE
1482                  : $mlen;
1483              $ciphertext = fread($ifp, $blockSize);
1484              if (!is_string($ciphertext)) {
1485                  throw new SodiumException('Could not read input file');
1486              }
1487              $pBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
1488                  $ciphertext,
1489                  $realNonce,
1490                  $iter,
1491                  $subkey
1492              );
1493              fwrite($ofp, $pBlock, $blockSize);
1494              $mlen -= $blockSize;
1495              $iter += $incr;
1496          }
1497          return true;
1498      }
1499  
1500      /**
1501       * One-time message authentication for 32-bit systems
1502       *
1503       * @param ParagonIE_Sodium_Core32_Poly1305_State $state
1504       * @param resource $ifp
1505       * @param string $tag
1506       * @param int $mlen
1507       * @return bool
1508       * @throws SodiumException
1509       * @throws TypeError
1510       */
1511      protected static function onetimeauth_verify_core32(
1512          ParagonIE_Sodium_Core32_Poly1305_State $state,
1513          $ifp,
1514          $tag = '',
1515          $mlen = 0
1516      ) {
1517          /** @var int $pos */
1518          $pos = ftell($ifp);
1519  
1520          /** @var int $iter */
1521          $iter = 1;
1522  
1523          /** @var int $incr */
1524          $incr = self::BUFFER_SIZE >> 6;
1525  
1526          while ($mlen > 0) {
1527              $blockSize = $mlen > self::BUFFER_SIZE
1528                  ? self::BUFFER_SIZE
1529                  : $mlen;
1530              $ciphertext = fread($ifp, $blockSize);
1531              if (!is_string($ciphertext)) {
1532                  throw new SodiumException('Could not read input file');
1533              }
1534              $state->update($ciphertext);
1535              $mlen -= $blockSize;
1536              $iter += $incr;
1537          }
1538          $res = ParagonIE_Sodium_Core32_Util::verify_16($tag, $state->finish());
1539  
1540          fseek($ifp, $pos, SEEK_SET);
1541          return $res;
1542      }
1543  }


Generated: Sat Sep 21 01:00:03 2019 Cross-referenced by PHPXref 0.7.1