Skip to content

Commit 5829b06

Browse files
committed
Harden PKCS#7 EnvelopedData key unwrap
1 parent dddc16b commit 5829b06

4 files changed

Lines changed: 444 additions & 5 deletions

File tree

.wolfssl_known_macro_extras

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,7 @@ WOLFSSL_NO_KCAPI_HMAC_SHA256
829829
WOLFSSL_NO_KCAPI_HMAC_SHA384
830830
WOLFSSL_NO_KCAPI_HMAC_SHA512
831831
WOLFSSL_NO_KCAPI_SHA224
832+
WOLFSSL_NO_KTRI_ORACLE_WARNING
832833
WOLFSSL_NO_OCSP_DATE_CHECK
833834
WOLFSSL_NO_OCSP_ISSUER_CHAIN_CHECK
834835
WOLFSSL_NO_OCSP_OPTIONAL_CERTS

tests/api/test_pkcs7.c

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,229 @@ int test_wc_PKCS7_EnvelopedData_KTRI_RSA_PSS(void)
11181118
#endif
11191119

11201120

1121+
/*
1122+
* Bleichenbacher padding-oracle regression: wc_PKCS7_DecryptKtri must not
1123+
* return a distinguishable error when RSA PKCS#1 v1.5 unwrap of the
1124+
* encrypted CEK fails vs. when it succeeds with a wrong key. The
1125+
* mitigation substitutes a deterministic pseudo-random CEK on RSA failure
1126+
* so content decryption fails indistinguishably. This test corrupts the
1127+
* encryptedKey in a valid EnvelopedData and asserts the error matches
1128+
* content corruption rather than surfacing an RSA/recipient-level code.
1129+
* Runs for AES-128 and AES-256 because the fake CEK is a fixed 32 bytes:
1130+
* AES-128 (key size 16) exercises the path where the fake size differs
1131+
* from the real CEK size.
1132+
*/
1133+
#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) && \
1134+
!defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \
1135+
defined(WOLFSSL_AES_256) && !defined(NO_HMAC) && \
1136+
!defined(WOLFSSL_NO_MALLOC) && \
1137+
(defined(USE_CERT_BUFFERS_2048) || defined(USE_CERT_BUFFERS_1024) || \
1138+
!defined(NO_FILESYSTEM))
1139+
static int pkcs7_ktri_bad_pad_case(int encryptOID, byte* rsaCert,
1140+
word32 rsaCertSz, byte* rsaPrivKey,
1141+
word32 rsaPrivKeySz, byte* encrypted,
1142+
word32 encryptedCap, byte* decoded,
1143+
word32 decodedCap)
1144+
{
1145+
EXPECT_DECLS;
1146+
PKCS7* pkcs7 = NULL;
1147+
byte data[] = "PKCS7 KTRI bad-RSA-padding regression payload.";
1148+
int encryptedSz = 0;
1149+
int badKeyRet = 0;
1150+
int badContentRet = 0;
1151+
byte savedKeyByte = 0;
1152+
byte savedContentByte = 0;
1153+
word32 i;
1154+
word32 encryptedKeyOff = 0;
1155+
static const byte rsaEncOid[] = {
1156+
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
1157+
0x01, 0x01, 0x01
1158+
};
1159+
1160+
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
1161+
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0);
1162+
if (pkcs7 != NULL) {
1163+
pkcs7->content = data;
1164+
pkcs7->contentSz = (word32)sizeof(data);
1165+
pkcs7->contentOID = DATA;
1166+
pkcs7->encryptOID = encryptOID;
1167+
}
1168+
ExpectIntGT(encryptedSz = wc_PKCS7_EncodeEnvelopedData(pkcs7,
1169+
encrypted, encryptedCap), 0);
1170+
wc_PKCS7_Free(pkcs7);
1171+
pkcs7 = NULL;
1172+
1173+
/* Locate the KTRI encryptedKey OCTET STRING. After the rsaEncryption
1174+
* OID there are NULL algorithm parameters (05 00), then a 256-byte
1175+
* OCTET STRING (tag 04, long-form length 82 01 00 for RSA-2048). */
1176+
for (i = 0; (int)(i + sizeof(rsaEncOid)) < encryptedSz; i++) {
1177+
if (XMEMCMP(&encrypted[i], rsaEncOid, sizeof(rsaEncOid)) == 0) {
1178+
word32 p = i + (word32)sizeof(rsaEncOid);
1179+
if (p + 2 < (word32)encryptedSz &&
1180+
encrypted[p] == 0x05 && encrypted[p + 1] == 0x00) {
1181+
p += 2;
1182+
}
1183+
if (p + 4 < (word32)encryptedSz && encrypted[p] == 0x04) {
1184+
if (encrypted[p + 1] == 0x82) {
1185+
encryptedKeyOff = p + 4;
1186+
}
1187+
else if (encrypted[p + 1] == 0x81) {
1188+
encryptedKeyOff = p + 3;
1189+
}
1190+
else {
1191+
encryptedKeyOff = p + 2;
1192+
}
1193+
}
1194+
break;
1195+
}
1196+
}
1197+
ExpectIntGT(encryptedKeyOff, 0);
1198+
ExpectIntLT(encryptedKeyOff + 32, (word32)encryptedSz);
1199+
1200+
/* Case 1: corrupt a byte inside the RSA ciphertext, decode, restore. */
1201+
savedKeyByte = encrypted[encryptedKeyOff + 16];
1202+
encrypted[encryptedKeyOff + 16] ^= 0xA5;
1203+
1204+
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
1205+
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0);
1206+
if (pkcs7 != NULL) {
1207+
pkcs7->privateKey = rsaPrivKey;
1208+
pkcs7->privateKeySz = rsaPrivKeySz;
1209+
}
1210+
badKeyRet = wc_PKCS7_DecodeEnvelopedData(pkcs7, encrypted,
1211+
(word32)encryptedSz, decoded, decodedCap);
1212+
wc_PKCS7_Free(pkcs7);
1213+
pkcs7 = NULL;
1214+
encrypted[encryptedKeyOff + 16] = savedKeyByte;
1215+
1216+
/* Case 2: corrupt a byte in the second-to-last AES ciphertext block.
1217+
* In CBC mode this deterministically XOR-flips the corresponding byte
1218+
* in the last plaintext block, invalidating the PKCS#7 padding
1219+
* (original pad byte 0x01 becomes 0x01^0xA5 = 0xA4 > blockSz).
1220+
* Corrupting the last ciphertext block directly would randomize the
1221+
* entire last plaintext block, giving ~1/256 chance of accidentally
1222+
* valid padding and intermittent test failures. */
1223+
savedContentByte = encrypted[encryptedSz - 17];
1224+
encrypted[encryptedSz - 17] ^= 0xA5;
1225+
1226+
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
1227+
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0);
1228+
if (pkcs7 != NULL) {
1229+
pkcs7->privateKey = rsaPrivKey;
1230+
pkcs7->privateKeySz = rsaPrivKeySz;
1231+
}
1232+
badContentRet = wc_PKCS7_DecodeEnvelopedData(pkcs7, encrypted,
1233+
(word32)encryptedSz, decoded, decodedCap);
1234+
wc_PKCS7_Free(pkcs7);
1235+
pkcs7 = NULL;
1236+
encrypted[encryptedSz - 17] = savedContentByte;
1237+
1238+
/* Case 2 must always fail: the CBC-chain corruption deterministically
1239+
* invalidates the PKCS#7 padding. */
1240+
ExpectIntLT(badContentRet, 0);
1241+
/* Bad-key must NOT leak as an RSA- or recipient-level error. */
1242+
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(PKCS7_RECIP_E));
1243+
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(RSA_PAD_E));
1244+
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(RSA_BUFFER_E));
1245+
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(BAD_PADDING_E));
1246+
/* Case 1 (bad RSA key) decrypts content with a random fake CEK,
1247+
* producing fully random plaintext. With ~1/256 probability the
1248+
* PKCS#7 padding accidentally looks valid, causing a positive
1249+
* garbage-length return instead of an error. This does not leak
1250+
* RSA key information, so it is acceptable. When both cases do
1251+
* fail, verify they fail at the same content-decryption layer. */
1252+
if (badKeyRet < 0) {
1253+
ExpectIntEQ(badKeyRet, badContentRet);
1254+
}
1255+
1256+
return EXPECT_RESULT();
1257+
}
1258+
1259+
int test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad(void)
1260+
{
1261+
EXPECT_DECLS;
1262+
byte encrypted[FOURK_BUF];
1263+
byte decoded[FOURK_BUF];
1264+
byte* rsaCert = NULL;
1265+
byte* rsaPrivKey = NULL;
1266+
word32 rsaCertSz = 0;
1267+
word32 rsaPrivKeySz = 0;
1268+
#if !defined(USE_CERT_BUFFERS_1024) && !defined(USE_CERT_BUFFERS_2048) && \
1269+
!defined(NO_FILESYSTEM)
1270+
XFILE f = XBADFILE;
1271+
#endif
1272+
1273+
/* Load RSA cert and key */
1274+
#if defined(USE_CERT_BUFFERS_1024)
1275+
rsaCertSz = (word32)sizeof_client_cert_der_1024;
1276+
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
1277+
DYNAMIC_TYPE_TMP_BUFFER));
1278+
if (rsaCert != NULL)
1279+
XMEMCPY(rsaCert, client_cert_der_1024, rsaCertSz);
1280+
rsaPrivKeySz = (word32)sizeof_client_key_der_1024;
1281+
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
1282+
DYNAMIC_TYPE_TMP_BUFFER));
1283+
if (rsaPrivKey != NULL)
1284+
XMEMCPY(rsaPrivKey, client_key_der_1024, rsaPrivKeySz);
1285+
#elif defined(USE_CERT_BUFFERS_2048)
1286+
rsaCertSz = (word32)sizeof_client_cert_der_2048;
1287+
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
1288+
DYNAMIC_TYPE_TMP_BUFFER));
1289+
if (rsaCert != NULL)
1290+
XMEMCPY(rsaCert, client_cert_der_2048, rsaCertSz);
1291+
rsaPrivKeySz = (word32)sizeof_client_key_der_2048;
1292+
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
1293+
DYNAMIC_TYPE_TMP_BUFFER));
1294+
if (rsaPrivKey != NULL)
1295+
XMEMCPY(rsaPrivKey, client_key_der_2048, rsaPrivKeySz);
1296+
#elif !defined(NO_FILESYSTEM)
1297+
rsaCertSz = FOURK_BUF;
1298+
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
1299+
DYNAMIC_TYPE_TMP_BUFFER));
1300+
ExpectTrue((f = XFOPEN("./certs/client-cert.der", "rb")) != XBADFILE);
1301+
ExpectTrue((rsaCertSz = (word32)XFREAD(rsaCert, 1, rsaCertSz, f)) > 0);
1302+
if (f != XBADFILE) {
1303+
XFCLOSE(f);
1304+
f = XBADFILE;
1305+
}
1306+
rsaPrivKeySz = FOURK_BUF;
1307+
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
1308+
DYNAMIC_TYPE_TMP_BUFFER));
1309+
ExpectTrue((f = XFOPEN("./certs/client-key.der", "rb")) != XBADFILE);
1310+
ExpectTrue((rsaPrivKeySz = (word32)XFREAD(rsaPrivKey, 1,
1311+
rsaPrivKeySz, f)) > 0);
1312+
if (f != XBADFILE)
1313+
XFCLOSE(f);
1314+
#endif
1315+
1316+
if (rsaCert == NULL || rsaPrivKey == NULL) {
1317+
XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
1318+
XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
1319+
return TEST_SKIPPED;
1320+
}
1321+
1322+
/* AES-128: 32-byte fake CEK larger than real CEK size (16 bytes). */
1323+
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES128CBCb, rsaCert, rsaCertSz,
1324+
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
1325+
decoded, sizeof(decoded)), TEST_SUCCESS);
1326+
#ifdef WOLFSSL_AES_192
1327+
/* AES-192: fake CEK (32) vs real CEK (24) - another size mismatch. */
1328+
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES192CBCb, rsaCert, rsaCertSz,
1329+
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
1330+
decoded, sizeof(decoded)), TEST_SUCCESS);
1331+
#endif
1332+
/* AES-256: fake CEK size matches real CEK size (32 bytes). */
1333+
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES256CBCb, rsaCert, rsaCertSz,
1334+
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
1335+
decoded, sizeof(decoded)), TEST_SUCCESS);
1336+
1337+
XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
1338+
XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
1339+
return EXPECT_RESULT();
1340+
} /* END test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad */
1341+
#endif
1342+
1343+
11211344
/*
11221345
* Testing wc_PKCS7_EncodeSignedData_ex() and wc_PKCS7_VerifySignedData_ex()
11231346
*/

