Various useful utilities, used within Halite and available for general use This library makes heavy use of return-type declarations, which are a PHP 7 only feature. Read more about them here:
Ejemplo n.º 1
1
 /**
  * Add an autoRun entry.
  *
  * @param array $run
  * @param string $workspace
  * @string file
  * @return bool
  * @throws \Error
  */
 protected function addautoRun(array $run, string $workspace, string $file) : bool
 {
     static $db_tpl = null;
     if ($db_tpl === null) {
         $db_tpl = \file_get_contents(\dirname(HANGAR_ROOT) . DIRECTORY_SEPARATOR . 'res' . DIRECTORY_SEPARATOR . 'index.php.tmp');
     }
     $hash = Util::hash($file);
     switch ($run['type']) {
         case 'php':
             \file_put_contents($workspace . DIRECTORY_SEPARATOR . 'autoRun' . DIRECTORY_SEPARATOR . $hash . '.php', Base64::decode($run['data']));
             \file_put_contents($workspace . DIRECTORY_SEPARATOR . 'autoRun.php', 'require_once __DIR__ . DIRECTORY_SEPARATOR . "autoRun" . DIRECTORY_SEPARATOR . "' . $hash . '.php";' . "\n", FILE_APPEND);
             return true;
         case 'mysql':
         case 'pgsql':
             $exec = \str_replace(['@_QUERY_@', '@_DRIVER_@'], [\str_replace('"', '\\"', Base64::decode($run['data'])), $run['type']], $db_tpl);
             // Save the template file:
             \file_put_contents($workspace . DIRECTORY_SEPARATOR . 'autoRun' . DIRECTORY_SEPARATOR . $hash . '.php', $exec);
             // Add the autoRun script to the autoRun list:
             \file_put_contents($workspace . DIRECTORY_SEPARATOR . 'autoRun.php', 'require_once __DIR__ . DIRECTORY_SEPARATOR . "autoRun" . DIRECTORY_SEPARATOR . $hash . ".php";' . "\n", FILE_APPEND);
             return true;
         default:
             throw new \Error('Unknown type: ' . $run['type']);
     }
 }
Ejemplo n.º 2
0
 /**
  * Get a hash of the data (defaults to hex encoded)
  *
  * @param bool $raw
  *
  * These two aren't really meant to be used externally:
  * @param int $outputSize
  * @param string $personalization
  *
  * @return string
  */
 public function getHash(bool $raw = false, int $outputSize = \Sodium\CRYPTO_GENERICHASH_BYTES, string $personalization = '') : string
 {
     if ($raw) {
         return Util::raw_hash($personalization . $this->data, $outputSize);
     }
     return Util::hash($personalization . $this->data, $outputSize);
 }
Ejemplo n.º 3
0
 /**
  * @param string $keyMaterial - The actual key data
  * @param bool $signing - Is this a signing key?
  */
 public function __construct(string $keyMaterial = '', ...$args)
 {
     if (CryptoUtil::safeStrlen($keyMaterial) !== \Sodium\CRYPTO_BOX_PUBLICKEYBYTES) {
         throw new InvalidKey('Encryption public key must be CRYPTO_BOX_PUBLICKEYBYTES bytes long');
     }
     parent::__construct($keyMaterial, false);
 }
Ejemplo n.º 4
0
 /**
  * Prevent accidental echoing of a hidden string
  *
  * @return string
  */
 public function __toString() : string
 {
     if ($this->allowInline) {
         return CryptoUtil::safeStrcpy($this->internalStringValue);
     }
     return '';
 }
