diff options
Diffstat (limited to 'lib/range.c')
-rw-r--r-- | lib/range.c | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/lib/range.c b/lib/range.c new file mode 100644 index 0000000..041578c --- /dev/null +++ b/lib/range.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2012 INRIA Paris-Rocquencourt + * + * Author: Alfredo Pironti + * + * 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 "algorithms.h" +#include "constate.h" +#include "record.h" + +static void +_gnutls_set_range(gnutls_range_st * dst, const size_t low, + const size_t high) +{ + dst->low = low; + dst->high = high; + return; +} + +/* + * Returns how much LH pad we can put in this fragment, given we'll + * put at least data_length bytes of user data. + */ +static ssize_t +_gnutls_range_max_lh_pad(gnutls_session_t session, ssize_t data_length, + ssize_t max_frag) +{ + int ret; + ssize_t max_pad; + unsigned int fixed_pad; + record_parameters_st *record_params; + ssize_t this_pad; + ssize_t block_size; + ssize_t tag_size, overflow; + const version_entry_st *vers = get_version(session); + + if (unlikely(vers == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + ret = + _gnutls_epoch_get(session, EPOCH_WRITE_CURRENT, + &record_params); + if (ret < 0) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (!vers->tls13_sem && record_params->write.is_aead) /* not yet ready */ + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (vers->tls13_sem) { + max_pad = max_record_send_size(session, record_params); + fixed_pad = 2; + } else { + max_pad = MAX_PAD_SIZE; + fixed_pad = 1; + } + + this_pad = MIN(max_pad, max_frag - data_length); + + block_size = _gnutls_cipher_get_block_size(record_params->cipher); + tag_size = + _gnutls_auth_cipher_tag_len(&record_params->write. + ctx.tls12); + switch (_gnutls_cipher_type(record_params->cipher)) { + case CIPHER_AEAD: + case CIPHER_STREAM: + return this_pad; + + case CIPHER_BLOCK: + overflow = + (data_length + this_pad + tag_size + + fixed_pad) % block_size; + if (overflow > this_pad) { + return this_pad; + } else { + return this_pad - overflow; + } + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } +} + +/** + * gnutls_record_can_use_length_hiding: + * @session: is a #gnutls_session_t type. + * + * If the session supports length-hiding padding, you can + * invoke gnutls_record_send_range() to send a message whose + * length is hidden in the given range. If the session does not + * support length hiding padding, you can use the standard + * gnutls_record_send() function, or gnutls_record_send_range() + * making sure that the range is the same as the length of the + * message you are trying to send. + * + * Returns: true (1) if the current session supports length-hiding + * padding, false (0) if the current session does not. + **/ +unsigned gnutls_record_can_use_length_hiding(gnutls_session_t session) +{ + int ret; + record_parameters_st *record_params; + const version_entry_st *vers = get_version(session); + + if (unlikely(vers == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (vers->tls13_sem) + return 1; + +#ifdef ENABLE_SSL3 + if (vers->id == GNUTLS_SSL3) + return 0; +#endif + + ret = + _gnutls_epoch_get(session, EPOCH_WRITE_CURRENT, + &record_params); + if (ret < 0) { + return 0; + } + + switch (_gnutls_cipher_type(record_params->cipher)) { + case CIPHER_BLOCK: + return 1; + case CIPHER_STREAM: + case CIPHER_AEAD: + default: + return 0; + } +} + +/** + * gnutls_range_split: + * @session: is a #gnutls_session_t type + * @orig: is the original range provided by the user + * @next: is the returned range that can be conveyed in a TLS record + * @remainder: is the returned remaining range + * + * This function should be used when it is required to hide the length + * of very long data that cannot be directly provided to gnutls_record_send_range(). + * In that case this function should be called with the desired length + * hiding range in @orig. The returned @next value should then be used in + * the next call to gnutls_record_send_range() with the partial data. + * That process should be repeated until @remainder is (0,0). + * + * Returns: 0 in case splitting succeeds, non zero in case of error. + * Note that @orig is not changed, while the values of @next + * and @remainder are modified to store the resulting values. + */ +int +gnutls_range_split(gnutls_session_t session, + const gnutls_range_st * orig, + gnutls_range_st * next, gnutls_range_st * remainder) +{ + int ret; + ssize_t max_frag; + ssize_t orig_low = (ssize_t) orig->low; + ssize_t orig_high = (ssize_t) orig->high; + record_parameters_st *record_params; + + ret = + _gnutls_epoch_get(session, EPOCH_WRITE_CURRENT, + &record_params); + if (ret < 0) + return gnutls_assert_val(ret); + + max_frag = max_record_send_size(session, record_params); + + if (orig_high == orig_low) { + int length = MIN(orig_high, max_frag); + int rem = orig_high - length; + _gnutls_set_range(next, length, length); + _gnutls_set_range(remainder, rem, rem); + + return 0; + } else { + if (orig_low >= max_frag) { + _gnutls_set_range(next, max_frag, max_frag); + _gnutls_set_range(remainder, orig_low - max_frag, + orig_high - max_frag); + } else { + ret = + _gnutls_range_max_lh_pad(session, orig_low, + max_frag); + if (ret < 0) + return gnutls_assert_val(ret); + + ssize_t this_pad = MIN(ret, orig_high - orig_low); + + _gnutls_set_range(next, orig_low, + orig_low + this_pad); + _gnutls_set_range(remainder, 0, + orig_high - (orig_low + + this_pad)); + } + + return 0; + } +} + +static size_t +_gnutls_range_fragment(size_t data_size, gnutls_range_st cur, + gnutls_range_st next) +{ + return MIN(cur.high, data_size - next.low); +} + +/** + * gnutls_record_send_range: + * @session: is a #gnutls_session_t type. + * @data: contains the data to send. + * @data_size: is the length of the data. + * @range: is the range of lengths in which the real data length must be hidden. + * + * This function operates like gnutls_record_send() but, while + * gnutls_record_send() adds minimal padding to each TLS record, + * this function uses the TLS extra-padding feature to conceal the real + * data size within the range of lengths provided. + * Some TLS sessions do not support extra padding (e.g. stream ciphers in standard + * TLS or SSL3 sessions). To know whether the current session supports extra + * padding, and hence length hiding, use the gnutls_record_can_use_length_hiding() + * function. + * + * Note: This function currently is limited to blocking sockets. + * + * Returns: The number of bytes sent (that is data_size in a successful invocation), + * or a negative error code. + **/ +ssize_t +gnutls_record_send_range(gnutls_session_t session, const void *data, + size_t data_size, const gnutls_range_st * range) +{ + size_t sent = 0; + size_t next_fragment_length; + ssize_t ret; + gnutls_range_st cur_range, next_range; + + /* sanity check on range and data size */ + if (range->low > range->high || + data_size < range->low || data_size > range->high) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + ret = gnutls_record_can_use_length_hiding(session); + if (ret == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + _gnutls_set_range(&cur_range, range->low, range->high); + + _gnutls_record_log + ("RANGE: Preparing message with size %d, range (%d,%d)\n", + (int) data_size, (int) range->low, (int) range->high); + + while (cur_range.high != 0) { + ret = + gnutls_range_split(session, &cur_range, &cur_range, + &next_range); + if (ret < 0) { + return ret; /* already gnutls_assert_val'd */ + } + + next_fragment_length = + _gnutls_range_fragment(data_size, cur_range, + next_range); + + _gnutls_record_log + ("RANGE: Next fragment size: %d (%d,%d); remaining range: (%d,%d)\n", + (int) next_fragment_length, (int) cur_range.low, + (int) cur_range.high, (int) next_range.low, + (int) next_range.high); + + ret = + _gnutls_send_tlen_int(session, GNUTLS_APPLICATION_DATA, + -1, EPOCH_WRITE_CURRENT, + &(((char *) data)[sent]), + next_fragment_length, + cur_range.high - + next_fragment_length, + MBUFFER_FLUSH); + + while (ret == GNUTLS_E_AGAIN + || ret == GNUTLS_E_INTERRUPTED) { + ret = + _gnutls_send_tlen_int(session, + GNUTLS_APPLICATION_DATA, + -1, EPOCH_WRITE_CURRENT, + NULL, 0, 0, + MBUFFER_FLUSH); + } + + if (ret < 0) { + return gnutls_assert_val(ret); + } + if (ret != (ssize_t) next_fragment_length) { + _gnutls_record_log + ("RANGE: ERROR: ret = %d; next_fragment_length = %d\n", + (int) ret, (int) next_fragment_length); + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + sent += next_fragment_length; + data_size -= next_fragment_length; + _gnutls_set_range(&cur_range, next_range.low, + next_range.high); + } + + return sent; +} |