From 199b709e538161b1f7742b6f1f01618f72bc1b85 Mon Sep 17 00:00:00 2001 From: Hanno Becker Date: Wed, 11 Sep 2019 14:21:26 +0100 Subject: [PATCH] ASN.1: Add ASN.1 SEQUENCE traversal API --- include/mbedtls/asn1.h | 94 ++++++++++++++++++++++ library/asn1parse.c | 52 ++++++++++++ tests/suites/test_suite_asn1parse.data | 54 +++++++++++++ tests/suites/test_suite_asn1parse.function | 83 +++++++++++++++++++ 4 files changed, 283 insertions(+) diff --git a/include/mbedtls/asn1.h b/include/mbedtls/asn1.h index 0a2727e12..07fbe56a2 100644 --- a/include/mbedtls/asn1.h +++ b/include/mbedtls/asn1.h @@ -413,6 +413,100 @@ int mbedtls_asn1_get_sequence_of( unsigned char **p, */ void mbedtls_asn1_sequence_free( mbedtls_asn1_sequence *seq ); +/** + * \brief Traverse an ASN.1 SEQUENCE container and + * call a callback for each entry. + * + * This function checks that the input is a SEQUENCE of elements that + * each have a "must" tag, and calls a callback function on the elements + * that have a "may" tag. + * + * For example, to validate that the input is a SEQUENCE of `tag1` and call + * `cb` on each element, use + * ``` + * mbedtls_asn1_traverse_sequence_of(&p, end, 0xff, tag1, 0, 0, cb, ctx); + * ``` + * + * To validate that the input is a SEQUENCE of ANY and call `cb` on + * each element, use + * ``` + * mbedtls_asn1_traverse_sequence_of(&p, end, 0, 0, 0, 0, cb, ctx); + * ``` + * + * To validate that the input is a SEQUENCE of CHOICE {NULL, OCTET STRING} + * and call `cb` on each element that is an OCTET STRING, use + * ``` + * mbedtls_asn1_traverse_sequence_of(&p, end, 0xfe, 0x04, 0xff, 0x04, cb, ctx); + * ``` + * + * The callback is called on the elements with a "may" tag from left to + * right. If the input is not a valid SEQUENCE of elements with a "must" tag, + * the callback is called on the elements up to the leftmost point where + * the input is invalid. + * + * \warning This function is still experimental and may change + * at any time. + * + * \param p The address of the pointer to the beginning of + * the ASN.1 SEQUENCE header. This is updated to + * point to the end of the ASN.1 SEQUENCE container + * on a successful invocation. + * \param end The end of the ASN.1 SEQUENCE container. + * \param tag_must_mask A mask to be applied to the ASN.1 tags found within + * the SEQUENCE before comparing to \p tag_must_value. + * \param tag_must_val The required value of each ASN.1 tag found in the + * SEQUENCE, after masking with \p tag_must_mask. + * Mismatching tags lead to an error. + * For example, a value of \c 0 for both \p tag_must_mask + * and \p tag_must_val means that every tag is allowed, + * while a value of \c 0xFF for \p tag_must_mask means + * that \p tag_must_val is the only allowed tag. + * \param tag_may_mask A mask to be applied to the ASN.1 tags found within + * the SEQUENCE before comparing to \p tag_may_value. + * \param tag_may_val The desired value of each ASN.1 tag found in the + * SEQUENCE, after masking with \p tag_may_mask. + * Mismatching tags will be silently ignored. + * For example, a value of \c 0 for \p tag_may_mask and + * \p tag_may_val means that any tag will be considered, + * while a value of \c 0xFF for \p tag_may_mask means + * that all tags with value different from \p tag_may_val + * will be ignored. + * \param cb The callback to trigger for each component + * in the ASN.1 SEQUENCE that matches \p tag_may_val. + * The callback function is called with the following + * parameters: + * - \p ctx. + * - The tag of the current element. + * - A pointer to the start of the current element's + * content inside the input. + * - The length of the content of the current element. + * If the callback returns a non-zero value, + * the function stops immediately, + * forwarding the callback's return value. + * \param ctx The context to be passed to the callback \p cb. + * + * \return \c 0 if successful the entire ASN.1 SEQUENCE + * was traversed without parsing or callback errors. + * \return #MBEDTLS_ERR_ASN1_LENGTH_MISMATCH if the input + * contains extra data after a valid SEQUENCE + * of elements with an accepted tag. + * \return #MBEDTLS_ERR_ASN1_UNEXPECTED_TAG if the input starts + * with an ASN.1 SEQUENCE in which an element has a tag + * that is not accepted. + * \return An ASN.1 error code if the input does not start with + * a valid ASN.1 SEQUENCE. + * \return A non-zero error code forwarded from the callback + * \p cb in case the latter returns a non-zero value. + */ +int mbedtls_asn1_traverse_sequence_of( + unsigned char **p, + const unsigned char *end, + uint8_t tag_must_mask, uint8_t tag_must_val, + uint8_t tag_may_mask, uint8_t tag_may_val, + int (*cb)( void *ctx, int tag, + unsigned char* start, size_t len ), + void *ctx ); + #if defined(MBEDTLS_BIGNUM_C) /** * \brief Retrieve an integer ASN.1 tag and its value. diff --git a/library/asn1parse.c b/library/asn1parse.c index 3105d32b3..8506b49ae 100644 --- a/library/asn1parse.c +++ b/library/asn1parse.c @@ -247,6 +247,58 @@ int mbedtls_asn1_get_bitstring( unsigned char **p, const unsigned char *end, return( 0 ); } +/* + * Traverse an ASN.1 "SEQUENCE OF " + * and call a callback for each entry found. + */ +int mbedtls_asn1_traverse_sequence_of( + unsigned char **p, + const unsigned char *end, + uint8_t tag_must_mask, uint8_t tag_must_val, + uint8_t tag_may_mask, uint8_t tag_may_val, + int (*cb)( void *ctx, int tag, + unsigned char *start, size_t len ), + void *ctx ) +{ + int ret; + size_t len; + + /* Get main sequence tag */ + if( ( ret = mbedtls_asn1_get_tag( p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ) != 0 ) + { + return( ret ); + } + + if( *p + len != end ) + return( MBEDTLS_ERR_ASN1_LENGTH_MISMATCH ); + + while( *p < end ) + { + unsigned char const tag = *(*p)++; + + if( ( tag & tag_must_mask ) != tag_must_val ) + return( MBEDTLS_ERR_ASN1_UNEXPECTED_TAG ); + + if( ( ret = mbedtls_asn1_get_len( p, end, &len ) ) != 0 ) + return( ret ); + + if( ( tag & tag_may_mask ) == tag_may_val ) + { + if( cb != NULL ) + { + ret = cb( ctx, tag, *p, len ); + if( ret != 0 ) + return( ret ); + } + } + + *p += len; + } + + return( 0 ); +} + /* * Get a bit string without unused bits */ diff --git a/tests/suites/test_suite_asn1parse.data b/tests/suites/test_suite_asn1parse.data index e26f93af7..6a66ee9f5 100644 --- a/tests/suites/test_suite_asn1parse.data +++ b/tests/suites/test_suite_asn1parse.data @@ -481,6 +481,60 @@ get_sequence_of:"1000":0x04:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG Not a SEQUENCE (not SEQUENCE) get_sequence_of:"3100":0x04:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG +Traverse empty SEQUENCE +traverse_sequence_of:"3000":0:0:0:0:"":0 + +Traverse empty SEQUENCE plus trailing garbage +traverse_sequence_of:"30007e":0:0:0:0:"":MBEDTLS_ERR_ASN1_LENGTH_MISMATCH + +Traverse SEQUENCE of INTEGER: 1 INTEGER +traverse_sequence_of:"30050203123456":0xff:0x02:0:0:"4,0x02,3":0 + +Traverse SEQUENCE of INTEGER: 2 INTEGERs +traverse_sequence_of:"30080203123456020178":0xff:0x02:0:0:"4,0x02,3,9,0x02,1":0 + +Traverse SEQUENCE of INTEGER: INTEGER, NULL +traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"4,0x02,3":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG + +Traverse SEQUENCE of INTEGER: NULL, INTEGER +traverse_sequence_of:"300705000203123456":0xff:0x02:0:0:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG + +Traverse SEQUENCE of ANY: NULL, INTEGER +traverse_sequence_of:"300705000203123456":0:0:0:0:"4,0x05,0,6,0x02,3":0 + +Traverse SEQUENCE of ANY, skip non-INTEGER: INTEGER, NULL +traverse_sequence_of:"300702031234560500":0:0:0xff:0x02:"4,0x02,3":0 + +Traverse SEQUENCE of ANY, skip non-INTEGER: NULL, INTEGER +traverse_sequence_of:"300705000203123456":0:0:0xff:0x02:"6,0x02,3":0 + +Traverse SEQUENCE of INTEGER, skip everything +traverse_sequence_of:"30080203123456020178":0xff:0x02:0:1:"":0 + +Traverse SEQUENCE of {NULL, OCTET STRING}, skip NULL: OS, NULL +traverse_sequence_of:"300704031234560500":0xfe:0x04:0xff:0x04:"4,0x04,3":0 + +Traverse SEQUENCE of {NULL, OCTET STRING}, skip NULL: NULL, OS +traverse_sequence_of:"300705000403123456":0xfe:0x04:0xff:0x04:"6,0x04,3":0 + +Traverse SEQUENCE of {NULL, OCTET STRING}, skip everything +traverse_sequence_of:"300705000403123456":0xfe:0x04:0:1:"":0 + +Traverse SEQUENCE of INTEGER, stop at 0: NULL +traverse_sequence_of:"30020500":0xff:0x02:0:0:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG + +Traverse SEQUENCE of INTEGER, stop at 0: INTEGER +traverse_sequence_of:"30050203123456":0xff:0x02:0:0:"":RET_TRAVERSE_STOP + +Traverse SEQUENCE of INTEGER, stop at 0: INTEGER, NULL +traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"":RET_TRAVERSE_STOP + +Traverse SEQUENCE of INTEGER, stop at 1: INTEGER, NULL +traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"4,0x02,3":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG + +Traverse SEQUENCE of INTEGER, stop at 1: INTEGER, INTEGER +traverse_sequence_of:"30080203123456020178":0xff:0x02:0:0:"4,0x02,3":RET_TRAVERSE_STOP + AlgorithmIdentifier, no params get_alg:"300506034f4944":4:3:0:0:0:7:0 diff --git a/tests/suites/test_suite_asn1parse.function b/tests/suites/test_suite_asn1parse.function index 898f7297d..3419f03b5 100644 --- a/tests/suites/test_suite_asn1parse.function +++ b/tests/suites/test_suite_asn1parse.function @@ -170,6 +170,53 @@ exit: return( 0 ); } +typedef struct +{ + const unsigned char *input_start; + const char *description; +} traverse_state_t; + +/* Value returned by traverse_callback if description runs out. */ +#define RET_TRAVERSE_STOP 1 +/* Value returned by traverse_callback if description has an invalid format + * (see traverse_sequence_of). */ +#define RET_TRAVERSE_ERROR 2 + + +static int traverse_callback( void *ctx, int tag, + unsigned char *content, size_t len ) +{ + traverse_state_t *state = ctx; + size_t offset; + const char *rest = state->description; + unsigned long n; + + TEST_ASSERT( content > state->input_start ); + offset = content - state->input_start; + test_set_step( offset ); + + if( *rest == 0 ) + return( RET_TRAVERSE_STOP ); + n = strtoul( rest, (char **) &rest, 0 ); + TEST_EQUAL( n, offset ); + TEST_EQUAL( *rest, ',' ); + ++rest; + n = strtoul( rest, (char **) &rest, 0 ); + TEST_EQUAL( n, (unsigned) tag ); + TEST_EQUAL( *rest, ',' ); + ++rest; + n = strtoul( rest, (char **) &rest, 0 ); + TEST_EQUAL( n, len ); + if( *rest == ',' ) + ++rest; + + state->description = rest; + return( 0 ); + +exit: + return( RET_TRAVERSE_ERROR ); +} + /* END_HEADER */ /* BEGIN_DEPENDENCIES @@ -507,6 +554,13 @@ void get_sequence_of( const data_t *input, int tag, const char *description, int expected_result ) { + /* The description string is a comma-separated list of integers. + * For each element in the SEQUENCE in input, description contains + * two integers: the offset of the element (offset from the start + * of input to the tag of the element) and the length of the + * element's contents. + * "offset1,length1,..." */ + mbedtls_asn1_sequence head = { { 0, 0, NULL }, NULL }; mbedtls_asn1_sequence *cur; unsigned char *p = input->x; @@ -553,6 +607,35 @@ exit: } /* END_CASE */ +/* BEGIN_CASE */ +void traverse_sequence_of( const data_t *input, + int tag_must_mask, int tag_must_val, + int tag_may_mask, int tag_may_val, + const char *description, + int expected_result ) +{ + /* The description string is a comma-separated list of integers. + * For each element in the SEQUENCE in input, description contains + * three integers: the offset of the element's content (offset from + * the start of input to the content of the element), the element's tag, + * and the length of the element's contents. + * "offset1,tag1,length1,..." */ + + unsigned char *p = input->x; + traverse_state_t traverse_state = {input->x, description}; + int ret; + + ret = mbedtls_asn1_traverse_sequence_of( &p, input->x + input->len, + (uint8_t) tag_must_mask, (uint8_t) tag_must_val, + (uint8_t) tag_may_mask, (uint8_t) tag_may_val, + traverse_callback, &traverse_state ); + if( ret == RET_TRAVERSE_ERROR ) + goto exit; + TEST_EQUAL( ret, expected_result ); + TEST_EQUAL( *traverse_state.description, 0 ); +} +/* END_CASE */ + /* BEGIN_CASE */ void get_alg( const data_t *input, int oid_offset, int oid_length,