Skip to content

Commit 199b709

Browse files
author
Hanno Becker
committed
ASN.1: Add ASN.1 SEQUENCE traversal API
1 parent b5c74a5 commit 199b709

File tree

4 files changed

+283
-0
lines changed

4 files changed

+283
-0
lines changed

include/mbedtls/asn1.h

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,100 @@ int mbedtls_asn1_get_sequence_of( unsigned char **p,
413413
*/
414414
void mbedtls_asn1_sequence_free( mbedtls_asn1_sequence *seq );
415415

416+
/**
417+
* \brief Traverse an ASN.1 SEQUENCE container and
418+
* call a callback for each entry.
419+
*
420+
* This function checks that the input is a SEQUENCE of elements that
421+
* each have a "must" tag, and calls a callback function on the elements
422+
* that have a "may" tag.
423+
*
424+
* For example, to validate that the input is a SEQUENCE of `tag1` and call
425+
* `cb` on each element, use
426+
* ```
427+
* mbedtls_asn1_traverse_sequence_of(&p, end, 0xff, tag1, 0, 0, cb, ctx);
428+
* ```
429+
*
430+
* To validate that the input is a SEQUENCE of ANY and call `cb` on
431+
* each element, use
432+
* ```
433+
* mbedtls_asn1_traverse_sequence_of(&p, end, 0, 0, 0, 0, cb, ctx);
434+
* ```
435+
*
436+
* To validate that the input is a SEQUENCE of CHOICE {NULL, OCTET STRING}
437+
* and call `cb` on each element that is an OCTET STRING, use
438+
* ```
439+
* mbedtls_asn1_traverse_sequence_of(&p, end, 0xfe, 0x04, 0xff, 0x04, cb, ctx);
440+
* ```
441+
*
442+
* The callback is called on the elements with a "may" tag from left to
443+
* right. If the input is not a valid SEQUENCE of elements with a "must" tag,
444+
* the callback is called on the elements up to the leftmost point where
445+
* the input is invalid.
446+
*
447+
* \warning This function is still experimental and may change
448+
* at any time.
449+
*
450+
* \param p The address of the pointer to the beginning of
451+
* the ASN.1 SEQUENCE header. This is updated to
452+
* point to the end of the ASN.1 SEQUENCE container
453+
* on a successful invocation.
454+
* \param end The end of the ASN.1 SEQUENCE container.
455+
* \param tag_must_mask A mask to be applied to the ASN.1 tags found within
456+
* the SEQUENCE before comparing to \p tag_must_value.
457+
* \param tag_must_val The required value of each ASN.1 tag found in the
458+
* SEQUENCE, after masking with \p tag_must_mask.
459+
* Mismatching tags lead to an error.
460+
* For example, a value of \c 0 for both \p tag_must_mask
461+
* and \p tag_must_val means that every tag is allowed,
462+
* while a value of \c 0xFF for \p tag_must_mask means
463+
* that \p tag_must_val is the only allowed tag.
464+
* \param tag_may_mask A mask to be applied to the ASN.1 tags found within
465+
* the SEQUENCE before comparing to \p tag_may_value.
466+
* \param tag_may_val The desired value of each ASN.1 tag found in the
467+
* SEQUENCE, after masking with \p tag_may_mask.
468+
* Mismatching tags will be silently ignored.
469+
* For example, a value of \c 0 for \p tag_may_mask and
470+
* \p tag_may_val means that any tag will be considered,
471+
* while a value of \c 0xFF for \p tag_may_mask means
472+
* that all tags with value different from \p tag_may_val
473+
* will be ignored.
474+
* \param cb The callback to trigger for each component
475+
* in the ASN.1 SEQUENCE that matches \p tag_may_val.
476+
* The callback function is called with the following
477+
* parameters:
478+
* - \p ctx.
479+
* - The tag of the current element.
480+
* - A pointer to the start of the current element's
481+
* content inside the input.
482+
* - The length of the content of the current element.
483+
* If the callback returns a non-zero value,
484+
* the function stops immediately,
485+
* forwarding the callback's return value.
486+
* \param ctx The context to be passed to the callback \p cb.
487+
*
488+
* \return \c 0 if successful the entire ASN.1 SEQUENCE
489+
* was traversed without parsing or callback errors.
490+
* \return #MBEDTLS_ERR_ASN1_LENGTH_MISMATCH if the input
491+
* contains extra data after a valid SEQUENCE
492+
* of elements with an accepted tag.
493+
* \return #MBEDTLS_ERR_ASN1_UNEXPECTED_TAG if the input starts
494+
* with an ASN.1 SEQUENCE in which an element has a tag
495+
* that is not accepted.
496+
* \return An ASN.1 error code if the input does not start with
497+
* a valid ASN.1 SEQUENCE.
498+
* \return A non-zero error code forwarded from the callback
499+
* \p cb in case the latter returns a non-zero value.
500+
*/
501+
int mbedtls_asn1_traverse_sequence_of(
502+
unsigned char **p,
503+
const unsigned char *end,
504+
uint8_t tag_must_mask, uint8_t tag_must_val,
505+
uint8_t tag_may_mask, uint8_t tag_may_val,
506+
int (*cb)( void *ctx, int tag,
507+
unsigned char* start, size_t len ),
508+
void *ctx );
509+
416510
#if defined(MBEDTLS_BIGNUM_C)
417511
/**
418512
* \brief Retrieve an integer ASN.1 tag and its value.

library/asn1parse.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,58 @@ int mbedtls_asn1_get_bitstring( unsigned char **p, const unsigned char *end,
247247
return( 0 );
248248
}
249249

250+
/*
251+
* Traverse an ASN.1 "SEQUENCE OF <tag>"
252+
* and call a callback for each entry found.
253+
*/
254+
int mbedtls_asn1_traverse_sequence_of(
255+
unsigned char **p,
256+
const unsigned char *end,
257+
uint8_t tag_must_mask, uint8_t tag_must_val,
258+
uint8_t tag_may_mask, uint8_t tag_may_val,
259+
int (*cb)( void *ctx, int tag,
260+
unsigned char *start, size_t len ),
261+
void *ctx )
262+
{
263+
int ret;
264+
size_t len;
265+
266+
/* Get main sequence tag */
267+
if( ( ret = mbedtls_asn1_get_tag( p, end, &len,
268+
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ) != 0 )
269+
{
270+
return( ret );
271+
}
272+
273+
if( *p + len != end )
274+
return( MBEDTLS_ERR_ASN1_LENGTH_MISMATCH );
275+
276+
while( *p < end )
277+
{
278+
unsigned char const tag = *(*p)++;
279+
280+
if( ( tag & tag_must_mask ) != tag_must_val )
281+
return( MBEDTLS_ERR_ASN1_UNEXPECTED_TAG );
282+
283+
if( ( ret = mbedtls_asn1_get_len( p, end, &len ) ) != 0 )
284+
return( ret );
285+
286+
if( ( tag & tag_may_mask ) == tag_may_val )
287+
{
288+
if( cb != NULL )
289+
{
290+
ret = cb( ctx, tag, *p, len );
291+
if( ret != 0 )
292+
return( ret );
293+
}
294+
}
295+
296+
*p += len;
297+
}
298+
299+
return( 0 );
300+
}
301+
250302
/*
251303
* Get a bit string without unused bits
252304
*/

tests/suites/test_suite_asn1parse.data

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,60 @@ get_sequence_of:"1000":0x04:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
481481
Not a SEQUENCE (not SEQUENCE)
482482
get_sequence_of:"3100":0x04:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
483483

484+
Traverse empty SEQUENCE
485+
traverse_sequence_of:"3000":0:0:0:0:"":0
486+
487+
Traverse empty SEQUENCE plus trailing garbage
488+
traverse_sequence_of:"30007e":0:0:0:0:"":MBEDTLS_ERR_ASN1_LENGTH_MISMATCH
489+
490+
Traverse SEQUENCE of INTEGER: 1 INTEGER
491+
traverse_sequence_of:"30050203123456":0xff:0x02:0:0:"4,0x02,3":0
492+
493+
Traverse SEQUENCE of INTEGER: 2 INTEGERs
494+
traverse_sequence_of:"30080203123456020178":0xff:0x02:0:0:"4,0x02,3,9,0x02,1":0
495+
496+
Traverse SEQUENCE of INTEGER: INTEGER, NULL
497+
traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"4,0x02,3":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
498+
499+
Traverse SEQUENCE of INTEGER: NULL, INTEGER
500+
traverse_sequence_of:"300705000203123456":0xff:0x02:0:0:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
501+
502+
Traverse SEQUENCE of ANY: NULL, INTEGER
503+
traverse_sequence_of:"300705000203123456":0:0:0:0:"4,0x05,0,6,0x02,3":0
504+
505+
Traverse SEQUENCE of ANY, skip non-INTEGER: INTEGER, NULL
506+
traverse_sequence_of:"300702031234560500":0:0:0xff:0x02:"4,0x02,3":0
507+
508+
Traverse SEQUENCE of ANY, skip non-INTEGER: NULL, INTEGER
509+
traverse_sequence_of:"300705000203123456":0:0:0xff:0x02:"6,0x02,3":0
510+
511+
Traverse SEQUENCE of INTEGER, skip everything
512+
traverse_sequence_of:"30080203123456020178":0xff:0x02:0:1:"":0
513+
514+
Traverse SEQUENCE of {NULL, OCTET STRING}, skip NULL: OS, NULL
515+
traverse_sequence_of:"300704031234560500":0xfe:0x04:0xff:0x04:"4,0x04,3":0
516+
517+
Traverse SEQUENCE of {NULL, OCTET STRING}, skip NULL: NULL, OS
518+
traverse_sequence_of:"300705000403123456":0xfe:0x04:0xff:0x04:"6,0x04,3":0
519+
520+
Traverse SEQUENCE of {NULL, OCTET STRING}, skip everything
521+
traverse_sequence_of:"300705000403123456":0xfe:0x04:0:1:"":0
522+
523+
Traverse SEQUENCE of INTEGER, stop at 0: NULL
524+
traverse_sequence_of:"30020500":0xff:0x02:0:0:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
525+
526+
Traverse SEQUENCE of INTEGER, stop at 0: INTEGER
527+
traverse_sequence_of:"30050203123456":0xff:0x02:0:0:"":RET_TRAVERSE_STOP
528+
529+
Traverse SEQUENCE of INTEGER, stop at 0: INTEGER, NULL
530+
traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"":RET_TRAVERSE_STOP
531+
532+
Traverse SEQUENCE of INTEGER, stop at 1: INTEGER, NULL
533+
traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"4,0x02,3":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
534+
535+
Traverse SEQUENCE of INTEGER, stop at 1: INTEGER, INTEGER
536+
traverse_sequence_of:"30080203123456020178":0xff:0x02:0:0:"4,0x02,3":RET_TRAVERSE_STOP
537+
484538
AlgorithmIdentifier, no params
485539
get_alg:"300506034f4944":4:3:0:0:0:7:0
486540

tests/suites/test_suite_asn1parse.function

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,53 @@ exit:
170170
return( 0 );
171171
}
172172

173+
typedef struct
174+
{
175+
const unsigned char *input_start;
176+
const char *description;
177+
} traverse_state_t;
178+
179+
/* Value returned by traverse_callback if description runs out. */
180+
#define RET_TRAVERSE_STOP 1
181+
/* Value returned by traverse_callback if description has an invalid format
182+
* (see traverse_sequence_of). */
183+
#define RET_TRAVERSE_ERROR 2
184+
185+
186+
static int traverse_callback( void *ctx, int tag,
187+
unsigned char *content, size_t len )
188+
{
189+
traverse_state_t *state = ctx;
190+
size_t offset;
191+
const char *rest = state->description;
192+
unsigned long n;
193+
194+
TEST_ASSERT( content > state->input_start );
195+
offset = content - state->input_start;
196+
test_set_step( offset );
197+
198+
if( *rest == 0 )
199+
return( RET_TRAVERSE_STOP );
200+
n = strtoul( rest, (char **) &rest, 0 );
201+
TEST_EQUAL( n, offset );
202+
TEST_EQUAL( *rest, ',' );
203+
++rest;
204+
n = strtoul( rest, (char **) &rest, 0 );
205+
TEST_EQUAL( n, (unsigned) tag );
206+
TEST_EQUAL( *rest, ',' );
207+
++rest;
208+
n = strtoul( rest, (char **) &rest, 0 );
209+
TEST_EQUAL( n, len );
210+
if( *rest == ',' )
211+
++rest;
212+
213+
state->description = rest;
214+
return( 0 );
215+
216+
exit:
217+
return( RET_TRAVERSE_ERROR );
218+
}
219+
173220
/* END_HEADER */
174221

175222
/* BEGIN_DEPENDENCIES
@@ -507,6 +554,13 @@ void get_sequence_of( const data_t *input, int tag,
507554
const char *description,
508555
int expected_result )
509556
{
557+
/* The description string is a comma-separated list of integers.
558+
* For each element in the SEQUENCE in input, description contains
559+
* two integers: the offset of the element (offset from the start
560+
* of input to the tag of the element) and the length of the
561+
* element's contents.
562+
* "offset1,length1,..." */
563+
510564
mbedtls_asn1_sequence head = { { 0, 0, NULL }, NULL };
511565
mbedtls_asn1_sequence *cur;
512566
unsigned char *p = input->x;
@@ -553,6 +607,35 @@ exit:
553607
}
554608
/* END_CASE */
555609

