diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 5501c5cc2d..d28d28976c 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -483,6 +483,38 @@ int wolfSSL_memsave_session_cache(void* mem, int sz) } +#if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ + (defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) +static void SessionSanityPointerSet(SessionRow* row) +{ + int j; + + /* Reset pointers to safe values after raw copy */ + for (j = 0; j < SESSIONS_PER_ROW; j++) { + WOLFSSL_SESSION* s = &row->Sessions[j]; +#ifdef HAVE_SESSION_TICKET + s->ticket = s->staticTicket; + s->ticketLenAlloc = 0; + if (s->ticketLen > SESSION_TICKET_LEN) { + s->ticketLen = SESSION_TICKET_LEN; + } +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ + (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) + s->ticketNonce.data = s->ticketNonce.dataStatic; + if (s->ticketNonce.len > MAX_TICKET_NONCE_STATIC_SZ) { + s->ticketNonce.len = MAX_TICKET_NONCE_STATIC_SZ; + } +#endif +#if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) + s->peer = NULL; +#endif + } +} +#endif + /* Restore the persistent session cache from memory */ int wolfSSL_memrestore_session_cache(const void* mem, int sz) { @@ -522,6 +554,11 @@ int wolfSSL_memrestore_session_cache(const void* mem, int sz) #endif XMEMCPY(&SessionCache[i], row++, SIZEOF_SESSION_ROW); + #if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ + (defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) + SessionSanityPointerSet(&SessionCache[i]); + #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); #endif @@ -681,6 +718,11 @@ int wolfSSL_restore_session_cache(const char *fname) #endif ret = (int)XFREAD(&SessionCache[i], SIZEOF_SESSION_ROW, 1, file); + #if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ + (defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) + SessionSanityPointerSet(&SessionCache[i]); + #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); #endif diff --git a/src/tls.c b/src/tls.c index 09e6c92174..93984ac4ba 100644 --- a/src/tls.c +++ b/src/tls.c @@ -9950,6 +9950,10 @@ static int TLSX_KeyShare_ProcessPqcClient_ex(WOLFSSL* ssl, } #endif + if (ret == 0 && keyShareEntry->keLen < ctSz) { + WOLFSSL_MSG("PQC key share data too short for ciphertext."); + ret = BUFFER_E; + } if (ret == 0) { ret = wc_KyberKey_Decapsulate(kem, ssOutput, keyShareEntry->ke, ctSz); diff --git a/tests/api.c b/tests/api.c index eba7a6c301..5eca4071a8 100644 --- a/tests/api.c +++ b/tests/api.c @@ -165,6 +165,19 @@ #include #endif +#ifdef HAVE_DILITHIUM + #include +#endif +#if defined(WOLFSSL_HAVE_MLKEM) + #include +#endif +#if defined(HAVE_PKCS7) + #include +#endif +#if !defined(NO_BIG_INT) + #include +#endif + /* include misc.c here regardless of NO_INLINE, because misc.c implementations * have default (hidden) visibility, and in the absence of visibility, it's * benign to mask out the library implementation. @@ -34758,6 +34771,47 @@ static int test_DhAgree_rejects_p_minus_1(void) return EXPECT_RESULT(); } + +/* ML-DSA HashML-DSA verify must reject hashLen > WC_MAX_DIGEST_SIZE */ +static int test_mldsa_verify_hash(void) +{ + EXPECT_DECLS; +#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_WC_DILITHIUM) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_VERIFY) + dilithium_key key; + WC_RNG rng; + int res = 0; + byte sig[4000]; + byte hash[4096]; /* larger than WC_MAX_DIGEST_SIZE */ + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(sig, 0x41, sizeof(sig)); + XMEMSET(hash, 'A', sizeof(hash)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_dilithium_init(&key), 0); +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_65), 0); +#elif !defined(WOLFSSL_NO_ML_DSA_44) + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_44), 0); +#else + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_87), 0); +#endif + ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); + + /* hashLen=4096 must be rejected, not overflow the stack */ + ExpectIntEQ(wc_dilithium_verify_ctx_hash(sig, sizeof(sig), NULL, 0, + WC_HASH_TYPE_SHA256, hash, sizeof(hash), &res, &key), + WC_NO_ERR_TRACE(BAD_LENGTH_E)); + + wc_dilithium_free(&key); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} + /* Test: Ed448 must reject identity public key (0,1) */ static int test_ed448_rejects_identity_key(void) { @@ -34936,6 +34990,133 @@ static int test_pkcs7_ori_oversized_oid(void) return EXPECT_RESULT(); } +/* Dilithium verify_ctx_msg must reject absurdly large msgLen */ +static int test_dilithium_hash(void) +{ + EXPECT_DECLS; +#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_WC_DILITHIUM) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_VERIFY) + dilithium_key key; + WC_RNG rng; + int res = 0; + byte sig[4000]; + byte msg[64]; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(sig, 0, sizeof(sig)); + XMEMSET(msg, 'A', sizeof(msg)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_dilithium_init(&key), 0); +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_65), 0); +#elif !defined(WOLFSSL_NO_ML_DSA_44) + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_44), 0); +#else + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_87), 0); +#endif + ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); + + ExpectIntEQ(wc_dilithium_verify_ctx_msg(sig, sizeof(sig), NULL, 0, + msg, 0xFFFFFFC0, &res, &key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + wc_dilithium_free(&key); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} + +/* PKCS7 CBC must validate all padding bytes */ +static int test_pkcs7_padding(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS7) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_256) && !defined(NO_PKCS7_ENCRYPTED_DATA) + PKCS7 pkcs7; + byte key[32]; + byte plaintext[27]; /* 27 bytes -> padded to 32 -> padding = 05 05 05 05 05 */ + byte encoded[4096]; + byte output[256]; + byte modified[4096]; + int encodedSz = 0; + int outSz; + int ctOff = -1; + int ctLen = 0; + int i; + + XMEMSET(key, 0xAA, sizeof(key)); + XMEMSET(plaintext, 'X', sizeof(plaintext)); + + /* Encode EncryptedData */ + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); + ExpectIntEQ(wc_PKCS7_Init(&pkcs7, NULL, 0), 0); + pkcs7.content = plaintext; + pkcs7.contentSz = sizeof(plaintext); + pkcs7.contentOID = DATA; + pkcs7.encryptOID = AES256CBCb; + pkcs7.encryptionKey = key; + pkcs7.encryptionKeySz = sizeof(key); + + ExpectIntGT(encodedSz = wc_PKCS7_EncodeEncryptedData(&pkcs7, encoded, + sizeof(encoded)), 0); + + /* Verify normal decrypt works */ + ExpectIntEQ(outSz = wc_PKCS7_DecodeEncryptedData(&pkcs7, encoded, + (word32)encodedSz, output, sizeof(output)), (int)sizeof(plaintext)); + wc_PKCS7_Free(&pkcs7); + + /* Find ciphertext block in encoded DER */ + if (EXPECT_SUCCESS()) { + for (i = encodedSz - 10; i > 10; i--) { + if (encoded[i] == 0x04 || encoded[i] == 0x80) { + int len, lbytes; + + if (encoded[i+1] < 0x80) { + len = encoded[i+1]; lbytes = 1; + } + else if (encoded[i+1] == 0x81) { + len = encoded[i+2]; lbytes = 2; + } + else { + continue; + } + if (len > 0 && len % 16 == 0 && + i + 1 + lbytes + len <= encodedSz) { + ctOff = i + 1 + lbytes; + ctLen = len; + break; + } + } + } + } + ExpectIntGT(ctOff, 0); + ExpectIntGE(ctLen, 32); + + /* Corrupt an interior padding byte via CBC bit-flip */ + if (EXPECT_SUCCESS()) { + XMEMCPY(modified, encoded, (size_t)encodedSz); + /* Flip byte in penultimate block to corrupt interior padding */ + modified[ctOff + ctLen - 32 + 11] ^= 0x42; + + /* Decrypt modified ciphertext - must fail, not succeed */ + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); + ExpectIntEQ(wc_PKCS7_Init(&pkcs7, NULL, 0), 0); + pkcs7.encryptionKey = key; + pkcs7.encryptionKeySz = sizeof(key); + + outSz = wc_PKCS7_DecodeEncryptedData(&pkcs7, modified, + (word32)encodedSz, output, sizeof(output)); + /* Must return an error - if it returns plaintext size, padding + * oracle vulnerability exists */ + ExpectIntLT(outSz, 0); + wc_PKCS7_Free(&pkcs7); + } +#endif + return EXPECT_RESULT(); +} + TEST_CASE testCases[] = { TEST_DECL(test_fileAccess), @@ -35754,11 +35935,14 @@ TEST_CASE testCases[] = { TEST_DECL(test_ed448_rejects_identity_key), TEST_DECL(test_pkcs7_decode_encrypted_outputsz), TEST_DECL(test_pkcs7_ori_oversized_oid), + TEST_DECL(test_pkcs7_padding), #if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_CHAIN_INPUT) TEST_DECL(test_sniffer_chain_input_overflow), #endif + TEST_DECL(test_mldsa_verify_hash), + TEST_DECL(test_dilithium_hash), /* This test needs to stay at the end to clean up any caches allocated. */ TEST_DECL(test_wolfSSL_Cleanup) }; diff --git a/wolfcrypt/src/curve25519.c b/wolfcrypt/src/curve25519.c index bc2961aca4..f8ca74255c 100644 --- a/wolfcrypt/src/curve25519.c +++ b/wolfcrypt/src/curve25519.c @@ -1113,10 +1113,12 @@ curve25519_key* wc_curve25519_new(void* heap, int devId, int *result_code) } int wc_curve25519_delete(curve25519_key* key, curve25519_key** key_p) { + void* heap; if (key == NULL) return BAD_FUNC_ARG; + heap = key->heap; wc_curve25519_free(key); - XFREE(key, key->heap, DYNAMIC_TYPE_CURVE25519); + XFREE(key, heap, DYNAMIC_TYPE_CURVE25519); if (key_p != NULL) *key_p = NULL; return 0; diff --git a/wolfcrypt/src/dilithium.c b/wolfcrypt/src/dilithium.c index 8e1ea8e638..228c5b22ff 100644 --- a/wolfcrypt/src/dilithium.c +++ b/wolfcrypt/src/dilithium.c @@ -503,6 +503,9 @@ static int dilithium_hash256(wc_Shake* shake256, const byte* data1, word64* state = shake256->s; word8 *state8 = (word8*)state; + if (data2Len > (UINT32_MAX - data1Len)) { + return BAD_FUNC_ARG; + } if (data1Len + data2Len >= WC_SHA3_256_COUNT * 8) { XMEMCPY(state8, data1, data1Len); XMEMCPY(state8 + data1Len, data2, WC_SHA3_256_COUNT * 8 - data1Len); @@ -10554,6 +10557,10 @@ int wc_dilithium_verify_ctx_msg(const byte* sig, word32 sigLen, const byte* ctx, if ((ret == 0) && (ctx == NULL) && (ctxLen > 0)) { ret = BAD_FUNC_ARG; } + /* Reject msgLen that would cause integer overflow in hash computations */ + if ((ret == 0) && (msgLen > UINT32_MAX / 2)) { + ret = BAD_FUNC_ARG; + } #ifdef WOLF_CRYPTO_CB if (ret == 0) { @@ -10737,10 +10744,12 @@ dilithium_key* wc_dilithium_new(void* heap, int devId) int wc_dilithium_delete(dilithium_key* key, dilithium_key** key_p) { + void* heap; if (key == NULL) return BAD_FUNC_ARG; + heap = key->heap; wc_dilithium_free(key); - XFREE(key, key->heap, DYNAMIC_TYPE_DILITHIUM); + XFREE(key, heap, DYNAMIC_TYPE_DILITHIUM); if (key_p != NULL) *key_p = NULL; diff --git a/wolfcrypt/src/ed25519.c b/wolfcrypt/src/ed25519.c index bf5cf590d0..1bf12f7b24 100644 --- a/wolfcrypt/src/ed25519.c +++ b/wolfcrypt/src/ed25519.c @@ -1047,10 +1047,12 @@ ed25519_key* wc_ed25519_new(void* heap, int devId, int *result_code) } int wc_ed25519_delete(ed25519_key* key, ed25519_key** key_p) { + void* heap; if (key == NULL) return BAD_FUNC_ARG; + heap = key->heap; wc_ed25519_free(key); - XFREE(key, key->heap, DYNAMIC_TYPE_ED25519); + XFREE(key, heap, DYNAMIC_TYPE_ED25519); if (key_p != NULL) *key_p = NULL; return 0; diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index e7a87d249b..76e1496c25 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -318,6 +318,7 @@ int wolfSSL_EVP_PKEY_is_a(const WOLFSSL_EVP_PKEY *pkey, const char *name) { #define WOLFSSL_EVP_PKEY_PRINT_LINE_WIDTH_MAX 80 #define WOLFSSL_EVP_PKEY_PRINT_DIGITS_PER_LINE 15 +#define WOLFSSL_EVP_EXPONENT_PRINT_MAX 24 static unsigned int cipherType(const WOLFSSL_EVP_CIPHER *cipher); @@ -11877,7 +11878,7 @@ static int PrintHexWithColon(WOLFSSL_BIO* out, const byte* input, static int PrintPubKeyRSA(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[8] = { 0 }; + byte buff[WOLFSSL_EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 inOutIdx = 0; word32 nSz; /* size of modulus */ @@ -12021,7 +12022,7 @@ static int PrintPubKeyEC(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, { byte* pub = NULL; word32 pubSz = 0; - byte buff[8] = { 0 }; + byte buff[WOLFSSL_EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WOLFSSL_SUCCESS; word32 inOutIdx = 0; int curveId = 0; @@ -12210,7 +12211,7 @@ static int PrintPubKeyDSA(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[8] = { 0 }; + byte buff[WOLFSSL_EVP_EXPONENT_PRINT_MAX] = { 0 }; int length; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 inOutIdx = 0; @@ -12417,7 +12418,7 @@ static int PrintPubKeyDH(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[8] = { 0 }; + byte buff[WOLFSSL_EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 length; word32 inOutIdx; diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index c46885b168..3f6649d0ad 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -12759,7 +12759,9 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, byte* encryptedContent = NULL; int explicitOctet = 0; word32 localIdx = 0; - byte tag = 0; + byte tag = 0; + byte padCheck = 0; + int padIndex; if (pkcs7 == NULL) return BAD_FUNC_ARG; @@ -13261,8 +13263,19 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, padLen = encryptedContent[encryptedContentSz-1]; - /* copy plaintext to output */ - if (padLen > encryptedContentSz) { + /* Constant-time padding check */ + padCheck |= ctMaskEq(padLen, 0); + padCheck |= ctMaskGT(padLen, expBlockSz); + padCheck |= ctMaskGT(padLen, encryptedContentSz); + padCheck |= ctMaskGT(expBlockSz, encryptedContentSz); + for (padIndex = encryptedContentSz < expBlockSz ? 0 : + encryptedContentSz - expBlockSz; + padIndex < encryptedContentSz; padIndex++) { + byte inPad = ctMaskGTE(padIndex, + encryptedContentSz - (int)padLen); + padCheck |= inPad & (encryptedContent[padIndex] ^ padLen); + } + if (padCheck != 0) { ret = BUFFER_E; break; } @@ -15038,6 +15051,8 @@ int wc_PKCS7_DecodeEncryptedData(wc_PKCS7* pkcs7, byte* in, word32 inSz, byte* pkiMsg = in; word32 pkiMsgSz = inSz; byte tag = 0; + byte padCheck = 0; + int padIndex; if (pkcs7 == NULL || ((pkcs7->encryptionKey == NULL || pkcs7->encryptionKeySz == 0) && @@ -15315,8 +15330,20 @@ int wc_PKCS7_DecodeEncryptedData(wc_PKCS7* pkcs7, byte* in, word32 inSz, if (ret == 0) { padLen = encryptedContent[encryptedContentSz-1]; - if (padLen > encryptedContentSz) { - WOLFSSL_MSG("Bad padding size found"); + /* Constant-time padding check */ + padCheck |= ctMaskEq(padLen, 0); + padCheck |= ctMaskGT(padLen, expBlockSz); + padCheck |= ctMaskGT(padLen, encryptedContentSz); + padCheck |= ctMaskGT(expBlockSz, encryptedContentSz); + for (padIndex = encryptedContentSz < expBlockSz ? 0 : + encryptedContentSz - expBlockSz; + padIndex < encryptedContentSz; padIndex++) { + byte inPad = ctMaskGTE(padIndex, + encryptedContentSz - (int)padLen); + padCheck |= inPad & (encryptedContent[padIndex] ^ padLen); + } + if (padCheck != 0) { + WOLFSSL_MSG("Bad padding bytes found"); ret = BUFFER_E; XFREE(encryptedContent, pkcs7->heap, DYNAMIC_TYPE_PKCS7); break; diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 6fd7f8198d..ec1e87a00b 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -2423,7 +2423,6 @@ enum Max_ASN { #endif /* WOLFSSL_CERT_GEN */ - #ifdef __cplusplus } /* extern "C" */ #endif