]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
Refactored PEAP client processing, handle Microsoft quirks
authorMartin Willi <martin@revosec.ch>
Thu, 30 Aug 2012 09:49:37 +0000 (11:49 +0200)
committerMartin Willi <martin@revosec.ch>
Mon, 3 Sep 2012 14:13:57 +0000 (16:13 +0200)
src/libcharon/plugins/eap_peap/eap_peap.h
src/libcharon/plugins/eap_peap/eap_peap_peer.c

index 2756ad3e6659167bc77ab4b4f3decb9aaeb3f981..c0456b1da1ebbd8eceef0973288d0a53c1163799 100644 (file)
 #define EAP_PEAP_H_
 
 typedef struct eap_peap_t eap_peap_t;
+typedef struct eap_hdr_t eap_hdr_t;
+typedef enum eap_mstlv_type_t eap_mstlv_type_t;
+typedef enum eap_mstlv_result_t eap_mstlv_result_t;
 
 #include <sa/eap/eap_method.h>
 
+/**
+ * 4 byte EAP header, without type field
+ */
+struct eap_hdr_t {
+       u_int8_t code;
+       u_int8_t identifier;
+       u_int16_t length;
+};
+
+/**
+ * TLV types within EAP_MSTLV
+ */
+enum eap_mstlv_type_t {
+       MSTLV_SOH = 1,
+       MSTLV_SOH_REQUEST = 2,
+       MSTLV_RESULT = 3,
+       MSTLV_VENDOR = 7,
+       MSTLV_CRYPTO_BINDING = 12,
+       /* not a type, but a flag to apply to types */
+       MSTLV_MANDATORY = 0x8000,
+};
+
+/**
+ * TLV result options
+ */
+enum eap_mstlv_result_t {
+       MSTLV_RESULT_SUCCESS = 1,
+       MSTLV_RESULT_FAILURE = 2,
+};
+
 /**
  * Implementation of eap_method_t using EAP-PEAP.
  */
index 1cee90dd6eeceac6d38966dc39509069c38309c3..84a877f11eb71d0b3046d72301f27267a1948c4d 100644 (file)
@@ -1,6 +1,8 @@
 /*
  * Copyright (C) 2011 Andreas Steffen
- * Copyright (C) 2011 HSR Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
+ * 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
@@ -14,7 +16,7 @@
  */
 
 #include "eap_peap_peer.h"
-#include "eap_peap_avp.h"
+#include "eap_peap.h"
 
 #include <debug.h>
 #include <daemon.h>
@@ -44,110 +46,80 @@ struct private_eap_peap_peer_t {
        /**
         * Outer phase 1 EAP method
         */
-       eap_method_t *ph1_method;
+       eap_method_t *ph1;
 
        /**
         * Current phase 2 EAP method
         */
-       eap_method_t *ph2_method;
+       eap_method_t *ph2;
 
        /**
         * Pending outbound EAP message
         */
        eap_payload_t *out;
-
-       /**
-        * AVP handler
-        */
-       eap_peap_avp_t *avp;
 };
 
