]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
Refactored PEAP server, supporting handling EAP-MS-SOH
authorMartin Willi <martin@revosec.ch>
Mon, 3 Sep 2012 14:12:57 +0000 (16:12 +0200)
committerMartin Willi <martin@revosec.ch>
Tue, 4 Sep 2012 09:45:33 +0000 (11:45 +0200)
src/libcharon/plugins/eap_peap/eap_peap_server.c

index d6ae65d68a7e96d12fd01d4491d13817b5dae1c0..2ef2994b3ffaa2833296f01d1bcac9a03db7ca16 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_server.h"
-#include "eap_peap_avp.h"
+#include "eap_peap.h"
 
 #include <debug.h>
 #include <daemon.h>
@@ -42,354 +44,466 @@ struct private_eap_peap_server_t {
        identification_t *peer;
 
        /**
-        * Current EAP-PEAP phase2 state
+        * Do EAP Identity authentication exchange?
         */
-       bool start_phase2;
+       bool identity;
 
        /**
-        * Current EAP-PEAP phase2 TNC state
+        * Use Microsoft Statement of Health EAP exchange?
         */
-       bool start_phase2_tnc;
+       bool soh;
 
        /**
-        * Starts phase 2 with EAP Identity request
+        * TLS exchange completed?
         */
-       bool start_phase2_id;
+       bool tls_completed;
 
        /**
-        * Final EAP-PEAP phase2 result
+        * Result TLV sent?
         */
-       eap_code_t phase2_result;
+       bool result_sent;
+
+       /**
+        * EAP-PEAP phase2 authentication result
+        */
+       status_t state;
 
        /**
         * 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;
 };
 
 /**
- * Start EAP client authentication protocol
+ * Process a TLV request
  */
-static status_t start_phase2_auth(private_eap_peap_server_t *this)
+static status_t process_tlv(private_eap_peap_server_t *this,
+                                                       bio_reader_t *reader)
 {
-       char *eap_type_str;
-       u_int32_t vendor;
-       eap_type_t type;
+       u_int16_t type, length, result;
+       chunk_t value;
+       bool mandatory;
+       status_t status = FAILED;
 
-       eap_type_str = lib->settings->get_str(lib->settings,
-                                                       "%s.plugins.eap-peap.phase2_method", "mschapv2",
-                                                       charon->name);
-       type = eap_type_from_string(eap_type_str, &vendor);
-       if (type == 0)
-       {
-               DBG1(DBG_IKE, "unrecognized phase2 method \"%s\"", eap_type_str);
-               return FAILED;
-       }
-       DBG1(DBG_IKE, "phase2 method %M selected", eap_type_get_names, &vendor, type);
-               this->ph2_method = charon->eap->create_instance(charon->eap, type,
-                                                               vendor, EAP_SERVER, this->server, this->peer);
-       if (this->ph2_method == NULL)
+       while (reader->remaining(reader))
        {
-               DBG1(DBG_IKE, "%M method not available",
-                        eap_type_get_names, &vendor, type);
+               if (!reader->read_uint16(reader, &type) ||
+                       !reader->read_uint16(reader, &length))
+               {
+                       return FAILED;
+               }
+               mandatory = type | MSTLV_MANDATORY;
+               type &= ~MSTLV_MANDATORY;
+               switch (type)
+               {
+                       case MSTLV_RESULT:
+                               if (length == sizeof(result) &&
+                                       reader->read_uint16(reader, &result) &&
+                                       result == MSTLV_RESULT_SUCCESS &&
+                                       this->state == SUCCESS)
+                               {
+                                       status = SUCCESS;
+                                       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;
+               }
                return FAILED;
        }
+       return status;
+}
 
-       /* synchronize EAP message identifiers of inner protocol with outer */
-       this->ph2_method->set_identifier(this->ph2_method,
-                                               this->ph1_method->get_identifier(this->ph1_method) + 1);
+/**
+ * Process a full EAP packet, EAP_MSTLV or EAP_EXPANDED
+ */
+static status_t process_eap_with_header(private_eap_peap_server_t *this,
+                                                       bio_reader_t *reader, u_int8_t code, u_int32_t type)
+{
+       u_int32_t vendor;
 
-       if (this->ph2_method->initiate(this->ph2_method, &this->out) == NEED_MORE)
+       if (type != EAP_EXPANDED)
        {
-               return NEED_MORE;
+               DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N/%N ]",
+                        eap_code_short_names, code, eap_type_short_names, type);
        }
-       else
+       switch (type)
        {
-               DBG1(DBG_IKE, "%M method failed", eap_type_get_names, &vendor, type);
-               return FAILED;
+               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, "parsing PEAP inner expanded EAP header failed");
+                               return FAILED;
+                       }
+                       DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N/%M ]",
+                                eap_code_short_names, code,
+                                eap_type_get_names, &vendor, type);
+                       /* TODO: process requested capabilities? */
+                       break;
+               default:
+                       break;
        }
