Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/paragonie/halite
Browse files Browse the repository at this point in the history
  • Loading branch information
paragonie-security committed Sep 12, 2019
2 parents 67f9379 + c3cf49a commit be87703
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 19 deletions.
48 changes: 37 additions & 11 deletions src/Stream/ReadOnlyFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function __construct($file, Key $key = null)

$this->closeAfter = true;
$this->pos = 0;
$this->stat = \fstat($this->fp);
$this->stat = $this->fstat();
} elseif (\is_resource($file)) {
/** @var array<string, string> $metadata */
$metadata = \stream_get_meta_data($file);
Expand All @@ -106,7 +106,7 @@ public function __construct($file, Key $key = null)
}
$this->fp = $file;
$this->pos = \ftell($this->fp);
$this->stat = \fstat($this->fp);
$this->stat = $this->fstat();
} else {
throw new InvalidType(
'Argument 1: Expected a filename or resource'
Expand Down Expand Up @@ -148,6 +148,10 @@ public function close(): void
*/
public function getHash(): string
{
if ($this->hash) {
$this->toctouTest();
return $this->hash;
}
$init = $this->pos;
\fseek($this->fp, 0, SEEK_SET);

Expand Down Expand Up @@ -176,7 +180,7 @@ public function getHash(): string

/**
* Where are we in the buffer?
*
*
* @return int
*/
public function getPos(): int
Expand All @@ -186,7 +190,7 @@ public function getPos(): int

/**
* How big is this buffer?
*
*
* @return int
*/
public function getSize(): int
Expand All @@ -203,13 +207,13 @@ public function getStreamMetadata(): array
{
return \stream_get_meta_data($this->fp);
}

/**
* Read from a stream; prevent partial reads (also uses run-time testing to
* prevent partial reads -- you can turn this off if you need performance
* and aren't concerned about race condition attacks, but this isn't a
* decision to make lightly!)
*
*
* @param int $num
* @param bool $skipTests Only set this to TRUE if you're absolutely sure
* that you don't want to defend against TOCTOU /
Expand Down Expand Up @@ -258,10 +262,10 @@ public function readBytes(int $num, bool $skipTests = false): string
} while ($remaining > 0);
return $buf;
}

/**
* Get number of bytes remaining
*
*
* @return int
*/
public function remainingBytes(): int
Expand Down Expand Up @@ -310,17 +314,17 @@ public function toctouTest()
);
// @codeCoverageIgnoreEnd
}
$stat = \fstat($this->fp);
$stat = $this->fstat();
if ($stat['size'] !== $this->stat['size']) {
throw new FileModified(
'Read-only file has been modified since it was opened for reading'
);
}
}

/**
* This is a meaningless operation for a Read-Only File!
*
*
* @param string $buf
* @param int $num (number of bytes)
* @return int
Expand All @@ -334,4 +338,26 @@ public function writeBytes(string $buf, int $num = null): int
'This is a read-only file handle.'
);
}

/**
* Wraps fstat to allow calculation of file-size on stream wrappers.
*
* @return array
*/
private function fstat() : array {
$stat = \fstat($this->fp);
if ($stat) {
return $stat;
}
// The resource is remote or a stream wrapper like php://input
$stat = [
'size' => 0,
];
\fseek($this->fp, 0);
while (!feof($this->fp)) {
$stat['size'] += \strlen(\fread($this->fp, 8192));
}
\fseek($this->fp, $this->pos);
return $stat;
}
}
8 changes: 4 additions & 4 deletions stub/Sodium.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -967,14 +967,14 @@ function compare(
/**
* Convert from hex without side-chanels
*
* @param string $binary
* @param string $hex
* @return string
*/
function hex2bin(
string $binary
string $hex
): string {
if (\extension_loaded('sodium')) {
return \sodium_hex2bin($binary);
return \sodium_hex2bin($hex);
}
return '';
}
Expand Down Expand Up @@ -1089,4 +1089,4 @@ function crypto_scalarmult_base(
}
return '';
}
}
}
8 changes: 4 additions & 4 deletions stub/sodium_stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -970,14 +970,14 @@ function sodium_compare(
/**
* Convert from hex without side-chanels
*
* @param string $binary
* @param string $hex
* @return string
*/
function sodium_hex2bin(
string $binary
string $hex
): string {
if (\extension_loaded('libsodium')) {
return \Sodium\hex2bin($binary);
return \Sodium\hex2bin($hex);
}
return '';
}
Expand Down Expand Up @@ -1092,4 +1092,4 @@ function sodium_crypto_scalarmult_base(
}
return '';
}
}
}
47 changes: 47 additions & 0 deletions test/unit/FileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,53 @@ public function testSeal()
unlink(__DIR__.'/tmp/paragon_avatar.opened.png');
}

