summaryrefslogtreecommitdiff
path: root/src/libpttls/pt_tls_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libpttls/pt_tls_client.c')
-rw-r--r--src/libpttls/pt_tls_client.c497
1 files changed, 497 insertions, 0 deletions
diff --git a/src/libpttls/pt_tls_client.c b/src/libpttls/pt_tls_client.c
new file mode 100644
index 000000000..d3ac936a2
--- /dev/null
+++ b/src/libpttls/pt_tls_client.c
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2012 Martin Willi
+ * Copyright (C) 2012 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 "pt_tls_client.h"
+#include "pt_tls.h"
+
+#include <sasl/sasl_mechanism.h>
+
+#include <tls_socket.h>
+#include <utils/debug.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+typedef struct private_pt_tls_client_t private_pt_tls_client_t;
+
+/**
+ * Private data of an pt_tls_client_t object.
+ */
+struct private_pt_tls_client_t {
+
+ /**
+ * Public pt_tls_client_t interface.
+ */
+ pt_tls_client_t public;
+
+ /**
+ * TLS secured socket used by PT-TLS
+ */
+ tls_socket_t *tls;
+
+ /**
+ * Server address/port
+ */
+ host_t *address;
+
+ /**
+ * Server identity
+ */
+ identification_t *server;
+
+ /**
+ * Client authentication identity
+ */
+ identification_t *client;
+
+ /**
+ * Current PT-TLS message identifier
+ */
+ u_int32_t identifier;
+};
+
+/**
+ * Establish TLS secured TCP connection to TNC server
+ */
+static bool make_connection(private_pt_tls_client_t *this)
+{
+ int fd;
+
+ fd = socket(this->address->get_family(this->address), SOCK_STREAM, 0);
+ if (fd == -1)
+ {
+ DBG1(DBG_TNC, "opening PT-TLS socket failed: %s", strerror(errno));
+ return FALSE;
+ }
+ if (connect(fd, this->address->get_sockaddr(this->address),
+ *this->address->get_sockaddr_len(this->address)) == -1)
+ {
+ DBG1(DBG_TNC, "connecting to PT-TLS server failed: %s", strerror(errno));
+ close(fd);
+ return FALSE;
+ }
+
+ this->tls = tls_socket_create(FALSE, this->server, this->client, fd, NULL);
+ if (!this->tls)
+ {
+ close(fd);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Negotiate PT-TLS version
+ */
+static bool negotiate_version(private_pt_tls_client_t *this)
+{
+ bio_writer_t *writer;
+ bio_reader_t *reader;
+ u_int32_t type, vendor, identifier, reserved;
+ u_int8_t version;
+
+ DBG1(DBG_TNC, "sending offer for PT-TLS version %d", PT_TLS_VERSION);
+
+ writer = bio_writer_create(4);
+ writer->write_uint8(writer, 0);
+ writer->write_uint8(writer, PT_TLS_VERSION);
+ writer->write_uint8(writer, PT_TLS_VERSION);
+ writer->write_uint8(writer, PT_TLS_VERSION);
+ if (!pt_tls_write(this->tls, writer, PT_TLS_VERSION_REQUEST,
+ this->identifier++))
+ {
+ return FALSE;
+ }
+
+ reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
+ if (!reader)
+ {
+ return FALSE;
+ }
+ if (vendor != 0 || type != PT_TLS_VERSION_RESPONSE ||
+ !reader->read_uint24(reader, &reserved) ||
+ !reader->read_uint8(reader, &version) ||
+ version != PT_TLS_VERSION)
+ {
+ DBG1(DBG_TNC, "PT-TLS version negotiation failed");
+ reader->destroy(reader);
+ return FALSE;
+ }
+ reader->destroy(reader);
+ return TRUE;
+}
+
+/**
+ * Run a SASL mechanism
+ */
+static status_t do_sasl(private_pt_tls_client_t *this, sasl_mechanism_t *sasl)
+{
+ u_int32_t type, vendor, identifier;
+ u_int8_t result;
+ bio_reader_t *reader;
+ bio_writer_t *writer;
+ chunk_t data;
+
+ writer = bio_writer_create(32);
+ writer->write_data8(writer, chunk_from_str(sasl->get_name(sasl)));
+ switch (sasl->build(sasl, &data))
+ {
+ case INVALID_STATE:
+ break;
+ case NEED_MORE:
+ writer->write_data(writer, data);
+ free(data.ptr);
+ break;
+ case SUCCESS:
+ /* shouldn't happen */
+ free(data.ptr);
+ /* FALL */
+ case FAILED:
+ default:
+ writer->destroy(writer);
+ return FAILED;
+ }
+ if (!pt_tls_write(this->tls, writer, PT_TLS_SASL_MECH_SELECTION,
+ this->identifier++))
+ {
+ return FAILED;
+ }
+ while (TRUE)
+ {
+ reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
+ if (!reader)
+ {
+ return FAILED;
+ }
+ if (vendor != 0)
+ {
+ reader->destroy(reader);
+ return FAILED;
+ }
+ switch (type)
+ {
+ case PT_TLS_SASL_AUTH_DATA:
+ switch (sasl->process(sasl, reader->peek(reader)))
+ {
+ case NEED_MORE:
+ reader->destroy(reader);
+ break;
+ case SUCCESS:
+ /* should not happen, as it would come in a RESULT */
+ case FAILED:
+ default:
+ reader->destroy(reader);
+ return FAILED;
+ }
+ break;
+ case PT_TLS_SASL_RESULT:
+ if (!reader->read_uint8(reader, &result))
+ {
+ reader->destroy(reader);
+ return FAILED;
+ }
+ switch (result)
+ {
+ case PT_TLS_SASL_RESULT_ABORT:
+ DBG1(DBG_TNC, "received SASL abort result");
+ reader->destroy(reader);
+ return FAILED;
+ case PT_TLS_SASL_RESULT_SUCCESS:
+ DBG1(DBG_TNC, "received SASL success result");
+ switch (sasl->process(sasl, reader->peek(reader)))
+ {
+ case SUCCESS:
+ reader->destroy(reader);
+ return SUCCESS;
+ case NEED_MORE:
+ /* inacceptable, it won't get more. FALL */
+ case FAILED:
+ default:
+ reader->destroy(reader);
+ return FAILED;
+ }
+ break;
+ case PT_TLS_SASL_RESULT_MECH_FAILURE:
+ case PT_TLS_SASL_RESULT_FAILURE:
+ DBG1(DBG_TNC, "received SASL failure result");
+ /* non-fatal failure, try again */
+ reader->destroy(reader);
+ return NEED_MORE;
+ }
+ /* fall-through */
+ default:
+ reader->destroy(reader);
+ return FAILED;
+ }
+
+ writer = bio_writer_create(32);
+ switch (sasl->build(sasl, &data))
+ {
+ case INVALID_STATE:
+ break;
+ case SUCCESS:
+ /* shoudln't happen, continue until we get a result */
+ case NEED_MORE:
+ writer->write_data(writer, data);
+ free(data.ptr);
+ break;
+ case FAILED:
+ default:
+ writer->destroy(writer);
+ return FAILED;
+ }
+ if (!pt_tls_write(this->tls, writer, PT_TLS_SASL_AUTH_DATA,
+ this->identifier++))
+ {
+ return FAILED;
+ }
+ }
+}
+
+/**
+ * Read SASL mechanism list, select and run mechanism
+ */
+static status_t select_and_do_sasl(private_pt_tls_client_t *this)
+{
+ bio_reader_t *reader;
+ sasl_mechanism_t *sasl = NULL;
+ u_int32_t type, vendor, identifier;
+ u_int8_t len;
+ chunk_t chunk;
+ char buf[21];
+ status_t status = NEED_MORE;
+
+ reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
+ if (!reader)
+ {
+ return FAILED;
+ }
+ if (vendor != 0 || type != PT_TLS_SASL_MECHS)
+ {
+ reader->destroy(reader);
+ return FAILED;
+ }
+ if (!reader->remaining(reader))
+ { /* mechanism list empty, SASL completed */
+ DBG1(DBG_TNC, "PT-TLS authentication complete");
+ reader->destroy(reader);
+ return SUCCESS;
+ }
+ while (reader->remaining(reader))
+ {
+ if (!reader->read_uint8(reader, &len) ||
+ !reader->read_data(reader, len & 0x1F, &chunk))
+ {
+ reader->destroy(reader);
+ return FAILED;
+ }
+ snprintf(buf, sizeof(buf), "%.*s", (int)chunk.len, chunk.ptr);
+ sasl = sasl_mechanism_create(buf, this->client);
+ if (sasl)
+ {
+ break;
+ }
+ }
+ reader->destroy(reader);
+
+ if (!sasl)
+ {
+ /* TODO: send PT-TLS error (5) */
+ return FAILED;
+ }
+ while (status == NEED_MORE)
+ {
+ status = do_sasl(this, sasl);
+ }
+ sasl->destroy(sasl);
+ if (status == SUCCESS)
+ { /* continue until we receive empty SASL mechanism list */
+ return NEED_MORE;
+ }
+ return FAILED;
+}
+
+/**
+ * Authenticate session using SASL
+ */
+static bool authenticate(private_pt_tls_client_t *this)
+{
+ while (TRUE)
+ {
+ switch (select_and_do_sasl(this))
+ {
+ case NEED_MORE:
+ continue;
+ case SUCCESS:
+ return TRUE;
+ case FAILED:
+ default:
+ return FALSE;
+ }
+ }
+}
+
+/**
+ * Perform assessment
+ */
+static bool assess(private_pt_tls_client_t *this, tls_t *tnccs)
+{
+ while (TRUE)
+ {
+ bio_writer_t *writer;
+ bio_reader_t *reader;
+ u_int32_t vendor, type, identifier;
+ chunk_t data;
+
+ writer = bio_writer_create(32);
+ while (TRUE)
+ {
+ char buf[2048];
+ size_t buflen, msglen;
+
+ buflen = sizeof(buf);
+ switch (tnccs->build(tnccs, buf, &buflen, &msglen))
+ {
+ case SUCCESS:
+ writer->destroy(writer);
+ return tnccs->is_complete(tnccs);
+ case FAILED:
+ default:
+ writer->destroy(writer);
+ return FALSE;
+ case INVALID_STATE:
+ writer->destroy(writer);
+ break;
+ case NEED_MORE:
+ writer->write_data(writer, chunk_create(buf, buflen));
+ continue;
+ case ALREADY_DONE:
+ writer->write_data(writer, chunk_create(buf, buflen));
+ if (!pt_tls_write(this->tls, writer, PT_TLS_PB_TNC_BATCH,
+ this->identifier++))
+ {
+ return FALSE;
+ }
+ writer = bio_writer_create(32);
+ continue;
+ }
+ break;
+ }
+
+ reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
+ if (!reader)
+ {
+ return FALSE;
+ }
+ if (vendor == 0)
+ {
+ if (type == PT_TLS_ERROR)
+ {
+ DBG1(DBG_TNC, "received PT-TLS error");
+ reader->destroy(reader);
+ return FALSE;
+ }
+ if (type != PT_TLS_PB_TNC_BATCH)
+ {
+ DBG1(DBG_TNC, "unexpected PT-TLS message: %d", type);
+ reader->destroy(reader);
+ return FALSE;
+ }
+ data = reader->peek(reader);
+ switch (tnccs->process(tnccs, data.ptr, data.len))
+ {
+ case SUCCESS:
+ reader->destroy(reader);
+ return tnccs->is_complete(tnccs);
+ case FAILED:
+ default:
+ reader->destroy(reader);
+ return FALSE;
+ case NEED_MORE:
+ break;
+ }
+ }
+ else
+ {
+ DBG1(DBG_TNC, "ignoring vendor specific PT-TLS message");
+ }
+ reader->destroy(reader);
+ }
+}
+
+METHOD(pt_tls_client_t, run_assessment, status_t,
+ private_pt_tls_client_t *this, tnccs_t *tnccs)
+{
+ if (!this->tls)
+ {
+ if (!make_connection(this))
+ {
+ return FAILED;
+ }
+ }
+ if (!negotiate_version(this))
+ {
+ return FAILED;
+ }
+ if (!authenticate(this))
+ {
+ return FAILED;
+ }
+ if (!assess(this, (tls_t*)tnccs))
+ {
+ return FAILED;
+ }
+ return SUCCESS;
+}
+
+
+METHOD(pt_tls_client_t, destroy, void,
+ private_pt_tls_client_t *this)
+{
+ if (this->tls)
+ {
+ int fd;
+
+ fd = this->tls->get_fd(this->tls);
+ this->tls->destroy(this->tls);
+ close(fd);
+ }
+ this->address->destroy(this->address);
+ this->server->destroy(this->server);
+ this->client->destroy(this->client);
+ free(this);
+}
+
+/**
+ * See header
+ */
+pt_tls_client_t *pt_tls_client_create(host_t *address, identification_t *server,
+ identification_t *client)
+{
+ private_pt_tls_client_t *this;
+
+ INIT(this,
+ .public = {
+ .run_assessment = _run_assessment,
+ .destroy = _destroy,
+ },
+ .address = address,
+ .server = server,
+ .client = client,
+ );
+
+ return &this->public;
+}