Ejemplo n.º 5
0
 /**
  * Calculate the Merkle root, taking care to distinguish between
  * leaves and branches (0x01 for the nodes, 0x00 for the branches)
  * to protect against second-preimage attacks
  *
  * @return string
  */
 protected function calculateRoot() : string
 {
     $size = \count($this->nodes);
     if ($size < 1) {
         return '';
     }
     $hash = [];
     // Population (Use self::MERKLE_LEAF as a prefix)
     for ($i = 0; $i < $size; ++$i) {
         $hash[$i] = self::MERKLE_LEAF . $this->personalization . $this->nodes[$i]->getHash(true, $this->outputSize, $this->personalization);
     }
     // Calculation (Use self::MERKLE_BRANCH as a prefix)
     do {
         $tmp = [];
         $j = 0;
         for ($i = 0; $i < $size; $i += 2) {
             if (empty($hash[$i + 1])) {
                 $tmp[$j] = $hash[$i];
             } else {
                 $tmp[$j] = Util::raw_hash(self::MERKLE_BRANCH . $this->personalization . $hash[$i] . $hash[$i + 1], $this->outputSize);
             }
             ++$j;
         }
         $hash = $tmp;
         $size >>= 1;
     } while ($size > 1);
     // We should only have one value left:
     $this->rootCalculated = true;
     return \array_shift($hash);
 }
Ejemplo n.º 6
0
 /**
  * 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 boolean $skipTests Only set this to TRUE if you're absolutely sure
  *                           that you don't want to defend against TOCTOU /
  *                           race condition attacks on the filesystem!
  * @return string
  * @throws FileAlert\AccessDenied
  */
 public function readBytes($num, $skipTests = false)
 {
     if ($num <= 0) {
         throw new \Exception('num < 0');
     }
     if ($this->pos + $num > $this->stat['size']) {
         throw new \Exception('Out-of-bounds read');
     }
     $buf = '';
     $remaining = $num;
     if (!$skipTests) {
         $this->toctouTest();
     }
     do {
         if ($remaining <= 0) {
             break;
         }
         $read = \fread($this->fp, $remaining);
         if ($read === false) {
             throw new CryptoException\FileAccessDenied('Could not read from the file');
         }
         $buf .= $read;
         $readSize = Util::safeStrlen($read);
         $this->pos += $readSize;
         $remaining -= $readSize;
     } while ($remaining > 0);
     return $buf;
 }
Ejemplo n.º 7
0
 /**
  * @param HiddenString $keyMaterial - The actual key data
  * @throws InvalidKey
  */
 public function __construct(HiddenString $keyMaterial)
 {
     if (CryptoUtil::safeStrlen($keyMaterial->getString()) !== \Sodium\CRYPTO_BOX_SECRETKEYBYTES) {
         throw new InvalidKey('Encryption secret key must be CRYPTO_BOX_SECRETKEYBYTES bytes long');
     }
     parent::__construct($keyMaterial);
 }
Ejemplo n.º 8
0
 /**
  * Execute a block of code.
  * 
  * @param string $code
  * @param boolean $cache
  * @param boolean $do_not_eval
  * @return mixed
  */
 protected static function coreEval(string $code, bool $cache = false, bool $do_not_eval = false)
 {
     \clearstatcache();
     if ($do_not_eval || \Airship\is_disabled('eval')) {
         if ($cache) {
             if (!\file_exists(ROOT . "/tmp/cache/gear")) {
                 \mkdir(ROOT . "/tmp/cache/gear", 0777);
                 \clearstatcache();
             }
             $hashed = Base64UrlSafe::encode(CryptoUtil::raw_hash($code, 33));
             if (!\file_exists(ROOT . '/tmp/cache/gear/' . $hashed . '.tmp.php')) {
                 \file_put_contents(ROOT . '/tmp/cache/gear/' . $hashed . '.tmp.php', '<?php' . "\n" . $code);
             }
             return self::sandboxRequire(ROOT . '/cache/' . $hashed . '.tmp.php');
         } else {
             if (!\file_exists(ROOT . '/tmp/gear')) {
                 \mkdir(ROOT . '/tmp/gear', 0777);
                 \clearstatcache();
             }
             $file = \Airship\tempnam('gear-', 'php', ROOT . '/tmp/gear');
             \file_put_contents($file, '<?php' . "\n" . $code);
             \clearstatcache();
             $ret = self::sandboxRequire($file);
             \unlink($file);
             \clearstatcache();
             return $ret;
         }
     } else {
         return eval($code);
     }
 }
