[openssl-commits] [openssl] master update

Matt Caswell matt at openssl.org
Wed Jan 24 18:11:09 UTC 2018


The branch master has been updated
       via  97ea1e7f42eea97b117af08b3c1d29f6443850ab (commit)
       via  d0debc0a1cea232d3131b941dba435dc001f3cd3 (commit)
       via  c2f9648d5dd78a856d01533281dae7ba5fa53d52 (commit)
       via  e93597193dae5bc2e0dcfbf6d812f71d6961576f (commit)
       via  1e6122774a75ee4a59b5de129eac122222905b52 (commit)
       via  dd77962e09c32ebe35bcea9f6e79e89187085abf (commit)
       via  d6bb50a5f9201aab638ddf9131a6754cca0ef842 (commit)
       via  808d1601612626b09eb4e8a098cd1edc5d105cfa (commit)
       via  c7b8ff2502d8f3ee3eef20bdb4d25811f329e9ae (commit)
       via  c36001c3a89691e21dc4940425fc880c89c57ffc (commit)
       via  042c57539bfe7bbd642cdf6410c56327e91ad908 (commit)
       via  10ee72461254643bd152a7f3f6112edb6f517d4b (commit)
       via  43054d3d734a8fa8a3d2da20c206a47d4060b7bd (commit)
      from  14262ca950b8a75014e5495a2b93e1baa62d33a9 (commit)


- Log -----------------------------------------------------------------
commit 97ea1e7f42eea97b117af08b3c1d29f6443850ab
Author: Matt Caswell <matt at openssl.org>
Date:   Tue Jan 23 12:23:23 2018 +0000

    Updates following review of SSL_stateless() code
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit d0debc0a1cea232d3131b941dba435dc001f3cd3
Author: Matt Caswell <matt at openssl.org>
Date:   Wed Jan 17 14:29:22 2018 +0000

    Add a timestamp to the cookie
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit c2f9648d5dd78a856d01533281dae7ba5fa53d52
Author: Matt Caswell <matt at openssl.org>
Date:   Fri Dec 29 17:37:04 2017 +0000

    Add the ability for s_server to operate statelessly
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit e93597193dae5bc2e0dcfbf6d812f71d6961576f
Author: Matt Caswell <matt at openssl.org>
Date:   Fri Dec 29 17:36:28 2017 +0000

    Don't send unexpected_message if we receive CCS while stateless
    
    Probably this is the CCS between the first and second ClientHellos. It
    should be ignored.
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit 1e6122774a75ee4a59b5de129eac122222905b52
Author: Matt Caswell <matt at openssl.org>
Date:   Thu Sep 28 15:05:58 2017 +0100

    Add documentation for SSL_stateless()
    
    Fixes #4283
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit dd77962e09c32ebe35bcea9f6e79e89187085abf
Author: Matt Caswell <matt at openssl.org>
Date:   Thu Sep 28 13:25:23 2017 +0100

    Fix the cookie/key_share extensions for use with SSL_stateless()
    
    Fixes some bugs identified during testing.
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit d6bb50a5f9201aab638ddf9131a6754cca0ef842
Author: Matt Caswell <matt at openssl.org>
Date:   Thu Sep 28 13:24:58 2017 +0100

    Fix the SSL_stateless() return code
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit 808d1601612626b09eb4e8a098cd1edc5d105cfa
Author: Matt Caswell <matt at openssl.org>
Date:   Thu Sep 28 13:23:49 2017 +0100

    Fix interaction between SSL_stateless() and SSL_clear()
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit c7b8ff2502d8f3ee3eef20bdb4d25811f329e9ae
Author: Matt Caswell <matt at openssl.org>
Date:   Wed Sep 27 09:46:38 2017 +0100

    Add some tests for the SSL_stateless() capability
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit c36001c3a89691e21dc4940425fc880c89c57ffc
Author: Matt Caswell <matt at openssl.org>
Date:   Wed Sep 13 14:50:49 2017 +0100

    Fix logic around when to send an HRR based on cookies
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit 042c57539bfe7bbd642cdf6410c56327e91ad908
Author: Matt Caswell <matt at openssl.org>
Date:   Wed Sep 13 13:48:48 2017 +0100

    Add the SSL_stateless() function
    
    This enables sending and receiving of the TLSv1.3 cookie on the server side
    as appropriate.
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit 10ee72461254643bd152a7f3f6112edb6f517d4b
Author: Matt Caswell <matt at openssl.org>
Date:   Tue Sep 12 16:19:09 2017 +0100

    Enable the cookie callbacks to work even in TLS in the apps
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

