Skip to content

Commit b119d4e

Browse files
committed
Harden PKCS#7 EnvelopedData key unwrap
1 parent 3ee2ff8 commit b119d4e

File tree

3 files changed

+398
-5
lines changed

3 files changed

+398
-5
lines changed

tests/api/test_pkcs7.c

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,219 @@ 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 the last byte of the content, decode, restore. */
1217+
savedContentByte = encrypted[encryptedSz - 1];
1218+
encrypted[encryptedSz - 1] ^= 0xA5;
1219+
1220+
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
1221+
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0);
1222+
if (pkcs7 != NULL) {
1223+
pkcs7->privateKey = rsaPrivKey;
1224+
pkcs7->privateKeySz = rsaPrivKeySz;
1225+
}
1226+
badContentRet = wc_PKCS7_DecodeEnvelopedData(pkcs7, encrypted,
1227+
(word32)encryptedSz, decoded, decodedCap);
1228+
wc_PKCS7_Free(pkcs7);
1229+
pkcs7 = NULL;
1230+
encrypted[encryptedSz - 1] = savedContentByte;
1231+
1232+
/* Both must fail. */
1233+
ExpectIntLT(badKeyRet, 0);
1234+
ExpectIntLT(badContentRet, 0);
1235+
/* Bad-key must NOT leak as an RSA- or recipient-level error. */
1236+
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(PKCS7_RECIP_E));
1237+
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(RSA_PAD_E));
1238+
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(RSA_BUFFER_E));
1239+
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(BAD_PADDING_E));
1240+
/* Both corruption paths should fail at the same content layer.
1241+
* Note: this validates error-code indistinguishability only, not
1242+
* timing indistinguishability, which would require statistical
1243+
* measurement of execution time. */
1244+
ExpectIntEQ(badKeyRet, badContentRet);
1245+
1246+
return EXPECT_RESULT();
1247+
}
1248+
1249+
int test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad(void)
1250+
{
1251+
EXPECT_DECLS;
1252+
byte encrypted[FOURK_BUF];
1253+
byte decoded[FOURK_BUF];
1254+
byte* rsaCert = NULL;
1255+
byte* rsaPrivKey = NULL;
1256+
word32 rsaCertSz = 0;
1257+
word32 rsaPrivKeySz = 0;
1258+
#if !defined(USE_CERT_BUFFERS_1024) && !defined(USE_CERT_BUFFERS_2048) && \
1259+
!defined(NO_FILESYSTEM)
1260+
XFILE f = XBADFILE;
1261+
#endif
1262+
1263+
/* Load RSA cert and key */
1264+
#if defined(USE_CERT_BUFFERS_1024)
1265+
rsaCertSz = (word32)sizeof_client_cert_der_1024;
1266+
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
1267+
DYNAMIC_TYPE_TMP_BUFFER));
1268+
if (rsaCert != NULL)
1269+
XMEMCPY(rsaCert, client_cert_der_1024, rsaCertSz);
1270+
rsaPrivKeySz = (word32)sizeof_client_key_der_1024;
1271+
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
1272+
DYNAMIC_TYPE_TMP_BUFFER));
1273+
if (rsaPrivKey != NULL)
1274+
XMEMCPY(rsaPrivKey, client_key_der_1024, rsaPrivKeySz);
1275+
#elif defined(USE_CERT_BUFFERS_2048)
1276+
rsaCertSz = (word32)sizeof_client_cert_der_2048;
1277+
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
1278+
DYNAMIC_TYPE_TMP_BUFFER));
1279+
if (rsaCert != NULL)
1280+
XMEMCPY(rsaCert, client_cert_der_2048, rsaCertSz);
1281+
rsaPrivKeySz = (word32)sizeof_client_key_der_2048;
1282+
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
1283+
DYNAMIC_TYPE_TMP_BUFFER));
1284+
if (rsaPrivKey != NULL)
1285+
XMEMCPY(rsaPrivKey, client_key_der_2048, rsaPrivKeySz);
1286+
#elif !defined(NO_FILESYSTEM)
1287+
rsaCertSz = FOURK_BUF;
1288+
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
1289+
DYNAMIC_TYPE_TMP_BUFFER));
1290+
ExpectTrue((f = XFOPEN("./certs/client-cert.der", "rb")) != XBADFILE);
1291+
ExpectTrue((rsaCertSz = (word32)XFREAD(rsaCert, 1, rsaCertSz, f)) > 0);
1292+
if (f != XBADFILE) {
1293+
XFCLOSE(f);
1294+
f = XBADFILE;
1295+
}
1296+
rsaPrivKeySz = FOURK_BUF;
1297+
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
1298+
DYNAMIC_TYPE_TMP_BUFFER));
1299+
ExpectTrue((f = XFOPEN("./certs/client-key.der", "rb")) != XBADFILE);
1300+
ExpectTrue((rsaPrivKeySz = (word32)XFREAD(rsaPrivKey, 1,
1301+
rsaPrivKeySz, f)) > 0);
1302+
if (f != XBADFILE)
1303+
XFCLOSE(f);
1304+
#endif
1305+
1306+
if (rsaCert == NULL || rsaPrivKey == NULL) {
1307+
XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
1308+
XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
1309+
return TEST_SKIPPED;
1310+
}
1311+
1312+
/* AES-128: 32-byte fake CEK larger than real CEK size (16 bytes). */
1313+
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES128CBCb, rsaCert, rsaCertSz,
1314+
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
1315+
decoded, sizeof(decoded)), TEST_SUCCESS);
1316+
#ifdef WOLFSSL_AES_192
1317+
/* AES-192: fake CEK (32) vs real CEK (24) - another size mismatch. */
1318+
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES192CBCb, rsaCert, rsaCertSz,
1319+
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
1320+
decoded, sizeof(decoded)), TEST_SUCCESS);
1321+
#endif
1322+
/* AES-256: fake CEK size matches real CEK size (32 bytes). */
1323+
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES256CBCb, rsaCert, rsaCertSz,
1324+
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
1325+
decoded, sizeof(decoded)), TEST_SUCCESS);
1326+
1327+
XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
1328+
XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
1329+
return EXPECT_RESULT();
1330+
} /* END test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad */
1331+
#endif
1332+
1333+
11211334
/*
11221335
* Testing wc_PKCS7_EncodeSignedData_ex() and wc_PKCS7_VerifySignedData_ex()
11231336
*/

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)