Ejemplo n.º 9
0
 /**
  * @param string $keyMaterial - The actual key data
  * @param bool $signing - Is this a signing key?
  */
 public function __construct(string $keyMaterial = '', ...$args)
 {
     if (CryptoUtil::safeStrlen($keyMaterial) !== \Sodium\CRYPTO_SIGN_SECRETKEYBYTES) {
         throw new InvalidKey('Signature secret key must be CRYPTO_SIGN_SECRETKEYBYTES bytes long');
     }
     parent::__construct($keyMaterial, true);
 }
Ejemplo n.º 10
0
 /**
  * @param string $keyMaterial - The actual key data
  */
 public function __construct(string $keyMaterial = '', ...$args)
 {
     if (CryptoUtil::safeStrlen($keyMaterial) !== \Sodium\CRYPTO_AUTH_KEYBYTES) {
         throw new InvalidKey('Authentication key must be CRYPTO_AUTH_KEYBYTES bytes long');
     }
     parent::__construct($keyMaterial, true);
 }
Ejemplo n.º 11
0
 /**
  * @param HiddenString $keyMaterial - The actual key data
  * @throws InvalidKey
  */
 public function __construct(HiddenString $keyMaterial)
 {
     if (CryptoUtil::safeStrlen($keyMaterial->getString()) !== \Sodium\CRYPTO_AUTH_KEYBYTES) {
         throw new InvalidKey('Authentication key must be CRYPTO_AUTH_KEYBYTES bytes long');
     }
     parent::__construct($keyMaterial);
     $this->isSigningKey = true;
 }
Ejemplo n.º 12
0
 /**
  * @param HiddenString $keyMaterial - The actual key data
  * @throws InvalidKey
  */
 public function __construct(HiddenString $keyMaterial)
 {
     if (CryptoUtil::safeStrlen($keyMaterial->getString()) !== \Sodium\CRYPTO_SIGN_SECRETKEYBYTES) {
         throw new InvalidKey('Signature secret key must be CRYPTO_SIGN_SECRETKEYBYTES bytes long');
     }
     parent::__construct($keyMaterial);
     $this->isSigningKey = true;
 }
Ejemplo n.º 13
0
 /**
  * test safeStrLen() with illegal parameter. We expect to see an exception
  * @return void
  * @throws CannotPerformOperation
  */
 public function testSafeStrlen()
 {
     $this->setExpectedException('\\ParagonIE\\Halite\\Alerts\\HaliteAlert');
     $teststring = [];
     // is not a string, will provoke a warning
     //suppress php warning
     Util::safeStrlen($teststring);
 }
Ejemplo n.º 14
0
 /**
  * Get the configuration for this version of halite
  *
  * @param string $stored   A stored password hash
  * @return SymmetricConfig
  * @throws InvalidMessage
  */
 protected static function getConfig(string $stored) : SymmetricConfig
 {
     $length = Util::safeStrlen($stored);
     // This doesn't even have a header.
     if ($length < 8) {
         throw new InvalidMessage('Encrypted password hash is way too short.');
     }
     if (\hash_equals(Util::safeSubstr($stored, 0, 5), Halite::VERSION_PREFIX)) {
         return SymmetricConfig::getConfig(Base64UrlSafe::decode($stored), 'encrypt');
     }
     $v = \Sodium\hex2bin(Util::safeSubstr($stored, 0, 8));
     return SymmetricConfig::getConfig($v, 'encrypt');
 }
Ejemplo n.º 15
0
 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);
     }
 }
