From 8fa2a71f8337ce7b9c70abed444647b9183479ad Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 14 Oct 2020 13:03:03 +0200 Subject: [PATCH 1/3] Add support for OCB mode --- ext/openssl/openssl.c | 21 ++++++-- ext/openssl/tests/cipher_tests.inc | 45 ++++++++++++++++- ext/openssl/tests/openssl_decrypt_ocb.phpt | 49 +++++++++++++++++++ ext/openssl/tests/openssl_encrypt_ocb.phpt | 57 ++++++++++++++++++++++ 4 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 ext/openssl/tests/openssl_decrypt_ocb.phpt create mode 100644 ext/openssl/tests/openssl_encrypt_ocb.phpt diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 8489d9bfddcad..2b811578a6e05 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -6491,8 +6491,20 @@ 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->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->is_single_run_aead = 0; @@ -6500,8 +6512,8 @@ static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, c 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->is_single_run_aead = 1; @@ -6509,6 +6521,7 @@ static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, c 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)); diff --git a/ext/openssl/tests/cipher_tests.inc b/ext/openssl/tests/cipher_tests.inc index 779bfa8515cbd..a6f670e2b1b8c 100644 --- a/ext/openssl/tests/cipher_tests.inc +++ b/ext/openssl/tests/cipher_tests.inc @@ -108,7 +108,50 @@ $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', + ), + ), ); 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 0000000000000..f63096894aa93 --- /dev/null +++ b/ext/openssl/tests/openssl_decrypt_ocb.phpt @@ -0,0 +1,49 @@ +--TEST-- +openssl_decrypt() with OCB cipher algorithm tests +--SKIPIF-- + +--FILE-- + $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'])); +// 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) + +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 0000000000000..593ee52624e94 --- /dev/null +++ b/ext/openssl/tests/openssl_encrypt_ocb.phpt @@ -0,0 +1,57 @@ +--TEST-- +openssl_encrypt() with OCB cipher algorithm tests +--SKIPIF-- + +--FILE-- + $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 exactly 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) + +Warning: openssl_encrypt(): Setting of IV length for AEAD mode failed in %s on line %d +bool(false) + +Warning: openssl_encrypt(): Retrieving verification tag 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) From 12d3149b943c05257026447f1689cf71434f2521 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 15 Oct 2020 11:35:00 +0200 Subject: [PATCH 2/3] Set tag length before encryption --- ext/openssl/openssl.c | 7 +++- ext/openssl/tests/cipher_tests.inc | 38 +++++++++++++--------- ext/openssl/tests/openssl_decrypt_ocb.phpt | 15 +++++++++ ext/openssl/tests/openssl_encrypt_ocb.phpt | 7 ++-- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 2b811578a6e05..ff57beba9fdce 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; @@ -6498,6 +6499,8 @@ static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, c 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; @@ -6507,6 +6510,7 @@ static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, c # 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; @@ -6516,6 +6520,7 @@ static void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, c # 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; @@ -6606,7 +6611,7 @@ 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 (enc && mode->should_set_tag_length) { 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; diff --git a/ext/openssl/tests/cipher_tests.inc b/ext/openssl/tests/cipher_tests.inc index a6f670e2b1b8c..1d4988c5a8c3f 100644 --- a/ext/openssl/tests/cipher_tests.inc +++ b/ext/openssl/tests/cipher_tests.inc @@ -113,43 +113,51 @@ $php_openssl_cipher_tests = array( 'aes-128-ocb' => array( array( 'key' => '000102030405060708090A0B0C0D0E0F', - 'iv' => 'BBAA99887766554433221100', + 'iv' => 'BBAA99887766554433221100', 'aad' => '', - 'pt' => '', + 'pt' => '', 'tag' => '785407BFFFC8AD9EDCC5520AC9111EE6', - 'ct' => '', + 'ct' => '', ), array( 'key' => '000102030405060708090A0B0C0D0E0F', - 'iv' => 'BBAA99887766554433221101', + 'iv' => 'BBAA99887766554433221101', 'aad' => '0001020304050607', - 'pt' => '0001020304050607', + 'pt' => '0001020304050607', 'tag' => '5725BDA0D3B4EB3A257C9AF1F8F03009', - 'ct' => '6820B3657B6F615A', + 'ct' => '6820B3657B6F615A', ), array( 'key' => '000102030405060708090A0B0C0D0E0F', - 'iv' => 'BBAA99887766554433221102', + 'iv' => 'BBAA99887766554433221102', 'aad' => '0001020304050607', - 'pt' => '', + 'pt' => '', 'tag' => '81017F8203F081277152FADE694A0A00', - 'ct' => '', + 'ct' => '', ), array( 'key' => '000102030405060708090A0B0C0D0E0F', - 'iv' => 'BBAA99887766554433221103', + 'iv' => 'BBAA99887766554433221103', 'aad' => '', - 'pt' => '0001020304050607', + 'pt' => '0001020304050607', 'tag' => '14054CD1F35D82760B2CD00D2F99BFA9', - 'ct' => '45DD69F8F5AAE724', + 'ct' => '45DD69F8F5AAE724', ), array( 'key' => '000102030405060708090A0B0C0D0E0F', - 'iv' => 'BBAA99887766554433221104', + 'iv' => 'BBAA99887766554433221104', 'aad' => '000102030405060708090A0B0C0D0E0F', - 'pt' => '000102030405060708090A0B0C0D0E0F', + 'pt' => '000102030405060708090A0B0C0D0E0F', 'tag' => '3AD7A4FF3835B8C5701C1CCEC8FC3358', - 'ct' => '571D535B60B277188BE5147170A9A22C', + 'ct' => '571D535B60B277188BE5147170A9A22C', + ), + array( + 'key' => '0F0E0D0C0B0A09080706050403020100', + 'iv' => 'BBAA9988776655443322110D', + 'aad' => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627', + 'pt' => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627', + 'tag' => 'D0C515F4D1CDD4FDAC4F02AA', + 'ct' => '1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6A', ), ), ); diff --git a/ext/openssl/tests/openssl_decrypt_ocb.phpt b/ext/openssl/tests/openssl_decrypt_ocb.phpt index f63096894aa93..65a3f292907fc 100644 --- a/ext/openssl/tests/openssl_decrypt_ocb.phpt +++ b/ext/openssl/tests/openssl_decrypt_ocb.phpt @@ -23,9 +23,15 @@ foreach ($tests as $idx => $test) { // 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'])); @@ -42,8 +48,17 @@ TEST 3 bool(true) TEST 4 bool(true) +TEST 5 + +Warning: openssl_decrypt(): Setting tag for AEAD cipher decryption 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) + +Warning: openssl_decrypt(): Setting of IV length for AEAD mode failed in %s on line %d +bool(false) + +Warning: openssl_decrypt(): Setting tag for AEAD cipher decryption failed in %s on line %d bool(false) bool(false) diff --git a/ext/openssl/tests/openssl_encrypt_ocb.phpt b/ext/openssl/tests/openssl_encrypt_ocb.phpt index 593ee52624e94..ee35a37ce43b7 100644 --- a/ext/openssl/tests/openssl_encrypt_ocb.phpt +++ b/ext/openssl/tests/openssl_encrypt_ocb.phpt @@ -24,7 +24,7 @@ foreach ($tests as $idx => $test) { // Empty IV error var_dump(openssl_encrypt('data', $method, 'password', 0, NULL, $tag, '')); -// Failing to retrieve tag (must be exactly 16 bytes) +// 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 @@ -46,11 +46,14 @@ 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(): Retrieving verification tag failed in %s on line %d +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 From cefe878efc56cf5bfac5a2da069930f800548793 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 15 Oct 2020 11:42:02 +0200 Subject: [PATCH 3/3] Set tag length before decryption as well --- ext/openssl/openssl.c | 7 +++++-- ext/openssl/tests/openssl_decrypt_ocb.phpt | 6 +----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index ff57beba9fdce..b18e5f8d99c49 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -6611,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 (enc && mode->should_set_tag_length) { + 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/openssl_decrypt_ocb.phpt b/ext/openssl/tests/openssl_decrypt_ocb.phpt index 65a3f292907fc..cea35501dd352 100644 --- a/ext/openssl/tests/openssl_decrypt_ocb.phpt +++ b/ext/openssl/tests/openssl_decrypt_ocb.phpt @@ -49,16 +49,12 @@ bool(true) TEST 4 bool(true) TEST 5 - -Warning: openssl_decrypt(): Setting tag for AEAD cipher decryption failed in %s on line %d -bool(false) +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) - -Warning: openssl_decrypt(): Setting tag for AEAD cipher decryption failed in %s on line %d bool(false) bool(false)