}
}
+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;
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]);
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;
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;
}
#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;
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;
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);
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;
+ }
}
}
}
+#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
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;
}
#endif
#define COMPR_ALL (COMPR_STATELESS | COMPR_DEFLATE)
+#define DTLS_APP_ID_EXT 48018
+
struct keepalive_info {
int dpd;
int keepalive;
#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
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;
#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 */
}
#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;
} 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
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);
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);
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));
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");
}