Merge pull request #7106 from davidhorstmann-arm/parse-oid-from-string
Parse an OID from a string
diff --git a/ChangeLog.d/oid-parse-from-numeric-string.txt b/ChangeLog.d/oid-parse-from-numeric-string.txt
new file mode 100644
index 0000000..82ed2fd
--- /dev/null
+++ b/ChangeLog.d/oid-parse-from-numeric-string.txt
@@ -0,0 +1,3 @@
+Features
+ * Add function mbedtls_oid_from_numeric_string() to parse an OID from a
+ string to a DER-encoded mbedtls_asn1_buf.
diff --git a/include/mbedtls/oid.h b/include/mbedtls/oid.h
index 1cbf968..ec36748 100644
--- a/include/mbedtls/oid.h
+++ b/include/mbedtls/oid.h
@@ -64,6 +64,11 @@
#define MBEDTLS_OID_X509_EXT_NS_CERT_TYPE (1 << 16)
/*
+ * Maximum number of OID components allowed
+ */
+#define MBEDTLS_OID_MAX_COMPONENTS 128
+
+/*
* Top level OID tuples
*/
#define MBEDTLS_OID_ISO_MEMBER_BODIES "\x2a" /* {iso(1) member-body(2)} */
@@ -479,6 +484,25 @@
int mbedtls_oid_get_numeric_string(char *buf, size_t size, const mbedtls_asn1_buf *oid);
/**
+ * \brief Translate a string containing a dotted-decimal
+ * representation of an ASN.1 OID into its encoded form
+ * (e.g. "1.2.840.113549" into "\x2A\x86\x48\x86\xF7\x0D").
+ * On success, this function allocates oid->buf from the
+ * heap. It must be freed by the caller using mbedtls_free().
+ *
+ * \param oid #mbedtls_asn1_buf to populate with the DER-encoded OID
+ * \param oid_str string representation of the OID to parse
+ * \param size length of the OID string, not including any null terminator
+ *
+ * \return 0 if successful
+ * \return #MBEDTLS_ERR_ASN1_INVALID_DATA if \p oid_str does not
+ * represent a valid OID
+ * \return #MBEDTLS_ERR_ASN1_ALLOC_FAILED if the function fails to
+ * allocate oid->buf
+ */
+int mbedtls_oid_from_numeric_string(mbedtls_asn1_buf *oid, const char *oid_str, size_t size);
+
+/**
* \brief Translate an X.509 extension OID into local values
*
* \param oid OID to use
diff --git a/library/oid.c b/library/oid.c
index f78ee85..a580992 100644
--- a/library/oid.c
+++ b/library/oid.c
@@ -933,4 +933,180 @@
return (int) (size - n);
}
+static int oid_parse_number(unsigned int *num, const char **p, const char *bound)
+{
+ int ret = MBEDTLS_ERR_ASN1_INVALID_DATA;
+
+ *num = 0;
+
+ while (*p < bound && **p >= '0' && **p <= '9') {
+ ret = 0;
+ if (*num > (UINT_MAX / 10)) {
+ return MBEDTLS_ERR_ASN1_INVALID_DATA;
+ }
+ *num *= 10;
+ *num += **p - '0';
+ (*p)++;
+ }
+ return ret;
+}
+
+static size_t oid_subidentifier_num_bytes(unsigned int value)
+{
+ size_t num_bytes = 0;
+
+ do {
+ value >>= 7;
+ num_bytes++;
+ } while (value != 0);
+
+ return num_bytes;
+}
+
+static int oid_subidentifier_encode_into(unsigned char **p,
+ unsigned char *bound,
+ unsigned int value)
+{
+ size_t num_bytes = oid_subidentifier_num_bytes(value);
+
+ if ((size_t) (bound - *p) < num_bytes) {
+ return MBEDTLS_ERR_OID_BUF_TOO_SMALL;
+ }
+ (*p)[num_bytes - 1] = (unsigned char) (value & 0x7f);
+ value >>= 7;
+
+ for (size_t i = 2; i <= num_bytes; i++) {
+ (*p)[num_bytes - i] = 0x80 | (unsigned char) (value & 0x7f);
+ value >>= 7;
+ }
+ *p += num_bytes;
+
+ return 0;
+}
+
+/* Return the OID for the given x.y.z.... style numeric string */
+int mbedtls_oid_from_numeric_string(mbedtls_asn1_buf *oid,
+ const char *oid_str, size_t size)
+{
+ int ret = MBEDTLS_ERR_ASN1_INVALID_DATA;
+ const char *str_ptr = oid_str;
+ const char *str_bound = oid_str + size;
+ unsigned int val = 0;
+ unsigned int component1, component2;
+ size_t encoded_len;
+ unsigned char *resized_mem;
+
+ /* Count the number of dots to get a worst-case allocation size. */
+ size_t num_dots = 0;
+ for (size_t i = 0; i < size; i++) {
+ if (oid_str[i] == '.') {
+ num_dots++;
+ }
+ }
+ /* Allocate maximum possible required memory:
+ * There are (num_dots + 1) integer components, but the first 2 share the
+ * same subidentifier, so we only need num_dots subidentifiers maximum. */
+ if (num_dots == 0 || (num_dots > MBEDTLS_OID_MAX_COMPONENTS - 1)) {
+ return MBEDTLS_ERR_ASN1_INVALID_DATA;
+ }
+ /* Each byte can store 7 bits, calculate number of bytes for a
+ * subidentifier:
+ *
+ * bytes = ceil(subidentifer_size * 8 / 7)
+ */
+ size_t bytes_per_subidentifier = (((sizeof(unsigned int) * 8) - 1) / 7)
+ + 1;
+ size_t max_possible_bytes = num_dots * bytes_per_subidentifier;
+ oid->p = mbedtls_calloc(max_possible_bytes, 1);
+ if (oid->p == NULL) {
+ return MBEDTLS_ERR_ASN1_ALLOC_FAILED;
+ }
+ unsigned char *out_ptr = oid->p;
+ unsigned char *out_bound = oid->p + max_possible_bytes;
+
+ ret = oid_parse_number(&component1, &str_ptr, str_bound);
+ if (ret != 0) {
+ goto error;
+ }
+ if (component1 > 2) {
+ /* First component can't be > 2 */
+ ret = MBEDTLS_ERR_ASN1_INVALID_DATA;
+ goto error;
+ }
+ if (str_ptr >= str_bound || *str_ptr != '.') {
+ ret = MBEDTLS_ERR_ASN1_INVALID_DATA;
+ goto error;
+ }
+ str_ptr++;
+
+ ret = oid_parse_number(&component2, &str_ptr, str_bound);
+ if (ret != 0) {
+ goto error;
+ }
+ if ((component1 < 2) && (component2 > 39)) {
+ /* Root nodes 0 and 1 may have up to 40 children, numbered 0-39 */
+ ret = MBEDTLS_ERR_ASN1_INVALID_DATA;
+ goto error;
+ }
+ if (str_ptr < str_bound) {
+ if (*str_ptr == '.') {
+ str_ptr++;
+ } else {
+ ret = MBEDTLS_ERR_ASN1_INVALID_DATA;
+ goto error;
+ }
+ }
+
+ if (component2 > (UINT_MAX - (component1 * 40))) {
+ ret = MBEDTLS_ERR_ASN1_INVALID_DATA;
+ goto error;
+ }
+ ret = oid_subidentifier_encode_into(&out_ptr, out_bound,
+ (component1 * 40) + component2);
+ if (ret != 0) {
+ goto error;
+ }
+
+ while (str_ptr < str_bound) {
+ ret = oid_parse_number(&val, &str_ptr, str_bound);
+ if (ret != 0) {
+ goto error;
+ }
+ if (str_ptr < str_bound) {
+ if (*str_ptr == '.') {
+ str_ptr++;
+ } else {
+ ret = MBEDTLS_ERR_ASN1_INVALID_DATA;
+ goto error;
+ }
+ }
+
+ ret = oid_subidentifier_encode_into(&out_ptr, out_bound, val);
+ if (ret != 0) {
+ goto error;
+ }
+ }
+
+ encoded_len = out_ptr - oid->p;
+ resized_mem = mbedtls_calloc(encoded_len, 1);
+ if (resized_mem == NULL) {
+ ret = MBEDTLS_ERR_ASN1_ALLOC_FAILED;
+ goto error;
+ }
+ memcpy(resized_mem, oid->p, encoded_len);
+ mbedtls_free(oid->p);
+ oid->p = resized_mem;
+ oid->len = encoded_len;
+
+ oid->tag = MBEDTLS_ASN1_OID;
+
+ return 0;
+
+error:
+ mbedtls_free(oid->p);
+ oid->p = NULL;
+ oid->len = 0;
+ return ret;
+}
+
#endif /* MBEDTLS_OID_C */
diff --git a/tests/suites/test_suite_oid.data b/tests/suites/test_suite_oid.data
index e7fbf1b..00f6e0b 100644
--- a/tests/suites/test_suite_oid.data
+++ b/tests/suites/test_suite_oid.data
@@ -137,3 +137,51 @@
OID get numeric string - overlong encoding, second subidentifier
oid_get_numeric_string:"2B8001":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - hardware module name
+oid_from_numeric_string:"1.3.6.1.5.5.7.8.4":0:"2B06010505070804"
+
+OID from numeric string - multi-byte subidentifier
+oid_from_numeric_string:"1.1.2108":0:"29903C"
+
+OID from numeric string - second component greater than 39
+oid_from_numeric_string:"2.49.0.0.826.0":0:"81010000863A00"
+
+OID from numeric string - multi-byte first subidentifier
+oid_from_numeric_string:"2.999":0:"8837"
+
+OID from numeric string - empty string input
+oid_from_numeric_string:"":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - first component not a number
+oid_from_numeric_string:"abc.1.2":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - second component not a number
+oid_from_numeric_string:"1.abc.2":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - first component too large
+oid_from_numeric_string:"3.1":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - first component < 2, second > 39
+oid_from_numeric_string:"1.40":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - third component not a number
+oid_from_numeric_string:"1.2.abc":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - non-'.' separator between first and second
+oid_from_numeric_string:"1/2.3.4":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - non-'.' separator between second and third
+oid_from_numeric_string:"1.2/3.4":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - non-'.' separator between third and fourth
+oid_from_numeric_string:"1.2.3/4":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - OID greater than max length (129 components)
+oid_from_numeric_string:"1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1.2.3.4.5.6.7.8.1":MBEDTLS_ERR_ASN1_INVALID_DATA:""
+
+OID from numeric string - OID with maximum subidentifier
+oid_from_numeric_string:"2.4294967215":0:"8FFFFFFF7F"
+
+OID from numeric string - OID with overflowing subidentifier
+oid_from_numeric_string:"2.4294967216":MBEDTLS_ERR_ASN1_INVALID_DATA:""
diff --git a/tests/suites/test_suite_oid.function b/tests/suites/test_suite_oid.function
index 68b769f..3adc6af 100644
--- a/tests/suites/test_suite_oid.function
+++ b/tests/suites/test_suite_oid.function
@@ -119,3 +119,29 @@
}
}
/* END_CASE */
+
+/* BEGIN_CASE */
+void oid_from_numeric_string(char *oid_str, int error_ret,
+ data_t *exp_oid_buf)
+{
+ mbedtls_asn1_buf oid = { 0, 0, NULL };
+ mbedtls_asn1_buf exp_oid = { 0, 0, NULL };
+ int ret;
+
+ exp_oid.tag = MBEDTLS_ASN1_OID;
+ exp_oid.p = exp_oid_buf->x;
+ exp_oid.len = exp_oid_buf->len;
+
+ ret = mbedtls_oid_from_numeric_string(&oid, oid_str, strlen(oid_str));
+
+ if (error_ret == 0) {
+ TEST_EQUAL(oid.len, exp_oid.len);
+ TEST_ASSERT(memcmp(oid.p, exp_oid.p, oid.len) == 0);
+ mbedtls_free(oid.p);
+ oid.p = NULL;
+ oid.len = 0;
+ } else {
+ TEST_EQUAL(ret, error_ret);
+ }
+}
+/* END_CASE */