Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
300 changes: 277 additions & 23 deletions src/wp_des.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,21 @@ typedef struct wp_Des3BlockCtx {
/** wolfSSL DES object. */
Des3 des3;

/** Provider context - needed for wolfCrypt RNG access. */
WOLFPROV_CTX *provCtx;

/** Cipher mode - CBC or ECB. */
int mode;

unsigned int tls_version;

/** Pointer to the MAC extracted from a decrypted TLS record. */
unsigned char *tlsmac;
/** Size of the MAC expected in TLS records. */
size_t tlsmacsize;
/** Whether tlsmac was separately allocated. */
int tlsmacAlloced;

/** Length of key in bytes. */
size_t keyLen;
/** Length of IV in bytes */
Expand Down Expand Up @@ -79,6 +89,9 @@ static int wp_des3_block_set_ctx_params(wp_Des3BlockCtx *ctx,
*/
static void wp_des3_block_freectx(wp_Des3BlockCtx *ctx)
{
if (ctx->tlsmacAlloced) {
OPENSSL_free(ctx->tlsmac);
}
wc_Des3Free(&ctx->des3);
OPENSSL_clear_free(ctx, sizeof(*ctx));
}
Expand All @@ -98,8 +111,24 @@ static void *wp_des3_block_dupctx(wp_Des3BlockCtx *src)
dst = OPENSSL_malloc(sizeof(*dst));
}
if (dst != NULL) {
/* TODO: copying des3 may not work if it has pointers in it. */
/* Safe byte-copy: Des3 owns no heap as used here (sync, no async
* devId); async offload would need a deep copy. */
XMEMCPY(dst, src, sizeof(*src));
dst->tlsmac = NULL;
dst->tlsmacAlloced = 0;
/* Deep-copy tlsmac to avoid double-free between src and dst. */
if (src->tlsmacAlloced && src->tlsmac != NULL) {
dst->tlsmac = OPENSSL_malloc(src->tlsmacsize);
if (dst->tlsmac == NULL) {
Comment thread
yosuke-wolfssl marked this conversation as resolved.
Comment thread
yosuke-wolfssl marked this conversation as resolved.
/* dst->des3 aliases src's - must not wc_Des3Free it here. */
OPENSSL_clear_free(dst, sizeof(*dst));
dst = NULL;
}
else {
XMEMCPY(dst->tlsmac, src->tlsmac, src->tlsmacsize);
dst->tlsmacAlloced = 1;
}
}
}

return dst;
Expand Down Expand Up @@ -193,6 +222,7 @@ static const OSSL_PARAM* wp_cipher_gettable_ctx_params(wp_Des3BlockCtx* ctx,
OSSL_PARAM_uint(OSSL_CIPHER_PARAM_NUM, NULL),
OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_IV, NULL, 0),
OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_UPDATED_IV, NULL, 0),
OSSL_PARAM_octet_ptr(OSSL_CIPHER_PARAM_TLS_MAC, NULL, 0),
OSSL_PARAM_END
};
(void)ctx;
Expand All @@ -218,6 +248,7 @@ static const OSSL_PARAM* wp_cipher_settable_ctx_params(wp_Des3BlockCtx* ctx,
OSSL_PARAM_uint(OSSL_CIPHER_PARAM_NUM, NULL),
OSSL_PARAM_uint(OSSL_CIPHER_PARAM_USE_BITS, NULL),
OSSL_PARAM_uint(OSSL_CIPHER_PARAM_TLS_VERSION, NULL),
OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_TLS_MAC_SIZE, NULL),
OSSL_PARAM_END
};
(void)ctx;
Expand Down Expand Up @@ -405,6 +436,227 @@ static int wp_des3_block_doit(wp_Des3BlockCtx *ctx, unsigned char *out,
return rc == 0;
}

