diff --git a/.travis.yml b/.travis.yml index 126f56a..0811982 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,13 +4,14 @@ php: - 5.6 - 7.0 +- 7.1 before_install: - sudo apt-get update - sudo apt-get install make build-essential automake php5-dev php-pear - git clone git://github.com/jedisct1/libsodium.git - cd libsodium -- git checkout 1.0.4 +- git checkout 1.0.14 - ./autogen.sh - ./configure && make check - sudo make install @@ -25,4 +26,7 @@ install: - composer update - chmod +x ./test/phpunit.sh -script: ./test/phpunit.sh +script: + +- vendor/bin/phpunit --bootstrap autoload.php test/unit +- vendor/bin/psalm diff --git a/composer.json b/composer.json index 4d1406e..2622c5d 100644 --- a/composer.json +++ b/composer.json @@ -3,8 +3,12 @@ "description": "High-level cryptography interface powered by libsodium", "type": "library", "require": { - "php": "^5.6.0 || ^7.0.0", - "ext-libsodium": "^1.0.2" + "php": ">=5.6.0 <7.2", + "paragonie/sodium_compat": "^1.3" + }, + "require-dev": { + "phpunit/phpunit": "^5", + "vimeo/psalm": "^0|^1" }, "autoload": { "psr-4": { diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..58a36ff --- /dev/null +++ b/psalm.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/src/Asymmetric/Crypto.php b/src/Asymmetric/Crypto.php index cc5dd27..abc0f06 100644 --- a/src/Asymmetric/Crypto.php +++ b/src/Asymmetric/Crypto.php @@ -90,6 +90,7 @@ public static function decrypt( $ecdh = new EncryptionKey( self::getSharedSecret($ourPrivateKey, $theirPublicKey) ); + /** @var string $ciphertext */ $ciphertext = SymmetricCrypto::decrypt($source, $ecdh, $raw); unset($ecdh); return $ciphertext; @@ -132,7 +133,7 @@ public static function getSharedSecret( ) ); } - return \Sodium\crypto_scalarmult( + return (string) \Sodium\crypto_scalarmult( $privateKey->get(), $publicKey->get() ); @@ -171,12 +172,13 @@ public static function seal( 'crypto_box_seal is not available' ); } - + + /** @var string $sealed */ $sealed = \Sodium\crypto_box_seal($source, $publicKey->get()); if ($raw) { - return $sealed; + return (string) $sealed; } - return \Sodium\bin2hex($sealed); + return (string) \Sodium\bin2hex($sealed); } /** @@ -206,14 +208,15 @@ public static function sign( 'Argument 2: Expected an instance of SignatureSecretKey' ); } + /** @var string $signed */ $signed = \Sodium\crypto_sign_detached( $message, $privateKey->get() ); if ($raw) { - return $signed; + return (string) $signed; } - return \Sodium\bin2hex($signed); + return (string) \Sodium\bin2hex($signed); } /** @@ -245,6 +248,7 @@ public static function unseal( ); } if (!$raw) { + /** @var string $source */ $source = \Sodium\hex2bin($source); } if (!\is_callable('\\Sodium\\crypto_box_seal_open')) { @@ -254,8 +258,12 @@ public static function unseal( } // Get a box keypair (needed by crypto_box_seal_open) + + /** @var string $secret_key */ $secret_key = $privateKey->get(); + /** @var string $public_key */ $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key); + /** @var string $kp */ $kp = \Sodium\crypto_box_keypair_from_secretkey_and_publickey( $secret_key, $public_key @@ -266,6 +274,7 @@ public static function unseal( \Sodium\memzero($public_key); // Now let's open that sealed box + /** @var string $message */ $message = \Sodium\crypto_box_seal_open($source, $kp); // Always memzero after retrieving a value @@ -288,7 +297,7 @@ public static function unseal( * @param string $signature * @param boolean $raw Don't hex decode the input? * - * @return boolean + * @return bool * * @throws CryptoException\InvalidKey * @throws CryptoException\InvalidType @@ -317,6 +326,7 @@ public static function verify( ); } if (!$raw) { + /** @var string $signature */ $signature = \Sodium\hex2bin($signature); } if (CryptoUtil::safeStrlen($signature) !== \Sodium\CRYPTO_SIGN_BYTES) { @@ -325,7 +335,7 @@ public static function verify( ); } - return \Sodium\crypto_sign_verify_detached( + return (bool) \Sodium\crypto_sign_verify_detached( $signature, $message, $publicKey->get() diff --git a/src/Asymmetric/EncryptionSecretKey.php b/src/Asymmetric/EncryptionSecretKey.php index 7287781..6051a50 100644 --- a/src/Asymmetric/EncryptionSecretKey.php +++ b/src/Asymmetric/EncryptionSecretKey.php @@ -24,10 +24,11 @@ public function __construct($keyMaterial = '', ...$args) /** * See the appropriate derived class. * - * @return SignaturePublicKey + * @return EncryptionPublicKey */ public function derivePublicKey() { + /** @var string $publicKey */ $publicKey = \Sodium\crypto_box_publickey_from_secretkey( $this->get() ); diff --git a/src/Asymmetric/PublicKey.php b/src/Asymmetric/PublicKey.php index 4f0dbda..c81520a 100644 --- a/src/Asymmetric/PublicKey.php +++ b/src/Asymmetric/PublicKey.php @@ -12,6 +12,7 @@ class PublicKey extends Key implements Contract\KeyInterface */ public function __construct($keyMaterial = '', ...$args) { + /** @var bool $signing */ $signing = \count($args) >= 1 ? $args[0] : false; diff --git a/src/Asymmetric/SecretKey.php b/src/Asymmetric/SecretKey.php index d7627be..e453b99 100644 --- a/src/Asymmetric/SecretKey.php +++ b/src/Asymmetric/SecretKey.php @@ -13,6 +13,7 @@ class SecretKey extends Key implements Contract\KeyInterface */ public function __construct($keyMaterial = '', ...$args) { + /** @var bool $signing */ $signing = \count($args) >= 1 ? $args[0] : false; @@ -21,6 +22,8 @@ public function __construct($keyMaterial = '', ...$args) /** * See the appropriate derived class. + * @throws CannotPerformOperation + * @return void */ public function derivePublicKey() { diff --git a/src/Asymmetric/SignatureSecretKey.php b/src/Asymmetric/SignatureSecretKey.php index 3dbc591..beb8d36 100644 --- a/src/Asymmetric/SignatureSecretKey.php +++ b/src/Asymmetric/SignatureSecretKey.php @@ -28,6 +28,7 @@ public function __construct($keyMaterial = '', ...$args) */ public function derivePublicKey() { + /** @var string $publicKey */ $publicKey = \Sodium\crypto_sign_publickey_from_secretkey( $this->get() ); diff --git a/src/Config.php b/src/Config.php index afb31dd..1fbaafa 100644 --- a/src/Config.php +++ b/src/Config.php @@ -8,6 +8,7 @@ */ class Config { + /** @var array */ private $config; public function __construct(array $set = []) diff --git a/src/Contract/FileInterface.php b/src/Contract/FileInterface.php index 7896de7..ac2216e 100644 --- a/src/Contract/FileInterface.php +++ b/src/Contract/FileInterface.php @@ -65,8 +65,8 @@ public static function unsealFile( /** * Encrypt a (file handle) * - * @param $input - * @param $output + * @param resource $input + * @param resource $output * @param SymmetricKey $key */ public static function encryptResource( @@ -78,8 +78,8 @@ public static function encryptResource( /** * Decrypt a (file handle) * - * @param $input - * @param $output + * @param resource $input + * @param resource $output * @param SymmetricKey $key */ public static function decryptResource( @@ -91,8 +91,8 @@ public static function decryptResource( /** * Seal a (file handle) * - * @param $input - * @param $output + * @param resource $input + * @param resource $output * @param PublicKey $publickey */ public static function sealResource( @@ -104,8 +104,8 @@ public static function sealResource( /** * Unseal a (file handle) * - * @param $input - * @param $output + * @param resource $input + * @param resource $output * @param SecretKey $secretkey */ public static function unsealResource( @@ -134,7 +134,7 @@ public static function checksumFile( /** * Calculate a BLAHE2b checksum of a file * - * @param string $fileHandle The file you'd like to checksum + * @param resource $fileHandle The file you'd like to checksum * @param SymmetricKey $key An optional BLAKE2b key * @param bool $raw Set to true if you don't want hex * diff --git a/src/Contract/KeyInterface.php b/src/Contract/KeyInterface.php index 38fc77d..8ae8c59 100644 --- a/src/Contract/KeyInterface.php +++ b/src/Contract/KeyInterface.php @@ -61,4 +61,18 @@ public function isSecretKey(); * @return bool */ public function isSigningKey(); + + /** + * We rename this in version 2. Keep this for now. + * + * @return string + */ + public function get(); + + /** + * Get the actual key material + * + * @return string + */ + public function getRawKeyMaterial(); } diff --git a/src/Contract/StreamInterface.php b/src/Contract/StreamInterface.php index 2e0c139..229f8d3 100644 --- a/src/Contract/StreamInterface.php +++ b/src/Contract/StreamInterface.php @@ -1,6 +1,8 @@ key); + /** @var string $decrypted */ + $decrypted = Crypto::decrypt((string) $_COOKIE[$name], $this->key); if (empty($decrypted)) { return null; } - return \json_decode($decrypted, true); + return (string) \json_decode($decrypted, true); } catch (InvalidMessage $e) { - return; + return null; } } @@ -67,7 +69,7 @@ public function fetch($name) * @param mixed $value * @param int $expire (defaults to 0) * @param string $path (defaults to '/') - * @param string $domain (defaults to NULL) + * @param string $domain (defaults to '') * @param bool $secure (defaults to TRUE) * @param bool $httponly (defaults to TRUE) * @return bool @@ -78,7 +80,7 @@ public function store( $value, $expire = 0, $path = '/', - $domain = null, + $domain = '', $secure = true, $httponly = true ) { @@ -90,7 +92,7 @@ public function store( return \setcookie( $name, Crypto::encrypt( - \json_encode($value), + (string) \json_encode($value), $this->key ), $expire, diff --git a/src/EncryptionKeyPair.php b/src/EncryptionKeyPair.php index 4c80ce6..4cc8f2b 100644 --- a/src/EncryptionKeyPair.php +++ b/src/EncryptionKeyPair.php @@ -11,10 +11,13 @@ final class EncryptionKeyPair extends KeyPair { /** - * + * * Pass it a secret key, it will automatically generate a public key - * + * * @param ...Key $keys + * @throws CryptoException\InvalidKey + * @throws \InvalidArgumentException + * @throws \TypeError */ public function __construct(Key ...$keys) { @@ -36,20 +39,25 @@ public function __construct(Key ...$keys) ); } // $keys[0] is public, $keys[1] is secret + /** @var EncryptionSecretKey secret_key */ $this->secret_key = $keys[1] instanceof EncryptionSecretKey ? $keys[1] - : new EncryptionSecretKey($keys[1]->get()); + : new EncryptionSecretKey((string) ($keys[1]->get())); /** * Let's use the secret key to calculate the *correct* * public key. We're effectively discarding $keys[0] but * this ensures correct usage down the line. */ + if (!($this->secret_key instanceof EncryptionSecretKey)) { + throw new \TypeError(); + } if (!$this->secret_key->isEncryptionKey()) { throw new CryptoException\InvalidKey( 'Must be an encryption key pair' ); } // crypto_box - Curve25519 + /** @var string $pub */ $pub = \Sodium\crypto_box_publickey_from_secretkey( $keys[1]->get() ); @@ -71,6 +79,7 @@ public function __construct(Key ...$keys) ); } // crypto_box - Curve25519 + /** @var string $pub */ $pub = \Sodium\crypto_box_publickey_from_secretkey( $keys[0]->get() ); @@ -99,7 +108,7 @@ public function __construct(Key ...$keys) $this->secret_key = $keys[0] instanceof EncryptionSecretKey ? $keys[0] : new EncryptionSecretKey( - $keys[0]->get(), + (string) ($keys[0]->get()), $keys[0]->isEncryptionKey() ); @@ -109,9 +118,11 @@ public function __construct(Key ...$keys) ); } // We need to calculate the public key from the secret key + /** @var string $pub */ $pub = \Sodium\crypto_box_publickey_from_secretkey( - $keys[0]->get() + (string) ($keys[0]->get()) ); + /** @var EncryptionPublicKey public_key */ $this->public_key = new EncryptionPublicKey($pub, true); \Sodium\memzero($pub); break; @@ -120,6 +131,9 @@ public function __construct(Key ...$keys) 'Halite\\EncryptionKeyPair expects 1 or 2 keys' ); } + if (false) { + parent::__construct(...$keys); + } } /** @@ -141,27 +155,21 @@ public function __debugInfo() * @param string $password * @param string $salt * @param int $type - * @return array|\ParagonIE\Halite\Key + * @return array|\ParagonIE\Halite\KeyPair * @throws CryptoException\InvalidFlags */ - public static function deriveFromPassword( - $password, - $salt, - $type = self::CRYPTO_BOX - ) { + public static function deriveFromPassword($password, $salt, $type = Key::CRYPTO_BOX) + { if (Key::doesNotHaveFlag($type, Key::ASYMMETRIC)) { throw new CryptoException\InvalidKey( 'An asymmetric key type must be passed to KeyPair::generate()' ); } if (Key::hasFlag($type, Key::ENCRYPTION)) { - $key = EncryptionSecretKey::deriveFromPassword( + return KeyFactory::deriveEncryptionKeyPair( $password, - $salt, - Key::CRYPTO_BOX + $salt ); - $keypair = new KeyPair($key[0]); - return $keypair; } throw new CryptoException\InvalidKey( 'You must specify encryption or authentication flags.' @@ -172,7 +180,7 @@ public static function deriveFromPassword( * Generate a new keypair * * @param int $type Key flags - * @param &string $secret_key - Reference to optional variable to store secret key in + * @param string $secret_key - Reference to optional variable to store secret key in * @return KeyPair * @throws CryptoException\InvalidKey */ @@ -184,9 +192,7 @@ public static function generate($type = Key::CRYPTO_BOX, &$secret_key = null) ); } if (Key::hasFlag($type, Key::ENCRYPTION)) { - $key = EncryptionSecretKey::generate(Key::CRYPTO_BOX, $secret_key); - $keypair = new EncryptionKeyPair(...$key); - return $keypair; + return KeyFactory::generateEncryptionKeyPair($secret_key); } throw new CryptoException\InvalidKey( 'Only encryption keys can be generated.' @@ -222,15 +228,9 @@ public function getSecretKey() * * @throws CryptoException\InvalidFlags */ - public static function fromFile( - $filePath, - $type = Key::CRYPTO_BOX - ) { - $keys = Key::fromFile( - $filePath, - ($type | Key::ASYMMETRIC | Key::ENCRYPTION) - ); - return new KeyPair(...$keys); + public static function fromFile($filePath) + { + return KeyFactory::loadEncryptionKeyPair($filePath); } /** @@ -241,6 +241,6 @@ public static function fromFile( */ public function saveToFile($filePath) { - return $this->secret_key->saveToFile($filePath); + return KeyFactory::save($this->secret_key, $filePath); } } diff --git a/src/File.php b/src/File.php index 53783d7..8778c05 100644 --- a/src/File.php +++ b/src/File.php @@ -48,7 +48,7 @@ public static function checksum( * @param string|resource $input * @param string|resource $output * @param EncryptionKey $key - * @return int|null + * @return bool * @throws CryptoException\InvalidType */ public static function encrypt( @@ -146,16 +146,21 @@ public static function unseal( $output, KeyInterface $secretkey ) { + if (!($secretkey instanceof EncryptionSecretKey)) { + throw new \TypeError(); + } if ( (\is_resource($input) || \is_string($input)) && ( \is_resource($output) || \is_string($output)) ) { - return self::unsealStream( + /** @var bool $return */ + $return = self::unsealStream( new ReadOnlyFile($input), new MutableFile($output), $secretkey ); + return $return; } throw new \ParagonIE\Halite\Alerts\InvalidType( 'Arguments 1 and 2 expect a filename or open file handle' @@ -200,7 +205,7 @@ public static function sign( * @param string $signature * @param bool $raw_binary * - * @return string + * @return bool * @throws CryptoException\InvalidType */ public static function verify( @@ -246,12 +251,13 @@ public static function checksumFile( ); } $fp = \fopen($filepath, 'rb'); - if ($fp === false) { + if (!\is_resource($fp)) { throw new CryptoException\FileAccessDenied( 'Could not read the file' ); } try { + /** @var string $checksum */ $checksum = self::checksumResource($fp, $key, $raw); } finally { \fclose($fp); @@ -263,7 +269,7 @@ public static function checksumFile( /** * Calculate a BLAHE2b checksum of a file * - * @param string $fileHandle The file you'd like to checksum + * @param resource $fileHandle The file you'd like to checksum * @param string $key An optional BLAKE2b key * @param bool $raw Set to true if you don't want hex * @@ -296,6 +302,7 @@ public static function checksumResource( * @return string * * @throws CryptoException\InvalidKey + * @throws \TypeError */ public static function checksumStream( StreamInterface $fileStream, @@ -309,23 +316,26 @@ public static function checksumStream( 'Argument 2: Expected an instance of AuthenticationKey' ); } - $state = \Sodium\crypto_generichash_init($key->get(), $config->HASH_LEN); + $state = (string) \Sodium\crypto_generichash_init($key->get(), $config->HASH_LEN); } else { - $state = \Sodium\crypto_generichash_init('', $config->HASH_LEN); + $state = (string) \Sodium\crypto_generichash_init('', $config->HASH_LEN); + } + if (!($fileStream instanceof ReadOnlyFile || $fileStream instanceof MutableFile)) { + throw new \TypeError(); } $size = $fileStream->getSize(); while ($fileStream->remainingBytes() > 0) { $read = $fileStream->readBytes( - ($fileStream->getPos() + $config->BUFFER) > $size + ($fileStream->getPos() + (int) $config->BUFFER) > $size ? ($size - $fileStream->getPos()) - : $config->BUFFER + : (int) $config->BUFFER ); \Sodium\crypto_generichash_update($state, $read); } if ($raw) { - return \Sodium\crypto_generichash_final($state, $config->HASH_LEN); + return (string) \Sodium\crypto_generichash_final($state, $config->HASH_LEN); } - return \Sodium\bin2hex( + return (string) \Sodium\bin2hex( \Sodium\crypto_generichash_final($state, $config->HASH_LEN) ); } @@ -336,7 +346,7 @@ public static function checksumStream( * @param string $inputFile * @param string $outputFile * @param EncryptionKey $key - * @return int|null + * @return bool * @throws CryptoException\FileAccessDenied */ public static function encryptFile( @@ -355,13 +365,13 @@ public static function encryptFile( ); } $inputHandle = \fopen($inputFile, 'rb'); - if ($inputHandle === false) { + if (!\is_resource($inputHandle)) { throw new CryptoException\FileAccessDenied( 'Could not read from the file' ); } $outputHandle = \fopen($outputFile, 'wb'); - if ($outputHandle === false) { + if (!\is_resource($outputHandle)) { \fclose($inputHandle); throw new CryptoException\FileAccessDenied( 'Could not write to the file' @@ -369,6 +379,7 @@ public static function encryptFile( } try { + /** @var bool $result */ $result = self::encryptResource( $inputHandle, $outputHandle, @@ -387,6 +398,7 @@ public static function encryptFile( * @param string $inputFile * @param string $outputFile * @param EncryptionKey $key + * @return bool * @throws CryptoException\FileAccessDenied */ public static function decryptFile( @@ -405,13 +417,13 @@ public static function decryptFile( ); } $inputHandle = \fopen($inputFile, 'rb'); - if ($inputHandle === false) { + if (!\is_resource($inputHandle)) { throw new CryptoException\FileAccessDenied( 'Could not read from the file' ); } $outputHandle = \fopen($outputFile, 'wb'); - if ($outputHandle === false) { + if (!\is_resource($outputHandle)) { \fclose($inputHandle); throw new CryptoException\FileAccessDenied( 'Could not write to the file' @@ -419,6 +431,7 @@ public static function decryptFile( } try { + /** @var bool $result */ $result = self::decryptResource( $inputHandle, $outputHandle, @@ -437,7 +450,7 @@ public static function decryptFile( * @param string $inputFile * @param string $outputFile * @param EncryptionPublicKey $publickey - * @return int|null + * @return bool * @throws CryptoException\FileAccessDenied */ public static function sealFile( @@ -457,13 +470,13 @@ public static function sealFile( ); } $inputHandle = \fopen($inputFile, 'rb'); - if ($inputHandle === false) { + if (!\is_resource($inputHandle)) { throw new CryptoException\FileAccessDenied( 'Could not read from the file' ); } $outputHandle = \fopen($outputFile, 'wb'); - if ($outputHandle === false) { + if (!\is_resource($outputHandle)) { \fclose($inputHandle); throw new CryptoException\FileAccessDenied( 'Could not write to the file' @@ -508,13 +521,13 @@ public static function unsealFile( ); } $inputHandle = \fopen($inputFile, 'rb'); - if ($inputHandle === false) { + if (!\is_resource($inputHandle)) { throw new CryptoException\FileAccessDenied( 'Could not read from the file' ); } $outputHandle = \fopen($outputFile, 'wb'); - if ($outputHandle === false) { + if (!\is_resource($outputHandle)) { \fclose($inputHandle); throw new CryptoException\FileAccessDenied( 'Could not write to the file' @@ -556,13 +569,14 @@ public static function signFile( ); } $inputHandle = \fopen($filename, 'rb'); - if ($inputHandle === false) { + if (!\is_resource($inputHandle)) { throw new CryptoException\FileAccessDenied( 'Could not read from the file' ); } try { + /** @var string $return */ $return = self::signResource( $inputHandle, $secretkey, @@ -580,6 +594,7 @@ public static function signFile( * @param string $filename * @param SignaturePublicKey $publickey * @param string $signature + * @param bool $raw_binary * @return bool * @throws CryptoException\FileAccessDenied */ @@ -589,20 +604,20 @@ public static function verifyFile( $signature, $raw_binary = false ) { - if (!\is_readable($filename)) { throw new CryptoException\FileAccessDenied( 'Could not read from the file' ); } $inputHandle = \fopen($filename, 'rb'); - if ($inputHandle === false) { + if (!\is_resource($inputHandle)) { throw new CryptoException\FileAccessDenied( 'Could not read from the file' ); } try { + /** @var bool $return */ $return = self::verifyResource( $inputHandle, $publickey, @@ -621,7 +636,7 @@ public static function verifyFile( * @param $input * @param $output * @param EncryptionKey $key - * @return int|null + * @return bool * @throws CryptoException\InvalidType */ public static function encryptResource( @@ -661,6 +676,12 @@ public static function encryptStream( MutableFile $output, KeyInterface $key ) { + $authKey = ''; + $encKey = ''; + $nonce = ''; + $hkdfsalt = ''; + $eph_public = ''; + if (!($key instanceof EncryptionKey)) { throw new \ParagonIE\Halite\Alerts\InvalidKey( 'Argument 3: Expected an instance of EncryptionKey' @@ -669,32 +690,35 @@ public static function encryptStream( $config = self::getConfig(Halite::HALITE_VERSION_FILE, 'encrypt'); // Generate a nonce and HKDF salt - $firstnonce = \Sodium\randombytes_buf($config->NONCE_BYTES); - $hkdfsalt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN); + /** @var string $firstnonce */ + $firstnonce = \random_bytes((int) $config->NONCE_BYTES); + $hkdfsalt = \random_bytes((int) $config->HKDF_SALT_LEN); // Let's split our key list ($encKey, $authKey) = self::splitKeys($key, $hkdfsalt, $config); - $mac = \hash_init('sha256', HASH_HMAC, $authKey); + $mac = \hash_init('sha256', HASH_HMAC, (string) $authKey); // We no longer need $authKey after we set up the hash context unset($authKey); // Write the header $output->writeBytes(Halite::HALITE_VERSION_FILE, Halite::VERSION_TAG_LEN); $output->writeBytes($firstnonce, \Sodium\CRYPTO_STREAM_NONCEBYTES); - $output->writeBytes($hkdfsalt, $config->HKDF_SALT_LEN); + $output->writeBytes($hkdfsalt, (int) $config->HKDF_SALT_LEN); \hash_update($mac, Halite::HALITE_VERSION_FILE); \hash_update($mac, $firstnonce); \hash_update($mac, $hkdfsalt); - - return self::streamEncrypt( + + /** @var bool $return */ + $return = self::streamEncrypt( $input, $output, - new EncryptionKey($encKey), + new EncryptionKey((string) $encKey), $firstnonce, $mac, $config ); + return $return; } /** @@ -756,12 +780,14 @@ public static function decryptStream( $config = self::getConfig($header, 'encrypt'); // Let's grab the first nonce and salt - $firstnonce = $input->readBytes($config->NONCE_BYTES); - $hkdfsalt = $input->readBytes($config->HKDF_SALT_LEN); + /** @var string $firstnonce */ + $firstnonce = $input->readBytes((int) $config->NONCE_BYTES); + /** @var string $hkdfsalt */ + $hkdfsalt = $input->readBytes((int) $config->HKDF_SALT_LEN); // Split our keys, begin the HMAC instance list ($encKey, $authKey) = self::splitKeys($key, $hkdfsalt, $config); - $mac = \hash_init('sha256', HASH_HMAC, $authKey); + $mac = \hash_init('sha256', HASH_HMAC, (string) $authKey); \hash_update($mac, $header); \hash_update($mac, $firstnonce); @@ -769,11 +795,12 @@ public static function decryptStream( // This will throw an exception if it fails. $old_macs = self::streamVerify($input, \hash_copy($mac), $config); - + + /** @var bool $ret */ $ret = self::streamDecrypt( $input, $output, - new EncryptionKey($encKey), + new EncryptionKey((string) $encKey), $firstnonce, $mac, $config, @@ -835,6 +862,12 @@ public static function sealStream( MutableFile $output, KeyInterface $publickey ) { + $authKey = ''; + $encKey = ''; + $nonce = ''; + $hkdfsalt = ''; + $eph_public = ''; + if (!($publickey instanceof EncryptionPublicKey)) { throw new CryptoException\InvalidKey( 'Argument 3: Expected an instance of EncryptionPublicKey' @@ -847,6 +880,7 @@ public static function sealStream( unset($eph_kp); // Calculate the shared secret key + /** @var EncryptionKey $key */ $key = AsymmetricCrypto::getSharedSecret($eph_secret, $publickey, true); // Destroy the secre tkey after we have the shared secret @@ -854,6 +888,7 @@ public static function sealStream( $config = self::getConfig(Halite::HALITE_VERSION_FILE, 'seal'); // Generate a nonce as per crypto_box_seal + /** @var string $nonce */ $nonce = \Sodium\crypto_generichash( $eph_public->get().$publickey->get(), null, @@ -861,7 +896,8 @@ public static function sealStream( ); // Generate a random HKDF salt - $hkdfsalt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN); + /** @var string $hkdfsalt */ + $hkdfsalt = \random_bytes((int) $config->HKDF_SALT_LEN); // Split the keys list ($encKey, $authKey) = self::splitKeys($key, $hkdfsalt, $config); @@ -869,28 +905,30 @@ public static function sealStream( // We no longer need the original key after we split it unset($key); - $mac = \hash_init('sha256', HASH_HMAC, $authKey); + $mac = \hash_init('sha256', HASH_HMAC, (string) $authKey); // We no longer need to retain this after we've set up the hash context unset($authKey); $output->writeBytes(Halite::HALITE_VERSION_FILE, Halite::VERSION_TAG_LEN); $output->writeBytes($eph_public->get(), \Sodium\CRYPTO_BOX_PUBLICKEYBYTES); - $output->writeBytes($hkdfsalt, $config->HKDF_SALT_LEN); + $output->writeBytes($hkdfsalt, (int) $config->HKDF_SALT_LEN); \hash_update($mac, Halite::HALITE_VERSION_FILE); \hash_update($mac, $eph_public->get()); \hash_update($mac, $hkdfsalt); unset($eph_public); - - return self::streamEncrypt( + + /** @var bool $return */ + $return = self::streamEncrypt( $input, $output, - new EncryptionKey($encKey), + new EncryptionKey((string) $encKey), $nonce, $mac, $config ); + return $return; } /** @@ -918,11 +956,16 @@ public static function unsealResource( 'Expected output handle to be a resource' ); } - return self::unsealStream( + if (!($secretkey instanceof EncryptionSecretKey)) { + throw new \TypeError(); + } + /** @var bool $ret */ + $ret = self::unsealStream( new ReadOnlyFile($input), new MutableFile($output), $secretkey ); + return $ret; } /** @@ -939,12 +982,15 @@ public static function unsealStream( MutableFile $output, EncryptionSecretKey $secretkey ) { - if (!($secretkey instanceof EncryptionSecretKey)) { - throw new \ParagonIE\Halite\Alerts\InvalidKey( - 'Argument 3: Expected an instance of EncryptionSecretKey' - ); - } + $authKey = ''; + $encKey = ''; + $nonce = ''; + $hkdfsalt = ''; + $eph_public = ''; + + /** @var string $secret_key */ $secret_key = $secretkey->get(); + /** @var string $public_key */ $public_key = \Sodium\crypto_box_publickey_from_secretkey($secret_key); // Parse the header, ensuring we get 4 bytes @@ -952,40 +998,45 @@ public static function unsealStream( // Load the config $config = self::getConfig($header, 'seal'); // Let's grab the public key and salt - $eph_public = $input->readBytes($config->PUBLICKEY_BYTES); - $hkdfsalt = $input->readBytes($config->HKDF_SALT_LEN); - + /** @var string $eph_public */ + $eph_public = $input->readBytes((int) $config->PUBLICKEY_BYTES); + /** @var string $hkdfsalt */ + $hkdfsalt = $input->readBytes((int) $config->HKDF_SALT_LEN); + + /** @var string $nonce */ $nonce = \Sodium\crypto_generichash( $eph_public . $public_key, null, \Sodium\CRYPTO_STREAM_NONCEBYTES ); - $ephemeral = new EncryptionPublicKey($eph_public); - + $ephemeral = new EncryptionPublicKey((string) $eph_public); + + /** @var EncryptionKey $key */ $key = AsymmetricCrypto::getSharedSecret( - $secretkey, + $secretkey, $ephemeral, true ); - list ($encKey, $authKey) = self::splitKeys($key, $hkdfsalt, $config); + list ($encKey, $authKey) = self::splitKeys($key, (string) $hkdfsalt, $config); // We no longer need the original key after we split it unset($key); - $mac = \hash_init('sha256', HASH_HMAC, $authKey); + $mac = \hash_init('sha256', HASH_HMAC, (string) $authKey); - \hash_update($mac, $header); - \hash_update($mac, $eph_public); - \hash_update($mac, $hkdfsalt); + \hash_update($mac, (string) $header); + \hash_update($mac, (string) $eph_public); + \hash_update($mac, (string) $hkdfsalt); // This will throw an exception if it fails. $old_macs = self::streamVerify($input, \hash_copy($mac), $config); - + + /** @var bool $ret */ $ret = self::streamDecrypt( $input, $output, - new EncryptionKey($encKey), - $nonce, + new EncryptionKey((string) $encKey), + (string) $nonce, $mac, $config, $old_macs @@ -997,13 +1048,13 @@ public static function unsealStream( unset($mac); unset($config); unset($old_macs); - return $ret; + return (bool) $ret; } /** * Sign the contents of a file * - * @param $input (file handle) + * @param resource $input (file handle) * @param SignatureSecretKey $secretkey * @param bool $raw_binary Don't hex encode? * @return string @@ -1046,7 +1097,7 @@ public static function signStream( /** * Verify the contents of a file * - * @param $input (file handle) + * @param resource $input (file handle) * @param SignaturePublicKey $publickey * @param string $signature * @param bool $raw_binary Don't hex encode? @@ -1059,12 +1110,14 @@ public static function verifyResource( $signature, $raw_binary = false ) { - return self::verifyStream( + /** @var bool $return */ + $return = (bool) self::verifyStream( new ReadOnlyFile($input), $publickey, $signature, $raw_binary ); + return $return; } @@ -1091,7 +1144,7 @@ public static function verifyStream( ); } $csum = self::checksumStream($input, null, true); - return AsymmetricCrypto::verify( + return (bool) AsymmetricCrypto::verify( $csum, $publickey, $signature, @@ -1106,6 +1159,7 @@ public static function verifyStream( * @param string $mode * @return \ParagonIE\Halite\Config * @throws CryptoException\InvalidMessage + * @throws \Error */ protected static function getConfig($header, $mode = 'encrypt') { @@ -1131,6 +1185,7 @@ protected static function getConfig($header, $mode = 'encrypt') self::getConfigChecksum($major, $minor) ); } + throw new \Error('Could not find a valid configuration'); } /** @@ -1220,27 +1275,34 @@ protected static function getConfigChecksum($major, $minor) * @param string $salt * @param Config $config * @return array + * @throws \TypeError */ protected static function splitKeys( \ParagonIE\Halite\Contract\KeyInterface $master, $salt = null, Config $config = null ) { + if (!($config instanceof Config)) { + throw new \TypeError(); + } + /** @var string $binary */ $binary = $master->get(); - return [ + /** @var array $return */ + $return = [ Util::hkdfBlake2b( $binary, \Sodium\CRYPTO_SECRETBOX_KEYBYTES, - $config->HKDF_SBOX, + (string) $config->HKDF_SBOX, $salt ), Util::hkdfBlake2b( $binary, \Sodium\CRYPTO_AUTH_KEYBYTES, - $config->HKDF_AUTH, + (string) $config->HKDF_AUTH, $salt ) ]; + return $return; } /** @@ -1274,11 +1336,12 @@ final private static function streamEncrypt( $size = $input->getSize(); while ($input->remainingBytes() > 0) { $read = $input->readBytes( - ($input->getPos() + $config->BUFFER) > $size + ($input->getPos() + (int) $config->BUFFER) > $size ? ($size - $input->getPos()) - : $config->BUFFER + : (int) $config->BUFFER ); - + + /** @var string $encrypted */ $encrypted = \Sodium\crypto_stream_xor( $read, $nonce, @@ -1329,21 +1392,23 @@ final private static function streamDecrypt( ); } $start = $input->getPos(); - $cipher_end = $input->getSize() - $config->MAC_SIZE; + /** @var int $cipher_end */ + $cipher_end = $input->getSize() - (int) $config->MAC_SIZE; // Begin the streaming decryption $input->reset($start); while ($input->remainingBytes() > $config->MAC_SIZE) { - if (($input->getPos() + $config->BUFFER) > $cipher_end) { + if (($input->getPos() + (int) $config->BUFFER) > $cipher_end) { $read = $input->readBytes( $cipher_end - $input->getPos() ); } else { - $read = $input->readBytes($config->BUFFER); + $read = $input->readBytes((int) $config->BUFFER); } \hash_update($mac, $read); + /** @var resource $calcMAC */ $calcMAC = \hash_copy($mac); - if ($calcMAC === false) { + if (!\is_resource($calcMAC)) { throw new CryptoException\CannotPerformOperation( 'An unknown error has occurred' ); @@ -1355,6 +1420,7 @@ final private static function streamDecrypt( 'Invalid message authentication code' ); } else { + /** @var string $chkmac */ $chkmac = \array_shift($chunk_macs); if (!\hash_equals($chkmac, $calc)) { throw new CryptoException\InvalidMessage( @@ -1362,7 +1428,8 @@ final private static function streamDecrypt( ); } } - + + /** @var string $decrypted */ $decrypted = \Sodium\crypto_stream_xor( $read, $nonce, @@ -1392,49 +1459,51 @@ final private static function streamVerify( Config $config ) { $start = $input->getPos(); - - $cipher_end = $input->getSize() - $config->MAC_SIZE; + + $cipher_end = $input->getSize() - (int) $config->MAC_SIZE; $input->reset($cipher_end); - $stored_mac = $input->readBytes($config->MAC_SIZE); + /** @var string $stored_mac */ + $stored_mac = $input->readBytes((int) $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) { + if ($input->getPos() + (int) $config->BUFFER >= $cipher_end) { $break = true; - $read = $input->readBytes($cipher_end - $input->getPos()); + $read = $input->readBytes((int) $cipher_end - $input->getPos()); } else { - $read = $input->readBytes($config->BUFFER); + $read = $input->readBytes((int) $config->BUFFER); } - + /** * We're updating our HMAC and nothing else */ \hash_update($mac, $read); - + /** * Store a MAC of each chunk + * @var resource $chunkMAC */ $chunkMAC = \hash_copy($mac); - if ($chunkMAC === false) { + if (!\is_resource($chunkMAC)) { 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 */ diff --git a/src/Key.php b/src/Key.php index 4a01a62..6de071a 100644 --- a/src/Key.php +++ b/src/Key.php @@ -25,10 +25,17 @@ abstract class Key implements Contract\KeyInterface const CRYPTO_AUTH = 9; const CRYPTO_BOX = 20; const CRYPTO_SIGN = 24; - + + /** @var bool $is_public_key */ private $is_public_key = false; + + /** @var bool $is_signing_key */ private $is_signing_key = false; + + /** @var bool $is_asymmetric_key */ private $is_asymmetric_key = false; + + /** @var string $key_material */ private $key_material = ''; /** @@ -52,20 +59,20 @@ public function __construct( ...$args ) { // Workaround: Inherited classes have simpler constructors: - $public = \count($args) >= 1 ? $args[0] : false; - $signing = \count($args) >= 2 ? $args[1] : false; - $asymmetric = \count($args) >= 3 ? $args[2] : false; + $public = (bool) (\count($args) >= 1 ? $args[0] : false); + $signing = (bool) (\count($args) >= 2 ? $args[1] : false); + $asymmetric = (bool) (\count($args) >= 3 ? $args[2] : false); // String concatenation used to undo a PHP 7 optimization that causes // the wrong memory to get overwritten by \Sodium\memzero: $this->key_material .= $keyMaterial; - $this->is_public_key = $public; - $this->is_signing_key = $signing; + $this->is_public_key = !empty($public); + $this->is_signing_key = !empty($signing); if ($public && !$asymmetric) { // This is implied. $asymmetric = true; } - $this->is_asymmetric_key = $asymmetric; + $this->is_asymmetric_key = !empty($asymmetric); } /** @@ -90,7 +97,7 @@ public function __destruct() { if (!$this->is_public_key) { \Sodium\memzero($this->key_material); - $this->key_material = null; + $this->key_material = ''; } } @@ -117,6 +124,8 @@ public function __toString() /** * We rename this in version 2. Keep this for now. + * + * @return string */ public function get() { diff --git a/src/KeyFactory.php b/src/KeyFactory.php index 08dc2ed..7af025e 100644 --- a/src/KeyFactory.php +++ b/src/KeyFactory.php @@ -1,6 +1,7 @@ get() ); + /** @var string $pub */ $pub = \Sodium\crypto_sign_publickey_from_secretkey( $keys[1]->get() ); @@ -64,6 +68,7 @@ public function __construct(Key ...$keys) $keys[1]->get() ); // crypto_box - Curve25519 + /** @var string $pub */ $pub = \Sodium\crypto_box_publickey_from_secretkey( $keys[1]->get() ); @@ -80,6 +85,7 @@ public function __construct(Key ...$keys) $keys[0]->get() ); // crypto_sign - Ed25519 + /** @var string $pub */ $pub = \Sodium\crypto_sign_publickey_from_secretkey( $keys[0]->get() ); @@ -92,6 +98,7 @@ public function __construct(Key ...$keys) $keys[0]->get() ); // crypto_box - Curve25519 + /** @var string $pub */ $pub = \Sodium\crypto_box_publickey_from_secretkey( $keys[0]->get() ); @@ -127,6 +134,7 @@ public function __construct(Key ...$keys) $keys[0]->get() ); // crypto_sign - Ed25519 + /** @var string $pub */ $pub = \Sodium\crypto_sign_publickey_from_secretkey( $keys[0]->get() ); @@ -139,6 +147,7 @@ public function __construct(Key ...$keys) $keys[0]->get() ); // crypto_box - Curve25519 + /** @var string $pub */ $pub = \Sodium\crypto_box_publickey_from_secretkey( $keys[0]->get() ); @@ -190,10 +199,11 @@ public function getSecretKey() * Save a copy of the secret key to a file * * @param string $filePath - * @return bool|int + * @return bool + * @throws InvalidArgumentException */ public function saveToFile($filePath) { - return $this->secret_key->saveToFile($filePath); + throw new InvalidArgumentException('Not implemented in base class'); } } diff --git a/src/Password.php b/src/Password.php index ac130c4..f019c7a 100644 --- a/src/Password.php +++ b/src/Password.php @@ -34,6 +34,7 @@ public static function hash($password, KeyInterface $secret_key) ); } // First, let's calculate the hash + /** @var string $hashed */ $hashed = \Sodium\crypto_pwhash_scryptsalsa208sha256_str( $password, \Sodium\CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE, @@ -41,7 +42,9 @@ public static function hash($password, KeyInterface $secret_key) ); // Now let's encrypt the result - return Crypto::encrypt($hashed, $secret_key); + /** @var string $encrypted */ + $encrypted = Crypto::encrypt($hashed, $secret_key); + return (string) $encrypted; } /** @@ -74,6 +77,6 @@ public static function verify($password, $stored, KeyInterface $secret_key) // First let's decrypt the hash $hash_str = Crypto::decrypt($stored, $secret_key); // Upon successful decryption, verify the password is correct - return \Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $password); + return (bool) \Sodium\crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $password); } } diff --git a/src/SignatureKeyPair.php b/src/SignatureKeyPair.php index a8fa8ac..f915c99 100644 --- a/src/SignatureKeyPair.php +++ b/src/SignatureKeyPair.php @@ -15,6 +15,9 @@ final class SignatureKeyPair extends KeyPair * Pass it a secret key, it will automatically generate a public key * * @param ...Key $keys + * @throws CryptoException\InvalidKey + * @throws \InvalidArgumentException + * @throws \TypeError */ public function __construct(Key ...$keys) { @@ -50,6 +53,7 @@ public function __construct(Key ...$keys) ); } // crypto_sign - Ed25519 + /** @var string $pub */ $pub = \Sodium\crypto_sign_publickey_from_secretkey( $keys[1]->get() ); @@ -71,6 +75,7 @@ public function __construct(Key ...$keys) ); } // crypto_sign - Ed25519 + /** @var string $pub */ $pub = \Sodium\crypto_sign_publickey_from_secretkey( $keys[0]->get() ); @@ -109,6 +114,7 @@ public function __construct(Key ...$keys) ); } // We need to calculate the public key from the secret key + /** @var string $pub */ $pub = \Sodium\crypto_sign_publickey_from_secretkey( $keys[0]->get() ); @@ -120,6 +126,9 @@ public function __construct(Key ...$keys) 'Halite\\EncryptionKeyPair expects 1 or 2 keys' ); } + if (false) { + parent::__construct(...$keys); + } } /** * Hide this from var_dump(), etc. @@ -133,19 +142,22 @@ public function __debugInfo() 'publicKey' => '**protected**' ]; } + /** * Derive an encryption key from a password and a salt - * + * * @param string $password * @param string $salt * @param int $type - * @return array|\ParagonIE\Halite\Key - * @throws CryptoException\InvalidFlags + * @return KeyPair + * @throws Alerts\InvalidFlags + * @return EncryptionKeyPair + * @throws Alerts\InvalidKey */ public static function deriveFromPassword( $password, $salt, - $type = self::CRYPTO_SIGN + $type = Key::CRYPTO_SIGN ) { if (Key::doesNotHaveFlag($type, Key::ASYMMETRIC)) { throw new CryptoException\InvalidKey( @@ -153,13 +165,10 @@ public static function deriveFromPassword( ); } if (Key::hasFlag($type, Key::SIGNATURE)) { - $key = SignatureSecretKey::deriveFromPassword( + return KeyFactory::deriveSignatureKeyPair( $password, - $salt, - Key::CRYPTO_SIGN + $salt ); - $keypair = new SignatureKeyPair($key[0]); - return $keypair; } throw new CryptoException\InvalidKey( 'You must specify encryption or authentication flags.' @@ -170,9 +179,10 @@ public static function deriveFromPassword( * Generate a new keypair * * @param int $type Key flags - * @param &string $secret_key - Reference to optional variable to store secret key in + * @param string $secret_key - Reference to optional variable to store secret key in * @return KeyPair * @throws CryptoException\InvalidKey + * @psalm-suppress ConflictingReferenceConstraint */ public static function generate($type = Key::CRYPTO_SIGN, &$secret_key = null) { @@ -182,9 +192,7 @@ public static function generate($type = Key::CRYPTO_SIGN, &$secret_key = null) ); } if (Key::hasFlag($type, Key::SIGNATURE)) { - $key = SignatureSecretKey::generate(Key::CRYPTO_SIGN, $secret_key); - $keypair = new SignatureKeyPair(...$key); - return $keypair; + return KeyFactory::generateSignatureKeyPair($secret_key); } throw new CryptoException\InvalidKey( 'You must specify encryption or authentication flags.' @@ -220,25 +228,20 @@ public function getSecretKey() * * @throws CryptoException\InvalidFlags */ - public static function fromFile( - $filePath, - $type = Key::CRYPTO_SIGN - ) { - $keys = SignatureSecretKey::fromFile( - $filePath, - ($type | Key::ASYMMETRIC | Key::ENCRYPTION) - ); - return new SignatureKeyPair(...$keys); + public static function fromFile($filePath) + { + return KeyFactory::loadSignatureKeyPair($filePath); } /** * Save a copy of the secret key to a file * * @param string $filePath - * @return bool|int + * @return bool */ public function saveToFile($filePath) { - return $this->secret_key->saveToFile($filePath); + $saved = KeyFactory::save($this->secret_key, $filePath); + return !empty($saved); } } diff --git a/src/Stream/MutableFile.php b/src/Stream/MutableFile.php index 2653a70..68cd2d1 100644 --- a/src/Stream/MutableFile.php +++ b/src/Stream/MutableFile.php @@ -11,18 +11,34 @@ class MutableFile implements StreamInterface { const CHUNK = 8192; // PHP's fread() buffer is set to 8192 by default - + + /** @var resource $fp */ private $fp; + + /** @var int $pos*/ private $pos; + + /** @var array */ private $stat = []; - + + /** + * MutableFile constructor. + * + * @param string|resource $file + * @throws CryptoException\InvalidType + * @throws CryptoException\FileAccessDenied + */ public function __construct($file) { - if (is_string($file)) { - $this->fp = \fopen($file, 'wb'); + if (\is_string($file)) { + $fp = \fopen($file, 'wb'); + if (!\is_resource($fp)) { + throw new CryptoException\FileAccessDenied('Cannot open file'); + } + $this->fp = $fp; $this->pos = 0; $this->stat = \fstat($this->fp); - } elseif (is_resource($file)) { + } elseif (\is_resource($file)) { $this->fp = $file; $this->pos = \ftell($this->fp); $this->stat = \fstat($this->fp); @@ -108,6 +124,35 @@ public function writeBytes($buf, $num = null) } while ($remaining > 0); return $num; } + + /** + * Where are we in the buffer? + * + * @return int + */ + public function getPos() + { + return (int) $this->pos; + } + /** + * How big is this buffer? + * + * @return int + */ + public function getSize() + { + return (int) $this->stat['size']; + } + + /** + * Get number of bytes remaining + * + * @return int + */ + public function remainingBytes() + { + return (int) (PHP_INT_MAX & ((int) $this->stat['size'] - $this->pos)); + } /** * Set the current cursor position to the desired location diff --git a/src/Stream/ReadOnlyFile.php b/src/Stream/ReadOnlyFile.php index b8f6150..490695c 100644 --- a/src/Stream/ReadOnlyFile.php +++ b/src/Stream/ReadOnlyFile.php @@ -8,19 +8,38 @@ class ReadOnlyFile implements StreamInterface { const CHUNK = 8192; // PHP's fread() buffer is set to 8192 by default - + + /** @var resource */ private $fp; + + /** @var string */ private $hash; + + /** @var int */ private $pos; + + /** @var array */ private $stat = []; - + + /** + * ReadOnlyFile constructor. + * + * @param string|resource $file + * @throws CryptoException\InvalidType + * @throws CryptoException\FileAccessDenied + */ public function __construct($file) { - if (is_string($file)) { - $this->fp = \fopen($file, 'rb'); + if (\is_string($file)) { + /** @var resource $fp */ + $fp = \fopen($file, 'rb'); + if (!\is_resource($fp)) { + throw new CryptoException\FileAccessDenied('Cannot open file'); + } + $this->fp = $fp; $this->pos = 0; $this->stat = \fstat($this->fp); - } elseif (is_resource($file)) { + } elseif (\is_resource($file)) { $this->fp = $file; $this->pos = \ftell($this->fp); $this->stat = \fstat($this->fp); @@ -39,7 +58,7 @@ public function __construct($file) */ public function getPos() { - return $this->pos; + return (int) $this->pos; } /** * How big is this buffer? @@ -48,7 +67,7 @@ public function getPos() */ public function getSize() { - return $this->stat['size']; + return (int) $this->stat['size']; } /** @@ -85,7 +104,7 @@ public function readBytes($num, $skipTests = false) break; } $read = \fread($this->fp, $remaining); - if ($read === false) { + if (!\is_string($read)) { throw new CryptoException\FileAccessDenied( 'Could not read from the file' ); @@ -105,7 +124,7 @@ public function readBytes($num, $skipTests = false) */ public function remainingBytes() { - return (PHP_INT_MAX & ($this->stat['size'] - $this->pos)); + return (int) (PHP_INT_MAX & ((int) $this->stat['size'] - $this->pos)); } /** @@ -113,6 +132,7 @@ public function remainingBytes() * * @param string $buf * @param int $num (number of bytes) + * @return void * @throws CryptoException\FileAccessDenied */ public function writeBytes($buf, $num = null) @@ -153,13 +173,14 @@ public function getHash() \fseek($this->fp, 0, SEEK_SET); // Create a hash context: + /** @var string $h */ $h = \Sodium\crypto_generichash_init( null, \Sodium\CRYPTO_GENERICHASH_BYTES_MAX ); for ($i = 0; $i < $this->stat['size']; $i += self::CHUNK) { if (($i + self::CHUNK) > $this->stat['size']) { - $c = \fread($this->fp, ($this->stat['size'] - $i)); + $c = \fread($this->fp, ((int) $this->stat['size'] - $i)); } else { $c = \fread($this->fp, self::CHUNK); } @@ -167,7 +188,7 @@ public function getHash() } // Reset the file pointer's internal cursor to where it was: \fseek($this->fp, $init, SEEK_SET); - return \Sodium\crypto_generichash_final($h); + return (string) \Sodium\crypto_generichash_final($h); } /** @@ -176,7 +197,7 @@ public function getHash() * size matches their values when the file was first opened. * * @throws CryptoException\FileModified - * @return true + * @return bool */ public function toctouTest() { @@ -191,5 +212,6 @@ public function toctouTest() 'Read-only file has been modified since it was opened for reading' ); } + return true; } } diff --git a/src/Symmetric/Crypto.php b/src/Symmetric/Crypto.php index 6a78e73..cf27cce 100644 --- a/src/Symmetric/Crypto.php +++ b/src/Symmetric/Crypto.php @@ -15,7 +15,7 @@ abstract class Crypto implements Contract\SymmetricKeyCryptoInterface * * @param string $message * @param AuthenticationKey $secretKey - * @param boolean $raw + * @param bool $raw * @return string * @throws CryptoException\InvalidKey * @throws CryptoException\InvalidType @@ -35,12 +35,11 @@ public static function authenticate( 'Argument 2: Expected an instnace of AuthenticationKey' ); } - $config = SymmetricConfig::getConfig(Halite::HALITE_VERSION, 'auth'); - $mac = self::calculateMAC($message, $secretKey->get(), $config); + $mac = self::calculateMAC($message, $secretKey->get()); if ($raw) { return $mac; } - return \Sodium\bin2hex($mac); + return (string) \Sodium\bin2hex($mac); } /** @@ -49,6 +48,7 @@ public static function authenticate( * @param string $ciphertext * @param EncryptionKey $secretKey * @param boolean $raw Don't hex decode the input? + * @return string * @throws CryptoException\InvalidKey * @throws CryptoException\InvalidMessage * @throws CryptoException\InvalidType @@ -70,22 +70,35 @@ public static function decrypt( } if (!$raw) { // We were given hex data: + /** @var string $ciphertext */ $ciphertext = \Sodium\hex2bin($ciphertext); } + $version = ''; + $config = null; + $eKey = ''; + $aKey = ''; + $salt = ''; + $nonce = ''; + $xored = ''; + $auth = ''; + list($version, $config, $salt, $nonce, $xored, $auth) = self::unpackMessageForDecryption($ciphertext); + if (!($config instanceof Config)) { + throw new \TypeError(); + } // Split our keys - list($eKey, $aKey) = self::splitKeys($secretKey, $salt, $config); + list($eKey, $aKey) = self::splitKeys($secretKey, (string) $salt, $config); // Check the MAC first if (!self::verifyMAC( - $auth, - $version . $salt . $nonce . $xored, - $aKey + (string) $auth, + (string) $version . (string) $salt . (string) $nonce . (string) $xored, + (string) $aKey )) { throw new CryptoException\InvalidMessage( - 'Invalid message authenticaiton code' + 'Invalid message authentication code' ); } @@ -93,10 +106,11 @@ public static function decrypt( // need to upgrade our protocol. // Add version logic above - $plaintext = \Sodium\crypto_stream_xor($xored, $nonce, $eKey); - if ($plaintext === false) { + /** @var string $plaintext */ + $plaintext = \Sodium\crypto_stream_xor((string) $xored, (string) $nonce, (string) $eKey); + if (!\is_string($plaintext)) { throw new CryptoException\InvalidMessage( - 'Invalid message authenticaiton code' + 'Decrpytion failed' ); } return $plaintext; @@ -131,25 +145,29 @@ public static function encrypt( $config = SymmetricConfig::getConfig(Halite::HALITE_VERSION, 'encrypt'); // Generate a nonce and HKDF salt: + /** @var string $nonce */ $nonce = \Sodium\randombytes_buf(\Sodium\CRYPTO_SECRETBOX_NONCEBYTES); + /** @var string $salt */ $salt = \Sodium\randombytes_buf($config->HKDF_SALT_LEN); // Split our keys according to the HKDF salt: list($eKey, $aKey) = self::splitKeys($secretKey, $salt, $config); // Encrypt our message with the encryption key: + /** @var string $xored */ $xored = \Sodium\crypto_stream_xor($plaintext, $nonce, $eKey); // Calculate an authentication tag: + /** @var string $auth */ $auth = self::calculateMAC( Halite::HALITE_VERSION . $salt . $nonce . $xored, - $aKey + (string) $aKey ); \Sodium\memzero($eKey); \Sodium\memzero($aKey); if (!$raw) { - return \Sodium\bin2hex( + return (string) \Sodium\bin2hex( Halite::HALITE_VERSION . $salt . $nonce . $xored . $auth ); } @@ -165,32 +183,39 @@ public static function encrypt( * @param Config $config * @return array * @throws CryptoException\InvalidType + * @throws \TypeError */ public static function splitKeys( Contract\KeyInterface $master, $salt = null, Config $config = null ) { + if (!($config instanceof Config)) { + throw new \TypeError(); + } if (!empty($salt) && !is_string($salt)) { throw new CryptoException\InvalidType( 'Argument 2: Expected the salt as a string' ); } + /** @var string $binary */ $binary = $master->get(); - return [ + /** @var array $return */ + $return = [ CryptoUtil::hkdfBlake2b( $binary, - \Sodium\CRYPTO_SECRETBOX_KEYBYTES, - $config->HKDF_SBOX, + (int) \Sodium\CRYPTO_SECRETBOX_KEYBYTES, + (string) $config->HKDF_SBOX, $salt ), CryptoUtil::hkdfBlake2b( $binary, - \Sodium\CRYPTO_AUTH_KEYBYTES, - $config->HKDF_AUTH, + (int) \Sodium\CRYPTO_AUTH_KEYBYTES, + (string) $config->HKDF_AUTH, $salt ) ]; + return $return; } /** @@ -210,32 +235,34 @@ public static function unpackMessageForDecryption($ciphertext) // The HKDF is used for key splitting $salt = CryptoUtil::safeSubstr( $ciphertext, - Halite::VERSION_TAG_LEN, - $config->HKDF_SALT_LEN + (int) Halite::VERSION_TAG_LEN, + (int) $config->HKDF_SALT_LEN ); // This is the nonce (we authenticated it): $nonce = CryptoUtil::safeSubstr( $ciphertext, // 36: - Halite::VERSION_TAG_LEN + $config->HKDF_SALT_LEN, + ((int) Halite::VERSION_TAG_LEN + (int) $config->HKDF_SALT_LEN), // 24: - \Sodium\CRYPTO_STREAM_NONCEBYTES + (int) \Sodium\CRYPTO_STREAM_NONCEBYTES ); // This is the crypto_stream_xor()ed ciphertext $xored = CryptoUtil::safeSubstr( $ciphertext, // 60: - Halite::VERSION_TAG_LEN + - $config->HKDF_SALT_LEN + - \Sodium\CRYPTO_STREAM_NONCEBYTES, + (int) ( + (int) Halite::VERSION_TAG_LEN + + (int) $config->HKDF_SALT_LEN + + (int) \Sodium\CRYPTO_STREAM_NONCEBYTES + ), // $length - 92: - $length - ( - Halite::VERSION_TAG_LEN + - $config->HKDF_SALT_LEN + - \Sodium\CRYPTO_STREAM_NONCEBYTES + - \Sodium\CRYPTO_AUTH_BYTES + $length - (int) ( + (int) Halite::VERSION_TAG_LEN + + (int) $config->HKDF_SALT_LEN + + (int) \Sodium\CRYPTO_STREAM_NONCEBYTES + + (int) \Sodium\CRYPTO_AUTH_BYTES ) ); @@ -250,11 +277,11 @@ public static function unpackMessageForDecryption($ciphertext) /** * Verify a MAC, given a MAC key * - * @param string $message + * @param string $message * @param AuthenticationKey $secretKey - * @param string $mac - * @param boolean $raw - * @return boolean + * @param string $mac + * @param bool $raw + * @return bool * @throws CryptoException\InvalidKey * @throws CryptoException\InvalidType */ @@ -280,13 +307,16 @@ public static function verify( ); } if (!$raw) { + /** @var string $mac */ $mac = \Sodium\hex2bin($mac); } - return self::verifyMAC( + /** @var bool $return */ + $return = self::verifyMAC( $mac, $message, - $secretKey->get() + (string) $secretKey->get() ); + return !empty($return); } /** @@ -300,7 +330,7 @@ protected static function calculateMAC( $message, $authKey ) { - return \Sodium\crypto_auth( + return (string) \Sodium\crypto_auth( $message, $authKey ); @@ -325,7 +355,7 @@ protected static function verifyMAC( 'Message Authentication Code is not the correct length; is it encoded?' ); } - return \Sodium\crypto_auth_verify( + return (bool) \Sodium\crypto_auth_verify( $mac, $message, $aKey diff --git a/src/Symmetric/SecretKey.php b/src/Symmetric/SecretKey.php index f128597..e2f087e 100644 --- a/src/Symmetric/SecretKey.php +++ b/src/Symmetric/SecretKey.php @@ -12,6 +12,7 @@ class SecretKey extends Key implements Contract\KeyInterface */ public function __construct($keyMaterial = '', ...$args) { + /** @var bool $signing */ $signing = \count($args) >= 1 ? $args[0] : false; parent::__construct($keyMaterial, false, $signing, false); } diff --git a/src/Util.php b/src/Util.php index 05515f9..aefa7cf 100644 --- a/src/Util.php +++ b/src/Util.php @@ -41,6 +41,7 @@ public static function hkdfBlake2b($ikm, $length, $info = '', $salt = null) // HKDF-Extract: // PRK = HMAC-Hash(salt, IKM) // The salt is the HMAC key. + /** @var string $prk */ $prk = \Sodium\crypto_generichash($ikm, $salt); // HKDF-Expand: @@ -55,6 +56,7 @@ public static function hkdfBlake2b($ikm, $length, $info = '', $salt = null) $last_block = ''; for ($block_index = 1; self::safeStrlen($t) < $length; ++$block_index) { // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??) + /** @var string $last_block */ $last_block = \Sodium\crypto_generichash( $last_block . $info . \chr($block_index), $prk @@ -94,16 +96,18 @@ public static function safeStrlen($str) ); } if ($exists) { + /** @var int $length */ $length = \mb_strlen($str, '8bit'); - if ($length === false) { + if (!\is_int($length)) { throw new Alerts\CannotPerformOperation( 'mb_strlen() failed unexpectedly' ); } } else { // If we reached here, we can rely on strlen to count bytes: + /** @var int $length */ $length = \strlen($str); - if ($length === false) { + if (!\is_int($length)) { throw new Alerts\CannotPerformOperation( 'strlen() failed unexpectedly' );