Implement v3 Extension parsing through ASN.1 SEQUENCE OF traversal

This commit rewrites the v3 ext parsing routine `x509_crt_get_ext_cb()`
in terms of the generic ASN.1 SEQUENCE traversal routine.
diff --git a/library/x509_crt.c b/library/x509_crt.c
index ae82e92..c9bc163 100644
--- a/library/x509_crt.c
+++ b/library/x509_crt.c
@@ -393,7 +393,7 @@
 
     if( ( ret = mbedtls_asn1_get_tag( p, end, &len,
             MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ) != 0 )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
+        return( ret );
 
     if( *p == end )
         return( 0 );
@@ -404,7 +404,7 @@
             ret = mbedtls_asn1_get_int( p, end, ca_istrue );
 
         if( ret != 0 )
-            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
+            return( ret );
 
         if( *ca_istrue != 0 )
             *ca_istrue = 1;
@@ -414,11 +414,10 @@
         return( 0 );
 
     if( ( ret = mbedtls_asn1_get_int( p, end, max_pathlen ) ) != 0 )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
+        return( ret );
 
     if( *p != end )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
-                MBEDTLS_ERR_ASN1_LENGTH_MISMATCH );
+        return( MBEDTLS_ERR_ASN1_LENGTH_MISMATCH );
 
     (*max_pathlen)++;
 
@@ -433,11 +432,10 @@
     mbedtls_x509_bitstring bs = { 0, 0, NULL };
 
     if( ( ret = mbedtls_asn1_get_bitstring( p, end, &bs ) ) != 0 )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
+        return( ret );
 
     if( bs.len != 1 )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
-                MBEDTLS_ERR_ASN1_INVALID_LENGTH );
+        return( MBEDTLS_ERR_ASN1_INVALID_LENGTH );
 
     /* Get actual bitstring */
     *ns_cert_type = *bs.p;
@@ -453,11 +451,10 @@
     mbedtls_x509_bitstring bs = { 0, 0, NULL };
 
     if( ( ret = mbedtls_asn1_get_bitstring( p, end, &bs ) ) != 0 )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
+        return( ret );
 
     if( bs.len < 1 )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
-                MBEDTLS_ERR_ASN1_INVALID_LENGTH );
+        return( MBEDTLS_ERR_ASN1_INVALID_LENGTH );
 
     /* Get actual bitstring */
     *key_usage = 0;
@@ -478,17 +475,8 @@
                                const unsigned char *end,
                                mbedtls_x509_sequence *ext_key_usage)
 {
-    int ret;
-
-    if( ( ret = mbedtls_asn1_get_sequence_of( p, end, ext_key_usage, MBEDTLS_ASN1_OID ) ) != 0 )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
-
-    /* Sequence length must be >= 1 */
-    if( ext_key_usage->buf.p == NULL )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
-                MBEDTLS_ERR_ASN1_INVALID_LENGTH );
-
-    return( 0 );
+    return( mbedtls_asn1_get_sequence_of( p, end, ext_key_usage,
+                                          MBEDTLS_ASN1_OID ) );
 }
 
 /*
@@ -546,162 +534,202 @@
                                       const unsigned char *end,
                                       mbedtls_x509_sequence *subject_alt_name )
 {
-    int ret;
-    ret = mbedtls_asn1_traverse_sequence_of( &p, end,
-                                             MBEDTLS_ASN1_TAG_CLASS_MASK,
-                                             MBEDTLS_ASN1_CONTEXT_SPECIFIC,
-                                             MBEDTLS_ASN1_TAG_VALUE_MASK,
-                                             2 /* SubjectAlt DNS */,
-                                             x509_get_subject_alt_name_cb,
-                                             (void*) &subject_alt_name );
-    if( ret != 0 )
-        ret += MBEDTLS_ERR_X509_INVALID_EXTENSIONS;
-    return( ret );
+    return( mbedtls_asn1_traverse_sequence_of( &p, end,
+                                               MBEDTLS_ASN1_TAG_CLASS_MASK,
+                                               MBEDTLS_ASN1_CONTEXT_SPECIFIC,
+                                               MBEDTLS_ASN1_TAG_VALUE_MASK,
+                                               2 /* SubjectAlt DNS */,
+                                               x509_get_subject_alt_name_cb,
+                                               (void*) &subject_alt_name ) );
 }
 
 /*
  * X.509 v3 extensions
  *
  */
