From b7ec4bb7365fee42e681224115071693d9515ea8 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 22 Jul 2026 15:55:24 -0700 Subject: [PATCH 1/7] Add Enable/DisableNormalMasterSecret APIs to enforce EMS requirement at runtime. Disable server EMS when DisableExtendedMasterSecret is called, not just client EMS. --- src/internal.c | 8 +- src/ssl_api_ext.c | 119 ++++++++++++++++++++++++++- src/tls.c | 17 +++- tests/api.c | 3 + tests/api/test_tls_ext.c | 173 +++++++++++++++++++++++++++++++++++++++ tests/api/test_tls_ext.h | 3 + wolfssl/error-ssl.h | 2 +- wolfssl/internal.h | 13 +++ wolfssl/ssl.h | 4 + 9 files changed, 336 insertions(+), 6 deletions(-) diff --git a/src/internal.c b/src/internal.c index afa472608b6..045fe6b561d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8675,6 +8675,8 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) #ifdef HAVE_EXTENDED_MASTER ssl->options.haveEMS = ctx->haveEMS; + ssl->options.disableEMS = ctx->disableEMS; + ssl->options.requireEMS = ctx->requireEMS; #endif ssl->options.useClientOrder = ctx->useClientOrder; ssl->options.mutualAuth = ctx->mutualAuth; @@ -30059,7 +30061,7 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e) return "Initialize ctx mutex error"; case EXT_MASTER_SECRET_NEEDED_E: - return "Extended Master Secret must be enabled to resume EMS session"; + return "Extended Master Secret required but not negotiated with peer"; case DTLS_POOL_SZ_E: return "Maximum DTLS pool size exceeded"; @@ -41548,7 +41550,9 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) i += hashSigAlgoSz; } #ifdef HAVE_EXTENDED_MASTER - else if (extId == HELLO_EXT_EXTMS) + /* Honor a user request to disable EMS on the server by + * ignoring the peer's extension. */ + else if (extId == HELLO_EXT_EXTMS && !ssl->options.disableEMS) ssl->options.haveEMS = 1; #endif else diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 2be0da03757..a47d235bc5d 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -1562,9 +1562,12 @@ int wolfSSL_set_SessionTicket_cb(WOLFSSL* ssl, #ifdef HAVE_EXTENDED_MASTER -#ifndef NO_WOLFSSL_CLIENT /* Disable the Extended Master Secret extension on the context. + * + * For a client this stops the extension being advertised. For a server this + * causes the peer's Extended Master Secret request to be ignored so that a + * standard master secret is negotiated. * * @param [in] ctx SSL/TLS context object. * @return WOLFSSL_SUCCESS on success. @@ -1579,6 +1582,9 @@ int wolfSSL_CTX_DisableExtendedMasterSecret(WOLFSSL_CTX* ctx) } else { ctx->haveEMS = 0; + ctx->disableEMS = 1; + /* Disabling EMS is mutually exclusive with requiring it. */ + ctx->requireEMS = 0; } return ret; @@ -1586,6 +1592,10 @@ int wolfSSL_CTX_DisableExtendedMasterSecret(WOLFSSL_CTX* ctx) /* Disable the Extended Master Secret extension on the object. + * + * For a client this stops the extension being advertised. For a server this + * causes the peer's Extended Master Secret request to be ignored so that a + * standard master secret is negotiated. * * @param [in, out] ssl SSL/TLS object. * @return WOLFSSL_SUCCESS on success. @@ -1600,12 +1610,117 @@ int wolfSSL_DisableExtendedMasterSecret(WOLFSSL* ssl) } else { ssl->options.haveEMS = 0; + ssl->options.disableEMS = 1; + /* Disabling EMS is mutually exclusive with requiring it. */ + ssl->options.requireEMS = 0; } return ret; } -#endif + +/* Disable the standard (non-extended) master secret on the context. + * + * The Extended Master Secret extension (RFC 7627) becomes mandatory: if it is + * not negotiated with the peer the connection is aborted with + * EXT_MASTER_SECRET_NEEDED_E rather than falling back to a standard master + * secret. This only applies to TLS 1.2 and earlier; TLS 1.3 always uses a + * secure key schedule and is unaffected. + * + * @param [in] ctx SSL/TLS context object. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ctx is NULL. + */ +int wolfSSL_CTX_DisableNormalMasterSecret(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + + ctx->requireEMS = 1; + /* Requiring EMS is mutually exclusive with disabling it. */ + ctx->disableEMS = 0; + /* A client must advertise the extension for it to be negotiated. Undo any + * previous disable so the extension is offered. A server keeps its EMS + * state driven by the incoming ClientHello. */ + if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) + ctx->haveEMS = 1; + + return WOLFSSL_SUCCESS; +} + + +/* Disable the standard (non-extended) master secret on the object. + * + * The Extended Master Secret extension (RFC 7627) becomes mandatory: if it is + * not negotiated with the peer the connection is aborted with + * EXT_MASTER_SECRET_NEEDED_E rather than falling back to a standard master + * secret. This only applies to TLS 1.2 and earlier; TLS 1.3 always uses a + * secure key schedule and is unaffected. + * + * @param [in] ssl SSL/TLS object. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ssl is NULL. + */ +int wolfSSL_DisableNormalMasterSecret(WOLFSSL* ssl) +{ + if (ssl == NULL) + return BAD_FUNC_ARG; + + ssl->options.requireEMS = 1; + /* Requiring EMS is mutually exclusive with disabling it. */ + ssl->options.disableEMS = 0; + /* A client must advertise the extension for it to be negotiated. Undo any + * previous disable so the extension is offered. A server keeps its EMS + * state driven by the incoming ClientHello. */ + if (ssl->options.side == WOLFSSL_CLIENT_END) + ssl->options.haveEMS = 1; + + return WOLFSSL_SUCCESS; +} + + +/* Re-enable the standard (non-extended) master secret on the context. + * + * Undoes wolfSSL_CTX_DisableNormalMasterSecret so that a standard master + * secret is once again acceptable when the Extended Master Secret extension + * (RFC 7627) is not negotiated. Extended Master Secret support itself is left + * unchanged. + * + * @param [in] ctx SSL/TLS context object. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ctx is NULL. + */ +int wolfSSL_CTX_EnableNormalMasterSecret(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + + ctx->requireEMS = 0; + + return WOLFSSL_SUCCESS; +} + + +/* Re-enable the standard (non-extended) master secret on the object. + * + * Undoes wolfSSL_DisableNormalMasterSecret so that a standard master secret is + * once again acceptable when the Extended Master Secret extension (RFC 7627) + * is not negotiated. Extended Master Secret support itself is left unchanged. + * + * @param [in] ssl SSL/TLS object. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ssl is NULL. + */ +int wolfSSL_EnableNormalMasterSecret(WOLFSSL* ssl) +{ + if (ssl == NULL) + return BAD_FUNC_ARG; + + ssl->options.requireEMS = 0; + + return ret; +} + #endif #endif /* !NO_TLS */ diff --git a/src/tls.c b/src/tls.c index 08e1f98c62e..09747e3808d 100644 --- a/src/tls.c +++ b/src/tls.c @@ -704,6 +704,19 @@ int MakeTlsMasterSecret(WOLFSSL* ssl) { int ret; +#ifdef HAVE_EXTENDED_MASTER + /* The user disabled the standard master secret and requires the Extended + * Master Secret extension (RFC 7627). If it was not negotiated with the + * peer, abort rather than derive a standard master secret. Only reachable + * for TLS 1.2 and earlier; TLS 1.3 uses a separate key schedule. */ + if (ssl->options.requireEMS && !ssl->options.haveEMS) { + WOLFSSL_MSG("EMS required but not negotiated with peer"); + SendAlert(ssl, alert_fatal, handshake_failure); + WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); + return EXT_MASTER_SECRET_NEEDED_E; + } +#endif + #if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_KEYLOGFILE) /* If this is called from a sniffer session with keylog file support, obtain * the master secret from the callback */ @@ -18850,7 +18863,9 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, return BUFFER_ERROR; #ifndef NO_WOLFSSL_SERVER - if (isRequest) + /* Honor a user request to disable EMS on the server by + * ignoring the peer's extension rather than enabling it. */ + if (isRequest && !ssl->options.disableEMS) ssl->options.haveEMS = 1; #endif pendingEMS = 1; diff --git a/tests/api.c b/tests/api.c index 2304f24f86f..abb9b8dc85d 100644 --- a/tests/api.c +++ b/tests/api.c @@ -40455,6 +40455,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_tls_ems_downgrade), TEST_DECL(test_tls_ems_resumption_downgrade), TEST_DECL(test_tls_ems_resumption_server_downgrade), + TEST_DECL(test_tls_ems_server_disable), + TEST_DECL(test_tls_require_ems), TEST_DECL(test_tls12_chacha20_poly1305_bad_tag), TEST_DECL(test_tls13_null_cipher_bad_hmac), TEST_DECL(test_scr_verify_data_mismatch), @@ -40464,6 +40466,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_tls13_hrr_cipher_suite_mismatch), TEST_DECL(test_tls13_ticket_age_out_of_window), TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret), + TEST_DECL(test_wolfSSL_DisableNormalMasterSecret), TEST_DECL(test_certificate_authorities_certificate_request), TEST_DECL(test_certificate_authorities_client_hello), TEST_DECL(test_TLSX_TCA_Find), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 6091ffc89eb..76aa1942a30 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -313,6 +313,129 @@ int test_tls_ems_resumption_server_downgrade(void) } +/* wolfSSL_DisableExtendedMasterSecret must disable EMS on the server as well + * as the client. When the server disables EMS it ignores the client's + * extended_master_secret extension and both peers fall back to a standard + * master secret while the handshake still completes. */ +int test_tls_ems_server_disable(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + 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, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + + /* The client still advertises EMS; the server disables it. */ + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_s), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Neither side ends up using EMS. */ + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.haveEMS, 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->options.haveEMS, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) +/* Exercise wolfSSL_DisableNormalMasterSecret. When serverSide is set the + * server requires EMS, otherwise the client does. When peerDisables is set the + * opposite side disables EMS, so the extension cannot be negotiated and the + * requiring side must abort with EXT_MASTER_SECRET_NEEDED_E. */ +static int test_tls_require_ems_ex(int serverSide, int peerDisables) +{ + EXPECT_DECLS; + 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, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + + if (serverSide) + ExpectIntEQ(wolfSSL_DisableNormalMasterSecret(ssl_s), WOLFSSL_SUCCESS); + else + ExpectIntEQ(wolfSSL_DisableNormalMasterSecret(ssl_c), WOLFSSL_SUCCESS); + + if (peerDisables) { + if (serverSide) + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_c), + WOLFSSL_SUCCESS); + else + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_s), + WOLFSSL_SUCCESS); + + /* EMS cannot be negotiated so the requiring side must abort. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (serverSide) + ExpectIntEQ(wolfSSL_get_error(ssl_s, 0), + WC_NO_ERR_TRACE(EXT_MASTER_SECRET_NEEDED_E)); + else + ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), + WC_NO_ERR_TRACE(EXT_MASTER_SECRET_NEEDED_E)); + } + else { + /* Peer supports EMS so the handshake completes using EMS. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->options.haveEMS, 1); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->options.haveEMS, 1); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* wolfSSL_DisableNormalMasterSecret makes the Extended Master Secret extension + * mandatory: the handshake succeeds when the peer supports EMS and is aborted + * with EXT_MASTER_SECRET_NEEDED_E otherwise, on both client and server. */ +int test_tls_require_ems(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + /* Client requires EMS, server supports it -> success. */ + ExpectIntEQ(test_tls_require_ems_ex(0, 0), TEST_SUCCESS); + /* Client requires EMS, server disabled it -> abort. */ + ExpectIntEQ(test_tls_require_ems_ex(0, 1), TEST_SUCCESS); + /* Server requires EMS, client offers it -> success. */ + ExpectIntEQ(test_tls_require_ems_ex(1, 0), TEST_SUCCESS); + /* Server requires EMS, client disabled it -> abort. */ + ExpectIntEQ(test_tls_require_ems_ex(1, 1), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + + #if !defined(WOLFSSL_NO_TLS12) && \ defined(BUILD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) && \ defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) @@ -878,6 +1001,56 @@ int test_wolfSSL_DisableExtendedMasterSecret(void) } +int test_wolfSSL_DisableNormalMasterSecret(void) +{ + EXPECT_DECLS; +#if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_TLS) + WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); + WOLFSSL *ssl = wolfSSL_new(ctx); + + ExpectNotNull(ctx); + ExpectNotNull(ssl); + + /* error cases */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableNormalMasterSecret(NULL)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_DisableNormalMasterSecret(NULL)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_EnableNormalMasterSecret(NULL)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_EnableNormalMasterSecret(NULL)); + + /* success cases */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableNormalMasterSecret(ctx)); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableNormalMasterSecret(ssl)); + + /* Requiring EMS must (re)enable advertising it on a client. */ + ExpectIntEQ(ctx->haveEMS, 1); + ExpectIntEQ(ssl->options.haveEMS, 1); + ExpectIntEQ(ctx->requireEMS, 1); + ExpectIntEQ(ssl->options.requireEMS, 1); + + /* Re-enabling the normal master secret clears the requirement but leaves + * EMS support intact. */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_EnableNormalMasterSecret(ctx)); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_EnableNormalMasterSecret(ssl)); + ExpectIntEQ(ctx->requireEMS, 0); + ExpectIntEQ(ssl->options.requireEMS, 0); + ExpectIntEQ(ctx->haveEMS, 1); + ExpectIntEQ(ssl->options.haveEMS, 1); + + /* Disabling EMS afterwards clears the requirement (mutually exclusive). */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableNormalMasterSecret(ssl)); + ExpectIntEQ(ssl->options.requireEMS, 1); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableExtendedMasterSecret(ssl)); + ExpectIntEQ(ssl->options.requireEMS, 0); + ExpectIntEQ(ssl->options.disableEMS, 1); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + + #if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ !defined(WOLFSSL_NO_CA_NAMES) && !defined(NO_BIO) && \ !defined(NO_CERTS) && !defined(NO_TLS) && (defined(OPENSSL_EXTRA) || \ diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 40041c05333..e676d4e4547 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -25,6 +25,8 @@ int test_tls_ems_downgrade(void); int test_tls_ems_resumption_downgrade(void); int test_tls_ems_resumption_server_downgrade(void); +int test_tls_ems_server_disable(void); +int test_tls_require_ems(void); int test_tls12_chacha20_poly1305_bad_tag(void); int test_tls13_null_cipher_bad_hmac(void); int test_scr_verify_data_mismatch(void); @@ -34,6 +36,7 @@ int test_helloRequest_advertise_only_refused(void); int test_tls13_hrr_cipher_suite_mismatch(void); int test_tls13_ticket_age_out_of_window(void); int test_wolfSSL_DisableExtendedMasterSecret(void); +int test_wolfSSL_DisableNormalMasterSecret(void); int test_certificate_authorities_certificate_request(void); int test_certificate_authorities_client_hello(void); int test_TLSX_TCA_Find(void); diff --git a/wolfssl/error-ssl.h b/wolfssl/error-ssl.h index 4c6893fbeb3..8c28405705d 100644 --- a/wolfssl/error-ssl.h +++ b/wolfssl/error-ssl.h @@ -159,7 +159,7 @@ enum wolfSSL_ErrorCodes { DTLS_EXPORT_VER_E = -411, /* export version error */ INPUT_SIZE_E = -412, /* input size too big error */ CTX_INIT_MUTEX_E = -413, /* initialize ctx mutex error */ - EXT_MASTER_SECRET_NEEDED_E = -414, /* need EMS enabled to resume */ + EXT_MASTER_SECRET_NEEDED_E = -414, /* EMS required but not negotiated */ DTLS_POOL_SZ_E = -415, /* exceeded DTLS pool size */ DECODE_E = -416, /* decode handshake message error */ HTTP_TIMEOUT = -417, /* HTTP timeout for OCSP or CRL req */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 2f96892e06e..2057ba650fb 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -4215,6 +4215,13 @@ struct WOLFSSL_CTX { byte groupMessages:1; /* group handshake messages before sending */ byte minDowngrade; /* minimum downgrade version */ byte haveEMS:1; /* have extended master secret extension */ +#ifdef HAVE_EXTENDED_MASTER + byte disableEMS:1; /* user disabled extended master secret, + * ignore peer's EMS request (server) and + * don't advertise it (client) */ + byte requireEMS:1; /* user requires extended master secret, + * abort if EMS is not negotiated */ +#endif byte useClientOrder:1; /* Use client's cipher preference order */ #if defined(HAVE_SESSION_TICKET) byte noTicketTls12:1; /* TLS 1.2 server won't send ticket */ @@ -5455,6 +5462,12 @@ struct Options { word16 weOwnRng:1; /* will be true unless CTX owns */ word16 dontFreeDigest:1; /* when true, we used SetDigest */ word16 haveEMS:1; /* using extended master secret */ +#ifdef HAVE_EXTENDED_MASTER + word16 disableEMS:1; /* user disabled extended master + * secret */ + word16 requireEMS:1; /* user requires extended master + * secret */ +#endif #ifdef HAVE_POLY1305 word16 oldPoly:1; /* set when to use old rfc way of poly*/ #endif diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 14507d16888..d4368512abf 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -5195,6 +5195,10 @@ WOLFSSL_API int wolfSSL_CTX_add_client_custom_ext(WOLFSSL_CTX* ctx, /* TLS Extended Master Secret Extension */ WOLFSSL_API int wolfSSL_DisableExtendedMasterSecret(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_CTX_DisableExtendedMasterSecret(WOLFSSL_CTX* ctx); +WOLFSSL_API int wolfSSL_DisableNormalMasterSecret(WOLFSSL* ssl); +WOLFSSL_API int wolfSSL_CTX_DisableNormalMasterSecret(WOLFSSL_CTX* ctx); +WOLFSSL_API int wolfSSL_EnableNormalMasterSecret(WOLFSSL* ssl); +WOLFSSL_API int wolfSSL_CTX_EnableNormalMasterSecret(WOLFSSL_CTX* ctx); #define WOLFSSL_CRL_MONITOR 0x01 /* monitor this dir flag */ From 631313fd3ec3ddde4487abd89531728d5e6044e7 Mon Sep 17 00:00:00 2001 From: Kareem Date: Wed, 22 Jul 2026 16:28:50 -0700 Subject: [PATCH 2/7] Code review feedback --- src/tls.c | 12 +++++++----- tests/api/test_tls_ext.c | 25 ++++++++++++++----------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/tls.c b/src/tls.c index 09747e3808d..f75044b11f9 100644 --- a/src/tls.c +++ b/src/tls.c @@ -18862,13 +18862,15 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, if (size != 0) return BUFFER_ERROR; + /* Honor a user request to disable EMS by ignoring the peer's + * extension rather than enabling it. */ + if (!ssl->options.disableEMS) { #ifndef NO_WOLFSSL_SERVER - /* Honor a user request to disable EMS on the server by - * ignoring the peer's extension rather than enabling it. */ - if (isRequest && !ssl->options.disableEMS) - ssl->options.haveEMS = 1; + if (isRequest) + ssl->options.haveEMS = 1; #endif - pendingEMS = 1; + pendingEMS = 1; + } break; #endif diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 76aa1942a30..9ed8980a3df 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -369,6 +369,7 @@ static int test_tls_require_ems_ex(int serverSide, int peerDisables) WOLFSSL_CTX *ctx_s = NULL; WOLFSSL *ssl_c = NULL; WOLFSSL *ssl_s = NULL; + int ret; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); @@ -389,12 +390,13 @@ static int test_tls_require_ems_ex(int serverSide, int peerDisables) WOLFSSL_SUCCESS); /* EMS cannot be negotiated so the requiring side must abort. */ - ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ret = test_memio_do_handshake(ssl_c, ssl_s, 10, NULL); + ExpectIntNE(ret, 0); if (serverSide) - ExpectIntEQ(wolfSSL_get_error(ssl_s, 0), + ExpectIntEQ(wolfSSL_get_error(ssl_s, ret), WC_NO_ERR_TRACE(EXT_MASTER_SECRET_NEEDED_E)); else - ExpectIntEQ(wolfSSL_get_error(ssl_c, 0), + ExpectIntEQ(wolfSSL_get_error(ssl_c, ret), WC_NO_ERR_TRACE(EXT_MASTER_SECRET_NEEDED_E)); } else { @@ -980,11 +982,11 @@ int test_wolfSSL_DisableExtendedMasterSecret(void) EXPECT_DECLS; #if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && \ !defined(NO_TLS) - WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); - WOLFSSL *ssl = wolfSSL_new(ctx); + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; - ExpectNotNull(ctx); - ExpectNotNull(ssl); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); /* error cases */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableExtendedMasterSecret(NULL)); @@ -1006,11 +1008,12 @@ int test_wolfSSL_DisableNormalMasterSecret(void) EXPECT_DECLS; #if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && \ !defined(NO_TLS) - WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); - WOLFSSL *ssl = wolfSSL_new(ctx); + WOLFSSL_CTX *ctx = NULL; + WOLFSSL *ssl = NULL; - ExpectNotNull(ctx); - ExpectNotNull(ssl); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); /* error cases */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableNormalMasterSecret(NULL)); From f665f1fbc5ff21d62b800debf6c1521c97c6a865 Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 6 Aug 2026 17:03:49 -0700 Subject: [PATCH 3/7] Code review feedback --- doc/dox_comments/header_files/ssl.h | 143 +++++++++++++++++ src/internal.c | 47 +++++- src/ssl_api_ext.c | 79 +++++----- src/ssl_sess.c | 12 +- tests/api.c | 4 +- tests/api/test_tls_ext.c | 236 ++++++++++++++++++++++++---- tests/api/test_tls_ext.h | 4 +- wolfssl/ssl.h | 8 +- 8 files changed, 449 insertions(+), 84 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index b1b2ebe50e9..d29a38114a1 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -17372,3 +17372,146 @@ int wolfSSL_get_scr_check_enabled(const WOLFSSL* ssl); \sa wolfSSL_get_scr_check_enabled */ int wolfSSL_set_scr_check_enabled(WOLFSSL* ssl, byte enabled); + +/*! + \ingroup Setup + \brief Disables the TLS Extended Master Secret extension (RFC 7627) on + the context: a client stops advertising it and a server ignores the + peer's request, so a standard master secret is negotiated. TLS 1.2 and + earlier only. Requires HAVE_EXTENDED_MASTER. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ctx is NULL. + + \param ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + + _Example_ + \code + wolfSSL_CTX_DisableExtendedMasterSecret(ctx); + \endcode + + \sa wolfSSL_DisableExtendedMasterSecret + \sa wolfSSL_CTX_EnableExtendedMasterSecret + \sa wolfSSL_CTX_RequireExtendedMasterSecret +*/ +int wolfSSL_CTX_DisableExtendedMasterSecret(WOLFSSL_CTX* ctx); + +/*! + \ingroup Setup + \brief Disables the TLS Extended Master Secret extension (RFC 7627) on + the SSL object: a client stops advertising it and a server ignores the + peer's request, so a standard master secret is negotiated. TLS 1.2 and + earlier only. Requires HAVE_EXTENDED_MASTER. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ssl is NULL. + + \param ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). + + _Example_ + \code + wolfSSL_DisableExtendedMasterSecret(ssl); + \endcode + + \sa wolfSSL_CTX_DisableExtendedMasterSecret + \sa wolfSSL_EnableExtendedMasterSecret + \sa wolfSSL_RequireExtendedMasterSecret +*/ +int wolfSSL_DisableExtendedMasterSecret(WOLFSSL* ssl); + +/*! + \ingroup Setup + \brief Re-enables the TLS Extended Master Secret extension (RFC 7627) on + the context (the default): EMS is used when the peer supports it but is + not mandatory. Undoes a previous disable or require. Requires + HAVE_EXTENDED_MASTER. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ctx is NULL. + + \param ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + + _Example_ + \code + wolfSSL_CTX_EnableExtendedMasterSecret(ctx); + \endcode + + \sa wolfSSL_EnableExtendedMasterSecret + \sa wolfSSL_CTX_DisableExtendedMasterSecret + \sa wolfSSL_CTX_RequireExtendedMasterSecret +*/ +int wolfSSL_CTX_EnableExtendedMasterSecret(WOLFSSL_CTX* ctx); + +/*! + \ingroup Setup + \brief Re-enables the TLS Extended Master Secret extension (RFC 7627) on + the SSL object (the default): EMS is used when the peer supports it but + is not mandatory. Undoes a previous disable or require. Requires + HAVE_EXTENDED_MASTER. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ssl is NULL. + + \param ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). + + _Example_ + \code + wolfSSL_EnableExtendedMasterSecret(ssl); + \endcode + + \sa wolfSSL_CTX_EnableExtendedMasterSecret + \sa wolfSSL_DisableExtendedMasterSecret + \sa wolfSSL_RequireExtendedMasterSecret +*/ +int wolfSSL_EnableExtendedMasterSecret(WOLFSSL* ssl); + +/*! + \ingroup Setup + \brief Makes the TLS Extended Master Secret extension (RFC 7627) + mandatory on the context: if it is not negotiated, the connection + is aborted with EXT_MASTER_SECRET_NEEDED_E. A client advertises + the extension even after a previous disable. TLS 1.2 and earlier + only. Requires HAVE_EXTENDED_MASTER. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ctx is NULL. + + \param ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + + _Example_ + \code + wolfSSL_CTX_RequireExtendedMasterSecret(ctx); + \endcode + + \sa wolfSSL_RequireExtendedMasterSecret + \sa wolfSSL_CTX_EnableExtendedMasterSecret + \sa wolfSSL_CTX_DisableExtendedMasterSecret +*/ +int wolfSSL_CTX_RequireExtendedMasterSecret(WOLFSSL_CTX* ctx); + +/*! + \ingroup Setup + \brief Makes the TLS Extended Master Secret extension (RFC 7627) + mandatory on the SSL object: if it is not negotiated, including on + resumption, the connection is aborted with EXT_MASTER_SECRET_NEEDED_E. A + client advertises the extension even after a previous disable. TLS 1.2 + and earlier only. Requires HAVE_EXTENDED_MASTER. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ssl is NULL. + + \param ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). + + _Example_ + \code + wolfSSL_RequireExtendedMasterSecret(ssl); + \endcode + + \sa wolfSSL_CTX_RequireExtendedMasterSecret + \sa wolfSSL_EnableExtendedMasterSecret + \sa wolfSSL_DisableExtendedMasterSecret +*/ +int wolfSSL_RequireExtendedMasterSecret(WOLFSSL* ssl); diff --git a/src/internal.c b/src/internal.c index 045fe6b561d..ee4415b1675 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2362,7 +2362,8 @@ int InitSSL_Side(WOLFSSL* ssl, word16 side) #endif /* WOLFSSL_HAVE_SLHDSA */ #if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) - if (ssl->options.side == WOLFSSL_CLIENT_END) { + /* Don't re-arm EMS advertising that the user disabled. */ + if (ssl->options.side == WOLFSSL_CLIENT_END && !ssl->options.disableEMS) { if ((ssl->ctx->method->version.major == SSLv3_MAJOR) && (ssl->ctx->method->version.minor >= TLSv1_MINOR)) { ssl->options.haveEMS = 1; @@ -24713,24 +24714,36 @@ static void DropAndRestartProcessReply(WOLFSSL* ssl) * Returns 0 if consistent, else sends a fatal alert and returns an error. */ static int CheckResumptionConsistency(WOLFSSL* ssl) { + byte skipEmsCheck = 0; + if (ssl->session == NULL) /* nothing to compare against */ return 0; - /* EMS must match (RFC 7627 5.3); skip EAP-FAST (session-secret callback). */ - if ( #ifdef HAVE_SECRET_CALLBACK - !(ssl->sessionSecretCb != NULL + /* Skip the EMS checks for EAP-FAST (session-secret callback): the master + * secret comes from the callback rather than the cached session. */ + skipEmsCheck = ssl->sessionSecretCb != NULL #ifdef HAVE_SESSION_TICKET && ssl->session->ticketLen > 0 #endif - ) && + ; #endif - ssl->session->haveEMS != ssl->options.haveEMS) { + /* EMS must match (RFC 7627 5.3). */ + if (!skipEmsCheck && ssl->session->haveEMS != ssl->options.haveEMS) { WOLFSSL_MSG("Resumed session EMS state does not match " "ServerHello EMS state"); SendAlert(ssl, alert_fatal, handshake_failure); WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); return EXT_MASTER_SECRET_NEEDED_E; } +#ifdef HAVE_EXTENDED_MASTER + /* Resumption skips MakeTlsMasterSecret, so enforce required EMS here. */ + if (!skipEmsCheck && ssl->options.requireEMS && !ssl->options.haveEMS) { + WOLFSSL_MSG("EMS required but not negotiated with peer"); + SendAlert(ssl, alert_fatal, handshake_failure); + WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); + return EXT_MASTER_SECRET_NEEDED_E; + } +#endif /* HAVE_EXTENDED_MASTER */ #ifndef NO_RESUME_SUITE_CHECK /* Suite must match (RFC 5246 7.4.1.3), tickets included. Skip when no suite * was retained (both zero = TLS_NULL_WITH_NULL_NULL, e.g. EAP-FAST PAC). */ @@ -34702,8 +34715,16 @@ static void MakePSKPreMasterSecret(Arrays* arrays, byte use_psk_key) if (OPAQUE16_LEN + OPAQUE16_LEN + extSz > totalExtSz) return BUFFER_ERROR; - if (extId == HELLO_EXT_EXTMS) + if (extId == HELLO_EXT_EXTMS) { +#ifdef HAVE_EXTENDED_MASTER + /* Ignore the peer's extension when the user + * disabled EMS. */ + if (!ssl->options.disableEMS) + pendingEMS = 1; +#else pendingEMS = 1; +#endif + } else i += extSz; @@ -40832,6 +40853,18 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #endif /* !WOLFSSL_NO_TICKET_EXPIRE && !NO_ASN_TIME */ +#ifdef HAVE_EXTENDED_MASTER + else if (ssl->options.requireEMS && !ssl->options.haveEMS) { + /* Resumption skips MakeTlsMasterSecret, so enforce required EMS + * here. */ + WOLFSSL_MSG("EMS required but not negotiated with peer"); + #ifdef WOLFSSL_EXTRA_ALERTS + SendAlert(ssl, alert_fatal, handshake_failure); + #endif + ret = EXT_MASTER_SECRET_NEEDED_E; + WOLFSSL_ERROR_VERBOSE(ret); + } +#endif /* HAVE_EXTENDED_MASTER */ else if (session->haveEMS != ssl->options.haveEMS) { /* RFC 7627, 5.3, server-side */ /* if old sess didn't have EMS, but new does, full handshake */ diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index a47d235bc5d..e6db252ca28 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -1619,29 +1619,25 @@ int wolfSSL_DisableExtendedMasterSecret(WOLFSSL* ssl) } -/* Disable the standard (non-extended) master secret on the context. +/* Re-enable the Extended Master Secret extension on the context (default). * - * The Extended Master Secret extension (RFC 7627) becomes mandatory: if it is - * not negotiated with the peer the connection is aborted with - * EXT_MASTER_SECRET_NEEDED_E rather than falling back to a standard master - * secret. This only applies to TLS 1.2 and earlier; TLS 1.3 always uses a - * secure key schedule and is unaffected. + * Undoes a previous disable or require: EMS is used when the peer supports it + * but is not mandatory. * * @param [in] ctx SSL/TLS context object. * @return WOLFSSL_SUCCESS on success. * @return BAD_FUNC_ARG when ctx is NULL. */ -int wolfSSL_CTX_DisableNormalMasterSecret(WOLFSSL_CTX* ctx) +int wolfSSL_CTX_EnableExtendedMasterSecret(WOLFSSL_CTX* ctx) { if (ctx == NULL) return BAD_FUNC_ARG; - ctx->requireEMS = 1; - /* Requiring EMS is mutually exclusive with disabling it. */ ctx->disableEMS = 0; - /* A client must advertise the extension for it to be negotiated. Undo any - * previous disable so the extension is offered. A server keeps its EMS - * state driven by the incoming ClientHello. */ + ctx->requireEMS = 0; + /* Re-arm client advertising. A side-less (wolfSSLv23) object is armed by + * InitSSL_Side instead; arming it here would make a server echo an + * unsolicited extension. */ if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) ctx->haveEMS = 1; @@ -1649,29 +1645,25 @@ int wolfSSL_CTX_DisableNormalMasterSecret(WOLFSSL_CTX* ctx) } -/* Disable the standard (non-extended) master secret on the object. +/* Re-enable the Extended Master Secret extension on the object (default). * - * The Extended Master Secret extension (RFC 7627) becomes mandatory: if it is - * not negotiated with the peer the connection is aborted with - * EXT_MASTER_SECRET_NEEDED_E rather than falling back to a standard master - * secret. This only applies to TLS 1.2 and earlier; TLS 1.3 always uses a - * secure key schedule and is unaffected. + * Undoes a previous disable or require: EMS is used when the peer supports it + * but is not mandatory. * * @param [in] ssl SSL/TLS object. * @return WOLFSSL_SUCCESS on success. * @return BAD_FUNC_ARG when ssl is NULL. */ -int wolfSSL_DisableNormalMasterSecret(WOLFSSL* ssl) +int wolfSSL_EnableExtendedMasterSecret(WOLFSSL* ssl) { if (ssl == NULL) return BAD_FUNC_ARG; - ssl->options.requireEMS = 1; - /* Requiring EMS is mutually exclusive with disabling it. */ ssl->options.disableEMS = 0; - /* A client must advertise the extension for it to be negotiated. Undo any - * previous disable so the extension is offered. A server keeps its EMS - * state driven by the incoming ClientHello. */ + ssl->options.requireEMS = 0; + /* Re-arm client advertising. A side-less (wolfSSLv23) object is armed by + * InitSSL_Side instead; arming it here would make a server echo an + * unsolicited extension. */ if (ssl->options.side == WOLFSSL_CLIENT_END) ssl->options.haveEMS = 1; @@ -1679,44 +1671,57 @@ int wolfSSL_DisableNormalMasterSecret(WOLFSSL* ssl) } -/* Re-enable the standard (non-extended) master secret on the context. +/* Require the Extended Master Secret extension on the context. * - * Undoes wolfSSL_CTX_DisableNormalMasterSecret so that a standard master - * secret is once again acceptable when the Extended Master Secret extension - * (RFC 7627) is not negotiated. Extended Master Secret support itself is left - * unchanged. + * If EMS (RFC 7627) is not negotiated the connection is aborted with + * EXT_MASTER_SECRET_NEEDED_E instead of deriving a standard master secret. + * TLS 1.3 has its own key schedule and is unaffected. * * @param [in] ctx SSL/TLS context object. * @return WOLFSSL_SUCCESS on success. * @return BAD_FUNC_ARG when ctx is NULL. */ -int wolfSSL_CTX_EnableNormalMasterSecret(WOLFSSL_CTX* ctx) +int wolfSSL_CTX_RequireExtendedMasterSecret(WOLFSSL_CTX* ctx) { if (ctx == NULL) return BAD_FUNC_ARG; - ctx->requireEMS = 0; + ctx->requireEMS = 1; + /* Mutually exclusive with disabling EMS. */ + ctx->disableEMS = 0; + /* Re-arm client advertising. A side-less (wolfSSLv23) object is armed by + * InitSSL_Side instead; arming it here would make a server echo an + * unsolicited extension. */ + if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) + ctx->haveEMS = 1; return WOLFSSL_SUCCESS; } -/* Re-enable the standard (non-extended) master secret on the object. +/* Require the Extended Master Secret extension on the object. * - * Undoes wolfSSL_DisableNormalMasterSecret so that a standard master secret is - * once again acceptable when the Extended Master Secret extension (RFC 7627) - * is not negotiated. Extended Master Secret support itself is left unchanged. + * If EMS (RFC 7627) is not negotiated the connection is aborted with + * EXT_MASTER_SECRET_NEEDED_E instead of deriving a standard master secret. + * TLS 1.3 has its own key schedule and is unaffected. * * @param [in] ssl SSL/TLS object. * @return WOLFSSL_SUCCESS on success. * @return BAD_FUNC_ARG when ssl is NULL. */ -int wolfSSL_EnableNormalMasterSecret(WOLFSSL* ssl) +int wolfSSL_RequireExtendedMasterSecret(WOLFSSL* ssl) { if (ssl == NULL) return BAD_FUNC_ARG; - ssl->options.requireEMS = 0; + ssl->options.requireEMS = 1; + /* Mutually exclusive with disabling EMS. */ + ssl->options.disableEMS = 0; + /* Re-arm client advertising. A side-less (wolfSSLv23) object is armed by + * InitSSL_Side instead; arming it here would make a server echo an + * unsolicited extension. */ + if (ssl->options.side == WOLFSSL_CLIENT_END) + ssl->options.haveEMS = 1; return ret; } diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 862a7afbd97..7061faca0e0 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1607,7 +1607,17 @@ int wolfSSL_SetSession(WOLFSSL* ssl, WOLFSSL_SESSION* session) #endif } ssl->options.resuming = 1; - ssl->options.haveEMS = (ssl->session->haveEMS) ? 1 : 0; +#ifdef HAVE_EXTENDED_MASTER + /* A user EMS override takes precedence over the session's EMS state. */ + if (ssl->options.requireEMS) + ssl->options.haveEMS = 1; + else if (ssl->options.disableEMS) + ssl->options.haveEMS = 0; + else +#endif + { + ssl->options.haveEMS = (ssl->session->haveEMS) ? 1 : 0; + } if (ssl->session->version.major != 0) { /* Reject sessions whose protocol version is below the configured diff --git a/tests/api.c b/tests/api.c index abb9b8dc85d..535607e2523 100644 --- a/tests/api.c +++ b/tests/api.c @@ -40456,7 +40456,9 @@ TEST_CASE testCases[] = { TEST_DECL(test_tls_ems_resumption_downgrade), TEST_DECL(test_tls_ems_resumption_server_downgrade), TEST_DECL(test_tls_ems_server_disable), + TEST_DECL(test_tls_ems_disable_v23), TEST_DECL(test_tls_require_ems), + TEST_DECL(test_tls_require_ems_resumption), TEST_DECL(test_tls12_chacha20_poly1305_bad_tag), TEST_DECL(test_tls13_null_cipher_bad_hmac), TEST_DECL(test_scr_verify_data_mismatch), @@ -40466,7 +40468,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_tls13_hrr_cipher_suite_mismatch), TEST_DECL(test_tls13_ticket_age_out_of_window), TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret), - TEST_DECL(test_wolfSSL_DisableNormalMasterSecret), + TEST_DECL(test_wolfSSL_RequireExtendedMasterSecret), TEST_DECL(test_certificate_authorities_certificate_request), TEST_DECL(test_certificate_authorities_client_hello), TEST_DECL(test_TLSX_TCA_Find), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 9ed8980a3df..13350870e3b 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -340,10 +340,8 @@ int test_tls_ems_server_disable(void) ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); /* Neither side ends up using EMS. */ - if (ssl_s != NULL) - ExpectIntEQ(ssl_s->options.haveEMS, 0); - if (ssl_c != NULL) - ExpectIntEQ(ssl_c->options.haveEMS, 0); + ExpectIntEQ(ssl_s->options.haveEMS, 0); + ExpectIntEQ(ssl_c->options.haveEMS, 0); wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); @@ -357,10 +355,8 @@ int test_tls_ems_server_disable(void) #if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) -/* Exercise wolfSSL_DisableNormalMasterSecret. When serverSide is set the - * server requires EMS, otherwise the client does. When peerDisables is set the - * opposite side disables EMS, so the extension cannot be negotiated and the - * requiring side must abort with EXT_MASTER_SECRET_NEEDED_E. */ +/* serverSide selects which side requires EMS. peerDisables makes the other + * side disable EMS, so the requiring side must abort. */ static int test_tls_require_ems_ex(int serverSide, int peerDisables) { EXPECT_DECLS; @@ -377,9 +373,11 @@ static int test_tls_require_ems_ex(int serverSide, int peerDisables) wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); if (serverSide) - ExpectIntEQ(wolfSSL_DisableNormalMasterSecret(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(ssl_s), + WOLFSSL_SUCCESS); else - ExpectIntEQ(wolfSSL_DisableNormalMasterSecret(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(ssl_c), + WOLFSSL_SUCCESS); if (peerDisables) { if (serverSide) @@ -402,10 +400,8 @@ static int test_tls_require_ems_ex(int serverSide, int peerDisables) else { /* Peer supports EMS so the handshake completes using EMS. */ ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - if (ssl_c != NULL) - ExpectIntEQ(ssl_c->options.haveEMS, 1); - if (ssl_s != NULL) - ExpectIntEQ(ssl_s->options.haveEMS, 1); + ExpectIntEQ(ssl_c->options.haveEMS, 1); + ExpectIntEQ(ssl_s->options.haveEMS, 1); } wolfSSL_free(ssl_c); @@ -416,9 +412,8 @@ static int test_tls_require_ems_ex(int serverSide, int peerDisables) } #endif -/* wolfSSL_DisableNormalMasterSecret makes the Extended Master Secret extension - * mandatory: the handshake succeeds when the peer supports EMS and is aborted - * with EXT_MASTER_SECRET_NEEDED_E otherwise, on both client and server. */ +/* wolfSSL_RequireExtendedMasterSecret: the handshake succeeds when the peer + * supports EMS and aborts with EXT_MASTER_SECRET_NEEDED_E otherwise. */ int test_tls_require_ems(void) { EXPECT_DECLS; @@ -438,6 +433,165 @@ int test_tls_require_ems(void) } +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) +/* Resume a non-EMS session without EMS while one side requires EMS: that side + * must abort with EXT_MASTER_SECRET_NEEDED_E. serverSide selects whether the + * server (context level) or the client (object level) requires EMS. */ +static int test_tls_require_ems_resumption_ex(int serverSide) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + WOLFSSL_SESSION *session = NULL; + int ret; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Establish a session that does not use EMS. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectNotNull(session = wolfSSL_get1_session(ssl_c)); + ExpectFalse(session->haveEMS); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + + if (serverSide) { + /* Set on the context so the new server object must inherit it. */ + ExpectIntEQ(wolfSSL_CTX_RequireExtendedMasterSecret(ctx_s), + WOLFSSL_SUCCESS); + } + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + + if (serverSide) { + ExpectIntEQ(ssl_s->options.requireEMS, 1); + /* Client presents the non-EMS session without offering EMS. */ + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_c), + WOLFSSL_SUCCESS); + } + else { + /* Server ignores the offered extension and resumes without EMS. */ + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(ssl_c), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_s), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_session(ssl_c, session), WOLFSSL_SUCCESS); + if (!serverSide) { + /* Require keeps EMS advertised despite the non-EMS session. */ + ExpectIntEQ(ssl_c->options.haveEMS, 1); + } + + /* The requiring side must catch the missing EMS and abort. */ + ret = test_memio_do_handshake(ssl_c, ssl_s, 10, NULL); + ExpectIntNE(ret, 0); + if (serverSide) + ExpectIntEQ(wolfSSL_get_error(ssl_s, ret), + WC_NO_ERR_TRACE(EXT_MASTER_SECRET_NEEDED_E)); + else + ExpectIntEQ(wolfSSL_get_error(ssl_c, ret), + WC_NO_ERR_TRACE(EXT_MASTER_SECRET_NEEDED_E)); + /* Server took the abbreviated path, so the resumption check fired. */ + ExpectIntEQ(ssl_s->options.resuming, 1); + + wolfSSL_SESSION_free(session); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* Requiring EMS is enforced on TLS 1.2 abbreviated handshakes, both sides. */ +int test_tls_require_ems_resumption(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) + ExpectIntEQ(test_tls_require_ems_resumption_ex(1), TEST_SUCCESS); + ExpectIntEQ(test_tls_require_ems_resumption_ex(0), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + + +/* A wolfSSLv23_method object gains its side in InitSSL_Side, which used to + * re-arm EMS advertising and undo a previous disable. Disable must survive + * becoming a client, and require must arm advertising then. */ +int test_tls_ems_disable_v23(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(OPENSSL_EXTRA) + 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, + wolfSSLv23_method, wolfTLSv1_2_server_method), 0); + + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_connect_state(ssl_c); + ExpectIntEQ(ssl_c->options.haveEMS, 0); + + /* Handshake completes without EMS. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), TLS1_2_VERSION); + ExpectIntEQ(ssl_c->options.haveEMS, 0); + ExpectIntEQ(ssl_s->options.haveEMS, 0); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + + /* Requiring EMS on a side-less object arms advertising at connect. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfSSLv23_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_RequireExtendedMasterSecret(ssl_c), WOLFSSL_SUCCESS); + wolfSSL_set_connect_state(ssl_c); + ExpectIntEQ(ssl_c->options.haveEMS, 1); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_version(ssl_c), TLS1_2_VERSION); + ExpectIntEQ(ssl_c->options.haveEMS, 1); + ExpectIntEQ(ssl_s->options.haveEMS, 1); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + #if !defined(WOLFSSL_NO_TLS12) && \ defined(BUILD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) && \ defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) @@ -1003,7 +1157,7 @@ int test_wolfSSL_DisableExtendedMasterSecret(void) } -int test_wolfSSL_DisableNormalMasterSecret(void) +int test_wolfSSL_RequireExtendedMasterSecret(void) { EXPECT_DECLS; #if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && \ @@ -1011,37 +1165,53 @@ int test_wolfSSL_DisableNormalMasterSecret(void) WOLFSSL_CTX *ctx = NULL; WOLFSSL *ssl = NULL; - ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); ExpectNotNull(ssl = wolfSSL_new(ctx)); /* error cases */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableNormalMasterSecret(NULL)); - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_DisableNormalMasterSecret(NULL)); - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_EnableNormalMasterSecret(NULL)); - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_EnableNormalMasterSecret(NULL)); + ExpectIntNE(WOLFSSL_SUCCESS, + wolfSSL_CTX_RequireExtendedMasterSecret(NULL)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_RequireExtendedMasterSecret(NULL)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_EnableExtendedMasterSecret(NULL)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_EnableExtendedMasterSecret(NULL)); - /* success cases */ - ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableNormalMasterSecret(ctx)); - ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableNormalMasterSecret(ssl)); + /* Disable first so require has to restore state. */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_DisableExtendedMasterSecret(ctx)); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableExtendedMasterSecret(ssl)); + ExpectIntEQ(ctx->haveEMS, 0); + ExpectIntEQ(ssl->options.haveEMS, 0); + ExpectIntEQ(ctx->disableEMS, 1); + ExpectIntEQ(ssl->options.disableEMS, 1); - /* Requiring EMS must (re)enable advertising it on a client. */ + /* Require clears the disable and re-enables client advertising. */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_RequireExtendedMasterSecret(ctx)); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_RequireExtendedMasterSecret(ssl)); ExpectIntEQ(ctx->haveEMS, 1); ExpectIntEQ(ssl->options.haveEMS, 1); + ExpectIntEQ(ctx->disableEMS, 0); + ExpectIntEQ(ssl->options.disableEMS, 0); ExpectIntEQ(ctx->requireEMS, 1); ExpectIntEQ(ssl->options.requireEMS, 1); - /* Re-enabling the normal master secret clears the requirement but leaves - * EMS support intact. */ - ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_EnableNormalMasterSecret(ctx)); - ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_EnableNormalMasterSecret(ssl)); + /* Enable clears the requirement, keeps EMS on. */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_EnableExtendedMasterSecret(ctx)); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_EnableExtendedMasterSecret(ssl)); ExpectIntEQ(ctx->requireEMS, 0); ExpectIntEQ(ssl->options.requireEMS, 0); ExpectIntEQ(ctx->haveEMS, 1); ExpectIntEQ(ssl->options.haveEMS, 1); - /* Disabling EMS afterwards clears the requirement (mutually exclusive). */ - ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableNormalMasterSecret(ssl)); + /* Enable also undoes a disable. */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableExtendedMasterSecret(ssl)); + ExpectIntEQ(ssl->options.disableEMS, 1); + ExpectIntEQ(ssl->options.haveEMS, 0); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_EnableExtendedMasterSecret(ssl)); + ExpectIntEQ(ssl->options.disableEMS, 0); + ExpectIntEQ(ssl->options.requireEMS, 0); + ExpectIntEQ(ssl->options.haveEMS, 1); + + /* Disabling EMS clears the requirement (mutually exclusive). */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_RequireExtendedMasterSecret(ssl)); ExpectIntEQ(ssl->options.requireEMS, 1); ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_DisableExtendedMasterSecret(ssl)); ExpectIntEQ(ssl->options.requireEMS, 0); diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index e676d4e4547..1f65ee57696 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -26,7 +26,9 @@ int test_tls_ems_downgrade(void); int test_tls_ems_resumption_downgrade(void); int test_tls_ems_resumption_server_downgrade(void); int test_tls_ems_server_disable(void); +int test_tls_ems_disable_v23(void); int test_tls_require_ems(void); +int test_tls_require_ems_resumption(void); int test_tls12_chacha20_poly1305_bad_tag(void); int test_tls13_null_cipher_bad_hmac(void); int test_scr_verify_data_mismatch(void); @@ -36,7 +38,7 @@ int test_helloRequest_advertise_only_refused(void); int test_tls13_hrr_cipher_suite_mismatch(void); int test_tls13_ticket_age_out_of_window(void); int test_wolfSSL_DisableExtendedMasterSecret(void); -int test_wolfSSL_DisableNormalMasterSecret(void); +int test_wolfSSL_RequireExtendedMasterSecret(void); int test_certificate_authorities_certificate_request(void); int test_certificate_authorities_client_hello(void); int test_TLSX_TCA_Find(void); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index d4368512abf..d4637bd5760 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -5195,10 +5195,10 @@ WOLFSSL_API int wolfSSL_CTX_add_client_custom_ext(WOLFSSL_CTX* ctx, /* TLS Extended Master Secret Extension */ WOLFSSL_API int wolfSSL_DisableExtendedMasterSecret(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_CTX_DisableExtendedMasterSecret(WOLFSSL_CTX* ctx); -WOLFSSL_API int wolfSSL_DisableNormalMasterSecret(WOLFSSL* ssl); -WOLFSSL_API int wolfSSL_CTX_DisableNormalMasterSecret(WOLFSSL_CTX* ctx); -WOLFSSL_API int wolfSSL_EnableNormalMasterSecret(WOLFSSL* ssl); -WOLFSSL_API int wolfSSL_CTX_EnableNormalMasterSecret(WOLFSSL_CTX* ctx); +WOLFSSL_API int wolfSSL_EnableExtendedMasterSecret(WOLFSSL* ssl); +WOLFSSL_API int wolfSSL_CTX_EnableExtendedMasterSecret(WOLFSSL_CTX* ctx); +WOLFSSL_API int wolfSSL_RequireExtendedMasterSecret(WOLFSSL* ssl); +WOLFSSL_API int wolfSSL_CTX_RequireExtendedMasterSecret(WOLFSSL_CTX* ctx); #define WOLFSSL_CRL_MONITOR 0x01 /* monitor this dir flag */ From 3b6f321d018ee2c5cfc9543792aa92e3b03b1e29 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 7 Aug 2026 13:55:42 -0700 Subject: [PATCH 4/7] Code review feedback --- .github/configs/os-check-linux.json | 3 + src/internal.c | 39 ++++++----- src/keys.c | 10 +++ src/ssl_api_ext.c | 104 +++++++++++++++++----------- src/ssl_sess.c | 2 +- src/tls.c | 13 ---- tests/api/test_tls_ext.c | 2 +- wolfssl/ssl.h | 2 + 8 files changed, 102 insertions(+), 73 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 9fb37b775db..2abe7f44de7 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -30,6 +30,9 @@ {"name": "all-faultharden-pk-privkey", "minutes": 7.8, "comment": "Test holding private key in the PK callback with fault harden. Based on existing customer use case.", "configure": ["--enable-all", "--enable-faultharden", "--enable-pkcallbacks", "CPPFLAGS=-DTEST_PK_PRIVKEY"]}, +{"name": "all-no-ticket-expire", "minutes": 7.8, + "comment": "Nothing else in CI compiles WOLFSSL_NO_TICKET_EXPIRE; it removes the session expiry check that heads the resumption control flow in HandleTlsResumption.", + "configure": ["--enable-all", "CPPFLAGS=-DWOLFSSL_NO_TICKET_EXPIRE"]}, {"name": "all-secure-renegotiation", "minutes": 7.8, "configure": ["--enable-all", "--enable-secure-renegotiation"]}, {"name": "all-debug-certs", "minutes": 7.8, diff --git a/src/internal.c b/src/internal.c index ee4415b1675..e50e43701bc 100644 --- a/src/internal.c +++ b/src/internal.c @@ -24721,11 +24721,11 @@ static int CheckResumptionConsistency(WOLFSSL* ssl) #ifdef HAVE_SECRET_CALLBACK /* Skip the EMS checks for EAP-FAST (session-secret callback): the master * secret comes from the callback rather than the cached session. */ - skipEmsCheck = ssl->sessionSecretCb != NULL + skipEmsCheck = (ssl->sessionSecretCb != NULL #ifdef HAVE_SESSION_TICKET && ssl->session->ticketLen > 0 #endif - ; + ) ? 1 : 0; #endif /* EMS must match (RFC 7627 5.3). */ if (!skipEmsCheck && ssl->session->haveEMS != ssl->options.haveEMS) { @@ -24736,7 +24736,7 @@ static int CheckResumptionConsistency(WOLFSSL* ssl) return EXT_MASTER_SECRET_NEEDED_E; } #ifdef HAVE_EXTENDED_MASTER - /* Resumption skips MakeTlsMasterSecret, so enforce required EMS here. */ + /* Resumption skips MakeMasterSecret, so enforce required EMS here. */ if (!skipEmsCheck && ssl->options.requireEMS && !ssl->options.haveEMS) { WOLFSSL_MSG("EMS required but not negotiated with peer"); SendAlert(ssl, alert_fatal, handshake_failure); @@ -40845,6 +40845,19 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif } #endif /* HAVE_SESSION_TICKET && (HAVE_SNI || HAVE_ALPN) */ + +#ifdef HAVE_EXTENDED_MASTER + /* Resumption skips MakeMasterSecret, so enforce required EMS here. */ + if (ssl->options.requireEMS && !ssl->options.haveEMS) { + WOLFSSL_MSG("EMS required but not negotiated with peer"); + #ifdef WOLFSSL_EXTRA_ALERTS + SendAlert(ssl, alert_fatal, handshake_failure); + #endif + WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); + return EXT_MASTER_SECRET_NEEDED_E; + } +#endif /* HAVE_EXTENDED_MASTER */ + #if !defined(WOLFSSL_NO_TICKET_EXPIRE) && !defined(NO_ASN_TIME) /* check if the ticket is valid */ if (LowResTimer() > session->bornOn + ssl->timeout) { @@ -40853,19 +40866,12 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } #endif /* !WOLFSSL_NO_TICKET_EXPIRE && !NO_ASN_TIME */ -#ifdef HAVE_EXTENDED_MASTER - else if (ssl->options.requireEMS && !ssl->options.haveEMS) { - /* Resumption skips MakeTlsMasterSecret, so enforce required EMS - * here. */ - WOLFSSL_MSG("EMS required but not negotiated with peer"); - #ifdef WOLFSSL_EXTRA_ALERTS - SendAlert(ssl, alert_fatal, handshake_failure); - #endif - ret = EXT_MASTER_SECRET_NEEDED_E; - WOLFSSL_ERROR_VERBOSE(ret); + if (!ssl->options.resuming) { + /* Expired above: DoClientHello falls back to a full handshake. */ + return ret; } -#endif /* HAVE_EXTENDED_MASTER */ - else if (session->haveEMS != ssl->options.haveEMS) { + + if (session->haveEMS != ssl->options.haveEMS) { /* RFC 7627, 5.3, server-side */ /* if old sess didn't have EMS, but new does, full handshake */ if (!session->haveEMS && ssl->options.haveEMS) { @@ -41585,7 +41591,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #ifdef HAVE_EXTENDED_MASTER /* Honor a user request to disable EMS on the server by * ignoring the peer's extension. */ - else if (extId == HELLO_EXT_EXTMS && !ssl->options.disableEMS) + else if (extId == HELLO_EXT_EXTMS && + !ssl->options.disableEMS) ssl->options.haveEMS = 1; #endif else diff --git a/src/keys.c b/src/keys.c index f9e7d3b7b37..744da03ce6a 100644 --- a/src/keys.c +++ b/src/keys.c @@ -4231,6 +4231,16 @@ static int MakeSslMasterSecret(WOLFSSL* ssl) /* Master wrapper, doesn't use SSL stack space in TLS mode */ int MakeMasterSecret(WOLFSSL* ssl) { +#ifdef HAVE_EXTENDED_MASTER + /* User requires EMS but it was not negotiated: abort rather than derive + * a standard master secret (RFC 7627). */ + if (ssl->options.requireEMS && !ssl->options.haveEMS) { + WOLFSSL_MSG("EMS required but not negotiated with peer"); + SendAlert(ssl, alert_fatal, handshake_failure); + WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); + return EXT_MASTER_SECRET_NEEDED_E; + } +#endif /* append secret to premaster : premaster | SerSi | CliSi */ #ifndef NO_OLD_TLS if (ssl->options.tls) return MakeTlsMasterSecret(ssl); diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index e6db252ca28..65d225fee95 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -1630,18 +1630,22 @@ int wolfSSL_DisableExtendedMasterSecret(WOLFSSL* ssl) */ int wolfSSL_CTX_EnableExtendedMasterSecret(WOLFSSL_CTX* ctx) { - if (ctx == NULL) - return BAD_FUNC_ARG; + int ret = WOLFSSL_SUCCESS; - ctx->disableEMS = 0; - ctx->requireEMS = 0; - /* Re-arm client advertising. A side-less (wolfSSLv23) object is armed by - * InitSSL_Side instead; arming it here would make a server echo an - * unsolicited extension. */ - if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) - ctx->haveEMS = 1; + if (ctx == NULL) { + ret = BAD_FUNC_ARG; + } + else { + ctx->disableEMS = 0; + ctx->requireEMS = 0; + /* Re-arm client advertising. A side-less (wolfSSLv23) object is + * armed by InitSSL_Side instead; arming it here would make a server + * echo an unsolicited extension. */ + if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) + ctx->haveEMS = 1; + } - return WOLFSSL_SUCCESS; + return ret; } @@ -1656,21 +1660,27 @@ int wolfSSL_CTX_EnableExtendedMasterSecret(WOLFSSL_CTX* ctx) */ int wolfSSL_EnableExtendedMasterSecret(WOLFSSL* ssl) { - if (ssl == NULL) - return BAD_FUNC_ARG; + int ret = WOLFSSL_SUCCESS; - ssl->options.disableEMS = 0; - ssl->options.requireEMS = 0; - /* Re-arm client advertising. A side-less (wolfSSLv23) object is armed by - * InitSSL_Side instead; arming it here would make a server echo an - * unsolicited extension. */ - if (ssl->options.side == WOLFSSL_CLIENT_END) - ssl->options.haveEMS = 1; + if (ssl == NULL) { + ret = BAD_FUNC_ARG; + } + else { + ssl->options.disableEMS = 0; + ssl->options.requireEMS = 0; + /* Re-arm client advertising. A side-less (wolfSSLv23) object is + * armed by InitSSL_Side instead; arming it here would make a server + * echo an unsolicited extension. */ + if (ssl->options.side == WOLFSSL_CLIENT_END) + ssl->options.haveEMS = 1; + } - return WOLFSSL_SUCCESS; + return ret; } +#ifndef WOLFSSL_NO_TLS12 + /* Require the Extended Master Secret extension on the context. * * If EMS (RFC 7627) is not negotiated the connection is aborted with @@ -1683,19 +1693,23 @@ int wolfSSL_EnableExtendedMasterSecret(WOLFSSL* ssl) */ int wolfSSL_CTX_RequireExtendedMasterSecret(WOLFSSL_CTX* ctx) { - if (ctx == NULL) - return BAD_FUNC_ARG; + int ret = WOLFSSL_SUCCESS; - ctx->requireEMS = 1; - /* Mutually exclusive with disabling EMS. */ - ctx->disableEMS = 0; - /* Re-arm client advertising. A side-less (wolfSSLv23) object is armed by - * InitSSL_Side instead; arming it here would make a server echo an - * unsolicited extension. */ - if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) - ctx->haveEMS = 1; + if (ctx == NULL) { + ret = BAD_FUNC_ARG; + } + else { + ctx->requireEMS = 1; + /* Mutually exclusive with disabling EMS. */ + ctx->disableEMS = 0; + /* Re-arm client advertising. A side-less (wolfSSLv23) object is + * armed by InitSSL_Side instead; arming it here would make a server + * echo an unsolicited extension. */ + if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) + ctx->haveEMS = 1; + } - return WOLFSSL_SUCCESS; + return ret; } @@ -1711,22 +1725,28 @@ int wolfSSL_CTX_RequireExtendedMasterSecret(WOLFSSL_CTX* ctx) */ int wolfSSL_RequireExtendedMasterSecret(WOLFSSL* ssl) { - if (ssl == NULL) - return BAD_FUNC_ARG; + int ret = WOLFSSL_SUCCESS; - ssl->options.requireEMS = 1; - /* Mutually exclusive with disabling EMS. */ - ssl->options.disableEMS = 0; - /* Re-arm client advertising. A side-less (wolfSSLv23) object is armed by - * InitSSL_Side instead; arming it here would make a server echo an - * unsolicited extension. */ - if (ssl->options.side == WOLFSSL_CLIENT_END) - ssl->options.haveEMS = 1; + if (ssl == NULL) { + ret = BAD_FUNC_ARG; + } + else { + ssl->options.requireEMS = 1; + /* Mutually exclusive with disabling EMS. */ + ssl->options.disableEMS = 0; + /* Re-arm client advertising. A side-less (wolfSSLv23) object is + * armed by InitSSL_Side instead; arming it here would make a server + * echo an unsolicited extension. */ + if (ssl->options.side == WOLFSSL_CLIENT_END) + ssl->options.haveEMS = 1; + } return ret; } -#endif +#endif /* !WOLFSSL_NO_TLS12 */ + +#endif /* HAVE_EXTENDED_MASTER */ #endif /* !NO_TLS */ /* ---- OpenSSL-compatibility TLS extension APIs (moved from ssl.c) ---- */ diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 7061faca0e0..bf206de9248 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1609,7 +1609,7 @@ int wolfSSL_SetSession(WOLFSSL* ssl, WOLFSSL_SESSION* session) ssl->options.resuming = 1; #ifdef HAVE_EXTENDED_MASTER /* A user EMS override takes precedence over the session's EMS state. */ - if (ssl->options.requireEMS) + if (ssl->options.requireEMS && ssl->options.side == WOLFSSL_CLIENT_END) ssl->options.haveEMS = 1; else if (ssl->options.disableEMS) ssl->options.haveEMS = 0; diff --git a/src/tls.c b/src/tls.c index f75044b11f9..83fa7a8cfc1 100644 --- a/src/tls.c +++ b/src/tls.c @@ -704,19 +704,6 @@ int MakeTlsMasterSecret(WOLFSSL* ssl) { int ret; -#ifdef HAVE_EXTENDED_MASTER - /* The user disabled the standard master secret and requires the Extended - * Master Secret extension (RFC 7627). If it was not negotiated with the - * peer, abort rather than derive a standard master secret. Only reachable - * for TLS 1.2 and earlier; TLS 1.3 uses a separate key schedule. */ - if (ssl->options.requireEMS && !ssl->options.haveEMS) { - WOLFSSL_MSG("EMS required but not negotiated with peer"); - SendAlert(ssl, alert_fatal, handshake_failure); - WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); - return EXT_MASTER_SECRET_NEEDED_E; - } -#endif - #if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_KEYLOGFILE) /* If this is called from a sniffer session with keylog file support, obtain * the master secret from the callback */ diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 13350870e3b..d3ed1ce121a 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -1161,7 +1161,7 @@ int test_wolfSSL_RequireExtendedMasterSecret(void) { EXPECT_DECLS; #if defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(NO_TLS) + !defined(NO_TLS) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX *ctx = NULL; WOLFSSL *ssl = NULL; diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index d4637bd5760..b49d6ff00ce 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -5197,8 +5197,10 @@ WOLFSSL_API int wolfSSL_DisableExtendedMasterSecret(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_CTX_DisableExtendedMasterSecret(WOLFSSL_CTX* ctx); WOLFSSL_API int wolfSSL_EnableExtendedMasterSecret(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_CTX_EnableExtendedMasterSecret(WOLFSSL_CTX* ctx); +#ifndef WOLFSSL_NO_TLS12 WOLFSSL_API int wolfSSL_RequireExtendedMasterSecret(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_CTX_RequireExtendedMasterSecret(WOLFSSL_CTX* ctx); +#endif #define WOLFSSL_CRL_MONITOR 0x01 /* monitor this dir flag */ From 0dce84bda420a4e6cebf63fa7af54e909601614d Mon Sep 17 00:00:00 2001 From: Kareem Date: Mon, 10 Aug 2026 16:18:36 -0700 Subject: [PATCH 5/7] EMS code review feedback: Check as early in the connection as possible Abort resumption rather than fully aborting connection when EMS is disabled Make version check around haveEMS consistent Clear premaster secret when EMS check fails Update documentation --- doc/dox_comments/header_files/ssl.h | 20 ++++++---- src/internal.c | 62 +++++++++++++++++++++-------- src/keys.c | 45 +++++++++++---------- src/ssl_api_ext.c | 29 ++++++++++++-- tests/api.c | 1 + tests/api/test_tls_ext.c | 53 ++++++++++++++++++++++++ tests/api/test_tls_ext.h | 1 + 7 files changed, 162 insertions(+), 49 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index d29a38114a1..26284b44424 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -17377,8 +17377,10 @@ int wolfSSL_set_scr_check_enabled(WOLFSSL* ssl, byte enabled); \ingroup Setup \brief Disables the TLS Extended Master Secret extension (RFC 7627) on the context: a client stops advertising it and a server ignores the - peer's request, so a standard master secret is negotiated. TLS 1.2 and - earlier only. Requires HAVE_EXTENDED_MASTER. + peer's request, so a standard master secret is negotiated. A server also + declines resumption of sessions or tickets that used EMS and does a full + handshake instead. TLS 1.2 and earlier only. Requires + HAVE_EXTENDED_MASTER. \return WOLFSSL_SUCCESS on success. \return BAD_FUNC_ARG if ctx is NULL. @@ -17401,8 +17403,10 @@ int wolfSSL_CTX_DisableExtendedMasterSecret(WOLFSSL_CTX* ctx); \ingroup Setup \brief Disables the TLS Extended Master Secret extension (RFC 7627) on the SSL object: a client stops advertising it and a server ignores the - peer's request, so a standard master secret is negotiated. TLS 1.2 and - earlier only. Requires HAVE_EXTENDED_MASTER. + peer's request, so a standard master secret is negotiated. A server also + declines resumption of sessions or tickets that used EMS and does a full + handshake instead. TLS 1.2 and earlier only. Requires + HAVE_EXTENDED_MASTER. \return WOLFSSL_SUCCESS on success. \return BAD_FUNC_ARG if ssl is NULL. @@ -17472,7 +17476,8 @@ int wolfSSL_EnableExtendedMasterSecret(WOLFSSL* ssl); \brief Makes the TLS Extended Master Secret extension (RFC 7627) mandatory on the context: if it is not negotiated, the connection is aborted with EXT_MASTER_SECRET_NEEDED_E. A client advertises - the extension even after a previous disable. TLS 1.2 and earlier + the extension even after a previous disable. Sessions using a + session-secret callback (EAP-FAST) are exempt. TLS 1.2 and earlier only. Requires HAVE_EXTENDED_MASTER. \return WOLFSSL_SUCCESS on success. @@ -17497,8 +17502,9 @@ int wolfSSL_CTX_RequireExtendedMasterSecret(WOLFSSL_CTX* ctx); \brief Makes the TLS Extended Master Secret extension (RFC 7627) mandatory on the SSL object: if it is not negotiated, including on resumption, the connection is aborted with EXT_MASTER_SECRET_NEEDED_E. A - client advertises the extension even after a previous disable. TLS 1.2 - and earlier only. Requires HAVE_EXTENDED_MASTER. + client advertises the extension even after a previous disable. Sessions + using a session-secret callback (EAP-FAST) are exempt. TLS 1.2 and + earlier only. Requires HAVE_EXTENDED_MASTER. \return WOLFSSL_SUCCESS on success. \return BAD_FUNC_ARG if ssl is NULL. diff --git a/src/internal.c b/src/internal.c index e50e43701bc..adac79a0857 100644 --- a/src/internal.c +++ b/src/internal.c @@ -24735,15 +24735,6 @@ static int CheckResumptionConsistency(WOLFSSL* ssl) WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); return EXT_MASTER_SECRET_NEEDED_E; } -#ifdef HAVE_EXTENDED_MASTER - /* Resumption skips MakeMasterSecret, so enforce required EMS here. */ - if (!skipEmsCheck && ssl->options.requireEMS && !ssl->options.haveEMS) { - WOLFSSL_MSG("EMS required but not negotiated with peer"); - SendAlert(ssl, alert_fatal, handshake_failure); - WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); - return EXT_MASTER_SECRET_NEEDED_E; - } -#endif /* HAVE_EXTENDED_MASTER */ #ifndef NO_RESUME_SUITE_CHECK /* Suite must match (RFC 5246 7.4.1.3), tickets included. Skip when no suite * was retained (both zero = TLS_NULL_WITH_NULL_NULL, e.g. EAP-FAST PAC). */ @@ -34744,6 +34735,31 @@ static void MakePSKPreMasterSecret(Arrays* arrays, byte use_psk_key) } #endif /* HAVE_TLS_EXTENSIONS */ +#ifdef HAVE_EXTENDED_MASTER + /* The negotiated EMS state is final once the ServerHello extensions + * are parsed: abort a requiring client here, before any key material + * is computed or sent. */ + if (ssl->options.requireEMS && !ssl->options.haveEMS) { + byte skipEmsCheck = 0; +#ifdef HAVE_SECRET_CALLBACK + /* Skip for EAP-FAST (session-secret callback): the master secret + * comes from the callback. */ + skipEmsCheck = (ssl->sessionSecretCb != NULL +#ifdef HAVE_SESSION_TICKET + && ssl->session != NULL + && ssl->session->ticketLen > 0 +#endif + ) ? 1 : 0; +#endif + if (!skipEmsCheck) { + WOLFSSL_MSG("EMS required but not negotiated with peer"); + SendAlert(ssl, alert_fatal, handshake_failure); + WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); + return EXT_MASTER_SECRET_NEEDED_E; + } + } +#endif /* HAVE_EXTENDED_MASTER */ + #if !defined(NO_WOLFSSL_CLIENT) && !defined(WOLFSSL_NO_TLS12) && \ defined(HAVE_SERVER_RENEGOTIATION_INFO) && \ !defined(WOLFSSL_HARDEN_TLS_NO_SCR_CHECK) @@ -40867,7 +40883,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) #endif /* !WOLFSSL_NO_TICKET_EXPIRE && !NO_ASN_TIME */ if (!ssl->options.resuming) { - /* Expired above: DoClientHello falls back to a full handshake. */ + /* Resumption abandoned: DoClientHello runs a full handshake. */ return ret; } @@ -40882,13 +40898,25 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) } /* if old sess used EMS, but new doesn't, MUST abort */ else if (session->haveEMS && !ssl->options.haveEMS) { - WOLFSSL_MSG("Trying to resume a session with EMS without " - "using EMS"); - #ifdef WOLFSSL_EXTRA_ALERTS - SendAlert(ssl, alert_fatal, handshake_failure); - #endif - ret = EXT_MASTER_SECRET_NEEDED_E; - WOLFSSL_ERROR_VERBOSE(ret); +#ifdef HAVE_EXTENDED_MASTER + if (ssl->options.disableEMS) { + /* Local disable, not a client downgrade: decline the + * resumption and do a full handshake. */ + WOLFSSL_MSG("EMS disabled locally, declining resumption " + "of an EMS session. Do full handshake."); + ssl->options.resuming = 0; + } + else +#endif + { + WOLFSSL_MSG("Trying to resume a session with EMS without " + "using EMS"); + #ifdef WOLFSSL_EXTRA_ALERTS + SendAlert(ssl, alert_fatal, handshake_failure); + #endif + ret = EXT_MASTER_SECRET_NEEDED_E; + WOLFSSL_ERROR_VERBOSE(ret); + } } } else { diff --git a/src/keys.c b/src/keys.c index 744da03ce6a..b153c142175 100644 --- a/src/keys.c +++ b/src/keys.c @@ -3935,6 +3935,28 @@ int StoreKeys(WOLFSSL* ssl, const byte* keyData, int side) return 0; } +#if !defined(NO_OLD_TLS) || defined(HAVE_EXTENDED_MASTER) +static void CleanPreMaster(WOLFSSL* ssl) +{ + int sz = (int)(ssl->arrays->preMasterSz); + +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("CleanPreMaster preMasterSecret", + ssl->arrays->preMasterSecret, sz); +#endif + + ForceZero(ssl->arrays->preMasterSecret, sz); + +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(ssl->arrays->preMasterSecret, sz); +#endif + + XFREE(ssl->arrays->preMasterSecret, ssl->heap, DYNAMIC_TYPE_SECRET); + ssl->arrays->preMasterSecret = NULL; + ssl->arrays->preMasterSz = 0; +} +#endif /* !NO_OLD_TLS || HAVE_EXTENDED_MASTER */ + #ifndef NO_OLD_TLS int DeriveKeys(WOLFSSL* ssl) { @@ -4062,27 +4084,6 @@ int DeriveKeys(WOLFSSL* ssl) } -static void CleanPreMaster(WOLFSSL* ssl) -{ - int sz = (int)(ssl->arrays->preMasterSz); - -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Add("CleanPreMaster preMasterSecret", - ssl->arrays->preMasterSecret, sz); -#endif - - ForceZero(ssl->arrays->preMasterSecret, sz); - -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(ssl->arrays->preMasterSecret, sz); -#endif - - XFREE(ssl->arrays->preMasterSecret, ssl->heap, DYNAMIC_TYPE_SECRET); - ssl->arrays->preMasterSecret = NULL; - ssl->arrays->preMasterSz = 0; -} - - /* Create and store the master secret see page 32, 6.1 */ static int MakeSslMasterSecret(WOLFSSL* ssl) { @@ -4238,6 +4239,8 @@ int MakeMasterSecret(WOLFSSL* ssl) WOLFSSL_MSG("EMS required but not negotiated with peer"); SendAlert(ssl, alert_fatal, handshake_failure); WOLFSSL_ERROR_VERBOSE(EXT_MASTER_SECRET_NEEDED_E); + if (ssl->arrays->preMasterSecret != NULL) + CleanPreMaster(ssl); return EXT_MASTER_SECRET_NEEDED_E; } #endif diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 65d225fee95..135ab79cbda 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -1563,6 +1563,23 @@ int wolfSSL_set_SessionTicket_cb(WOLFSSL* ssl, #ifdef HAVE_EXTENDED_MASTER +/* EMS applies to (D)TLS 1.0-1.2 only; the same version gate is applied by + * InitSSL_Ctx and InitSSL_Side when arming the default advertisement. */ +static int EmsAllowedForVersion(ProtocolVersion pv) +{ + int allowed = 0; + + if (pv.major == SSLv3_MAJOR && pv.minor >= TLSv1_MINOR) + allowed = 1; +#ifdef WOLFSSL_DTLS + if (pv.major == DTLS_MAJOR) + allowed = 1; +#endif + + return allowed; +} + + /* Disable the Extended Master Secret extension on the context. * * For a client this stops the extension being advertised. For a server this @@ -1641,7 +1658,8 @@ int wolfSSL_CTX_EnableExtendedMasterSecret(WOLFSSL_CTX* ctx) /* Re-arm client advertising. A side-less (wolfSSLv23) object is * armed by InitSSL_Side instead; arming it here would make a server * echo an unsolicited extension. */ - if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) + if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END && + EmsAllowedForVersion(ctx->method->version)) ctx->haveEMS = 1; } @@ -1671,7 +1689,8 @@ int wolfSSL_EnableExtendedMasterSecret(WOLFSSL* ssl) /* Re-arm client advertising. A side-less (wolfSSLv23) object is * armed by InitSSL_Side instead; arming it here would make a server * echo an unsolicited extension. */ - if (ssl->options.side == WOLFSSL_CLIENT_END) + if (ssl->options.side == WOLFSSL_CLIENT_END && + EmsAllowedForVersion(ssl->ctx->method->version)) ssl->options.haveEMS = 1; } @@ -1705,7 +1724,8 @@ int wolfSSL_CTX_RequireExtendedMasterSecret(WOLFSSL_CTX* ctx) /* Re-arm client advertising. A side-less (wolfSSLv23) object is * armed by InitSSL_Side instead; arming it here would make a server * echo an unsolicited extension. */ - if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END) + if (ctx->method != NULL && ctx->method->side == WOLFSSL_CLIENT_END && + EmsAllowedForVersion(ctx->method->version)) ctx->haveEMS = 1; } @@ -1737,7 +1757,8 @@ int wolfSSL_RequireExtendedMasterSecret(WOLFSSL* ssl) /* Re-arm client advertising. A side-less (wolfSSLv23) object is * armed by InitSSL_Side instead; arming it here would make a server * echo an unsolicited extension. */ - if (ssl->options.side == WOLFSSL_CLIENT_END) + if (ssl->options.side == WOLFSSL_CLIENT_END && + EmsAllowedForVersion(ssl->ctx->method->version)) ssl->options.haveEMS = 1; } diff --git a/tests/api.c b/tests/api.c index 535607e2523..39bede37dd2 100644 --- a/tests/api.c +++ b/tests/api.c @@ -40456,6 +40456,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_tls_ems_resumption_downgrade), TEST_DECL(test_tls_ems_resumption_server_downgrade), TEST_DECL(test_tls_ems_server_disable), + TEST_DECL(test_tls_ems_server_disable_resumption), TEST_DECL(test_tls_ems_disable_v23), TEST_DECL(test_tls_require_ems), TEST_DECL(test_tls_require_ems_resumption), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index d3ed1ce121a..2010fec1773 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -352,6 +352,59 @@ int test_tls_ems_server_disable(void) } +/* A server that disables EMS declines resumption of a session that used EMS + * from an EMS-offering client: full handshake instead of a fatal alert. */ +int test_tls_ems_server_disable_resumption(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + WOLFSSL_SESSION *session = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Establish a session that uses EMS. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(session = wolfSSL_get1_session(ssl_c)); + ExpectTrue(session->haveEMS); + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c, session), WOLFSSL_SUCCESS); + + /* The handshake must complete as a full handshake without EMS. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->options.resuming, 0); + ExpectIntEQ(ssl_c->options.haveEMS, 0); + ExpectIntEQ(ssl_s->options.haveEMS, 0); + + wolfSSL_SESSION_free(session); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + + #if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 1f65ee57696..a2832c1a59c 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -26,6 +26,7 @@ int test_tls_ems_downgrade(void); int test_tls_ems_resumption_downgrade(void); int test_tls_ems_resumption_server_downgrade(void); int test_tls_ems_server_disable(void); +int test_tls_ems_server_disable_resumption(void); int test_tls_ems_disable_v23(void); int test_tls_require_ems(void); int test_tls_require_ems_resumption(void); From ed6bed52af14e33292e49bd903bdc155eb19b7a5 Mon Sep 17 00:00:00 2001 From: Kareem Date: Fri, 14 Aug 2026 16:40:43 -0700 Subject: [PATCH 6/7] EMS code review feedback: Disable peerAuthGood when rejecting ticket due to EMS being disabled Ensure that if EMS is disabled, sessions with EMS are not used Add client resumption test and extend server resumption test to optionally use session tickets --- src/internal.c | 2 + src/ssl_sess.c | 7 ++- tests/api.c | 1 + tests/api/test_tls_ext.c | 105 +++++++++++++++++++++++++++++++++++++-- tests/api/test_tls_ext.h | 1 + 5 files changed, 112 insertions(+), 4 deletions(-) diff --git a/src/internal.c b/src/internal.c index adac79a0857..6dd568903f8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -40905,6 +40905,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) WOLFSSL_MSG("EMS disabled locally, declining resumption " "of an EMS session. Do full handshake."); ssl->options.resuming = 0; + /* A declined ticket must not satisfy client auth. */ + ssl->options.peerAuthGood = 0; } else #endif diff --git a/src/ssl_sess.c b/src/ssl_sess.c index bf206de9248..83b802d7761 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1611,8 +1611,13 @@ int wolfSSL_SetSession(WOLFSSL* ssl, WOLFSSL_SESSION* session) /* A user EMS override takes precedence over the session's EMS state. */ if (ssl->options.requireEMS && ssl->options.side == WOLFSSL_CLIENT_END) ssl->options.haveEMS = 1; - else if (ssl->options.disableEMS) + else if (ssl->options.disableEMS) { ssl->options.haveEMS = 0; + /* An EMS session cannot be offered without the extension + * (RFC 7627 5.3): decline it and do a full handshake. */ + if (ssl->session->haveEMS) + ssl->options.resuming = 0; + } else #endif { diff --git a/tests/api.c b/tests/api.c index 39bede37dd2..d85b29172e2 100644 --- a/tests/api.c +++ b/tests/api.c @@ -40457,6 +40457,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_tls_ems_resumption_server_downgrade), TEST_DECL(test_tls_ems_server_disable), TEST_DECL(test_tls_ems_server_disable_resumption), + TEST_DECL(test_tls_ems_client_disable_resumption), TEST_DECL(test_tls_ems_disable_v23), TEST_DECL(test_tls_require_ems), TEST_DECL(test_tls_require_ems_resumption), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 2010fec1773..a61fc0f354a 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -352,9 +352,106 @@ int test_tls_ems_server_disable(void) } +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) /* A server that disables EMS declines resumption of a session that used EMS - * from an EMS-offering client: full handshake instead of a fatal alert. */ + * from an EMS-offering client: full handshake instead of a fatal alert. + * useTicket selects session-ticket resumption instead of session-ID + * resumption. */ +static int test_tls_ems_server_disable_resumption_ex(int useTicket) +{ + EXPECT_DECLS; + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + WOLFSSL_SESSION *session = NULL; + +#ifndef HAVE_SESSION_TICKET + (void)useTicket; +#endif + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* Establish a session that uses EMS. */ + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); +#ifdef HAVE_SESSION_TICKET + if (useTicket) + ExpectIntEQ(wolfSSL_UseSessionTicket(ssl_c), WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNotNull(session = wolfSSL_get1_session(ssl_c)); + ExpectTrue(session->haveEMS); +#ifdef HAVE_SESSION_TICKET + if (useTicket) + ExpectIntGT(session->ticketLen, 0); +#endif + + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(ssl_s); + ssl_s = NULL; + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_s), WOLFSSL_SUCCESS); + /* Verify the peer so the fallback's client auth cannot be satisfied by + * anything but the full handshake itself. */ + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(wolfSSL_set_session(ssl_c, session), WOLFSSL_SUCCESS); + + /* ClientHello */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* Server flight declining the resumption: the declined session or ticket + * must not count as peer auth for the full handshake. */ + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.resuming, 0); + ExpectIntEQ(ssl_s->options.peerAuthGood, 0); + + /* The handshake completes as a full handshake without EMS. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_c->options.haveEMS, 0); + ExpectIntEQ(ssl_s->options.haveEMS, 0); + + wolfSSL_SESSION_free(session); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* Server-side EMS disable declines EMS-session resumption gracefully, on + * both session-ID and session-ticket resumption. */ int test_tls_ems_server_disable_resumption(void) +{ + EXPECT_DECLS; +#if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_SESSION_CACHE) + ExpectIntEQ(test_tls_ems_server_disable_resumption_ex(0), TEST_SUCCESS); +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + ExpectIntEQ(test_tls_ems_server_disable_resumption_ex(1), TEST_SUCCESS); +#endif +#endif + return EXPECT_RESULT(); +} + + +/* A client that disables EMS must not offer an EMS-bound session: the session + * is declined at wolfSSL_set_session and a full handshake is done instead of + * an offer the server is required to reject (RFC 7627 5.3). */ +int test_tls_ems_client_disable_resumption(void) { EXPECT_DECLS; #if !defined(WOLFSSL_NO_TLS12) && defined(HAVE_EXTENDED_MASTER) && \ @@ -386,10 +483,12 @@ int test_tls_ems_server_disable_resumption(void) ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); - ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_DisableExtendedMasterSecret(ssl_c), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_set_session(ssl_c, session), WOLFSSL_SUCCESS); + /* The EMS session is declined rather than offered without EMS. */ + ExpectIntEQ(ssl_c->options.resuming, 0); - /* The handshake must complete as a full handshake without EMS. */ + /* The handshake completes as a full handshake without EMS. */ ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); ExpectIntEQ(ssl_s->options.resuming, 0); ExpectIntEQ(ssl_c->options.haveEMS, 0); diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index a2832c1a59c..018bb780f6b 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -27,6 +27,7 @@ int test_tls_ems_resumption_downgrade(void); int test_tls_ems_resumption_server_downgrade(void); int test_tls_ems_server_disable(void); int test_tls_ems_server_disable_resumption(void); +int test_tls_ems_client_disable_resumption(void); int test_tls_ems_disable_v23(void); int test_tls_require_ems(void); int test_tls_require_ems_resumption(void); From 6e9db1b2db85d89d00bd72b5121e479e8b5aa22d Mon Sep 17 00:00:00 2001 From: Kareem Date: Tue, 18 Aug 2026 15:29:20 -0700 Subject: [PATCH 7/7] Fix failing resume test --- src/ssl_sess.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 83b802d7761..2edd0ba2c0f 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -1614,8 +1614,11 @@ int wolfSSL_SetSession(WOLFSSL* ssl, WOLFSSL_SESSION* session) else if (ssl->options.disableEMS) { ssl->options.haveEMS = 0; /* An EMS session cannot be offered without the extension - * (RFC 7627 5.3): decline it and do a full handshake. */ - if (ssl->session->haveEMS) + * (RFC 7627 5.3): decline it and do a full handshake. TLS 1.3 + * sessions resume independently of EMS; their haveEMS is only the + * RFC 8446 Appendix D indicator. */ + if (ssl->session->haveEMS && + !IsAtLeastTLSv1_3(ssl->session->version)) ssl->options.resuming = 0; } else