New mbedtls_x509_crt_parse_der_ext() routine

This routine is functionally equivalent to mbedtls_x509_crt_parse_der(),
but it accepts an additional callback function which it calls with
every unsupported certificate extension.

Proposed solution to https://github.com/ARMmbed/mbedtls/issues/3241

Signed-off-by: Nicola Di Lieto <nicola.dilieto@gmail.com>
diff --git a/include/mbedtls/x509_crt.h b/include/mbedtls/x509_crt.h
index e4fb135..19de1e9 100644
--- a/include/mbedtls/x509_crt.h
+++ b/include/mbedtls/x509_crt.h
@@ -304,6 +304,62 @@
                                 size_t buflen );
 
 /**
+ * \brief          The type of certificate extension callbacks.
+ *
+ *                 Callbacks of this type are passed to and used by the
+ *                 mbedtls_x509_crt_parse_der_ext() routine when it encounters
+ *                 an unsupported extension.
+ *
+ * \param crt      Pointer to the certificate being parsed
+ * \param oid      Extension's OID
+ * \param critical If the extension is critical (per the RFC's definition)
+ * \param p        On entry \c *p points to the start of the extension ASN.1
+ *                 data.  On successful completion \c *p must point to the
+ *                 first byte after it.
+ *                 On error, the value of \c *p is undefined.
+ * \param end      End of extension data.
+  *
+ * \note           The callback must fail and return a negative error code if
+ *                 it can not parse or does not support the extension.
+ *
+ * \return         \c 0 on success.
+ * \return         A negative error code on failure.
+ */
+typedef int (*mbedtls_x509_crt_ext_cb_t)( mbedtls_x509_crt const *crt,
+                                        mbedtls_x509_buf const *oid,
+                                        int critical,
+                                        unsigned char **p,
+                                        const unsigned char *end );
+
+/**
+ * \brief          Parse a single DER formatted certificate and add it
+ *                 to the end of the provided chained list.
+ *
+ * \param chain    The pointer to the start of the CRT chain to attach to.
+ *                 When parsing the first CRT in a chain, this should point
+ *                 to an instance of ::mbedtls_x509_crt initialized through
+ *                 mbedtls_x509_crt_init().
+ * \param buf      The buffer holding the DER encoded certificate.
+ * \param buflen   The size in Bytes of \p buf.
+ * \param cb       A callback invoked for every unsupported certificate
+ *                 extension.
+ *
+ * \note           This call is functionally equivalent to
+ *                 mbedtls_x509_crt_parse_der(), but it calls the callback
+ *                 with every unsupported certificate extension.
+ *                 The callback must return a negative error code if it
+ *                 does not know how to handle such an extension.
+ *
+ * \return         \c 0 if successful.
+ * \return         A negative error code on failure.
+ */
+int mbedtls_x509_crt_parse_der_ext( mbedtls_x509_crt *chain,
+                                const unsigned char *buf,
+                                size_t buflen,
+                                mbedtls_x509_crt_ext_cb_t cb
+                                );
+
+/**
  * \brief          Parse a single DER formatted certificate and add it
  *                 to the end of the provided chained list. This is a
  *                 variant of mbedtls_x509_crt_parse_der() which takes
diff --git a/library/x509_crt.c b/library/x509_crt.c
index 1e62ed5..9076b32 100644
--- a/library/x509_crt.c
+++ b/library/x509_crt.c
@@ -892,7 +892,8 @@
  */
 static int x509_get_crt_ext( unsigned char **p,
                              const unsigned char *end,
-                             mbedtls_x509_crt *crt )
+                             mbedtls_x509_crt *crt,
+                             mbedtls_x509_crt_ext_cb_t cb )
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
     size_t len;
@@ -955,6 +956,10 @@
 
         if( ret != 0 )
         {
+            /* Give the callback (if any) a chance to handle the extension */
+            if (cb && cb(crt, &extn_oid, is_critical, p, end_ext_octet) == 0)
+                continue;
+
             /* No parser found, skip extension */
             *p = end_ext_octet;
 
@@ -1061,7 +1066,8 @@
 static int x509_crt_parse_der_core( mbedtls_x509_crt *crt,
                                     const unsigned char *buf,
                                     size_t buflen,
-                                    int make_copy )
+                                    int make_copy,
+                                    mbedtls_x509_crt_ext_cb_t cb )
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
     size_t len;
@@ -1260,7 +1266,7 @@
     if( crt->version == 3 )
 #endif
     {
-        ret = x509_get_crt_ext( &p, end, crt );
+        ret = x509_get_crt_ext( &p, end, crt, cb );
         if( ret != 0 )
         {
             mbedtls_x509_crt_free( crt );
@@ -1323,7 +1329,8 @@
 static int mbedtls_x509_crt_parse_der_internal( mbedtls_x509_crt *chain,
                                                 const unsigned char *buf,
                                                 size_t buflen,
-                                                int make_copy )
+                                                int make_copy,
+                                                mbedtls_x509_crt_ext_cb_t cb )
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
     mbedtls_x509_crt *crt = chain, *prev = NULL;
@@ -1355,7 +1362,8 @@
         crt = crt->next;
     }
 
-    if( ( ret = x509_crt_parse_der_core( crt, buf, buflen, make_copy ) ) != 0 )
+    ret = x509_crt_parse_der_core( crt, buf, buflen, make_copy, cb );
+    if( ret != 0 )
     {
         if( prev )
             prev->next = NULL;
@@ -1373,14 +1381,22 @@
                                        const unsigned char *buf,
                                        size_t buflen )
 {
-    return( mbedtls_x509_crt_parse_der_internal( chain, buf, buflen, 0 ) );
+    return( mbedtls_x509_crt_parse_der_internal( chain, buf, buflen, 0, NULL ) );
+}
+
+int mbedtls_x509_crt_parse_der_ext( mbedtls_x509_crt *chain,
+                                const unsigned char *buf,
+                                size_t buflen,
+                                mbedtls_x509_crt_ext_cb_t cb )
+{
+    return( mbedtls_x509_crt_parse_der_internal( chain, buf, buflen, 1, cb ) );
 }
 
 int mbedtls_x509_crt_parse_der( mbedtls_x509_crt *chain,
                                 const unsigned char *buf,
                                 size_t buflen )
 {
-    return( mbedtls_x509_crt_parse_der_internal( chain, buf, buflen, 1 ) );
+    return( mbedtls_x509_crt_parse_der_internal( chain, buf, buflen, 1, NULL ) );
 }
 
 /*