-static int x509_get_crt_ext( unsigned char **p,
-                             const unsigned char *end,
-                             mbedtls_x509_crt *crt )
+static int x509_crt_get_ext_cb( void *ctx,
+                                int tag,
+                                unsigned char *p,
+                                size_t ext_len )
 {
     int ret;
+    mbedtls_x509_crt *crt = (mbedtls_x509_crt *) ctx;
     size_t len;
-    unsigned char *end_ext_data, *end_ext_octet;
+    unsigned char *end, *end_ext_octet;
+    mbedtls_x509_buf extn_oid = { 0, 0, NULL };
+    int is_critical = 0; /* DEFAULT FALSE */
+    int ext_type = 0;
 
-    if( *p == end )
-        return( 0 );
+    ((void) tag);
 
-    if( ( ret = mbedtls_x509_get_ext( p, end, &crt->v3_ext, 3 ) ) != 0 )
-        return( ret );
+    /*
+     * Extension  ::=  SEQUENCE  {
+     *      extnID      OBJECT IDENTIFIER,
+     *      critical    BOOLEAN DEFAULT FALSE,
+     *      extnValue   OCTET STRING  }
+     */
 
-    end = crt->v3_ext.p + crt->v3_ext.len;
-    while( *p < end )
+    end = p + ext_len;
+
+    /* Get extension ID */
+    if( ( ret = mbedtls_asn1_get_tag( &p, end, &extn_oid.len,
+                                      MBEDTLS_ASN1_OID ) ) != 0 )
+        goto err;
+
+    extn_oid.tag = MBEDTLS_ASN1_OID;
+    extn_oid.p = p;
+    p += extn_oid.len;
+
+    /* Get optional critical */
+    if( ( ret = mbedtls_asn1_get_bool( &p, end, &is_critical ) ) != 0 &&
+        ( ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG ) )
+        goto err;
+
+    /* Data should be octet string type */
+    if( ( ret = mbedtls_asn1_get_tag( &p, end, &len,
+                                      MBEDTLS_ASN1_OCTET_STRING ) ) != 0 )
+        goto err;
+
+    end_ext_octet = p + len;
+    if( end_ext_octet != end )
     {
-        /*
-         * Extension  ::=  SEQUENCE  {
-         *      extnID      OBJECT IDENTIFIER,
-         *      critical    BOOLEAN DEFAULT FALSE,
-         *      extnValue   OCTET STRING  }
-         */
-        mbedtls_x509_buf extn_oid = {0, 0, NULL};
-        int is_critical = 0; /* DEFAULT FALSE */
-        int ext_type = 0;
+        ret = MBEDTLS_ERR_ASN1_LENGTH_MISMATCH;
+        goto err;
+    }
 
-        if( ( ret = mbedtls_asn1_get_tag( p, end, &len,
-                MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE ) ) != 0 )
-            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
-
-        end_ext_data = *p + len;
-
-        /* Get extension ID */
-        if( ( ret = mbedtls_asn1_get_tag( p, end_ext_data, &extn_oid.len,
-                                          MBEDTLS_ASN1_OID ) ) != 0 )
-            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
-
-        extn_oid.tag = MBEDTLS_ASN1_OID;
-        extn_oid.p = *p;
-        *p += extn_oid.len;
-
-        /* Get optional critical */
-        if( ( ret = mbedtls_asn1_get_bool( p, end_ext_data, &is_critical ) ) != 0 &&
-            ( ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG ) )
-            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
-
-        /* Data should be octet string type */
-        if( ( ret = mbedtls_asn1_get_tag( p, end_ext_data, &len,
-                MBEDTLS_ASN1_OCTET_STRING ) ) != 0 )
-            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret );
-
-        end_ext_octet = *p + len;
-
-        if( end_ext_octet != end_ext_data )
-            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
-                    MBEDTLS_ERR_ASN1_LENGTH_MISMATCH );
-
-        /*
-         * Detect supported extensions
-         */
-        ret = mbedtls_oid_get_x509_ext_type( &extn_oid, &ext_type );
-
-        if( ret != 0 )
-        {
-            /* No parser found, skip extension */
-            *p = end_ext_octet;
-
+    /*
+     * Detect supported extensions
+     */
+    ret = mbedtls_oid_get_x509_ext_type( &extn_oid, &ext_type );
+    if( ret != 0 )
+    {
 #if !defined(MBEDTLS_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION)
-            if( is_critical )
-            {
-                /* Data is marked as critical: fail */
-                return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
-                        MBEDTLS_ERR_ASN1_UNEXPECTED_TAG );
-            }
-#endif
-            continue;
-        }
-
-        /* Forbid repeated extensions */
-        if( ( crt->ext_types & ext_type ) != 0 )
-            return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS );
-
-        crt->ext_types |= ext_type;
-
-        switch( ext_type )
+        if( is_critical )
         {
+            /* Data is marked as critical: fail */
+            ret = MBEDTLS_ERR_ASN1_UNEXPECTED_TAG;
+            goto err;
+        }
+#endif
+        return( 0 );
+    }
+
+    /* Forbid repeated extensions */
+    if( ( crt->ext_types & ext_type ) != 0 )
+        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS );
+
+    crt->ext_types |= ext_type;
+    switch( ext_type )
+    {
         case MBEDTLS_X509_EXT_BASIC_CONSTRAINTS:
+        {
+            int ca_istrue;
+            int max_pathlen;
+
             /* Parse basic constraints */
-            if( ( ret = x509_get_basic_constraints( p, end_ext_octet,
-                    &crt->ca_istrue, &crt->max_pathlen ) ) != 0 )
-                return( ret );
+            ret = x509_get_basic_constraints( &p, end_ext_octet,
+                                              &ca_istrue,
+                                              &max_pathlen );
+            if( ret != 0 )
+                goto err;
+
+            crt->ca_istrue   = ca_istrue;
+            crt->max_pathlen = max_pathlen;
             break;
+        }
 
         case MBEDTLS_X509_EXT_KEY_USAGE:
             /* Parse key usage */
-            if( ( ret = x509_get_key_usage( p, end_ext_octet,
-                    &crt->key_usage ) ) != 0 )
+            ret = x509_get_key_usage( &p, end_ext_octet,
+                                      &crt->key_usage );
+            if( ret != 0 )
+                goto err;
+            break;
+
+        case MBEDTLS_X509_EXT_SUBJECT_ALT_NAME:
+            /* Copy reference to raw subject alt name data. */
+            crt->subject_alt_raw.p   = p;
+            crt->subject_alt_raw.len = end_ext_octet - p;
+            if( ( ret = x509_get_subject_alt_name( p, end_ext_octet,
+                                            &crt->subject_alt_names ) ) != 0 )
+            {
                 return( ret );
+            }
             break;
 
         case MBEDTLS_X509_EXT_EXTENDED_KEY_USAGE:
             /* Parse extended key usage */
-            crt->ext_key_usage_raw.p = *p;
-            crt->ext_key_usage_raw.len = end_ext_octet - *p;
-            if( ( ret = x509_get_ext_key_usage( p, end_ext_octet,
-                    &crt->ext_key_usage ) ) != 0 )
-                return( ret );
-            break;
-
-        case MBEDTLS_X509_EXT_SUBJECT_ALT_NAME:
-            /* Parse subject alt name */
-            crt->subject_alt_raw.p = *p;
-            crt->subject_alt_raw.len = end_ext_octet - *p;
-            if( ( ret = x509_get_subject_alt_name( *p, end_ext_octet,
-                    &crt->subject_alt_names ) ) != 0 )
+            crt->ext_key_usage_raw.p   = p;
+            crt->ext_key_usage_raw.len = end_ext_octet - p;
+            if( ( ret = x509_get_ext_key_usage( &p, end_ext_octet,
+                                                &crt->ext_key_usage ) ) != 0 )
             {
                 return( ret );
             }
-            *p = end_ext_octet;
             break;
 
         case MBEDTLS_X509_EXT_NS_CERT_TYPE:
             /* Parse netscape certificate type */
-            if( ( ret = x509_get_ns_cert_type( p, end_ext_octet,
-                    &crt->ns_cert_type ) ) != 0 )
-                return( ret );
+            ret = x509_get_ns_cert_type( &p, end_ext_octet,
+                                         &crt->ns_cert_type );
+            if( ret != 0 )
+                goto err;
             break;
 
         default:
-            return( MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE );
-        }
+            /*
+             * If this is a non-critical extension, which the oid layer
+             * supports, but there isn't an X.509 parser for it,
+             * skip the extension.
+             */
+#if !defined(MBEDTLS_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION)
+            if( is_critical )
+                return( MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE );
+#endif
+            p = end_ext_octet;
     }
 
