}
/**
- * Error handling for unencrypted attributes
+ * Determine if an attribute is only sent encrypted
*/
-static bool not_encrypted(simaka_attribute_t type)
+static bool is_encrypted_attribute(simaka_attribute_t type)
{
- DBG1(DBG_LIB, "received unencrypted %N", simaka_attribute_names, type);
- return FALSE;
+ switch (type)
+ {
+ case AT_NONCE_S:
+ case AT_NEXT_PSEUDONYM:
+ case AT_NEXT_REAUTH_ID:
+ case AT_COUNTER:
+ case AT_COUNTER_TOO_SMALL:
+ return TRUE;
+ default:
+ return FALSE;
+ }
}
/**
}
hdr = (attr_hdr_t*)in.ptr;
+ if (is_encrypted_attribute(hdr->type) && !this->encrypted)
+ {
+ DBG1(DBG_LIB, "received unencrypted %N", simaka_attribute_names,
+ hdr->type);
+ return FALSE;
+ }
+
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:
}
/* 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:
/* 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:
}
/* 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_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;
- }
+ /* remember P bit for MAC verification */
+ this->p_bit &= !!(data.ptr[0] & 0x40);
/* FALL */
default:
add_attribute(this, hdr->type, data);
return TRUE;
}
-METHOD(simaka_message_t, generate, bool,
- private_simaka_message_t *this, chunk_t sigdata, chunk_t *gen)
+/**
+ * Encode the given attribute into buf. If buf is NULL, the required size is
+ * returned.
+ */
+static bool encode_attribute(simaka_attribute_t type, chunk_t data,
+ chunk_t *buf, size_t *len)
{
- /* 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;
- uint16_t len;
- signer_t *signer;
+ attr_hdr_t *hdr = NULL;
- 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));
+ if (buf)
+ {
+ hdr = (attr_hdr_t*)buf->ptr;
+ hdr->type = type;
+ }
- /* encode attributes */
- enumerator = create_attribute_enumerator(this);
- while (enumerator->enumerate(enumerator, &type, &data))
+ switch (type)
{
- /* encrypt this attribute? */
- 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:
{
- 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;
+ if (data.len)
+ {
+ return invalid_length(type);
+ }
+ if (!buf)
+ {
+ *len = 4;
break;
+ }
+ hdr->length = 1;
+ memset(buf->ptr + 2, 0, 2);
+ *buf = chunk_skip(*buf, 4);
+ break;
}
-
- hdr = (attr_hdr_t*)target->ptr;
- hdr->type = type;
-
- /* encode type specific */
- switch (type)
+ /* attributes with two bytes data */
+ case AT_COUNTER:
+ case AT_CLIENT_ERROR_CODE:
+ case AT_SELECTED_VERSION:
+ case AT_NOTIFICATION:
{
- /* attributes without data */
- case AT_COUNTER_TOO_SMALL:
- case AT_ANY_ID_REQ:
- case AT_PERMANENT_ID_REQ:
- case AT_FULLAUTH_ID_REQ:
+ if (data.len != 2)
{
- hdr->length = 1;
- memset(target->ptr + 2, 0, 2);
- *target = chunk_skip(*target, 4);
- break;
+ return invalid_length(type);
}
- /* attributes with two bytes data */
- case AT_COUNTER:
- case AT_CLIENT_ERROR_CODE:
- case AT_SELECTED_VERSION:
- case AT_NOTIFICATION:
+ if (!buf)
{
- hdr->length = 1;
- memcpy(target->ptr + 2, data.ptr, 2);
- *target = chunk_skip(*target, 4);
+ *len = 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:
- {
- uint16_t len, padding;
+ hdr->length = 1;
+ memcpy(buf->ptr + 2, data.ptr, 2);
+ *buf = chunk_skip(*buf, 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:
+ {
+ uint16_t actual_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);
+ if (data.len > UINT8_MAX * 4 - 4)
+ {
+ return invalid_length(type);
+ }
+ if (!buf)
+ {
+ *len = round_up(4 + data.len, 4);
break;
}
- /* attributes with two reserved bytes, 16 bytes length */
- case AT_NONCE_S:
- case AT_NONCE_MT:
- case AT_AUTN:
+ actual_len = data.len;
+ if (type == AT_RES)
+ { /* AT_RES uses length encoding in bits */
+ actual_len *= 8;
+ }
+ htoun16(buf->ptr + 2, actual_len);
+ memcpy(buf->ptr + 4, data.ptr, data.len);
+ hdr->length = data.len / 4 + 1;
+ padding = pad_len(data.len, 4);
+ if (padding)
+ {
+ hdr->length++;
+ memset(buf->ptr + 4 + data.len, 0, padding);
+ }
+ *buf = chunk_skip(*buf, hdr->length * 4);
+ break;
+ }
+ /* attributes with two reserved bytes, 16 bytes length */
+ case AT_NONCE_S:
+ case AT_NONCE_MT:
+ case AT_AUTN:
+ {
+ if (data.len != 16)
{
- hdr->length = 5;
- memset(target->ptr + 2, 0, 2);
- memcpy(target->ptr + 4, data.ptr, data.len);
- *target = chunk_skip(*target, 20);
+ return invalid_length(type);
+ }
+ if (!buf)
+ {
+ *len = 20;
break;
}
- /* attributes with two reserved bytes, variable length */
- case AT_RAND:
+ hdr->length = 5;
+ memset(buf->ptr + 2, 0, 2);
+ memcpy(buf->ptr + 4, data.ptr, data.len);
+ *buf = chunk_skip(*buf, 20);
+ break;
+ }
+ /* attributes with two reserved bytes, variable length */
+ case AT_RAND:
+ {
+ if (data.len % 4 || data.len > UINT8_MAX * 4 - 4)
{
- 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);
+ return invalid_length(type);
+ }
+ if (!buf)
+ {
+ *len = 4 + data.len;
break;
}
- /* attributes with no reserved bytes, 14 bytes length */
- case AT_AUTS:
+ hdr->length = 1 + data.len / 4;
+ memset(buf->ptr + 2, 0, 2);
+ memcpy(buf->ptr + 4, data.ptr, data.len);
+ *buf = chunk_skip(*buf, data.len + 4);
+ break;
+ }
+ /* attributes with no reserved bytes, 14 bytes length */
+ case AT_AUTS:
+ {
+ if (data.len != 14)
{
- hdr->length = 4;
- memcpy(target->ptr + 2, data.ptr, data.len);
- *target = chunk_skip(*target, 16);
+ return invalid_length(type);
+ }
+ if (!buf)
+ {
+ *len = 16;
break;
}
- default:
+ hdr->length = 4;
+ memcpy(buf->ptr + 2, data.ptr, data.len);
+ *buf = chunk_skip(*buf, 16);
+ break;
+ }
+ default:
+ {
+ if (!buf)
{
- DBG1(DBG_LIB, "no rule to encode %N, skipped",
- simaka_attribute_names, type);
+ *len = 0;
break;
}
+ DBG1(DBG_LIB, "no rule to encode %N, skipped",
+ simaka_attribute_names, type);
+ break;
}
}
- enumerator->destroy(enumerator);
+ return TRUE;
+}
- /* encrypt attributes, if any */
- if (encr.len < sizeof(encr_buf))
+METHOD(simaka_message_t, generate, bool,
+ private_simaka_message_t *this, chunk_t sigdata, chunk_t *gen)
+{
+ enumerator_t *enumerator;
+ chunk_t out_buf, out, encr_buf, encr, data, *target, mac;
+ simaka_attribute_t type;
+ attr_hdr_t *hdr;
+ size_t out_len = sizeof(hdr_t), encr_len = 0, attr_len;
+ size_t iv_len = 0, encr_pad = 0, mac_bs = 0;
+ bool notify_no_p_bit = FALSE, add_mac = FALSE, success = FALSE;
+ crypter_t *crypter = NULL;
+ signer_t *signer = NULL;
+ rng_t *rng = NULL;
+
+ call_hook(this, FALSE, TRUE);
+
+ enumerator = create_attribute_enumerator(this);
+ while (enumerator->enumerate(enumerator, &type, &data))
{
- chunk_t iv;
- size_t bs, padding;
- crypter_t *crypter;
- rng_t *rng;
+ if (!encode_attribute(type, data, NULL, &attr_len))
+ {
+ enumerator->destroy(enumerator);
+ return FALSE;
+ }
+ if (!attr_len)
+ {
+ continue;
+ }
+ if (is_encrypted_attribute(type))
+ {
+ encr_len += attr_len;
+ }
+ else
+ {
+ if (type == AT_NOTIFICATION)
+ {
+ notify_no_p_bit |= !(data.ptr[0] & 0x40);
+ }
+ out_len += attr_len;
+ }
+ }
+ enumerator->destroy(enumerator);
+ if (encr_len)
+ {
crypter = this->crypto->get_crypter(this->crypto);
- bs = crypter->get_block_size(crypter);
- iv.len = crypter->get_iv_size(crypter);
+ if (!crypter)
+ {
+ DBG1(DBG_LIB, "%N message requires encryption, but no crypter found",
+ simaka_subtype_names, this->hdr->subtype);
+ return FALSE;
+ }
+ rng = this->crypto->get_rng(this->crypto);
+ if (!rng)
+ {
+ DBG1(DBG_LIB, "%N message requires RNG, but none found",
+ simaka_subtype_names, this->hdr->subtype);
+ return FALSE;
+ }
+ iv_len = crypter->get_iv_size(crypter);
+ encr_pad = pad_len(encr_len, crypter->get_block_size(crypter));
+ /* AT_PADDING */
+ encr_len += encr_pad;
+ if (encr_len > UINT8_MAX * 4 - 4)
+ {
+ DBG1(DBG_LIB, "encrypted attributes exceed maximum length");
+ return FALSE;
+ }
+ /* AT_IV */
+ out_len += 4 + iv_len;
+ /* AT_ENCR_DATA */
+ out_len += 4 + encr_len;
+ }
+
+ switch (this->hdr->subtype)
+ {
+ case SIM_CHALLENGE:
+ case AKA_CHALLENGE:
+ case SIM_REAUTHENTICATION:
+ /* AKA_REAUTHENTICATION: */
+ add_mac = TRUE;
+ break;
+ case SIM_NOTIFICATION:
+ /* AKA_NOTIFICATION: */
+ add_mac = notify_no_p_bit;
+ break;
+ default:
+ break;
+ }
+
+ if (add_mac)
+ {
+ signer = this->crypto->get_signer(this->crypto);
+ if (!signer)
+ {
+ DBG1(DBG_LIB, "%N message requires AT_MAC, but no signer found",
+ simaka_subtype_names, this->hdr->subtype);
+ return FALSE;
+ }
+ mac_bs = signer->get_block_size(signer);
+ /* AT_MAC */
+ out_len += 4 + mac_bs;
+ }
+
+ if (out_len > UINT16_MAX)
+ {
+ DBG1(DBG_LIB, "EAP-SIM/AKA message exceeds maximum length");
+ return FALSE;
+ }
+
+ out = out_buf = chunk_alloc(out_len);
+ encr = encr_buf = chunk_alloc(encr_len);
+
+ /* copy header, set length */
+ memcpy(out.ptr, this->hdr, sizeof(hdr_t));
+ htoun16(out.ptr + 2, out.len);
+
+ out = chunk_skip(out, sizeof(hdr_t));
+
+ /* encode attributes */
+ enumerator = create_attribute_enumerator(this);
+ while (enumerator->enumerate(enumerator, &type, &data))
+ {
+ target = is_encrypted_attribute(type) ? &encr : &out;
+ encode_attribute(type, data, target, NULL);
+ }
+ enumerator->destroy(enumerator);
+
+ if (encr_len)
+ {
+ chunk_t iv;
/* add AT_PADDING attribute */
- padding = bs - ((sizeof(encr_buf) - encr.len) % bs);
- if (padding)
+ if (encr_pad)
{
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);
+ hdr->length = encr_pad / 4;
+ memset(encr.ptr + 2, 0, encr_pad - 2);
}
- 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;
+ 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))
+ iv = chunk_create(out.ptr, iv_len);
+ if (!rng->get_bytes(rng, iv.len, iv.ptr))
{
- return FALSE;
+ goto failed;
}
-
- iv = chunk_clonea(chunk_create(out.ptr, iv.len));
out = chunk_skip(out, iv.len);
/* inline encryption */
- if (!crypter->encrypt(crypter, encr, iv, NULL))
+ if (!crypter->encrypt(crypter, encr_buf, iv, NULL))
{
- return FALSE;
+ goto failed;
}
/* add ENCR_DATA attribute */
hdr = (attr_hdr_t*)out.ptr;
hdr->type = AT_ENCR_DATA;
- hdr->length = encr.len / 4 + 1;
+ hdr->length = encr_buf.len / 4 + 1;
memset(out.ptr + 2, 0, 2);
- memcpy(out.ptr + 4, encr.ptr, encr.len);
- out = chunk_skip(out, encr.len + 4);
+ memcpy(out.ptr + 4, encr_buf.ptr, encr_buf.len);
+ out = chunk_skip(out, encr_buf.len + 4);
}
- /* include MAC ? */
- signer = this->crypto->get_signer(this->crypto);
- switch (this->hdr->subtype)
+ if (add_mac)
{
- 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));
+ hdr = (attr_hdr_t*)out.ptr;
+ hdr->type = AT_MAC;
+ hdr->length = mac_bs / 4 + 1;
+ memset(out.ptr + 2, 0, 2 + mac_bs);
+ mac = chunk_create(out.ptr + 4, mac_bs);
- /* generate MAC */
- if (mac.len)
- {
- data = chunk_cata("cc", out, sigdata);
- if (!signer->get_signature(signer, data, mac.ptr))
+ if (!signer->get_signature(signer, out_buf, NULL) ||
+ !signer->get_signature(signer, sigdata, mac.ptr))
{
- return FALSE;
+ goto failed;
}
}
call_hook(this, FALSE, FALSE);
- *gen = chunk_clone(out);
- return TRUE;
+ *gen = out_buf;
+ success = TRUE;
+
+failed:
+ chunk_clear(&encr_buf);
+ if (!success)
+ {
+ chunk_free(&out_buf);
+ }
+ return success;
}
METHOD(simaka_message_t, destroy, void,