]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Enable DTLS protocol negotiation
authorNikos Mavrogiannopoulos <nmav@redhat.com>
Wed, 27 Jul 2016 09:58:11 +0000 (11:58 +0200)
committerNikos Mavrogiannopoulos <nmav@redhat.com>
Wed, 21 Sep 2016 15:50:27 +0000 (17:50 +0200)
The new negotiation is as follows: If the client's X-DTLS-CipherSuite
contains the "PSK-NEGOTIATE" keyword, the server will reply with
"X-DTLS-CipherSuite: PSK-NEGOTIATE" and will enable DTLS-PSK negotiation on the
DTLS channel.

That change utilizes the value provided by sever's X-DTLS-App-ID header
and sets that value to a TLS extension on client hello. The
extension used is defined on (draft-mavrogiannopoulos-app-id).

Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
cstp.c
gnutls-dtls.c
gnutls.c
openconnect-internal.h
openssl-dtls.c

diff --git a/cstp.c b/cstp.c
index bf8cac98b2fccc8a4e139d730805971194b36f6d..42b70c85f28ff31bb5ec32c2137b71244ea439bd 100644 (file)
--- a/cstp.c
+++ b/cstp.c
@@ -188,6 +188,26 @@ static void append_mobile_headers(struct openconnect_info *vpninfo, struct oc_te
        }
 }
 
