1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
|
/*
* 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);
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);
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;
}
|