public function testFileRead() { $filename = \tempnam('/tmp', 'x'); $buf = \Sodium\randombytes_buf(65537); \file_put_contents($filename, $buf); $fStream = new ReadOnlyFile($filename); $this->assertSame($fStream->readBytes(65537), $buf); $fStream->reset(0); \file_put_contents($filename, Util::safeSubstr($buf, 0, 32768) . 'x' . Util::safeSubstr($buf, 32768)); try { $fStream->readBytes(65537); throw new \Exception('fail'); } catch (CryptoException\FileModified $ex) { $this->assertTrue($ex instanceof CryptoException\FileModified); } }
/** * Recalculate and verify the HMAC of the input file * * @param resource $input * @param resource $mac (hash context) * @param Config $config * * @return Hashes of various chunks * @throws FileAlert\AccessDenied */ private static final function streamVerify(ReadOnlyFile $input, $mac, Config $config) { $start = $input->getPos(); $cipher_end = $input->getSize() - $config->MAC_SIZE; $input->reset($cipher_end); $stored_mac = $input->readBytes($config->MAC_SIZE); $input->reset($start); $chunk_macs = []; $break = false; while (!$break && $input->getPos() < $cipher_end) { /** * Would a full BUFFER read put it past the end of the * ciphertext? If so, only return a portion of the file. */ if ($input->getPos() + $config->BUFFER >= $cipher_end) { $break = true; $read = $input->readBytes($cipher_end - $input->getPos()); } else { $read = $input->readBytes($config->BUFFER); } /** * We're updating our HMAC and nothing else */ \hash_update($mac, $read); /** * Store a MAC of each chunk */ $chunkMAC = \hash_copy($mac); if ($chunkMAC === false) { throw new CryptoException\CannotPerformOperation('An unknown error has occurred'); } $chunk_macs[] = \hash_final($chunkMAC, true); } /** * We should now have enough data to generate an identical HMAC */ $finalHMAC = \hash_final($mac, true); /** * Use hash_equals() to be timing-invariant */ if (!\hash_equals($finalHMAC, $stored_mac)) { throw new CryptoException\InvalidMessage('Invalid message authentication code'); } $input->reset($start); return $chunk_macs; }