/**
* Constant-time TLS CBC padding removal and MAC extraction after decryption.
*
* For ETM/no-MAC modes, strips the explicit IV and validates/removes padding.
* For MtE, also extracts the MAC using a constant-time rotation pattern and
* substitutes a random MAC on bad padding so no padding oracle is exposed.
*
* @param [in, out] ctx DES3 block context object.
* @param [in] out Decrypted output buffer.
* @param [in] oLen Length of decrypted data in bytes.
* @param [out] outLen Updated with length after padding/MAC removal.
* @return 1 on success.
* @return 0 on failure.
*/
static int wp_des3_block_tls_dec_record(wp_Des3BlockCtx *ctx,
unsigned char *out, size_t oLen, size_t *outLen)
{
int ok = 1;

/* Buffer: [explicit_IV(BS)][payload][MAC(macsize)][padding(pad+1)].
* Padding validation follows OpenSSL tls1_cbc_remove_padding_and_mac;
* MAC extraction follows the ssl3_cbc_copy_mac rotation pattern. */
unsigned char *rec;
size_t recLen;
size_t origRecLen;
unsigned char padVal;
size_t overhead;
size_t toCheck;
size_t good;
size_t i, j;
size_t macSize = ctx->tlsmacsize;

WOLFPROV_ENTER(WP_LOG_COMP_DES, "wp_des3_block_tls_dec_record");

/* Free any previously allocated MAC */
if (ctx->tlsmacAlloced) {
OPENSSL_free(ctx->tlsmac);
ctx->tlsmacAlloced = 0;
ctx->tlsmac = NULL;
}

/* Below TLS 1.1 there is no explicit per-record IV, so stripping one
* would discard payload. Reject rather than corrupt the record. */
if (ctx->tls_version == SSL3_VERSION ||
ctx->tls_version == TLS1_VERSION) {
ok = 0;
}

if (ok && (macSize > EVP_MAX_MD_SIZE ||
oLen < DES_BLOCK_SIZE + macSize + 1)) {
ok = 0;
}

if (ok && macSize == 0) {
/* ETM/no-MAC: record layer handled the MAC, so only strip the
* explicit IV and validate+remove padding. */
unsigned char *ivRec = out + DES_BLOCK_SIZE;
size_t ivRecLen = oLen - DES_BLOCK_SIZE;
unsigned char padV = ivRec[ivRecLen - 1];
size_t gd = (size_t)0 - ((size_t)(
wp_ct_int_mask_gte((int)ivRecLen, (int)padV + 1) & 1));
size_t tc = 256;
size_t d;
if (tc > ivRecLen) {
tc = ivRecLen;
}

for (i = 0; i < tc; i++) {
byte m = wp_ct_int_mask_gte((int)padV, (int)i);
unsigned char bv = ivRec[ivRecLen - 1 - i];
gd &= ~((size_t)(m & (padV ^ bv)));
}
/* Collapse lower 8 bits to a full-width size_t mask. */
d = (gd & 0xff) ^ 0xff;
d |= (0 - d);
d >>= (sizeof(size_t) * 8 - 1);
gd = d - 1;
ivRecLen -= gd & ((size_t)padV + 1);
/* ETM/no-MAC: bad padding is a real error (no oracle concern). */
if (gd == 0) {
ok = 0;
*outLen = 0;
}
else {
*outLen = ivRecLen;
}
}
else if (ok) {
/* 64-byte aligned buffer for cache-line-aware MAC rotation */
unsigned char rotatedMacBuf[64 + EVP_MAX_MD_SIZE];
unsigned char *rotatedMac;
unsigned char randMac[EVP_MAX_MD_SIZE];
size_t macEnd;
size_t macStart;
size_t scanStart = 0;
size_t diff;
byte inMac;
size_t rotateOff;

/* Align rotatedMac to a 64-byte boundary so the whole MAC buffer
* sits at known positions within one or two cache lines. */
rotatedMac = rotatedMacBuf +
((0 - (size_t)rotatedMacBuf) & 63);

/* For TLS 1.1+/DTLS: skip explicit IV */
rec = out + DES_BLOCK_SIZE;
recLen = oLen - DES_BLOCK_SIZE;
origRecLen = recLen;

padVal = rec[recLen - 1];
overhead = macSize + (size_t)padVal + 1;

/* CT overhead check: recLen >= overhead, folded into good mask. */
good = (size_t)0 -
((size_t)(wp_ct_int_mask_gte((int)recLen, (int)overhead) & 1));

/* Validate up to 256 padding bytes in constant time. */
toCheck = 256;
if (toCheck > recLen) {
toCheck = recLen;
}

for (i = 0; i < toCheck; i++) {
byte mask = wp_ct_int_mask_gte((int)padVal, (int)i);
unsigned char b = rec[recLen - 1 - i];
good &= ~((size_t)(mask & (padVal ^ b)));
}
/* Collapse lower 8 bits to a full-width size_t mask. */
diff = (good & 0xff) ^ 0xff;
diff |= (0 - diff);
diff >>= (sizeof(size_t) * 8 - 1);
good = diff - 1;

recLen -= good & ((size_t)padVal + 1);

macEnd = recLen;
macStart = macEnd - macSize;

recLen -= macSize;
*outLen = recLen;
Comment thread
yosuke-wolfssl marked this conversation as resolved.

#ifndef WP_SINGLE_THREADED
if (wp_provctx_lock_rng(ctx->provCtx)) {
if (wc_RNG_GenerateBlock(wp_provctx_get_rng(ctx->provCtx),
randMac, (word32)macSize) != 0) {
ok = 0;
}
wp_provctx_unlock_rng(ctx->provCtx);
}
else {
/* Cannot safely use the RNG without the lock. */
ok = 0;
}
#else
if (wc_RNG_GenerateBlock(wp_provctx_get_rng(ctx->provCtx),
randMac, (word32)macSize) != 0) {
ok = 0;
}
#endif

/* Only build the substitute MAC if RNG succeeded; otherwise randMac
* is unset and must not be read. */
if (ok) {
ctx->tlsmac = OPENSSL_malloc(macSize);
if (ctx->tlsmac == NULL) {
ok = 0;
}
}
if (ok) {
ctx->tlsmacAlloced = 1;

/* Scan all bytes that could hold the MAC (position varies). */
if (origRecLen > macSize + 255 + 1) {
scanStart = origRecLen - (macSize + 255 + 1);
}

XMEMSET(rotatedMac, 0, EVP_MAX_MD_SIZE);
inMac = 0;
rotateOff = 0;
for (i = scanStart, j = 0; i < origRecLen; i++) {
byte started = wp_ct_int_mask_eq((int)i, (int)macStart);
byte notEnded = wp_ct_int_mask_lt((int)i, (int)macEnd);
unsigned char b = rec[i];

inMac |= started;
inMac &= notEnded;
rotateOff |= j & (size_t)started;
rotatedMac[j++] |= b & inMac;
j &= (size_t)wp_ct_int_mask_lt((int)j, (int)macSize);
}

/* Cache-line-aware un-rotation: load from both halves and
* ct-select so rotateOff does not leak via cache access. */
for (i = 0; i < macSize; i++) {
byte aux1 = rotatedMac[rotateOff & ~32];
byte aux2 = rotatedMac[rotateOff | 32];
byte eqMask = wp_ct_int_mask_eq(
(int)(rotateOff & ~32), (int)rotateOff);
unsigned char real = wp_ct_byte_mask_sel(
eqMask, aux1, aux2);
byte goodMask = (byte)(good & 0xff);

ctx->tlsmac[i] = wp_ct_byte_mask_sel(goodMask, real,
randMac[i]);
rotateOff++;
rotateOff &= (size_t)wp_ct_int_mask_lt(
(int)rotateOff, (int)macSize);
}
}
}

/* Report no output on any failure path, never a partial length. */
if (!ok) {
*outLen = 0;
}

WOLFPROV_LEAVE(WP_LOG_COMP_DES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);

return ok;
}

