Skip to content

Commit a2fe12a

Browse files
TLS ECH OuterExtensions support (Server side)
1 parent 30cfb38 commit a2fe12a

3 files changed

Lines changed: 334 additions & 1 deletion

File tree

src/tls.c

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13440,6 +13440,328 @@ static int TLSX_ECH_GetSize(WOLFSSL_ECH* ech, byte msgType)
1344013440
return (int)size;
1344113441
}
1344213442

13443+
/* locate the given extension type, use the extOffset to start off after where a
13444+
* previous call to this function ended */
13445+
static const byte* EchFindOuterExtension(const byte* outerCh, word32 chLen,
13446+
word16 extType, word16* extLen, word16* extOffset,
13447+
word16* extensionsStart, word16* extensionsLen)
13448+
{
13449+
word32 idx = *extOffset;
13450+
byte sessionIdLen;
13451+
word16 cipherSuitesLen;
13452+
byte compressionLen;
13453+
word16 type;
13454+
word16 len;
13455+
13456+
if (idx == 0) {
13457+
idx = OPAQUE16_LEN + RAN_LEN;
13458+
if (idx >= chLen)
13459+
return NULL;
13460+
13461+
sessionIdLen = outerCh[idx++];
13462+
idx += sessionIdLen;
13463+
if (idx + OPAQUE16_LEN > chLen)
13464+
return NULL;
13465+
13466+
ato16(outerCh + idx, &cipherSuitesLen);
13467+
idx += OPAQUE16_LEN + cipherSuitesLen;
13468+
if (idx >= chLen)
13469+
return NULL;
13470+
13471+
compressionLen = outerCh[idx++];
13472+
idx += compressionLen;
13473+
if (idx + OPAQUE16_LEN > chLen)
13474+
return NULL;
13475+
13476+
ato16(outerCh + idx, extensionsLen);
13477+
idx += OPAQUE16_LEN;
13478+
*extensionsStart = (word16)idx;
13479+
13480+
if (idx + *extensionsLen > chLen)
13481+
return NULL;
13482+
}
13483+
13484+
while (idx < chLen && (idx - *extensionsStart) < *extensionsLen) {
13485+
if (idx + OPAQUE16_LEN + OPAQUE16_LEN > chLen)
13486+
return NULL;
13487+
13488+
ato16(outerCh + idx, &type);
13489+
idx += OPAQUE16_LEN;
13490+
ato16(outerCh + idx, &len);
13491+
idx += OPAQUE16_LEN;
13492+
13493+
if (idx + len > chLen)
13494+
return NULL;
13495+
13496+
if (type == extType) {
13497+
*extLen = len + OPAQUE16_LEN + OPAQUE16_LEN;
13498+
*extOffset = idx + len;
13499+
return outerCh + idx - OPAQUE16_LEN - OPAQUE16_LEN;
13500+
}
13501+
13502+
idx += len;
13503+
}
13504+
13505+
return NULL;
13506+
}
13507+
13508+
/* if newInnerCh is NULL, validate ordering and existence of references
13509+
* - updates newInnerChLen with total length of selected extensions
13510+
* if not NULL, copy extensions into the provided buffer */
13511+
static int EchCopyOuterExtensions(const byte* outerCh, word32 outerChLen,
13512+
byte** newInnerCh, word32* newInnerChLen,
13513+
word16 numOuterRefs, const byte* outerRefTypes)
13514+
{
13515+
int ret = 0;
13516+
word16 refType;
13517+
word16 outerExtLen;
13518+
word16 outerExtOffset = 0;
13519+
word16 extsStart;
13520+
word16 extsLen;
13521+
const byte* outerExtData;
13522+
13523+
if (newInnerCh == NULL) {
13524+
*newInnerChLen = 0;
13525+
13526+
while (numOuterRefs-- > 0) {
13527+
ato16(outerRefTypes, &refType);
13528+
13529+
if (refType == TLSXT_ECH) {
13530+
WOLFSSL_MSG("ECH: ech_outer_extensions references ECH");
13531+
ret = INVALID_PARAMETER;
13532+
break;
13533+
}
13534+
13535+
outerExtData = EchFindOuterExtension(outerCh, outerChLen,
13536+
refType, &outerExtLen, &outerExtOffset,
13537+
&extsStart, &extsLen);
13538+
13539+
if (outerExtData == NULL) {
13540+
WOLFSSL_MSG("ECH: referenced extension not in outer CH");
13541+
ret = INVALID_PARAMETER;
13542+
break;
13543+
}
13544+
13545+
*newInnerChLen += outerExtLen;
13546+
13547+
outerRefTypes += OPAQUE16_LEN;
13548+
}
13549+
}
13550+
else {
13551+
while (numOuterRefs-- > 0) {
13552+
ato16(outerRefTypes, &refType);
13553+
13554+
outerExtData = EchFindOuterExtension(outerCh, outerChLen,
13555+
refType, &outerExtLen, &outerExtOffset,
13556+
&extsStart, &extsLen);
13557+
13558+
if (outerExtData == NULL) {
13559+
ret = INVALID_PARAMETER;
13560+
break;
13561+
}
13562+
13563+
XMEMCPY(*newInnerCh, outerExtData, outerExtLen);
13564+
*newInnerCh += outerExtLen;
13565+
13566+
outerRefTypes += OPAQUE16_LEN;
13567+
}
13568+
}
13569+
13570+
return ret;
13571+
}
13572+
13573+
/* expand ech_outer_extensions in the inner ClientHello by copying referenced
13574+
* extensions from the outer ClientHello
13575+
*/
13576+
static int TLSX_ExpandEchOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech,
13577+
void* heap)
13578+
{
13579+
int ret = 0;
13580+
const byte* innerCh;
13581+
word32 innerChLen;
13582+
const byte* outerCh;
13583+
word32 outerChLen;
13584+
word32 idx;
13585+
byte sessionIdLen;
13586+
word16 cipherSuitesLen;
13587+
byte compressionLen;
13588+
13589+
word32 innerExtIdx;
13590+
word16 innerExtLen;
13591+
word32 echOuterExtIdx = 0;
13592+
word16 echOuterExtLen = 0;
13593+
int foundEchOuter = 0;
13594+
word16 numOuterRefs = 0;
13595+
const byte* outerRefTypes = NULL;
13596+
word32 extraSize = 0;
13597+
byte* newInnerCh = NULL;
13598+
byte* newInnerChRef;
13599+
word32 newInnerChLen;
13600+
word32 copyLen;
13601+
13602+
WOLFSSL_ENTER("TLSX_ExpandEchOuterExtensions");
13603+
13604+
if (ech == NULL || ech->innerClientHello == NULL || ech->aad == NULL)
13605+
return BAD_FUNC_ARG;
13606+
13607+
innerCh = ech->innerClientHello + HANDSHAKE_HEADER_SZ;
13608+
innerChLen = ech->innerClientHelloLen;
13609+
outerCh = ech->aad;
13610+
outerChLen = ech->aadLen;
13611+
13612+
idx = OPAQUE16_LEN + RAN_LEN;
13613+
if (idx >= innerChLen)
13614+
return BUFFER_ERROR;
13615+
13616+
sessionIdLen = innerCh[idx++];
13617+
idx += sessionIdLen;
13618+
/* the ECH spec details that innerhello sessionID must initially be empty */
13619+
if (sessionIdLen != 0)
13620+
return INVALID_PARAMETER;
13621+
if (idx + OPAQUE16_LEN > innerChLen)
13622+
return BUFFER_ERROR;
13623+
13624+
ato16(innerCh + idx, &cipherSuitesLen);
13625+
idx += OPAQUE16_LEN + cipherSuitesLen;
13626+
if (idx >= innerChLen)
13627+
return BUFFER_ERROR;
13628+
13629+
compressionLen = innerCh[idx++];
13630+
idx += compressionLen;
13631+
if (idx + OPAQUE16_LEN > innerChLen)
13632+
return BUFFER_ERROR;
13633+
13634+
ato16(innerCh + idx, &innerExtLen);
13635+
idx += OPAQUE16_LEN;
13636+
innerExtIdx = idx;
13637+
if (idx + innerExtLen > innerChLen)
13638+
return BUFFER_ERROR;
13639+
13640+
/* validate ech_outer_extensions and calculate extra size */
13641+
while (idx < innerChLen && (idx - innerExtIdx) < innerExtLen) {
13642+
word16 type;
13643+
word16 len;
13644+
byte outerExtListLen;
13645+
13646+
if (idx + OPAQUE16_LEN + OPAQUE16_LEN > innerChLen)
13647+
return BUFFER_ERROR;
13648+
13649+
ato16(innerCh + idx, &type);
13650+
idx += OPAQUE16_LEN;
13651+
ato16(innerCh + idx, &len);
13652+
idx += OPAQUE16_LEN;
13653+
13654+
if (idx + len > innerChLen)
13655+
return BUFFER_ERROR;
13656+
13657+
if (type == TLSXT_ECH_OUTER_EXTENSIONS) {
13658+
if (foundEchOuter) {
13659+
WOLFSSL_MSG("ECH: duplicate ech_outer_extensions");
13660+
return INVALID_PARAMETER;
13661+
}
13662+
foundEchOuter = 1;
13663+
echOuterExtIdx = idx - OPAQUE16_LEN - OPAQUE16_LEN;
13664+
echOuterExtLen = len + OPAQUE16_LEN + OPAQUE16_LEN;
13665+
13666+
/* ech_outer_extensions data format: 1-byte length + extension types
13667+
* ExtensionType OuterExtensions<2..254>; */
13668+
if (len < 1)
13669+
return BUFFER_ERROR;
13670+
outerExtListLen = innerCh[idx];
13671+
if (outerExtListLen + 1 != len || outerExtListLen < 2 ||
13672+
outerExtListLen == 255)
13673+
return BUFFER_ERROR;
13674+
13675+
outerRefTypes = innerCh + idx + 1;
13676+
numOuterRefs = outerExtListLen / OPAQUE16_LEN;
13677+
13678+
ret = EchCopyOuterExtensions(outerCh, outerChLen, NULL, &extraSize,
13679+
numOuterRefs, outerRefTypes);
13680+
if (ret != 0)
13681+
return ret;
13682+
}
13683+
13684+
idx += len;
13685+
}
13686+
13687+
newInnerChLen = innerChLen - echOuterExtLen + extraSize - sessionIdLen +
13688+
ssl->session->sessionIDSz;
13689+
13690+
if (!foundEchOuter && sessionIdLen == ssl->session->sessionIDSz) {
13691+
/* no extensions + no sessionID to copy */
13692+
WOLFSSL_MSG("ECH: no EchOuterExtensions extension found");
13693+
return ret;
13694+
}
13695+
else {
13696+
newInnerCh = (byte*)XMALLOC(newInnerChLen + HANDSHAKE_HEADER_SZ, heap,
13697+
DYNAMIC_TYPE_TMP_BUFFER);
13698+
if (newInnerCh == NULL)
13699+
return MEMORY_E;
13700+
}
13701+
13702+
/* note: The first HANDSHAKE_HEADER_SZ bytes are reserved for the header
13703+
* but not initialized here. The header will be properly set later by
13704+
* AddTls13HandShakeHeader() in DoTls13ClientHello(). */
13705+
13706+
/* copy everything up to EchOuterExtensions */
13707+
newInnerChRef = newInnerCh + HANDSHAKE_HEADER_SZ;
13708+
copyLen = OPAQUE16_LEN + RAN_LEN;
13709+
XMEMCPY(newInnerChRef, innerCh, copyLen);
13710+
newInnerChRef += copyLen;
13711+
13712+
*newInnerChRef = ssl->session->sessionIDSz;
13713+
newInnerChRef += OPAQUE8_LEN;
13714+
13715+
copyLen = ssl->session->sessionIDSz;
13716+
XMEMCPY(newInnerChRef, ssl->session->sessionID, copyLen);
13717+
newInnerChRef += copyLen;
13718+
13719+
if (!foundEchOuter) {
13720+
WOLFSSL_MSG("ECH: no EchOuterExtensions extension found");
13721+
13722+
copyLen = innerChLen - OPAQUE16_LEN - RAN_LEN - OPAQUE8_LEN -
13723+
sessionIdLen;
13724+
XMEMCPY(newInnerChRef, innerCh + OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN +
13725+
sessionIdLen, copyLen);
13726+
}
13727+
else {
13728+
copyLen = echOuterExtIdx - OPAQUE16_LEN - RAN_LEN - OPAQUE8_LEN -
13729+
sessionIdLen;
13730+
XMEMCPY(newInnerChRef, innerCh + OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN +
13731+
sessionIdLen, copyLen);
13732+
newInnerChRef += copyLen;
13733+
13734+
/* update extensions length in the new ClientHello */
13735+
innerExtIdx = innerExtIdx - sessionIdLen + ssl->session->sessionIDSz;
13736+
c16toa(innerExtLen - echOuterExtLen + (word16)extraSize,
13737+
newInnerChRef - OPAQUE16_LEN);
13738+
13739+
/* insert expanded extensions from outer ClientHello */
13740+
ret = EchCopyOuterExtensions(outerCh, outerChLen, &newInnerChRef,
13741+
&newInnerChLen, numOuterRefs, outerRefTypes);
13742+
if (ret == 0) {
13743+
/* copy remaining extensions after ech_outer_extensions */
13744+
copyLen = innerChLen - (echOuterExtIdx + echOuterExtLen);
13745+
XMEMCPY(newInnerChRef, innerCh + echOuterExtIdx + echOuterExtLen,
13746+
copyLen);
13747+
13748+
WOLFSSL_MSG("ECH: expanded ech_outer_extensions successfully");
13749+
}
13750+
}
13751+
13752+
if (ret == 0) {
13753+
XFREE(ech->innerClientHello, heap, DYNAMIC_TYPE_TMP_BUFFER);
13754+
ech->innerClientHello = newInnerCh;
13755+
ech->innerClientHelloLen = (word16)newInnerChLen;
13756+
newInnerCh = NULL;
13757+
}
13758+
13759+
if (newInnerCh != NULL)
13760+
XFREE(newInnerCh, heap, DYNAMIC_TYPE_TMP_BUFFER);
13761+
13762+
return ret;
13763+
}
13764+
1344313765
/* return status after attempting to open the hpke encrypted ech extension, if
1344413766
* successful the inner client hello will be stored in
1344513767
* ech->innerClientHelloLen */
@@ -13713,6 +14035,16 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
1371314035
}
1371414036
/* subtract the length of the padding from the length */
1371514037
ech->innerClientHelloLen -= i;
14038+
14039+
/* expand EchOuterExtensions if present */
14040+
ret = TLSX_ExpandEchOuterExtensions(ssl, ech, ssl->heap);
14041+
if (ret != 0) {
14042+
WOLFSSL_MSG_EX("ECH: failed to expand EchOuterExtensions");
14043+
XFREE(ech->innerClientHello, ssl->heap,
14044+
DYNAMIC_TYPE_TMP_BUFFER);
14045+
ech->innerClientHello = NULL;
14046+
ech->state = ECH_WRITE_RETRY_CONFIGS;
14047+
}
1371614048
}
1371714049
XFREE(aadCopy, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
1371814050
return 0;

src/tls13.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13138,7 +13138,6 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx,
1313813138
if (echX != NULL &&
1313913139
((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) {
1314013140
byte copyRandom = ((WOLFSSL_ECH*)echX->data)->innerCount == 0;
13141-
1314213141
/* reset the inOutIdx to the outer start */
1314313142
*inOutIdx = echInOutIdx;
1314413143
/* call again with the inner hello */

wolfssl/internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2974,6 +2974,8 @@ typedef struct Options Options;
29742974
#define TLSXT_KEY_QUIC_TP_PARAMS 0x0039 /* RFC 9001, ch. 8.2 */
29752975
#define TLSXT_ECH 0xfe0d /* from */
29762976
/* draft-ietf-tls-esni-13 */
2977+
#define TLSXT_ECH_OUTER_EXTENSIONS 0xfd00 /* from
2978+
draft-ietf-tls-esni-13 */
29772979
/* The 0xFF section is experimental/custom/personal use */
29782980
#define TLSXT_CKS 0xff92 /* X9.146 */
29792981
#define TLSXT_RENEGOTIATION_INFO 0xff01

0 commit comments

Comments
 (0)