--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/der/decode.c
+ * @brief Functions to decode DER encoded data.
+ *
+ * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @author Ethan Thompson (ethan.thompson@inkbridge.io)
+ *
+ * @copyright 2025 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @copyright 2025 Network RADIUS SAS (legal@networkradius.com)
+ */
+
+#include <freeradius-devel/io/test_point.h>
+#include <freeradius-devel/util/dbuff.h>
+#include <freeradius-devel/util/decode.h>
+#include <freeradius-devel/util/dict.h>
+#include <freeradius-devel/util/pair.h>
+#include <freeradius-devel/util/proto.h>
+#include <freeradius-devel/util/sbuff.h>
+#include <freeradius-devel/util/struct.h>
+#include <freeradius-devel/util/time.h>
+#include <freeradius-devel/util/dict_ext.h>
+
+#include "der.h"
+
+typedef struct {
+ uint8_t *tmp_ctx;
+ bool oid_value_pairs;
+} fr_der_decode_ctx_t;
+
+#define IS_DER_TAG_CONTINUATION(_tag) (((_tag) & DER_TAG_CONTINUATION) == DER_TAG_CONTINUATION)
+#define IS_DER_TAG_CONSTRUCTED(_tag) (((_tag) & 0x20) == 0x20)
+#define IS_DER_LEN_MULTI_BYTE(_len) (((_len) & DER_LEN_MULTI_BYTE) == DER_LEN_MULTI_BYTE)
+
+typedef ssize_t (*fr_der_decode_oid_t)(uint64_t subidentifier, void *uctx, bool is_last);
+
+static ssize_t fr_der_decode_oid(fr_pair_list_t *out, fr_dbuff_t *in, fr_der_decode_oid_t func, void *uctx) CC_HINT(nonnull(2,3,4));
+
+static ssize_t fr_der_decode_oid_value_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dbuff_t *in,
+ fr_dict_attr_t const *parent, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_hdr(fr_dict_attr_t const *parent, fr_dbuff_t *in, uint64_t *tag, size_t *len) CC_HINT(nonnull(2,3,4));
+
+typedef ssize_t (*fr_der_decode_t)(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ fr_der_decode_ctx_t *decode_ctx);
+
+typedef struct {
+ fr_der_tag_constructed_t constructed;
+ fr_der_decode_t decode;
+} fr_der_tag_decode_t;
+
+/** Function signature for DER decode functions
+ *
+ * @param[in] ctx Allocation context
+ * @param[in] out Where to store the decoded pairs.
+ * @param[in] parent Parent attribute. This should be the root of the dictionary
+ * we're using to decode DER data initially, and then nested children.
+ * @param[in] in The DER encoded data.
+ * @param[in] decode_ctx Any decode specific data.
+ * @return
+ * - > 0 on success. How many bytes were decoded.
+ * - 0 no bytes decoded.
+ * - < 0 on error. May be the offset (as a negative value) where the error occurred.
+ */
+static ssize_t fr_der_decode_pair_dbuff(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_boolean(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_integer(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_bitstring(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_octetstring(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_null(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_enumerated(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_utf8_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_sequence(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_set(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_printable_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_t61_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_ia5_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_utc_time(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_generalized_time(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_visible_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_general_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static ssize_t fr_der_decode_universal_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull);
+
+static fr_der_tag_decode_t tag_funcs[] = {
+ [FR_DER_TAG_BOOLEAN] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_boolean },
+ [FR_DER_TAG_INTEGER] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_integer },
+ [FR_DER_TAG_BITSTRING] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_bitstring },
+ [FR_DER_TAG_OCTETSTRING] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_octetstring },
+ [FR_DER_TAG_NULL] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_null },
+ [FR_DER_TAG_ENUMERATED] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_enumerated },
+ [FR_DER_TAG_UTF8_STRING] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_utf8_string },
+ [FR_DER_TAG_SEQUENCE] = { .constructed = FR_DER_TAG_CONSTRUCTED, .decode = fr_der_decode_sequence },
+ [FR_DER_TAG_SET] = { .constructed = FR_DER_TAG_CONSTRUCTED, .decode = fr_der_decode_set },
+ [FR_DER_TAG_PRINTABLE_STRING] = { .constructed = FR_DER_TAG_PRIMITIVE,
+ .decode = fr_der_decode_printable_string },
+ [FR_DER_TAG_T61_STRING] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_t61_string },
+ [FR_DER_TAG_IA5_STRING] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_ia5_string },
+ [FR_DER_TAG_UTC_TIME] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_utc_time },
+ [FR_DER_TAG_GENERALIZED_TIME] = { .constructed = FR_DER_TAG_PRIMITIVE,
+ .decode = fr_der_decode_generalized_time },
+ [FR_DER_TAG_VISIBLE_STRING] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_visible_string },
+ [FR_DER_TAG_GENERAL_STRING] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_general_string },
+ [FR_DER_TAG_UNIVERSAL_STRING] = { .constructed = FR_DER_TAG_PRIMITIVE,
+ .decode = fr_der_decode_universal_string },
+
+ [UINT8_MAX] = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = NULL },
+};
+
+static ssize_t fr_der_decode_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ bool const allowed_chars[], fr_der_decode_ctx_t *decode_ctx) CC_HINT(nonnull(1,2,3,4,6));
+
+static ssize_t fr_der_decode_boolean(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ uint8_t value = 0;
+
+ size_t len = fr_dbuff_remaining(&our_in);
+
+ if (!fr_type_is_bool(parent->type)) {
+ fr_strerror_printf("Boolean found in non-boolean attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.2 Encoding of a boolean value
+ * 8.2.1 The encoding of a boolean value shall be primitive.
+ * The contents octets shall consist of a single octet.
+ * 8.2.2 If the boolean value is:
+ * FALSE the octet shall be zero [0x00].
+ * If the boolean value is TRUE the octet shall have any non-zero value, as a sender's option.
+ *
+ * 11.1 Boolean values
+ * If the encoding represents the boolean value TRUE, its single contents octet shall have all
+ * eight bits set to one [0xff]. (Contrast with 8.2.2.)
+ */
+ if (len != 1) {
+ fr_strerror_printf("Boolean has incorrect length (%zu). Must be 1.", len);
+ return -1;
+ }
+
+ FR_DBUFF_OUT_RETURN(&value, &our_in);
+
+ if (unlikely((value != DER_BOOLEAN_FALSE) && (value != DER_BOOLEAN_TRUE))) {
+ fr_strerror_printf("Boolean is not correctly DER encoded (0x%02" PRIx32 " or 0x%02" PRIx32 ").", DER_BOOLEAN_FALSE,
+ DER_BOOLEAN_TRUE);
+ return -1;
+ }
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(vp == NULL)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ vp->vp_bool = value > 0;
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_integer(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ uint64_t value = 0;
+ uint8_t sign = 0;
+ size_t i;
+
+ size_t len = fr_dbuff_remaining(&our_in);
+
+ if (parent->type != FR_TYPE_INT64) {
+ fr_strerror_printf("Expected parent type 'int64', got attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ if (len > sizeof(value)) {
+ fr_strerror_printf("Integer too large (%zu)", len);
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.3 Encoding of an integer value
+ * 8.3.1 The encoding of an integer value shall be primitive.
+ * The contents octets shall consist of one or more octets.
+ * 8.3.2 If the contents octets of an integer value encoding consist of more than one octet,
+ * then the bits of the first octet and bit 8 of the second octet:
+ * a) shall not all be ones; and
+ * b) shall not all be zero.
+ * NOTE - These rules ensure that an integer value is always encoded in the smallest possible number
+ * of octets. 8.3.3 The contents octets shall be a two's complement binary number equal to the
+ * integer value, and consisting of bits 8 to 1 of the first octet, followed by bits 8 to 1 of the
+ * second octet, followed by bits 8 to 1 of each octet in turn up to and including the last octet of
+ * the contents octets.
+ */
+ FR_DBUFF_OUT_RETURN(&sign, &our_in);
+
+ if (sign & 0x80) {
+ /*
+ * If the sign bit is set, this is a negative number.
+ * This will fill the upper bits with 1s.
+ * This is important for the case where the length of the integer is less than the length of the
+ * integer type.
+ */
+ value = UINT64_MAX;
+ }
+
+ value = (value << 8) | sign;
+
+ if (len > 1) {
+ /*
+ * If the length of the integer is greater than 1, we need to check that the first 9 bits:
+ * 1. are not all 0s; and
+ * 2. are not all 1s
+ * These two conditions are necessary to ensure that the integer conforms to DER.
+ */
+ uint8_t byte;
+
+ FR_DBUFF_OUT_RETURN(&byte, &our_in);
+
+ if ((((value & 0xff) == 0xff) && (byte & 0x80)) || (((~value & 0xff) == 0xff) && !(byte & 0x80))) {
+ fr_strerror_const("Integer is not correctly DER encoded. First two bytes are all 0s or all 1s.");
+ return -1;
+ }
+
+ value = (value << 8) | byte;
+ }
+
+ for (i = 2; i < len; i++) {
+ uint8_t byte;
+
+ FR_DBUFF_OUT_RETURN(&byte, &our_in);
+ value = (value << 8) | byte;
+ }
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(vp == NULL)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ vp->vp_int64 = value;
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_bitstring(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ uint8_t unused_bits = 0;
+ uint8_t *data;
+
+ ssize_t data_len = 0, index = 0;
+ size_t len = fr_dbuff_remaining(&our_in);
+
+ if (!fr_type_is_octets(parent->type) && !fr_type_is_struct(parent->type)) {
+ fr_strerror_printf("Bitstring found in non-octets attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * Now we know that the parent is an octets attribute, we can decode the bitstring
+ */
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.6 Encoding of a bitstring value
+ * 8.6.1 The encoding of a bitstring value shall be either primitive or constructed at the option
+ * of the sender.
+ * NOTE - Where it is necessary to transfer part of a bit string before the entire
+ * bitstring is available, the constructed encoding is used.
+ * 8.6.2 The contents octets for the primitive encoding shall contain an initial octet followed
+ * by zero, one or more subsequent octets.
+ * 8.6.2.1 The bits in the bitstring value, commencing with the leading bit and proceeding
+ * to the trailing bit, shall be placed in bits 8 to 1 of the first subsequent
+ * octet, followed by bits 8 to 1 of the second subsequent octet, followed by bits
+ * 8 to 1 of each octet in turn, followed by as many bits as are needed of the
+ * final subsequent octet, commencing with bit 8.
+ * NOTE - The terms "leading bit" and "trailing bit" are defined in
+ * Rec. ITU-T X.680 | ISO/IEC 8824-1, 22.2.
+ * 8.6.2.2 The initial octet shall encode, as an unsigned binary integer with bit 1 as the
+ * least significant bit, the number of unused bits in the final subsequent octet.
+ * The number shall be in the range zero to seven.
+ * 8.6.2.3 If the bitstring is empty, there shall be no subsequent octets, and the initial
+ * octet shall be zero.
+ *
+ * 10.2 String encoding forms
+ * For bitstring, octetstring and restricted character string types, the constructed form of
+ * encoding shall not be used. (Contrast with 8.23.6.)
+ *
+ * 11.2 Unused bits 11.2.1 Each unused bit in the final octet of the encoding of a bit string value shall
+ * be set to zero.
+ */
+
+ FR_DBUFF_OUT_RETURN(&unused_bits, &our_in);
+
+ if (unlikely(unused_bits > 7)) {
+ /*
+ * This means an entire byte is unused bits. Which is not allowed.
+ */
+ fr_strerror_const("Invalid number of unused bits in bitstring");
+ return -1;
+ }
+
+ if ((len == 1) && unused_bits) {
+ fr_strerror_const("Insufficient data for bitstring. Missing data bytes");
+ return -1;
+ }
+
+ if (fr_type_is_struct(parent->type)) {
+ /*
+ * If the parent is a struct attribute, we will not be adding the unused bits count to the first
+ * byte
+ */
+ data_len = len - 1;
+ } else {
+ data_len = len;
+ }
+
+ data = talloc_array(decode_ctx->tmp_ctx, uint8_t, data_len);
+ if (unlikely(!data)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ if (fr_type_is_octets(parent->type)) {
+ /*
+ * If the parent is an octets attribute, we need to add the unused bits count to the first byte
+ */
+ index = 1;
+ data[0] = unused_bits;
+ }
+
+ for (; index < data_len; index++) {
+ uint8_t byte;
+
+ FR_DBUFF_OUT_RETURN(&byte, &our_in);
+
+ data[index] = byte;
+ }
+
+ /*
+ * Remove the unused bits from the last byte
+ */
+ if (unused_bits) {
+ uint8_t mask = 0xff << unused_bits;
+
+ data[data_len - 1] &= mask;
+ }
+
+ if (fr_type_is_struct(parent->type)) {
+ ssize_t slen;
+
+ slen = fr_struct_from_network(ctx, out, parent, data, data_len, decode_ctx, NULL, NULL);
+
+ /*
+ * If the structure decoder didn't consume all the data, we need to free the data and bail out
+ */
+ if (unlikely(slen < data_len - 1)) {
+ fr_strerror_printf("Bitstring structure decoder didn't consume all data. Consumed %zd of %zu bytes",
+ slen, data_len);
+ error:
+ talloc_free(data);
+ return -1;
+ }
+
+ talloc_free(data);
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ fr_strerror_const("Out of memory");
+ goto error;
+ }
+
+ /*
+ * Add the bitstring to the pair value as octets
+ */
+ fr_pair_value_memdup(vp, data, len, false);
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_octetstring(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ uint8_t *data = NULL;
+
+ size_t len = fr_dbuff_remaining(&our_in);
+
+ if (!fr_type_is_octets(parent->type)) {
+ fr_strerror_printf("Octetstring found in non-octets attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.7 Encoding of an octetstring value
+ * 8.7.1 The encoding of an octetstring value shall be either primitive or constructed at the
+ * option of the sender.
+ * NOTE - Where it is necessary to transfer part of an octet string before the entire
+ * octetstring is available, the constructed encoding is used.
+ * 8.7.2 The primitive encoding contains zero, one or more contents octets equal in value to the
+ * octets in the data value, in the order they appear in the data value, and with the most
+ * significant bit of an octet of the data value aligned with the most significant bit of an
+ * octet of the contents octets.
+ * 8.7.3 The contents octets for the constructed encoding shall consist of zero, one, or more
+ * encodings.
+ * NOTE - Each such encoding includes identifier, length, and contents octets, and may
+ * include end-of-contents octets if it is constructed.
+ * 8.7.3.1 To encode an octetstring value in this way, it is segmented. Each segment shall
+ * consist of a series of consecutive octets of the value. There shall be no
+ * significance placed on the segment boundaries.
+ * NOTE - A segment may be of size zero, i.e. contain no octets.
+ *
+ * 10.2 String encoding forms
+ * For bitstring, octetstring and restricted character string types, the constructed form of
+ * encoding shall not be used. (Contrast with 8.23.6.)
+ */
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ oom:
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ if (unlikely(fr_pair_value_mem_alloc(vp, &data, len, false) < 0)) {
+ talloc_free(vp);
+ goto oom;
+ }
+
+ fr_dbuff_out_memcpy(data, &our_in, len);
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_null(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+
+ if (fr_dbuff_remaining(&our_in) != 0) {
+ fr_strerror_const("Null has non-zero length");
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.8 Encoding of a null value 8.8.1 The encoding of a null value shall be primitive. 8.8.2 The contents
+ * octets shall not contain any octets. NOTE - The length octet is zero.
+ */
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+typedef struct {
+ TALLOC_CTX *ctx; //!< Allocation context
+ fr_dict_attr_t const *parent_da; //!< Parent dictionary attribute
+ fr_pair_list_t *parent_list; //!< Parent pair list
+ char oid_buff[1024]; //!< Buffer to store the OID string
+ fr_sbuff_marker_t marker; //!< Marker of the current position in the OID buffer
+} fr_der_decode_oid_to_str_ctx_t; //!< Context for decoding an OID to a string
+
+/** Decode an OID to a string
+ *
+ * @param[in] subidentifier The subidentifier to decode
+ * @param[in] uctx User context
+ * @param[in] is_last Is this the last subidentifier in the OID
+ * @return
+ * - 1 on success
+ * - < 0 on error
+ */
+static ssize_t fr_der_decode_oid_to_str(uint64_t subidentifier, void *uctx, bool is_last)
+{
+ fr_der_decode_oid_to_str_ctx_t *decode_ctx = uctx;
+ fr_sbuff_marker_t marker = decode_ctx->marker;
+ fr_sbuff_t sb = FR_SBUFF_OUT(decode_ctx->oid_buff, sizeof(decode_ctx->oid_buff));
+
+ FR_PROTO_TRACE("Decoding OID to string");
+ if (decode_ctx->oid_buff[0] == '\0') {
+ /*
+ * First subidentifier
+ */
+ if (unlikely(fr_sbuff_in_sprintf(&sb, "%" PRIu64, subidentifier) < 0)) {
+ oom:
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ fr_sbuff_marker(&marker, &sb);
+
+ decode_ctx->marker = marker;
+ return 1;
+ }
+
+ fr_sbuff_set(&sb, &marker);
+
+ fr_sbuff_in_sprintf(&sb, ".%" PRIu64, subidentifier);
+ fr_sbuff_marker(&marker, &sb);
+
+ decode_ctx->marker = marker;
+
+ if (is_last) {
+ /*
+ * If this is the last subidentifier, we need to terminate the string,
+ * create a vp with the oid string, and add it to the parent list
+ */
+ fr_pair_t *vp;
+
+ vp = fr_pair_afrom_da(decode_ctx->ctx, decode_ctx->parent_da);
+ if (unlikely(!vp)) goto oom;
+
+ if (unlikely(!fr_type_is_string(vp->da->type))) {
+ fr_strerror_printf("OID found in non-string attribute %s of type %s", vp->da->name,
+ fr_type_to_str(vp->da->type));
+ talloc_free(vp);
+ return -1;
+ }
+
+ fr_sbuff_terminate(&sb);
+
+ fr_pair_value_strdup(vp, decode_ctx->oid_buff, false);
+
+ fr_pair_append(decode_ctx->parent_list, vp);
+
+ decode_ctx->ctx = vp;
+ }
+
+ return 1;
+}
+
+typedef struct {
+ TALLOC_CTX *ctx; //!< Allocation context
+ fr_dict_attr_t const *parent_da; //!< Parent dictionary attribute
+ fr_pair_list_t *parent_list; //!< Parent pair list
+} fr_der_decode_oid_to_da_ctx_t; //!< Context for decoding an OID to a dictionary attribute
+
+/** Decode an OID to a dictionary attribute
+ *
+ * @param[in] subidentifier The subidentifier to decode
+ * @param[in] uctx User context
+ * @param[in] is_last Is this the last subidentifier in the OID
+ * @return
+ * - 1 on success
+ * - < 0 on error
+ */
+static ssize_t fr_der_decode_oid_to_da(uint64_t subidentifier, void *uctx, bool is_last)
+{
+ fr_der_decode_oid_to_da_ctx_t *decode_ctx = uctx;
+ fr_pair_t *vp;
+ fr_dict_attr_t const *da;
+
+ fr_dict_attr_t const *parent_da = fr_type_is_group(decode_ctx->parent_da->type) ?
+ fr_dict_attr_ref(decode_ctx->parent_da) :
+ decode_ctx->parent_da;
+
+ FR_PROTO_TRACE("Decoding OID to dictionary attribute");
+ FR_PROTO_TRACE("decode context - Parent Name: %s Sub-Identifier %" PRIu64, parent_da->name, subidentifier);
+ FR_PROTO_TRACE("decode context - Parent Address: %p", parent_da);
+
+ da = fr_dict_attr_child_by_num(parent_da, subidentifier);
+
+ if (is_last) {
+ if (unlikely(da == NULL)) {
+ decode_ctx->parent_da = fr_dict_attr_unknown_typed_afrom_num(decode_ctx->ctx, parent_da,
+ subidentifier, FR_TYPE_OCTETS);
+
+ if (unlikely(decode_ctx->parent_da == NULL)) {
+ return -1;
+ }
+
+ FR_PROTO_TRACE("Created DA: ", decode_ctx->parent_da->name);
+ return 1;
+ }
+
+ decode_ctx->parent_da = da;
+
+ FR_PROTO_TRACE("Created DA: ", decode_ctx->parent_da->name);
+ return 1;
+ }
+
+ if (unlikely(da == NULL)) {
+ /*
+ * We need to create an unknown attribute for this subidentifier so we can store the raw data
+ */
+ fr_dict_attr_t *unknown_da =
+ fr_dict_attr_unknown_typed_afrom_num(decode_ctx->ctx, parent_da, subidentifier, FR_TYPE_TLV);
+
+ if (unlikely(unknown_da == NULL)) {
+ oom:
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ vp = fr_pair_afrom_da(decode_ctx->ctx, unknown_da);
+
+ talloc_free(unknown_da);
+ } else {
+ vp = fr_pair_afrom_da(decode_ctx->ctx, da);
+ }
+
+ if (unlikely(!vp)) goto oom;
+
+ fr_pair_append(decode_ctx->parent_list, vp);
+
+ decode_ctx->ctx = vp;
+ decode_ctx->parent_da = vp->da;
+ decode_ctx->parent_list = &vp->vp_group;
+
+ FR_PROTO_TRACE("Created DA: ", decode_ctx->parent_da->name);
+ return 1;
+}
+
+/** Decode an OID from a DER encoded buffer using a callback
+ *
+ * @param[in] out The pair list to add the OID to.
+ * @param[in] in The DER encoded data.
+ * @param[in] func The callback function to call for each subidentifier.
+ * @param[in] uctx User context for the callback function.
+ * @return
+ * - 0 on success
+ * - < 0 on error
+ */
+static ssize_t fr_der_decode_oid(UNUSED fr_pair_list_t *out, fr_dbuff_t *in, fr_der_decode_oid_t func, void *uctx)
+{
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ uint64_t oid_a = 0;
+ uint64_t oid_b = 0;
+ bool is_last = false;
+
+ size_t index, magnitude = 1;
+ size_t len = fr_dbuff_remaining(&our_in);
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.19 Encoding of an object identifier value
+ * 8.19.1 The encoding of an object identifier value shall be primitive.
+ * 8.19.2 The contents octets shall be an (ordered) list of encodings of subidentifiers (see 8.19.3
+ * and 8.19.4) concatenated together. Each subidentifier is represented as a series of
+ * (one or more) octets. Bit 8 of each octet indicates whether it is the last in the series: bit 8
+ * of the last octet is zero; bit 8 of each preceding octet is one. Bits 7 to 1 of the octets in
+ * the series collectively encode the subidentifier. Conceptually, these groups of bits are
+ * concatenated to form an unsigned binary number whose most significant bit is bit 7 of the first
+ * octet and whose least significant bit is bit 1 of the last octet. The subidentifier shall be
+ * encoded in the fewest possible octets, that is, the leading octet of the subidentifier shall not
+ * have the value 8016.
+ * 8.19.3 The number of subidentifiers (N) shall be one less than the number of object identifier
+ * components in the object identifier value being encoded. 8.19.4 The numerical value of the
+ * first subidentifier is derived from the values of the first two object identifier components in
+ * the object identifier value being encoded, using the formula: (X*40) + Y where X is the value
+ * of the first object identifier component and Y is the value of the second object identifier
+ * component. NOTE - This packing of the first two object identifier components recognizes that
+ * only three values are allocated from the root node, and at most 39 subsequent values from nodes
+ * reached by X = 0 and X = 1. 8.19.5 The numerical value of the ith subidentifier, (2 <= i <= N) is
+ * that of the (i + 1)th object identifier component.
+ */
+
+ FR_PROTO_TRACE("Decoding OID");
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&our_in), len, "buff in OID");
+ /*
+ * The first subidentifier is the encoding of the first two object identifier components, encoded as:
+ * (X * 40) + Y
+ * where X is the first number and Y is the second number.
+ * The first number is 0, 1, or 2.
+ */
+ for (index = 1; index < len; index++) {
+ uint8_t byte;
+
+ FR_DBUFF_OUT_RETURN(&byte, &our_in);
+
+ oid_b = (oid_b << 7) | (byte & 0x7f);
+
+ if (!(byte & 0x80)) {
+ /*
+ * If the high bit is not set, this is the last byte of the subidentifier
+ */
+ if (oid_b < 40) {
+ oid_a = 0;
+ } else if (oid_b < 80) {
+ oid_a = 1;
+ oid_b = oid_b - 40;
+ } else {
+ oid_a = 2;
+ oid_b = oid_b - 80;
+ }
+
+ magnitude = 1;
+ break;
+ }
+
+ magnitude++;
+
+ /*
+ * We need to check that the subidentifier is not too large
+ * Since the subidentifier is encoded using 7-bit "chunks", we can't have a subidentifier larger
+ * than 9 chunks
+ */
+ if (unlikely(magnitude > 9)) {
+ fr_strerror_const("OID subidentifier too large (9 chunks)");
+ return -1;
+ }
+ }
+
+ FR_PROTO_TRACE("decode context - OID A: %" PRIu64, oid_a);
+ FR_PROTO_TRACE("decode context - OID B: %" PRIu64, oid_b);
+
+ if (unlikely(func(oid_a, uctx, is_last) < 0)) return -1;
+
+ if (index == len) is_last = true;
+
+ if (unlikely(func(oid_b, uctx, is_last) < 0)) return -1;
+
+ /*
+ * The remaining subidentifiers are encoded individually
+ */
+ oid_b = 0;
+ for (; index < len; index++) {
+ uint8_t byte;
+
+ FR_DBUFF_OUT_RETURN(&byte, &our_in);
+
+ oid_b = (oid_b << 7) | (byte & 0x7f);
+
+ if (!(byte & 0x80)) {
+ if (index == len - 1) is_last = true;
+
+ if (unlikely(func(oid_b, uctx, is_last) < 0)) return -1;
+
+ oid_b = 0;
+ magnitude = 1;
+ continue;
+ }
+
+ magnitude++;
+
+ /*
+ * We need to check that the subidentifier is not too large
+ * Since the subidentifier is encoded using 7-bit "chunks", we can't have a subidentifier larger
+ * than 9 chunks
+ */
+ if (unlikely(magnitude > 9)) {
+ fr_strerror_const("OID subidentifier too large (9 chunks)");
+ return -1;
+ }
+ }
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_enumerated(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx)
+{
+ return fr_der_decode_integer(ctx, out, parent, in, decode_ctx);
+}
+
+static ssize_t fr_der_decode_utf8_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx)
+{
+ return fr_der_decode_string(ctx, out, parent, in, NULL, decode_ctx);
+}
+
+static ssize_t fr_der_decode_sequence(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dict_attr_t const *child = NULL;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+
+ if (!fr_type_is_struct(parent->type) && !fr_type_is_tlv(parent->type) && !fr_type_is_group(parent->type)) {
+ fr_strerror_printf("Sequence found in incompatible attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.9 Encoding of a sequence value
+ * 8.9.1 The encoding of a sequence value shall be constructed.
+ * 8.9.2 The contents octets shall consist of the complete encoding of one data value from each of
+ * the types listed in the ASN.1 definition of the sequence type, in the order of their
+ * appearance in the definition, unless the type was referenced with the keyword OPTIONAL
+ * or the keyword DEFAULT.
+ * 8.9.3 The encoding of a data value may, but need not, be present for a type referenced with the
+ * keyword OPTIONAL or the keyword DEFAULT. If present, it shall appear in the order of
+ * appearance of the corresponding type in the ASN.1 definition.
+ *
+ * 11.5 Set and sequence components with default value
+ * The encoding of a set value or sequence value shall not include an encoding for any component
+ * value which is equal to its default value.
+ */
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ if (unlikely(fr_der_flag_is_pair(parent))) {
+ /*
+ * This sequence contains an oid value pair
+ */
+ if (unlikely(!fr_type_is_group(parent->type))) {
+ fr_strerror_printf("Sequence with pair found in incompatible attribute %s of type %s",
+ parent->name, fr_type_to_str(parent->type));
+ talloc_free(vp);
+ return -1;
+ }
+
+ if (unlikely(fr_der_decode_oid_value_pair(vp, &vp->vp_group, &our_in, vp->da, decode_ctx) < 0)) {
+ talloc_free(vp);
+ return -1;
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ if (unlikely(fr_der_flag_is_pairs(parent) || decode_ctx->oid_value_pairs)) {
+ /*
+ * This sequence contains sequences/sets of pairs
+ */
+ bool old = decode_ctx->oid_value_pairs;
+ decode_ctx->oid_value_pairs = true;
+ while (fr_dbuff_remaining(&our_in) > 0) {
+ child = NULL;
+ child = fr_dict_attr_iterate_children(parent, &child);
+
+ FR_PROTO_TRACE("decode context %s -> %s", parent->name, child->name);
+
+ if (unlikely(fr_der_decode_pair_dbuff(vp, &vp->vp_group, child, &our_in, decode_ctx) < 0)) {
+ talloc_free(vp);
+ return -1;
+ }
+ }
+
+ decode_ctx->oid_value_pairs = old;
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ if (unlikely(fr_der_flag_is_sequence_of(parent))) {
+ /*
+ * This is a sequence-of, meaning there are restrictions on the types which can be present
+ */
+
+ bool restriction_types[] = { [UINT8_MAX] = false };
+
+ if (fr_der_flag_sequence_of(parent) != FR_DER_TAG_CHOICE) {
+ restriction_types[fr_der_flag_sequence_of(parent)] = true;
+
+ } else {
+ /*
+ * If it is a seuqnec of choices, then we must construct the list of restriction_types.
+ * This will be a list of the number of choices, starting at 0.
+ */
+ fr_dict_attr_t const *choices = NULL;
+
+ if (unlikely(!fr_type_is_structural(parent->type))) {
+ fr_strerror_printf("Sequence-of choice found in incompatible attribute %s of type %s",
+ parent->name, fr_type_to_str(parent->type));
+ talloc_free(vp);
+ return -1;
+ }
+
+ if (fr_type_is_group(parent->type)) {
+ while ((choices = fr_dict_attr_iterate_children(fr_dict_attr_ref(parent), &choices))) {
+ restriction_types[choices->attr] = true;
+ }
+ } else {
+ while ((choices = fr_dict_attr_iterate_children(parent, &choices))) {
+ restriction_types[choices->attr] = true;
+ }
+ }
+ }
+
+ while (fr_dbuff_remaining(&our_in) > 0) {
+ ssize_t ret;
+ uint8_t current_tag;
+ uint8_t tag_byte;
+ uint8_t *current_marker = fr_dbuff_current(&our_in);
+
+ FR_DBUFF_OUT_RETURN(&tag_byte, &our_in);
+
+ current_tag = (tag_byte & DER_TAG_CONTINUATION);
+
+ if (unlikely(!restriction_types[current_tag])) {
+ fr_strerror_printf("Attribute %s is a sequence-of, but received tag %" PRIu32, parent->name,
+ current_tag);
+ error:
+ talloc_free(vp);
+ return -1;
+
+ }
+
+ if (unlikely(fr_der_flag_sequence_of(parent) == FR_DER_TAG_CHOICE)) {
+ child = fr_dict_attr_child_by_num(parent, current_tag);
+ if (unlikely(!child)) {
+ fr_strerror_printf(
+ "Attribute %s is a sequence-of choice, but received unknown option %" PRIu32,
+ parent->name, current_tag);
+ goto error;
+ }
+
+ } else if (!child) {
+ child = fr_dict_attr_iterate_children(parent, &child);
+ }
+
+ FR_PROTO_TRACE("decode context %s -> %s", parent->name, child->name);
+
+ fr_dbuff_set(&our_in, current_marker);
+
+ ret = fr_der_decode_pair_dbuff(vp, &vp->vp_group, child, &our_in, decode_ctx);
+ if (unlikely(ret < 0)) {
+ goto error;
+ }
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ while ((child = fr_dict_attr_iterate_children(parent, &child))) {
+ ssize_t ret;
+
+ FR_PROTO_TRACE("decode context %s -> %s", parent->name, child->name);
+
+ ret = fr_der_decode_pair_dbuff(vp, &vp->vp_group, child, &our_in, decode_ctx);
+ if (unlikely(ret < 0)) {
+ talloc_free(vp);
+ return ret;
+ }
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_set(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dict_attr_t const *child = NULL;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ fr_dbuff_marker_t previous_marker;
+ uint8_t previous_tag = 0x00;
+ size_t previous_len = 0;
+
+ if (!fr_type_is_struct(parent->type) && !fr_type_is_tlv(parent->type) && !fr_type_is_group(parent->type)) {
+ fr_strerror_printf("Set found in incompatible attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.11 Encoding of a set value
+ * 8.11.1 The encoding of a set value shall be constructed.
+ * 8.11.2 The contents octets shall consist of the complete encoding of one data value from each
+ * of the types listed in the ASN.1 definition of the set type, in an order chosen by the
+ * sender, unless the type was referenced with the keyword OPTIONAL or the keyword DEFAULT.
+ * 8.11.3 The encoding of a data value may, but need not, be present for a type referenced with the
+ * keyword OPTIONAL or the keyword DEFAULT.
+ *
+ * 11.5 Set and sequence components with default value
+ * The encoding of a set value or sequence value shall not include an encoding for any component
+ * value which is equal to its default value.
+ */
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ if (fr_der_flag_is_pair(parent)) {
+ /*
+ * This set contains an oid value pair
+ */
+ if (unlikely(!fr_type_is_group(parent->type))) {
+ fr_strerror_printf("Set with pair found in incompatible attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ talloc_free(vp);
+ return -1;
+ }
+
+ if (unlikely(fr_der_decode_oid_value_pair(vp, &vp->vp_group, &our_in, vp->da, decode_ctx) < 0)) {
+ talloc_free(vp);
+ return -1;
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ if (fr_der_flag_is_pairs(parent) || decode_ctx->oid_value_pairs) {
+ /*
+ * This set contains sequences/sets of pairs
+ */
+ bool old = decode_ctx->oid_value_pairs;
+
+ decode_ctx->oid_value_pairs = true;
+ while (fr_dbuff_remaining(&our_in) > 0) {
+ child = NULL;
+ child = fr_dict_attr_iterate_children(parent, &child);
+
+ FR_PROTO_TRACE("decode context %s -> %s", parent->name, child->name);
+
+ if (unlikely(fr_der_decode_pair_dbuff(vp, &vp->vp_group, child, &our_in, decode_ctx) < 0)) {
+ talloc_free(vp);
+ return -1;
+ }
+ }
+
+ decode_ctx->oid_value_pairs = old;
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ if (fr_der_flag_is_set_of(parent)) {
+ /*
+ * This is a set-of, meaning there are restrictions on the types which can be present
+ */
+ fr_der_tag_num_t restriction_type = fr_der_flag_set_of(parent);
+
+ while (fr_dbuff_remaining(&our_in) > 0) {
+ fr_dbuff_marker_t current_value_marker;
+ ssize_t ret;
+ uint64_t current_tag;
+ uint8_t *current_marker = fr_dbuff_current(&our_in);
+ size_t len;
+
+ if (!child) {
+ child = fr_dict_attr_iterate_children(parent, &child);
+ }
+
+ FR_PROTO_TRACE("decode context %s -> %s", parent->name, child->name);
+
+ if (unlikely(fr_der_decode_hdr(NULL, &our_in, ¤t_tag, &len) < 0)) {
+ fr_strerror_const("Insufficient data for set. Missing tag");
+ ret = -1;
+ error:
+ talloc_free(vp);
+ return ret;
+ }
+
+ if (unlikely(current_tag != restriction_type)) {
+ fr_strerror_printf("Attribute %s is a set-of type %" PRIu32 ", but found type %" PRIu64,
+ parent->name, restriction_type, current_tag);
+ ret = -1;
+ goto error;
+ }
+
+ fr_dbuff_marker(¤t_value_marker, &our_in);
+
+ if (previous_tag != 0x00) {
+ uint8_t prev_char = 0, curr_char = 0;
+ fr_dbuff_t previous_item = FR_DBUFF(&previous_marker);
+
+ fr_dbuff_set_end(&previous_item, fr_dbuff_current(&previous_marker) + previous_len);
+
+ do {
+ FR_DBUFF_OUT_RETURN(&prev_char, &previous_item);
+ FR_DBUFF_OUT_RETURN(&curr_char, &our_in);
+
+ if (prev_char > curr_char) {
+ fr_strerror_const("Set tags are not in ascending order");
+ ret = -1;
+ goto error;
+ }
+
+ } while (fr_dbuff_remaining(&our_in) > 0 && fr_dbuff_remaining(&previous_item) > 0);
+
+ if (fr_dbuff_remaining(&previous_item) > 0) {
+ fr_strerror_const(
+ "Set tags are not in ascending order. Previous item has more data");
+ ret = -1;
+ goto error;
+ }
+ }
+
+ previous_tag = current_tag;
+ previous_len = len;
+
+ previous_marker = current_value_marker;
+
+ fr_dbuff_set(&our_in, current_marker);
+
+ ret = fr_der_decode_pair_dbuff(vp, &vp->vp_group, child, &our_in, decode_ctx);
+ if (unlikely(ret < 0)) {
+ goto error;
+ }
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ while ((child = fr_dict_attr_iterate_children(parent, &child))) {
+ ssize_t ret;
+ uint8_t current_tag;
+ uint8_t *current_marker = fr_dbuff_current(&our_in);
+
+ FR_PROTO_TRACE("decode context %s -> %s", parent->name, child->name);
+
+ /*
+ * Check that the tag is in ascending order
+ */
+ FR_DBUFF_OUT_RETURN(¤t_tag, &our_in);
+
+ if (unlikely(current_tag < previous_tag)) {
+ fr_strerror_const("Set tags are not in ascending order");
+ talloc_free(vp);
+ return -1;
+ }
+
+ previous_tag = current_tag;
+
+ /*
+ * Reset the buffer to the start of the tag
+ */
+ fr_dbuff_set(&our_in, current_marker);
+
+ ret = fr_der_decode_pair_dbuff(vp, &vp->vp_group, child, &our_in, decode_ctx);
+ if (unlikely(ret < 0)) {
+ talloc_free(vp);
+ return ret;
+ }
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_printable_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ static bool const allowed_chars[] = {
+ [' '] = true, ['\''] = true, ['('] = true, [')'] = true,
+ ['+'] = true, [','] = true, ['-'] = true, ['.'] = true,
+ ['/'] = true, [':'] = true, ['='] = true, ['?'] = true,
+ ['A' ... 'Z'] = true, ['a' ... 'z'] = true,
+ ['0' ... '9'] = true, [UINT8_MAX] = false
+ };
+
+ return fr_der_decode_string(ctx, out, parent, in, allowed_chars, decode_ctx);
+}
+
+static ssize_t fr_der_decode_t61_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ static bool const allowed_chars[] = {
+ [0x08] = true, [0x0A] = true, [0x0C] = true, [0x0D] = true,
+ [0x0E] = true, [0x0F] = true, [0x19] = true, [0x1A] = true,
+ [0x1B] = true, [0x1D] = true, [' '] = true, ['!'] = true,
+ ['"'] = true, ['%'] = true, ['&'] = true, ['\''] = true,
+ ['('] = true, [')'] = true, ['*'] = true, ['+'] = true,
+ [','] = true, ['-'] = true, ['.'] = true, ['/'] = true,
+ [':'] = true, [';'] = true, ['<'] = true, ['='] = true,
+ ['>'] = true, ['?'] = true, ['@'] = true, ['['] = true,
+ [']'] = true, ['_'] = true, ['|'] = true, [0x7F] = true,
+ [0x8B] = true, [0x8C] = true, [0x9B] = true, [0xA0] = true,
+ [0xA1] = true, [0xA2] = true, [0xA3] = true, [0xA4] = true,
+ [0xA5] = true, [0xA6] = true, [0xA7] = true, [0xA8] = true,
+ [0xAB] = true, [0xB0] = true, [0xB1] = true, [0xB2] = true,
+ [0xB3] = true, [0xB4] = true, [0xB5] = true, [0xB6] = true,
+ [0xB7] = true, [0xB8] = true, [0xBB] = true, [0xBC] = true,
+ [0xBD] = true, [0xBE] = true, [0xBF] = true, [0xC1] = true,
+ [0xC2] = true, [0xC3] = true, [0xC4] = true, [0xC5] = true,
+ [0xC6] = true, [0xC7] = true, [0xC8] = true, [0xC9] = true,
+ [0xCA] = true, [0xCB] = true, [0xCC] = true, [0xCD] = true,
+ [0xCE] = true, [0xCF] = true, [0xE0] = true, [0xE1] = true,
+ [0xE2] = true, [0xE3] = true, [0xE4] = true, [0xE5] = true,
+ [0xE7] = true, [0xE8] = true, [0xE9] = true, [0xEA] = true,
+ [0xEB] = true, [0xEC] = true, [0xED] = true, [0xEE] = true,
+ [0xEF] = true, [0xF0] = true, [0xF1] = true, [0xF2] = true,
+ [0xF3] = true, [0xF4] = true, [0xF5] = true, [0xF6] = true,
+ [0xF7] = true, [0xF8] = true, [0xF9] = true, [0xFA] = true,
+ [0xFB] = true, [0xFC] = true, [0xFD] = true, [0xFE] = true,
+ ['A' ... 'Z'] = true, ['a' ... 'z'] = true,
+ ['0' ... '9'] = true, [UINT8_MAX] = false
+ };
+
+ return fr_der_decode_string(ctx, out, parent, in, allowed_chars, decode_ctx);
+}
+static ssize_t fr_der_decode_ia5_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ return fr_der_decode_string(ctx, out, parent, in, NULL, decode_ctx);
+}
+
+static ssize_t fr_der_decode_utc_time(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ char timestr[DER_UTC_TIME_LEN + 1] = {};
+ char *p;
+ struct tm tm = {};
+
+ if (!fr_type_is_date(parent->type)) {
+ fr_strerror_printf("UTC time found in non-date attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.25 Encoding for values of the useful types
+ * The following "useful types" shall be encoded as if they had been replaced by their definitions
+ * given in clauses 46-48 of Rec. ITU-T X.680 | ISO/IEC 8824-1:
+ * - generalized time;
+ * - universal time;
+ * - object descriptor.
+ *
+ * 8.26 Encoding for values of the TIME type and the useful time types
+ * 8.26 Encoding for values of the TIME type and the useful time types 8.26.1 Encoding for values
+ * of the TIME type NOTE - The defined time types are subtypes of the TIME type, with the same
+ * tag, and have the same encoding as the TIME type. 8.26.1.1 The encoding of the TIME type shall
+ * be primitive. 8.26.1.2 The contents octets shall be the UTF-8 encoding of the value notation,
+ * after the removal of initial and final QUOTATION MARK (34) characters.
+ *
+ * 11.8 UTCTime
+ * 11.8.1 The encoding shall terminate with "Z", as described in the ITU-T X.680 | ISO/IEC 8824-1
+ * clause on UTCTime.
+ * 11.8.2 The seconds element shall always be present.
+ * 11.8.3 Midnight (GMT) shall be represented as "YYMMDD000000Z", where "YYMMDD" represents the
+ * day following the midnight in question.
+ */
+
+ /*
+ * The format of a UTC time is "YYMMDDhhmmssZ"
+ * Where:
+ * 1. YY is the year
+ * 2. MM is the month
+ * 3. DD is the day
+ * 4. hh is the hour
+ * 5. mm is the minute
+ * 6. ss is the second (not optional in DER)
+ * 7. Z is the timezone (UTC)
+ */
+
+ FR_DBUFF_OUT_MEMCPY_RETURN((uint8_t *)timestr, &our_in, DER_UTC_TIME_LEN);
+
+ if (memchr(timestr, '\0', DER_UTC_TIME_LEN) != NULL) {
+ fr_strerror_const("UTC time contains null byte");
+ return -1;
+ }
+
+ timestr[DER_UTC_TIME_LEN] = '\0';
+
+ p = strptime(timestr, "%y%m%d%H%M%SZ", &tm);
+
+ if (unlikely(p == NULL) || *p != '\0') {
+ fr_strerror_const("Invalid UTC time format");
+ return -1;
+ }
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ vp->vp_date = fr_unix_time_from_tm(&tm);
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_generalized_time(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ char timestr[DER_GENERALIZED_TIME_LEN_MIN + 1] = {};
+ char *p;
+ unsigned long subseconds = 0;
+ struct tm tm = {};
+
+ size_t len = fr_dbuff_remaining(&our_in);
+
+ if (!fr_type_is_date(parent->type)) {
+ fr_strerror_printf("Generalized time found in non-date attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ if (len < DER_GENERALIZED_TIME_LEN_MIN) {
+ fr_strerror_const("Insufficient data for generalized time or incorrect length");
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.25 Encoding for values of the useful types
+ * The following "useful types" shall be encoded as if they had been replaced by their definitions
+ * given in clauses 46-48 of Rec. ITU-T X.680 | ISO/IEC 8824-1:
+ * - generalized time;
+ * - universal time;
+ * - object descriptor.
+ *
+ * 8.26 Encoding for values of the TIME type and the useful time types
+ * 8.26 Encoding for values of the TIME type and the useful time types 8.26.1 Encoding for values
+ * of the TIME type NOTE - The defined time types are subtypes of the TIME type, with the same
+ * tag, and have the same encoding as the TIME type. 8.26.1.1 The encoding of the TIME type shall
+ * be primitive. 8.26.1.2 The contents octets shall be the UTF-8 encoding of the value notation,
+ * after the removal of initial and final QUOTATION MARK (34) characters.
+ *
+ * 11.7 GeneralizedTime
+ * 11.7.1 The encoding shall terminate with a "Z", as described in the Rec. ITU-T X.680 | ISO/IEC
+ * 8824-1 clause on GeneralizedTime.
+ * 11.7.2 The seconds element shall always be present.
+ * 11.7.3 The fractional-seconds elements, if present, shall omit all trailing zeros; if the
+ * elements correspond to 0, they shall be wholly omitted, and the decimal point element
+ * also shall be omitted.
+ */
+
+ /*
+ * The format of a generalized time is "YYYYMMDDHHMMSS[.fff]Z"
+ * Where:
+ * 1. YYYY is the year
+ * 2. MM is the month
+ * 3. DD is the day
+ * 4. HH is the hour
+ * 5. MM is the minute
+ * 6. SS is the second
+ * 7. fff is the fraction of a second (optional)
+ * 8. Z is the timezone (UTC)
+ */
+
+ FR_DBUFF_OUT_MEMCPY_RETURN((uint8_t *)timestr, &our_in, DER_GENERALIZED_TIME_LEN_MIN);
+
+ if (memchr(timestr, '\0', DER_GENERALIZED_TIME_LEN_MIN) != NULL) {
+ fr_strerror_const("Generalized time contains null byte");
+ return -1;
+ }
+
+ if (timestr[DER_GENERALIZED_TIME_LEN_MIN - 1] != 'Z' && timestr[DER_GENERALIZED_TIME_LEN_MIN - 1] != '.') {
+ fr_strerror_const("Incorrect format for generalized time. Missing timezone");
+ return -1;
+ }
+
+ /*
+ * Check if the fractional seconds are present
+ */
+ if (timestr[DER_GENERALIZED_TIME_LEN_MIN - 1] == '.') {
+ /*
+ * We only support subseconds up to 4 decimal places
+ */
+ char subsecstring[DER_GENERALIZED_TIME_PRECISION_MAX + 1];
+
+ uint8_t precision = DER_GENERALIZED_TIME_PRECISION_MAX;
+
+ if (unlikely(fr_dbuff_remaining(&our_in) - 1 < DER_GENERALIZED_TIME_PRECISION_MAX)) {
+ precision = fr_dbuff_remaining(&our_in) - 1;
+ }
+
+ if (unlikely(precision == 0)) {
+ fr_strerror_const("Insufficient data for subseconds");
+ return -1;
+ }
+
+ FR_DBUFF_OUT_MEMCPY_RETURN((uint8_t *)subsecstring, &our_in, precision);
+
+ if (memchr(subsecstring, '\0', precision) != NULL) {
+ fr_strerror_const("Generalized time contains null byte in subseconds");
+ return -1;
+ }
+
+ subsecstring[DER_GENERALIZED_TIME_PRECISION_MAX] = '\0';
+
+ /*
+ * Convert the subseconds to an unsigned long
+ */
+ subseconds = strtoul(subsecstring, NULL, 10);
+
+ /*
+ * Scale to nanoseconds
+ */
+ subseconds *= 1000000;
+ }
+
+ /*
+ * Make sure the timezone is UTC (Z)
+ */
+ timestr[DER_GENERALIZED_TIME_LEN_MIN - 1] = 'Z';
+
+ timestr[DER_GENERALIZED_TIME_LEN_MIN] = '\0';
+
+ p = strptime(timestr, "%Y%m%d%H%M%SZ", &tm);
+
+ if (unlikely(p == NULL)) {
+ fr_strerror_const("Invalid generalized time format (strptime)");
+ return -1;
+ }
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ vp->vp_date = fr_unix_time_add(fr_unix_time_from_tm(&tm), fr_time_delta_wrap(subseconds));
+
+ fr_pair_append(out, vp);
+
+ /*
+ * Move to the end of the buffer
+ * This is necessary because the fractional seconds are being ignored
+ */
+ fr_dbuff_advance(&our_in, fr_dbuff_remaining(&our_in));
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_visible_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ static bool const allowed_chars[] = {
+ [' '] = true, ['!'] = true, ['"'] = true, ['#'] = true,
+ ['$'] = true, ['%'] = true, ['&'] = true, ['\''] = true,
+ ['('] = true, [')'] = true, ['*'] = true, ['+'] = true,
+ [','] = true, ['-'] = true, ['.'] = true, ['/'] = true,
+ [':'] = true, [';'] = true, ['<'] = true, ['='] = true,
+ ['>'] = true, ['?'] = true, ['@'] = true, ['['] = true,
+ ['\\'] = true, [']'] = true, ['^'] = true, ['_'] = true,
+ ['`'] = true, ['{'] = true, ['|'] = true, ['}'] = true,
+ ['A' ... 'Z'] = true, ['a' ... 'z'] = true,
+ ['0' ... '9'] = true, [UINT8_MAX] = false
+ };
+
+ return fr_der_decode_string(ctx, out, parent, in, allowed_chars, decode_ctx);
+}
+
+static ssize_t fr_der_decode_general_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ return fr_der_decode_string(ctx, out, parent, in, NULL, decode_ctx);
+}
+
+static ssize_t fr_der_decode_universal_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ return fr_der_decode_string(ctx, out, parent, in, NULL, decode_ctx);
+}
+
+/** Decode the tag and length fields of a DER encoded structure
+ *
+ * @param[in] parent Parent attribute
+ * @param[in] in Input buffer
+ * @param[out] tag Tag value
+ * @param[out] len Length of the value field
+ *
+ * @return 0 on success, -1 on failure
+ */
+static ssize_t fr_der_decode_hdr(fr_dict_attr_t const *parent, fr_dbuff_t *in, uint64_t *tag, size_t *len)
+{
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ uint8_t tag_byte;
+ uint8_t len_byte;
+ fr_der_tag_decode_t *func;
+ fr_der_tag_class_t tag_class;
+ fr_der_tag_constructed_t constructed;
+
+ if (unlikely(fr_dbuff_out(&tag_byte, &our_in) < 0)) {
+ fr_strerror_const("Insufficient data for tag field");
+ return -1;
+ }
+
+ /*
+ * Decode the tag flags
+ */
+ tag_class = (tag_byte & DER_TAG_CLASS_MASK);
+ constructed = IS_DER_TAG_CONSTRUCTED(tag_byte);
+
+ /*
+ * Decode the tag
+ */
+ if (IS_DER_TAG_CONTINUATION(tag_byte)) {
+ /*
+ * We have a multi-byte tag
+ *
+ * Note: Multi-byte tags would mean having a tag number that is greater than 30 (0x1E) (since tag
+ * 31 would indicate a multi-byte tag). For most use-cases, this should not be needed, since all
+ * of the basic ASN.1 types are tagged under 30, and if a CHOICE type were to have over 30 options
+ * (meaning a multi-byte tag would be needed), that would be a very complex CHOICE type that
+ * should probably be simplified.
+ */
+ fr_strerror_const("Multi-byte tags are not supported");
+ return -1;
+ }
+
+ *tag = tag_byte & DER_TAG_CONTINUATION;
+
+ /*
+ * Check if the tag is not universal
+ */
+ if (tag_class != FR_DER_CLASS_UNIVERSAL) {
+ /*
+ * The data type will need to be resolved using the dictionary and the tag value
+ */
+
+ if (parent == NULL) {
+ fr_strerror_const("No parent attribute to resolve tag");
+ return -1;
+ }
+
+ if (tag_class == fr_der_flag_class(parent)) {
+ if (*tag == fr_der_flag_tagnum(parent)) {
+ *tag = fr_der_flag_subtype(parent);
+ } else {
+ goto bad_tag;
+ }
+ } else {
+ bad_tag:
+ fr_strerror_printf("Invalid tag %" PRIu64 " for attribute %s. Expected %" PRIu32, *tag, parent->name,
+ fr_der_flag_tagnum(parent));
+ return -1;
+ }
+ }
+
+ if ((*tag > NUM_ELEMENTS(tag_funcs)) || (*tag == FR_DER_TAG_INVALID)) {
+ fr_strerror_printf("Unknown tag %" PRIu64, *tag);
+ return -1;
+ }
+
+ func = &tag_funcs[*tag];
+ /*
+ * Check if the tag is an OID. OID tags will be handled differently
+ */
+ if (*tag != FR_DER_TAG_OID) {
+ if (unlikely(func->decode == NULL)) {
+ fr_strerror_printf("No decode function for tag %" PRIu64, *tag);
+ return -1;
+ }
+
+ if (IS_DER_TAG_CONSTRUCTED(func->constructed) != constructed) {
+ fr_strerror_printf("Constructed flag mismatch for tag %" PRIu64, *tag);
+ return -1;
+ }
+ }
+
+ if (unlikely(fr_dbuff_out(&len_byte, &our_in) < 0)) {
+ fr_strerror_const("Missing length field");
+ return -1;
+ }
+
+ /*
+ * Check if the length is a multi-byte length field
+ */
+ if (IS_DER_LEN_MULTI_BYTE(len_byte)) {
+ uint8_t len_len = len_byte & 0x7f;
+ *len = 0;
+
+ /*
+ * Length bits of zero is an indeterminate length field where
+ * the length is encoded in the data instead.
+ */
+ if (len_len > 0) {
+ if (unlikely(len_len > sizeof(*len))) {
+ fr_strerror_printf("Length field too large (%" PRIu32 ")", len_len);
+ return -1;
+ }
+
+ while (len_len--) {
+ if (unlikely(fr_dbuff_out(&len_byte, &our_in) < 0)) {
+ fr_strerror_const("Insufficient data to satisfy multi-byte length field");
+ return -1;
+ }
+ *len = (*len << 8) | len_byte;
+ }
+ }
+
+ else if (!constructed) {
+ fr_strerror_const("Primitive data with indefinite form length field is invalid");
+ return -1;
+ }
+ } else {
+ *len = len_byte;
+ }
+
+ /*
+ * Check if the length is valid for our buffer
+ */
+ if (unlikely(fr_dbuff_extend_lowat(NULL, &our_in, *len) < *len)) {
+ fr_strerror_printf("Insufficient data for length field (%zu)", *len);
+ return -1;
+ }
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+/** Decode a CHOICE type
+ * This is where the actual decoding of the CHOICE type happens. The CHOICE type is a type that can have multiple
+ * types, but only one of them can be present at a time. The type that is present is determined by the tag of the
+ * data
+ *
+ * @param[in] ctx Talloc context
+ * @param[in] out Output list
+ * @param[in] parent Parent attribute
+ * @param[in] in Input buffer
+ * @param[in] decode_ctx Decode context
+ */
+static ssize_t fr_der_decode_choice(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dict_attr_t const *child = NULL;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ uint64_t tag_num;
+ uint8_t tag_byte;
+ uint8_t *current_marker = fr_dbuff_current(&our_in);
+
+ if (!fr_type_is_struct(parent->type) && !fr_type_is_tlv(parent->type) && !fr_type_is_group(parent->type)) {
+ fr_strerror_printf("Sequence found in incompatible attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ FR_DBUFF_OUT_RETURN(&tag_byte, &our_in);
+
+ if (unlikely(IS_DER_TAG_CONTINUATION(tag_byte))) {
+ fr_strerror_printf("Attribute %s is a choice, but received tag with continuation bit set",
+ parent->name);
+ return -1;
+ }
+
+ tag_num = (tag_byte & DER_TAG_CONTINUATION);
+
+ child = fr_dict_attr_child_by_num(parent, tag_num);
+ if (unlikely(!child)) {
+ fr_strerror_printf("Attribute %s is a choice, but received unknown option %" PRIu64, parent->name,
+ tag_num);
+ return -1;
+ }
+
+ fr_dbuff_set(&our_in, current_marker);
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ if (unlikely(fr_der_decode_pair_dbuff(vp, &vp->vp_group, child, &our_in, decode_ctx) < 0)) {
+ talloc_free(vp);
+ return -1;
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+/** Decode an X509 Extentions Field
+ *
+ * @param[in] ctx Talloc context
+ * @param[in] out Output list
+ * @param[in] in Input buffer
+ * @param[in] parent Parent attribute
+ * @param[in] decode_ctx Decode context
+ *
+ * @return 0 on success, -1 on failure
+ */
+static ssize_t fr_der_decode_x509_extensions(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dbuff_t *in,
+ fr_dict_attr_t const *parent, fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ fr_pair_t *vp, *vp2;
+
+ uint64_t tag;
+ int64_t max;
+ size_t len;
+ ssize_t slen;
+
+ FR_PROTO_TRACE("Decoding extensions");
+ FR_PROTO_TRACE("Attribute %s", parent->name);
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(in), fr_dbuff_remaining(in), "Top of extension decoding");
+
+ if (unlikely(!fr_type_is_group(parent->type))) {
+ fr_strerror_printf("Pair found in non-group attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * RFC 5280 Section 4.2
+ * The extensions defined for X.509 v3 certificates provide methods for
+ * associating additional attributes with users or public keys and for
+ * managing relationships between CAs. The X.509 v3 certificate format
+ * also allows communities to define private extensions to carry
+ * information unique to those communities. Each extension in a
+ * certificate is designated as either critical or non-critical.
+ *
+ * Each extension includes an OID and an ASN.1 structure. When an
+ * extension appears in a certificate, the OID appears as the field
+ * extnID and the corresponding ASN.1 DER encoded structure is the value
+ * of the octet string extnValue.
+ *
+ * RFC 5280 Section A.1 Explicitly Tagged Module, 1988 Syntax
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Extension ::= SEQUENCE {
+ * extnID OBJECT IDENTIFIER,
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET STRING
+ * -- contains the DER encoding of an ASN.1 value
+ * -- corresponding to the extension type identified
+ * -- by extnID
+ * }
+ *
+ * So the extensions are a SEQUENCE of SEQUENCEs containing an OID, a boolean and an OCTET STRING.
+ * Note: If the boolean value is false, it is not included in the encoding.
+ */
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ oom:
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ vp2 = fr_pair_afrom_da(vp, fr_dict_attr_by_name(NULL, fr_dict_attr_ref(parent), "critical"));
+
+ if (unlikely(vp2 == NULL)) {
+ talloc_free(vp);
+ goto oom;
+ }
+
+ if (unlikely((slen = fr_der_decode_hdr(parent, &our_in, &tag, &len)) < 0)) {
+ fr_strerror_const_push("Failed decoding extensions list header");
+ error:
+ talloc_free(vp2);
+ talloc_free(vp);
+ return slen;
+ }
+
+ if (tag != FR_DER_TAG_SEQUENCE) {
+ fr_strerror_printf("Expected SEQUENCE tag as the first item in an extensions list. Got tag: %" PRIu64, tag);
+ slen = -1;
+ goto error;
+ }
+
+ FR_PROTO_TRACE("Attribute %s, tag %" PRIu64, parent->name, tag);
+
+ max = fr_der_flag_max(parent); /* Maximum number of extensions specified in the dictionary*/
+
+ while (fr_dbuff_remaining(&our_in) > 0) {
+ fr_dbuff_t sub_in = FR_DBUFF(&our_in);
+ fr_dbuff_marker_t sub_marker;
+ fr_der_decode_oid_to_da_ctx_t uctx;
+
+ size_t sub_len, len_peek;
+ uint8_t is_critical = false;
+
+ fr_dbuff_set_end(&sub_in, fr_dbuff_current(&sub_in) + len);
+
+ if (unlikely((slen = fr_der_decode_hdr(parent, &sub_in, &tag, &sub_len)) < 0)) {
+ fr_strerror_const_push("Failed decoding extension sequence header");
+ goto error;
+ }
+
+ if (tag != FR_DER_TAG_SEQUENCE) {
+ fr_strerror_printf("Expected SEQUENCE tag as the first tag in an extension. Got tag: %" PRIu64,
+ tag);
+ slen = -1;
+ goto error;
+ }
+
+ FR_PROTO_TRACE("Attribute %s, tag %" PRIu64, parent->name, tag);
+
+ if (unlikely((slen = fr_der_decode_hdr(NULL, &sub_in, &tag, &sub_len)) < 0)) {
+ fr_strerror_const_push("Failed decoding oid header");
+ goto error;
+ }
+
+ if (tag != FR_DER_TAG_OID) {
+ fr_strerror_printf("Expected OID tag as the first item in an extension. Got tag: %" PRIu64, tag);
+ slen = -1;
+ goto error;
+ }
+
+ FR_PROTO_TRACE("Attribute %s, tag %" PRIu64, parent->name, tag);
+
+ uctx.ctx = vp;
+ uctx.parent_da = vp->da;
+ uctx.parent_list = &vp->vp_group;
+
+ fr_dbuff_marker(&sub_marker, &sub_in);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&sub_in), fr_dbuff_remaining(&sub_in),
+ "Before moving buffer in extension");
+
+ FR_DBUFF_ADVANCE_RETURN(&sub_in, sub_len);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&sub_in), fr_dbuff_remaining(&sub_in),
+ "After moving buffer in extension");
+
+ if (unlikely(fr_der_decode_hdr(NULL, &sub_in, &tag, &len_peek) < 0)) {
+ fr_strerror_const_push("Failed decoding value header for extension ");
+ slen = -1;
+ fr_dbuff_marker_release(&sub_marker);
+ goto error;
+ }
+
+ if (tag == FR_DER_TAG_BOOLEAN) {
+ /*
+ * This Extension has the isCritical field.
+ * If this value is true, we will be storing the pair in the critical list
+ */
+ if (unlikely(fr_dbuff_out(&is_critical, &sub_in) < 0)) {
+ fr_strerror_const("Insufficient data for isCritical field");
+ slen = -1;
+ fr_dbuff_marker_release(&sub_marker);
+ goto error;
+ }
+
+ if (is_critical) {
+ uctx.ctx = vp2;
+ uctx.parent_da = vp2->da;
+ uctx.parent_list = &vp2->vp_group;
+ }
+ }
+
+ /*
+ * Restore the marker and rewind the buffer
+ */
+ fr_dbuff_set(&sub_in, &sub_marker);
+ fr_dbuff_marker_release(&sub_marker);
+
+ fr_dbuff_set_end(&sub_in, fr_dbuff_current(&sub_in) + sub_len);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&sub_in), fr_dbuff_remaining(&sub_in),
+ "Before decoding extension oid");
+
+ /*
+ * Decode the OID to get the attribute to use for the extension value
+ */
+ if (unlikely((slen = fr_der_decode_oid(NULL, &sub_in, fr_der_decode_oid_to_da, &uctx)) < 0)) {
+ fr_strerror_const_push("Failed decoding extension");
+ goto error;
+ }
+
+ fr_dbuff_set(&our_in, &sub_in);
+
+ sub_in = FR_DBUFF(&our_in);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&sub_in), fr_dbuff_remaining(&sub_in),
+ "After decoding extension oid");
+
+ if (is_critical) {
+ /*
+ * Skip over boolean value
+ */
+ FR_DBUFF_ADVANCE_RETURN(&sub_in, 3);
+ }
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&sub_in), fr_dbuff_remaining(&sub_in),
+ "After advancing buffer in extension");
+
+ if (unlikely((slen = fr_der_decode_hdr(NULL, &sub_in, &tag, &sub_len)) < 0)) {
+ fr_strerror_const_push("Failed decoding value header for extension value");
+ goto error;
+ }
+
+ if (unlikely(tag != FR_DER_TAG_OCTETSTRING)) {
+ fr_strerror_printf("Expected OCTETSTRING tag as the second item in an extension. Got tag: %" PRIu64,
+ tag);
+ slen = -1;
+ goto error;
+ }
+
+ fr_dbuff_set_end(&sub_in, fr_dbuff_current(&sub_in) + sub_len);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&sub_in), fr_dbuff_remaining(&sub_in),
+ "Before decoding extension value");
+
+ if (uctx.parent_da->flags.is_unknown) {
+ /*
+ * The extension was not found in the dictionary. We will store the value as raw octets
+ */
+ if (unlikely((slen = fr_der_decode_octetstring(uctx.ctx, uctx.parent_list, uctx.parent_da,
+ &sub_in, decode_ctx)) < 0)) {
+ fr_strerror_const_push("Failed decoding extension value");
+ goto error;
+ }
+
+ } else if (unlikely((slen = fr_der_decode_pair_dbuff(uctx.ctx, uctx.parent_list, uctx.parent_da, &sub_in,
+ decode_ctx)) < 0)) {
+ fr_strerror_const_push("Failed decoding extension value");
+ goto error;
+ }
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&sub_in), fr_dbuff_remaining(&sub_in),
+ "After decoding extension value");
+
+ fr_dbuff_set(&our_in, &sub_in);
+
+ if ((--max == 0) && (fr_dbuff_remaining(&our_in) > 0)) {
+ fr_strerror_const("Too many extensions");
+ return -1;
+ }
+ }
+
+ if (vp2->children.order.head.dlist_head.num_elements > 0) {
+ fr_pair_prepend(&vp->vp_group, vp2);
+ }
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+/** Decode an OID value pair
+ *
+ * @param[in] ctx Talloc context
+ * @param[out] out Output list
+ * @param[in] in Input buffer
+ * @param[in] parent Parent attribute
+ * @param[in] decode_ctx Decode context
+ *
+ * @return 0 on success, -1 on failure
+ */
+static ssize_t fr_der_decode_oid_value_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dbuff_t *in,
+ fr_dict_attr_t const *parent, fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ fr_dbuff_marker_t marker;
+ fr_der_decode_oid_to_da_ctx_t uctx;
+
+ uint64_t tag;
+ size_t len;
+ ssize_t slen;
+
+ FR_PROTO_TRACE("Decoding OID value pair");
+ if (unlikely(!fr_type_is_group(parent->type))) {
+ fr_strerror_printf("Pair found in non-group attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * A very common pattern in DER encoding is ro have a sequence of set containing two things: an OID and a
+ * value, where the OID is used to determine how to decode the value.
+ * We will be decoding the OID first and then try to find the attribute associated with that OID to then
+ * decode the value. If no attribute is found, one will be created and the value will be stored as raw
+ * octets in the attribute.
+ */
+
+ fr_dbuff_marker(&marker, in);
+
+ if (unlikely((slen = fr_der_decode_hdr(parent, &our_in, &tag, &len)) < 0)) {
+ fr_strerror_const_push("Failed decoding oid header");
+ error:
+ fr_dbuff_marker_release(&marker);
+ return slen;
+ }
+
+ if (tag != FR_DER_TAG_OID) {
+ fr_strerror_printf("Expected OID tag as the first item in a pair. Got tag: %" PRIu64, tag);
+ slen = -1;
+ goto error;
+ }
+
+ FR_PROTO_TRACE("Attribute %s, tag %" PRIu64, parent->name, tag);
+
+ uctx.ctx = ctx;
+ uctx.parent_da = fr_dict_attr_ref(parent);
+ uctx.parent_list = out;
+
+ fr_dbuff_set_end(&our_in, fr_dbuff_current(&our_in) + len);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&our_in), fr_dbuff_remaining(&our_in), "DER pair value");
+
+ slen = fr_der_decode_oid(out, &our_in, fr_der_decode_oid_to_da, &uctx);
+ if (unlikely(slen < 0)) goto error;
+
+ /*
+ * We have the attribute associated with the OID
+ * We will now decode the value.
+ *
+ * We will advance the buffer to the end of the OID, and then reuse the our_in buffer to decode the value.
+ * This looks strange in the code, but it is necessary to reset the end restrictions on the our_in buffer
+ * which were set to avoid overreading the buffer when decoding the OID.
+ */
+ fr_dbuff_set(in, &our_in);
+
+ our_in = FR_DBUFF(in);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&our_in), fr_dbuff_remaining(&our_in), "DER pair value");
+
+ if (unlikely(uctx.parent_da->flags.is_unknown)) {
+ /*
+ * The this pair is not in the dictionary
+ * We will store the value as raw octets
+ */
+ if (unlikely(slen = fr_der_decode_octetstring(uctx.ctx, uctx.parent_list, uctx.parent_da, &our_in,
+ decode_ctx) < 0)) {
+ fr_strerror_const_push("Failed decoding extension value");
+ goto error;
+ }
+ } else if (unlikely(slen = fr_der_decode_pair_dbuff(uctx.ctx, uctx.parent_list, uctx.parent_da, &our_in,
+ decode_ctx) < 0)) {
+ fr_strerror_const_push("Failed decoding extension value");
+ goto error;
+ }
+
+ fr_dbuff_set(in, &our_in);
+
+ FR_PROTO_HEX_DUMP(fr_dbuff_current(&our_in), fr_dbuff_remaining(&our_in), "DER pair value");
+
+ return fr_dbuff_marker_release_behind(&marker);
+}
+
+static ssize_t fr_der_decode_string(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, fr_dbuff_t *in,
+ bool const allowed_chars[], UNUSED fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_pair_t *vp;
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ char *str = NULL;
+
+ size_t pos, len = fr_dbuff_remaining(&our_in);
+
+ if (!fr_type_is_string(parent->type)) {
+ fr_strerror_printf("String found in non-string attribute %s of type %s", parent->name,
+ fr_type_to_str(parent->type));
+ return -1;
+ }
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * 8.23 Encoding for values of the restricted character string types
+ * 8.23.1 The data value consists of a string of characters from the character set specified in the ASN.1
+ * type definition. 8.23.2 Each data value shall be encoded independently of other data values of
+ * the same type.
+ * 8.23.3 Each character string type shall be encoded as if it had been declared:
+ * [UNIVERSAL x] IMPLICIT OCTET STRING
+ * where x is the number of the universal class tag assigned to the character string type in
+ * Rec. ITU-T X.680 | ISO/IEC 8824-1. The value of the octet string is specified in 8.23.4 and
+ * 8.23.5.
+ */
+
+ vp = fr_pair_afrom_da(ctx, parent);
+ if (unlikely(!vp)) {
+ oom:
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ if (unlikely(fr_pair_value_bstr_alloc(vp, &str, len, false) < 0)) {
+ talloc_free(vp);
+ goto oom;
+ }
+
+ fr_dbuff_out_memcpy((uint8_t *)str, &our_in, len);
+
+ if (allowed_chars) {
+ fr_sbuff_t sbuff;
+ sbuff = FR_SBUFF_OUT(str, len);
+
+ if ((pos = fr_sbuff_adv_past_allowed(&sbuff, SIZE_MAX, allowed_chars, NULL)) < len - 1) {
+ invalid:
+ fr_strerror_printf("Invalid character in a string (%" PRId32 ")", str[pos]);
+ return -1;
+ }
+
+ // Check the final character
+ if (!allowed_chars[(uint8_t)str[pos]]) goto invalid;
+ }
+
+ str[len] = '\0';
+
+ fr_pair_append(out, vp);
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_pair_dbuff(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+ fr_dbuff_t *in, fr_der_decode_ctx_t *decode_ctx)
+{
+ fr_dbuff_t our_in = FR_DBUFF(in);
+ fr_der_tag_decode_t *func;
+ ssize_t slen;
+ uint64_t tag, max;
+ size_t len;
+
+ /*
+ * ISO/IEC 8825-1:2021
+ * The structure of a DER encoding is as follows:
+ *
+ * +------------+--------+-------+
+ * | IDENTIFIER | LENGTH | VALUE |
+ * +------------+--------+-------+
+ *
+ * The IDENTIFIER is a tag that specifies the type of the value field and is encoded as follows:
+ *
+ * 8 7 6 5 4 3 2 1
+ * +---+---+-----+---+---+---+---+---+
+ * | Class | P/C | Tag Number |
+ * +---+---+-----+---+---+---+---+---+
+ * |
+ * |- 0 = Primitive
+ * |- 1 = Constructed
+ *
+ * The CLASS field specifies the encoding class of the tag and may be one of the following values:
+ *
+ * +------------------+-------+-------+
+ * | Class | Bit 8 | Bit 7 |
+ * +------------------+-------+-------+
+ * | UNIVERSAL | 0 | 0 |
+ * | APPLICATION | 0 | 1 |
+ * | CONTEXT-SPECIFIC | 1 | 0 |
+ * | PRIVATE | 1 | 1 |
+ * +------------------+-------+-------+
+ *
+ * The P/C field specifies whether the value field is primitive or constructed.
+ * The TAG NUMBER field specifies the tag number of the value field and is encoded as an unsigned binary
+ * integer.
+ *
+ * The LENGTH field specifies the length of the VALUE field and is encoded as an unsigned binary integer
+ * and may be encoded as a single byte or multiple bytes.
+ *
+ * The VALUE field contains LENGTH number of bytes and is encoded according to the tag.
+ *
+ */
+
+ /*
+ * If there are no more bytes to read, it is possible that the value was not encoded since
+ * it may be using the DEFAULT value. Here we try extending the buffer to see if we can read
+ * the next header.
+ */
+ if ((fr_dbuff_extend_lowat(NULL, &our_in, 2) < 2)) {
+ if (fr_der_flag_has_default(parent)) {
+ fr_pair_t *vp = fr_pair_afrom_da(ctx, parent);
+ fr_dict_enum_value_t *ev;
+
+ if (unlikely(!vp)) {
+ oom:
+ fr_strerror_const("Out of memory");
+ return -1;
+ }
+
+ ev = fr_dict_enum_by_name(parent, "DEFAULT", strlen("DEFAULT"));
+ if (unlikely(ev == NULL)) {
+ fr_strerror_printf("No DEFAULT value for attribute %s", parent->name);
+ error:
+ talloc_free(vp);
+ return -1;
+ }
+
+ if (fr_value_box_copy(vp, &vp->data, ev->value) < 0) goto error;
+
+ vp->data.enumv = vp->da;
+
+ fr_pair_append(out, vp);
+
+ return 0;
+ }
+
+ /*
+ * This may look like a "quiet fail", but this is needed when looping over a sequence
+ * and the last attribute has a default value. This will allow the loop to continue
+ * without failing.
+ */
+ return 0;
+ }
+
+ if (unlikely(fr_der_flag_is_choice(parent))) {
+ slen = fr_der_decode_choice(ctx, out, parent, &our_in, decode_ctx);
+
+ if (unlikely(slen < 0)) return slen;
+
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ if (unlikely(fr_der_decode_hdr(parent, &our_in, &tag, &len) < 0)) {
+ fr_strerror_const_push("Failed decoding header");
+ return -1;
+ }
+
+ FR_PROTO_TRACE("Attribute %s, tag %" PRIu64, parent->name, tag);
+
+ if (unlikely(tag != FR_DER_TAG_NULL) && (!fr_type_to_der_tag_valid(parent->type, tag) || fr_dbuff_remaining(&our_in) == 0)) {
+ if (fr_der_flag_has_default(parent)) {
+ /*
+ * If the attribute has a default value, we will use that.
+ * We could end up here if we are decoding a sequence which has a default value
+ * for an attribute that is not the last attribute in the sequence.
+ */
+ fr_pair_t *vp = fr_pair_afrom_da(ctx, parent);
+ fr_dict_enum_value_t *ev;
+
+ if (unlikely(!vp)) goto oom;
+
+ ev = fr_dict_enum_by_name(parent, "DEFAULT", strlen("DEFAULT"));
+ if (unlikely(ev == NULL)) {
+ fr_strerror_printf("No DEFAULT value for attribute %s", parent->name);
+ talloc_free(vp);
+ return -1;
+ }
+
+ if (fr_value_box_copy(vp, &vp->data, ev->value) < 0) {
+ talloc_free(vp);
+ goto oom;
+ }
+
+ vp->data.enumv = vp->da;
+
+ fr_pair_append(out, vp);
+
+ /*
+ * If we did not consume any bytes (tag didn't match, and DEFAULT was used), we
+ * should return 0 to indicate that we successfully decoded the attribute.
+ * If we did consume bytes, we should return the number of bytes consumed.
+ */
+ if (fr_dbuff_remaining(&our_in) == 0) return fr_dbuff_set(in, &our_in);
+
+ return 0;
+ }
+
+ if (fr_type_is_octets(parent->type)) {
+ /*
+ * We will store the value as raw octets if indicated by the dictionary
+ */
+ tag = FR_DER_TAG_OCTETSTRING;
+
+ } else if (!(fr_type_to_der_tag_valid(parent->type, tag) && fr_type_is_structural(parent->type))) {
+ /*
+ * If this is not a sequence/set/structure like thing, then it does not have children that
+ * could have defaults.
+ */
+ fr_strerror_printf("Attribute %s of type %s cannot store type %" PRIu64, parent->name,
+ fr_type_to_str(parent->type), tag);
+ return -1;
+ }
+ }
+
+ if (fr_der_flag_is_extensions(parent)) {
+ slen = fr_der_decode_x509_extensions(ctx, out, &our_in, parent, decode_ctx);
+ if (slen < 0) return slen;
+
+ return fr_dbuff_set(in, &our_in);
+ }
+
+ func = &tag_funcs[tag];
+
+ /*
+ * Make sure the data length is less than the maximum allowed
+ */
+ switch (tag) {
+ case FR_DER_TAG_SEQUENCE:
+ case FR_DER_TAG_SET:
+ break;
+ default:
+ max = fr_der_flag_max(parent) ? fr_der_flag_max(parent) : DER_MAX_STR;
+
+ if (unlikely(len > max)) {
+ fr_strerror_printf("Data length (%zu) exceeds max size (%" PRIu64 ")", len, max);
+ return -1;
+ }
+ break;
+ }
+
+ if (tag != FR_DER_TAG_OID) {
+ fr_dbuff_set_end(&our_in, fr_dbuff_current(&our_in) + len);
+ slen = func->decode(ctx, out, parent, &our_in, decode_ctx);
+
+ } else {
+ fr_der_decode_oid_to_str_ctx_t uctx = {
+ .ctx = ctx,
+ .parent_da = parent,
+ .parent_list = out,
+ .oid_buff = {},
+ .marker = {},
+ };
+
+ fr_dbuff_set_end(&our_in, fr_dbuff_current(&our_in) + len);
+
+ slen = fr_der_decode_oid(out, &our_in, fr_der_decode_oid_to_str, &uctx);
+ }
+
+ if (unlikely(slen < 0)) return slen;
+
+ return fr_dbuff_set(in, &our_in);
+}
+
+static ssize_t fr_der_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len,
+ void *proto_ctx)
+{
+ fr_dbuff_t our_in = FR_DBUFF_TMP(data, data_len);
+
+ fr_dict_attr_t const *parent = fr_dict_root(dict_der);
+
+ if (unlikely(parent == fr_dict_root(dict_der))) {
+ fr_strerror_printf("Invalid dictionary. DER decoding requires a specific dictionary.");
+ return -1;
+ }
+
+ return fr_der_decode_pair_dbuff(ctx, out, parent, &our_in, proto_ctx);
+}
+
+/** Decode a DER structure using the specific dictionary
+ *
+ * @param[in] ctx to allocate new pairs in.
+ * @param[in] out where new VPs will be added
+ * @param[in] parent Parent attribute. This should be the root of the dictionary
+ * we're using to decode DER data. This only specifies structures
+ * like SEQUENCES. OID based pairs are resolved using the global
+ * dictionary tree.
+ *
+ */
+static ssize_t decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent, uint8_t const *data,
+ size_t data_len, void *decode_ctx)
+{
+ if (unlikely(parent == fr_dict_root(dict_der))) {
+ fr_strerror_printf("Invalid dictionary. DER decoding requires a specific dictionary.");
+ return -1;
+ }
+
+ return fr_der_decode_pair_dbuff(ctx, out, parent, &FR_DBUFF_TMP(data, data_len), decode_ctx);
+}
+
+/*
+ * Test points
+ */
+static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict)
+{
+ fr_der_decode_ctx_t *test_ctx;
+
+ test_ctx = talloc_zero(ctx, fr_der_decode_ctx_t);
+ if (!test_ctx) return -1;
+
+ test_ctx->tmp_ctx = talloc(test_ctx, uint8_t);
+ test_ctx->oid_value_pairs = false;
+
+ *out = test_ctx;
+
+ return 0;
+}
+
+extern fr_test_point_pair_decode_t der_tp_decode_pair;
+fr_test_point_pair_decode_t der_tp_decode_pair = {
+ .test_ctx = decode_test_ctx,
+ .func = decode_pair,
+};
+
+extern fr_test_point_proto_decode_t der_tp_decode_proto;
+fr_test_point_proto_decode_t der_tp_decode_proto = {
+ .test_ctx = decode_test_ctx,
+ .func = fr_der_decode_proto,
+};