|
31 | 31 | #include <wolfssl/wolfcrypt/pkcs12.h> |
32 | 32 | #include <wolfssl/wolfcrypt/pwdbased.h> |
33 | 33 | #include <wolfssl/wolfcrypt/types.h> |
| 34 | +#include <wolfssl/wolfcrypt/aes.h> |
34 | 35 | #include <tests/api/api.h> |
35 | 36 | #include <tests/api/test_pkcs12.h> |
36 | 37 |
|
@@ -272,6 +273,198 @@ int test_wc_d2i_PKCS12_oid_underflow(void) |
272 | 273 | return EXPECT_RESULT(); |
273 | 274 | } |
274 | 275 |
|
| 276 | +/* Test that validates the fix for heap OOB read vulnerability where |
| 277 | + * ASN.1 parsing after DecryptContent() would use stale ContentInfo bounds. |
| 278 | + * This is a basic test that verifies the fix compiles and basic PKCS#12 |
| 279 | + * functionality still works after adding contentSz bounds checking. */ |
| 280 | +int test_wc_PKCS12_encrypted_content_bounds(void) |
| 281 | +{ |
| 282 | + EXPECT_DECLS; |
| 283 | +#if !defined(NO_ASN) && !defined(NO_PWDBASED) && defined(HAVE_PKCS12) && \ |
| 284 | + !defined(NO_RSA) && !defined(NO_AES) && !defined(NO_SHA) && \ |
| 285 | + !defined(NO_SHA256) && defined(USE_CERT_BUFFERS_2048) |
| 286 | + |
| 287 | + /* This test validates that the fix for heap OOB read is in place. |
| 288 | + * The fix ensures ASN.1 parsing uses contentSz (actual decrypted size) |
| 289 | + * instead of ci->dataSz (original ContentInfo size) as bounds. |
| 290 | + * |
| 291 | + * We test this by exercising the PKCS#12 parsing path with encrypted |
| 292 | + * content to ensure the fix doesn't break normal operation. */ |
| 293 | + |
| 294 | + byte* inKey = (byte*) server_key_der_2048; |
| 295 | + const word32 inKeySz = sizeof_server_key_der_2048; |
| 296 | + byte* inCert = (byte*) server_cert_der_2048; |
| 297 | + const word32 inCertSz = sizeof_server_cert_der_2048; |
| 298 | + WC_DerCertList inCa = { |
| 299 | + (byte*)ca_cert_der_2048, sizeof_ca_cert_der_2048, NULL |
| 300 | + }; |
| 301 | + char pkcs12Passwd[] = "test_bounds_fix"; |
| 302 | + |
| 303 | + WC_PKCS12* pkcs12Export = NULL; |
| 304 | + WC_PKCS12* pkcs12Import = NULL; |
| 305 | + byte* pkcs12Der = NULL; |
| 306 | + byte* outKey = NULL; |
| 307 | + byte* outCert = NULL; |
| 308 | + WC_DerCertList* outCaList = NULL; |
| 309 | + int exportRet = 0; |
| 310 | + word32 pkcs12DerSz = 0; |
| 311 | + word32 outKeySz = 0; |
| 312 | + word32 outCertSz = 0; |
| 313 | + |
| 314 | + /* Create a PKCS#12 with encrypted content */ |
| 315 | + ExpectNotNull(pkcs12Export = wc_PKCS12_create(pkcs12Passwd, |
| 316 | + sizeof(pkcs12Passwd) - 1, NULL, inKey, inKeySz, inCert, inCertSz, |
| 317 | + &inCa, -1, -1, 2048, 2048, 0, NULL)); |
| 318 | + |
| 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; |
| 323 | + |
| 324 | + /* Parse it back - this exercises the fixed bounds checking code path */ |
| 325 | + ExpectNotNull(pkcs12Import = wc_PKCS12_new_ex(NULL)); |
| 326 | + ExpectIntGE(wc_d2i_PKCS12(pkcs12Der, pkcs12DerSz, pkcs12Import), 0); |
| 327 | + |
| 328 | + /* This parse operation now uses contentSz instead of ci->dataSz for bounds, |
| 329 | + * preventing the heap OOB read that existed before the fix */ |
| 330 | + ExpectIntEQ(wc_PKCS12_parse(pkcs12Import, pkcs12Passwd, &outKey, &outKeySz, |
| 331 | + &outCert, &outCertSz, &outCaList), 0); |
| 332 | + |
| 333 | + /* Verify the parsing worked correctly */ |
| 334 | + ExpectIntEQ(outKeySz, inKeySz); |
| 335 | + ExpectIntEQ(outCertSz, inCertSz); |
| 336 | + ExpectNotNull(outCaList); |
| 337 | + 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); |
| 341 | + |
| 342 | + /* Clean up */ |
| 343 | + XFREE(outKey, NULL, DYNAMIC_TYPE_PUBLIC_KEY); |
| 344 | + XFREE(outCert, NULL, DYNAMIC_TYPE_PKCS); |
| 345 | + wc_FreeCertList(outCaList, NULL); |
| 346 | + wc_PKCS12_free(pkcs12Import); |
| 347 | + XFREE(pkcs12Der, NULL, DYNAMIC_TYPE_PKCS); |
| 348 | + wc_PKCS12_free(pkcs12Export); |
| 349 | + |
| 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(®Aes, NULL, INVALID_DEVID), 0); |
| 443 | + ExpectIntEQ(wc_AesSetKey(®Aes, regKey, 32, regIv, |
| 444 | + AES_ENCRYPTION), 0); |
| 445 | + ExpectIntEQ(wc_AesCbcEncrypt(®Aes, regCiphertext, regPlaintext, |
| 446 | + sizeof(regPlaintext)), 0); |
| 447 | + wc_AesFree(®Aes); |
| 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, ®Pkey, ®PkeySz, |
| 458 | + ®Cert, ®CertSz, NULL), 0); |
| 459 | + |
| 460 | + XFREE(regPkey, NULL, DYNAMIC_TYPE_PUBLIC_KEY); |
| 461 | + XFREE(regCert, NULL, DYNAMIC_TYPE_PKCS); |
| 462 | + wc_PKCS12_free(regP12); |
| 463 | + } |
| 464 | +#endif |
| 465 | + return EXPECT_RESULT(); |
| 466 | +} |
| 467 | + |
275 | 468 | int test_wc_PKCS12_PBKDF(void) |
276 | 469 | { |
277 | 470 | EXPECT_DECLS; |
@@ -675,4 +868,3 @@ int test_wc_PKCS12_PBKDF_ex_sha512_256(void) |
675 | 868 | #endif |
676 | 869 | return EXPECT_RESULT(); |
677 | 870 | } |
678 | | - |
|
0 commit comments