]> granicus.if.org Git - php/commitdiff
Fix bug #79983: Add support for OCB mode
authorNikita Popov <nikita.ppv@gmail.com>
Wed, 14 Oct 2020 11:03:03 +0000 (13:03 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Mon, 19 Oct 2020 09:09:00 +0000 (11:09 +0200)
OCB mode ciphers were already exposed to openssl_encrypt/decrypt,
but misbehaved, because they were not treated as AEAD ciphers.
From that perspective, OCB should be treated the same way as GCM.
In OpenSSL 1.1 the necessary controls were unified under
EVP_CTRL_AEAD_* (and OCB is only supported since OpenSSL 1.1).

Closes GH-6337.

NEWS
ext/openssl/openssl.c
ext/openssl/tests/cipher_tests.inc
ext/openssl/tests/openssl_decrypt_ocb.phpt [new file with mode: 0644]
ext/openssl/tests/openssl_encrypt_ocb.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 2202f04b393049f59dc8847c9a7534b20f7bd2fa..e5e20b0e5c31e3aab9ed00066c569d83df750f82 100644 (file)
--- 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:
index 8489d9bfddcadcfcf99c97a0d13c2bfc02becea3..b18e5f8d99c495c393ccef24273882ece76698e0 100644 (file)
@@ -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)) {
index 779bfa8515cbd440714037596f292c54fca33d50..1d4988c5a8c3f4433a535627a269bd57f2a0a1ef 100644 (file)
@@ -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 (file)
index 0000000..cea3550
--- /dev/null
@@ -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 (file)
index 0000000..ee35a37
--- /dev/null
@@ -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)