+       DBG1(DBG_IKE, "unsupported PEAP payload");
+       return FAILED;
 }
 
 /**
- * If configured, start EAP-TNC protocol
+ * Process EAP-Identity response
  */
-static status_t start_phase2_tnc(private_eap_peap_server_t *this)
+static status_t process_identity(private_eap_peap_server_t *this,
+                                                                eap_payload_t *in)
 {
-       if (this->start_phase2_tnc && lib->settings->get_bool(lib->settings,
-                                               "%s.plugins.eap-peap.phase2_tnc", FALSE, charon->name))
+       eap_payload_t *out;
+       chunk_t id;
+
+       switch (this->ph2->process(this->ph2, in, &out))
        {
-               DBG1(DBG_IKE, "phase2 method %N selected", eap_type_names, EAP_TNC);
-               this->ph2_method = charon->eap->create_instance(charon->eap, EAP_TNC,
-                                                                       0, EAP_SERVER, this->server, this->peer);
-               if (this->ph2_method == NULL)
-               {
-                       DBG1(DBG_IKE, "%N method not available", eap_type_names, EAP_TNC);
+               case SUCCESS:
+                       break;
+               case NEED_MORE:
+                       /* not expected */
+                       out->destroy(out);
                        return FAILED;
-               }
-               this->start_phase2_tnc = FALSE;
-
-               /* synchronize EAP message identifiers of inner protocol with outer */
-               this->ph2_method->set_identifier(this->ph2_method,
-                                               this->ph1_method->get_identifier(this->ph1_method) + 1);
-
-               if (this->ph2_method->initiate(this->ph2_method, &this->out) == NEED_MORE)
-               {
-                       return NEED_MORE;
-               }
-               else
-               {
-                       DBG1(DBG_IKE, "%N method failed", eap_type_names, EAP_TNC);
+               default:
                        return FAILED;
-               }
        }
-       return SUCCESS;
+
+       if (this->ph2->get_msk(this->ph2, &id) == SUCCESS)
+       {
+               this->peer->destroy(this->peer);
+               this->peer = identification_create_from_data(id);
+               DBG1(DBG_IKE, "received tunneled EAP identity '%Y'", this->peer);
+       }
+       in->destroy(in);
+       this->ph2->destroy(this->ph2);
+       this->ph2 = NULL;
+       this->identity = FALSE;
+       return NEED_MORE;
 }
 
