ASN.1: Add ASN.1 SEQUENCE traversal API
This commit is contained in:
parent
b5c74a53d8
commit
199b709e53
4 changed files with 283 additions and 0 deletions
|
@ -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.
|
||||
|
|
|
@ -247,6 +247,58 @@ int mbedtls_asn1_get_bitstring( unsigned char **p, const unsigned char *end,
|
|||
return( 0 );
|
||||
}
|
||||
|
||||
/*
|
||||
* Traverse an ASN.1 "SEQUENCE OF <tag>"
|
||||
* 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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue