strongswan/src/libpttls/pt_tls_client.c

499 lines
10 KiB
C

/*
* 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;
bool res;
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);
res = pt_tls_write(this->tls, PT_TLS_VERSION_REQUEST, this->identifier++,
writer->get_buf(writer));
writer->destroy(writer);
if (!res)
{
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;
bool res;
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;
}
res = pt_tls_write(this->tls, PT_TLS_SASL_MECH_SELECTION,
this->identifier++, writer->get_buf(writer));
writer->destroy(writer);
if (!res)
{
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;
}
DBG1(DBG_TNC, "received SASL %N result",
pt_tls_sasl_result_names, result);
switch (result)
{
case PT_TLS_SASL_RESULT_ABORT:
reader->destroy(reader);
return FAILED;
case PT_TLS_SASL_RESULT_SUCCESS:
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:
/* 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;
}
res = pt_tls_write(this->tls, PT_TLS_SASL_AUTH_DATA,
this->identifier++, writer->get_buf(writer));
writer->destroy(writer);
if (!res)
{
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)
{
size_t msglen;
size_t buflen = PT_TLS_MAX_MESSAGE_LEN;
char buf[buflen];
bio_reader_t *reader;
u_int32_t vendor, type, identifier;
chunk_t data;
switch (tnccs->build(tnccs, buf, &buflen, &msglen))
{
case SUCCESS:
return tnccs->is_complete(tnccs);
case ALREADY_DONE:
data = chunk_create(buf, buflen);
if (!pt_tls_write(this->tls, PT_TLS_PB_TNC_BATCH,
this->identifier++, data))
{
return FALSE;
}
break;
case INVALID_STATE:
break;
case FAILED:
default:
return FALSE;
}
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)
{
DBG1(DBG_TNC, "entering PT-TLS setup phase");
if (!make_connection(this))
{
return FAILED;
}
}
DBG1(DBG_TNC, "entering PT-TLS negotiation phase");
if (!negotiate_version(this))
{
return FAILED;
}
DBG1(DBG_TNC, "doing SASL client authentication");
if (!authenticate(this))
{
return FAILED;
}
DBG1(DBG_TNC, "entering PT-TLS data transport phase");
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;
}