610+
/* BEGIN_CASE */
611+
void traverse_sequence_of( const data_t *input,
612+
int tag_must_mask, int tag_must_val,
613+
int tag_may_mask, int tag_may_val,
614+
const char *description,
615+
int expected_result )
616+
{
617+
/* The description string is a comma-separated list of integers.
618+
* For each element in the SEQUENCE in input, description contains
619+
* three integers: the offset of the element's content (offset from
620+
* the start of input to the content of the element), the element's tag,
621+
* and the length of the element's contents.
622+
* "offset1,tag1,length1,..." */
623+
624+
unsigned char *p = input->x;
625+
traverse_state_t traverse_state = {input->x, description};
626+
int ret;
627+
628+
ret = mbedtls_asn1_traverse_sequence_of( &p, input->x + input->len,
629+
(uint8_t) tag_must_mask, (uint8_t) tag_must_val,
630+
(uint8_t) tag_may_mask, (uint8_t) tag_may_val,
631+
traverse_callback, &traverse_state );
632+
if( ret == RET_TRAVERSE_ERROR )
633+
goto exit;
634+
TEST_EQUAL( ret, expected_result );
635+
TEST_EQUAL( *traverse_state.description, 0 );
636+
}
637+
/* END_CASE */
638+
556639
/* BEGIN_CASE */
557640
void get_alg( const data_t *input,
558641
int oid_offset, int oid_length,

0 commit comments

Comments
 (0)