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;
     }