-METHOD(tls_application_t, process, status_t,
-       private_eap_peap_server_t *this, bio_reader_t *reader)
+/**
+ * Process Statement of Health response
+ */
+static status_t process_soh(private_eap_peap_server_t *this, eap_payload_t *in)
 {
-       chunk_t data = chunk_empty;
-       status_t status;
-       payload_t *payload;
-       eap_payload_t *in;
-       eap_code_t code;
-       eap_type_t type = EAP_NAK, received_type;
-       u_int32_t vendor, received_vendor;
+       eap_type_t type;
+       u_int32_t vendor;
 
-       status = this->avp->process(this->avp, reader, &data,
-                                                       this->ph1_method->get_identifier(this->ph1_method));
-       switch (status)
+       type = this->ph2->get_type(this->ph2, &vendor);
+       switch (this->ph2->process(this->ph2, in, &this->out))
        {
                case SUCCESS:
+                       DBG1(DBG_IKE, "%N SoH exchange successful", eap_type_names,
+                                EAP_PEAP, this->peer, eap_type_get_names, &vendor, type);
+                       this->ph2->destroy(this->ph2);
+                       this->ph2 = NULL;
                        break;
                case NEED_MORE:
-                       return NEED_MORE;
+                       break;
                case FAILED:
                default:
-                       return FAILED;
+                       DBG1(DBG_IKE, "EAP-%M method failed",
+                                eap_type_get_names, &vendor, type);
+                       break;
        }
+       in->destroy(in);
+       this->soh = FALSE;
+       return NEED_MORE;
+}
 
-       in = eap_payload_create_data(data);
-       DBG3(DBG_IKE, "%B", &data);
-       chunk_free(&data);
-       payload = (payload_t*)in;
+/**
+ * Process EAP authentication method response
+ */
+static status_t process_auth(private_eap_peap_server_t *this, eap_payload_t *in)
+{
+       eap_type_t type;
+       u_int32_t vendor;
 
+       type = this->ph2->get_type(this->ph2, &vendor);
+       switch (this->ph2->process(this->ph2, in, &this->out))
+       {
+               case SUCCESS:
+                       DBG1(DBG_IKE, "%N phase2 authentication of '%Y' with %M successful",
+                                eap_type_names, EAP_PEAP, this->peer,
+                                eap_type_get_names, &vendor, type);
+                       this->ph2->destroy(this->ph2);
+                       this->ph2 = NULL;
+                       this->state = SUCCESS;
+                       break;
+               case NEED_MORE:
+                       break;
+               case FAILED:
+               default:
+                       DBG1(DBG_IKE, "EAP-%M method failed",
+                                eap_type_get_names, &vendor, type);
+                       this->state = FAILED;
+                       break;
+       }
+       in->destroy(in);
+       return NEED_MORE;
+}
+
+/**
+ * Construct an EAP header and append data
+ */
+static eap_payload_t *construct_eap(private_eap_peap_server_t *this,
+                                                                       chunk_t data)
+{
+       payload_t *payload;
+       eap_payload_t *eap;
+       eap_hdr_t hdr = {
+               .code = EAP_RESPONSE,
+               .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;
+}
 
-       code = in->get_code(in);
-       if (code == EAP_REQUEST || code == EAP_RESPONSE)
+METHOD(tls_application_t, process, status_t,
+       private_eap_peap_server_t *this, bio_reader_t *reader)
+{
+       u_int8_t code, identifier, type;
+       u_int16_t length;
+       u_int32_t vendor;
+       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)
        {
-               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_RESPONSE)
-               {
-                       DBG1(DBG_IKE, "%N expected", eap_code_names, EAP_RESPONSE);
-                       in->destroy(in);
-                       return FAILED;
-               }
+               return NEED_MORE;
        }
-       else
+       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_RESPONSE &&
+               identifier == this->ph1->get_identifier(this->ph1))
        {
-               DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N ]",
-                                                               eap_code_short_names, code);
-
-               /* if EAP_SUCCESS check if to continue phase2 with EAP-TNC */
-               return (this->phase2_result == EAP_SUCCESS && code == EAP_SUCCESS) ?
-                          start_phase2_tnc(this) : FAILED;
+               return process_eap_with_header(this, reader, code, type);
        }
 
-       if (this->ph2_method)
+       if (chunk.ptr[0] == EAP_NAK)
        {
-               type = this->ph2_method->get_type(this->ph2_method, &vendor);
-
-               if (type != received_type || vendor != received_vendor)
-               {
-                       if (received_vendor == 0 && received_type == EAP_NAK)
-                       {
-                               DBG1(DBG_IKE, "peer does not support %N", eap_type_names, type);
-                       }
-                       else
-                       {
-                               DBG1(DBG_IKE, "received invalid EAP response");
-                       }
-                       in->destroy(in);
-                       return FAILED;
-               }
+               DBG1(DBG_IKE, "received EAP-NAK within EAP-PEAP, aborting");
+               return FAILED;
        }
