]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
encode and decode IPv4 and IPv6 prefixes
authorAlan T. DeKok <aland@freeradius.org>
Tue, 25 Feb 2025 21:25:54 +0000 (16:25 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 25 Feb 2025 21:25:54 +0000 (16:25 -0500)
src/protocols/der/base.c
src/protocols/der/decode.c
src/protocols/der/encode.c
src/tests/unit/protocols/der/base.txt
src/tests/unit/protocols/der/dictionary.test

index 8d082bc76ff638c38a4be3c1d6a12681d00f3f8a..dc27383329fd273c8fccb3c7af3ba8098d8d70c1 100644 (file)
@@ -108,10 +108,18 @@ static const bool *fr_type_to_der_tags[FR_DER_TAG_MAX] = {
                [FR_DER_TAG_BITSTRING] = true,
        },
 
+       [FR_TYPE_IPV4_PREFIX] = (bool [FR_DER_TAG_MAX]) {
+               [FR_DER_TAG_BITSTRING] = true,
+       },
+
        [FR_TYPE_IPV6_ADDR] = (bool [FR_DER_TAG_MAX]) {
                [FR_DER_TAG_BITSTRING] = true,
        },
 
+       [FR_TYPE_IPV6_PREFIX] = (bool [FR_DER_TAG_MAX]) {
+               [FR_DER_TAG_BITSTRING] = true,
+       },
+
        [FR_TYPE_BOOL] = (bool [FR_DER_TAG_MAX]) {
                [FR_DER_TAG_BOOLEAN] = true,
                [FR_DER_TAG_INTEGER] = true,
@@ -595,8 +603,6 @@ static bool type_parse(fr_type_t *type_p,fr_dict_attr_t **da_p, char const *name
                fr_strerror_const("Cannot use 'tlv' in DER.  Please use 'sequence'");
                return false;
 
-       case FR_TYPE_IPV4_PREFIX:
-       case FR_TYPE_IPV6_PREFIX:
        case FR_TYPE_IFID:
        case FR_TYPE_COMBO_IP_ADDR:
        case FR_TYPE_COMBO_IP_PREFIX:
@@ -687,7 +693,9 @@ static const fr_der_tag_t fr_type_to_der_tag_defaults[FR_TYPE_MAX + 1] = {
        [FR_TYPE_STRING]        = FR_DER_TAG_UTF8_STRING,
 
        [FR_TYPE_IPV4_ADDR]     = FR_DER_TAG_BITSTRING,
+       [FR_TYPE_IPV4_PREFIX]   = FR_DER_TAG_BITSTRING,
        [FR_TYPE_IPV6_ADDR]     = FR_DER_TAG_BITSTRING,
+       [FR_TYPE_IPV6_PREFIX]   = FR_DER_TAG_BITSTRING,
 
        [FR_TYPE_BOOL]          = FR_DER_TAG_BOOLEAN,
 
index 4977beff0e78bda9dcbf4c2f640358e6ea5e5652..9e06b32fcfc5ba99a1cd59d340306e9f7c319d76 100644 (file)
@@ -1452,6 +1452,20 @@ static ssize_t fr_der_decode_ipv4_addr(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_
        fr_pair_t *vp;
        fr_dbuff_t our_in = FR_DBUFF(in);
 
+       /*
+        *      RFC3779 Section 2.1.1.
+        *
+        *      An IP address or prefix is encoded in the IP address delegation
+        *      extension as a DER-encoded ASN.1 BIT STRING containing the constant
+        *      most-significant bits.  Recall [X.690] that the DER encoding of a BIT
+        *      STRING consists of the BIT STRING type (0x03), followed by (an
+        *      encoding of) the number of value octets, followed by the value.  The
+        *      value consists of an "initial octet" that specifies the number of
+        *      unused bits in the last value octet, followed by the "subsequent
+        *      octets" that contain the octets of the bit string.  (For IP
+        *      addresses, the encoding of the length will be just the length.)
+        */
+
        if (fr_dbuff_remaining(&our_in) != 1 + sizeof(vp->vp_ipv4addr)) {
                fr_strerror_printf("Invalid ipv4addr size.  Expected %zu, got %zu",
                                   1 + sizeof(vp->vp_ipv4addr), fr_dbuff_remaining(&our_in));
@@ -1479,6 +1493,57 @@ static ssize_t fr_der_decode_ipv4_addr(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_
        return fr_dbuff_set(in, &our_in);
 }
 
+static ssize_t fr_der_decode_ipv4_prefix(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)
+{
+       uint8_t byte;
+       fr_pair_t *vp;
+       fr_dbuff_t our_in = FR_DBUFF(in);
+       size_t len = fr_dbuff_remaining(&our_in);
+
+       /*
+        *      RFC3779 Section 2.1.1.
+        *
+        *      An IP address or prefix is encoded in the IP address delegation
+        *      extension as a DER-encoded ASN.1 BIT STRING containing the constant
+        *      most-significant bits.  Recall [X.690] that the DER encoding of a BIT
+        *      STRING consists of the BIT STRING type (0x03), followed by (an
+        *      encoding of) the number of value octets, followed by the value.  The
+        *      value consists of an "initial octet" that specifies the number of
+        *      unused bits in the last value octet, followed by the "subsequent
+        *      octets" that contain the octets of the bit string.  (For IP
+        *      addresses, the encoding of the length will be just the length.)
+        */
+
+       if (!len || (len > 1 + sizeof(vp->vp_ipv4addr))) {
+               fr_strerror_printf("Invalid ipv4prefix size.  Expected 1..%zu, got %zu",
+                                  1 + sizeof(vp->vp_ipv4addr), len);
+               return -1;
+       }
+       len--;
+
+       FR_DBUFF_OUT_RETURN(&byte, &our_in);
+       if (byte > 7) {
+               fr_strerror_printf("Invalid ipv4prefix is too large (%02x)", byte);
+               return -1;
+       }
+
+       vp = fr_pair_afrom_da(ctx, parent);
+       if (unlikely(!vp)) {
+               fr_strerror_const("Out of memory");
+               return -1;
+       }
+
+       vp->vp_ip.af = AF_INET;
+       vp->vp_ip.prefix = len * 8 - byte;
+
+       if (len) FR_DBUFF_OUT_MEMCPY_RETURN((uint8_t *) &vp->vp_ipv4addr, &our_in, len);
+
+       fr_pair_append(out, vp);
+
+       return fr_dbuff_set(in, &our_in);
+}
+
 static ssize_t fr_der_decode_ipv6_addr(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)
 {
@@ -1486,6 +1551,20 @@ static ssize_t fr_der_decode_ipv6_addr(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_
        fr_pair_t *vp;
        fr_dbuff_t our_in = FR_DBUFF(in);
 
+       /*
+        *      RFC3779 Section 2.1.1.
+        *
+        *      An IP address or prefix is encoded in the IP address delegation
+        *      extension as a DER-encoded ASN.1 BIT STRING containing the constant
+        *      most-significant bits.  Recall [X.690] that the DER encoding of a BIT
+        *      STRING consists of the BIT STRING type (0x03), followed by (an
+        *      encoding of) the number of value octets, followed by the value.  The
+        *      value consists of an "initial octet" that specifies the number of
+        *      unused bits in the last value octet, followed by the "subsequent
+        *      octets" that contain the octets of the bit string.  (For IP
+        *      addresses, the encoding of the length will be just the length.)
+        */
+
        if (fr_dbuff_remaining(&our_in) != 1 + sizeof(vp->vp_ipv6addr)) {
                fr_strerror_printf("Invalid ipv6addr size.  Expected %zu, got %zu",
                                   1 + sizeof(vp->vp_ipv6addr), fr_dbuff_remaining(&our_in));
@@ -1513,6 +1592,57 @@ static ssize_t fr_der_decode_ipv6_addr(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_
        return fr_dbuff_set(in, &our_in);
 }
 
+static ssize_t fr_der_decode_ipv6_prefix(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)
+{
+       uint8_t byte;
+       fr_pair_t *vp;
+       fr_dbuff_t our_in = FR_DBUFF(in);
+       size_t len = fr_dbuff_remaining(&our_in);
+
+       /*
+        *      RFC3779 Section 2.1.1.
+        *
+        *      An IP address or prefix is encoded in the IP address delegation
+        *      extension as a DER-encoded ASN.1 BIT STRING containing the constant
+        *      most-significant bits.  Recall [X.690] that the DER encoding of a BIT
+        *      STRING consists of the BIT STRING type (0x03), followed by (an
+        *      encoding of) the number of value octets, followed by the value.  The
+        *      value consists of an "initial octet" that specifies the number of
+        *      unused bits in the last value octet, followed by the "subsequent
+        *      octets" that contain the octets of the bit string.  (For IP
+        *      addresses, the encoding of the length will be just the length.)
+        */
+
+       if (!len || (len > 1 + sizeof(vp->vp_ipv6addr))) {
+               fr_strerror_printf("Invalid ipv6prefix size.  Expected 1..%zu, got %zu",
+                                  1 + sizeof(vp->vp_ipv6addr), len);
+               return -1;
+       }
+       len--;
+
+       FR_DBUFF_OUT_RETURN(&byte, &our_in);
+       if (byte > 7) {
+               fr_strerror_printf("Invalid ipv6prefix is too large (%02x)", byte);
+               return -1;
+       }
+
+       vp = fr_pair_afrom_da(ctx, parent);
+       if (unlikely(!vp)) {
+               fr_strerror_const("Out of memory");
+               return -1;
+       }
+
+       vp->vp_ip.af = AF_INET;
+       vp->vp_ip.prefix = len * 8 - byte;
+
+       if (len) FR_DBUFF_OUT_MEMCPY_RETURN((uint8_t *) &vp->vp_ipv6addr, &our_in, len);
+
+       fr_pair_append(out, vp);
+
+       return fr_dbuff_set(in, &our_in);
+}
+
 static const fr_der_tag_decode_t tag_funcs[FR_DER_TAG_MAX] = {
        [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 },
@@ -1538,7 +1668,9 @@ static const fr_der_tag_decode_t tag_funcs[FR_DER_TAG_MAX] = {
 
 static const fr_der_tag_decode_t type_funcs[FR_TYPE_MAX] = {
        [FR_TYPE_IPV4_ADDR]     = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_ipv4_addr },
+       [FR_TYPE_IPV4_PREFIX]   = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_ipv4_prefix },
        [FR_TYPE_IPV6_ADDR]     = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_ipv6_addr },
+       [FR_TYPE_IPV6_PREFIX]   = { .constructed = FR_DER_TAG_PRIMITIVE, .decode = fr_der_decode_ipv6_prefix },
 };
 
 /** Decode the tag and length fields of a DER encoded structure
@@ -1626,18 +1758,8 @@ static ssize_t fr_der_decode_hdr(fr_dict_attr_t const *parent, fr_dbuff_t *in, u
                return -1;
        }
 
-       if (parent) switch (parent->type) {
-       case FR_TYPE_IPV4_ADDR:
-       case FR_TYPE_IPV6_ADDR:
-               func = &type_funcs[parent->type];
-               break;
-
-       default:
-               func = &tag_funcs[*tag];
-               break;
-       } else {
-               func = &tag_funcs[*tag];
-       }
+       func = &tag_funcs[*tag];
+       fr_assert(func != NULL);
 
        /*
         *      Check if the tag is an OID. OID tags will be handled differently
@@ -2370,16 +2492,8 @@ static ssize_t fr_der_decode_pair_dbuff(TALLOC_CTX *ctx, fr_pair_list_t *out, fr
                return fr_dbuff_set(in, &our_in);
        }
 
-       switch (parent->type) {
-       case FR_TYPE_IPV4_ADDR:
-       case FR_TYPE_IPV6_ADDR:
-               func = &type_funcs[parent->type];
-               break;
-
-       default:
-               func = &tag_funcs[tag];
-               break;
-       }
+       func = &type_funcs[parent->type];
+       if (!func->decode) func = &tag_funcs[tag];
        fr_assert(func != NULL);
 
        /*
@@ -2436,6 +2550,8 @@ static ssize_t fr_der_decode_pair_dbuff(TALLOC_CTX *ctx, fr_pair_list_t *out, fr
        }
 
        if (tag != FR_DER_TAG_OID) {
+               fr_assert(func->decode != NULL);
+
                /*
                 *      The decode function can return 0 if len==0.  This is true for 'null' data types, and
                 *      for variable-sized types such as strings.
index e1a9befbd732259b0da9272dd8dc5e7717a6cfa5..4c9152cfb9c7ae4c138efaa529febe6b00b1fadd 100644 (file)
@@ -386,6 +386,43 @@ static ssize_t fr_der_encode_ipv4_addr(fr_dbuff_t *dbuff, fr_dcursor_t *cursor,
        return fr_dbuff_set(dbuff, &our_dbuff);
 }
 
+static ssize_t fr_der_encode_ipv4_prefix(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, UNUSED fr_der_encode_ctx_t *encode_ctx)
+{
+       fr_dbuff_t       our_dbuff = FR_DBUFF(dbuff);
+       fr_pair_t const *vp;
+       size_t          len;
+
+       vp = fr_dcursor_current(cursor);
+       PAIR_VERIFY(vp);
+       fr_assert(!vp->da->flags.is_raw);
+
+       if (vp->vp_ip.prefix == 0) {
+               FR_DBUFF_IN_RETURN(&our_dbuff, (uint8_t) 0x00);
+               return fr_dbuff_set(dbuff, &our_dbuff);
+       }
+
+       /*
+        *      RFC3779 Section 2.1.1.
+        *
+        *      An IP address or prefix is encoded in the IP address delegation
+        *      extension as a DER-encoded ASN.1 BIT STRING containing the constant
+        *      most-significant bits.  Recall [X.690] that the DER encoding of a BIT
+        *      STRING consists of the BIT STRING type (0x03), followed by (an
+        *      encoding of) the number of value octets, followed by the value.  The
+        *      value consists of an "initial octet" that specifies the number of
+        *      unused bits in the last value octet, followed by the "subsequent
+        *      octets" that contain the octets of the bit string.  (For IP
+        *      addresses, the encoding of the length will be just the length.)
+        */
+       FR_DBUFF_IN_RETURN(&our_dbuff, (uint8_t) (8 - (vp->vp_ip.prefix & 0x07)));
+
+       len = (vp->vp_ip.prefix + 0x07) >> 3;
+
+       if (len) FR_DBUFF_IN_MEMCPY_RETURN(&our_dbuff, (uint8_t const *) &vp->vp_ipv4addr, len);
+
+       return fr_dbuff_set(dbuff, &our_dbuff);
+}
+
 static ssize_t fr_der_encode_ipv6_addr(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, UNUSED fr_der_encode_ctx_t *encode_ctx)
 {
        fr_dbuff_t       our_dbuff = FR_DBUFF(dbuff);
@@ -418,6 +455,38 @@ static ssize_t fr_der_encode_ipv6_addr(fr_dbuff_t *dbuff, fr_dcursor_t *cursor,
        return fr_dbuff_set(dbuff, &our_dbuff);
 }
 
+static ssize_t fr_der_encode_ipv6_prefix(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, UNUSED fr_der_encode_ctx_t *encode_ctx)
+{
+       fr_dbuff_t       our_dbuff = FR_DBUFF(dbuff);
+       fr_pair_t const *vp;
+       size_t          len;
+
+       vp = fr_dcursor_current(cursor);
+       PAIR_VERIFY(vp);
+       fr_assert(!vp->da->flags.is_raw);
+
+       /*
+        *      RFC3779 Section 2.1.1.
+        *
+        *      An IP address or prefix is encoded in the IP address delegation
+        *      extension as a DER-encoded ASN.1 BIT STRING containing the constant
+        *      most-significant bits.  Recall [X.690] that the DER encoding of a BIT
+        *      STRING consists of the BIT STRING type (0x03), followed by (an
+        *      encoding of) the number of value octets, followed by the value.  The
+        *      value consists of an "initial octet" that specifies the number of
+        *      unused bits in the last value octet, followed by the "subsequent
+        *      octets" that contain the octets of the bit string.  (For IP
+        *      addresses, the encoding of the length will be just the length.)
+        */
+
+       FR_DBUFF_IN_RETURN(&our_dbuff, (uint8_t) (8 - (vp->vp_ip.prefix & 0x07)));
+
+       len = (vp->vp_ip.prefix + 0x07) >> 3;
+
+       if (len) FR_DBUFF_IN_MEMCPY_RETURN(&our_dbuff, (uint8_t const *) &vp->vp_ipv6addr, len);
+
+       return fr_dbuff_set(dbuff, &our_dbuff);
+}
 
 
 static ssize_t fr_der_encode_octetstring(fr_dbuff_t *dbuff, fr_dcursor_t *cursor,
@@ -1576,7 +1645,9 @@ static const fr_der_tag_encode_t tag_funcs[FR_DER_TAG_MAX] = {
 
 static const fr_der_tag_encode_t type_funcs[FR_TYPE_MAX] = {
        [FR_TYPE_IPV4_ADDR]       = { .constructed = FR_DER_TAG_PRIMITIVE, .encode = fr_der_encode_ipv4_addr },
+       [FR_TYPE_IPV4_PREFIX]     = { .constructed = FR_DER_TAG_PRIMITIVE, .encode = fr_der_encode_ipv4_prefix },
        [FR_TYPE_IPV6_ADDR]       = { .constructed = FR_DER_TAG_PRIMITIVE, .encode = fr_der_encode_ipv6_addr },
+       [FR_TYPE_IPV6_PREFIX]     = { .constructed = FR_DER_TAG_PRIMITIVE, .encode = fr_der_encode_ipv6_prefix },
 };
 
 
@@ -1810,16 +1881,10 @@ static ssize_t encode_value(fr_dbuff_t *dbuff, UNUSED fr_da_stack_t *da_stack, U
 
        fr_assert(tag < FR_DER_TAG_MAX);
 
-       switch (vp->vp_type) {
-       case FR_TYPE_IPV4_ADDR:
-       case FR_TYPE_IPV6_ADDR:
-               func = &type_funcs[tag];
-               break;
+       func = &type_funcs[vp->vp_type];
+       if (!func->encode) func = &tag_funcs[tag];
 
-       default:
-               func = &tag_funcs[tag];
-               break;
-       }
+       fr_assert(func != NULL);
        fr_assert(func->encode != NULL);
 
        /*
index c7750ed338bac0813c4486ae88290e77a55e987a..7c1cf91c93fb306fb15b8c59f4be82350d544f9c 100644 (file)
@@ -884,5 +884,24 @@ proto-dictionary-root Test-IPv4-Address
 decode-pair 03 05 00 0a 05 00 04
 match Test-IPv4-Address = 10.5.0.4
 
+proto-dictionary der
+
+encode-pair Test-IPv4-Prefix = 10.5.0.0/23
+match 03 04 01 0a 05 00
+
+#
+#  Just the length byte
+#
+encode-pair Test-IPv4-Prefix = 0/0
+match 03 01 00
+
+proto-dictionary-root Test-IPv4-Prefix
+
+decode-pair 03 04 01 0a 05 00
+match Test-IPv4-Prefix = 10.5.0.0/23
+
+decode-pair 03 01 00
+match Test-IPv4-Prefix = 0.0.0.0/0
+
 count
-match 535
+match 545
index 7ce33f51eeee69cc08d6d92d17f666009d82e933..06e8971adcae114843f015add432d3377b40feae 100644 (file)
@@ -31,7 +31,9 @@ DEFINE        Test-Boolean                                    bool
 DEFINE Test-Integer                                    integer
 
 DEFINE Test-IPv4-Address                               ipv4addr
+DEFINE Test-IPv4-Prefix                                ipv4prefix
 DEFINE Test-IPv6-Address                               ipv4addr
+DEFINE Test-IPv6-Prefix                                ipv4prefix
 
 DEFINE Foo                                             sequence sequence_of=integer
 BEGIN Foo