summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@redhat.com>2016-02-08 11:23:33 +0100
committerNikos Mavrogiannopoulos <nmav@redhat.com>2016-02-08 11:37:24 +0100
commitc25217eba307e04beb11a8ac54230bff6202c2ea (patch)
treed3304249f3c9046b3fc6de4be8e7e3d28a429fad
parent0a00b26b20c2999f8509c31dc5f8b6e81260e048 (diff)
downloadgnutls-alpn.tar.gz
ALPN: Added GNUTLS_ALPN_SERVER_PRECEDENCE flagalpn
This allows the server to set precedence on the protocols it supports, rather than following the client's order. Relates #71
-rw-r--r--lib/ext/alpn.c32
-rw-r--r--lib/includes/gnutls/gnutls.h.in17
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/alpn-server-prec.c302
4 files changed, 343 insertions, 10 deletions
diff --git a/lib/ext/alpn.c b/lib/ext/alpn.c
index de53060853..f3fab465e2 100644
--- a/lib/ext/alpn.c
+++ b/lib/ext/alpn.c
@@ -60,6 +60,7 @@ _gnutls_alpn_recv_params(gnutls_session_t session,
ssize_t data_size = _data_size;
alpn_ext_st *priv;
extension_priv_data_t epriv;
+ int selected_protocol_index;
ret =
_gnutls_ext_get_session_data(session, GNUTLS_EXTENSION_ALPN,
@@ -78,23 +79,37 @@ _gnutls_alpn_recv_params(gnutls_session_t session,
gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
if (session->security_parameters.entity == GNUTLS_SERVER) {
+ selected_protocol_index = MAX_ALPN_PROTOCOLS+1;
+
while (data_size > 0) {
DECR_LENGTH_RET(data_size, 1, 0);
len1 = *p;
p += 1;
DECR_LENGTH_RET(data_size, len1, 0);
- for (i = 0; i < priv->size; i++)
+ for (i = 0; i < priv->size; i++) {
if (priv->protocol_size[i] == len1
&& memcmp(p, priv->protocols[i],
len1) == 0) {
- priv->selected_protocol =
- priv->protocols[i];
- priv->selected_protocol_size =
- priv->protocol_size[i];
- return 0;
+ if (priv->flags & GNUTLS_ALPN_SERVER_PRECEDENCE) {
+ if (selected_protocol_index > i) {
+ selected_protocol_index = i;
+ priv->selected_protocol =
+ priv->protocols[i];
+ priv->selected_protocol_size =
+ priv->protocol_size[i];
+ break;
+ }
+ } else {
+ priv->selected_protocol =
+ priv->protocols[i];
+ priv->selected_protocol_size =
+ priv->protocol_size[i];
+ return 0;
+ }
}
+ }
p += len1;
}
} else {
@@ -103,7 +118,7 @@ _gnutls_alpn_recv_params(gnutls_session_t session,
p += 1;
DECR_LENGTH_RET(data_size, len1, 0);
- for (i = 0; i < priv->size; i++)
+ for (i = 0; i < priv->size; i++) {
if (priv->protocol_size[i] == len1
&& memcmp(p, priv->protocols[i], len1) == 0) {
priv->selected_protocol =
@@ -112,6 +127,7 @@ _gnutls_alpn_recv_params(gnutls_session_t session,
priv->protocol_size[i];
break;
}
+ }
p += len1;
}
@@ -246,7 +262,7 @@ gnutls_alpn_get_selected_protocol(gnutls_session_t session,
* @session: is a #gnutls_session_t type.
* @protocols: is the protocol names to add.
* @protocols_size: the number of protocols to add.
- * @flags: zero or %GNUTLS_ALPN_*
+ * @flags: zero or a sequence of %gnutls_alpn_flags_t
*
* This function is to be used by both clients and servers, to declare
* the supported ALPN protocols, which are used during negotiation with peer.
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index e9e613918d..ec7065b50e 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -1212,7 +1212,22 @@ int gnutls_srtp_set_mki(gnutls_session_t session,
int gnutls_srtp_get_mki(gnutls_session_t session, gnutls_datum_t * mki);
/* ALPN TLS extension */
-#define GNUTLS_ALPN_MAND 1
+
+/**
+ * gnutls_alpn_flags_t:
+ * @GNUTLS_ALPN_MANDATORY: Require ALPN negotiation. The connection will be
+ * aborted if no matching ALPN protocol is found.
+ * @GNUTLS_ALPN_SERVER_PRECEDENCE: The choices set by the server
+ * will take precedence over the client's.
+ *
+ * Enumeration of different ALPN flags. These are used by gnutls_alpn_set_protocols().
+ */
+typedef enum {
+ GNUTLS_ALPN_MANDATORY = 1,
+ GNUTLS_ALPN_SERVER_PRECEDENCE = (1<<1)
+} gnutls_alpn_flags_t;
+
+#define GNUTLS_ALPN_MAND GNUTLS_ALPN_MANDATORY
int gnutls_alpn_get_selected_protocol(gnutls_session_t session,
gnutls_datum_t * protocol);
int gnutls_alpn_set_protocols(gnutls_session_t session,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d0f9b50d8c..da7a47edc2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -94,7 +94,7 @@ ctests = mini-record-2 simple gc set_pkcs12_cred certder certuniqueid \
mini-x509-dual mini-x509-kx global-init-override tlsext-decoding \
rehandshake-switch-cert rehandshake-switch-cert-allow rehandshake-switch-cert-client \
rehandshake-switch-cert-client-allow handshake-versions dtls-handshake-versions \
- dtls-max-record tls-max-record
+ dtls-max-record tls-max-record alpn-server-prec
if HAVE_SECCOMP_TESTS
ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp
diff --git a/tests/alpn-server-prec.c b/tests/alpn-server-prec.c
new file mode 100644
index 0000000000..db8c95ab29
--- /dev/null
+++ b/tests/alpn-server-prec.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2013-2016 Nikos Mavrogiannopoulos
+ * Copyright (C) 2016 Red Hat, Inc.
+ *
+ * This file is part of GnuTLS.
+ *
+ * GnuTLS is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuTLS 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GnuTLS; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/* This program tests the GNUTLS_ALPN_SERVER_PRECEDENCE
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(_WIN32) || !defined(ENABLE_ALPN)
+
+int main(int argc, char **argv)
+{
+ exit(77);
+}
+
+#else
+
+#include <string.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/dtls.h>
+
+#include "utils.h"
+
+static void terminate(void);
+
+static void server_log_func(int level, const char *str)
+{
+ fprintf(stderr, "server|<%d>| %s", level, str);
+}
+
+static void client_log_func(int level, const char *str)
+{
+ fprintf(stderr, "client|<%d>| %s", level, str);
+}
+
+/* These are global */
+static pid_t child;
+
+static void client(int fd, const char *protocol0, const char *protocol1, const char *protocol2)
+{
+ gnutls_session_t session;
+ int ret;
+ gnutls_datum_t proto;
+ gnutls_anon_client_credentials_t anoncred;
+ /* Need to enable anonymous KX specifically. */
+
+ global_init();
+
+ if (debug) {
+ gnutls_global_set_log_function(client_log_func);
+ gnutls_global_set_log_level(4711);
+ }
+
+ gnutls_anon_allocate_client_credentials(&anoncred);
+
+ /* Initialize TLS session
+ */
+ gnutls_init(&session, GNUTLS_CLIENT);
+
+ /* Use default priorities */
+ gnutls_priority_set_direct(session,
+ "NONE:+VERS-TLS1.0:+CIPHER-ALL:+MAC-ALL:+SIGN-ALL:+COMP-ALL:+ANON-ECDH:+CURVE-ALL",
+ NULL);
+ if (protocol1) {
+ gnutls_datum_t t[3];
+ t[0].data = (void *) protocol0;
+ t[0].size = strlen(protocol0);
+ t[1].data = (void *) protocol1;
+ t[1].size = strlen(protocol1);
+ t[2].data = (void *) protocol2;
+ t[2].size = strlen(protocol2);
+
+ ret = gnutls_alpn_set_protocols(session, t, 3, 0);
+ if (ret < 0) {
+ gnutls_perror(ret);
+ exit(1);
+ }
+ }
+
+ /* put the anonymous credentials to the current session
+ */
+ gnutls_credentials_set(session, GNUTLS_CRD_ANON, anoncred);
+
+ gnutls_transport_set_int(session, fd);
+
+ /* Perform the TLS handshake
+ */
+ do {
+ ret = gnutls_handshake(session);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret < 0) {
+ fail("client: Handshake failed\n");
+ gnutls_perror(ret);
+ exit(1);
+ } else {
+ if (debug)
+ success("client: Handshake was completed\n");
+ }
+
+ if (debug)
+ success("client: TLS version is: %s\n",
+ gnutls_protocol_get_name
+ (gnutls_protocol_get_version(session)));
+
+ ret = gnutls_alpn_get_selected_protocol(session, &proto);
+ if (ret < 0) {
+ gnutls_perror(ret);
+ exit(1);
+ }
+
+ if (debug) {
+ fprintf(stderr, "selected protocol: %.*s\n",
+ (int) proto.size, proto.data);
+ }
+
+
+ gnutls_bye(session, GNUTLS_SHUT_WR);
+
+ close(fd);
+
+ gnutls_deinit(session);
+
+ gnutls_anon_free_client_credentials(anoncred);
+
+ gnutls_global_deinit();
+}
+
+static void terminate(void)
+{
+ int status;
+
+ kill(child, SIGTERM);
+ wait(&status);
+ exit(1);
+}
+
+static void server(int fd, const char *protocol1, const char *protocol2, const char *expected)
+{
+ int ret;
+ gnutls_session_t session;
+ gnutls_anon_server_credentials_t anoncred;
+ gnutls_datum_t t[2];
+ gnutls_datum_t selected;
+
+ /* this must be called once in the program
+ */
+ global_init();
+
+ if (debug) {
+ gnutls_global_set_log_function(server_log_func);
+ gnutls_global_set_log_level(4711);
+ }
+
+ gnutls_anon_allocate_server_credentials(&anoncred);
+
+ gnutls_init(&session, GNUTLS_SERVER);
+
+ /* avoid calling all the priority functions, since the defaults
+ * are adequate.
+ */
+ gnutls_priority_set_direct(session,
+ "NONE:+VERS-TLS1.0:+CIPHER-ALL:+MAC-ALL:+SIGN-ALL:+COMP-ALL:+ANON-ECDH:+CURVE-ALL",
+ NULL);
+
+ t[0].data = (void *) protocol1;
+ t[0].size = strlen(protocol1);
+ t[1].data = (void *) protocol2;
+ t[1].size = strlen(protocol2);
+
+ ret = gnutls_alpn_set_protocols(session, t, 2, GNUTLS_ALPN_SERVER_PRECEDENCE);
+ if (ret < 0) {
+ gnutls_perror(ret);
+ exit(1);
+ }
+
+ gnutls_credentials_set(session, GNUTLS_CRD_ANON, anoncred);
+
+ gnutls_transport_set_int(session, fd);
+
+ do {
+ ret = gnutls_handshake(session);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+ if (ret < 0) {
+ close(fd);
+ gnutls_deinit(session);
+ fail("server: Handshake has failed (%s)\n\n",
+ gnutls_strerror(ret));
+ terminate();
+ }
+ if (debug)
+ success("server: Handshake was completed\n");
+
+ if (debug)
+ success("server: TLS version is: %s\n",
+ gnutls_protocol_get_name
+ (gnutls_protocol_get_version(session)));
+
+ ret = gnutls_alpn_get_selected_protocol(session, &selected);
+ if (ret < 0) {
+ gnutls_perror(ret);
+ exit(1);
+ }
+
+ if (debug) {
+ success("Protocol: %.*s\n", (int) selected.size, selected.data);
+ }
+
+ if (selected.size != strlen(expected) || memcmp(selected.data, expected, selected.size) != 0) {
+ fail("did not select the expected protocol (selected %.*s, expected %s)\n", selected.size, selected.data, expected);
+ exit(1);
+ }
+
+ /* do not wait for the peer to close the connection.
+ */
+ gnutls_bye(session, GNUTLS_SHUT_WR);
+
+ close(fd);
+ gnutls_deinit(session);
+
+ gnutls_anon_free_server_credentials(anoncred);
+
+ gnutls_global_deinit();
+
+ if (debug)
+ success("server: finished\n");
+}
+
+static void start(const char *p1, const char *p2, const char *cp1, const char *cp2)
+{
+ int fd[2];
+ int ret;
+
+ ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
+ if (ret < 0) {
+ perror("socketpair");
+ exit(1);
+ }
+
+ child = fork();
+ if (child < 0) {
+ perror("fork");
+ fail("fork");
+ exit(1);
+ }
+
+ if (child) {
+ int status;
+ /* parent */
+
+ server(fd[0], p1, p2, p1);
+ wait(&status);
+ if (WEXITSTATUS(status) != 0)
+ fail("Child died with status %d\n",
+ WEXITSTATUS(status));
+ } else {
+ close(fd[0]);
+ client(fd[1], cp1, "unknown/1.4", cp2);
+ exit(0);
+ }
+}
+
+void doit(void)
+{
+ start("h2", "http/1.1", "h2", "http/1.1");
+ start("spdy/2", "spdy/3", "spdy/2", "spdy/3");
+ start("spdy/3", "spdy/2", "spdy/3", "spdy/2");
+ start("spdy/3", "spdy/2", "spdy/2", "spdy/3");
+}
+
+#endif /* _WIN32 */