-
-       if (!received_vendor && received_type == EAP_IDENTITY)
+       if (!this->ph2)
        {
-               chunk_t eap_id;
-
-               if (this->ph2_method == NULL)
-               {
-                       /* Received an EAP Identity response without a matching request */
-                       this->ph2_method = charon->eap->create_instance(charon->eap,
-                                                                                        EAP_IDENTITY, 0, EAP_SERVER,
-                                                                                        this->server, this->peer);
-                       if (this->ph2_method == NULL)
-                       {
-                               DBG1(DBG_IKE, "%N method not available",
-                                                          eap_type_names, EAP_IDENTITY);
-                               return FAILED;
-                       }
-               }
+               return FAILED;
+       }
+       in = construct_eap(this, chunk);
+       /* consume peeked reader bytes */
+       reader->read_data(reader, reader->remaining(reader), &chunk);
+       if (!in)
+       {
+               return FAILED;
+       }
 
-               if (this->ph2_method->process(this->ph2_method, in, &this->out) != SUCCESS)
-               {
+       type = in->get_type(in, &vendor);
+       DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [ EAP/%N/%M ]",
+                eap_code_short_names, EAP_RESPONSE, eap_type_get_names, &vendor, type);
 
-                       DBG1(DBG_IKE, "%N method failed", eap_type_names, EAP_IDENTITY);
-                       return FAILED;
-               }
+       if (this->identity)
+       {
+               return process_identity(this, in);
+       }
+       if (this->soh)
+       {
+               return process_soh(this, in);
+       }
+       return process_auth(this, in);
+}
 
-               if (this->ph2_method->get_msk(this->ph2_method, &eap_id) == SUCCESS)
-               {
-                       this->peer->destroy(this->peer);
-                       this->peer = identification_create_from_data(eap_id);
-                       DBG1(DBG_IKE, "received EAP identity '%Y'", this->peer);
-               }
+/**
+ * Build result TLV
+ */
+static status_t build_result(private_eap_peap_server_t *this,
+                                                        bio_writer_t *writer, eap_mstlv_result_t result)
+{
+       writer->write_uint8(writer, EAP_REQUEST);
+       writer->write_uint8(writer, this->ph1->get_identifier(this->ph1));
+       /* write complete EAP packet length */
+       writer->write_uint16(writer, 11);
+       writer->write_uint8(writer, EAP_MSTLV);
+       /* TLV type: Result */
+       writer->write_uint16(writer, MSTLV_RESULT | MSTLV_MANDATORY);
+       /* TLV length */
+       writer->write_uint16(writer, 2);
+       writer->write_uint16(writer, result);
+
+       DBG1(DBG_IKE, "sending tunneled EAP-PEAP AVP [ EAP/%N/%N ]",
+                eap_code_short_names, EAP_REQUEST, eap_type_short_names, EAP_MSTLV);
+
+       this->result_sent = TRUE;
+
+       return NEED_MORE;
+}
 
