Add support for the psk_key_exchange_modes extension

This is required for the later addition of resumption support.

Reviewed-by: Rich Salz <rsalz@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/2259)
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index e95fdb4..708d59b 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2281,6 +2281,7 @@
 # define SSL_F_TLS_CONSTRUCT_CTOS_KEY_SHARE               470
 # define SSL_F_TLS_CONSTRUCT_CTOS_NPN                     471
 # define SSL_F_TLS_CONSTRUCT_CTOS_PADDING                 472
+# define SSL_F_TLS_CONSTRUCT_CTOS_PSK_KEX_MODES           509
 # define SSL_F_TLS_CONSTRUCT_CTOS_RENEGOTIATE             473
 # define SSL_F_TLS_CONSTRUCT_CTOS_SCT                     474
 # define SSL_F_TLS_CONSTRUCT_CTOS_SERVER_NAME             475
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 707fb96..65569bd 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -178,6 +178,7 @@
 /* As defined for TLS1.3 */
 # define TLSEXT_TYPE_key_share                   40
 # define TLSEXT_TYPE_supported_versions          43
+# define TLSEXT_TYPE_psk_kex_modes               45
 
 /* Temporary extension type */
 # define TLSEXT_TYPE_renegotiate                 0xff01
diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c
index 099af01..2d9401a 100644
--- a/ssl/ssl_err.c
+++ b/ssl/ssl_err.c
@@ -300,6 +300,8 @@
     {ERR_FUNC(SSL_F_TLS_CONSTRUCT_CTOS_NPN), "tls_construct_ctos_npn"},
     {ERR_FUNC(SSL_F_TLS_CONSTRUCT_CTOS_PADDING),
      "tls_construct_ctos_padding"},
+    {ERR_FUNC(SSL_F_TLS_CONSTRUCT_CTOS_PSK_KEX_MODES),
+     "tls_construct_ctos_psk_kex_modes"},
     {ERR_FUNC(SSL_F_TLS_CONSTRUCT_CTOS_RENEGOTIATE),
      "tls_construct_ctos_renegotiate"},
     {ERR_FUNC(SSL_F_TLS_CONSTRUCT_CTOS_SCT), "tls_construct_ctos_sct"},
diff --git a/ssl/ssl_locl.h b/ssl/ssl_locl.h
index ef525fe..17b377f 100644
--- a/ssl/ssl_locl.h
+++ b/ssl/ssl_locl.h
@@ -1105,6 +1105,9 @@
          */
         unsigned char *npn;
         size_t npn_len;
+
+        /* The selected PSK key exchange mode */
+        int psk_kex_mode;
     } ext;
 
     /*-
@@ -1680,6 +1683,7 @@
     TLSEXT_IDX_signed_certificate_timestamp,
     TLSEXT_IDX_extended_master_secret,
     TLSEXT_IDX_supported_versions,
+    TLSEXT_IDX_psk_kex_modes,
     TLSEXT_IDX_key_share,
     TLSEXT_IDX_cryptopro_bug,
     TLSEXT_IDX_padding
@@ -1711,6 +1715,17 @@
 #define TLSEXT_SIGALG_gostr34102012_512_gostr34112012_512       0xefef
 #define TLSEXT_SIGALG_gostr34102001_gostr3411                   0xeded
 
+/* Known PSK key exchange modes */
+#define TLSEXT_KEX_MODE_KE                                      0x00
+#define TLSEXT_KEX_MODE_KE_DHE                                  0x01
+
+/*
+ * Internal representations of key exchange modes
+ */
+#define TLSEXT_KEX_MODE_FLAG_NONE                               0
+#define TLSEXT_KEX_MODE_FLAG_KE                                 1
+#define TLSEXT_KEX_MODE_FLAG_KE_DHE                             2
+
 #define SIGID_IS_PSS(sigid) ((sigid) == TLSEXT_SIGALG_rsa_pss_sha256 \
                              || (sigid) == TLSEXT_SIGALG_rsa_pss_sha384 \
                              || (sigid) == TLSEXT_SIGALG_rsa_pss_sha512)
diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index ee5b0d7..dc99201 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -35,6 +35,7 @@
 static int init_etm(SSL *s, unsigned int context);
 static int init_ems(SSL *s, unsigned int context);
 static int final_ems(SSL *s, unsigned int context, int sent, int *al);
+static int init_psk_kex_modes(SSL *s, unsigned int context);
 #ifndef OPENSSL_NO_SRTP
 static int init_srtp(SSL *s, unsigned int context);
 #endif