/**
* Update encryption/decryption with more data.
*
Expand Down Expand Up @@ -493,28 +745,8 @@ static int wp_des3_block_update(wp_Des3BlockCtx *ctx, unsigned char *out,
}
*outLen = oLen;
}
if (ok && (ctx->tls_version > 0) && (!ctx->enc) &&
(oLen < (size_t)(2 * DES_BLOCK_SIZE))) {
ok = 0;
}
if (ok && (ctx->tls_version > 0) && (!ctx->enc)) {
unsigned char pad = outStart[oLen-1];
int padStart = DES_BLOCK_SIZE - pad - 1;
unsigned char invalid = (pad < DES_BLOCK_SIZE) - 1;
int i;

for (i = DES_BLOCK_SIZE - 1; i >= 0; i--) {
byte check = wp_ct_int_mask_gte(i, padStart);
check &= wp_ct_byte_mask_ne(outStart[oLen - DES_BLOCK_SIZE + i], pad);
invalid |= check;
}
ok = invalid == 0;
if (ok) {
*outLen = oLen - pad - 1 - DES_BLOCK_SIZE;
}
else {
*outLen = 0;
}
ok = wp_des3_block_tls_dec_record(ctx, outStart, oLen, outLen);
}

WOLFPROV_LEAVE(WP_LOG_COMP_DES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
Expand Down Expand Up @@ -764,6 +996,13 @@ static int wp_des3_block_get_ctx_params(wp_Des3BlockCtx* ctx, OSSL_PARAM params[
ok = 0;
}
}
if (ok) {
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_TLS_MAC);
if ((p != NULL) &&
(!OSSL_PARAM_set_octet_ptr(p, ctx->tlsmac, ctx->tlsmacsize))) {
ok = 0;
}
}

WOLFPROV_LEAVE(WP_LOG_COMP_DES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
return ok;
Expand All @@ -787,6 +1026,7 @@ static int wp_des3_block_set_ctx_params(wp_Des3BlockCtx *ctx,
if (params != NULL) {
unsigned int val;
int set;
size_t macSz = ctx->tlsmacsize;

if (!wp_params_get_uint(params, OSSL_CIPHER_PARAM_PADDING, &val,
&set)) {
Expand All @@ -812,6 +1052,20 @@ static int wp_des3_block_set_ctx_params(wp_Des3BlockCtx *ctx,
&ctx->tls_version, NULL))) {
ok = 0;
}
if (ok && (!wp_params_get_size_t(params, OSSL_CIPHER_PARAM_TLS_MAC_SIZE,
&macSz))) {
ok = 0;
}
if (ok && (macSz != ctx->tlsmacsize)) {
/* Any stored MAC was sized for the old value - drop it so tlsmac
* is never shorter than tlsmacsize. */
if (ctx->tlsmacAlloced) {
OPENSSL_free(ctx->tlsmac);
ctx->tlsmacAlloced = 0;
}
ctx->tlsmac = NULL;
ctx->tlsmacsize = macSz;
}
}

WOLFPROV_LEAVE(WP_LOG_COMP_DES, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok);
Expand Down Expand Up @@ -865,12 +1119,12 @@ static wp_Des3BlockCtx* wp_des3_block_##lcmode##_newctx( \
WOLFPROV_CTX *provCtx) \
{ \
wp_Des3BlockCtx *ctx = NULL; \
(void)provCtx; \
if (wolfssl_prov_is_running()) { \
ctx = OPENSSL_zalloc(sizeof(*ctx)); \
} \
if (ctx != NULL) { \
wp_des3_block_init_ctx(ctx, kBits, ivBits, EVP_CIPH_##UCMODE##_MODE); \
ctx->provCtx = provCtx; \
} \
return ctx; \
}
Expand Down
Loading
Loading