+static int parse_hex_val(const char *str, unsigned char *storage, unsigned int max_storage_len, int *changed)
+{
+       int len = strlen(str);
+       unsigned i;
+
+       if (len % 2 == 1 || len > 2*max_storage_len) {
+               return -EINVAL;
+       }
+
+       for (i = 0; i < len; i += 2) {
+               unsigned char c = unhex(str + i);
+               if (storage[i/2] != c) {
+                       storage[i/2] = c;
+                       *changed = 1;
+               }
+       }
+
+       return len/2;
+}
+
 static int start_cstp_connection(struct openconnect_info *vpninfo)
 {
        struct oc_text_buf *reqbuf;
@@ -242,6 +262,9 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                buf_append(reqbuf, "X-CSTP-Full-IPv6-Capability: true\r\n");
 #ifdef HAVE_DTLS
        if (vpninfo->dtls_state != DTLS_DISABLED) {
+               /* The X-DTLS-Master-Secret is only used for the legacy protocol negotation
+                * which required the client to send explicitly the secret. In the PSK-NEGOTIATE
+                * method, the master secret is implicitly agreed on */
                buf_append(reqbuf, "X-DTLS-Master-Secret: ");
                for (i = 0; i < sizeof(vpninfo->dtls_secret); i++) {
                        buf_append(reqbuf, "%02X", vpninfo->dtls_secret[i]);
@@ -381,25 +404,39 @@ static int start_cstp_connection(struct openconnect_info *vpninfo)
                                        mtu = dtlsmtu;
                        } else if (!strcmp(buf + 7, "Session-ID")) {
                                int dtls_sessid_changed = 0;
+                               int vsize;
 
-                               if (strlen(colon) != 64) {
+                               vsize = parse_hex_val(colon, vpninfo->dtls_session_id, sizeof(vpninfo->dtls_session_id), &dtls_sessid_changed);
+                               if (vsize != 32) {
                                        vpn_progress(vpninfo, PRG_ERR,
-                                                    _("X-DTLS-Session-ID not 64 characters; is: \"%s\"\n"),
-                                                    colon);
+                                            _("X-DTLS-Session-ID not 64 characters; is: \"%s\"\n"),
+                                               colon);
                                        vpninfo->dtls_attempt_period = 0;
                                        return -EINVAL;
                                }
-                               for (i = 0; i < 64; i += 2) {
-                                       unsigned char c = unhex(colon + i);
-                                       if (vpninfo->dtls_session_id[i/2] != c) {
-                                               vpninfo->dtls_session_id[i/2] = c;
-                                               dtls_sessid_changed = 1;
-                                       }
-                               }
+
                                sessid_found = 1;
 
                                if (dtls_sessid_changed && vpninfo->dtls_state > DTLS_SLEEPING)
                                        vpninfo->dtls_need_reconnect = 1;
+                       } else if (!strcmp(buf + 7, "App-ID")) {
+                               int dtls_appid_changed = 0;
+                               int vsize;
+
+                               vsize = parse_hex_val(colon, vpninfo->dtls_app_id, sizeof(vpninfo->dtls_app_id), &dtls_appid_changed);
+                               if (vsize <= 0) {
+                                       vpn_progress(vpninfo, PRG_ERR,
+                                            _("X-DTLS-Session-ID is invalid; is: \"%s\"\n"),
+                                               colon);
+                                       vpninfo->dtls_attempt_period = 0;
+                                       return -EINVAL;
+                               }
+
+                               vpninfo->dtls_app_id_size = vsize;
+                               sessid_found = 1;
+
+                               if (dtls_appid_changed && vpninfo->dtls_state > DTLS_SLEEPING)
+                                       vpninfo->dtls_need_reconnect = 1;
                        } else if (!strcmp(buf + 7, "Content-Encoding")) {
                                if (!strcmp(colon, "lzs"))
                                        vpninfo->dtls_compr = COMPR_LZS;
index 541f186f2c6026d108fe2148b465360f6736dd8e..032e522a9c604aa59df4c7951c723394818bc240 100644 (file)
@@ -101,6 +101,9 @@ void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *b
        gnutls_priority_t cache;
        uint32_t used = 0;
 
+       buf_append(buf, "PSK-NEGOTIATE");
+       first = 0;
+
        ret = gnutls_priority_init(&cache, vpninfo->gnutls_prio, NULL);
        if (ret < 0) {
                buf->error = -EIO;
@@ -133,6 +136,113 @@ void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *b
 }
 #endif
 
+/* This enables a DTLS protocol negotiation. The new negotiation is as follows:
+ *
+ * If the client's X-DTLS-CipherSuite contains the "PSK-NEGOTIATE" keyword,
+ * the server will reply with "X-DTLS-CipherSuite: PSK-NEGOTIATE" and will
+ * enable DTLS-PSK negotiation on the DTLS channel. This allows the protocol
+ * to use new DTLS versions, as well as new DTLS ciphersuites, as long as
+ * they are also permitted by the system crypto policy in use.
+ *
+ * That change still requires to client to pretend it is resuming by setting
+ * in the TLS ClientHello the session ID provided by the X-DTLS-Session-ID
+ * header. That is, because there is no TLS extension we can use to set an
+ * identifier in the client hello (draft-jay-tls-psk-identity-extension
+ * could be used in the future). The session is not actually resumed.
+ */
+static int start_dtls_psk_handshake(struct openconnect_info *vpninfo, int dtls_fd)
+{
+       gnutls_session_t dtls_ssl;
+       gnutls_datum_t key;
+       struct oc_text_buf *prio;
+       int err;
+
+       prio = buf_alloc();
+       buf_append(prio, "%s:-VERS-TLS-ALL:+VERS-DTLS-ALL:-KX-ALL:+PSK", vpninfo->gnutls_prio);
+       if (buf_error(prio)) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to generate DTLS priority string\n"));
+               vpninfo->dtls_attempt_period = 0;
+               return buf_free(prio);
+       }
+
+
+       err = gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to initialize DTLS: %s\n"),
+                            gnutls_strerror(err));
+               goto fail;
+       }
+       gnutls_session_set_ptr(dtls_ssl, (void *) vpninfo);
+
+       err = gnutls_priority_set_direct(dtls_ssl, prio->data, NULL);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to set DTLS priority: '%s': %s\n"),
+                            prio->data, gnutls_strerror(err));
+               goto fail;
+       }
+
+       gnutls_transport_set_ptr(dtls_ssl,
+                                (gnutls_transport_ptr_t)(intptr_t)dtls_fd);
+
+       /* set PSK credentials */
+       err = gnutls_psk_allocate_client_credentials(&vpninfo->psk_cred);
+       if (err < 0) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to allocate credentials: %s\n"),
+                            gnutls_strerror(err));
+               goto fail;
+       }
+
+       /* generate key */
+       /* we should have used gnutls_prf_rfc5705() but since we don't use
+        * the RFC5705 context, the output is identical with gnutls_prf(). The
+        * latter is available in much earlier versions of gnutls. */
+       err = gnutls_prf(vpninfo->https_sess, PSK_LABEL_SIZE, PSK_LABEL,
+                        0, 0, 0, PSK_KEY_SIZE, (char*)vpninfo->dtls_secret);
+       if (err < 0) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to generate DTLS key: %s\n"),
+                            gnutls_strerror(err));
+               goto fail;
+       }
+
+       key.data = vpninfo->dtls_secret;
+       key.size = PSK_KEY_SIZE;
+
+       /* we set an arbitrary username here. We cannot take advantage of the
+        * username field to send our ID to the server, since the username in TLS-PSK
+        * is sent after the server-hello. */
+       err = gnutls_psk_set_client_credentials(vpninfo->psk_cred, "psk", &key, 0);
+       if (err < 0) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to set DTLS key: %s\n"),
+                            gnutls_strerror(err));
+               goto fail;
+       }
+
+       err = gnutls_credentials_set(dtls_ssl, GNUTLS_CRD_PSK, vpninfo->psk_cred);
+       if (err) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to set DTLS PSK credentials: %s\n"),
+                            gnutls_strerror(err));
+               goto fail;
+       }
+
+       buf_free(prio);
+       vpninfo->dtls_ssl = dtls_ssl;
+       return 0;
+ fail:
+       buf_free(prio);
+       gnutls_deinit(dtls_ssl);
+       gnutls_psk_free_client_credentials(vpninfo->psk_cred);
+       vpninfo->psk_cred = NULL;
+       vpninfo->dtls_attempt_period = 0;
+       return -EINVAL;
+}
+
 int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
 {
        gnutls_session_t dtls_ssl;
@@ -140,6 +250,9 @@ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
        int err;
        int cipher;
 
+       if (strcmp(vpninfo->dtls_cipher, "PSK-NEGOTIATE") == 0)
+               return start_dtls_psk_handshake(vpninfo, dtls_fd);
+
        for (cipher = 0; cipher < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); cipher++) {
                if (gnutls_check_version(gnutls_dtls_ciphers[cipher].min_gnutls_version) == NULL)
                        continue;
@@ -154,6 +267,8 @@ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
 
  found_cipher:
        gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK);
