Skip to content

Commit b8fccb7

Browse files
Hanno Beckergilles-peskine-arm
authored andcommitted
ASN.1: Add ASN.1 SEQUENCE traversal API
1 parent 84a6cc3 commit b8fccb7

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
@@ -393,6 +393,100 @@ int mbedtls_asn1_get_sequence_of( unsigned char **p,
393393
*/
394394
void mbedtls_asn1_sequence_free( mbedtls_asn1_sequence *seq );
395395

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

232+
/*
233+
* Traverse an ASN.1 "SEQUENCE OF <tag>"
234+
* and call a callback for each entry found.
235+
*/
236+
int mbedtls_asn1_traverse_sequence_of(
237+
unsigned char **p,
238+
const unsigned char *end,
239+
uint8_t tag_must_mask, uint8_t tag_must_val,
240+
uint8_t tag_may_mask, uint8_t tag_may_val,
241+
int (*cb)( void *ctx, int tag,
242+
unsigned char *start, size_t len ),
243+
void *ctx )
244+
{
245+
int ret;
246+
size_t len;
247+
248+
/* Get main sequence tag */
249+
if( ( ret = mbedtls_asn1_get_tag( p, end, &len,
250+
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ) != 0 )
251+
{
252+
return( ret );
253+
}
254+
255+
if( *p + len != end )
256+
return( MBEDTLS_ERR_ASN1_LENGTH_MISMATCH );
257+
258+
while( *p < end )
259+
{
260+
unsigned char const tag = *(*p)++;
261+
262+
if( ( tag & tag_must_mask ) != tag_must_val )
263+
return( MBEDTLS_ERR_ASN1_UNEXPECTED_TAG );
264+
265+
if( ( ret = mbedtls_asn1_get_len( p, end, &len ) ) != 0 )
266+
return( ret );
267+
268+
if( ( tag & tag_may_mask ) == tag_may_val )
269+
{
270+
if( cb != NULL )
271+
{
272+
ret = cb( ctx, tag, *p, len );
273+
if( ret != 0 )
274+
return( ret );
275+
}
276+
}
277+
278+
*p += len;
279+
}
280+
281+
return( 0 );
282+
}
283+
232284
/*
233285
* Get a bit string without unused bits
234286
*/

tests/suites/test_suite_asn1parse.data

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,60 @@ get_sequence_of:"1000":0x04:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
397397
Not a SEQUENCE (not SEQUENCE)
398398
get_sequence_of:"3100":0x04:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
399399

400+
Traverse empty SEQUENCE
401+
traverse_sequence_of:"3000":0:0:0:0:"":0
402+
403+
Traverse empty SEQUENCE plus trailing garbage
404+
traverse_sequence_of:"30007e":0:0:0:0:"":MBEDTLS_ERR_ASN1_LENGTH_MISMATCH
405+
406+
Traverse SEQUENCE of INTEGER: 1 INTEGER
407+
traverse_sequence_of:"30050203123456":0xff:0x02:0:0:"4,0x02,3":0
408+
409+
Traverse SEQUENCE of INTEGER: 2 INTEGERs
410+
traverse_sequence_of:"30080203123456020178":0xff:0x02:0:0:"4,0x02,3,9,0x02,1":0
411+
412+
Traverse SEQUENCE of INTEGER: INTEGER, NULL
413+
traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"4,0x02,3":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
414+
415+
Traverse SEQUENCE of INTEGER: NULL, INTEGER
416+
traverse_sequence_of:"300705000203123456":0xff:0x02:0:0:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
417+
418+
Traverse SEQUENCE of ANY: NULL, INTEGER
419+
traverse_sequence_of:"300705000203123456":0:0:0:0:"4,0x05,0,6,0x02,3":0
420+
421+
Traverse SEQUENCE of ANY, skip non-INTEGER: INTEGER, NULL
422+
traverse_sequence_of:"300702031234560500":0:0:0xff:0x02:"4,0x02,3":0
423+
424+
Traverse SEQUENCE of ANY, skip non-INTEGER: NULL, INTEGER
425+
traverse_sequence_of:"300705000203123456":0:0:0xff:0x02:"6,0x02,3":0
426+
427+
Traverse SEQUENCE of INTEGER, skip everything
428+
traverse_sequence_of:"30080203123456020178":0xff:0x02:0:1:"":0
429+
430+
Traverse SEQUENCE of {NULL, OCTET STRING}, skip NULL: OS, NULL
431+
traverse_sequence_of:"300704031234560500":0xfe:0x04:0xff:0x04:"4,0x04,3":0
432+
433+
Traverse SEQUENCE of {NULL, OCTET STRING}, skip NULL: NULL, OS
434+
traverse_sequence_of:"300705000403123456":0xfe:0x04:0xff:0x04:"6,0x04,3":0
435+
436+
Traverse SEQUENCE of {NULL, OCTET STRING}, skip everything
437+
traverse_sequence_of:"300705000403123456":0xfe:0x04:0:1:"":0
438+
439+
Traverse SEQUENCE of INTEGER, stop at 0: NULL
440+
traverse_sequence_of:"30020500":0xff:0x02:0:0:"":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
441+
442+
Traverse SEQUENCE of INTEGER, stop at 0: INTEGER
443+
traverse_sequence_of:"30050203123456":0xff:0x02:0:0:"":RET_TRAVERSE_STOP
444+
445+
Traverse SEQUENCE of INTEGER, stop at 0: INTEGER, NULL
446+
traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"":RET_TRAVERSE_STOP
447+
448+
Traverse SEQUENCE of INTEGER, stop at 1: INTEGER, NULL
449+
traverse_sequence_of:"300702031234560500":0xff:0x02:0:0:"4,0x02,3":MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
450+
451+
Traverse SEQUENCE of INTEGER, stop at 1: INTEGER, INTEGER
452+
traverse_sequence_of:"30080203123456020178":0xff:0x02:0:0:"4,0x02,3":RET_TRAVERSE_STOP
453+
400454
AlgorithmIdentifier, no params
401455
get_alg:"300506034f4944":4:3:0:0:0:7:0
402456

tests/suites/test_suite_asn1parse.function

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,53 @@ exit:
167167
return( 0 );
168168
}
169169

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

172219
/* BEGIN_DEPENDENCIES
@@ -461,6 +508,13 @@ void get_sequence_of( const data_t *input, int tag,
461508
const char *description,
462509
int expected_result )
463510
{
511+
/* The description string is a comma-separated list of integers.
512+
* For each element in the SEQUENCE in input, description contains
513+
* two integers: the offset of the element (offset from the start
514+
* of input to the tag of the element) and the length of the
515+
* element's contents.
516+
* "offset1,length1,..." */
517+
464518
mbedtls_asn1_sequence head = { { 0, 0, NULL }, NULL };
465519
mbedtls_asn1_sequence *cur;
466520
unsigned char *p = input->x;
@@ -507,6 +561,35 @@ exit:
507561
}
508562
/* END_CASE */
509563

