Implement SASL authentication in PT-TLS client
authorMartin Willi <martin@revosec.ch>
Wed, 27 Feb 2013 12:47:08 +0000 (13:47 +0100)
committerMartin Willi <martin@revosec.ch>
Thu, 28 Feb 2013 15:46:07 +0000 (16:46 +0100)
src/libpttls/pt_tls_client.c

index 2f695ee..a01da39 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>
 
@@ -133,33 +135,211 @@ 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;
+
+       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;
+                               }
+                       default:
+                               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 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;
+               }
+       }
 }
 
 /**