Ejemplo n.º 16
0
 /**
  * Split a key using HKDF
  * 
  * @param \ParagonIE\Halite\Contract\CryptoKeyInterface $master
  * @param string $salt
  * @return array
  */
 protected static function splitKeys(\ParagonIE\Halite\Contract\CryptoKeyInterface $master, $salt = null)
 {
     $binary = $master->get();
     return [CryptoUtil::hkdfBlake2b($binary, \Sodium\CRYPTO_SECRETBOX_KEYBYTES, Halite::HKDF_SBOX, $salt), CryptoUtil::hkdfBlake2b($binary, \Sodium\CRYPTO_AUTH_KEYBYTES, Halite::HKDF_AUTH, $salt)];
 }
Ejemplo n.º 17
0
 /**
  * Write to a stream; prevent partial writes
  * 
  * @param resource $stream
  * @param string $buf
  * @param int $num (number of bytes)
  * @throws FileAlert\AccessDenied
  */
 public function writeBytes($buf, $num = null)
 {
     $bufSize = Util::safeStrlen($buf);
     if ($num === null || $num > $bufSize) {
         $num = $bufSize;
     }
     if ($num < 0) {
         throw new \Exception('num < 0');
     }
     $remaining = $num;
     do {
         if ($remaining <= 0) {
             break;
         }
         $written = \fwrite($this->fp, $buf, $remaining);
         if ($written === false) {
             throw new CryptoException\FileAccessDenied('Could not write to the file');
         }
         $buf = Util::safeSubstr($buf, $written, null);
         $this->pos += $written;
         $this->stat = \fstat($this->fp);
         $remaining -= $written;
     } while ($remaining > 0);
     return $num;
 }
Ejemplo n.º 18
0
 /**
  * Split a key using a variant of HKDF that used a keyed BLAKE2b hash rather
  * than an HMAC construct
  * 
  * @param EncryptionKey $master
  * @param string $salt
  * @param Config $config
  * @return array
  */
 public static function splitKeys(Contract\KeyInterface $master, $salt = null, Config $config = null)
 {
     $binary = $master->get();
     return [CryptoUtil::hkdfBlake2b($binary, \Sodium\CRYPTO_SECRETBOX_KEYBYTES, $config->HKDF_SBOX, $salt), CryptoUtil::hkdfBlake2b($binary, \Sodium\CRYPTO_AUTH_KEYBYTES, $config->HKDF_AUTH, $salt)];
 }
Ejemplo n.º 19
0
 /**
  * Decrypt then verify a password
  *
  * @param HiddenString $password    The user's password
  * @param string $stored            The encrypted password hash
  * @param EncryptionKey $secretKey  The master key for all passwords
  * @return bool                     Is this password valid?
  * @throws InvalidMessage
  */
 public static function verify(HiddenString $password, string $stored, EncryptionKey $secretKey) : bool
 {
     $config = self::getConfig($stored);
     // Base64-urlsafe encoded, so 4/3 the size of raw binary
     if (Util::safeStrlen($stored) < $config->SHORTEST_CIPHERTEXT_LENGTH * 4 / 3) {
         throw new InvalidMessage('Encrypted password hash is too short.');
     }
     // First let's decrypt the hash
     $hash_str = Crypto::decrypt($stored, $secretKey, $config->ENCODING);
     // Upon successful decryption, verify the password is correct
     return \Sodium\crypto_pwhash_str_verify($hash_str->getString(), $password->getString());
 }
Ejemplo n.º 20
0
 /**
  * @covers File::checksum()
  */
 public function testChecksum()
 {
     $csum = File::checksum(__DIR__ . '/tmp/paragon_avatar.png', null, false);
     $this->assertSame($csum, "09f9f74a0e742d057ca08394db4c2e444be88c0c94fe9a914c3d3758c7eccafb" . "8dd286e3d6bc37f353e76c0c5aa2036d978ca28ffaccfa59f5dc1f076c5517a0");
     $data = \Sodium\randombytes_buf(32);
     \file_put_contents(__DIR__ . '/tmp/garbage.dat', $data);
     $hash = Util::raw_hash($data, 64);
     $file = File::checksum(__DIR__ . '/tmp/garbage.dat', null, true);
     $this->assertSame($hash, $file);
     \unlink(__DIR__ . '/tmp/garbage.dat');
 }
