Diff: STRATO-apps/wordpress_03/app/wp-includes/sodium_compat/src/File.php

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