Parsing v3 extensions from a CSR

A parsed CSR struct (`mbedtls_x509_csr`) now includes some of the
X.509v3 extensions included in the CSR -- the key usage, Netscape
cert-type, and Subject Alternative Names.

Author: Jens Alfke <jens@couchbase.com>

Signed-off-by: Przemek Stekiel <przemyslaw.stekiel@mobica.com>
diff --git a/include/mbedtls/x509.h b/include/mbedtls/x509.h
index 52fadad..a98748c 100644
--- a/include/mbedtls/x509.h
+++ b/include/mbedtls/x509.h
@@ -370,6 +370,15 @@
 int mbedtls_x509_write_sig(unsigned char **p, unsigned char *start,
                            const char *oid, size_t oid_len,
                            unsigned char *sig, size_t size);
+int x509_get_ns_cert_type(unsigned char **p,
+                          const unsigned char *end,
+                          unsigned char *ns_cert_type);
+int x509_get_key_usage(unsigned char **p,
+                       const unsigned char *end,
+                       unsigned int *key_usage);
+int x509_get_subject_alt_name(unsigned char **p,
+                              const unsigned char *end,
+                              mbedtls_x509_sequence *subject_alt_name);
 
 #define MBEDTLS_X509_SAFE_SNPRINTF                          \
     do {                                                    \
diff --git a/include/mbedtls/x509_csr.h b/include/mbedtls/x509_csr.h
index 50998c4..216a0a2 100644
--- a/include/mbedtls/x509_csr.h
+++ b/include/mbedtls/x509_csr.h
@@ -58,6 +58,10 @@
 
     mbedtls_pk_context pk;          /**< Container for the public key context. */
 
+    unsigned int key_usage;     /**< Optional key usage extension value: See the values in x509.h */
+    unsigned char ns_cert_type; /**< Optional Netscape certificate type extension value: See the values in x509.h */
+    mbedtls_x509_sequence subject_alt_names;    /**< Optional list of raw entries of Subject Alternative Names extension (currently only dNSName and OtherName are listed). */
+
     mbedtls_x509_buf sig_oid;
     mbedtls_x509_buf MBEDTLS_PRIVATE(sig);
     mbedtls_md_type_t MBEDTLS_PRIVATE(sig_md);       /**< Internal representation of the MD algorithm of the signature algorithm, e.g. MBEDTLS_MD_SHA256 */
diff --git a/library/x509_crt.c b/library/x509_crt.c
index 0330097..e02d18e 100644
--- a/library/x509_crt.c
+++ b/library/x509_crt.c
@@ -562,9 +562,9 @@
     return 0;
 }
 
-static int x509_get_ns_cert_type(unsigned char **p,
-                                 const unsigned char *end,
-                                 unsigned char *ns_cert_type)
+int x509_get_ns_cert_type(unsigned char **p,
+                          const unsigned char *end,
+                          unsigned char *ns_cert_type)
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
     mbedtls_x509_bitstring bs = { 0, 0, NULL };
@@ -583,9 +583,9 @@
     return 0;
 }
 
-static int x509_get_key_usage(unsigned char **p,
-                              const unsigned char *end,
-                              unsigned int *key_usage)
+int x509_get_key_usage(unsigned char **p,
+                       const unsigned char *end,
+                       unsigned int *key_usage)
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
     size_t i;
@@ -660,9 +660,9 @@
  * NOTE: we list all types, but only use dNSName and otherName
  * of type HwModuleName, as defined in RFC 4108, at this point.
  */