commit 43054d3d734a8fa8a3d2da20c206a47d4060b7bd
Author: Matt Caswell <matt at openssl.org>
Date:   Mon Sep 11 15:43:56 2017 +0100

    Add support for sending TLSv1.3 cookies
    
    This just adds the various extension functions. More changes will be
    required to actually use them.
    
    Reviewed-by: Ben Kaduk <kaduk at mit.edu>
    (Merged from https://github.com/openssl/openssl/pull/4435)

-----------------------------------------------------------------------

Summary of changes:
 apps/apps.h                  |   2 +
 apps/s_cb.c                  |  29 +--
 apps/s_server.c              | 120 ++++++------
 apps/s_socket.c              |  14 +-
 crypto/err/openssl.txt       |   2 +
 doc/man3/DTLSv1_listen.pod   | 116 +++++++-----
 include/openssl/dtls1.h      |   4 +
 include/openssl/ssl.h        |   4 +
 include/openssl/ssl3.h       |   2 +
 include/openssl/sslerr.h     |   2 +
 ssl/ssl_err.c                |   4 +
 ssl/ssl_lib.c                |  25 +++
 ssl/ssl_locl.h               |   5 +
 ssl/statem/extensions.c      | 187 ++++++++++++-------
 ssl/statem/extensions_srvr.c | 425 +++++++++++++++++++++++++++++++++++++++++--
 ssl/statem/statem.c          |   6 +-
 ssl/statem/statem_clnt.c     |   2 +-
 ssl/statem/statem_lib.c      |  47 ++++-
 ssl/statem/statem_locl.h     |   8 +-
 ssl/statem/statem_srvr.c     |   5 +-
 test/sslapitest.c            |  90 +++++++++
 util/libssl.num              |   1 +
 22 files changed, 898 insertions(+), 202 deletions(-)

diff --git a/apps/apps.h b/apps/apps.h
index 6d0d701..601797b 100644
--- a/apps/apps.h
+++ b/apps/apps.h
@@ -46,6 +46,8 @@ extern BIO *bio_out;
 extern BIO *bio_err;
 extern const unsigned char tls13_aes128gcmsha256_id[];
 extern const unsigned char tls13_aes256gcmsha384_id[];
+extern BIO_ADDR *ourpeer;
+
 BIO *dup_bio_in(int format);
 BIO *dup_bio_out(int format);
 BIO *dup_bio_err(int format);
diff --git a/apps/s_cb.c b/apps/s_cb.c
index c7c9ecb..575fb04 100644
--- a/apps/s_cb.c
+++ b/apps/s_cb.c
@@ -686,9 +686,9 @@ int generate_cookie_callback(SSL *ssl, unsigned char *cookie,
                              unsigned int *cookie_len)
 {
     unsigned char *buffer;
-    size_t length;
+    size_t length = 0;
     unsigned short port;
-    BIO_ADDR *peer = NULL;
+    BIO_ADDR *lpeer = NULL, *peer = NULL;
 
     /* Initialize a random secret */
     if (!cookie_initialized) {
@@ -699,17 +699,24 @@ int generate_cookie_callback(SSL *ssl, unsigned char *cookie,
         cookie_initialized = 1;
     }
 
-    peer = BIO_ADDR_new();
-    if (peer == NULL) {
-        BIO_printf(bio_err, "memory full\n");
-        return 0;
-    }
+    if (SSL_is_dtls(ssl)) {
+        lpeer = peer = BIO_ADDR_new();
+        if (peer == NULL) {
+            BIO_printf(bio_err, "memory full\n");
+            return 0;
+        }
 
-    /* Read peer information */
-    (void)BIO_dgram_get_peer(SSL_get_rbio(ssl), peer);
+        /* Read peer information */
+        (void)BIO_dgram_get_peer(SSL_get_rbio(ssl), peer);
+    } else {
+        peer = ourpeer;
+    }
 
     /* Create buffer with peer's address and port */
-    BIO_ADDR_rawaddress(peer, NULL, &length);
+    if (!BIO_ADDR_rawaddress(peer, NULL, &length)) {
+        BIO_printf(bio_err, "Failed getting peer address\n");
+        return 0;
+    }
     OPENSSL_assert(length != 0);
     port = BIO_ADDR_rawport(peer);
     length += sizeof(port);
@@ -723,7 +730,7 @@ int generate_cookie_callback(SSL *ssl, unsigned char *cookie,
          buffer, length, cookie, cookie_len);
 
     OPENSSL_free(buffer);
-    BIO_ADDR_free(peer);
+    BIO_ADDR_free(lpeer);
 
     return 1;
 }
diff --git a/apps/s_server.c b/apps/s_server.c
index f61ddd2..bbe44f7 100644
--- a/apps/s_server.c
+++ b/apps/s_server.c
@@ -114,6 +114,7 @@ static long socket_mtu;
  * code.
  */
 static int dtlslisten = 0;
+static int stateless = 0;
 
 static int early_data = 0;
 static SSL_SESSION *psksess = NULL;
@@ -751,7 +752,7 @@ typedef enum OPTION_choice {
     OPT_UPPER_WWW, OPT_HTTP, OPT_ASYNC, OPT_SSL_CONFIG,
     OPT_MAX_SEND_FRAG, OPT_SPLIT_SEND_FRAG, OPT_MAX_PIPELINES, OPT_READ_BUF,
     OPT_SSL3, OPT_TLS1_3, OPT_TLS1_2, OPT_TLS1_1, OPT_TLS1, OPT_DTLS, OPT_DTLS1,
-    OPT_DTLS1_2, OPT_SCTP, OPT_TIMEOUT, OPT_MTU, OPT_LISTEN,
+    OPT_DTLS1_2, OPT_SCTP, OPT_TIMEOUT, OPT_MTU, OPT_LISTEN, OPT_STATELESS,
     OPT_ID_PREFIX, OPT_SERVERNAME, OPT_SERVERNAME_FATAL,
     OPT_CERT2, OPT_KEY2, OPT_NEXTPROTONEG, OPT_ALPN,
     OPT_SRTP_PROFILES, OPT_KEYMATEXPORT, OPT_KEYMATEXPORTLEN,
@@ -933,6 +934,7 @@ const OPTIONS s_server_options[] = {
     {"listen", OPT_LISTEN, '-',
      "Listen for a DTLS ClientHello with a cookie and then connect"},
 #endif
+    {"stateless", OPT_STATELESS, '-', "Require TLSv1.3 cookies"},
 #ifndef OPENSSL_NO_DTLS1
     {"dtls1", OPT_DTLS1, '-', "Just talk DTLSv1"},
 #endif
@@ -1496,6 +1498,9 @@ int s_server_main(int argc, char *argv[])
             dtlslisten = 1;
 #endif
             break;
+        case OPT_STATELESS:
+            stateless = 1;
+            break;
         case OPT_ID_PREFIX:
             session_id_prefix = opt_arg();
             break;
@@ -1588,6 +1593,11 @@ int s_server_main(int argc, char *argv[])
     }
 #endif
 
+    if (stateless && socket_type != SOCK_STREAM) {
+        BIO_printf(bio_err, "Can only use --stateless with TLS\n");
+        goto end;
+    }
+
 #ifdef AF_UNIX
     if (socket_family == AF_UNIX && socket_type != SOCK_STREAM) {
         BIO_printf(bio_err,
@@ -2691,81 +2701,87 @@ static int init_ssl_connection(SSL *con)
     long verify_err;
     int retry = 0;
 
-#ifndef OPENSSL_NO_DTLS
-    if (dtlslisten) {
+    if (dtlslisten || stateless) {
         BIO_ADDR *client = NULL;
 
-        if ((client = BIO_ADDR_new()) == NULL) {
-            BIO_printf(bio_err, "ERROR - memory\n");
-            return 0;
+        if (dtlslisten) {
+            if ((client = BIO_ADDR_new()) == NULL) {
+                BIO_printf(bio_err, "ERROR - memory\n");
+                return 0;
+            }
+            i = DTLSv1_listen(con, client);
+        } else {
+            i = SSL_stateless(con);
         }
-        i = DTLSv1_listen(con, client);
         if (i > 0) {
             BIO *wbio;
             int fd = -1;
 
-            wbio = SSL_get_wbio(con);
-            if (wbio) {
-                BIO_get_fd(wbio, &fd);
-            }
+            if (dtlslisten) {
+                wbio = SSL_get_wbio(con);
+                if (wbio) {
+                    BIO_get_fd(wbio, &fd);
+                }
 
-            if (!wbio || BIO_connect(fd, client, 0) == 0) {
-                BIO_printf(bio_err, "ERROR - unable to connect\n");
+                if (!wbio || BIO_connect(fd, client, 0) == 0) {
+                    BIO_printf(bio_err, "ERROR - unable to connect\n");
+                    BIO_ADDR_free(client);
+                    return 0;
+                }
                 BIO_ADDR_free(client);
-                return 0;
+                dtlslisten = 0;
+            } else {
+                stateless = 0;
             }
-            BIO_ADDR_free(client);
-            dtlslisten = 0;
             i = SSL_accept(con);
         } else {
             BIO_ADDR_free(client);
         }
-    } else
-#endif
-
-    do {
-        i = SSL_accept(con);
+    } else {
+        do {
+            i = SSL_accept(con);
 
-        if (i <= 0)
-            retry = is_retryable(con, i);
+            if (i <= 0)
+                retry = is_retryable(con, i);
 #ifdef CERT_CB_TEST_RETRY
-        {
+            {
+                while (i <= 0
+                        && SSL_get_error(con, i) == SSL_ERROR_WANT_X509_LOOKUP
+                        && SSL_get_state(con) == TLS_ST_SR_CLNT_HELLO) {
+                    BIO_printf(bio_err,
+                               "LOOKUP from certificate callback during accept\n");
+                    i = SSL_accept(con);
+                    if (i <= 0)
+                        retry = is_retryable(con, i);
+                }
+            }
+#endif
+
+#ifndef OPENSSL_NO_SRP
             while (i <= 0
-                    && SSL_get_error(con, i) == SSL_ERROR_WANT_X509_LOOKUP
-                    && SSL_get_state(con) == TLS_ST_SR_CLNT_HELLO) {
-                BIO_printf(bio_err,
-                           "LOOKUP from certificate callback during accept\n");
+                   && SSL_get_error(con, i) == SSL_ERROR_WANT_X509_LOOKUP) {
+                BIO_printf(bio_s_out, "LOOKUP during accept %s\n",
+                           srp_callback_parm.login);
+                SRP_user_pwd_free(srp_callback_parm.user);
+                srp_callback_parm.user =
+                    SRP_VBASE_get1_by_user(srp_callback_parm.vb,
+                                           srp_callback_parm.login);
+                if (srp_callback_parm.user)
+                    BIO_printf(bio_s_out, "LOOKUP done %s\n",
+                               srp_callback_parm.user->info);
+                else
+                    BIO_printf(bio_s_out, "LOOKUP not successful\n");
                 i = SSL_accept(con);
                 if (i <= 0)
                     retry = is_retryable(con, i);
             }
-        }
-#endif
-
-#ifndef OPENSSL_NO_SRP
-        while (i <= 0
-               && SSL_get_error(con, i) == SSL_ERROR_WANT_X509_LOOKUP) {
-            BIO_printf(bio_s_out, "LOOKUP during accept %s\n",
-                       srp_callback_parm.login);
-            SRP_user_pwd_free(srp_callback_parm.user);
-            srp_callback_parm.user =
-                SRP_VBASE_get1_by_user(srp_callback_parm.vb,
-                                       srp_callback_parm.login);
-            if (srp_callback_parm.user)
-                BIO_printf(bio_s_out, "LOOKUP done %s\n",
-                           srp_callback_parm.user->info);
-            else
-                BIO_printf(bio_s_out, "LOOKUP not successful\n");
-            i = SSL_accept(con);
-            if (i <= 0)
-                retry = is_retryable(con, i);
-        }
 #endif
-    } while (i < 0 && SSL_waiting_for_async(con));
+        } while (i < 0 && SSL_waiting_for_async(con));
+    }
 
     if (i <= 0) {
-        if ((dtlslisten && i == 0)
-                || (!dtlslisten && retry)) {
+        if (((dtlslisten || stateless) && i == 0)
+                || (!dtlslisten && !stateless && retry)) {
             BIO_printf(bio_s_out, "DELAY\n");
             return 1;
         }
diff --git a/apps/s_socket.c b/apps/s_socket.c
index 74cf8d2..a9e46f9 100644
--- a/apps/s_socket.c
+++ b/apps/s_socket.c
@@ -35,6 +35,9 @@ typedef unsigned int u_int;
 # include <openssl/bio.h>
 # include <openssl/err.h>
 
+/* Keep track of our peer's address for the cookie callback */
+BIO_ADDR *ourpeer = NULL;
+
 /*
  * init_client - helper routine to set up socket communication
  * @sock: pointer to storage of resulting socket.
@@ -212,8 +215,15 @@ int do_server(int *accept_sock, const char *host, const char *port,
         *accept_sock = asock;
     for (;;) {
         if (type == SOCK_STREAM) {
+            BIO_ADDR_free(ourpeer);
+            ourpeer = BIO_ADDR_new();
+            if (ourpeer == NULL) {
+                BIO_closesocket(asock);
+                ERR_print_errors(bio_err);
+                goto end;
+            }
             do {
-                sock = BIO_accept_ex(asock, NULL, 0);
+                sock = BIO_accept_ex(asock, ourpeer, 0);
             } while (sock < 0 && BIO_sock_should_retry(sock));
             if (sock < 0) {
                 ERR_print_errors(bio_err);
@@ -264,6 +274,8 @@ int do_server(int *accept_sock, const char *host, const char *port,
     if (family == AF_UNIX)
         unlink(host);
 # endif
+    BIO_ADDR_free(ourpeer);
+    ourpeer = NULL;
     return ret;
 }
 
diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index e98bd95..0ed1b09 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -1275,6 +1275,7 @@ SSL_F_TLS_CONSTRUCT_SERVER_HELLO:491:tls_construct_server_hello
 SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE:492:tls_construct_server_key_exchange
 SSL_F_TLS_CONSTRUCT_STOC_ALPN:451:tls_construct_stoc_alpn
 SSL_F_TLS_CONSTRUCT_STOC_CERTIFICATE:374:*
+SSL_F_TLS_CONSTRUCT_STOC_COOKIE:613:tls_construct_stoc_cookie
 SSL_F_TLS_CONSTRUCT_STOC_CRYPTOPRO_BUG:452:tls_construct_stoc_cryptopro_bug
 SSL_F_TLS_CONSTRUCT_STOC_DONE:375:*
 SSL_F_TLS_CONSTRUCT_STOC_EARLY_DATA:531:tls_construct_stoc_early_data
@@ -1307,6 +1308,7 @@ SSL_F_TLS_HANDLE_STATUS_REQUEST:563:tls_handle_status_request
 SSL_F_TLS_PARSE_CERTIFICATE_AUTHORITIES:566:tls_parse_certificate_authorities
 SSL_F_TLS_PARSE_CLIENTHELLO_TLSEXT:449:*
 SSL_F_TLS_PARSE_CTOS_ALPN:567:tls_parse_ctos_alpn
+SSL_F_TLS_PARSE_CTOS_COOKIE:614:tls_parse_ctos_cookie
 SSL_F_TLS_PARSE_CTOS_EARLY_DATA:568:tls_parse_ctos_early_data
 SSL_F_TLS_PARSE_CTOS_EC_PT_FORMATS:569:tls_parse_ctos_ec_pt_formats
 SSL_F_TLS_PARSE_CTOS_EMS:570:tls_parse_ctos_ems
diff --git a/doc/man3/DTLSv1_listen.pod b/doc/man3/DTLSv1_listen.pod
index 3e0e251..062215e 100644
--- a/doc/man3/DTLSv1_listen.pod
+++ b/doc/man3/DTLSv1_listen.pod
@@ -2,55 +2,69 @@
 
 =head1 NAME
 
-DTLSv1_listen - listen for incoming DTLS connections
+SSL_stateless,
+DTLSv1_listen
+- Statelessly listen for incoming connections
 
 =head1 SYNOPSIS
 
  #include <openssl/ssl.h>
 
+ int SSL_stateless(SSL *s);
  int DTLSv1_listen(SSL *ssl, BIO_ADDR *peer);
 
 =head1 DESCRIPTION
 
-DTLSv1_listen() listens for new incoming DTLS connections. If a ClientHello is
-received that does not contain a cookie, then DTLSv1_listen() responds with a
-HelloVerifyRequest. If a ClientHello is received with a cookie that is verified
-then control is returned to user code to enable the handshake to be completed
-(for example by using SSL_accept()).
+SSL_stateless() statelessly listens for new incoming TLSv1.3 connections.
+DTLSv1_listen() statelessly listens for new incoming DTLS connections. If a
+ClientHello is received that does not contain a cookie, then they respond with a
+request for a new ClientHello that does contain a cookie. If a ClientHello is
+received with a cookie that is verified then the function returns in order to
+enable the handshake to be completed (for example by using SSL_accept()).
 
 =head1 NOTES
 
-Datagram based protocols can be susceptible to Denial of Service attacks. A
-DTLS attacker could, for example, submit a series of handshake initiation
-requests that cause the server to allocate state (and possibly perform
-cryptographic operations) thus consuming server resources. The attacker could
-also (with UDP) quite simply forge the source IP address in such an attack.
-
-As a counter measure to that DTLS includes a stateless cookie mechanism. The
-idea is that when a client attempts to connect to a server it sends a
-ClientHello message. The server responds with a HelloVerifyRequest which
-contains a unique cookie. The client then resends the ClientHello, but this time
-includes the cookie in the message thus proving that the client is capable of
-receiving messages sent to that address. All of this can be done by the server
-without allocating any state, and thus without consuming expensive resources.
-
-OpenSSL implements this capability via the DTLSv1_listen() function. The B<ssl>
-parameter should be a newly allocated SSL object with its read and write BIOs
-set, in the same way as might be done for a call to SSL_accept(). Typically the
-read BIO will be in an "unconnected" state and thus capable of receiving
-messages from any peer.
+Some transport protocols (such as UDP) can be susceptible to amplification
+attacks. Unlike TCP there is no initial connection setup in UDP that
+validates that the client can actually receive messages on its advertised source
+address. An attacker could forge its source IP address and then send handshake
+initiation messages to the server. The server would then send its response to
+the forged source IP. If the response messages are larger than the original
+message then the amplification attack has succeeded.
+
+If DTLS is used over UDP (or any datagram based protocol that does not validate
+the source IP) then it is susceptible to this type of attack. TLSv1.3 is
+designed to operate over a stream-based transport protocol (such as TCP).
+If TCP is being used then there is no need to use SSL_stateless(). However some
+stream-based transport protocols (e.g. QUIC) may not validate the source
+address. In this case a TLSv1.3 application would be susceptible to this attack.
+
+As a countermeasure to this issue TLSv1.3 and DTLS include a stateless cookie
+mechanism. The idea is that when a client attempts to connect to a server it
+sends a ClientHello message. The server responds with a HelloRetryRequest (in
+TLSv1.3) or a HelloVerifyRequest (in DTLS) which contains a unique cookie. The
+client then resends the ClientHello, but this time includes the cookie in the
+message thus proving that the client is capable of receiving messages sent to
+that address. All of this can be done by the server without allocating any
+state, and thus without consuming expensive resources.
+
+OpenSSL implements this capability via the SSL_stateless() and DTLSv1_listen()
+functions. The B<ssl> parameter should be a newly allocated SSL object with its
+read and write BIOs set, in the same way as might be done for a call to
+SSL_accept(). Typically, for DTLS, the read BIO will be in an "unconnected"
+state and thus capable of receiving messages from any peer.
 
 When a ClientHello is received that contains a cookie that has been verified,
-then DTLSv1_listen() will return with the B<ssl> parameter updated into a state
+then these functions will return with the B<ssl> parameter updated into a state
 where the handshake can be continued by a call to (for example) SSL_accept().
-Additionally the B<BIO_ADDR> pointed to by B<peer> will be filled in with
-details of the peer that sent the ClientHello. If the underlying BIO is unable
-to obtain the B<BIO_ADDR> of the peer (for example because the BIO does not
-support this), then B<*peer> will be cleared and the family set to AF_UNSPEC.
-Typically user code is expected to "connect" the underlying socket to the peer
-and continue the handshake in a connected state.
-
-Prior to calling DTLSv1_listen() user code must ensure that cookie generation
+Additionally, for DTLSv1_listen(), the B<BIO_ADDR> pointed to by B<peer> will be
+filled in with details of the peer that sent the ClientHello. If the underlying
+BIO is unable to obtain the B<BIO_ADDR> of the peer (for example because the BIO
+does not support this), then B<*peer> will be cleared and the family set to
+AF_UNSPEC. Typically user code is expected to "connect" the underlying socket to
+the peer and continue the handshake in a connected state.
+
+Prior to calling these functions user code must ensure that cookie generation
 and verification callbacks have been set up using
 SSL_CTX_set_cookie_generate_cb() and SSL_CTX_set_cookie_verify_cb()
 respectively.
@@ -60,25 +74,39 @@ ClientHellos it is unable to process fragmented messages (since this would
 require the allocation of state). An implication of this is that DTLSv1_listen()
 B<only> supports ClientHellos that fit inside a single datagram.
 
+For SSL_stateless() if an entire ClientHello message cannot be read without the
+"read" BIO becoming empty then the SSL_stateless() call will fail. It is the
+application's responsibility to ensure that data read from the "read" BIO during
+a single SSL_stateless() call is all from the same peer.
+
+SSL_stateless() will fail (with a 0 return value) if some TLS version less than
+TLSv1.3 is used.
+
+Both SSL_stateless() and DTLSv1_listen() will clear the error queue when they
+start.
+
 =head1 RETURN VALUES
 
-From OpenSSL 1.1.0 a return value of >= 1 indicates success. In this instance
-the B<peer> value will be filled in and the B<ssl> object set up ready to
-continue the handshake.
+For SSL_stateless() a return value of 1 indicates success and the B<ssl> object
+will be set up ready to continue the handshake. A return value of 0 indicates
+failure. User code may retry the SSL_stateless() call.
+
+For DTLSv1_listen() a return value of >= 1 indicates success. The B<ssl> object
+will be set up ready to continue the handshake.  the B<peer> value will also be
+filled in.
 
 A return value of 0 indicates a non-fatal error. This could (for
 example) be because of non-blocking IO, or some invalid message having been
 received from a peer. Errors may be placed on the OpenSSL error queue with
 further information if appropriate. Typically user code is expected to retry the
-call to DTLSv1_listen() in the event of a non-fatal error. Any old errors on the
-error queue will be cleared in the subsequent call.
+call to DTLSv1_listen() in the event of a non-fatal error.
 
 A return value of <0 indicates a fatal error. This could (for example) be
 because of a failure to allocate sufficient memory for the operation.
 
-Prior to OpenSSL 1.1.0 fatal and non-fatal errors both produce return codes
-<= 0 (in typical implementations user code treats all errors as non-fatal),
-whilst return codes >0 indicate success.
+For DTLSv1_listen(), prior to OpenSSL 1.1.0, fatal and non-fatal errors both
+produce return codes <= 0 (in typical implementations user code treats all
+errors as non-fatal), whilst return codes >0 indicate success.
 
 =head1 SEE ALSO
 
@@ -87,12 +115,14 @@ L<ssl(7)>, L<bio(7)>
 
 =head1 HISTORY
 
+SSL_stateless() was first added in OpenSSL 1.1.1.
+
 DTLSv1_listen() return codes were clarified in OpenSSL 1.1.0. The type of "peer"
 also changed in OpenSSL 1.1.0.
 
 =head1 COPYRIGHT
 
-Copyright 2015-2016 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2015-2017 The OpenSSL Project Authors. All Rights Reserved.
 
 Licensed under the OpenSSL license (the "License").  You may not use
 this file except in compliance with the License.  You can obtain a copy
diff --git a/include/openssl/dtls1.h b/include/openssl/dtls1.h
index 86a8981..aee8cfd 100644
--- a/include/openssl/dtls1.h
+++ b/include/openssl/dtls1.h
@@ -26,6 +26,10 @@ extern "C" {
 # define DTLS_ANY_VERSION                0x1FFFF
 
 /* lengths of messages */
+/*
+ * Actually the max cookie length in DTLS is 255. But we can't change this now
+ * due to compatibility concerns.
+ */
 # define DTLS1_COOKIE_LENGTH                     256
 
 # define DTLS1_RT_HEADER_LENGTH                  13
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 05a07eb..cfb0696 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -546,6 +546,9 @@ typedef int (*SSL_verify_cb)(int preverify_ok, X509_STORE_CTX *x509_ctx);
 # define SSL_CONF_TYPE_DIR               0x3
 # define SSL_CONF_TYPE_NONE              0x4
 
+/* Length of a TLSv1.3 cookie */
+# define SSL_COOKIE_LENGTH                       255
+
 /*
  * Note: SSL[_CTX]_set_{options,mode} use |= op on the previous value, they
  * cannot be used to clear bits.
@@ -1750,6 +1753,7 @@ __owur int SSL_get_changed_async_fds(SSL *s, OSSL_ASYNC_FD *addfd,
                                      size_t *numdelfds);
 # endif
 __owur int SSL_accept(SSL *ssl);
+__owur int SSL_stateless(SSL *s);
 __owur int SSL_connect(SSL *ssl);
 __owur int SSL_read(SSL *ssl, void *buf, int num);
 __owur int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes);
diff --git a/include/openssl/ssl3.h b/include/openssl/ssl3.h
index b781f61..6e8ef6b 100644
--- a/include/openssl/ssl3.h
+++ b/include/openssl/ssl3.h
@@ -284,6 +284,8 @@ extern "C" {
 
 # define TLS1_FLAGS_ENCRYPT_THEN_MAC_WRITE       0x0400
 
+# define TLS1_FLAGS_STATELESS                    0x0800
+
 # define SSL3_MT_HELLO_REQUEST                   0
 # define SSL3_MT_CLIENT_HELLO                    1
 # define SSL3_MT_SERVER_HELLO                    2
diff --git a/include/openssl/sslerr.h b/include/openssl/sslerr.h
index fdb17a5..2431b49 100644
--- a/include/openssl/sslerr.h
+++ b/include/openssl/sslerr.h
@@ -322,6 +322,7 @@ int ERR_load_SSL_strings(void);
 # define SSL_F_TLS_CONSTRUCT_SERVER_KEY_EXCHANGE          492
 # define SSL_F_TLS_CONSTRUCT_STOC_ALPN                    451
 # define SSL_F_TLS_CONSTRUCT_STOC_CERTIFICATE             374
+# define SSL_F_TLS_CONSTRUCT_STOC_COOKIE                  613
 # define SSL_F_TLS_CONSTRUCT_STOC_CRYPTOPRO_BUG           452
 # define SSL_F_TLS_CONSTRUCT_STOC_DONE                    375
 # define SSL_F_TLS_CONSTRUCT_STOC_EARLY_DATA              531
@@ -351,6 +352,7 @@ int ERR_load_SSL_strings(void);
 # define SSL_F_TLS_PARSE_CERTIFICATE_AUTHORITIES          566
 # define SSL_F_TLS_PARSE_CLIENTHELLO_TLSEXT               449
 # define SSL_F_TLS_PARSE_CTOS_ALPN                        567
+# define SSL_F_TLS_PARSE_CTOS_COOKIE                      614
 # define SSL_F_TLS_PARSE_CTOS_EARLY_DATA                  568
 # define SSL_F_TLS_PARSE_CTOS_EC_PT_FORMATS               569
 # define SSL_F_TLS_PARSE_CTOS_EMS                         570
diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c
index 1e3eb2c..111579b 100644
--- a/ssl/ssl_err.c
+++ b/ssl/ssl_err.c
@@ -492,6 +492,8 @@ static const ERR_STRING_DATA SSL_str_functs[] = {
     {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_STOC_ALPN, 0),
      "tls_construct_stoc_alpn"},
     {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_STOC_CERTIFICATE, 0), ""},
+    {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_STOC_COOKIE, 0),
+     "tls_construct_stoc_cookie"},
     {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_STOC_CRYPTOPRO_BUG, 0),
      "tls_construct_stoc_cryptopro_bug"},
     {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_CONSTRUCT_STOC_DONE, 0), ""},
@@ -544,6 +546,8 @@ static const ERR_STRING_DATA SSL_str_functs[] = {
     {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CLIENTHELLO_TLSEXT, 0), ""},
     {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CTOS_ALPN, 0),
      "tls_parse_ctos_alpn"},
+    {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CTOS_COOKIE, 0),
+     "tls_parse_ctos_cookie"},
     {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CTOS_EARLY_DATA, 0),
      "tls_parse_ctos_early_data"},
     {ERR_PACK(ERR_LIB_SSL, SSL_F_TLS_PARSE_CTOS_EC_PT_FORMATS, 0),
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 8094e2f..f68031e 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -590,6 +590,7 @@ int SSL_clear(SSL *s)
     OPENSSL_free(s->psksession_id);
     s->psksession_id = NULL;
     s->psksession_id_len = 0;
+    s->hello_retry_request = 0;
 
     s->error = 0;
     s->hit = 0;
@@ -2939,6 +2940,10 @@ SSL_CTX *SSL_CTX_new(const SSL_METHOD *meth)
                        sizeof(ret->ext.tick_aes_key)) <= 0))
         ret->options |= SSL_OP_NO_TICKET;
 
+    if (RAND_bytes(ret->ext.cookie_hmac_key,
+                   sizeof(ret->ext.cookie_hmac_key)) <= 0)
+        goto err;
+
 #ifndef OPENSSL_NO_SRP
     if (!SSL_CTX_SRP_CTX_init(ret))
         goto err;
@@ -5291,3 +5296,23 @@ __owur unsigned int ssl_get_split_send_fragment(const SSL *ssl)
     /* return current SSL connection setting */
     return ssl->split_send_fragment;
 }
+
+int SSL_stateless(SSL *s)
+{
+    int ret;
+
+    /* Ensure there is no state left over from a previous invocation */
+    if (!SSL_clear(s))
+        return 0;
+
+    ERR_clear_error();
+
+    s->s3->flags |= TLS1_FLAGS_STATELESS;
+    ret = SSL_accept(s);
+    s->s3->flags &= ~TLS1_FLAGS_STATELESS;
+
+    if (ret > 0 && s->ext.cookieok)
+        return 1;
+
+    return 0;
+}
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h
index de73f44..0079577 100644
--- a/ssl/ssl_locl.h
+++ b/ssl/ssl_locl.h
@@ -972,6 +972,8 @@ struct ssl_ctx_st {
         SSL_CTX_npn_select_cb_func npn_select_cb;
         void *npn_select_cb_arg;
 # endif
+
+        unsigned char cookie_hmac_key[SHA256_DIGEST_LENGTH];
     } ext;
 
 # ifndef OPENSSL_NO_PSK
@@ -1274,6 +1276,9 @@ struct ssl_st {
         /* May be sent by a server in HRR. Must be echoed back in ClientHello */
         unsigned char *tls13_cookie;
         size_t tls13_cookie_len;
+        /* Have we received a cookie from the client? */
+        int cookieok;
+
         /*
          * Maximum Fragment Length as per RFC 4366.
          * If this member contains one of the allowed values (1-4)
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index cc862f5..5a0fa25 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -12,6 +12,7 @@
 #include "internal/cryptlib.h"
 #include "../ssl_locl.h"
 #include "statem_locl.h"
+#include "internal/cryptlib.h"
 
 static int final_renegotiate(SSL *s, unsigned int context, int sent);
 static int init_server_name(SSL *s, unsigned int context);
@@ -320,11 +321,12 @@ static const EXTENSION_DEFINITION ext_defs[] = {
     },
 #endif
     {
+        /* Must be after key_share */
         TLSEXT_TYPE_cookie,
         SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST
         | SSL_EXT_TLS_IMPLEMENTATION_ONLY | SSL_EXT_TLS1_3_ONLY,
-        NULL, NULL, tls_parse_stoc_cookie, NULL, tls_construct_ctos_cookie,
-        NULL
+        NULL, tls_parse_ctos_cookie, tls_parse_stoc_cookie,
+        tls_construct_stoc_cookie, tls_construct_ctos_cookie, NULL
     },
     {
         /*
@@ -1237,85 +1239,136 @@ static int final_key_share(SSL *s, unsigned int context, int sent)
         return 0;
     }
     /*
-     * If
+     * IF
      *     we are a server
-     *     AND
-     *     we have no key_share
      * THEN
-     *     If
-     *         we didn't already send a HelloRetryRequest
-     *         AND
-     *         the client sent a key_share extension
-     *         AND
-     *         (we are not resuming
-     *          OR the kex_mode allows key_share resumes)
-     *         AND
-     *         a shared group exists
-     *     THEN
-     *         send a HelloRetryRequest
-     *     ELSE If
-     *         we are not resuming
-     *         OR
-     *         the kex_mode doesn't allow non key_share resumes
+     *     IF
+     *         we have a suitable key_share
      *     THEN
-     *         fail;
+     *         IF
+     *             we are stateless AND we have no cookie
+     *         THEN
+     *             send a HelloRetryRequest
+     *     ELSE
+     *         IF
+     *             we didn't already send a HelloRetryRequest
+     *             AND
+     *             the client sent a key_share extension
+     *             AND
+     *             (we are not resuming
+     *              OR the kex_mode allows key_share resumes)
+     *             AND
+     *             a shared group exists
+     *         THEN
+     *             send a HelloRetryRequest
+     *         ELSE IF
+     *             we are not resuming
+     *             OR
+     *             the kex_mode doesn't allow non key_share resumes
+     *         THEN
+     *             fail
+     *         ELSE IF
+     *             we are stateless AND we have no cookie
+     *         THEN
+     *             send a HelloRetryRequest
      */
-    if (s->server && s->s3->peer_tmp == NULL) {
-        /* No suitable share */
-        if (s->hello_retry_request == SSL_HRR_NONE && sent
-                && (!s->hit
-                    || (s->ext.psk_kex_mode & TLSEXT_KEX_MODE_FLAG_KE_DHE)
-                       != 0)) {
-            const uint16_t *pgroups, *clntgroups;
-            size_t num_groups, clnt_num_groups, i;
-            unsigned int group_id = 0;
-
-            /* Check if a shared group exists */
-
-            /* Get the clients list of supported groups. */
-            tls1_get_peer_groups(s, &clntgroups, &clnt_num_groups);
-            tls1_get_supported_groups(s, &pgroups, &num_groups);
-
-            /* Find the first group we allow that is also in client's list */
-            for (i = 0; i < num_groups; i++) {
-                group_id = pgroups[i];
-
-                if (check_in_list(s, group_id, clntgroups, clnt_num_groups, 1))
-                    break;
+    if (s->server) {
+        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, SSL_F_FINAL_KEY_SHARE,
+                             ERR_R_INTERNAL_ERROR);
+                    return 0;
+                }
+                s->hello_retry_request = SSL_HRR_PENDING;
+                return 1;
+            }
+        } else {
+            /* No suitable key_share */
+            if (s->hello_retry_request == SSL_HRR_NONE && sent
+                    && (!s->hit
+                        || (s->ext.psk_kex_mode & TLSEXT_KEX_MODE_FLAG_KE_DHE)
+                           != 0)) {
+                const uint16_t *pgroups, *clntgroups;
+                size_t num_groups, clnt_num_groups, i;
+                unsigned int group_id = 0;
+
+                /* Check if a shared group exists */
+
+                /* Get the clients list of supported groups. */
+                tls1_get_peer_groups(s, &clntgroups, &clnt_num_groups);
+                tls1_get_supported_groups(s, &pgroups, &num_groups);
+
+                /*
+                 * Find the first group we allow that is also in client's list
+                 */
+                for (i = 0; i < num_groups; i++) {
+                    group_id = pgroups[i];
+
+                    if (check_in_list(s, group_id, clntgroups, clnt_num_groups,
+                                      1))
+                        break;
+                }
+
+                if (i < num_groups) {
+                    /* A shared group exists so send a HelloRetryRequest */
+                    s->s3->group_id = group_id;
+                    s->hello_retry_request = SSL_HRR_PENDING;
+                    return 1;
+                }
+            }
+            if (!s->hit
+                    || (s->ext.psk_kex_mode & TLSEXT_KEX_MODE_FLAG_KE) == 0) {
+                /* Nothing left we can do - just fail */
+                SSLfatal(s, sent ? SSL_AD_HANDSHAKE_FAILURE
+                                 : SSL_AD_MISSING_EXTENSION,
+                         SSL_F_FINAL_KEY_SHARE, SSL_R_NO_SUITABLE_KEY_SHARE);
+                return 0;
             }
 
-            if (i < num_groups) {
-                /* A shared group exists so send a HelloRetryRequest */
-                s->s3->group_id = group_id;
+            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, SSL_F_FINAL_KEY_SHARE,
+                             ERR_R_INTERNAL_ERROR);
+                    return 0;
+                }
                 s->hello_retry_request = SSL_HRR_PENDING;
                 return 1;
             }
         }
-        if (!s->hit
-                || (s->ext.psk_kex_mode & TLSEXT_KEX_MODE_FLAG_KE) == 0) {
-            /* Nothing left we can do - just fail */
-            SSLfatal(s,
-                     sent ? SSL_AD_HANDSHAKE_FAILURE : SSL_AD_MISSING_EXTENSION,
-                     SSL_F_FINAL_KEY_SHARE, SSL_R_NO_SUITABLE_KEY_SHARE);
+
+        /*
+         * We have a key_share so don't send any more HelloRetryRequest
+         * messages
+         */
+        if (s->hello_retry_request == SSL_HRR_PENDING)
+            s->hello_retry_request = SSL_HRR_COMPLETE;
+    } else {
+        /*
+         * For a client side resumption with no key_share we need to generate
+         * the handshake secret (otherwise this is done during key_share
+         * processing).
+         */
+        if (!sent && !tls13_generate_handshake_secret(s, NULL, 0)) {
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_FINAL_KEY_SHARE,
+                     ERR_R_INTERNAL_ERROR);
             return 0;
         }
     }
 
-    /* We have a key_share so don't send any more HelloRetryRequest messages */
-    if (s->server && s->hello_retry_request == SSL_HRR_PENDING)
-        s->hello_retry_request = SSL_HRR_COMPLETE;
-
-    /*
-     * For a client side resumption with no key_share we need to generate
-     * the handshake secret (otherwise this is done during key_share
-     * processing).
-     */
-    if (!sent && !s->server && !tls13_generate_handshake_secret(s, NULL, 0)) {
-        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_FINAL_KEY_SHARE,
-                 ERR_R_INTERNAL_ERROR);
-        return 0;
-    }
-
     return 1;
 }
 #endif
diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c
index 30cbf9e..fadc6a7 100644
--- a/ssl/statem/extensions_srvr.c
+++ b/ssl/statem/extensions_srvr.c
@@ -10,6 +10,30 @@
 #include <openssl/ocsp.h>
 #include "../ssl_locl.h"
 #include "statem_locl.h"
+#include "internal/cryptlib.h"
+
+#define COOKIE_STATE_FORMAT_VERSION     0
+
+/*
+ * 2 bytes for packet length, 2 bytes for format version, 2 bytes for
+ * protocol version, 2 bytes for group id, 2 bytes for cipher id, 1 byte for
+ * key_share present flag, 4 bytes for timestamp, 2 bytes for the hashlen,
+ * EVP_MAX_MD_SIZE for transcript hash, 1 byte for app cookie length, app cookie
+ * length bytes, SHA256_DIGEST_LENGTH bytes for the HMAC of the whole thing.
+ */
+#define MAX_COOKIE_SIZE (2 + 2 + 2 + 2 + 2 + 1 + 4 + 2 + EVP_MAX_MD_SIZE + 1 \
+                         + SSL_COOKIE_LENGTH + SHA256_DIGEST_LENGTH)
+
+/*
+ * Message header + 2 bytes for protocol version + number of random bytes +
+ * + 1 byte for legacy session id length + number of bytes in legacy session id
+ * + 2 bytes for ciphersuite + 1 byte for legacy compression
+ * + 2 bytes for extension block length + 6 bytes for key_share extension
+ * + 4 bytes for cookie extension header + the number of bytes in the cookie
+ */
+#define MAX_HRR_SIZE    (SSL3_HM_HEADER_LENGTH + 2 + SSL3_RANDOM_SIZE + 1 \
+                         + SSL_MAX_SSL_SESSION_ID_LENGTH + 2 + 1 + 2 + 6 + 4 \
+                         + MAX_COOKIE_SIZE)
 
 /*
  * Parse the client's renegotiation binding and abort if it's not right
@@ -594,6 +618,17 @@ int tls_parse_ctos_key_share(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
         return 0;
     }
 
+    if (s->s3->group_id != 0 && PACKET_remaining(&key_share_list) == 0) {
+        /*
+         * If we set a group_id already, then we must have sent an HRR
+         * requesting a new key_share. If we haven't got one then that is an
+         * error
+         */
+        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS_PARSE_CTOS_KEY_SHARE,
+                 SSL_R_BAD_KEY_SHARE);
+        return 0;
+    }
+
     while (PACKET_remaining(&key_share_list) > 0) {
         if (!PACKET_get_net_2(&key_share_list, &group_id)
                 || !PACKET_get_length_prefixed_2(&key_share_list, &encoded_pt)
@@ -610,6 +645,18 @@ int tls_parse_ctos_key_share(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
         if (found)
             continue;
 
+        /*
+         * If we sent an HRR then the key_share sent back MUST be for the group
+         * we requested, and must be the only key_share sent.
+         */
+        if (s->s3->group_id != 0
+                && (group_id != s->s3->group_id
+                    || PACKET_remaining(&key_share_list) != 0)) {
+            SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
+                     SSL_F_TLS_PARSE_CTOS_KEY_SHARE, SSL_R_BAD_KEY_SHARE);
+            return 0;
+        }
+
         /* Check if this share is in supported_groups sent from client */
         if (!check_in_list(s, group_id, clntgroups, clnt_num_groups, 0)) {
             SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER,
@@ -646,6 +693,227 @@ int tls_parse_ctos_key_share(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
     return 1;
 }
 
+int tls_parse_ctos_cookie(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
+                          size_t chainidx)
+{
+    unsigned int format, version, key_share, group_id;
+    EVP_MD_CTX *hctx;
+    EVP_PKEY *pkey;
+    PACKET cookie, raw, chhash, appcookie;
+    WPACKET hrrpkt;
+    const unsigned char *data, *mdin, *ciphdata;
+    unsigned char hmac[SHA256_DIGEST_LENGTH];
+    unsigned char hrr[MAX_HRR_SIZE];
+    size_t rawlen, hmaclen, hrrlen, ciphlen;
+    unsigned long tm, now;
+
+    /* Ignore any cookie if we're not set up to verify it */
+    if (s->ctx->app_verify_cookie_cb == NULL
+            || (s->s3->flags & TLS1_FLAGS_STATELESS) == 0)
+        return 1;
+
+    if (!PACKET_as_length_prefixed_2(pkt, &cookie)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_LENGTH_MISMATCH);
+        return 0;
+    }
+
+    raw = cookie;
+    data = PACKET_data(&raw);
+    rawlen = PACKET_remaining(&raw);
+    if (rawlen < SHA256_DIGEST_LENGTH
+            || !PACKET_forward(&raw, rawlen - SHA256_DIGEST_LENGTH)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_LENGTH_MISMATCH);
+        return 0;
+    }
+    mdin = PACKET_data(&raw);
+
+    /* Verify the HMAC of the cookie */
+    hctx = EVP_MD_CTX_create();
+    pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL,
+                                s->session_ctx->ext.cookie_hmac_key,
+                                sizeof(s->session_ctx->ext.cookie_hmac_key));
+    if (hctx == NULL || pkey == NULL) {
+        EVP_MD_CTX_free(hctx);
+        EVP_PKEY_free(pkey);
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 ERR_R_MALLOC_FAILURE);
+        return 0;
+    }
+
+    hmaclen = SHA256_DIGEST_LENGTH;
+    if (EVP_DigestSignInit(hctx, NULL, EVP_sha256(), NULL, pkey) <= 0
+            || EVP_DigestSign(hctx, hmac, &hmaclen, data,
+                              rawlen - SHA256_DIGEST_LENGTH) <= 0
+            || hmaclen != SHA256_DIGEST_LENGTH) {
+        EVP_MD_CTX_free(hctx);
+        EVP_PKEY_free(pkey);
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+
+    EVP_MD_CTX_free(hctx);
+    EVP_PKEY_free(pkey);
+
+    if (CRYPTO_memcmp(hmac, mdin, SHA256_DIGEST_LENGTH) != 0) {
+        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_COOKIE_MISMATCH);
+        return 0;
+    }
+
+    if (!PACKET_get_net_2(&cookie, &format)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_LENGTH_MISMATCH);
+        return 0;
+    }
+    /* Check the cookie format is something we recognise. Ignore it if not */
+    if (format != COOKIE_STATE_FORMAT_VERSION)
+        return 1;
+
+    /*
+     * The rest of these checks really shouldn't fail since we have verified the
+     * HMAC above.
+     */
+
+    /* Check the version number is sane */
+    if (!PACKET_get_net_2(&cookie, &version)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_LENGTH_MISMATCH);
+        return 0;
+    }
+    if (version != TLS1_3_VERSION) {
+        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_BAD_PROTOCOL_VERSION_NUMBER);
+        return 0;
+    }
+
+    if (!PACKET_get_net_2(&cookie, &group_id)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_LENGTH_MISMATCH);
+        return 0;
+    }
+
+    ciphdata = PACKET_data(&cookie);
+    if (!PACKET_forward(&cookie, 2)) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_LENGTH_MISMATCH);
+        return 0;
+    }
+    if (group_id != s->s3->group_id
+            || s->s3->tmp.new_cipher
+               != ssl_get_cipher_by_char(s, ciphdata, 0)) {
+        /*
+         * We chose a different cipher or group id this time around to what is
+         * in the cookie. Something must have changed.
+         */
+        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_BAD_CIPHER);
+        return 0;
+    }
+
+    if (!PACKET_get_1(&cookie, &key_share)
+            || !PACKET_get_net_4(&cookie, &tm)
+            || !PACKET_get_length_prefixed_2(&cookie, &chhash)
+            || !PACKET_get_length_prefixed_1(&cookie, &appcookie)
+            || PACKET_remaining(&cookie) != SHA256_DIGEST_LENGTH) {
+        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_LENGTH_MISMATCH);
+        return 0;
+    }
+
+    /* We tolerate a cookie age of up to 10 minutes (= 60 * 10 seconds) */
+    now = (unsigned long)time(NULL);
+    if (tm > now || (now - tm) > 600) {
+        /* Cookie is stale. Ignore it */
+        return 1;
+    }
+
+    /* Verify the app cookie */
+    if (s->ctx->app_verify_cookie_cb(s, PACKET_data(&appcookie),
+                                     PACKET_remaining(&appcookie)) == 0) {
+        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 SSL_R_COOKIE_MISMATCH);
+        return 0;
+    }
+
+    /*
+     * Reconstruct the HRR that we would have sent in response to the original
+     * ClientHello so we can add it to the transcript hash.
+     * Note: This won't work with custom HRR extensions
+     */
+    if (!WPACKET_init_static_len(&hrrpkt, hrr, sizeof(hrr), 0)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    if (!WPACKET_put_bytes_u8(&hrrpkt, SSL3_MT_SERVER_HELLO)
+            || !WPACKET_start_sub_packet_u24(&hrrpkt)
+            || !WPACKET_put_bytes_u16(&hrrpkt, TLS1_2_VERSION)
+            || !WPACKET_memcpy(&hrrpkt, hrrrandom, SSL3_RANDOM_SIZE)
+            || !WPACKET_sub_memcpy_u8(&hrrpkt, s->tmp_session_id,
+                                      s->tmp_session_id_len)
+            || !s->method->put_cipher_by_char(s->s3->tmp.new_cipher, &hrrpkt,
+                                              &ciphlen)
+            || !WPACKET_put_bytes_u8(&hrrpkt, 0)
+            || !WPACKET_start_sub_packet_u16(&hrrpkt)) {
+        WPACKET_cleanup(&hrrpkt);
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    if (!WPACKET_put_bytes_u16(&hrrpkt, TLSEXT_TYPE_supported_versions)
+            || !WPACKET_start_sub_packet_u16(&hrrpkt)
+               /* TODO(TLS1.3): Fix this before release */
+            || !WPACKET_put_bytes_u16(&hrrpkt, TLS1_3_VERSION_DRAFT)
+            || !WPACKET_close(&hrrpkt)) {
+        WPACKET_cleanup(&hrrpkt);
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+    if (key_share) {
+        if (!WPACKET_put_bytes_u16(&hrrpkt, TLSEXT_TYPE_key_share)
+                || !WPACKET_start_sub_packet_u16(&hrrpkt)
+                || !WPACKET_put_bytes_u16(&hrrpkt, s->s3->group_id)
+                || !WPACKET_close(&hrrpkt)) {
+            WPACKET_cleanup(&hrrpkt);
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                     ERR_R_INTERNAL_ERROR);
+            return 0;
+        }
+    }
+    if (!WPACKET_put_bytes_u16(&hrrpkt, TLSEXT_TYPE_cookie)
+            || !WPACKET_start_sub_packet_u16(&hrrpkt)
+            || !WPACKET_sub_memcpy_u16(&hrrpkt, data, rawlen)
+            || !WPACKET_close(&hrrpkt) /* cookie extension */
+            || !WPACKET_close(&hrrpkt) /* extension block */
+            || !WPACKET_close(&hrrpkt) /* message */
+            || !WPACKET_get_total_written(&hrrpkt, &hrrlen)
+            || !WPACKET_finish(&hrrpkt)) {
+        WPACKET_cleanup(&hrrpkt);
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_PARSE_CTOS_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+
+    /* Reconstruct the transcript hash */
+    if (!create_synthetic_message_hash(s, PACKET_data(&chhash),
+                                       PACKET_remaining(&chhash), hrr,
+                                       hrrlen)) {
+        /* SSLfatal() already called */
+        return 0;
+    }
+
+    /* Act as if this ClientHello came after a HelloRetryRequest */
+    s->hello_retry_request = 1;
+
+    s->ext.cookieok = 1;
+
+    return 1;
+}
+
 #ifndef OPENSSL_NO_EC
 int tls_parse_ctos_supported_groups(SSL *s, PACKET *pkt, unsigned int context,
                                     X509 *x, size_t chainidx)
@@ -1243,23 +1511,26 @@ EXT_RETURN tls_construct_stoc_key_share(SSL *s, WPACKET *pkt,
     size_t encoded_pt_len = 0;
     EVP_PKEY *ckey = s->s3->peer_tmp, *skey = NULL;
 
-    if (ckey == NULL) {
-        /* No key_share received from client */
-        if (s->hello_retry_request == SSL_HRR_PENDING) {
-            if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_key_share)
-                    || !WPACKET_start_sub_packet_u16(pkt)
-                    || !WPACKET_put_bytes_u16(pkt, s->s3->group_id)
-                    || !WPACKET_close(pkt)) {
-                SSLfatal(s, SSL_AD_INTERNAL_ERROR,
-                         SSL_F_TLS_CONSTRUCT_STOC_KEY_SHARE,
-                         ERR_R_INTERNAL_ERROR);
-                return EXT_RETURN_FAIL;
-            }
-
-            return EXT_RETURN_SENT;
+    if (s->hello_retry_request == SSL_HRR_PENDING) {
+        if (ckey != NULL) {
+            /* Original key_share was acceptable so don't ask for another one */
+            return EXT_RETURN_NOT_SENT;
+        }
+        if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_key_share)
+                || !WPACKET_start_sub_packet_u16(pkt)
+                || !WPACKET_put_bytes_u16(pkt, s->s3->group_id)
+                || !WPACKET_close(pkt)) {
+            SSLfatal(s, SSL_AD_INTERNAL_ERROR,
+                     SSL_F_TLS_CONSTRUCT_STOC_KEY_SHARE,
+                     ERR_R_INTERNAL_ERROR);
+            return EXT_RETURN_FAIL;
         }
 
-        /* Must be resuming. */
+        return EXT_RETURN_SENT;
+    }
+
+    if (ckey == NULL) {
+        /* No key_share received from client - must be resuming */
         if (!s->hit || !tls13_generate_handshake_secret(s, NULL, 0)) {
             SSLfatal(s, SSL_AD_INTERNAL_ERROR,
                      SSL_F_TLS_CONSTRUCT_STOC_KEY_SHARE, ERR_R_INTERNAL_ERROR);
@@ -1313,6 +1584,130 @@ EXT_RETURN tls_construct_stoc_key_share(SSL *s, WPACKET *pkt,
     return EXT_RETURN_SENT;
 }
 
+EXT_RETURN tls_construct_stoc_cookie(SSL *s, WPACKET *pkt, unsigned int context,
+                                     X509 *x, size_t chainidx)
+{
+    unsigned char *hashval1, *hashval2, *appcookie1, *appcookie2, *cookie;
+    unsigned char *hmac, *hmac2;
+    size_t startlen, ciphlen, totcookielen, hashlen, hmaclen;
+    unsigned int appcookielen;
+    EVP_MD_CTX *hctx;
+    EVP_PKEY *pkey;
+    int ret = EXT_RETURN_FAIL;
+
+    if (s->ctx->app_gen_cookie_cb == NULL
+            || (s->s3->flags & TLS1_FLAGS_STATELESS) == 0)
+        return EXT_RETURN_NOT_SENT;
+
+    if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_cookie)
+            || !WPACKET_start_sub_packet_u16(pkt)
+            || !WPACKET_start_sub_packet_u16(pkt)
+            || !WPACKET_get_total_written(pkt, &startlen)
+            || !WPACKET_reserve_bytes(pkt, MAX_COOKIE_SIZE, &cookie)
+            || !WPACKET_put_bytes_u16(pkt, COOKIE_STATE_FORMAT_VERSION)
+            || !WPACKET_put_bytes_u16(pkt, TLS1_3_VERSION)
+            || !WPACKET_put_bytes_u16(pkt, s->s3->group_id)
+            || !s->method->put_cipher_by_char(s->s3->tmp.new_cipher, pkt,
+                                              &ciphlen)
+               /* Is there a key_share extension present in this HRR? */
+            || !WPACKET_put_bytes_u8(pkt, s->s3->peer_tmp == NULL)
+            || !WPACKET_put_bytes_u32(pkt, (unsigned int)time(NULL))
+            || !WPACKET_start_sub_packet_u16(pkt)
+            || !WPACKET_reserve_bytes(pkt, EVP_MAX_MD_SIZE, &hashval1)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return EXT_RETURN_FAIL;
+    }
+
+    /*
+     * Get the hash of the initial ClientHello. ssl_handshake_hash() operates
+     * on raw buffers, so we first reserve sufficient bytes (above) and then
+     * subsequently allocate them (below)
+     */
+    if (!ssl3_digest_cached_records(s, 0)
+            || !ssl_handshake_hash(s, hashval1, EVP_MAX_MD_SIZE, &hashlen)) {
+        /* SSLfatal() already called */
+        return EXT_RETURN_FAIL;
+    }
+
+    if (!WPACKET_allocate_bytes(pkt, hashlen, &hashval2)
+            || !ossl_assert(hashval1 == hashval2)
+            || !WPACKET_close(pkt)
+            || !WPACKET_start_sub_packet_u8(pkt)
+            || !WPACKET_reserve_bytes(pkt, SSL_COOKIE_LENGTH, &appcookie1)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return EXT_RETURN_FAIL;
+    }
+
+    /* Generate the application cookie */
+    if (s->ctx->app_gen_cookie_cb(s, appcookie1, &appcookielen) == 0) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 SSL_R_COOKIE_GEN_CALLBACK_FAILURE);
+        return EXT_RETURN_FAIL;
+    }
+
+    if (!WPACKET_allocate_bytes(pkt, appcookielen, &appcookie2)
+            || !ossl_assert(appcookie1 == appcookie2)
+            || !WPACKET_close(pkt)
+            || !WPACKET_get_total_written(pkt, &totcookielen)
+            || !WPACKET_reserve_bytes(pkt, SHA256_DIGEST_LENGTH, &hmac)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return EXT_RETURN_FAIL;
+    }
+    hmaclen = SHA256_DIGEST_LENGTH;
+
+    totcookielen -= startlen;
+    if (!ossl_assert(totcookielen <= MAX_COOKIE_SIZE - SHA256_DIGEST_LENGTH)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        return EXT_RETURN_FAIL;
+    }
+
+    /* HMAC the cookie */
+    hctx = EVP_MD_CTX_create();
+    pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL,
+                                s->session_ctx->ext.cookie_hmac_key,
+                                sizeof(s->session_ctx->ext.cookie_hmac_key));
+    if (hctx == NULL || pkey == NULL) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 ERR_R_MALLOC_FAILURE);
+        goto err;
+    }
+
+    if (EVP_DigestSignInit(hctx, NULL, EVP_sha256(), NULL, pkey) <= 0
+            || EVP_DigestSign(hctx, hmac, &hmaclen, cookie,
+                              totcookielen) <= 0) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+
+    if (!ossl_assert(totcookielen + hmaclen <= MAX_COOKIE_SIZE)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+
+    if (!WPACKET_allocate_bytes(pkt, hmaclen, &hmac2)
+            || !ossl_assert(hmac == hmac2)
+            || !ossl_assert(cookie == hmac - totcookielen)
+            || !WPACKET_close(pkt)
+            || !WPACKET_close(pkt)) {
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_F_TLS_CONSTRUCT_STOC_COOKIE,
+                 ERR_R_INTERNAL_ERROR);
+        goto err;
+    }
+
+    ret = EXT_RETURN_SENT;
+
+ err:
+    EVP_MD_CTX_free(hctx);
+    EVP_PKEY_free(pkey);
+    return ret;
+}
+
 EXT_RETURN tls_construct_stoc_cryptopro_bug(SSL *s, WPACKET *pkt,
                                             unsigned int context, X509 *x,
                                             size_t chainidx)
