From d7a02888c4736d0e02925a62b8f4602af333ae5a Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos Date: Wed, 27 Jul 2016 11:58:11 +0200 Subject: [PATCH] WIP: enable DTLS protocol negotiation The new negotiation is as follows: If the client's X-DTLS-CipherSuite contains the PSK keyword, the server will reply with "X-DTLS-CipherSuite: PSK" and will enable DTLS-PSK negotiation on the DTLS channel. The ciphersuite set in the DTLS channel, must match the one set in TLS one. That, makes the protocol consistent in security properties (DTLS and TLS channel will match cipher/mac combinations), and allows the protocol to use new DTLS versions, as well as new DTLS ciphersuites, as long as they are also used in the TLS channel establishment. That change still requires to client to pretend it is resuming by setting in the TLS client hello 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). --- cstp.c | 9 +++- dtls.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++- openconnect-internal.h | 1 + 3 files changed, 140 insertions(+), 2 deletions(-) diff --git a/cstp.c b/cstp.c index 77a4400..12b861e 100644 --- a/cstp.c +++ b/cstp.c @@ -166,6 +166,12 @@ static void append_mobile_headers(struct openconnect_info *vpninfo, struct oc_te } } +#if GNUTLS_VERSION_NUMBER >= 0x030201 +# define DTLS_CIPHERSUITE_HEAD "X-DTLS-CipherSuite: " +#else +# define DTLS_CIPHERSUITE_HEAD "X-DTLS-CipherSuite: PSK:" +#endif + static int start_cstp_connection(struct openconnect_info *vpninfo) { struct oc_text_buf *reqbuf; @@ -228,7 +234,8 @@ static int start_cstp_connection(struct openconnect_info *vpninfo) buf_free(reqbuf); return -EINVAL; } - buf_append(reqbuf, "\r\nX-DTLS-CipherSuite: "); + buf_append(reqbuf, "\r\n"DTLS_CIPHERSUITE_HEAD); + if (vpninfo->dtls_ciphers) buf_append(reqbuf, "%s", vpninfo->dtls_ciphers); else diff --git a/dtls.c b/dtls.c index e196bf8..fb674af 100644 --- a/dtls.c +++ b/dtls.c @@ -93,6 +93,7 @@ int RAND_bytes(char *buf, int len) #define DTLS_SEND SSL_write #define DTLS_RECV SSL_read #define DTLS_FREE SSL_free +#define DTLS_CRED_FREE /* In the very early days there were cases where this wasn't found in * the header files but it did still work somehow. I forget the details @@ -559,6 +560,130 @@ void append_dtls_ciphers(struct openconnect_info *vpninfo, struct oc_text_buf *b #define DTLS_SEND gnutls_record_send #define DTLS_RECV gnutls_record_recv #define DTLS_FREE gnutls_deinit +#define DTLS_CRED_FREE if (vpninfo->psk_cred) { \ + gnutls_psk_free_client_credentials(vpninfo->psk_cred); \ + vpninfo->psk_cred = NULL; \ + } + +#if GNUTLS_VERSION_NUMBER >= 0x030201 + +#define PSK_LABEL "openconnect-psk" +#define PSK_LABEL_SIZE sizeof(PSK_LABEL)-1 +#define PSK_KEY_SIZE 32 + +/* This enables a DTLS protocol negotiation. The new negotiation is as follows: + * If the client's X-DTLS-CipherSuite contains the PSK keyword, the + * server will reply with "X-DTLS-CipherSuite: PSK" and will enable + * DTLS-PSK negotiation on the DTLS channel. The ciphersuite set in the + * DTLS channel, must match the one set in TLS one. That, makes the protocol + * consistent in security properties (DTLS and TLS channel will match cipher/mac + * combinations), and allows the protocol to use new DTLS versions, as well as new + * DTLS ciphersuites, as long as they are also used in the TLS channel establishment. + * + * That change still requires to client to pretend it is resuming by setting in the + * TLS client hello 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). + */ +static int start_dtls_psk_handshake(struct openconnect_info *vpninfo, int dtls_fd) +{ + gnutls_session_t dtls_ssl; + gnutls_datum_t session_id, key; + char prio_string[128]; + int err; + gnutls_mac_algorithm_t mac; + gnutls_cipher_algorithm_t cipher; + + /* make a priority string based on the cipher/MAC at the TLS connection */ + cipher = gnutls_cipher_get(vpninfo->https_sess); + mac = gnutls_mac_get(vpninfo->https_sess); + + snprintf(prio_string, sizeof(prio_string), "NORMAL:-VERS-ALL:-CIPHER-ALL:-MAC-ALL:-KX-ALL:+VERS-DTLS-ALL:+PSK:+%s:+%s", + gnutls_mac_get_name(mac), gnutls_cipher_get_name(cipher)); + + 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)); + vpninfo->dtls_attempt_period = 0; + return -EINVAL; + } + + err = gnutls_priority_set_direct(dtls_ssl, + prio_string, + NULL); + if (err) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to set DTLS priority: '%s': %s\n"), + prio_string, 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 */ + err = gnutls_prf_rfc5705(vpninfo->https_sess, PSK_LABEL_SIZE, PSK_LABEL, 0, NULL, + 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; + } + + session_id.data = vpninfo->dtls_session_id; + session_id.size = sizeof(vpninfo->dtls_session_id); + err = gnutls_session_set_id(dtls_ssl, &session_id); + if (err) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to set DTLS session ID: %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; + } + + vpninfo->dtls_ssl = dtls_ssl; + return 0; + fail: + gnutls_deinit(dtls_ssl); + DTLS_CRED_FREE; + vpninfo->dtls_attempt_period = 0; + return -EINVAL; +} +#endif + static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) { gnutls_session_t dtls_ssl; @@ -566,6 +691,11 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) int err; int cipher; +#if GNUTLS_VERSION_NUMBER >= 0x030201 + if (strcmp(vpninfo->dtls_cipher, "PSK") == 0) + return start_dtls_psk_handshake(vpninfo, dtls_fd); +#endif + 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; @@ -575,7 +705,6 @@ static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) vpn_progress(vpninfo, PRG_ERR, _("Unknown DTLS parameters for requested CipherSuite '%s'\n"), vpninfo->dtls_cipher); vpninfo->dtls_attempt_period = 0; - return -EINVAL; found_cipher: @@ -752,6 +881,7 @@ void dtls_close(struct openconnect_info *vpninfo) { if (vpninfo->dtls_ssl) { DTLS_FREE(vpninfo->dtls_ssl); + DTLS_CRED_FREE; closesocket(vpninfo->dtls_fd); unmonitor_read_fd(vpninfo, dtls); unmonitor_write_fd(vpninfo, dtls); diff --git a/openconnect-internal.h b/openconnect-internal.h index aa6561c..2bb6e52 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -472,6 +472,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 -- 2.7.4