-               in->destroy(in);
-               this->ph2_method->destroy(this->ph2_method);
-               this->ph2_method = NULL;
+/**
+ * Write stored EAP payload to writer
+ */
+static status_t build_eap(private_eap_peap_server_t *this, bio_writer_t *writer)
+{
+       u_int32_t type, vendor;
+       chunk_t data;
 
-               /* Start Phase 2 of EAP-PEAP authentication */
-               if (lib->settings->get_bool(lib->settings,
-                               "%s.plugins.eap-peap.request_peer_auth", FALSE, charon->name))
-               {
-                       return start_phase2_tnc(this);
-               }
-               else
-               {
-                       return start_phase2_auth(this);
-               }
+       if (!this->out)
+       {
+               return INVALID_STATE;
        }
+       type = this->out->get_type(this->out, &vendor);
+       DBG1(DBG_IKE, "sending tunneled EAP-PEAP AVP [ EAP/%N/%M ]",
+                eap_code_short_names, this->out->get_code(this->out),
+                eap_type_get_names, &vendor, type);
+
+       data = this->out->get_data(this->out);
 
-       if (this->ph2_method == 0)
+       if (!(vendor == 0 && type == EAP_MSTLV) &&
+               !(vendor == PEN_MICROSOFT && type == EAP_MS_CAPABILITES))
        {
-               DBG1(DBG_IKE, "no %N phase2 method installed", eap_type_names, EAP_PEAP);
-               in->destroy(in);
-               return FAILED;
+               /* remove EAP header for compressed types */
+               data = chunk_skip(data, sizeof(eap_hdr_t));
        }
+       writer->write_data(writer, data);
 
-       status = this->ph2_method->process(this->ph2_method, in, &this->out);
-       in->destroy(in);
+       this->out->destroy(this->out);
+       this->out = NULL;
 
-       switch (status)
+       return NEED_MORE;
+}
+
+/**
+ * Initiate an arbitrary EAP method
+ */
+static status_t initiate_eap(private_eap_peap_server_t *this, eap_type_t type,
+                                                        u_int32_t vendor, bio_writer_t *writer)
+{
+       this->ph2 = charon->eap->create_instance(charon->eap, type,
+                                                               vendor, EAP_SERVER, this->server, this->peer);
+       if (!this->ph2)
        {
-               case SUCCESS:
-                       DBG1(DBG_IKE, "%N phase2 authentication of '%Y' with %N successful",
-                                                       eap_type_names, EAP_PEAP, this->peer,
-                                                       eap_type_names, type);
-                       this->ph2_method->destroy(this->ph2_method);
-                       this->ph2_method = NULL;
-
-                       /* EAP-PEAP requires the sending of an inner EAP_SUCCESS message */
-                       this->phase2_result = EAP_SUCCESS;
-                       this->out = eap_payload_create_code(this->phase2_result, 1 +
-                                                       this->ph1_method->get_identifier(this->ph1_method));
-                       return NEED_MORE;
-               case NEED_MORE:
-                       break;
-               case FAILED:
-               default:
-                       if (vendor)
-                       {
-                               DBG1(DBG_IKE, "vendor specific EAP method %d-%d failed",
-                                                          type, vendor);
-                       }
-                       else
-                       {
-                               DBG1(DBG_IKE, "%N method failed", eap_type_names, type);
-                       }
-                       /* EAP-PEAP requires the sending of an inner EAP_FAILURE message */
-                       this->phase2_result = EAP_FAILURE;
-                       this->out = eap_payload_create_code(this->phase2_result, 1 +
-                                                       this->ph1_method->get_identifier(this->ph1_method));
-                       return NEED_MORE;
+               DBG1(DBG_IKE, "EAP-%M method not available",
+                        eap_type_get_names, &vendor, type);
+               return FAILED;
        }
-       return status;
+       this->ph2->set_identifier(this->ph2, this->ph1->get_identifier(this->ph1));
+       if (this->ph2->initiate(this->ph2, &this->out) != NEED_MORE)
+       {
+               DBG1(DBG_IKE, "initiating %M within PEAP failed",
+                        eap_type_get_names, &vendor, type);
+               return FAILED;
+       }
+       return build_eap(this, writer);
 }
 
