Commit 1449f02ce6 for openssl.org
commit 1449f02ce622d92bf8195ed76892fbebf652cf5b
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date: Sun Oct 4 15:55:25 2026 +0200
dtls: honour SSL_OP_COOKIE_EXCHANGE for DTLS 1.3
Setting SSL_OP_COOKIE_EXCHANGE on a server connection only ever produced
a HelloVerifyRequest, so once the peer negotiated DTLS 1.3 the option was
silently ignored and the client was accepted without any address
validation. The HelloRetryRequest cookie code was gated purely on the
internal stateless flag, which only SSL_stateless() and the DTLS
listener set, leaving no way for an application driving its own
per-peer SSL objects with SSL_accept() to get a cookie exchange.
Treat the option as "a cookie is required" for DTLS 1.3 as well. A
ClientHello without a valid cookie is answered with a HelloRetryRequest
carrying one, and the handshake continues only once it is echoed back.
The stateless cookie callbacks are used for the application part of the
cookie, falling back to the HelloVerifyRequest callbacks so existing
servers written for DTLS 1.2 keep working unchanged. A client that
repeats its ClientHello after the HelloRetryRequest and still presents
no valid cookie now gets an illegal_parameter alert rather than tripping
an internal error.
The listener is unaffected since it sets both the flag and the option.
s_server always sets the option for DTLS, so teach the TLSProxy
handshake checker that every DTLS handshake now starts with a cookie
round trip.
Add a test covering the 1.2 and 1.3 paths, the callback fallback, the
missing-callback failure and the PSK-only resumption, and make the
dtlsecho demo server validate its peer this way.
Assisted-by: Claude:claude-fable-5-1
Reviewed-by: Matt Caswell <matt@openssl.foundation>
Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
Merge-date: Tue Oct 6 14:46:56 2026
Merged-from: https://github.com/openssl/openssl/pull/33093
diff --git a/demos/dtlsecho/main.c b/demos/dtlsecho/main.c
index 1b6b0b5b11..88d7da32b7 100644
--- a/demos/dtlsecho/main.c
+++ b/demos/dtlsecho/main.c
@@ -119,6 +119,44 @@ static SSL_CTX *create_context(flag isServer)
return ctx;
}
+/* Bind the HelloRetryRequest cookie to the peer address */
+static int peer_cookie(SSL *ssl, unsigned char *cookie, size_t *cookie_len)
+{
+ BIO_ADDR *peer = BIO_ADDR_new();
+ unsigned short port;
+ size_t addrlen;
+ int ret = 0;
+
+ if (peer == NULL)
+ return 0;
+ if (BIO_dgram_get_peer(SSL_get_rbio(ssl), peer) <= 0
+ || !BIO_ADDR_rawaddress(peer, cookie, &addrlen))
+ goto end;
+ port = BIO_ADDR_rawport(peer);
+ memcpy(cookie + addrlen, &port, sizeof(port));
+ *cookie_len = addrlen + sizeof(port);
+ ret = 1;
+end:
+ BIO_ADDR_free(peer);
+ return ret;
+}
+
+static int generate_cookie(SSL *ssl, unsigned char *cookie, size_t *cookie_len)
+{
+ return peer_cookie(ssl, cookie, cookie_len);
+}
+
+static int verify_cookie(SSL *ssl, const unsigned char *cookie,
+ size_t cookie_len)
+{
+ unsigned char expected[SSL_COOKIE_LENGTH];
+ size_t expected_len;
+
+ return peer_cookie(ssl, expected, &expected_len)
+ && expected_len == cookie_len
+ && memcmp(expected, cookie, cookie_len) == 0;
+}
+
static void configure_server_context(SSL_CTX *ctx)
{
/* Set the key and cert */
@@ -131,6 +169,11 @@ static void configure_server_context(SSL_CTX *ctx)
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
+
+ /* Validate the client address with a HelloRetryRequest cookie */
+ SSL_CTX_set_options(ctx, SSL_OP_COOKIE_EXCHANGE);
+ SSL_CTX_set_stateless_cookie_generate_cb(ctx, generate_cookie);
+ SSL_CTX_set_stateless_cookie_verify_cb(ctx, verify_cookie);
}
static void configure_client_context(SSL_CTX *ctx)
diff --git a/doc/man3/DTLSv1_listen.pod b/doc/man3/DTLSv1_listen.pod
index 4dc2d20622..1ac5b37ad1 100644
--- a/doc/man3/DTLSv1_listen.pod
+++ b/doc/man3/DTLSv1_listen.pod
@@ -115,7 +115,8 @@ DTLSv1_listen() cannot be used with DTLS 1.3. If the SSL object is configured
for DTLS 1.3 only (i.e., both minimum and maximum protocol versions are set to
DTLS 1.3), DTLSv1_listen() will fail. For DTLS 1.3 server applications, use
L<SSL_new_listener(3)> instead, which performs address validation via
-HelloRetryRequest (HRR) by default.
+HelloRetryRequest (HRR) by default, or set B<SSL_OP_COOKIE_EXCHANGE> on a
+server SSL object to have SSL_accept() request an HRR cookie itself.
=head1 RETURN VALUES
diff --git a/doc/man3/SSL_CTX_set_options.pod b/doc/man3/SSL_CTX_set_options.pod
index 888c545206..d60e614793 100644
--- a/doc/man3/SSL_CTX_set_options.pod
+++ b/doc/man3/SSL_CTX_set_options.pod
@@ -154,8 +154,10 @@ being used).
=item SSL_OP_COOKIE_EXCHANGE
-Turn on Cookie Exchange as described in RFC4347 Section 4.2.1. Only affects
-DTLS connections.
+Turn on Cookie Exchange as described in RFC4347 Section 4.2.1 for DTLS 1.0 and
+DTLS 1.2, and in RFC9147 Section 5.1 (HelloRetryRequest cookie) for DTLS 1.3.
+Only affects DTLS connections. See L<SSL_CTX_set_cookie_generate_cb(3)> for the
+callbacks that must be set.
=item SSL_OP_DISABLE_TLSEXT_CA_NAMES
diff --git a/doc/man3/SSL_CTX_set_stateless_cookie_generate_cb.pod b/doc/man3/SSL_CTX_set_stateless_cookie_generate_cb.pod
index b42577f7cf..26c1eabd78 100644
--- a/doc/man3/SSL_CTX_set_stateless_cookie_generate_cb.pod
+++ b/doc/man3/SSL_CTX_set_stateless_cookie_generate_cb.pod
@@ -6,7 +6,7 @@ SSL_CTX_set_stateless_cookie_generate_cb,
SSL_CTX_set_stateless_cookie_verify_cb,
SSL_CTX_set_cookie_generate_cb,
SSL_CTX_set_cookie_verify_cb
-- Callback functions for stateless TLS1.3 cookies
+- Callback functions for TLS1.3 and DTLS cookies
=head1 SYNOPSIS
@@ -39,36 +39,46 @@ SSL_CTX_set_cookie_verify_cb
=head1 DESCRIPTION
SSL_CTX_set_stateless_cookie_generate_cb() sets the callback used by
-L<SSL_stateless(3)> to generate the application-controlled portion of the cookie
-provided to clients in the HelloRetryRequest transmitted as a response to a
-ClientHello with a missing or invalid cookie. gen_stateless_cookie_cb() must
-write at most SSL_COOKIE_LENGTH bytes into B<cookie>, and must write the number
-of bytes written to B<cookie_len>. If a cookie cannot be generated, a zero
-return value can be used to abort the handshake.
+L<SSL_stateless(3)>, and by DTLS 1.3 server connections with
+B<SSL_OP_COOKIE_EXCHANGE> set (see L<SSL_CTX_set_options(3)>), to generate the
+application-controlled portion of the cookie provided to clients in the
+HelloRetryRequest transmitted as a response to a ClientHello with a missing or
+invalid cookie. gen_stateless_cookie_cb() must write at most SSL_COOKIE_LENGTH
+bytes into B<cookie>, and must write the number of bytes written to
+B<cookie_len>. If a cookie cannot be generated, a zero return value can be used
+to abort the handshake.
SSL_CTX_set_stateless_cookie_verify_cb() sets the callback used by
-L<SSL_stateless(3)> to determine whether the application-controlled portion of a
-ClientHello cookie is valid. The cookie data is pointed to by B<cookie> and is of
-length B<cookie_len>. A nonzero return value from verify_stateless_cookie_cb()
-communicates that the cookie is valid. The integrity of the entire cookie,
-including the application-controlled portion, is automatically verified by HMAC
-before verify_stateless_cookie_cb() is called.
-
-SSL_CTX_set_cookie_generate_cb() sets the callback used by L<DTLSv1_listen(3)>
-to generate the cookie provided to clients in the HelloVerifyRequest transmitted
-as a response to a ClientHello with a missing or invalid cookie.
+L<SSL_stateless(3)>, and by DTLS 1.3 server connections with
+B<SSL_OP_COOKIE_EXCHANGE> set, to determine whether the application-controlled
+portion of a ClientHello cookie is valid. The cookie data is pointed to by
+B<cookie> and is of length B<cookie_len>. A nonzero return value from
+verify_stateless_cookie_cb() communicates that the cookie is valid. The
+integrity of the entire cookie, including the application-controlled portion, is
+automatically verified by HMAC before verify_stateless_cookie_cb() is called.
+
+SSL_CTX_set_cookie_generate_cb() sets the callback used by L<DTLSv1_listen(3)>,
+and by DTLS 1.0 and DTLS 1.2 server connections with B<SSL_OP_COOKIE_EXCHANGE>
+set, to generate the cookie provided to clients in the HelloVerifyRequest
+transmitted as a response to a ClientHello with a missing or invalid cookie.
app_gen_cookie_cb() must write at most DTLS1_COOKIE_LENGTH bytes into
B<cookie>, and must write the number of bytes written to B<cookie_len>. If a
cookie cannot be generated, a zero return value can be used to abort the
handshake.
-SSL_CTX_set_cookie_verify_cb() sets the callback used by L<DTLSv1_listen(3)> to
-determine whether the cookie in a ClientHello is valid. The cookie data is
-pointed to by B<cookie> and is of length B<cookie_len>. A nonzero return value
-from app_verify_cookie_cb() communicates that the cookie is valid. The
+SSL_CTX_set_cookie_verify_cb() sets the callback used by L<DTLSv1_listen(3)>,
+and by DTLS 1.0 and DTLS 1.2 server connections with B<SSL_OP_COOKIE_EXCHANGE>
+set, to determine whether the cookie in a ClientHello is valid. The cookie data
+is pointed to by B<cookie> and is of length B<cookie_len>. A nonzero return
+value from app_verify_cookie_cb() communicates that the cookie is valid. The
integrity of the cookie is not verified by OpenSSL. This is an application
responsibility.
+If no stateless cookie callbacks are set, a DTLS 1.3 server connection with
+B<SSL_OP_COOKIE_EXCHANGE> set uses app_gen_cookie_cb() and
+app_verify_cookie_cb() for the application-controlled portion of the
+HelloRetryRequest cookie instead.
+
=head1 RETURN VALUES
Neither function returns a value.
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index 2ce0db3e2b..82a18721f4 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -1699,6 +1699,16 @@ static int final_supported_versions(SSL_CONNECTION *s, unsigned int context,
return 1;
}
+/* Is a HelloRetryRequest cookie required before the handshake can proceed? */
+int tls_hrr_cookie_required(const SSL_CONNECTION *s)
+{
+ if ((s->s3.flags & TLS1_FLAGS_STATELESS) != 0)
+ return 1;
+
+ return SSL_CONNECTION_IS_DTLS(s)
+ && (s->options & SSL_OP_COOKIE_EXCHANGE) != 0;
+}
+
static int final_key_share(SSL_CONNECTION *s, unsigned int context, int sent)
{
#if !(defined(OPENSSL_NO_TLS1_3) && defined(OPENSSL_NO_DTLS1_3))
@@ -1739,7 +1749,7 @@ static int final_key_share(SSL_CONNECTION *s, unsigned int context, int sent)
* we have a suitable key_share
* THEN
* IF
- * we are stateless AND we have no cookie
+ * a cookie is required AND we have no cookie
* THEN
* send a HelloRetryRequest
* ELSE
@@ -1761,7 +1771,7 @@ static int final_key_share(SSL_CONNECTION *s, unsigned int context, int sent)
* THEN
* fail
* ELSE IF
- * we are stateless AND we have no cookie
+ * a cookie is required AND we have no cookie
* THEN
* send a HelloRetryRequest
*/
@@ -1785,15 +1795,10 @@ static int final_key_share(SSL_CONNECTION *s, unsigned int context, int sent)
if (s->s3.peer_tmp != NULL) {
/* We have a suitable key_share */
- if ((s->s3.flags & TLS1_FLAGS_STATELESS) != 0
- && !s->ext.cookieok) {
- if (!ossl_assert(s->hello_retry_request == SSL_HRR_NONE)) {
- /*
- * If we are stateless then we wouldn't know about any
- * previously sent HRR - so how can this be anything other
- * than 0?
- */
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+ if (tls_hrr_cookie_required(s) && !s->ext.cookieok) {
+ if (s->hello_retry_request != SSL_HRR_NONE) {
+ /* We already sent an HRR and still have no valid cookie */
+ SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_COOKIE_MISMATCH);
return 0;
}
s->hello_retry_request = SSL_HRR_PENDING;
@@ -1821,15 +1826,10 @@ static int final_key_share(SSL_CONNECTION *s, unsigned int context, int sent)
return 0;
}
- if ((s->s3.flags & TLS1_FLAGS_STATELESS) != 0
- && !s->ext.cookieok) {
- if (!ossl_assert(s->hello_retry_request == SSL_HRR_NONE)) {
- /*
- * If we are stateless then we wouldn't know about any
- * previously sent HRR - so how can this be anything other
- * than 0?
- */
- SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+ if (tls_hrr_cookie_required(s) && !s->ext.cookieok) {
+ if (s->hello_retry_request != SSL_HRR_NONE) {
+ /* We already sent an HRR and still have no valid cookie */
+ SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_COOKIE_MISMATCH);
return 0;
}
s->hello_retry_request = SSL_HRR_PENDING;
diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c
index e3a25e2bd3..0e7a467cc5 100644
--- a/ssl/statem/extensions_srvr.c
+++ b/ssl/statem/extensions_srvr.c
@@ -1014,13 +1014,14 @@ int tls_parse_ctos_cookie(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
? (DTLS_LISTENER *)s->d1->listener
: NULL;
int have_verify_cb = (sctx->verify_stateless_cookie_cb != NULL)
- || (dl != NULL && dl->require_hrr_cookie);
+ || (dl != NULL && dl->require_hrr_cookie)
+ || (SSL_CONNECTION_IS_DTLS(s) && sctx->app_verify_cookie_cb != NULL);
#else
int have_verify_cb = (sctx->verify_stateless_cookie_cb != NULL);
#endif
/* Ignore any cookie if we're not set up to verify it */
- if (!have_verify_cb || (s->s3.flags & TLS1_FLAGS_STATELESS) == 0)
+ if (!have_verify_cb || !tls_hrr_cookie_required(s))
return 1;
if (!PACKET_as_length_prefixed_2(pkt, &cookie)) {
@@ -1141,6 +1142,12 @@ int tls_parse_ctos_cookie(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
SSL_CONNECTION_GET_USER_SSL(s),
PACKET_data(&appcookie),
PACKET_remaining(&appcookie));
+ } else if (sctx->verify_stateless_cookie_cb == NULL
+ && SSL_CONNECTION_IS_DTLS(s) && sctx->app_verify_cookie_cb != NULL) {
+ /* Fall back to the HelloVerifyRequest cookie callbacks */
+ verify_ret = sctx->app_verify_cookie_cb(SSL_CONNECTION_GET_USER_SSL(s),
+ PACKET_data(&appcookie),
+ (unsigned int)PACKET_remaining(&appcookie));
} else
#endif
if (sctx->verify_stateless_cookie_cb != NULL) {
@@ -2185,12 +2192,13 @@ EXT_RETURN tls_construct_stoc_cookie(SSL_CONNECTION *s, WPACKET *pkt,
? (DTLS_LISTENER *)s->d1->listener
: NULL;
int have_gen_cb = (sctx->gen_stateless_cookie_cb != NULL)
- || (dl != NULL && dl->require_hrr_cookie);
+ || (dl != NULL && dl->require_hrr_cookie)
+ || (SSL_CONNECTION_IS_DTLS(s) && sctx->app_gen_cookie_cb != NULL);
#else
int have_gen_cb = (sctx->gen_stateless_cookie_cb != NULL);
#endif
- if ((s->s3.flags & TLS1_FLAGS_STATELESS) == 0)
+ if (!tls_hrr_cookie_required(s))
return EXT_RETURN_NOT_SENT;
if (!have_gen_cb) {
@@ -2242,6 +2250,14 @@ EXT_RETURN tls_construct_stoc_cookie(SSL_CONNECTION *s, WPACKET *pkt,
if (dl != NULL && dl->require_hrr_cookie && sctx->gen_stateless_cookie_cb == NULL) {
gen_ret = ossl_dtls_listener_gen_stateless_cookie_cb(ussl, appcookie1,
&appcookielen);
+ } else if (sctx->gen_stateless_cookie_cb == NULL
+ && SSL_CONNECTION_IS_DTLS(s) && sctx->app_gen_cookie_cb != NULL) {
+ unsigned int applen = 0;
+
+ /* Fall back to the HelloVerifyRequest cookie callbacks */
+ gen_ret = sctx->app_gen_cookie_cb(ussl, appcookie1, &applen)
+ && applen <= DTLS1_COOKIE_LENGTH;
+ appcookielen = applen;
} else
#endif
if (sctx->gen_stateless_cookie_cb != NULL) {
diff --git a/ssl/statem/statem_local.h b/ssl/statem/statem_local.h
index 079ff703c2..744518d89f 100644
--- a/ssl/statem/statem_local.h
+++ b/ssl/statem/statem_local.h
@@ -303,6 +303,7 @@ __owur int tls_psk_do_binder(SSL_CONNECTION *s, const EVP_MD *md,
SSL_SESSION *sess, int sign, int external);
/* Server Extension processing */
+__owur int tls_hrr_cookie_required(const SSL_CONNECTION *s);
int tls_parse_ctos_renegotiate(SSL_CONNECTION *s, PACKET *pkt,
unsigned int context,
X509 *x, size_t chainidx);
diff --git a/test/dtlstest.c b/test/dtlstest.c
index 150ab4a25e..8b1612fb24 100644
--- a/test/dtlstest.c
+++ b/test/dtlstest.c
@@ -486,6 +486,131 @@ end:
return testresult;
}
+#ifndef OPENSSL_NO_DTLS1_3
+static int generate_stateless_cookie_cb(SSL *ssl, unsigned char *cookie,
+ size_t *cookie_len)
+{
+ memcpy(cookie, dummy_cookie, sizeof(dummy_cookie));
+ *cookie_len = sizeof(dummy_cookie);
+ return 1;
+}
+
+static int verify_stateless_cookie_cb(SSL *ssl, const unsigned char *cookie,
+ size_t cookie_len)
+{
+ return TEST_mem_eq(cookie, cookie_len, dummy_cookie, sizeof(dummy_cookie));
+}
+
+static int client_hello_count, client_hello_cookie_count;
+
+static int count_client_hello_cb(SSL *s, int *al, void *arg)
+{
+ const unsigned char *ext;
+ size_t extlen;
+
+ client_hello_count++;
+ if (SSL_client_hello_get0_ext(s, TLSEXT_TYPE_cookie, &ext, &extlen))
+ client_hello_cookie_count++;
+
+ return SSL_CLIENT_HELLO_SUCCESS;
+}
+
+/*
+ * Test SSL_OP_COOKIE_EXCHANGE without DTLSv1_listen():
+ * 0: DTLS 1.2 client answered with a HelloVerifyRequest
+ * 1: DTLS 1.3 client answered with a HelloRetryRequest cookie
+ * 2: as 1 with only the HelloVerifyRequest callbacks set
+ * 3: DTLS 1.3 client with no callbacks set fails
+ * 4: as 1 for a PSK-only resumption, where the HelloRetryRequest has no key_share
+ */
+static int test_cookie_exchange(int idx)
+{
+ SSL_CTX *sctx = NULL, *cctx = NULL;
+ SSL *serverssl = NULL, *clientssl = NULL;
+ SSL_SESSION *sess = NULL;
+ int testresult = 0;
+
+#ifdef OPENSSL_NO_DTLS1_2
+ if (idx == 0)
+ return TEST_skip("DTLS 1.2 is disabled");
+#endif
+
+ if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
+ DTLS_client_method(), 0, 0,
+ &sctx, &cctx, cert, privkey)))
+ return 0;
+
+ SSL_CTX_set_options(sctx, SSL_OP_COOKIE_EXCHANGE);
+ if (idx != 3) {
+ SSL_CTX_set_cookie_generate_cb(sctx, generate_cookie_cb);
+ SSL_CTX_set_cookie_verify_cb(sctx, verify_cookie_cb);
+ }
+ if (idx != 2 && idx != 3) {
+ SSL_CTX_set_stateless_cookie_generate_cb(sctx,
+ generate_stateless_cookie_cb);
+ SSL_CTX_set_stateless_cookie_verify_cb(sctx,
+ verify_stateless_cookie_cb);
+ }
+ if (idx == 4) {
+ SSL_CTX_set_options(sctx, SSL_OP_ALLOW_NO_DHE_KEX | SSL_OP_PREFER_NO_DHE_KEX);
+ SSL_CTX_set_options(cctx, SSL_OP_ALLOW_NO_DHE_KEX);
+ }
+ SSL_CTX_set_client_hello_cb(sctx, count_client_hello_cb, NULL);
+
+ if (idx == 0
+ && !TEST_true(SSL_CTX_set_max_proto_version(cctx, DTLS1_2_VERSION)))
+ goto end;
+
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+ NULL, NULL)))
+ goto end;
+
+ if (idx == 3) {
+ if (!TEST_false(create_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_SSL))
+ || !TEST_int_eq(ERR_GET_REASON(ERR_peek_error()),
+ SSL_R_NO_COOKIE_CALLBACK_SET))
+ goto end;
+ testresult = 1;
+ goto end;
+ }
+
+ if (idx == 4) {
+ if (!TEST_true(create_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_NONE))
+ || !TEST_ptr(sess = SSL_get1_session(clientssl)))
+ goto end;
+ shutdown_ssl_connection(serverssl, clientssl);
+ serverssl = clientssl = NULL;
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+ NULL, NULL))
+ || !TEST_true(SSL_set_session(clientssl, sess)))
+ goto end;
+ }
+ client_hello_count = client_hello_cookie_count = 0;
+
+ if (!TEST_true(create_ssl_connection(serverssl, clientssl,
+ SSL_ERROR_NONE))
+ || !TEST_int_eq(SSL_version(clientssl),
+ idx == 0 ? DTLS1_2_VERSION : DTLS1_3_VERSION)
+ || !TEST_int_eq(SSL_session_reused(clientssl), idx == 4)
+ /* The second ClientHello carries the cookie */
+ || !TEST_int_eq(client_hello_count, 2)
+ || !TEST_int_eq(client_hello_cookie_count, idx == 0 ? 0 : 1))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_SESSION_free(sess);
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+
+ return testresult;
+}
+#endif /* OPENSSL_NO_DTLS1_3 */
+
static int test_dtls_duplicate_records(void)
{
SSL_CTX *sctx = NULL, *cctx = NULL;
@@ -2369,6 +2494,9 @@ int setup_tests(void)
#endif
#endif
ADD_TEST(test_cookie);
+#ifndef OPENSSL_NO_DTLS1_3
+ ADD_ALL_TESTS(test_cookie_exchange, 5);
+#endif
ADD_TEST(test_dtls_duplicate_records);
ADD_TEST(test_just_finished);
#ifndef OPENSSL_NO_DTLS1_2
diff --git a/test/recipes/70-test_tls13certcomp.t b/test/recipes/70-test_tls13certcomp.t
index 241512fc79..088c66f2b8 100644
--- a/test/recipes/70-test_tls13certcomp.t
+++ b/test/recipes/70-test_tls13certcomp.t
@@ -41,6 +41,10 @@ plan skip_all => "$test_name needs compression and algorithms enabled"
@handmessages = (
[TLSProxy::Message::MT_CLIENT_HELLO,
checkhandshake::ALL_HANDSHAKES],
+ [TLSProxy::Message::MT_SERVER_HELLO,
+ checkhandshake::HRR_HANDSHAKE],
+ [TLSProxy::Message::MT_CLIENT_HELLO,
+ checkhandshake::HRR_HANDSHAKE],
[TLSProxy::Message::MT_SERVER_HELLO,
checkhandshake::ALL_HANDSHAKES],
[TLSProxy::Message::MT_ENCRYPTED_EXTENSIONS,
@@ -123,6 +127,9 @@ plan skip_all => "$test_name needs compression and algorithms enabled"
[TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_KEY_SHARE,
TLSProxy::Message::SERVER,
checkhandshake::KEY_SHARE_HRR_EXTENSION],
+ [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_COOKIE,
+ TLSProxy::Message::SERVER,
+ checkhandshake::COOKIE_EXTENSION],
[TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SERVER_NAME,
TLSProxy::Message::CLIENT,
@@ -172,6 +179,12 @@ plan skip_all => "$test_name needs compression and algorithms enabled"
[TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_COMPRESS_CERTIFICATE,
TLSProxy::Message::CLIENT,
checkhandshake::CERT_COMP_CLI_EXTENSION],
+ [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_RENEGOTIATE,
+ TLSProxy::Message::CLIENT,
+ checkhandshake::DEFAULT_EXTENSIONS],
+ [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_COOKIE,
+ TLSProxy::Message::CLIENT,
+ checkhandshake::COOKIE_EXTENSION],
[TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS,
TLSProxy::Message::SERVER,
diff --git a/test/recipes/70-test_tls13kexmodes.t b/test/recipes/70-test_tls13kexmodes.t
index b7e3bb1c46..f37f954893 100644
--- a/test/recipes/70-test_tls13kexmodes.t
+++ b/test/recipes/70-test_tls13kexmodes.t
@@ -123,6 +123,9 @@ sub setup_extensions
[TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_KEY_SHARE,
TLSProxy::Message::SERVER,
checkhandshake::KEY_SHARE_HRR_EXTENSION],
+ [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_COOKIE,
+ TLSProxy::Message::SERVER,
+ checkhandshake::COOKIE_EXTENSION],
[TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SERVER_NAME,
TLSProxy::Message::CLIENT,
@@ -170,6 +173,9 @@ sub setup_extensions
[TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_RENEGOTIATE,
TLSProxy::Message::CLIENT,
checkhandshake::DEFAULT_EXTENSIONS],
+ [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_COOKIE,
+ TLSProxy::Message::CLIENT,
+ checkhandshake::COOKIE_EXTENSION],
[TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS,
TLSProxy::Message::SERVER,
@@ -467,8 +473,10 @@ sub modify_kex_modes_filter
{
my $proxy = shift;
- # We're only interested in the initial ClientHello
- return if ($proxy->flight != 0);
+ # We're only interested in the initial ClientHello, and in DTLS also in the
+ # one resent with the HelloRetryRequest cookie
+ return if ($proxy->flight != 0
+ && !($proxy->isdtls() && $proxy->flight == 2));
foreach my $message (@{$proxy->message_list}) {
if ($message->mt == TLSProxy::Message::MT_CLIENT_HELLO) {
diff --git a/test/recipes/70-test_tls13messages.t b/test/recipes/70-test_tls13messages.t
index 486b92206f..e89e5a71d5 100644
--- a/test/recipes/70-test_tls13messages.t
+++ b/test/recipes/70-test_tls13messages.t
@@ -120,6 +120,9 @@ sub setup_extensions
[TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_KEY_SHARE,
TLSProxy::Message::SERVER,
checkhandshake::KEY_SHARE_HRR_EXTENSION],
+ [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_COOKIE,
+ TLSProxy::Message::SERVER,
+ checkhandshake::COOKIE_EXTENSION],
[TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SERVER_NAME,
TLSProxy::Message::CLIENT,
@@ -170,6 +173,9 @@ sub setup_extensions
[TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_RENEGOTIATE,
TLSProxy::Message::CLIENT,
checkhandshake::DEFAULT_EXTENSIONS],
+ [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_COOKIE,
+ TLSProxy::Message::CLIENT,
+ checkhandshake::COOKIE_EXTENSION],
[TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS,
TLSProxy::Message::SERVER,
diff --git a/util/perl/checkhandshake.pm b/util/perl/checkhandshake.pm
index 618ee99577..aca1dd5902 100644
--- a/util/perl/checkhandshake.pm
+++ b/util/perl/checkhandshake.pm
@@ -59,7 +59,8 @@ use constant {
SUPPORTED_GROUPS_SRV_EXTENSION => 0x00100000,
POST_HANDSHAKE_AUTH_CLI_EXTENSION => 0x00200000,
CERT_COMP_CLI_EXTENSION => 0x00400000,
- CERT_COMP_SRV_EXTENSION => 0x00800000
+ CERT_COMP_SRV_EXTENSION => 0x00800000,
+ COOKIE_EXTENSION => 0x01000000
};
our @handmessages = ();
@@ -68,6 +69,19 @@ our @extensions = ();
sub checkhandshake($$$$)
{
my ($proxy, $handtype, $exttype, $testname) = @_;
+ my $msgs = \@handmessages;
+
+ # s_server validates DTLS peers with a HelloRetryRequest cookie, so every
+ # DTLS handshake exchanges the HRR messages and the cookie extension
+ if ($proxy->isdtls()) {
+ $msgs = [map {
+ [$_->[0],
+ $_->[1] != 0
+ && ($_->[1] & ~(HRR_HANDSHAKE | HRR_RESUME_HANDSHAKE)) == 0
+ ? ALL_HANDSHAKES : $_->[1]]
+ } @handmessages];
+ $exttype |= COOKIE_EXTENSION;
+ }
subtest $testname => sub {
my $loop = 0;
@@ -79,11 +93,11 @@ sub checkhandshake($$$$)
my $numsh = 0;
if (TLSProxy::Proxy::is_tls13()) {
#How many ServerHellos are we expecting?
- for ($numtests = 0; $handmessages[$loop][1] != 0; $loop++) {
- next if (($handmessages[$loop][1] & $handtype) == 0);
+ for ($numtests = 0; $msgs->[$loop][1] != 0; $loop++) {
+ next if (($msgs->[$loop][1] & $handtype) == 0);
$numsh++ if ($lastmt != TLSProxy::Message::MT_SERVER_HELLO
- && $handmessages[$loop][0] == TLSProxy::Message::MT_SERVER_HELLO);
- $lastmt = $handmessages[$loop][0];
+ && $msgs->[$loop][0] == TLSProxy::Message::MT_SERVER_HELLO);
+ $lastmt = $msgs->[$loop][0];
}
}
@@ -102,8 +116,8 @@ sub checkhandshake($$$$)
#first ServerHello in the list completely
$shnum++ if ($numsh == 1 && TLSProxy::Proxy::is_tls13());
$loop = 0;
- for ($numtests = 0; $handmessages[$loop][1] != 0; $loop++) {
- next if (($handmessages[$loop][1] & $handtype) == 0);
+ for ($numtests = 0; $msgs->[$loop][1] != 0; $loop++) {
+ next if (($msgs->[$loop][1] & $handtype) == 0);
if (scalar @{$proxy->message_list} > $nextmess) {
$message = ${$proxy->message_list}[$nextmess];
$nextmess++;
@@ -163,8 +177,8 @@ sub checkhandshake($$$$)
#If we're only expecting one ServerHello out of two then we skip the
#first ServerHello in the list completely
$shnum++ if ($numsh == 1 && TLSProxy::Proxy::is_tls13());
- for ($loop = 0; $handmessages[$loop][1] != 0; $loop++) {
- next if (($handmessages[$loop][1] & $handtype) == 0);
+ for ($loop = 0; $msgs->[$loop][1] != 0; $loop++) {
+ next if (($msgs->[$loop][1] & $handtype) == 0);
if (scalar @{$proxy->message_list} > $nextmess) {
$message = ${$proxy->message_list}[$nextmess];
$nextmess++;
@@ -173,12 +187,12 @@ sub checkhandshake($$$$)
}
if (!defined $message) {
fail("Message type check. Got nothing, expected "
- .$handmessages[$loop][0]);
+ .$msgs->[$loop][0]);
next;
} else {
- ok($message->mt == $handmessages[$loop][0],
+ ok($message->mt == $msgs->[$loop][0],
"Message type check. Got ".$message->mt
- .", expected ".$handmessages[$loop][0]);
+ .", expected ".$msgs->[$loop][0]);
}
if (TLSProxy::Proxy::is_tls13()) {
$chnum++ if $message->mt() == TLSProxy::Message::MT_CLIENT_HELLO;