diff --git a/ssl/statem/statem.c b/ssl/statem/statem.c
index 29660d5..45cb9ab 100644
--- a/ssl/statem/statem.c
+++ b/ssl/statem/statem.c
@@ -311,7 +311,11 @@ static int state_machine(SSL *s, int server)
 
     st->in_handshake++;
     if (!SSL_in_init(s) || SSL_in_before(s)) {
-        if (!SSL_clear(s))
+        /*
+         * If we are stateless then we already called SSL_clear() - don't do
+         * it again and clear the STATELESS flag itself.
+         */
+        if ((s->s3->flags & TLS1_FLAGS_STATELESS) == 0 && !SSL_clear(s))
             return -1;
     }
 #ifndef OPENSSL_NO_SCTP
diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c
index 40fedb2..3129138 100644
--- a/ssl/statem/statem_clnt.c
+++ b/ssl/statem/statem_clnt.c
@@ -1739,7 +1739,7 @@ static MSG_PROCESS_RETURN tls_process_as_hello_retry_request(SSL *s,
      * Re-initialise the Transcript Hash. We're going to prepopulate it with
      * a synthetic message_hash in place of ClientHello1.
      */
-    if (!create_synthetic_message_hash(s)) {
+    if (!create_synthetic_message_hash(s, NULL, 0, NULL, 0)) {
         /* SSLfatal() already called */
         goto err;
     }
diff --git a/ssl/statem/statem_lib.c b/ssl/statem/statem_lib.c
index 02d75e7..6bd54ac 100644
--- a/ssl/statem/statem_lib.c
+++ b/ssl/statem/statem_lib.c
@@ -1120,6 +1120,17 @@ int tls_get_message_header(SSL *s, int *mt)
                              SSL_R_BAD_CHANGE_CIPHER_SPEC);
                     return 0;
                 }
