diff options
Diffstat (limited to 'lib/tls13')
-rw-r--r-- | lib/tls13/anti_replay.c | 266 | ||||
-rw-r--r-- | lib/tls13/anti_replay.h | 31 | ||||
-rw-r--r-- | lib/tls13/certificate.c | 662 | ||||
-rw-r--r-- | lib/tls13/certificate.h | 29 | ||||
-rw-r--r-- | lib/tls13/certificate_request.c | 393 | ||||
-rw-r--r-- | lib/tls13/certificate_request.h | 31 | ||||
-rw-r--r-- | lib/tls13/certificate_verify.c | 241 | ||||
-rw-r--r-- | lib/tls13/certificate_verify.h | 29 | ||||
-rw-r--r-- | lib/tls13/early_data.c | 107 | ||||
-rw-r--r-- | lib/tls13/early_data.h | 30 | ||||
-rw-r--r-- | lib/tls13/encrypted_extensions.c | 75 | ||||
-rw-r--r-- | lib/tls13/encrypted_extensions.h | 29 | ||||
-rw-r--r-- | lib/tls13/finished.c | 183 | ||||
-rw-r--r-- | lib/tls13/finished.h | 33 | ||||
-rw-r--r-- | lib/tls13/hello_retry.c | 195 | ||||
-rw-r--r-- | lib/tls13/hello_retry.h | 28 | ||||
-rw-r--r-- | lib/tls13/key_update.c | 217 | ||||
-rw-r--r-- | lib/tls13/key_update.h | 29 | ||||
-rw-r--r-- | lib/tls13/post_handshake.c | 252 | ||||
-rw-r--r-- | lib/tls13/psk_ext_parser.c | 111 | ||||
-rw-r--r-- | lib/tls13/psk_ext_parser.h | 58 | ||||
-rw-r--r-- | lib/tls13/session_ticket.c | 496 | ||||
-rw-r--r-- | lib/tls13/session_ticket.h | 50 |
23 files changed, 3575 insertions, 0 deletions
diff --git a/lib/tls13/anti_replay.c b/lib/tls13/anti_replay.c new file mode 100644 index 0000000..f8333c5 --- /dev/null +++ b/lib/tls13/anti_replay.c @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * 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 "db.h" +#include "system.h" +#include "tls13/anti_replay.h" + +/* The default time window in milliseconds; RFC8446 suggests the order + * of ten seconds is sufficient for the clients on the Internet. */ +#define DEFAULT_WINDOW_MS 10000 + +struct gnutls_anti_replay_st { + uint32_t window; + struct timespec start_time; + gnutls_db_add_func db_add_func; + void *db_ptr; +}; + +/** + * gnutls_anti_replay_init: + * @anti_replay: is a pointer to #gnutls_anti_replay_t type + * + * This function will allocate and initialize the @anti_replay context + * to be usable for detect replay attacks. The context can then be + * attached to a @gnutls_session_t with + * gnutls_anti_replay_enable(). + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.5 + **/ +int +gnutls_anti_replay_init(gnutls_anti_replay_t *anti_replay) +{ + *anti_replay = gnutls_calloc(1, sizeof(struct gnutls_anti_replay_st)); + if (!*anti_replay) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + (*anti_replay)->window = DEFAULT_WINDOW_MS; + + gnutls_gettime(&(*anti_replay)->start_time); + + return 0; +} + +/** + * gnutls_anti_replay_set_window: + * @anti_replay: is a #gnutls_anti_replay_t type. + * @window: is the time window recording ClientHello, in milliseconds + * + * Sets the time window used for ClientHello recording. In order to + * protect against replay attacks, the server records ClientHello + * messages within this time period from the last update, and + * considers it a replay when a ClientHello outside of the period; if + * a ClientHello arrives within this period, the server checks the + * database and detects duplicates. + * + * For the details of the algorithm, see RFC 8446, section 8.2. + * + * Since: 3.6.5 + */ +void +gnutls_anti_replay_set_window(gnutls_anti_replay_t anti_replay, + unsigned int window) +{ + anti_replay->window = window; +} + +/** + * gnutls_anti_replay_deinit: + * @anti_replay: is a #gnutls_anti_replay type + * + * This function will deinitialize all resources occupied by the given + * anti-replay context. + * + * Since: 3.6.5 + **/ +void +gnutls_anti_replay_deinit(gnutls_anti_replay_t anti_replay) +{ + gnutls_free(anti_replay); +} + +/** + * gnutls_anti_replay_enable: + * @session: is a #gnutls_session_t type. + * @anti_replay: is a #gnutls_anti_replay_t type. + * + * Request that the server should use anti-replay mechanism. + * + * Since: 3.6.5 + **/ +void +gnutls_anti_replay_enable(gnutls_session_t session, + gnutls_anti_replay_t anti_replay) +{ + if (unlikely(session->security_parameters.entity != GNUTLS_SERVER)) { + gnutls_assert(); + return; + } + + session->internals.anti_replay = anti_replay; +} + +int +_gnutls_anti_replay_check(gnutls_anti_replay_t anti_replay, + uint32_t client_ticket_age, + struct timespec *ticket_creation_time, + gnutls_datum_t *id) +{ + struct timespec now; + time_t window; + uint32_t server_ticket_age, diff; + gnutls_datum_t key = { NULL, 0 }; + gnutls_datum_t entry = { NULL, 0 }; + unsigned char key_buffer[MAX_HASH_SIZE + 12]; + unsigned char entry_buffer[12]; /* magic + timestamp + expire_time */ + unsigned char *p; + int ret; + + if (unlikely(id->size > MAX_HASH_SIZE)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + gnutls_gettime(&now); + server_ticket_age = timespec_sub_ms(&now, ticket_creation_time); + + /* It shouldn't be possible that the server's view of ticket + * age is smaller than the client's view. + */ + if (unlikely(server_ticket_age < client_ticket_age)) + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + + /* If ticket is created before recording has started, discard + * reject early data. + */ + if (_gnutls_timespec_cmp(ticket_creation_time, + &anti_replay->start_time) < 0) { + _gnutls_handshake_log("anti_replay: ticket is created before recording has started\n"); + return gnutls_assert_val(GNUTLS_E_EARLY_DATA_REJECTED); + } + + /* If certain amount of time (window) has elapsed, rollover + * the recording. + */ + diff = timespec_sub_ms(&now, &anti_replay->start_time); + if (diff > anti_replay->window) + gnutls_gettime(&anti_replay->start_time); + + /* If expected_arrival_time is out of window, reject early + * data. + */ + if (server_ticket_age - client_ticket_age > anti_replay->window) { + _gnutls_handshake_log("anti_replay: server ticket age: %u, client ticket age: %u\n", + server_ticket_age, + client_ticket_age); + return gnutls_assert_val(GNUTLS_E_EARLY_DATA_REJECTED); + } + + /* Check if the ClientHello is stored in the database. + */ + if (!anti_replay->db_add_func) + return gnutls_assert_val(GNUTLS_E_EARLY_DATA_REJECTED); + + /* Create a key for database lookup, prefixing window start + * time to ID. Note that this shouldn't clash with session ID + * used in TLS 1.2, because such IDs are 32 octets, while here + * the key becomes 44+ octets. + */ + p = key_buffer; + _gnutls_write_uint32((uint64_t) anti_replay->start_time.tv_sec >> 32, p); + p += 4; + _gnutls_write_uint32(anti_replay->start_time.tv_sec & 0xFFFFFFFF, p); + p += 4; + _gnutls_write_uint32(anti_replay->start_time.tv_nsec, p); + p += 4; + memcpy(p, id->data, id->size); + p += id->size; + key.data = key_buffer; + key.size = p - key_buffer; + + /* Create an entry to be stored on database if the lookup + * failed. This is formatted so that + * gnutls_db_check_entry_expire_time() work. + */ + p = entry_buffer; + _gnutls_write_uint32(PACKED_SESSION_MAGIC, p); + p += 4; + _gnutls_write_uint32(now.tv_sec, p); + p += 4; + window = anti_replay->window / 1000; + _gnutls_write_uint32(window, p); + p += 4; + entry.data = entry_buffer; + entry.size = p - entry_buffer; + + ret = anti_replay->db_add_func(anti_replay->db_ptr, + (uint64_t)now.tv_sec+(uint64_t)window, &key, &entry); + if (ret < 0) { + _gnutls_handshake_log("anti_replay: duplicate ClientHello found\n"); + return gnutls_assert_val(GNUTLS_E_EARLY_DATA_REJECTED); + } + + return 0; +} + +/** + * gnutls_anti_replay_set_ptr: + * @anti_replay: is a #gnutls_anti_replay_t type. + * @ptr: is the pointer + * + * Sets the pointer that will be provided to db add function + * as the first argument. + **/ +void gnutls_anti_replay_set_ptr(gnutls_anti_replay_t anti_replay, void *ptr) +{ + anti_replay->db_ptr = ptr; +} + +/** + * gnutls_anti_replay_set_add_function: + * @anti_replay: is a #gnutls_anti_replay_t type. + * @add_func: is the function. + * + * Sets the function that will be used to store an entry if it is not + * already present in the resumed sessions database. This function returns 0 + * if the entry is successfully stored, and a negative error code + * otherwise. In particular, if the entry is found in the database, + * it returns %GNUTLS_E_DB_ENTRY_EXISTS. + * + * The arguments to the @add_func are: + * - %ptr: the pointer set with gnutls_anti_replay_set_ptr() + * - %exp_time: the expiration time of the entry + * - %key: a pointer to the key + * - %data: a pointer to data to store + * + * The data set by this function can be examined using + * gnutls_db_check_entry_expire_time() and gnutls_db_check_entry_time(). + * + * Since: 3.6.5 + **/ +void +gnutls_anti_replay_set_add_function(gnutls_anti_replay_t anti_replay, + gnutls_db_add_func add_func) +{ + anti_replay->db_add_func = add_func; +} diff --git a/lib/tls13/anti_replay.h b/lib/tls13/anti_replay.h new file mode 100644 index 0000000..87726c3 --- /dev/null +++ b/lib/tls13/anti_replay.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_ANTI_REPLAY_H +#define GNUTLS_LIB_TLS13_ANTI_REPLAY_H + +int _gnutls_anti_replay_check(gnutls_anti_replay_t, + uint32_t client_ticket_age, + struct timespec *ticket_creation_time, + gnutls_datum_t *id); + +#endif /* GNUTLS_LIB_TLS13_ANTI_REPLAY_H */ diff --git a/lib/tls13/certificate.c b/lib/tls13/certificate.c new file mode 100644 index 0000000..9792629 --- /dev/null +++ b/lib/tls13/certificate.c @@ -0,0 +1,662 @@ +/* + * 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 "compress.h" +#include "errors.h" +#include "extv.h" +#include "handshake.h" +#include "tls13/certificate.h" +#include "auth/cert.h" +#include "mbuffers.h" +#include "ext/compress_certificate.h" +#include "ext/status_request.h" + +static int parse_cert_extension(void *ctx, unsigned tls_id, const uint8_t *data, unsigned data_size); +static int parse_cert_list(gnutls_session_t session, uint8_t * data, size_t data_size); +static int compress_certificate(gnutls_buffer_st * buf, unsigned cert_pos_mark, + gnutls_compression_method_t comp_method); +static int decompress_certificate(gnutls_buffer_st * buf); + +int _gnutls13_recv_certificate(gnutls_session_t session) +{ + int ret, err, decompress_cert = 0; + gnutls_buffer_st buf; + unsigned optional = 0; + + if (!session->internals.initial_negotiation_completed && + session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + + if (session->security_parameters.entity == GNUTLS_SERVER) { + /* if we didn't request a certificate, there will not be any */ + if (session->internals.send_cert_req == 0) + return 0; + + if (session->internals.send_cert_req != GNUTLS_CERT_REQUIRE) + optional = 1; + } + + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_CERTIFICATE_PKT, 0, &buf); + if (ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET) { + /* check if we received compressed certificate */ + err = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT, 0, &buf); + if (err >= 0) { + decompress_cert = 1; + ret = err; + } + } + if (ret < 0) { + if (ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET && session->internals.send_cert_req) + return gnutls_assert_val(GNUTLS_E_NO_CERTIFICATE_FOUND); + + return gnutls_assert_val(ret); + } + + if (buf.length == 0) { + gnutls_assert(); + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + goto cleanup; + } + + if (decompress_cert) { + ret = decompress_certificate(&buf); + if (ret < 0) { + gnutls_assert(); + gnutls_alert_send(session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + goto cleanup; + } + } + + if (session->internals.initial_negotiation_completed && + session->internals.post_handshake_cr_context.size > 0) { + gnutls_datum_t context; + + /* verify whether the context matches */ + ret = _gnutls_buffer_pop_datum_prefix8(&buf, &context); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + if (context.size != session->internals.post_handshake_cr_context.size || + memcmp(context.data, session->internals.post_handshake_cr_context.data, + context.size) != 0) { + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + gnutls_assert(); + goto cleanup; + } + } else { + if (buf.data[0] != 0) { + /* The context field must be empty during handshake */ + gnutls_assert(); + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + goto cleanup; + } + + /* buf.length is positive */ + buf.data++; + buf.length--; + } + + _gnutls_handshake_log("HSK[%p]: parsing certificate message\n", session); + + ret = parse_cert_list(session, buf.data, buf.length); + if (ret < 0) { + if (ret == GNUTLS_E_NO_CERTIFICATE_FOUND) { + if (optional) + ret = 0; + else if (session->security_parameters.entity == + GNUTLS_SERVER) + ret = GNUTLS_E_CERTIFICATE_REQUIRED; + } + gnutls_assert(); + goto cleanup; + } + + session->internals.hsk_flags |= HSK_CRT_VRFY_EXPECTED; + + ret = 0; +cleanup: + + _gnutls_buffer_clear(&buf); + return ret; +} + +struct ocsp_req_ctx_st { + gnutls_pcert_st *pcert; + unsigned cert_index; + gnutls_session_t session; + gnutls_certificate_credentials_t cred; +}; + +static +int append_status_request(void *_ctx, gnutls_buffer_st *buf) +{ + struct ocsp_req_ctx_st *ctx = _ctx; + gnutls_session_t session = ctx->session; + int ret; + gnutls_datum_t resp; + unsigned free_resp = 0; + + assert(session->internals.selected_ocsp_func != NULL || + session->internals.selected_ocsp_length != 0); + + /* The global ocsp callback function can only be used to return + * a single certificate request */ + if (session->internals.selected_ocsp_length == 1 && ctx->cert_index != 0) + return 0; + + if (session->internals.selected_ocsp_length > 0) { + if (ctx->cert_index < session->internals.selected_ocsp_length) { + if ((session->internals.selected_ocsp[ctx->cert_index].exptime != 0 && + gnutls_time(0) >= session->internals.selected_ocsp[ctx->cert_index].exptime) || + session->internals.selected_ocsp[ctx->cert_index].response.data == NULL) { + return 0; + } + + resp.data = session->internals.selected_ocsp[ctx->cert_index].response.data; + resp.size = session->internals.selected_ocsp[ctx->cert_index].response.size; + ret = 0; + } else { + return 0; + } + } else if (session->internals.selected_ocsp_func) { + if (ctx->cert_index == 0) { + ret = session->internals.selected_ocsp_func(session, session->internals.selected_ocsp_func_ptr, &resp); + free_resp = 1; + } else { + return 0; + } + } else + return 0; + + if (ret == GNUTLS_E_NO_CERTIFICATE_STATUS || resp.data == 0) { + return 0; + } else if (ret < 0) { + return gnutls_assert_val(ret); + } + + ret = _gnutls_buffer_append_data(buf, "\x01", 1); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_data_prefix(buf, 24, resp.data, resp.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = 0; + cleanup: + if (free_resp) + gnutls_free(resp.data); + return ret; +} + +int _gnutls13_send_certificate(gnutls_session_t session, unsigned again) +{ + int ret, compress_cert; + gnutls_pcert_st *apr_cert_list = NULL; + gnutls_privkey_t apr_pkey = NULL; + int apr_cert_list_length = 0; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + unsigned pos_mark, ext_pos_mark, cert_pos_mark; + unsigned i; + struct ocsp_req_ctx_st ctx; + gnutls_certificate_credentials_t cred; + gnutls_compression_method_t comp_method; + gnutls_handshake_description_t h_type; + + comp_method = gnutls_compress_certificate_get_selected_method(session); + compress_cert = comp_method != GNUTLS_COMP_UNKNOWN; + h_type = compress_cert ? GNUTLS_HANDSHAKE_COMPRESSED_CERTIFICATE_PKT + : GNUTLS_HANDSHAKE_CERTIFICATE_PKT; + + if (again == 0) { + if (!session->internals.initial_negotiation_completed && + session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + + if (session->security_parameters.entity == GNUTLS_SERVER && + session->internals.resumed) + return 0; + + cred = (gnutls_certificate_credentials_t) + _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); + if (cred == NULL) { + gnutls_assert(); + return GNUTLS_E_INSUFFICIENT_CREDENTIALS; + } + + if (session->security_parameters.entity == GNUTLS_CLIENT && + !(session->internals.hsk_flags & HSK_CRT_ASKED)) { + return 0; + } + + ret = _gnutls_get_selected_cert(session, &apr_cert_list, + &apr_cert_list_length, &apr_pkey); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + cert_pos_mark = buf.length; + + if (session->security_parameters.entity == GNUTLS_CLIENT) { + ret = _gnutls_buffer_append_data_prefix(&buf, 8, + session->internals.post_handshake_cr_context.data, + session->internals.post_handshake_cr_context.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + } else { + ret = _gnutls_buffer_append_prefix(&buf, 8, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } + + /* mark total size */ + pos_mark = buf.length; + ret = _gnutls_buffer_append_prefix(&buf, 24, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + for (i=0;i<(unsigned)apr_cert_list_length;i++) { + ret = _gnutls_buffer_append_data_prefix(&buf, 24, + apr_cert_list[i].cert.data, + apr_cert_list[i].cert.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + +#ifdef ENABLE_OCSP + if ((session->internals.selected_ocsp_length > 0 || + session->internals.selected_ocsp_func) && + (((session->internals.hsk_flags & HSK_OCSP_REQUESTED) && IS_SERVER(session)) || + ((session->internals.hsk_flags & HSK_CLIENT_OCSP_REQUESTED) && !IS_SERVER(session)))) { + /* append status response if available */ + ret = _gnutls_extv_append_init(&buf); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + ext_pos_mark = ret; + + ctx.pcert = &apr_cert_list[i]; + ctx.cert_index = i; + ctx.session = session; + ctx.cred = cred; + ret = _gnutls_extv_append(&buf, STATUS_REQUEST_TLS_ID, + &ctx, append_status_request); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_extv_append_final(&buf, ext_pos_mark, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } else +#endif + { + ret = _gnutls_buffer_append_prefix(&buf, 16, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } + } + + _gnutls_write_uint24(buf.length-pos_mark-3, &buf.data[pos_mark]); + + if (compress_cert) { + ret = compress_certificate(&buf, cert_pos_mark, comp_method); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } + + bufel = _gnutls_buffer_to_mbuffer(&buf); + } + + return _gnutls_send_handshake(session, bufel, h_type); + + cleanup: + _gnutls_buffer_clear(&buf); + return ret; +} + +typedef struct crt_cert_ctx_st { + gnutls_session_t session; + gnutls_datum_t *ocsp; + unsigned idx; +} crt_cert_ctx_st; + +static int parse_cert_extension(void *_ctx, unsigned tls_id, const uint8_t *data, unsigned data_size) +{ + crt_cert_ctx_st *ctx = _ctx; + gnutls_session_t session = ctx->session; + int ret; + + if (tls_id == STATUS_REQUEST_TLS_ID) { +#ifdef ENABLE_OCSP + if (!_gnutls_hello_ext_is_present(session, ext_mod_status_request.gid)) { + gnutls_assert(); + goto unexpected; + } + + _gnutls_handshake_log("Found OCSP response on cert %d\n", ctx->idx); + + ret = _gnutls_parse_ocsp_response(session, data, data_size, ctx->ocsp); + if (ret < 0) + return gnutls_assert_val(ret); +#endif + } else { + goto unexpected; + } + + return 0; + + unexpected: + _gnutls_debug_log("received unexpected certificate extension (%d)\n", (int)tls_id); + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION); +} + +static int +parse_cert_list(gnutls_session_t session, uint8_t * data, size_t data_size) +{ + int ret; + size_t len; + uint8_t *p = data; + cert_auth_info_t info; + gnutls_certificate_credentials_t cred; + size_t size; + int i; + unsigned npeer_certs, npeer_ocsp, j; + crt_cert_ctx_st ctx; + gnutls_datum_t *peer_certs = NULL; + gnutls_datum_t *peer_ocsp = NULL; + unsigned nentries = 0; + + cred = (gnutls_certificate_credentials_t) + _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); + if (cred == NULL) { + gnutls_assert(); + return GNUTLS_E_INSUFFICIENT_CREDENTIALS; + } + + if ((ret = + _gnutls_auth_info_init(session, GNUTLS_CRD_CERTIFICATE, + sizeof(cert_auth_info_st), 1)) < 0) { + gnutls_assert(); + return ret; + } + + if (data == NULL || data_size == 0) { + /* no certificate was sent */ + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + } + + info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); + if (info == NULL) + return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + + DECR_LEN(data_size, 3); + size = _gnutls_read_uint24(p); + p += 3; + + if (size != data_size) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + if (size == 0) + return gnutls_assert_val(GNUTLS_E_NO_CERTIFICATE_FOUND); + + i = data_size; + + while (i > 0) { + DECR_LEN(data_size, 3); + len = _gnutls_read_uint24(p); + if (len == 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + DECR_LEN(data_size, len); + p += len + 3; + i -= len + 3; + + DECR_LEN(data_size, 2); + len = _gnutls_read_uint16(p); + DECR_LEN(data_size, len); + + i -= len + 2; + p += len + 2; + + nentries++; + } + + if (data_size != 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + /* this is unnecessary - keeping to avoid a regression due to a re-org + * of the loop above */ + if (nentries == 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + npeer_ocsp = 0; + npeer_certs = 0; + + /* Ok we now allocate the memory to hold the + * certificate list + */ + peer_certs = gnutls_calloc(nentries, sizeof(gnutls_datum_t)); + if (peer_certs == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + peer_ocsp = gnutls_calloc(nentries, sizeof(gnutls_datum_t)); + if (peer_ocsp == NULL) { + ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + goto cleanup; + } + + p = data+3; + + /* Now we start parsing the list (again). + * We don't use DECR_LEN since the list has + * been parsed before. + */ + + ctx.session = session; + + for (j = 0; j < nentries; j++) { + len = _gnutls_read_uint24(p); + p += 3; + + ret = _gnutls_set_datum(&peer_certs[j], p, len); + if (ret < 0) { + gnutls_assert(); + ret = GNUTLS_E_CERTIFICATE_ERROR; + goto cleanup; + } + npeer_certs++; + + p += len; + + len = _gnutls_read_uint16(p); + + ctx.ocsp = &peer_ocsp[j]; + ctx.idx = j; + + ret = _gnutls_extv_parse(&ctx, parse_cert_extension, p, len+2); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + p += len+2; + npeer_ocsp++; + } + + /* The OCSP entries match the certificate entries, although + * the contents of each OCSP entry may be NULL. + */ + for(j=0;j<info->ncerts;j++) + gnutls_free(info->raw_certificate_list[j].data); + gnutls_free(info->raw_certificate_list); + + for(j=0;j<info->nocsp;j++) + gnutls_free(info->raw_ocsp_list[j].data); + gnutls_free(info->raw_ocsp_list); + + + info->raw_certificate_list = peer_certs; + info->ncerts = npeer_certs; + + info->raw_ocsp_list = peer_ocsp; + info->nocsp = npeer_ocsp; + + return 0; + + cleanup: + for(j=0;j<npeer_certs;j++) + gnutls_free(peer_certs[j].data); + + for(j=0;j<npeer_ocsp;j++) + gnutls_free(peer_ocsp[j].data); + gnutls_free(peer_certs); + gnutls_free(peer_ocsp); + return ret; + +} + +static int +compress_certificate(gnutls_buffer_st * buf, unsigned cert_pos_mark, + gnutls_compression_method_t comp_method) +{ + int ret, method_num; + size_t comp_bound; + gnutls_datum_t plain, comp = { NULL, 0 }; + + method_num = _gnutls_compress_certificate_method2num(comp_method); + if (method_num == GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + + plain.data = buf->data + cert_pos_mark; + plain.size = buf->length - cert_pos_mark; + + comp_bound = _gnutls_compress_bound(comp_method, plain.size); + if (comp_bound == 0) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + comp.data = gnutls_malloc(comp_bound); + if (comp.data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + ret = _gnutls_compress(comp_method, comp.data, comp_bound, plain.data, plain.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + comp.size = ret; + + buf->length = cert_pos_mark; + ret = _gnutls_buffer_append_prefix(buf, 16, method_num); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + ret = _gnutls_buffer_append_prefix(buf, 24, plain.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + ret = _gnutls_buffer_append_data_prefix(buf, 24, comp.data, comp.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + +cleanup: + gnutls_free(comp.data); + return ret; +} + +static int +decompress_certificate(gnutls_buffer_st * buf) +{ + int ret; + size_t method_num, plain_exp_len; + gnutls_datum_t comp, plain = { NULL, 0 }; + gnutls_compression_method_t comp_method; + + ret = _gnutls_buffer_pop_prefix16(buf, &method_num, 0); + if (ret < 0) + return gnutls_assert_val(ret); + comp_method = _gnutls_compress_certificate_num2method(method_num); + + ret = _gnutls_buffer_pop_prefix24(buf, &plain_exp_len, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_buffer_pop_datum_prefix24(buf, &comp); + if (ret < 0) + return gnutls_assert_val(ret); + + plain.data = gnutls_malloc(plain_exp_len); + if (plain.data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + ret = _gnutls_decompress(comp_method, plain.data, plain_exp_len, comp.data, comp.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + plain.size = ret; + + if (plain.size != plain_exp_len) { + gnutls_assert(); + ret = GNUTLS_E_DECOMPRESSION_FAILED; + goto cleanup; + } + + _gnutls_buffer_clear(buf); + ret = _gnutls_buffer_append_data(buf, plain.data, plain.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + +cleanup: + gnutls_free(plain.data); + return ret; +} diff --git a/lib/tls13/certificate.h b/lib/tls13/certificate.h new file mode 100644 index 0000000..6f46cc2 --- /dev/null +++ b/lib/tls13/certificate.h @@ -0,0 +1,29 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_CERTIFICATE_H +#define GNUTLS_LIB_TLS13_CERTIFICATE_H + +int _gnutls13_recv_certificate(gnutls_session_t session); +int _gnutls13_send_certificate(gnutls_session_t session, unsigned again); + +#endif /* GNUTLS_LIB_TLS13_CERTIFICATE_H */ diff --git a/lib/tls13/certificate_request.c b/lib/tls13/certificate_request.c new file mode 100644 index 0000000..b613cab --- /dev/null +++ b/lib/tls13/certificate_request.c @@ -0,0 +1,393 @@ +/* + * 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 "errors.h" +#include "extv.h" +#include "handshake.h" +#include "tls13/certificate_request.h" +#include "ext/compress_certificate.h" +#include "ext/signature.h" +#include "ext/status_request.h" +#include "mbuffers.h" +#include "algorithms.h" +#include "auth/cert.h" + +/* for tlist dereference */ +#include "x509/verify-high.h" + +#define EXTID_CERTIFICATE_AUTHORITIES 47 + +typedef struct crt_req_ctx_st { + gnutls_session_t session; + unsigned got_sig_algo; + gnutls_pk_algorithm_t pk_algos[MAX_ALGOS]; + unsigned pk_algos_length; + const uint8_t *rdn; /* pointer inside the message buffer */ + unsigned rdn_size; +} crt_req_ctx_st; + +static unsigned is_algo_in_list(gnutls_pk_algorithm_t algo, gnutls_pk_algorithm_t *list, unsigned list_size) +{ + unsigned j; + + for (j=0;j<list_size;j++) { + if (list[j] == algo) + return 1; + } + return 0; +} + +static +int parse_cert_extension(void *_ctx, unsigned tls_id, const uint8_t *data, unsigned data_size) +{ + crt_req_ctx_st *ctx = _ctx; + gnutls_session_t session = ctx->session; + unsigned v; + int ret; + + /* Decide which certificate to use if the signature algorithms extension + * is present. + */ + if (tls_id == ext_mod_sig.tls_id) { + const version_entry_st *ver = get_version(session); + const gnutls_sign_entry_st *se; + /* signature algorithms; let's use it to decide the certificate to use */ + unsigned i; + + if (ctx->got_sig_algo) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION); + + ctx->got_sig_algo = 1; + + if (data_size < 2) + return gnutls_assert_val(GNUTLS_E_TLS_PACKET_DECODING_ERROR); + + v = _gnutls_read_uint16(data); + if (v != data_size-2) + return gnutls_assert_val(GNUTLS_E_TLS_PACKET_DECODING_ERROR); + + data += 2; + data_size -= 2; + + ret = _gnutls_sign_algorithm_parse_data(session, data, data_size); + if (ret < 0) + return gnutls_assert_val(ret); + + /* The APIs to retrieve a client certificate accept the public + * key algorithms instead of signatures. Get the public key algorithms + * from the signatures. + */ + for (i=0;i<(unsigned)data_size;i+=2) { + se = _gnutls_tls_aid_to_sign_entry(data[i], data[i+1], ver); + if (se == NULL) + continue; + + if (ctx->pk_algos_length >= sizeof(ctx->pk_algos)/sizeof(ctx->pk_algos[0])) + break; + + if (is_algo_in_list(se->pk, ctx->pk_algos, ctx->pk_algos_length)) + continue; + + ctx->pk_algos[ctx->pk_algos_length++] = se->pk; + } +#ifdef ENABLE_OCSP + } else if (tls_id == ext_mod_status_request.tls_id) { + if (data_size != 0) + return gnutls_assert_val(GNUTLS_E_TLS_PACKET_DECODING_ERROR); + + /* we are now allowed to send OCSP staples */ + session->internals.hsk_flags |= HSK_CLIENT_OCSP_REQUESTED; +#endif + } else if (tls_id == EXTID_CERTIFICATE_AUTHORITIES) { + if (data_size < 3) { + return gnutls_assert_val(GNUTLS_E_TLS_PACKET_DECODING_ERROR); + } + + v = _gnutls_read_uint16(data); + if (v != data_size-2) + return gnutls_assert_val(GNUTLS_E_TLS_PACKET_DECODING_ERROR); + + ctx->rdn = data+2; + ctx->rdn_size = v; + } else if (tls_id == ext_mod_compress_certificate.tls_id) { + ret = _gnutls_compress_certificate_recv_params(session, + data, + data_size); + if (ret < 0) { + return gnutls_assert_val(ret); + } + } + + return 0; +} + +int _gnutls13_recv_certificate_request_int(gnutls_session_t session, gnutls_buffer_st *buf) +{ + int ret; + crt_req_ctx_st ctx; + gnutls_pcert_st *apr_cert_list; + gnutls_privkey_t apr_pkey; + int apr_cert_list_length; + + _gnutls_handshake_log("HSK[%p]: parsing certificate request\n", session); + + if (unlikely(session->security_parameters.entity == GNUTLS_SERVER)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + /* if initial negotiation is complete, this is a post-handshake auth */ + if (!session->internals.initial_negotiation_completed) { + if (buf->data[0] != 0) { + /* The context field must be empty during handshake */ + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + } + + /* buf->length is positive */ + buf->data++; + buf->length--; + } else { + gnutls_datum_t context; + + ret = _gnutls_buffer_pop_datum_prefix8(buf, &context); + if (ret < 0) + return gnutls_assert_val(ret); + + gnutls_free(session->internals.post_handshake_cr_context.data); + ret = _gnutls_set_datum(&session->internals.post_handshake_cr_context, + context.data, context.size); + if (ret < 0) + return gnutls_assert_val(ret); + } + + memset(&ctx, 0, sizeof(ctx)); + ctx.session = session; + + ret = _gnutls_extv_parse(&ctx, parse_cert_extension, buf->data, buf->length); + if (ret < 0) + return gnutls_assert_val(ret); + + /* The "signature_algorithms" extension MUST be specified */ + if (!ctx.got_sig_algo) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION); + + session->internals.hsk_flags |= HSK_CRT_ASKED; + + ret = _gnutls_select_client_cert(session, ctx.rdn, ctx.rdn_size, + ctx.pk_algos, ctx.pk_algos_length); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_get_selected_cert(session, &apr_cert_list, + &apr_cert_list_length, &apr_pkey); + if (ret < 0) + return gnutls_assert_val(ret); + + if (apr_cert_list_length > 0) { + gnutls_sign_algorithm_t algo; + + algo = _gnutls_session_get_sign_algo(session, &apr_cert_list[0], apr_pkey, 0, GNUTLS_KX_UNKNOWN); + if (algo == GNUTLS_SIGN_UNKNOWN) { + _gnutls_handshake_log("HSK[%p]: rejecting client auth because of no suitable signature algorithm\n", session); + _gnutls_selected_certs_deinit(session); + return gnutls_assert_val(0); + } + + gnutls_sign_algorithm_set_client(session, algo); + } + + return 0; +} + +int _gnutls13_recv_certificate_request(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + + if (!session->internals.initial_negotiation_completed && + session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + + if (unlikely(session->security_parameters.entity != GNUTLS_CLIENT)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST, 1, &buf); + if (ret < 0) + return gnutls_assert_val(ret); + + /* if not received */ + if (buf.length == 0) { + _gnutls_buffer_clear(&buf); + return 0; + } + + ret = _gnutls13_recv_certificate_request_int(session, &buf); + + _gnutls_buffer_clear(&buf); + return ret; +} + +static +int write_certificate_authorities(void *ctx, gnutls_buffer_st *buf) +{ + gnutls_session_t session = ctx; + gnutls_certificate_credentials_t cred; + + if (session->internals.ignore_rdn_sequence != 0) + return 0; + + cred = (gnutls_certificate_credentials_t) + _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); + if (cred == NULL) { + gnutls_assert(); + return GNUTLS_E_INSUFFICIENT_CREDENTIALS; + } + + if (cred->tlist->x509_rdn_sequence.size == 0) + return 0; + + return + _gnutls_buffer_append_data_prefix(buf, 16, + cred-> + tlist->x509_rdn_sequence. + data, + cred-> + tlist->x509_rdn_sequence. + size); +} + +static int append_empty_ext(void *ctx, gnutls_buffer_st *buf) +{ + return GNUTLS_E_INT_RET_0; +} + +int _gnutls13_send_certificate_request(gnutls_session_t session, unsigned again) +{ + gnutls_certificate_credentials_t cred; + int ret; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + unsigned init_pos; + + if (again == 0) { + unsigned char rnd[12]; + + if (!session->internals.initial_negotiation_completed && + session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + + if (session->internals.send_cert_req == 0) + return 0; + + cred = (gnutls_certificate_credentials_t) + _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); + if (cred == NULL) + return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + if (session->internals.initial_negotiation_completed) { /* reauth */ + ret = gnutls_rnd(GNUTLS_RND_NONCE, rnd, sizeof(rnd)); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + gnutls_free(session->internals.post_handshake_cr_context.data); + ret = _gnutls_set_datum(&session->internals.post_handshake_cr_context, + rnd, sizeof(rnd)); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_data_prefix(&buf, 8, + session->internals.post_handshake_cr_context.data, + session->internals.post_handshake_cr_context.size); + } else { + ret = _gnutls_buffer_append_prefix(&buf, 8, 0); + } + + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_extv_append_init(&buf); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + init_pos = ret; + + ret = _gnutls_extv_append(&buf, ext_mod_sig.tls_id, session, + (extv_append_func)_gnutls_sign_algorithm_write_params); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_extv_append(&buf, EXTID_CERTIFICATE_AUTHORITIES, session, + write_certificate_authorities); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + +#ifdef ENABLE_OCSP + /* We always advertise our support for OCSP stapling */ + ret = _gnutls_extv_append(&buf, ext_mod_status_request.tls_id, session, + append_empty_ext); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + session->internals.hsk_flags |= HSK_CLIENT_OCSP_REQUESTED; +#endif + + ret = _gnutls_extv_append(&buf, ext_mod_compress_certificate.tls_id, session, + (extv_append_func)_gnutls_compress_certificate_send_params); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_extv_append_final(&buf, init_pos, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + bufel = _gnutls_buffer_to_mbuffer(&buf); + + session->internals.hsk_flags |= HSK_CRT_REQ_SENT; + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST); + + cleanup: + _gnutls_buffer_clear(&buf); + return ret; + +} + diff --git a/lib/tls13/certificate_request.h b/lib/tls13/certificate_request.h new file mode 100644 index 0000000..40ac9f2 --- /dev/null +++ b/lib/tls13/certificate_request.h @@ -0,0 +1,31 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_CERTIFICATE_REQUEST_H +#define GNUTLS_LIB_TLS13_CERTIFICATE_REQUEST_H + +int _gnutls13_recv_certificate_request(gnutls_session_t session); +int _gnutls13_recv_certificate_request_int(gnutls_session_t session, gnutls_buffer_st *buf); + +int _gnutls13_send_certificate_request(gnutls_session_t session, unsigned again); + +#endif /* GNUTLS_LIB_TLS13_CERTIFICATE_REQUEST_H */ diff --git a/lib/tls13/certificate_verify.c b/lib/tls13/certificate_verify.c new file mode 100644 index 0000000..45ff6fa --- /dev/null +++ b/lib/tls13/certificate_verify.c @@ -0,0 +1,241 @@ +/* + * 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 "errors.h" +#include "handshake.h" +#include "auth/cert.h" +#include "ext/signature.h" +#include "algorithms.h" +#include "tls13-sig.h" +#include "mbuffers.h" +#include "tls13/certificate_verify.h" + +#define SRV_CTX "TLS 1.3, server CertificateVerify" +static const gnutls_datum_t srv_ctx = { + (void*)SRV_CTX, sizeof(SRV_CTX)-1 +}; + +#define CLI_CTX "TLS 1.3, client CertificateVerify" +static const gnutls_datum_t cli_ctx = { + (void*)CLI_CTX, sizeof(CLI_CTX)-1 +}; + +int _gnutls13_recv_certificate_verify(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + const gnutls_sign_entry_st *se; + gnutls_datum_t sig_data; + gnutls_certificate_credentials_t cred; + unsigned vflags; + gnutls_pcert_st peer_cert; + cert_auth_info_t info = _gnutls_get_auth_info(session, GNUTLS_CRD_CERTIFICATE); + bool server = 0; + gnutls_certificate_type_t cert_type; + + memset(&peer_cert, 0, sizeof(peer_cert)); + + /* this message is only expected if we have received + * a certificate message */ + if (!(session->internals.hsk_flags & HSK_CRT_VRFY_EXPECTED)) + return 0; + + if (session->security_parameters.entity == GNUTLS_SERVER) + server = 1; + + cred = (gnutls_certificate_credentials_t) + _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); + if (unlikely(cred == NULL)) + return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + if (unlikely(info == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY, 0, &buf); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_handshake_log("HSK[%p]: Parsing certificate verify\n", session); + + if (buf.length < 2) { + gnutls_assert(); + ret = GNUTLS_E_UNEXPECTED_PACKET_LENGTH; + goto cleanup; + } + + se = _gnutls_tls_aid_to_sign_entry(buf.data[0], buf.data[1], get_version(session)); + if (se == NULL) { + _gnutls_handshake_log("Found unsupported signature (%d.%d)\n", (int)buf.data[0], (int)buf.data[1]); + ret = gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + goto cleanup; + } + + if (server) + gnutls_sign_algorithm_set_client(session, se->id); + else + gnutls_sign_algorithm_set_server(session, se->id); + + buf.data+=2; + buf.length-=2; + + /* we check during verification whether the algorithm is enabled */ + + ret = _gnutls_buffer_pop_datum_prefix16(&buf, &sig_data); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + if (sig_data.size == 0) { + gnutls_assert(); + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + goto cleanup; + } + + /* We verify the certificate of the peer. Therefore we need to + * retrieve the negotiated certificate type for the peer. */ + cert_type = get_certificate_type(session, GNUTLS_CTYPE_PEERS); + + /* Verify the signature */ + ret = _gnutls_get_auth_info_pcert(&peer_cert, cert_type, info); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + vflags = cred->verify_flags | session->internals.additional_verify_flags; + + ret = _gnutls13_handshake_verify_data(session, vflags, &peer_cert, + server?(&cli_ctx):(&srv_ctx), + &sig_data, se); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + if (buf.length > 0) { + gnutls_assert(); + ret = GNUTLS_E_UNEXPECTED_PACKET_LENGTH; + goto cleanup; + } + + ret = 0; + cleanup: + gnutls_pcert_deinit(&peer_cert); + _gnutls_buffer_clear(&buf); + return ret; +} + +int _gnutls13_send_certificate_verify(gnutls_session_t session, unsigned again) +{ + int ret; + gnutls_pcert_st *apr_cert_list; + gnutls_privkey_t apr_pkey; + int apr_cert_list_length; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + gnutls_datum_t sig = {NULL, 0}; + gnutls_sign_algorithm_t algo; + const gnutls_sign_entry_st *se; + bool server = 0; + + if (again == 0) { + if (!session->internals.initial_negotiation_completed && + session->internals.hsk_flags & HSK_PSK_SELECTED) + return 0; + + if (session->security_parameters.entity == GNUTLS_SERVER && + session->internals.resumed) + return 0; + + if (session->security_parameters.entity == GNUTLS_SERVER) + server = 1; + + ret = _gnutls_get_selected_cert(session, &apr_cert_list, + &apr_cert_list_length, &apr_pkey); + if (ret < 0) + return gnutls_assert_val(ret); + + if (apr_cert_list_length == 0) { + if (server) { + return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + } else { + /* for client, this means either we + * didn't get a cert request or we are + * declining authentication; in either + * case we don't send a cert verify */ + return 0; + } + } + + if (server) { + algo = _gnutls_session_get_sign_algo(session, &apr_cert_list[0], apr_pkey, 0, GNUTLS_KX_UNKNOWN); + if (algo == GNUTLS_SIGN_UNKNOWN) + return gnutls_assert_val(GNUTLS_E_INCOMPATIBLE_SIG_WITH_KEY); + + gnutls_sign_algorithm_set_server(session, algo); + } else { + /* for client, signature algorithm is already + * determined from Certificate Request */ + algo = gnutls_sign_algorithm_get_client(session); + if (unlikely(algo == GNUTLS_SIGN_UNKNOWN)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + se = _gnutls_sign_to_entry(algo); + + ret = _gnutls13_handshake_sign_data(session, &apr_cert_list[0], apr_pkey, + server?(&srv_ctx):(&cli_ctx), + &sig, se); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_data(&buf, se->aid.id, 2); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_data_prefix(&buf, 16, sig.data, sig.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + bufel = _gnutls_buffer_to_mbuffer(&buf); + + gnutls_free(sig.data); + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY); + + cleanup: + gnutls_free(sig.data); + _gnutls_buffer_clear(&buf); + return ret; +} diff --git a/lib/tls13/certificate_verify.h b/lib/tls13/certificate_verify.h new file mode 100644 index 0000000..9d4ce31 --- /dev/null +++ b/lib/tls13/certificate_verify.h @@ -0,0 +1,29 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_CERTIFICATE_VERIFY_H +#define GNUTLS_LIB_TLS13_CERTIFICATE_VERIFY_H + +int _gnutls13_recv_certificate_verify(gnutls_session_t session); +int _gnutls13_send_certificate_verify(gnutls_session_t session, unsigned again); + +#endif /* GNUTLS_LIB_TLS13_CERTIFICATE_VERIFY_H */ diff --git a/lib/tls13/early_data.c b/lib/tls13/early_data.c new file mode 100644 index 0000000..3d565d5 --- /dev/null +++ b/lib/tls13/early_data.c @@ -0,0 +1,107 @@ +/* + * 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 "handshake.h" +#include "tls13/early_data.h" + +int _gnutls13_send_early_data(gnutls_session_t session) +{ + int ret; + + if (!(session->security_parameters.entity == GNUTLS_CLIENT && + session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) + return 0; + + while (session->internals.early_data_presend_buffer.length > 0) { + ret = + gnutls_record_send(session, + session->internals. + early_data_presend_buffer.data, + session->internals. + early_data_presend_buffer. + length); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.early_data_presend_buffer.data += ret; + session->internals.early_data_presend_buffer.length -= ret; + } + + + return 0; +} + +int _gnutls13_send_end_of_early_data(gnutls_session_t session, unsigned again) +{ + int ret; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + + if (!(session->security_parameters.entity == GNUTLS_CLIENT && + session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED)) + return 0; + + if (session->internals.flags & GNUTLS_NO_END_OF_EARLY_DATA) { + return 0; + } + + if (again == 0) { + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + bufel = _gnutls_buffer_to_mbuffer(&buf); + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_END_OF_EARLY_DATA); +} + +int _gnutls13_recv_end_of_early_data(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + + if (!(session->security_parameters.entity == GNUTLS_SERVER && + session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED)) + return 0; + + if (!(session->internals.flags & GNUTLS_NO_END_OF_EARLY_DATA)) { + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_END_OF_EARLY_DATA, 0, &buf); + if (ret < 0) + return gnutls_assert_val(ret); + + if (buf.length != 0) { + gnutls_assert(); + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + goto cleanup; + } + } + + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + + ret = 0; +cleanup: + + _gnutls_buffer_clear(&buf); + return ret; +} diff --git a/lib/tls13/early_data.h b/lib/tls13/early_data.h new file mode 100644 index 0000000..6fef12c --- /dev/null +++ b/lib/tls13/early_data.h @@ -0,0 +1,30 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_EARLY_DATA_H +#define GNUTLS_LIB_TLS13_EARLY_DATA_H + +int _gnutls13_send_end_of_early_data(gnutls_session_t session, unsigned again); +int _gnutls13_recv_end_of_early_data(gnutls_session_t session); +int _gnutls13_send_early_data(gnutls_session_t session); + +#endif /* GNUTLS_LIB_TLS13_EARLY_DATA_H */ diff --git a/lib/tls13/encrypted_extensions.c b/lib/tls13/encrypted_extensions.c new file mode 100644 index 0000000..84c328f --- /dev/null +++ b/lib/tls13/encrypted_extensions.c @@ -0,0 +1,75 @@ +/* + * 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 "errors.h" +#include "hello_ext.h" +#include "handshake.h" +#include "mbuffers.h" +#include "tls13/encrypted_extensions.h" + +int _gnutls13_recv_encrypted_extensions(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_ENCRYPTED_EXTENSIONS, 0, &buf); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_handshake_log("HSK[%p]: parsing encrypted extensions\n", session); + ret = _gnutls_parse_hello_extensions(session, GNUTLS_EXT_FLAG_EE, GNUTLS_EXT_ANY, + buf.data, buf.length); + _gnutls_buffer_clear(&buf); + + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} + +int _gnutls13_send_encrypted_extensions(gnutls_session_t session, unsigned again) +{ + int ret; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + + if (again == 0) { + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_gen_hello_extensions(session, &buf, GNUTLS_EXT_FLAG_EE, GNUTLS_EXT_ANY); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + bufel = _gnutls_buffer_to_mbuffer(&buf); + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_ENCRYPTED_EXTENSIONS); + + cleanup: + _gnutls_buffer_clear(&buf); + return ret; +} diff --git a/lib/tls13/encrypted_extensions.h b/lib/tls13/encrypted_extensions.h new file mode 100644 index 0000000..2c7cf75 --- /dev/null +++ b/lib/tls13/encrypted_extensions.h @@ -0,0 +1,29 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_ENCRYPTED_EXTENSIONS_H +#define GNUTLS_LIB_TLS13_ENCRYPTED_EXTENSIONS_H + +int _gnutls13_recv_encrypted_extensions(gnutls_session_t session); +int _gnutls13_send_encrypted_extensions(gnutls_session_t session, unsigned again); + +#endif /* GNUTLS_LIB_TLS13_ENCRYPTED_EXTENSIONS_H */ diff --git a/lib/tls13/finished.c b/lib/tls13/finished.c new file mode 100644 index 0000000..ec646e6 --- /dev/null +++ b/lib/tls13/finished.c @@ -0,0 +1,183 @@ +/* + * 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 "errors.h" +#include "handshake.h" +#include "tls13/finished.h" +#include "mem.h" +#include "mbuffers.h" +#include "secrets.h" + +int _gnutls13_compute_finished(const mac_entry_st *prf, + const uint8_t *base_key, + gnutls_buffer_st *handshake_hash_buffer, + void *out) +{ + int ret; + uint8_t fkey[MAX_HASH_SIZE]; + uint8_t ts_hash[MAX_HASH_SIZE]; + + ret = _tls13_expand_secret2(prf, + "finished", 8, + NULL, 0, + base_key, + prf->output_size, fkey); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = gnutls_hash_fast(MAC_TO_DIG(prf->id), + handshake_hash_buffer->data, + handshake_hash_buffer->length, + ts_hash); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = gnutls_hmac_fast(prf->id, + fkey, prf->output_size, + ts_hash, prf->output_size, + out); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} + +int _gnutls13_recv_finished(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + uint8_t verifier[MAX_HASH_SIZE]; + const uint8_t *base_key; + unsigned hash_size; + + if (unlikely(session->security_parameters.prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + hash_size = session->security_parameters.prf->output_size; + + if (!session->internals.initial_negotiation_completed) { + if (session->security_parameters.entity == GNUTLS_CLIENT) + base_key = session->key.proto.tls13.hs_skey; + else + base_key = session->key.proto.tls13.hs_ckey; + } else { + if (session->security_parameters.entity == GNUTLS_CLIENT) + base_key = session->key.proto.tls13.ap_skey; + else + base_key = session->key.proto.tls13.ap_ckey; + } + + ret = _gnutls13_compute_finished(session->security_parameters.prf, + base_key, + &session->internals.handshake_hash_buffer, + verifier); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_FINISHED, 0, &buf); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_handshake_log("HSK[%p]: parsing finished\n", session); + + if (buf.length != hash_size) { + gnutls_assert(); + ret = GNUTLS_E_UNEXPECTED_PACKET_LENGTH; + goto cleanup; + } + + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +# warning This is unsafe for production builds +#else + if (gnutls_memcmp(verifier, buf.data, buf.length) != 0) { + gnutls_assert(); + ret = GNUTLS_E_ERROR_IN_FINISHED_PACKET; + goto cleanup; + } +#endif + + ret = 0; +cleanup: + + _gnutls_buffer_clear(&buf); + return ret; +} + +int _gnutls13_send_finished(gnutls_session_t session, unsigned again) +{ + int ret; + uint8_t verifier[MAX_HASH_SIZE]; + mbuffer_st *bufel = NULL; + const uint8_t *base_key; + unsigned hash_size; + + if (again == 0) { + if (unlikely(session->security_parameters.prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + hash_size = session->security_parameters.prf->output_size; + + if (!session->internals.initial_negotiation_completed) { + if (session->security_parameters.entity == GNUTLS_CLIENT) + base_key = session->key.proto.tls13.hs_ckey; + else + base_key = session->key.proto.tls13.hs_skey; + } else { + if (session->security_parameters.entity == GNUTLS_CLIENT) + base_key = session->key.proto.tls13.ap_ckey; + else + base_key = session->key.proto.tls13.ap_skey; + } + + ret = _gnutls13_compute_finished(session->security_parameters.prf, + base_key, + &session->internals.handshake_hash_buffer, + verifier); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + _gnutls_handshake_log("HSK[%p]: sending finished\n", session); + + bufel = _gnutls_handshake_alloc(session, hash_size); + if (bufel == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + _mbuffer_set_udata_size(bufel, 0); + ret = _mbuffer_append_data(bufel, verifier, hash_size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_FINISHED); + +cleanup: + _mbuffer_xfree(&bufel); + return ret; +} diff --git a/lib/tls13/finished.h b/lib/tls13/finished.h new file mode 100644 index 0000000..cf475b2 --- /dev/null +++ b/lib/tls13/finished.h @@ -0,0 +1,33 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_FINISHED_H +#define GNUTLS_LIB_TLS13_FINISHED_H + +int _gnutls13_compute_finished(const mac_entry_st *prf, + const uint8_t *base_key, + gnutls_buffer_st *handshake_hash_buffer, + void *out); +int _gnutls13_recv_finished(gnutls_session_t session); +int _gnutls13_send_finished(gnutls_session_t session, unsigned again); + +#endif /* GNUTLS_LIB_TLS13_FINISHED_H */ diff --git a/lib/tls13/hello_retry.c b/lib/tls13/hello_retry.c new file mode 100644 index 0000000..dd4cba5 --- /dev/null +++ b/lib/tls13/hello_retry.c @@ -0,0 +1,195 @@ +/* + * 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 "errors.h" +#include "hello_ext.h" +#include "handshake.h" +#include "tls13/hello_retry.h" +#include "auth/cert.h" +#include "mbuffers.h" +#include "state.h" + +int _gnutls13_send_hello_retry_request(gnutls_session_t session, unsigned again) +{ + int ret; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + const version_entry_st *ver; + const uint8_t vbuf[2] = {0x03, 0x03}; + + if (again == 0) { + ver = get_version(session); + if (unlikely(ver == NULL || session->security_parameters.cs == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_buffer_append_data(&buf, vbuf, 2); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_buffer_append_data(&buf, + HRR_RANDOM, + GNUTLS_RANDOM_SIZE); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_data_prefix(&buf, 8, + session->security_parameters.session_id, + session->security_parameters.session_id_size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_data(&buf, session->security_parameters.cs->id, 2); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* compression */ + ret = _gnutls_buffer_append_prefix(&buf, 8, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_gen_hello_extensions(session, &buf, + GNUTLS_EXT_FLAG_HRR, + GNUTLS_EXT_ANY); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* reset extensions sent by this session to allow re-sending them */ + session->internals.used_exts = 0; + + reset_binders(session); + + bufel = _gnutls_buffer_to_mbuffer(&buf); + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_HELLO_RETRY_REQUEST); + + cleanup: + _gnutls_buffer_clear(&buf); + return ret; +} + +int +_gnutls13_recv_hello_retry_request(gnutls_session_t session, + gnutls_buffer_st *buf) +{ + int ret; + uint8_t tmp[2]; + const gnutls_cipher_suite_entry_st *cs; + const mac_entry_st *prf; + gnutls_datum_t session_id; + uint8_t random[GNUTLS_RANDOM_SIZE]; + + /* only under TLS 1.3 */ + if (IS_DTLS(session)) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + + if (session->internals.hsk_flags & HSK_HRR_RECEIVED) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + + session->internals.hsk_flags |= HSK_HRR_RECEIVED; + + /* version */ + ret = _gnutls_buffer_pop_data(buf, tmp, 2); + if (ret < 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + if (unlikely(tmp[0] != 0x03 || tmp[1] != 0x03)) + return gnutls_assert_val(GNUTLS_E_UNSUPPORTED_VERSION_PACKET); + + ret = _gnutls_buffer_pop_data(buf, random, GNUTLS_RANDOM_SIZE); + if (ret < 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + if (memcmp(random, HRR_RANDOM, GNUTLS_RANDOM_SIZE) != 0) { + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + } + + ret = _gnutls_buffer_pop_datum_prefix8(buf, &session_id); + if (ret < 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + /* read ciphersuites */ + ret = _gnutls_buffer_pop_data(buf, tmp, 2); + if (ret < 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + cs = ciphersuite_to_entry(tmp); + if (unlikely(cs == NULL)) + return gnutls_assert_val(GNUTLS_E_UNKNOWN_CIPHER_SUITE); + + _gnutls_handshake_log("EXT[%p]: Hello Retry Request with %s\n", session, cs->name); + memcpy(session->internals.hrr_cs, cs->id, 2); + + prf = mac_to_entry(cs->prf); + if (unlikely(prf == NULL)) + return gnutls_assert_val(GNUTLS_E_UNKNOWN_CIPHER_SUITE); + + /* compression */ + ret = _gnutls_buffer_pop_data(buf, tmp, 1); + if (ret < 0) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + if (unlikely(tmp[0] != 0)) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + + ret = _gnutls13_handshake_hash_buffers_synth(session, prf, 1); + if (ret < 0) + return gnutls_assert_val(ret); + + if (buf->length <= 2) { + /* no extensions present */ + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_EXTENSIONS_LENGTH); + } + + /* figure version first */ + ret = + _gnutls_parse_hello_extensions(session, GNUTLS_EXT_FLAG_HRR, + GNUTLS_EXT_VERSION_NEG, + buf->data, buf->length); + if (ret < 0) + return gnutls_assert_val(ret); + + /* parse the rest of extensions */ + ret = _gnutls_parse_hello_extensions(session, GNUTLS_EXT_FLAG_HRR, GNUTLS_EXT_ANY, + buf->data, buf->length); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.used_exts = 0; + + return 0; +} diff --git a/lib/tls13/hello_retry.h b/lib/tls13/hello_retry.h new file mode 100644 index 0000000..373670f --- /dev/null +++ b/lib/tls13/hello_retry.h @@ -0,0 +1,28 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_HELLO_RETRY_H +#define GNUTLS_LIB_TLS13_HELLO_RETRY_H + +int _gnutls13_send_hello_retry_request(gnutls_session_t session, unsigned again); + +#endif /* GNUTLS_LIB_TLS13_HELLO_RETRY_H */ diff --git a/lib/tls13/key_update.c b/lib/tls13/key_update.c new file mode 100644 index 0000000..c6f6e0a --- /dev/null +++ b/lib/tls13/key_update.c @@ -0,0 +1,217 @@ +/* + * 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 "errors.h" +#include "handshake.h" +#include "tls13/key_update.h" +#include "mem.h" +#include "mbuffers.h" +#include "secrets.h" + +#define KEY_UPDATES_WINDOW 1000 +#define KEY_UPDATES_PER_WINDOW 8 + +static int update_keys(gnutls_session_t session, hs_stage_t stage) +{ + int ret; + + ret = _tls13_update_secret(session, session->key.proto.tls13.temp_secret, + session->key.proto.tls13.temp_secret_size); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_epoch_bump(session); + ret = _gnutls_epoch_dup(session, EPOCH_READ_CURRENT); + if (ret < 0) + return gnutls_assert_val(ret); + + /* If we send a key update during early start, only update our + * write keys */ + if (session->internals.recv_state == RECV_STATE_EARLY_START) { + ret = _tls13_write_connection_state_init(session, stage); + } else { + ret = _tls13_connection_state_init(session, stage); + } + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} + +int _gnutls13_recv_key_update(gnutls_session_t session, gnutls_buffer_st *buf) +{ + int ret; + struct timespec now; + + if (buf->length != 1) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + gnutls_gettime(&now); + + /* Roll over the counter if the time window has elapsed */ + if (session->internals.key_update_count == 0 || + timespec_sub_ms(&now, &session->internals.last_key_update) > + KEY_UPDATES_WINDOW) { + session->internals.last_key_update = now; + session->internals.key_update_count = 0; + } + + if (unlikely(++session->internals.key_update_count > + KEY_UPDATES_PER_WINDOW)) { + _gnutls_debug_log("reached maximum number of key updates per %d milliseconds (%d)\n", + KEY_UPDATES_WINDOW, + KEY_UPDATES_PER_WINDOW); + return gnutls_assert_val(GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS); + } + + _gnutls_epoch_gc(session); + + _gnutls_handshake_log("HSK[%p]: received TLS 1.3 key update (%u)\n", + session, (unsigned)buf->data[0]); + + switch(buf->data[0]) { + case 0: + /* peer updated its key, not requested our key update */ + ret = update_keys(session, STAGE_UPD_PEERS); + if (ret < 0) + return gnutls_assert_val(ret); + + break; + case 1: + if (session->internals.hsk_flags & HSK_KEY_UPDATE_ASKED) { + /* if we had asked a key update we shouldn't get this + * reply */ + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + } + + /* peer updated its key, requested our key update */ + ret = update_keys(session, STAGE_UPD_PEERS); + if (ret < 0) + return gnutls_assert_val(ret); + + /* we mark that a key update is schedule, and it + * will be performed prior to sending the next application + * message. + */ + if (session->internals.rsend_state == RECORD_SEND_NORMAL) + session->internals.rsend_state = RECORD_SEND_KEY_UPDATE_1; + else if (session->internals.rsend_state == RECORD_SEND_CORKED) + session->internals.rsend_state = RECORD_SEND_CORKED_TO_KU; + + break; + default: + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + } + + session->internals.hsk_flags &= ~(unsigned)(HSK_KEY_UPDATE_ASKED); + + return 0; +} + +int _gnutls13_send_key_update(gnutls_session_t session, unsigned again, unsigned flags /* GNUTLS_KU_* */) +{ + int ret; + mbuffer_st *bufel = NULL; + uint8_t val; + + if (again == 0) { + if (flags & GNUTLS_KU_PEER) { + /* mark that we asked a key update to prevent an + * infinite ping pong when receiving the reply */ + session->internals.hsk_flags |= HSK_KEY_UPDATE_ASKED; + val = 0x01; + } else { + val = 0x00; + } + + _gnutls_handshake_log("HSK[%p]: sending key update (%u)\n", session, (unsigned)val); + + bufel = _gnutls_handshake_alloc(session, 1); + if (bufel == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + _mbuffer_set_udata_size(bufel, 0); + ret = _mbuffer_append_data(bufel, &val, 1); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_KEY_UPDATE); + +cleanup: + _mbuffer_xfree(&bufel); + return ret; +} + +/** + * gnutls_session_key_update: + * @session: is a #gnutls_session_t type. + * @flags: zero of %GNUTLS_KU_PEER + * + * This function will update/refresh the session keys when the + * TLS protocol is 1.3 or better. The peer is notified of the + * update by sending a message, so this function should be + * treated similarly to gnutls_record_send() --i.e., it may + * return %GNUTLS_E_AGAIN or %GNUTLS_E_INTERRUPTED. + * + * When this flag %GNUTLS_KU_PEER is specified, this function + * in addition to updating the local keys, will ask the peer to + * refresh its keys too. + * + * If the negotiated version is not TLS 1.3 or better this + * function will return %GNUTLS_E_INVALID_REQUEST. + * + * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code. + * + * Since: 3.6.3 + **/ +int gnutls_session_key_update(gnutls_session_t session, unsigned flags) +{ + int ret; + const version_entry_st *vers = get_version(session); + + if (!vers->tls13_sem) + return GNUTLS_E_INVALID_REQUEST; + + ret = + _gnutls13_send_key_update(session, AGAIN(STATE150), flags); + STATE = STATE150; + + if (ret < 0) { + gnutls_assert(); + return ret; + } + STATE = STATE0; + + _gnutls_epoch_gc(session); + + /* it was completely sent, update the keys */ + ret = update_keys(session, STAGE_UPD_OURS); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} diff --git a/lib/tls13/key_update.h b/lib/tls13/key_update.h new file mode 100644 index 0000000..4a0123a --- /dev/null +++ b/lib/tls13/key_update.h @@ -0,0 +1,29 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_KEY_UPDATE_H +#define GNUTLS_LIB_TLS13_KEY_UPDATE_H + +int _gnutls13_recv_key_update(gnutls_session_t session, gnutls_buffer_st *buf); +int _gnutls13_send_key_update(gnutls_session_t session, unsigned again, unsigned flags); + +#endif /* GNUTLS_LIB_TLS13_KEY_UPDATE_H */ diff --git a/lib/tls13/post_handshake.c b/lib/tls13/post_handshake.c new file mode 100644 index 0000000..97ca287 --- /dev/null +++ b/lib/tls13/post_handshake.c @@ -0,0 +1,252 @@ +/* + * 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/> + * + */ + +/* Functions that relate to the TLS handshake procedure. + */ + +#include "gnutls_int.h" +#include "errors.h" +#include "dh.h" +#include "debug.h" +#include "algorithms.h" +#include "cipher.h" +#include "buffers.h" +#include "mbuffers.h" +#include "kx.h" +#include "handshake.h" +#include "num.h" +#include "hash_int.h" +#include "db.h" +#include "hello_ext.h" +#include "supplemental.h" +#include "auth.h" +#include "sslv2_compat.h" +#include <auth/cert.h> +#include "constate.h" +#include <record.h> +#include <state.h> +#include <random.h> +#include <dtls.h> +#include "tls13/certificate_request.h" +#include "tls13/certificate_verify.h" +#include "tls13/certificate.h" +#include "tls13/finished.h" + +#undef AGAIN +#define AGAIN(x) ((x)==(REAUTH_STATE)) + +/* + * _gnutls13_reauth_client + * This function performs the client side of the post-handshake authentication + */ +static +int _gnutls13_reauth_client(gnutls_session_t session) +{ + int ret = 0; + size_t tmp; + + if (!session->internals.initial_negotiation_completed) + return gnutls_assert_val(GNUTLS_E_UNAVAILABLE_DURING_HANDSHAKE); + + if (!(session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (session->internals.reauth_buffer.length == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + switch (REAUTH_STATE) { + case REAUTH_STATE0: + + /* restore handshake transcript */ + _gnutls_buffer_reset(&session->internals.handshake_hash_buffer); + ret = gnutls_buffer_append_data(&session->internals.handshake_hash_buffer, + session->internals.post_handshake_hash_buffer.data, + session->internals.post_handshake_hash_buffer.length); + if (ret < 0) + return gnutls_assert_val(ret); + + /* append the previously received certificate request message, to the + * transcript. */ + ret = gnutls_buffer_append_data(&session->internals.handshake_hash_buffer, + session->internals.reauth_buffer.data, + session->internals.reauth_buffer.length); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.handshake_hash_buffer_prev_len = session->internals.handshake_hash_buffer.length; + + /* skip the reauth buffer handshake message headers */ + ret = _gnutls_buffer_pop_prefix32(&session->internals.reauth_buffer, &tmp, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + FALLTHROUGH; + case REAUTH_STATE1: + ret = _gnutls13_recv_certificate_request_int(session, + &session->internals.reauth_buffer); + REAUTH_STATE = REAUTH_STATE1; + IMED_RET("recv certificate request", ret, 0); + FALLTHROUGH; + case REAUTH_STATE2: + ret = _gnutls13_send_certificate(session, AGAIN(REAUTH_STATE2)); + REAUTH_STATE = REAUTH_STATE2; + IMED_RET("send certificate", ret, 0); + FALLTHROUGH; + case REAUTH_STATE3: + ret = _gnutls13_send_certificate_verify(session, AGAIN(REAUTH_STATE3)); + REAUTH_STATE = REAUTH_STATE3; + IMED_RET("send certificate verify", ret, 0); + FALLTHROUGH; + case REAUTH_STATE4: + ret = _gnutls13_send_finished(session, AGAIN(REAUTH_STATE4)); + REAUTH_STATE = REAUTH_STATE4; + IMED_RET("send finished", ret, 0); + break; + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + _gnutls_handshake_hash_buffers_clear(session); + _gnutls_buffer_reset(&session->internals.reauth_buffer); + REAUTH_STATE = REAUTH_STATE0; + + return 0; +} + +/* + * _gnutls13_reauth_server + * This function does the server stuff of the post-handshake authentication. + */ +static +int _gnutls13_reauth_server(gnutls_session_t session) +{ + int ret = 0; + + if (session->security_parameters.post_handshake_auth == 0 || + (!(session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH))) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (session->internals.send_cert_req == 0) { + _gnutls_debug_log("You need to call gnutls_certificate_server_set_request to enable post handshake auth\n"); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + switch (REAUTH_STATE) { + case REAUTH_STATE0: + /* restore handshake transcript */ + _gnutls_buffer_reset(&session->internals.handshake_hash_buffer); + ret = gnutls_buffer_append_data(&session->internals.handshake_hash_buffer, + session->internals.post_handshake_hash_buffer.data, + session->internals.post_handshake_hash_buffer.length); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.handshake_hash_buffer_prev_len = session->internals.handshake_hash_buffer.length; + + FALLTHROUGH; + case REAUTH_STATE1: + ret = _gnutls13_send_certificate_request(session, AGAIN(REAUTH_STATE1)); + REAUTH_STATE = REAUTH_STATE1; + IMED_RET("send certificate request", ret, 0); + FALLTHROUGH; + case REAUTH_STATE2: + /* here we should tolerate application data */ + ret = _gnutls13_recv_certificate(session); + REAUTH_STATE = REAUTH_STATE2; + IMED_RET("recv certificate", ret, 0); + FALLTHROUGH; + case REAUTH_STATE3: + ret = _gnutls13_recv_certificate_verify(session); + REAUTH_STATE = REAUTH_STATE3; + IMED_RET("recv certificate verify", ret, 0); + FALLTHROUGH; + case REAUTH_STATE4: + ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT); + REAUTH_STATE = REAUTH_STATE4; + if (ret < 0) + return gnutls_assert_val(ret); + FALLTHROUGH; + case REAUTH_STATE5: + ret = _gnutls13_recv_finished(session); + REAUTH_STATE = REAUTH_STATE5; + IMED_RET("recv finished", ret, 0); + break; + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + _gnutls_handshake_hash_buffers_clear(session); + REAUTH_STATE = REAUTH_STATE0; + + return 0; +} + +/** + * gnutls_reauth: + * @session: is a #gnutls_session_t type. + * @flags: must be zero + * + * This function performs the post-handshake authentication + * for TLS 1.3. The post-handshake authentication is initiated by the server + * by calling this function. Clients respond when %GNUTLS_E_REAUTH_REQUEST + * has been seen while receiving data. + * + * The non-fatal errors expected by this function are: + * %GNUTLS_E_INTERRUPTED, %GNUTLS_E_AGAIN, as well as + * %GNUTLS_E_GOT_APPLICATION_DATA when called on server side. + * + * The former two interrupt the authentication procedure due to the transport + * layer being interrupted, and the latter because there were pending data prior + * to peer initiating the re-authentication. The server should read/process that + * data as unauthenticated and retry calling gnutls_reauth(). + * + * When this function is called under TLS1.2 or earlier or the peer didn't + * advertise post-handshake auth, it always fails with + * %GNUTLS_E_INVALID_REQUEST. The verification of the received peers certificate + * is delegated to the session or credentials verification callbacks. A + * server can check whether post handshake authentication is supported + * by the client by checking the session flags with gnutls_session_get_flags(). + * + * Prior to calling this function in server side, the function + * gnutls_certificate_server_set_request() must be called setting expectations + * for the received certificate (request or require). If none are set + * this function will return with %GNUTLS_E_INVALID_REQUEST. + * + * Note that post handshake authentication is available irrespective + * of the initial negotiation type (PSK or certificate). In all cases + * however, certificate credentials must be set to the session prior + * to calling this function. + * + * Returns: %GNUTLS_E_SUCCESS on a successful authentication, otherwise a negative error code. + **/ +int gnutls_reauth(gnutls_session_t session, unsigned int flags) +{ + const version_entry_st *vers = get_version(session); + + if (unlikely(!vers->tls13_sem)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (session->security_parameters.entity == GNUTLS_SERVER) + return _gnutls13_reauth_server(session); + else + return _gnutls13_reauth_client(session); +} diff --git a/lib/tls13/psk_ext_parser.c b/lib/tls13/psk_ext_parser.c new file mode 100644 index 0000000..33ebc04 --- /dev/null +++ b/lib/tls13/psk_ext_parser.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017-2018 Free Software Foundation, Inc. + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Ander Juaristi, 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 "tls13/psk_ext_parser.h" + +/* Returns GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE when no identities + * are present, or 0, on success. + */ +int _gnutls13_psk_ext_parser_init(psk_ext_parser_st *p, + const unsigned char *data, size_t len) +{ + if (!p || !data || !len) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + memset(p, 0, sizeof(*p)); + + DECR_LEN(len, 2); + p->identities_len = _gnutls_read_uint16(data); + data += 2; + + if (p->identities_len == 0) + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + p->identities_data = (unsigned char *) data; + + DECR_LEN(len, p->identities_len); + data += p->identities_len; + + DECR_LEN(len, 2); + p->binders_len = _gnutls_read_uint16(data); + data += 2; + + p->binders_data = data; + DECR_LEN(len, p->binders_len); + + return 0; +} + +/* Extract PSK identity and move to the next iteration. + * + * Returns GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE when no more identities + * are present, or 0, on success. + */ +int _gnutls13_psk_ext_iter_next_identity(psk_ext_iter_st *iter, + struct psk_st *psk) +{ + if (iter->identities_len == 0) + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + + DECR_LEN(iter->identities_len, 2); + psk->identity.size = _gnutls_read_uint16(iter->identities_data); + if (psk->identity.size == 0) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + + iter->identities_data += 2; + psk->identity.data = (void*)iter->identities_data; + + DECR_LEN(iter->identities_len, psk->identity.size); + iter->identities_data += psk->identity.size; + + DECR_LEN(iter->identities_len, 4); + psk->ob_ticket_age = _gnutls_read_uint32(iter->identities_data); + iter->identities_data += 4; + + return 0; +} + +/* Extract PSK binder and move to the next iteration. + * + * Returns GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE when no more identities + * are present, or 0, on success. + */ +int _gnutls13_psk_ext_iter_next_binder(psk_ext_iter_st *iter, + gnutls_datum_t *binder) +{ + if (iter->binders_len == 0) + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + + DECR_LEN(iter->binders_len, 1); + binder->size = *iter->binders_data; + if (binder->size == 0) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); + + iter->binders_data++; + binder->data = (uint8_t *)iter->binders_data; + DECR_LEN(iter->binders_len, binder->size); + iter->binders_data += binder->size; + + return 0; +} diff --git a/lib/tls13/psk_ext_parser.h b/lib/tls13/psk_ext_parser.h new file mode 100644 index 0000000..f46b211 --- /dev/null +++ b/lib/tls13/psk_ext_parser.h @@ -0,0 +1,58 @@ +/* + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_PSK_EXT_PARSER_H +#define GNUTLS_LIB_TLS13_PSK_EXT_PARSER_H + +struct psk_ext_parser_st { + const unsigned char *identities_data; + size_t identities_len; + + const unsigned char *binders_data; + size_t binders_len; +}; + +typedef struct psk_ext_parser_st psk_ext_parser_st; +typedef struct psk_ext_parser_st psk_ext_iter_st; + +struct psk_st { + /* constant values */ + gnutls_datum_t identity; + uint32_t ob_ticket_age; +}; + +int _gnutls13_psk_ext_parser_init(psk_ext_parser_st *p, + const unsigned char *data, size_t len); + +inline static +void _gnutls13_psk_ext_iter_init(psk_ext_iter_st *iter, + const psk_ext_parser_st *p) +{ + memcpy(iter, p, sizeof(*p)); +} + +int _gnutls13_psk_ext_iter_next_identity(psk_ext_iter_st *iter, + struct psk_st *psk); +int _gnutls13_psk_ext_iter_next_binder(psk_ext_iter_st *iter, + gnutls_datum_t *binder); + +#endif /* GNUTLS_LIB_TLS13_PSK_EXT_PARSER_H */ diff --git a/lib/tls13/session_ticket.c b/lib/tls13/session_ticket.c new file mode 100644 index 0000000..909ab05 --- /dev/null +++ b/lib/tls13/session_ticket.c @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos, 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 "errors.h" +#include "extv.h" +#include "handshake.h" +#include "mbuffers.h" +#include "ext/pre_shared_key.h" +#include "ext/session_ticket.h" +#include "ext/early_data.h" +#include "auth/cert.h" +#include "tls13/session_ticket.h" +#include "session_pack.h" +#include "db.h" + +static int +pack_ticket(gnutls_session_t session, tls13_ticket_st *ticket, gnutls_datum_t *packed) +{ + uint8_t *p; + gnutls_datum_t state; + int ret; + + ret = _gnutls_session_pack(session, &state); + if (ret < 0) + return gnutls_assert_val(ret); + + packed->size = 2 + 4 + 4 + + 1 + ticket->prf->output_size + + 1 + ticket->nonce_size + 2 + state.size + 12; + + packed->data = gnutls_malloc(packed->size); + if (!packed->data) { + gnutls_assert(); + ret = GNUTLS_E_MEMORY_ERROR; + goto cleanup; + } + + p = packed->data; + + _gnutls_write_uint16(ticket->prf->id, p); + p += 2; + _gnutls_write_uint32(ticket->age_add, p); + p += 4; + _gnutls_write_uint32(ticket->lifetime, p); + p += 4; + *p = ticket->prf->output_size; + p += 1; + memcpy(p, ticket->resumption_master_secret, ticket->prf->output_size); + p += ticket->prf->output_size; + *p = ticket->nonce_size; + + p += 1; + memcpy(p, ticket->nonce, ticket->nonce_size); + p += ticket->nonce_size; + + _gnutls_write_uint16(state.size, p); + p += 2; + + memcpy(p, state.data, state.size); + p += state.size; + + _gnutls_write_uint32((uint64_t) ticket->creation_time.tv_sec >> 32, p); + p += 4; + _gnutls_write_uint32(ticket->creation_time.tv_sec & 0xFFFFFFFF, p); + p += 4; + _gnutls_write_uint32(ticket->creation_time.tv_nsec, p); + + ret = 0; + + cleanup: + gnutls_free(state.data); + return ret; +} + +static int +unpack_ticket(gnutls_session_t session, gnutls_datum_t *packed, tls13_ticket_st *data) +{ + uint32_t age_add, lifetime; + struct timespec creation_time; + uint8_t resumption_master_secret[MAX_HASH_SIZE]; + size_t resumption_master_secret_size; + uint8_t nonce[UINT8_MAX]; + size_t nonce_size; + gnutls_datum_t state; + gnutls_mac_algorithm_t kdf; + const mac_entry_st *prf; + uint8_t *p; + size_t len; + uint64_t v; + int ret; + + if (unlikely(packed == NULL || data == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + memset(data, 0, sizeof(*data)); + + p = packed->data; + len = packed->size; + + DECR_LEN(len, 2); + kdf = _gnutls_read_uint16(p); + p += 2; + + /* Check if the MAC ID we got is valid */ + prf = _gnutls_mac_to_entry(kdf); + if (prf == NULL) + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + + /* Read the ticket age add and the ticket lifetime */ + DECR_LEN(len, 4); + age_add = _gnutls_read_uint32(p); + p += 4; + + DECR_LEN(len, 4); + lifetime = _gnutls_read_uint32(p); + p += 4; + + /* + * Check if the whole ticket is large enough, + * and read the resumption master secret + */ + DECR_LEN(len, 1); + resumption_master_secret_size = *p; + p += 1; + + /* Check if the size of resumption_master_secret matches the PRF */ + if (resumption_master_secret_size != prf->output_size) + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + + DECR_LEN(len, resumption_master_secret_size); + memcpy(resumption_master_secret, p, resumption_master_secret_size); + p += resumption_master_secret_size; + + /* Read the ticket nonce */ + DECR_LEN(len, 1); + nonce_size = *p; + p += 1; + + DECR_LEN(len, nonce_size); + memcpy(nonce, p, nonce_size); + p += nonce_size; + + DECR_LEN(len, 2); + state.size = _gnutls_read_uint16(p); + p += 2; + + DECR_LEN(len, state.size); + state.data = p; + p += state.size; + + DECR_LEN(len, 12); + v = _gnutls_read_uint32(p); + p += 4; + creation_time.tv_sec = (v << 32) | _gnutls_read_uint32(p); + p += 4; + creation_time.tv_nsec = _gnutls_read_uint32(p); + + ret = _gnutls_session_unpack(session, &state); + if (ret < 0) + return gnutls_assert_val(ret); + + /* No errors - Now return all the data to the caller */ + data->prf = prf; + memcpy(data->resumption_master_secret, resumption_master_secret, + resumption_master_secret_size); + memcpy(data->nonce, nonce, nonce_size); + data->nonce_size = nonce_size; + data->age_add = age_add; + data->lifetime = lifetime; + memcpy(&data->creation_time, &creation_time, sizeof(struct timespec)); + + return 0; +} + +static int +generate_session_ticket(gnutls_session_t session, tls13_ticket_st *ticket) +{ + int ret; + gnutls_datum_t packed = { NULL, 0 }; + struct timespec now; + tls13_ticket_st ticket_data; + + gnutls_gettime(&now); + if (session->internals.resumed) { + /* If we are resuming ensure that we don't extend the lifetime + * of the ticket past the original session expiration time */ + if (now.tv_sec >= session->security_parameters.timestamp + session->internals.expire_time) + return GNUTLS_E_INT_RET_0; /* don't send ticket */ + else + ticket->lifetime = session->security_parameters.timestamp + + session->internals.expire_time - now.tv_sec; + } else { + /* Set ticket lifetime to the default expiration time */ + ticket->lifetime = session->internals.expire_time; + } + + /* Generate a random 32-bit ticket nonce */ + ticket->nonce_size = 4; + + if ((ret = gnutls_rnd(GNUTLS_RND_NONCE, + ticket->nonce, ticket->nonce_size)) < 0) + return gnutls_assert_val(ret); + + if ((ret = gnutls_rnd(GNUTLS_RND_NONCE, &ticket->age_add, sizeof(uint32_t))) < 0) + return gnutls_assert_val(ret); + /* This is merely to produce the same binder value on + * different endian architectures. */ +#ifdef WORDS_BIGENDIAN + ticket->age_add = bswap_32(ticket->age_add); +#endif + + ticket->prf = session->security_parameters.prf; + + /* Encrypt the ticket and place the result in ticket->ticket */ + ticket_data.lifetime = ticket->lifetime; + ticket_data.age_add = ticket->age_add; + memcpy(&ticket_data.creation_time, &now, sizeof(struct timespec)); + memcpy(ticket_data.nonce, ticket->nonce, ticket->nonce_size); + ticket_data.nonce_size = ticket->nonce_size; + ticket_data.prf = ticket->prf; + memcpy(&ticket_data.resumption_master_secret, + session->key.proto.tls13.ap_rms, + ticket->prf->output_size); + + ret = pack_ticket(session, &ticket_data, &packed); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_encrypt_session_ticket(session, &packed, &ticket->ticket); + _gnutls_free_datum(&packed); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} + +static int append_nst_extension(void *ctx, gnutls_buffer_st *buf) +{ + gnutls_session_t session = ctx; + int ret; + + if (!(session->internals.flags & GNUTLS_ENABLE_EARLY_DATA)) + return 0; + + ret = _gnutls_buffer_append_prefix(buf, 32, + session->security_parameters. + max_early_data_size); + if (ret < 0) + gnutls_assert(); + + return ret; +} + +int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigned again) +{ + int ret = 0; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + tls13_ticket_st ticket; + unsigned i; + + /* Client does not send a NewSessionTicket */ + if (unlikely(session->security_parameters.entity == GNUTLS_CLIENT)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + /* Session resumption has not been enabled */ + if (session->internals.flags & GNUTLS_NO_TICKETS) + return gnutls_assert_val(0); + + /* If we received the psk_key_exchange_modes extension which + * does not have overlap with the server configuration, don't + * send a session ticket */ + if (session->internals.hsk_flags & HSK_PSK_KE_MODE_INVALID) + return gnutls_assert_val(0); + + if (again == 0) { + for (i=0;i<nr;i++) { + unsigned init_pos; + + memset(&ticket, 0, sizeof(tls13_ticket_st)); + bufel = NULL; + + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = generate_session_ticket(session, &ticket); + if (ret < 0) { + if (ret == GNUTLS_E_INT_RET_0) { + ret = gnutls_assert_val(0); + goto cleanup; + } + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_prefix(&buf, 32, ticket.lifetime); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_prefix(&buf, 32, ticket.age_add); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* append ticket_nonce */ + ret = _gnutls_buffer_append_data_prefix(&buf, 8, ticket.nonce, ticket.nonce_size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* append ticket */ + ret = _gnutls_buffer_append_data_prefix(&buf, 16, ticket.ticket.data, ticket.ticket.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + _gnutls_free_datum(&ticket.ticket); + + /* append extensions */ + ret = _gnutls_extv_append_init(&buf); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + init_pos = ret; + + ret = _gnutls_extv_append(&buf, ext_mod_early_data.tls_id, session, + (extv_append_func)append_nst_extension); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_extv_append_final(&buf, init_pos, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + bufel = _gnutls_buffer_to_mbuffer(&buf); + + ret = _gnutls_send_handshake2(session, bufel, + GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, 1); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + session->internals.hsk_flags |= HSK_TLS13_TICKET_SENT; + } + } + + ret = _gnutls_handshake_io_write_flush(session); + + return ret; + +cleanup: + _gnutls_free_datum(&ticket.ticket); + _mbuffer_xfree(&bufel); + _gnutls_buffer_clear(&buf); + + return ret; +} + +static int parse_nst_extension(void *ctx, unsigned tls_id, const unsigned char *data, unsigned data_size) +{ + gnutls_session_t session = ctx; + if (tls_id == ext_mod_early_data.tls_id) { + if (data_size < 4) + return gnutls_assert_val(GNUTLS_E_TLS_PACKET_DECODING_ERROR); + session->security_parameters.max_early_data_size = + _gnutls_read_uint32(data); + } + return 0; +} + +int _gnutls13_recv_session_ticket(gnutls_session_t session, gnutls_buffer_st *buf) +{ + int ret; + uint8_t value; + tls13_ticket_st *ticket = &session->internals.tls13_ticket; + gnutls_datum_t t; + size_t val; + + if (unlikely(buf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + _gnutls_free_datum(&ticket->ticket); + memset(ticket, 0, sizeof(tls13_ticket_st)); + + _gnutls_handshake_log("HSK[%p]: parsing session ticket message\n", session); + + /* ticket_lifetime */ + ret = _gnutls_buffer_pop_prefix32(buf, &val, 0); + if (ret < 0) + return gnutls_assert_val(ret); + ticket->lifetime = val; + + /* ticket_age_add */ + ret = _gnutls_buffer_pop_prefix32(buf, &val, 0); + if (ret < 0) + return gnutls_assert_val(ret); + ticket->age_add = val; + + /* ticket_nonce */ + ret = _gnutls_buffer_pop_prefix8(buf, &value, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + ticket->nonce_size = value; + ret = _gnutls_buffer_pop_data(buf, ticket->nonce, ticket->nonce_size); + if (ret < 0) + return gnutls_assert_val(ret); + + /* ticket */ + ret = _gnutls_buffer_pop_datum_prefix16(buf, &t); + if (ret < 0) + return gnutls_assert_val(ret); + + gnutls_free(ticket->ticket.data); + ret = _gnutls_set_datum(&ticket->ticket, t.data, t.size); + if (ret < 0) + return gnutls_assert_val(ret); + + /* Extensions */ + ret = _gnutls_extv_parse(session, parse_nst_extension, buf->data, buf->length); + if (ret < 0) + return gnutls_assert_val(ret); + + /* Record the ticket arrival time */ + gnutls_gettime(&ticket->arrival_time); + + return 0; +} + +/* + * Parse the ticket in 'data' and return the resumption master secret + * and the KDF ID associated to it. + */ +int _gnutls13_unpack_session_ticket(gnutls_session_t session, + gnutls_datum_t *data, + tls13_ticket_st *ticket_data) +{ + int ret; + gnutls_datum_t decrypted = { NULL, 0 }; + + if (unlikely(data == NULL || ticket_data == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (!session->key.stek_initialized) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + /* Check MAC and decrypt ticket */ + ret = _gnutls_decrypt_session_ticket(session, data, &decrypted); + if (ret < 0) + return gnutls_assert_val(ret); + + /* Return ticket parameters */ + ret = unpack_ticket(session, &decrypted, ticket_data); + _gnutls_free_datum(&decrypted); + if (ret < 0) + return ret; + + ret = _gnutls_check_resumed_params(session); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} diff --git a/lib/tls13/session_ticket.h b/lib/tls13/session_ticket.h new file mode 100644 index 0000000..db0be15 --- /dev/null +++ b/lib/tls13/session_ticket.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos, 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/> + * + */ + +#ifndef GNUTLS_LIB_TLS13_SESSION_TICKET_H +#define GNUTLS_LIB_TLS13_SESSION_TICKET_H + +int _gnutls13_recv_session_ticket(gnutls_session_t session, gnutls_buffer_st *buf); +int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigned again); + +static inline bool +_gnutls13_can_send_session_ticket(gnutls_session_t session) +{ + return session->key.stek_initialized && + !(session->internals.flags & GNUTLS_NO_TICKETS); +} + +int _gnutls13_unpack_session_ticket(gnutls_session_t session, + gnutls_datum_t *data, + tls13_ticket_st *ticket_data); + +inline static +void tls13_ticket_deinit(tls13_ticket_st *ticket) +{ + zeroize_temp_key(&ticket->resumption_master_secret, + sizeof(ticket->resumption_master_secret)); + + _gnutls_free_datum(&ticket->ticket); + memset(ticket, 0, sizeof(tls13_ticket_st)); +} + +#endif /* GNUTLS_LIB_TLS13_SESSION_TICKET_H */ |