-static int x509_get_subject_alt_name(unsigned char **p,
-                                     const unsigned char *end,
-                                     mbedtls_x509_sequence *subject_alt_name)
+int x509_get_subject_alt_name(unsigned char **p,
+                              const unsigned char *end,
+                              mbedtls_x509_sequence *subject_alt_name)
 {
     int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
     size_t len, tag_len;
diff --git a/library/x509_csr.c b/library/x509_csr.c
index 0c664d9..824d17a 100644
--- a/library/x509_csr.c
+++ b/library/x509_csr.c
@@ -70,6 +70,153 @@
 }
 
 /*
+ * Parse CSR extension requests in DER format
+ */
+static int x509_csr_parse_extensions(mbedtls_x509_csr *csr,
+                                     unsigned char **p, const unsigned char *end)
+{
+    int ret;
+    size_t len;
+    unsigned char *end_ext_data;
+
+    while (*p < end) {
+        mbedtls_x509_buf extn_oid = { 0, 0, NULL };
+        int ext_type = 0;
+
+        /* Read sequence tag */
+        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;
+
+        /* 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;
+        }
+        if (*p + len != end_ext_data) {
+            return MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
+                   MBEDTLS_ERR_ASN1_LENGTH_MISMATCH;
+        }
+
+        if (mbedtls_oid_get_x509_ext_type(&extn_oid, &ext_type) == 0) {
+            switch (ext_type) {
+                case MBEDTLS_X509_EXT_KEY_USAGE:
+                    /* Parse key usage */
+                    if ((ret = x509_get_key_usage(p, end_ext_data,
+                                                  &csr->key_usage)) != 0) {
+                        return ret;
+                    }
+                    break;
+
+                case MBEDTLS_X509_EXT_SUBJECT_ALT_NAME:
+                    /* Parse subject alt name */
+                    if ((ret = x509_get_subject_alt_name(p, end_ext_data,
+                                                         &csr->subject_alt_names)) != 0) {
+                        return ret;
+                    }
+                    break;
+
+                case MBEDTLS_X509_EXT_NS_CERT_TYPE:
+                    /* Parse netscape certificate type */
+                    if ((ret = x509_get_ns_cert_type(p, end_ext_data,
+                                                     &csr->ns_cert_type)) != 0) {
+                        return ret;
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        *p = end_ext_data;
+    }
+
+    if (*p != end) {
+        return MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
+               MBEDTLS_ERR_ASN1_LENGTH_MISMATCH;
+    }
+
+    return 0;
+}
+
+/*
+ * Parse CSR attributes in DER format
+ */
+static int x509_csr_parse_attributes(mbedtls_x509_csr *csr,
+                                     const unsigned char *start, const unsigned char *end)
+{
+    int ret;
+    size_t len;
+    unsigned char *end_attr_data;
+    unsigned char **p = (unsigned char **) &start;
+
+    while (*p < end) {
+        mbedtls_x509_buf attr_oid = { 0, 0, NULL };
+
+        if ((ret = mbedtls_asn1_get_tag(p, end, &len,
+                                        MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) != 0) {
+            return MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret;
+        }
+        end_attr_data = *p + len;
+
+        /* Get attribute ID */
+        if ((ret = mbedtls_asn1_get_tag(p, end_attr_data, &attr_oid.len,
+                                        MBEDTLS_ASN1_OID)) != 0) {
+            return MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret;
+        }
+
+        attr_oid.tag = MBEDTLS_ASN1_OID;
+        attr_oid.p = *p;
+        *p += attr_oid.len;
+
+        /* Check that this is an extension-request attribute */
+        if (MBEDTLS_OID_CMP(MBEDTLS_OID_PKCS9_CSR_EXT_REQ, &attr_oid) == 0) {
+            if ((ret = mbedtls_asn1_get_tag(p, end, &len,
+                                            MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SET)) != 0) {
+                return MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret;
+            }
+
+            if ((ret = mbedtls_asn1_get_tag(p, end, &len,
+                                            MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) !=
+                0) {
+                return MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret;
+            }
+
+            if ((ret = x509_csr_parse_extensions(csr, p, *p + len)) != 0) {
+                return MBEDTLS_ERR_X509_INVALID_EXTENSIONS + ret;
+            }
+
+            if (*p != end_attr_data) {
+                return MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
+                       MBEDTLS_ERR_ASN1_LENGTH_MISMATCH;
+            }
+        }
+
+        *p = end_attr_data;
+    }
+
+    if (*p != end) {
+        return MBEDTLS_ERR_X509_INVALID_EXTENSIONS +
+               MBEDTLS_ERR_ASN1_LENGTH_MISMATCH;
+    }
+
+    return 0;
+}
+
+/*
  * Parse a CSR in DER format
  */
 int mbedtls_x509_csr_parse_der(mbedtls_x509_csr *csr,
@@ -197,6 +344,11 @@
         return MBEDTLS_ERROR_ADD(MBEDTLS_ERR_X509_INVALID_FORMAT, ret);
     }
 
+    if ((ret = x509_csr_parse_attributes(csr, p, p + len)) != 0) {
+        mbedtls_x509_csr_free(csr);
+        return ret;
+    }
+
     p += len;
 
     end = csr->raw.p + csr->raw.len;