Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Enable 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.

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>
  • Loading branch information
Nikos Mavrogiannopoulos committed Sep 21, 2016
1 parent 5060a8c commit 120b592
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 13 deletions.
53 changes: 45 additions & 8 deletions cstp.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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);
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;
Expand Down
120 changes: 120 additions & 0 deletions gnutls-dtls.c
Expand Up @@ -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;
Expand Down Expand Up @@ -133,13 +136,123 @@ 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;
gnutls_datum_t master_secret, session_id;
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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}
117 changes: 117 additions & 0 deletions gnutls.c
Expand Up @@ -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
Expand All @@ -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;
}

Expand Down

0 comments on commit 120b592

Please sign in to comment.