tests/api/test_pkcs7.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void);
3838
!defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_256)
3939
int test_wc_PKCS7_EnvelopedData_KTRI_RSA_PSS(void);
4040
#endif
41+
#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) && \
42+
!defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \
43+
defined(WOLFSSL_AES_256) && !defined(NO_HMAC) && \
44+
!defined(WOLFSSL_NO_MALLOC) && \
45+
(defined(USE_CERT_BUFFERS_2048) || defined(USE_CERT_BUFFERS_1024) || \
46+
!defined(NO_FILESYSTEM))
47+
int test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad(void);
48+
#endif
4149
int test_wc_PKCS7_EncodeSignedData_ex(void);
4250
int test_wc_PKCS7_VerifySignedData_RSA(void);
4351
int test_wc_PKCS7_VerifySignedData_ECC(void);
@@ -82,6 +90,18 @@ int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
8290
#define TEST_PKCS7_RSA_PSS_ED_DECL
8391
#endif
8492

93+
#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) && \
94+
!defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \
95+
defined(WOLFSSL_AES_256) && !defined(NO_HMAC) && \
96+
!defined(WOLFSSL_NO_MALLOC) && \
97+
(defined(USE_CERT_BUFFERS_2048) || defined(USE_CERT_BUFFERS_1024) || \
98+
!defined(NO_FILESYSTEM))
99+
#define TEST_PKCS7_KTRI_BADRSAPAD_DECL \
100+
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad),
101+
#else
102+
#define TEST_PKCS7_KTRI_BADRSAPAD_DECL
103+
#endif
104+
85105
#define TEST_PKCS7_SIGNED_DATA_DECLS \
86106
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_InitWithCert), \
87107
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeData), \
@@ -100,6 +120,7 @@ int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
100120
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_stream), \
101121
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_EncodeDecodeEnvelopedData), \
102122
TEST_PKCS7_RSA_PSS_ED_DECL \
123+
TEST_PKCS7_KTRI_BADRSAPAD_DECL \
103124
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_SetAESKeyWrapUnwrapCb), \
104125
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_GetEnvelopedDataKariRid), \
105126
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_EncodeEncryptedData), \

0 commit comments

Comments
 (0)