564+
/* BEGIN_CASE */
565+
void traverse_sequence_of( const data_t *input,
566+
int tag_must_mask, int tag_must_val,
567+
int tag_may_mask, int tag_may_val,
568+
const char *description,
569+
int expected_result )
570+
{
571+
/* The description string is a comma-separated list of integers.
572+
* For each element in the SEQUENCE in input, description contains
573+
* three integers: the offset of the element's content (offset from
574+
* the start of input to the content of the element), the element's tag,
575+
* and the length of the element's contents.
576+
* "offset1,tag1,length1,..." */
577+
578+
unsigned char *p = input->x;
579+
traverse_state_t traverse_state = {input->x, description};
580+
int ret;
581+
582+
ret = mbedtls_asn1_traverse_sequence_of( &p, input->x + input->len,
583+
(uint8_t) tag_must_mask, (uint8_t) tag_must_val,
584+
(uint8_t) tag_may_mask, (uint8_t) tag_may_val,
585+
traverse_callback, &traverse_state );
586+
if( ret == RET_TRAVERSE_ERROR )
587+
goto exit;
588+
TEST_EQUAL( ret, expected_result );
589+
TEST_EQUAL( *traverse_state.description, 0 );
590+
}
591+
/* END_CASE */
592+
510593
/* BEGIN_CASE */
511594
void get_alg( const data_t *input,
512595
int oid_offset, int oid_length,

0 commit comments

Comments
 (0)