(De-)fragment EAP-TLS packets, pass TLS records to upper layer
This commit is contained in:
@@ -44,18 +44,241 @@ struct private_eap_tls_t {
|
||||
* Is this method instance acting as server?
|
||||
*/
|
||||
bool is_server;
|
||||
|
||||
/**
|
||||
* Allocated input buffer
|
||||
*/
|
||||
chunk_t input;
|
||||
|
||||
/**
|
||||
* Number of bytes read in input buffer
|
||||
*/
|
||||
size_t inpos;
|
||||
|
||||
/**
|
||||
* Allocated ouput buffer
|
||||
*/
|
||||
chunk_t output;
|
||||
|
||||
/**
|
||||
* Number of bytes sent from output buffer
|
||||
*/
|
||||
size_t outpos;
|
||||
};
|
||||
|
||||
/** Size limit for a single TLS message */
|
||||
#define MAX_TLS_MESSAGE_LEN 16192
|
||||
/** Size of a EAP-TLS fragment */
|
||||
#define EAP_TLS_FRAGMENT_LEN 1014
|
||||
|
||||
/**
|
||||
* Flags of an EAP-TLS message
|
||||
*/
|
||||
typedef enum {
|
||||
EAP_TLS_LENGTH = (1<<7),
|
||||
EAP_TLS_MORE_FRAGS = (1<<6),
|
||||
EAP_TLS_START = (1<<5),
|
||||
} eap_tls_flags_t;
|
||||
|
||||
/**
|
||||
* EAP-TLS 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;
|
||||
|
||||
/**
|
||||
* TLS record
|
||||
*/
|
||||
typedef struct __attribute__((packed)) {
|
||||
u_int8_t type;
|
||||
u_int16_t version;
|
||||
u_int16_t length;
|
||||
char data[];
|
||||
} tls_record_t;
|
||||
|
||||
METHOD(eap_method_t, initiate, status_t,
|
||||
private_eap_tls_t *this, eap_payload_t **out)
|
||||
{
|
||||
if (this->is_server)
|
||||
{
|
||||
eap_tls_packet_t pkt = {
|
||||
.type = EAP_TLS,
|
||||
.code = EAP_REQUEST,
|
||||
.flags = EAP_TLS_START,
|
||||
};
|
||||
htoun16(&pkt.length, sizeof(eap_tls_packet_t));
|
||||
/* start with non-zero random identifier */
|
||||
do {
|
||||
pkt.identifier = random();
|
||||
} while (!pkt.identifier);
|
||||
|
||||
*out = eap_payload_create_data(chunk_from_thing(pkt));
|
||||
return NEED_MORE;
|
||||
}
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write received TLS data to the input buffer
|
||||
*/
|
||||
static bool write_buf(private_eap_tls_t *this, eap_tls_packet_t *pkt)
|
||||
{
|
||||
u_int32_t msg_len;
|
||||
u_int16_t pkt_len;
|
||||
|
||||
chunk_free(&this->input);
|
||||
pkt_len = untoh16(&pkt->length);
|
||||
|
||||
if (pkt->flags & EAP_TLS_LENGTH)
|
||||
{ /* first fragment */
|
||||
if (pkt_len < sizeof(eap_tls_packet_t) + 4)
|
||||
{
|
||||
DBG1(DBG_IKE, "EAP-TLS packet too short");
|
||||
return FALSE;
|
||||
}
|
||||
msg_len = untoh32(pkt + 1);
|
||||
this->inpos = pkt_len - sizeof(eap_tls_packet_t) - 4;
|
||||
if (msg_len < this->inpos || msg_len > MAX_TLS_MESSAGE_LEN)
|
||||
{
|
||||
DBG1(DBG_IKE, "invalid EAP-TLS packet length");
|
||||
return FALSE;
|
||||
}
|
||||
this->input = chunk_alloc(msg_len);
|
||||
memcpy(this->input.ptr, ((char*)(pkt + 1)) + 4, this->inpos);
|
||||
}
|
||||
else
|
||||
{ /* non-first fragment */
|
||||
if (pkt_len > this->input.len - this->inpos)
|
||||
{
|
||||
DBG1(DBG_IKE, "EAP-TLS fragment exceeds TLS message length");
|
||||
return FALSE;
|
||||
}
|
||||
memcpy(this->input.ptr + this->inpos, (char*)(pkt + 1),
|
||||
pkt_len - sizeof(eap_tls_packet_t));
|
||||
this->inpos += pkt_len - sizeof(eap_tls_packet_t);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a eap response from data in the TLS output buffer
|
||||
*/
|
||||
static eap_payload_t *read_buf(private_eap_tls_t *this, u_int8_t identifier)
|
||||
{
|
||||
char buf[EAP_TLS_FRAGMENT_LEN + sizeof(eap_tls_packet_t) + 4], *start;
|
||||
eap_tls_packet_t *pkt = (eap_tls_packet_t*)buf;
|
||||
u_int16_t pkt_len = sizeof(eap_tls_packet_t);
|
||||
|
||||
pkt->code = this->is_server ? EAP_REQUEST : EAP_RESPONSE;
|
||||
pkt->identifier = this->is_server ? identifier + 1 : identifier;
|
||||
pkt->type = EAP_TLS;
|
||||
pkt->flags = 0;
|
||||
|
||||
start = (char*)(pkt + 1);
|
||||
if (this->outpos == 0)
|
||||
{ /* first fragment */
|
||||
pkt->flags = EAP_TLS_LENGTH;
|
||||
pkt_len += 4;
|
||||
start += 4;
|
||||
htoun32(pkt + 1, this->output.len);
|
||||
}
|
||||
|
||||
if (this->output.len - this->outpos > EAP_TLS_FRAGMENT_LEN)
|
||||
{
|
||||
pkt->flags |= EAP_TLS_MORE_FRAGS;
|
||||
pkt_len += EAP_TLS_FRAGMENT_LEN;
|
||||
memcpy(start, this->output.ptr + this->outpos, EAP_TLS_FRAGMENT_LEN);
|
||||
this->outpos += EAP_TLS_FRAGMENT_LEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
pkt_len += this->output.len - this->outpos;
|
||||
memcpy(start, this->output.ptr + this->outpos,
|
||||
this->output.len - this->outpos);
|
||||
chunk_free(&this->output);
|
||||
this->outpos = 0;
|
||||
}
|
||||
htoun16(&pkt->length, pkt_len);
|
||||
return eap_payload_create_data(chunk_create(buf, pkt_len));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass data in input buffer to upper layers, write result to output buffer
|
||||
*/
|
||||
static status_t process_buf(private_eap_tls_t *this)
|
||||
{
|
||||
tls_record_t *record;
|
||||
chunk_t input;
|
||||
u_int16_t len;
|
||||
|
||||
input = this->input;
|
||||
while (input.len > sizeof(tls_record_t))
|
||||
{
|
||||
record = (tls_record_t*)input.ptr;
|
||||
len = untoh16(&record->length);
|
||||
if (len > input.len)
|
||||
{
|
||||
DBG1(DBG_IKE, "TLS record length invalid");
|
||||
break;
|
||||
}
|
||||
/* TODO: pass record to next layer */
|
||||
input = chunk_skip(input, len);
|
||||
}
|
||||
chunk_free(&this->input);
|
||||
this->inpos = 0;
|
||||
|
||||
/* TODO: read records from next layer */
|
||||
return NEED_MORE;
|
||||
}
|
||||
|
||||
METHOD(eap_method_t, process, status_t,
|
||||
private_eap_tls_t *this, eap_payload_t *in, eap_payload_t **out)
|
||||
{
|
||||
return FAILED;
|
||||
eap_tls_packet_t *pkt;
|
||||
chunk_t data;
|
||||
status_t status;
|
||||
|
||||
data = in->get_data(in);
|
||||
pkt = (eap_tls_packet_t*)data.ptr;
|
||||
if (data.len < sizeof(eap_tls_packet_t) ||
|
||||
untoh16(&pkt->length) != data.len)
|
||||
{
|
||||
DBG1(DBG_IKE, "invalid EAP-TLS packet length");
|
||||
return FAILED;
|
||||
}
|
||||
if (!(pkt->flags & EAP_TLS_START))
|
||||
{
|
||||
if (data.len == sizeof(eap_tls_packet_t))
|
||||
{ /* ACK to our fragment, send next */
|
||||
*out = read_buf(this, pkt->identifier);
|
||||
return NEED_MORE;
|
||||
}
|
||||
if (!write_buf(this, pkt))
|
||||
{
|
||||
return FAILED;
|
||||
}
|
||||
if (pkt->flags & EAP_TLS_MORE_FRAGS)
|
||||
{ /* more fragments follow */
|
||||
*out = read_buf(this, pkt->identifier);
|
||||
return NEED_MORE;
|
||||
}
|
||||
else if (this->input.len != this->inpos)
|
||||
{
|
||||
DBG1(DBG_IKE, "defragemented TLS message has invalid length");
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
status = process_buf(this);
|
||||
if (status == NEED_MORE)
|
||||
{
|
||||
*out = read_buf(this, pkt->identifier);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
METHOD(eap_method_t, get_type, eap_type_t,
|
||||
@@ -82,6 +305,10 @@ METHOD(eap_method_t, destroy, void,
|
||||
{
|
||||
this->peer->destroy(this->peer);
|
||||
this->server->destroy(this->server);
|
||||
|
||||
free(this->input.ptr);
|
||||
free(this->output.ptr);
|
||||
|
||||
free(this);
|
||||
}
|
||||
|
||||
@@ -106,6 +333,7 @@ static eap_tls_t *eap_tls_create(identification_t *server,
|
||||
.server = server->clone(server),
|
||||
.is_server = is_server,
|
||||
);
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user