-METHOD(tls_application_t, process, status_t,
-       private_eap_peap_peer_t *this, bio_reader_t *reader)
+/**
+ * Construct an EAP header and append data
+ */
+static eap_payload_t *construct_eap(private_eap_peap_peer_t *this, chunk_t data)
 {
-       chunk_t data = chunk_empty;
-       status_t status;
        payload_t *payload;
-       eap_payload_t *in;
-       eap_code_t code;
-       eap_type_t type, received_type;
-       u_int32_t vendor, received_vendor;
-
-       status = this->avp->process(this->avp, reader, &data,
-                                                       this->ph1_method->get_identifier(this->ph1_method));
-       switch (status)
-       {
-               case SUCCESS:
-                       break;
-               case NEED_MORE:
-                       return NEED_MORE;
-               case FAILED:
-               default:
-                       return FAILED;
-       }
-
-       in = eap_payload_create_data(data);
-       DBG3(DBG_IKE, "%B", &data);
-       chunk_free(&data);
-       payload = (payload_t*)in;
+       eap_payload_t *eap;
+       eap_hdr_t hdr = {
+               .code = EAP_REQUEST,
+               .identifier = this->ph1->get_identifier(this->ph1),
+               .length = ntohs(data.len + sizeof(hdr)),
+       };
 
+       data = chunk_cat("cc", chunk_from_thing(hdr), data);
+       eap = eap_payload_create_data_own(data);
+       payload = &eap->payload_interface;
        if (payload->verify(payload) != SUCCESS)
        {
-               in->destroy(in);
-               return FAILED;
+               eap->destroy(eap);
+               return NULL;
        }
+       return eap;
+}
+
+/**
+ * Process an authentication EAP method
+ */
+static status_t process_phase2(private_eap_peap_peer_t *this, eap_payload_t *in)
+{
+       status_t status;
+       eap_code_t code;
+       eap_type_t type, received_type;
+       u_int32_t vendor, received_vendor;
 
        code = in->get_code(in);
-       if (code == EAP_REQUEST || code == EAP_RESPONSE)
-       {
-               received_type = in->get_type(in, &received_vendor);
-               DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N/%N ]",
-                        eap_code_short_names, code, eap_type_short_names, received_type);
-               if (code != EAP_REQUEST)
-               {
-                       DBG1(DBG_IKE, "%N expected", eap_code_names, EAP_REQUEST);
-                       in->destroy(in);
-                       return FAILED;
-               }
-       }
-       else
-       {
-               DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N ]",
-                        eap_code_short_names, code);
-               this->out = eap_payload_create_code(code, in->get_identifier(in));
-               in->destroy(in);
-               return NEED_MORE;
-       }
+       received_type = in->get_type(in, &received_vendor);
+       DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N/%N ]",
+                eap_code_short_names, code,
+                eap_type_get_names(received_vendor), received_type);
 
        /* yet another phase2 authentication? */
-       if (this->ph2_method)
+       if (this->ph2)
        {
-               type = this->ph2_method->get_type(this->ph2_method, &vendor);
+               type = this->ph2->get_type(this->ph2, &vendor);
 
                if (type != received_type || vendor != received_vendor)
                {
-                       this->ph2_method->destroy(this->ph2_method);
-                       this->ph2_method = NULL;
+                       this->ph2->destroy(this->ph2);
+                       this->ph2 = NULL;
                }
        }
 