+       gnutls_session_set_ptr(dtls_ssl, (void *) vpninfo);
+
        err = gnutls_priority_set_direct(dtls_ssl,
                                         gnutls_dtls_ciphers[cipher].prio,
                                         NULL);
@@ -266,4 +381,9 @@ void dtls_shutdown(struct openconnect_info *vpninfo)
 void dtls_ssl_free(struct openconnect_info *vpninfo)
 {
        gnutls_deinit(vpninfo->dtls_ssl);
+
+       if (vpninfo->psk_cred) {
+               gnutls_psk_free_client_credentials(vpninfo->psk_cred);
+               vpninfo->psk_cred = NULL;
+       }
 }
index 88835bd60b62ea1e50ac66b6e4d0512402f08936..ecd5cf7fa505eb73b043d01489bf80b39f766a28 100644 (file)
--- a/gnutls.c
+++ b/gnutls.c
@@ -2539,6 +2539,117 @@ void openconnect_close_https(struct openconnect_info *vpninfo, int final)
        }
 }
 
+#if GNUTLS_VERSION_NUMBER >= 0x030400
+static int ext_recv_client(gnutls_session_t sess, const unsigned char *buf, size_t buflen)
+{
+       /* we shouldn't have received that */
+       return 0;
+}
+
+static int ext_send_client(gnutls_session_t sess, gnutls_buffer_t extdata)
+{
+       struct openconnect_info *vpninfo = gnutls_session_get_ptr(sess);
+
+       if (vpninfo->dtls_ssl != sess)
+               return 0;
+
+       if (vpninfo->dtls_app_id_size > 0) {
+               uint8_t size = vpninfo->dtls_app_id_size;
+               int ret;
+
+               ret = gnutls_buffer_append_data(extdata, &size, 1);
+               if (ret < 0)
+                       return ret;
+
+               ret = gnutls_buffer_append_data(extdata, vpninfo->dtls_app_id, vpninfo->dtls_app_id_size);
+               if (ret < 0)
+                       return ret;
+
+               return vpninfo->dtls_app_id_size + 1;
+       }
+
+       return 0;
+}
+#else
+
+/* previously to 3.4.0 we can only use internal-but-exported APIs
+ */
+typedef int (*gnutls_ext_recv_func) (gnutls_session_t session,
+                                    const unsigned char *data,
+                                    size_t len);
+typedef int (*gnutls_ext_send_func) (gnutls_session_t session,
+                                    void* extdata);
+int _gnutls_buffer_append_data(void *, const void *data, size_t data_size);
+
+typedef struct {
+       const char *name;
+       uint16_t type;
+       int parse_type;
+
+       /* this function must return 0 when Not Applicable
+        * size of extension data if ok
+        * < 0 on other error.
+        */
+       gnutls_ext_recv_func recv_func;
+
+       /* this function must return 0 when Not Applicable
+        * size of extension data if ok
+        * GNUTLS_E_INT_RET_0 if extension data size is zero
+        * < 0 on other error.
+        */
+       gnutls_ext_send_func send_func;
+
+       void *deinit_func;      /* this will be called to deinitialize
+                                                        * internal data 
+                                                        */
+       void *pack_func;        /* packs internal data to machine independent format */
+       void *unpack_func;      /* unpacks internal data */
+       void *epoch_func;       /* called after the handshake is finished */
+} extension_entry_st;
+
+int _gnutls_ext_register(extension_entry_st *);
+
+static int ext_recv_client(gnutls_session_t sess, const unsigned char *buf, size_t buflen)
+{
+       /* we shouldn't have received that */
+       return 0;
+}
+static int ext_send_client(gnutls_session_t sess, void *extdata)
+{
+       struct openconnect_info *vpninfo = gnutls_session_get_ptr(sess);
+
+       if (vpninfo->dtls_ssl != sess)
+               return 0;
+
+       if (vpninfo->dtls_app_id_size > 0) {
+               uint8_t size = vpninfo->dtls_app_id_size;
+               int ret;
+
+               ret = _gnutls_buffer_append_data(extdata, &size, 1);
+               if (ret < 0)
+                       return ret;
+               ret = _gnutls_buffer_append_data(extdata, vpninfo->dtls_app_id, vpninfo->dtls_app_id_size);
+               if (ret < 0)
+                       return ret;
+
+               return vpninfo->dtls_app_id_size + 1;
+       }
+
+       return 0;
+}
+
+extension_entry_st ext_app_id  = {
+       .name = "app-id",
+       .type = DTLS_APP_ID_EXT,
+       .parse_type = 2,
+       .recv_func = ext_recv_client,
+       .send_func = ext_send_client,
+       .pack_func = NULL,
+       .unpack_func = NULL,
+       .deinit_func = NULL
+};
+#endif
+
 int openconnect_init_ssl(void)
 {
 #ifdef _WIN32
@@ -2549,6 +2660,12 @@ int openconnect_init_ssl(void)
        if (gnutls_global_init())
                return -EIO;
 
+#if GNUTLS_VERSION_NUMBER >= 0x030400
+       gnutls_ext_register("APP-ID", DTLS_APP_ID_EXT, GNUTLS_EXT_TLS, ext_recv_client, ext_send_client, NULL, NULL, NULL);
+#else
+       _gnutls_ext_register(&ext_app_id);
+#endif
+
        return 0;
 }
 
