diff options
Diffstat (limited to 'lib/ext/server_name.c')
-rw-r--r-- | lib/ext/server_name.c | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/lib/ext/server_name.c b/lib/ext/server_name.c new file mode 100644 index 0000000..d52c8d0 --- /dev/null +++ b/lib/ext/server_name.c @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2002-2012 Free Software Foundation, Inc. + * Copyright (C) 2017 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * 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 "auth.h" +#include "errors.h" +#include "num.h" +#include "str.h" +#include <ext/server_name.h> +#include "hello_ext_lib.h" + +static int _gnutls_server_name_recv_params(gnutls_session_t session, + const uint8_t * data, + size_t data_size); +static int _gnutls_server_name_send_params(gnutls_session_t session, + gnutls_buffer_st * extdata); + +int +_gnutls_server_name_set_raw(gnutls_session_t session, + gnutls_server_name_type_t type, + const void *name, size_t name_length); + +const hello_ext_entry_st ext_mod_server_name = { + .name = "Server Name Indication", + .tls_id = 0, + .gid = GNUTLS_EXTENSION_SERVER_NAME, + .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_DTLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | + GNUTLS_EXT_FLAG_EE | GNUTLS_EXT_FLAG_TLS12_SERVER_HELLO, + .client_parse_point = GNUTLS_EXT_MANDATORY, + .server_parse_point = GNUTLS_EXT_MANDATORY, + .recv_func = _gnutls_server_name_recv_params, + .send_func = _gnutls_server_name_send_params, + .pack_func = _gnutls_hello_ext_default_pack, + .unpack_func = _gnutls_hello_ext_default_unpack, + .deinit_func = _gnutls_hello_ext_default_deinit, + .cannot_be_overriden = 1 +}; + +/* + * In case of a server: if a NAME_DNS extension type is received then + * it stores into the session the value of NAME_DNS. The server may + * use gnutls_ext_get_server_name(), in order to access it. + * + * In case of a client: If a proper NAME_DNS extension type is found + * in the session then it sends the extension to the peer. + * + */ +static int +_gnutls_server_name_recv_params(gnutls_session_t session, + const uint8_t * data, size_t data_size) +{ + const unsigned char *p; + uint16_t len, type; + gnutls_datum_t name; + + if (session->security_parameters.entity == GNUTLS_SERVER) { + DECR_LENGTH_RET(data_size, 2, GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + len = _gnutls_read_uint16(data); + if (len == 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + if (len != data_size) { + gnutls_assert(); + return GNUTLS_E_UNEXPECTED_PACKET_LENGTH; + } + + p = data + 2; + + while (data_size > 0) { + DECR_LEN(data_size, 1); + type = *p; + p++; + + DECR_LEN(data_size, 2); + len = _gnutls_read_uint16(p); + p += 2; + + if (len == 0) { + _gnutls_handshake_log + ("HSK[%p]: Received server name size of zero\n", + session); + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + } + + DECR_LEN(data_size, len); + + if (type == 0) { /* NAME_DNS */ + if (!_gnutls_dnsname_is_valid((char*)p, len)) { + _gnutls_handshake_log + ("HSK[%p]: Server name is not acceptable: '%.*s'\n", + session, (int) len, p); + return gnutls_assert_val(GNUTLS_E_RECEIVED_DISALLOWED_NAME); + } + + name.data = (void*)p; + name.size = len; + + _gnutls_hello_ext_unset_priv(session, GNUTLS_EXTENSION_SERVER_NAME); + return _gnutls_hello_ext_set_datum(session, + GNUTLS_EXTENSION_SERVER_NAME, + &name); + } + p += len; + + } + + + } + + return 0; +} + +/* returns data_size or a negative number on failure + */ +static int +_gnutls_server_name_send_params(gnutls_session_t session, + gnutls_buffer_st * extdata) +{ + int total_size = 0, ret; + gnutls_datum_t name; + + ret = + _gnutls_hello_ext_get_datum(session, GNUTLS_EXTENSION_SERVER_NAME, + &name); + if (ret < 0) + return 0; + + /* this function sends the client extension data (dnsname) + */ + if (session->security_parameters.entity == GNUTLS_CLIENT) { + if (name.size == 0) + return 0; + + /* uint8_t + uint16_t + size + */ + total_size = 2 + 1 + 2 + name.size; + + /* UINT16: write total size of all names + */ + ret = + _gnutls_buffer_append_prefix(extdata, 16, + total_size - 2); + if (ret < 0) + return gnutls_assert_val(ret); + + /* UINT8: type of this extension + * UINT16: size of the first name + * LEN: the actual server name. + */ + ret = + _gnutls_buffer_append_prefix(extdata, 8, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_debug_log("HSK[%p]: sent server name: '%.*s'\n", session, name.size, name.data); + + ret = + _gnutls_buffer_append_data_prefix + (extdata, 16, + name.data, name.size); + if (ret < 0) + return gnutls_assert_val(ret); + } else { + return 0; + } + + return total_size; +} + +/** + * gnutls_server_name_get: + * @session: is a #gnutls_session_t type. + * @data: will hold the data + * @data_length: will hold the data length. Must hold the maximum size of data. + * @type: will hold the server name indicator type + * @indx: is the index of the server_name + * + * This function will allow you to get the name indication (if any), a + * client has sent. The name indication may be any of the enumeration + * gnutls_server_name_type_t. + * + * If @type is GNUTLS_NAME_DNS, then this function is to be used by + * servers that support virtual hosting, and the data will be a null + * terminated IDNA ACE string (prior to GnuTLS 3.4.0 it was a UTF-8 string). + * + * If @data has not enough size to hold the server name + * GNUTLS_E_SHORT_MEMORY_BUFFER is returned, and @data_length will + * hold the required size. + * + * @indx is used to retrieve more than one server names (if sent by + * the client). The first server name has an index of 0, the second 1 + * and so on. If no name with the given index exists + * GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE is returned. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, on UTF-8 + * decoding error %GNUTLS_E_IDNA_ERROR is returned, otherwise a negative + * error code is returned. + **/ +int +gnutls_server_name_get(gnutls_session_t session, void *data, + size_t * data_length, + unsigned int *type, unsigned int indx) +{ + char *_data = data; + gnutls_datum_t name; + int ret; + + if (session->security_parameters.entity == GNUTLS_CLIENT) { + gnutls_assert(); + return GNUTLS_E_INVALID_REQUEST; + } + + if (indx != 0) + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + + ret = + _gnutls_hello_ext_get_datum(session, GNUTLS_EXTENSION_SERVER_NAME, &name); + if (ret < 0) { + gnutls_assert(); + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + } + + if (name.size == 0) { + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + } + + *type = GNUTLS_NAME_DNS; + + if (*data_length > name.size) { /* greater since we need one extra byte for the null */ + *data_length = name.size; + memcpy(data, name.data, *data_length); + + /* null terminate */ + _data[(*data_length)] = 0; + + } else { + *data_length = name.size + 1; + ret = GNUTLS_E_SHORT_MEMORY_BUFFER; + goto cleanup; + } + + ret = 0; + cleanup: + return ret; +} + +/* This does not do any conversion not perform any check */ +int +_gnutls_server_name_set_raw(gnutls_session_t session, + gnutls_server_name_type_t type, + const void *name, size_t name_length) +{ + int ret; + gnutls_datum_t dname; + + if (name_length >= MAX_SERVER_NAME_SIZE) { + return GNUTLS_E_INVALID_REQUEST; + } + + _gnutls_hello_ext_unset_priv(session, GNUTLS_EXTENSION_SERVER_NAME); + + dname.data = (void*)name; + dname.size = name_length; + + ret = _gnutls_hello_ext_set_datum(session, GNUTLS_EXTENSION_SERVER_NAME, &dname); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} + +/** + * gnutls_server_name_set: + * @session: is a #gnutls_session_t type. + * @type: specifies the indicator type + * @name: is a string that contains the server name. + * @name_length: holds the length of name excluding the terminating null byte + * + * This function is to be used by clients that want to inform (via a + * TLS extension mechanism) the server of the name they connected to. + * This should be used by clients that connect to servers that do + * virtual hosting. + * + * The value of @name depends on the @type type. In case of + * %GNUTLS_NAME_DNS, a UTF-8 null-terminated domain name string, + * without the trailing dot, is expected. + * + * IPv4 or IPv6 addresses are not permitted to be set by this function. + * If the function is called with a name of @name_length zero it will clear + * all server names set. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, + * otherwise a negative error code is returned. + **/ +int +gnutls_server_name_set(gnutls_session_t session, + gnutls_server_name_type_t type, + const void *name, size_t name_length) +{ + int ret; + gnutls_datum_t idn_name = {NULL,0}; + + if (session->security_parameters.entity == GNUTLS_SERVER) { + gnutls_assert(); + return GNUTLS_E_INVALID_REQUEST; + } + + if (name_length == 0) { /* unset extension */ + _gnutls_hello_ext_unset_priv(session, GNUTLS_EXTENSION_SERVER_NAME); + return 0; + } + + ret = gnutls_idna_map(name, name_length, &idn_name, 0); + if (ret < 0) { + _gnutls_debug_log("unable to convert name %s to IDNA2008 format\n", (char*)name); + return ret; + } + + name = idn_name.data; + name_length = idn_name.size; + + ret = _gnutls_server_name_set_raw(session, type, name, name_length); + gnutls_free(idn_name.data); + + return ret; +} + +unsigned _gnutls_server_name_matches_resumed(gnutls_session_t session) +{ + gnutls_datum_t name1, name2; + int ret; + + ret = + _gnutls_hello_ext_get_datum(session, + GNUTLS_EXTENSION_SERVER_NAME, + &name1); + if (ret < 0) { /* no server name in this session */ + name1.data = NULL; + name1.size = 0; + } + + ret = + _gnutls_hello_ext_get_resumed_datum(session, + GNUTLS_EXTENSION_SERVER_NAME, + &name2); + if (ret < 0) { /* no server name in this session */ + name2.data = NULL; + name2.size = 0; + } + + if (name1.data == NULL || name2.data == NULL) { + if (name1.data == name2.data) + return 1; + else + return 0; + } + + if (name1.size != name2.size) + return 0; + + if (memcmp(name1.data, name2.data, name1.size) != 0) + return 0; + + return 1; +} |