Skip to content

Commit 2ed33b5

Browse files
TLS ECH OuterExtensions support (Server side)
1 parent 9ee3b45 commit 2ed33b5

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
@@ -13407,6 +13407,328 @@ static int TLSX_ECH_GetSize(WOLFSSL_ECH* ech, byte msgType)
1340713407
return (int)size;
1340813408
}
1340913409

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

src/tls13.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13046,7 +13046,6 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx,
1304613046
if (echX != NULL &&
1304713047
((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) {
1304813048
byte copyRandom = ((WOLFSSL_ECH*)echX->data)->innerCount == 0;
13049-
1305013049
/* reset the inOutIdx to the outer start */
1305113050
*inOutIdx = echInOutIdx;
1305213051
/* call again with the inner hello */

wolfssl/internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2964,6 +2964,8 @@ typedef struct Options Options;
29642964
#define TLSXT_KEY_QUIC_TP_PARAMS 0x0039 /* RFC 9001, ch. 8.2 */
29652965
#define TLSXT_ECH 0xfe0d /* from */
29662966
/* draft-ietf-tls-esni-13 */
2967+
#define TLSXT_ECH_OUTER_EXTENSIONS 0xfd00 /* from
2968+
draft-ietf-tls-esni-13 */
29672969
/* The 0xFF section is experimental/custom/personal use */
29682970
#define TLSXT_CKS 0xff92 /* X9.146 */
29692971
#define TLSXT_RENEGOTIATION_INFO 0xff01

0 commit comments

Comments
 (0)