wp/wp-includes/sodium_compat/src/File.php
changeset 9 177826044cd9
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
       
     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 }