summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS4
-rw-r--r--ext/openssl/openssl.c33
-rw-r--r--ext/openssl/tests/cipher_tests.inc53
-rw-r--r--ext/openssl/tests/openssl_decrypt_ocb.phpt60
-rw-r--r--ext/openssl/tests/openssl_encrypt_ocb.phpt60
5 files changed, 203 insertions, 7 deletions
diff --git a/NEWS b/NEWS
index 2202f04b39..e5e20b0e5c 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,10 @@ PHP NEWS
. Fixed bug #64076 (imap_sort() does not return FALSE on failure). (cmb)
. Fixed bug #80239 (imap_rfc822_write_address() leaks memory). (cmb)
+- OpenSSL:
+ . Fixed bug #79983 (openssl_encrypt / openssl_decrypt fail with OCB mode).
+ (Nikita)
+
29 Oct 2020, PHP 7.4.12
- Core:
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index 8489d9bfdd..b18e5f8d99 100644
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -6483,6 +6483,7 @@ PHP_FUNCTION(openssl_digest)
/* Cipher mode info */
struct php_openssl_cipher_mode {
zend_bool is_aead;
+ zend_bool should_set_tag_length;
zend_bool is_single_run_aead;
int aead_get_tag_flag;
int aead_set_tag_flag;
@@ -6491,24 +6492,41 @@ struct php_openssl_cipher_mode {
static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, const EVP_CIPHER *cipher_type) /* {{{ */
{
- switch (EVP_CIPHER_mode(cipher_type)) {
-#ifdef EVP_CIPH_GCM_MODE
+ int cipher_mode = EVP_CIPHER_mode(cipher_type);
+ switch (cipher_mode) {
+#if PHP_OPENSSL_API_VERSION >= 0x10100
+ case EVP_CIPH_GCM_MODE:
+ case EVP_CIPH_OCB_MODE:
+ case EVP_CIPH_CCM_MODE:
+ mode->is_aead = 1;
+ mode->should_set_tag_length =
+ cipher_mode == EVP_CIPH_CCM_MODE || cipher_mode == EVP_CIPH_OCB_MODE;
+ mode->is_single_run_aead = cipher_mode == EVP_CIPH_CCM_MODE;
+ mode->aead_get_tag_flag = EVP_CTRL_AEAD_GET_TAG;
+ mode->aead_set_tag_flag = EVP_CTRL_AEAD_SET_TAG;
+ mode->aead_ivlen_flag = EVP_CTRL_AEAD_SET_IVLEN;
+ break;
+#else
+# ifdef EVP_CIPH_GCM_MODE
case EVP_CIPH_GCM_MODE:
mode->is_aead = 1;
+ mode->should_set_tag_length = 0;
mode->is_single_run_aead = 0;
mode->aead_get_tag_flag = EVP_CTRL_GCM_GET_TAG;
mode->aead_set_tag_flag = EVP_CTRL_GCM_SET_TAG;
mode->aead_ivlen_flag = EVP_CTRL_GCM_SET_IVLEN;
break;
-#endif
-#ifdef EVP_CIPH_CCM_MODE
+# endif
+# ifdef EVP_CIPH_CCM_MODE
case EVP_CIPH_CCM_MODE:
mode->is_aead = 1;
+ mode->should_set_tag_length = 1;
mode->is_single_run_aead = 1;
mode->aead_get_tag_flag = EVP_CTRL_CCM_GET_TAG;
mode->aead_set_tag_flag = EVP_CTRL_CCM_SET_TAG;
mode->aead_ivlen_flag = EVP_CTRL_CCM_SET_IVLEN;
break;
+# endif
#endif
default:
memset(mode, 0, sizeof(struct php_openssl_cipher_mode));
@@ -6593,12 +6611,15 @@ static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type,
if (php_openssl_validate_iv(piv, piv_len, max_iv_len, free_iv, cipher_ctx, mode) == FAILURE) {
return FAILURE;
}
- if (mode->is_single_run_aead && enc) {
+ if (mode->should_set_tag_length) {
+ /* Explicitly set the tag length even when decrypting,
+ * see https://github.com/openssl/openssl/issues/8331. */
if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL)) {
php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed");
return FAILURE;
}
- } else if (!enc && tag && tag_len > 0) {
+ }
+ if (!enc && tag && tag_len > 0) {
if (!mode->is_aead) {
php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher method does not support AEAD");
} else if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag)) {
diff --git a/ext/openssl/tests/cipher_tests.inc b/ext/openssl/tests/cipher_tests.inc
index 779bfa8515..1d4988c5a8 100644
--- a/ext/openssl/tests/cipher_tests.inc
+++ b/ext/openssl/tests/cipher_tests.inc
@@ -108,7 +108,58 @@ $php_openssl_cipher_tests = array(
'01e4a9a4fba43c90ccdcb281d48c7c6f' .
'd62875d2aca417034c34aee5',
),
- )
+ ),
+ // First few test vectors from RFC 7253.
+ 'aes-128-ocb' => array(
+ array(
+ 'key' => '000102030405060708090A0B0C0D0E0F',
+ 'iv' => 'BBAA99887766554433221100',
+ 'aad' => '',
+ 'pt' => '',
+ 'tag' => '785407BFFFC8AD9EDCC5520AC9111EE6',
+ 'ct' => '',
+ ),
+ array(
+ 'key' => '000102030405060708090A0B0C0D0E0F',
+ 'iv' => 'BBAA99887766554433221101',
+ 'aad' => '0001020304050607',
+ 'pt' => '0001020304050607',
+ 'tag' => '5725BDA0D3B4EB3A257C9AF1F8F03009',
+ 'ct' => '6820B3657B6F615A',
+ ),
+ array(
+ 'key' => '000102030405060708090A0B0C0D0E0F',
+ 'iv' => 'BBAA99887766554433221102',
+ 'aad' => '0001020304050607',
+ 'pt' => '',
+ 'tag' => '81017F8203F081277152FADE694A0A00',
+ 'ct' => '',
+ ),
+ array(
+ 'key' => '000102030405060708090A0B0C0D0E0F',
+ 'iv' => 'BBAA99887766554433221103',
+ 'aad' => '',
+ 'pt' => '0001020304050607',
+ 'tag' => '14054CD1F35D82760B2CD00D2F99BFA9',
+ 'ct' => '45DD69F8F5AAE724',
+ ),
+ array(
+ 'key' => '000102030405060708090A0B0C0D0E0F',
+ 'iv' => 'BBAA99887766554433221104',
+ 'aad' => '000102030405060708090A0B0C0D0E0F',
+ 'pt' => '000102030405060708090A0B0C0D0E0F',
+ 'tag' => '3AD7A4FF3835B8C5701C1CCEC8FC3358',
+ 'ct' => '571D535B60B277188BE5147170A9A22C',
+ ),
+ array(
+ 'key' => '0F0E0D0C0B0A09080706050403020100',
+ 'iv' => 'BBAA9988776655443322110D',
+ 'aad' => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627',
+ 'pt' => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627',
+ 'tag' => 'D0C515F4D1CDD4FDAC4F02AA',
+ 'ct' => '1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6A',
+ ),
+ ),
);
function openssl_get_cipher_tests($method)
diff --git a/ext/openssl/tests/openssl_decrypt_ocb.phpt b/ext/openssl/tests/openssl_decrypt_ocb.phpt
new file mode 100644
index 0000000000..cea35501dd
--- /dev/null
+++ b/ext/openssl/tests/openssl_decrypt_ocb.phpt
@@ -0,0 +1,60 @@
+--TEST--
+openssl_decrypt() with OCB cipher algorithm tests
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl"))
+ die("skip");
+if (!in_array('aes-128-ocb', openssl_get_cipher_methods()))
+ die("skip: aes-128-ocb not available");
+?>
+--FILE--
+<?php
+require_once __DIR__ . "/cipher_tests.inc";
+$method = 'aes-128-ocb';
+$tests = openssl_get_cipher_tests($method);
+
+foreach ($tests as $idx => $test) {
+ echo "TEST $idx\n";
+ $pt = openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
+ $test['iv'], $test['tag'], $test['aad']);
+ var_dump($test['pt'] === $pt);
+}
+
+// no IV
+var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
+ NULL, $test['tag'], $test['aad']));
+
+// IV too long
+var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
+ str_repeat('x', 32), $test['tag'], $test['aad']));
+
+// failed because no AAD
+var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
+ $test['iv'], $test['tag']));
+
+// failed because wrong tag
+var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
+ $test['iv'], str_repeat('x', 16), $test['aad']));
+
+?>
+--EXPECTF--
+TEST 0
+bool(true)
+TEST 1
+bool(true)
+TEST 2
+bool(true)
+TEST 3
+bool(true)
+TEST 4
+bool(true)
+TEST 5
+bool(true)
+
+Warning: openssl_decrypt(): Setting of IV length for AEAD mode failed in %s on line %d
+bool(false)
+
+Warning: openssl_decrypt(): Setting of IV length for AEAD mode failed in %s on line %d
+bool(false)
+bool(false)
+bool(false)
diff --git a/ext/openssl/tests/openssl_encrypt_ocb.phpt b/ext/openssl/tests/openssl_encrypt_ocb.phpt
new file mode 100644
index 0000000000..ee35a37ce4
--- /dev/null
+++ b/ext/openssl/tests/openssl_encrypt_ocb.phpt
@@ -0,0 +1,60 @@
+--TEST--
+openssl_encrypt() with OCB cipher algorithm tests
+--SKIPIF--
+<?php
+if (!extension_loaded("openssl"))
+ die("skip");
+if (!in_array('aes-128-ocb', openssl_get_cipher_methods()))
+ die("skip: aes-128-ocb not available");
+?>
+--FILE--
+<?php
+require_once __DIR__ . "/cipher_tests.inc";
+$method = 'aes-128-ocb';
+$tests = openssl_get_cipher_tests($method);
+
+foreach ($tests as $idx => $test) {
+ echo "TEST $idx\n";
+ $ct = openssl_encrypt($test['pt'], $method, $test['key'], OPENSSL_RAW_DATA,
+ $test['iv'], $tag, $test['aad'], strlen($test['tag']));
+ var_dump($test['ct'] === $ct);
+ var_dump($test['tag'] === $tag);
+}
+
+// Empty IV error
+var_dump(openssl_encrypt('data', $method, 'password', 0, NULL, $tag, ''));
+
+// Failing to retrieve tag (must be at most 16 bytes)
+var_dump(openssl_encrypt('data', $method, 'password', 0, str_repeat('x', 12), $tag, '', 20));
+
+// Failing when no tag supplied
+var_dump(openssl_encrypt('data', $method, 'password', 0, str_repeat('x', 12)));
+?>
+--EXPECTF--
+TEST 0
+bool(true)
+bool(true)
+TEST 1
+bool(true)
+bool(true)
+TEST 2
+bool(true)
+bool(true)
+TEST 3
+bool(true)
+bool(true)
+TEST 4
+bool(true)
+bool(true)
+TEST 5
+bool(true)
+bool(true)
+
+Warning: openssl_encrypt(): Setting of IV length for AEAD mode failed in %s on line %d
+bool(false)
+
+Warning: openssl_encrypt(): Setting tag length for AEAD cipher failed in %s on line %d
+bool(false)
+
+Warning: openssl_encrypt(): A tag should be provided when using AEAD mode in %s on line %d
+bool(false)