-       if (this->ph2_method == NULL)
+       if (this->ph2 == NULL)
        {
-               if (received_vendor)
-               {
-                       DBG1(DBG_IKE, "server requested vendor specific EAP method %d-%d "
-                                "(id 0x%02X", received_type, received_vendor,
-                                in->get_identifier(in));
-               }
-               else
-               {
-                       DBG1(DBG_IKE, "server requested %N authentication (id 0x%02X)",
-                                eap_type_names, received_type, in->get_identifier(in));
-               }
-               this->ph2_method = charon->eap->create_instance(charon->eap,
+               DBG1(DBG_IKE, "server requested EAP method %N (id 0x%02X)",
+                        eap_type_get_names(received_vendor), received_type,
+                        in->get_identifier(in));
+               this->ph2 = charon->eap->create_instance(charon->eap,
                                                                        received_type, received_vendor,
                                                                        EAP_PEER, this->server, this->peer);
-               if (!this->ph2_method)
+               if (!this->ph2)
                {
                        DBG1(DBG_IKE, "EAP method not supported");
                        this->out = eap_payload_create_nak(in->get_identifier(in), 0, 0,
@@ -155,65 +127,235 @@ METHOD(tls_application_t, process, status_t,
                        in->destroy(in);
                        return NEED_MORE;
                }
-               type = this->ph2_method->get_type(this->ph2_method, &vendor);
+               type = this->ph2->get_type(this->ph2, &vendor);
        }
 
-       status = this->ph2_method->process(this->ph2_method, in, &this->out);
+       status = this->ph2->process(this->ph2, in, &this->out);
        in->destroy(in);
 
        switch (status)
        {
                case SUCCESS:
-                       this->ph2_method->destroy(this->ph2_method);
-                       this->ph2_method = NULL;
+                       this->ph2->destroy(this->ph2);
+                       this->ph2 = NULL;
                        /* fall through to NEED_MORE */
                case NEED_MORE:
                        return NEED_MORE;
                case FAILED:
                default:
-                       if (vendor)
+                       DBG1(DBG_IKE, "EAP-%N failed", eap_type_get_names(vendor), type);
+                       return FAILED;
+       }
+}
+
+/**
+ * Create an EAP payload from a buffered writer, prepend header
+ */
+static eap_payload_t *create_eap_from_writer(private_eap_peap_peer_t *this,
+                                                                                        bio_writer_t *writer)
+{
+       chunk_t data;
+       eap_hdr_t hdr = {
+               .code = EAP_RESPONSE,
+               .identifier = this->ph1->get_identifier(this->ph1),
+               .length = htons(sizeof(hdr) + writer->get_buf(writer).len),
+       };
+
+       data = chunk_cat("cc", chunk_from_thing(hdr), writer->get_buf(writer));
+       return eap_payload_create_data_own(data);
+}
+
+/**
+ * Process a capabilities request
+ */
+static status_t process_capabilities(private_eap_peap_peer_t *this,
+                                                                        bio_reader_t *reader)
+{      u_int32_t capabilities;
+       bio_writer_t *writer;
+
+       if (!reader->read_uint32(reader, &capabilities))
+       {
+               return FAILED;
+       }
+
+       writer = bio_writer_create(16);
+       writer->write_uint8(writer, EAP_EXPANDED);
+       writer->write_uint24(writer, PEN_MICROSOFT);
+       writer->write_uint32(writer, EAP_MS_CAPABILITES);
+       writer->write_uint32(writer, 0);
+
+       this->out = create_eap_from_writer(this, writer);
+       writer->destroy(writer);
+
+       return NEED_MORE;
+}
+
+/**
+ * Process a TLV request
+ */
+static status_t process_tlv(private_eap_peap_peer_t *this, bio_reader_t *reader)
+{
+       bio_writer_t *writer;
+       u_int16_t type, length, result;
+       chunk_t value;
+       bool mandatory;
+
+       writer = bio_writer_create(32);
+       writer->write_uint8(writer, EAP_MSTLV);
+
+       while (reader->remaining(reader))
+       {
+               if (!reader->read_uint16(reader, &type) ||
+                       !reader->read_uint16(reader, &length))
+               {
+                       writer->destroy(writer);
+                       return FAILED;
+               }
+               mandatory = type | MSTLV_MANDATORY;
+               type &= ~MSTLV_MANDATORY;
+               switch (type)
+               {
+                       case MSTLV_RESULT:
+                               if (length == sizeof(result) &&
+                                       reader->read_uint16(reader, &result))
+                               {
+                                       /* echo back result */
+                                       writer->write_uint16(writer, MSTLV_RESULT | MSTLV_MANDATORY);
+                                       writer->write_uint16(writer, length);
+                                       writer->write_uint16(writer, result);
+                                       continue;
+                               }
+                               break;
+                       case MSTLV_CRYPTO_BINDING:
+                               if (reader->read_data(reader, length, &value))
+                               {
+                                       /* TODO: add crypto binding support */
+                                       continue;
+                               }
+                               break;
+                       case MSTLV_SOH:
+                       case MSTLV_SOH_REQUEST:
+                       case MSTLV_VENDOR:
+                       default:
+                               DBG1(DBG_IKE, "%smandatory PEAP TLV %d",
+                                        mandatory ? "received " : "ignoring non-", type);
+                               if (mandatory || !reader->read_data(reader, length, &value))
+                               {
+                                       break;
+                               }
+                               continue;
+               }
+               writer->destroy(writer);
+               return FAILED;
+       }
+
+       this->out = create_eap_from_writer(this, writer);
+       writer->destroy(writer);
+       return NEED_MORE;
+}
+
+/**
+ * Process a full EAP packet, EAP_MSTLV or EAP_EXPANDED
+ */
+static status_t process_eap_with_header(private_eap_peap_peer_t *this,
+                                                       bio_reader_t *reader, u_int8_t code, u_int32_t type)
+{
+       u_int32_t vendor;
+
+       if (type != EAP_EXPANDED)
+       {
+               DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N/%N ]",
+                        eap_code_short_names, code, eap_type_short_names, type);
+       }
+       switch (type)
+       {
+               case EAP_MSTLV:
+                       return process_tlv(this, reader);
+               case EAP_EXPANDED:
+                       if (!reader->read_uint24(reader, &vendor) ||
+                               !reader->read_uint32(reader, &type))
                        {
-                               DBG1(DBG_IKE, "vendor specific EAP method %d-%d failed",
-                                        type, vendor);
+                               DBG1(DBG_IKE, "parsing PEAP inner expanded EAP header failed");
+                               return FAILED;
                        }
-                       else
+                       DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N/%N ]",
+                                eap_code_short_names, code,
+                                eap_type_get_names(vendor), type);
+                       if (vendor == PEN_MICROSOFT && type == EAP_MS_CAPABILITES)
                        {
-                               DBG1(DBG_IKE, "%N method failed", eap_type_names, type);
+                               return process_capabilities(this, reader);
                        }