/**
* @throws CryptoException\CannotPerformOperation
* @throws CryptoException\FileAccessDenied
* @throws CryptoException\FileError
* @throws CryptoException\FileModified
* @throws CryptoException\InvalidDigestLength
* @throws CryptoException\InvalidMessage
* @throws CryptoException\InvalidType
* @throws Exception
* @throws TypeError
*/
public function testSealFromStreamWrapper()
{
require_once __DIR__ . '/RemoteStream.php';
stream_register_wrapper('haliteTest', RemoteStream::class);
touch(__DIR__.'/tmp/paragon_avatar.sealed.png');
chmod(__DIR__.'/tmp/paragon_avatar.sealed.png', 0777);
touch(__DIR__.'/tmp/paragon_avatar.opened.png');
chmod(__DIR__.'/tmp/paragon_avatar.opened.png', 0777);

$keypair = KeyFactory::generateEncryptionKeyPair();
$secretkey = $keypair->getSecretKey();
$publickey = $keypair->getPublicKey();

$file = new ReadOnlyFile(fopen('haliteTest://paragon_avatar.png', 'rb'));
File::seal(
$file,
__DIR__.'/tmp/paragon_avatar.sealed.png',
$publickey
);

File::unseal(
__DIR__.'/tmp/paragon_avatar.sealed.png',
__DIR__.'/tmp/paragon_avatar.opened.png',
$secretkey
);

$this->assertSame(
hash_file('sha256', __DIR__.'/tmp/paragon_avatar.png'),
hash_file('sha256', __DIR__.'/tmp/paragon_avatar.opened.png')
);

unlink(__DIR__.'/tmp/paragon_avatar.sealed.png');
unlink(__DIR__.'/tmp/paragon_avatar.opened.png');
$this->assertEquals($file->getHash(), (new ReadOnlyFile(__DIR__.'/tmp/paragon_avatar.png'))->getHash());
}

/**
* @throws CryptoException\CannotPerformOperation
* @throws CryptoException\FileAccessDenied
Expand Down
79 changes: 79 additions & 0 deletions test/unit/RemoteStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);

/**
* Defines a fake stream wrapper for testing ReadOnlyFile operations against a stream that doesn't support fstat.
*/
final class RemoteStream
{
private $contents;
private $position = 0;

function stream_open($path, $mode, $options, &$opened_path)
{
$this->contents = \file_get_contents(__DIR__ . '/tmp/' . parse_url($path, PHP_URL_HOST));
return true;
}

function stream_read($count)
{
$return = \substr($this->contents, $this->position, $count);
$this->position += strlen($return);
return $return;
}

function stream_write($data)
{
return false;
}

function stream_tell()
{
return $this->position;
}

function stream_eof()
{
return $this->position >= \strlen($this->contents);
}

function stream_seek($offset, $whence)
{
switch ($whence) {
case SEEK_SET:
if ($offset < strlen($this->contents) && $offset >= 0) {
$this->position = $offset;
return true;
}
return false;

case SEEK_CUR:
if ($offset >= 0) {
$this->position += $offset;
return true;
}
return false;

case SEEK_END:
if (strlen($this->contents) + $offset >= 0) {
$this->position = strlen($this->contents) + $offset;
return true;
}
return false;

default:
return false;
}
}

function stream_metadata($path, $option, $var)
{
return false;
}

function stream_stat()
{
return false;
}

}

0 comments on commit be87703

Please sign in to comment.