+                if (s->statem.hand_state == TLS_ST_BEFORE
+                        && (s->s3->flags & TLS1_FLAGS_STATELESS) != 0) {
+                    /*
+                     * We are stateless and we received a CCS. Probably this is
+                     * from a client between the first and second ClientHellos.
+                     * We should ignore this, but return an error because we do
+                     * not return success until we see the second ClientHello
+                     * with a valid cookie.
+                     */
+                    return 0;
+                }
                 s->s3->tmp.message_type = *mt = SSL3_MT_CHANGE_CIPHER_SPEC;
                 s->init_num = readbytes - 1;
                 s->init_msg = s->init_buf->data;
@@ -2033,19 +2044,25 @@ int check_in_list(SSL *s, uint16_t group_id, const uint16_t *groups,
 #endif
 
 /* Replace ClientHello1 in the transcript hash with a synthetic message */
-int create_synthetic_message_hash(SSL *s)
+int create_synthetic_message_hash(SSL *s, const unsigned char *hashval,
+                                  size_t hashlen, const unsigned char *hrr,
+                                  size_t hrrlen)
 {
-    unsigned char hashval[EVP_MAX_MD_SIZE];
-    size_t hashlen = 0;
+    unsigned char hashvaltmp[EVP_MAX_MD_SIZE];
     unsigned char msghdr[SSL3_HM_HEADER_LENGTH];
 
     memset(msghdr, 0, sizeof(msghdr));
 
-    /* Get the hash of the initial ClientHello */
-    if (!ssl3_digest_cached_records(s, 0)
-            || !ssl_handshake_hash(s, hashval, sizeof(hashval), &hashlen)) {
-        /* SSLfatal() already called */
-        return 0;
+    if (hashval == NULL) {
+        hashval = hashvaltmp;
+        hashlen = 0;
+        /* Get the hash of the initial ClientHello */
+        if (!ssl3_digest_cached_records(s, 0)
+                || !ssl_handshake_hash(s, hashvaltmp, sizeof(hashvaltmp),
+                                       &hashlen)) {
+            /* SSLfatal() already called */
+            return 0;
+        }
     }
 
     /* Reinitialise the transcript hash */
@@ -2063,6 +2080,20 @@ int create_synthetic_message_hash(SSL *s)
         return 0;
     }
 
+    /*
+     * Now re-inject the HRR and current message if appropriate (we just deleted
+     * it when we reinitialised the transcript hash above). Only necessary after
+     * receiving a ClientHello2 with a cookie.
+     */
+    if (hrr != NULL
+            && (!ssl3_finish_mac(s, hrr, hrrlen)
+                || !ssl3_finish_mac(s, (unsigned char *)s->init_buf->data,
+                                    s->s3->tmp.message_size
+                                    + SSL3_HM_HEADER_LENGTH))) {
+        /* SSLfatal() already called */
+        return 0;
+    }
+
     return 1;
 }
 
diff --git a/ssl/statem/statem_locl.h b/ssl/statem/statem_locl.h
index eae9a36..38b0ce8 100644
--- a/ssl/statem/statem_locl.h
+++ b/ssl/statem/statem_locl.h
@@ -56,7 +56,9 @@ typedef int (*confunc_f) (SSL *s, WPACKET *pkt);
 
 int check_in_list(SSL *s, uint16_t group_id, const uint16_t *groups,
                   size_t num_groups, int checkallow);
-int create_synthetic_message_hash(SSL *s);
+int create_synthetic_message_hash(SSL *s, const unsigned char *hashval,
+                                  size_t hashlen, const unsigned char *hrr,
+                                  size_t hrrlen);
 int parse_ca_names(SSL *s, PACKET *pkt);
 int construct_ca_names(SSL *s, WPACKET *pkt);
 size_t construct_key_exchange_tbs(SSL *s, unsigned char **ptbs,
@@ -223,6 +225,8 @@ int tls_parse_ctos_etm(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
                        size_t chainidx);
 int tls_parse_ctos_key_share(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
                              size_t chainidx);
+int tls_parse_ctos_cookie(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
+                          size_t chainidx);
 int tls_parse_ctos_ems(SSL *s, PACKET *pkt, unsigned int context, X509 *x,
                        size_t chainidx);
 int tls_parse_ctos_psk_kex_modes(SSL *s, PACKET *pkt, unsigned int context,
@@ -279,6 +283,8 @@ EXT_RETURN tls_construct_stoc_supported_versions(SSL *s, WPACKET *pkt,
 EXT_RETURN tls_construct_stoc_key_share(SSL *s, WPACKET *pkt,
                                         unsigned int context, X509 *x,
                                         size_t chainidx);
+EXT_RETURN tls_construct_stoc_cookie(SSL *s, WPACKET *pkt, unsigned int context,
+                                     X509 *x, size_t chainidx);
 /*
  * Not in public headers as this is not an official extension. Only used when
  * SSL_OP_CRYPTOPRO_TLSEXT_BUG is set.
diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c
index 70e026d..5651f64 100644
--- a/ssl/statem/statem_srvr.c
+++ b/ssl/statem/statem_srvr.c
@@ -687,7 +687,8 @@ WORK_STATE ossl_statem_server_pre_work(SSL *s, WORK_STATE wst)
         return WORK_FINISHED_CONTINUE;
 
     case TLS_ST_EARLY_DATA:
-        if (s->early_data_state != SSL_EARLY_DATA_ACCEPTING)
+        if (s->early_data_state != SSL_EARLY_DATA_ACCEPTING
+                && (s->s3->flags & TLS1_FLAGS_STATELESS) == 0)
             return WORK_FINISHED_CONTINUE;
         /* Fall through */
 
@@ -2303,7 +2304,7 @@ int tls_construct_server_hello(SSL *s, WPACKET *pkt)
          * Re-initialise the Transcript Hash. We're going to prepopulate it with
          * a synthetic message_hash in place of ClientHello1.
          */
-        if (!create_synthetic_message_hash(s)) {
+        if (!create_synthetic_message_hash(s, NULL, 0, NULL, 0)) {
             /* SSLfatal() already called */
             return 0;
         }
diff --git a/test/sslapitest.c b/test/sslapitest.c
index 5ba5f6e..a338578 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -2552,6 +2552,95 @@ static int test_tls13_psk(void)
     return testresult;
 }
 
+static unsigned char cookie_magic_value[] = "cookie magic";
+
+static int generate_cookie_callback(SSL *ssl, unsigned char *cookie,
+                                    unsigned int *cookie_len)
+{
+    /*
+     * Not suitable as a real cookie generation function but good enough for
+     * testing!
+     */
+    memcpy(cookie, cookie_magic_value, sizeof(cookie_magic_value) - 1);
+    *cookie_len = sizeof(cookie_magic_value) - 1;
+
+    return 1;
+}
+
+static int verify_cookie_callback(SSL *ssl, const unsigned char *cookie,
+                                  unsigned int cookie_len)
+{
+    if (cookie_len == sizeof(cookie_magic_value) - 1
+        && memcmp(cookie, cookie_magic_value, cookie_len) == 0)
+        return 1;
+
+    return 0;
+}
+
+static int test_stateless(void)
+{
+    SSL_CTX *sctx = NULL, *cctx = NULL;
+    SSL *serverssl = NULL, *clientssl = NULL;
+    int testresult = 0;
+
+    if (!TEST_true(create_ssl_ctx_pair(TLS_server_method(),
+                                       TLS_client_method(), &sctx,
+                                       &cctx, cert, privkey)))
+        goto end;
+
+    /* Set up the cookie generation and verification callbacks */
+    SSL_CTX_set_cookie_generate_cb(sctx, generate_cookie_callback);
+    SSL_CTX_set_cookie_verify_cb(sctx, verify_cookie_callback);
+
+    /* The arrival of CCS messages can confuse the test */
+    SSL_CTX_clear_options(cctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
+
+    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+                                             NULL, NULL))
+               /* Send the first ClientHello */
+            || !TEST_false(create_ssl_connection(serverssl, clientssl,
+                                                SSL_ERROR_WANT_READ))
+               /* This should fail because there is no cookie */
+            || !TEST_false(SSL_stateless(serverssl)))
+        goto end;
+
+    /* Abandon the connection from this client */
+    SSL_free(clientssl);
+    clientssl = NULL;
+
+    /*
+     * Now create a connection from a new client but with the same server SSL
+     * object
+     */
+    if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
+                                             NULL, NULL))
+               /* Send the first ClientHello */
+            || !TEST_false(create_ssl_connection(serverssl, clientssl,
+                                                SSL_ERROR_WANT_READ))
+               /* This should fail because there is no cookie */
+            || !TEST_false(SSL_stateless(serverssl))
+               /* Send the second ClientHello */
+            || !TEST_false(create_ssl_connection(serverssl, clientssl,
+                                                SSL_ERROR_WANT_READ))
+               /* This should succeed because a cookie is now present */
+            || !TEST_true(SSL_stateless(serverssl))
+               /* Complete the connection */
+            || !TEST_true(create_ssl_connection(serverssl, clientssl,
+                                                SSL_ERROR_NONE)))
+        goto end;
+
+    shutdown_ssl_connection(serverssl, clientssl);
+    serverssl = clientssl = NULL;
+    testresult = 1;
+
+ end:
+    SSL_free(serverssl);
+    SSL_free(clientssl);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(cctx);
+    return testresult;
+
+}
 #endif /* OPENSSL_NO_TLS1_3 */
 
 static int clntaddoldcb = 0;
@@ -3262,6 +3351,7 @@ int setup_tests(void)
     ADD_TEST(test_ciphersuite_change);
     ADD_TEST(test_tls13_psk);
     ADD_ALL_TESTS(test_custom_exts, 5);
+    ADD_TEST(test_stateless);
 #else
     ADD_ALL_TESTS(test_custom_exts, 3);
 #endif
diff --git a/util/libssl.num b/util/libssl.num
index 243c1fb..abaa5bf 100644
--- a/util/libssl.num
+++ b/util/libssl.num
@@ -473,3 +473,4 @@ DTLS_set_timer_cb                       473	1_1_1	EXIST::FUNCTION:
 SSL_CTX_set_tlsext_max_fragment_length  474	1_1_1	EXIST::FUNCTION:
 SSL_set_tlsext_max_fragment_length      475	1_1_1	EXIST::FUNCTION:
 SSL_SESSION_get_max_fragment_length     476	1_1_1	EXIST::FUNCTION:
+SSL_stateless                           477	1_1_1	EXIST::FUNCTION:


More information about the openssl-commits mailing list