Ejemplo n.º 21
0
 /**
  * Take a stored key string, get the derived key (after verifying the
  * checksum)
  * 
  * @param string $data
  * @return string
  * @throws Alerts\InvalidKey
  */
 public static function getKeyDataFromString(string $data) : string
 {
     $vtag = Util::safeSubstr($data, 0, Halite::VERSION_TAG_LEN);
     $kdat = Util::safeSubstr($data, Halite::VERSION_TAG_LEN, -\Sodium\CRYPTO_GENERICHASH_BYTES_MAX);
     $csum = Util::safeSubstr($data, -\Sodium\CRYPTO_GENERICHASH_BYTES_MAX, \Sodium\CRYPTO_GENERICHASH_BYTES_MAX);
     $calc = \Sodium\crypto_generichash($vtag . $kdat, '', \Sodium\CRYPTO_GENERICHASH_BYTES_MAX);
     if (!\hash_equals($calc, $csum)) {
         throw new Alerts\InvalidKey('Checksum validation fail');
     }
     \Sodium\memzero($data);
     \Sodium\memzero($vtag);
     \Sodium\memzero($calc);
     \Sodium\memzero($csum);
     return $kdat;
 }
Ejemplo n.º 22
0
 /**
  * Unpack a message string into an array.
  * 
  * @param string $ciphertext
  * @return array
  */
 public static function unpackMessageForDecryption($ciphertext)
 {
     $length = CryptoUtil::safeStrlen($ciphertext);
     // The first 4 bytes are reserved for the version size
     $version = CryptoUtil::safeSubstr($ciphertext, 0, Halite::VERSION_TAG_LEN);
     $config = SymmetricConfig::getConfig($version, 'encrypt');
     // The HKDF is used for key splitting
     $salt = CryptoUtil::safeSubstr($ciphertext, Halite::VERSION_TAG_LEN, $config->HKDF_SALT_LEN);
     // This is the nonce (we authenticated it):
     $nonce = CryptoUtil::safeSubstr($ciphertext, Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN, \Sodium\CRYPTO_STREAM_NONCEBYTES);
     // This is the crypto_stream_xor()ed ciphertext
     $xored = CryptoUtil::safeSubstr($ciphertext, Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN + \Sodium\CRYPTO_STREAM_NONCEBYTES, $length - (Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN + \Sodium\CRYPTO_STREAM_NONCEBYTES + \Sodium\CRYPTO_AUTH_BYTES));
     // $auth is the last 32 bytes
     $auth = CryptoUtil::safeSubstr($ciphertext, $length - \Sodium\CRYPTO_AUTH_BYTES);
     // We don't need this anymore.
     \Sodium\memzero($ciphertext);
     return [$version, $config, $salt, $nonce, $xored, $auth];
 }
Ejemplo n.º 23
0
 /**
  * Split a key using HKDF
  *
  * @param Key $master
  * @param string $salt
  * @param Config $config
  * @return string[]
  */
 protected static function splitKeys(Key $master, string $salt = '', Config $config = null) : array
 {
     $binary = $master->getRawKeyMaterial();
     return [Util::hkdfBlake2b($binary, \Sodium\CRYPTO_SECRETBOX_KEYBYTES, $config->HKDF_SBOX, $salt), Util::hkdfBlake2b($binary, \Sodium\CRYPTO_AUTH_KEYBYTES, $config->HKDF_AUTH, $salt)];
 }
