strongswan/src/libsimaka/simaka_message.c

930 lines
21 KiB
C

/*
* Copyright (C) 2009 Martin Willi
* Hochschule fuer Technik Rapperswil
*
* 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 "simaka_message.h"
#include "simaka_manager.h"
#include <utils/debug.h>
#include <collections/linked_list.h>
typedef struct private_simaka_message_t private_simaka_message_t;
typedef struct hdr_t hdr_t;
typedef struct attr_hdr_t attr_hdr_t;
typedef struct attr_t attr_t;
/**
* packed EAP-SIM/AKA header struct
*/
struct hdr_t {
/** EAP code (REQUEST/RESPONSE) */
u_int8_t code;
/** unique message identifier */
u_int8_t identifier;
/** length of whole message */
u_int16_t length;
/** EAP type => EAP_SIM/EAP_AKA */
u_int8_t type;
/** SIM subtype */
u_int8_t subtype;
/** reserved bytes */
u_int16_t reserved;
} __attribute__((__packed__));
/**
* packed EAP-SIM/AKA attribute header struct
*/
struct attr_hdr_t {
/** attribute type */
u_int8_t type;
/** attibute length */
u_int8_t length;
} __attribute__((__packed__));
/**
* SIM/AKA attribute, parsed
*/
struct attr_t {
/** type of attribute */
simaka_attribute_t type;
/** length of data */
size_t len;
/** start of data, variable length */
char data[];
};
ENUM_BEGIN(simaka_subtype_names, AKA_CHALLENGE, AKA_IDENTITY,
"AKA_CHALLENGE",
"AKA_AUTHENTICATION_REJECT",
"AKA_3",
"AKA_SYNCHRONIZATION_FAILURE",
"AKA_IDENTITY");
ENUM_NEXT(simaka_subtype_names, SIM_START, AKA_CLIENT_ERROR, AKA_IDENTITY,
"SIM_START",
"SIM_CHALLENGE",
"SIM/AKA_NOTIFICATION",
"SIM/AKA_REAUTHENTICATION",
"SIM/AKA_CLIENT_ERROR");
ENUM_END(simaka_subtype_names, AKA_CLIENT_ERROR);
ENUM_BEGIN(simaka_attribute_names, AT_RAND, AT_CLIENT_ERROR_CODE,
"AT_RAND",
"AT_AUTN",
"AT_RES",
"AT_AUTS",
"AT_5",
"AT_PADDING",
"AT_NONCE_MT",
"AT_8",
"AT_9",
"AT_PERMANENT_ID_REQ",
"AT_MAC",
"AT_NOTIFICATION",
"AT_ANY_ID_REQ",
"AT_IDENTITY",
"AT_VERSION_LIST",
"AT_SELECTED_VERSION",
"AT_FULLAUTH_ID_REQ",
"AT_18",
"AT_COUNTER",
"AT_COUNTER_TOO_SMALL",
"AT_NONCE_S",
"AT_CLIENT_ERROR_CODE");
ENUM_NEXT(simaka_attribute_names, AT_IV, AT_RESULT_IND, AT_CLIENT_ERROR_CODE,
"AT_IV",
"AT_ENCR_DATA",
"AT_131",
"AT_NEXT_PSEUDONYM",
"AT_NEXT_REAUTH_ID",
"AT_CHECKCODE",
"AT_RESULT_IND");
ENUM_END(simaka_attribute_names, AT_RESULT_IND);
ENUM_BEGIN(simaka_notification_names, SIM_GENERAL_FAILURE_AA, SIM_GENERAL_FAILURE_AA,
"General failure after authentication");
ENUM_NEXT(simaka_notification_names, SIM_TEMP_DENIED, SIM_TEMP_DENIED, SIM_GENERAL_FAILURE_AA,
"User has been temporarily denied access");
ENUM_NEXT(simaka_notification_names, SIM_NOT_SUBSCRIBED, SIM_NOT_SUBSCRIBED, SIM_TEMP_DENIED,
"User has not subscribed to the requested service");
ENUM_NEXT(simaka_notification_names, SIM_GENERAL_FAILURE, SIM_GENERAL_FAILURE, SIM_NOT_SUBSCRIBED,
"General failure");
ENUM_NEXT(simaka_notification_names, SIM_SUCCESS, SIM_SUCCESS, SIM_GENERAL_FAILURE,
"User has been successfully authenticated");
ENUM_END(simaka_notification_names, SIM_SUCCESS);
ENUM(simaka_client_error_names, SIM_UNABLE_TO_PROCESS, SIM_RANDS_NOT_FRESH,
"unable to process packet",
"unsupported version",
"insufficient number of challenges",
"RANDs are not fresh",
);
/**
* Check if an EAP-SIM/AKA attribute is skippable
*/
bool simaka_attribute_skippable(simaka_attribute_t attribute)
{
bool skippable = !((int)attribute >= 0 && attribute <= 127);
DBG1(DBG_LIB, "%sskippable EAP-SIM/AKA attribute %N",
skippable ? "ignoring " : "found non-",
simaka_attribute_names, attribute);
return skippable;
}
/**
* Private data of an simaka_message_t object.
*/
struct private_simaka_message_t {
/**
* Public simaka_message_t interface.
*/
simaka_message_t public;
/**
* EAP message, starting with EAP header
*/
hdr_t *hdr;
/**
* List of parsed attributes, attr_t
*/
linked_list_t *attributes;
/**
* Currently parsing AT_ENCR_DATA wrapped attributes?
*/
bool encrypted;
/**
* crypto helper
*/
simaka_crypto_t *crypto;
/**
* Phase a NOTIFICATION is sent within
*/
bool p_bit;
/**
* MAC value, pointing into message
*/
chunk_t mac;
/**
* ENCR_DATA value, pointing into message
*/
chunk_t encr;
/**
* IV value, pointing into message
*/
chunk_t iv;
};
METHOD(simaka_message_t, is_request, bool,
private_simaka_message_t *this)
{
return this->hdr->code == EAP_REQUEST;
}
METHOD(simaka_message_t, get_identifier, u_int8_t,
private_simaka_message_t *this)
{
return this->hdr->identifier;
}
METHOD(simaka_message_t, get_subtype, simaka_subtype_t,
private_simaka_message_t *this)
{
return this->hdr->subtype;
}
METHOD(simaka_message_t, get_type, eap_type_t,
private_simaka_message_t *this)
{
return this->hdr->type;
}
/**
* convert attr_t to type and data enumeration
*/
static bool attr_enum_filter(void *null, attr_t **in, simaka_attribute_t *type,
void *dummy, chunk_t *data)
{
attr_t *attr = *in;
*type = attr->type;
*data = chunk_create(attr->data, attr->len);
return TRUE;
}
METHOD(simaka_message_t, create_attribute_enumerator, enumerator_t*,
private_simaka_message_t *this)
{
return enumerator_create_filter(
this->attributes->create_enumerator(this->attributes),
(void*)attr_enum_filter, NULL, NULL);
}
METHOD(simaka_message_t, add_attribute, void,
private_simaka_message_t *this, simaka_attribute_t type, chunk_t data)
{
attr_t *attr;
attr = malloc(sizeof(attr_t) + data.len);
attr->len = data.len;
attr->type = type;
memcpy(attr->data, data.ptr, data.len);
this->attributes->insert_last(this->attributes, attr);
}
/**
* Error handling for unencrypted attributes
*/
static bool not_encrypted(simaka_attribute_t type)
{
DBG1(DBG_LIB, "received unencrypted %N", simaka_attribute_names, type);
return FALSE;
}
/**
* Error handling for invalid length
*/
static bool invalid_length(simaka_attribute_t type)
{
DBG1(DBG_LIB, "invalid length of %N", simaka_attribute_names, type);
return FALSE;
}
/**
* Call SIM/AKA message hooks
*/
static void call_hook(private_simaka_message_t *this,
bool inbound, bool decrypted)
{
simaka_manager_t *mgr;
switch (this->hdr->type)
{
case EAP_SIM:
mgr = lib->get(lib, "sim-manager");
break;
case EAP_AKA:
mgr = lib->get(lib, "aka-manager");
break;
default:
return;
}
mgr->message_hook(mgr, &this->public, inbound, decrypted);
}
/**
* Parse attributes from a chunk of data
*/
static bool parse_attributes(private_simaka_message_t *this, chunk_t in)
{
while (in.len)
{
attr_hdr_t *hdr;
chunk_t data;
if (in.len < sizeof(attr_hdr_t))
{
DBG1(DBG_LIB, "found short %N attribute header",
eap_type_names, this->hdr->type);
return FALSE;
}
hdr = (attr_hdr_t*)in.ptr;
switch (hdr->type)
{
/* attributes without data */
case AT_COUNTER_TOO_SMALL:
if (!this->encrypted)
{
return not_encrypted(hdr->type);
}
/* FALL */
case AT_ANY_ID_REQ:
case AT_PERMANENT_ID_REQ:
case AT_FULLAUTH_ID_REQ:
{
if (hdr->length != 1 || in.len < 4)
{
return invalid_length(hdr->type);
}
data = chunk_empty;
in = chunk_skip(in, 4);
break;
}
/* attributes with two bytes data */
case AT_COUNTER:
if (!this->encrypted)
{
return not_encrypted(hdr->type);
}
/* FALL */
case AT_CLIENT_ERROR_CODE:
case AT_SELECTED_VERSION:
case AT_NOTIFICATION:
{
if (hdr->length != 1 || in.len < 4)
{
return invalid_length(hdr->type);
}
data = chunk_create(in.ptr + 2, 2);
in = chunk_skip(in, 4);
break;
}
/* attributes with an additional actual-length in bits or bytes */
case AT_NEXT_PSEUDONYM:
case AT_NEXT_REAUTH_ID:
if (!this->encrypted)
{
return not_encrypted(hdr->type);
}
/* FALL */
case AT_RES:
case AT_IDENTITY:
case AT_VERSION_LIST:
{
u_int16_t len;
if (hdr->length < 1 || in.len < 4)
{
return invalid_length(hdr->type);
}
memcpy(&len, in.ptr + 2, 2);
len = ntohs(len);
if (hdr->type == AT_RES)
{ /* AT_RES uses length encoding in bits */
len /= 8;
}
if (len > hdr->length * 4 || len > in.len)
{
return invalid_length(hdr->type);
}
data = chunk_create(in.ptr + 4, len);
in = chunk_skip(in, hdr->length * 4);
break;
}
/* attributes with two reserved bytes, 16 bytes length */
case AT_NONCE_S:
if (!this->encrypted)
{
return not_encrypted(hdr->type);
}
/* FALL */
case AT_AUTN:
case AT_NONCE_MT:
case AT_IV:
case AT_MAC:
{
if (hdr->length != 5 || in.len < 20)
{
return invalid_length(hdr->type);
}
data = chunk_create(in.ptr + 4, 16);
in = chunk_skip(in, 20);
break;
}
/* attributes with two reserved bytes, variable length */
case AT_ENCR_DATA:
case AT_RAND:
{
if (hdr->length * 4 > in.len || in.len < 4)
{
return invalid_length(hdr->type);
}
data = chunk_create(in.ptr + 4, hdr->length * 4 - 4);
in = chunk_skip(in, hdr->length * 4);
break;
}
/* attributes with no reserved bytes, 14 bytes length */
case AT_AUTS:
{
if (hdr->length != 4 || in.len < 16)
{
return invalid_length(hdr->type);
}
data = chunk_create(in.ptr + 2, 14);
in = chunk_skip(in, 16);
break;
}
/* other attributes (with 4n + 2 length) */
case AT_PADDING:
default:
{
if (hdr->length * 4 > in.len || in.len < 4)
{
return invalid_length(hdr->type);
}
data = chunk_create(in.ptr + 2, hdr->length * 4 - 2);
in = chunk_skip(in, hdr->length * 4);
break;
}
}
/* handle special attributes */
switch (hdr->type)
{
case AT_MAC:
this->mac = data;
break;
case AT_IV:
this->iv = data;
break;
case AT_ENCR_DATA:
this->encr = data;
break;
case AT_PADDING:
break;
case AT_NOTIFICATION:
if (this->p_bit)
{ /* remember P bit for MAC verification */
this->p_bit = !!(data.ptr[0] & 0x40);
}
else if (!this->encrypted)
{
DBG1(DBG_LIB, "found P-bit 0 notify in unencrypted message");
return FALSE;
}
/* FALL */
default:
add_attribute(this, hdr->type, data);
break;
}
}
call_hook(this, TRUE, this->encrypted);
return TRUE;
}
/**
* Decrypt a message and parse the decrypted attributes
*/
static bool decrypt(private_simaka_message_t *this)
{
bool success;
crypter_t *crypter;
chunk_t plain;
crypter = this->crypto->get_crypter(this->crypto);
if (!crypter || !this->iv.len || !this->encr.len || this->encrypted)
{
return TRUE;
}
if (this->encr.len % crypter->get_block_size(crypter))
{
DBG1(DBG_LIB, "%N ENCR_DATA not a multiple of block size",
eap_type_names, this->hdr->type);
return FALSE;
}
if (!crypter->decrypt(crypter, this->encr, this->iv, &plain))
{
return FALSE;
}
this->encrypted = TRUE;
success = parse_attributes(this, plain);
this->encrypted = FALSE;
free(plain.ptr);
return success;
}
METHOD(simaka_message_t, parse, bool,
private_simaka_message_t *this)
{
chunk_t in;
if (this->attributes->get_count(this->attributes))
{ /* Already parsed. Try to decrypt and parse AT_ENCR_DATA. */
return decrypt(this);
}
in = chunk_create((char*)this->hdr, ntohs(this->hdr->length));
if (!parse_attributes(this, chunk_skip(in, sizeof(hdr_t))))
{
return FALSE;
}
/* try to decrypt if we already have keys */
return decrypt(this);
}
METHOD(simaka_message_t, verify, bool,
private_simaka_message_t *this, chunk_t sigdata)
{
chunk_t data, backup;
signer_t *signer;
signer = this->crypto->get_signer(this->crypto);
switch (this->hdr->subtype)
{
case SIM_START:
case SIM_CLIENT_ERROR:
/* AKA_CLIENT_ERROR: */
case AKA_AUTHENTICATION_REJECT:
case AKA_SYNCHRONIZATION_FAILURE:
case AKA_IDENTITY:
/* skip MAC if available */
return TRUE;
case SIM_CHALLENGE:
case AKA_CHALLENGE:
case SIM_REAUTHENTICATION:
/* AKA_REAUTHENTICATION: */
{
if (!this->mac.ptr || !signer)
{ /* require MAC, but not found */
DBG1(DBG_LIB, "%N message requires a MAC, but none found",
simaka_subtype_names, this->hdr->subtype);
return FALSE;
}
break;
}
case SIM_NOTIFICATION:
/* AKA_NOTIFICATION: */
{
if (this->p_bit)
{ /* MAC not verified if in Phase 1 */
return TRUE;
}
if (!this->mac.ptr || !signer)
{
DBG1(DBG_LIB, "%N message has a phase 0 notify, but "
"no MAC found", simaka_subtype_names, this->hdr->subtype);
return FALSE;
}
break;
}
default:
/* unknown message? */
DBG1(DBG_LIB, "signature rule for %N messages missing",
simaka_subtype_names, this->hdr->subtype);
return FALSE;
}
/* zero MAC for verification */
backup = chunk_clonea(this->mac);
memset(this->mac.ptr, 0, this->mac.len);
data = chunk_create((char*)this->hdr, ntohs(this->hdr->length));
if (sigdata.len)
{
data = chunk_cata("cc", data, sigdata);
}
if (!signer->verify_signature(signer, data, backup))
{
DBG1(DBG_LIB, "%N MAC verification failed",
eap_type_names, this->hdr->type);
return FALSE;
}
return TRUE;
}
METHOD(simaka_message_t, generate, bool,
private_simaka_message_t *this, chunk_t sigdata, chunk_t *gen)
{
/* buffers large enough for messages we generate */
char out_buf[1024], encr_buf[512];
enumerator_t *enumerator;
chunk_t out, encr, data, *target, mac = chunk_empty;
simaka_attribute_t type;
attr_hdr_t *hdr;
u_int16_t len;
signer_t *signer;
call_hook(this, FALSE, TRUE);
out = chunk_create(out_buf, sizeof(out_buf));
encr = chunk_create(encr_buf, sizeof(encr_buf));
/* copy header */
memcpy(out.ptr, this->hdr, sizeof(hdr_t));
out = chunk_skip(out, sizeof(hdr_t));
/* encode attributes */
enumerator = create_attribute_enumerator(this);
while (enumerator->enumerate(enumerator, &type, &data))
{
/* encrypt this attribute? */
switch (type)
{
case AT_NONCE_S:
case AT_NEXT_PSEUDONYM:
case AT_NEXT_REAUTH_ID:
case AT_COUNTER:
case AT_COUNTER_TOO_SMALL:
target = &encr;
break;
case AT_NOTIFICATION:
/* P bit not set, encrypt */
if (!(data.ptr[0] & 0x40))
{
target = &encr;
break;
}
/* FALL */
default:
target = &out;
break;
}
hdr = (attr_hdr_t*)target->ptr;
hdr->type = type;
/* encode type specific */
switch (type)
{
/* attributes without data */
case AT_COUNTER_TOO_SMALL:
case AT_ANY_ID_REQ:
case AT_PERMANENT_ID_REQ:
case AT_FULLAUTH_ID_REQ:
{
hdr->length = 1;
memset(target->ptr + 2, 0, 2);
*target = chunk_skip(*target, 4);
break;
}
/* attributes with two bytes data */
case AT_COUNTER:
case AT_CLIENT_ERROR_CODE:
case AT_SELECTED_VERSION:
case AT_NOTIFICATION:
{
hdr->length = 1;
memcpy(target->ptr + 2, data.ptr, 2);
*target = chunk_skip(*target, 4);
break;
}
/* attributes with an additional actual-length in bits or bytes */
case AT_NEXT_PSEUDONYM:
case AT_NEXT_REAUTH_ID:
case AT_IDENTITY:
case AT_VERSION_LIST:
case AT_RES:
{
u_int16_t len, padding;
len = htons(data.len);
if (type == AT_RES)
{ /* AT_RES uses length encoding in bits */
len *= 8;
}
memcpy(target->ptr + 2, &len, sizeof(len));
memcpy(target->ptr + 4, data.ptr, data.len);
hdr->length = data.len / 4 + 1;
padding = (4 - (data.len % 4)) % 4;
if (padding)
{
hdr->length++;
memset(target->ptr + 4 + data.len, 0, padding);
}
*target = chunk_skip(*target, hdr->length * 4);
break;
}
/* attributes with two reserved bytes, 16 bytes length */
case AT_NONCE_S:
case AT_NONCE_MT:
case AT_AUTN:
{
hdr->length = 5;
memset(target->ptr + 2, 0, 2);
memcpy(target->ptr + 4, data.ptr, data.len);
*target = chunk_skip(*target, 20);
break;
}
/* attributes with two reserved bytes, variable length */
case AT_RAND:
{
hdr->length = 1 + data.len / 4;
memset(target->ptr + 2, 0, 2);
memcpy(target->ptr + 4, data.ptr, data.len);
*target = chunk_skip(*target, data.len + 4);
break;
}
/* attributes with no reserved bytes, 14 bytes length */
case AT_AUTS:
{
hdr->length = 4;
memcpy(target->ptr + 2, data.ptr, data.len);
*target = chunk_skip(*target, 16);
break;
}
default:
{
DBG1(DBG_LIB, "no rule to encode %N, skipped",
simaka_attribute_names, type);
break;
}
}
}
enumerator->destroy(enumerator);
/* encrypt attributes, if any */
if (encr.len < sizeof(encr_buf))
{
chunk_t iv;
size_t bs, padding;
crypter_t *crypter;
rng_t *rng;
crypter = this->crypto->get_crypter(this->crypto);
bs = crypter->get_block_size(crypter);
iv.len = crypter->get_iv_size(crypter);
/* add AT_PADDING attribute */
padding = bs - ((sizeof(encr_buf) - encr.len) % bs);
if (padding)
{
hdr = (attr_hdr_t*)encr.ptr;
hdr->type = AT_PADDING;
hdr->length = padding / 4;
memset(encr.ptr + 2, 0, padding - 2);
encr = chunk_skip(encr, padding);
}
encr = chunk_create(encr_buf, sizeof(encr_buf) - encr.len);
/* add IV attribute */
hdr = (attr_hdr_t*)out.ptr;
hdr->type = AT_IV;
hdr->length = iv.len / 4 + 1;
memset(out.ptr + 2, 0, 2);
out = chunk_skip(out, 4);
rng = this->crypto->get_rng(this->crypto);
if (!rng->get_bytes(rng, iv.len, out.ptr))
{
return FALSE;
}
iv = chunk_clonea(chunk_create(out.ptr, iv.len));
out = chunk_skip(out, iv.len);
/* inline encryption */
if (!crypter->encrypt(crypter, encr, iv, NULL))
{
return FALSE;
}
/* add ENCR_DATA attribute */
hdr = (attr_hdr_t*)out.ptr;
hdr->type = AT_ENCR_DATA;
hdr->length = encr.len / 4 + 1;
memset(out.ptr + 2, 0, 2);
memcpy(out.ptr + 4, encr.ptr, encr.len);
out = chunk_skip(out, encr.len + 4);
}
/* include MAC ? */
signer = this->crypto->get_signer(this->crypto);
switch (this->hdr->subtype)
{
case SIM_CHALLENGE:
case AKA_CHALLENGE:
case SIM_REAUTHENTICATION:
/* AKA_REAUTHENTICATION: */
/* TODO: Notifications without P bit */
{
size_t bs;
bs = signer->get_block_size(signer);
hdr = (attr_hdr_t*)out.ptr;
hdr->type = AT_MAC;
hdr->length = bs / 4 + 1;
memset(out.ptr + 2, 0, 2 + bs);
mac = chunk_create(out.ptr + 4, bs);
out = chunk_skip(out, bs + 4);
break;
}
default:
break;
}
/* calculate message length */
out = chunk_create(out_buf, sizeof(out_buf) - out.len);
len = htons(out.len);
memcpy(out.ptr + 2, &len, sizeof(len));
/* generate MAC */
if (mac.len)
{
data = chunk_cata("cc", out, sigdata);
if (!signer->get_signature(signer, data, mac.ptr))
{
return FALSE;
}
}
call_hook(this, FALSE, FALSE);
*gen = chunk_clone(out);
return TRUE;
}
METHOD(simaka_message_t, destroy, void,
private_simaka_message_t *this)
{
this->attributes->destroy_function(this->attributes, free);
free(this->hdr);
free(this);
}
/**
* Generic constructor.
*/
static simaka_message_t *simaka_message_create_data(chunk_t data,
simaka_crypto_t *crypto)
{
private_simaka_message_t *this;
hdr_t *hdr = (hdr_t*)data.ptr;
if (data.len < sizeof(hdr_t) || hdr->length != htons(data.len))
{
DBG1(DBG_LIB, "EAP-SIM/AKA header has invalid length");
return NULL;
}
if (hdr->code != EAP_REQUEST && hdr->code != EAP_RESPONSE)
{
DBG1(DBG_LIB, "invalid EAP code in EAP-SIM/AKA message",
eap_type_names, hdr->type);
return NULL;
}
if (hdr->type != EAP_SIM && hdr->type != EAP_AKA)
{
DBG1(DBG_LIB, "invalid EAP type in EAP-SIM/AKA message",
eap_type_names, hdr->type);
return NULL;
}
INIT(this,
.public = {
.is_request = _is_request,
.get_identifier = _get_identifier,
.get_type = _get_type,
.get_subtype = _get_subtype,
.create_attribute_enumerator = _create_attribute_enumerator,
.add_attribute = _add_attribute,
.parse = _parse,
.verify = _verify,
.generate = _generate,
.destroy = _destroy,
},
.attributes = linked_list_create(),
.crypto = crypto,
.p_bit = TRUE,
.hdr = malloc(data.len),
);
memcpy(this->hdr, hdr, data.len);
return &this->public;
}
/**
* See header.
*/
simaka_message_t *simaka_message_create_from_payload(chunk_t data,
simaka_crypto_t *crypto)
{
return simaka_message_create_data(data, crypto);
}
/**
* See header.
*/
simaka_message_t *simaka_message_create(bool request, u_int8_t identifier,
eap_type_t type, simaka_subtype_t subtype,
simaka_crypto_t *crypto)
{
hdr_t hdr = {
.code = request ? EAP_REQUEST : EAP_RESPONSE,
.identifier = identifier,
.length = htons(sizeof(hdr_t)),
.type = type,
.subtype = subtype,
};
return simaka_message_create_data(chunk_create((char*)&hdr, sizeof(hdr)),
crypto);
}