index b24342030e1dfe3548a0a857fd35c887aedc6670..5a6d60941c0002a27e750b3754ae9eff5ffdc0d8 100644 (file)
@@ -175,6 +175,8 @@ struct pkt {
 #endif
 #define COMPR_ALL      (COMPR_STATELESS | COMPR_DEFLATE)
 
+#define DTLS_APP_ID_EXT 48018
+
 struct keepalive_info {
        int dpd;
        int keepalive;
@@ -483,6 +485,7 @@ struct openconnect_info {
 #elif defined(OPENCONNECT_GNUTLS)
        gnutls_session_t https_sess;
        gnutls_certificate_credentials_t https_cred;
+       gnutls_psk_client_credentials_t psk_cred;
        char local_cert_md5[MD5_SIZE * 2 + 1]; /* For CSD */
        char gnutls_prio[256];
 #ifdef HAVE_TROUSERS
@@ -546,6 +549,8 @@ struct openconnect_info {
        struct keepalive_info dtls_times;
        unsigned char dtls_session_id[32];
        unsigned char dtls_secret[48];
+       unsigned char dtls_app_id[32];
+       unsigned dtls_app_id_size;
 
        char *dtls_cipher;
        char *vpnc_script;
@@ -664,6 +669,11 @@ struct openconnect_info {
 #define read_fd_monitored(_v, _n) FD_ISSET(_v->_n##_fd, &_v->_select_rfds)
 #endif
 
+/* Key material for DTLS-PSK */
+#define PSK_LABEL "EXPORTER-openconnect-psk"
+#define PSK_LABEL_SIZE sizeof(PSK_LABEL)-1
+#define PSK_KEY_SIZE 32
+
 /* Packet types */
 
 #define AC_PKT_DATA            0       /* Uncompressed data */
index 96ed13b1529f42715d4662638c70059bff3b6eef..1905ef5ad3b9b4019432a926d1c320d0e85c8256 100644 (file)
@@ -176,6 +176,58 @@ static SSL_SESSION *generate_dtls_session(struct openconnect_info *vpninfo,
 }
 #endif
 
+#if defined (HAVE_DTLS12) && !defined(OPENSSL_NO_PSK)
+static unsigned int psk_callback(SSL *ssl, const char *hint, char *identity,
+                                unsigned int max_identity_len, unsigned char *psk,
+                                unsigned int max_psk_len)
+{
+       struct openconnect_info *vpninfo = SSL_get_app_data(ssl);
+
+       if (!vpninfo || max_identity_len < 4 || max_psk_len < PSK_KEY_SIZE)
+               return 0;
+       vpn_progress(vpninfo, PRG_TRACE, _("PSK callback\n"));
+
+       snprintf(identity, max_psk_len, "psk");
+
+       memcpy(psk, vpninfo->dtls_secret, PSK_KEY_SIZE);
+       return PSK_KEY_SIZE;
+}
+
+static int pskident_add(SSL *s, unsigned int ext_type, const unsigned char **out, size_t *outlen,
+                       int *al, void *add_arg)
+{
+       struct openconnect_info *vpninfo = add_arg;
+       unsigned char *buf;
+
+       buf = malloc(vpninfo->dtls_app_id_size + 1);
+       if (!buf) {
+               vpn_progress(vpninfo, PRG_ERR,
+                            _("Failed to create app-identity extension for OpenSSL\n"));
+               return 0;
+       }
+
+       buf[0] = vpninfo->dtls_app_id_size;
+       memcpy(&buf[1], vpninfo->dtls_app_id, vpninfo->dtls_app_id_size);
+
+       *out = buf;
+       *outlen = vpninfo->dtls_app_id_size + 1;
+
+       return 1;
+}
+
+static void pskident_free(SSL *s, unsigned int ext_type, const unsigned char *out, void *add_arg)
+{
+       free((void *)out);
+}
+
+static int pskident_parse(SSL *s, unsigned int ext_type, const unsigned char *in, size_t inlen,
+                         int *al, void *parse_arg)
+{
+       return 1;
+}
+
+#endif
+
 int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
 {
        STACK_OF(SSL_CIPHER) *ciphers;
@@ -193,6 +245,10 @@ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
        } else if (!strcmp(cipher, "OC-DTLS1_2-AES256-GCM")) {
                dtlsver = DTLS1_2_VERSION;
                cipher = "AES256-GCM-SHA384";
+#ifndef OPENSSL_NO_PSK
+       } else if (!strcmp(cipher, "PSK-NEGOTIATE")) {
+               dtlsver = 0; /* Let it negotiate */
+#endif
        }
 #endif
 
@@ -215,22 +271,45 @@ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
                        vpninfo->dtls_attempt_period = 0;
                        return -EINVAL;
                }
+               if (dtlsver) {
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
-               if (dtlsver == DTLS1_BAD_VER)
-                       SSL_CTX_set_options(vpninfo->dtls_ctx, SSL_OP_CISCO_ANYCONNECT);
+                       if (dtlsver == DTLS1_BAD_VER)
+                               SSL_CTX_set_options(vpninfo->dtls_ctx, SSL_OP_CISCO_ANYCONNECT);
 #else
-               if (!SSL_CTX_set_min_proto_version(vpninfo->dtls_ctx, dtlsver) ||
-                   !SSL_CTX_set_max_proto_version(vpninfo->dtls_ctx, dtlsver)) {
-                       vpn_progress(vpninfo, PRG_ERR,
-                                    _("Set DTLS CTX version failed\n"));
-                       openconnect_report_ssl_errors(vpninfo);
-                       SSL_CTX_free(vpninfo->dtls_ctx);
-                       vpninfo->dtls_ctx = NULL;
-                       vpninfo->dtls_attempt_period = 0;
-                       return -EINVAL;
-               }
+                       if (!SSL_CTX_set_min_proto_version(vpninfo->dtls_ctx, dtlsver) ||
+                           !SSL_CTX_set_max_proto_version(vpninfo->dtls_ctx, dtlsver)) {
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Set DTLS CTX version failed\n"));
+                               openconnect_report_ssl_errors(vpninfo);
+                               SSL_CTX_free(vpninfo->dtls_ctx);
+                               vpninfo->dtls_ctx = NULL;
+                               vpninfo->dtls_attempt_period = 0;
+                               return -EINVAL;
+                       }
 #endif
-
+#if defined (HAVE_DTLS12) && !defined(OPENSSL_NO_PSK)
+               } else {
+                       SSL_CTX_set_psk_client_callback(vpninfo->dtls_ctx, psk_callback);
+                       /* For PSK we override the DTLS master secret with one derived
+                        * from the HTTPS session. */
+                       if (!SSL_export_keying_material(vpninfo->https_ssl,
+                                                       vpninfo->dtls_secret, PSK_KEY_SIZE,
+                                                       PSK_LABEL, PSK_LABEL_SIZE, NULL, 0, 0)) {
+                               vpn_progress(vpninfo, PRG_ERR,
+                                            _("Failed to generate DTLS key\n"));
+                               openconnect_report_ssl_errors(vpninfo);
+                               SSL_CTX_free(vpninfo->dtls_ctx);
+                               vpninfo->dtls_ctx = NULL;
+                               vpninfo->dtls_attempt_period = 0;
+                               return -EINVAL;
+                       }
+                       SSL_CTX_add_client_custom_ext(vpninfo->dtls_ctx, DTLS_APP_ID_EXT,
+                                                     pskident_add, pskident_free, vpninfo,
+                                                     pskident_parse, vpninfo);
+                       /* For SSL_CTX_set_cipher_list() */
+                       cipher = "PSK";
+#endif
+               }
                /* If we don't readahead, then we do short reads and throw
                   away the tail of data packets. */
                SSL_CTX_set_read_ahead(vpninfo->dtls_ctx, 1);
@@ -247,9 +326,10 @@ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
 
        dtls_ssl = SSL_new(vpninfo->dtls_ctx);
        SSL_set_connect_state(dtls_ssl);
+       SSL_set_app_data(dtls_ssl, vpninfo);
 
        ciphers = SSL_get_ciphers(dtls_ssl);
-       if (sk_SSL_CIPHER_num(ciphers) != 1) {
+       if (dtlsver != 0 && sk_SSL_CIPHER_num(ciphers) != 1) {
                vpn_progress(vpninfo, PRG_ERR, _("Not precisely one DTLS cipher\n"));
                SSL_CTX_free(vpninfo->dtls_ctx);
                SSL_free(dtls_ssl);
@@ -258,6 +338,17 @@ int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd)
                return -EINVAL;
        }
 
+#if defined (HAVE_DTLS12) && !defined(OPENSSL_NO_PSK)
+       /* In the PSK case, OpenSSL 1.1+ will negotiate properly regardless of
+        * this. But OpenSSL = 1.0.2 will do precisely the version requested
+        * here. Which we don't want because we *want* it to negotiate. The
+        * session we're pretending to resume is *only* to let the server know
+        * who we are, since draft-jay-tls-psk-identify-extension isn't here
+        * yet. */
+       if (!dtlsver)
+               dtlsver = DTLS1_2_VERSION;
+#endif
+
        /* We're going to "resume" a session which never existed. Fake it... */
        dtls_session = generate_dtls_session(vpninfo, dtlsver,
                                             sk_SSL_CIPHER_value(ciphers, 0));
@@ -418,15 +509,18 @@ void dtls_shutdown(struct openconnect_info *vpninfo)
 
 void dtls_ssl_free(struct openconnect_info *vpninfo)
 {
+       /* We are only ever called when this is non-NULL */
        SSL_free(vpninfo->dtls_ssl);
 }
 
 void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *buf)
 {
 #ifdef HAVE_DTLS12
-       buf_append(buf, "OC-DTLS1_2-AES256-GCM:OC-DTLS1_2-AES128-GCM:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA");
-#else
-       buf_append(buf, "AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA");
+#ifndef OPENSSL_NO_PSK
+       buf_append(buf, "PSK-NEGOTIATE:");
 #endif
+       buf_append(buf, "OC-DTLS1_2-AES256-GCM:OC-DTLS1_2-AES128-GCM:");
+#endif
+       buf_append(buf, "AES256-SHA:AES128-SHA:DES-CBC3-SHA:DES-CBC-SHA");
 }