diff --git a/src/tls.c b/src/tls.c index 34d6f3b2b2..29e2ec773d 100644 --- a/src/tls.c +++ b/src/tls.c @@ -4947,20 +4947,74 @@ static int TLSX_SupportedCurve_New(SupportedCurve** curve, word16 name, return 0; } -static int TLSX_PointFormat_New(PointFormat** point, byte format, void* heap) +/* Formats the first allocation holds. */ +#define POINT_FORMATS_INIT_CNT 4 +/* Most formats a list can hold. The capacity is a byte. */ +#define POINT_FORMATS_MAX_CNT 128 +/* Append() grows with (byte)(count + 1), which must not wrap. */ +wc_static_assert(POINT_FORMATS_MAX_CNT < 0xFFU); +/* Powers of two, so the doubling in Grow() lands on the maximum exactly. */ +wc_static_assert((POINT_FORMATS_INIT_CNT & + (POINT_FORMATS_INIT_CNT - 1)) == 0); +wc_static_assert((POINT_FORMATS_MAX_CNT & + (POINT_FORMATS_MAX_CNT - 1)) == 0); +wc_static_assert(POINT_FORMATS_INIT_CNT <= POINT_FORMATS_MAX_CNT); + +/* Make room for the requested number of point formats. + * + * formats The list to grow, allocated when NULL. Updated on reallocation. + * cnt The number of formats the list has to hold. + * heap The heap used for allocation. + * returns 0 on success, otherwise failure. + */ +static int TLSX_PointFormat_Grow(PointFormats** formats, byte cnt, void* heap) { - if (point == NULL) + PointFormats* list = *formats; + PointFormats* newList; + word16 cap = POINT_FORMATS_INIT_CNT; + + if ((list != NULL) && (cnt <= list->cap)) + return 0; + if (cnt > POINT_FORMATS_MAX_CNT) return BAD_FUNC_ARG; - (void)heap; + while (cap < cnt) + cap *= 2; - *point = (PointFormat*)XMALLOC(sizeof(PointFormat), heap, - DYNAMIC_TYPE_TLSX); - if (*point == NULL) + newList = (PointFormats*)XMALLOC(sizeof(PointFormats) + cap, heap, + DYNAMIC_TYPE_TLSX); + if (newList == NULL) return MEMORY_E; - (*point)->format = format; - (*point)->next = NULL; + newList->count = 0; + newList->cap = (byte)cap; + + if (list != NULL) { + newList->count = list->count; + XMEMCPY(newList->format, list->format, list->count); + XFREE(list, heap, DYNAMIC_TYPE_TLSX); + } + + *formats = newList; + + return 0; +} + +static int TLSX_PointFormat_New(PointFormats** formats, byte format, + void* heap) +{ + PointFormats* list = NULL; + int ret; + + if (formats == NULL) + return BAD_FUNC_ARG; + + ret = TLSX_PointFormat_Grow(&list, 1, heap); + if (ret != 0) + return ret; + + list->format[list->count++] = format; + *formats = list; return 0; } @@ -4976,14 +5030,10 @@ static void TLSX_SupportedCurve_FreeAll(SupportedCurve* list, void* heap) (void)heap; } -static void TLSX_PointFormat_FreeAll(PointFormat* list, void* heap) +static void TLSX_PointFormat_FreeAll(PointFormats* list, void* heap) { - PointFormat* point; - - while ((point = list)) { - list = point->next; - XFREE(point, heap, DYNAMIC_TYPE_TLSX); - } + if (list != NULL) + XFREE(list, heap, DYNAMIC_TYPE_TLSX); (void)heap; } @@ -5009,25 +5059,30 @@ static int TLSX_SupportedCurve_Append(SupportedCurve* list, word16 name, return ret; } -static int TLSX_PointFormat_Append(PointFormat* list, byte format, void* heap) +static int TLSX_PointFormat_Append(PointFormats** formats, byte format, + void* heap) { - int ret = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + PointFormats* list; + int ret; + byte i; - while (list) { - if (list->format == format) { - ret = 0; /* format already in use */ - break; - } + if ((formats == NULL) || (*formats == NULL)) + return BAD_FUNC_ARG; - if (list->next == NULL) { - ret = TLSX_PointFormat_New(&list->next, format, heap); - break; - } - - list = list->next; + list = *formats; + for (i = 0; i < list->count; i++) { + if (list->format[i] == format) + return 0; /* format already in use */ } - return ret; + ret = TLSX_PointFormat_Grow(formats, (byte)(list->count + 1), heap); + if (ret != 0) + return ret; + + list = *formats; + list->format[list->count++] = format; + + return 0; } #if defined(WOLFSSL_TLS13) || !defined(NO_WOLFSSL_CLIENT) @@ -5192,15 +5247,12 @@ static word16 TLSX_SupportedCurve_GetSize(SupportedCurve* list) #endif -static word16 TLSX_PointFormat_GetSize(PointFormat* list) +static word16 TLSX_PointFormat_GetSize(PointFormats* list) { - PointFormat* point; word16 length = ENUM_LEN; /* list length */ - while ((point = list)) { - list = point->next; - length += ENUM_LEN; /* format length */ - } + if (list != NULL) + length = (word16)(length + list->count * ENUM_LEN); return length; } @@ -5224,13 +5276,14 @@ static word16 TLSX_SupportedCurve_Write(SupportedCurve* list, byte* output) #endif -static word16 TLSX_PointFormat_Write(PointFormat* list, byte* output) +static word16 TLSX_PointFormat_Write(PointFormats* list, byte* output) { word16 offset = ENUM_LEN; + byte i; - while (list) { - output[offset++] = list->format; - list = list->next; + if (list != NULL) { + for (i = 0; i < list->count; i++) + output[offset++] = list->format[i]; } output[0] = (byte)(offset - ENUM_LEN); @@ -6211,10 +6264,19 @@ int TLSX_UseSupportedCurve(TLSX** extensions, word16 name, void* heap, int side) return WOLFSSL_SUCCESS; } +/* Add a point format to the extension's list, creating the extension when it + * does not exist yet. Appending may reallocate extension->data. + * + * extensions The list of extensions. + * format The point format to add. + * heap The heap used for allocation. + * returns WOLFSSL_SUCCESS on success, BAD_FUNC_ARG at + * POINT_FORMATS_MAX_CNT formats, otherwise an allocation failure. + */ int TLSX_UsePointFormat(TLSX** extensions, byte format, void* heap) { TLSX* extension = NULL; - PointFormat* point = NULL; + PointFormats* formats = NULL; int ret = 0; if (extensions == NULL) @@ -6223,19 +6285,19 @@ int TLSX_UsePointFormat(TLSX** extensions, byte format, void* heap) extension = TLSX_Find(*extensions, TLSX_EC_POINT_FORMATS); if (!extension) { - ret = TLSX_PointFormat_New(&point, format, heap); + ret = TLSX_PointFormat_New(&formats, format, heap); if (ret != 0) return ret; - ret = TLSX_Push(extensions, TLSX_EC_POINT_FORMATS, point, heap); + ret = TLSX_Push(extensions, TLSX_EC_POINT_FORMATS, formats, heap); if (ret != 0) { - XFREE(point, heap, DYNAMIC_TYPE_TLSX); + XFREE(formats, heap, DYNAMIC_TYPE_TLSX); return ret; } } else { - ret = TLSX_PointFormat_Append((PointFormat*)extension->data, format, - heap); + ret = TLSX_PointFormat_Append((PointFormats**)&extension->data, format, + heap); if (ret != 0) return ret; } @@ -15523,7 +15585,7 @@ void TLSX_FreeAll(TLSX* list, void* heap) case TLSX_EC_POINT_FORMATS: WOLFSSL_MSG("Point Formats extension free"); - PF_FREE_ALL((PointFormat*)extension->data, heap); + PF_FREE_ALL((PointFormats*)extension->data, heap); break; case TLSX_STATUS_REQUEST: @@ -15765,7 +15827,7 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, break; case TLSX_EC_POINT_FORMATS: - length += PF_GET_SIZE((PointFormat*)extension->data); + length += PF_GET_SIZE((PointFormats*)extension->data); break; case TLSX_STATUS_REQUEST: @@ -16038,7 +16100,7 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, case TLSX_EC_POINT_FORMATS: WOLFSSL_MSG("Point Formats extension to write"); - offset += PF_WRITE((PointFormat*)extension->data, + offset += PF_WRITE((PointFormats*)extension->data, output + offset); break; diff --git a/tests/api.c b/tests/api.c index 5858e1d371..6ca1a7cd0e 100644 --- a/tests/api.c +++ b/tests/api.c @@ -45741,6 +45741,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_TLSX_ALPN_server_response_count), TEST_DECL(test_TLSX_SupportedCurve_empty_or_unsupported), TEST_DECL(test_TLSX_SignatureAlgorithmsCert_parse), + TEST_DECL(test_TLSX_PointFormat_list), TEST_DECL(test_TLSX_MaxFragment_value), TEST_DECL(test_TLSX_PointFormat_uncompressed_required), TEST_DECL(test_wolfSSL_CTX_add_client_custom_ext), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index cb231af9cd..a4eefb08ee 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -3264,6 +3264,95 @@ int test_TLSX_SignatureAlgorithmsCert_parse(void) return EXPECT_RESULT(); } +/* The formats live in a counted array that doubles when it fills. No in-tree + * caller reaches the growth path, so drive TLSX_UsePointFormat() directly. */ +int test_TLSX_PointFormat_list(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SUPPORTED_CURVES) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + (defined(HAVE_ECC) || defined(HAVE_CURVE25519) || \ + defined(HAVE_CURVE448)) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + TLSX** exts = NULL; + TLSX* extension = NULL; + PointFormats* formats = NULL; + void* heap = NULL; + int ret; + int i; + +#ifndef WOLFSSL_NO_TLS12 + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); +#else + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); +#endif + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) { + exts = &ssl->extensions; + heap = ssl->heap; + ExpectNull(TLSX_Find(ssl->extensions, TLSX_EC_POINT_FORMATS)); + } + if (exts == NULL) { + /* ssl is NULL too, so there is nothing else to release. */ + wolfSSL_CTX_free(ctx); + return EXPECT_RESULT(); + } + + /* The three formats RFC 8422 defines, then a repeat of the first. */ + ExpectIntEQ(TLSX_UsePointFormat(exts, WOLFSSL_EC_PF_UNCOMPRESSED, heap), + WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_UsePointFormat(exts, 1, heap), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_UsePointFormat(exts, 2, heap), WOLFSSL_SUCCESS); + ExpectIntEQ(TLSX_UsePointFormat(exts, WOLFSSL_EC_PF_UNCOMPRESSED, heap), + WOLFSSL_SUCCESS); + ExpectNotNull(extension = TLSX_Find(*exts, TLSX_EC_POINT_FORMATS)); + if (extension != NULL) + formats = (PointFormats*)extension->data; + ExpectNotNull(formats); + /* The repeat is dropped, and four is still enough. */ + ExpectIntEQ(formats == NULL ? -1 : (int)formats->count, 3); + ExpectIntEQ(formats == NULL ? -1 : (int)formats->cap, 4); + if (formats != NULL) { + ExpectIntEQ(formats->format[0], WOLFSSL_EC_PF_UNCOMPRESSED); + ExpectIntEQ(formats->format[1], 1); + ExpectIntEQ(formats->format[2], 2); + } + + /* Past the initial capacity the list grows and keeps what it held. */ + for (i = 3; EXPECT_SUCCESS() && (i < 10); i++) { + ExpectIntEQ(TLSX_UsePointFormat(exts, (byte)i, heap), + WOLFSSL_SUCCESS); + } + if (extension != NULL) + formats = (PointFormats*)extension->data; + ExpectNotNull(formats); + ExpectIntEQ(formats == NULL ? -1 : (int)formats->count, 10); + ExpectIntGE(formats == NULL ? -1 : (int)formats->cap, 10); + if (formats != NULL) { + ExpectIntEQ(formats->format[0], WOLFSSL_EC_PF_UNCOMPRESSED); + ExpectIntEQ(formats->format[9], 9); + } + + /* The bound is internal to src/tls.c, so discover it. 256 values exceed + * any bound a byte capacity can hold. */ + ret = WOLFSSL_SUCCESS; + for (i = 10; (ret == WOLFSSL_SUCCESS) && (i < 256); i++) + ret = TLSX_UsePointFormat(exts, (byte)i, heap); + ExpectIntEQ(ret, WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + if (extension != NULL) + formats = (PointFormats*)extension->data; + ExpectNotNull(formats); + /* The refused insert leaves the list full. */ + if (formats != NULL) + ExpectIntEQ((int)formats->count, (int)formats->cap); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* The requested code is held in the extension and a response must match. */ int test_TLSX_MaxFragment_value(void) { diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 7d212f0479..3ba806a662 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -68,6 +68,7 @@ int test_TLSX_QUIC_TP_non_quic(void); int test_TLSX_ALPN_server_response_count(void); 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_PointFormat_uncompressed_required(void); int test_wolfSSL_CTX_add_client_custom_ext(void); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 38507be199..8e1fb178b5 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3889,10 +3889,16 @@ typedef struct SupportedCurve { struct SupportedCurve* next; /* List Behavior */ } SupportedCurve; -typedef struct PointFormat { - byte format; /* PointFormat */ - struct PointFormat* next; /* List Behavior */ -} PointFormat; +typedef struct PointFormats { + byte count; /* Formats in use */ + byte cap; /* Formats allocated */ + /* Ignore "nonstandard extension used : zero-sized array in struct/union" + * MSVC warning */ + #ifdef _MSC_VER + #pragma warning(disable: 4200) + #endif + byte format[WC_FLEXIBLE_ARRAY_SIZE]; /* Point formats */ +} PointFormats; WOLFSSL_LOCAL int TLSX_SupportedCurve_Copy(TLSX* src, TLSX** dst, void* heap); WOLFSSL_LOCAL int TLSX_UseSupportedCurve(TLSX** extensions, word16 name,