From: Martin Willi Date: Mon, 3 Sep 2012 14:12:57 +0000 (+0200) Subject: Refactored PEAP server, supporting handling EAP-MS-SOH X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=765cb51261d24476fb1cd637ad1dda19cdd76cec;p=thirdparty%2Fstrongswan.git Refactored PEAP server, supporting handling EAP-MS-SOH --- diff --git a/src/libcharon/plugins/eap_peap/eap_peap_server.c b/src/libcharon/plugins/eap_peap/eap_peap_server.c index d6ae65d68a..2ef2994b3f 100644 --- a/src/libcharon/plugins/eap_peap/eap_peap_server.c +++ b/src/libcharon/plugins/eap_peap/eap_peap_server.c @@ -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 #include @@ -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;