From e508c3431d8e1ace6118e150837a0d0d67f1672a Mon Sep 17 00:00:00 2001 From: Gordon Tetlow Date: Wed, 29 Apr 2026 18:23:24 +1000 Subject: [PATCH] openssl: Fix multiple vulnerabilities This is a rollup commit from upstream to fix: Reject oversized inputs in ASN1_mbstring_ncopy() cms: kek_unwrap_key: Fix out-of-bounds read in check-byte validation cms: kek_unwrap_key: test for fix out-of-bounds read in check-byte validation Avoid length truncation in ASN1_STRING_set pkcs12: verify that the pbmac1 key length is safe Reject potentially forged encrypted CMS AuthEnvelopedData messages QUIC stack must limit the number of PATH_CHALLENGE frames processed in RX Fix NULL dereference in QUIC address validation Fix potential NULL dereference processing CMS PasswordRecipientInfo Fix potential NULL dereference in OSSL_CRMF_ENCRYPTEDVALUE_decrypt() Enforce implicit rejection for CMS/PKCS#7 decryption Use the correct issuer when validating rootCAKeyUpdate Match the local q DHX parameter against the peer's q Apply the buffered IV on the AES-OCB EVP_Cipher() path Fix handling of empty-ciphertext messages in AES-GCM-SIV and AES-SIV Fix possible use-after-free in OpenSSL PKCS7_verify() Approved by: so Obtained from: OpenSSL Security: FreeBSD-SA-26:35.openssl Security: CVE-2026-7383 Security: CVE-2026-9076 Security: CVE-2026-34180 Security: CVE-2026-34181 Security: CVE-2026-34182 Security: CVE-2026-34183 Security: CVE-2026-42764 Security: CVE-2026-42766 Security: CVE-2026-42767 Security: CVE-2026-42768 Security: CVE-2026-42769 Security: CVE-2026-42770 Security: CVE-2026-45445 Security: CVE-2026-45446 Security: CVE-2026-45447 --- crypto/openssl/crypto/asn1/a_mbstr.c | 31 +++- crypto/openssl/crypto/asn1/tasn_dec.c | 24 ++- crypto/openssl/crypto/cmp/cmp_genm.c | 6 +- crypto/openssl/crypto/cms/cms_enc.c | 10 +- crypto/openssl/crypto/cms/cms_env.c | 7 - crypto/openssl/crypto/cms/cms_pwri.c | 13 +- crypto/openssl/crypto/crmf/crmf_lib.c | 10 +- crypto/openssl/crypto/pkcs12/p12_mutl.c | 8 +- crypto/openssl/crypto/pkcs7/pk7_doit.c | 7 - crypto/openssl/crypto/pkcs7/pk7_smime.c | 9 +- crypto/openssl/doc/man3/CMS_decrypt.pod | 4 +- crypto/openssl/doc/man3/PKCS7_decrypt.pod | 10 +- crypto/openssl/include/internal/quic_cfq.h | 1 + .../openssl/include/internal/quic_channel.h | 1 + crypto/openssl/include/internal/quic_fifd.h | 1 + .../ciphers/cipher_aes_gcm_siv_hw.c | 27 ++-- .../implementations/ciphers/cipher_aes_ocb.c | 13 ++ .../implementations/ciphers/cipher_aes_siv.c | 3 + .../implementations/exchange/dh_exch.c | 5 +- crypto/openssl/ssl/quic/quic_cfq.c | 15 ++ crypto/openssl/ssl/quic/quic_channel.c | 6 + crypto/openssl/ssl/quic/quic_channel_local.h | 39 +++++ crypto/openssl/ssl/quic/quic_fifd.c | 43 ++++++ crypto/openssl/ssl/quic/quic_port.c | 6 +- crypto/openssl/ssl/quic/quic_rx_depack.c | 62 ++++---- crypto/openssl/ssl/quic/quic_txp.c | 2 + crypto/openssl/test/cmsapitest.c | 48 +++++- crypto/openssl/test/evp_extra_test.c | 140 ++++++++++++++++++ crypto/openssl/test/recipes/80-test_cmsapi.t | 3 +- .../80-test_cmsapi_data/cms_pwri_kek_oob.der | Bin 0 -> 193 bytes crypto/openssl/test/recipes/80-test_pkcs12.t | 13 +- .../pbmac1_256_256.bad-key-len.p12 | Bin 0 -> 2803 bytes .../pbmac1_256_256.good-shorter-key-len.p12 | Bin 0 -> 2803 bytes 33 files changed, 473 insertions(+), 94 deletions(-) create mode 100644 crypto/openssl/test/recipes/80-test_cmsapi_data/cms_pwri_kek_oob.der create mode 100644 crypto/openssl/test/recipes/80-test_pkcs12_data/pbmac1_256_256.bad-key-len.p12 create mode 100644 crypto/openssl/test/recipes/80-test_pkcs12_data/pbmac1_256_256.good-shorter-key-len.p12 diff --git a/crypto/openssl/crypto/asn1/a_mbstr.c b/crypto/openssl/crypto/asn1/a_mbstr.c index 2270e63d51d..962e19b2cea 100644 --- a/crypto/openssl/crypto/asn1/a_mbstr.c +++ b/crypto/openssl/crypto/asn1/a_mbstr.c @@ -174,11 +174,27 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len, break; case MBSTRING_BMP: + if (nchar > INT_MAX / 2) { + ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG); + if (free_out) { + ASN1_STRING_free(dest); + *out = NULL; + } + return -1; + } outlen = nchar << 1; cpyfunc = cpy_bmp; break; case MBSTRING_UNIV: + if (nchar > INT_MAX / 4) { + ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG); + if (free_out) { + ASN1_STRING_free(dest); + *out = NULL; + } + return -1; + } outlen = nchar << 2; cpyfunc = cpy_univ; break; @@ -186,8 +202,11 @@ int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len, case MBSTRING_UTF8: outlen = 0; ret = traverse_string(in, len, inform, out_utf8, &outlen); - if (ret < 0) { - ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING); + if (ret < 0) { /* error already raised in out_utf8() */ + if (free_out) { + ASN1_STRING_free(dest); + *out = NULL; + } return -1; } cpyfunc = cpy_utf8; @@ -270,9 +289,15 @@ static int out_utf8(unsigned long value, void *arg) int *outlen, len; len = UTF8_putc(NULL, -1, value); - if (len <= 0) + if (len <= 0) { + ERR_raise(ERR_LIB_ASN1, ASN1_R_INVALID_UTF8STRING); return len; + } outlen = arg; + if (*outlen > INT_MAX - len) { + ERR_raise(ERR_LIB_ASN1, ASN1_R_STRING_TOO_LONG); + return -1; + } *outlen += len; return 1; } diff --git a/crypto/openssl/crypto/asn1/tasn_dec.c b/crypto/openssl/crypto/asn1/tasn_dec.c index 91c2e524f55..e9532b9f48f 100644 --- a/crypto/openssl/crypto/asn1/tasn_dec.c +++ b/crypto/openssl/crypto/asn1/tasn_dec.c @@ -54,7 +54,7 @@ static int asn1_d2i_ex_primitive(ASN1_VALUE **pval, const ASN1_ITEM *it, int tag, int aclass, char opt, ASN1_TLC *ctx); -static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len, +static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, long len, int utype, char *free_cont, const ASN1_ITEM *it); /* Table to convert tags to bit values, used for MSTRING type */ @@ -855,19 +855,24 @@ static int asn1_d2i_ex_primitive(ASN1_VALUE **pval, /* Translate ASN1 content octets into a structure */ -static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len, +static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, long len, int utype, char *free_cont, const ASN1_ITEM *it) { ASN1_VALUE **opval = NULL; ASN1_STRING *stmp; ASN1_TYPE *typ = NULL; int ret = 0; + int ilen = (int)len; const ASN1_PRIMITIVE_FUNCS *pf; ASN1_INTEGER **tint; pf = it->funcs; - if (pf && pf->prim_c2i) - return pf->prim_c2i(pval, cont, len, utype, free_cont, it); + if (pf && pf->prim_c2i) { + if (len == (long)ilen) + return pf->prim_c2i(pval, cont, ilen, utype, free_cont, it); + ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_LONG); + return 0; + } /* If ANY type clear type and set pointer to internal value */ if (it->utype == V_ASN1_ANY) { if (*pval == NULL) { @@ -885,7 +890,8 @@ static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len, } switch (utype) { case V_ASN1_OBJECT: - if (!ossl_c2i_ASN1_OBJECT((ASN1_OBJECT **)pval, &cont, len)) + if (len != (long)ilen + || !ossl_c2i_ASN1_OBJECT((ASN1_OBJECT **)pval, &cont, ilen)) goto err; break; @@ -940,6 +946,10 @@ static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len, case V_ASN1_SET: case V_ASN1_SEQUENCE: default: + if (len != (long)ilen) { + ERR_raise(ERR_LIB_ASN1, ASN1_R_TOO_LONG); + goto err; + } if (utype == V_ASN1_BMPSTRING && (len & 1)) { ERR_raise(ERR_LIB_ASN1, ASN1_R_BMPSTRING_IS_WRONG_LENGTH); goto err; @@ -970,10 +980,10 @@ static int asn1_ex_c2i(ASN1_VALUE **pval, const unsigned char *cont, int len, } /* If we've already allocated a buffer use it */ if (*free_cont) { - ASN1_STRING_set0(stmp, (unsigned char *)cont /* UGLY CAST! */, len); + ASN1_STRING_set0(stmp, (unsigned char *)cont /* UGLY CAST! */, ilen); *free_cont = 0; } else { - if (!ASN1_STRING_set(stmp, cont, len)) { + if (!ASN1_STRING_set(stmp, cont, ilen)) { ERR_raise(ERR_LIB_ASN1, ERR_R_ASN1_LIB); ASN1_STRING_free(stmp); *pval = NULL; diff --git a/crypto/openssl/crypto/cmp/cmp_genm.c b/crypto/openssl/crypto/cmp/cmp_genm.c index bcc121f1469..ec1f03d20c1 100644 --- a/crypto/openssl/crypto/cmp/cmp_genm.c +++ b/crypto/openssl/crypto/cmp/cmp_genm.c @@ -202,7 +202,7 @@ static int selfsigned_verify_cb(int ok, X509_STORE_CTX *store_ctx) for (i = 0; i < sk_X509_num(trust); i++) { issuer = sk_X509_value(trust, i); if ((*check_issued)(store_ctx, cert, issuer)) { - if (X509_add_cert(chain, cert, X509_ADD_FLAG_UP_REF)) + if (X509_add_cert(chain, issuer, X509_ADD_FLAG_UP_REF)) ok = 1; break; } @@ -235,6 +235,7 @@ static int verify_ss_cert(OSSL_LIB_CTX *libctx, const char *propq, if ((csc = X509_STORE_CTX_new_ex(libctx, propq)) == NULL || !X509_STORE_CTX_init(csc, ts, target, untrusted)) goto err; + X509_STORE_CTX_set_flags(csc, X509_V_FLAG_CHECK_SS_SIGNATURE); X509_STORE_CTX_set_verify_cb(csc, selfsigned_verify_cb); ok = X509_verify_cert(csc) > 0; @@ -253,7 +254,8 @@ verify_ss_cert_trans(OSSL_CMP_CTX *ctx, X509 *trusted /* may be NULL */, int res = 0; if (trusted != NULL) { - X509_VERIFY_PARAM *vpm = X509_STORE_get0_param(ts); + X509_VERIFY_PARAM *vpm = (ts == NULL) ? NULL + : X509_STORE_get0_param(ts); if ((ts = X509_STORE_new()) == NULL) return 0; diff --git a/crypto/openssl/crypto/cms/cms_enc.c b/crypto/openssl/crypto/cms/cms_enc.c index 08afb5ab114..ba7082cebd7 100644 --- a/crypto/openssl/crypto/cms/cms_enc.c +++ b/crypto/openssl/crypto/cms/cms_enc.c @@ -109,13 +109,15 @@ BIO *ossl_cms_EncryptedContent_init_bio(CMS_EncryptedContentInfo *ec, goto err; } piv = aparams.iv; - if (ec->taglen > 0 - && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, - ec->taglen, ec->tag) - <= 0) { + + if (ec->taglen < 4 || ec->taglen > 16 + || EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, (int)ec->taglen, ec->tag) <= 0) { ERR_raise(ERR_LIB_CMS, CMS_R_CIPHER_AEAD_SET_TAG_ERROR); goto err; } + } else if (auth) { + ERR_raise(ERR_LIB_CMS, CMS_R_UNSUPPORTED_CONTENT_ENCRYPTION_ALGORITHM); + goto err; } } len = EVP_CIPHER_CTX_get_key_length(ctx); diff --git a/crypto/openssl/crypto/cms/cms_env.c b/crypto/openssl/crypto/cms/cms_env.c index 0828d157fad..70dd59c0616 100644 --- a/crypto/openssl/crypto/cms/cms_env.c +++ b/crypto/openssl/crypto/cms/cms_env.c @@ -619,13 +619,6 @@ static int cms_RecipientInfo_ktri_decrypt(CMS_ContentInfo *cms, if (!ossl_cms_env_asn1_ctrl(ri, 1)) goto err; - if (EVP_PKEY_is_a(pkey, "RSA")) - /* upper layer CMS code incorrectly assumes that a successful RSA - * decryption means that the key matches ciphertext (which never - * was the case, implicit rejection or not), so to make it work - * disable implicit rejection for RSA keys */ - EVP_PKEY_CTX_ctrl_str(ktri->pctx, "rsa_pkcs1_implicit_rejection", "0"); - if (evp_pkey_decrypt_alloc(ktri->pctx, &ek, &eklen, fixlen, ktri->encryptedKey->data, ktri->encryptedKey->length) diff --git a/crypto/openssl/crypto/cms/cms_pwri.c b/crypto/openssl/crypto/cms/cms_pwri.c index d62dbbde881..faf6a164669 100644 --- a/crypto/openssl/crypto/cms/cms_pwri.c +++ b/crypto/openssl/crypto/cms/cms_pwri.c @@ -200,18 +200,18 @@ static int kek_unwrap_key(unsigned char *out, size_t *outlen, const unsigned char *in, size_t inlen, EVP_CIPHER_CTX *ctx) { - size_t blocklen = EVP_CIPHER_CTX_get_block_size(ctx); + int blocklen = EVP_CIPHER_CTX_get_block_size(ctx); unsigned char *tmp; int outl, rv = 0; - if (blocklen == 0) + if (blocklen < 4) return 0; - if (inlen < 2 * blocklen) { + if (inlen < 2 * (size_t)blocklen) { /* too small */ return 0; } - if (inlen % blocklen) { + if (inlen > INT_MAX || inlen % blocklen) { /* Invalid size */ return 0; } @@ -367,6 +367,11 @@ int ossl_cms_RecipientInfo_pwri_crypt(const CMS_ContentInfo *cms, /* Finish password based key derivation to setup key in "ctx" */ + if (algtmp == NULL) { + ERR_raise_data(ERR_LIB_CMS, CMS_R_INVALID_KEY_ENCRYPTION_PARAMETER, + "Missing KeyDerivationAlgorithm"); + goto err; + } if (!EVP_PBE_CipherInit_ex(algtmp->algorithm, (char *)pwri->pass, (int)pwri->passlen, algtmp->parameter, kekctx, en_de, diff --git a/crypto/openssl/crypto/crmf/crmf_lib.c b/crypto/openssl/crypto/crmf/crmf_lib.c index d5c8983b2fd..34477d52662 100644 --- a/crypto/openssl/crypto/crmf/crmf_lib.c +++ b/crypto/openssl/crypto/crmf/crmf_lib.c @@ -766,6 +766,7 @@ unsigned char *OSSL_CRMF_ENCRYPTEDVALUE_decrypt(const OSSL_CRMF_ENCRYPTEDVALUE * EVP_CIPHER *cipher = NULL; /* used cipher */ int cikeysize = 0; /* key size from cipher */ unsigned char *iv = NULL; /* initial vector for symmetric encryption */ + int iv_len; /* iv length */ unsigned char *out = NULL; /* decryption output buffer */ int n, ret = 0; EVP_PKEY_CTX *pkctx = NULL; /* private key context */ @@ -820,11 +821,12 @@ unsigned char *OSSL_CRMF_ENCRYPTEDVALUE_decrypt(const OSSL_CRMF_ENCRYPTEDVALUE * } else { goto end; } - if ((iv = OPENSSL_malloc(EVP_CIPHER_get_iv_length(cipher))) == NULL) + iv_len = EVP_CIPHER_get_iv_length(cipher); + if ((iv = OPENSSL_malloc(iv_len)) == NULL) goto end; - if (ASN1_TYPE_get_octetstring(enc->symmAlg->parameter, iv, - EVP_CIPHER_get_iv_length(cipher)) - != EVP_CIPHER_get_iv_length(cipher)) { + if (enc->symmAlg->parameter == NULL + || ASN1_TYPE_get_octetstring(enc->symmAlg->parameter, iv, iv_len) + != iv_len) { ERR_raise(ERR_LIB_CRMF, CRMF_R_MALFORMED_IV); goto end; } diff --git a/crypto/openssl/crypto/pkcs12/p12_mutl.c b/crypto/openssl/crypto/pkcs12/p12_mutl.c index 01956252df7..15072e12f26 100644 --- a/crypto/openssl/crypto/pkcs12/p12_mutl.c +++ b/crypto/openssl/crypto/pkcs12/p12_mutl.c @@ -144,11 +144,13 @@ static int PBMAC1_PBKDF2_HMAC(OSSL_LIB_CTX *ctx, const char *propq, } pbkdf2_salt = pbkdf2_param->salt->value.octet_string; - /* RFC 9579 specifies missing key length as invalid */ + /* RFC 9879 specifies missing key length as invalid */ if (pbkdf2_param->keylength != NULL) keylen = ASN1_INTEGER_get(pbkdf2_param->keylength); - if (keylen <= 0 || keylen > EVP_MAX_MD_SIZE) { - ERR_raise(ERR_LIB_PKCS12, PKCS12_R_PARSE_ERROR); + /* RFC 9879 specifies too short key length as untrustworthy too */ + if (keylen < 20 || keylen > EVP_MAX_MD_SIZE) { + ERR_raise_data(ERR_LIB_PKCS12, PKCS12_R_PARSE_ERROR, + "Invalid Key length (%d is not in the range 20..64)", keylen); goto err; } diff --git a/crypto/openssl/crypto/pkcs7/pk7_doit.c b/crypto/openssl/crypto/pkcs7/pk7_doit.c index d6513cf3a37..1ec7895fc19 100644 --- a/crypto/openssl/crypto/pkcs7/pk7_doit.c +++ b/crypto/openssl/crypto/pkcs7/pk7_doit.c @@ -203,13 +203,6 @@ static int pkcs7_decrypt_rinfo(unsigned char **pek, int *peklen, if (EVP_PKEY_decrypt_init(pctx) <= 0) goto err; - if (EVP_PKEY_is_a(pkey, "RSA")) - /* upper layer pkcs7 code incorrectly assumes that a successful RSA - * decryption means that the key matches ciphertext (which never - * was the case, implicit rejection or not), so to make it work - * disable implicit rejection for RSA keys */ - EVP_PKEY_CTX_ctrl_str(pctx, "rsa_pkcs1_implicit_rejection", "0"); - ret = evp_pkey_decrypt_alloc(pctx, &ek, &eklen, fixlen, ri->enc_key->data, ri->enc_key->length); if (ret <= 0) diff --git a/crypto/openssl/crypto/pkcs7/pk7_smime.c b/crypto/openssl/crypto/pkcs7/pk7_smime.c index 97f20058979..dc003ee2aff 100644 --- a/crypto/openssl/crypto/pkcs7/pk7_smime.c +++ b/crypto/openssl/crypto/pkcs7/pk7_smime.c @@ -222,6 +222,7 @@ int PKCS7_verify(PKCS7 *p7, STACK_OF(X509) *certs, X509_STORE *store, int i, j = 0, k, ret = 0; BIO *p7bio = NULL; BIO *tmpout = NULL; + BIO *next = NULL; const PKCS7_CTX *p7_ctx; if (p7 == NULL) { @@ -352,9 +353,11 @@ int PKCS7_verify(PKCS7 *p7, STACK_OF(X509) *certs, X509_STORE *store, BIO_free(tmpout); X509_STORE_CTX_free(cert_ctx); OPENSSL_free(buf); - if (indata != NULL) - BIO_pop(p7bio); - BIO_free_all(p7bio); + while (p7bio != NULL && p7bio != indata) { + next = BIO_pop(p7bio); + BIO_free(p7bio); + p7bio = next; + } sk_X509_free(signers); sk_X509_free(untrusted); return ret; diff --git a/crypto/openssl/doc/man3/CMS_decrypt.pod b/crypto/openssl/doc/man3/CMS_decrypt.pod index 121b74a30a1..66a94287b6f 100644 --- a/crypto/openssl/doc/man3/CMS_decrypt.pod +++ b/crypto/openssl/doc/man3/CMS_decrypt.pod @@ -68,7 +68,7 @@ then the above behaviour is modified and an error B returned if no recipient encrypted key can be decrypted B generating a random content encryption key. Applications should use this flag with B especially in automated gateways as it can leave them -open to attack. +open to attack. See L for more details. It is possible to determine the correct recipient key by other means (for example looking them up in a database) and setting them in the CMS structure @@ -103,7 +103,7 @@ mentioned in CMS_verify() also applies to CMS_decrypt(). =head1 SEE ALSO -L, L +L, L, L =head1 HISTORY diff --git a/crypto/openssl/doc/man3/PKCS7_decrypt.pod b/crypto/openssl/doc/man3/PKCS7_decrypt.pod index aea15937ab8..cfb5b3f8737 100644 --- a/crypto/openssl/doc/man3/PKCS7_decrypt.pod +++ b/crypto/openssl/doc/man3/PKCS7_decrypt.pod @@ -22,6 +22,14 @@ B is an optional set of flags. Although the recipients certificate is not needed to decrypt the data it is needed to locate the appropriate (of possible several) recipients in the PKCS#7 structure. +When RSA PKCS#1 v1.5 Key Transport is in use, the invoked EVP_PKEY_decrypt() +will use implicit rejection mechanism. It always returns the result of RSA +decryption of the symmetric key to avoid Marvin attack. This result is +deterministic and can happen to match the symmetric cipher used for the content +encryption. In case when the certificate is not provided, the last +RecipientInfo producing the key looking valid will be used. It may cause +getting garbage content on decryption. + The following flags can be passed in the B parameter. If the B flag is set MIME headers for type B are deleted @@ -43,7 +51,7 @@ mentioned in PKCS7_sign() also applies to PKCS7_verify(). =head1 SEE ALSO -L, L +L, L, L =head1 COPYRIGHT diff --git a/crypto/openssl/include/internal/quic_cfq.h b/crypto/openssl/include/internal/quic_cfq.h index 0b2a3a4cb2d..96c8d89eb60 100644 --- a/crypto/openssl/include/internal/quic_cfq.h +++ b/crypto/openssl/include/internal/quic_cfq.h @@ -149,6 +149,7 @@ QUIC_CFQ_ITEM *ossl_quic_cfq_get_priority_head(const QUIC_CFQ *cfq, QUIC_CFQ_ITEM *ossl_quic_cfq_item_get_priority_next(const QUIC_CFQ_ITEM *item, uint32_t pn_space); +int ossl_quic_cfq_discard_unreliable(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item); #endif #endif diff --git a/crypto/openssl/include/internal/quic_channel.h b/crypto/openssl/include/internal/quic_channel.h index b917b966abe..cfaeab72817 100644 --- a/crypto/openssl/include/internal/quic_channel.h +++ b/crypto/openssl/include/internal/quic_channel.h @@ -468,6 +468,7 @@ int ossl_quic_bind_channel(QUIC_CHANNEL *ch, const BIO_ADDR *peer, const QUIC_CONN_ID *scid, const QUIC_CONN_ID *dcid, const QUIC_CONN_ID *odcid); +void ossl_ch_reset_rx_state(QUIC_CHANNEL *ch); #endif #endif diff --git a/crypto/openssl/include/internal/quic_fifd.h b/crypto/openssl/include/internal/quic_fifd.h index 4ea7a2e0d22..afa330cbc4a 100644 --- a/crypto/openssl/include/internal/quic_fifd.h +++ b/crypto/openssl/include/internal/quic_fifd.h @@ -83,6 +83,7 @@ int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt); void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg), void *arg); +void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *tpkt); #endif #endif diff --git a/crypto/openssl/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c b/crypto/openssl/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c index d0b6ae4b070..5bdc567b4bb 100644 --- a/crypto/openssl/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c +++ b/crypto/openssl/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c @@ -58,6 +58,9 @@ static int aes_gcm_siv_initkey(void *vctx) memset(&data, 0, sizeof(data)); memcpy(&data.block[sizeof(data.counter)], ctx->nonce, NONCE_SIZE); + ctx->generated_tag = 0; + memset(ctx->tag, 0, TAG_SIZE); + /* msg_auth_key is always 16 bytes in size, regardless of AES128/AES256 */ /* counter is stored little-endian */ for (i = 0; i < BLOCK_SIZE; i += 8) { @@ -134,17 +137,6 @@ static int aes_gcm_siv_aad(PROV_AES_GCM_SIV_CTX *ctx, return 1; } -static int aes_gcm_siv_finish(PROV_AES_GCM_SIV_CTX *ctx) -{ - int ret = 0; - - if (ctx->enc) - return ctx->generated_tag; - ret = !CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag)); - ret &= ctx->have_user_tag; - return ret; -} - static int aes_gcm_siv_encrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *in, unsigned char *out, size_t len) { @@ -271,6 +263,19 @@ static int aes_gcm_siv_decrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *i return !error; } +static int aes_gcm_siv_finish(PROV_AES_GCM_SIV_CTX *ctx) +{ + int ret = 0; + + if (ctx->enc) + return ctx->generated_tag; + if (!ctx->generated_tag) + aes_gcm_siv_decrypt(ctx, NULL, NULL, 0); + ret = !CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag)); + ret &= ctx->have_user_tag; + return ret; +} + static int aes_gcm_siv_cipher(void *vctx, unsigned char *out, const unsigned char *in, size_t len) { diff --git a/crypto/openssl/providers/implementations/ciphers/cipher_aes_ocb.c b/crypto/openssl/providers/implementations/ciphers/cipher_aes_ocb.c index b724c425e39..99254cb49a8 100644 --- a/crypto/openssl/providers/implementations/ciphers/cipher_aes_ocb.c +++ b/crypto/openssl/providers/implementations/ciphers/cipher_aes_ocb.c @@ -514,6 +514,19 @@ static int aes_ocb_cipher(void *vctx, unsigned char *out, size_t *outl, return 0; } + /* + * Mirror the streaming handler: refuse if the key has not been set, + * and push the buffered IV into the OCB context before any data is + * processed. Without this, CRYPTO_ocb128_encrypt/decrypt runs with + * Offset_0 = 0 regardless of the caller's IV -- catastrophic + * (key, nonce) reuse, and a subsequent EVP_*Final_ex() emits a tag + * that is a function of (key, iv) only. + */ + if (!ctx->key_set || !update_iv(ctx)) { + ERR_raise(ERR_LIB_PROV, PROV_R_CIPHER_OPERATION_FAILED); + return 0; + } + if (!aes_generic_ocb_cipher(ctx, in, out, inl)) { ERR_raise(ERR_LIB_PROV, PROV_R_CIPHER_OPERATION_FAILED); return 0; diff --git a/crypto/openssl/providers/implementations/ciphers/cipher_aes_siv.c b/crypto/openssl/providers/implementations/ciphers/cipher_aes_siv.c index 96f26757abe..754e0757cda 100644 --- a/crypto/openssl/providers/implementations/ciphers/cipher_aes_siv.c +++ b/crypto/openssl/providers/implementations/ciphers/cipher_aes_siv.c @@ -192,6 +192,7 @@ static int aes_siv_set_ctx_params(void *vctx, const OSSL_PARAM params[]) PROV_AES_SIV_CTX *ctx = (PROV_AES_SIV_CTX *)vctx; const OSSL_PARAM *p; unsigned int speed = 0; + SIV128_CONTEXT *sctx = &ctx->siv; if (ossl_param_is_empty(params)) return 1; @@ -226,6 +227,8 @@ static int aes_siv_set_ctx_params(void *vctx, const OSSL_PARAM params[]) if (keylen != ctx->keylen) return 0; } + sctx->final_ret = -1; + return 1; } diff --git a/crypto/openssl/providers/implementations/exchange/dh_exch.c b/crypto/openssl/providers/implementations/exchange/dh_exch.c index 94d4254ed5d..2bfefc0aedf 100644 --- a/crypto/openssl/providers/implementations/exchange/dh_exch.c +++ b/crypto/openssl/providers/implementations/exchange/dh_exch.c @@ -146,12 +146,15 @@ static int dh_init(void *vpdhctx, void *vdh, const OSSL_PARAM params[]) static int dh_match_params(DH *priv, DH *peer) { int ret; + int ignore_q = 1; FFC_PARAMS *dhparams_priv = ossl_dh_get0_params(priv); FFC_PARAMS *dhparams_peer = ossl_dh_get0_params(peer); + if (dhparams_priv != NULL && dhparams_priv->q != NULL) + ignore_q = 0; ret = dhparams_priv != NULL && dhparams_peer != NULL - && ossl_ffc_params_cmp(dhparams_priv, dhparams_peer, 1); + && ossl_ffc_params_cmp(dhparams_priv, dhparams_peer, ignore_q); if (!ret) ERR_raise(ERR_LIB_PROV, PROV_R_MISMATCHING_DOMAIN_PARAMETERS); return ret; diff --git a/crypto/openssl/ssl/quic/quic_cfq.c b/crypto/openssl/ssl/quic/quic_cfq.c index 3c59234ff0f..16818e55f57 100644 --- a/crypto/openssl/ssl/quic/quic_cfq.c +++ b/crypto/openssl/ssl/quic/quic_cfq.c @@ -7,6 +7,7 @@ * https://www.openssl.org/source/license.html */ +#include "internal/quic_channel.h" #include "internal/quic_cfq.h" #include "internal/numbers.h" @@ -307,6 +308,20 @@ void ossl_quic_cfq_mark_lost(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item, } } +int ossl_quic_cfq_discard_unreliable(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item) +{ + int discarded; + + if (ossl_quic_cfq_item_is_unreliable(item)) { + ossl_quic_cfq_release(cfq, item); + discarded = 1; + } else { + discarded = 0; + } + + return discarded; +} + /* * Releases a CFQ item. The item may be in either state (NEW or TX) prior to the * call. The QUIC_CFQ_ITEM pointer must not be used following this call. diff --git a/crypto/openssl/ssl/quic/quic_channel.c b/crypto/openssl/ssl/quic/quic_channel.c index 13692e5bd09..5f81a8560d5 100644 --- a/crypto/openssl/ssl/quic/quic_channel.c +++ b/crypto/openssl/ssl/quic/quic_channel.c @@ -2213,6 +2213,12 @@ static void ch_rx_check_forged_pkt_limit(QUIC_CHANNEL *ch) "forgery limit"); } +void ossl_ch_reset_rx_state(QUIC_CHANNEL *ch) +{ + ch->did_crypto_frame = 0; + ch->seen_path_challenge = 0; +} + /* Process queued incoming packets and handle frames, if any. */ static int ch_rx(QUIC_CHANNEL *ch, int channel_only, int *notify_other_threads) { diff --git a/crypto/openssl/ssl/quic/quic_channel_local.h b/crypto/openssl/ssl/quic/quic_channel_local.h index ae443fccca1..e40b4901cbc 100644 --- a/crypto/openssl/ssl/quic/quic_channel_local.h +++ b/crypto/openssl/ssl/quic/quic_channel_local.h @@ -12,6 +12,28 @@ #include "internal/quic_stream_map.h" #include "internal/quic_tls.h" +/* + * This is a part of PATH_CHALLENGE flood [1] mitigation. This limits the + * number of PATH_CHALLENGE frames QUIC stack is willing to process for + * connection. Local QUIC stack creates PATH_RESPONSE frame for PATH_CHALLENGE + * frame it receives from remote peer. The response frame is put Control Frame + * Queue waiting to be dispatched. The PATH_RESPONSE frame is removed from CFQ + * after it is dispatched. The QUIC_PATH_RESPONSE_QLEN limits the number of + * PATH_RESPONSE frames waiting to be dispatched. No new PATH_RESPONSE frames + * are inserted into CFQ if queue limit is exceeded. + * + * QUIC implementations use different limits for PATH_RESPONSE queue lengths: + * quic-go defines maxPathResponses as 256 + * quiche from cloadflare sets DEFAULT_MAX_PATH_CHALLENGE_RX_QUEUE_LEN to 3 + * t-quic from tencent chooses MAX_PATH_CHALS_RECV to be 8 + * + * OpenSSL here introduces QUIC_PATH_RESPONSE_QLEN as 32. + * + * [1] https://www.ietf.org/archive/id/draft-chen-quic-logical-vuln-mitigations-00.txt + * (section 4.2) + */ +#define QUIC_PATH_RESPONSE_QLEN 32 + /* * QUIC Channel Structure * ====================== @@ -457,6 +479,18 @@ struct quic_channel_st { /* Has qlog been requested? */ unsigned int is_tserver_ch : 1; + /* + * RFC 9000 Section 9.2.1 says: + * However, an endpoint SHOULD NOT send multiple + * PATH_CHALLENGE frames in a single packet. + * The counter here allows us to detect multiple presence + * of PATH_CHALLENGE frame in packet. We process only the + * first PATH_CHALLENGE frame found in packet. Remaining PATH_CHALLENGE + * frames are ignored. + * seen_path_challenge flag is always reset before + * ossl_quic_handle_frames() gets called. + */ + unsigned int seen_path_challenge : 1; /* Saved error stack in case permanent error was encountered */ ERR_STATE *err_state; @@ -467,6 +501,11 @@ struct quic_channel_st { /* Title for qlog purposes. We own this copy. */ char *qlog_title; + /* + * number of path responses waiting to be dispatched + * from control frame queue (CFQ) + */ + unsigned int path_response_limit; }; #endif diff --git a/crypto/openssl/ssl/quic/quic_fifd.c b/crypto/openssl/ssl/quic/quic_fifd.c index 03b8cebd305..e80483b501d 100644 --- a/crypto/openssl/ssl/quic/quic_fifd.c +++ b/crypto/openssl/ssl/quic/quic_fifd.c @@ -310,3 +310,46 @@ void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg) fifd->get_qlog_cb = get_qlog_cb; fifd->get_qlog_cb_arg = get_qlog_cb_arg; } + +static void txpim_pkt_remove_cfq_item(QUIC_TXPIM_PKT *pkt, QUIC_CFQ_ITEM *cfq_item) +{ + QUIC_CFQ_ITEM *prev = cfq_item->pkt_prev; + + if (prev != NULL) { + prev->pkt_next = cfq_item->pkt_next; + } else { + pkt->retx_head = cfq_item->pkt_next; + } + + if (cfq_item->pkt_next != NULL) + cfq_item->pkt_next->pkt_prev = prev; + + cfq_item->pkt_prev = NULL; + cfq_item->pkt_next = NULL; +} + +void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt) +{ + QUIC_CFQ_ITEM *cfq_item, *cfq_next; + + /* + * The packet has been written to network. We can discard frames we don't + * retransmit when loss is detected. + */ + cfq_item = pkt->retx_head; + while (cfq_item != NULL) { + /* + * Discarded items are moved to free list. If item + * got moved to free list we must also remove it from + * cfq list kept in pkt, so ACKM does not find it when + * receives an ACK for pkt. + */ + if (ossl_quic_cfq_discard_unreliable(fifd->cfq, cfq_item)) { + cfq_next = cfq_item->pkt_next; + txpim_pkt_remove_cfq_item(pkt, cfq_item); + cfq_item = cfq_next; + } else { + cfq_item = cfq_item->pkt_next; + } + } +} diff --git a/crypto/openssl/ssl/quic/quic_port.c b/crypto/openssl/ssl/quic/quic_port.c index 1e247e1ec62..dc79485b96a 100644 --- a/crypto/openssl/ssl/quic/quic_port.c +++ b/crypto/openssl/ssl/quic/quic_port.c @@ -1666,8 +1666,10 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg, * forget qrx so channel can create a new one * with valid initial encryption level keys. */ - qrx_src = qrx; - qrx = NULL; + if (qrx != NULL) { + qrx_src = qrx; + qrx = NULL; + } } port_bind_channel(port, &e->peer, &scid, &hdr.dst_conn_id, diff --git a/crypto/openssl/ssl/quic/quic_rx_depack.c b/crypto/openssl/ssl/quic/quic_rx_depack.c index 786af9b4c22..1bdb43b7d63 100644 --- a/crypto/openssl/ssl/quic/quic_rx_depack.c +++ b/crypto/openssl/ssl/quic/quic_rx_depack.c @@ -931,6 +931,12 @@ static int depack_do_frame_retire_conn_id(PACKET *pkt, static void free_path_response(unsigned char *buf, size_t buf_len, void *arg) { + QUIC_CHANNEL *ch = (QUIC_CHANNEL *)arg; + + assert(ch->path_response_limit > 0); + + ch->path_response_limit--; + OPENSSL_free(buf); } @@ -951,34 +957,40 @@ static int depack_do_frame_path_challenge(PACKET *pkt, return 0; } - /* - * RFC 9000 s. 8.2.2: On receiving a PATH_CHALLENGE frame, an endpoint MUST - * respond by echoing the data contained in the PATH_CHALLENGE frame in a - * PATH_RESPONSE frame. - * - * TODO(QUIC FUTURE): We should try to avoid allocation here in the future. - */ - encoded_len = sizeof(uint64_t) + 1; - if ((encoded = OPENSSL_malloc(encoded_len)) == NULL) - goto err; + if (ch->seen_path_challenge == 0 + && ch->path_response_limit < QUIC_PATH_RESPONSE_QLEN) { + /* + * RFC 9000 s. 8.2.2: On receiving a PATH_CHALLENGE frame, an endpoint + * MUST respond by echoing the data contained in the PATH_CHALLENGE + * frame in a PATH_RESPONSE frame. + * + * TODO(QUIC FUTURE): We should try to avoid allocation here in the + * future. + */ + encoded_len = sizeof(uint64_t) + 1; + if ((encoded = OPENSSL_malloc(encoded_len)) == NULL) + goto err; - if (!WPACKET_init_static_len(&wpkt, encoded, encoded_len, 0)) - goto err; + if (!WPACKET_init_static_len(&wpkt, encoded, encoded_len, 0)) + goto err; - if (!ossl_quic_wire_encode_frame_path_response(&wpkt, frame_data)) { - WPACKET_cleanup(&wpkt); - goto err; + if (!ossl_quic_wire_encode_frame_path_response(&wpkt, frame_data)) { + WPACKET_cleanup(&wpkt); + goto err; + } + + WPACKET_finish(&wpkt); + + if (!ossl_quic_cfq_add_frame(ch->cfq, 0, QUIC_PN_SPACE_APP, + OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE, + QUIC_CFQ_ITEM_FLAG_UNRELIABLE, + encoded, encoded_len, + free_path_response, ch)) + goto err; + ch->seen_path_challenge = 1; + ch->path_response_limit++; } - WPACKET_finish(&wpkt); - - if (!ossl_quic_cfq_add_frame(ch->cfq, 0, QUIC_PN_SPACE_APP, - OSSL_QUIC_FRAME_TYPE_PATH_RESPONSE, - QUIC_CFQ_ITEM_FLAG_UNRELIABLE, - encoded, encoded_len, - free_path_response, NULL)) - goto err; - return 1; err: @@ -1432,7 +1444,7 @@ int ossl_quic_handle_frames(QUIC_CHANNEL *ch, OSSL_QRX_PKT *qpacket) if (ch == NULL) return 0; - ch->did_crypto_frame = 0; + ossl_ch_reset_rx_state(ch); /* Initialize |ackm_data| (and reinitialize |ok|)*/ memset(&ackm_data, 0, sizeof(ackm_data)); diff --git a/crypto/openssl/ssl/quic/quic_txp.c b/crypto/openssl/ssl/quic/quic_txp.c index 44aaad868d2..b2565c1a9fe 100644 --- a/crypto/openssl/ssl/quic/quic_txp.c +++ b/crypto/openssl/ssl/quic/quic_txp.c @@ -3133,6 +3133,8 @@ static int txp_pkt_commit(OSSL_QUIC_TX_PACKETISER *txp, --probe_info->pto[pn_space]; } + ossl_quic_fifd_pkt_discard_unreliable(&txp->fifd, tpkt); + return rc; } diff --git a/crypto/openssl/test/cmsapitest.c b/crypto/openssl/test/cmsapitest.c index 0752d14df09..d908bc6fc4c 100644 --- a/crypto/openssl/test/cmsapitest.c +++ b/crypto/openssl/test/cmsapitest.c @@ -21,6 +21,7 @@ static X509 *cert = NULL; static EVP_PKEY *privkey = NULL; static char *derin = NULL; static char *too_long_iv_cms_in = NULL; +static char *pwri_kek_oob_der_in = NULL; static int test_encrypt_decrypt(const EVP_CIPHER *cipher) { @@ -512,7 +513,48 @@ static int test_cms_aesgcm_iv_too_long(void) 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) { @@ -527,7 +569,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"); @@ -564,6 +607,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 eec9364f42b..8fb5b216feb 100644 --- a/crypto/openssl/test/evp_extra_test.c +++ b/crypto/openssl/test/evp_extra_test.c @@ -6528,6 +6528,142 @@ static int test_aes_rc4_keylen_change_cve_2023_5363(void) } #endif +static int test_aes_gcm_siv_empty_data(void) +{ + unsigned char key[16] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 }; + unsigned char nonce[12] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, + 0x22, 0x33, 0x44, 0x55 }; + unsigned char aad[33] = "this AAD was never authenticated"; + unsigned char zero_tag[16] = { 0 }; + unsigned char real_tag[16]; + unsigned char out[16]; + int outl, ret = 0; + EVP_CIPHER_CTX *ctx = NULL; + EVP_CIPHER *c = EVP_CIPHER_fetch(NULL, "AES-128-GCM-SIV", NULL); + + if (c == NULL) { + return TEST_skip("AES-128-GCM-SIV cipher is not available"); + } + + /* Compute the CORRECT tag for (key,nonce,aad,pt="") via encrypt */ + ctx = EVP_CIPHER_CTX_new(); + if (!TEST_ptr(ctx) + || !TEST_true(EVP_EncryptInit_ex2(ctx, c, key, nonce, NULL)) + || !TEST_true(EVP_EncryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) /* AAD */ + || !TEST_true(EVP_EncryptUpdate(ctx, out, &outl, aad, 0)) /* empty PT, out!=NULL */ + || !TEST_true(EVP_EncryptFinal_ex(ctx, out, &outl)) + || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, real_tag))) + goto err; + EVP_CIPHER_CTX_free(ctx); + + /* SANITY: decrypt with CORRECT tag and an explicit empty-PT Update */ + ctx = EVP_CIPHER_CTX_new(); + if (!TEST_ptr(ctx) + || !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL)) + || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, real_tag)) + || !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) + || !TEST_true(EVP_DecryptUpdate(ctx, out, &outl, aad, 0)) /* force aes_gcm_siv_decrypt(len=0) */ + || !TEST_true(EVP_DecryptFinal_ex(ctx, out, &outl))) + goto err; + EVP_CIPHER_CTX_free(ctx); + + /* FORGERY A: AAD only, NO ciphertext Update, ALL-ZERO tag */ + ctx = EVP_CIPHER_CTX_new(); + if (!TEST_ptr(ctx) + || !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL)) + || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, zero_tag)) + || !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) /* AAD only, out==NULL */ + || !TEST_false(EVP_DecryptFinal_ex(ctx, out, &outl))) + goto err; + EVP_CIPHER_CTX_free(ctx); + + /* FORGERY B: no AAD, no Update at all, ALL-ZERO tag */ + ctx = EVP_CIPHER_CTX_new(); + if (!TEST_ptr(ctx) + || !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL)) + || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, zero_tag)) + || !TEST_false(EVP_DecryptFinal_ex(ctx, out, &outl))) + goto err; + EVP_CIPHER_CTX_free(ctx); + + /* CONTROL: AAD only, NO ciphertext Update, CORRECT tag */ + ctx = EVP_CIPHER_CTX_new(); + if (!TEST_ptr(ctx) + || !TEST_true(EVP_DecryptInit_ex2(ctx, c, key, nonce, NULL)) + || !TEST_true(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, real_tag)) + || !TEST_true(EVP_DecryptUpdate(ctx, NULL, &outl, aad, sizeof(aad))) + || !TEST_true(EVP_DecryptFinal_ex(ctx, out, &outl))) + goto err; + EVP_CIPHER_CTX_free(ctx); + ctx = NULL; + + ret = 1; +err: + EVP_CIPHER_CTX_free(ctx); + + EVP_CIPHER_free(c); + return ret; +} + +/* + * 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; @@ -6987,6 +7123,10 @@ int setup_tests(void) ADD_TEST(test_aes_rc4_keylen_change_cve_2023_5363); #endif + /* Test cases for CVE-2026-45446 */ + ADD_TEST(test_aes_gcm_siv_empty_data); + 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_cmsapi.t b/crypto/openssl/test/recipes/80-test_cmsapi.t index 8d9371e005c..3d1dae84646 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 0000000000000000000000000000000000000000..c3ef3abd10e6b0a57b1eef985b0c8c43cb057371 GIT binary patch literal 193 zcmXqL+{ebL)#lOmotKfFc|qd_gT}Q?jLe2!i#?ba85StRC0Th4#8?FEa~A$hV(x0n zFrRcQQS~(+6B7r6ffO4z)C5ieW=;ccHqL}L55`nx7Dg5pCI$wB7`P$qj0Um@Stb?% z*}W&;-kY#&W8?d>8TmW9IT{Q$uJ-6FX$+dodfRzpO2J!$Gg}?F()_mBI35*0&tMe5 VHoNtcLX?S)y&B`}phcqR3jwDUJP7~* literal 0 HcmV?d00001 diff --git a/crypto/openssl/test/recipes/80-test_pkcs12.t b/crypto/openssl/test/recipes/80-test_pkcs12.t index d258b7eb0e4..56ab93803e7 100644 --- a/crypto/openssl/test/recipes/80-test_pkcs12.t +++ b/crypto/openssl/test/recipes/80-test_pkcs12.t @@ -56,7 +56,7 @@ $ENV{OPENSSL_WIN32_UTF8}=1; my $no_fips = disabled('fips') || ($ENV{NO_FIPS} // 0); -plan tests => $no_fips ? 53 : 59; +plan tests => $no_fips ? 55 : 61; # Test different PKCS#12 formats ok(run(test(["pkcs12_format_test"])), "test pkcs12 formats"); @@ -205,8 +205,11 @@ for my $instance (sort keys %pbmac1_tests) { } } -# Test pbmac1 pkcs12 good files, RFC 9579 -for my $file ("pbmac1_256_256.good.p12", "pbmac1_512_256.good.p12", "pbmac1_512_512.good.p12") +# Test pbmac1 pkcs12 good files, RFC 9579, and one extra with shorter key +# length +for my $file ("pbmac1_256_256.good.p12", "pbmac1_512_256.good.p12", + "pbmac1_512_512.good.p12", + "pbmac1_256_256.good-shorter-key-len.p12") { my $path = srctop_file("test", "recipes", "80-test_pkcs12_data", $file); ok(run(app(["openssl", "pkcs12", "-in", $path, "-password", "pass:1234", "-noenc"])), @@ -235,12 +238,12 @@ unless ($no_fips) { } } -# Test pbmac1 pkcs12 bad files, RFC 9579 and CVE-2025-11187 +# Test pbmac1 pkcs12 bad files, RFC 9579, CVE-2025-11187 and CVE-2026-34181 for my $file ("pbmac1_256_256.bad-iter.p12", "pbmac1_256_256.bad-salt.p12", "pbmac1_256_256.no-len.p12", "pbmac1_256_256.bad-len.p12", "pbmac1_256_256.bad-salt-type.p12", "pbmac1_256_256.negative-len.p12", "pbmac1_256_256.no-salt.p12", "pbmac1_256_256.very-big-len.p12", - "pbmac1_256_256.zero-len.p12") + "pbmac1_256_256.zero-len.p12", "pbmac1_256_256.bad-key-len.p12") { my $path = srctop_file("test", "recipes", "80-test_pkcs12_data", $file); with({ exit_checker => sub { return shift == 1; } }, diff --git a/crypto/openssl/test/recipes/80-test_pkcs12_data/pbmac1_256_256.bad-key-len.p12 b/crypto/openssl/test/recipes/80-test_pkcs12_data/pbmac1_256_256.bad-key-len.p12 new file mode 100644 index 0000000000000000000000000000000000000000..7162fd1871790e3ab9cbc4e00fb73cdf63d5e64b GIT binary patch literal 2803 zcmai$c{J4j8pj7S7+D6R$Pyux_CuDoVPuU#vj4`K zrOhb6nPiOySwfgH64&qCd+OYK?m53dp7T8K_xm}Y&wsD;064BSRu(n@$K}WlJ(*;d z^qq%=gQW<^bqcf?+V1IHX#5v(krgBJWdVFBEK2?!5h4Iq9?Za@YM z^Wii0br`!Ggequ;YZ{@XSXnti05?15zkg+gLO_5xJJch|jO8jDnB^oGR^g?cgEne4 zl#mJ8=oQ6AV{u?m%k~t4Ew}NVWar5_U5=ge+lx9QYm}&}CFACR(%&P|)_f&6#ox(3INg_3?z5>ssUB-tL!RtNV(&KePwl0m`q6R&m{~6} z4{YPaTkY*zyE=@#wvmdJNX=Ps%k7`mk&2TiZ|z-IjaYDcoDj@l6}0+j$1OAH_46$v(lD^b1*m zHG|TuS370~5tU~Z^(W_iuA}0F-4bl(hkWbbeIE^~t|&LXItR@iQAIgOZ-p|+4buxc zq6JGqbW9g;5gO;Ab2>_fj5eI0oBBATyW(cGYVpG>Cvd4sZQMwg=sTH)G0{r$E^TcIcE(He>AOe*)b^|+#a zPuC)Avt)nDJmSev@QaupE~2g+uyCpL`wIW*Ui9tArpx#{3$+OWMytIN(0ok%6b@}c zb-e%M{0Cx5?>n!`edT6!Yt_#JjZ>+onzXjwKf3-NpH80$$s|Y&FeRH4qU6xk*3QiG zr{kUl`C-jFGq*Ia9LAR}@X#5tn6lY!`mF0D--D98$f6rc?`^^l8^7+@X|0jD#i8&r zpgLM#UU$45I*n|)sPu_*mGcw-K=tB@LPLiQ+)I=vC@w4=8+Hm8)E0U7{jt@3jVk;3 z%KhG`W+XFy)UQvbu};`padp{%j-w?eL7ekZcbvBR z!idUT&JbYHyt?syvBA~f{~FqY&(JgTK7|*1bgp|jB=$w7a2%Vc zm>uyczrs50skoi0&aTsOcZ~VB%7k*)O8CCs4hC6?gJdq{vhmheFps9uwDSNQr1lqA z{stAK2#kX~IIs&2DwqAxe;CHi#lm_JY9E;D{{$vkr>;B##UN?0gx>G)Dy#4x!PNN0 zL{h{2*dNSe3DcCSS{*PBJRF`Sm}Pc0a$+K^hOL)qFYzWvv@zuHU-t@r!WxubxDjq~ zwPi#a1J@}ML+&=2qS5-^PMBnL^lrqfH^f8XvG6@efM2*)D)k9tR8B1?$4pE0Xw%^W zj-Gj{;?9)RpwTH^2JOdC+SJIpYR{sY;?kXmZnNY*3B;xySK^CURBXodmbOB;9Cy7} zBf(_Gh9a0CmWGX?hEkpM@$U0C#3N{yD!$!N|8kdkfmq|cjJQ>uTD7Vhck;o|YUh>l zcha&%##owt=1ZD)lO!)Q+V6wHQUa~8)Poqbp)~-ZX(7$}iAj&ikWN`3n4r1YhrV4a z#rAnHQN3@ycxkMvd2*hbVSeQNiJz4G2X)yED|bWVp3Q@a#v`4+yzShS_6S zx)gQpu}iHWn0(nX`)iTP8_Pck;pbd8zC_M%TAOPNRYy76OCr4kQ7z6glZfS*XS+#m z*(JJM7sTD;Mne4RrY}mxsdU^qlHQ+Is{j=cF z(}_rQ(X!XwVw8L9+7${pr47~0RTVrxYu*B}s9C?;l zPC)7feZDy_R=|gLEY^`OizwiGs!nyX=oi#$mtq0`F`F3X8Thwg2!wt1yqPLT92;BQEM(<-LOn9#y%ciore+uj$kmZjbxxMOTwX?+ z3W(R`aNUI+`)nHb_}WAERLrtwMAl^|Hf-~go255dFcFX=@zPlP>dacq>FL%rd#)`U z(Cgvf=Jhw^2WyIrVv+{(f}o;W|}A&Gn>qhPbGu z*~0Wrpyrtj4elMRhAHl$=cvtsexbxS_mGf`_FNnJJq}B&ZM++lCDHxC*Dh&J+xG>x zNI{P^bJcG}2<+mKXHmxjYjULSAL;FRDXwTE+@E{VDVs8LUO?E%;nm=r zqcCRD*DPNnq@RSg`(#CxZCFo(UaqB$W;~a-_xBVvXA zXP070esMeu*_!~eP# p!IEkKDV_8&u%4!IcN?uKd-cjZ4l9KP8|r=hepF&( zVLQM^z;T@g;kZui+h_z1l>8@x5OARIeJcdW2H0=#i@ZtdDg);p z%`{Byl19hiKmh!0K}_kzGN8k|+XslKKKI34RXPr4L}`&Qw9J`wViIGn?w~WHP?WKv zwIyG_$tyLX0`kX>uOYL2Qi;p`P2bm3yy4~N$~l}%Hz1V)^Br&xMlJ*6Z@$YjIK%5J zB%yUB_vxi$Z&l=c_u_6IpE{;|AmYfSyaGN++tx3aFv2axcRoG`KmTeR6+Nh~WWbql zIWFhK#;FjDWlGQcW*_oo9Fi)d+&^sT7|v&iG?HLtdmgQI7R8f!ySnn~b84^ab<9$U zdz$n|zBOJgeVycNIUyJ`KcqkO?4B9Jd>UTKqrKo>zE&_3Y5YjkZKh0gE|)`Xiu1tW z;e0ZKs?1-ofU-_yjW};o;f0JFs2FG?p$={tqS&e^AnnaIgHA1q5t*n zA7vds25OsI`P~t;2l~&MH-?K!-bU$!+!EJRAvwcY8+Xgf%_`};$!@a;dhGQIH^oxO zBr8SFLI*1bGq(RecC^oCiCOgu|Ba$@37~wHJL8?&Y#uhTOYYS+>KB`kG&_I>It8pKEf7%(uvCzL0+_(!KmV)`pHJ{{ z)LtCeDL<5z+Ri152Jx0SE`V0$USY!*S<6};`eHvmRq2M^IMOLQHxx2L=IPT1p6y!H z_pPy%g4yDe9d+SPGvmp={tMROCQ8;ZwfH*QyxmJBaWA&~lB?r{9(&XF0g zR#J=vb4Hp=Gt!uKPsHNk<&l~#Uah)(^^nuL97h931&E!&Lq#h)#(u292oD38B-dd` zkCwcj<2mw++oEkG<7}E*DTC%cg8Oym)t*oYvOAj6H?!lJXY(wj{nQ9(!_9}lOdqhm z>XInUr%_}U$qGU&H?hxjJfbH_O$@}7Wv!debFj!7)AxunM})WOZXcsAwkK8=+vvn5 z!=Vptc6pDWw?b!4;x~RxG^PvQ$(h(u*MI6dqQX10HWH=X9KI)~isVD!z>j}(pJ*T)sMoZI@p1&#LRb#JPihOU`l;56i%b`}i0$ro@7CyI88wK*${Nd# znVhyVFx6-v8EY7C$-O^*Fe@a?18Qx}bS+oD{4V~kMr#|<_4>0p!X41`QhLXcqk%`x z3H7+Un!>(ohHm^s2lYqaDvujhqHn$sgYA5Ng|9v!+kE|HckSp0mDJ~^?Jm>eqfe%J zXLXGVUP`O+4VPhsmE^Tw01Zp0NNPowxjL#J^_I!4t4Q%*{!|q*o~j^l^T#cC1z>H| zUe#W?@`3jm^9gzeDJh!Uv=(6MUj&UPYR^B1S&W1o-wX-^Odb;{<+|Tn4V5X$AL*QR z8JnIGd2*_IkZYcKmR{Z%xP3A=xM-wvT{0nR<4K0ic3X+1uu!GImFAoj67Xf zZ7}Ax0e;B6a9wp)ouF8^aQyMsYqgT`cN=LWzJw_9Qf)4K(y2zu_hf;OSG-rxK_8-t z&!%2`L*L)OE!~0-LO{tfO?i2aXXd27wR|w(`I(a4d*GIdeA_e*|8~9m+hXIM+djH2~u9QW6rz&LqC&})h z%ghLjknNz@+iMgHihNDAr<(2K37c#YZO~iY_PT=MPiu`@rZZGa>kEx^Ef-+B&sf}80d^3j>*kEL8O*K5Ws4>y`I zzOO%ky*cBryvojeBV6OQks;S?$bG9R^~8%7@?YEU26zqu(>6(&r3Nv$?Dy?>xF;*~ zv`#ec!3<#TuFT2`8qP_84n=C`4C*xtu~{eR=j8JASteT~sen8WX*cBb>)U$HwSm4g zVSU7>vSd-9D|wCcZxdt$By%@E@b2_onhNW!jIB=&y26%n0bJ(PI3%j5T5+-n(|MzB z5LAO%q^X^MhQAQsk?OP)Z1M10e+w;&i$*CtsPxxx)d^<$sL=`_EKK0%WlCu6aw3)AVNB@}8rly@= zLd1Kj5u%M&ot>TC-n+}*@bK`kTw%?cnHP#0d%lDkSo2z^+0uJy-clWm?;MK9t5(n3 zyL&!%t%!)6TmgTDT#bM+*@L^ndWtgDBj$9!(>Uom3!>-3T2x9nbE&2FemC`zQJU_P z37lfCm8*&c_LrX*J`O!J>+Y3y^W@kRVl>-^3XSXxB{k4s)?b&m)wmVvqPOBT;TG#M z;KmsW`{RLoZUma6C=u5)(n$opw2!(pEGs#}f@ArlWs)?&sr5G|hwfrGWfHH`Kjz@P z^BEr~I>eo8Rce*1Ej_NS?*pz?2WSS|Dqb-$H(H~c_>lsa%L5%E^~~$AXlF=EWew64 zA^jV)oPtQf!!Upk00VFbT;1>2_NPEV5JL9PiV#v5B>2X?o;R6k=LH@W%FN|PRinky z