Skip to content

Commit 750a74e

Browse files
committed
Fix bug #79983: Add support for OCB mode
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.
1 parent a4c1a43 commit 750a74e

File tree

5 files changed

+203
-7
lines changed

5 files changed

+203
-7
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ PHP NEWS
66
. Fixed bug #64076 (imap_sort() does not return FALSE on failure). (cmb)
77
. Fixed bug #80239 (imap_rfc822_write_address() leaks memory). (cmb)
88

9+
- OpenSSL:
10+
. Fixed bug #79983 (openssl_encrypt / openssl_decrypt fail with OCB mode).
11+
(Nikita)
12+
913
29 Oct 2020, PHP 7.4.12
1014

1115
- Core:

ext/openssl/openssl.c

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6483,6 +6483,7 @@ PHP_FUNCTION(openssl_digest)
64836483
/* Cipher mode info */
64846484
struct php_openssl_cipher_mode {
64856485
zend_bool is_aead;
6486+
zend_bool should_set_tag_length;
64866487
zend_bool is_single_run_aead;
64876488
int aead_get_tag_flag;
64886489
int aead_set_tag_flag;
@@ -6491,24 +6492,41 @@ struct php_openssl_cipher_mode {
64916492

64926493
static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, const EVP_CIPHER *cipher_type) /* {{{ */
64936494
{
6494-
switch (EVP_CIPHER_mode(cipher_type)) {
6495-
#ifdef EVP_CIPH_GCM_MODE
6495+
int cipher_mode = EVP_CIPHER_mode(cipher_type);
6496+
switch (cipher_mode) {
6497+
#if PHP_OPENSSL_API_VERSION >= 0x10100
6498+
case EVP_CIPH_GCM_MODE:
6499+
case EVP_CIPH_OCB_MODE:
6500+
case EVP_CIPH_CCM_MODE:
6501+
mode->is_aead = 1;
6502+
mode->should_set_tag_length =
6503+
cipher_mode == EVP_CIPH_CCM_MODE || cipher_mode == EVP_CIPH_OCB_MODE;
6504+
mode->is_single_run_aead = cipher_mode == EVP_CIPH_CCM_MODE;
6505+
mode->aead_get_tag_flag = EVP_CTRL_AEAD_GET_TAG;
6506+
mode->aead_set_tag_flag = EVP_CTRL_AEAD_SET_TAG;
6507+
mode->aead_ivlen_flag = EVP_CTRL_AEAD_SET_IVLEN;
6508+
break;
6509+
#else
6510+
# ifdef EVP_CIPH_GCM_MODE
64966511
case EVP_CIPH_GCM_MODE:
64976512
mode->is_aead = 1;
6513+
mode->should_set_tag_length = 0;
64986514
mode->is_single_run_aead = 0;
64996515
mode->aead_get_tag_flag = EVP_CTRL_GCM_GET_TAG;
65006516
mode->aead_set_tag_flag = EVP_CTRL_GCM_SET_TAG;
65016517
mode->aead_ivlen_flag = EVP_CTRL_GCM_SET_IVLEN;
65026518
break;
6503-
#endif
6504-
#ifdef EVP_CIPH_CCM_MODE
6519+
# endif
6520+
# ifdef EVP_CIPH_CCM_MODE
65056521
case EVP_CIPH_CCM_MODE:
65066522
mode->is_aead = 1;
6523+
mode->should_set_tag_length = 1;
65076524
mode->is_single_run_aead = 1;
65086525
mode->aead_get_tag_flag = EVP_CTRL_CCM_GET_TAG;
65096526
mode->aead_set_tag_flag = EVP_CTRL_CCM_SET_TAG;
65106527
mode->aead_ivlen_flag = EVP_CTRL_CCM_SET_IVLEN;
65116528
break;
6529+
# endif
65126530
#endif
65136531
default:
65146532
memset(mode, 0, sizeof(struct php_openssl_cipher_mode));
@@ -6593,12 +6611,15 @@ static int php_openssl_cipher_init(const EVP_CIPHER *cipher_type,
65936611
if (php_openssl_validate_iv(piv, piv_len, max_iv_len, free_iv, cipher_ctx, mode) == FAILURE) {
65946612
return FAILURE;
65956613
}
6596-
if (mode->is_single_run_aead && enc) {
6614+
if (mode->should_set_tag_length) {
6615+
/* Explicitly set the tag length even when decrypting,
6616+
* see https://github.com/openssl/openssl/issues/8331. */
65976617
if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, NULL)) {
65986618
php_error_docref(NULL, E_WARNING, "Setting tag length for AEAD cipher failed");
65996619
return FAILURE;
66006620
}
6601-
} else if (!enc && tag && tag_len > 0) {
6621+
}
6622+
if (!enc && tag && tag_len > 0) {
66026623
if (!mode->is_aead) {
66036624
php_error_docref(NULL, E_WARNING, "The tag cannot be used because the cipher method does not support AEAD");
66046625
} else if (!EVP_CIPHER_CTX_ctrl(cipher_ctx, mode->aead_set_tag_flag, tag_len, (unsigned char *) tag)) {

ext/openssl/tests/cipher_tests.inc

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,58 @@ $php_openssl_cipher_tests = array(
108108
'01e4a9a4fba43c90ccdcb281d48c7c6f' .
109109
'd62875d2aca417034c34aee5',
110110
),
111-
)
111+
),
112+
// First few test vectors from RFC 7253.
113+
'aes-128-ocb' => array(
114+
array(
115+
'key' => '000102030405060708090A0B0C0D0E0F',
116+
'iv' => 'BBAA99887766554433221100',
117+
'aad' => '',
118+
'pt' => '',
119+
'tag' => '785407BFFFC8AD9EDCC5520AC9111EE6',
120+
'ct' => '',
121+
),
122+
array(
123+
'key' => '000102030405060708090A0B0C0D0E0F',
124+
'iv' => 'BBAA99887766554433221101',
125+
'aad' => '0001020304050607',
126+
'pt' => '0001020304050607',
127+
'tag' => '5725BDA0D3B4EB3A257C9AF1F8F03009',
128+
'ct' => '6820B3657B6F615A',
129+
),
130+
array(
131+
'key' => '000102030405060708090A0B0C0D0E0F',
132+
'iv' => 'BBAA99887766554433221102',
133+
'aad' => '0001020304050607',
134+
'pt' => '',
135+
'tag' => '81017F8203F081277152FADE694A0A00',
136+
'ct' => '',
137+
),
138+
array(
139+
'key' => '000102030405060708090A0B0C0D0E0F',
140+
'iv' => 'BBAA99887766554433221103',
141+
'aad' => '',
142+
'pt' => '0001020304050607',
143+
'tag' => '14054CD1F35D82760B2CD00D2F99BFA9',
144+
'ct' => '45DD69F8F5AAE724',
145+
),
146+
array(
147+
'key' => '000102030405060708090A0B0C0D0E0F',
148+
'iv' => 'BBAA99887766554433221104',
149+
'aad' => '000102030405060708090A0B0C0D0E0F',
150+
'pt' => '000102030405060708090A0B0C0D0E0F',
151+
'tag' => '3AD7A4FF3835B8C5701C1CCEC8FC3358',
152+
'ct' => '571D535B60B277188BE5147170A9A22C',
153+
),
154+
array(
155+
'key' => '0F0E0D0C0B0A09080706050403020100',
156+
'iv' => 'BBAA9988776655443322110D',
157+
'aad' => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627',
158+
'pt' => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627',
159+
'tag' => 'D0C515F4D1CDD4FDAC4F02AA',
160+
'ct' => '1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6A',
161+
),
162+
),
112163
);
113164

114165
function openssl_get_cipher_tests($method)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
openssl_decrypt() with OCB cipher algorithm tests
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("openssl"))
6+
die("skip");
7+
if (!in_array('aes-128-ocb', openssl_get_cipher_methods()))
8+
die("skip: aes-128-ocb not available");
9+
?>
10+
--FILE--
11+
<?php
12+
require_once __DIR__ . "/cipher_tests.inc";
13+
$method = 'aes-128-ocb';
14+
$tests = openssl_get_cipher_tests($method);
15+
16+
foreach ($tests as $idx => $test) {
17+
echo "TEST $idx\n";
18+
$pt = openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
19+
$test['iv'], $test['tag'], $test['aad']);
20+
var_dump($test['pt'] === $pt);
21+
}
22+
23+
// no IV
24+
var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
25+
NULL, $test['tag'], $test['aad']));
26+
27+
// IV too long
28+
var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
29+
str_repeat('x', 32), $test['tag'], $test['aad']));
30+
31+
// failed because no AAD
32+
var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
33+
$test['iv'], $test['tag']));
34+
35+
// failed because wrong tag
36+
var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA,
37+
$test['iv'], str_repeat('x', 16), $test['aad']));
38+
39+
?>
40+
--EXPECTF--
41+
TEST 0
42+
bool(true)
43+
TEST 1
44+
bool(true)
45+
TEST 2
46+
bool(true)
47+
TEST 3
48+
bool(true)
49+
TEST 4
50+
bool(true)
51+
TEST 5
52+
bool(true)
53+
54+
Warning: openssl_decrypt(): Setting of IV length for AEAD mode failed in %s on line %d
55+
bool(false)
56+
57+
Warning: openssl_decrypt(): Setting of IV length for AEAD mode failed in %s on line %d
58+
bool(false)
59+
bool(false)
60+
bool(false)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
openssl_encrypt() with OCB cipher algorithm tests
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded("openssl"))
6+
die("skip");
7+
if (!in_array('aes-128-ocb', openssl_get_cipher_methods()))
8+
die("skip: aes-128-ocb not available");
9+
?>
10+
--FILE--
11+
<?php
12+
require_once __DIR__ . "/cipher_tests.inc";
13+
$method = 'aes-128-ocb';
14+
$tests = openssl_get_cipher_tests($method);
15+
16+
foreach ($tests as $idx => $test) {
17+
echo "TEST $idx\n";
18+
$ct = openssl_encrypt($test['pt'], $method, $test['key'], OPENSSL_RAW_DATA,
19+
$test['iv'], $tag, $test['aad'], strlen($test['tag']));
20+
var_dump($test['ct'] === $ct);
21+
var_dump($test['tag'] === $tag);
22+
}
23+
24+
// Empty IV error
25+
var_dump(openssl_encrypt('data', $method, 'password', 0, NULL, $tag, ''));
26+
27+
// Failing to retrieve tag (must be at most 16 bytes)
28+
var_dump(openssl_encrypt('data', $method, 'password', 0, str_repeat('x', 12), $tag, '', 20));
29+
30+
// Failing when no tag supplied
31+
var_dump(openssl_encrypt('data', $method, 'password', 0, str_repeat('x', 12)));
32+
?>
33+
--EXPECTF--
34+
TEST 0
35+
bool(true)
36+
bool(true)
37+
TEST 1
38+
bool(true)
39+
bool(true)
40+
TEST 2
41+
bool(true)
42+
bool(true)
43+
TEST 3
44+
bool(true)
45+
bool(true)
46+
TEST 4
47+
bool(true)
48+
bool(true)
49+
TEST 5
50+
bool(true)
51+
bool(true)
52+
53+
Warning: openssl_encrypt(): Setting of IV length for AEAD mode failed in %s on line %d
54+
bool(false)
55+
56+
Warning: openssl_encrypt(): Setting tag length for AEAD cipher failed in %s on line %d
57+
bool(false)
58+
59+
Warning: openssl_encrypt(): A tag should be provided when using AEAD mode in %s on line %d
60+
bool(false)

0 commit comments

Comments
 (0)