Commit 5578e426cc for openssl.org
commit 5578e426ccd22135301379557683130d8bc30afe
Author: Bob Beck <beck@openssl.org>
Date: Fri Aug 14 11:58:52 2026 -0700
Remove the F5 ClientHello-padding workaround
And remove the support for the padding extension (RFC7685).
With this change our client will cease sending the padding extension.
It was only ever sent, via SSL_OP_TLSEXT_PADDING, to work around a
very old bug in F5 middleboxes that choked on ClientHellos between 256
and 511 bytes.
The fix shipped long ago from F5, and the affected hardware is long out of
support, so nothing should still be running the problematic version.
SSL_OP_TLSEXT_PADDING is now a no-op retained for compatibility and
is no longer part of SSL_OP_ALL.
In case anyone wants to actually still use the padding extension
for some reason, as we no longer have a handler for this extension,
they can register a custom extension handler to do whatever they like
with it.
Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
Merge-date: Tue Aug 18 08:39:12 2026
Merged-from: https://github.com/openssl/openssl/pull/32389
diff --git a/CHANGES.md b/CHANGES.md
index a467cbe9b5..92e1632cb1 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -45,6 +45,16 @@ OpenSSL Releases
*Daniel Kubec*
+ * TLS clients no longer send the TLS padding extension (RFC 7685). It was
+ only ever sent when `SSL_OP_TLSEXT_PADDING` was set, to work around a
+ ClientHello-length bug in F5 middleboxes; the fix shipped long ago and
+ the affected hardware is long out of support, so nothing should still
+ be running the problematic version.
+ `SSL_OP_TLSEXT_PADDING` is now a no-op retained for compatibility, and
+ is no longer included in `SSL_OP_ALL`.
+
+ *Bob Beck*
+
* Repeated fields in the `basicConstraints`, `basicAttConstraints`,
and `policyConstraints` X.509v3 extension configurations are now rejected
instead of silently using the last value.
diff --git a/doc/designs/quic-design/quic-api-ssl-funcs.md b/doc/designs/quic-design/quic-api-ssl-funcs.md
index 37229c26e6..1a702b8173 100644
--- a/doc/designs/quic-design/quic-api-ssl-funcs.md
+++ b/doc/designs/quic-design/quic-api-ssl-funcs.md
@@ -876,7 +876,6 @@ The following options must be explicitly forbidden:
- `SSL_OP_ENABLE_MIDDLEBOX_COMPAT` — forbidden by QUIC RFCs
- `SSL_OP_ENABLE_KTLS` — not currently supported for QUIC
- `SSL_OP_SAFARI_ECDHE_ECDSA_BUG`
-- `SSL_OP_TLSEXT_PADDING`
- `SSL_OP_TLS_ROLLBACK_BUG`
- `SSL_OP_IGNORE_UNEXPECTED_EOF`
- `SSL_OP_ALLOW_NO_DHE_KEX`
diff --git a/doc/man3/SSL_CTX_set_options.pod b/doc/man3/SSL_CTX_set_options.pod
index 400e492f3a..67ffbb2e27 100644
--- a/doc/man3/SSL_CTX_set_options.pod
+++ b/doc/man3/SSL_CTX_set_options.pod
@@ -81,12 +81,6 @@ using other ciphers.
Don't prefer ECDHE-ECDSA ciphers when the client appears to be Safari on OS X.
OS X 10.8..10.8.3 has broken support for ECDHE-ECDSA ciphers.
-=item SSL_OP_TLSEXT_PADDING
-
-Adds a padding extension to ensure the ClientHello size is never between
-256 and 511 bytes in length. This is needed as a workaround for some
-implementations.
-
=item SSL_OP_ALL
All of the above bug workarounds.
@@ -467,6 +461,8 @@ retained for compatibility purposes:
=item SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
+=item SSL_OP_TLSEXT_PADDING
+
=back
=head1 SECURE RENEGOTIATION
@@ -610,6 +606,12 @@ As of OpenSSL 3.6, use of the B<SSL_OP_CIPHER_SERVER_PREFERENCE> constant
is discouraged and its semantics became available using the more aptly named
B<SSL_OP_SERVER_PREFERENCE> constant.
+As of OpenSSL 4.1, B<SSL_OP_TLSEXT_PADDING> has no effect. It previously
+added a padding extension (RFC 7685) to the ClientHello to work around a
+bug in certain F5 middleboxes; the fix shipped long ago and the affected
+hardware is long out of support, so nothing should still be running the
+problematic version.
+
=head1 COPYRIGHT
Copyright 2001-2026 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man7/ossl-guide-migration.pod b/doc/man7/ossl-guide-migration.pod
index 55a50a4376..fbf0ce6629 100644
--- a/doc/man7/ossl-guide-migration.pod
+++ b/doc/man7/ossl-guide-migration.pod
@@ -44,6 +44,22 @@ This function is deprecated in favour of EVP_KDF_CTX_get0_ctx(), to align
with the naming of functions that provide similar functionality for other kinds
of EVP context oobjects.
+=head3 B<SSL_OP_TLSEXT_PADDING> is now a no-op
+
+Prior to OpenSSL 4.1, when B<SSL_OP_TLSEXT_PADDING> was set, TLS clients sent
+a padding extension (RFC 7685) whenever the ClientHello would otherwise be
+between 256 and 511 bytes long. The option was part of B<SSL_OP_ALL>, so any
+application setting B<SSL_OP_ALL> sent this padding. It worked around a bug
+in F5 middleboxes; the fix shipped long ago and the affected hardware is
+long out of support, so nothing should still be running the problematic
+version. The option now has no effect and has been removed from
+B<SSL_OP_ALL>.
+
+Applications that require the padding extension can construct it themselves
+by registering a handler for B<TLSEXT_TYPE_padding> with
+L<SSL_CTX_add_custom_ext(3)>. Registering a handler for this extension type
+was previously refused.
+
=head1 OPENSSL 4.0
=head2 Main Changes from OpenSSL 3.6
diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in
index 2215cc4176..7061ebf777 100644
--- a/include/openssl/ssl.h.in
+++ b/include/openssl/ssl.h.in
@@ -338,7 +338,7 @@ typedef int (*SSL_async_callback_fn)(SSL *s, void *arg);
#define SSL_OP_LEGACY_SERVER_CONNECT SSL_OP_BIT(2)
/* Enable support for Kernel TLS */
#define SSL_OP_ENABLE_KTLS SSL_OP_BIT(3)
-#define SSL_OP_TLSEXT_PADDING SSL_OP_BIT(4)
+/* SSL_OP_BIT(4) was SSL_OP_TLSEXT_PADDING, now a no-op */
#define SSL_OP_SAFARI_ECDHE_ECDSA_BUG SSL_OP_BIT(6)
#define SSL_OP_IGNORE_UNEXPECTED_EOF SSL_OP_BIT(7)
#define SSL_OP_ALLOW_CLIENT_RENEGOTIATION SSL_OP_BIT(8)
@@ -468,7 +468,7 @@ typedef int (*SSL_async_callback_fn)(SSL *s, void *arg);
/* Various bug workarounds that should be rather harmless. */
#define SSL_OP_ALL \
(SSL_OP_CRYPTOPRO_TLSEXT_BUG | SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS \
- | SSL_OP_TLSEXT_PADDING | SSL_OP_SAFARI_ECDHE_ECDSA_BUG)
+ | SSL_OP_SAFARI_ECDHE_ECDSA_BUG)
/*
* OBSOLETE OPTIONS retained for compatibility
@@ -491,6 +491,7 @@ typedef int (*SSL_async_callback_fn)(SSL *s, void *arg);
#define SSL_OP_PKCS1_CHECK_2 0x0
#define SSL_OP_NETSCAPE_CA_DN_BUG 0x0
#define SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG 0x0
+#define SSL_OP_TLSEXT_PADDING 0x0
/*
* Allow SSL_write(..., n) to return r with 0 < r < n (i.e. report success
diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h
index ab31dde3c8..15bba2177b 100644
--- a/ssl/ssl_local.h
+++ b/ssl/ssl_local.h
@@ -711,7 +711,6 @@ typedef enum tlsext_index_en {
TLSEXT_IDX_outer_extensions,
TLSEXT_IDX_grease1,
TLSEXT_IDX_grease2,
- TLSEXT_IDX_padding,
TLSEXT_IDX_psk,
/* Dummy index - must always be the last entry */
TLSEXT_IDX_num_builtins
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index d75de303b3..561a5e1fa9 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -478,13 +478,6 @@ static const EXTENSION_DEFINITION ext_defs[] = {
0,
NULL,
NULL, NULL, NULL, tls_construct_ctos_grease2, NULL },
- { /* Must be immediately before pre_shared_key */
- TLSEXT_TYPE_padding,
- SSL_EXT_CLIENT_HELLO,
- OSSL_ECH_HANDLING_CALL_BOTH,
- NULL,
- /* We send this, but don't read it */
- NULL, NULL, NULL, tls_construct_ctos_padding, NULL },
{ /* Required by the TLSv1.3 spec to always be the last extension */
TLSEXT_TYPE_psk,
SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_SERVER_HELLO
diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c
index 556103e30a..0982a91b07 100644
--- a/ssl/statem/extensions_clnt.c
+++ b/ssl/statem/extensions_clnt.c
@@ -1344,99 +1344,6 @@ EXT_RETURN tls_construct_ctos_early_data(SSL_CONNECTION *s, WPACKET *pkt,
return EXT_RETURN_SENT;
}
-#define F5_WORKAROUND_MIN_MSG_LEN 0xff
-#define F5_WORKAROUND_MAX_MSG_LEN 0x200
-
-/*
- * PSK pre binder overhead =
- * 2 bytes for TLSEXT_TYPE_psk
- * 2 bytes for extension length
- * 2 bytes for identities list length
- * 2 bytes for identity length
- * 4 bytes for obfuscated_ticket_age
- * 2 bytes for binder list length
- * 1 byte for binder length
- * The above excludes the number of bytes for the identity itself and the
- * subsequent binder bytes
- */
-#define PSK_PRE_BINDER_OVERHEAD (2 + 2 + 2 + 2 + 4 + 2 + 1)
-
-EXT_RETURN tls_construct_ctos_padding(SSL_CONNECTION *s, WPACKET *pkt,
- unsigned int context, X509 *x,
- size_t chainidx)
-{
- unsigned char *padbytes;
- size_t hlen;
-
- if ((s->options & SSL_OP_TLSEXT_PADDING) == 0)
- return EXT_RETURN_NOT_SENT;
-#ifndef OPENSSL_NO_ECH
- ECH_SAME_EXT(s, context, pkt);
-#endif
-
- /*
- * Add padding to workaround bugs in F5 terminators. See RFC7685.
- * This code calculates the length of all extensions added so far but
- * excludes the PSK extension (because that MUST be written last). Therefore
- * this extension MUST always appear second to last.
- */
- if (!WPACKET_get_total_written(pkt, &hlen)) {
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- return EXT_RETURN_FAIL;
- }
-
- /*
- * If we're going to send a PSK then that will be written out after this
- * extension, so we need to calculate how long it is going to be.
- */
- if (s->session->ssl_version == TLS1_3_VERSION
- && s->session->ext.ticklen != 0
- && s->session->cipher != NULL) {
- const EVP_MD *md = ssl_md(SSL_CONNECTION_GET_CTX(s),
- s->session->cipher->algorithm2);
-
- if (md != NULL) {
- /*
- * Add the fixed PSK overhead, the identity length and the binder
- * length.
- */
- int md_size = EVP_MD_get_size(md);
-
- if (md_size <= 0) {
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- return EXT_RETURN_FAIL;
- }
- hlen += PSK_PRE_BINDER_OVERHEAD + s->session->ext.ticklen
- + md_size;
- }
- }
-
- if (hlen > F5_WORKAROUND_MIN_MSG_LEN && hlen < F5_WORKAROUND_MAX_MSG_LEN) {
- /* Calculate the amount of padding we need to add */
- hlen = F5_WORKAROUND_MAX_MSG_LEN - hlen;
-
- /*
- * Take off the size of extension header itself (2 bytes for type and
- * 2 bytes for length bytes), but ensure that the extension is at least
- * 1 byte long so as not to have an empty extension last (WebSphere 7.x,
- * 8.x are intolerant of that condition)
- */
- if (hlen > 4)
- hlen -= 4;
- else
- hlen = 1;
-
- if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_padding)
- || !WPACKET_sub_allocate_bytes_u16(pkt, hlen, &padbytes)) {
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
- return EXT_RETURN_FAIL;
- }
- memset(padbytes, 0, hlen);
- }
-
- return EXT_RETURN_SENT;
-}
-
/*
* Construct the pre_shared_key extension
*/
@@ -1455,12 +1362,6 @@ EXT_RETURN tls_construct_ctos_psk(SSL_CONNECTION *s, WPACKET *pkt,
s->ext.tick_identity = 0;
- /*
- * Note: At this stage of the code we only support adding a single
- * resumption PSK. If we add support for multiple PSKs then the length
- * calculations in the padding extension will need to be adjusted.
- */
-
/*
* If this is an incompatible or new session then we have nothing to resume
* so don't add this extension.
diff --git a/ssl/statem/extensions_cust.c b/ssl/statem/extensions_cust.c
index 71fb169c31..b1a369847e 100644
--- a/ssl/statem/extensions_cust.c
+++ b/ssl/statem/extensions_cust.c
@@ -626,7 +626,6 @@ int SSL_extension_supported(unsigned int ext_type)
#ifndef OPENSSL_NO_NEXTPROTONEG
case TLSEXT_TYPE_next_proto_neg:
#endif
- case TLSEXT_TYPE_padding:
case TLSEXT_TYPE_renegotiate:
case TLSEXT_TYPE_max_fragment_length:
case TLSEXT_TYPE_server_name:
diff --git a/ssl/statem/statem_local.h b/ssl/statem/statem_local.h
index ffb1d428fd..3e3f6a7251 100644
--- a/ssl/statem/statem_local.h
+++ b/ssl/statem/statem_local.h
@@ -495,9 +495,6 @@ EXT_RETURN tls_construct_ctos_grease1(SSL_CONNECTION *s, WPACKET *pkt,
EXT_RETURN tls_construct_ctos_grease2(SSL_CONNECTION *s, WPACKET *pkt,
unsigned int context, X509 *x,
size_t chainidx);
-EXT_RETURN tls_construct_ctos_padding(SSL_CONNECTION *s, WPACKET *pkt,
- unsigned int context, X509 *x,
- size_t chainidx);
EXT_RETURN tls_construct_ctos_psk(SSL_CONNECTION *s, WPACKET *pkt,
unsigned int context,
X509 *x, size_t chainidx);
diff --git a/test/clienthellotest.c b/test/clienthellotest.c
index 068bc8e9f1..c2a052ae02 100644
--- a/test/clienthellotest.c
+++ b/test/clienthellotest.c
@@ -23,30 +23,13 @@
#define CLIENT_VERSION_LEN 2
-#define TOTAL_NUM_TESTS 3
+#define TOTAL_NUM_TESTS 1
/*
* Test that explicitly setting ticket data results in it appearing in the
* ClientHello for a negotiated SSL/TLS version
*/
#define TEST_SET_SESSION_TICK_DATA_VER_NEG 0
-/* Enable padding and make sure ClientHello is long enough to require it */
-#define TEST_ADD_PADDING 1
-/* Enable padding and make sure ClientHello is short enough to not need it */
-#define TEST_PADDING_NOT_NEEDED 2
-
-#define F5_WORKAROUND_MIN_MSG_LEN 0x7f
-#define F5_WORKAROUND_MAX_MSG_LEN 0x200
-
-/* Dummy ALPN protocols used to pad out the size of the ClientHello */
-/* ASCII 'O' = 79 = 0x4F = EBCDIC '|'*/
-#ifdef CHARSET_EBCDIC
-static const char alpn_prots[] = "|1234567890123456789012345678901234567890123456789012345678901234567890123456789"
- "|1234567890123456789012345678901234567890123456789012345678901234567890123456789";
-#else
-static const char alpn_prots[] = "O1234567890123456789012345678901234567890123456789012345678901234567890123456789"
- "O1234567890123456789012345678901234567890123456789012345678901234567890123456789";
-#endif
static int test_client_hello(int currtest)
{
@@ -60,7 +43,6 @@ static int test_client_hello(int currtest)
char *dummytick = "Hello World!";
unsigned int type = 0;
int testresult = 0;
- size_t msglen;
BIO *sessbio = NULL;
SSL_SESSION *sess = NULL;
@@ -91,37 +73,6 @@ static int test_client_hello(int currtest)
#endif
break;
- case TEST_ADD_PADDING:
- case TEST_PADDING_NOT_NEEDED:
- SSL_CTX_set_options(ctx, SSL_OP_TLSEXT_PADDING);
- /* Make sure we get a consistent size across TLS versions */
- SSL_CTX_clear_options(ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
- /* Avoid large keyshares */
- if (!TEST_true(SSL_CTX_set1_groups_list(ctx,
- "?X25519:?secp256r1:?ffdhe2048:?ffdhe3072")))
- goto end;
- /*
- * Add some dummy ALPN protocols so that the ClientHello is at least
- * F5_WORKAROUND_MIN_MSG_LEN bytes long - meaning padding will be
- * needed.
- */
- if (currtest == TEST_ADD_PADDING) {
- if (!TEST_false(SSL_CTX_set_alpn_protos(ctx,
- (unsigned char *)alpn_prots,
- sizeof(alpn_prots) - 1)))
- goto end;
- /*
- * Otherwise we need to make sure we have a small enough message to
- * not need padding.
- */
- } else if (!TEST_true(SSL_CTX_set_cipher_list(ctx,
- "AES128-SHA"))
- || !TEST_true(SSL_CTX_set_ciphersuites(ctx,
- "TLS_AES_128_GCM_SHA256"))) {
- goto end;
- }
- break;
-
default:
goto end;
}
@@ -158,8 +109,6 @@ static int test_client_hello(int currtest)
|| !PACKET_forward(&pkt, SSL3_RT_HEADER_LENGTH))
goto end;
- msglen = PACKET_remaining(&pkt);
-
/* Skip the handshake message header */
if (!TEST_true(PACKET_forward(&pkt, SSL3_HM_HEADER_LENGTH))
/* Skip client version and random */
@@ -191,17 +140,8 @@ static int test_client_hello(int currtest)
goto end;
}
}
- if (type == TLSEXT_TYPE_padding) {
- if (!TEST_false(currtest == TEST_PADDING_NOT_NEEDED))
- goto end;
- else if (TEST_true(currtest == TEST_ADD_PADDING))
- testresult = TEST_true(msglen == F5_WORKAROUND_MAX_MSG_LEN);
- }
}
- if (currtest == TEST_PADDING_NOT_NEEDED)
- testresult = 1;
-
end:
SSL_free(con);
SSL_CTX_free(ctx);
diff --git a/test/ext_internal_test.c b/test/ext_internal_test.c
index c88f026dee..993f962b2a 100644
--- a/test/ext_internal_test.c
+++ b/test/ext_internal_test.c
@@ -81,7 +81,6 @@ static EXT_LIST ext_list[] = {
#endif
EXT_ENTRY(grease1),
EXT_ENTRY(grease2),
- EXT_ENTRY(padding),
EXT_ENTRY(psk),
EXT_END(num_builtins)
};
diff --git a/test/recipes/70-test_clienthello.t b/test/recipes/70-test_clienthello.t
index 662b31dc4a..ffec5f10bc 100644
--- a/test/recipes/70-test_clienthello.t
+++ b/test/recipes/70-test_clienthello.t
@@ -15,10 +15,6 @@ setup("test_clienthello");
plan skip_all => "No TLS/SSL protocols are supported by this OpenSSL build"
if alldisabled(available_protocols("tls"));
-#No EC with TLSv1.3 confuses the padding calculations in this test
-plan skip_all => "No EC with TLSv1.3 is not supported by this test"
- if disabled("ec") && !disabled("tls1_3");
-
plan tests => 1;
ok(run(test(["clienthellotest"])),