/* * Copyright (C) 2007-2012 Free Software Foundation, Inc. * * Author: Simon Josefsson * * 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 * */ /* This file contains support functions for 'TLS Handshake Message for * Supplemental Data' (RFC 4680). * * The idea here is simple. gnutls_handshake() in gnuts_handshake.c * will call _gnutls_gen_supplemental and _gnutls_parse_supplemental * when some extension requested that supplemental data be sent or * received. Extension request this by setting the flags * do_recv_supplemental or do_send_supplemental in the session. * * The functions in this file iterate through the _gnutls_supplemental * array, and calls the send/recv functions for each respective data * type. * * The receive function of each data type is responsible for decoding * its own data. If the extension did not expect to receive * supplemental data, it should return GNUTLS_E_UNEXPECTED_PACKET. * Otherwise, it just parse the data as normal. * * The send function needs to append the 2-byte data format type, and * append the 2-byte length of its data, and the data. If it doesn't * want to send any data, it is fine to return without doing anything. */ #include "gnutls_int.h" #include #include "supplemental.h" #include "errors.h" #include "num.h" typedef struct gnutls_supplemental_entry_st { char *name; gnutls_supplemental_data_format_type_t type; gnutls_supp_recv_func supp_recv_func; gnutls_supp_send_func supp_send_func; } gnutls_supplemental_entry_st; static size_t suppfunc_size = 0; static gnutls_supplemental_entry_st *suppfunc = NULL; /** * gnutls_supplemental_get_name: * @type: is a supplemental data format type * * Convert a #gnutls_supplemental_data_format_type_t value to a * string. * * Returns: a string that contains the name of the specified * supplemental data format type, or %NULL for unknown types. **/ const char *gnutls_supplemental_get_name(gnutls_supplemental_data_format_type_t type) { size_t i; for (i = 0; i < suppfunc_size; i++) { if (suppfunc[i].type == type) return suppfunc[i].name; } return NULL; } void _gnutls_supplemental_deinit(void) { unsigned i; for (i = 0; i < suppfunc_size; i++) { gnutls_free(suppfunc[i].name); } gnutls_free(suppfunc); suppfunc = NULL; suppfunc_size = 0; } static gnutls_supp_recv_func get_supp_func_recv(gnutls_session_t session, gnutls_supplemental_data_format_type_t type) { size_t i; for (i = 0; i < session->internals.rsup_size; i++) { if (session->internals.rsup[i].type == type) return session->internals.rsup[i].supp_recv_func; } for (i = 0; i < suppfunc_size; i++) { if (suppfunc[i].type == type) return suppfunc[i].supp_recv_func; } return NULL; } static int gen_supplemental(gnutls_session_t session, const gnutls_supplemental_entry_st *supp, gnutls_buffer_st * buf) { int ret; gnutls_supp_send_func supp_send = supp->supp_send_func; size_t sizepos = buf->length; /* Make room for supplement type and length byte length field. */ ret = _gnutls_buffer_append_data(buf, "\0\0\0\0", 4); if (ret < 0) { gnutls_assert(); return ret; } ret = supp_send(session, buf); if (ret < 0) { gnutls_assert(); return ret; } /* If data were added, store type+length, otherwise reset. */ if (buf->length > sizepos + 4) { buf->data[sizepos] = (supp->type >> 8) & 0xFF; buf->data[sizepos + 1] = supp->type & 0xFF; buf->data[sizepos + 2] = ((buf->length - sizepos - 4) >> 8) & 0xFF; buf->data[sizepos + 3] = (buf->length - sizepos - 4) & 0xFF; } else buf->length -= 4; return 0; } int _gnutls_gen_supplemental(gnutls_session_t session, gnutls_buffer_st * buf) { size_t i; int ret; unsigned init_pos = buf->length; /* Make room for 3 byte length field. */ ret = _gnutls_buffer_append_data(buf, "\0\0\0", 3); if (ret < 0) { gnutls_assert(); return ret; } for (i = 0; i < session->internals.rsup_size; i++) { ret = gen_supplemental(session, &session->internals.rsup[i], buf); if (ret < 0) return gnutls_assert_val(ret); } for (i = 0; i < suppfunc_size; i++) { ret = gen_supplemental(session, &suppfunc[i], buf); if (ret < 0) return gnutls_assert_val(ret); } i = buf->length - init_pos - 3; buf->data[init_pos] = (i >> 16) & 0xFF; buf->data[init_pos+1] = (i >> 8) & 0xFF; buf->data[init_pos+2] = i & 0xFF; _gnutls_debug_log ("EXT[%p]: Sending %d bytes of supplemental data\n", session, (int) buf->length); return buf->length - init_pos; } int _gnutls_parse_supplemental(gnutls_session_t session, const uint8_t * data, int datalen) { const uint8_t *p = data; ssize_t dsize = datalen; size_t total_size; DECR_LEN(dsize, 3); total_size = _gnutls_read_uint24(p); p += 3; if (dsize != (ssize_t) total_size) { gnutls_assert(); return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; } do { uint16_t supp_data_type; uint16_t supp_data_length; gnutls_supp_recv_func recv_func; DECR_LEN(dsize, 2); supp_data_type = _gnutls_read_uint16(p); p += 2; DECR_LEN(dsize, 2); supp_data_length = _gnutls_read_uint16(p); p += 2; _gnutls_debug_log ("EXT[%p]: Got supplemental type=%02x length=%d\n", session, supp_data_type, supp_data_length); recv_func = get_supp_func_recv(session, supp_data_type); if (recv_func) { int ret = recv_func(session, p, supp_data_length); if (ret < 0) { gnutls_assert(); return ret; } } else { gnutls_assert(); return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; } DECR_LEN(dsize, supp_data_length); p += supp_data_length; } while (dsize > 0); return 0; } static int _gnutls_supplemental_register(gnutls_supplemental_entry_st *entry) { gnutls_supplemental_entry_st *p; unsigned i; for (i = 0; i < suppfunc_size; i++) { if (entry->type == suppfunc[i].type) return gnutls_assert_val(GNUTLS_E_ALREADY_REGISTERED); } p = gnutls_realloc_fast(suppfunc, sizeof(*suppfunc) * (suppfunc_size + 1)); if (!p) { gnutls_assert(); return GNUTLS_E_MEMORY_ERROR; } suppfunc = p; memcpy(&suppfunc[suppfunc_size], entry, sizeof(*entry)); suppfunc_size++; return GNUTLS_E_SUCCESS; } /** * gnutls_supplemental_register: * @name: the name of the supplemental data to register * @type: the type of the supplemental data format * @recv_func: the function to receive the data * @send_func: the function to send the data * * This function will register a new supplemental data type (rfc4680). * The registered data will remain until gnutls_global_deinit() * is called. The provided @type must be an unassigned type in * %gnutls_supplemental_data_format_type_t. If the type is already * registered or handled by GnuTLS internally %GNUTLS_E_ALREADY_REGISTERED * will be returned. * * This function is not thread safe. * * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code. * * Since: 3.4.0 **/ int gnutls_supplemental_register(const char *name, gnutls_supplemental_data_format_type_t type, gnutls_supp_recv_func recv_func, gnutls_supp_send_func send_func) { gnutls_supplemental_entry_st tmp_entry; int ret; tmp_entry.name = gnutls_strdup(name); tmp_entry.type = type; tmp_entry.supp_recv_func = recv_func; tmp_entry.supp_send_func = send_func; ret = _gnutls_supplemental_register(&tmp_entry); if (ret < 0) { gnutls_free(tmp_entry.name); } return ret; } /** * gnutls_session_supplemental_register: * @session: the session for which this will be registered * @name: the name of the supplemental data to register * @type: the type of the supplemental data format * @recv_func: the function to receive the data * @send_func: the function to send the data * @flags: must be zero * * This function will register a new supplemental data type (rfc4680). * The registered supplemental functions will be used for that specific * session. The provided @type must be an unassigned type in * %gnutls_supplemental_data_format_type_t. * * If the type is already registered or handled by GnuTLS internally * %GNUTLS_E_ALREADY_REGISTERED will be returned. * * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code. * * Since: 3.5.5 **/ int gnutls_session_supplemental_register(gnutls_session_t session, const char *name, gnutls_supplemental_data_format_type_t type, gnutls_supp_recv_func recv_func, gnutls_supp_send_func send_func, unsigned flags) { gnutls_supplemental_entry_st tmp_entry; gnutls_supplemental_entry_st *p; unsigned i; tmp_entry.name = NULL; tmp_entry.type = type; tmp_entry.supp_recv_func = recv_func; tmp_entry.supp_send_func = send_func; for (i = 0; i < suppfunc_size; i++) { if (type == suppfunc[i].type) return gnutls_assert_val(GNUTLS_E_ALREADY_REGISTERED); } p = gnutls_realloc(session->internals.rsup, sizeof(gnutls_supplemental_entry_st)*(session->internals.rsup_size + 1)); if (!p) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); session->internals.rsup = p; memcpy(&session->internals.rsup[session->internals.rsup_size], &tmp_entry, sizeof(tmp_entry)); session->internals.rsup_size++; return GNUTLS_E_SUCCESS; } /** * gnutls_supplemental_recv: * @session: is a #gnutls_session_t type. * @do_recv_supplemental: non-zero in order to expect supplemental data * * This function is to be called by an extension handler to * instruct gnutls to attempt to receive supplemental data * during the handshake process. * * Since: 3.4.0 **/ void gnutls_supplemental_recv(gnutls_session_t session, unsigned do_recv_supplemental) { session->security_parameters.do_recv_supplemental = do_recv_supplemental; } /** * gnutls_supplemental_send: * @session: is a #gnutls_session_t type. * @do_send_supplemental: non-zero in order to send supplemental data * * This function is to be called by an extension handler to * instruct gnutls to send supplemental data during the handshake process. * * Since: 3.4.0 **/ void gnutls_supplemental_send(gnutls_session_t session, unsigned do_send_supplemental) { session->security_parameters.do_send_supplemental = do_send_supplemental; }