From 986c19371b6a0b510bf85e2071e0d1a552e530b8 Mon Sep 17 00:00:00 2001 From: Tobias Frauenschlaeger Date: Fri, 21 Aug 2026 14:18:13 +0000 Subject: [PATCH 1/5] Hold the supported groups extension in one allocation The supported groups extension stored its named groups as a linked list with one node per two byte group identifier. A client advertising 16 groups made 16 allocations for 32 bytes of wire data, and a server made a second set of nodes for the intersection with the client's list. Every insert also walked the list to check for a duplicate. Replace the list with a single block holding a count, a capacity and a flat array of names. The block starts with room for WOLFSSL_MAX_GROUP_COUNT names, the most wolfSSL_set_groups() accepts, so a handshake makes one allocation per list; it doubles only for a peer that sends more. Inserts and lookups index the array instead of chasing pointers, and freeing the extension is a single XFREE. --- src/tls.c | 416 +++++++++++++++++++++++-------------- tests/api.c | 1 + tests/api/test_tls_ext.c | 62 ++++++ tests/api/test_tls_ext.h | 1 + tests/api/test_tls_parse.c | 16 +- wolfssl/internal.h | 14 +- 6 files changed, 341 insertions(+), 169 deletions(-) diff --git a/src/tls.c b/src/tls.c index e060476f4b0..4719b3ba5fb 100644 --- a/src/tls.c +++ b/src/tls.c @@ -5004,21 +5004,86 @@ int TLSX_IsGroupSupported(int namedGroup, int side) define HAVE_ECC. Alternatively use FFDHE for DH cipher suites. #endif -static int TLSX_SupportedCurve_New(SupportedCurve** curve, word16 name, +/* Names the first allocation holds; the list doubles from here. */ +#define SUPPORTED_CURVES_INIT_CNT WOLFSSL_MAX_GROUP_COUNT +/* Most names the extension can encode: both its lengths are a word16. */ +#define SUPPORTED_CURVES_MAX_CNT ((0xFFFFU - OPAQUE16_LEN) / OPAQUE16_LEN) +/* Append() grows with (word16)(count + 1), which must not wrap. */ +wc_static_assert(SUPPORTED_CURVES_MAX_CNT < 0xFFFFU); +wc_static_assert(OPAQUE16_LEN + SUPPORTED_CURVES_MAX_CNT * OPAQUE16_LEN <= + 0xFFFFU); + +/* Make room for the requested number of curve names. + * + * curves The list to grow, allocated when NULL. Updated on reallocation. + * cnt The number of names the list has to hold. + * heap The heap used for allocation. + * returns 0 on success, otherwise failure. + */ +static int TLSX_SupportedCurve_Grow(SupportedCurves** curves, word16 cnt, void* heap) { - if (curve == NULL) + SupportedCurves* list = *curves; + SupportedCurves* newList; + word32 cap = SUPPORTED_CURVES_INIT_CNT; + + if ((list != NULL) && (cnt <= list->cap)) + return 0; + if (cnt > SUPPORTED_CURVES_MAX_CNT) return BAD_FUNC_ARG; - (void)heap; + while (cap < cnt) + cap *= 2; + /* The doubling can overshoot the most the extension can encode. */ + if (cap > SUPPORTED_CURVES_MAX_CNT) + cap = SUPPORTED_CURVES_MAX_CNT; - *curve = (SupportedCurve*)XMALLOC(sizeof(SupportedCurve), heap, - DYNAMIC_TYPE_TLSX); - if (*curve == NULL) + newList = (SupportedCurves*)XMALLOC(sizeof(SupportedCurves) + + cap * sizeof(word16), heap, + DYNAMIC_TYPE_TLSX); + if (newList == NULL) return MEMORY_E; - (*curve)->name = name; - (*curve)->next = NULL; + newList->count = 0; + newList->cap = (word16)cap; + + if (list != NULL) { + newList->count = list->count; + XMEMCPY(newList->name, list->name, list->count * sizeof(word16)); + XFREE(list, heap, DYNAMIC_TYPE_TLSX); + } + + *curves = newList; + + return 0; +} + +/* Allocate a list holding one name. + * + * *curves is written unconditionally, so it must not already hold a list: + * unlike TLSX_SupportedCurve_Grow(), this does not read what is there and + * would leak it. Both callers pass a NULL-initialised pointer. + * + * curves Receives the new list. + * name The first name to store. + * heap The heap used for allocation. + * returns 0 on success, otherwise failure. + */ +static int TLSX_SupportedCurve_New(SupportedCurves** curves, word16 name, + void* heap) +{ + SupportedCurves* list = NULL; + int ret; + + if (curves == NULL) + return BAD_FUNC_ARG; + + ret = TLSX_SupportedCurve_Grow(&list, 1, heap); + if (ret != 0) + return ret; + + list->name[list->count++] = name; + *curves = list; return 0; } @@ -5095,14 +5160,11 @@ static int TLSX_PointFormat_New(PointFormats** formats, byte format, return 0; } -static void TLSX_SupportedCurve_FreeAll(SupportedCurve* list, void* heap) +static void TLSX_SupportedCurve_FreeAll(SupportedCurves* list, void* heap) { - SupportedCurve* curve; - - while ((curve = list)) { - list = curve->next; - XFREE(curve, heap, DYNAMIC_TYPE_TLSX); - } + /* Pushed without a list when no group the peer offered is supported. */ + if (list != NULL) + XFREE(list, heap, DYNAMIC_TYPE_TLSX); (void)heap; } @@ -5113,26 +5175,30 @@ static void TLSX_PointFormat_FreeAll(PointFormats* list, void* heap) (void)heap; } -static int TLSX_SupportedCurve_Append(SupportedCurve* list, word16 name, +static int TLSX_SupportedCurve_Append(SupportedCurves** curves, word16 name, void* heap) { - int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); - - while (list) { - if (list->name == name) { - ret = 0; /* curve already in use */ - break; - } + SupportedCurves* list; + int ret; + word16 i; - if (list->next == NULL) { - ret = TLSX_SupportedCurve_New(&list->next, name, heap); - break; - } + if ((curves == NULL) || (*curves == NULL)) + return BAD_FUNC_ARG; - list = list->next; + list = *curves; + for (i = 0; i < list->count; i++) { + if (list->name[i] == name) + return 0; /* curve already in use */ } - return ret; + ret = TLSX_SupportedCurve_Grow(curves, (word16)(list->count + 1), heap); + if (ret != 0) + return ret; + + list = *curves; + list->name[list->count++] = name; + + return 0; } static int TLSX_PointFormat_Append(PointFormats** formats, byte format, @@ -5308,15 +5374,12 @@ static void TLSX_PointFormat_ValidateResponse(WOLFSSL* ssl, byte* semaphore) #if !defined(NO_WOLFSSL_CLIENT) || defined(WOLFSSL_TLS13) -static word16 TLSX_SupportedCurve_GetSize(SupportedCurve* list) +static word16 TLSX_SupportedCurve_GetSize(SupportedCurves* list) { - SupportedCurve* curve; word16 length = OPAQUE16_LEN; /* list length */ - while ((curve = list)) { - list = curve->next; - length += OPAQUE16_LEN; /* curve length */ - } + if (list != NULL) + length = (word16)(length + list->count * OPAQUE16_LEN); return length; } @@ -5335,14 +5398,16 @@ static word16 TLSX_PointFormat_GetSize(PointFormats* list) #if !defined(NO_WOLFSSL_CLIENT) || defined(WOLFSSL_TLS13) -static word16 TLSX_SupportedCurve_Write(SupportedCurve* list, byte* output) +static word16 TLSX_SupportedCurve_Write(SupportedCurves* list, byte* output) { word16 offset = OPAQUE16_LEN; + word16 i; - while (list) { - c16toa(list->name, output + offset); - offset += OPAQUE16_LEN; - list = list->next; + if (list != NULL) { + for (i = 0; i < list->count; i++) { + c16toa(list->name[i], output + offset); + offset += OPAQUE16_LEN; + } } c16toa(offset - OPAQUE16_LEN, output); /* writing list length */ @@ -5423,7 +5488,7 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, WOLFSSL_NAMED_GROUP_IS_FFDHE(name)) { TLSX* ext = TLSX_Find(*extensions, TLSX_SUPPORTED_GROUPS); if (ext == NULL) { - SupportedCurve* curve = NULL; + SupportedCurves* curve = NULL; ret = TLSX_SupportedCurve_New(&curve, name, ssl->heap); if (ret == 0) { ret = TLSX_Push(extensions, TLSX_SUPPORTED_GROUPS, @@ -5434,7 +5499,7 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, } else { ret = TLSX_SupportedCurve_Append( - (SupportedCurve*)ext->data, name, ssl->heap); + (SupportedCurves**)&ext->data, name, ssl->heap); } if (ret != 0) break; @@ -5453,18 +5518,24 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, } else { /* Find the intersection with what the user has set */ - SupportedCurve* commonCurves = NULL; - for (; offset < length; offset += OPAQUE16_LEN) { - SupportedCurve* foundCurve = (SupportedCurve*)extension->data; + SupportedCurves* ourCurves = (SupportedCurves*)extension->data; + SupportedCurves* commonCurves = NULL; + word16 i; + + for (; (ourCurves != NULL) && (offset < length); + offset += OPAQUE16_LEN) { ato16(input + offset, &name); - while (foundCurve != NULL && foundCurve->name != name) - foundCurve = foundCurve->next; + for (i = 0; i < ourCurves->count; i++) { + if (ourCurves->name[i] == name) + break; + } - if (foundCurve != NULL) { + if (i < ourCurves->count) { ret = commonCurves == NULL ? TLSX_SupportedCurve_New(&commonCurves, name, ssl->heap) : - TLSX_SupportedCurve_Append(commonCurves, name, ssl->heap); + TLSX_SupportedCurve_Append(&commonCurves, name, + ssl->heap); if (ret != 0) break; } @@ -5475,7 +5546,8 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, !TLSX_IsGroupSupported(name, ssl->options.side)) { ret = commonCurves == NULL ? TLSX_SupportedCurve_New(&commonCurves, name, ssl->heap) : - TLSX_SupportedCurve_Append(commonCurves, name, ssl->heap); + TLSX_SupportedCurve_Append(&commonCurves, name, + ssl->heap); if (ret != 0) break; } @@ -5488,7 +5560,7 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, ret = ECC_CURVE_ERROR; if (ret == 0) { /* Now swap out the curves in the extension */ - TLSX_SupportedCurve_FreeAll((SupportedCurve*)extension->data, + TLSX_SupportedCurve_FreeAll((SupportedCurves*)extension->data, ssl->heap); extension->data = commonCurves; commonCurves = NULL; @@ -5517,7 +5589,9 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) TLSX* priority = NULL; TLSX* ext = NULL; word16 name; - SupportedCurve* curve; + word16 i; + SupportedCurves* curves; + SupportedCurves* tmp; extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); /* May be doing PSK with no key exchange. */ @@ -5537,24 +5611,27 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) return 0; } - curve = (SupportedCurve*)ext->data; - name = curve->name; + curves = (SupportedCurves*)ext->data; + if ((curves == NULL) || (curves->count == 0)) { + TLSX_FreeAll(priority, ssl->heap); + return 0; + } + name = curves->name[0]; - curve = (SupportedCurve*)extension->data; - while (curve != NULL) { - if (curve->name == name) + curves = (SupportedCurves*)extension->data; + for (i = 0; (curves != NULL) && (i < curves->count); i++) { + if (curves->name[i] == name) break; - curve = curve->next; } - if (curve == NULL) { + if ((curves == NULL) || (i == curves->count)) { /* Couldn't find the preferred group in client list. */ extension->resp = 1; /* Send server list back and free client list. */ - curve = (SupportedCurve*)extension->data; + tmp = (SupportedCurves*)extension->data; extension->data = ext->data; - ext->data = curve; + ext->data = tmp; } TLSX_FreeAll(priority, ssl->heap); @@ -5567,22 +5644,28 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) #if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) #ifdef HAVE_FFDHE #ifdef HAVE_PUBLIC_FFDHE -static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurve* clientGroup, - SupportedCurve* serverGroup) +static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurves* clientGroups, + SupportedCurves* serverGroups) { int ret = 0; - SupportedCurve* group; const DhParams* params = NULL; + word16 name = WOLFSSL_NAMED_GROUP_INVALID; + word16 i; + word16 j; + int found = 0; - for (; serverGroup != NULL; serverGroup = serverGroup->next) { - if (!WOLFSSL_NAMED_GROUP_IS_FFDHE(serverGroup->name)) + if ((clientGroups == NULL) || (serverGroups == NULL)) + return 0; + + for (i = 0; i < serverGroups->count; i++) { + if (!WOLFSSL_NAMED_GROUP_IS_FFDHE(serverGroups->name[i])) continue; - for (group = clientGroup; group != NULL; group = group->next) { - if (serverGroup->name != group->name) + for (j = 0; j < clientGroups->count; j++) { + if (serverGroups->name[i] != clientGroups->name[j]) continue; - switch (serverGroup->name) { + switch (serverGroups->name[i]) { #ifdef HAVE_FFDHE_2048 case WOLFSSL_FFDHE_2048: params = wc_Dh_ffdhe2048_Get(); @@ -5617,23 +5700,26 @@ static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurve* clientGroup, } if (params->p_len >= ssl->options.minDhKeySz && params->p_len <= ssl->options.maxDhKeySz) { + found = 1; break; } } if (ret != 0) break; - if ((group != NULL) && (serverGroup->name == group->name)) + if (found) { + name = serverGroups->name[i]; break; + } } - if ((ret == 0) && (serverGroup != NULL) && (params != NULL)) { + if ((ret == 0) && found && (params != NULL)) { ssl->buffers.serverDH_P.buffer = (unsigned char *)params->p; ssl->buffers.serverDH_P.length = params->p_len; ssl->buffers.serverDH_G.buffer = (unsigned char *)params->g; ssl->buffers.serverDH_G.length = params->g_len; - ssl->namedGroup = serverGroup->name; + ssl->namedGroup = name; #if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) ssl->options.dhDoKeyTest = 0; @@ -5644,22 +5730,29 @@ static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurve* clientGroup, return ret; } #else -static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurve* clientGroup, - SupportedCurve* serverGroup) +static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurves* clientGroups, + SupportedCurves* serverGroups) { int ret = 0; - SupportedCurve* group; word32 p_len; + word16 name = WOLFSSL_NAMED_GROUP_INVALID; + word16 i; + word16 j; + int found = 0; - for (; serverGroup != NULL; serverGroup = serverGroup->next) { - if (!WOLFSSL_NAMED_GROUP_IS_FFDHE(serverGroup->name)) + if ((clientGroups == NULL) || (serverGroups == NULL)) + return 0; + + for (i = 0; i < serverGroups->count; i++) { + if (!WOLFSSL_NAMED_GROUP_IS_FFDHE(serverGroups->name[i])) continue; - for (group = clientGroup; group != NULL; group = group->next) { - if (serverGroup->name != group->name) + for (j = 0; j < clientGroups->count; j++) { + if (serverGroups->name[i] != clientGroups->name[j]) continue; - ret = wc_DhGetNamedKeyParamSize(serverGroup->name, &p_len, NULL, NULL); + ret = wc_DhGetNamedKeyParamSize(serverGroups->name[i], &p_len, NULL, + NULL); if (ret == 0) { if (p_len == 0) { ret = BAD_FUNC_ARG; @@ -5667,6 +5760,7 @@ static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurve* clientGroup, } if (p_len >= ssl->options.minDhKeySz && p_len <= ssl->options.maxDhKeySz) { + found = 1; break; } } @@ -5674,16 +5768,18 @@ static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurve* clientGroup, if (ret != 0) break; - if ((group != NULL) && (serverGroup->name == group->name)) + if (found) { + name = serverGroups->name[i]; break; + } } - if ((ret == 0) && (serverGroup != NULL)) { + if ((ret == 0) && found) { word32 pSz, gSz; ssl->buffers.serverDH_P.buffer = NULL; ssl->buffers.serverDH_G.buffer = NULL; - ret = wc_DhGetNamedKeyParamSize(serverGroup->name, &pSz, &gSz, NULL); + ret = wc_DhGetNamedKeyParamSize(name, &pSz, &gSz, NULL); if (ret == 0) { ssl->buffers.serverDH_P.buffer = (byte*)XMALLOC(pSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); @@ -5701,7 +5797,7 @@ static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurve* clientGroup, ssl->buffers.serverDH_G.length = gSz; } if (ret == 0) { - ret = wc_DhCopyNamedKey(serverGroup->name, + ret = wc_DhCopyNamedKey(name, ssl->buffers.serverDH_P.buffer, &pSz, ssl->buffers.serverDH_G.buffer, &gSz, NULL, NULL); @@ -5709,7 +5805,7 @@ static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurve* clientGroup, if (ret == 0) { ssl->buffers.weOwnDH = 1; - ssl->namedGroup = serverGroup->name; + ssl->namedGroup = name; #if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) ssl->options.dhDoKeyTest = 0; @@ -5751,17 +5847,17 @@ int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl) TLSX* ext = NULL; #endif TLSX* extension; - SupportedCurve* clientGroup; - SupportedCurve* group; + SupportedCurves* clientGroups; + word16 i; int found = 0; extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); /* May be doing PSK with no key exchange. */ if (extension == NULL) return 0; - clientGroup = (SupportedCurve*)extension->data; - for (group = clientGroup; group != NULL; group = group->next) { - if (WOLFSSL_NAMED_GROUP_IS_FFDHE(group->name)) { + clientGroups = (SupportedCurves*)extension->data; + for (i = 0; (clientGroups != NULL) && (i < clientGroups->count); i++) { + if (WOLFSSL_NAMED_GROUP_IS_FFDHE(clientGroups->name[i])) { found = 1; break; } @@ -5785,16 +5881,14 @@ int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl) #ifdef HAVE_FFDHE ret = TLSX_PopulateSupportedGroups(ssl, &priority); if (ret == WOLFSSL_SUCCESS) { - SupportedCurve* serverGroup; - ext = TLSX_Find(priority, TLSX_SUPPORTED_GROUPS); if (ext == NULL) { WOLFSSL_MSG("Could not find supported groups extension"); ret = 0; } else { - serverGroup = (SupportedCurve*)ext->data; - ret = tlsx_ffdhe_find_group(ssl, clientGroup, serverGroup); + ret = tlsx_ffdhe_find_group(ssl, clientGroups, + (SupportedCurves*)ext->data); } } @@ -5815,17 +5909,17 @@ int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl) int TLSX_SupportedCurve_IsSupported(WOLFSSL* ssl, word16 name) { TLSX* extension; - SupportedCurve* curve; + SupportedCurves* curves; + word16 i; extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); if (extension == NULL) return 0; - curve = (SupportedCurve*)extension->data; - while (curve != NULL) { - if (curve->name == name) + curves = (SupportedCurves*)extension->data; + for (i = 0; (curves != NULL) && (i < curves->count); i++) { + if (curves->name[i] == name) return 1; - curve = curve->next; } return 0; @@ -5841,18 +5935,18 @@ int TLSX_SupportedCurve_IsSupported(WOLFSSL* ssl, word16 name) int TLSX_SupportedCurve_Preferred(WOLFSSL* ssl, int checkSupported) { TLSX* extension; - SupportedCurve* curve; + SupportedCurves* curves; + word16 i; extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); if (extension == NULL) return BAD_FUNC_ARG; - curve = (SupportedCurve*)extension->data; - while (curve != NULL) { + curves = (SupportedCurves*)extension->data; + for (i = 0; (curves != NULL) && (i < curves->count); i++) { if (!checkSupported || - TLSX_IsGroupSupported(curve->name, ssl->options.side)) - return curve->name; - curve = curve->next; + TLSX_IsGroupSupported(curves->name[i], ssl->options.side)) + return curves->name[i]; } return BAD_FUNC_ARG; @@ -5907,18 +6001,20 @@ static int TLSX_PointFormat_Parse(WOLFSSL* ssl, const byte* input, #if defined(HAVE_ECC) || defined(HAVE_CURVE25519) || defined(HAVE_CURVE448) int TLSX_ValidateSupportedCurves(const WOLFSSL* ssl, byte first, byte second, word32* ecdhCurveOID) { - TLSX* extension = NULL; - SupportedCurve* curve = NULL; - word32 oid = 0; - word32 defOid = 0; - word32 defSz = 80; /* Maximum known curve size is 66. */ - word32 nextOid = 0; - word32 nextSz = 80; /* Maximum known curve size is 66. */ - word32 currOid = ssl->ecdhCurveOID; - int ephmSuite = 0; - word16 octets = 0; /* according to 'ecc_set_type ecc_sets[];' */ - int key = 0; /* validate key */ - int foundCurve = 0; /* Found at least one supported curve */ + TLSX* extension = NULL; + SupportedCurves* curves = NULL; + word16 curveName = 0; + word16 i = 0; + word32 oid = 0; + word32 defOid = 0; + word32 defSz = 80; /* Maximum known curve size is 66. */ + word32 nextOid = 0; + word32 nextSz = 80; /* Maximum known curve size is 66. */ + word32 currOid = ssl->ecdhCurveOID; + int ephmSuite = 0; + word16 octets = 0; /* per 'ecc_set_type ecc_sets[];' */ + int key = 0; /* validate key */ + int foundCurve = 0; /* Found at least one supported curve */ (void)oid; @@ -5940,19 +6036,19 @@ int TLSX_ValidateSupportedCurves(const WOLFSSL* ssl, byte first, byte second, if (!extension) return 1; /* no suite restriction */ - for (curve = (SupportedCurve*)extension->data; - curve && !key; - curve = curve->next) { + curves = (SupportedCurves*)extension->data; + for (i = 0; (curves != NULL) && (i < curves->count) && !key; i++) { + curveName = curves->name[i]; #ifdef OPENSSL_EXTRA /* skip if name is not in supported ECC range * or disabled by user */ - if (wolfSSL_curve_is_disabled(ssl, curve->name)) + if (wolfSSL_curve_is_disabled(ssl, curveName)) continue; #endif /* find supported curve */ - switch (curve->name) { + switch (curveName) { #ifdef HAVE_ECC #if (defined(HAVE_ECC160) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 160 #ifndef NO_ECC_SECP @@ -6270,12 +6366,13 @@ int TLSX_SupportedCurve_Copy(TLSX* src, TLSX** dst, void* heap) extension = TLSX_Find(src, TLSX_SUPPORTED_GROUPS); if (extension != NULL) { - SupportedCurve* curve; - for (curve = (SupportedCurve*)extension->data; curve != NULL; - curve = curve->next) { + SupportedCurves* curves = (SupportedCurves*)extension->data; + word16 i; + + for (i = 0; (curves != NULL) && (i < curves->count); i++) { /* Copying an already validated list - don't drop a group based on * the side, so accept when either side has the crypto support. */ - ret = TLSX_UseSupportedCurve(dst, curve->name, heap, + ret = TLSX_UseSupportedCurve(dst, curves->name[i], heap, WOLFSSL_NEITHER_END); if (ret != WOLFSSL_SUCCESS) return MEMORY_E; @@ -6288,7 +6385,7 @@ int TLSX_SupportedCurve_Copy(TLSX* src, TLSX** dst, void* heap) int TLSX_UseSupportedCurve(TLSX** extensions, word16 name, void* heap, int side) { TLSX* extension = NULL; - SupportedCurve* curve = NULL; + SupportedCurves* curves = NULL; int ret; if (extensions == NULL) { @@ -6302,33 +6399,36 @@ int TLSX_UseSupportedCurve(TLSX** extensions, word16 name, void* heap, int side) extension = TLSX_Find(*extensions, TLSX_SUPPORTED_GROUPS); if (!extension) { - ret = TLSX_SupportedCurve_New(&curve, name, heap); + ret = TLSX_SupportedCurve_New(&curves, name, heap); if (ret != 0) return ret; - ret = TLSX_Push(extensions, TLSX_SUPPORTED_GROUPS, curve, heap); + ret = TLSX_Push(extensions, TLSX_SUPPORTED_GROUPS, curves, heap); if (ret != 0) { - XFREE(curve, heap, DYNAMIC_TYPE_TLSX); + XFREE(curves, heap, DYNAMIC_TYPE_TLSX); return ret; } } else { - ret = TLSX_SupportedCurve_Append((SupportedCurve*)extension->data, name, - heap); + ret = TLSX_SupportedCurve_Append((SupportedCurves**)&extension->data, + name, heap); if (ret != 0) return ret; #if defined(WOLFSSL_ML_KEM_USE_OLD_IDS) && \ defined (WOLFSSL_EXTRA_PQC_HYBRIDS) if (name == WOLFSSL_SECP256R1MLKEM512) { - ret = TLSX_SupportedCurve_Append((SupportedCurve*)extension->data, + ret = TLSX_SupportedCurve_Append( + (SupportedCurves**)&extension->data, WOLFSSL_P256_ML_KEM_512_OLD, heap); } else if (name == WOLFSSL_SECP384R1MLKEM768) { - ret = TLSX_SupportedCurve_Append((SupportedCurve*)extension->data, + ret = TLSX_SupportedCurve_Append( + (SupportedCurves**)&extension->data, WOLFSSL_P384_ML_KEM_768_OLD, heap); } else if (name == WOLFSSL_SECP521R1MLKEM1024) { - ret = TLSX_SupportedCurve_Append((SupportedCurve*)extension->data, + ret = TLSX_SupportedCurve_Append( + (SupportedCurves**)&extension->data, WOLFSSL_P521_ML_KEM_1024_OLD, heap); } if (ret != 0) { @@ -11008,8 +11108,9 @@ static int TLSX_SupportedGroups_Find(const WOLFSSL* ssl, word16 name, TLSX* extensions) { #ifdef HAVE_SUPPORTED_CURVES - TLSX* extension; - SupportedCurve* curve = NULL; + TLSX* extension; + SupportedCurves* curves = NULL; + word16 i; if ((extension = TLSX_Find(extensions, TLSX_SUPPORTED_GROUPS)) == NULL) { if ((extension = TLSX_Find(ssl->ctx->extensions, @@ -11018,8 +11119,9 @@ static int TLSX_SupportedGroups_Find(const WOLFSSL* ssl, word16 name, } } - for (curve = (SupportedCurve*)extension->data; curve; curve = curve->next) { - if (curve->name == name) + curves = (SupportedCurves*)extension->data; + for (i = 0; (curves != NULL) && (i < curves->count); i++) { + if (curves->name[i] == name) return 1; } #endif @@ -12072,43 +12174,41 @@ int TLSX_KeyShare_SetSupported(const WOLFSSL* ssl, TLSX** extensions) { int ret; #ifdef HAVE_SUPPORTED_CURVES - TLSX* extension; - SupportedCurve* curve = NULL; - SupportedCurve* preferredCurve = NULL; - word16 name = WOLFSSL_NAMED_GROUP_INVALID; - KeyShareEntry* kse = NULL; - int preferredRank = WOLFSSL_WORST_GROUP_RANK; - int rank; + TLSX* extension; + SupportedCurves* curves = NULL; + word16 name = WOLFSSL_NAMED_GROUP_INVALID; + word16 i; + KeyShareEntry* kse = NULL; + int preferredRank = WOLFSSL_WORST_GROUP_RANK; + int rank; extension = TLSX_Find(*extensions, TLSX_SUPPORTED_GROUPS); if (extension != NULL) - curve = (SupportedCurve*)extension->data; - for (; curve != NULL; curve = curve->next) { + curves = (SupportedCurves*)extension->data; + for (i = 0; (curves != NULL) && (i < curves->count); i++) { /* Use server's preference order. Common group was found but key share * was missing */ - if (!TLSX_IsGroupSupported(curve->name, ssl->options.side)) + if (!TLSX_IsGroupSupported(curves->name[i], ssl->options.side)) continue; - if (wolfSSL_curve_is_disabled(ssl, curve->name)) + if (wolfSSL_curve_is_disabled(ssl, curves->name[i])) continue; - rank = TLSX_KeyShare_GroupRank(ssl, curve->name); + rank = TLSX_KeyShare_GroupRank(ssl, curves->name[i]); if (rank == -1) continue; if (rank < preferredRank) { - preferredCurve = curve; + name = curves->name[i]; preferredRank = rank; } } - curve = preferredCurve; - if (curve == NULL) { + if (name == WOLFSSL_NAMED_GROUP_INVALID) { /* No mutual group exists. An HRR may request only a group the * client advertised in supported_groups, so it cannot recover. * RFC 8446 4.2.1. */ WOLFSSL_ERROR_VERBOSE(KEY_SHARE_ERROR); return KEY_SHARE_ERROR; } - name = curve->name; #ifdef WOLFSSL_ASYNC_CRYPT /* Check the old key share data list. */ @@ -15665,7 +15765,7 @@ void TLSX_FreeAll(TLSX* list, void* heap) case TLSX_SUPPORTED_GROUPS: WOLFSSL_MSG("Supported Groups extension free"); - EC_FREE_ALL((SupportedCurve*)extension->data, heap); + EC_FREE_ALL((SupportedCurves*)extension->data, heap); break; case TLSX_EC_POINT_FORMATS: @@ -15908,7 +16008,7 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, break; case TLSX_SUPPORTED_GROUPS: - length += EC_GET_SIZE((SupportedCurve*)extension->data); + length += EC_GET_SIZE((SupportedCurves*)extension->data); break; case TLSX_EC_POINT_FORMATS: @@ -16179,7 +16279,7 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, case TLSX_SUPPORTED_GROUPS: WOLFSSL_MSG("Supported Groups extension to write"); - offset += EC_WRITE((SupportedCurve*)extension->data, + offset += EC_WRITE((SupportedCurves*)extension->data, output + offset); break; diff --git a/tests/api.c b/tests/api.c index f0da14b6d3e..d97197d3c94 100644 --- a/tests/api.c +++ b/tests/api.c @@ -47156,6 +47156,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_TLSX_SignatureAlgorithmsCert_parse), TEST_DECL(test_TLSX_PointFormat_list), TEST_DECL(test_TLSX_MaxFragment_value), + TEST_DECL(test_TLSX_SupportedCurve_append), TEST_DECL(test_TLSX_PointFormat_uncompressed_required), TEST_DECL(test_wolfSSL_CTX_add_client_custom_ext), TEST_DECL(test_wolfSSL_custom_ext_handshake), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index a4eefb08ee2..fd0b5bb9399 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3424,6 +3424,68 @@ int test_TLSX_MaxFragment_value(void) return EXPECT_RESULT(); } +/* Appending keeps every group the build accepts, in order, and drops a + * repeat. */ +int test_TLSX_SupportedCurve_append(void) +{ + EXPECT_DECLS; +#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_SUPPORTED_CURVES) && defined(HAVE_TLS_EXTENSIONS) + /* Every standalone group the API knows; a build rejects the rest. */ + static const word16 known[] = { + WOLFSSL_ECC_SECP160K1, WOLFSSL_ECC_SECP160R1, WOLFSSL_ECC_SECP160R2, + WOLFSSL_ECC_SECP192K1, WOLFSSL_ECC_SECP192R1, WOLFSSL_ECC_SECP224K1, + WOLFSSL_ECC_SECP224R1, WOLFSSL_ECC_SECP256K1, WOLFSSL_ECC_SECP256R1, + WOLFSSL_ECC_SECP384R1, WOLFSSL_ECC_SECP521R1, + WOLFSSL_ECC_BRAINPOOLP256R1, WOLFSSL_ECC_BRAINPOOLP384R1, + WOLFSSL_ECC_BRAINPOOLP512R1, WOLFSSL_ECC_SM2P256V1, + WOLFSSL_ECC_X25519, WOLFSSL_ECC_X448, + WOLFSSL_FFDHE_2048, WOLFSSL_FFDHE_3072, WOLFSSL_FFDHE_4096, + WOLFSSL_FFDHE_6144, WOLFSSL_FFDHE_8192, + WOLFSSL_ML_KEM_512, WOLFSSL_ML_KEM_768, WOLFSSL_ML_KEM_1024, + WOLFSSL_KYBER_LEVEL1, WOLFSSL_KYBER_LEVEL3, WOLFSSL_KYBER_LEVEL5 + }; + word16 added[sizeof(known) / sizeof(known[0])]; + word16 addedCnt = 0; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* extension = NULL; + SupportedCurves* curves = NULL; + word16 i; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + for (i = 0; (ssl != NULL) && (i < sizeof(known) / sizeof(known[0])); i++) { + if (wolfSSL_UseSupportedCurve(ssl, known[i]) == WOLFSSL_SUCCESS) + added[addedCnt++] = known[i]; + } + ExpectIntGT(addedCnt, 0); + /* A repeat is dropped rather than appended. */ + if (addedCnt > 0) { + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, added[0]), + WOLFSSL_SUCCESS); + } + + if (ssl != NULL) + extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + ExpectNotNull(extension); + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + ExpectNotNull(curves); + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, (int)addedCnt); + ExpectIntLE(curves == NULL ? 1 : (int)curves->count, + curves == NULL ? 0 : (int)curves->cap); + for (i = 0; (curves != NULL) && (i < addedCnt); i++) { + ExpectIntEQ(curves->name[i], added[i]); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* RFC 8422 Section 5.1.2: a client that sends the ec_point_formats extension * MUST include the uncompressed (0) point format. When the uncompressed format * is omitted the server records this (ssl->options.peerNoUncompPF) during diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 3ba806a662b..44c0284fc45 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -70,6 +70,7 @@ int test_TLSX_SupportedCurve_empty_or_unsupported(void); int test_TLSX_SignatureAlgorithmsCert_parse(void); int test_TLSX_PointFormat_list(void); int test_TLSX_MaxFragment_value(void); +int test_TLSX_SupportedCurve_append(void); int test_TLSX_PointFormat_uncompressed_required(void); int test_wolfSSL_CTX_add_client_custom_ext(void); int test_wolfSSL_custom_ext_handshake(void); diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 619461048c3..fe9ece85c0b 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -153,15 +153,17 @@ TEST_TLS_PARSE_UNUSED static int test_tls_parse_push_curve(TLSX** extensions, WOLFSSL* ssl, word16 name) { - SupportedCurve* curve = (SupportedCurve*)XMALLOC(sizeof(SupportedCurve), - ssl->heap, DYNAMIC_TYPE_TLSX); - if (curve == NULL) + SupportedCurves* curves = (SupportedCurves*)XMALLOC( + sizeof(SupportedCurves) + sizeof(word16), ssl->heap, + DYNAMIC_TYPE_TLSX); + if (curves == NULL) return WC_NO_ERR_TRACE(MEMORY_E); - curve->name = name; - curve->next = NULL; - if (TLSX_Push(extensions, TLSX_SUPPORTED_GROUPS, curve, ssl->heap) != 0) { + curves->count = 1; + curves->cap = 1; + curves->name[0] = name; + if (TLSX_Push(extensions, TLSX_SUPPORTED_GROUPS, curves, ssl->heap) != 0) { /* TLSX_New failed before it stored curve, so the entry is still ours. */ - XFREE(curve, ssl->heap, DYNAMIC_TYPE_TLSX); + XFREE(curves, ssl->heap, DYNAMIC_TYPE_TLSX); return WC_NO_ERR_TRACE(MEMORY_E); } return 0; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index a6333cd5653..2f9baee1a5a 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3916,10 +3916,16 @@ WOLFSSL_LOCAL void TLSX_CertificateAuthorities_FreeAll( /** Supported Elliptic Curves - RFC 4492 (session 4) */ #ifdef HAVE_SUPPORTED_CURVES -typedef struct SupportedCurve { - word16 name; /* Curve Names */ - struct SupportedCurve* next; /* List Behavior */ -} SupportedCurve; +typedef struct SupportedCurves { + word16 count; /* Curve names in use */ + word16 cap; /* Curve names allocated */ + /* Ignore "nonstandard extension used : zero-sized array in struct/union" + * MSVC warning */ + #ifdef _MSC_VER + #pragma warning(disable: 4200) + #endif + word16 name[WC_FLEXIBLE_ARRAY_SIZE]; /* Curve names */ +} SupportedCurves; typedef struct PointFormats { byte count; /* Formats in use */ From c483f89649d48d6a02c58a69268fee584a37362c Mon Sep 17 00:00:00 2001 From: Tobias Frauenschlaeger Date: Fri, 21 Aug 2026 22:19:26 +0000 Subject: [PATCH 2/5] Intersect the supported groups in place A server with its own supported groups list built a second list holding the groups it has in common with the client, swapped that into the extension and freed the original. Now that the groups are a flat array, the common ones are moved to the front of the server's own list instead, so the intersection needs no allocation. The kept groups stay in the client's order, as before. A name that has already been moved sits outside the range still searched, so a group repeated in the client list is added once. Adds a test for the resulting list and for the TLS 1.2 handshake failure when no group is common. --- src/tls.c | 68 +++++------ tests/api.c | 2 + tests/api/test_tls_ext.c | 235 +++++++++++++++++++++++++++++++++++++ tests/api/test_tls_ext.h | 2 + tests/api/test_tls_parse.c | 75 +++++++++++- 5 files changed, 340 insertions(+), 42 deletions(-) diff --git a/src/tls.c b/src/tls.c index 4719b3ba5fb..0eb492c8838 100644 --- a/src/tls.c +++ b/src/tls.c @@ -5060,11 +5060,7 @@ static int TLSX_SupportedCurve_Grow(SupportedCurves** curves, word16 cnt, /* Allocate a list holding one name. * - * *curves is written unconditionally, so it must not already hold a list: - * unlike TLSX_SupportedCurve_Grow(), this does not read what is there and - * would leak it. Both callers pass a NULL-initialised pointer. - * - * curves Receives the new list. + * curves Receives the new list. Must not already hold one, which would leak. * name The first name to store. * heap The heap used for allocation. * returns 0 on success, otherwise failure. @@ -5075,7 +5071,7 @@ static int TLSX_SupportedCurve_New(SupportedCurves** curves, word16 name, SupportedCurves* list = NULL; int ret; - if (curves == NULL) + if ((curves == NULL) || (*curves != NULL)) return BAD_FUNC_ARG; ret = TLSX_SupportedCurve_Grow(&list, 1, heap); @@ -5517,55 +5513,53 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, } } else { - /* Find the intersection with what the user has set */ - SupportedCurves* ourCurves = (SupportedCurves*)extension->data; - SupportedCurves* commonCurves = NULL; + /* Intersect in place: the groups both sides have move to the front + * of our own list, in the peer's order. */ + SupportedCurves* curves = (SupportedCurves*)extension->data; + word16 common = 0; word16 i; - for (; (ourCurves != NULL) && (offset < length); - offset += OPAQUE16_LEN) { + for (; offset < length; offset += OPAQUE16_LEN) { + word16 count = (curves != NULL) ? curves->count : 0; + ato16(input + offset, &name); - for (i = 0; i < ourCurves->count; i++) { - if (ourCurves->name[i] == name) + for (i = 0; i < count; i++) { + if (curves->name[i] == name) break; } - - if (i < ourCurves->count) { - ret = commonCurves == NULL ? - TLSX_SupportedCurve_New(&commonCurves, name, ssl->heap) : - TLSX_SupportedCurve_Append(&commonCurves, name, - ssl->heap); - if (ret != 0) - break; + if (i < count) { + /* Below common it is a repeat in the peer list. */ + if (i >= common) { + curves->name[i] = curves->name[common]; + curves->name[common++] = name; + } } #if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) && \ !defined(NO_WOLFSSL_SERVER) - /* RFC 7919 Section 4 (see comment above). */ + /* RFC 7919 Section 4 (see above): an unsupported FFDHE codepoint + * is not in our list yet, so append it before keeping it. */ else if (isRequest && WOLFSSL_NAMED_GROUP_IS_FFDHE(name) && !TLSX_IsGroupSupported(name, ssl->options.side)) { - ret = commonCurves == NULL ? - TLSX_SupportedCurve_New(&commonCurves, name, ssl->heap) : - TLSX_SupportedCurve_Append(&commonCurves, name, - ssl->heap); + if (curves == NULL) + ret = TLSX_SupportedCurve_New(&curves, name, ssl->heap); + else + ret = TLSX_SupportedCurve_Append(&curves, name, ssl->heap); if (ret != 0) break; + extension->data = curves; + curves->name[curves->count - 1] = curves->name[common]; + curves->name[common++] = name; } #endif /* !NO_DH && !WOLFSSL_NO_TLS12 && !NO_WOLFSSL_SERVER */ } - /* If no common curves return error. In TLS 1.3 we can still try to save - * this by using HRR. */ - if (ret == 0 && commonCurves == NULL && - !IsAtLeastTLSv1_3(ssl->version)) - ret = ECC_CURVE_ERROR; + /* Commit only on success; a failed handshake never reads it again. */ if (ret == 0) { - /* Now swap out the curves in the extension */ - TLSX_SupportedCurve_FreeAll((SupportedCurves*)extension->data, - ssl->heap); - extension->data = commonCurves; - commonCurves = NULL; + if (common == 0 && !IsAtLeastTLSv1_3(ssl->version)) + ret = ECC_CURVE_ERROR; + else if (curves != NULL) + curves->count = common; } - TLSX_SupportedCurve_FreeAll(commonCurves, ssl->heap); } return ret; diff --git a/tests/api.c b/tests/api.c index d97197d3c94..cbbea23cea2 100644 --- a/tests/api.c +++ b/tests/api.c @@ -47157,6 +47157,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_TLSX_PointFormat_list), TEST_DECL(test_TLSX_MaxFragment_value), TEST_DECL(test_TLSX_SupportedCurve_append), + TEST_DECL(test_TLSX_SupportedCurve_grow), + TEST_DECL(test_TLSX_SupportedCurve_intersection), TEST_DECL(test_TLSX_PointFormat_uncompressed_required), TEST_DECL(test_wolfSSL_CTX_add_client_custom_ext), TEST_DECL(test_wolfSSL_custom_ext_handshake), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index fd0b5bb9399..33760d56d11 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3486,6 +3486,241 @@ int test_TLSX_SupportedCurve_append(void) return EXPECT_RESULT(); } +/* Growing past the initial capacity, driven through the RFC 7919 rule: an + * FFDHE codepoint is kept even when this build has no such group. */ +int test_TLSX_SupportedCurve_grow(void) +{ + EXPECT_DECLS; +#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_SUPPORTED_CURVES) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_SERVER) + /* FFDHE range (256..511) codepoints no build implements, so every one + * of them takes the keep path. */ + #define TEST_GROW_CNT (WOLFSSL_MAX_GROUP_COUNT + 4) + #define TEST_GROW_FIRST 0x0140 + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + Suites* suites = NULL; + TLSX* extension = NULL; + SupportedCurves* curves = NULL; + byte peerList[6 + TEST_GROW_CNT * 2]; + /* One name: its capacity is the first allocation's, read not hard coded. */ + const byte oneName[] = { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x01, 0x40 }; + int initCap = -1; + word16 i; + + /* supported_groups (0x000a), extension length, list length, names. */ + peerList[0] = 0x00; + peerList[1] = 0x0a; + peerList[2] = (byte)(((2 + TEST_GROW_CNT * 2) >> 8) & 0xff); + peerList[3] = (byte)((2 + TEST_GROW_CNT * 2) & 0xff); + peerList[4] = (byte)(((TEST_GROW_CNT * 2) >> 8) & 0xff); + peerList[5] = (byte)((TEST_GROW_CNT * 2) & 0xff); + for (i = 0; i < TEST_GROW_CNT; i++) { + peerList[6 + i * 2] = (byte)(((TEST_GROW_FIRST + i) >> 8) & 0xff); + peerList[7 + i * 2] = (byte)((TEST_GROW_FIRST + i) & 0xff); + } + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + + /* First the capacity a single name gets. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, oneName, (word16)sizeof(oneName), + client_hello, suites), 0); + if (ssl != NULL) + extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + ExpectNotNull(extension); + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + ExpectNotNull(curves); + if (curves != NULL) + initCap = (int)curves->cap; + ExpectIntGT(initCap, 0); + wolfSSL_free(ssl); + ssl = NULL; + extension = NULL; + curves = NULL; + + /* No list of our own, so the peer's names are the ones stored. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, peerList, (word16)sizeof(peerList), + client_hello, suites), 0); + + if (ssl != NULL) + extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + ExpectNotNull(extension); + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + ExpectNotNull(curves); + + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, TEST_GROW_CNT); + ExpectIntGE(curves == NULL ? -1 : (int)curves->cap, TEST_GROW_CNT); + /* It really did grow, so the copy path ran. */ + ExpectIntGT(curves == NULL ? -1 : (int)curves->cap, initCap); + for (i = 0; (curves != NULL) && (i < TEST_GROW_CNT); i++) { + ExpectIntEQ(curves->name[i], TEST_GROW_FIRST + i); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + #undef TEST_GROW_CNT + #undef TEST_GROW_FIRST +#endif + return EXPECT_RESULT(); +} + +/* The intersection a server keeps when it has its own list: common groups in + * the peer's order, repeats and unknown groups dropped. */ +int test_TLSX_SupportedCurve_intersection(void) +{ + EXPECT_DECLS; +#if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ + defined(HAVE_SUPPORTED_CURVES) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_ECC) && !defined(NO_ECC_SECP) && ECC_MIN_KEY_SZ <= 256 && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ + (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ + (!defined(NO_WOLFSSL_SERVER) || (defined(WOLFSSL_TLS13) && \ + !defined(WOLFSSL_NO_SERVER_GROUPS_EXT))) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + Suites* suites = NULL; + TLSX* extension = NULL; + SupportedCurves* curves = NULL; + /* supported_groups: secp384r1 (0x0018), an unknown 0xeeee, secp384r1 + * again, secp256r1 (0x0017) */ + const byte peerList[] = { 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, + 0x00, 0x18, 0xee, 0xee, 0x00, 0x18, 0x00, 0x17 }; + /* supported_groups (0x000a), ext len 0x0004, list len 0x0002, 0xeeee */ + const byte noCommon[] = { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0xee, 0xee }; +#ifdef WOLFSSL_TLS13 + /* The same plus a key_share (0x0033) with no shares, which TLS 1.3 + * requires alongside supported_groups. */ + const byte noCommon13[] = { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0xee, 0xee, + 0x00, 0x33, 0x00, 0x02, 0x00, 0x00 }; +#endif + /* FFDHE codepoints this build lacks, interleaved with common groups so + * the append-then-swap runs mid-partition. */ + const byte ffdheMix[] = { 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, + 0x00, 0x18, 0x01, 0x03, 0x00, 0x17, 0x01, 0x04 }; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + /* Our own list, least preferred group of the two last. */ + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP384R1), + WOLFSSL_SUCCESS); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, peerList, (word16)sizeof(peerList), + client_hello, suites), 0); + if (ssl != NULL) + extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + ExpectNotNull(extension); + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + ExpectNotNull(curves); + /* Both common groups kept, in the peer's order, the repeat dropped. */ + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 2); + ExpectIntEQ(curves == NULL ? 0 : curves->name[0], WOLFSSL_ECC_SECP384R1); + ExpectIntEQ(curves == NULL ? 0 : curves->name[1], WOLFSSL_ECC_SECP256R1); + wolfSSL_free(ssl); + ssl = NULL; + + /* No group in common is fatal before TLS 1.3, which has no HRR. */ + extension = NULL; + curves = NULL; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1), + WOLFSSL_SUCCESS); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, noCommon, (word16)sizeof(noCommon), + client_hello, suites), + WC_NO_ERR_TRACE(ECC_CURVE_ERROR)); + wolfSSL_free(ssl); + ssl = NULL; + + /* RFC 7919 Section 4: an FFDHE codepoint this build lacks is appended and + * then swapped into the kept prefix, leaving the peer's order. */ + extension = NULL; + curves = NULL; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP384R1), + WOLFSSL_SUCCESS); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, ffdheMix, (word16)sizeof(ffdheMix), + client_hello, suites), 0); + if (ssl != NULL) + extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + ExpectNotNull(extension); + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + ExpectNotNull(curves); +/* The exact result needs both codepoints to be unimplemented here: having one + * of them shifts secp256r1's position. */ +#if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(HAVE_FFDHE_6144) && \ + !defined(HAVE_FFDHE_8192) + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 4); + ExpectIntEQ(curves == NULL ? 0 : curves->name[0], WOLFSSL_ECC_SECP384R1); + ExpectIntEQ(curves == NULL ? 0 : curves->name[1], WOLFSSL_FFDHE_6144); + ExpectIntEQ(curves == NULL ? 0 : curves->name[2], WOLFSSL_ECC_SECP256R1); + ExpectIntEQ(curves == NULL ? 0 : curves->name[3], WOLFSSL_FFDHE_8192); +#else + /* Otherwise the count and secp256r1's position depend on the build, but + * both ECC groups are kept and secp384r1 leads. */ + ExpectIntGE(curves == NULL ? -1 : (int)curves->count, 2); + ExpectIntEQ(curves == NULL ? 0 : curves->name[0], WOLFSSL_ECC_SECP384R1); + if (curves != NULL) { + word16 j; + for (j = 0; j < curves->count; j++) { + if (curves->name[j] == WOLFSSL_ECC_SECP256R1) + break; + } + ExpectIntLT(j, curves->count); + } +#endif + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + +#ifdef WOLFSSL_TLS13 + /* TLS 1.3 can answer with an HRR, so no common group empties the list. */ + extension = NULL; + curves = NULL; + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1), + WOLFSSL_SUCCESS); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, noCommon13, (word16)sizeof(noCommon13), + client_hello, suites), 0); + if (ssl != NULL) + extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + ExpectNotNull(extension); + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + ExpectNotNull(curves); + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 0); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); +#endif +#endif + return EXPECT_RESULT(); +} + /* RFC 8422 Section 5.1.2: a client that sends the ec_point_formats extension * MUST include the uncompressed (0) point format. When the uncompressed format * is omitted the server records this (ssl->options.peerNoUncompPF) during diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 44c0284fc45..9fa07d19143 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -71,6 +71,8 @@ int test_TLSX_SignatureAlgorithmsCert_parse(void); int test_TLSX_PointFormat_list(void); int test_TLSX_MaxFragment_value(void); int test_TLSX_SupportedCurve_append(void); +int test_TLSX_SupportedCurve_grow(void); +int test_TLSX_SupportedCurve_intersection(void); int test_TLSX_PointFormat_uncompressed_required(void); int test_wolfSSL_CTX_add_client_custom_ext(void); int test_wolfSSL_custom_ext_handshake(void); diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index fe9ece85c0b..9620de78314 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -2331,10 +2331,7 @@ int test_TLSX_SupportedGroups_parse(void) extensions = NULL; #ifdef WOLFSSL_TEST_STATIC_BUILD - /* Offered group matches the local restriction, but recording it - * in the intersection list is forced to fail: a non-zero ret - * reaches the "no common curve" check below without commonCurves - * ever becoming non-NULL through the normal path. */ + /* The intersection is made in place, so it allocates nothing. */ { wolfSSL_Malloc_cb prevM = NULL; wolfSSL_Free_cb prevF = NULL; @@ -2351,12 +2348,73 @@ int test_TLSX_SupportedGroups_parse(void) tls_parse_fail_after = 0; ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, - (word16)sizeof(goodBody), 1, &extensions), + (word16)sizeof(goodBody), 1, &extensions), 0); + ExpectIntEQ(tls_parse_alloc_seen, 0); + + tls_parse_fail_after = -1; + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + } + + /* Unsupported FFDHE codepoints are appended until the list is full, + * then the regrow fails and the list in the extension survives. */ + { + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + byte ffdheMany[OPAQUE16_LEN + + WOLFSSL_MAX_GROUP_COUNT * OPAQUE16_LEN]; + TLSX* ext = NULL; + word16 cap = 0; + word16 len; + word16 i; + + ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, + WOLFSSL_ECC_SECP256R1, ssl->heap, + ssl->options.side), WOLFSSL_SUCCESS); + ExpectNotNull(ext = TLSX_Find(extensions, + TLSX_SUPPORTED_GROUPS)); + if ((ext != NULL) && (ext->data != NULL)) + cap = ((SupportedCurves*)ext->data)->cap; + ExpectIntGT(cap, 0); + ExpectIntLE(cap, WOLFSSL_MAX_GROUP_COUNT); + if (cap > WOLFSSL_MAX_GROUP_COUNT) + cap = 0; + + len = (word16)(cap * OPAQUE16_LEN); + ffdheMany[0] = (byte)(len >> 8); + ffdheMany[1] = (byte)len; + for (i = 0; i < cap; i++) { + word16 name = (word16)(0x0120 + i); + + ffdheMany[OPAQUE16_LEN + i * OPAQUE16_LEN] = (byte)(name >> 8); + ffdheMany[OPAQUE16_LEN + i * OPAQUE16_LEN + 1] = (byte)name; + } + + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, + tls_parse_fail_free, tls_parse_fail_realloc), 0); + tls_parse_alloc_seen = 0; + tls_parse_fail_after = 0; + + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, ffdheMany, + (word16)(OPAQUE16_LEN + len), 1, &extensions), WC_NO_ERR_TRACE(MEMORY_E)); + ExpectIntEQ(tls_parse_alloc_seen, 1); tls_parse_fail_after = -1; (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + ExpectNotNull(ext = TLSX_Find(extensions, + TLSX_SUPPORTED_GROUPS)); + if (ext != NULL) { + ExpectNotNull(ext->data); + if (ext->data != NULL) { + ExpectIntEQ(((SupportedCurves*)ext->data)->count, cap); + } + } TLSX_FreeAll(extensions, NULL); + extensions = NULL; } #endif } @@ -2382,6 +2440,13 @@ int test_TLSX_SupportedGroups_parse(void) WOLFSSL_SUCCESS); ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, noMatch261, (word16)sizeof(noMatch261), 0, &extensions), 0); + { + TLSX* ext = TLSX_Find(extensions, TLSX_SUPPORTED_GROUPS); + + ExpectNotNull(ext); + if ((ext != NULL) && (ext->data != NULL)) + ExpectIntEQ(((SupportedCurves*)ext->data)->count, 0); + } TLSX_FreeAll(extensions, NULL); } wolfSSL_free(ssl); From 1e146fb38dc4c976e61c5b25a7fe7f215a84fedd Mon Sep 17 00:00:00 2001 From: Tobias Frauenschlaeger Date: Fri, 21 Aug 2026 22:25:11 +0000 Subject: [PATCH 3/5] Walk the supported groups instead of building a throwaway list Before sending EncryptedExtensions a TLS 1.3 server checked whether the client offered the group the server prefers most. It answered that by populating a whole temporary extension list with every group this build offers, reading the first name out of it, and freeing the list again. The TLS 1.2 FFDHE selection did the same to find the highest priority group both sides have. Both cost a TLSX node, a groups block and a walk of the full preference chain on every handshake. Turn the preference chain into TLSX_WalkSupportedGroups(), which calls a callback for each group in the same order. Populating an extension list is now one caller of the walk, finding the preferred group is another that stops at the first group, and the FFDHE selection is a third that stops at the first group the client also has. The server builds a list only in the case that sends it back to the client. --- src/tls.c | 652 ++++++++++++++++++--------------------- tests/api.c | 2 + tests/api/test_tls_ext.c | 215 +++++++++++++ tests/api/test_tls_ext.h | 2 + wolfssl/internal.h | 10 +- 5 files changed, 527 insertions(+), 354 deletions(-) diff --git a/src/tls.c b/src/tls.c index 0eb492c8838..a07924b9f6e 100644 --- a/src/tls.c +++ b/src/tls.c @@ -161,7 +161,15 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap); #endif #ifdef HAVE_SUPPORTED_CURVES +/* Callback for TLSX_WalkSupportedGroups(): returns 0 to continue the walk. */ +typedef int (*TLSX_GroupWalkCb)(WOLFSSL* ssl, word16 name, void* ctx); +static int TLSX_WalkSupportedGroups(WOLFSSL* ssl, TLSX_GroupWalkCb cb, + void* ctx); static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions); +#if !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) && \ + !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) +static word16 TLSX_PreferredSupportedGroup(WOLFSSL* ssl); +#endif #endif /* Digest enable checks */ @@ -5592,6 +5600,17 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) if (extension == NULL) return 0; + name = TLSX_PreferredSupportedGroup(ssl); + if (name == 0) + return 0; + + /* Nothing to say when the client offers the group we prefer. */ + curves = (SupportedCurves*)extension->data; + for (i = 0; (curves != NULL) && (i < curves->count); i++) { + if (curves->name[i] == name) + return 0; + } + ret = TLSX_PopulateSupportedGroups(ssl, &priority); if (ret != WOLFSSL_SUCCESS) { TLSX_FreeAll(priority, ssl->heap); @@ -5605,28 +5624,12 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) return 0; } - curves = (SupportedCurves*)ext->data; - if ((curves == NULL) || (curves->count == 0)) { - TLSX_FreeAll(priority, ssl->heap); - return 0; - } - name = curves->name[0]; - - curves = (SupportedCurves*)extension->data; - for (i = 0; (curves != NULL) && (i < curves->count); i++) { - if (curves->name[i] == name) - break; - } - - if ((curves == NULL) || (i == curves->count)) { - /* Couldn't find the preferred group in client list. */ - extension->resp = 1; + extension->resp = 1; - /* Send server list back and free client list. */ - tmp = (SupportedCurves*)extension->data; - extension->data = ext->data; - ext->data = tmp; - } + /* Send server list back and free client list. */ + tmp = (SupportedCurves*)extension->data; + extension->data = ext->data; + ext->data = tmp; TLSX_FreeAll(priority, ssl->heap); @@ -5638,191 +5641,148 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) #if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) #ifdef HAVE_FFDHE #ifdef HAVE_PUBLIC_FFDHE -static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurves* clientGroups, - SupportedCurves* serverGroups) +static int tlsx_ffdhe_check_group(WOLFSSL* ssl, word16 name, void* ctx) { - int ret = 0; + SupportedCurves* clientGroups = (SupportedCurves*)ctx; const DhParams* params = NULL; - word16 name = WOLFSSL_NAMED_GROUP_INVALID; word16 i; - word16 j; - int found = 0; - if ((clientGroups == NULL) || (serverGroups == NULL)) + if ((clientGroups == NULL) || !WOLFSSL_NAMED_GROUP_IS_FFDHE(name)) return 0; - for (i = 0; i < serverGroups->count; i++) { - if (!WOLFSSL_NAMED_GROUP_IS_FFDHE(serverGroups->name[i])) - continue; - - for (j = 0; j < clientGroups->count; j++) { - if (serverGroups->name[i] != clientGroups->name[j]) - continue; - - switch (serverGroups->name[i]) { - #ifdef HAVE_FFDHE_2048 - case WOLFSSL_FFDHE_2048: - params = wc_Dh_ffdhe2048_Get(); - break; - #endif - #ifdef HAVE_FFDHE_3072 - case WOLFSSL_FFDHE_3072: - params = wc_Dh_ffdhe3072_Get(); - break; - #endif - #ifdef HAVE_FFDHE_4096 - case WOLFSSL_FFDHE_4096: - params = wc_Dh_ffdhe4096_Get(); - break; - #endif - #ifdef HAVE_FFDHE_6144 - case WOLFSSL_FFDHE_6144: - params = wc_Dh_ffdhe6144_Get(); - break; - #endif - #ifdef HAVE_FFDHE_8192 - case WOLFSSL_FFDHE_8192: - params = wc_Dh_ffdhe8192_Get(); - break; - #endif - default: - break; - } - if (params == NULL) { - ret = BAD_FUNC_ARG; - break; - } - if (params->p_len >= ssl->options.minDhKeySz && - params->p_len <= ssl->options.maxDhKeySz) { - found = 1; - break; - } - } + for (i = 0; i < clientGroups->count; i++) { + if (clientGroups->name[i] == name) + break; + } + if (i == clientGroups->count) + return 0; - if (ret != 0) + switch (name) { + #ifdef HAVE_FFDHE_2048 + case WOLFSSL_FFDHE_2048: + params = wc_Dh_ffdhe2048_Get(); break; - if (found) { - name = serverGroups->name[i]; + #endif + #ifdef HAVE_FFDHE_3072 + case WOLFSSL_FFDHE_3072: + params = wc_Dh_ffdhe3072_Get(); + break; + #endif + #ifdef HAVE_FFDHE_4096 + case WOLFSSL_FFDHE_4096: + params = wc_Dh_ffdhe4096_Get(); + break; + #endif + #ifdef HAVE_FFDHE_6144 + case WOLFSSL_FFDHE_6144: + params = wc_Dh_ffdhe6144_Get(); + break; + #endif + #ifdef HAVE_FFDHE_8192 + case WOLFSSL_FFDHE_8192: + params = wc_Dh_ffdhe8192_Get(); + break; + #endif + default: break; - } + } + if (params == NULL) + return BAD_FUNC_ARG; + if (params->p_len < ssl->options.minDhKeySz || + params->p_len > ssl->options.maxDhKeySz) { + return 0; } - if ((ret == 0) && found && (params != NULL)) { - ssl->buffers.serverDH_P.buffer = (unsigned char *)params->p; - ssl->buffers.serverDH_P.length = params->p_len; - ssl->buffers.serverDH_G.buffer = (unsigned char *)params->g; - ssl->buffers.serverDH_G.length = params->g_len; + ssl->buffers.serverDH_P.buffer = (unsigned char *)params->p; + ssl->buffers.serverDH_P.length = params->p_len; + ssl->buffers.serverDH_G.buffer = (unsigned char *)params->g; + ssl->buffers.serverDH_G.length = params->g_len; - ssl->namedGroup = name; - #if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ - !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) - ssl->options.dhDoKeyTest = 0; - #endif - ssl->options.haveDH = 1; - } + ssl->namedGroup = name; +#if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ + !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + ssl->options.dhDoKeyTest = 0; +#endif + ssl->options.haveDH = 1; - return ret; + return 1; } #else -static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurves* clientGroups, - SupportedCurves* serverGroups) +static int tlsx_ffdhe_check_group(WOLFSSL* ssl, word16 name, void* ctx) { - int ret = 0; + SupportedCurves* clientGroups = (SupportedCurves*)ctx; + int ret; word32 p_len; - word16 name = WOLFSSL_NAMED_GROUP_INVALID; + word32 pSz, gSz; word16 i; - word16 j; - int found = 0; - if ((clientGroups == NULL) || (serverGroups == NULL)) + if ((clientGroups == NULL) || !WOLFSSL_NAMED_GROUP_IS_FFDHE(name)) return 0; - for (i = 0; i < serverGroups->count; i++) { - if (!WOLFSSL_NAMED_GROUP_IS_FFDHE(serverGroups->name[i])) - continue; - - for (j = 0; j < clientGroups->count; j++) { - if (serverGroups->name[i] != clientGroups->name[j]) - continue; - - ret = wc_DhGetNamedKeyParamSize(serverGroups->name[i], &p_len, NULL, - NULL); - if (ret == 0) { - if (p_len == 0) { - ret = BAD_FUNC_ARG; - break; - } - if (p_len >= ssl->options.minDhKeySz && - p_len <= ssl->options.maxDhKeySz) { - found = 1; - break; - } - } - } - - if (ret != 0) - break; - if (found) { - name = serverGroups->name[i]; + for (i = 0; i < clientGroups->count; i++) { + if (clientGroups->name[i] == name) break; - } } + if (i == clientGroups->count) + return 0; - if ((ret == 0) && found) { - word32 pSz, gSz; - - ssl->buffers.serverDH_P.buffer = NULL; - ssl->buffers.serverDH_G.buffer = NULL; - ret = wc_DhGetNamedKeyParamSize(name, &pSz, &gSz, NULL); - if (ret == 0) { - ssl->buffers.serverDH_P.buffer = - (byte*)XMALLOC(pSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); - if (ssl->buffers.serverDH_P.buffer == NULL) - ret = MEMORY_E; - else - ssl->buffers.serverDH_P.length = pSz; - } - if (ret == 0) { - ssl->buffers.serverDH_G.buffer = - (byte*)XMALLOC(gSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); - if (ssl->buffers.serverDH_G.buffer == NULL) { - ret = MEMORY_E; - } else - ssl->buffers.serverDH_G.length = gSz; - } - if (ret == 0) { - ret = wc_DhCopyNamedKey(name, - ssl->buffers.serverDH_P.buffer, &pSz, - ssl->buffers.serverDH_G.buffer, &gSz, - NULL, NULL); - } - if (ret == 0) { - ssl->buffers.weOwnDH = 1; + ret = wc_DhGetNamedKeyParamSize(name, &p_len, NULL, NULL); + if (ret != 0) + return ret; + if (p_len == 0) + return BAD_FUNC_ARG; + if (p_len < ssl->options.minDhKeySz || p_len > ssl->options.maxDhKeySz) + return 0; - ssl->namedGroup = name; - #if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ - !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) - ssl->options.dhDoKeyTest = 0; - #endif - ssl->options.haveDH = 1; + ssl->buffers.serverDH_P.buffer = NULL; + ssl->buffers.serverDH_G.buffer = NULL; + ret = wc_DhGetNamedKeyParamSize(name, &pSz, &gSz, NULL); + if (ret == 0) { + ssl->buffers.serverDH_P.buffer = + (byte*)XMALLOC(pSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + if (ssl->buffers.serverDH_P.buffer == NULL) + ret = MEMORY_E; + else + ssl->buffers.serverDH_P.length = pSz; + } + if (ret == 0) { + ssl->buffers.serverDH_G.buffer = + (byte*)XMALLOC(gSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + if (ssl->buffers.serverDH_G.buffer == NULL) + ret = MEMORY_E; + else + ssl->buffers.serverDH_G.length = gSz; + } + if (ret == 0) { + ret = wc_DhCopyNamedKey(name, ssl->buffers.serverDH_P.buffer, &pSz, + ssl->buffers.serverDH_G.buffer, &gSz, NULL, + NULL); + } + if (ret != 0) { + if (ssl->buffers.serverDH_P.buffer != NULL) { + XFREE(ssl->buffers.serverDH_P.buffer, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ssl->buffers.serverDH_P.length = 0; + ssl->buffers.serverDH_P.buffer = NULL; } - else { - if (ssl->buffers.serverDH_P.buffer != NULL) { - XFREE(ssl->buffers.serverDH_P.buffer, ssl->heap, - DYNAMIC_TYPE_PUBLIC_KEY); - ssl->buffers.serverDH_P.length = 0; - ssl->buffers.serverDH_P.buffer = NULL; - } - if (ssl->buffers.serverDH_G.buffer != NULL) { - XFREE(ssl->buffers.serverDH_G.buffer, ssl->heap, - DYNAMIC_TYPE_PUBLIC_KEY); - ssl->buffers.serverDH_G.length = 0; - ssl->buffers.serverDH_G.buffer = NULL; - } + if (ssl->buffers.serverDH_G.buffer != NULL) { + XFREE(ssl->buffers.serverDH_G.buffer, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ssl->buffers.serverDH_G.length = 0; + ssl->buffers.serverDH_G.buffer = NULL; } + return ret; } - return ret; + ssl->buffers.weOwnDH = 1; + ssl->namedGroup = name; +#if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ + !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + ssl->options.dhDoKeyTest = 0; +#endif + ssl->options.haveDH = 1; + + return 1; } #endif #endif /* HAVE_FFDHE */ @@ -5835,10 +5795,8 @@ static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurves* clientGroups, */ int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl) { - int ret = 0; #ifdef HAVE_FFDHE - TLSX* priority = NULL; - TLSX* ext = NULL; + int ret; #endif TLSX* extension; SupportedCurves* clientGroups; @@ -5873,23 +5831,12 @@ int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl) ssl->options.haveDH = 0; #ifdef HAVE_FFDHE - ret = TLSX_PopulateSupportedGroups(ssl, &priority); - if (ret == WOLFSSL_SUCCESS) { - ext = TLSX_Find(priority, TLSX_SUPPORTED_GROUPS); - if (ext == NULL) { - WOLFSSL_MSG("Could not find supported groups extension"); - ret = 0; - } - else { - ret = tlsx_ffdhe_find_group(ssl, clientGroups, - (SupportedCurves*)ext->data); - } - } - - TLSX_FreeAll(priority, ssl->heap); + ret = TLSX_WalkSupportedGroups(ssl, tlsx_ffdhe_check_group, clientGroups); + if (ret < 0) + return ret; #endif /* HAVE_FFDHE */ - return ret; + return 0; } #endif /* !NO_DH && !WOLFSSL_NO_TLS12 */ #endif /* !NO_WOLFSSL_SERVER */ @@ -11851,7 +11798,7 @@ int TLSX_KeyShare_Empty(WOLFSSL* ssl) return ret; } -/* Compile-time gating must stay aligned with TLSX_PopulateSupportedGroups(). +/* Compile-time gating must stay aligned with TLSX_WalkSupportedGroups(). * Runtime-only conditions in that function (TLS 1.3 version check, FFDHE * key-size bounds, session-resumption short-circuit, downgrade-aware * Brainpool TLS 1.2 selection) are intentionally not represented here. */ @@ -16516,24 +16463,31 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, #ifdef HAVE_SUPPORTED_CURVES -/* Populates the default supported groups / curves */ -static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) +/* Walk the named groups this build offers, most preferred first. + * + * ssl The SSL/TLS object. + * cb Called with each group; 0 continues the walk, anything else stops it. + * ctx Context passed to the callback. + * returns WOLFSSL_SUCCESS when the walk completes, otherwise the value the + * callback stopped it with. + */ +static int TLSX_WalkSupportedGroups(WOLFSSL* ssl, TLSX_GroupWalkCb cb, + void* ctx) { int ret = WOLFSSL_SUCCESS; #ifdef WOLFSSL_TLS13 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) if (ssl->options.resuming && ssl->session->namedGroup != 0) { - return TLSX_UseSupportedCurve(extensions, ssl->session->namedGroup, - ssl->heap, ssl->options.side); + ret = cb(ssl, ssl->session->namedGroup, ctx); + return (ret == 0) ? WOLFSSL_SUCCESS : ret; } #endif if (ssl->numGroups != 0) { int i; for (i = 0; i < ssl->numGroups; i++) { - ret = TLSX_UseSupportedCurve(extensions, ssl->group[i], ssl->heap, - ssl->options.side); - if (ret != WOLFSSL_SUCCESS) + ret = cb(ssl, ssl->group[i], ctx); + if (ret != 0) return ret; } return WOLFSSL_SUCCESS; @@ -16547,23 +16501,20 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) TLSX_IsMlKemGroupSupported(ssl->options.side)) { #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE25519) && \ ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_X25519MLKEM768, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_X25519MLKEM768, ctx); + if (ret != 0) return ret; #endif #if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ ECC_MIN_KEY_SZ <= 384 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_SECP384R1MLKEM1024, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_SECP384R1MLKEM1024, ctx); + if (ret != 0) return ret; #endif #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_SECP256R1MLKEM768, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_SECP256R1MLKEM768, ctx); + if (ret != 0) return ret; #endif } #endif @@ -16573,9 +16524,8 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) if (IsAtLeastTLSv1_3(ssl->version) && TLSX_IsMlKemGroupSupported(ssl->options.side)) { - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ML_KEM_1024, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ML_KEM_1024, ctx); + if (ret != 0) return ret; } #endif @@ -16583,33 +16533,29 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) /* list in order by strength, since not all servers choose by strength */ #if (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 #ifndef NO_ECC_SECP - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP521R1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP521R1, ctx); + if (ret != 0) return ret; #endif #endif #if (defined(HAVE_ECC512) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 512 #ifdef HAVE_ECC_BRAINPOOL if (IsAtLeastTLSv1_3(ssl->version)) { /* TLS 1.3 BrainpoolP512 curve */ - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP512R1TLS13, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP512R1TLS13, ctx); + if (ret != 0) return ret; /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ if (ssl->options.downgrade && (ssl->options.minDowngrade <= TLSv1_2_MINOR || ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP512R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP512R1, ctx); + if (ret != 0) return ret; } } else { /* TLS 1.2 only */ - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP512R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP512R1, ctx); + if (ret != 0) return ret; } #endif #endif @@ -16620,40 +16566,35 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) if (IsAtLeastTLSv1_3(ssl->version) && TLSX_IsMlKemGroupSupported(ssl->options.side)) { - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ML_KEM_768, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ML_KEM_768, ctx); + if (ret != 0) return ret; } #endif #if defined(HAVE_ECC) #if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 #ifndef NO_ECC_SECP - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP384R1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP384R1, ctx); + if (ret != 0) return ret; #endif #ifdef HAVE_ECC_BRAINPOOL if (IsAtLeastTLSv1_3(ssl->version)) { /* TLS 1.3 BrainpoolP384 curve */ - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP384R1TLS13, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP384R1TLS13, ctx); + if (ret != 0) return ret; /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ if (ssl->options.downgrade && (ssl->options.minDowngrade <= TLSv1_2_MINOR || ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP384R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP384R1, ctx); + if (ret != 0) return ret; } } else { /* TLS 1.2 only */ - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP384R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP384R1, ctx); + if (ret != 0) return ret; } #endif #endif @@ -16661,9 +16602,8 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) #ifndef HAVE_FIPS #if defined(HAVE_CURVE448) && ECC_MIN_KEY_SZ <= 448 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_X448, ssl->heap, - ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_X448, ctx); + if (ret != 0) return ret; #endif #endif /* HAVE_FIPS */ @@ -16672,105 +16612,90 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) if (IsAtLeastTLSv1_3(ssl->version) && TLSX_IsMlKemGroupSupported(ssl->options.side)) { - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ML_KEM_512, ssl->heap, - ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ML_KEM_512, ctx); + if (ret != 0) return ret; } #endif #if defined(HAVE_ECC) && defined(HAVE_SUPPORTED_CURVES) #if (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 #ifndef NO_ECC_SECP - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP256R1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP256R1, ctx); + if (ret != 0) return ret; #endif #ifdef HAVE_ECC_KOBLITZ - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP256K1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP256K1, ctx); + if (ret != 0) return ret; #endif #ifdef HAVE_ECC_BRAINPOOL if (IsAtLeastTLSv1_3(ssl->version)) { /* TLS 1.3 BrainpoolP256 curve */ - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP256R1TLS13, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP256R1TLS13, ctx); + if (ret != 0) return ret; /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ if (ssl->options.downgrade && (ssl->options.minDowngrade <= TLSv1_2_MINOR || ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP256R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP256R1, ctx); + if (ret != 0) return ret; } } else { /* TLS 1.2 only */ - ret = TLSX_UseSupportedCurve(extensions, - WOLFSSL_ECC_BRAINPOOLP256R1, ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP256R1, ctx); + if (ret != 0) return ret; } #endif #if !defined(HAVE_FIPS) && defined(WOLFSSL_SM2) - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SM2P256V1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SM2P256V1, ctx); + if (ret != 0) return ret; #endif #endif #endif /* HAVE_ECC */ #ifndef HAVE_FIPS #if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_X25519, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_X25519, ctx); + if (ret != 0) return ret; #endif #endif /* HAVE_FIPS */ #if defined(HAVE_ECC) && defined(HAVE_SUPPORTED_CURVES) #if (defined(HAVE_ECC224) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 224 #ifndef NO_ECC_SECP - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP224R1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP224R1, ctx); + if (ret != 0) return ret; #endif #ifdef HAVE_ECC_KOBLITZ - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP224K1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP224K1, ctx); + if (ret != 0) return ret; #endif #endif #ifndef HAVE_FIPS #if (defined(HAVE_ECC192) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 192 #ifndef NO_ECC_SECP - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP192R1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP192R1, ctx); + if (ret != 0) return ret; #endif #ifdef HAVE_ECC_KOBLITZ - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP192K1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP192K1, ctx); + if (ret != 0) return ret; #endif #endif #if (defined(HAVE_ECC160) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 160 #ifndef NO_ECC_SECP - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP160R1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP160R1, ctx); + if (ret != 0) return ret; #endif #ifdef HAVE_ECC_SECPR2 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP160R2, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP160R2, ctx); + if (ret != 0) return ret; #endif #ifdef HAVE_ECC_KOBLITZ - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_ECC_SECP160K1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_ECC_SECP160K1, ctx); + if (ret != 0) return ret; #endif #endif #endif /* HAVE_FIPS */ @@ -16781,45 +16706,40 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) #ifdef HAVE_FFDHE_8192 if (8192/8 >= ssl->options.minDhKeySz && 8192/8 <= ssl->options.maxDhKeySz) { - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_FFDHE_8192, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) + ret = cb(ssl, WOLFSSL_FFDHE_8192, ctx); + if (ret != 0) return ret; } #endif #ifdef HAVE_FFDHE_6144 if (6144/8 >= ssl->options.minDhKeySz && 6144/8 <= ssl->options.maxDhKeySz) { - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_FFDHE_6144, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) + ret = cb(ssl, WOLFSSL_FFDHE_6144, ctx); + if (ret != 0) return ret; } #endif #ifdef HAVE_FFDHE_4096 if (4096/8 >= ssl->options.minDhKeySz && 4096/8 <= ssl->options.maxDhKeySz) { - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_FFDHE_4096, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) + ret = cb(ssl, WOLFSSL_FFDHE_4096, ctx); + if (ret != 0) return ret; } #endif #ifdef HAVE_FFDHE_3072 if (3072/8 >= ssl->options.minDhKeySz && 3072/8 <= ssl->options.maxDhKeySz) { - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_FFDHE_3072, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) + ret = cb(ssl, WOLFSSL_FFDHE_3072, ctx); + if (ret != 0) return ret; } #endif #ifdef HAVE_FFDHE_2048 if (2048/8 >= ssl->options.minDhKeySz && 2048/8 <= ssl->options.maxDhKeySz) { - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_FFDHE_2048, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) + ret = cb(ssl, WOLFSSL_FFDHE_2048, ctx); + if (ret != 0) return ret; } #endif @@ -16831,33 +16751,28 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) TLSX_IsMlKemGroupSupported(ssl->options.side)) { #if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_SECP521R1MLKEM1024, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_SECP521R1MLKEM1024, ctx); + if (ret != 0) return ret; #endif #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_SECP384R1MLKEM768, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_SECP384R1MLKEM768, ctx); + if (ret != 0) return ret; #endif #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE448) && \ ECC_MIN_KEY_SZ <= 448 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_X448MLKEM768, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_X448MLKEM768, ctx); + if (ret != 0) return ret; #endif #if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_ECC) && \ (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_SECP256R1MLKEM512, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_SECP256R1MLKEM512, ctx); + if (ret != 0) return ret; #endif #if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_CURVE25519) && \ ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_X25519MLKEM512, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_X25519MLKEM512, ctx); + if (ret != 0) return ret; #endif } #endif @@ -16867,68 +16782,101 @@ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) if (IsAtLeastTLSv1_3(ssl->version) && TLSX_IsMlKemGroupSupported(ssl->options.side)) { #ifdef WOLFSSL_KYBER1024 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_KYBER_LEVEL5, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_KYBER_LEVEL5, ctx); + if (ret != 0) return ret; #if defined(HAVE_ECC) && (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && \ ECC_MIN_KEY_SZ <= 521 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_P521_KYBER_LEVEL5, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_P521_KYBER_LEVEL5, ctx); + if (ret != 0) return ret; #endif #endif #ifdef WOLFSSL_KYBER768 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_KYBER_LEVEL3, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_KYBER_LEVEL3, ctx); + if (ret != 0) return ret; #if defined(HAVE_ECC) && (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ ECC_MIN_KEY_SZ <= 384 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_P384_KYBER_LEVEL3, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_P384_KYBER_LEVEL3, ctx); + if (ret != 0) return ret; #endif #if defined(HAVE_ECC) && (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_P256_KYBER_LEVEL3, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_P256_KYBER_LEVEL3, ctx); + if (ret != 0) return ret; #endif #if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_X25519_KYBER_LEVEL3, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_X25519_KYBER_LEVEL3, ctx); + if (ret != 0) return ret; #endif #if defined(HAVE_CURVE448) && ECC_MIN_KEY_SZ <= 448 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_X448_KYBER_LEVEL3, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_X448_KYBER_LEVEL3, ctx); + if (ret != 0) return ret; #endif #endif #ifdef WOLFSSL_KYBER512 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_KYBER_LEVEL1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_KYBER_LEVEL1, ctx); + if (ret != 0) return ret; #if defined(HAVE_ECC) && (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_P256_KYBER_LEVEL1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_P256_KYBER_LEVEL1, ctx); + if (ret != 0) return ret; #endif #if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - ret = TLSX_UseSupportedCurve(extensions, WOLFSSL_X25519_KYBER_LEVEL1, - ssl->heap, ssl->options.side); - if (ret != WOLFSSL_SUCCESS) return ret; + ret = cb(ssl, WOLFSSL_X25519_KYBER_LEVEL1, ctx); + if (ret != 0) return ret; #endif #endif } #endif (void)ssl; - (void)extensions; + (void)ctx; - return ret; + return WOLFSSL_SUCCESS; } +/* Adds a group offered by this build to an extension list. */ +static int TLSX_SupportedGroup_Add(WOLFSSL* ssl, word16 name, void* ctx) +{ + int ret = TLSX_UseSupportedCurve((TLSX**)ctx, name, ssl->heap, + ssl->options.side); + + return (ret == WOLFSSL_SUCCESS) ? 0 : ret; +} + +/* Populates the default supported groups / curves */ +static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) +{ + return TLSX_WalkSupportedGroups(ssl, TLSX_SupportedGroup_Add, extensions); +} + +#if !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) && \ + !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) +/* Keeps the first group the walk offers. */ +static int TLSX_SupportedGroup_Keep(WOLFSSL* ssl, word16 name, void* ctx) +{ + (void)ssl; + + *(word16*)ctx = name; + + return 1; +} + +/* Return the group this build prefers most, without building a list of them. + * + * ssl The SSL/TLS object. + * returns the group name, or 0 when this build offers none. + */ +static word16 TLSX_PreferredSupportedGroup(WOLFSSL* ssl) +{ + word16 name = 0; + + /* The callback always stops the walk, so it cannot fail. */ + (void)TLSX_WalkSupportedGroups(ssl, TLSX_SupportedGroup_Keep, &name); + + return name; +} +#endif + #endif /* HAVE_SUPPORTED_CURVES */ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) diff --git a/tests/api.c b/tests/api.c index cbbea23cea2..319b1c6eac3 100644 --- a/tests/api.c +++ b/tests/api.c @@ -47159,6 +47159,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_TLSX_SupportedCurve_append), TEST_DECL(test_TLSX_SupportedCurve_grow), TEST_DECL(test_TLSX_SupportedCurve_intersection), + TEST_DECL(test_TLSX_SupportedCurve_check_priority), + TEST_DECL(test_TLSX_SupportedFFDHE_Set), TEST_DECL(test_TLSX_PointFormat_uncompressed_required), TEST_DECL(test_wolfSSL_CTX_add_client_custom_ext), TEST_DECL(test_wolfSSL_custom_ext_handshake), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 33760d56d11..d1b8ec76d92 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3721,6 +3721,221 @@ int test_TLSX_SupportedCurve_intersection(void) return EXPECT_RESULT(); } +/* The server keeps the first group in its own preference order that the client + * also offered, within the configured DH bounds. */ +int test_TLSX_SupportedFFDHE_Set(void) +{ + EXPECT_DECLS; +/* HAVE_FFDHE_2048 pins the result: a client list holding only ffdhe2048 + * leaves one outcome whatever else the build implements. */ +#if !defined(NO_TLS) && !defined(NO_DH) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SUPPORTED_CURVES) && \ + defined(HAVE_TLS_EXTENSIONS) && defined(HAVE_FFDHE) && \ + defined(HAVE_FFDHE_2048) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && !defined(NO_RSA) && \ + WOLFSSL_MIN_DHKEY_BITS <= 2048 && WOLFSSL_MAX_DHKEY_BITS >= 2048 + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + Suites* suites = NULL; + /* supported_groups (0x000a), ext len 0x0004, list len 0x0002, + * ffdhe2048 (0x0100) */ + const byte ffdheOnly[] = { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, + 0x01, 0x00 }; + /* The same extension carrying secp256r1 (0x0017) instead. */ + const byte noFfdhe[] = { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, + 0x00, 0x17 }; +#if defined(HAVE_FFDHE_3072) && WOLFSSL_MAX_DHKEY_BITS >= 3072 + /* ffdhe2048 then ffdhe3072 (0x0101). */ + const byte ffdheBoth[] = { 0x00, 0x0a, 0x00, 0x06, 0x00, 0x04, + 0x01, 0x00, 0x01, 0x01 }; +#ifdef WOLFSSL_TLS13 + int setGroups[] = { WOLFSSL_FFDHE_2048, WOLFSSL_FFDHE_3072 }; +#endif +#endif + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + + /* The one group both sides have is selected. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, ffdheOnly, (word16)sizeof(ffdheOnly), + client_hello, suites), 0); + ExpectIntEQ(TLSX_SupportedFFDHE_Set(ssl), 0); + if (ssl != NULL) { + ExpectIntEQ(ssl->namedGroup, WOLFSSL_FFDHE_2048); + ExpectIntEQ(ssl->options.haveDH, 1); + ExpectIntEQ((int)ssl->buffers.serverDH_P.length, 2048 / 8); + ExpectNotNull(ssl->buffers.serverDH_G.buffer); + /* Without public FFDHE parameters the group's P and G are copied. */ +#ifdef HAVE_PUBLIC_FFDHE + ExpectIntEQ(ssl->buffers.weOwnDH, 0); +#else + ExpectIntEQ(ssl->buffers.weOwnDH, 1); +#endif + } + wolfSSL_free(ssl); + ssl = NULL; + suites = NULL; + + /* A floor above the only common group selects nothing, and that is + * success. The walk's bound stops it before the callback's own check. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_SetMinDhKey_Sz(ssl, 8192), WOLFSSL_SUCCESS); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, ffdheOnly, (word16)sizeof(ffdheOnly), + client_hello, suites), 0); + ExpectIntEQ(TLSX_SupportedFFDHE_Set(ssl), 0); + if (ssl != NULL) { + ExpectIntEQ(ssl->options.haveDH, 0); + ExpectNull(ssl->buffers.serverDH_P.buffer); + } + wolfSSL_free(ssl); + ssl = NULL; + suites = NULL; + +#if defined(HAVE_FFDHE_3072) && WOLFSSL_MAX_DHKEY_BITS >= 3072 + /* The server's order decides: 3072 wins though the client lists it + * second. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, ffdheBoth, (word16)sizeof(ffdheBoth), + client_hello, suites), 0); + ExpectIntEQ(TLSX_SupportedFFDHE_Set(ssl), 0); + if (ssl != NULL) + ExpectIntEQ(ssl->namedGroup, WOLFSSL_FFDHE_3072); + wolfSSL_free(ssl); + ssl = NULL; + suites = NULL; + +#ifdef WOLFSSL_TLS13 + /* A list from wolfSSL_set_groups() replaces the default order. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_set_groups(ssl, setGroups, + (int)(sizeof(setGroups) / sizeof(setGroups[0]))), + WOLFSSL_SUCCESS); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, ffdheBoth, (word16)sizeof(ffdheBoth), + client_hello, suites), 0); + ExpectIntEQ(TLSX_SupportedFFDHE_Set(ssl), 0); + if (ssl != NULL) + ExpectIntEQ(ssl->namedGroup, WOLFSSL_FFDHE_2048); + wolfSSL_free(ssl); + ssl = NULL; + suites = NULL; +#endif +#endif + + /* No FFDHE codepoint on offer: nothing touches the DH state. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + ExpectIntEQ(TLSX_Parse(ssl, noFfdhe, (word16)sizeof(noFfdhe), + client_hello, suites), 0); + ExpectIntEQ(TLSX_SupportedFFDHE_Set(ssl), 0); + if (ssl != NULL) { + ExpectIntEQ(ssl->options.haveDH, 0); + ExpectIntEQ(ssl->namedGroup, 0); + } + wolfSSL_free(ssl); + + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* When the client does not offer the group this build prefers, the server swaps + * its own list in and marks it for sending back (the TLS 1.3 HRR). */ +int test_TLSX_SupportedCurve_check_priority(void) +{ + EXPECT_DECLS; +/* CheckPriority() only exists under !NO_WOLFSSL_SERVER; nothing to link + * against otherwise. */ +#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_CERTS) && \ + !defined(NO_FILESYSTEM) && \ + !defined(NO_TLS) && defined(HAVE_SUPPORTED_CURVES) && \ + defined(HAVE_ECC) && !defined(NO_ECC_SECP) && ECC_MIN_KEY_SZ <= 256 && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX* extension = NULL; + SupportedCurves* curves = NULL; + word16 preferred = 0; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, eccCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, eccKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + + /* The client offers only a private-use codepoint, so this build's own list + * goes back. Its first group is the preference the second half needs. */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1), + WOLFSSL_SUCCESS); + extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + } + ExpectNotNull(extension); + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + ExpectNotNull(curves); + /* The public API rejects an unsupported name, so store it directly. */ + if (curves != NULL) + curves->name[0] = 0xeeee; + ExpectIntEQ(TLSX_SupportedCurve_CheckPriority(ssl), 0); + if (extension != NULL) + ExpectIntEQ(extension->resp, 1); + if ((extension != NULL) && (extension->data != NULL)) { + curves = (SupportedCurves*)extension->data; + /* Our own list replaced the client's, so the codepoint is gone. */ + ExpectIntGT((int)curves->count, 0); + if (curves->count > 0) + preferred = curves->name[0]; + ExpectIntNE(preferred, 0xeeee); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* The client now offers the preferred group, so nothing is sent back. */ + extension = NULL; + curves = NULL; + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1), + WOLFSSL_SUCCESS); + extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + } + ExpectNotNull(extension); + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + ExpectNotNull(curves); + if ((curves != NULL) && (preferred != 0)) + curves->name[0] = preferred; + ExpectIntEQ(TLSX_SupportedCurve_CheckPriority(ssl), 0); + if (extension != NULL) + ExpectIntEQ(extension->resp, 0); + if ((extension != NULL) && (extension->data != NULL)) { + curves = (SupportedCurves*)extension->data; + ExpectIntEQ((int)curves->count, 1); + ExpectIntEQ(curves->name[0], preferred); + } + wolfSSL_free(ssl); + ssl = NULL; + + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* RFC 8422 Section 5.1.2: a client that sends the ec_point_formats extension * MUST include the uncompressed (0) point format. When the uncompressed format * is omitted the server records this (ssl->options.peerNoUncompPF) during diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 9fa07d19143..2e00dd4e9ff 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -73,6 +73,8 @@ int test_TLSX_MaxFragment_value(void); int test_TLSX_SupportedCurve_append(void); int test_TLSX_SupportedCurve_grow(void); int test_TLSX_SupportedCurve_intersection(void); +int test_TLSX_SupportedCurve_check_priority(void); +int test_TLSX_SupportedFFDHE_Set(void); int test_TLSX_PointFormat_uncompressed_required(void); int test_wolfSSL_CTX_add_client_custom_ext(void); int test_wolfSSL_custom_ext_handshake(void); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 2f9baee1a5a..5689b7c7ba2 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3954,8 +3954,14 @@ WOLFSSL_LOCAL int TLSX_IsGroupSupported(int namedGroup, int side); #ifndef NO_WOLFSSL_SERVER WOLFSSL_LOCAL int TLSX_ValidateSupportedCurves(const WOLFSSL* ssl, byte first, byte second, word32* ecdhCurveOID); -WOLFSSL_LOCAL int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl); -WOLFSSL_LOCAL int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl); +#ifdef WOLFSSL_API_PREFIX_MAP + #define TLSX_SupportedCurve_CheckPriority \ + wolfSSL_TLSX_SupportedCurve_CheckPriority + #define TLSX_SupportedFFDHE_Set wolfSSL_TLSX_SupportedFFDHE_Set +#endif +/* WOLFSSL_TEST_VIS: no public API reaches these; the API tests call them. */ +WOLFSSL_TEST_VIS int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl); +WOLFSSL_TEST_VIS int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl); #endif WOLFSSL_LOCAL int TLSX_SupportedCurve_IsSupported(WOLFSSL* ssl, word16 name); WOLFSSL_LOCAL int TLSX_SupportedCurve_Preferred(WOLFSSL* ssl, From 22e2dafce034ecc79a41890577c351b2293f8b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 21 Oct 2026 14:15:37 +0200 Subject: [PATCH 4/5] Address review of the supported groups rework Walk the groups with TLSX_NextSupportedGroup(), an index into the existing preferredGroup[] table filtered at run time, instead of a callback per group. Populating the extension, CheckPriority() and the FFDHE selection now loop over it directly. The FFDHE selection is back to master's shape: find the group in the loop and set it once afterwards. The table now decides the ML-KEM groups per side for the walk as it already did for the key share ranking, which only differs in encapsulate-only server builds. Build the server's intersection in a separate list instead of moving names around inside its own one, and swap it in only on success, so an error leaves the list as it was. The list is allocated once with room for the shorter of the two lists plus one, where master made one allocation per common group. It is not capped: after an HRR the second ClientHello is intersected with the client's whole list from the first, which can be longer than WOLFSSL_MAX_GROUP_COUNT. Only the first unsupported FFDHE codepoint is kept, since TLSX_SupportedFFDHE_Set() only needs to see one. Grow a live list with XREALLOC, keeping malloc and copy for builds without it, such as FreeRTOS without OPENSSL_EXTRA. A new list still comes from XMALLOC: the uITRON4 and uT-Kernel reallocs return NULL for a NULL pointer, and with no realloc callback installed wolfSSL_Realloc() falls back to the native one. The first allocation now holds the larger of WOLFSSL_MAX_GROUP_COUNT and the build's default group table, plus the three old hybrid codepoints when WOLFSSL_ML_KEM_USE_OLD_IDS adds them, so a default list no longer grows in builds with many groups but no ML-KEM, where WOLFSSL_MAX_GROUP_COUNT is only 10. TLSX_SupportedCurve_Copy() makes one allocation and copy instead of re-adding every group. Append through a typed local rather than casting &extension->data, which GCC flags with -Wstrict-aliasing=2. Drop two casts and some comments. Adds tests for an intersection longer than WOLFSSL_MAX_GROUP_COUNT, for the list surviving a failed allocation or a failed grow, for creating a list when realloc always fails, and for which brainpool and ML-KEM groups the default list holds for TLS 1.2, TLS 1.3 and version-flexible clients. --- src/tls.c | 1478 +++++++++++++++--------------------- tests/api.c | 1 + tests/api/test_tls_ext.c | 115 ++- tests/api/test_tls_ext.h | 1 + tests/api/test_tls_parse.c | 182 ++++- wolfssl/internal.h | 1 - 6 files changed, 836 insertions(+), 942 deletions(-) diff --git a/src/tls.c b/src/tls.c index a07924b9f6e..a63b717c9f3 100644 --- a/src/tls.c +++ b/src/tls.c @@ -161,15 +161,7 @@ static void TLSX_KeyShare_FreeAll(KeyShareEntry* list, void* heap); #endif #ifdef HAVE_SUPPORTED_CURVES -/* Callback for TLSX_WalkSupportedGroups(): returns 0 to continue the walk. */ -typedef int (*TLSX_GroupWalkCb)(WOLFSSL* ssl, word16 name, void* ctx); -static int TLSX_WalkSupportedGroups(WOLFSSL* ssl, TLSX_GroupWalkCb cb, - void* ctx); static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions); -#if !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) && \ - !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) -static word16 TLSX_PreferredSupportedGroup(WOLFSSL* ssl); -#endif #endif /* Digest enable checks */ @@ -5012,11 +5004,304 @@ int TLSX_IsGroupSupported(int namedGroup, int side) define HAVE_ECC. Alternatively use FFDHE for DH cipher suites. #endif -/* Names the first allocation holds; the list doubles from here. */ -#define SUPPORTED_CURVES_INIT_CNT WOLFSSL_MAX_GROUP_COUNT +/* The groups this build offers, most preferred first. + * TLSX_DefaultGroupAllowed() applies the runtime conditions. */ +static const word16 preferredGroup[] = { + /* Sort by strength, but prefer non-experimental PQ/T hybrid groups */ +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ + !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) + #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE25519) && \ + ECC_MIN_KEY_SZ <= 256 + WOLFSSL_X25519MLKEM768, + #endif + #if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ + (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 384 + WOLFSSL_SECP384R1MLKEM1024, + #endif + #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 256 + WOLFSSL_SECP256R1MLKEM768, + #endif +#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM && !WOLFSSL_NO_ML_KEM && + * WOLFSSL_PQC_HYBRIDS */ +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ + !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_1024) && \ + !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) + WOLFSSL_ML_KEM_1024, +#endif +#if defined(HAVE_ECC) && (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && \ + !defined(NO_ECC_SECP) && ECC_MIN_KEY_SZ <= 521 + WOLFSSL_ECC_SECP521R1, +#endif +#if defined(HAVE_ECC) && (defined(HAVE_ECC512) || defined(HAVE_ALL_CURVES)) && \ + defined(HAVE_ECC_BRAINPOOL) && ECC_MIN_KEY_SZ <= 512 + WOLFSSL_ECC_BRAINPOOLP512R1TLS13, + WOLFSSL_ECC_BRAINPOOLP512R1, +#endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ + !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_768) && \ + !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) + WOLFSSL_ML_KEM_768, +#endif +#if defined(HAVE_ECC) && (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 384 + #ifndef NO_ECC_SECP + WOLFSSL_ECC_SECP384R1, + #endif + #ifdef HAVE_ECC_BRAINPOOL + WOLFSSL_ECC_BRAINPOOLP384R1TLS13, + WOLFSSL_ECC_BRAINPOOLP384R1, + #endif +#endif +#if !defined(HAVE_FIPS) && defined(HAVE_CURVE448) && ECC_MIN_KEY_SZ <= 448 + WOLFSSL_ECC_X448, +#endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ + !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_512) && \ + !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) + WOLFSSL_ML_KEM_512, +#endif +#if defined(HAVE_ECC) && (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 256 + #ifndef NO_ECC_SECP + WOLFSSL_ECC_SECP256R1, + #endif + #ifdef HAVE_ECC_KOBLITZ + WOLFSSL_ECC_SECP256K1, + #endif + #ifdef HAVE_ECC_BRAINPOOL + WOLFSSL_ECC_BRAINPOOLP256R1TLS13, + WOLFSSL_ECC_BRAINPOOLP256R1, + #endif + #if !defined(HAVE_FIPS) && defined(WOLFSSL_SM2) + WOLFSSL_ECC_SM2P256V1, + #endif +#endif +#if !defined(HAVE_FIPS) && defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 + WOLFSSL_ECC_X25519, +#endif +#if defined(HAVE_ECC) && (defined(HAVE_ECC224) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 224 + #ifndef NO_ECC_SECP + WOLFSSL_ECC_SECP224R1, + #endif + #ifdef HAVE_ECC_KOBLITZ + WOLFSSL_ECC_SECP224K1, + #endif +#endif +#if !defined(HAVE_FIPS) && defined(HAVE_ECC) + #if (defined(HAVE_ECC192) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 192 + #ifndef NO_ECC_SECP + WOLFSSL_ECC_SECP192R1, + #endif + #ifdef HAVE_ECC_KOBLITZ + WOLFSSL_ECC_SECP192K1, + #endif + #endif + #if (defined(HAVE_ECC160) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 160 + #ifndef NO_ECC_SECP + WOLFSSL_ECC_SECP160R1, + #endif + #ifdef HAVE_ECC_SECPR2 + WOLFSSL_ECC_SECP160R2, + #endif + #ifdef HAVE_ECC_KOBLITZ + WOLFSSL_ECC_SECP160K1, + #endif + #endif +#endif /* !HAVE_FIPS && HAVE_ECC */ +#if defined(HAVE_FFDHE_8192) + WOLFSSL_FFDHE_8192, +#endif +#if defined(HAVE_FFDHE_6144) + WOLFSSL_FFDHE_6144, +#endif +#if defined(HAVE_FFDHE_4096) + WOLFSSL_FFDHE_4096, +#endif +#if defined(HAVE_FFDHE_3072) + WOLFSSL_FFDHE_3072, +#endif +#if defined(HAVE_FFDHE_2048) + WOLFSSL_FFDHE_2048, +#endif +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ + !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_EXTRA_PQC_HYBRIDS) + #if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ + (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 521 + WOLFSSL_SECP521R1MLKEM1024, + #endif + #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ + (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 384 + WOLFSSL_SECP384R1MLKEM768, + #endif + #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE448) && \ + ECC_MIN_KEY_SZ <= 448 + WOLFSSL_X448MLKEM768, + #endif + #if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_ECC) && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 256 + WOLFSSL_SECP256R1MLKEM512, + #endif + #if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_CURVE25519) && \ + ECC_MIN_KEY_SZ <= 256 + WOLFSSL_X25519MLKEM512, + #endif +#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM && !WOLFSSL_NO_ML_KEM && + * WOLFSSL_EXTRA_PQC_HYBRIDS */ +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ + defined(WOLFSSL_MLKEM_KYBER) + #ifdef WOLFSSL_KYBER1024 + WOLFSSL_KYBER_LEVEL5, + #if defined(HAVE_ECC) && (defined(HAVE_ECC521) || \ + defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 + WOLFSSL_P521_KYBER_LEVEL5, + #endif + #endif + #ifdef WOLFSSL_KYBER768 + WOLFSSL_KYBER_LEVEL3, + #if defined(HAVE_ECC) && (defined(HAVE_ECC384) || \ + defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 + WOLFSSL_P384_KYBER_LEVEL3, + #endif + #if defined(HAVE_ECC) && (!defined(NO_ECC256) || \ + defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 + WOLFSSL_P256_KYBER_LEVEL3, + #endif + #if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 + WOLFSSL_X25519_KYBER_LEVEL3, + #endif + #if defined(HAVE_CURVE448) && ECC_MIN_KEY_SZ <= 448 + WOLFSSL_X448_KYBER_LEVEL3, + #endif + #endif + #ifdef WOLFSSL_KYBER512 + WOLFSSL_KYBER_LEVEL1, + #if defined(HAVE_ECC) && (!defined(NO_ECC256) || \ + defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 + WOLFSSL_P256_KYBER_LEVEL1, + #endif + #if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 + WOLFSSL_X25519_KYBER_LEVEL1, + #endif + #endif +#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM && WOLFSSL_MLKEM_KYBER */ + WOLFSSL_NAMED_GROUP_INVALID +}; + +#define PREFERRED_GROUP_SZ \ + ((sizeof(preferredGroup)/sizeof(*preferredGroup)) - 1) + /* -1 for the invalid group */ + +/* Runtime conditions on a group from preferredGroup[]. + * + * ssl The SSL/TLS object. + * name The group to check. + * returns 1 when the group is offered, 0 otherwise. + */ +static int TLSX_DefaultGroupAllowed(const WOLFSSL* ssl, word16 name) +{ + if (WOLFSSL_NAMED_GROUP_IS_PQC(name) || + WOLFSSL_NAMED_GROUP_IS_PQC_HYBRID(name)) { + #ifdef WOLFSSL_HAVE_MLKEM + return IsAtLeastTLSv1_3(ssl->version) && + TLSX_IsMlKemGroupSupported(ssl->options.side); + #else + return 0; + #endif + } + if (WOLFSSL_NAMED_GROUP_IS_FFDHE(name)) { + #ifdef NO_DH + return 0; + #else + word32 sz = 0; + + switch (name) { + case WOLFSSL_FFDHE_2048: sz = 2048 / 8; break; + case WOLFSSL_FFDHE_3072: sz = 3072 / 8; break; + case WOLFSSL_FFDHE_4096: sz = 4096 / 8; break; + case WOLFSSL_FFDHE_6144: sz = 6144 / 8; break; + case WOLFSSL_FFDHE_8192: sz = 8192 / 8; break; + default: break; + } + return sz >= ssl->options.minDhKeySz && sz <= ssl->options.maxDhKeySz; + #endif + } + switch (name) { + /* RFC 8734 codepoints are TLS 1.3 only. */ + case WOLFSSL_ECC_BRAINPOOLP256R1TLS13: + case WOLFSSL_ECC_BRAINPOOLP384R1TLS13: + case WOLFSSL_ECC_BRAINPOOLP512R1TLS13: + return IsAtLeastTLSv1_3(ssl->version); + /* RFC 7027 codepoints, also offered by TLS 1.3 when it may fall back + * to TLS 1.2. */ + case WOLFSSL_ECC_BRAINPOOLP256R1: + case WOLFSSL_ECC_BRAINPOOLP384R1: + case WOLFSSL_ECC_BRAINPOOLP512R1: + return !IsAtLeastTLSv1_3(ssl->version) || + (ssl->options.downgrade && + (ssl->options.minDowngrade <= TLSv1_2_MINOR || + ssl->options.minDowngrade <= DTLSv1_2_MINOR)); + default: + return 1; + } +} + +/* Next group this ssl offers, most preferred first. + * + * ssl The SSL/TLS object. + * idx Iteration state. Set to 0 before the first call. + * returns the group, or WOLFSSL_NAMED_GROUP_INVALID when there are no more. + */ +static word16 TLSX_NextSupportedGroup(const WOLFSSL* ssl, word16* idx) +{ +#ifdef WOLFSSL_TLS13 +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) + /* Resuming offers only the group of the session. */ + if (ssl->options.resuming && ssl->session->namedGroup != 0) { + if ((*idx)++ == 0) { + return ssl->session->namedGroup; + } + return WOLFSSL_NAMED_GROUP_INVALID; + } +#endif + if (ssl->numGroups != 0) { + if (*idx < ssl->numGroups) { + return ssl->group[(*idx)++]; + } + return WOLFSSL_NAMED_GROUP_INVALID; + } +#endif + while (*idx < PREFERRED_GROUP_SZ) { + word16 name = preferredGroup[(*idx)++]; + + if (TLSX_DefaultGroupAllowed(ssl, name)) + return name; + } + return WOLFSSL_NAMED_GROUP_INVALID; +} + +#if defined(WOLFSSL_ML_KEM_USE_OLD_IDS) && defined(WOLFSSL_EXTRA_PQC_HYBRIDS) + /* TLSX_UseSupportedCurve() adds an old codepoint after three hybrids. */ + #define SUPPORTED_CURVES_OLD_ID_CNT 3 +#else + #define SUPPORTED_CURVES_OLD_ID_CNT 0 +#endif +/* Names the first allocation holds, enough for any default or set_groups() + * list; the list doubles from here. */ +#define SUPPORTED_CURVES_INIT_CNT \ + ((((size_t)WOLFSSL_MAX_GROUP_COUNT > PREFERRED_GROUP_SZ) ? \ + (size_t)WOLFSSL_MAX_GROUP_COUNT : PREFERRED_GROUP_SZ) + \ + SUPPORTED_CURVES_OLD_ID_CNT) /* Most names the extension can encode: both its lengths are a word16. */ #define SUPPORTED_CURVES_MAX_CNT ((0xFFFFU - OPAQUE16_LEN) / OPAQUE16_LEN) -/* Append() grows with (word16)(count + 1), which must not wrap. */ wc_static_assert(SUPPORTED_CURVES_MAX_CNT < 0xFFFFU); wc_static_assert(OPAQUE16_LEN + SUPPORTED_CURVES_MAX_CNT * OPAQUE16_LEN <= 0xFFFFU); @@ -5033,10 +5318,15 @@ static int TLSX_SupportedCurve_Grow(SupportedCurves** curves, word16 cnt, { SupportedCurves* list = *curves; SupportedCurves* newList; - word32 cap = SUPPORTED_CURVES_INIT_CNT; + word32 cap = (word32)SUPPORTED_CURVES_INIT_CNT; + word32 sz; + word16 count = 0; - if ((list != NULL) && (cnt <= list->cap)) - return 0; + if (list != NULL) { + if (cnt <= list->cap) + return 0; + count = list->count; + } if (cnt > SUPPORTED_CURVES_MAX_CNT) return BAD_FUNC_ARG; @@ -5046,21 +5336,27 @@ static int TLSX_SupportedCurve_Grow(SupportedCurves** curves, word16 cnt, if (cap > SUPPORTED_CURVES_MAX_CNT) cap = SUPPORTED_CURVES_MAX_CNT; - newList = (SupportedCurves*)XMALLOC(sizeof(SupportedCurves) + - cap * sizeof(word16), heap, - DYNAMIC_TYPE_TLSX); + sz = (word32)(sizeof(SupportedCurves) + cap * sizeof(word16)); + if (list == NULL) { + newList = (SupportedCurves*)XMALLOC(sz, heap, DYNAMIC_TYPE_TLSX); + } + else { + #ifdef XREALLOC + newList = (SupportedCurves*)XREALLOC(list, sz, heap, DYNAMIC_TYPE_TLSX); + #else + newList = (SupportedCurves*)XMALLOC(sz, heap, DYNAMIC_TYPE_TLSX); + if (newList != NULL) { + XMEMCPY(newList->name, list->name, count * sizeof(word16)); + XFREE(list, heap, DYNAMIC_TYPE_TLSX); + } + #endif + } if (newList == NULL) return MEMORY_E; - newList->count = 0; + newList->count = count; newList->cap = (word16)cap; - if (list != NULL) { - newList->count = list->count; - XMEMCPY(newList->name, list->name, list->count * sizeof(word16)); - XFREE(list, heap, DYNAMIC_TYPE_TLSX); - } - *curves = newList; return 0; @@ -5068,7 +5364,7 @@ static int TLSX_SupportedCurve_Grow(SupportedCurves** curves, word16 cnt, /* Allocate a list holding one name. * - * curves Receives the new list. Must not already hold one, which would leak. + * curves Receives the new list; BAD_FUNC_ARG when it already holds one. * name The first name to store. * heap The heap used for allocation. * returns 0 on success, otherwise failure. @@ -5166,7 +5462,6 @@ static int TLSX_PointFormat_New(PointFormats** formats, byte format, static void TLSX_SupportedCurve_FreeAll(SupportedCurves* list, void* heap) { - /* Pushed without a list when no group the peer offered is supported. */ if (list != NULL) XFREE(list, heap, DYNAMIC_TYPE_TLSX); (void)heap; @@ -5195,7 +5490,7 @@ static int TLSX_SupportedCurve_Append(SupportedCurves** curves, word16 name, return 0; /* curve already in use */ } - ret = TLSX_SupportedCurve_Grow(curves, (word16)(list->count + 1), heap); + ret = TLSX_SupportedCurve_Grow(curves, list->count + 1, heap); if (ret != 0) return ret; @@ -5383,7 +5678,7 @@ static word16 TLSX_SupportedCurve_GetSize(SupportedCurves* list) word16 length = OPAQUE16_LEN; /* list length */ if (list != NULL) - length = (word16)(length + list->count * OPAQUE16_LEN); + length += list->count * OPAQUE16_LEN; return length; } @@ -5502,8 +5797,11 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, } } else { - ret = TLSX_SupportedCurve_Append( - (SupportedCurves**)&ext->data, name, ssl->heap); + SupportedCurves* curves = (SupportedCurves*)ext->data; + + ret = TLSX_SupportedCurve_Append(&curves, name, + ssl->heap); + ext->data = curves; } if (ret != 0) break; @@ -5521,14 +5819,18 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, } } else { - /* Intersect in place: the groups both sides have move to the front - * of our own list, in the peer's order. */ + /* Our groups that the peer also offers, in the peer's order. */ SupportedCurves* curves = (SupportedCurves*)extension->data; - word16 common = 0; + SupportedCurves* common = NULL; + word16 count = (curves != NULL) ? curves->count : 0; +#if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_SERVER) + int ffdhe = 0; +#endif word16 i; for (; offset < length; offset += OPAQUE16_LEN) { - word16 count = (curves != NULL) ? curves->count : 0; + int keep; ato16(input + offset, &name); @@ -5536,38 +5838,39 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, if (curves->name[i] == name) break; } - if (i < count) { - /* Below common it is a repeat in the peer list. */ - if (i >= common) { - curves->name[i] = curves->name[common]; - curves->name[common++] = name; - } - } + keep = (i < count); #if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) && \ !defined(NO_WOLFSSL_SERVER) - /* RFC 7919 Section 4 (see above): an unsupported FFDHE codepoint - * is not in our list yet, so append it before keeping it. */ - else if (isRequest && WOLFSSL_NAMED_GROUP_IS_FFDHE(name) && + /* RFC 7919 Section 4 (see above): one unsupported FFDHE codepoint + * is enough to keep the restriction. */ + if (!keep && !ffdhe && isRequest && + WOLFSSL_NAMED_GROUP_IS_FFDHE(name) && !TLSX_IsGroupSupported(name, ssl->options.side)) { - if (curves == NULL) - ret = TLSX_SupportedCurve_New(&curves, name, ssl->heap); - else - ret = TLSX_SupportedCurve_Append(&curves, name, ssl->heap); - if (ret != 0) - break; - extension->data = curves; - curves->name[curves->count - 1] = curves->name[common]; - curves->name[common++] = name; + keep = 1; + ffdhe = 1; } #endif /* !NO_DH && !WOLFSSL_NO_TLS12 && !NO_WOLFSSL_SERVER */ + if (!keep) + continue; + + if (common == NULL) { + ret = TLSX_SupportedCurve_Grow(&common, count + 1, ssl->heap); + } + if (ret == 0) + ret = TLSX_SupportedCurve_Append(&common, name, ssl->heap); + if (ret != 0) + break; } - /* Commit only on success; a failed handshake never reads it again. */ + /* If no common curves return error. In TLS 1.3 we can still try to save + * this by using HRR. */ + if (ret == 0 && common == NULL && !IsAtLeastTLSv1_3(ssl->version)) + ret = ECC_CURVE_ERROR; if (ret == 0) { - if (common == 0 && !IsAtLeastTLSv1_3(ssl->version)) - ret = ECC_CURVE_ERROR; - else if (curves != NULL) - curves->count = common; + TLSX_SupportedCurve_FreeAll(curves, ssl->heap); + extension->data = common; + common = NULL; } + TLSX_SupportedCurve_FreeAll(common, ssl->heap); } return ret; @@ -5591,6 +5894,7 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) TLSX* priority = NULL; TLSX* ext = NULL; word16 name; + word16 idx = 0; word16 i; SupportedCurves* curves; SupportedCurves* tmp; @@ -5600,8 +5904,8 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) if (extension == NULL) return 0; - name = TLSX_PreferredSupportedGroup(ssl); - if (name == 0) + name = TLSX_NextSupportedGroup(ssl, &idx); + if (name == WOLFSSL_NAMED_GROUP_INVALID) return 0; /* Nothing to say when the client offers the group we prefer. */ @@ -5641,148 +5945,172 @@ int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl) #if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) #ifdef HAVE_FFDHE #ifdef HAVE_PUBLIC_FFDHE -static int tlsx_ffdhe_check_group(WOLFSSL* ssl, word16 name, void* ctx) +static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurves* clientGroups) { - SupportedCurves* clientGroups = (SupportedCurves*)ctx; - const DhParams* params = NULL; + int ret = 0; + word16 idx = 0; + word16 name; word16 i; + const DhParams* params = NULL; + int found = 0; - if ((clientGroups == NULL) || !WOLFSSL_NAMED_GROUP_IS_FFDHE(name)) - return 0; + while ((name = TLSX_NextSupportedGroup(ssl, &idx)) != + WOLFSSL_NAMED_GROUP_INVALID) { + if (!WOLFSSL_NAMED_GROUP_IS_FFDHE(name)) + continue; - for (i = 0; i < clientGroups->count; i++) { - if (clientGroups->name[i] == name) - break; - } - if (i == clientGroups->count) - return 0; + for (i = 0; i < clientGroups->count; i++) { + if (clientGroups->name[i] == name) + break; + } + if (i == clientGroups->count) + continue; - switch (name) { - #ifdef HAVE_FFDHE_2048 - case WOLFSSL_FFDHE_2048: - params = wc_Dh_ffdhe2048_Get(); - break; - #endif - #ifdef HAVE_FFDHE_3072 - case WOLFSSL_FFDHE_3072: - params = wc_Dh_ffdhe3072_Get(); - break; - #endif - #ifdef HAVE_FFDHE_4096 - case WOLFSSL_FFDHE_4096: - params = wc_Dh_ffdhe4096_Get(); - break; - #endif - #ifdef HAVE_FFDHE_6144 - case WOLFSSL_FFDHE_6144: - params = wc_Dh_ffdhe6144_Get(); - break; - #endif - #ifdef HAVE_FFDHE_8192 - case WOLFSSL_FFDHE_8192: - params = wc_Dh_ffdhe8192_Get(); + switch (name) { + #ifdef HAVE_FFDHE_2048 + case WOLFSSL_FFDHE_2048: + params = wc_Dh_ffdhe2048_Get(); + break; + #endif + #ifdef HAVE_FFDHE_3072 + case WOLFSSL_FFDHE_3072: + params = wc_Dh_ffdhe3072_Get(); + break; + #endif + #ifdef HAVE_FFDHE_4096 + case WOLFSSL_FFDHE_4096: + params = wc_Dh_ffdhe4096_Get(); + break; + #endif + #ifdef HAVE_FFDHE_6144 + case WOLFSSL_FFDHE_6144: + params = wc_Dh_ffdhe6144_Get(); + break; + #endif + #ifdef HAVE_FFDHE_8192 + case WOLFSSL_FFDHE_8192: + params = wc_Dh_ffdhe8192_Get(); + break; + #endif + default: + break; + } + if (params == NULL) { + ret = BAD_FUNC_ARG; break; - #endif - default: + } + if (params->p_len >= ssl->options.minDhKeySz && + params->p_len <= ssl->options.maxDhKeySz) { + found = 1; break; - } - if (params == NULL) - return BAD_FUNC_ARG; - if (params->p_len < ssl->options.minDhKeySz || - params->p_len > ssl->options.maxDhKeySz) { - return 0; + } } - ssl->buffers.serverDH_P.buffer = (unsigned char *)params->p; - ssl->buffers.serverDH_P.length = params->p_len; - ssl->buffers.serverDH_G.buffer = (unsigned char *)params->g; - ssl->buffers.serverDH_G.length = params->g_len; + if ((ret == 0) && found) { + ssl->buffers.serverDH_P.buffer = (unsigned char *)params->p; + ssl->buffers.serverDH_P.length = params->p_len; + ssl->buffers.serverDH_G.buffer = (unsigned char *)params->g; + ssl->buffers.serverDH_G.length = params->g_len; - ssl->namedGroup = name; -#if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ - !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) - ssl->options.dhDoKeyTest = 0; -#endif - ssl->options.haveDH = 1; + ssl->namedGroup = name; + #if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ + !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + ssl->options.dhDoKeyTest = 0; + #endif + ssl->options.haveDH = 1; + } - return 1; + return ret; } #else -static int tlsx_ffdhe_check_group(WOLFSSL* ssl, word16 name, void* ctx) +static int tlsx_ffdhe_find_group(WOLFSSL* ssl, SupportedCurves* clientGroups) { - SupportedCurves* clientGroups = (SupportedCurves*)ctx; - int ret; - word32 p_len; - word32 pSz, gSz; + int ret = 0; + word16 idx = 0; + word16 name; word16 i; + word32 p_len; + int found = 0; - if ((clientGroups == NULL) || !WOLFSSL_NAMED_GROUP_IS_FFDHE(name)) - return 0; + while ((name = TLSX_NextSupportedGroup(ssl, &idx)) != + WOLFSSL_NAMED_GROUP_INVALID) { + if (!WOLFSSL_NAMED_GROUP_IS_FFDHE(name)) + continue; + + for (i = 0; i < clientGroups->count; i++) { + if (clientGroups->name[i] == name) + break; + } + if (i == clientGroups->count) + continue; - for (i = 0; i < clientGroups->count; i++) { - if (clientGroups->name[i] == name) + ret = wc_DhGetNamedKeyParamSize(name, &p_len, NULL, NULL); + if ((ret == 0) && (p_len == 0)) + ret = BAD_FUNC_ARG; + if (ret != 0) + break; + if (p_len >= ssl->options.minDhKeySz && + p_len <= ssl->options.maxDhKeySz) { + found = 1; break; + } } - if (i == clientGroups->count) - return 0; - ret = wc_DhGetNamedKeyParamSize(name, &p_len, NULL, NULL); - if (ret != 0) - return ret; - if (p_len == 0) - return BAD_FUNC_ARG; - if (p_len < ssl->options.minDhKeySz || p_len > ssl->options.maxDhKeySz) - return 0; + if ((ret == 0) && found) { + word32 pSz, gSz; - ssl->buffers.serverDH_P.buffer = NULL; - ssl->buffers.serverDH_G.buffer = NULL; - ret = wc_DhGetNamedKeyParamSize(name, &pSz, &gSz, NULL); - if (ret == 0) { - ssl->buffers.serverDH_P.buffer = - (byte*)XMALLOC(pSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); - if (ssl->buffers.serverDH_P.buffer == NULL) - ret = MEMORY_E; - else - ssl->buffers.serverDH_P.length = pSz; - } - if (ret == 0) { - ssl->buffers.serverDH_G.buffer = - (byte*)XMALLOC(gSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); - if (ssl->buffers.serverDH_G.buffer == NULL) - ret = MEMORY_E; - else - ssl->buffers.serverDH_G.length = gSz; - } - if (ret == 0) { - ret = wc_DhCopyNamedKey(name, ssl->buffers.serverDH_P.buffer, &pSz, - ssl->buffers.serverDH_G.buffer, &gSz, NULL, - NULL); - } - if (ret != 0) { - if (ssl->buffers.serverDH_P.buffer != NULL) { - XFREE(ssl->buffers.serverDH_P.buffer, ssl->heap, - DYNAMIC_TYPE_PUBLIC_KEY); - ssl->buffers.serverDH_P.length = 0; - ssl->buffers.serverDH_P.buffer = NULL; + ssl->buffers.serverDH_P.buffer = NULL; + ssl->buffers.serverDH_G.buffer = NULL; + ret = wc_DhGetNamedKeyParamSize(name, &pSz, &gSz, NULL); + if (ret == 0) { + ssl->buffers.serverDH_P.buffer = + (byte*)XMALLOC(pSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + if (ssl->buffers.serverDH_P.buffer == NULL) + ret = MEMORY_E; + else + ssl->buffers.serverDH_P.length = pSz; } - if (ssl->buffers.serverDH_G.buffer != NULL) { - XFREE(ssl->buffers.serverDH_G.buffer, ssl->heap, - DYNAMIC_TYPE_PUBLIC_KEY); - ssl->buffers.serverDH_G.length = 0; - ssl->buffers.serverDH_G.buffer = NULL; + if (ret == 0) { + ssl->buffers.serverDH_G.buffer = + (byte*)XMALLOC(gSz, ssl->heap, DYNAMIC_TYPE_PUBLIC_KEY); + if (ssl->buffers.serverDH_G.buffer == NULL) { + ret = MEMORY_E; + } else + ssl->buffers.serverDH_G.length = gSz; } - return ret; - } + if (ret == 0) { + ret = wc_DhCopyNamedKey(name, + ssl->buffers.serverDH_P.buffer, &pSz, + ssl->buffers.serverDH_G.buffer, &gSz, + NULL, NULL); + } + if (ret == 0) { + ssl->buffers.weOwnDH = 1; - ssl->buffers.weOwnDH = 1; - ssl->namedGroup = name; -#if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ - !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) - ssl->options.dhDoKeyTest = 0; -#endif - ssl->options.haveDH = 1; + ssl->namedGroup = name; + #if !defined(WOLFSSL_OLD_PRIME_CHECK) && \ + !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + ssl->options.dhDoKeyTest = 0; + #endif + ssl->options.haveDH = 1; + } + else { + if (ssl->buffers.serverDH_P.buffer != NULL) { + XFREE(ssl->buffers.serverDH_P.buffer, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ssl->buffers.serverDH_P.length = 0; + ssl->buffers.serverDH_P.buffer = NULL; + } + if (ssl->buffers.serverDH_G.buffer != NULL) { + XFREE(ssl->buffers.serverDH_G.buffer, ssl->heap, + DYNAMIC_TYPE_PUBLIC_KEY); + ssl->buffers.serverDH_G.length = 0; + ssl->buffers.serverDH_G.buffer = NULL; + } + } + } - return 1; + return ret; } #endif #endif /* HAVE_FFDHE */ @@ -5795,9 +6123,7 @@ static int tlsx_ffdhe_check_group(WOLFSSL* ssl, word16 name, void* ctx) */ int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl) { -#ifdef HAVE_FFDHE - int ret; -#endif + int ret = 0; TLSX* extension; SupportedCurves* clientGroups; word16 i; @@ -5831,12 +6157,10 @@ int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl) ssl->options.haveDH = 0; #ifdef HAVE_FFDHE - ret = TLSX_WalkSupportedGroups(ssl, tlsx_ffdhe_check_group, clientGroups); - if (ret < 0) - return ret; + ret = tlsx_ffdhe_find_group(ssl, clientGroups); #endif /* HAVE_FFDHE */ - return 0; + return ret; } #endif /* !NO_DH && !WOLFSSL_NO_TLS12 */ #endif /* !NO_WOLFSSL_SERVER */ @@ -6303,24 +6627,29 @@ int TLSX_ValidateSupportedCurves(const WOLFSSL* ssl, byte first, byte second, int TLSX_SupportedCurve_Copy(TLSX* src, TLSX** dst, void* heap) { TLSX* extension; + SupportedCurves* curves = NULL; + SupportedCurves* copy; + word32 sz; int ret; extension = TLSX_Find(src, TLSX_SUPPORTED_GROUPS); - if (extension != NULL) { - SupportedCurves* curves = (SupportedCurves*)extension->data; - word16 i; + if (extension != NULL) + curves = (SupportedCurves*)extension->data; + if ((curves == NULL) || (curves->count == 0)) + return 0; - for (i = 0; (curves != NULL) && (i < curves->count); i++) { - /* Copying an already validated list - don't drop a group based on - * the side, so accept when either side has the crypto support. */ - ret = TLSX_UseSupportedCurve(dst, curves->name[i], heap, - WOLFSSL_NEITHER_END); - if (ret != WOLFSSL_SUCCESS) - return MEMORY_E; - } - } + sz = (word32)(sizeof(SupportedCurves) + curves->count * sizeof(word16)); + copy = (SupportedCurves*)XMALLOC(sz, heap, DYNAMIC_TYPE_TLSX); + if (copy == NULL) + return MEMORY_E; + XMEMCPY(copy, curves, sz); + copy->cap = copy->count; - return 0; + ret = TLSX_Push(dst, TLSX_SUPPORTED_GROUPS, copy, heap); + if (ret != 0) + XFREE(copy, heap, DYNAMIC_TYPE_TLSX); + + return ret; } int TLSX_UseSupportedCurve(TLSX** extensions, word16 name, void* heap, int side) @@ -6351,27 +6680,26 @@ int TLSX_UseSupportedCurve(TLSX** extensions, word16 name, void* heap, int side) } } else { - ret = TLSX_SupportedCurve_Append((SupportedCurves**)&extension->data, - name, heap); + curves = (SupportedCurves*)extension->data; + ret = TLSX_SupportedCurve_Append(&curves, name, heap); + extension->data = curves; if (ret != 0) return ret; #if defined(WOLFSSL_ML_KEM_USE_OLD_IDS) && \ defined (WOLFSSL_EXTRA_PQC_HYBRIDS) if (name == WOLFSSL_SECP256R1MLKEM512) { - ret = TLSX_SupportedCurve_Append( - (SupportedCurves**)&extension->data, + ret = TLSX_SupportedCurve_Append(&curves, WOLFSSL_P256_ML_KEM_512_OLD, heap); } else if (name == WOLFSSL_SECP384R1MLKEM768) { - ret = TLSX_SupportedCurve_Append( - (SupportedCurves**)&extension->data, + ret = TLSX_SupportedCurve_Append(&curves, WOLFSSL_P384_ML_KEM_768_OLD, heap); } else if (name == WOLFSSL_SECP521R1MLKEM1024) { - ret = TLSX_SupportedCurve_Append( - (SupportedCurves**)&extension->data, + ret = TLSX_SupportedCurve_Append(&curves, WOLFSSL_P521_ML_KEM_1024_OLD, heap); } + extension->data = curves; if (ret != 0) { return ret; } @@ -11798,266 +12126,68 @@ int TLSX_KeyShare_Empty(WOLFSSL* ssl) return ret; } -/* Compile-time gating must stay aligned with TLSX_WalkSupportedGroups(). - * Runtime-only conditions in that function (TLS 1.3 version check, FFDHE - * key-size bounds, session-resumption short-circuit, downgrade-aware - * Brainpool TLS 1.2 selection) are intentionally not represented here. */ -static const word16 preferredGroup[] = { - /* Sort by strength, but prefer non-experimental PQ/T hybrid groups */ -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) - #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE25519) && \ - ECC_MIN_KEY_SZ <= 256 - WOLFSSL_X25519MLKEM768, - #endif - #if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ - (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 384 - WOLFSSL_SECP384R1MLKEM1024, - #endif - #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ - (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 256 - WOLFSSL_SECP256R1MLKEM768, - #endif -#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM && !WOLFSSL_NO_ML_KEM && - * WOLFSSL_PQC_HYBRIDS */ -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_1024) && \ - !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) - WOLFSSL_ML_KEM_1024, -#endif -#if defined(HAVE_ECC) && (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && \ - !defined(NO_ECC_SECP) && ECC_MIN_KEY_SZ <= 521 - WOLFSSL_ECC_SECP521R1, -#endif -#if defined(HAVE_ECC) && (defined(HAVE_ECC512) || defined(HAVE_ALL_CURVES)) && \ - defined(HAVE_ECC_BRAINPOOL) && ECC_MIN_KEY_SZ <= 512 - WOLFSSL_ECC_BRAINPOOLP512R1TLS13, - WOLFSSL_ECC_BRAINPOOLP512R1, -#endif -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_768) && \ - !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) - WOLFSSL_ML_KEM_768, -#endif -#if defined(HAVE_ECC) && (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 384 - #ifndef NO_ECC_SECP - WOLFSSL_ECC_SECP384R1, - #endif - #ifdef HAVE_ECC_BRAINPOOL - WOLFSSL_ECC_BRAINPOOLP384R1TLS13, - WOLFSSL_ECC_BRAINPOOLP384R1, - #endif -#endif -#if !defined(HAVE_FIPS) && defined(HAVE_CURVE448) && ECC_MIN_KEY_SZ <= 448 - WOLFSSL_ECC_X448, -#endif -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_512) && \ - !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) - WOLFSSL_ML_KEM_512, -#endif -#if defined(HAVE_ECC) && (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 256 - #ifndef NO_ECC_SECP - WOLFSSL_ECC_SECP256R1, - #endif - #ifdef HAVE_ECC_KOBLITZ - WOLFSSL_ECC_SECP256K1, - #endif - #ifdef HAVE_ECC_BRAINPOOL - WOLFSSL_ECC_BRAINPOOLP256R1TLS13, - WOLFSSL_ECC_BRAINPOOLP256R1, - #endif - #if !defined(HAVE_FIPS) && defined(WOLFSSL_SM2) - WOLFSSL_ECC_SM2P256V1, - #endif -#endif -#if !defined(HAVE_FIPS) && defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - WOLFSSL_ECC_X25519, -#endif -#if defined(HAVE_ECC) && (defined(HAVE_ECC224) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 224 - #ifndef NO_ECC_SECP - WOLFSSL_ECC_SECP224R1, - #endif - #ifdef HAVE_ECC_KOBLITZ - WOLFSSL_ECC_SECP224K1, - #endif -#endif -#if !defined(HAVE_FIPS) && defined(HAVE_ECC) - #if (defined(HAVE_ECC192) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 192 - #ifndef NO_ECC_SECP - WOLFSSL_ECC_SECP192R1, - #endif - #ifdef HAVE_ECC_KOBLITZ - WOLFSSL_ECC_SECP192K1, - #endif - #endif - #if (defined(HAVE_ECC160) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 160 - #ifndef NO_ECC_SECP - WOLFSSL_ECC_SECP160R1, - #endif - #ifdef HAVE_ECC_SECPR2 - WOLFSSL_ECC_SECP160R2, - #endif - #ifdef HAVE_ECC_KOBLITZ - WOLFSSL_ECC_SECP160K1, - #endif - #endif -#endif /* !HAVE_FIPS && HAVE_ECC */ -#if defined(HAVE_FFDHE_8192) - WOLFSSL_FFDHE_8192, -#endif -#if defined(HAVE_FFDHE_6144) - WOLFSSL_FFDHE_6144, -#endif -#if defined(HAVE_FFDHE_4096) - WOLFSSL_FFDHE_4096, -#endif -#if defined(HAVE_FFDHE_3072) - WOLFSSL_FFDHE_3072, -#endif -#if defined(HAVE_FFDHE_2048) - WOLFSSL_FFDHE_2048, -#endif -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_EXTRA_PQC_HYBRIDS) - #if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ - (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 521 - WOLFSSL_SECP521R1MLKEM1024, - #endif - #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ - (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 384 - WOLFSSL_SECP384R1MLKEM768, - #endif - #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE448) && \ - ECC_MIN_KEY_SZ <= 448 - WOLFSSL_X448MLKEM768, - #endif - #if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_ECC) && \ - (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 256 - WOLFSSL_SECP256R1MLKEM512, - #endif - #if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_CURVE25519) && \ - ECC_MIN_KEY_SZ <= 256 - WOLFSSL_X25519MLKEM512, - #endif -#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM && !WOLFSSL_NO_ML_KEM && - * WOLFSSL_EXTRA_PQC_HYBRIDS */ -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - defined(WOLFSSL_MLKEM_KYBER) - #ifdef WOLFSSL_KYBER1024 - WOLFSSL_KYBER_LEVEL5, - #if defined(HAVE_ECC) && (defined(HAVE_ECC521) || \ - defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 - WOLFSSL_P521_KYBER_LEVEL5, - #endif - #endif - #ifdef WOLFSSL_KYBER768 - WOLFSSL_KYBER_LEVEL3, - #if defined(HAVE_ECC) && (defined(HAVE_ECC384) || \ - defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 - WOLFSSL_P384_KYBER_LEVEL3, - #endif - #if defined(HAVE_ECC) && (!defined(NO_ECC256) || \ - defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 - WOLFSSL_P256_KYBER_LEVEL3, - #endif - #if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - WOLFSSL_X25519_KYBER_LEVEL3, - #endif - #if defined(HAVE_CURVE448) && ECC_MIN_KEY_SZ <= 448 - WOLFSSL_X448_KYBER_LEVEL3, - #endif - #endif - #ifdef WOLFSSL_KYBER512 - WOLFSSL_KYBER_LEVEL1, - #if defined(HAVE_ECC) && (!defined(NO_ECC256) || \ - defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 - WOLFSSL_P256_KYBER_LEVEL1, - #endif - #if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - WOLFSSL_X25519_KYBER_LEVEL1, - #endif - #endif -#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM && WOLFSSL_MLKEM_KYBER */ - WOLFSSL_NAMED_GROUP_INVALID -}; - -#define PREFERRED_GROUP_SZ \ - ((sizeof(preferredGroup)/sizeof(*preferredGroup)) - 1) - /* -1 for the invalid group */ - -/* One past the worst rank TLSX_KeyShare_GroupRank() can return. It ranks - * against ssl->group[] when the user set a list and against preferredGroup[] - * otherwise, so the sentinel has to cover the longer of the two. */ -#define WOLFSSL_WORST_GROUP_RANK \ - ((int)(((size_t)WOLFSSL_MAX_GROUP_COUNT > PREFERRED_GROUP_SZ) ? \ - (size_t)WOLFSSL_MAX_GROUP_COUNT : PREFERRED_GROUP_SZ)) - -/* WOLFSSL_KEY_SHARE_DEFAULT_GROUP - group used for the speculative key share - * in ClientHello messages when the application has not selected one via - * wolfSSL_CTX_set_groups() / wolfSSL_set_groups() or wolfSSL_UseKeyShare(). - * - * The default is optimized for the likelihood that the server will accept the - * speculative key share without forcing a HelloRetryRequest. It therefore - * differs from preferredGroup[] (which is sorted by strength): we pick the - * most widely deployed group at each tier rather than the strongest. - * - * Selection order when not user-defined: - * 1. A standardized PQ/T hybrid using X25519 or SECP256R1, if available. - * 2. SECP256R1, then X25519, then SECP384R1. - * 3. FFDHE 2048 or 3072, for DH-only TLS 1.3 builds. - * 4. preferredGroup[0] as a final fallback for any other configuration. - * - * Users can override the default by defining WOLFSSL_KEY_SHARE_DEFAULT_GROUP - * in user_settings.h to any of the WOLFSSL_* group identifiers from - * wolfssl/ssl.h (or the numeric IANA code point). The macro is substituted - * directly into an assignment, so wrap non-trivial expressions in parentheses. - */ -#ifndef WOLFSSL_KEY_SHARE_DEFAULT_GROUP -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ - !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) && \ - !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE25519) && \ - ECC_MIN_KEY_SZ <= 256 - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_X25519MLKEM768 -#elif defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ - !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) && \ - !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ - (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 256 - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_SECP256R1MLKEM768 -#elif defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ - !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) && \ - !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ - (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 384 - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_SECP384R1MLKEM1024 -#elif defined(HAVE_ECC) && (!defined(NO_ECC256) || \ - defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 && \ - !defined(NO_ECC_SECP) - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_ECC_SECP256R1 -#elif !defined(HAVE_FIPS) && defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_ECC_X25519 -#elif defined(HAVE_ECC) && (defined(HAVE_ECC384) || \ - defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 && \ - !defined(NO_ECC_SECP) - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_ECC_SECP384R1 -#elif defined(HAVE_FFDHE_2048) - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_FFDHE_2048 -#elif defined(HAVE_FFDHE_3072) - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_FFDHE_3072 -#else - /* Fall back to whatever preferredGroup[] starts with. */ - #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP (preferredGroup[0]) +/* One past the worst rank TLSX_KeyShare_GroupRank() can return. It ranks + * against ssl->group[] when the user set a list and against preferredGroup[] + * otherwise, so the sentinel has to cover the longer of the two. */ +#define WOLFSSL_WORST_GROUP_RANK \ + ((int)(((size_t)WOLFSSL_MAX_GROUP_COUNT > PREFERRED_GROUP_SZ) ? \ + (size_t)WOLFSSL_MAX_GROUP_COUNT : PREFERRED_GROUP_SZ)) + +/* WOLFSSL_KEY_SHARE_DEFAULT_GROUP - group used for the speculative key share + * in ClientHello messages when the application has not selected one via + * wolfSSL_CTX_set_groups() / wolfSSL_set_groups() or wolfSSL_UseKeyShare(). + * + * The default is optimized for the likelihood that the server will accept the + * speculative key share without forcing a HelloRetryRequest. It therefore + * differs from preferredGroup[] (which is sorted by strength): we pick the + * most widely deployed group at each tier rather than the strongest. + * + * Selection order when not user-defined: + * 1. A standardized PQ/T hybrid using X25519 or SECP256R1, if available. + * 2. SECP256R1, then X25519, then SECP384R1. + * 3. FFDHE 2048 or 3072, for DH-only TLS 1.3 builds. + * 4. preferredGroup[0] as a final fallback for any other configuration. + * + * Users can override the default by defining WOLFSSL_KEY_SHARE_DEFAULT_GROUP + * in user_settings.h to any of the WOLFSSL_* group identifiers from + * wolfssl/ssl.h (or the numeric IANA code point). The macro is substituted + * directly into an assignment, so wrap non-trivial expressions in parentheses. + */ +#ifndef WOLFSSL_KEY_SHARE_DEFAULT_GROUP +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ + !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) && \ + !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE25519) && \ + ECC_MIN_KEY_SZ <= 256 + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_X25519MLKEM768 +#elif defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ + !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) && \ + !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 256 + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_SECP256R1MLKEM768 +#elif defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ + !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) && \ + !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ + (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ + ECC_MIN_KEY_SZ <= 384 + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_SECP384R1MLKEM1024 +#elif defined(HAVE_ECC) && (!defined(NO_ECC256) || \ + defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 && \ + !defined(NO_ECC_SECP) + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_ECC_SECP256R1 +#elif !defined(HAVE_FIPS) && defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_ECC_X25519 +#elif defined(HAVE_ECC) && (defined(HAVE_ECC384) || \ + defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 && \ + !defined(NO_ECC_SECP) + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_ECC_SECP384R1 +#elif defined(HAVE_FFDHE_2048) + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_FFDHE_2048 +#elif defined(HAVE_FFDHE_3072) + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP WOLFSSL_FFDHE_3072 +#else + /* Fall back to whatever preferredGroup[] starts with. */ + #define WOLFSSL_KEY_SHARE_DEFAULT_GROUP (preferredGroup[0]) #endif #endif /* !WOLFSSL_KEY_SHARE_DEFAULT_GROUP */ @@ -16463,419 +16593,23 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, #ifdef HAVE_SUPPORTED_CURVES -/* Walk the named groups this build offers, most preferred first. - * - * ssl The SSL/TLS object. - * cb Called with each group; 0 continues the walk, anything else stops it. - * ctx Context passed to the callback. - * returns WOLFSSL_SUCCESS when the walk completes, otherwise the value the - * callback stopped it with. - */ -static int TLSX_WalkSupportedGroups(WOLFSSL* ssl, TLSX_GroupWalkCb cb, - void* ctx) -{ - int ret = WOLFSSL_SUCCESS; -#ifdef WOLFSSL_TLS13 -#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) - if (ssl->options.resuming && ssl->session->namedGroup != 0) { - ret = cb(ssl, ssl->session->namedGroup, ctx); - return (ret == 0) ? WOLFSSL_SUCCESS : ret; - } -#endif - - if (ssl->numGroups != 0) { - int i; - for (i = 0; i < ssl->numGroups; i++) { - ret = cb(ssl, ssl->group[i], ctx); - if (ret != 0) - return ret; - } - return WOLFSSL_SUCCESS; - } -#endif /* WOLFSSL_TLS13 */ - -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ - !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_PQC_HYBRIDS) - /* Prefer non-experimental PQ/T hybrid groups (only for TLS 1.3) */ - if (IsAtLeastTLSv1_3(ssl->version) && - TLSX_IsMlKemGroupSupported(ssl->options.side)) { - #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE25519) && \ - ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_X25519MLKEM768, ctx); - if (ret != 0) return ret; - #endif - #if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ - (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 384 - ret = cb(ssl, WOLFSSL_SECP384R1MLKEM1024, ctx); - if (ret != 0) return ret; - #endif - #if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ - (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_SECP256R1MLKEM768, ctx); - if (ret != 0) return ret; - #endif - } -#endif - -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM_CLIENT_SUPPORT) && \ - !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_1024) && \ - !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) - if (IsAtLeastTLSv1_3(ssl->version) && - TLSX_IsMlKemGroupSupported(ssl->options.side)) { - ret = cb(ssl, WOLFSSL_ML_KEM_1024, ctx); - if (ret != 0) return ret; - } -#endif - -#if defined(HAVE_ECC) - /* list in order by strength, since not all servers choose by strength */ - #if (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 - #ifndef NO_ECC_SECP - ret = cb(ssl, WOLFSSL_ECC_SECP521R1, ctx); - if (ret != 0) return ret; - #endif - #endif - #if (defined(HAVE_ECC512) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 512 - #ifdef HAVE_ECC_BRAINPOOL - if (IsAtLeastTLSv1_3(ssl->version)) { - /* TLS 1.3 BrainpoolP512 curve */ - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP512R1TLS13, ctx); - if (ret != 0) return ret; - - /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ - if (ssl->options.downgrade && - (ssl->options.minDowngrade <= TLSv1_2_MINOR || - ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP512R1, ctx); - if (ret != 0) return ret; - } - } - else { - /* TLS 1.2 only */ - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP512R1, ctx); - if (ret != 0) return ret; - } - #endif - #endif -#endif - -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_768) && \ - !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) - if (IsAtLeastTLSv1_3(ssl->version) && - TLSX_IsMlKemGroupSupported(ssl->options.side)) { - ret = cb(ssl, WOLFSSL_ML_KEM_768, ctx); - if (ret != 0) return ret; - } -#endif - -#if defined(HAVE_ECC) - #if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 - #ifndef NO_ECC_SECP - ret = cb(ssl, WOLFSSL_ECC_SECP384R1, ctx); - if (ret != 0) return ret; - #endif - #ifdef HAVE_ECC_BRAINPOOL - if (IsAtLeastTLSv1_3(ssl->version)) { - /* TLS 1.3 BrainpoolP384 curve */ - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP384R1TLS13, ctx); - if (ret != 0) return ret; - - /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ - if (ssl->options.downgrade && - (ssl->options.minDowngrade <= TLSv1_2_MINOR || - ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP384R1, ctx); - if (ret != 0) return ret; - } - } - else { - /* TLS 1.2 only */ - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP384R1, ctx); - if (ret != 0) return ret; - } - #endif - #endif -#endif /* HAVE_ECC */ - -#ifndef HAVE_FIPS - #if defined(HAVE_CURVE448) && ECC_MIN_KEY_SZ <= 448 - ret = cb(ssl, WOLFSSL_ECC_X448, ctx); - if (ret != 0) return ret; - #endif -#endif /* HAVE_FIPS */ - -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_NO_ML_KEM_512) && \ - !defined(WOLFSSL_TLS_NO_MLKEM_STANDALONE) - if (IsAtLeastTLSv1_3(ssl->version) && - TLSX_IsMlKemGroupSupported(ssl->options.side)) { - ret = cb(ssl, WOLFSSL_ML_KEM_512, ctx); - if (ret != 0) return ret; - } -#endif - -#if defined(HAVE_ECC) && defined(HAVE_SUPPORTED_CURVES) - #if (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 - #ifndef NO_ECC_SECP - ret = cb(ssl, WOLFSSL_ECC_SECP256R1, ctx); - if (ret != 0) return ret; - #endif - #ifdef HAVE_ECC_KOBLITZ - ret = cb(ssl, WOLFSSL_ECC_SECP256K1, ctx); - if (ret != 0) return ret; - #endif - #ifdef HAVE_ECC_BRAINPOOL - if (IsAtLeastTLSv1_3(ssl->version)) { - /* TLS 1.3 BrainpoolP256 curve */ - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP256R1TLS13, ctx); - if (ret != 0) return ret; - - /* If TLS 1.2 is allowed, also add the TLS 1.2 curve */ - if (ssl->options.downgrade && - (ssl->options.minDowngrade <= TLSv1_2_MINOR || - ssl->options.minDowngrade <= DTLSv1_2_MINOR)) { - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP256R1, ctx); - if (ret != 0) return ret; - } - } - else { - /* TLS 1.2 only */ - ret = cb(ssl, WOLFSSL_ECC_BRAINPOOLP256R1, ctx); - if (ret != 0) return ret; - } - #endif - #if !defined(HAVE_FIPS) && defined(WOLFSSL_SM2) - ret = cb(ssl, WOLFSSL_ECC_SM2P256V1, ctx); - if (ret != 0) return ret; - #endif - #endif -#endif /* HAVE_ECC */ - -#ifndef HAVE_FIPS - #if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_ECC_X25519, ctx); - if (ret != 0) return ret; - #endif -#endif /* HAVE_FIPS */ - -#if defined(HAVE_ECC) && defined(HAVE_SUPPORTED_CURVES) - #if (defined(HAVE_ECC224) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 224 - #ifndef NO_ECC_SECP - ret = cb(ssl, WOLFSSL_ECC_SECP224R1, ctx); - if (ret != 0) return ret; - #endif - #ifdef HAVE_ECC_KOBLITZ - ret = cb(ssl, WOLFSSL_ECC_SECP224K1, ctx); - if (ret != 0) return ret; - #endif - #endif - - #ifndef HAVE_FIPS - #if (defined(HAVE_ECC192) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 192 - #ifndef NO_ECC_SECP - ret = cb(ssl, WOLFSSL_ECC_SECP192R1, ctx); - if (ret != 0) return ret; - #endif - #ifdef HAVE_ECC_KOBLITZ - ret = cb(ssl, WOLFSSL_ECC_SECP192K1, ctx); - if (ret != 0) return ret; - #endif - #endif - #if (defined(HAVE_ECC160) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 160 - #ifndef NO_ECC_SECP - ret = cb(ssl, WOLFSSL_ECC_SECP160R1, ctx); - if (ret != 0) return ret; - #endif - #ifdef HAVE_ECC_SECPR2 - ret = cb(ssl, WOLFSSL_ECC_SECP160R2, ctx); - if (ret != 0) return ret; - #endif - #ifdef HAVE_ECC_KOBLITZ - ret = cb(ssl, WOLFSSL_ECC_SECP160K1, ctx); - if (ret != 0) return ret; - #endif - #endif - #endif /* HAVE_FIPS */ -#endif /* HAVE_ECC */ - -#ifndef NO_DH - /* Add FFDHE supported groups. */ - #ifdef HAVE_FFDHE_8192 - if (8192/8 >= ssl->options.minDhKeySz && - 8192/8 <= ssl->options.maxDhKeySz) { - ret = cb(ssl, WOLFSSL_FFDHE_8192, ctx); - if (ret != 0) - return ret; - } - #endif - #ifdef HAVE_FFDHE_6144 - if (6144/8 >= ssl->options.minDhKeySz && - 6144/8 <= ssl->options.maxDhKeySz) { - ret = cb(ssl, WOLFSSL_FFDHE_6144, ctx); - if (ret != 0) - return ret; - } - #endif - #ifdef HAVE_FFDHE_4096 - if (4096/8 >= ssl->options.minDhKeySz && - 4096/8 <= ssl->options.maxDhKeySz) { - ret = cb(ssl, WOLFSSL_FFDHE_4096, ctx); - if (ret != 0) - return ret; - } - #endif - #ifdef HAVE_FFDHE_3072 - if (3072/8 >= ssl->options.minDhKeySz && - 3072/8 <= ssl->options.maxDhKeySz) { - ret = cb(ssl, WOLFSSL_FFDHE_3072, ctx); - if (ret != 0) - return ret; - } - #endif - #ifdef HAVE_FFDHE_2048 - if (2048/8 >= ssl->options.minDhKeySz && - 2048/8 <= ssl->options.maxDhKeySz) { - ret = cb(ssl, WOLFSSL_FFDHE_2048, ctx); - if (ret != 0) - return ret; - } - #endif -#endif - -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - !defined(WOLFSSL_NO_ML_KEM) && defined(WOLFSSL_EXTRA_PQC_HYBRIDS) - if (IsAtLeastTLSv1_3(ssl->version) && - TLSX_IsMlKemGroupSupported(ssl->options.side)) { -#if !defined(WOLFSSL_NO_ML_KEM_1024) && defined(HAVE_ECC) && \ - (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 - ret = cb(ssl, WOLFSSL_SECP521R1MLKEM1024, ctx); - if (ret != 0) return ret; -#endif -#if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ - (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 - ret = cb(ssl, WOLFSSL_SECP384R1MLKEM768, ctx); - if (ret != 0) return ret; -#endif -#if !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_CURVE448) && \ - ECC_MIN_KEY_SZ <= 448 - ret = cb(ssl, WOLFSSL_X448MLKEM768, ctx); - if (ret != 0) return ret; -#endif -#if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_ECC) && \ - (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_SECP256R1MLKEM512, ctx); - if (ret != 0) return ret; -#endif -#if !defined(WOLFSSL_NO_ML_KEM_512) && defined(HAVE_CURVE25519) && \ - ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_X25519MLKEM512, ctx); - if (ret != 0) return ret; -#endif - } -#endif - -#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) && \ - defined(WOLFSSL_MLKEM_KYBER) - if (IsAtLeastTLSv1_3(ssl->version) && - TLSX_IsMlKemGroupSupported(ssl->options.side)) { -#ifdef WOLFSSL_KYBER1024 - ret = cb(ssl, WOLFSSL_KYBER_LEVEL5, ctx); - if (ret != 0) return ret; -#if defined(HAVE_ECC) && (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 521 - ret = cb(ssl, WOLFSSL_P521_KYBER_LEVEL5, ctx); - if (ret != 0) return ret; -#endif -#endif -#ifdef WOLFSSL_KYBER768 - ret = cb(ssl, WOLFSSL_KYBER_LEVEL3, ctx); - if (ret != 0) return ret; -#if defined(HAVE_ECC) && (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 384 - ret = cb(ssl, WOLFSSL_P384_KYBER_LEVEL3, ctx); - if (ret != 0) return ret; -#endif -#if defined(HAVE_ECC) && (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_P256_KYBER_LEVEL3, ctx); - if (ret != 0) return ret; -#endif -#if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_X25519_KYBER_LEVEL3, ctx); - if (ret != 0) return ret; -#endif -#if defined(HAVE_CURVE448) && ECC_MIN_KEY_SZ <= 448 - ret = cb(ssl, WOLFSSL_X448_KYBER_LEVEL3, ctx); - if (ret != 0) return ret; -#endif -#endif -#ifdef WOLFSSL_KYBER512 - ret = cb(ssl, WOLFSSL_KYBER_LEVEL1, ctx); - if (ret != 0) return ret; -#if defined(HAVE_ECC) && (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && \ - ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_P256_KYBER_LEVEL1, ctx); - if (ret != 0) return ret; -#endif -#if defined(HAVE_CURVE25519) && ECC_MIN_KEY_SZ <= 256 - ret = cb(ssl, WOLFSSL_X25519_KYBER_LEVEL1, ctx); - if (ret != 0) return ret; -#endif -#endif - } -#endif - - (void)ssl; - (void)ctx; - - return WOLFSSL_SUCCESS; -} - -/* Adds a group offered by this build to an extension list. */ -static int TLSX_SupportedGroup_Add(WOLFSSL* ssl, word16 name, void* ctx) -{ - int ret = TLSX_UseSupportedCurve((TLSX**)ctx, name, ssl->heap, - ssl->options.side); - - return (ret == WOLFSSL_SUCCESS) ? 0 : ret; -} - /* Populates the default supported groups / curves */ static int TLSX_PopulateSupportedGroups(WOLFSSL* ssl, TLSX** extensions) { - return TLSX_WalkSupportedGroups(ssl, TLSX_SupportedGroup_Add, extensions); -} - -#if !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TLS13) && \ - !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) -/* Keeps the first group the walk offers. */ -static int TLSX_SupportedGroup_Keep(WOLFSSL* ssl, word16 name, void* ctx) -{ - (void)ssl; - - *(word16*)ctx = name; - - return 1; -} - -/* Return the group this build prefers most, without building a list of them. - * - * ssl The SSL/TLS object. - * returns the group name, or 0 when this build offers none. - */ -static word16 TLSX_PreferredSupportedGroup(WOLFSSL* ssl) -{ - word16 name = 0; + word16 idx = 0; + word16 name; + int ret; - /* The callback always stops the walk, so it cannot fail. */ - (void)TLSX_WalkSupportedGroups(ssl, TLSX_SupportedGroup_Keep, &name); + while ((name = TLSX_NextSupportedGroup(ssl, &idx)) != + WOLFSSL_NAMED_GROUP_INVALID) { + ret = TLSX_UseSupportedCurve(extensions, name, ssl->heap, + ssl->options.side); + if (ret != WOLFSSL_SUCCESS) + return ret; + } - return name; + return WOLFSSL_SUCCESS; } -#endif #endif /* HAVE_SUPPORTED_CURVES */ diff --git a/tests/api.c b/tests/api.c index 319b1c6eac3..37880e8c9b2 100644 --- a/tests/api.c +++ b/tests/api.c @@ -47161,6 +47161,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_TLSX_SupportedCurve_intersection), TEST_DECL(test_TLSX_SupportedCurve_check_priority), TEST_DECL(test_TLSX_SupportedFFDHE_Set), + TEST_DECL(test_TLSX_SupportedGroups_default_filter), TEST_DECL(test_TLSX_PointFormat_uncompressed_required), TEST_DECL(test_wolfSSL_CTX_add_client_custom_ext), TEST_DECL(test_wolfSSL_custom_ext_handshake), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index d1b8ec76d92..257281c2735 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3497,7 +3497,7 @@ int test_TLSX_SupportedCurve_grow(void) !defined(NO_WOLFSSL_SERVER) /* FFDHE range (256..511) codepoints no build implements, so every one * of them takes the keep path. */ - #define TEST_GROW_CNT (WOLFSSL_MAX_GROUP_COUNT + 4) + #define TEST_GROW_CNT 128 #define TEST_GROW_FIRST 0x0140 WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -3505,7 +3505,6 @@ int test_TLSX_SupportedCurve_grow(void) TLSX* extension = NULL; SupportedCurves* curves = NULL; byte peerList[6 + TEST_GROW_CNT * 2]; - /* One name: its capacity is the first allocation's, read not hard coded. */ const byte oneName[] = { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x01, 0x40 }; int initCap = -1; word16 i; @@ -3524,7 +3523,6 @@ int test_TLSX_SupportedCurve_grow(void) ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); - /* First the capacity a single name gets. */ ExpectNotNull(ssl = wolfSSL_new(ctx)); if (ssl != NULL) suites = (Suites*)WOLFSSL_SUITES(ssl); @@ -3560,7 +3558,6 @@ int test_TLSX_SupportedCurve_grow(void) ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, TEST_GROW_CNT); ExpectIntGE(curves == NULL ? -1 : (int)curves->cap, TEST_GROW_CNT); - /* It really did grow, so the copy path ran. */ ExpectIntGT(curves == NULL ? -1 : (int)curves->cap, initCap); for (i = 0; (curves != NULL) && (i < TEST_GROW_CNT); i++) { ExpectIntEQ(curves->name[i], TEST_GROW_FIRST + i); @@ -3603,8 +3600,7 @@ int test_TLSX_SupportedCurve_intersection(void) const byte noCommon13[] = { 0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0xee, 0xee, 0x00, 0x33, 0x00, 0x02, 0x00, 0x00 }; #endif - /* FFDHE codepoints this build lacks, interleaved with common groups so - * the append-then-swap runs mid-partition. */ + /* FFDHE codepoints this build lacks, interleaved with common groups. */ const byte ffdheMix[] = { 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x18, 0x01, 0x03, 0x00, 0x17, 0x01, 0x04 }; @@ -3646,8 +3642,8 @@ int test_TLSX_SupportedCurve_intersection(void) wolfSSL_free(ssl); ssl = NULL; - /* RFC 7919 Section 4: an FFDHE codepoint this build lacks is appended and - * then swapped into the kept prefix, leaving the peer's order. */ + /* RFC 7919 Section 4: the first FFDHE codepoint this build lacks is kept, + * in the peer's order. */ extension = NULL; curves = NULL; ExpectNotNull(ssl = wolfSSL_new(ctx)); @@ -3665,19 +3661,15 @@ int test_TLSX_SupportedCurve_intersection(void) if (extension != NULL) curves = (SupportedCurves*)extension->data; ExpectNotNull(curves); -/* The exact result needs both codepoints to be unimplemented here: having one - * of them shifts secp256r1's position. */ #if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(HAVE_FFDHE_6144) && \ - !defined(HAVE_FFDHE_8192) - ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 4); + !defined(NO_WOLFSSL_SERVER) && !defined(HAVE_FFDHE_6144) + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 3); ExpectIntEQ(curves == NULL ? 0 : curves->name[0], WOLFSSL_ECC_SECP384R1); ExpectIntEQ(curves == NULL ? 0 : curves->name[1], WOLFSSL_FFDHE_6144); ExpectIntEQ(curves == NULL ? 0 : curves->name[2], WOLFSSL_ECC_SECP256R1); - ExpectIntEQ(curves == NULL ? 0 : curves->name[3], WOLFSSL_FFDHE_8192); #else - /* Otherwise the count and secp256r1's position depend on the build, but - * both ECC groups are kept and secp384r1 leads. */ + /* Otherwise the FFDHE entry depends on the build, but both ECC groups + * are kept and secp384r1 leads. */ ExpectIntGE(curves == NULL ? -1 : (int)curves->count, 2); ExpectIntEQ(curves == NULL ? 0 : curves->name[0], WOLFSSL_ECC_SECP384R1); if (curves != NULL) { @@ -3695,7 +3687,7 @@ int test_TLSX_SupportedCurve_intersection(void) ctx = NULL; #ifdef WOLFSSL_TLS13 - /* TLS 1.3 can answer with an HRR, so no common group empties the list. */ + /* TLS 1.3 can answer with an HRR, so no common group is not an error. */ extension = NULL; curves = NULL; ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); @@ -3710,9 +3702,7 @@ int test_TLSX_SupportedCurve_intersection(void) extension = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); ExpectNotNull(extension); if (extension != NULL) - curves = (SupportedCurves*)extension->data; - ExpectNotNull(curves); - ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 0); + ExpectNull(extension->data); wolfSSL_free(ssl); ssl = NULL; wolfSSL_CTX_free(ctx); @@ -3726,8 +3716,6 @@ int test_TLSX_SupportedCurve_intersection(void) int test_TLSX_SupportedFFDHE_Set(void) { EXPECT_DECLS; -/* HAVE_FFDHE_2048 pins the result: a client list holding only ffdhe2048 - * leaves one outcome whatever else the build implements. */ #if !defined(NO_TLS) && !defined(NO_DH) && !defined(NO_WOLFSSL_SERVER) && \ !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SUPPORTED_CURVES) && \ defined(HAVE_TLS_EXTENSIONS) && defined(HAVE_FFDHE) && \ @@ -3783,7 +3771,7 @@ int test_TLSX_SupportedFFDHE_Set(void) suites = NULL; /* A floor above the only common group selects nothing, and that is - * success. The walk's bound stops it before the callback's own check. */ + * success. */ ExpectNotNull(ssl = wolfSSL_new(ctx)); ExpectIntEQ(wolfSSL_SetMinDhKey_Sz(ssl, 8192), WOLFSSL_SUCCESS); if (ssl != NULL) @@ -3852,12 +3840,10 @@ int test_TLSX_SupportedFFDHE_Set(void) } /* When the client does not offer the group this build prefers, the server swaps - * its own list in and marks it for sending back (the TLS 1.3 HRR). */ + * its own list in and marks it for sending back in EncryptedExtensions. */ int test_TLSX_SupportedCurve_check_priority(void) { EXPECT_DECLS; -/* CheckPriority() only exists under !NO_WOLFSSL_SERVER; nothing to link - * against otherwise. */ #if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_SERVER_GROUPS_EXT) && \ !defined(NO_WOLFSSL_SERVER) && !defined(NO_CERTS) && \ !defined(NO_FILESYSTEM) && \ @@ -3876,8 +3862,8 @@ int test_TLSX_SupportedCurve_check_priority(void) ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, eccKeyFile, CERT_FILETYPE), WOLFSSL_SUCCESS); - /* The client offers only a private-use codepoint, so this build's own list - * goes back. Its first group is the preference the second half needs. */ + /* The client offers only a private-use codepoint, so our own list goes + * back. */ ExpectNotNull(ssl = wolfSSL_new(ctx)); if (ssl != NULL) { ExpectIntEQ(wolfSSL_UseSupportedCurve(ssl, WOLFSSL_ECC_SECP256R1), @@ -3936,6 +3922,79 @@ int test_TLSX_SupportedCurve_check_priority(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && defined(HAVE_ECC_BRAINPOOL) && \ + defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_TLS12) && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 +static int test_tls_ext_group_count(WOLFSSL* ssl, word16 name) +{ + TLSX* ext = TLSX_Find(ssl->extensions, TLSX_SUPPORTED_GROUPS); + SupportedCurves* curves = NULL; + int cnt = 0; + word16 i; + + if (ext != NULL) + curves = (SupportedCurves*)ext->data; + for (i = 0; (curves != NULL) && (i < curves->count); i++) { + if (curves->name[i] == name) + cnt++; + } + return cnt; +} +#endif + +/* The default list follows the version: RFC 7027 brainpool groups for TLS 1.2, + * RFC 8734 ones for TLS 1.3, both when TLS 1.3 may fall back to TLS 1.2, and + * no ML-KEM groups for TLS 1.2. */ +int test_TLSX_SupportedGroups_default_filter(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(HAVE_SUPPORTED_CURVES) && defined(HAVE_ECC_BRAINPOOL) && \ + defined(WOLFSSL_TLS13) && !defined(WOLFSSL_NO_TLS12) && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 256 + const method_provider methods[] = { wolfTLSv1_2_client_method, + wolfTLSv1_3_client_method, wolfSSLv23_client_method }; + const int want12[] = { 1, 0, 1 }; + const int want13[] = { 0, 1, 1 }; + int m; + + for (m = 0; m < (int)(sizeof(methods) / sizeof(methods[0])); m++) { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, methods[m], wolfSSLv23_server_method), 0); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + if (ssl_c != NULL) { + ExpectIntEQ(test_tls_ext_group_count(ssl_c, + WOLFSSL_ECC_BRAINPOOLP256R1), want12[m]); + ExpectIntEQ(test_tls_ext_group_count(ssl_c, + WOLFSSL_ECC_BRAINPOOLP256R1TLS13), want13[m]); + if (want13[m] == 0) { + ExpectIntEQ(test_tls_ext_group_count(ssl_c, + WOLFSSL_ML_KEM_768), 0); + ExpectIntEQ(test_tls_ext_group_count(ssl_c, + WOLFSSL_X25519MLKEM768), 0); + ExpectIntEQ(test_tls_ext_group_count(ssl_c, + WOLFSSL_SECP256R1MLKEM768), 0); + } + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + /* RFC 8422 Section 5.1.2: a client that sends the ec_point_formats extension * MUST include the uncompressed (0) point format. When the uncompressed format * is omitted the server records this (ssl->options.peerNoUncompPF) during diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 2e00dd4e9ff..2a647eb5660 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -75,6 +75,7 @@ int test_TLSX_SupportedCurve_grow(void); int test_TLSX_SupportedCurve_intersection(void); int test_TLSX_SupportedCurve_check_priority(void); int test_TLSX_SupportedFFDHE_Set(void); +int test_TLSX_SupportedGroups_default_filter(void); int test_TLSX_PointFormat_uncompressed_required(void); int test_wolfSSL_CTX_add_client_custom_ext(void); int test_wolfSSL_custom_ext_handshake(void); diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index 9620de78314..dde73199be7 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -141,6 +141,14 @@ static void* tls_parse_fail_realloc(void* ptr, size_t size) { return realloc(ptr, size); } + +TEST_TLS_PARSE_UNUSED +static void* tls_parse_null_realloc(void* ptr, size_t size) +{ + (void)ptr; + (void)size; + return NULL; +} #endif /* WOLFSSL_TEST_STATIC_BUILD && !NO_TLS */ #if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(WOLFSSL_TLS13) && \ @@ -2331,66 +2339,72 @@ int test_TLSX_SupportedGroups_parse(void) extensions = NULL; #ifdef WOLFSSL_TEST_STATIC_BUILD - /* The intersection is made in place, so it allocates nothing. */ + /* New lists come from malloc and do not depend on realloc(NULL). */ { wolfSSL_Malloc_cb prevM = NULL; wolfSSL_Free_cb prevF = NULL; wolfSSL_Realloc_cb prevR = NULL; + ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); + ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, + tls_parse_fail_free, tls_parse_null_realloc), 0); ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, WOLFSSL_ECC_SECP256R1, ssl->heap, ssl->options.side), WOLFSSL_SUCCESS); + (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + } + + /* A failed grow leaves the list in the extension as it was. */ + { + wolfSSL_Malloc_cb prevM = NULL; + wolfSSL_Free_cb prevF = NULL; + wolfSSL_Realloc_cb prevR = NULL; + TLSX* ext = NULL; + SupportedCurves* before = NULL; + SupportedCurves* curves = NULL; + + ExpectIntEQ(test_tls_parse_push_curve(&extensions, ssl, + WOLFSSL_ECC_SECP256R1), 0); + ExpectNotNull(ext = TLSX_Find(extensions, + TLSX_SUPPORTED_GROUPS)); + if (ext != NULL) + before = (SupportedCurves*)ext->data; ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, - tls_parse_fail_free, tls_parse_fail_realloc), 0); + tls_parse_fail_free, tls_parse_null_realloc), 0); tls_parse_alloc_seen = 0; tls_parse_fail_after = 0; - - ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, - (word16)sizeof(goodBody), 1, &extensions), 0); - ExpectIntEQ(tls_parse_alloc_seen, 0); - + ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, + WOLFSSL_ECC_SECP384R1, ssl->heap, ssl->options.side), + WC_NO_ERR_TRACE(MEMORY_E)); tls_parse_fail_after = -1; (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + + if (ext != NULL) + curves = (SupportedCurves*)ext->data; + ExpectPtrEq(curves, before); + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 1); + ExpectIntEQ(curves == NULL ? 0 : curves->name[0], + WOLFSSL_ECC_SECP256R1); TLSX_FreeAll(extensions, NULL); extensions = NULL; } - /* Unsupported FFDHE codepoints are appended until the list is full, - * then the regrow fails and the list in the extension survives. */ + /* The intersection is one allocation; when it fails the list in the + * extension is left as it was. */ { wolfSSL_Malloc_cb prevM = NULL; wolfSSL_Free_cb prevF = NULL; wolfSSL_Realloc_cb prevR = NULL; - byte ffdheMany[OPAQUE16_LEN + - WOLFSSL_MAX_GROUP_COUNT * OPAQUE16_LEN]; TLSX* ext = NULL; - word16 cap = 0; - word16 len; - word16 i; + SupportedCurves* curves = NULL; ExpectIntEQ(TLSX_UseSupportedCurve(&extensions, WOLFSSL_ECC_SECP256R1, ssl->heap, ssl->options.side), WOLFSSL_SUCCESS); - ExpectNotNull(ext = TLSX_Find(extensions, - TLSX_SUPPORTED_GROUPS)); - if ((ext != NULL) && (ext->data != NULL)) - cap = ((SupportedCurves*)ext->data)->cap; - ExpectIntGT(cap, 0); - ExpectIntLE(cap, WOLFSSL_MAX_GROUP_COUNT); - if (cap > WOLFSSL_MAX_GROUP_COUNT) - cap = 0; - - len = (word16)(cap * OPAQUE16_LEN); - ffdheMany[0] = (byte)(len >> 8); - ffdheMany[1] = (byte)len; - for (i = 0; i < cap; i++) { - word16 name = (word16)(0x0120 + i); - - ffdheMany[OPAQUE16_LEN + i * OPAQUE16_LEN] = (byte)(name >> 8); - ffdheMany[OPAQUE16_LEN + i * OPAQUE16_LEN + 1] = (byte)name; - } ExpectIntEQ(wolfSSL_GetAllocators(&prevM, &prevF, &prevR), 0); ExpectIntEQ(wolfSSL_SetAllocators(tls_parse_fail_malloc, @@ -2398,23 +2412,109 @@ int test_TLSX_SupportedGroups_parse(void) tls_parse_alloc_seen = 0; tls_parse_fail_after = 0; - ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, ffdheMany, - (word16)(OPAQUE16_LEN + len), 1, &extensions), + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, + (word16)sizeof(goodBody), 1, &extensions), WC_NO_ERR_TRACE(MEMORY_E)); ExpectIntEQ(tls_parse_alloc_seen, 1); + ExpectNotNull(ext = TLSX_Find(extensions, + TLSX_SUPPORTED_GROUPS)); + if (ext != NULL) + curves = (SupportedCurves*)ext->data; + ExpectNotNull(curves); + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 1); + ExpectIntEQ(curves == NULL ? 0 : curves->name[0], + WOLFSSL_ECC_SECP256R1); + + tls_parse_alloc_seen = 0; + tls_parse_fail_after = 1; + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, goodBody, + (word16)sizeof(goodBody), 1, &extensions), 0); + ExpectIntEQ(tls_parse_alloc_seen, 1); + tls_parse_fail_after = -1; (void)wolfSSL_SetAllocators(prevM, prevF, prevR); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + } + + /* Only the first FFDHE codepoint this build lacks is kept, in the + * peer's order. */ + { + /* secp256r1, then two FFDHE codepoints no build implements. */ + const byte ffdheTwo[] = { 0x00, 0x06, 0x00, 0x17, 0x01, 0x20, + 0x01, 0x21 }; + TLSX* ext = NULL; + SupportedCurves* curves = NULL; + + ExpectIntEQ(test_tls_parse_push_curve(&extensions, ssl, + WOLFSSL_ECC_SECP256R1), 0); + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, ffdheTwo, + (word16)sizeof(ffdheTwo), 1, &extensions), 0); ExpectNotNull(ext = TLSX_Find(extensions, TLSX_SUPPORTED_GROUPS)); - if (ext != NULL) { - ExpectNotNull(ext->data); - if (ext->data != NULL) { - ExpectIntEQ(((SupportedCurves*)ext->data)->count, cap); - } + if (ext != NULL) + curves = (SupportedCurves*)ext->data; + ExpectNotNull(curves); + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, 2); + ExpectIntEQ(curves == NULL ? 0 : curves->name[0], + WOLFSSL_ECC_SECP256R1); + ExpectIntEQ(curves == NULL ? 0 : curves->name[1], 0x0120); + TLSX_FreeAll(extensions, NULL); + extensions = NULL; + } + + /* After an HRR the second ClientHello is intersected with the first + * one's list, which can be longer than WOLFSSL_MAX_GROUP_COUNT. */ + { + #define TEST_LONG_CNT (WOLFSSL_MAX_GROUP_COUNT + 2) + byte peer[OPAQUE16_LEN + TEST_LONG_CNT * OPAQUE16_LEN]; + TLSX* ext = NULL; + SupportedCurves* curves = NULL; + word16 name; + word16 i; + int ret; + + curves = (SupportedCurves*)XMALLOC(sizeof(SupportedCurves) + + TEST_LONG_CNT * sizeof(word16), ssl->heap, + DYNAMIC_TYPE_TLSX); + ExpectNotNull(curves); + if (curves != NULL) { + curves->count = TEST_LONG_CNT; + curves->cap = TEST_LONG_CNT; + for (i = 0; i < TEST_LONG_CNT; i++) + curves->name[i] = (word16)(0x0a00 + i); + ret = TLSX_Push(&extensions, TLSX_SUPPORTED_GROUPS, curves, + ssl->heap); + if (ret != 0) + XFREE(curves, ssl->heap, DYNAMIC_TYPE_TLSX); + ExpectIntEQ(ret, 0); + } + + peer[0] = (byte)((TEST_LONG_CNT * OPAQUE16_LEN) >> 8); + peer[1] = (byte)(TEST_LONG_CNT * OPAQUE16_LEN); + for (i = 0; i < TEST_LONG_CNT; i++) { + name = (word16)(0x0a00 + TEST_LONG_CNT - 1 - i); + peer[OPAQUE16_LEN + i * OPAQUE16_LEN] = (byte)(name >> 8); + peer[OPAQUE16_LEN + i * OPAQUE16_LEN + 1] = (byte)name; + } + + ExpectIntEQ(TLSX_SupportedCurve_Parse(ssl, peer, + (word16)sizeof(peer), 1, &extensions), 0); + ExpectNotNull(ext = TLSX_Find(extensions, + TLSX_SUPPORTED_GROUPS)); + curves = NULL; + if (ext != NULL) + curves = (SupportedCurves*)ext->data; + ExpectNotNull(curves); + ExpectIntEQ(curves == NULL ? -1 : (int)curves->count, + TEST_LONG_CNT); + for (i = 0; (curves != NULL) && (i < curves->count); i++) { + ExpectIntEQ(curves->name[i], 0x0a00 + TEST_LONG_CNT - 1 - i); } TLSX_FreeAll(extensions, NULL); extensions = NULL; + #undef TEST_LONG_CNT } #endif } @@ -2444,8 +2544,8 @@ int test_TLSX_SupportedGroups_parse(void) TLSX* ext = TLSX_Find(extensions, TLSX_SUPPORTED_GROUPS); ExpectNotNull(ext); - if ((ext != NULL) && (ext->data != NULL)) - ExpectIntEQ(((SupportedCurves*)ext->data)->count, 0); + if (ext != NULL) + ExpectNull(ext->data); } TLSX_FreeAll(extensions, NULL); } diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 5689b7c7ba2..bf1b9fd6a66 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3959,7 +3959,6 @@ WOLFSSL_LOCAL int TLSX_ValidateSupportedCurves(const WOLFSSL* ssl, byte first, wolfSSL_TLSX_SupportedCurve_CheckPriority #define TLSX_SupportedFFDHE_Set wolfSSL_TLSX_SupportedFFDHE_Set #endif -/* WOLFSSL_TEST_VIS: no public API reaches these; the API tests call them. */ WOLFSSL_TEST_VIS int TLSX_SupportedCurve_CheckPriority(WOLFSSL* ssl); WOLFSSL_TEST_VIS int TLSX_SupportedFFDHE_Set(WOLFSSL* ssl); #endif From 302ea953c002fd6500854f9cc6185281a8c1a1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 22 Oct 2026 15:18:24 +0200 Subject: [PATCH 5/5] Restate the RFC 7919 rule in the intersection comment The comment in the server's intersection loop pointed back at the one in the parse loop. State the RFC 7919 Section 4 requirement directly instead. --- src/tls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tls.c b/src/tls.c index a63b717c9f3..df592f9b2ca 100644 --- a/src/tls.c +++ b/src/tls.c @@ -5841,8 +5841,8 @@ int TLSX_SupportedCurve_Parse(const WOLFSSL* ssl, const byte* input, keep = (i < count); #if !defined(NO_DH) && !defined(WOLFSSL_NO_TLS12) && \ !defined(NO_WOLFSSL_SERVER) - /* RFC 7919 Section 4 (see above): one unsupported FFDHE codepoint - * is enough to keep the restriction. */ + /* RFC 7919 Section 4: any FFDHE codepoint offered restricts DHE + * to named groups; keep the first unsupported one to record it. */ if (!keep && !ffdhe && isRequest && WOLFSSL_NAMED_GROUP_IS_FFDHE(name) && !TLSX_IsGroupSupported(name, ssl->options.side)) {