diff --git a/src/Compat.php b/src/Compat.php index d1fb86e6..15f749de 100644 --- a/src/Compat.php +++ b/src/Compat.php @@ -916,15 +916,7 @@ public static function crypto_scalarmult($secretKey, $publicKey) if (ParagonIE_Sodium_Core_Util::hashEquals($publicKey, str_repeat("\0", self::CRYPTO_BOX_PUBLICKEYBYTES))) { throw new Error('Zero public key is not allowed'); } - $q = ParagonIE_Sodium_Crypto::scalarmult($secretKey, $publicKey); - $d = 0; - for ($i = 0; $i < self::CRYPTO_SCALARMULT_BYTES; ++$i) { - $d |= ParagonIE_Sodium_Core_Util::chrToInt($q[$i]); - } - if (-(1 & (($d - 1) >> 8))) { - throw new Error('Zero public key is not allowed'); - } - return $q; + return ParagonIE_Sodium_Crypto::scalarmult($secretKey, $publicKey); } /** @@ -949,15 +941,7 @@ public static function crypto_scalarmult_base($secretKey) if (ParagonIE_Sodium_Core_Util::hashEquals($secretKey, str_repeat("\0", self::CRYPTO_BOX_SECRETKEYBYTES))) { throw new Error('Zero secret key is not allowed'); } - $q = ParagonIE_Sodium_Crypto::scalarmult_base($secretKey); - $d = 0; - for ($i = 0; $i < self::CRYPTO_SCALARMULT_BYTES; ++$i) { - $d |= ParagonIE_Sodium_Core_Util::chrToInt($q[$i]); - } - if (-(1 & (($d - 1) >> 8))) { - throw new Error('Zero public key returned. This is not safe to use.'); - } - return $q; + return ParagonIE_Sodium_Crypto::scalarmult_base($secretKey); } /** diff --git a/src/Crypto.php b/src/Crypto.php index 774d66cb..f39ef88c 100644 --- a/src/Crypto.php +++ b/src/Crypto.php @@ -633,10 +633,20 @@ public static function keyExchange($my_sk, $their_pk, $client_pk, $server_pk) * @param string $sKey * @param string $pKey * @return string + * + * @throws Error */ public static function scalarmult($sKey, $pKey) { - return ParagonIE_Sodium_Core_X25519::crypto_scalarmult_curve25519_ref10($sKey, $pKey); + $q = ParagonIE_Sodium_Core_X25519::crypto_scalarmult_curve25519_ref10($sKey, $pKey); + $d = 0; + for ($i = 0; $i < self::box_curve25519xsalsa20poly1305_SECRETKEYBYTES; ++$i) { + $d |= ParagonIE_Sodium_Core_Util::chrToInt($q[$i]); + } + if (-(1 & (($d - 1) >> 8))) { + throw new Error('Zero public key is not allowed'); + } + return $q; } /** @@ -645,10 +655,20 @@ public static function scalarmult($sKey, $pKey) * * @param string $secret * @return string + * + * @throws Error */ public static function scalarmult_base($secret) { - return ParagonIE_Sodium_Core_X25519::crypto_scalarmult_curve25519_ref10_base($secret); + $q = ParagonIE_Sodium_Core_X25519::crypto_scalarmult_curve25519_ref10_base($secret); + $d = 0; + for ($i = 0; $i < self::box_curve25519xsalsa20poly1305_SECRETKEYBYTES; ++$i) { + $d |= ParagonIE_Sodium_Core_Util::chrToInt($q[$i]); + } + if (-(1 & (($d - 1) >> 8))) { + throw new Error('Zero public key is not allowed'); + } + return $q; } /** diff --git a/tests/unit/CryptoTest.php b/tests/unit/CryptoTest.php index f88d5129..9618b5e0 100644 --- a/tests/unit/CryptoTest.php +++ b/tests/unit/CryptoTest.php @@ -7,6 +7,26 @@ public function setUp() ParagonIE_Sodium_Compat::$disableFallbackForUnitTests = true; } + /** + * @covers ParagonIE_Sodium_Compat::crypto_auth() + * @covers ParagonIE_Sodium_Compat::crypto_auth_verify() + */ + public function testCryptoAuth() + { + $key = random_bytes(ParagonIE_Sodium_Compat::CRYPTO_AUTH_KEYBYTES); + $message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + $message .= random_bytes(64); + + $mac = ParagonIE_Sodium_Compat::crypto_auth($message, $key); + $this->assertTrue( + ParagonIE_Sodium_Compat::crypto_auth_verify($mac, $message, $key) + ); + $message .= 'wrong'; + $this->assertFalse( + ParagonIE_Sodium_Compat::crypto_auth_verify($mac, $message, $key) + ); + } + /** * @covers ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_decrypt() * @covers ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_encrypt() diff --git a/tests/unit/ExceptionTest.php b/tests/unit/ExceptionTest.php new file mode 100644 index 00000000..696c55ea --- /dev/null +++ b/tests/unit/ExceptionTest.php @@ -0,0 +1,340 @@ +fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof TypeError); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof TypeError); + } + + try { + ParagonIE_Sodium_Compat::crypto_auth($message, array()); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof TypeError); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof TypeError); + } + try { + ParagonIE_Sodium_Compat::crypto_auth($key, $message); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + } + + /** + * @covers ParagonIE_Sodium_Compat::crypto_auth_verify() + */ + public function testCryptoAuthVerify() + { + $key = random_bytes(ParagonIE_Sodium_Compat::CRYPTO_AUTH_KEYBYTES); + $message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + $message .= random_bytes(64); + $mac = ParagonIE_Sodium_Compat::crypto_auth($message, $key); + try { + ParagonIE_Sodium_Compat::crypto_auth_verify(array(), $message, $key); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof TypeError); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof TypeError); + } + try { + ParagonIE_Sodium_Compat::crypto_auth_verify($mac, array(), $key); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof TypeError); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof TypeError); + } + try { + ParagonIE_Sodium_Compat::crypto_auth_verify($mac, $message, array()); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof TypeError); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof TypeError); + } + + try { + ParagonIE_Sodium_Compat::crypto_auth_verify($mac, $key, $message); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + + try { + ParagonIE_Sodium_Compat::crypto_auth_verify($message, $mac, $key); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + } + + /** + * @covers ParagonIE_Sodium_Compat::crypto_box() + * @covers ParagonIE_Sodium_Compat::crypto_box_open() + */ + public function testCryptoBox() + { + $nonce = str_repeat("\x00", 24); + $message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + $message .= str_repeat("\x20", 64); + + $zero_key = str_repeat("\x00", 32); + $alice_secret = ParagonIE_Sodium_Core_Util::hex2bin('69f208412d8dd5db9d0c6d18512e86f0ec75665ab841372d57b042b27ef89d8c'); + $alice_public = ParagonIE_Sodium_Core_Util::hex2bin('ac3a70ba35df3c3fae427a7c72021d68f2c1e044040b75f17313c0c8b5d4241d'); + $bob_secret = ParagonIE_Sodium_Core_Util::hex2bin('b581fb5ae182a16f603f39270d4e3b95bc008310b727a11dd4e784a0044d461b'); + $bob_public = ParagonIE_Sodium_Core_Util::hex2bin('e8980c86e032f1eb2975052e8d65bddd15c3b59641174ec9678a53789d92c754'); + + $alice_to_bob = ParagonIE_Sodium_Crypto::box_keypair_from_secretkey_and_publickey( + $alice_secret, + $bob_public + ); + + $alice_to_zero = ParagonIE_Sodium_Crypto::box_keypair_from_secretkey_and_publickey( + $alice_secret, + $zero_key + ); + $bob_to_zero = ParagonIE_Sodium_Crypto::box_keypair_from_secretkey_and_publickey( + $bob_secret, + $zero_key + ); + + try { + ParagonIE_Sodium_Compat::crypto_box($message, $nonce, $alice_to_zero); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + + try { + ParagonIE_Sodium_Compat::crypto_box($message, $nonce, $bob_to_zero); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + $ciphertext = ParagonIE_Sodium_Compat::crypto_box($message, $nonce, $alice_to_bob); + + try { + ParagonIE_Sodium_Compat::crypto_box_open(array(), $nonce, $alice_to_bob); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + + try { + ParagonIE_Sodium_Compat::crypto_box_open($ciphertext, array(), $alice_to_bob); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + + try { + ParagonIE_Sodium_Compat::crypto_box_open($ciphertext, $nonce, array()); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + } + + /** + * @covers ParagonIE_Sodium_Compat::crypto_secretbox() + * @covers ParagonIE_Sodium_Compat::crypto_secretbox_open() + */ + public function testCryptoSecretbox() + { + $message = str_repeat("\x00", 128); + $nonce = str_repeat("\x00", 24); + $key = str_repeat("\x00", 32); + + try { + ParagonIE_Sodium_Compat::crypto_secretbox( + array(), + $nonce, + $key + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + try { + ParagonIE_Sodium_Compat::crypto_secretbox( + $message, + array(), + $key + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + try { + ParagonIE_Sodium_Compat::crypto_secretbox( + $message, + $nonce, + array() + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + + // Now we test decryption + $ciphertext = ParagonIE_Sodium_Compat::crypto_secretbox( + $message, + $nonce, + $key + ); + try { + ParagonIE_Sodium_Compat::crypto_secretbox_open( + array(), + $nonce, + $key + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + try { + ParagonIE_Sodium_Compat::crypto_secretbox_open( + $ciphertext, + array(), + $key + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + try { + ParagonIE_Sodium_Compat::crypto_secretbox_open( + $ciphertext, + $nonce, + array() + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + try { + ParagonIE_Sodium_Compat::crypto_secretbox_open( + ParagonIE_Sodium_Core_Util::substr($ciphertext, 1), + $nonce, + $key + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + } + + /** + * @covers ParagonIE_Sodium_Compat::crypto_sign_verify_detached() + */ + public function testCryptoSignVerifyDetached() + { + $secretKey = str_repeat("\x00", 64); + $publicKey = ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey($secretKey); + $message = str_repeat("\x00", 128); + $signature = ParagonIE_Sodium_Compat::crypto_sign_detached($message, $secretKey); + try { + ParagonIE_Sodium_Compat::crypto_sign_verify_detached( + array(), + $message, + $publicKey + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + try { + ParagonIE_Sodium_Compat::crypto_sign_verify_detached( + $signature, + array(), + $publicKey + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + try { + ParagonIE_Sodium_Compat::crypto_sign_verify_detached( + $signature, + $message, + array() + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Throwable $ex) { + $this->assertTrue($ex instanceof Error); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Error); + } + try { + // Zero byte signature + ParagonIE_Sodium_Compat::crypto_sign_verify_detached( + str_repeat("\x00", 64), + $message, + $publicKey + ); + $this->fail('Silent failure occurred instead of exception being thrown'); + } catch (Exception $ex) { + $this->assertTrue($ex instanceof Exception); + } + } +}