configure: Fix typo when enabling CPAN modules as dependency
[strongswan.git] / src / libpttls / pt_tls_client.c
index 59e5cd9..bd5b96f 100644 (file)
@@ -16,6 +16,8 @@
 #include "pt_tls_client.h"
 #include "pt_tls.h"
 
+#include <sasl/sasl_mechanism.h>
+
 #include <tls_socket.h>
 #include <utils/debug.h>
 
@@ -41,14 +43,19 @@ struct private_pt_tls_client_t {
        tls_socket_t *tls;
 
        /**
-        * Server address
+        * Server address/port
+        */
+       host_t *address;
+
+       /**
+        * Server identity
         */
-       char *server;
+       identification_t *server;
 
        /**
-        * Server port
+        * Client authentication identity
         */
-       u_int16_t port;
+       identification_t *client;
 
        /**
         * Current PT-TLS message identifier
@@ -61,36 +68,24 @@ struct private_pt_tls_client_t {
  */
 static bool make_connection(private_pt_tls_client_t *this)
 {
-       identification_t *id;
-       host_t *server;
        int fd;
 
-       server = host_create_from_dns(this->server, AF_UNSPEC, this->port);
-       if (!server)
-       {
-               return FALSE;
-       }
-
-       fd = socket(server->get_family(server), SOCK_STREAM, 0);
+       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));
-               server->destroy(server);
                return FALSE;
        }
-       if (connect(fd, server->get_sockaddr(server),
-                               *server->get_sockaddr_len(server)) == -1)
+       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));
-               server->destroy(server);
                close(fd);
                return FALSE;
        }
-       server->destroy(server);
 
-       id = identification_create_from_string(this->server);
-       this->tls = tls_socket_create(FALSE, id, NULL, fd, NULL);
-       id->destroy(id);
+       this->tls = tls_socket_create(FALSE, this->server, this->client, fd,
+                                                                 NULL, TLS_1_2, FALSE);
        if (!this->tls)
        {
                close(fd);
@@ -108,6 +103,7 @@ static bool negotiate_version(private_pt_tls_client_t *this)
        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);
 
@@ -116,8 +112,10 @@ static bool negotiate_version(private_pt_tls_client_t *this)
        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++))
+       res = pt_tls_write(this->tls, PT_TLS_VERSION_REQUEST, this->identifier++,
+                                          writer->get_buf(writer));
+       writer->destroy(writer);
+       if (!res)
        {
                return FALSE;
        }
@@ -141,33 +139,218 @@ static bool negotiate_version(private_pt_tls_client_t *this)
 }
 
 /**
- * Authenticate session using SASL
+ * Run a SASL mechanism
  */
-static bool authenticate(private_pt_tls_client_t *this)
+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 FALSE;
+               return FAILED;
        }
        if (vendor != 0 || type != PT_TLS_SASL_MECHS)
        {
-               DBG1(DBG_TNC, "PT-TLS authentication failed");
                reader->destroy(reader);
-               return FALSE;
+               return FAILED;
        }
-
-       if (reader->remaining(reader))
-       {       /* mechanism list not empty, FAIL until we support it */
+       if (!reader->remaining(reader))
+       {       /* mechanism list empty, SASL completed */
+               DBG1(DBG_TNC, "PT-TLS authentication complete");
                reader->destroy(reader);
-               return FALSE;
+               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;
+               }
        }
-       DBG1(DBG_TNC, "PT-TLS authentication complete");
        reader->destroy(reader);
-       return TRUE;
+
+       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;
+               }
+       }
 }
 
 /**
@@ -177,44 +360,30 @@ static bool assess(private_pt_tls_client_t *this, tls_t *tnccs)
 {
        while (TRUE)
        {
-               bio_writer_t *writer;
+               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;
 
-               writer = bio_writer_create(32);
-               while (TRUE)
+               switch (tnccs->build(tnccs, buf, &buflen, &msglen))
                {
-                       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);
+                       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;
-                               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;
+                               }
+                               break;
+                       case INVALID_STATE:
+                               break;
+                       case FAILED:
+                       default:
+                               return FALSE;
                }
 
                reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
@@ -263,19 +432,27 @@ METHOD(pt_tls_client_t, run_assessment, status_t,
 {
        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;
        }
+       tnccs->set_auth_type(tnccs, TNC_AUTH_X509_CERT);
+
+       DBG1(DBG_TNC, "entering PT-TLS data transport phase");
        if (!assess(this, (tls_t*)tnccs))
        {
                return FAILED;
@@ -289,17 +466,23 @@ METHOD(pt_tls_client_t, destroy, void,
 {
        if (this->tls)
        {
-               close(this->tls->get_fd(this->tls));
+               int fd;
+
+               fd = this->tls->get_fd(this->tls);
                this->tls->destroy(this->tls);
+               close(fd);
        }
-       free(this->server);
+       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(char *server, u_int16_t port)
+pt_tls_client_t *pt_tls_client_create(host_t *address, identification_t *server,
+                                                                         identification_t *client)
 {
        private_pt_tls_client_t *this;
 
@@ -308,8 +491,9 @@ pt_tls_client_t *pt_tls_client_create(char *server, u_int16_t port)
                        .run_assessment = _run_assessment,
                        .destroy = _destroy,
                },
-               .server = strdup(server),
-               .port = port,
+               .address = address,
+               .server = server,
+               .client = client,
        );
 
        return &this->public;