return ret; } -OPT_TEST_DECLARE_USAGE("certfile privkeyfile derfile\n") +/* + * CMS EnvelopedData with a single PasswordRecipientInfo using + * id-alg-PWRI-KEK and an AES-128-CFB key encryption cipher + * (1-byte effective block size). The encryptedKey OCTET STRING is + * only two bytes long, so the wrapped key buffer is shorter than + * the seven octets read by the check-byte test in kek_unwrap_key(). + * Prior to CVE-2026-9076 this triggered an out-of-bounds heap read; + * CMS_decrypt() must now fail cleanly. + */ +static int test_pwri_kek_unwrap_short_encrypted_key(void) +{ + BIO *in = NULL; + CMS_ContentInfo *cms = NULL; + unsigned long err = 0; + int ret = 0; + + if (!TEST_ptr(in = BIO_new_file(pwri_kek_oob_der_in, "rb")) + || !TEST_ptr(cms = d2i_CMS_bio(in, NULL))) + goto end; + + /* + * The unwrap is attempted eagerly inside CMS_decrypt_set1_password(). + * It must fail cleanly (no OOB read) and report CMS_R_UNWRAP_FAILURE. + */ + if (!TEST_false(CMS_decrypt_set1_password(cms, + (unsigned char *)"password", -1))) + goto end; + + err = ERR_peek_last_error(); + if (!TEST_int_eq(ERR_GET_LIB(err), ERR_LIB_CMS) + || !TEST_int_eq(ERR_GET_REASON(err), CMS_R_UNWRAP_FAILURE)) + goto end; + + ERR_clear_error(); + ret = 1; +end: + CMS_ContentInfo_free(cms); + BIO_free(in); + return ret; +} + +OPT_TEST_DECLARE_USAGE("certfile privkeyfile derfile tooLongIVpem pwriKekOobDer\n") int setup_tests(void) { @@ -499,7 +547,8 @@ int setup_tests(void) if (!TEST_ptr(certin = test_get_argument(0)) || !TEST_ptr(privkeyin = test_get_argument(1)) || !TEST_ptr(derin = test_get_argument(2)) - || !TEST_ptr(too_long_iv_cms_in = test_get_argument(3))) + || !TEST_ptr(too_long_iv_cms_in = test_get_argument(3)) + || !TEST_ptr(pwri_kek_oob_der_in = test_get_argument(4))) return 0; certbio = BIO_new_file(certin, "r"); @@ -535,6 +584,7 @@ int setup_tests(void) ADD_TEST(test_encrypted_data_aead); ADD_ALL_TESTS(test_d2i_CMS_decode, 2); ADD_TEST(test_cms_aesgcm_iv_too_long); + ADD_TEST(test_pwri_kek_unwrap_short_encrypted_key); return 1; } diff --git a/crypto/openssl/test/evp_extra_test.c b/crypto/openssl/test/evp_extra_test.c index 1b0f0711cb57..7bd41db115ca 100644 --- a/crypto/openssl/test/evp_extra_test.c +++ b/crypto/openssl/test/evp_extra_test.c @@ -5414,6 +5414,64 @@ static int test_aes_rc4_keylen_change_cve_2023_5363(void) } #endif +/* + * AES-SIV reuse-without-rekey: + * msg1: legit non-empty CT, tag verifies, final_ret=0 + * msg2: no reinit (or reinit with key=NULL), set forged tag, + * AAD only, DecryptFinal -> does stale final_ret leak through? + */ +static int test_aes_siv_ctx_reuse(void) +{ + unsigned char key[32] = { 7 }; /* AES-128-SIV => 2*16 */ + unsigned char pt[9] = "payload!"; + unsigned char ct[9], tagbuf[16], out[16], zero16[16] = { 0 }; + unsigned char aad[14] = "forged header"; + int outl, ret = 0; + EVP_CIPHER_CTX *e = NULL, *d = NULL; + EVP_CIPHER *c = EVP_CIPHER_fetch(NULL, "AES-128-SIV", NULL); + + if (c == NULL) { + return TEST_skip("AES-128-SIV cipher is not available"); + } + + /* produce a valid (ct,tag) for msg1 */ + e = EVP_CIPHER_CTX_new(); + if (!TEST_ptr(e) + || !TEST_true(EVP_EncryptInit_ex2(e, c, key, NULL, NULL)) + || !TEST_true(EVP_EncryptUpdate(e, NULL, &outl, (unsigned char *)"hdr1", 4)) + || !TEST_true(EVP_EncryptUpdate(e, ct, &outl, pt, sizeof(pt))) + || !TEST_true(EVP_EncryptFinal_ex(e, out, &outl)) + || !TEST_true(EVP_CIPHER_CTX_ctrl(e, EVP_CTRL_AEAD_GET_TAG, 16, tagbuf))) { + EVP_CIPHER_CTX_free(e); + goto err; + } + EVP_CIPHER_CTX_free(e); + + /* msg1 decrypt */ + d = EVP_CIPHER_CTX_new(); + if (!TEST_ptr(d) + || !TEST_true(EVP_DecryptInit_ex2(d, c, key, NULL, NULL)) + || !TEST_true(EVP_CIPHER_CTX_ctrl(d, EVP_CTRL_AEAD_SET_TAG, 16, tagbuf)) + || !TEST_true(EVP_DecryptUpdate(d, NULL, &outl, (unsigned char *)"hdr1", 4)) + || !TEST_true(EVP_DecryptUpdate(d, out, &outl, ct, sizeof(ct))) + || !TEST_true(EVP_DecryptFinal_ex(d, out, &outl))) + goto err; + + /* msg2 on SAME ctx, reinit with key=NULL => initkey skipped, final_ret should be reset */ + if (!TEST_true(EVP_DecryptInit_ex2(d, NULL, NULL, NULL, NULL)) + || !TEST_true(EVP_CIPHER_CTX_ctrl(d, EVP_CTRL_AEAD_SET_TAG, 16, zero16)) + || !TEST_true(EVP_DecryptUpdate(d, NULL, &outl, aad, sizeof(aad))) /* forged AAD */ + || !TEST_false(EVP_DecryptFinal_ex(d, out, &outl))) + goto err; + + ret = 1; + +err: + EVP_CIPHER_CTX_free(d); + EVP_CIPHER_free(c); + return ret; +} + static int test_invalid_ctx_for_digest(void) { int ret; @@ -5637,6 +5695,9 @@ int setup_tests(void) ADD_TEST(test_aes_rc4_keylen_change_cve_2023_5363); #endif + /* Test case for CVE-2026-45446 */ + ADD_TEST(test_aes_siv_ctx_reuse); + ADD_TEST(test_invalid_ctx_for_digest); ADD_TEST(test_evp_cipher_negative_length); diff --git a/crypto/openssl/test/recipes/80-test_cms.t b/crypto/openssl/test/recipes/80-test_cms.t index b6ee61464409..f573651e26bc 100644 --- a/crypto/openssl/test/recipes/80-test_cms.t +++ b/crypto/openssl/test/recipes/80-test_cms.t @@ -51,7 +51,7 @@ my ($no_des, $no_dh, $no_dsa, $no_ec, $no_ec2m, $no_rc2, $no_zlib) $no_rc2 = 1 if disabled("legacy"); -plan tests => 23; +plan tests => 24; ok(run(test(["pkcs7_test"])), "test pkcs7"); @@ -1054,6 +1054,16 @@ ok(!run(app(['openssl', 'cms', '-verify', ])), "issue#19643"); +# Check that users get error when using incorrect envelope type for AEAD algorithms +ok(!run(app(['openssl', 'cms', '-decrypt', + '-inform', 'PEM', '-stream', + '-secretkey', '000102030405060708090A0B0C0D0E0F', + '-secretkeyid', 'C0FEE0', + '-in', srctop_file("test/cms-msg", + "enveloped-content-type-for-aes-gcm.pem") + ])), + "Error AES-GCM in enveloped content type"); + # Check that kari encryption with originator does not segfault with({ exit_checker => sub { return shift == 3; } }, sub { diff --git a/crypto/openssl/test/recipes/80-test_cmsapi.t b/crypto/openssl/test/recipes/80-test_cmsapi.t index 8d9371e005c0..3d1dae846464 100644 --- a/crypto/openssl/test/recipes/80-test_cmsapi.t +++ b/crypto/openssl/test/recipes/80-test_cmsapi.t @@ -19,5 +19,6 @@ plan tests => 1; ok(run(test(["cmsapitest", srctop_file("test", "certs", "servercert.pem"), srctop_file("test", "certs", "serverkey.pem"), srctop_file("test", "recipes", "80-test_cmsapi_data", "encryptedData.der"), - srctop_file("test", "recipes", "80-test_cmsapi_data", "encDataWithTooLongIV.pem")])), + srctop_file("test", "recipes", "80-test_cmsapi_data", "encDataWithTooLongIV.pem"), + srctop_file("test", "recipes", "80-test_cmsapi_data", "cms_pwri_kek_oob.der")])), "running cmsapitest"); diff --git a/crypto/openssl/test/recipes/80-test_cmsapi_data/cms_pwri_kek_oob.der b/crypto/openssl/test/recipes/80-test_cmsapi_data/cms_pwri_kek_oob.der new file mode 100644 index 000000000000..c3ef3abd10e6 Binary files /dev/null and b/crypto/openssl/test/recipes/80-test_cmsapi_data/cms_pwri_kek_oob.der differ