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