From 8730610ae062661598168caf0d54e9817e8ad974 Mon Sep 17 00:00:00 2001 From: Hanno Becker Date: Thu, 21 Feb 2019 20:52:09 +0000 Subject: [PATCH] Introduce ASN.1 API for traversing ASN.1 SEQUENCEs This commit adds a new function `mbedtls_asn1_traverse_sequence_of()` which traverses an ASN.1 SEQUENCE and calls a user-provided callback for each entry. It allows to put the following constraints on the tags allowed in the SEQUENCE: - A tag mask and mandatory tag value w.r.t. that mask. A non-matching tag leads to an MBEDTLS_ERR_ASN1_UNEXPECTED_TAG error. For example, it the mask if 0xFF, this means that only a single tag will be allowed in the SEQUENCE. - A tag mask and optional tag value w.r.t. that mask. A non-matching tag is silently ignored. The main use for this flexibility is the traversal of the `SubjectAlternativeNames` extension, where some parts of the tag are fixed but some are flexible to indicate which type of name the entry describes. --- include/mbedtls/asn1.h | 52 +++++++++++++++++ library/asn1parse.c | 130 +++++++++++++++++++++++++++++------------ 2 files changed, 144 insertions(+), 38 deletions(-) diff --git a/include/mbedtls/asn1.h b/include/mbedtls/asn1.h index b49fe5ad4d..5b7b2b82d7 100644 --- a/include/mbedtls/asn1.h +++ b/include/mbedtls/asn1.h @@ -291,6 +291,58 @@ int mbedtls_asn1_get_sequence_of( unsigned char **p, mbedtls_asn1_sequence *cur, int tag); +/** + * \brief Traverse an ASN.1 SEQUENCE container and + * call a callback for each entry. + * + * \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. 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 A negative ASN.1 error code on a parsing failure. + * \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 a MPI value from an integer ASN.1 tag. diff --git a/library/asn1parse.c b/library/asn1parse.c index dffc67fb33..f24fee69f3 100644 --- a/library/asn1parse.c +++ b/library/asn1parse.c @@ -230,6 +230,93 @@ int mbedtls_asn1_get_bitstring_null( unsigned char **p, const unsigned char *end } +/* + * 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( ( tag & tag_may_mask ) == tag_may_val ) + { + if( ( ret = mbedtls_asn1_get_len( p, end, &len ) ) != 0 ) + return( ret ); + + if( cb != NULL ) + { + ret = cb( ctx, tag, *p, len ); + if( ret != 0 ) + return( ret ); + } + } + + *p += len; + } + + return( 0 ); +} + +typedef struct +{ + int tag; + mbedtls_asn1_sequence *cur; +} asn1_get_sequence_of_cb_ctx_t; + +static int asn1_get_sequence_of_cb( void *ctx, + int tag, + unsigned char *start, + size_t len ) +{ + asn1_get_sequence_of_cb_ctx_t *cb_ctx = + (asn1_get_sequence_of_cb_ctx_t *) ctx; + mbedtls_asn1_sequence *cur = + cb_ctx->cur; + + if( cur->buf.p != NULL ) + { + cur->next = + mbedtls_calloc( 1, sizeof( mbedtls_asn1_sequence ) ); + + if( cur->next == NULL ) + return( MBEDTLS_ERR_ASN1_ALLOC_FAILED ); + + cur = cur->next; + } + + cur->buf.p = start; + cur->buf.len = len; + cur->buf.tag = tag; + + cb_ctx->cur = cur; + return( 0 ); +} + /* * Parses and splits an ASN.1 "SEQUENCE OF " @@ -239,44 +326,11 @@ int mbedtls_asn1_get_sequence_of( unsigned char **p, mbedtls_asn1_sequence *cur, int tag) { - int ret; - size_t len; - mbedtls_asn1_buf *buf; - - /* 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( 1 ) - { - buf = &(cur->buf); - buf->tag = **p; - - if( ( ret = mbedtls_asn1_get_tag( p, end, &buf->len, tag ) ) != 0 ) - return( ret ); - - buf->p = *p; - *p += buf->len; - - if( *p == end ) - { - cur->next = NULL; - break; - } - - cur->next = (mbedtls_asn1_sequence*)mbedtls_calloc( 1, - sizeof( mbedtls_asn1_sequence ) ); - if( cur->next == NULL ) - return( MBEDTLS_ERR_ASN1_ALLOC_FAILED ); - - cur = cur->next; - } - - return( 0 ); + asn1_get_sequence_of_cb_ctx_t cb_ctx = { tag, cur }; + memset( cur, 0, sizeof( mbedtls_asn1_sequence ) ); + return( mbedtls_asn1_traverse_sequence_of( + p, end, 0xFF, tag, 0, 0, + asn1_get_sequence_of_cb, &cb_ctx ) ); } int mbedtls_asn1_get_alg( unsigned char **p,