3131#include <wolfssl/wolfcrypt/pkcs12.h>
3232#include <wolfssl/wolfcrypt/pwdbased.h>
3333#include <wolfssl/wolfcrypt/types.h>
34+ #include <wolfssl/wolfcrypt/aes.h>
3435#include <tests/api/api.h>
3536#include <tests/api/test_pkcs12.h>
3637
@@ -305,6 +306,7 @@ int test_wc_PKCS12_encrypted_content_bounds(void)
305306 byte * outKey = NULL ;
306307 byte * outCert = NULL ;
307308 WC_DerCertList * outCaList = NULL ;
309+ int exportRet = 0 ;
308310 word32 pkcs12DerSz = 0 ;
309311 word32 outKeySz = 0 ;
310312 word32 outCertSz = 0 ;
@@ -314,8 +316,10 @@ int test_wc_PKCS12_encrypted_content_bounds(void)
314316 sizeof (pkcs12Passwd ) - 1 , NULL , inKey , inKeySz , inCert , inCertSz ,
315317 & inCa , -1 , -1 , 2048 , 2048 , 0 , NULL ));
316318
317- /* Serialize to DER */
318- ExpectIntGE ((pkcs12DerSz = wc_i2d_PKCS12 (pkcs12Export , & pkcs12Der , NULL )), 0 );
319+ /* Serialize to DER - use int intermediate to avoid word32 truncation
320+ * of negative error codes from wc_i2d_PKCS12(). */
321+ ExpectIntGE ((exportRet = wc_i2d_PKCS12 (pkcs12Export , & pkcs12Der , NULL )), 0 );
322+ pkcs12DerSz = (word32 )exportRet ;
319323
320324 /* Parse it back - this exercises the fixed bounds checking code path */
321325 ExpectNotNull (pkcs12Import = wc_PKCS12_new_ex (NULL ));
@@ -331,6 +335,9 @@ int test_wc_PKCS12_encrypted_content_bounds(void)
331335 ExpectIntEQ (outCertSz , inCertSz );
332336 ExpectNotNull (outCaList );
333337 ExpectIntEQ (outCaList -> bufferSz , inCa .bufferSz );
338+ ExpectIntEQ (XMEMCMP (outKey , inKey , inKeySz ), 0 );
339+ ExpectIntEQ (XMEMCMP (outCert , inCert , inCertSz ), 0 );
340+ ExpectIntEQ (XMEMCMP (outCaList -> buffer , inCa .buffer , inCa .bufferSz ), 0 );
334341
335342 /* Clean up */
336343 XFREE (outKey , NULL , DYNAMIC_TYPE_PUBLIC_KEY );
@@ -340,6 +347,120 @@ int test_wc_PKCS12_encrypted_content_bounds(void)
340347 XFREE (pkcs12Der , NULL , DYNAMIC_TYPE_PKCS );
341348 wc_PKCS12_free (pkcs12Export );
342349
350+ #endif
351+
352+ /* Part 2: True regression test - craft a malformed PKCS#12 whose decrypted
353+ * SafeBags SEQUENCE claims a length that exceeds the decrypted content
354+ * bounds (contentSz) but fits within the stale ContentInfo bounds
355+ * (ci->dataSz). Before the fix, the parser used ci->dataSz, allowing a
356+ * heap OOB read; with the fix it uses contentSz and rejects the blob. */
357+ #if !defined(NO_ASN ) && !defined(NO_PWDBASED ) && defined(HAVE_PKCS12 ) && \
358+ defined(WOLFSSL_AES_256 ) && defined(HAVE_AES_CBC ) && \
359+ defined(HAVE_AES_DECRYPT ) && !defined(NO_SHA256 ) && !defined(NO_HMAC ) && \
360+ defined(WOLFSSL_ASN_TEMPLATE ) && !defined(HAVE_FIPS )
361+ {
362+ static const char regPassword [] = "test" ;
363+ static const byte regSalt [8 ] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 };
364+ static const byte regIv [16 ] = {0 };
365+
366+ /* Malformed SafeBags plaintext (one AES block = 16 bytes).
367+ * The outer SEQUENCE claims length 100 - this exceeds the decrypted
368+ * content size (16) but fits inside the stale ci->dataSz (127) that
369+ * the unfixed code used as the parsing bound. */
370+ static const byte regPlaintext [16 ] = {
371+ 0x30 , 0x64 , /* SEQUENCE, length 100 */
372+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
373+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00
374+ };
375+
376+ /* Complete PKCS#12 DER (170 bytes).
377+ * Structure: PFX { version 3, authSafe { DATA { AuthenticatedSafe {
378+ * EncryptedData { PBES2(AES-256-CBC, HMAC-SHA256, PBKDF2)
379+ * <ciphertext placeholder at offset 154> } } } } }
380+ * No MacData - macIter=0 skips MAC verification. */
381+ byte regDer [170 ] = {
382+ 0x30 , 0x81 , 0xA7 , /* PFX SEQ (167) */
383+ 0x02 , 0x01 , 0x03 , /* version 3 */
384+ 0x30 , 0x81 , 0xA1 , /* authSafe ContentInfo (161) */
385+ 0x06 , 0x09 , 0x2A , 0x86 , 0x48 , 0x86 ,
386+ 0xF7 , 0x0D , 0x01 , 0x07 , 0x01 , /* OID data */
387+ 0xA0 , 0x81 , 0x93 , /* [0] CONS. (147) */
388+ 0x04 , 0x81 , 0x90 , /* OCTET STRING (144) */
389+ 0x30 , 0x81 , 0x8D , /* AuthenticatedSafe SEQ (141) */
390+ 0x30 , 0x81 , 0x8A , /* ContentInfo SEQ (138) */
391+ 0x06 , 0x09 , 0x2A , 0x86 , 0x48 , 0x86 ,
392+ 0xF7 , 0x0D , 0x01 , 0x07 , 0x06 , /* OID encryptedData */
393+ 0xA0 , 0x7D , /* [0] CONS. (125) */
394+ 0x30 , 0x7B , /* EncryptedData SEQ (123) */
395+ 0x02 , 0x01 , 0x00 , /* version 0 */
396+ 0x30 , 0x76 , /* EncryptedContentInfo SEQ (118) */
397+ 0x06 , 0x09 , 0x2A , 0x86 , 0x48 , 0x86 ,
398+ 0xF7 , 0x0D , 0x01 , 0x07 , 0x01 , /* OID data */
399+ /* --- EncryptContent payload (107 bytes) --- */
400+ 0x30 , 0x57 , /* AlgorithmIdentifier SEQ (87) */
401+ 0x06 , 0x09 , 0x2A , 0x86 , 0x48 , 0x86 ,
402+ 0xF7 , 0x0D , 0x01 , 0x05 , 0x0D , /* OID pbes2 */
403+ 0x30 , 0x4A , /* PBES2-params SEQ (74) */
404+ 0x30 , 0x29 , /* keyDerivFunc SEQ (41) */
405+ 0x06 , 0x09 , 0x2A , 0x86 , 0x48 , 0x86 ,
406+ 0xF7 , 0x0D , 0x01 , 0x05 , 0x0C , /* OID pbkdf2 */
407+ 0x30 , 0x1C , /* PBKDF2-params SEQ (28) */
408+ 0x04 , 0x08 ,
409+ 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 , /* salt */
410+ 0x02 , 0x02 , 0x08 , 0x00 , /* iterations 2048 */
411+ 0x30 , 0x0C , /* PRF SEQ (12) */
412+ 0x06 , 0x08 , 0x2A , 0x86 , 0x48 , 0x86 ,
413+ 0xF7 , 0x0D , 0x02 , 0x09 , /* OID hmac-sha256 */
414+ 0x05 , 0x00 , /* NULL */
415+ 0x30 , 0x1D , /* encryptionScheme SEQ (29) */
416+ 0x06 , 0x09 , 0x60 , 0x86 , 0x48 , 0x01 ,
417+ 0x65 , 0x03 , 0x04 , 0x01 , 0x2A , /* OID aes256-cbc */
418+ 0x04 , 0x10 , /* IV OCT (16) */
419+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
420+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
421+ 0x80 , 0x10 , /* [0] IMPLICIT CT (16) */
422+ /* 16 bytes ciphertext - filled at runtime */
423+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
424+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00
425+ };
426+
427+ byte regKey [32 ];
428+ byte regCiphertext [16 ];
429+ Aes regAes ;
430+ WC_PKCS12 * regP12 = NULL ;
431+ byte * regPkey = NULL ;
432+ byte * regCert = NULL ;
433+ word32 regPkeySz = 0 ;
434+ word32 regCertSz = 0 ;
435+
436+ /* Derive AES-256 key with the same PBKDF2 that DecryptContent uses */
437+ ExpectIntEQ (wc_PBKDF2 (regKey , (const byte * )regPassword ,
438+ (int )XSTRLEN (regPassword ), regSalt , (int )sizeof (regSalt ),
439+ 2048 , 32 , WC_SHA256 ), 0 );
440+
441+ /* Encrypt the malformed plaintext */
442+ ExpectIntEQ (wc_AesInit (& regAes , NULL , INVALID_DEVID ), 0 );
443+ ExpectIntEQ (wc_AesSetKey (& regAes , regKey , 32 , regIv ,
444+ AES_ENCRYPTION ), 0 );
445+ ExpectIntEQ (wc_AesCbcEncrypt (& regAes , regCiphertext , regPlaintext ,
446+ sizeof (regPlaintext )), 0 );
447+ wc_AesFree (& regAes );
448+
449+ /* Patch ciphertext into the DER template at offset 154 */
450+ XMEMCPY (regDer + 154 , regCiphertext , sizeof (regCiphertext ));
451+
452+ /* Parse the crafted PKCS#12 - d2i should succeed (outer structure
453+ * is valid), but wc_PKCS12_parse must fail because GetSequence
454+ * rejects SEQUENCE length 100 against contentSz 16. */
455+ ExpectNotNull (regP12 = wc_PKCS12_new_ex (NULL ));
456+ ExpectIntGE (wc_d2i_PKCS12 (regDer , (word32 )sizeof (regDer ), regP12 ), 0 );
457+ ExpectIntLT (wc_PKCS12_parse (regP12 , regPassword , & regPkey , & regPkeySz ,
458+ & regCert , & regCertSz , NULL ), 0 );
459+
460+ XFREE (regPkey , NULL , DYNAMIC_TYPE_PUBLIC_KEY );
461+ XFREE (regCert , NULL , DYNAMIC_TYPE_PKCS );
462+ wc_PKCS12_free (regP12 );
463+ }
343464#endif
344465 return EXPECT_RESULT ();
345466}
@@ -747,4 +868,3 @@ int test_wc_PKCS12_PBKDF_ex_sha512_256(void)
747868#endif
748869 return EXPECT_RESULT ();
749870}
750-
0 commit comments