summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/gnutls_buffers.c14
-rw-r--r--lib/gnutls_buffers.h2
-rw-r--r--lib/gnutls_dtls.c61
-rw-r--r--lib/gnutls_dtls.h66
-rw-r--r--lib/gnutls_handshake.c11
-rw-r--r--lib/gnutls_handshake.h5
-rw-r--r--lib/gnutls_int.h8
-rw-r--r--lib/gnutls_record.c78
-rw-r--r--lib/includes/gnutls/gnutls.h.in2
-rw-r--r--lib/system.c26
-rw-r--r--lib/system.h2
11 files changed, 193 insertions, 82 deletions
diff --git a/lib/gnutls_buffers.c b/lib/gnutls_buffers.c
index 31e56b5264..778af8d0bc 100644
--- a/lib/gnutls_buffers.c
+++ b/lib/gnutls_buffers.c
@@ -600,7 +600,7 @@ _gnutls_io_write_flush (gnutls_session_t session)
* on timeout and a negative value on error.
*/
int
-_gnutls_io_check_recv (gnutls_session_t session, void* data, size_t data_size, unsigned int ms)
+_gnutls_io_check_recv (gnutls_session_t session, unsigned int ms)
{
gnutls_transport_ptr_t fd = session->internals.transport_send_ptr;
int ret = 0;
@@ -609,7 +609,7 @@ _gnutls_io_check_recv (gnutls_session_t session, void* data, size_t data_size, u
session->internals.pull_func != system_read)
return gnutls_assert_val(GNUTLS_E_PULL_ERROR);
- ret = session->internals.pull_timeout_func(fd, data, data_size, ms);
+ ret = session->internals.pull_timeout_func(fd, ms);
if (ret == -1)
return gnutls_assert_val(GNUTLS_E_PULL_ERROR);
@@ -740,6 +740,8 @@ parse_handshake_header (gnutls_session_t session, mbuffer_st* bufel, gnutls_hand
return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
dataptr = _mbuffer_get_udata_ptr(bufel);
+ data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size;
+
/* if reading a client hello of SSLv2 */
if (!IS_DTLS(session) && htype == GNUTLS_HANDSHAKE_CLIENT_HELLO &&
bufel->htype == GNUTLS_HANDSHAKE_CLIENT_HELLO_V2)
@@ -785,14 +787,16 @@ parse_handshake_header (gnutls_session_t session, mbuffer_st* bufel, gnutls_hand
/* make the length offset */
if (hsk->end_offset > 0) hsk->end_offset--;
- _gnutls_handshake_log ("HSK[%p]: %s was received. Length %d, frag offset %d, frag length: %d, sequence: %d\n",
+ _gnutls_handshake_log ("HSK[%p]: %s was received. Length %d[%d], frag offset %d, frag length: %d, sequence: %d\n",
session, _gnutls_handshake2str (hsk->htype),
- (int) hsk->length, hsk->start_offset, hsk->end_offset-hsk->start_offset+1, (int)hsk->sequence);
+ (int) hsk->length, (int)data_size, hsk->start_offset, hsk->end_offset-hsk->start_offset+1, (int)hsk->sequence);
hsk->header_size = handshake_header_size;
memcpy(hsk->header, _mbuffer_get_udata_ptr(bufel), handshake_header_size);
- data_size = _mbuffer_get_udata_size(bufel) - handshake_header_size;
+ if (hsk->length > 0 &&
+ (hsk->end_offset-hsk->start_offset >= data_size))
+ return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH);
if (hsk->length > 0 && (hsk->start_offset >= hsk->end_offset ||
hsk->end_offset-hsk->start_offset >= data_size ||
diff --git a/lib/gnutls_buffers.h b/lib/gnutls_buffers.h
index 60d2e6442a..bc432bdb7e 100644
--- a/lib/gnutls_buffers.h
+++ b/lib/gnutls_buffers.h
@@ -79,7 +79,7 @@ _gnutls_handshake_io_recv_int (gnutls_session_t session,
ssize_t _gnutls_io_write_flush (gnutls_session_t session);
int
-_gnutls_io_check_recv (gnutls_session_t session, void* data, size_t data_size, unsigned int ms);
+_gnutls_io_check_recv (gnutls_session_t session, unsigned int ms);
ssize_t _gnutls_handshake_io_write_flush (gnutls_session_t session);
inline static void _gnutls_handshake_buffer_clear(handshake_buffer_st* hsk)
diff --git a/lib/gnutls_dtls.c b/lib/gnutls_dtls.c
index 335e237657..076be8c643 100644
--- a/lib/gnutls_dtls.c
+++ b/lib/gnutls_dtls.c
@@ -46,7 +46,7 @@ transmit_message (gnutls_session_t session,
opaque *data, *mtu_data;
int ret = 0;
unsigned int offset, frag_len, data_size;
- const uint mtu = gnutls_dtls_get_mtu(session) - DTLS_HANDSHAKE_HEADER_SIZE;
+ const uint mtu = gnutls_dtls_get_data_mtu(session) - DTLS_HANDSHAKE_HEADER_SIZE;
if (bufel->type == GNUTLS_CHANGE_CIPHER_SPEC)
{
@@ -104,14 +104,13 @@ transmit_message (gnutls_session_t session,
_gnutls_handshake2str (bufel->htype),
bufel->htype, data_size, offset, frag_len);
- /* FIXME: We should collaborate with the record layer to pack as
- * many records possible into a single datagram. We should also
- * tell the record layer which epoch to use for encryption.
- */
ret = _gnutls_send_int (session, bufel->type, bufel->htype,
bufel->epoch, mtu_data, DTLS_HANDSHAKE_HEADER_SIZE + frag_len, 0);
if (ret < 0)
- break;
+ {
+ gnutls_assert();
+ break;
+ }
}
gnutls_free (mtu_data);
@@ -119,11 +118,9 @@ transmit_message (gnutls_session_t session,
return ret;
}
-static int drop_usage_count(gnutls_session_t session)
+static int drop_usage_count(gnutls_session_t session, mbuffer_head_st *const send_buffer)
{
int ret;
- mbuffer_head_st *const send_buffer =
- &session->internals.handshake_send_buffer;
mbuffer_st *cur;
for (cur = send_buffer->head;
@@ -137,8 +134,26 @@ static int drop_usage_count(gnutls_session_t session)
return 0;
}
-#define MAX_TIMEOUT 60000
-#define FINISHED_TIMEOUT 3000
+#define RETRANSMIT_WINDOW 2
+
+/* This function is to be called from record layer once
+ * a handshake replay is detected. It will make sure
+ * it transmits only once per few seconds. Otherwise
+ * it is the same as _dtls_transmit().
+ */
+int _dtls_retransmit(gnutls_session_t session)
+{
+time_t now = time(0);
+
+ if (now - session->internals.dtls.last_retransmit > RETRANSMIT_WINDOW)
+ {
+ session->internals.dtls.last_retransmit = now;
+ return _dtls_transmit(session);
+ }
+ else
+ return 0;
+
+}
/* This function transmits the flight that has been previously
* buffered.
@@ -175,28 +190,22 @@ int ret;
return gnutls_assert_val(ret);
/* last message in handshake -> no ack */
- if (last_type == GNUTLS_HANDSHAKE_FINISHED &&
- ((session->security_parameters.entity == GNUTLS_SERVER && session->internals.resumed == RESUME_FALSE) ||
- (session->security_parameters.entity == GNUTLS_CLIENT && session->internals.resumed == RESUME_TRUE)))
+ if (last_type == GNUTLS_HANDSHAKE_FINISHED && _dtls_is_async(session))
{
- opaque c;
- ret = _gnutls_io_check_recv(session, &c, 1, FINISHED_TIMEOUT);
- if (ret == GNUTLS_E_TIMEDOUT)
- ret = 0;
- else if (ret >= 0)
- {
- if (c == GNUTLS_HANDSHAKE) /* retransmit */
- ret = GNUTLS_E_TIMEDOUT;
- }
+ /* we cannot do anything here. We just return 0 and
+ * if a retransmission occurs because peer didn't receive it
+ * we rely on the record layer calling this function again.
+ */
+ return 0;
}
else /* all other messages -> implicit ack (receive of next flight) */
{
- ret = _gnutls_io_check_recv(session, NULL, 0, timeout);
+ ret = _gnutls_io_check_recv(session, timeout);
}
total_timeout += timeout;
timeout *= 2;
- timeout %= MAX_TIMEOUT;
+ timeout %= MAX_DTLS_TIMEOUT;
if (total_timeout >= session->internals.dtls.total_timeout) {
ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT);
@@ -214,7 +223,7 @@ int ret;
ret = 0;
cleanup:
- drop_usage_count(session);
+ drop_usage_count(session, send_buffer);
_mbuffer_head_clear(send_buffer);
/* SENDING -> WAITING state transition */
diff --git a/lib/gnutls_dtls.h b/lib/gnutls_dtls.h
index ab4e4da6fa..f8436b3731 100644
--- a/lib/gnutls_dtls.h
+++ b/lib/gnutls_dtls.h
@@ -26,8 +26,74 @@
# define DTLS_H
#include "gnutls_int.h"
+#include "gnutls_buffers.h"
+#include "gnutls_mbuffers.h"
int _dtls_transmit(gnutls_session_t session);
+int _dtls_retransmit(gnutls_session_t session);
int _dtls_record_check(gnutls_session_t session, uint64 * _seq);
+#define MAX_DTLS_TIMEOUT 60000
+
+/* returns true or false depending on whether we need to
+ * handle asynchronously handshake data.
+ */
+inline static int _dtls_is_async(gnutls_session_t session)
+{
+ if ((session->security_parameters.entity == GNUTLS_SERVER && session->internals.resumed == RESUME_FALSE) ||
+ (session->security_parameters.entity == GNUTLS_CLIENT && session->internals.resumed == RESUME_TRUE))
+ return 1;
+ else
+ return 0;
+}
+
+inline static void _dtls_async_timer_init(gnutls_session_t session)
+{
+ if (_dtls_is_async(session))
+ {
+ _gnutls_dtls_log ("DTLS[%p]: Initializing timer for handshake state.\n", session);
+ session->internals.dtls.async_term = time(0) + MAX_DTLS_TIMEOUT/1000;
+ }
+ else
+ session->internals.dtls.async_term = 0;
+}
+
+inline static void _dtls_async_timer_delete(gnutls_session_t session)
+{
+ if (session->internals.dtls.async_term != 0)
+ {
+ _gnutls_dtls_log ("DTLS[%p]: Deinitializing handshake state.\n", session);
+ _gnutls_handshake_io_buffer_clear (session);
+ session->internals.dtls.async_term = 0; /* turn off "timer" */
+ }
+}
+
+/* Checks whether it is time to terminate the timer
+ */
+inline static void _dtls_async_timer_check(gnutls_session_t session)
+{
+ if (!IS_DTLS(session))
+ return;
+
+ if (session->internals.dtls.async_term != 0)
+ {
+ time_t now = time(0);
+
+ /* check if we need to expire the queued handshake data */
+ if (now > session->internals.dtls.async_term)
+ {
+ _dtls_async_timer_delete(session);
+ }
+ }
+}
+
+/* Returns non-zero if the async timer is active */
+inline static int _dtls_async_timer_active(gnutls_session_t session)
+{
+ if (!IS_DTLS(session))
+ return 0;
+
+ return session->internals.dtls.async_term;
+}
+
#endif
diff --git a/lib/gnutls_handshake.c b/lib/gnutls_handshake.c
index 7e0608da3c..fb4c3c2f77 100644
--- a/lib/gnutls_handshake.c
+++ b/lib/gnutls_handshake.c
@@ -56,6 +56,7 @@
#include <auth_anon.h> /* for gnutls_anon_server_credentials_t */
#include <auth_psk.h> /* for gnutls_psk_server_credentials_t */
#include <random.h>
+#include <gnutls_dtls.h>
#ifdef HANDSHAKE_DEBUG
#define ERR(x, y) _gnutls_handshake_log("HSK[%p]: %s (%d)\n", session, x,y)
@@ -2603,7 +2604,15 @@ gnutls_handshake (gnutls_session_t session)
STATE = STATE0;
- _gnutls_handshake_io_buffer_clear (session);
+ if (IS_DTLS(session)==0)
+ {
+ _gnutls_handshake_io_buffer_clear (session);
+ }
+ else
+ {
+ _dtls_async_timer_init(session);
+ }
+
_gnutls_handshake_internal_state_clear (session);
session->security_parameters.epoch_next++;
diff --git a/lib/gnutls_handshake.h b/lib/gnutls_handshake.h
index 6792bbe0ce..656c9093b8 100644
--- a/lib/gnutls_handshake.h
+++ b/lib/gnutls_handshake.h
@@ -23,6 +23,9 @@
*
*/
+#ifndef HANDSHAKE_H
+#define HANDSHAKE_H
+
typedef enum Optional
{ OPTIONAL_PACKET, MANDATORY_PACKET } optional_t;
@@ -63,3 +66,5 @@ void _gnutls_handshake_hash_buffers_clear (gnutls_session_t session);
*/
#define AGAIN(target) (STATE==target?1:0)
#define AGAIN2(state, target) (state==target?1:0)
+
+#endif
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index 1c18817571..4884888aa5 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -593,6 +593,14 @@ typedef struct
uint64_t record_sw[DTLS_RECORD_WINDOW_SIZE];
int record_sw_size;
+
+ /* timers to handle async handshake after gnutls_handshake()
+ * has terminated. Required to handle retransmissions.
+ */
+ time_t async_term;
+
+ /* last retransmission triggered by record layer */
+ time_t last_retransmit;
} dtls_st;
diff --git a/lib/gnutls_record.c b/lib/gnutls_record.c
index cc259c4d05..19f6aa3d1f 100644
--- a/lib/gnutls_record.c
+++ b/lib/gnutls_record.c
@@ -48,6 +48,19 @@
#include <gnutls_dtls.h>
#include <gnutls_dh.h>
+struct tls_record_st {
+ uint16_t header_size;
+ uint8_t version[2];
+ uint64 sequence; /* DTLS */
+ uint16_t length;
+ uint16_t packet_size; /* header_size + length */
+ content_type_t type;
+ uint16_t epoch; /* valid in DTLS only */
+ int v2:1; /* whether an SSLv2 client hello */
+ /* the data */
+};
+
+
/**
* gnutls_protocol_get_version:
* @session: is a #gnutls_session_t structure.
@@ -578,7 +591,7 @@ record_check_version (gnutls_session_t session,
*/
static int
record_add_to_buffers (gnutls_session_t session,
- content_type_t recv_type, content_type_t type,
+ struct tls_record_st *recv, content_type_t type,
gnutls_handshake_description_t htype,
uint64* seq,
mbuffer_st* bufel)
@@ -586,18 +599,22 @@ record_add_to_buffers (gnutls_session_t session,
int ret;
- if ((recv_type == type)
+ if ((recv->type == type)
&& (type == GNUTLS_APPLICATION_DATA ||
type == GNUTLS_CHANGE_CIPHER_SPEC ||
type == GNUTLS_HANDSHAKE))
{
_gnutls_record_buffer_put (session, type, seq, bufel);
+
+ /* if we received application data as expected then we
+ * deactivate the async timer */
+ _dtls_async_timer_delete(session);
}
else
{
/* if the expected type is different than the received
*/
- switch (recv_type)
+ switch (recv->type)
{
case GNUTLS_ALERT:
if (bufel->msg.size < 2)
@@ -651,9 +668,15 @@ record_add_to_buffers (gnutls_session_t session,
return GNUTLS_E_UNEXPECTED_PACKET;
case GNUTLS_APPLICATION_DATA:
+ if (session->internals.initial_negotiation_completed == 0)
+ {
+ ret = gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET);
+ goto cleanup;
+ }
+
/* even if data is unexpected put it into the buffer */
if ((ret =
- _gnutls_record_buffer_put (session, recv_type, seq,
+ _gnutls_record_buffer_put (session, recv->type, seq,
bufel)) < 0)
{
gnutls_assert ();
@@ -678,6 +701,15 @@ record_add_to_buffers (gnutls_session_t session,
break;
case GNUTLS_HANDSHAKE:
+ /* In DTLS we might receive a handshake replay from the peer to indicate
+ * the our last TLS handshake messages were not received.
+ */
+ if (_dtls_is_async(session) && _dtls_async_timer_active(session))
+ {
+ ret = _dtls_retransmit(session);
+ goto cleanup;
+ }
+
/* This is legal if HELLO_REQUEST is received - and we are a client.
* If we are a server, a client may initiate a renegotiation at any time.
*/
@@ -685,7 +717,7 @@ record_add_to_buffers (gnutls_session_t session,
{
gnutls_assert ();
ret =
- _gnutls_record_buffer_put (session, recv_type, seq, bufel);
+ _gnutls_record_buffer_put (session, recv->type, seq, bufel);
if (ret < 0)
{
gnutls_assert ();
@@ -708,7 +740,7 @@ record_add_to_buffers (gnutls_session_t session,
_gnutls_record_log
("REC[%p]: Received Unknown packet %d expecting %d\n",
- session, recv_type, type);
+ session, recv->type, type);
gnutls_assert ();
ret = GNUTLS_E_INTERNAL_ERROR;
@@ -737,17 +769,6 @@ int ret;
return ret;
}
-struct tls_record_st {
- uint16_t header_size;
- uint8_t version[2];
- uint64 sequence; /* DTLS */
- uint16_t length;
- uint16_t packet_size; /* header_size + length */
- content_type_t type;
- int v2:1; /* whether an SSLv2 client hello */
- /* the data */
-};
-
/* Checks the record headers and returns the length, version and
* content type.
*/
@@ -784,6 +805,7 @@ record_read_headers (gnutls_session_t session,
* V2 compatibility is a mess.
*/
record->v2 = 1;
+ record->epoch = 0;
_gnutls_record_log ("REC[%p]: SSL 2.0 %s packet received. Length: %d\n",
session,
@@ -799,14 +821,18 @@ record_read_headers (gnutls_session_t session,
record->type = headers[0];
record->version[0] = headers[1];
record->version[1] = headers[2];
-
+
if(IS_DTLS(session))
{
memcpy(record->sequence.i, &headers[3], 8);
record->length = _gnutls_read_uint16 (&headers[11]);
+ record->epoch = _gnutls_read_uint16(record->sequence.i);
}
else
- record->length = _gnutls_read_uint16 (&headers[3]);
+ {
+ record->length = _gnutls_read_uint16 (&headers[3]);
+ record->epoch = 0;
+ }
_gnutls_record_log ("REC[%p]: SSL %d.%d %s packet received. Length: %d\n",
session, (int)record->version[0], (int)record->version[1],
@@ -850,9 +876,7 @@ gnutls_datum_t raw; /* raw headers */
/* Check if the DTLS epoch is valid */
if (IS_DTLS(session))
{
- uint16_t epoch = _gnutls_read_uint16(record->sequence.i);
-
- if (_gnutls_epoch_is_valid(session, epoch) == 0)
+ if (_gnutls_epoch_is_valid(session, record->epoch) == 0)
{
_gnutls_audit_log("Discarded message[%u] with invalid epoch 0x%.2x%.2x.\n",
(unsigned int)_gnutls_uint64touint32 (&record->sequence), (int)record->sequence.i[0],
@@ -1049,7 +1073,7 @@ begin:
decrypted->htype = -1;
ret =
- record_add_to_buffers (session, record.type, type, htype,
+ record_add_to_buffers (session, &record, type, htype,
packet_sequence, decrypted);
/* bufel is now either deinitialized or buffered somewhere else */
@@ -1142,6 +1166,8 @@ _gnutls_recv_int (gnutls_session_t session, content_type_t type,
return GNUTLS_E_INVALID_SESSION;
}
+ _dtls_async_timer_check(session);
+
/* If we have enough data in the cache do not bother receiving
* a new packet. (in order to flush the cache)
*/
@@ -1220,9 +1246,9 @@ gnutls_record_send (gnutls_session_t session, const void *data,
* initiated a handshake. In that case the server can only initiate a
* handshake or terminate the connection.
*
- * Returns: the number of bytes received and zero on EOF. A negative
- * error code is returned in case of an error. The number of bytes
- * received might be less than @data_size.
+ * Returns: the number of bytes received and zero on EOF (for stream
+ * connections). A negative error code is returned in case of an error.
+ * The number of bytes received might be less than the requested @data_size.
**/
ssize_t
gnutls_record_recv (gnutls_session_t session, void *data, size_t data_size)
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index cc64d6290a..dfcf92a12e 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -1180,7 +1180,7 @@ extern "C"
typedef ssize_t (*gnutls_push_func) (gnutls_transport_ptr_t, const void *,
size_t);
- typedef int (*gnutls_pull_timeout_func) (gnutls_transport_ptr_t, void*data, size_t size, unsigned int ms);
+ typedef int (*gnutls_pull_timeout_func) (gnutls_transport_ptr_t, unsigned int ms);
typedef ssize_t (*gnutls_vec_push_func) (gnutls_transport_ptr_t,
const giovec_t * iov, int iovcnt);
diff --git a/lib/system.c b/lib/system.c
index dd2d74fc20..120fc8b0ca 100644
--- a/lib/system.c
+++ b/lib/system.c
@@ -111,7 +111,7 @@ system_read_peek (gnutls_transport_ptr_t ptr, void *data, size_t data_size)
*
* Returns -1 on error, 0 on timeout.
*/
-int system_recv_timeout(gnutls_transport_ptr_t ptr, void* data, size_t data_size, unsigned int ms)
+int system_recv_timeout(gnutls_transport_ptr_t ptr, unsigned int ms)
{
fd_set rfds;
struct timeval tv;
@@ -128,27 +128,11 @@ int ret, ret2;
return ret;
- if (data_size == 0)
- {
- ret2 = recv(GNUTLS_POINTER_TO_INT(ptr), NULL, 0, MSG_PEEK);
- if (ret2 == -1)
- return ret2;
+ ret2 = recv(GNUTLS_POINTER_TO_INT(ptr), NULL, 0, MSG_PEEK);
+ if (ret2 == -1)
+ return ret2;
- return ret;
- }
-
- /* only report ok if the next message is from the peer we expect
- * from
- */
- ret = recv(GNUTLS_POINTER_TO_INT(ptr), data, data_size, MSG_PEEK);
- if (ret > 0)
- {
- return ret;
- }
- else
- {
- return -1;
- }
+ return ret;
}
/* Thread stuff */
diff --git a/lib/system.h b/lib/system.h
index bdda73e4ca..b1f556557a 100644
--- a/lib/system.h
+++ b/lib/system.h
@@ -8,7 +8,7 @@
#endif
int system_errno (gnutls_transport_ptr_t);
-int system_recv_timeout(gnutls_transport_ptr_t ptr,void*data, size_t, unsigned int ms);
+int system_recv_timeout(gnutls_transport_ptr_t ptr, unsigned int ms);
#ifdef _WIN32
ssize_t system_write (gnutls_transport_ptr_t ptr, const void *data,