]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
Implemented a generic TLS EAP helper to implement EAP-TLS, TTLS and other variants
authorMartin Willi <martin@revosec.ch>
Tue, 31 Aug 2010 07:11:09 +0000 (09:11 +0200)
committerMartin Willi <martin@revosec.ch>
Tue, 31 Aug 2010 14:16:58 +0000 (16:16 +0200)
src/libtls/Makefile.am
src/libtls/tls_eap.c [new file with mode: 0644]
src/libtls/tls_eap.h [new file with mode: 0644]

index 66819f2a2a97aa3d49e325e1ccebd4e5be8c9fa5..a58e783d7c89082626e0b9c1cb70dd2973a83597 100644 (file)
@@ -12,6 +12,7 @@ libtls_la_SOURCES = \
        tls_reader.h tls_reader.c \
        tls_writer.h tls_writer.c \
        tls_socket.h tls_socket.c \
+       tls_eap.h tls_eap.c \
        tls_peer.h tls_peer.c \
        tls_server.h tls_server.c \
        tls_handshake.h tls_application.h tls.h tls.c
diff --git a/src/libtls/tls_eap.c b/src/libtls/tls_eap.c
new file mode 100644 (file)
index 0000000..0279298
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * 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_eap.h"
+
+#include "tls.h"
+
+#include <debug.h>
+#include <library.h>
+
+/** Size limit for a single TLS message */
+#define MAX_TLS_MESSAGE_LEN 65536
+/** Size of a EAP-TLS fragment */
+#define EAP_TLS_FRAGMENT_LEN 1014
+
+typedef struct private_tls_eap_t private_tls_eap_t;
+
+/**
+ * Private data of an tls_eap_t object.
+ */
+struct private_tls_eap_t {
+
+       /**
+        * Public tls_eap_t interface.
+        */
+       tls_eap_t public;
+
+       /**
+        * Type of EAP method, EAP-TLS or EAP-TTLS
+        */
+       eap_type_t type;
+
+       /**
+        * TLS stack
+        */
+       tls_t *tls;
+
+       /**
+        * Role
+        */
+       bool is_server;
+
+       /**
+        * First fragment of a multi-fragment record?
+        */
+       bool first_fragment;
+};
+
+/**
+ * Flags of an EAP-TLS/TTLS message
+ */
+typedef enum {
+       EAP_TLS_LENGTH = (1<<7),
+       EAP_TLS_MORE_FRAGS = (1<<6),
+       EAP_TLS_START = (1<<5),
+       EAP_TTLS_VERSION = (0x07),
+} eap_tls_flags_t;
+
+/**
+ * EAP-TLS/TTLS packet format
+ */
+typedef struct __attribute__((packed)) {
+       u_int8_t code;
+       u_int8_t identifier;
+       u_int16_t length;
+       u_int8_t type;
+       u_int8_t flags;
+} eap_tls_packet_t;
+
+METHOD(tls_eap_t, initiate, status_t,
+       private_tls_eap_t *this, chunk_t *out)
+{
+       if (this->is_server)
+       {
+               eap_tls_packet_t pkt = {
+                       .type = this->type,
+                       .code = EAP_REQUEST,
+                       .flags = EAP_TLS_START,
+               };
+               htoun16(&pkt.length, sizeof(eap_tls_packet_t));
+               do
+               {       /* start with non-zero random identifier */
+                       pkt.identifier = random();
+               }
+               while (!pkt.identifier);
+
+               DBG2(DBG_IKE, "sending %N start packet", eap_type_names, this->type);
+               *out = chunk_clone(chunk_from_thing(pkt));
+               return NEED_MORE;
+       }
+       return FAILED;
+}
+
+/**
+ * Process a received packet
+ */
+static status_t process_pkt(private_tls_eap_t *this, eap_tls_packet_t *pkt)
+{
+       u_int32_t msg_len;
+       u_int16_t pkt_len;
+
+       pkt_len = untoh16(&pkt->length);
+       if (pkt->flags & EAP_TLS_LENGTH)
+       {
+               if (pkt_len < sizeof(eap_tls_packet_t) + sizeof(msg_len))
+               {
+                       DBG1(DBG_TLS, "%N packet too short", eap_type_names, this->type);
+                       return FAILED;
+               }
+               msg_len = untoh32(pkt + 1);
+               if (msg_len < pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len) ||
+                       msg_len > MAX_TLS_MESSAGE_LEN)
+               {
+                       DBG1(DBG_TLS, "invalid %N packet length", eap_type_names, this->type);
+                       return FAILED;
+               }
+               return this->tls->process(this->tls, (char*)(pkt + 1) + sizeof(msg_len),
+                                               pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len));
+       }
+       return this->tls->process(this->tls, (char*)(pkt + 1),
+                                                         pkt_len - sizeof(eap_tls_packet_t));
+}
+
+/**
+ * Build a packet to send
+ */
+static status_t build_pkt(private_tls_eap_t *this,
+                                                 u_int8_t identifier, chunk_t *out)
+{
+       char buf[EAP_TLS_FRAGMENT_LEN];
+       eap_tls_packet_t *pkt;
+       size_t len, reclen;
+       status_t status;
+       char *kind;
+
+       pkt = (eap_tls_packet_t*)buf;
+       pkt->code = this->is_server ? EAP_REQUEST : EAP_RESPONSE;
+       pkt->identifier = this->is_server ? identifier + 1 : identifier;
+       pkt->type = this->type;
+       pkt->flags = 0;
+
+       if (this->first_fragment)
+       {
+               pkt->flags = EAP_TLS_LENGTH;
+               len = sizeof(buf) - sizeof(eap_tls_packet_t) - sizeof(u_int32_t);
+               status = this->tls->build(this->tls, buf + sizeof(eap_tls_packet_t) +
+                                                                 sizeof(u_int32_t), &len, &reclen);
+       }
+       else
+       {
+               len = sizeof(buf) - sizeof(eap_tls_packet_t);
+               status = this->tls->build(this->tls, buf + sizeof(eap_tls_packet_t),
+                                                                 &len, &reclen);
+       }
+       switch (status)
+       {
+               case NEED_MORE:
+                       pkt->flags |= EAP_TLS_MORE_FRAGS;
+                       kind = "non-first fragment";
+                       if (this->first_fragment)
+                       {
+                               this->first_fragment = FALSE;
+                               kind = "first fragment";
+                       }
+                       break;
+               case ALREADY_DONE:
+                       kind = "packet";
+                       if (!this->first_fragment)
+                       {
+                               this->first_fragment = TRUE;
+                               kind = "final fragment";
+                       }
+                       break;
+               default:
+                       return status;
+       }
+       DBG2(DBG_TLS, "sending %N %s (%u bytes)",
+                eap_type_names, this->type, kind, len);
+       if (reclen)
+       {
+               htoun32(pkt + 1, reclen);
+               len += sizeof(u_int32_t);
+               pkt->flags |= EAP_TLS_LENGTH;
+       }
+       len += sizeof(eap_tls_packet_t);
+       htoun16(&pkt->length, len);
+       *out = chunk_clone(chunk_create(buf, len));
+       return NEED_MORE;
+}
+
+/**
+ * Send an ack to request next fragment
+ */
+static chunk_t create_ack(private_tls_eap_t *this, u_int8_t identifier)
+{
+       eap_tls_packet_t pkt = {
+               .code = this->is_server ? EAP_REQUEST : EAP_RESPONSE,
+               .identifier = this->is_server ? identifier + 1 : identifier,
+               .type = this->type,
+       };
+       htoun16(&pkt.length, sizeof(pkt));
+       DBG2(DBG_TLS, "sending %N acknowledgement packet",
+                eap_type_names, this->type);
+       return chunk_clone(chunk_from_thing(pkt));
+}
+
+METHOD(tls_eap_t, process, status_t,
+       private_tls_eap_t *this, chunk_t in, chunk_t *out)
+{
+       eap_tls_packet_t *pkt;
+       status_t status;
+
+       pkt = (eap_tls_packet_t*)in.ptr;
+       if (in.len < sizeof(eap_tls_packet_t) ||
+               untoh16(&pkt->length) != in.len)
+       {
+               DBG1(DBG_IKE, "invalid EAP-TLS packet length");
+               return FAILED;
+       }
+       if (pkt->flags & EAP_TLS_START)
+       {
+               if (this->type == EAP_TTLS)
+               {
+                       DBG1(DBG_TLS, "EAP-TTLS version is v%u",
+                                pkt->flags & EAP_TTLS_VERSION);
+               }
+       }
+       else
+       {
+               if (in.len == sizeof(eap_tls_packet_t))
+               {
+                       DBG2(DBG_TLS, "received %N acknowledgement packet",
+                                eap_type_names, this->type);
+                       status = build_pkt(this, pkt->identifier, out);
+                       if (status == INVALID_STATE &&
+                               this->tls->is_complete(this->tls))
+                       {
+                               return SUCCESS;
+                       }
+                       return status;
+               }
+               status = process_pkt(this, pkt);
+               if (status != NEED_MORE)
+               {
+                       return status;
+               }
+       }
+       status = build_pkt(this, pkt->identifier, out);
+       switch (status)
+       {
+               case INVALID_STATE:
+                       *out = create_ack(this, pkt->identifier);
+                       return NEED_MORE;
+               case FAILED:
+                       if (!this->is_server)
+                       {
+                               *out = create_ack(this, pkt->identifier);
+                               return NEED_MORE;
+                       }
+                       return FAILED;
+               default:
+                       return status;
+       }
+}
+
+METHOD(tls_eap_t, get_msk, chunk_t,
+       private_tls_eap_t *this)
+{
+       return this->tls->get_eap_msk(this->tls);
+}
+
+METHOD(tls_eap_t, destroy, void,
+       private_tls_eap_t *this)
+{
+       this->tls->destroy(this->tls);
+       free(this);
+}
+
+/**
+ * See header
+ */
+tls_eap_t *tls_eap_create(eap_type_t type, bool is_server,
+                                                 identification_t *server, identification_t *peer,
+                                                 tls_application_t *application)
+{
+       private_tls_eap_t *this;
+       tls_purpose_t purpose;
+
+       switch (type)
+       {
+               case EAP_TLS:
+                       purpose = TLS_PURPOSE_EAP_TLS;
+                       break;
+               case EAP_TTLS:
+                       purpose = TLS_PURPOSE_EAP_TTLS;
+                       break;
+               default:
+                       return NULL;
+       };
+
+       INIT(this,
+               .public = {
+                       .initiate = _initiate,
+                       .process = _process,
+                       .get_msk = _get_msk,
+                       .destroy = _destroy,
+               },
+               .type = type,
+               .is_server = is_server,
+               .first_fragment = TRUE,
+               .tls = tls_create(is_server, server, peer, purpose, application),
+       );
+       if (!this->tls)
+       {
+               free(this);
+               return NULL;
+       }
+       return &this->public;
+}
diff --git a/src/libtls/tls_eap.h b/src/libtls/tls_eap.h
new file mode 100644 (file)
index 0000000..48b4dec
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+/**
+ * @defgroup tls_eap tls_eap
+ * @{ @ingroup libtls
+ */
+
+#ifndef TLS_EAP_H_
+#define TLS_EAP_H_
+
+typedef struct tls_eap_t tls_eap_t;
+
+#include <eap/eap.h>
+
+#include "tls_application.h"
+
+/**
+ * TLS over EAP helper, as used by EAP-TLS and EAP-TTLS.
+ */
+struct tls_eap_t {
+
+       /**
+        * Initiate TLS over EAP exchange (as client).
+        *
+        * @param out                   allocated EAP packet data to send
+        * @return
+        *                                              - NEED_MORE if more exchanges required
+        *                                              - FAILED if initiation failed
+        */
+       status_t (*initiate)(tls_eap_t *this, chunk_t *out);
+
+       /**
+        * Process a received EAP-TLS/TTLS packet, create response.
+        *
+        * @param in                    EAP packet data to process
+        * @param out                   allocated EAP packet data to send
+        * @return
+        *                                              - SUCCESS if TLS negotiation completed
+        *                                              - FAILED if TLS negotiation failed
+        *                                              - NEED_MORE if more exchanges required
+        */
+       status_t (*process)(tls_eap_t *this, chunk_t in, chunk_t *out);
+
+       /**
+        * Get the EAP-MSK.
+        *
+        * @return                              MSK
+        */
+       chunk_t (*get_msk)(tls_eap_t *this);
+
+       /**
+        * Destroy a tls_eap_t.
+        */
+       void (*destroy)(tls_eap_t *this);
+};
+
+/**
+ * Create a tls_eap instance.
+ *
+ * @param type                         EAP type, EAP-TLS or EAP-TTLS
+ * @param is_server                    role
+ * @param server                       server identity
+ * @param peer                         peer identity, NULL to omit peer authentication
+ * @param application          TLS application layer, if any
+ */
+tls_eap_t *tls_eap_create(eap_type_t type, bool is_server,
+                                                 identification_t *server, identification_t *peer,
+                                                 tls_application_t *application);
+
+#endif /** TLS_EAP_H_ @}*/