-    if( *p != end )
-        return( MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
-                MBEDTLS_ERR_ASN1_LENGTH_MISMATCH );
-
     return( 0 );
+
+err:
+    return( ret );
+}
+
+static int x509_get_crt_ext( unsigned char **p,
+                             unsigned char *end,
+                             mbedtls_x509_crt *crt )
+{
+    int ret;
+    size_t len;
+
+    if( *p == end )
+        return( 0 );
+
+    ret = mbedtls_asn1_get_tag( p, end, &len,
+              MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 3 );
+    if( ret != 0 )
+    {
+        goto err;
+    }
+
+    end = *p + len;
+    ret = mbedtls_asn1_traverse_sequence_of( p, end,
+                 0xFF, MBEDTLS_ASN1_SEQUENCE | MBEDTLS_ASN1_CONSTRUCTED,
+                 0, 0, x509_crt_get_ext_cb, crt );
+    if( ret != 0 )
+        goto err;
+
+err:
+
+    if( ret == MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE )
+        return( ret );
+    if( ret == MBEDTLS_ERR_X509_INVALID_EXTENSIONS )
+        return( ret );
+
+    if( ret != 0 )
+        ret += MBEDTLS_ERR_X509_INVALID_EXTENSIONS;
+
+    return( ret );
 }
 
 /*