diff options
author | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2016-02-08 11:23:33 +0100 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2016-02-10 10:10:31 +0100 |
commit | 397e5b476854a9b94bf0aeb52e94a26a7cdd3add (patch) | |
tree | d9749415e33595d6300a647f2eaa7a678db4d74c | |
parent | 5bf3ec7228dba60c27c1dc3ed5022d705ae9980d (diff) | |
download | gnutls-397e5b476854a9b94bf0aeb52e94a26a7cdd3add.tar.gz |
ALPN: added the GNUTLS_ALPN_SERVER_PRECEDENCE flag
This allows the server to set precedence on the protocols
it supports, rather than following the client's order.
Resolves #71
-rw-r--r-- | lib/ext/alpn.c | 36 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 17 | ||||
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/alpn-server-prec.c | 309 |
4 files changed, 352 insertions, 12 deletions
diff --git a/lib/ext/alpn.c b/lib/ext/alpn.c index de53060853..887d42c322 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 > (int)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,13 +262,13 @@ 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. * - * If %GNUTLS_ALPN_MAND is specified the connection will be aborted - * if no matching ALPN protocol is found. + * See %gnutls_alpn_flags_t description for the documentation of available + * flags. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, * otherwise a negative error code is returned. 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..45938d73be --- /dev/null +++ b/tests/alpn-server-prec.c @@ -0,0 +1,309 @@ +/* + * 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, const char *expected) +{ + 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, expected); + 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) +{ + /* A, B - A, B -> A */ + start("h2", "http/1.1", "h2", "http/1.1", "h2"); + + /* A, B - B, A -> A */ + start("spdy/3", "spdy/2", "spdy/2", "spdy/3", "spdy/3"); + + /* A, B - C, B -> B */ + start("spdy/3", "spdy/2", "h2", "spdy/2", "spdy/2"); + + /* A, B - B, C -> B */ + start("h2", "http/1.1", "http/1.1", "h3", "http/1.1"); +} + +#endif /* _WIN32 */ |