@@ -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;
0 commit comments