|
165 | 165 | #include <sys/uio.h> |
166 | 166 | #endif |
167 | 167 |
|
| 168 | +#ifdef HAVE_DILITHIUM |
| 169 | + #include <wolfssl/wolfcrypt/dilithium.h> |
| 170 | +#endif |
| 171 | +#if defined(WOLFSSL_HAVE_MLKEM) |
| 172 | + #include <wolfssl/wolfcrypt/mlkem.h> |
| 173 | +#endif |
| 174 | +#if defined(HAVE_PKCS7) |
| 175 | + #include <wolfssl/wolfcrypt/pkcs7.h> |
| 176 | +#endif |
| 177 | +#if !defined(NO_BIG_INT) |
| 178 | + #include <wolfssl/wolfcrypt/sp_int.h> |
| 179 | +#endif |
| 180 | + |
168 | 181 | /* include misc.c here regardless of NO_INLINE, because misc.c implementations |
169 | 182 | * have default (hidden) visibility, and in the absence of visibility, it's |
170 | 183 | * benign to mask out the library implementation. |
@@ -34387,6 +34400,264 @@ static int test_sniffer_chain_input_overflow(void) |
34387 | 34400 | } |
34388 | 34401 | #endif /* WOLFSSL_SNIFFER && WOLFSSL_SNIFFER_CHAIN_INPUT */ |
34389 | 34402 |
|
| 34403 | +/*----------------------------------------------------------------------------* |
| 34404 | + | ZD 21412-21426: Regression tests for reported vulnerabilities |
| 34405 | + *----------------------------------------------------------------------------*/ |
| 34406 | + |
| 34407 | +/* ZD 21412: ML-DSA HashML-DSA verify must reject hashLen > WC_MAX_DIGEST_SIZE */ |
| 34408 | +static int test_zd21412_mldsa_verify_hash_overflow(void) |
| 34409 | +{ |
| 34410 | + EXPECT_DECLS; |
| 34411 | +#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_WC_DILITHIUM) && \ |
| 34412 | + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ |
| 34413 | + !defined(WOLFSSL_DILITHIUM_NO_VERIFY) |
| 34414 | + dilithium_key key; |
| 34415 | + WC_RNG rng; |
| 34416 | + int res = 0; |
| 34417 | + byte sig[4000]; |
| 34418 | + byte hash[4096]; /* larger than WC_MAX_DIGEST_SIZE */ |
| 34419 | + |
| 34420 | + XMEMSET(&key, 0, sizeof(key)); |
| 34421 | + XMEMSET(&rng, 0, sizeof(rng)); |
| 34422 | + XMEMSET(sig, 0x41, sizeof(sig)); |
| 34423 | + XMEMSET(hash, 'A', sizeof(hash)); |
| 34424 | + |
| 34425 | + ExpectIntEQ(wc_InitRng(&rng), 0); |
| 34426 | + ExpectIntEQ(wc_dilithium_init(&key), 0); |
| 34427 | +#ifndef WOLFSSL_NO_ML_DSA_65 |
| 34428 | + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_65), 0); |
| 34429 | +#elif !defined(WOLFSSL_NO_ML_DSA_44) |
| 34430 | + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_44), 0); |
| 34431 | +#else |
| 34432 | + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_87), 0); |
| 34433 | +#endif |
| 34434 | + ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); |
| 34435 | + |
| 34436 | + /* hashLen=4096 must be rejected with BUFFER_E, not overflow the stack */ |
| 34437 | + ExpectIntEQ(wc_dilithium_verify_ctx_hash(sig, sizeof(sig), NULL, 0, |
| 34438 | + WC_HASH_TYPE_SHA256, hash, sizeof(hash), &res, &key), |
| 34439 | + WC_NO_ERR_TRACE(BUFFER_E)); |
| 34440 | + |
| 34441 | + wc_dilithium_free(&key); |
| 34442 | + DoExpectIntEQ(wc_FreeRng(&rng), 0); |
| 34443 | +#endif |
| 34444 | + return EXPECT_RESULT(); |
| 34445 | +} |
| 34446 | + |
| 34447 | +/* ZD 21414: PKCS7 ORI OID must be bounds-checked against MAX_OID_SZ. |
| 34448 | + * This DER was generated by encoding a valid EnvelopedData with ORI, then |
| 34449 | + * patching the OID length from 10 to 64 (exceeds MAX_OID_SZ=32) and inserting |
| 34450 | + * 54 extra bytes. Without the fix, wc_PKCS7_DecryptOri copies 64 bytes into |
| 34451 | + * oriOID[32], causing a stack buffer overflow. */ |
| 34452 | +#if defined(HAVE_PKCS7) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ |
| 34453 | + defined(WOLFSSL_AES_256) |
| 34454 | +static int oriDecryptCb_zd21414(PKCS7* pkcs7, byte* oriType, word32 oriTypeSz, |
| 34455 | + byte* oriValue, word32 oriValueSz, byte* decryptedKey, |
| 34456 | + word32* decryptedKeySz, void* ctx) |
| 34457 | +{ |
| 34458 | + word32 keySz; |
| 34459 | + (void)pkcs7; (void)oriType; (void)oriTypeSz; (void)ctx; |
| 34460 | + if (oriValueSz < 2) return -1; |
| 34461 | + keySz = oriValue[1]; |
| 34462 | + if (*decryptedKeySz < keySz) return -1; |
| 34463 | + XMEMCPY(decryptedKey, oriValue + 2, keySz); |
| 34464 | + *decryptedKeySz = keySz; |
| 34465 | + return 0; |
| 34466 | +} |
| 34467 | +#endif /* HAVE_PKCS7 && AES_CBC && AES_256 */ |
| 34468 | +static int test_zd21414_pkcs7_ori_oid_overflow(void) |
| 34469 | +{ |
| 34470 | + EXPECT_DECLS; |
| 34471 | +#if defined(HAVE_PKCS7) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ |
| 34472 | + defined(WOLFSSL_AES_256) |
| 34473 | + /* Pre-built malformed EnvelopedData: ORI OID length patched from 10 to 64 |
| 34474 | + * (0x40 at offset 26), with 54 extra 'X' bytes inserted after original OID. |
| 34475 | + * Generated by POC's encode-then-patch approach. */ |
| 34476 | + static const byte malformed[] = { |
| 34477 | + 0x30,0x81,0x82,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01, |
| 34478 | + 0x07,0x03,0xA0,0x75,0x30,0x73,0x02,0x01,0x03,0x31,0x30,0xA4, |
| 34479 | + 0x2E,0x06,0x40,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x01, |
| 34480 | + 0x00,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58, |
| 34481 | + 0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58, |
| 34482 | + 0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58, |
| 34483 | + 0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58, |
| 34484 | + 0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x04,0x20,0xEB,0xCE,0xF2, |
| 34485 | + 0x43,0xC1,0xCE,0xF8,0xCD,0xE2,0xC1,0x12,0xDB,0x46,0xFE,0x39, |
| 34486 | + 0xF0,0x2C,0xF2,0x9A,0x6A,0xD4,0x90,0xB8,0xAC,0x72,0x17,0x54, |
| 34487 | + 0x97,0xAE,0xE2,0x59,0xEA,0x30,0x3C,0x06,0x09,0x2A,0x86,0x48, |
| 34488 | + 0x86,0xF7,0x0D,0x01,0x07,0x01,0x30,0x1D,0x06,0x09,0x60,0x86, |
| 34489 | + 0x48,0x01,0x65,0x03,0x04,0x01,0x2A,0x04,0x10,0x98,0xBC,0x3A, |
| 34490 | + 0xF9,0x15,0xF3,0x6B,0x53,0x9D,0x81,0xBD,0x44,0x7E,0xA1,0x01, |
| 34491 | + 0x4D,0x80,0x10,0xBD,0x36,0x26,0xF5,0x6E,0x11,0xD0,0xBB,0x5C, |
| 34492 | + 0x15,0x19,0xA7,0x6B,0xC2,0xC2,0xB6 |
| 34493 | + }; /* 187 bytes */ |
| 34494 | + PKCS7* pkcs7 = NULL; |
| 34495 | + byte decoded[256]; |
| 34496 | + |
| 34497 | + ExpectNotNull(pkcs7 = wc_PKCS7_New(NULL, INVALID_DEVID)); |
| 34498 | + if (pkcs7 != NULL) { |
| 34499 | + /* ORI decrypt callback must be set or parser skips ORI processing */ |
| 34500 | + wc_PKCS7_SetOriDecryptCb(pkcs7, oriDecryptCb_zd21414); |
| 34501 | + /* Without fix: overflows oriOID[32] → crash. |
| 34502 | + * With fix: returns BUFFER_E before the copy. */ |
| 34503 | + ExpectIntLT(wc_PKCS7_DecodeEnvelopedData(pkcs7, (byte*)malformed, |
| 34504 | + (word32)sizeof(malformed), decoded, sizeof(decoded)), 0); |
| 34505 | + wc_PKCS7_Free(pkcs7); |
| 34506 | + } |
| 34507 | +#endif |
| 34508 | + return EXPECT_RESULT(); |
| 34509 | +} |
| 34510 | + |
| 34511 | + |
| 34512 | +/* ZD 21417: Dilithium verify_ctx_msg must reject absurdly large msgLen */ |
| 34513 | +static int test_zd21417_dilithium_hash_int_overflow(void) |
| 34514 | +{ |
| 34515 | + EXPECT_DECLS; |
| 34516 | +#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_WC_DILITHIUM) && \ |
| 34517 | + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ |
| 34518 | + !defined(WOLFSSL_DILITHIUM_NO_VERIFY) |
| 34519 | + dilithium_key key; |
| 34520 | + WC_RNG rng; |
| 34521 | + int res = 0; |
| 34522 | + byte sig[4000]; |
| 34523 | + byte msg[64]; |
| 34524 | + |
| 34525 | + XMEMSET(&key, 0, sizeof(key)); |
| 34526 | + XMEMSET(&rng, 0, sizeof(rng)); |
| 34527 | + XMEMSET(sig, 0, sizeof(sig)); |
| 34528 | + XMEMSET(msg, 'A', sizeof(msg)); |
| 34529 | + |
| 34530 | + ExpectIntEQ(wc_InitRng(&rng), 0); |
| 34531 | + ExpectIntEQ(wc_dilithium_init(&key), 0); |
| 34532 | +#ifndef WOLFSSL_NO_ML_DSA_65 |
| 34533 | + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_65), 0); |
| 34534 | +#elif !defined(WOLFSSL_NO_ML_DSA_44) |
| 34535 | + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_44), 0); |
| 34536 | +#else |
| 34537 | + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_87), 0); |
| 34538 | +#endif |
| 34539 | + ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); |
| 34540 | + |
| 34541 | + /* msgLen=0xFFFFFFC0 would cause integer overflow in hash computation. |
| 34542 | + * Must be rejected with BAD_FUNC_ARG. */ |
| 34543 | + ExpectIntEQ(wc_dilithium_verify_ctx_msg(sig, sizeof(sig), NULL, 0, |
| 34544 | + msg, 0xFFFFFFC0u, &res, &key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); |
| 34545 | + |
| 34546 | + wc_dilithium_free(&key); |
| 34547 | + DoExpectIntEQ(wc_FreeRng(&rng), 0); |
| 34548 | +#endif |
| 34549 | + return EXPECT_RESULT(); |
| 34550 | +} |
| 34551 | + |
| 34552 | +/* ZD 21418: ECC import must validate point is on curve */ |
| 34553 | +static int test_zd21418_ecc_invalid_curve_point(void) |
| 34554 | +{ |
| 34555 | + EXPECT_DECLS; |
| 34556 | +#if defined(HAVE_ECC) && !defined(NO_ECC256) |
| 34557 | + ecc_key peerKey; |
| 34558 | + /* Invalid point (3, 3) — NOT on P-256 curve y^2 = x^3 - 3x + b */ |
| 34559 | + byte badPt[65]; |
| 34560 | + |
| 34561 | + XMEMSET(&peerKey, 0, sizeof(peerKey)); |
| 34562 | + XMEMSET(badPt, 0, sizeof(badPt)); |
| 34563 | + badPt[0] = 0x04; /* uncompressed */ |
| 34564 | + badPt[32] = 3; /* x = 3 */ |
| 34565 | + badPt[64] = 3; /* y = 3 */ |
| 34566 | + |
| 34567 | + ExpectIntEQ(wc_ecc_init(&peerKey), 0); |
| 34568 | + |
| 34569 | + /* Import of invalid point as untrusted (TLS peer) must be rejected. |
| 34570 | + * Uses _ex2 with untrusted=1 to match the TLS key exchange path. */ |
| 34571 | + ExpectIntNE(wc_ecc_import_x963_ex2(badPt, sizeof(badPt), &peerKey, |
| 34572 | + ECC_SECP256R1, 1), 0); |
| 34573 | + |
| 34574 | + wc_ecc_free(&peerKey); |
| 34575 | +#endif |
| 34576 | + return EXPECT_RESULT(); |
| 34577 | +} |
| 34578 | + |
| 34579 | +/* ZD 21422: PKCS7 CBC padding must validate all padding bytes */ |
| 34580 | +static int test_zd21422_pkcs7_padding_oracle(void) |
| 34581 | +{ |
| 34582 | + EXPECT_DECLS; |
| 34583 | +#if defined(HAVE_PKCS7) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ |
| 34584 | + defined(WOLFSSL_AES_256) && !defined(NO_PKCS7_ENCRYPTED_DATA) |
| 34585 | + PKCS7 pkcs7; |
| 34586 | + byte key[32]; |
| 34587 | + byte plaintext[27]; /* 27 bytes → padded to 32 → padding = 05 05 05 05 05 */ |
| 34588 | + byte encoded[4096]; |
| 34589 | + byte output[256]; |
| 34590 | + byte modified[4096]; |
| 34591 | + int encodedSz; |
| 34592 | + int outSz; |
| 34593 | + int ctOff = -1; |
| 34594 | + int ctLen = 0; |
| 34595 | + int i; |
| 34596 | + |
| 34597 | + XMEMSET(key, 0xAA, sizeof(key)); |
| 34598 | + XMEMSET(plaintext, 'X', sizeof(plaintext)); |
| 34599 | + |
| 34600 | + /* Encode EncryptedData */ |
| 34601 | + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); |
| 34602 | + ExpectIntEQ(wc_PKCS7_Init(&pkcs7, NULL, 0), 0); |
| 34603 | + pkcs7.content = plaintext; |
| 34604 | + pkcs7.contentSz = sizeof(plaintext); |
| 34605 | + pkcs7.contentOID = DATA; |
| 34606 | + pkcs7.encryptOID = AES256CBCb; |
| 34607 | + pkcs7.encryptionKey = key; |
| 34608 | + pkcs7.encryptionKeySz = sizeof(key); |
| 34609 | + |
| 34610 | + ExpectIntGT(encodedSz = wc_PKCS7_EncodeEncryptedData(&pkcs7, encoded, |
| 34611 | + sizeof(encoded)), 0); |
| 34612 | + |
| 34613 | + /* Verify normal decrypt works */ |
| 34614 | + ExpectIntEQ(outSz = wc_PKCS7_DecodeEncryptedData(&pkcs7, encoded, |
| 34615 | + (word32)encodedSz, output, sizeof(output)), (int)sizeof(plaintext)); |
| 34616 | + wc_PKCS7_Free(&pkcs7); |
| 34617 | + |
| 34618 | + /* Find ciphertext block in encoded DER */ |
| 34619 | + if (EXPECT_SUCCESS()) { |
| 34620 | + for (i = encodedSz - 10; i > 10; i--) { |
| 34621 | + if (encoded[i] == 0x04 || encoded[i] == 0x80) { |
| 34622 | + int len, lbytes; |
| 34623 | + if (encoded[i+1] < 0x80) { len = encoded[i+1]; lbytes = 1; } |
| 34624 | + else if (encoded[i+1] == 0x81) { len = encoded[i+2]; lbytes = 2; } |
| 34625 | + else continue; |
| 34626 | + if (len > 0 && len % 16 == 0 && |
| 34627 | + i + 1 + lbytes + len <= encodedSz) { |
| 34628 | + ctOff = i + 1 + lbytes; |
| 34629 | + ctLen = len; |
| 34630 | + break; |
| 34631 | + } |
| 34632 | + } |
| 34633 | + } |
| 34634 | + } |
| 34635 | + ExpectIntGT(ctOff, 0); |
| 34636 | + ExpectIntGE(ctLen, 32); |
| 34637 | + |
| 34638 | + /* Corrupt an interior padding byte via CBC bit-flip */ |
| 34639 | + if (EXPECT_SUCCESS()) { |
| 34640 | + XMEMCPY(modified, encoded, (size_t)encodedSz); |
| 34641 | + /* Flip byte in penultimate block to corrupt interior padding */ |
| 34642 | + modified[ctOff + ctLen - 32 + 11] ^= 0x42; |
| 34643 | + |
| 34644 | + /* Decrypt modified ciphertext — must fail, not succeed */ |
| 34645 | + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); |
| 34646 | + ExpectIntEQ(wc_PKCS7_Init(&pkcs7, NULL, 0), 0); |
| 34647 | + pkcs7.encryptionKey = key; |
| 34648 | + pkcs7.encryptionKeySz = sizeof(key); |
| 34649 | + |
| 34650 | + outSz = wc_PKCS7_DecodeEncryptedData(&pkcs7, modified, |
| 34651 | + (word32)encodedSz, output, sizeof(output)); |
| 34652 | + /* Must return an error — if it returns plaintext size, padding |
| 34653 | + * oracle vulnerability exists */ |
| 34654 | + ExpectIntLT(outSz, 0); |
| 34655 | + wc_PKCS7_Free(&pkcs7); |
| 34656 | + } |
| 34657 | +#endif |
| 34658 | + return EXPECT_RESULT(); |
| 34659 | +} |
| 34660 | + |
34390 | 34661 | TEST_CASE testCases[] = { |
34391 | 34662 | TEST_DECL(test_fileAccess), |
34392 | 34663 |
|
@@ -35203,6 +35474,14 @@ TEST_CASE testCases[] = { |
35203 | 35474 | TEST_DECL(test_sniffer_chain_input_overflow), |
35204 | 35475 | #endif |
35205 | 35476 |
|
| 35477 | + /********************************* |
| 35478 | + * ZD 21412-21426 Regression Tests |
| 35479 | + *********************************/ |
| 35480 | + TEST_DECL(test_zd21412_mldsa_verify_hash_overflow), |
| 35481 | + TEST_DECL(test_zd21414_pkcs7_ori_oid_overflow), |
| 35482 | + TEST_DECL(test_zd21417_dilithium_hash_int_overflow), |
| 35483 | + TEST_DECL(test_zd21418_ecc_invalid_curve_point), |
| 35484 | + TEST_DECL(test_zd21422_pkcs7_padding_oracle), |
35206 | 35485 | /* This test needs to stay at the end to clean up any caches allocated. */ |
35207 | 35486 | TEST_DECL(test_wolfSSL_Cleanup) |
35208 | 35487 | }; |
|
0 commit comments