Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/der encoder decoder #5497

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

ethan-thompson
Copy link
Contributor

Wrote an encoder and decoder for DER. This adds the ability to decoder DER encoded X509 certificates and CSR's into dictionary attributes, as well as encode into DER.

src/protocols/der/base.c Outdated Show resolved Hide resolved
* @copyright 2024 Inkbridge Networks SAS.
*/

#include "include/build.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits: we usually put #include "foo" after all of the common includes. that way any location definitions don't affect the other header files.

*/

#include "include/build.h"
#include "der.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#include <freeradius-devel/util/table.h> etc.

};

fr_der_tag_constructed_t tag_labels[] = {
[FR_DER_TAG_BOOLEAN] = FR_DER_TAG_PRIMATIVE,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRIMATIVE? Is that a DER thing, or is it PRIMITIVE?

{
fr_der_attr_flags_t *flags = fr_dict_attr_ext(*da_p, FR_DICT_ATTR_EXT_PROTOCOL_SPECIFIC);

flags->tagnum = (uint8_t)atoi(value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want sanity checks here for overflow, or for bad formats?

Admins make mistakes. An error of "you screwed up" is usually better than silently doing the wrong thing.

static int dict_flag_set_of(fr_dict_attr_t **da_p, char const *value, UNUSED fr_dict_flag_parser_rule_t const *rules)
{
static fr_table_num_sorted_t const table[] = {
{ L("bitstring"), FR_DER_TAG_BITSTRING },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with repeated tables like this, it may be useful to put them into a common macro? depending on whether or not the C preprocessor likes the L() macro inside another macro :(

}

static fr_dict_flag_parser_t const der_flags[] = {
{ L("class"), { .func = dict_flag_class } },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whitespace / indentation


if (fr_der_flag_is_sequence_of(da->parent) || fr_der_flag_is_set_of(da->parent)) {
static fr_table_num_sorted_t const table[] = {
{ L("bitstring"), FR_DER_TAG_BITSTRING },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repeated repetition... :(

@@ -0,0 +1,171 @@
#include "include/build.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use <freeradius-devel/...

#include "include/build.h"
#include "lib/util/types.h"
#include <freeradius-devel/util/dict.h>
#include <stdbool.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stdbool and stdint should already be included by the types.h header

*/
static bool *fr_type_to_der_tags[] = {
[FR_TYPE_MAX] = NULL,
[FR_TYPE_BOOL] = (bool []){[FR_DER_TAG_BOOLEAN] = true, [FR_DER_TAG_INTEGER] = true, [FR_DER_TAG_NULL] = true, [FR_DER_TAG_MAX] = false},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe put line breaks after the , which can make the lines shorter, and a bit easier to read.


static inline CC_HINT(always_inline) fr_der_tag_num_t fr_type_to_der_tag_default(fr_type_t type)
{
return fr_type_to_der_tag_defaults[type];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about data types like FR_TYPE_IPADDR which aren't listed in the above table? The C compiler will initialize the array entry to 0. Is there a FR_DER_TAG_INVALID which is defined as 0 ?

#define DER_BOOLEAN_TRUE 0xff //!< DER encoded boolean true value.

typedef struct {
uint8_t tagnum;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe format these so that the names are further to the right, and then all lined up vertically. that makes it easier to read.

}

#define fr_der_flag_tagnum(_da) (fr_der_attr_flags(_da)->tagnum)
#define fr_der_flag_class(_da) (fr_der_attr_flags(_da)->class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whitespace nits :)

TARGET := libfreeradius-der$(L)

SOURCES := base.c \
decode.c \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe include push this commit until after the encode.c and decode.c files have been added. otherwise the build is broken.

* @copyright 2024 Arran Cudbard-Bell ([email protected])
* @copyright 2024 Inkbridge Networks SAS.
*/
#include "include/build.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

header name / "" versus <>

I'll stop commenting here. just take a pass through the code for these, and also stdbool etc. likely most of the std* includes can be dropped.

#include <freeradius-devel/util/time.h>
#include <freeradius-devel/util/value.h>
#include <stdlib.h>
#include <time.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

util/time.h already includes time.h


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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add CC_HINT(nonnull) for the function prototypes

[UINT8_MAX] = { .constructed = FR_DER_TAG_PRIMATIVE, .decode = NULL },
};

static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move the various test_ctx stuff to the bottom of the file, where it's located for the other protocols

}

/*
* ISO/IEC 8825-1:2021
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments and references to standards make me happy.

return -1;
}

if (unlikely(val != DER_BOOLEAN_FALSE && val != DER_BOOLEAN_TRUE)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

brackets around things. It's not required by C, but it cam avoid typos

if ((a == b) || (c == d))


vp = fr_pair_afrom_da(ctx, parent);
if (unlikely(vp == NULL)) {
fr_strerror_const("Out of memory for boolean pair");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just Out of memory is fine, like all of the other uses. arguably we could have a macro for this somewhere else in the code.

}

typedef struct {
TALLOC_CTX *ctx; /**< Allocation context */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments can be aligned vertically, and use //!< which is more consistent with the rest of the code.

char *str = NULL;

static bool const allowed_chars[] = {
[' '] = true, ['\''] = true, ['('] = true, [')'] = true, ['+'] = true, [','] = true, ['-'] = true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format as 4 per line, which makes it a bit easier to read

char *str = NULL;

static bool const allowed_chars[] = {
[0x08] = true, [0x0A] = true, [0x0C] = true, [0x0D] = true, [0x0E] = true, [0x0F] = true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see src/lib/util/token.c. GCC and clang support ranges, which makes these tables a lot easier

/*
 *	This is fine.  Don't complain.
 */
#ifdef __clang__
#pragma clang diagnostic ignored "-Wgnu-designator"
#endif
...
	[ 0 ... T_HASH ] = '?',	/* GCC extension for range initialization, also allowed by clang */

/*
* We have a multi-byte tag
*
* Note: Mutli-byte tags would mean having a tag number that is greater than 30 (0x1E) (since tag
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multi

PROTOCOL DER 11354911
BEGIN-PROTOCOL DER

$INCLUDE dictionary.test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't want test dictionaries in the default build.

the src/tests/unit stuff allows for local dictionaries to be loaded

DEFINE GeneralName choice
BEGIN GeneralName

ATTRIBUTE otherName 0 sequence option=0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run ./scripts/dict/format.dl share/dictionary/der/dictionary*

for consistent formatting

@ethan-thompson ethan-thompson force-pushed the feature/der-encoder-decoder branch from 74074a9 to 70badd1 Compare January 23, 2025 19:14
# Copyright (C) 2025 The FreeRADIUS Server project and contributors
# This work is licensed under CC-BY version 4.0 https://creativecommons.org/licenses/by/4.0
# Version $Id$
DEFINE Certificate-Extensions x509_extensions ref=OID-Tree
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run the dicts through scripts/dict/format.pl. I like pretty things

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the format after having run the format.pl script on the dictionaries

#

PROTOCOL DER 11354911
BEGIN-PROTOCOL DER
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete those two lines, and replace them with

BEGIN PROTOCOL DER 11354911

I think the server supports 64-bit protocol numbers? I haven't checked recently.

what's the significance of 11354911 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update that line. There was no significance in 11354911. I believe @arr2036 may have used this number to avoid collision

@ethan-thompson ethan-thompson force-pushed the feature/der-encoder-decoder branch from 70badd1 to ad1bf39 Compare January 27, 2025 22:08
fr_dbuff_t our_in = FR_DBUFF(in);
uint8_t val;

size_t len = fr_dbuff_remaining(&our_in);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check the return code of fr_dbuff_out instead of manually checking the length

@arr2036
Copy link
Member

arr2036 commented Jan 28, 2025

Default value flag should produce a value box. Using a DEFAULT enum value is counterintuitive.

@arr2036
Copy link
Member

arr2036 commented Jan 28, 2025

Where possible you should still remove fr_dbuff_remaining where fr_dbuff_out would perform the same check

@arr2036
Copy link
Member

arr2036 commented Jan 28, 2025

Have some bad news ... is a GCC extension, so probably wants to be converted to the explicit range

@arr2036
Copy link
Member

arr2036 commented Jan 28, 2025

Even with the artificial windows, it's probably better to use fr_dbuff_extend_lowat and check the return values. That lets the artificial "end" point to unrealised data.

@ethan-thompson ethan-thompson force-pushed the feature/der-encoder-decoder branch from ad1bf39 to d65db71 Compare February 3, 2025 20:33
@ethan-thompson ethan-thompson force-pushed the feature/der-encoder-decoder branch 2 times, most recently from 66c9ed8 to 41652a7 Compare February 12, 2025 16:19
@ethan-thompson ethan-thompson force-pushed the feature/der-encoder-decoder branch from 41652a7 to c4d809c Compare February 12, 2025 18:36
fr_dbuff_in(dbuff, (uint8_t)((slen) >> ((len_len - i - 1) * 8)));
}

fr_dbuff_set(dbuff, fr_dbuff_current(length_start) + len_len + 1 + slen);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right? The function encodes the length, and then skips over the length + the data which will be encoded?

and almost all calls to this function do

 ...fr_der_encode_len(&our_dbuff, &length_start, fr_dbuff_behind(&length_start) - 1)

except for one...

*/
fr_dict_enum_value_t const *evp;

evp = fr_dict_enum_by_name(vp->da, "DEFAULT", strlen("DEFAULT"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this in for now, but it could be in the dict_ext stuff?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made similar comments on the review call. It can just be a flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants