summaryrefslogtreecommitdiff
path: root/src/libtls/tls_fragmentation.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libtls/tls_fragmentation.c')
-rw-r--r--src/libtls/tls_fragmentation.c471
1 files changed, 471 insertions, 0 deletions
diff --git a/src/libtls/tls_fragmentation.c b/src/libtls/tls_fragmentation.c
new file mode 100644
index 000000000..5a598cfc4
--- /dev/null
+++ b/src/libtls/tls_fragmentation.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program 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 General Public License
+ * for more details.
+ */
+
+#include "tls_fragmentation.h"
+
+#include "tls_reader.h"
+
+#include <debug.h>
+
+typedef struct private_tls_fragmentation_t private_tls_fragmentation_t;
+
+/**
+ * Alert state
+ */
+typedef enum {
+ /* no alert received/sent */
+ ALERT_NONE,
+ /* currently sending an alert */
+ ALERT_SENDING,
+ /* alert sent and out */
+ ALERT_SENT,
+} alert_state_t;
+
+/**
+ * Private data of an tls_fragmentation_t object.
+ */
+struct private_tls_fragmentation_t {
+
+ /**
+ * Public tls_fragmentation_t interface.
+ */
+ tls_fragmentation_t public;
+
+ /**
+ * Upper layer handshake protocol
+ */
+ tls_handshake_t *handshake;
+
+ /**
+ * TLS alert handler
+ */
+ tls_alert_t *alert;
+
+ /**
+ * State of alert handling
+ */
+ alert_state_t state;
+
+ /**
+ * Did the application layer complete successfully?
+ */
+ bool application_finished;
+
+ /**
+ * Handshake input buffer
+ */
+ chunk_t input;
+
+ /**
+ * Position in input buffer
+ */
+ size_t inpos;
+
+ /**
+ * Currently processed handshake message type
+ */
+ tls_handshake_type_t type;
+
+ /**
+ * Handshake output buffer
+ */
+ chunk_t output;
+
+ /**
+ * Type of data in output buffer
+ */
+ tls_content_type_t output_type;
+
+ /**
+ * Upper layer application data protocol
+ */
+ tls_application_t *application;
+};
+
+/**
+ * Maximum size of a TLS fragment
+ */
+#define MAX_TLS_FRAGMENT_LEN 16384
+
+/**
+ * Maximum size of a TLS handshake message we accept
+ */
+#define MAX_TLS_HANDSHAKE_LEN 65536
+
+/**
+ * Process a TLS alert
+ */
+static status_t process_alert(private_tls_fragmentation_t *this,
+ tls_reader_t *reader)
+{
+ u_int8_t level, description;
+
+ if (!reader->read_uint8(reader, &level) ||
+ !reader->read_uint8(reader, &description))
+ {
+ this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
+ return NEED_MORE;
+ }
+ return this->alert->process(this->alert, level, description);
+}
+
+/**
+ * Process TLS handshake protocol data
+ */
+static status_t process_handshake(private_tls_fragmentation_t *this,
+ tls_reader_t *reader)
+{
+ while (reader->remaining(reader))
+ {
+ tls_reader_t *msg;
+ u_int8_t type;
+ u_int32_t len;
+ status_t status;
+ chunk_t data;
+
+ if (reader->remaining(reader) > MAX_TLS_FRAGMENT_LEN)
+ {
+ DBG1(DBG_TLS, "TLS fragment has invalid length");
+ this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
+ return NEED_MORE;
+ }
+
+ if (this->input.len == 0)
+ { /* new handshake message */
+ if (!reader->read_uint8(reader, &type) ||
+ !reader->read_uint24(reader, &len))
+ {
+ DBG1(DBG_TLS, "TLS handshake header invalid");
+ this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
+ return NEED_MORE;
+ }
+ this->type = type;
+ if (len > MAX_TLS_HANDSHAKE_LEN)
+ {
+ DBG1(DBG_TLS, "TLS handshake exceeds maximum length");
+ this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
+ return NEED_MORE;
+ }
+ chunk_free(&this->input);
+ this->inpos = 0;
+ if (len)
+ {
+ this->input = chunk_alloc(len);
+ }
+ }
+
+ len = min(this->input.len - this->inpos, reader->remaining(reader));
+ if (!reader->read_data(reader, len, &data))
+ {
+ DBG1(DBG_TLS, "TLS fragment has invalid length");
+ this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
+ return NEED_MORE;
+ }
+ memcpy(this->input.ptr + this->inpos, data.ptr, len);
+ this->inpos += len;
+
+ if (this->input.len == this->inpos)
+ { /* message completely defragmented, process */
+ msg = tls_reader_create(this->input);
+ DBG2(DBG_TLS, "received TLS %N handshake (%u bytes)",
+ tls_handshake_type_names, this->type, this->input.len);
+ status = this->handshake->process(this->handshake, this->type, msg);
+ msg->destroy(msg);
+ chunk_free(&this->input);
+ if (status != NEED_MORE)
+ {
+ return status;
+ }
+ }
+ if (this->alert->fatal(this->alert))
+ {
+ break;
+ }
+ }
+ return NEED_MORE;
+}
+
+/**
+ * Process TLS application data
+ */
+static status_t process_application(private_tls_fragmentation_t *this,
+ tls_reader_t *reader)
+{
+ while (reader->remaining(reader))
+ {
+ status_t status;
+
+ if (reader->remaining(reader) > MAX_TLS_FRAGMENT_LEN)
+ {
+ DBG1(DBG_TLS, "TLS fragment has invalid length");
+ this->alert->add(this->alert, TLS_FATAL, TLS_DECODE_ERROR);
+ return NEED_MORE;
+ }
+ status = this->application->process(this->application, reader);
+ switch (status)
+ {
+ case NEED_MORE:
+ continue;
+ case SUCCESS:
+ this->application_finished = TRUE;
+ return SUCCESS;
+ case FAILED:
+ default:
+ this->alert->add(this->alert, TLS_FATAL, TLS_CLOSE_NOTIFY);
+ return NEED_MORE;
+ }
+ }
+ return NEED_MORE;
+}
+
+METHOD(tls_fragmentation_t, process, status_t,
+ private_tls_fragmentation_t *this, tls_content_type_t type, chunk_t data)
+{
+ tls_reader_t *reader;
+ status_t status;
+
+ switch (this->state)
+ {
+ case ALERT_SENDING:
+ case ALERT_SENT:
+ /* don't accept more input, fatal error ocurred */
+ return NEED_MORE;
+ case ALERT_NONE:
+ break;
+ }
+ reader = tls_reader_create(data);
+ switch (type)
+ {
+ case TLS_CHANGE_CIPHER_SPEC:
+ if (this->handshake->change_cipherspec(this->handshake))
+ {
+ status = NEED_MORE;
+ break;
+ }
+ status = FAILED;
+ break;
+ case TLS_ALERT:
+ status = process_alert(this, reader);
+ break;
+ case TLS_HANDSHAKE:
+ status = process_handshake(this, reader);
+ break;
+ case TLS_APPLICATION_DATA:
+ status = process_application(this, reader);
+ break;
+ default:
+ DBG1(DBG_TLS, "received unknown TLS content type %d, ignored", type);
+ status = NEED_MORE;
+ break;
+ }
+ reader->destroy(reader);
+ return status;
+}
+
+/**
+ * Check if alerts are pending
+ */
+static bool check_alerts(private_tls_fragmentation_t *this, chunk_t *data)
+{
+ tls_alert_level_t level;
+ tls_alert_desc_t desc;
+ tls_writer_t *writer;
+
+ if (this->alert->get(this->alert, &level, &desc))
+ {
+ writer = tls_writer_create(2);
+
+ writer->write_uint8(writer, level);
+ writer->write_uint8(writer, desc);
+
+ *data = chunk_clone(writer->get_buf(writer));
+ writer->destroy(writer);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Build hanshake message
+ */
+static status_t build_handshake(private_tls_fragmentation_t *this)
+{
+ tls_writer_t *hs, *msg;
+ tls_handshake_type_t type;
+ status_t status;
+
+ msg = tls_writer_create(64);
+ while (TRUE)
+ {
+ hs = tls_writer_create(64);
+ status = this->handshake->build(this->handshake, &type, hs);
+ switch (status)
+ {
+ case NEED_MORE:
+ if (this->alert->fatal(this->alert))
+ {
+ break;
+ }
+ msg->write_uint8(msg, type);
+ msg->write_data24(msg, hs->get_buf(hs));
+ DBG2(DBG_TLS, "sending TLS %N handshake (%u bytes)",
+ tls_handshake_type_names, type, hs->get_buf(hs).len);
+ hs->destroy(hs);
+ continue;
+ case INVALID_STATE:
+ this->output_type = TLS_HANDSHAKE;
+ this->output = chunk_clone(msg->get_buf(msg));
+ break;
+ default:
+ break;
+ }
+ hs->destroy(hs);
+ break;
+ }
+ msg->destroy(msg);
+ return status;
+}
+
+/**
+ * Build TLS application data
+ */
+static status_t build_application(private_tls_fragmentation_t *this)
+{
+ tls_writer_t *msg;
+ status_t status;
+
+ msg = tls_writer_create(64);
+ while (TRUE)
+ {
+ status = this->application->build(this->application, msg);
+ switch (status)
+ {
+ case NEED_MORE:
+ continue;
+ case INVALID_STATE:
+ this->output_type = TLS_APPLICATION_DATA;
+ this->output = chunk_clone(msg->get_buf(msg));
+ break;
+ case SUCCESS:
+ this->application_finished = TRUE;
+ break;
+ case FAILED:
+ default:
+ this->alert->add(this->alert, TLS_FATAL, TLS_CLOSE_NOTIFY);
+ break;
+ }
+ break;
+ }
+ msg->destroy(msg);
+ return status;
+}
+
+METHOD(tls_fragmentation_t, build, status_t,
+ private_tls_fragmentation_t *this, tls_content_type_t *type, chunk_t *data)
+{
+ status_t status = INVALID_STATE;
+
+ switch (this->state)
+ {
+ case ALERT_SENDING:
+ this->state = ALERT_SENT;
+ return INVALID_STATE;
+ case ALERT_SENT:
+ return FAILED;
+ case ALERT_NONE:
+ break;
+ }
+ if (check_alerts(this, data))
+ {
+ this->state = ALERT_SENDING;
+ *type = TLS_ALERT;
+ return NEED_MORE;
+ }
+ if (!this->output.len)
+ {
+ if (this->handshake->cipherspec_changed(this->handshake))
+ {
+ *type = TLS_CHANGE_CIPHER_SPEC;
+ *data = chunk_clone(chunk_from_chars(0x01));
+ return NEED_MORE;
+ }
+ if (!this->handshake->finished(this->handshake))
+ {
+ status = build_handshake(this);
+ }
+ else if (this->application)
+ {
+ status = build_application(this);
+ }
+ if (check_alerts(this, data))
+ {
+ this->state = ALERT_SENDING;
+ *type = TLS_ALERT;
+ return NEED_MORE;
+ }
+ }
+ if (this->output.len)
+ {
+ *type = this->output_type;
+ if (this->output.len <= MAX_TLS_FRAGMENT_LEN)
+ {
+ *data = this->output;
+ this->output = chunk_empty;
+ return NEED_MORE;
+ }
+ *data = chunk_create(this->output.ptr, MAX_TLS_FRAGMENT_LEN);
+ this->output = chunk_clone(chunk_skip(this->output, MAX_TLS_FRAGMENT_LEN));
+ return NEED_MORE;
+ }
+ return status;
+}
+
+METHOD(tls_fragmentation_t, application_finished, bool,
+ private_tls_fragmentation_t *this)
+{
+ return this->application_finished;
+}
+
+METHOD(tls_fragmentation_t, destroy, void,
+ private_tls_fragmentation_t *this)
+{
+ free(this->input.ptr);
+ free(this->output.ptr);
+ free(this);
+}
+
+/**
+ * See header
+ */
+tls_fragmentation_t *tls_fragmentation_create(tls_handshake_t *handshake,
+ tls_alert_t *alert, tls_application_t *application)
+{
+ private_tls_fragmentation_t *this;
+
+ INIT(this,
+ .public = {
+ .process = _process,
+ .build = _build,
+ .application_finished = _application_finished,
+ .destroy = _destroy,
+ },
+ .handshake = handshake,
+ .alert = alert,
+ .state = ALERT_NONE,
+ .application = application,
+ );
+
+ return &this->public;
+}