@@ -235,6 +236,13 @@
         NULL, NULL, NULL, tls_construct_ctos_supported_versions, NULL
     },
     {
+        /* Must be before key_share */
+        TLSEXT_TYPE_psk_kex_modes,
+        EXT_CLIENT_HELLO | EXT_TLS_IMPLEMENTATION_ONLY | EXT_TLS1_3_ONLY,
+        init_psk_kex_modes, tls_parse_ctos_psk_kex_modes, NULL, NULL,
+        tls_construct_ctos_psk_kex_modes, NULL
+    },
+    {
         /*
          * Must be in this list after supported_groups. We need that to have
          * been parsed before we do this one.
@@ -938,3 +946,10 @@
 
     return 1;
 }
+
+static int init_psk_kex_modes(SSL *s, unsigned int context)
+{
+    s->ext.psk_kex_mode = TLSEXT_KEX_MODE_FLAG_NONE;
+
+    return 1;
+}
diff --git a/ssl/statem/extensions_clnt.c b/ssl/statem/extensions_clnt.c
index 1e2cc3f..57f6564 100644
--- a/ssl/statem/extensions_clnt.c
+++ b/ssl/statem/extensions_clnt.c
@@ -494,6 +494,33 @@
     return 1;
 }
 
+/*
+ * Construct a psk_kex_modes extension. We only have two modes we know about
+ * at this stage, so we send both.
+ */
+int tls_construct_ctos_psk_kex_modes(SSL *s, WPACKET *pkt, X509 *x,
+                                     size_t chainidx, int *al)
+{
+#ifndef OPENSSL_NO_TLS1_3
+    /*
+     * TODO(TLS1.3): Do we want this list to be configurable? For now we always
+     * just send both supported modes
+     */
+    if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_psk_kex_modes)
+            || !WPACKET_start_sub_packet_u16(pkt)
+            || !WPACKET_start_sub_packet_u8(pkt)
+            || !WPACKET_put_bytes_u8(pkt, TLSEXT_KEX_MODE_KE_DHE)
+            || !WPACKET_put_bytes_u8(pkt, TLSEXT_KEX_MODE_KE)
+            || !WPACKET_close(pkt)
+            || !WPACKET_close(pkt)) {
+        SSLerr(SSL_F_TLS_CONSTRUCT_CTOS_PSK_KEX_MODES, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
+#endif
+
+    return 1;
+}
+
 int tls_construct_ctos_key_share(SSL *s, WPACKET *pkt, X509 *x, size_t chainidx,
                                  int *al)
 {
diff --git a/ssl/statem/extensions_srvr.c b/ssl/statem/extensions_srvr.c
index 357b3b7..8ee2928 100644
--- a/ssl/statem/extensions_srvr.c
+++ b/ssl/statem/extensions_srvr.c
@@ -479,6 +479,35 @@
 #endif
 
 /*
+ * Process a psk_kex_modes extension received in the ClientHello. |pkt| contains
+ * the raw PACKET data for the extension. Returns 1 on success or 0 on failure.
+ * If a failure occurs then |*al| is set to an appropriate alert value.
+ */
+int tls_parse_ctos_psk_kex_modes(SSL *s, PACKET *pkt, X509 *x, size_t chainidx,
+                                 int *al)
+{
+#ifndef OPENSSL_NO_TLS1_3
+    PACKET psk_kex_modes;
+    unsigned int mode;
+
+    if (!PACKET_as_length_prefixed_1(pkt, &psk_kex_modes)
+            || PACKET_remaining(&psk_kex_modes) == 0) {
+        *al = SSL_AD_DECODE_ERROR;
+        return 0;
+    }
+
+    while (PACKET_get_1(&psk_kex_modes, &mode)) {
+        if (mode == TLSEXT_KEX_MODE_KE_DHE)
+            s->ext.psk_kex_mode |= TLSEXT_KEX_MODE_FLAG_KE_DHE;
+        else if (mode == TLSEXT_KEX_MODE_KE)
+            s->ext.psk_kex_mode |= TLSEXT_KEX_MODE_FLAG_KE;
+    }
+#endif
+
+    return 1;
+}
+
+/*
  * Process a key_share extension received in the ClientHello. |pkt| contains
  * the raw PACKET data for the extension. Returns 1 on success or 0 on failure.
  * If a failure occurs then |*al| is set to an appropriate alert value.
diff --git a/ssl/statem/statem_locl.h b/ssl/statem/statem_locl.h
index 51adfc6..be3167c 100644
--- a/ssl/statem/statem_locl.h
+++ b/ssl/statem/statem_locl.h
@@ -201,6 +201,8 @@
 int tls_parse_ctos_key_share(SSL *s, PACKET *pkt, X509 *x, size_t chainidx,
                              int *al);
 int tls_parse_ctos_ems(SSL *s, PACKET *pkt, X509 *x, size_t chainidx, int *al);
+int tls_parse_ctos_psk_kex_modes(SSL *s, PACKET *pkt, X509 *x, size_t chainidx,
+                                 int *al);
 
 int tls_construct_stoc_renegotiate(SSL *s, WPACKET *pkt, X509 *x,
                                    size_t chainidx, int *al);
@@ -285,6 +287,8 @@
                                           size_t chainidx, int *al);
 int tls_construct_ctos_key_share(SSL *s, WPACKET *pkt, X509 *x, size_t chainidx,
                                  int *al);
+int tls_construct_ctos_psk_kex_modes(SSL *s, WPACKET *pkt, X509 *x,
+                                     size_t chainidx, int *al);
 int tls_construct_ctos_padding(SSL *s, WPACKET *pkt, X509 *x, size_t chainidx,
                                int *al);
 int tls_parse_stoc_renegotiate(SSL *s, PACKET *pkt, X509 *x, size_t chainidx,
diff --git a/ssl/t1_trce.c b/ssl/t1_trce.c
index b3b6e18..9da8f75 100644
--- a/ssl/t1_trce.c
+++ b/ssl/t1_trce.c
@@ -449,6 +449,7 @@
     {TLSEXT_TYPE_server_authz, "server_authz"},
     {TLSEXT_TYPE_cert_type, "cert_type"},
     {TLSEXT_TYPE_key_share, "key_share"},
+    {TLSEXT_TYPE_psk_kex_modes, "psk_key_exchange_modes"},
     {TLSEXT_TYPE_supported_groups, "supported_groups"},
     {TLSEXT_TYPE_ec_point_formats, "ec_point_formats"},
     {TLSEXT_TYPE_srp, "srp"},
@@ -540,6 +541,11 @@
     {66, "ecdsa_fixed_ecdh"}
 };
 
+static ssl_trace_tbl ssl_psk_kex_modes_tbl[] = {
+    {TLSEXT_KEX_MODE_KE, "psk_ke"},
+    {TLSEXT_KEX_MODE_KE_DHE, "psk_dhe_ke"}
+};
+
 static ssl_trace_tbl ssl_crypto_tbl[] = {
     {TLS1_RT_CRYPTO_PREMASTER, "Premaster Secret"},
     {TLS1_RT_CRYPTO_CLIENT_RANDOM, "Client Random"},
@@ -760,6 +766,15 @@
         return ssl_trace_list(bio, indent + 2, ext + 1, xlen, 2,
                               ssl_version_tbl);
 
+    case TLSEXT_TYPE_psk_kex_modes:
+        if (extlen < 1)
+            return 0;
+        xlen = ext[0];
+        if (extlen != xlen + 1)
+            return 0;
+        return ssl_trace_list(bio, indent + 2, ext + 1, xlen, 1,
+                              ssl_psk_kex_modes_tbl);
+
     default:
         BIO_dump_indent(bio, (const char *)ext, extlen, indent + 2);
     }
diff --git a/test/recipes/70-test_tls13messages.t b/test/recipes/70-test_tls13messages.t
index d6512b5..fb69e7c 100755
--- a/test/recipes/70-test_tls13messages.t
+++ b/test/recipes/70-test_tls13messages.t
@@ -81,6 +81,8 @@
         checkhandshake::DEFAULT_EXTENSIONS],
     [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_SUPPORTED_VERSIONS,
         checkhandshake::DEFAULT_EXTENSIONS],
+    [TLSProxy::Message::MT_CLIENT_HELLO, TLSProxy::Message::EXT_PSK_KEX_MODES,
+        checkhandshake::DEFAULT_EXTENSIONS],
 
     [TLSProxy::Message::MT_SERVER_HELLO, TLSProxy::Message::EXT_KEY_SHARE,
         checkhandshake::DEFAULT_EXTENSIONS],
diff --git a/util/TLSProxy/Message.pm b/util/TLSProxy/Message.pm
index 7cb7b28..99c3689 100644
--- a/util/TLSProxy/Message.pm
+++ b/util/TLSProxy/Message.pm
@@ -75,6 +75,7 @@
     EXT_SESSION_TICKET => 35,
     EXT_KEY_SHARE => 40,
     EXT_SUPPORTED_VERSIONS => 43,
+    EXT_PSK_KEX_MODES => 45,
     EXT_RENEGOTIATE => 65281,
     EXT_NPN => 13172,
     # This extension is an unofficial extension only ever written by OpenSSL