static ssize_t decode_value(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t data_len) { unsigned int values, i; /* How many values we need to decode */ uint8_t const *p = data; size_t value_len; ssize_t len; FR_PROTO_TRACE("%s called to parse %zu byte(s)", __FUNCTION__, data_len); FR_PROTO_HEX_DUMP(data, data_len, NULL); /* * TLVs can't be coalesced as they're variable length */ if (parent->type == FR_TYPE_TLV) return decode_tlv(ctx, cursor, parent, data, data_len); /* * Values with a fixed length may be coalesced into a single option */ values = fr_dhcpv4_array_members(&value_len, data_len, parent); if (values) { FR_PROTO_TRACE("found %u coalesced values (%zu bytes each)", values, value_len); if ((values * value_len) != data_len) { fr_strerror_printf("Option length not divisible by its fixed value " "length (probably trailing garbage)"); return -1; } } /* * Decode each of the (maybe) coalesced values as its own * attribute. */ for (i = 0, p = data; i < values; i++) { len = decode_value_internal(ctx, cursor, parent, p, value_len); if (len <= 0) return len; if (len != (ssize_t)value_len) { fr_strerror_printf("Failed decoding complete option value"); return -1; } p += len; } return p - data; }
static ssize_t sim_decode_array(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t const attr_len, UNUSED size_t data_len, void *decoder_ctx) { uint8_t const *p = data, *end = p + attr_len; uint16_t actual_len; int elements, i; size_t element_len; ssize_t rcode; FR_PROTO_TRACE("Array attribute"); rad_assert(parent->flags.array); rad_assert(attr_len >= 2); /* Should have been caught earlier */ /* * Arrays with fixed length members that * are a multiple of 4 don't need an * actual_len value, as we can get the * number of elements from the attribute * length. */ if (!parent->flags.length || (parent->flags.length % 4)) { actual_len = (p[0] << 8) | p[1]; if (actual_len > (attr_len - 2)) { fr_strerror_printf("%s: Actual length field value (%hu) > attribute value length (%zu)", __FUNCTION__, actual_len, attr_len - 2); return -1; } } else { actual_len = attr_len - 2; /* -2 for the reserved bytes */ } p += 2; /* * Zero length array */ if (!actual_len) return p - data; /* * Get the number of elements */ elements = sim_array_members(&element_len, actual_len, parent); if (elements < 0) return elements; for (i = 0; i < elements; i++) { rcode = sim_decode_pair_value(ctx, cursor, parent, p, element_len, end - p, decoder_ctx); if (rcode < 0) return rcode; p += rcode; if (!fr_cond_assert(p <= end)) break; } return attr_len; /* Say we consumed attr_len because it may have padding */ }
/* * Decode ONE value into a VP */ static ssize_t decode_value_internal(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *da, uint8_t const *data, size_t data_len) { VALUE_PAIR *vp; uint8_t const *p = data; FR_PROTO_TRACE("%s called to parse %zu bytes", __FUNCTION__, data_len); FR_PROTO_HEX_DUMP(data, data_len, NULL); vp = fr_pair_afrom_da(ctx, da); if (!vp) return -1; /* * Unknown attributes always get converted to * octet types, so there's no way there could * be multiple attributes, so its safe to * steal the unknown attribute into the context * of the pair. */ if (da->flags.is_unknown) talloc_steal(vp, da); if (vp->da->type == FR_TYPE_STRING) { uint8_t const *q, *end; q = end = data + data_len; /* * Not allowed to be an array, copy the whole value */ if (!vp->da->flags.array) { fr_pair_value_bstrncpy(vp, (char const *)p, end - p); p = end; goto finish; } for (;;) { q = memchr(p, '\0', q - p); /* Malformed but recoverable */ if (!q) q = end; fr_pair_value_bstrncpy(vp, (char const *)p, q - p); p = q + 1; vp->vp_tainted = true; /* Need another VP for the next round */ if (p < end) { fr_cursor_append(cursor, vp); vp = fr_pair_afrom_da(ctx, da); if (!vp) return -1; continue; } break; } goto finish; } switch (vp->da->type) { /* * Doesn't include scope, whereas the generic format can */ case FR_TYPE_IPV6_ADDR: memcpy(&vp->vp_ipv6addr, p, sizeof(vp->vp_ipv6addr)); vp->vp_ip.af = AF_INET6; vp->vp_ip.scope_id = 0; vp->vp_ip.prefix = 128; vp->vp_tainted = true; p += sizeof(vp->vp_ipv6addr); break; case FR_TYPE_IPV6_PREFIX: memcpy(&vp->vp_ipv6addr, p + 1, sizeof(vp->vp_ipv6addr)); vp->vp_ip.af = AF_INET6; vp->vp_ip.scope_id = 0; vp->vp_ip.prefix = p[0]; vp->vp_tainted = true; p += sizeof(vp->vp_ipv6addr) + 1; break; default: { ssize_t ret; ret = fr_value_box_from_network(vp, &vp->data, vp->da->type, da, p, data_len, true); if (ret < 0) { FR_PROTO_TRACE("decoding as unknown type"); if (fr_pair_to_unknown(vp) < 0) return -1; fr_pair_value_memcpy(vp, p, data_len); ret = data_len; } p += (size_t) ret; } } finish: FR_PROTO_TRACE("decoding value complete, adding new pair and returning %zu byte(s)", p - data); fr_cursor_append(cursor, vp); return p - data; }
/** Decode DHCP option * * @param[in] ctx context to alloc new attributes in. * @param[in,out] cursor Where to write the decoded options. * @param[in] dict to lookup attributes in. * @param[in] data to parse. * @param[in] data_len of data to parse. * @param[in] decoder_ctx Unused. */ ssize_t fr_dhcpv4_decode_option(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_t const *dict, uint8_t const *data, size_t data_len, UNUSED void *decoder_ctx) { ssize_t ret; uint8_t const *p = data; fr_dict_attr_t const *child; fr_dict_attr_t const *parent; FR_PROTO_TRACE("%s called to parse %zu byte(s)", __FUNCTION__, data_len); if (data_len == 0) return 0; FR_PROTO_HEX_DUMP(data, data_len, NULL); parent = fr_dict_root(dict); /* * Padding / End of options */ if (p[0] == 0) return 1; /* 0x00 - Padding option */ if (p[0] == 255) { /* 0xff - End of options signifier */ size_t i; for (i = 1; i < data_len; i++) { if (p[i] != 0) { FR_PROTO_HEX_DUMP(p + i, data_len - i, "ignoring trailing junk at end of packet"); break; } } return data_len; } /* * Everything else should be real options */ if ((data_len < 2) || (data[1] > data_len)) { fr_strerror_printf("%s: Insufficient data", __FUNCTION__); return -1; } child = fr_dict_attr_child_by_num(parent, p[0]); if (!child) { /* * Unknown attribute, create an octets type * attribute with the contents of the sub-option. */ child = fr_dict_unknown_afrom_fields(ctx, parent, fr_dict_vendor_num_by_da(parent), p[0]); if (!child) return -1; } FR_PROTO_TRACE("decode context changed %s:%s -> %s:%s", fr_int2str(fr_value_box_type_table, parent->type, "<invalid>"), parent->name, fr_int2str(fr_value_box_type_table, child->type, "<invalid>"), child->name); ret = decode_value(ctx, cursor, child, data + 2, data[1]); if (ret < 0) { fr_dict_unknown_free(&child); return ret; } ret += 2; /* For header */ FR_PROTO_TRACE("decoding option complete, returning %zu byte(s)", ret); return ret; }
/** Decode DHCP suboptions * * @param[in] ctx context to alloc new attributes in. * @param[in,out] cursor Where to write the decoded options. * @param[in] parent of sub TLVs. * @param[in] data to parse. * @param[in] data_len of data parsed. */ static ssize_t decode_tlv(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t data_len) { uint8_t const *p = data; uint8_t const *end = data + data_len; fr_dict_attr_t const *child; if (data_len < 3) return -1; /* type, length, value */ FR_PROTO_TRACE("%s called to parse %zu byte(s)", __FUNCTION__, data_len); FR_PROTO_HEX_DUMP(data, data_len, NULL); /* * Each TLV may contain multiple children */ while (p < end) { ssize_t tlv_len; if (p[0] == 0) { p++; continue; } /* * RFC 3046 is very specific about not allowing termination * with a 255 sub-option. But it's required for decoding * option 43, and vendors will probably screw it up * anyway. */ if (p[0] == 255) { p++; return p - data; } /* * Everything else should be real options */ if ((end - p) < 2) { fr_strerror_printf("%s: Insufficient data: Needed at least 2 bytes, got %zu", __FUNCTION__, (end - p)); return -1; } if (p[1] > (end - p)) { fr_strerror_printf("%s: Suboption would overflow option. Remaining option data %zu byte(s) " "(from %zu), Suboption length %u", __FUNCTION__, (end - p), data_len, p[1]); return -1; } child = fr_dict_attr_child_by_num(parent, p[0]); if (!child) { fr_dict_attr_t const *unknown_child; FR_PROTO_TRACE("failed to find child %u of TLV %s", p[0], parent->name); /* * Build an unknown attr */ unknown_child = fr_dict_unknown_afrom_fields(ctx, parent, fr_dict_vendor_num_by_da(parent), p[0]); if (!unknown_child) return -1; child = unknown_child; } FR_PROTO_TRACE("decode context changed %s:%s -> %s:%s", fr_int2str(fr_value_box_type_table, parent->type, "<invalid>"), parent->name, fr_int2str(fr_value_box_type_table, child->type, "<invalid>"), child->name); tlv_len = decode_value(ctx, cursor, child, p + 2, p[1]); if (tlv_len < 0) { fr_dict_unknown_free(&child); return tlv_len; } p += tlv_len + 2; FR_PROTO_TRACE("decode_value returned %zu, adding 2 (for header)", tlv_len); FR_PROTO_TRACE("remaining TLV data %zu byte(s)" , end - p); } FR_PROTO_TRACE("tlv parsing complete, returning %zu byte(s)", p - data); return p - data; }
/** Decode SIM/AKA/AKA' attributes * * @param[in] ctx to allocate attributes in. * @param[in] cursor where to insert the attributes. * @param[in] parent of current attribute being decoded. * @param[in] data data to parse. * @param[in] data_len length of data. For top level attributes packet_ctx must be the length * of the packet (so we can hunt for AT_IV), for Sub-TLVs it should * be the length of the container value. * @param[in] decoder_ctx extra context to pass to the decoder. * @return * - The number of bytes parsed. * - -1 on error. */ static ssize_t sim_decode_pair_internal(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t data_len, void *decoder_ctx) { uint8_t sim_at; size_t sim_at_len; ssize_t rcode; fr_dict_attr_t const *da; /* * We need at least 2 bytes. We really need 4 but it's * useful to print the attribute number in the errors. */ if (data_len < 2) { fr_strerror_printf("%s: Insufficient data: Expected >= 2 bytes, got %zu bytes", __FUNCTION__, data_len); return -1; } sim_at = data[0]; sim_at_len = ((size_t)data[1]) << 2; if (sim_at_len > data_len) { fr_strerror_printf("%s: Insufficient data for attribute %d: Length field %zu, remaining data %zu", __FUNCTION__, sim_at, sim_at_len, data_len); return -1; } if (sim_at_len == 0) { fr_strerror_printf("%s: Malformed attribute %d: Length field 0", __FUNCTION__, sim_at); return -1; } da = fr_dict_attr_child_by_num(parent, sim_at); if (!da) { FR_PROTO_TRACE("Unknown attribute %u", sim_at); /* * Encountered none skippable attribute * * RFC says we need to die on these if we don't * understand them. non-skippables are < 128. */ if (sim_at <= SIM_SKIPPABLE_MAX) { fr_strerror_printf("Unknown (non-skippable) attribute %i", sim_at); return -1; } da = fr_dict_unknown_afrom_fields(ctx, parent, 0, sim_at); } if (!da) return -1; FR_PROTO_TRACE("decode context changed %s -> %s", da->parent->name, da->name); if (da->flags.array) { rcode = sim_decode_array(ctx, cursor, da, data + 2, sim_at_len - 2, data_len - 2, decoder_ctx); } else { rcode = sim_decode_pair_value(ctx, cursor, da, data + 2, sim_at_len - 2, data_len - 2, decoder_ctx); } if (rcode < 0) return rcode; return 2 + rcode; }
/** Create any kind of VP from the attribute contents * * @param[in] ctx to allocate new attributes in. * @param[in] cursor to addd new attributes to. * @param[in] parent the current attribute we're processing. * @param[in] data to parse. Points to the data field of the attribute. * @param[in] attr_len length of the attribute being parsed. * @param[in] data_len length of the remaining data in the packet. * @param[in] decoder_ctx IVs, keys etc... * @return * - Length on success. * - -1 on failure. */ static ssize_t sim_decode_pair_value(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t const attr_len, size_t const data_len, void *decoder_ctx) { VALUE_PAIR *vp; uint8_t const *p = data; size_t prefix = 0; fr_sim_decode_ctx_t *packet_ctx = decoder_ctx; if (!fr_cond_assert(attr_len <= data_len)) return -1; if (!fr_cond_assert(parent)) return -1; FR_PROTO_TRACE("Parent %s len %zu", parent->name, attr_len); FR_PROTO_HEX_DUMP(data, attr_len, __FUNCTION__ ); FR_PROTO_TRACE("Type \"%s\" (%u)", fr_int2str(fr_value_box_type_table, parent->type, "?Unknown?"), parent->type); /* * Special cases, attributes that either have odd formats, or need * have information we need to decode the packet. */ switch (parent->attr) { /* * We need to record packet_ctx so we can decrypt AT_ENCR attributes. * * If we don't find it before, then that's fine, we'll try and * find it in the rest of the packet after the encrypted * attribute. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_IV | Length = 5 | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | Initialization Vector | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_SIM_IV: if (sim_iv_extract(&packet_ctx->iv[0], data, attr_len) < 0) return -1; packet_ctx->have_iv = true; break; /* Now create the attribute */ /* * AT_RES - Special case (RES length is in bits) * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_RES | Length | RES Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| * | | * | RES | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_EAP_AKA_RES: { uint16_t res_len; if (attr_len < 2) goto raw; /* Need at least two bytes for the length field */ res_len = (p[0] << 8) | p[1]; if (res_len % 8) { fr_strerror_printf("%s: RES Length (%hu) is not a multiple of 8", __FUNCTION__, res_len); return -1; } res_len /= 8; if (res_len > (attr_len - 2)) { fr_strerror_printf("%s: RES Length field value (%u bits) > attribute value length (%zu bits)", __FUNCTION__, res_len * 8, (attr_len - 2) * 8); return -1; } if ((res_len < 4) || (res_len > 16)) { fr_strerror_printf("%s: RES Length field value must be between 32-128 bits, got %u bits", __FUNCTION__, (res_len * 8)); return -1; } vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; fr_pair_value_memcpy(vp, p + 2, res_len, true); } goto done; /* * AT_CHECKCODE - Special case (Variable length with no length field) * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_CHECKCODE | Length | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | Checkcode (0 or 20 bytes) | * | | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_EAP_AKA_CHECKCODE: if (attr_len < 2) goto raw; /* Need at least two bytes for reserved field */ vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; fr_pair_value_memcpy(vp, p + 2, attr_len - 2, true); goto done; default: break; } switch (parent->type) { case FR_TYPE_STRING: if (attr_len < 2) goto raw; /* Need at least two bytes for the length field */ if (parent->flags.length && (attr_len != parent->flags.length)) { wrong_len: fr_strerror_printf("%s: Attribute \"%s\" needs a value of exactly %zu bytes, " "but value was %zu bytes", __FUNCTION__, parent->name, (size_t)parent->flags.length, attr_len); goto raw; } break; case FR_TYPE_OCTETS: /* * Get the number of bytes we expect before the value */ prefix = fr_sim_octets_prefix_len(parent); if (attr_len < prefix) goto raw; if (parent->flags.length && (attr_len != (parent->flags.length + prefix))) goto wrong_len; break; case FR_TYPE_BOOL: case FR_TYPE_UINT8: case FR_TYPE_UINT16: case FR_TYPE_UINT32: case FR_TYPE_UINT64: if (attr_len != fr_sim_attr_sizes[parent->type][0]) goto raw; break; case FR_TYPE_TLV: if (attr_len < 2) goto raw; /* * We presume that the TLVs all fit into one * attribute, OR they've already been grouped * into a contiguous memory buffer. */ return sim_decode_tlv(ctx, cursor, parent, p, attr_len, data_len, decoder_ctx); default: raw: /* * We can't create unknowns for non-skippable attributes * as we're prohibited from continuing by the SIM RFCs. */ if (parent->attr <= SIM_SKIPPABLE_MAX) { fr_strerror_printf_push("%s: Failed parsing non-skippable attribute '%s'", __FUNCTION__, parent->name); return -1; } #ifdef __clang_analyzer__ if (!parent->parent) return -1; /* stupid static analyzers */ #endif rad_assert(parent->parent); /* * Re-write the attribute to be "raw". It is * therefore of type "octets", and will be * handled below. */ parent = fr_dict_unknown_afrom_fields(ctx, parent->parent, fr_dict_vendor_num_by_da(parent), parent->attr); if (!parent) { fr_strerror_printf_push("%s[%d]: Internal sanity check failed", __FUNCTION__, __LINE__); return -1; } } vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; /* * For unknown attributes copy the entire value, not skipping * any reserved bytes. */ if (parent->flags.is_unknown || parent->flags.is_raw) { fr_pair_value_memcpy(vp, p, attr_len, true); vp->vp_length = attr_len; goto done; } switch (parent->type) { /* * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<STRING> | Length | Actual <STRING> Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * . String . * . . * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_TYPE_STRING: { uint16_t actual_len = (p[0] << 8) | p[1]; if (actual_len > (attr_len - 2)) { fr_strerror_printf("%s: Actual length field value (%hu) > attribute value length (%zu)", __FUNCTION__, actual_len, attr_len - 2); return -1; } fr_pair_value_bstrncpy(vp, p + 2, actual_len); } break; case FR_TYPE_OCTETS: /* * Variable length octets buffer */ if (!parent->flags.length) { uint16_t actual_len = (p[0] << 8) | p[1]; if (actual_len > (attr_len - prefix)) { fr_strerror_printf("%s: Actual length field value (%hu) > attribute value length (%zu)", __FUNCTION__, actual_len, attr_len - 2); return -1; } fr_pair_value_memcpy(vp, p + prefix, actual_len, true); /* * Fixed length octets buffer */ } else { fr_pair_value_memcpy(vp, p + prefix, attr_len - prefix, true); } break; /* * Not proper bool. We Use packet_ctx to represent * flag attributes like AT_FULLAUTH_ID_REQ * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<BOOL> | Length = 1 | Reserved | * +---------------+---------------+-------------------------------+ */ case FR_TYPE_BOOL: vp->vp_bool = true; break; /* * Numbers are network byte order. * * In the base RFCs only short (16bit) unsigned integers are used. * We add support for more, just for completeness. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<SHORT> | Length = 1 | Short 1 | Short 2 | * +---------------+---------------+-------------------------------+ */ case FR_TYPE_UINT8: vp->vp_uint8 = p[0]; break; case FR_TYPE_UINT16: memcpy(&vp->vp_uint16, p, sizeof(vp->vp_uint16)); vp->vp_uint16 = ntohs(vp->vp_uint32); break; case FR_TYPE_UINT32: memcpy(&vp->vp_uint32, p, sizeof(vp->vp_uint32)); vp->vp_uint32 = ntohl(vp->vp_uint32); break; case FR_TYPE_UINT64: memcpy(&vp->vp_uint64, p, sizeof(vp->vp_uint64)); vp->vp_uint64 = ntohll(vp->vp_uint64); break; default: fr_pair_list_free(&vp); fr_strerror_printf_push("%s[%d]: Internal sanity check failed", __FUNCTION__, __LINE__); return -1; } done: vp->type = VT_DATA; fr_cursor_append(cursor, vp); return attr_len; }
/** Break apart a TLV attribute into individual attributes * * @param[in] ctx to allocate new attributes in. * @param[in] cursor to addd new attributes to. * @param[in] parent the current attribute TLV attribute we're processing. * @param[in] data to parse. Points to the data field of the attribute. * @param[in] attr_len length of the TLV attribute. * @param[in] data_len remaining data in the packet. * @param[in] decoder_ctx IVs, keys etc... * @return * - Length on success. * - < 0 on malformed attribute. */ static ssize_t sim_decode_tlv(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t const attr_len, size_t data_len, void *decoder_ctx) { uint8_t const *p = data, *end = p + attr_len; uint8_t *decr = NULL; ssize_t decr_len; fr_dict_attr_t const *child; VALUE_PAIR *head = NULL; fr_cursor_t tlv_cursor; ssize_t rcode; if (data_len < 2) { fr_strerror_printf("%s: Insufficient data", __FUNCTION__); return -1; /* minimum attr size */ } /* * We have an AES-128-CBC encrypted attribute * * IV is from AT_IV, key is from k_encr. * * unfortunately the ordering of these two attributes * aren't specified, so we may have to hunt for the IV. */ if (parent->flags.encrypt) { FR_PROTO_TRACE("found encrypted attribute '%s'", parent->name); decr_len = sim_value_decrypt(ctx, &decr, p + 2, attr_len - 2, data_len - 2, decoder_ctx); /* Skip reserved */ if (decr_len < 0) return -1; p = decr; end = p + decr_len; } else { p += 2; /* Skip the reserved bytes */ } FR_PROTO_HEX_DUMP(p, end - p, "tlvs"); /* * Record where we were in the list when packet_ctx function was called */ fr_cursor_init(&tlv_cursor, &head); while ((size_t)(end - p) >= sizeof(uint32_t)) { uint8_t sim_at = p[0]; size_t sim_at_len = ((size_t)p[1]) << 2; if ((p + sim_at_len) > end) { fr_strerror_printf("%s: Malformed nested attribute %d: Length field (%zu bytes) value " "longer than remaining data in parent (%zu bytes)", __FUNCTION__, sim_at, sim_at_len, end - p); error: talloc_free(decr); fr_pair_list_free(&head); return -1; } if (sim_at_len == 0) { fr_strerror_printf("%s: Malformed nested attribute %d: Length field 0", __FUNCTION__, sim_at); goto error; } /* * Padding attributes are cleartext inside of * encrypted TLVs to pad out the value to the * correct length for the block cipher * (16 in the case of AES-128-CBC). */ if (sim_at == FR_SIM_PADDING) { uint8_t zero = 0; uint8_t i; if (!parent->flags.encrypt) { fr_strerror_printf("%s: Found padding attribute outside of an encrypted TLV", __FUNCTION__); goto error; } if (!fr_cond_assert(data_len % 4)) goto error; if (sim_at_len > 12) { fr_strerror_printf("%s: Expected padding attribute length <= 12 bytes, got %zu bytes", __FUNCTION__, sim_at_len); goto error; } /* * RFC says we MUST verify that FR_SIM_PADDING * data is zeroed out. */ for (i = 2; i < sim_at_len; i++) zero |= p[i]; if (zero) { fr_strerror_printf("%s: Padding attribute value not zeroed 0x%pH", __FUNCTION__, fr_box_octets(p + 2, sim_at_len - 2)); goto error; } p += sim_at_len; continue; } child = fr_dict_attr_child_by_num(parent, p[0]); if (!child) { fr_dict_attr_t const *unknown_child; FR_PROTO_TRACE("Failed to find child %u of TLV %s", p[0], parent->name); /* * Encountered none skippable attribute * * RFC says we need to die on these if we don't * understand them. non-skippables are < 128. */ if (sim_at <= SIM_SKIPPABLE_MAX) { fr_strerror_printf("%s: Unknown (non-skippable) attribute %i", __FUNCTION__, sim_at); goto error; } /* * Build an unknown attr */ unknown_child = fr_dict_unknown_afrom_fields(ctx, parent, fr_dict_vendor_num_by_da(parent), p[0]); if (!unknown_child) goto error; child = unknown_child; } FR_PROTO_TRACE("decode context changed %s -> %s", parent->name, child->name); rcode = sim_decode_pair_value(ctx, &tlv_cursor, child, p + 2, sim_at_len - 2, (end - p) - 2, decoder_ctx); if (rcode < 0) goto error; p += sim_at_len; } fr_cursor_head(&tlv_cursor); fr_cursor_tail(cursor); fr_cursor_merge(cursor, &tlv_cursor); /* Wind to the end of the new pairs */ talloc_free(decr); return attr_len; }
/** Decrypt an AES-128-CBC encrypted attribute * * @param[in] ctx to allocate decr buffer in. * @param[out] out where to write pointer to decr buffer. * @param[in] data to decrypt. * @param[in] attr_len length of encrypted data. * @param[in] data_len length of data remaining in the packet. * @param[in] decoder_ctx containing keys, and the IV (if we already found it). * @return * - Number of decr bytes decrypted on success. * - < 0 on failure. */ static ssize_t sim_value_decrypt(TALLOC_CTX *ctx, uint8_t **out, uint8_t const *data, size_t const attr_len, size_t const data_len, void *decoder_ctx) { fr_sim_decode_ctx_t *packet_ctx = decoder_ctx; EVP_CIPHER_CTX *evp_ctx; EVP_CIPHER const *evp_cipher = EVP_aes_128_cbc(); size_t block_size = EVP_CIPHER_block_size(evp_cipher); size_t len = 0, decr_len = 0; uint8_t *decr = NULL; if (!fr_cond_assert(attr_len <= data_len)) return -1; FR_PROTO_HEX_DUMP(data, attr_len, "ciphertext"); /* * Encrypted values must be a multiple of 16. * * There's a padding attribute to ensure they * always can be... */ if (attr_len % block_size) { fr_strerror_printf("%s: Encrypted attribute is not a multiple of cipher's block size (%zu)", __FUNCTION__, block_size); return -1; } /* * Ugh, now we have to go hunting for it.... */ if (!packet_ctx->have_iv) { uint8_t const *p = data + attr_len; /* Skip to the end of packet_ctx attribute */ uint8_t const *end = data + data_len; while ((size_t)(end - p) >= sizeof(uint32_t)) { uint8_t sim_at = p[0]; size_t sim_at_len = p[1] * sizeof(uint32_t); if (sim_at_len == 0) { fr_strerror_printf("%s: Failed IV search. AT Length field is zero", __FUNCTION__); return -1; } if ((p + sim_at_len) > end) { fr_strerror_printf("%s: Invalid IV length, longer than remaining data", __FUNCTION__); return -1; } if (sim_at == FR_SIM_IV) { if (sim_iv_extract(&(packet_ctx->iv[0]), p + 2, sim_at_len - 2) < 0) return -1; packet_ctx->have_iv = true; break; } p += sim_at_len; } if (!packet_ctx->have_iv) { fr_strerror_printf("%s: No IV present in packet, can't decrypt data", __FUNCTION__); return -1; } } evp_ctx = EVP_CIPHER_CTX_new(); if (!evp_ctx) { tls_strerror_printf("%s: Failed initialising EVP ctx", __FUNCTION__); return -1; } if (!EVP_DecryptInit_ex(evp_ctx, evp_cipher, NULL, packet_ctx->keys->k_encr, packet_ctx->iv)) { tls_strerror_printf("%s: Failed setting decryption parameters", __FUNCTION__); error: talloc_free(decr); EVP_CIPHER_CTX_free(evp_ctx); return -1; } MEM(decr = talloc_zero_array(ctx, uint8_t, attr_len)); /* * By default OpenSSL expects 16 bytes of cleartext * to produce 32 bytes of ciphertext, due to padding * being added if the decr is a multiple of 16. * * There's no way for OpenSSL to determine if a * 16 byte ciphertext was padded or not, so we need to * inform OpenSSL explicitly that there's no padding. */ EVP_CIPHER_CTX_set_padding(evp_ctx, 0); if (!EVP_DecryptUpdate(evp_ctx, decr, (int *)&len, data, attr_len)) { tls_strerror_printf("%s: Failed decrypting attribute", __FUNCTION__); goto error; } decr_len = len; if (!EVP_DecryptFinal_ex(evp_ctx, decr + decr_len, (int *)&len)) { tls_strerror_printf("%s: Failed decrypting attribute", __FUNCTION__); goto error; } decr_len += len; EVP_CIPHER_CTX_free(evp_ctx); /* * Note: packet_ctx implicitly validates the length of the padding * attribute (if present), so we don't have to do it later. */ if (decr_len % block_size) { fr_strerror_printf("%s: Expected decrypted value length to be multiple of %zu, got %zu", __FUNCTION__, block_size, decr_len); goto error; } /* * Ciphertext should be same length as plaintext. */ if (unlikely(attr_len != decr_len)) { fr_strerror_printf("%s: Invalid plaintext length, expected %zu, got %zu", __FUNCTION__, attr_len, decr_len); goto error; } FR_PROTO_TRACE("decryption successful, got %zu bytes of cleartext", decr_len); FR_PROTO_HEX_DUMP(decr, decr_len, "cleartext"); *out = decr; return decr_len; }