diff --git a/src/internal.c b/src/internal.c index 45a1f809fc9..2299a7e0871 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3050,6 +3050,10 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) #ifdef HAVE_TLS_EXTENSIONS #if !defined(NO_TLS) TLSX_FreeAll(ctx->extensions, ctx->heap); +#ifdef OPENSSL_EXTRA + TLSX_CustomExt_FreeAll(ctx->customExt, ctx->heap); + ctx->customExt = NULL; +#endif #endif /* !NO_TLS */ #ifndef NO_WOLFSSL_SERVER #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) \ @@ -9056,6 +9060,18 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) #if !defined(NO_TLS) TLSX_FreeAll(ssl->extensions, ssl->heap); ssl->extensions = NULL; +#ifdef OPENSSL_EXTRA + if (ssl->customExtData != NULL) { + XFREE(ssl->customExtData, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + ssl->customExtData = NULL; + ssl->customExtSz = 0; + } + if (ssl->customExtSent != NULL) { + XFREE(ssl->customExtSent, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->customExtSent = NULL; + ssl->customExtSentCnt = 0; + } +#endif #if defined(HAVE_SECURE_RENEGOTIATION) \ || defined(HAVE_SERVER_RENEGOTIATION_INFO) ssl->secure_renegotiation = NULL; diff --git a/src/tls.c b/src/tls.c index 62118d0678b..3b8454f341f 100644 --- a/src/tls.c +++ b/src/tls.c @@ -16684,6 +16684,334 @@ static int TLSX_GetSizeWithEch(WOLFSSL* ssl, byte* semaphore, byte msgType, } #endif +#if defined(HAVE_TLS_EXTENSIONS) && defined(OPENSSL_EXTRA) +/* OpenSSL-compatible application-defined ("custom") TLS extensions. + * + * Unlike the standard extensions above, custom extensions carry arbitrary + * IANA types chosen by the application, so they cannot live in the TLSX list + * (which keys every extension on a fixed semaphore index). They are kept in a + * separate list on the WOLFSSL_CTX and processed alongside the unknown + * extension handling. Only the client side, for TLS 1.2 and below, is wired up + * here, matching the legacy SSL_CTX_add_client_custom_ext() contract. */ + +/* Returns 1 if ext_type is an extension wolfSSL handles internally, which the + * application is therefore not allowed to register a custom handler for. */ +static int TLSX_CustomExt_IsKnown(word16 ext_type) +{ + switch (ext_type) { + case TLSXT_SERVER_NAME: + case TLSXT_MAX_FRAGMENT_LENGTH: + case TLSXT_TRUSTED_CA_KEYS: + case TLSXT_TRUNCATED_HMAC: + case TLSXT_STATUS_REQUEST: + case TLSXT_SUPPORTED_GROUPS: + case TLSXT_EC_POINT_FORMATS: + case TLSXT_SIGNATURE_ALGORITHMS: + case TLSXT_USE_SRTP: + case TLSXT_APPLICATION_LAYER_PROTOCOL: + case TLSXT_STATUS_REQUEST_V2: + case TLSXT_CLIENT_CERTIFICATE: + case TLSXT_SERVER_CERTIFICATE: + case TLSXT_ENCRYPT_THEN_MAC: + case TLSXT_EXTENDED_MASTER_SECRET: + case TLSXT_CERT_WITH_EXTERN_PSK: + case TLSXT_SESSION_TICKET: + case TLSXT_PRE_SHARED_KEY: + case TLSXT_EARLY_DATA: + case TLSXT_SUPPORTED_VERSIONS: + case TLSXT_COOKIE: + case TLSXT_PSK_KEY_EXCHANGE_MODES: + case TLSXT_CERTIFICATE_AUTHORITIES: + case TLSXT_POST_HANDSHAKE_AUTH: + case TLSXT_SIGNATURE_ALGORITHMS_CERT: + case TLSXT_KEY_SHARE: + case TLSXT_CONNECTION_ID: + case TLSXT_KEY_QUIC_TP_PARAMS: + case TLSXT_ECH: + case TLSXT_CKS: + case TLSXT_RENEGOTIATION_INFO: + case TLSXT_KEY_QUIC_TP_PARAMS_DRAFT: + return 1; + default: + return 0; + } +} + +/* Registers an application-defined client extension on the context. Mirrors + * OpenSSL's SSL_CTX_add_client_custom_ext(): returns WOLFSSL_SUCCESS (1) on + * success, 0 on failure. */ +int wolfSSL_CTX_add_client_custom_ext(WOLFSSL_CTX* ctx, unsigned int ext_type, + wolfSSL_custom_ext_add_cb add_cb, wolfSSL_custom_ext_free_cb free_cb, + void* add_arg, wolfSSL_custom_ext_parse_cb parse_cb, void* parse_arg) +{ + WOLFSSL_CustomExt* meth; + + WOLFSSL_ENTER("wolfSSL_CTX_add_client_custom_ext"); + + if (ctx == NULL || ext_type > 0xffff) + return 0; + + /* free_cb without add_cb is meaningless: there is nothing to free. */ + if (add_cb == NULL && free_cb != NULL) + return 0; + + /* Don't allow shadowing of internally handled extensions. */ + if (TLSX_CustomExt_IsKnown((word16)ext_type)) + return 0; + + /* Reject duplicate registrations for the same type. */ + for (meth = ctx->customExt; meth != NULL; meth = meth->next) { + if (meth->ext_type == (word16)ext_type) + return 0; + } + + meth = (WOLFSSL_CustomExt*)XMALLOC(sizeof(WOLFSSL_CustomExt), ctx->heap, + DYNAMIC_TYPE_TLSX); + if (meth == NULL) + return 0; + + meth->ext_type = (word16)ext_type; + meth->add_cb = add_cb; + meth->free_cb = free_cb; + meth->parse_cb = parse_cb; + meth->add_arg = add_arg; + meth->parse_arg = parse_arg; + meth->next = ctx->customExt; + ctx->customExt = meth; + + return WOLFSSL_SUCCESS; +} + +/* Frees a list of registered custom extension methods. */ +void TLSX_CustomExt_FreeAll(WOLFSSL_CustomExt* list, void* heap) +{ + WOLFSSL_CustomExt* meth; + + while ((meth = list) != NULL) { + list = meth->next; + XFREE(meth, heap, DYNAMIC_TYPE_TLSX); + } + (void)heap; +} + +/* Invokes the registered add callbacks and serializes the resulting custom + * extensions for the ClientHello into ssl->customExtData. The total wire size + * (type + length + data for each included extension) is returned in *pSz. The + * buffer is consumed and released by TLSX_WriteRequest. */ +int TLSX_CustomExt_BuildRequest(WOLFSSL* ssl, word16* pSz) +{ + WOLFSSL_CustomExt* meth; + byte* data = NULL; + word32 dataSz = 0; /* word32 to detect a word16 wire-field overflow */ + int ret = 0; + + *pSz = 0; + + /* Discard buffers left over from a previous sizing pass, e.g. a + * WANT_WRITE retry re-entering SendClientHello. */ + if (ssl->customExtData != NULL) { + XFREE(ssl->customExtData, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + ssl->customExtData = NULL; + ssl->customExtSz = 0; + } + if (ssl->customExtSent != NULL) { + XFREE(ssl->customExtSent, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->customExtSent = NULL; + ssl->customExtSentCnt = 0; + } + + if (ssl->ctx == NULL || ssl->ctx->customExt == NULL) + return 0; + + for (meth = ssl->ctx->customExt; meth != NULL; meth = meth->next) { + const unsigned char* out = NULL; + size_t outlen = 0; + int al = unsupported_extension; + int addRet = 1; /* no add_cb => add a zero-length extension */ + word32 need; + byte* tmp; + + if (meth->add_cb != NULL) { + addRet = meth->add_cb(ssl, meth->ext_type, &out, &outlen, &al, + meth->add_arg); + } + + if (addRet < 0) { + /* Fatal: callback requested the connection be aborted. */ + SendAlert(ssl, alert_fatal, (byte)al); + ret = WOLFSSL_FATAL_ERROR; + break; + } + if (addRet == 0) + continue; /* extension omitted for this message */ + + if (outlen > WOLFSSL_MAX_16BIT) { + ret = BUFFER_ERROR; + } + else { + need = HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + (word32)outlen; + if (dataSz + need > (word32)WOLFSSL_MAX_16BIT) + ret = BUFFER_ERROR; + } + if (ret != 0) { + if (meth->free_cb != NULL) + meth->free_cb(ssl, meth->ext_type, out, meth->add_arg); + break; + } + + tmp = (byte*)XREALLOC(data, dataSz + need, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + ret = MEMORY_E; + if (meth->free_cb != NULL) + meth->free_cb(ssl, meth->ext_type, out, meth->add_arg); + break; + } + data = tmp; + + c16toa(meth->ext_type, data + dataSz); + dataSz += HELLO_EXT_TYPE_SZ; + c16toa((word16)outlen, data + dataSz); + dataSz += OPAQUE16_LEN; + if (outlen > 0) { + XMEMCPY(data + dataSz, out, outlen); + dataSz += (word32)outlen; + } + + /* Record the type as sent so the server may legitimately echo it. */ + { + word16* sent = (word16*)XREALLOC(ssl->customExtSent, + (ssl->customExtSentCnt + 1) * (word32)sizeof(word16), + ssl->heap, DYNAMIC_TYPE_TLSX); + if (sent == NULL) { + ret = MEMORY_E; + if (meth->free_cb != NULL) + meth->free_cb(ssl, meth->ext_type, out, meth->add_arg); + break; + } + ssl->customExtSent = sent; + ssl->customExtSent[ssl->customExtSentCnt++] = meth->ext_type; + } + + /* Data has been copied into the message buffer; release it now. */ + if (meth->free_cb != NULL) + meth->free_cb(ssl, meth->ext_type, out, meth->add_arg); + } + + if (ret != 0) { + XFREE(data, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (ssl->customExtSent != NULL) { + XFREE(ssl->customExtSent, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->customExtSent = NULL; + ssl->customExtSentCnt = 0; + } + return ret; + } + + ssl->customExtData = data; + ssl->customExtSz = (word16)dataSz; + *pSz = (word16)dataSz; + + return 0; +} + +/* Returns 1 if a custom extension handler is registered for the given type. */ +static int TLSX_CustomExt_IsRegistered(const WOLFSSL* ssl, word16 type) +{ + WOLFSSL_CustomExt* meth; + + if (ssl->ctx == NULL) + return 0; + for (meth = ssl->ctx->customExt; meth != NULL; meth = meth->next) { + if (meth->ext_type == type) + return 1; + } + return 0; +} + +/* Returns 1 if the given custom extension type was emitted in our ClientHello. */ +static int TLSX_CustomExt_WasSent(const WOLFSSL* ssl, word16 type) +{ + word16 i; + + for (i = 0; i < ssl->customExtSentCnt; i++) { + if (ssl->customExtSent[i] == type) + return 1; + } + return 0; +} + +/* Looks up a registered custom extension matching the received type and, if + * found, invokes its parse callback. *found is set to 1 when a handler matched + * (whether it succeeded or failed). Returns 0 on success, or a negative error + * (after sending the appropriate alert) when the extension is unsolicited or + * the callback rejects the data. */ +int TLSX_CustomExt_Parse(WOLFSSL* ssl, byte msgType, word16 type, + const byte* input, word16 size, int* found) +{ + WOLFSSL_CustomExt* meth; + + *found = 0; + + if (ssl->ctx == NULL || ssl->ctx->customExt == NULL) + return 0; + + /* Legacy client custom extensions only apply to the ServerHello of a + * TLS 1.2 (or below) handshake. For TLS 1.3, fall through so the unknown + * extension is handled per RFC 8446 (unsupported_extension alert). */ + if (msgType != server_hello || IsAtLeastTLSv1_3(ssl->version)) + return 0; + + /* OpenSSL registers the legacy API with SSL_EXT_IGNORE_ON_RESUMPTION, so on + * a resumed handshake the extension is not processed (the server echo is + * silently ignored). Only ignore when the server has actually confirmed + * resumption by echoing our session ID -- the RFC 5246 / RFC 5077 (tickets, + * non-empty session ID) signal. A cached ticket alone is not enough: if the + * server falls back to a full handshake it will not echo our session ID, so + * the extension is still parsed/validated below (and an unsolicited one is + * rejected). These fields are set from the ServerHello before this point. */ + if (ssl->options.resuming && ssl->options.haveSessionId && + ssl->arrays != NULL && ssl->session != NULL && + ssl->arrays->sessionIDSz == ID_LEN && + ssl->session->sessionIDSz == ID_LEN && + XMEMCMP(ssl->arrays->sessionID, ssl->session->sessionID, + ID_LEN) == 0) { + return 0; + } + + for (meth = ssl->ctx->customExt; meth != NULL; meth = meth->next) { + if (meth->ext_type != type) + continue; + + *found = 1; + + /* RFC 5246 7.4.1.4: the server must not send an extension the client + * did not request. add_cb may decline to send for a given handshake, + * so reject a response for any type we did not actually emit. */ + if (!TLSX_CustomExt_WasSent(ssl, type)) { + WOLFSSL_MSG("Unsolicited custom extension in ServerHello"); + SendAlert(ssl, alert_fatal, unsupported_extension); + WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); + return UNSUPPORTED_EXTENSION; + } + + if (meth->parse_cb != NULL) { + int al = unsupported_extension; + int parseRet = meth->parse_cb(ssl, type, input, (size_t)size, &al, + meth->parse_arg); + if (parseRet <= 0) { + SendAlert(ssl, alert_fatal, (byte)al); + WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); + return UNSUPPORTED_EXTENSION; + } + } + break; + } + + return 0; +} +#endif /* HAVE_TLS_EXTENSIONS && OPENSSL_EXTRA */ + /** Tells the buffered size of extensions to be sent into the client hello. */ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) { @@ -16790,6 +17118,23 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) } #endif +#if defined(HAVE_TLS_EXTENSIONS) && defined(OPENSSL_EXTRA) + /* Application-defined (custom) extensions. These are always offered in the + * ClientHello regardless of the client's maximum version (matching OpenSSL, + * whose is_tls13 check is false while constructing the ClientHello), so + * they work with flexible client methods that go on to negotiate TLS 1.2. + * The negotiated-version restriction is enforced on the parse side. The add + * callbacks run here and the resulting bytes are cached for + * TLSX_WriteRequest. */ + if (msgType == client_hello) { + word16 customSz = 0; + ret = TLSX_CustomExt_BuildRequest(ssl, &customSz); + if (ret != 0) + return ret; + length += customSz; + } +#endif + /* The TLS extensions block length prefix is a 2-byte field, so any * accumulated total above 0xFFFF must be rejected rather than silently * truncating and producing a short, malformed handshake message. */ @@ -17022,6 +17367,19 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) } #endif +#if defined(HAVE_TLS_EXTENSIONS) && defined(OPENSSL_EXTRA) + /* Copy out the application-defined (custom) extension bytes built during + * TLSX_GetRequestSize, then release the cached buffer. */ + if (msgType == client_hello && ssl->customExtData != NULL) { + WOLFSSL_MSG("Custom extensions to write"); + XMEMCPY(output + offset, ssl->customExtData, ssl->customExtSz); + offset += ssl->customExtSz; + XFREE(ssl->customExtData, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + ssl->customExtData = NULL; + ssl->customExtSz = 0; + } +#endif + #ifdef WOLFSSL_TLS13 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) if (msgType == client_hello && IsAtLeastTLSv1_3(ssl->version)) { @@ -17651,6 +18009,26 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, return DUPLICATE_TLS_EXT_E; } } +#if defined(HAVE_TLS_EXTENSIONS) && defined(OPENSSL_EXTRA) + /* The semaphore-based duplicate detection above does not cover + * application-registered custom extensions whose arbitrary type is + * above the semaphore range. Match OpenSSL, which gives each registered + * custom extension its own slot and rejects repeats: scan the + * already-parsed portion of this message for an earlier extension of + * the same type. (Types <= 62 are handled by the block above.) */ + else if (type > 62 && TLSX_CustomExt_IsRegistered(ssl, type)) { + word16 scan = 0; + word16 upto = (word16)(offset - HELLO_EXT_TYPE_SZ - OPAQUE16_LEN); + while (scan + HELLO_EXT_TYPE_SZ + OPAQUE16_LEN <= upto) { + word16 sT, sS; + ato16(input + scan, &sT); + ato16(input + scan + HELLO_EXT_TYPE_SZ, &sS); + if (sT == type) + return DUPLICATE_TLS_EXT_E; + scan = (word16)(scan + HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + sS); + } + } +#endif if (length - offset < size) return BUFFER_ERROR; @@ -18325,6 +18703,20 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, #endif default: WOLFSSL_MSG("Unknown TLS extension type"); +#if defined(HAVE_TLS_EXTENSIONS) && defined(OPENSSL_EXTRA) + { + /* Application-defined (custom) extension handler, if one + * was registered for this type. */ + int customFound = 0; + ret = TLSX_CustomExt_Parse(ssl, msgType, type, + input + offset, size, + &customFound); + if (ret != 0) + return ret; + if (customFound) + break; + } +#endif #if defined(WOLFSSL_TLS13) /* RFC 8446 Sec. 4.2: a TLS 1.3 client MUST abort with an * unsupported_extension alert when it receives an extension diff --git a/tests/api.c b/tests/api.c index a2873fd747d..8cd20200e56 100644 --- a/tests/api.c +++ b/tests/api.c @@ -34429,6 +34429,16 @@ TEST_CASE testCases[] = { TEST_DECL(test_TLSX_SNI_GetSize_overflow), TEST_DECL(test_TLSX_ECH_msg_type_validation), TEST_DECL(test_TLSX_SRTP_msg_type_validation), + TEST_DECL(test_wolfSSL_CTX_add_client_custom_ext), + TEST_DECL(test_wolfSSL_custom_ext_handshake), + TEST_DECL(test_wolfSSL_custom_ext_flexible_handshake), + TEST_DECL(test_wolfSSL_custom_ext_tls13_handshake), + TEST_DECL(test_wolfSSL_custom_ext_parse), + TEST_DECL(test_wolfSSL_custom_ext_unsolicited), + TEST_DECL(test_wolfSSL_custom_ext_duplicate), + TEST_DECL(test_wolfSSL_custom_ext_resumption_ignored), + TEST_DECL(test_wolfSSL_custom_ext_resumption_fallback), + TEST_DECL(test_wolfSSL_custom_ext_ticket_fallback), TEST_DECL(test_wolfSSL_wolfSSL_UseSecureRenegotiation), TEST_DECL(test_wolfSSL_clear_secure_renegotiation), TEST_DECL(test_wolfSSL_SCR_Reconnect), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 294fa9c903c..e9833d0212d 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -1009,6 +1009,450 @@ int test_TLSX_ECH_msg_type_validation(void) return EXPECT_RESULT(); } +/* ---- Application-defined ("custom") client extensions ------------------- */ +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) + +#define TEST_CUSTOM_EXT_TYPE 0x4242 +#define TEST_CUSTOM_ADD_ARG ((void*)0x1234) +#define TEST_CUSTOM_PARSE_ARG ((void*)0x5678) + +static const unsigned char test_custom_ext_data[] = { 'w', 'o', 'l', 'f' }; + +static int test_custom_ext_add_called; +static int test_custom_ext_free_called; +static int test_custom_ext_parse_called; +static int test_custom_ext_add_bad; +static int test_custom_ext_parse_bad; + +static int test_custom_ext_add_cb(WOLFSSL* ssl, unsigned int ext_type, + const unsigned char** out, size_t* outlen, int* al, void* add_arg) +{ + (void)ssl; + (void)al; + test_custom_ext_add_called++; + if (ext_type != TEST_CUSTOM_EXT_TYPE || add_arg != TEST_CUSTOM_ADD_ARG) + test_custom_ext_add_bad++; + *out = test_custom_ext_data; + *outlen = sizeof(test_custom_ext_data); + return 1; +} + +static void test_custom_ext_free_cb(WOLFSSL* ssl, unsigned int ext_type, + const unsigned char* out, void* add_arg) +{ + (void)ssl; + (void)ext_type; + (void)out; + (void)add_arg; + test_custom_ext_free_called++; +} + +static int test_custom_ext_parse_cb(WOLFSSL* ssl, unsigned int ext_type, + const unsigned char* in, size_t inlen, int* al, void* parse_arg) +{ + (void)ssl; + (void)al; + test_custom_ext_parse_called++; + if (ext_type != TEST_CUSTOM_EXT_TYPE || parse_arg != TEST_CUSTOM_PARSE_ARG) + test_custom_ext_parse_bad++; + if (inlen != sizeof(test_custom_ext_data) || + XMEMCMP(in, test_custom_ext_data, inlen) != 0) + test_custom_ext_parse_bad++; + return 1; +} + +static void test_custom_ext_reset(void) +{ + test_custom_ext_add_called = 0; + test_custom_ext_free_called = 0; + test_custom_ext_parse_called = 0; + test_custom_ext_add_bad = 0; + test_custom_ext_parse_bad = 0; +} + +#ifdef HAVE_SSL_MEMIO_TESTS_DEPENDENCIES +static int test_custom_ext_handshake_ctx_ready(WOLFSSL_CTX* ctx) +{ + EXPECT_DECLS; + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, TEST_CUSTOM_ADD_ARG, + test_custom_ext_parse_cb, TEST_CUSTOM_PARSE_ARG), WOLFSSL_SUCCESS); + return EXPECT_RESULT(); +} +#endif +#endif + +/* Validates the registration contract of wolfSSL_CTX_add_client_custom_ext. */ +int test_wolfSSL_CTX_add_client_custom_ext(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + + /* NULL ctx is rejected. */ + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(NULL, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, NULL, + test_custom_ext_parse_cb, NULL), 0); + + /* A type wolfSSL handles internally (server_name) is rejected. */ + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, 0x0000, + test_custom_ext_add_cb, test_custom_ext_free_cb, NULL, + test_custom_ext_parse_cb, NULL), 0); + + /* free_cb without add_cb is rejected. */ + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + NULL, test_custom_ext_free_cb, NULL, + test_custom_ext_parse_cb, NULL), 0); + + /* ext_type larger than a 16-bit value is rejected. */ + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, 0x10000, + test_custom_ext_add_cb, test_custom_ext_free_cb, NULL, + test_custom_ext_parse_cb, NULL), 0); + + /* A valid registration succeeds. */ + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, TEST_CUSTOM_ADD_ARG, + test_custom_ext_parse_cb, TEST_CUSTOM_PARSE_ARG), WOLFSSL_SUCCESS); + + /* Registering the same type twice is rejected. */ + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, NULL, + test_custom_ext_parse_cb, NULL), 0); + + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* Drives a TLS 1.2 handshake and checks the client's custom-extension add and + * free callbacks run while building the ClientHello. */ +int test_wolfSSL_custom_ext_handshake(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + test_ssl_cbf client_cbf; + test_ssl_cbf server_cbf; + + XMEMSET(&client_cbf, 0, sizeof(client_cbf)); + XMEMSET(&server_cbf, 0, sizeof(server_cbf)); + test_custom_ext_reset(); + + client_cbf.method = wolfTLSv1_2_client_method; + client_cbf.ctx_ready = test_custom_ext_handshake_ctx_ready; + server_cbf.method = wolfTLSv1_2_server_method; + + ExpectIntEQ(test_wolfSSL_client_server_nofail_memio(&client_cbf, + &server_cbf, NULL), TEST_SUCCESS); + + /* add_cb must have run at least once, free_cb must balance it, and no + * callback observed an unexpected type or argument. */ + ExpectIntGE(test_custom_ext_add_called, 1); + ExpectIntEQ(test_custom_ext_free_called, test_custom_ext_add_called); + ExpectIntEQ(test_custom_ext_add_bad, 0); +#endif + return EXPECT_RESULT(); +} + +/* A flexible client method (whose pre-handshake version is the maximum, e.g. + * TLS 1.3) must still offer the legacy custom extension so it works when the + * connection negotiates TLS 1.2. */ +int test_wolfSSL_custom_ext_flexible_handshake(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + test_ssl_cbf client_cbf; + test_ssl_cbf server_cbf; + + XMEMSET(&client_cbf, 0, sizeof(client_cbf)); + XMEMSET(&server_cbf, 0, sizeof(server_cbf)); + test_custom_ext_reset(); + + /* Flexible client (offers up to its max version), server pinned to TLS 1.2 + * so the handshake negotiates down. */ + client_cbf.method = wolfSSLv23_client_method; + client_cbf.ctx_ready = test_custom_ext_handshake_ctx_ready; + server_cbf.method = wolfTLSv1_2_server_method; + + ExpectIntEQ(test_wolfSSL_client_server_nofail_memio(&client_cbf, + &server_cbf, NULL), TEST_SUCCESS); + + /* The extension was offered despite the client's pre-handshake max version + * being above TLS 1.2. */ + ExpectIntGE(test_custom_ext_add_called, 1); + ExpectIntEQ(test_custom_ext_free_called, test_custom_ext_add_called); + ExpectIntEQ(test_custom_ext_add_bad, 0); +#endif + return EXPECT_RESULT(); +} + +/* Offering the legacy custom extension in a ClientHello that negotiates TLS 1.3 + * must not break the handshake (the server ignores the unknown extension). */ +int test_wolfSSL_custom_ext_tls13_handshake(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + test_ssl_cbf client_cbf; + test_ssl_cbf server_cbf; + + XMEMSET(&client_cbf, 0, sizeof(client_cbf)); + XMEMSET(&server_cbf, 0, sizeof(server_cbf)); + test_custom_ext_reset(); + + client_cbf.method = wolfTLSv1_3_client_method; + client_cbf.ctx_ready = test_custom_ext_handshake_ctx_ready; + server_cbf.method = wolfTLSv1_3_server_method; + + ExpectIntEQ(test_wolfSSL_client_server_nofail_memio(&client_cbf, + &server_cbf, NULL), TEST_SUCCESS); + + /* Sent in the ClientHello; the TLS 1.3 server ignores it and never echoes + * it, so parse never runs. */ + ExpectIntGE(test_custom_ext_add_called, 1); + ExpectIntEQ(test_custom_ext_free_called, test_custom_ext_add_called); + ExpectIntEQ(test_custom_ext_parse_called, 0); +#endif + return EXPECT_RESULT(); +} + +/* Feeds a ServerHello extension matching a registered custom type and checks + * the parse callback is invoked with the right data. */ +int test_wolfSSL_custom_ext_parse(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + word16 builtSz = 0; + /* type = 0x4242, len = 0x0004, data = "wolf" */ + const byte extBytes[] = { 0x42, 0x42, 0x00, 0x04, 'w', 'o', 'l', 'f' }; + + test_custom_ext_reset(); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, TEST_CUSTOM_ADD_ARG, + test_custom_ext_parse_cb, TEST_CUSTOM_PARSE_ARG), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Emit the extension first (records it as sent) so the server's echo is + * accepted rather than rejected as unsolicited. */ + ExpectIntEQ(TLSX_CustomExt_BuildRequest(ssl, &builtSz), 0); + + ExpectIntEQ(TLSX_Parse(ssl, extBytes, (word16)sizeof(extBytes), + server_hello, NULL), 0); + ExpectIntEQ(test_custom_ext_parse_called, 1); + ExpectIntEQ(test_custom_ext_parse_bad, 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* A ServerHello extension whose type was never emitted in the ClientHello + * (add_cb declined) must be rejected as unsolicited, without invoking parse. */ +int test_wolfSSL_custom_ext_unsolicited(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + const byte extBytes[] = { 0x42, 0x42, 0x00, 0x04, 'w', 'o', 'l', 'f' }; + + test_custom_ext_reset(); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, TEST_CUSTOM_ADD_ARG, + test_custom_ext_parse_cb, TEST_CUSTOM_PARSE_ARG), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* No BuildRequest: nothing was sent, so the echo is unsolicited. */ + ExpectIntEQ(TLSX_Parse(ssl, extBytes, (word16)sizeof(extBytes), + server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + ExpectIntEQ(test_custom_ext_parse_called, 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* A ServerHello carrying the same registered custom extension twice must abort + * with DUPLICATE_TLS_EXT_E (matching the built-in duplicate handling). */ +int test_wolfSSL_custom_ext_duplicate(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + word16 builtSz = 0; + /* Two copies of type 0x4242. */ + const byte extBytes[] = { 0x42, 0x42, 0x00, 0x04, 'w', 'o', 'l', 'f', + 0x42, 0x42, 0x00, 0x04, 'w', 'o', 'l', 'f' }; + + test_custom_ext_reset(); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, TEST_CUSTOM_ADD_ARG, + test_custom_ext_parse_cb, TEST_CUSTOM_PARSE_ARG), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + ExpectIntEQ(TLSX_CustomExt_BuildRequest(ssl, &builtSz), 0); + + ExpectIntEQ(TLSX_Parse(ssl, extBytes, (word16)sizeof(extBytes), + server_hello, NULL), + WC_NO_ERR_TRACE(DUPLICATE_TLS_EXT_E)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* On a resumed handshake the legacy custom extension is ignored (OpenSSL's + * SSL_EXT_IGNORE_ON_RESUMPTION): a server echo neither invokes parse nor + * aborts. */ +int test_wolfSSL_custom_ext_resumption_ignored(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + word16 builtSz = 0; + const byte extBytes[] = { 0x42, 0x42, 0x00, 0x04, 'w', 'o', 'l', 'f' }; + + test_custom_ext_reset(); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, TEST_CUSTOM_ADD_ARG, + test_custom_ext_parse_cb, TEST_CUSTOM_PARSE_ARG), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + ExpectIntEQ(TLSX_CustomExt_BuildRequest(ssl, &builtSz), 0); + + /* Simulate a confirmed resumption: the server echoes our session ID, the + * signal the parse gate keys on. */ + if (ssl != NULL && ssl->arrays != NULL && ssl->session != NULL) { + ssl->options.resuming = 1; + ssl->options.haveSessionId = 1; + ssl->arrays->sessionIDSz = ID_LEN; + ssl->session->sessionIDSz = ID_LEN; + XMEMSET(ssl->arrays->sessionID, 0xA5, ID_LEN); + XMEMSET(ssl->session->sessionID, 0xA5, ID_LEN); + } + + ExpectIntEQ(TLSX_Parse(ssl, extBytes, (word16)sizeof(extBytes), + server_hello, NULL), 0); + ExpectIntEQ(test_custom_ext_parse_called, 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* When resumption was attempted but the server falls back to a full handshake + * (session ID not echoed), custom extensions must still be validated: an + * unsolicited type is rejected, not silently ignored. */ +int test_wolfSSL_custom_ext_resumption_fallback(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + const byte extBytes[] = { 0x42, 0x42, 0x00, 0x04, 'w', 'o', 'l', 'f' }; + + test_custom_ext_reset(); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, TEST_CUSTOM_ADD_ARG, + test_custom_ext_parse_cb, TEST_CUSTOM_PARSE_ARG), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Resumption attempted, but no matching session ID => full handshake. The + * extension was never sent (no BuildRequest), so it is unsolicited and must + * be rejected rather than ignored on the optimistic resuming flag. */ + if (ssl != NULL) { + ssl->options.resuming = 1; + ssl->options.haveSessionId = 0; + } + + ExpectIntEQ(TLSX_Parse(ssl, extBytes, (word16)sizeof(extBytes), + server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + ExpectIntEQ(test_custom_ext_parse_called, 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* A cached session ticket present during a resumption attempt must NOT by + * itself suppress custom-extension handling: if the server does not echo our + * session ID (it fell back to a full handshake or will issue a new ticket), the + * extension is still validated, so an unsolicited one is rejected. */ +int test_wolfSSL_custom_ext_ticket_fallback(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_TLS) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFSSL_NO_TLS12) && defined(HAVE_SESSION_TICKET) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + const byte extBytes[] = { 0x42, 0x42, 0x00, 0x04, 'w', 'o', 'l', 'f' }; + + test_custom_ext_reset(); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())); + ExpectIntEQ(wolfSSL_CTX_add_client_custom_ext(ctx, TEST_CUSTOM_EXT_TYPE, + test_custom_ext_add_cb, test_custom_ext_free_cb, TEST_CUSTOM_ADD_ARG, + test_custom_ext_parse_cb, TEST_CUSTOM_PARSE_ARG), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* A ticket is cached and resumption is attempted, but the server did not + * echo our session ID. ticketLenAlloc stays 0 so no buffer is freed. */ + if (ssl != NULL && ssl->session != NULL) { + ssl->options.resuming = 1; + ssl->options.haveSessionId = 0; + ssl->session->ticketLen = 16; + } + + ExpectIntEQ(TLSX_Parse(ssl, extBytes, (word16)sizeof(extBytes), + server_hello, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + ExpectIntEQ(test_custom_ext_parse_called, 0); + + /* Avoid the test teardown treating the bogus length as a real ticket. */ + if (ssl != NULL && ssl->session != NULL) + ssl->session->ticketLen = 0; + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* use_srtp is only valid in ClientHello/ServerHello (pre-TLS 1.3) or * ClientHello/EncryptedExtensions (TLS 1.3) per RFC 5764. Feeding it in a * Finished message must be rejected with EXT_NOT_ALLOWED. */ diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index e9d07d81ecd..e07bab70585 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -36,5 +36,15 @@ int test_TLSX_TCA_Find(void); int test_TLSX_SNI_GetSize_overflow(void); int test_TLSX_ECH_msg_type_validation(void); int test_TLSX_SRTP_msg_type_validation(void); +int test_wolfSSL_CTX_add_client_custom_ext(void); +int test_wolfSSL_custom_ext_handshake(void); +int test_wolfSSL_custom_ext_flexible_handshake(void); +int test_wolfSSL_custom_ext_tls13_handshake(void); +int test_wolfSSL_custom_ext_parse(void); +int test_wolfSSL_custom_ext_unsolicited(void); +int test_wolfSSL_custom_ext_duplicate(void); +int test_wolfSSL_custom_ext_resumption_ignored(void); +int test_wolfSSL_custom_ext_resumption_fallback(void); +int test_wolfSSL_custom_ext_ticket_fallback(void); #endif /* TESTS_API_TEST_TLS_EMS_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index fc918fbc770..51e3977b6f7 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3204,6 +3204,29 @@ struct TLSX { struct TLSX* next; /* List Behavior */ }; +#if defined(HAVE_TLS_EXTENSIONS) && defined(OPENSSL_EXTRA) +/* OpenSSL-compatible custom (application-defined) TLS extension. + * Registered on a WOLFSSL_CTX via wolfSSL_CTX_add_client_custom_ext(). These + * extensions are not part of the TLSX framework (which keys every extension on + * a fixed semaphore index) but are processed in parallel for unknown extension + * types. Currently the client side for TLS 1.2 and below is supported, mirroring + * OpenSSL's SSL_CTX_add_client_custom_ext() legacy contract. */ +typedef struct WOLFSSL_CustomExt { + word16 ext_type; /* extension type on the wire */ + wolfSSL_custom_ext_add_cb add_cb; /* build outgoing extension data */ + wolfSSL_custom_ext_free_cb free_cb; /* free data produced by add_cb */ + wolfSSL_custom_ext_parse_cb parse_cb; /* parse incoming extension data */ + void* add_arg; /* opaque arg for add_cb/free_cb */ + void* parse_arg; /* opaque arg for parse_cb */ + struct WOLFSSL_CustomExt* next; /* list behaviour */ +} WOLFSSL_CustomExt; + +WOLFSSL_LOCAL void TLSX_CustomExt_FreeAll(WOLFSSL_CustomExt* list, void* heap); +WOLFSSL_LOCAL int TLSX_CustomExt_BuildRequest(WOLFSSL* ssl, word16* pSz); +WOLFSSL_LOCAL int TLSX_CustomExt_Parse(WOLFSSL* ssl, byte msgType, word16 type, + const byte* input, word16 size, int* found); +#endif /* HAVE_TLS_EXTENSIONS && OPENSSL_EXTRA */ + #ifdef WOLFSSL_API_PREFIX_MAP #define TLSX_Find wolfSSL_TLSX_Find #endif @@ -4198,6 +4221,9 @@ struct WOLFSSL_CTX { int devId; /* async device id to use */ #ifdef HAVE_TLS_EXTENSIONS TLSX* extensions; /* RFC 6066 TLS Extensions data */ + #ifdef OPENSSL_EXTRA + WOLFSSL_CustomExt* customExt; /* App-defined custom TLS extensions */ + #endif #ifndef NO_WOLFSSL_SERVER #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) \ || defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) @@ -6379,6 +6405,18 @@ struct WOLFSSL { #endif #ifdef HAVE_TLS_EXTENSIONS TLSX* extensions; /* RFC 6066 TLS Extensions data */ + #ifdef OPENSSL_EXTRA + /* Pre-built wire bytes for app-defined custom extensions in the + * ClientHello. Produced in TLSX_GetRequestSize and consumed (then + * freed) in TLSX_WriteRequest. See WOLFSSL_CustomExt. */ + byte* customExtData; + word16 customExtSz; + /* Custom extension types actually emitted in the ClientHello, so an + * unsolicited type echoed by the server can be rejected. Rebuilt with + * customExtData; persists until the connection is freed. */ + word16* customExtSent; + word16 customExtSentCnt; + #endif #ifdef HAVE_MAX_FRAGMENT word16 max_fragment; #endif diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 063500675e1..dee6b0135c5 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -1122,6 +1122,14 @@ wolfSSL_X509_STORE_set_verify_cb((WOLFSSL_X509_STORE *)(s), (WOLFSSL_X509_STORE_ #define SSL_set_info_callback wolfSSL_set_info_callback #define SSL_CTX_set_alpn_protos wolfSSL_CTX_set_alpn_protos +/* Application-defined ("custom") TLS extensions. */ +#ifdef OPENSSL_EXTRA +typedef wolfSSL_custom_ext_add_cb custom_ext_add_cb; +typedef wolfSSL_custom_ext_free_cb custom_ext_free_cb; +typedef wolfSSL_custom_ext_parse_cb custom_ext_parse_cb; +#define SSL_CTX_add_client_custom_ext wolfSSL_CTX_add_client_custom_ext +#endif + #define SSL_CTX_keylog_cb_func wolfSSL_CTX_keylog_cb_func #define SSL_CTX_set_keylog_callback wolfSSL_CTX_set_keylog_callback #define SSL_CTX_get_keylog_callback wolfSSL_CTX_get_keylog_callback diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 2281bb2f264..69565a05529 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -5016,6 +5016,34 @@ WOLFSSL_API int wolfSSL_CTX_set_num_tickets(WOLFSSL_CTX* ctx, size_t mxTickets); #endif /* HAVE_SESSION_TICKET */ +#ifdef OPENSSL_EXTRA +/* OpenSSL-compatible application-defined ("custom") TLS extension callbacks. + * + * add_cb is invoked while building an outgoing message. On return: + * 1 - include the extension; out and outlen describe the data to send. + * 0 - omit the extension. + * <0 - fatal error; *al holds the TLS alert to send. + * If add_cb is NULL a zero-length extension is added to the ClientHello. + * + * free_cb (if set) is called after the data returned by add_cb has been + * copied into the message, to release any allocation it made. + * + * parse_cb is invoked for a received extension of the registered type. On + * return 1 the handshake continues; on return <=0 it is aborted with the + * alert placed in *al. */ +typedef int (*wolfSSL_custom_ext_add_cb)(WOLFSSL* s, unsigned int ext_type, + const unsigned char** out, size_t* outlen, int* al, void* add_arg); +typedef void (*wolfSSL_custom_ext_free_cb)(WOLFSSL* s, unsigned int ext_type, + const unsigned char* out, void* add_arg); +typedef int (*wolfSSL_custom_ext_parse_cb)(WOLFSSL* s, unsigned int ext_type, + const unsigned char* in, size_t inlen, int* al, void* parse_arg); + +WOLFSSL_API int wolfSSL_CTX_add_client_custom_ext(WOLFSSL_CTX* ctx, + unsigned int ext_type, wolfSSL_custom_ext_add_cb add_cb, + wolfSSL_custom_ext_free_cb free_cb, void* add_arg, + wolfSSL_custom_ext_parse_cb parse_cb, void* parse_arg); +#endif /* OPENSSL_EXTRA */ + /* TLS Extended Master Secret Extension */ WOLFSSL_API int wolfSSL_DisableExtendedMasterSecret(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_CTX_DisableExtendedMasterSecret(WOLFSSL_CTX* ctx);