openhitls/0011-fix-the-possible-CBC-Padding-Oracle-vulnerability.patch

375 lines
14 KiB
Diff
Raw Permalink Normal View History

From 24bd3dc68ea74c196b308e5407e662e70e5e9183 Mon Sep 17 00:00:00 2001
From: Liu-Ermeng <liuermeng2@huawei.com>
Date: Mon, 9 Dec 2024 22:28:12 -0800
Subject: [PATCH] fix the possible CBC Padding Oracle vulnerability
Let the padding check and the cacluation to be constant-time, when
using CBC ciphersuite and using MtE(Mac-then-Encrypted) mode, to
resist possible time side channel attacks.
references:
https://blog.cloudflare.com/padding-oracles-and-the-decline-of-cbc-mode-ciphersuites/
https://www.isg.rhul.ac.uk/tls/TLStiming.pdf
https://www.imperialviolet.org/2013/02/04/luckythirteen.html
Cherry-picked from: https://gitcode.com/openHiTLS/openhitls/merge_requests/95
---
bsl/include/bsl_bytes.h | 27 +++-
tls/record/src/rec_crypto_cbc.c | 227 ++++++++++++++++++++++++++------
2 files changed, 206 insertions(+), 48 deletions(-)
diff --git a/bsl/include/bsl_bytes.h b/bsl/include/bsl_bytes.h
index 378c84e..aea84dd 100644
--- a/bsl/include/bsl_bytes.h
+++ b/bsl/include/bsl_bytes.h
@@ -201,20 +201,20 @@ static inline void BSL_Uint64ToByte(uint64_t num, uint8_t *data)
}
// if a's MSB is 0, output 0
-// else if a' MSB is 1 output x00ffffffff
+// else if a' MSB is 1 output 0xffffffff
static inline uint32_t Uint32ConstTimeMsb(uint32_t a)
{
// 31 == (4 * 8 - 1)
return 0u - (a >> 31);
}
-// if a is 0, output x00ffffffff, else output 0
+// if a is 0, output 0xffffffff, else output 0
static inline uint32_t Uint32ConstTimeIsZero(uint32_t a)
{
return Uint32ConstTimeMsb(~a & (a - 1));
}
-// if a == b, output x00ffffffff, else output 0
+// if a == b, output 0xffffffff, else output 0
static inline uint32_t Uint32ConstTimeEqual(uint32_t a, uint32_t b)
{
return Uint32ConstTimeIsZero(a ^ b);
@@ -232,24 +232,39 @@ static inline uint32_t Uint8ConstTimeSelect(uint32_t mask, uint8_t a, uint8_t b)
return (((mask) & a) | ((~mask) & b)) & 0xff;
}
-// if a < b, output x00ffffffff, else output 0
+// if a < b, output 0xffffffff, else output 0
static inline uint32_t Uint32ConstTimeLt(uint32_t a, uint32_t b)
{
return Uint32ConstTimeMsb(a ^ ((a ^ b) | ((a - b) ^ a)));
}
-// if a >= b, output x00ffffffff, else output 0
+// if a >= b, output 0xffffffff, else output 0
static inline uint32_t Uint32ConstTimeGe(uint32_t a, uint32_t b)
{
return ~Uint32ConstTimeLt(a, b);
}
-// if a > b, output x00ffffffff, else output 0
+// if a > b, output 0xffffffff, else output 0
static inline uint32_t Uint32ConstTimeGt(uint32_t a, uint32_t b)
{
return Uint32ConstTimeLt(b, a);
}
+static inline uint32_t Uint32ConstTimeLe(uint32_t a, uint32_t b)
+{
+ return Uint32ConstTimeGe(b, a);
+}
+
+// if a == b, return 0xffffffff, else return 0
+static inline uint32_t ConstTimeMemcmp(uint8_t *a, uint8_t *b, uint32_t l)
+{
+ uint8_t r = 0;
+ for (uint32_t i = 0; i < l; i++) {
+ r |= a[i] ^ b[i];
+ }
+ return Uint32ConstTimeIsZero(r);
+}
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/tls/record/src/rec_crypto_cbc.c b/tls/record/src/rec_crypto_cbc.c
index 18931b3..891bbd9 100644
--- a/tls/record/src/rec_crypto_cbc.c
+++ b/tls/record/src/rec_crypto_cbc.c
@@ -27,6 +27,7 @@
#include "rec_crypto_cbc.h"
#define CBC_PADDING_LEN_TAG_SIZE 1u
+#define HMAC_MAX_BLEN 144
uint8_t RecConnGetCbcPaddingLen(uint8_t blockLen, uint32_t plaintextLen)
{
@@ -102,53 +103,200 @@ static int32_t RecConnCbcCheckCryptMsg(TLS_Ctx *ctx, const RecConnState *state,
return HITLS_SUCCESS;
}
-static int32_t RecConnCbcCheckDecryptPadding(TLS_Ctx *ctx, const REC_TextInput *cryptMsg, uint8_t *data,
- uint32_t plaintextLen, uint32_t offset)
+static int32_t RecConnCbcDecCheckPaddingEtM(TLS_Ctx *ctx, const REC_TextInput *cryptMsg, uint8_t *plain,
+ uint32_t plainLen, uint32_t offset)
{
const RecConnState *state = ctx->recCtx->readStates.currentState;
- uint8_t mac[MAX_DIGEST_SIZE] = {0};
- uint32_t macLen = MAX_DIGEST_SIZE;
- uint8_t paddingLen = data[plaintextLen - 1];
+ uint8_t padLen = plain[plainLen - 1];
-#ifdef HITLS_TLS_FEATURE_ETM
- if (cryptMsg->isEncryptThenMac && (plaintextLen < paddingLen + CBC_PADDING_LEN_TAG_SIZE)) {
+ if (cryptMsg->isEncryptThenMac && (plainLen < padLen + CBC_PADDING_LEN_TAG_SIZE)) {
BSL_LOG_BINLOG_FIXLEN(BINLOG_ID15399, BSL_LOG_LEVEL_ERR, BSL_LOG_BINLOG_TYPE_RUN,
"record cbc mode decrypt error: ciphertext len = %u, plaintext len = %u, mac len = %u, padding len = %u.",
- cryptMsg->textLen - offset - state->suiteInfo->macLen, plaintextLen, state->suiteInfo->macLen, paddingLen);
- return RecordSendAlertMsg(ctx, ALERT_LEVEL_FATAL, ALERT_BAD_RECORD_MAC);
- }
-#endif
- if ((plaintextLen < state->suiteInfo->macLen + paddingLen + CBC_PADDING_LEN_TAG_SIZE)
-#ifdef HITLS_TLS_FEATURE_ETM
- && !cryptMsg->isEncryptThenMac
-#endif
- ) {
- BSL_LOG_BINLOG_FIXLEN(BINLOG_ID15928, BSL_LOG_LEVEL_ERR, BSL_LOG_BINLOG_TYPE_RUN,
- "record cbc mode decrypt error: ciphertext len = %u, plaintext len = %u, mac len = %u, padding len = %u.",
- cryptMsg->textLen - offset, plaintextLen, state->suiteInfo->macLen, paddingLen);
- /* Anti-side channel attack: Calculate the MAC address even if the padding is incorrect */
- (void)RecConnGenerateMac(state->suiteInfo, cryptMsg, mac, &macLen);
+ cryptMsg->textLen - offset - state->suiteInfo->macLen, plainLen, state->suiteInfo->macLen, padLen);
return RecordSendAlertMsg(ctx, ALERT_LEVEL_FATAL, ALERT_BAD_RECORD_MAC);
}
- for (uint32_t i = 1; i <= paddingLen; i++) {
- if (data[plaintextLen - 1 - i] != paddingLen) {
+ for (uint32_t i = 1; i <= padLen; i++) {
+ if (plain[plainLen - 1 - i] != padLen) {
BSL_LOG_BINLOG_FIXLEN(BINLOG_ID15400, BSL_LOG_LEVEL_ERR, BSL_LOG_BINLOG_TYPE_RUN,
"record cbc mode decrypt error: padding len = %u, %u-to-last padding data = %u.",
- paddingLen, i, data[plaintextLen - 1 - i], 0);
- /* Anti-side channel attack: Calculate the MAC address even if the padding is incorrect */
-#ifdef HITLS_TLS_FEATURE_ETM
- if (!cryptMsg->isEncryptThenMac)
-#endif
- {
- (void)RecConnGenerateMac(state->suiteInfo, cryptMsg, mac, &macLen);
- }
+ padLen, i, plain[plainLen - 1 - i], 0);
return RecordSendAlertMsg(ctx, ALERT_LEVEL_FATAL, ALERT_BAD_RECORD_MAC);
}
}
return HITLS_SUCCESS;
}
+static uint32_t GetHashOfMac(HITLS_MacAlgo macAlgo)
+{
+ switch (macAlgo) {
+ case HITLS_MAC_1:
+ return HITLS_HASH_SHA1;
+ case HITLS_MAC_256:
+ return HITLS_HASH_SHA_256;
+ case HITLS_MAC_224:
+ return HITLS_HASH_SHA_224;
+ case HITLS_MAC_384:
+ return HITLS_HASH_SHA_384;
+ case HITLS_MAC_512:
+ return HITLS_HASH_SHA_512;
+#ifdef HITLS_TLS_PROTO_TLCP11
+ case HITLS_MAC_SM3:
+ return HITLS_HASH_SM3;
+#endif
+ default:
+ BSL_LOG_BINLOG_FIXLEN(BINLOG_ID15388, BSL_LOG_LEVEL_ERR, BSL_LOG_BINLOG_TYPE_RUN,
+ "CBC encrypt error: unsupport MAC algorithm = %u.", macAlgo, 0, 0, 0);
+ break;
+ }
+ return HITLS_HASH_BUTT;
+}
+
+static uint32_t GetHmacBLen(HITLS_MacAlgo macAlgo)
+{
+ switch (macAlgo) {
+ case HITLS_MAC_1:
+ case HITLS_MAC_224:
+ case HITLS_MAC_256:
+ case HITLS_MAC_SM3:
+ return 64; // Blen of upper hmac is 64.
+ case HITLS_MAC_384:
+ case HITLS_MAC_512:
+ return 128; // Blen of upper hmac is 128.
+ default:
+ // should never be here.
+ return 0;
+ }
+}
+
+/**
+ * a constant-time implemenation of HMAC to prevent side-channel attacks
+ * reference: https://datatracker.ietf.org/doc/html/rfc2104#autoid-2
+ */
+
+static int32_t ConstTimeHmac(RecConnSuitInfo *suiteInfo, HITLS_HASH_Ctx **hashCtx, uint32_t good,
+ const REC_TextInput *cryptMsg, uint8_t *data, uint32_t dataLen, uint8_t *mac, uint32_t *macLen)
+{
+ HITLS_HASH_Ctx *obCtx = hashCtx[2];
+ uint32_t padLen = data[dataLen - 1];
+ padLen = Uint32ConstTimeSelect(good, padLen, 0);
+ uint32_t plainLen = dataLen - (suiteInfo->macLen + padLen + 1);
+ plainLen = Uint32ConstTimeSelect(good, plainLen, 0);
+
+ uint32_t blen = GetHmacBLen(suiteInfo->macAlg);
+ uint8_t ipad[HMAC_MAX_BLEN] = {0};
+ uint8_t opad[HMAC_MAX_BLEN * 2] = {0};
+ uint8_t key[HMAC_MAX_BLEN] = {0};
+ uint8_t ihash[MAX_DIGEST_SIZE] = {0};
+ uint32_t ihashLen = sizeof(ihash);
+ (void)memcpy_s(key, sizeof(key), suiteInfo->macKey, suiteInfo->macKeyLen);
+ for (uint32_t i = 0; i < blen; i++) {
+ ipad[i] = key[i] ^ 0x36;
+ opad[i] = key[i] ^ 0x5c;
+ }
+
+ // update K xor ipad
+ (void)SAL_CRYPT_DigestUpdate(hashCtx[0], ipad, blen);
+ // update the obscureHashCtx simultaneously
+ (void)SAL_CRYPT_DigestUpdate(obCtx, ipad, blen);
+
+ /**
+ * constant-time update plaintext to resist lucky13
+ * ref: https://www.isg.rhul.ac.uk/tls/TLStiming.pdf
+ */
+ uint8_t header[13] = {0}; // seq + record type
+ uint32_t pos = 0;
+ (void)memcpy_s(header, sizeof(header), cryptMsg->seq, REC_CONN_SEQ_SIZE);
+ pos += REC_CONN_SEQ_SIZE;
+ header[pos++] = cryptMsg->type;
+ BSL_Uint16ToByte(cryptMsg->version, header + pos);
+ pos += sizeof(uint16_t);
+ BSL_Uint16ToByte((uint16_t)plainLen, header + pos);
+ (void)SAL_CRYPT_DigestUpdate(hashCtx[0], header, sizeof(header));
+ (void)SAL_CRYPT_DigestUpdate(obCtx, header, sizeof(header));
+
+ uint32_t maxLen = dataLen - (suiteInfo->macLen + 1);
+ maxLen = Uint32ConstTimeSelect(good, maxLen, dataLen);
+ uint32_t flag = Uint32ConstTimeGt(maxLen, 256); // the value of 1 byte is up to 256
+ uint32_t minLen = Uint32ConstTimeSelect(flag, maxLen - 256, 0);
+ pos = 0;
+ (void)SAL_CRYPT_DigestUpdate(hashCtx[0], data, minLen);
+ (void)SAL_CRYPT_DigestUpdate(obCtx, data, minLen);
+ pos += minLen;
+ for (uint32_t i = minLen; i < maxLen; i++) {
+ if (i < plainLen) {
+ SAL_CRYPT_DigestUpdate(hashCtx[0], data + i, 1);
+ } else {
+ SAL_CRYPT_DigestUpdate(obCtx, data + i, 1);
+ }
+ }
+ (void)SAL_CRYPT_DigestFinal(hashCtx[0], ihash, &ihashLen);
+ (void)memcpy_s(opad + blen, MAX_DIGEST_SIZE, ihash, ihashLen);
+
+ // update (K xor opad) + ihash
+ (void)SAL_CRYPT_DigestUpdate(hashCtx[1], opad, blen + ihashLen);
+ (void)SAL_CRYPT_DigestFinal(hashCtx[1], mac, macLen);
+
+ BSL_SAL_CleanseData(ipad, sizeof(ipad));
+ BSL_SAL_CleanseData(opad, sizeof(opad));
+ BSL_SAL_CleanseData(key, sizeof(key));
+
+ return HITLS_SUCCESS;
+}
+
+static inline uint32_t ConstTimeSelectMemcmp(uint32_t good, uint8_t *a, uint8_t *b, uint32_t l)
+{
+ uint8_t *t = (good == 0) ? b : a;
+ return ConstTimeMemcmp(t, b, l);
+}
+
+static int32_t RecConnCbcDecMtECheckMacTls(TLS_Ctx *ctx, const REC_TextInput *cryptMsg,
+ uint8_t *plain, uint32_t plainLen)
+{
+ const RecConnState *state = ctx->recCtx->readStates.currentState;
+ uint32_t hashAlg = GetHashOfMac(state->suiteInfo->macAlg);
+ if (hashAlg == HITLS_HASH_BUTT) {
+ return HITLS_CRYPT_ERR_HMAC;
+ }
+
+ HITLS_HASH_Ctx *ihashCtx = NULL;
+ HITLS_HASH_Ctx *ohashCtx = NULL;
+ HITLS_HASH_Ctx *obscureHashCtx = NULL;
+ ihashCtx = SAL_CRYPT_DigestInit(hashAlg);
+ ohashCtx = SAL_CRYPT_DigestCopy(ihashCtx);
+ obscureHashCtx = SAL_CRYPT_DigestCopy(ihashCtx);
+ if (ihashCtx == NULL || ohashCtx == NULL || obscureHashCtx == NULL) {
+ SAL_CRYPT_DigestFree(ihashCtx);
+ SAL_CRYPT_DigestFree(ohashCtx);
+ SAL_CRYPT_DigestFree(obscureHashCtx);
+ return HITLS_REC_ERR_GENERATE_MAC;
+ }
+
+ uint8_t mac[MAX_DIGEST_SIZE] = {0};
+ uint32_t macLen = sizeof(mac);
+ uint8_t padLen = plain[plainLen - 1];
+
+ uint32_t good = Uint32ConstTimeGe(plainLen, state->suiteInfo->macLen + padLen + 1);
+
+ // constant-time check padding bytes
+ for (uint32_t i = 1; i <= 255; i++) {
+ uint32_t mask = good & Uint32ConstTimeLe(i, padLen);
+ good &= Uint32ConstTimeEqual(plain[plainLen - 1 - (i & mask)], padLen);
+ }
+
+ HITLS_HASH_Ctx *hashCtxs[3] = {ihashCtx, ohashCtx, obscureHashCtx};
+ ConstTimeHmac(state->suiteInfo, hashCtxs, good, cryptMsg, plain, plainLen, mac, &macLen);
+
+ // check mac
+ padLen = Uint32ConstTimeSelect(good, padLen, 0);
+ plainLen -= state->suiteInfo->macLen + padLen + 1;
+ good &= ConstTimeSelectMemcmp(good, &plain[plainLen], mac, macLen);
+ SAL_CRYPT_DigestFree(ihashCtx);
+ SAL_CRYPT_DigestFree(ohashCtx);
+ SAL_CRYPT_DigestFree(obscureHashCtx);
+ return ~good;
+}
+
static int32_t RecConnCbcDecryptByMacThenEncrypt(TLS_Ctx *ctx, const RecConnState *state, const REC_TextInput *cryptMsg,
uint8_t *data, uint32_t *dataLen)
{
@@ -180,18 +328,13 @@ static int32_t RecConnCbcDecryptByMacThenEncrypt(TLS_Ctx *ctx, const RecConnStat
}
/* Check padding and padding length */
- ret = RecConnCbcCheckDecryptPadding(ctx, cryptMsg, data, plaintextLen, offset);
+ ret = RecConnCbcDecMtECheckMacTls(ctx, cryptMsg, data, plaintextLen);
if (ret != HITLS_SUCCESS) {
BSL_LOG_BINLOG_FIXLEN(BINLOG_ID17244, BSL_LOG_LEVEL_ERR, BSL_LOG_BINLOG_TYPE_RUN,
- "RecConnCbcCheckDecryptPadding fail", 0, 0, 0, 0);
- return ret;
+ "RecConnCbcDecMtECheckMacTls fail", 0, 0, 0, 0);
+ return RecordSendAlertMsg(ctx, ALERT_LEVEL_FATAL, ALERT_BAD_RECORD_MAC);
}
- plaintextLen -= data[plaintextLen - 1] + CBC_PADDING_LEN_TAG_SIZE;
-
- /* Check MAC */
- ret = RecConnCheckMac(ctx, state->suiteInfo, cryptMsg, data, plaintextLen);
- plaintextLen -= state->suiteInfo->macLen;
- *dataLen = plaintextLen;
+ *dataLen = plaintextLen - (state->suiteInfo->macLen + data[plaintextLen - 1] + CBC_PADDING_LEN_TAG_SIZE);
return ret;
}
@@ -237,10 +380,10 @@ static int32_t RecConnCbcDecryptByEncryptThenMac(TLS_Ctx *ctx, const RecConnStat
/* Check padding and padding length */
uint8_t paddingLen = data[plaintextLen - 1];
- ret = RecConnCbcCheckDecryptPadding(ctx, cryptMsg, data, plaintextLen, offset);
+ ret = RecConnCbcDecCheckPaddingEtM(ctx, cryptMsg, data, plaintextLen, offset);
if (ret != HITLS_SUCCESS) {
BSL_LOG_BINLOG_FIXLEN(BINLOG_ID17247, BSL_LOG_LEVEL_ERR, BSL_LOG_BINLOG_TYPE_RUN,
- "RecConnCbcCheckDecryptPadding fail", 0, 0, 0, 0);
+ "RecConnCbcDecCheckPaddingEtM fail", 0, 0, 0, 0);
return ret;
}
*dataLen = plaintextLen - paddingLen - CBC_PADDING_LEN_TAG_SIZE;
--
2.42.0.windows.2