mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 14:00:48 +02:00
Harden PKCS#7 EnvelopedData key unwrap
This commit is contained in:
@@ -829,6 +829,7 @@ WOLFSSL_NO_KCAPI_HMAC_SHA256
|
||||
WOLFSSL_NO_KCAPI_HMAC_SHA384
|
||||
WOLFSSL_NO_KCAPI_HMAC_SHA512
|
||||
WOLFSSL_NO_KCAPI_SHA224
|
||||
WOLFSSL_NO_KTRI_ORACLE_WARNING
|
||||
WOLFSSL_NO_OCSP_DATE_CHECK
|
||||
WOLFSSL_NO_OCSP_ISSUER_CHAIN_CHECK
|
||||
WOLFSSL_NO_OCSP_OPTIONAL_CERTS
|
||||
|
||||
+245
-6
@@ -1118,6 +1118,229 @@ int test_wc_PKCS7_EnvelopedData_KTRI_RSA_PSS(void)
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Bleichenbacher padding-oracle regression: wc_PKCS7_DecryptKtri must not
|
||||
* return a distinguishable error when RSA PKCS#1 v1.5 unwrap of the
|
||||
* encrypted CEK fails vs. when it succeeds with a wrong key. The
|
||||
* mitigation substitutes a deterministic pseudo-random CEK on RSA failure
|
||||
* so content decryption fails indistinguishably. This test corrupts the
|
||||
* encryptedKey in a valid EnvelopedData and asserts the error matches
|
||||
* content corruption rather than surfacing an RSA/recipient-level code.
|
||||
* Runs for AES-128 and AES-256 because the fake CEK is a fixed 32 bytes:
|
||||
* AES-128 (key size 16) exercises the path where the fake size differs
|
||||
* from the real CEK size.
|
||||
*/
|
||||
#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) && \
|
||||
!defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \
|
||||
defined(WOLFSSL_AES_256) && !defined(NO_HMAC) && \
|
||||
!defined(WOLFSSL_NO_MALLOC) && \
|
||||
(defined(USE_CERT_BUFFERS_2048) || defined(USE_CERT_BUFFERS_1024) || \
|
||||
!defined(NO_FILESYSTEM))
|
||||
static int pkcs7_ktri_bad_pad_case(int encryptOID, byte* rsaCert,
|
||||
word32 rsaCertSz, byte* rsaPrivKey,
|
||||
word32 rsaPrivKeySz, byte* encrypted,
|
||||
word32 encryptedCap, byte* decoded,
|
||||
word32 decodedCap)
|
||||
{
|
||||
EXPECT_DECLS;
|
||||
PKCS7* pkcs7 = NULL;
|
||||
byte data[] = "PKCS7 KTRI bad-RSA-padding regression payload.";
|
||||
int encryptedSz = 0;
|
||||
int badKeyRet = 0;
|
||||
int badContentRet = 0;
|
||||
byte savedKeyByte = 0;
|
||||
byte savedContentByte = 0;
|
||||
word32 i;
|
||||
word32 encryptedKeyOff = 0;
|
||||
static const byte rsaEncOid[] = {
|
||||
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D,
|
||||
0x01, 0x01, 0x01
|
||||
};
|
||||
|
||||
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
|
||||
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0);
|
||||
if (pkcs7 != NULL) {
|
||||
pkcs7->content = data;
|
||||
pkcs7->contentSz = (word32)sizeof(data);
|
||||
pkcs7->contentOID = DATA;
|
||||
pkcs7->encryptOID = encryptOID;
|
||||
}
|
||||
ExpectIntGT(encryptedSz = wc_PKCS7_EncodeEnvelopedData(pkcs7,
|
||||
encrypted, encryptedCap), 0);
|
||||
wc_PKCS7_Free(pkcs7);
|
||||
pkcs7 = NULL;
|
||||
|
||||
/* Locate the KTRI encryptedKey OCTET STRING. After the rsaEncryption
|
||||
* OID there are NULL algorithm parameters (05 00), then a 256-byte
|
||||
* OCTET STRING (tag 04, long-form length 82 01 00 for RSA-2048). */
|
||||
for (i = 0; (int)(i + sizeof(rsaEncOid)) < encryptedSz; i++) {
|
||||
if (XMEMCMP(&encrypted[i], rsaEncOid, sizeof(rsaEncOid)) == 0) {
|
||||
word32 p = i + (word32)sizeof(rsaEncOid);
|
||||
if (p + 2 < (word32)encryptedSz &&
|
||||
encrypted[p] == 0x05 && encrypted[p + 1] == 0x00) {
|
||||
p += 2;
|
||||
}
|
||||
if (p + 4 < (word32)encryptedSz && encrypted[p] == 0x04) {
|
||||
if (encrypted[p + 1] == 0x82) {
|
||||
encryptedKeyOff = p + 4;
|
||||
}
|
||||
else if (encrypted[p + 1] == 0x81) {
|
||||
encryptedKeyOff = p + 3;
|
||||
}
|
||||
else {
|
||||
encryptedKeyOff = p + 2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
ExpectIntGT(encryptedKeyOff, 0);
|
||||
ExpectIntLT(encryptedKeyOff + 32, (word32)encryptedSz);
|
||||
|
||||
/* Case 1: corrupt a byte inside the RSA ciphertext, decode, restore. */
|
||||
savedKeyByte = encrypted[encryptedKeyOff + 16];
|
||||
encrypted[encryptedKeyOff + 16] ^= 0xA5;
|
||||
|
||||
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
|
||||
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0);
|
||||
if (pkcs7 != NULL) {
|
||||
pkcs7->privateKey = rsaPrivKey;
|
||||
pkcs7->privateKeySz = rsaPrivKeySz;
|
||||
}
|
||||
badKeyRet = wc_PKCS7_DecodeEnvelopedData(pkcs7, encrypted,
|
||||
(word32)encryptedSz, decoded, decodedCap);
|
||||
wc_PKCS7_Free(pkcs7);
|
||||
pkcs7 = NULL;
|
||||
encrypted[encryptedKeyOff + 16] = savedKeyByte;
|
||||
|
||||
/* Case 2: corrupt a byte in the second-to-last AES ciphertext block.
|
||||
* In CBC mode this deterministically XOR-flips the corresponding byte
|
||||
* in the last plaintext block, invalidating the PKCS#7 padding
|
||||
* (original pad byte 0x01 becomes 0x01^0xA5 = 0xA4 > blockSz).
|
||||
* Corrupting the last ciphertext block directly would randomize the
|
||||
* entire last plaintext block, giving ~1/256 chance of accidentally
|
||||
* valid padding and intermittent test failures. */
|
||||
savedContentByte = encrypted[encryptedSz - 17];
|
||||
encrypted[encryptedSz - 17] ^= 0xA5;
|
||||
|
||||
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
|
||||
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, rsaCert, rsaCertSz), 0);
|
||||
if (pkcs7 != NULL) {
|
||||
pkcs7->privateKey = rsaPrivKey;
|
||||
pkcs7->privateKeySz = rsaPrivKeySz;
|
||||
}
|
||||
badContentRet = wc_PKCS7_DecodeEnvelopedData(pkcs7, encrypted,
|
||||
(word32)encryptedSz, decoded, decodedCap);
|
||||
wc_PKCS7_Free(pkcs7);
|
||||
pkcs7 = NULL;
|
||||
encrypted[encryptedSz - 17] = savedContentByte;
|
||||
|
||||
/* Case 2 must always fail: the CBC-chain corruption deterministically
|
||||
* invalidates the PKCS#7 padding. */
|
||||
ExpectIntLT(badContentRet, 0);
|
||||
/* Bad-key must NOT leak as an RSA- or recipient-level error. */
|
||||
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(PKCS7_RECIP_E));
|
||||
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(RSA_PAD_E));
|
||||
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(RSA_BUFFER_E));
|
||||
ExpectIntNE(badKeyRet, WC_NO_ERR_TRACE(BAD_PADDING_E));
|
||||
/* Case 1 (bad RSA key) decrypts content with a random fake CEK,
|
||||
* producing fully random plaintext. With ~1/256 probability the
|
||||
* PKCS#7 padding accidentally looks valid, causing a positive
|
||||
* garbage-length return instead of an error. This does not leak
|
||||
* RSA key information, so it is acceptable. When both cases do
|
||||
* fail, verify they fail at the same content-decryption layer. */
|
||||
if (badKeyRet < 0) {
|
||||
ExpectIntEQ(badKeyRet, badContentRet);
|
||||
}
|
||||
|
||||
return EXPECT_RESULT();
|
||||
}
|
||||
|
||||
int test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad(void)
|
||||
{
|
||||
EXPECT_DECLS;
|
||||
byte encrypted[FOURK_BUF];
|
||||
byte decoded[FOURK_BUF];
|
||||
byte* rsaCert = NULL;
|
||||
byte* rsaPrivKey = NULL;
|
||||
word32 rsaCertSz = 0;
|
||||
word32 rsaPrivKeySz = 0;
|
||||
#if !defined(USE_CERT_BUFFERS_1024) && !defined(USE_CERT_BUFFERS_2048) && \
|
||||
!defined(NO_FILESYSTEM)
|
||||
XFILE f = XBADFILE;
|
||||
#endif
|
||||
|
||||
/* Load RSA cert and key */
|
||||
#if defined(USE_CERT_BUFFERS_1024)
|
||||
rsaCertSz = (word32)sizeof_client_cert_der_1024;
|
||||
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
|
||||
DYNAMIC_TYPE_TMP_BUFFER));
|
||||
if (rsaCert != NULL)
|
||||
XMEMCPY(rsaCert, client_cert_der_1024, rsaCertSz);
|
||||
rsaPrivKeySz = (word32)sizeof_client_key_der_1024;
|
||||
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
|
||||
DYNAMIC_TYPE_TMP_BUFFER));
|
||||
if (rsaPrivKey != NULL)
|
||||
XMEMCPY(rsaPrivKey, client_key_der_1024, rsaPrivKeySz);
|
||||
#elif defined(USE_CERT_BUFFERS_2048)
|
||||
rsaCertSz = (word32)sizeof_client_cert_der_2048;
|
||||
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
|
||||
DYNAMIC_TYPE_TMP_BUFFER));
|
||||
if (rsaCert != NULL)
|
||||
XMEMCPY(rsaCert, client_cert_der_2048, rsaCertSz);
|
||||
rsaPrivKeySz = (word32)sizeof_client_key_der_2048;
|
||||
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
|
||||
DYNAMIC_TYPE_TMP_BUFFER));
|
||||
if (rsaPrivKey != NULL)
|
||||
XMEMCPY(rsaPrivKey, client_key_der_2048, rsaPrivKeySz);
|
||||
#elif !defined(NO_FILESYSTEM)
|
||||
rsaCertSz = FOURK_BUF;
|
||||
ExpectNotNull(rsaCert = (byte*)XMALLOC(rsaCertSz, HEAP_HINT,
|
||||
DYNAMIC_TYPE_TMP_BUFFER));
|
||||
ExpectTrue((f = XFOPEN("./certs/client-cert.der", "rb")) != XBADFILE);
|
||||
ExpectTrue((rsaCertSz = (word32)XFREAD(rsaCert, 1, rsaCertSz, f)) > 0);
|
||||
if (f != XBADFILE) {
|
||||
XFCLOSE(f);
|
||||
f = XBADFILE;
|
||||
}
|
||||
rsaPrivKeySz = FOURK_BUF;
|
||||
ExpectNotNull(rsaPrivKey = (byte*)XMALLOC(rsaPrivKeySz, HEAP_HINT,
|
||||
DYNAMIC_TYPE_TMP_BUFFER));
|
||||
ExpectTrue((f = XFOPEN("./certs/client-key.der", "rb")) != XBADFILE);
|
||||
ExpectTrue((rsaPrivKeySz = (word32)XFREAD(rsaPrivKey, 1,
|
||||
rsaPrivKeySz, f)) > 0);
|
||||
if (f != XBADFILE)
|
||||
XFCLOSE(f);
|
||||
#endif
|
||||
|
||||
if (rsaCert == NULL || rsaPrivKey == NULL) {
|
||||
XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
return TEST_SKIPPED;
|
||||
}
|
||||
|
||||
/* AES-128: 32-byte fake CEK larger than real CEK size (16 bytes). */
|
||||
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES128CBCb, rsaCert, rsaCertSz,
|
||||
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
|
||||
decoded, sizeof(decoded)), TEST_SUCCESS);
|
||||
#ifdef WOLFSSL_AES_192
|
||||
/* AES-192: fake CEK (32) vs real CEK (24) - another size mismatch. */
|
||||
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES192CBCb, rsaCert, rsaCertSz,
|
||||
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
|
||||
decoded, sizeof(decoded)), TEST_SUCCESS);
|
||||
#endif
|
||||
/* AES-256: fake CEK size matches real CEK size (32 bytes). */
|
||||
ExpectIntEQ(pkcs7_ktri_bad_pad_case(AES256CBCb, rsaCert, rsaCertSz,
|
||||
rsaPrivKey, rsaPrivKeySz, encrypted, sizeof(encrypted),
|
||||
decoded, sizeof(decoded)), TEST_SUCCESS);
|
||||
|
||||
XFREE(rsaCert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
XFREE(rsaPrivKey, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
return EXPECT_RESULT();
|
||||
} /* END test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad */
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Testing wc_PKCS7_EncodeSignedData_ex() and wc_PKCS7_VerifySignedData_ex()
|
||||
*/
|
||||
@@ -2503,6 +2726,8 @@ int test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients(void)
|
||||
bytes */
|
||||
size_t testDerBufferSz = 0;
|
||||
byte decodedData[8192];
|
||||
byte serverDecodedData[8192];
|
||||
int serverRet = 0;
|
||||
|
||||
ExpectTrue((f = XFOPEN(testFile, "rb")) != XBADFILE);
|
||||
if (f != XBADFILE) {
|
||||
@@ -2522,12 +2747,13 @@ int test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients(void)
|
||||
ExpectIntEQ(wc_PKCS7_SetKey(pkcs7, (byte*)server_key_der_2048,
|
||||
sizeof_server_key_der_2048), 0);
|
||||
|
||||
ret = wc_PKCS7_DecodeEnvelopedData(pkcs7, testDerBuffer,
|
||||
(word32)testDerBufferSz, decodedData, sizeof(decodedData));
|
||||
serverRet = wc_PKCS7_DecodeEnvelopedData(pkcs7, testDerBuffer,
|
||||
(word32)testDerBufferSz, serverDecodedData,
|
||||
sizeof(serverDecodedData));
|
||||
#if defined(NO_AES) || defined(NO_AES_256)
|
||||
ExpectIntEQ(ret, ALGO_ID_E);
|
||||
ExpectIntEQ(serverRet, ALGO_ID_E);
|
||||
#else
|
||||
ExpectIntGT(ret, 0);
|
||||
ExpectIntGT(serverRet, 0);
|
||||
#endif
|
||||
wc_PKCS7_Free(pkcs7);
|
||||
pkcs7 = NULL;
|
||||
@@ -2553,7 +2779,14 @@ int test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients(void)
|
||||
pkcs7 = NULL;
|
||||
}
|
||||
|
||||
/* test with ca cert recipient (which should fail) */
|
||||
/* Test with ca cert recipient. The ca cert is not a listed recipient,
|
||||
* so RSA unwrap fails. The Bleichenbacher mitigation substitutes a
|
||||
* pseudo-random fake CEK on unwrap failure, so the call normally
|
||||
* returns a negative error when content decryption rejects the
|
||||
* resulting garbage padding - but around 1/256 of the time the random
|
||||
* CEK yields plaintext with accidentally-valid PKCS#7 padding and the
|
||||
* call returns a non-negative "decrypted" size. That case must not
|
||||
* produce the real plaintext. */
|
||||
ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId));
|
||||
if (pkcs7) {
|
||||
ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, (byte*)ca_cert_der_2048,
|
||||
@@ -2562,9 +2795,15 @@ int test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients(void)
|
||||
ExpectIntEQ(wc_PKCS7_SetKey(pkcs7, (byte*)ca_key_der_2048,
|
||||
sizeof_ca_key_der_2048), 0);
|
||||
|
||||
XMEMSET(decodedData, 0, sizeof(decodedData));
|
||||
ret = wc_PKCS7_DecodeEnvelopedData(pkcs7, testDerBuffer,
|
||||
(word32)testDerBufferSz, decodedData, sizeof(decodedData));
|
||||
ExpectIntLT(ret, 0);
|
||||
#if defined(NO_AES) || defined(NO_AES_256)
|
||||
ExpectIntEQ(ret, ALGO_ID_E);
|
||||
#else
|
||||
ExpectTrue(ret < 0 || ret != serverRet ||
|
||||
XMEMCMP(decodedData, serverDecodedData, (size_t)ret) != 0);
|
||||
#endif
|
||||
wc_PKCS7_Free(pkcs7);
|
||||
pkcs7 = NULL;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,14 @@ int test_wc_PKCS7_EncodeSignedData_RSA_PSS(void);
|
||||
!defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_256)
|
||||
int test_wc_PKCS7_EnvelopedData_KTRI_RSA_PSS(void);
|
||||
#endif
|
||||
#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) && \
|
||||
!defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \
|
||||
defined(WOLFSSL_AES_256) && !defined(NO_HMAC) && \
|
||||
!defined(WOLFSSL_NO_MALLOC) && \
|
||||
(defined(USE_CERT_BUFFERS_2048) || defined(USE_CERT_BUFFERS_1024) || \
|
||||
!defined(NO_FILESYSTEM))
|
||||
int test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad(void);
|
||||
#endif
|
||||
int test_wc_PKCS7_EncodeSignedData_ex(void);
|
||||
int test_wc_PKCS7_VerifySignedData_RSA(void);
|
||||
int test_wc_PKCS7_VerifySignedData_ECC(void);
|
||||
@@ -82,6 +90,18 @@ int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
|
||||
#define TEST_PKCS7_RSA_PSS_ED_DECL
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) && \
|
||||
!defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) && \
|
||||
defined(WOLFSSL_AES_256) && !defined(NO_HMAC) && \
|
||||
!defined(WOLFSSL_NO_MALLOC) && \
|
||||
(defined(USE_CERT_BUFFERS_2048) || defined(USE_CERT_BUFFERS_1024) || \
|
||||
!defined(NO_FILESYSTEM))
|
||||
#define TEST_PKCS7_KTRI_BADRSAPAD_DECL \
|
||||
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_EnvelopedData_KTRI_BadRsaPad),
|
||||
#else
|
||||
#define TEST_PKCS7_KTRI_BADRSAPAD_DECL
|
||||
#endif
|
||||
|
||||
#define TEST_PKCS7_SIGNED_DATA_DECLS \
|
||||
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_InitWithCert), \
|
||||
TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_EncodeData), \
|
||||
@@ -100,6 +120,7 @@ int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void);
|
||||
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_stream), \
|
||||
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_EncodeDecodeEnvelopedData), \
|
||||
TEST_PKCS7_RSA_PSS_ED_DECL \
|
||||
TEST_PKCS7_KTRI_BADRSAPAD_DECL \
|
||||
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_SetAESKeyWrapUnwrapCb), \
|
||||
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_GetEnvelopedDataKariRid), \
|
||||
TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_EncodeEncryptedData), \
|
||||
|
||||
+243
-7
@@ -44,6 +44,9 @@
|
||||
|
||||
#include <wolfssl/wolfcrypt/pkcs7.h>
|
||||
#include <wolfssl/wolfcrypt/hash.h>
|
||||
#ifndef NO_HMAC
|
||||
#include <wolfssl/wolfcrypt/hmac.h>
|
||||
#endif
|
||||
#ifndef NO_RSA
|
||||
#include <wolfssl/wolfcrypt/rsa.h>
|
||||
#endif
|
||||
@@ -207,6 +210,8 @@ static void wc_PKCS7_ResetStream(wc_PKCS7* pkcs7)
|
||||
#endif
|
||||
|
||||
/* free any buffers that may be allocated */
|
||||
if (pkcs7->stream->aad != NULL && pkcs7->stream->aadSz > 0)
|
||||
ForceZero(pkcs7->stream->aad, pkcs7->stream->aadSz);
|
||||
XFREE(pkcs7->stream->aad, pkcs7->heap, DYNAMIC_TYPE_PKCS7);
|
||||
XFREE(pkcs7->stream->tag, pkcs7->heap, DYNAMIC_TYPE_PKCS7);
|
||||
XFREE(pkcs7->stream->nonce, pkcs7->heap, DYNAMIC_TYPE_PKCS7);
|
||||
@@ -7741,6 +7746,9 @@ static int wc_PKCS7_KariGenerateKEK(WC_PKCS7_KARI* kari, WC_RNG* rng,
|
||||
secret = (byte*)XMALLOC(secretSz, kari->heap, DYNAMIC_TYPE_PKCS7);
|
||||
if (secret == NULL)
|
||||
return MEMORY_E;
|
||||
#ifdef WOLFSSL_CHECK_MEM_ZERO
|
||||
wc_MemZero_Add("wc_PKCS7_KariGenerateKEK secret", secret, secretSz);
|
||||
#endif
|
||||
|
||||
#if defined(ECC_TIMING_RESISTANT) && (!defined(HAVE_FIPS) || \
|
||||
(!defined(HAVE_FIPS_VERSION) || (HAVE_FIPS_VERSION != 2))) && \
|
||||
@@ -10608,6 +10616,81 @@ int wc_PKCS7_EncodeEnvelopedData(wc_PKCS7* pkcs7, byte* output, word32 outputSz)
|
||||
}
|
||||
|
||||
#ifndef NO_RSA
|
||||
#if !defined(NO_HMAC) && !defined(NO_SHA256)
|
||||
/* Bleichenbacher padding-oracle mitigation for PKCS#7/CMS KTRI: produce a
|
||||
* WC_SHA256_DIGEST_SIZE-byte pseudo-random CEK derived from a fresh
|
||||
* random seed and the encrypted-key ciphertext. The output is random per
|
||||
* call (driven by the RNG seed); deriving via HMAC of the ciphertext
|
||||
* simply gives the same value within one call regardless of where it is
|
||||
* referenced. Called unconditionally so the work is in the timing path
|
||||
* regardless of RSA padding validity. */
|
||||
static int wc_PKCS7_KtriFakeCEK(wc_PKCS7* pkcs7, const byte* encryptedKey,
|
||||
word32 encryptedKeySz, byte* out)
|
||||
{
|
||||
int ret;
|
||||
byte seed[WC_SHA256_DIGEST_SIZE];
|
||||
WC_RNG* rng = NULL;
|
||||
int ownRng = 0;
|
||||
WC_DECLARE_VAR(localRng, WC_RNG, 1, pkcs7->heap);
|
||||
WC_DECLARE_VAR(hmac, Hmac, 1, pkcs7->heap);
|
||||
|
||||
if (pkcs7 == NULL || encryptedKey == NULL || out == NULL) {
|
||||
return BAD_FUNC_ARG;
|
||||
}
|
||||
|
||||
WC_ALLOC_VAR_EX(hmac, Hmac, 1, pkcs7->heap, DYNAMIC_TYPE_HMAC,
|
||||
return MEMORY_E);
|
||||
|
||||
/* Prefer a caller-provided RNG to avoid paying a DRBG init/reseed cost
|
||||
* on every decrypt (and to keep the timing envelope flatter on FIPS /
|
||||
* HW-RNG builds). Fall back to a one-shot RNG when pkcs7->rng is not
|
||||
* set. */
|
||||
if (pkcs7->rng != NULL) {
|
||||
rng = pkcs7->rng;
|
||||
}
|
||||
else {
|
||||
WC_ALLOC_VAR_EX(localRng, WC_RNG, 1, pkcs7->heap, DYNAMIC_TYPE_RNG,
|
||||
WC_FREE_VAR_EX(hmac, pkcs7->heap, DYNAMIC_TYPE_HMAC);
|
||||
return MEMORY_E);
|
||||
ret = wc_InitRng_ex(localRng, pkcs7->heap, pkcs7->devId);
|
||||
if (ret != 0) {
|
||||
WC_FREE_VAR_EX(localRng, pkcs7->heap, DYNAMIC_TYPE_RNG);
|
||||
WC_FREE_VAR_EX(hmac, pkcs7->heap, DYNAMIC_TYPE_HMAC);
|
||||
return ret;
|
||||
}
|
||||
rng = localRng;
|
||||
ownRng = 1;
|
||||
}
|
||||
|
||||
ret = wc_RNG_GenerateBlock(rng, seed, (word32)sizeof(seed));
|
||||
|
||||
if (ownRng) {
|
||||
wc_FreeRng(localRng);
|
||||
WC_FREE_VAR_EX(localRng, pkcs7->heap, DYNAMIC_TYPE_RNG);
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
WC_FREE_VAR_EX(hmac, pkcs7->heap, DYNAMIC_TYPE_HMAC);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = wc_HmacInit(hmac, pkcs7->heap, pkcs7->devId);
|
||||
if (ret == 0) {
|
||||
ret = wc_HmacSetKey(hmac, WC_SHA256, seed, (word32)sizeof(seed));
|
||||
if (ret == 0) {
|
||||
ret = wc_HmacUpdate(hmac, encryptedKey, encryptedKeySz);
|
||||
}
|
||||
if (ret == 0) {
|
||||
ret = wc_HmacFinal(hmac, out);
|
||||
}
|
||||
wc_HmacFree(hmac);
|
||||
}
|
||||
ForceZero(seed, sizeof(seed));
|
||||
WC_FREE_VAR_EX(hmac, pkcs7->heap, DYNAMIC_TYPE_HMAC);
|
||||
return ret;
|
||||
}
|
||||
#endif /* !NO_HMAC && !NO_SHA256 */
|
||||
|
||||
/* decode KeyTransRecipientInfo (ktri), return 0 on success, <0 on error */
|
||||
static int wc_PKCS7_DecryptKtri(wc_PKCS7* pkcs7, byte* in, word32 inSz,
|
||||
word32* idx, byte* decryptedKey,
|
||||
@@ -10967,25 +11050,146 @@ static int wc_PKCS7_DecryptKtri(wc_PKCS7* pkcs7, byte* in, word32 inSz,
|
||||
}
|
||||
wc_FreeRsaKey(privKey);
|
||||
|
||||
#if !defined(NO_HMAC) && !defined(NO_SHA256)
|
||||
{
|
||||
/* Bleichenbacher padding-oracle mitigation: always compute
|
||||
* a pseudo-random fallback CEK so timing and error
|
||||
* behaviour do not depend on RSA padding validity. On
|
||||
* unwrap failure we substitute the fallback and let
|
||||
* content decryption fail indistinguishably from "unwrap
|
||||
* succeeded but CEK is wrong". */
|
||||
byte fakeKey[WC_SHA256_DIGEST_SIZE];
|
||||
int fakeRet = wc_PKCS7_KtriFakeCEK(pkcs7, encryptedKey,
|
||||
(word32)encryptedKeySz,
|
||||
fakeKey);
|
||||
|
||||
if (fakeRet != 0) {
|
||||
/* Fallback generation failed (e.g. RNG/HMAC error).
|
||||
* Return the fallback-generation status, which does
|
||||
* not depend on RSA padding validity, rather than the
|
||||
* RSA status which would re-open the oracle. */
|
||||
ForceZero(fakeKey, sizeof(fakeKey));
|
||||
/* In the non-OAEP path RSA is decrypted in-place via
|
||||
* wc_RsaPrivateDecryptInline, so encryptedKey holds
|
||||
* the (possibly valid) plaintext CEK. Zero it before
|
||||
* free. */
|
||||
ForceZero(encryptedKey, (word32)encryptedKeySz);
|
||||
XFREE(encryptedKey, pkcs7->heap, DYNAMIC_TYPE_WOLF_BIGINT);
|
||||
WC_FREE_VAR_EX(privKey, pkcs7->heap,
|
||||
DYNAMIC_TYPE_TMP_BUFFER);
|
||||
#ifndef WC_NO_RSA_OAEP
|
||||
if (encOID == RSAESOAEPk) {
|
||||
if (outKey != NULL) {
|
||||
ForceZero(outKey, outKeySz);
|
||||
XFREE(outKey, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return fakeRet;
|
||||
}
|
||||
|
||||
/* Constant-time select between fake and real CEK. On RSA
|
||||
* failure outKey may be NULL or keySz may be <= 0; in
|
||||
* both cases the mask selects fakeKey for every byte.
|
||||
*
|
||||
* To avoid data-dependent branches that leak realLen,
|
||||
* copy the real key into a fixed-size zero-padded buffer
|
||||
* first, then select byte-by-byte in constant time. */
|
||||
{
|
||||
word32 i;
|
||||
byte useFake;
|
||||
int realLen = keySz;
|
||||
byte realPad[WC_SHA256_DIGEST_SIZE];
|
||||
|
||||
XMEMSET(realPad, 0, sizeof(realPad));
|
||||
/* Constant-time copy: avoid data-dependent branches
|
||||
* that could leak whether RSA padding was valid.
|
||||
* When outKey is NULL (inline RSA failure), use
|
||||
* encryptedKey as a safe readable source; the mask
|
||||
* will zero out all bytes anyway. Both encryptedKey
|
||||
* and outKey (when non-NULL) are at least
|
||||
* sizeof(realPad) bytes for any RSA key size.
|
||||
*
|
||||
* Use constant-time pointer selection to avoid
|
||||
* branching on outKey nullity, which would leak
|
||||
* whether RSA PKCS#1 v1.5 padding was valid. */
|
||||
{
|
||||
byte haveSrc = ctMaskGTE(realLen, 1);
|
||||
const byte* srcTbl[2];
|
||||
const byte* src;
|
||||
word32 j = 0;
|
||||
word32 safeJ = 0;
|
||||
|
||||
/* Select source without integer pointer synthesis.
|
||||
* Some safety-oriented compilers (e.g. Fil-C) treat
|
||||
* int-to-pointer reconstruction as a null-object
|
||||
* pointer on dereference. */
|
||||
srcTbl[0] = encryptedKey;
|
||||
srcTbl[1] = outKey;
|
||||
src = srcTbl[haveSrc & 1];
|
||||
|
||||
/* safeJ is clamped to max(0, realLen-1): it
|
||||
* only advances while the next index would
|
||||
* still be inside realLen, so src[safeJ] is
|
||||
* always in bounds. Bytes at j >= realLen are
|
||||
* masked to zero by inBounds anyway. */
|
||||
for (j = 0; j < (word32)sizeof(realPad); j++) {
|
||||
byte inBounds = ctMaskLT((int)j, realLen);
|
||||
byte advance = ctMaskLT((int)(safeJ + 1),
|
||||
realLen);
|
||||
realPad[j] = src[safeJ] & haveSrc & inBounds;
|
||||
safeJ += (word32)(advance & 1);
|
||||
}
|
||||
}
|
||||
useFake = ctMaskLT(realLen, 1); /* 0xFF if realLen<=0 */
|
||||
|
||||
for (i = 0; i < (word32)sizeof(fakeKey); i++) {
|
||||
decryptedKey[i] = ctMaskSel(useFake, fakeKey[i],
|
||||
realPad[i]);
|
||||
}
|
||||
/* Report the real key size on success; on RSA
|
||||
* failure (realLen <= 0) report sizeof(fakeKey).
|
||||
* Constant-time select avoids branching on RSA
|
||||
* padding validity. */
|
||||
*decryptedKeySz = (word32)ctMaskSelInt(useFake,
|
||||
(int)sizeof(fakeKey), realLen);
|
||||
ForceZero(realPad, sizeof(realPad));
|
||||
}
|
||||
ForceZero(fakeKey, sizeof(fakeKey));
|
||||
/* In the non-OAEP path RSA is decrypted in-place via
|
||||
* wc_RsaPrivateDecryptInline, so encryptedKey holds the
|
||||
* plaintext CEK after the unwrap. Zero it before free. */
|
||||
ForceZero(encryptedKey, (word32)encryptedKeySz);
|
||||
}
|
||||
#else /* NO_HMAC || NO_SHA256: mitigation unavailable */
|
||||
#if !defined(WOLFSSL_NO_KTRI_ORACLE_WARNING)
|
||||
#warning "PKCS7 KTRI Bleichenbacher mitigation requires HMAC " \
|
||||
"and SHA256; build without them leaves the RSA unwrap " \
|
||||
"error path observable to callers. " \
|
||||
"Define WOLFSSL_NO_KTRI_ORACLE_WARNING to silence."
|
||||
#endif
|
||||
|
||||
if (keySz <= 0 || outKey == NULL) {
|
||||
ForceZero(encryptedKey, (word32)encryptedKeySz);
|
||||
XFREE(encryptedKey, pkcs7->heap, DYNAMIC_TYPE_WOLF_BIGINT);
|
||||
WC_FREE_VAR_EX(privKey, pkcs7->heap,
|
||||
DYNAMIC_TYPE_TMP_BUFFER);
|
||||
#ifndef WC_NO_RSA_OAEP
|
||||
#ifndef WC_NO_RSA_OAEP
|
||||
if (encOID == RSAESOAEPk) {
|
||||
if (outKey) {
|
||||
ForceZero(outKey, outKeySz);
|
||||
XFREE(outKey, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return keySz;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
*decryptedKeySz = (word32)keySz;
|
||||
XMEMCPY(decryptedKey, outKey, (word32)keySz);
|
||||
ForceZero(encryptedKey, (word32)encryptedKeySz);
|
||||
}
|
||||
#endif /* !NO_HMAC && !NO_SHA256 */
|
||||
|
||||
XFREE(encryptedKey, pkcs7->heap, DYNAMIC_TYPE_WOLF_BIGINT);
|
||||
WC_FREE_VAR_EX(privKey, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
@@ -12888,6 +13092,11 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in,
|
||||
DYNAMIC_TYPE_PKCS7);
|
||||
if (decryptedKey == NULL)
|
||||
return MEMORY_E;
|
||||
XMEMSET(decryptedKey, 0, MAX_ENCRYPTED_KEY_SZ);
|
||||
#ifdef WOLFSSL_CHECK_MEM_ZERO
|
||||
wc_MemZero_Add("wc_PKCS7 decryptedKey", decryptedKey,
|
||||
MAX_ENCRYPTED_KEY_SZ);
|
||||
#endif
|
||||
wc_PKCS7_ChangeState(pkcs7, WC_PKCS7_ENV_2);
|
||||
tmpIdx = idx;
|
||||
recipientSetSz = (word32)ret;
|
||||
@@ -13130,10 +13339,18 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in,
|
||||
wc_PKCS7_StreamStoreVar(pkcs7, encOID, expBlockSz, explicitOctet);
|
||||
|
||||
if (explicitOctet) {
|
||||
/* initialize decryption state in preparation */
|
||||
/* initialize decryption state in preparation. Use
|
||||
* contentSz (blockKeySz from the content algorithm) as
|
||||
* the AES key size rather than aadSz (the unwrapped CEK
|
||||
* length): the two are equal for well-formed messages,
|
||||
* but using blockKeySz avoids BAD_FUNC_ARG on crafted
|
||||
* messages where the CEK length does not match the
|
||||
* content cipher, which would otherwise be a
|
||||
* distinguishable error. */
|
||||
if (pkcs7->decryptionCb == NULL) {
|
||||
ret = wc_PKCS7_DecryptContentInit(pkcs7, encOID,
|
||||
pkcs7->stream->aad, pkcs7->stream->aadSz,
|
||||
pkcs7->stream->aad,
|
||||
(word32)pkcs7->stream->contentSz,
|
||||
pkcs7->stream->tmpIv, expBlockSz,
|
||||
pkcs7->devId, pkcs7->heap);
|
||||
if (ret != 0)
|
||||
@@ -13409,8 +13626,13 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in,
|
||||
|
||||
ret = (int)pkcs7->totalEncryptedContentSz - padLen;
|
||||
#ifndef NO_PKCS7_STREAM
|
||||
pkcs7->stream->aad = NULL;
|
||||
pkcs7->stream->aadSz = 0;
|
||||
/* decryptedKey (just freed) is the same buffer stream->aad
|
||||
* aliases. Null the stream handle so ResetStream doesn't
|
||||
* double-free it. */
|
||||
if (pkcs7->stream != NULL) {
|
||||
pkcs7->stream->aad = NULL;
|
||||
pkcs7->stream->aadSz = 0;
|
||||
}
|
||||
wc_PKCS7_ResetStream(pkcs7);
|
||||
#endif
|
||||
wc_PKCS7_ChangeState(pkcs7, WC_PKCS7_START);
|
||||
@@ -13423,6 +13645,16 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in,
|
||||
|
||||
#ifndef NO_PKCS7_STREAM
|
||||
if (ret < 0 && ret != WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E)) {
|
||||
/* stream->aad aliases the MAX_ENCRYPTED_KEY_SZ decryptedKey
|
||||
* buffer in this flow. ResetStream only zeros aadSz bytes, so
|
||||
* explicitly zero and release the full buffer here to satisfy
|
||||
* WOLFSSL_CHECK_MEM_ZERO and avoid leaking key material. */
|
||||
if (pkcs7->stream != NULL && pkcs7->stream->aad != NULL) {
|
||||
ForceZero(pkcs7->stream->aad, MAX_ENCRYPTED_KEY_SZ);
|
||||
XFREE(pkcs7->stream->aad, pkcs7->heap, DYNAMIC_TYPE_PKCS7);
|
||||
pkcs7->stream->aad = NULL;
|
||||
pkcs7->stream->aadSz = 0;
|
||||
}
|
||||
wc_PKCS7_ResetStream(pkcs7);
|
||||
wc_PKCS7_ChangeState(pkcs7, WC_PKCS7_START);
|
||||
if (pkcs7->cachedEncryptedContent != NULL) {
|
||||
@@ -14196,6 +14428,10 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in,
|
||||
}
|
||||
else {
|
||||
XMEMSET(decryptedKey, 0, MAX_ENCRYPTED_KEY_SZ);
|
||||
#ifdef WOLFSSL_CHECK_MEM_ZERO
|
||||
wc_MemZero_Add("wc_PKCS7 decryptedKey", decryptedKey,
|
||||
MAX_ENCRYPTED_KEY_SZ);
|
||||
#endif
|
||||
}
|
||||
#ifndef NO_PKCS7_STREAM
|
||||
pkcs7->stream->key = decryptedKey;
|
||||
|
||||
Reference in New Issue
Block a user