Ejemplo n.º 24
0
 /**
  * Validate a request based on $_SESSION and $_POST data
  *
  * @return bool
  */
 public function check() : bool
 {
     if (!isset($_SESSION[$this->sessionIndex])) {
         // We don't even have a session array initialized
         $_SESSION[$this->sessionIndex] = [];
         return false;
     }
     if (!isset($_POST[self::FORM_TOKEN]) || !\is_string($_POST[self::FORM_TOKEN])) {
         return false;
     }
     if (\strpos($_POST[self::FORM_TOKEN], ':') === false) {
         return false;
     }
     // Let's pull the POST data
     list($index, $token) = \explode(':', $_POST[self::FORM_TOKEN]);
     if (empty($index) || empty($token)) {
         return false;
     }
     if (!isset($_SESSION[$this->sessionIndex][$index])) {
         // CSRF Token not found
         return false;
     }
     // Grab the value stored at $index
     $stored = $_SESSION[$this->sessionIndex][$index];
     // We don't need this anymore
     unset($_SESSION[$this->sessionIndex][$index]);
     // Which form action="" is this token locked to?
     $lockTo = $_SERVER['REQUEST_URI'];
     if (\preg_match('#/$#', $lockTo)) {
         // Trailing slashes are to be ignored
         $lockTo = substr($lockTo, 0, strlen($lockTo) - 1);
     }
     if (!empty($stored['lockto'])) {
         if (!\hash_equals($lockTo, $stored['lockto'])) {
             // Form target did not match the request this token is locked to!
             return false;
         }
     }
     // This is the expected token value
     if ($this->hmacIP === false) {
         // We just stored it wholesale
         $expected = $stored['token'];
     } else {
         // We mixed in the client IP address to generate the output
         $expected = Base64UrlSafe::encode(CryptoUtil::raw_keyed_hash($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1', Base64UrlSafe::decode($stored['token'])));
     }
     return \hash_equals($token, $expected);
 }
Ejemplo n.º 25
0
 /**
  * Split a key using a variant of HKDF that used a keyed BLAKE2b hash rather
  * than an HMAC construct
  * 
  * @param \ParagonIE\Halite\Key $master
  * @param string $salt
  * @return array
  */
 public static function splitKeys(Key $master, $salt = null)
 {
     $binary = $master->get();
     return [CryptoUtil::hkdfBlake2b($binary, \Sodium\CRYPTO_SECRETBOX_KEYBYTES, Config::HKDF_SBOX, $salt), CryptoUtil::hkdfBlake2b($binary, \Sodium\CRYPTO_AUTH_KEYBYTES, Config::HKDF_AUTH, $salt)];
 }
Ejemplo n.º 26
0
 /**
  * @covers Symmetric::unpackMessageForDecryption()
  */
 public function testUnpack()
 {
     $key = new EncryptionKey(new HiddenString(\str_repeat('A', 32)));
     // Randomly sized plaintext
     $size = \Sodium\randombytes_uniform(1023) + 1;
     $plaintext = \Sodium\randombytes_buf($size);
     $message = Symmetric::encrypt(new HiddenString($plaintext), $key, true);
     // Let's unpack our message
     $unpacked = Symmetric::unpackMessageForDecryption($message);
     // Now to test our expected results!
     $this->assertSame(Util::safeStrlen($unpacked[0]), Halite::VERSION_TAG_LEN);
     $this->assertTrue($unpacked[1] instanceof \ParagonIE\Halite\Symmetric\Config);
     $config = $unpacked[1];
     if ($config instanceof \ParagonIE\Halite\Symmetric\Config) {
         $this->assertSame(Util::safeStrlen($unpacked[2]), $config->HKDF_SALT_LEN);
         $this->assertSame(Util::safeStrlen($unpacked[3]), \Sodium\CRYPTO_STREAM_NONCEBYTES);
         $this->assertSame(Util::safeStrlen($unpacked[4]), Util::safeStrlen($message) - (Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN + \Sodium\CRYPTO_STREAM_NONCEBYTES + $config->MAC_SIZE));
         $this->assertSame(Util::safeStrlen($unpacked[5]), $config->MAC_SIZE);
     } else {
         $this->fail('Cannot continue');
     }
 }