-METHOD(tls_application_t, build, status_t,
-       private_eap_peap_server_t *this, bio_writer_t *writer)
+/**
+ * Initiate inner authentication method
+ */
+static status_t initiate_auth(private_eap_peap_server_t *this,
+                                                         bio_writer_t *writer)
 {
-       chunk_t data;
-       eap_code_t code;
        eap_type_t type;
        u_int32_t vendor;
+       char *str;
 
-       if (this->ph2_method == NULL && this->start_phase2 && this->start_phase2_id)
+       str = lib->settings->get_str(lib->settings,
+                                                                "%s.plugins.eap-peap.ph2_method", "mschapv2",
+                                                                charon->name);
+       type = eap_type_from_string(str, &vendor);
+       if (!type)
        {
-               /*
-                * Start Phase 2 with an EAP Identity request either piggybacked right
-                * onto the TLS Finished payload or delayed after the reception of an
-                * empty EAP Acknowledge message.
-                */
-               this->ph2_method = charon->eap->create_instance(charon->eap, EAP_IDENTITY,
-                                                                0,     EAP_SERVER, this->server, this->peer);
-               if (this->ph2_method == NULL)
-               {
-                       DBG1(DBG_IKE, "%N method not available",
-                                eap_type_names, EAP_IDENTITY);
-                       return FAILED;
-               }
-
-               /* synchronize EAP message identifiers of inner protocol with outer */
-               this->ph2_method->set_identifier(this->ph2_method,
-                                       this->ph1_method->get_identifier(this->ph1_method));
-
-               this->ph2_method->initiate(this->ph2_method, &this->out);
-               this->start_phase2 = FALSE;
+               DBG1(DBG_IKE, "unknown EAP method: %s", str);
+               return FAILED;
        }
+       DBG1(DBG_IKE, "initiating %N inner authentication with EAP-%M",
+                eap_type_names, EAP_PEAP, eap_type_get_names, &vendor, type);
+       return initiate_eap(this, type, vendor, writer);
+}
 
-       this->start_phase2_id = TRUE;
-
-       if (this->out)
+METHOD(tls_application_t, build, status_t,
+       private_eap_peap_server_t *this, bio_writer_t *writer)
+{
+       if (!this->tls_completed)
        {
-               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);
-               }
-
-               /* 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);
+               /* don't piggyback application data to TLS handshake */
+               this->tls_completed = TRUE;
+               return INVALID_STATE;
+       }
 
-               this->out->destroy(this->out);
-               this->out = NULL;
+       switch (this->state)
+       {
+               case NEED_MORE:
+                       if (this->ph2)
+                       {
+                               return build_eap(this, writer);
+                       }
+                       if (this->identity)
+                       {
+                               return initiate_eap(this, EAP_IDENTITY, 0, writer);
+                       }
+                       if (this->soh)
+                       {
+                               return initiate_eap(this, EAP_MS_SOH, PEN_MICROSOFT, writer);
+                       }
+                       return initiate_auth(this, writer);
+               case SUCCESS:
+                       if (this->result_sent)
+                       {
+                               return INVALID_STATE;
+                       }
+                       return build_result(this, writer, MSTLV_RESULT_SUCCESS);
+               case FAILED:
+                       if (this->result_sent)
+                       {
+                               return INVALID_STATE;
+                       }
+                       return build_result(this, writer, MSTLV_RESULT_FAILURE);
+               default:
+                       return FAILED;
        }
-       return INVALID_STATE;
 }
 
 METHOD(tls_application_t, destroy, void,
@@ -397,9 +511,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);
 }
 
@@ -422,14 +535,12 @@ eap_peap_server_t *eap_peap_server_create(identification_t *server,
                },
                .server = server->clone(server),
                .peer = peer->clone(peer),
-               .ph1_method = eap_method,
-               .start_phase2 = TRUE,
-               .start_phase2_tnc = TRUE,
-               .start_phase2_id = lib->settings->get_bool(lib->settings,
-                                                                               "%s.plugins.eap-peap.phase2_piggyback",
-                                                                               FALSE, charon->name),
-               .phase2_result = EAP_FAILURE,
-               .avp = eap_peap_avp_create(TRUE),
+               .ph1 = eap_method,
+               .state = NEED_MORE,
+               .identity = lib->settings->get_bool(lib->settings,
+                                                       "%s.plugins.eap-peap.identity", TRUE, charon->name),
+               .soh = lib->settings->get_bool(lib->settings,
+                                                       "%s.plugins.eap-peap.soh", FALSE, charon->name),
        );
 
        return &this->public;