summaryrefslogtreecommitdiff
path: root/lib/range.c
blob: 788c56e92d657a9abff28221e16c71219b384068 (plain)
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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
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_user_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_user_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;
}