summaryrefslogtreecommitdiffstats
path: root/lib/ext/psk_ke_modes.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ext/psk_ke_modes.c')
-rw-r--r--lib/ext/psk_ke_modes.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/lib/ext/psk_ke_modes.c b/lib/ext/psk_ke_modes.c
new file mode 100644
index 0000000..cc28536
--- /dev/null
+++ b/lib/ext/psk_ke_modes.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 Free Software Foundation, Inc.
+ *
+ * Author: Ander Juaristi
+ *
+ * This file is part of GnuTLS.
+ *
+ * The GnuTLS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>
+ *
+ */
+
+#include "gnutls_int.h"
+#include "ext/psk_ke_modes.h"
+#include "ext/pre_shared_key.h"
+#include <assert.h>
+
+#define PSK_KE 0
+#define PSK_DHE_KE 1
+
+static int
+psk_ke_modes_send_params(gnutls_session_t session,
+ gnutls_buffer_t extdata)
+{
+ int ret;
+ const version_entry_st *vers;
+ uint8_t data[2];
+ unsigned pos, i;
+ unsigned have_dhpsk = 0;
+ unsigned have_psk = 0;
+
+ /* Server doesn't send psk_key_exchange_modes */
+ if (session->security_parameters.entity == GNUTLS_SERVER)
+ return 0;
+
+ /* If session ticket is disabled and no PSK key exchange is
+ * enabled, don't send the extension */
+ if ((session->internals.flags & GNUTLS_NO_TICKETS) &&
+ !session->internals.priorities->have_psk)
+ return 0;
+
+ vers = _gnutls_version_max(session);
+ if (!vers || !vers->tls13_sem)
+ return 0;
+
+ /* We send the list prioritized according to our preferences as a convention
+ * (used throughout the protocol), even if the protocol doesn't mandate that
+ * for this particular message. That way we can keep the TLS 1.2 semantics/
+ * prioritization when negotiating PSK or DHE-PSK. Receiving servers would
+ * very likely respect our prioritization if they parse the message serially. */
+ pos = 0;
+ for (i=0;i<session->internals.priorities->_kx.num_priorities;i++) {
+ if (session->internals.priorities->_kx.priorities[i] == GNUTLS_KX_PSK && !have_psk) {
+ assert(pos <= 1);
+ data[pos++] = PSK_KE;
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_PSK;
+ have_psk = 1;
+ } else if ((session->internals.priorities->_kx.priorities[i] == GNUTLS_KX_DHE_PSK ||
+ session->internals.priorities->_kx.priorities[i] == GNUTLS_KX_ECDHE_PSK) && !have_dhpsk) {
+ assert(pos <= 1);
+ data[pos++] = PSK_DHE_KE;
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_DHE_PSK;
+ have_dhpsk = 1;
+ }
+
+ if (have_psk && have_dhpsk)
+ break;
+ }
+
+ /* For session resumption we need to send at least one */
+ if (pos == 0) {
+ if (session->internals.flags & GNUTLS_NO_TICKETS)
+ return 0;
+
+ data[pos++] = PSK_DHE_KE;
+ data[pos++] = PSK_KE;
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_DHE_PSK;
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_PSK;
+ }
+
+ ret = _gnutls_buffer_append_data_prefix(extdata, 8, data, pos);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ session->internals.hsk_flags |= HSK_PSK_KE_MODES_SENT;
+
+ return 0;
+}
+
+#define MAX_POS INT_MAX
+
+/*
+ * Since we only support ECDHE-authenticated PSKs, the server
+ * just verifies that a "psk_key_exchange_modes" extension was received,
+ * and that it contains the value one.
+ */
+static int
+psk_ke_modes_recv_params(gnutls_session_t session,
+ const unsigned char *data, size_t len)
+{
+ uint8_t ke_modes_len;
+ const version_entry_st *vers = get_version(session);
+ gnutls_psk_server_credentials_t cred;
+ int dhpsk_pos = MAX_POS;
+ int psk_pos = MAX_POS;
+ int cli_psk_pos = MAX_POS;
+ int cli_dhpsk_pos = MAX_POS;
+ unsigned i;
+
+ /* Client doesn't receive psk_key_exchange_modes */
+ if (session->security_parameters.entity == GNUTLS_CLIENT)
+ return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION);
+
+ /* we set hsk_flags to HSK_PSK_KE_MODE_INVALID on failure to ensure that
+ * when we parse the pre-shared key extension we detect PSK_KE_MODES as
+ * received. */
+ if (!vers || !vers->tls13_sem) {
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_INVALID;
+ return gnutls_assert_val(0);
+ }
+
+ cred = (gnutls_psk_server_credentials_t)_gnutls_get_cred(session, GNUTLS_CRD_PSK);
+ if (cred == NULL && (session->internals.flags & GNUTLS_NO_TICKETS)) {
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_INVALID;
+ return gnutls_assert_val(0);
+ }
+
+ DECR_LEN(len, 1);
+ ke_modes_len = *(data++);
+
+ for (i=0;i<session->internals.priorities->_kx.num_priorities;i++) {
+ if (session->internals.priorities->_kx.priorities[i] == GNUTLS_KX_PSK && psk_pos == MAX_POS) {
+ psk_pos = i;
+ } else if ((session->internals.priorities->_kx.priorities[i] == GNUTLS_KX_DHE_PSK ||
+ session->internals.priorities->_kx.priorities[i] == GNUTLS_KX_ECDHE_PSK) &&
+ dhpsk_pos == MAX_POS) {
+ dhpsk_pos = i;
+ }
+
+ if (dhpsk_pos != MAX_POS && psk_pos != MAX_POS)
+ break;
+ }
+
+ if (psk_pos == MAX_POS && dhpsk_pos == MAX_POS) {
+ if (!(session->internals.flags & GNUTLS_NO_TICKETS))
+ dhpsk_pos = 0;
+ else if (session->internals.priorities->groups.size == 0)
+ return gnutls_assert_val(0);
+ }
+
+ for (i=0;i<ke_modes_len;i++) {
+ DECR_LEN(len, 1);
+ if (data[i] == PSK_DHE_KE)
+ cli_dhpsk_pos = i;
+ else if (data[i] == PSK_KE)
+ cli_psk_pos = i;
+
+ _gnutls_handshake_log("EXT[%p]: PSK KE mode %.2x received\n",
+ session, (unsigned)data[i]);
+ if (cli_psk_pos != MAX_POS && cli_dhpsk_pos != MAX_POS)
+ break;
+ }
+
+ if (session->internals.priorities->server_precedence) {
+ if (dhpsk_pos != MAX_POS && cli_dhpsk_pos != MAX_POS && (dhpsk_pos < psk_pos || cli_psk_pos == MAX_POS))
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_DHE_PSK;
+ else if (psk_pos != MAX_POS && cli_psk_pos != MAX_POS && (psk_pos < dhpsk_pos || cli_dhpsk_pos == MAX_POS))
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_PSK;
+ } else {
+ if (dhpsk_pos != MAX_POS && cli_dhpsk_pos != MAX_POS && (cli_dhpsk_pos < cli_psk_pos || psk_pos == MAX_POS))
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_DHE_PSK;
+ else if (psk_pos != MAX_POS && cli_psk_pos != MAX_POS && (cli_psk_pos < cli_dhpsk_pos || dhpsk_pos == MAX_POS))
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_PSK;
+ }
+
+ if ((session->internals.hsk_flags & HSK_PSK_KE_MODE_PSK) ||
+ (session->internals.hsk_flags & HSK_PSK_KE_MODE_DHE_PSK)) {
+
+ return 0;
+ } else {
+ session->internals.hsk_flags |= HSK_PSK_KE_MODE_INVALID;
+ return gnutls_assert_val(0);
+ }
+}
+
+const hello_ext_entry_st ext_mod_psk_ke_modes = {
+ .name = "PSK Key Exchange Modes",
+ .tls_id = 45,
+ .gid = GNUTLS_EXTENSION_PSK_KE_MODES,
+ .client_parse_point = GNUTLS_EXT_TLS,
+ .server_parse_point = GNUTLS_EXT_TLS,
+ .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_TLS13_SERVER_HELLO,
+ .send_func = psk_ke_modes_send_params,
+ .recv_func = psk_ke_modes_recv_params
+};