-                       return FAILED;
+                       /* no SoH processing here, as it comes with compressed EAP header */
+                       break;
+               default:
+                       break;
        }
+       DBG1(DBG_IKE, "unsupported PEAP payload");
+       return FAILED;
+}
+
+METHOD(tls_application_t, process, status_t,
+       private_eap_peap_peer_t *this, bio_reader_t *reader)
+{
+       u_int8_t code, identifier, type;
+       u_int16_t length;
+       eap_payload_t *in;
+       chunk_t chunk;
+
+       /* EAP_MSTLV and the capabilities EAP_EXPANDED come with a full EAP header,
+        * identity, SoH and the authentication method with a compressed header.
+        * Try to deduce what we got. */
+       chunk = reader->peek(reader);
+
+       if (chunk.len > sizeof(eap_hdr_t) &&
+               reader->read_uint8(reader, &code) &&
+               reader->read_uint8(reader, &identifier) &&
+               reader->read_uint16(reader, &length) &&
+               reader->read_uint8(reader, &type) &&
+               code == EAP_REQUEST &&
+               identifier == this->ph1->get_identifier(this->ph1))
+       {
+               return process_eap_with_header(this, reader, code, type);
+       }
+       in = construct_eap(this, chunk);
+       /* consume peeked reader bytes */
+       reader->read_data(reader, reader->remaining(reader), &chunk);
+       if (!in)
+       {
+               return FAILED;
+       }
+       return process_phase2(this, in);
 }
 
 METHOD(tls_application_t, build, status_t,
        private_eap_peap_peer_t *this, bio_writer_t *writer)
 {
-       chunk_t data;
        eap_code_t code;
-       eap_type_t type;
-       u_int32_t vendor;
+       u_int32_t vendor, type;
+       chunk_t data;
 
        if (this->out)
        {
                code = this->out->get_code(this->out);
                type = this->out->get_type(this->out, &vendor);
-               if (code == EAP_REQUEST || code == EAP_RESPONSE)
-               {
-                       DBG1(DBG_IKE, "sending tunneled EAP-PEAP AVP [ EAP/%N/%N ]",
-                                eap_code_short_names, code, eap_type_short_names, type);
-               }
-               else
-               {
-                       DBG1(DBG_IKE, "sending tunneled EAP-PEAP AVP [ EAP/%N ]",
-                                eap_code_short_names, code);
-               }
+               DBG1(DBG_IKE, "sending tunneled EAP-PEAP AVP [ EAP/%N/%N ]",
+                        eap_code_short_names, code,
+                        eap_type_get_names(vendor), type);
 
-               /* get the raw EAP message data */
                data = this->out->get_data(this->out);
-               DBG3(DBG_IKE, "%B", &data);
-               this->avp->build(this->avp, writer, data);
+
+               if (!(vendor == 0 && type == EAP_MSTLV) &&
+                       !(vendor == PEN_MICROSOFT && type == EAP_MS_CAPABILITES))
+               {
+                       /* remove EAP header for compressed types */
+                       data = chunk_skip(data, sizeof(eap_hdr_t));
+               }
+               writer->write_data(writer, data);
 
                this->out->destroy(this->out);
                this->out = NULL;
+
+               return NEED_MORE;
        }
        return INVALID_STATE;
 }
@@ -223,9 +365,8 @@ METHOD(tls_application_t, destroy, void,
 {
        this->server->destroy(this->server);
        this->peer->destroy(this->peer);
-       DESTROY_IF(this->ph2_method);
+       DESTROY_IF(this->ph2);
        DESTROY_IF(this->out);
-       this->avp->destroy(this->avp);
        free(this);
 }
 
@@ -248,8 +389,7 @@ eap_peap_peer_t *eap_peap_peer_create(identification_t *server,
                },
                .server = server->clone(server),
                .peer = peer->clone(peer),
-               .ph1_method = eap_method,
-               .avp = eap_peap_avp_create(FALSE),
+               .ph1 = eap_method,
        );
 
        return &this->public;