Commit 7428975d3f for openssl.org
commit 7428975d3f5a0de5c17f8675c7cab0b536b56e35
Author: sftcd <stephen.farrell@cs.tcd.ie>
Date: Wed Feb 25 14:24:00 2026 +0000
add check before releaseing retry-configs
Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Matt Caswell <matt@openssl.org>
MergeDate: Mon Mar 2 09:55:44 2026
(Merged from https://github.com/openssl/openssl/pull/30175)
diff --git a/doc/man3/SSL_set1_echstore.pod b/doc/man3/SSL_set1_echstore.pod
index 0181b6ea8b..7a5c1b25a2 100644
--- a/doc/man3/SSL_set1_echstore.pod
+++ b/doc/man3/SSL_set1_echstore.pod
@@ -207,6 +207,16 @@ and can then access the ECH retry config values via SSL_ech_get1_retry_config()
where the I<ec> value returned will contain a binary-encoded ECHConfigList
of length I<eclen>.
+If a client makes a real attempt at ECH that fails then the server can also
+return retry config values, and those can be used if they were authenticated
+under the outer SNI used for the session. In this case the TLS session fails
+returning the error B<SSL_R_ECH_REQUIRED>, and the ECH status of
+B<SSL_ECH_STATUS_FAILED_ECH> indicates that retry config values can be
+accessed using SSL_ech_get1_retry_config(). The application can then choose to
+use those in a subsequent attempt to establish a TLS session with the server.
+If the TLS session fails for some other reason (e.g. a bad server Finished
+message), then the retry config values will not be returned to the application.
+
"GREASEing" (defined in RFC8701) is a mechanism intended to discourage protocol
ossification that can be used for ECH.
diff --git a/ssl/ech/ech_local.h b/ssl/ech/ech_local.h
index 0a49f4c77e..b944b5c67e 100644
--- a/ssl/ech/ech_local.h
+++ b/ssl/ech/ech_local.h
@@ -235,6 +235,13 @@ typedef struct ossl_ech_conn_st {
* calculation based on the SH (or 2nd SH in case of HRR)
*/
int success;
+ /*
+ * we set this when we've gotten to the end of the handshake and
+ * the only thing that went wrong was ECH - in that case we're
+ * ok to provide the retry-configs to the client, otherwise better
+ * not.
+ */
+ int retry_configs_ok;
int grease; /* 1 if we're GREASEing, 0 otherwise */
char *grease_suite; /* HPKE suite string for GREASEing */
unsigned char *sent; /* GREASEy ECH value sent, in case needed for re-tx */
diff --git a/ssl/ech/ech_ssl_apis.c b/ssl/ech/ech_ssl_apis.c
index d7b7b8e5e7..83612ca8e9 100644
--- a/ssl/ech/ech_ssl_apis.c
+++ b/ssl/ech/ech_ssl_apis.c
@@ -312,6 +312,17 @@ int SSL_ech_get1_retry_config(SSL *ssl, unsigned char **ec, size_t *eclen)
*eclen = 0;
return 1;
}
+ /*
+ * before returning retry-configs check we're in a good
+ * state - either the session has worked, or else it
+ * failed but ECH was the only failure (we only set the
+ * retry_configs_ok flag when all else worked and we're
+ * about to send the ECH required alert)
+ */
+ if (SSL_is_init_finished(ssl) != 1 && s->ext.ech.retry_configs_ok != 1) {
+ ERR_raise(ERR_LIB_SSL, ERR_R_OSSL_STORE_LIB);
+ goto err;
+ }
/*
* To not hand rubbish to application, we'll decode the value we have
* so only syntactically good things are passed up. We won't insist
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index 18a08ca399..dbfc00e423 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -3395,6 +3395,7 @@ int tls_process_initial_server_flight(SSL_CONNECTION *s)
&& s->ext.ech.attempted == 1
&& s->ext.ech.success != 1
&& s->ext.ech.grease != OSSL_ECH_IS_GREASE) {
+ s->ext.ech.retry_configs_ok = 1; /* note those are good */
SSLfatal(s, SSL_AD_ECH_REQUIRED, SSL_R_ECH_REQUIRED);
return 0;
}