diff --git a/src/libsimaka/simaka_message.c b/src/libsimaka/simaka_message.c index 9c5363e41..97f0954f7 100644 --- a/src/libsimaka/simaka_message.c +++ b/src/libsimaka/simaka_message.c @@ -263,12 +263,21 @@ METHOD(simaka_message_t, add_attribute, void, } /** - * 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; + } } /** @@ -320,15 +329,17 @@ static bool parse_attributes(private_simaka_message_t *this, chunk_t in) } 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: @@ -343,11 +354,6 @@ static bool parse_attributes(private_simaka_message_t *this, chunk_t in) } /* 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: @@ -363,11 +369,6 @@ static bool parse_attributes(private_simaka_message_t *this, chunk_t in) /* 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: @@ -394,11 +395,6 @@ static bool parse_attributes(private_simaka_message_t *this, chunk_t in) } /* 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: @@ -464,15 +460,8 @@ static bool parse_attributes(private_simaka_message_t *this, chunk_t in) 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); @@ -607,245 +596,365 @@ METHOD(simaka_message_t, verify, bool, return TRUE; } +/** + * 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) +{ + attr_hdr_t *hdr = NULL; + + if (buf) + { + hdr = (attr_hdr_t*)buf->ptr; + hdr->type = type; + } + + 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: + { + 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; + } + /* attributes with two bytes data */ + case AT_COUNTER: + case AT_CLIENT_ERROR_CODE: + case AT_SELECTED_VERSION: + case AT_NOTIFICATION: + { + if (data.len != 2) + { + return invalid_length(type); + } + if (!buf) + { + *len = 4; + break; + } + 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; + + if (data.len > UINT8_MAX * 4 - 4) + { + return invalid_length(type); + } + if (!buf) + { + *len = round_up(4 + data.len, 4); + break; + } + 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) + { + return invalid_length(type); + } + if (!buf) + { + *len = 20; + break; + } + 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) + { + return invalid_length(type); + } + if (!buf) + { + *len = 4 + data.len; + break; + } + 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) + { + return invalid_length(type); + } + if (!buf) + { + *len = 16; + break; + } + hdr->length = 4; + memcpy(buf->ptr + 2, data.ptr, data.len); + *buf = chunk_skip(*buf, 16); + break; + } + default: + { + if (!buf) + { + *len = 0; + break; + } + DBG1(DBG_LIB, "no rule to encode %N, skipped", + simaka_attribute_names, type); + break; + } + } + 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; + chunk_t out_buf, out, encr_buf, encr, data, *target, mac; simaka_attribute_t type; attr_hdr_t *hdr; - uint16_t len; - signer_t *signer; + 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); - 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) + if (!encode_attribute(type, data, NULL, &attr_len)) { - 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; + enumerator->destroy(enumerator); + return FALSE; } - - hdr = (attr_hdr_t*)target->ptr; - hdr->type = type; - - /* encode type specific */ - switch (type) + if (!attr_len) { - /* attributes without data */ - case AT_COUNTER_TOO_SMALL: - case AT_ANY_ID_REQ: - case AT_PERMANENT_ID_REQ: - case AT_FULLAUTH_ID_REQ: + continue; + } + if (is_encrypted_attribute(type)) + { + encr_len += attr_len; + } + else + { + if (type == AT_NOTIFICATION) { - 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: - { - uint16_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; + notify_no_p_bit |= !(data.ptr[0] & 0x40); } + out_len += attr_len; } } enumerator->destroy(enumerator); - /* encrypt attributes, if any */ - if (encr.len < sizeof(encr_buf)) + if (encr_len) { - 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) + if (!crypter) { - 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); + DBG1(DBG_LIB, "%N message requires encryption, but no crypter found", + simaka_subtype_names, this->hdr->subtype); + return FALSE; } - 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)) + if (!rng) { + DBG1(DBG_LIB, "%N message requires RNG, but none found", + simaka_subtype_names, this->hdr->subtype); 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)) + 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; } - - /* 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); + /* AT_IV */ + out_len += 4 + iv_len; + /* AT_ENCR_DATA */ + out_len += 4 + encr_len; } - /* 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); + add_mac = TRUE; + break; + case SIM_NOTIFICATION: + /* AKA_NOTIFICATION: */ + add_mac = notify_no_p_bit; 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) + if (add_mac) { - data = chunk_cata("cc", out, sigdata); - if (!signer->get_signature(signer, data, mac.ptr)) + 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 */ + if (encr_pad) + { + hdr = (attr_hdr_t*)encr.ptr; + hdr->type = AT_PADDING; + hdr->length = encr_pad / 4; + memset(encr.ptr + 2, 0, encr_pad - 2); + } + + /* 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); + + iv = chunk_create(out.ptr, iv_len); + if (!rng->get_bytes(rng, iv.len, iv.ptr)) + { + goto failed; + } + out = chunk_skip(out, iv.len); + + /* inline encryption */ + if (!crypter->encrypt(crypter, encr_buf, iv, NULL)) + { + goto failed; + } + + /* add ENCR_DATA attribute */ + hdr = (attr_hdr_t*)out.ptr; + hdr->type = AT_ENCR_DATA; + hdr->length = encr_buf.len / 4 + 1; + memset(out.ptr + 2, 0, 2); + memcpy(out.ptr + 4, encr_buf.ptr, encr_buf.len); + out = chunk_skip(out, encr_buf.len + 4); + } + + if (add_mac) + { + 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); + + if (!signer->get_signature(signer, out_buf, NULL) || + !signer->get_signature(signer, sigdata, mac.ptr)) + { + 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,