_asn1_object_id_der: expanded to handle all OIDs that can be decoded

In addition to making a more precise OID encoding, we add
a unit test.

Signed-off-by: Nikos Mavrogiannopoulos <nmav@gnutls.org>
diff --git a/.gitignore b/.gitignore
index fc0f113..28ab335 100644
--- a/.gitignore
+++ b/.gitignore
@@ -202,3 +202,4 @@
 tests/*.trs
 windows/libtasn1-*-win??.zip
 windows/tmp
+tests/object-id-encoding
diff --git a/lib/coding.c b/lib/coding.c
index 90d6203..08aca3d 100644
--- a/lib/coding.c
+++ b/lib/coding.c
@@ -33,6 +33,10 @@
 #include "minmax.h"
 #include <structure.h>
 
+/* for unit testing */
+extern ASN1_API int
+_asn1_object_id_der (const char *str, unsigned char *der, int *der_len);
+
 #define MAX_TAG_LEN 16
 
 /******************************************************/
@@ -320,8 +324,30 @@
 }
 */
 
+static
+void encode_val(uint64_t val, unsigned char *der, int max_len, int *der_len)
+{
+  int first, k;
+  unsigned char bit7;
+
+  first = 0;
+  for (k = sizeof(val); k >= 0; k--)
+    {
+      bit7 = (val >> (k * 7)) & 0x7F;
+      if (bit7 || first || !k)
+	{
+	  if (k)
+	    bit7 |= 0x80;
+	  if (max_len > (*der_len))
+	    der[*der_len] = bit7;
+	  (*der_len)++;
+	  first = 1;
+	}
+    }
+}
+
 /******************************************************/
-/* Function : _asn1_objectid_der                      */
+/* Function : _asn1_object_id_der                     */
 /* Description: creates the DER coding for an         */
 /* OBJECT IDENTIFIER  type (length included).         */
 /* Parameters:                                        */
@@ -335,16 +361,16 @@
 /*   ASN1_SUCCESS if succesful                        */
 /*   or an error value.                               */
 /******************************************************/
-static int
-_asn1_objectid_der (unsigned char *str, unsigned char *der, int *der_len)
+int
+_asn1_object_id_der (const char *str, unsigned char *der, int *der_len)
 {
-  int len_len, counter, k, first, max_len;
+  int len_len, counter, max_len;
   char *temp, *n_end, *n_start;
-  unsigned char bit7;
   uint64_t val, val1 = 0;
   int str_len = _asn1_strlen (str);
 
   max_len = *der_len;
+  *der_len = 0;
 
   if (der == NULL && max_len > 0)
     return ASN1_VALUE_NOT_VALID;
@@ -366,30 +392,30 @@
       counter++;
 
       if (counter == 1)
-	val1 = val;
+        {
+	  val1 = val;
+	}
       else if (counter == 2)
 	{
-	  if (max_len > 0)
-	    der[0] = 40 * val1 + val;
-	  *der_len = 1;
+	  uint64_t val0;
+
+          if (val1 > 2)
+            {
+              free(temp);
+              return ASN1_VALUE_NOT_VALID;
+            }
+          else if ((val1 == 0 || val1 == 1) && val > 39)
+            {
+              free(temp);
+              return ASN1_VALUE_NOT_VALID;
+            }
+
+	  val0 = 40 * val1 + val;
+	  encode_val(val0, der, max_len, der_len);
 	}
       else
 	{
-	  first = 0;
-	  for (k = sizeof(val); k >= 0; k--)
-	    {
-	      bit7 = (val >> (k * 7)) & 0x7F;
-	      if (bit7 || first || !k)
-		{
-		  if (k)
-		    bit7 |= 0x80;
-		  if (max_len > (*der_len))
-		    der[*der_len] = bit7;
-		  (*der_len)++;
-		  first = 1;
-		}
-	    }
-
+	  encode_val(val, der, max_len, der_len);
 	}
       n_start = n_end + 1;
     }
@@ -1142,7 +1168,7 @@
 		  goto error;
 		}
 	      len2 = max_len;
-	      err = _asn1_objectid_der (p->value, der + counter, &len2);
+	      err = _asn1_object_id_der ((char*)p->value, der + counter, &len2);
 	      if (err != ASN1_SUCCESS && err != ASN1_MEM_ERROR)
 		goto error;
 
diff --git a/lib/libtasn1.map b/lib/libtasn1.map
index 007925c..3ce75d6 100644
--- a/lib/libtasn1.map
+++ b/lib/libtasn1.map
@@ -61,3 +61,8 @@
   local:
     *;
 };
+
+LIBTASN1_4_15_1 {
+  global:
+    _asn1_object_id_der;
+} LIBTASN1_0_3;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index c73a442..f850df7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -61,7 +61,7 @@
 	Test_errors Test_simple Test_overflow Test_strings Test_choice \
 	Test_encdec copynode coding-decoding2 strict-der Test_choice_ocsp \
 	ocsp-basic-response octet-string coding-long-oid object-id-decoding \
-	spc_pe_image_data setof CVE-2018-1000654 reproducers
+	spc_pe_image_data setof CVE-2018-1000654 reproducers object-id-encoding
 
 TESTS = Test_parser Test_tree Test_encoding Test_indefinite	\
 	Test_errors Test_simple Test_overflow crlf threadsafety \
@@ -69,7 +69,7 @@
 	strict-der Test_choice_ocsp decoding decoding-invalid-x509 \
 	ocsp-basic-response octet-string coding-long-oid object-id-decoding \
 	spc_pe_image_data decoding-invalid-pkcs7 coding setof \
-	CVE-2018-1000654 parser.sh reproducers
+	CVE-2018-1000654 parser.sh reproducers object-id-encoding
 
 CVE-2018-1000654-1_asn1_tab.h: $(srcdir)/CVE-2018-1000654-1.asn
 	$(top_builddir)/src/asn1Parser$(EXEEXT) $^ -o $@
diff --git a/tests/object-id-decoding.c b/tests/object-id-decoding.c
index 4c50e9c..7052b4f 100644
--- a/tests/object-id-decoding.c
+++ b/tests/object-id-decoding.c
@@ -27,36 +27,44 @@
 struct tv
 {
   int der_len;
-  const unsigned char *der_str;
+  const unsigned char *der;
   const char *oid;
   int expected_error;
 };
 
 static const struct tv tv[] = {
   {.der_len = 5,
-   .der_str = (void *) "\x06\x03\x80\x37\x03",
+   .der = (void *) "\x06\x03\x80\x37\x03",
    .oid = "2.999.3",
    .expected_error = ASN1_DER_ERROR /* leading 0x80 */
   },
   {.der_len = 12,
-   .der_str = (void *) "\x06\x0a\x2b\x06\x01\x80\x01\x92\x08\x09\x05\x01",
+   .der = (void *) "\x06\x0a\x2b\x06\x01\x80\x01\x92\x08\x09\x05\x01",
    .oid = "1.3.6.1.4.1.2312.9.5.1",
    .expected_error = ASN1_DER_ERROR /* leading 0x80 */
   },
+  {.der_len = 6,
+   .der = (void *) "\x06\x04\x01\x02\x03\x04",
+   .oid = "0.1.2.3.4",
+   .expected_error = ASN1_SUCCESS},
   {.der_len = 5,
-   .der_str = (void *) "\x06\x03\x88\x37\x03",
+   .der = (void *) "\x06\x03\x51\x02\x03",
+   .oid = "2.1.2.3",
+   .expected_error = ASN1_SUCCESS},
+  {.der_len = 5,
+   .der = (void *) "\x06\x03\x88\x37\x03",
    .oid = "2.999.3",
    .expected_error = ASN1_SUCCESS},
   {.der_len = 12,
-   .der_str = (void *) "\x06\x0a\x2b\x06\x01\x04\x01\x92\x08\x09\x05\x01",
+   .der = (void *) "\x06\x0a\x2b\x06\x01\x04\x01\x92\x08\x09\x05\x01",
    .oid = "1.3.6.1.4.1.2312.9.5.1",
    .expected_error = ASN1_SUCCESS},
   {.der_len = 19,
-   .der_str = (void *) "\x06\x11\xfa\x80\x00\x00\x00\x0e\x01\x0e\xfa\x80\x00\x00\x00\x0e\x63\x6f\x6d",
+   .der = (void *) "\x06\x11\xfa\x80\x00\x00\x00\x0e\x01\x0e\xfa\x80\x00\x00\x00\x0e\x63\x6f\x6d",
    .oid = "2.1998768.0.0.14.1.14.1998848.0.0.14.99.111.109",
    .expected_error = ASN1_SUCCESS},
   {.der_len = 19,
-   .der_str =
+   .der =
    (void *)
    "\x06\x11\x2b\x06\x01\x04\x01\x92\x08\x09\x02\xaa\xda\xbe\xbe\xfa\x72\x01\x07",
    .oid = "1.3.6.1.4.1.2312.9.2.1467399257458.1.7",
@@ -74,7 +82,7 @@
     {
       /* decode */
       ret =
-	asn1_get_object_id_der (tv[i].der_str+1,
+	asn1_get_object_id_der (tv[i].der+1,
 				tv[i].der_len-1, &ret_len, str,
 				sizeof (str));
       if (ret != tv[i].expected_error)
diff --git a/tests/object-id-encoding.c b/tests/object-id-encoding.c
new file mode 100644
index 0000000..7c39448
--- /dev/null
+++ b/tests/object-id-encoding.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2019 Nikos Mavrogiannopoulos
+ *
+ * This file is part of LIBTASN1.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "libtasn1.h"
+
+extern ASN1_API int
+_asn1_object_id_der (const char *str, unsigned char *der, int *der_len);
+
+
+struct tv
+{
+  int der_len;
+  const unsigned char *der;
+  const char *oid;
+  int expected_error;
+};
+
+static const struct tv tv[] = {
+  {.der_len = 0,
+   .der = (void *) "",
+   .oid = "5.999.3",
+   .expected_error = ASN1_VALUE_NOT_VALID /* cannot start with 5 */
+  },
+  {.der_len = 0,
+   .der = (void *) "",
+   .oid = "0.48.9",
+   .expected_error = ASN1_VALUE_NOT_VALID /* second field cannot be 48 */
+  },
+  {.der_len = 0,
+   .der = (void *) "",
+   .oid = "1.40.9",
+   .expected_error = ASN1_VALUE_NOT_VALID /* second field cannot be 40 */
+  },
+  {.der_len = 4,
+   .der = (void *) "\x06\x02\x4f\x63",
+   .oid = "1.39.99",
+   .expected_error = ASN1_SUCCESS,
+  },
+  {.der_len = 6,
+   .der = (void *) "\x06\x04\x01\x02\x03\x04",
+   .oid = "0.1.2.3.4",
+   .expected_error = ASN1_SUCCESS},
+  {.der_len = 5,
+   .der = (void *) "\x06\x03\x51\x02\x03",
+   .oid = "2.1.2.3",
+   .expected_error = ASN1_SUCCESS},
+  {.der_len = 5,
+   .der = (void *) "\x06\x03\x88\x37\x03",
+   .oid = "2.999.3",
+   .expected_error = ASN1_SUCCESS},
+  {.der_len = 12,
+   .der = (void *) "\x06\x0a\x2b\x06\x01\x04\x01\x92\x08\x09\x05\x01",
+   .oid = "1.3.6.1.4.1.2312.9.5.1",
+   .expected_error = ASN1_SUCCESS},
+  {.der_len = 19,
+   .der = (void *) "\x06\x11\xfa\x80\x00\x00\x00\x0e\x01\x0e\xfa\x80\x00\x00\x00\x0e\x63\x6f\x6d",
+   .oid = "2.1998768.0.0.14.1.14.1998848.0.0.14.99.111.109",
+   .expected_error = ASN1_SUCCESS},
+  {.der_len = 19,
+   .der =
+   (void *)
+   "\x06\x11\x2b\x06\x01\x04\x01\x92\x08\x09\x02\xaa\xda\xbe\xbe\xfa\x72\x01\x07",
+   .oid = "1.3.6.1.4.1.2312.9.2.1467399257458.1.7",
+   .expected_error = ASN1_SUCCESS},
+};
+
+int
+main (int argc, char *argv[])
+{
+  unsigned char der[128];
+  int ret, der_len, i, j, exp_der_len;
+  const unsigned char *exp_der;
+
+  for (i = 0; i < (int)(sizeof (tv) / sizeof (tv[0])); i++)
+    {
+      der_len = sizeof(der);
+      ret = _asn1_object_id_der(tv[i].oid, der, &der_len);
+      if (ret != ASN1_SUCCESS)
+	{
+	  if (ret == tv[i].expected_error)
+	    continue;
+	  fprintf (stderr,
+		   "%d: iter %lu, encoding of OID failed: %s\n",
+		   __LINE__, (unsigned long) i, asn1_strerror(ret));
+	  return 1;
+	}
+      else if (ret != tv[i].expected_error)
+        {
+	  fprintf (stderr,
+		   "%d: iter %lu, encoding of OID %s succeeded when expecting failure\n",
+		   __LINE__, (unsigned long) i, tv[i].oid);
+          return 1;
+        }
+
+      /* the internal function does not insert the tag */
+      exp_der = tv[i].der + 1;
+      exp_der_len = tv[i].der_len - 1;
+      if (der_len != exp_der_len || memcmp(der, exp_der, der_len-1) != 0)
+	{
+	  fprintf (stderr,
+		   "%d: iter %lu, re-encoding of OID %s resulted to different string (%d vs %d bytes)\n",
+		   __LINE__, (unsigned long) i, tv[i].oid, der_len, exp_der_len);
+          fprintf(stderr, "\nGot:\t\t");
+          for (j=0;j<der_len;j++)
+            fprintf(stderr, "%.2x", der[j]);
+
+          fprintf(stderr, "\nExpected:\t");
+          for (j=0;j<exp_der_len;j++)
+            fprintf(stderr, "%.2x", exp_der[j]);
+          fprintf(stderr, "\n");
+
+	  return 1;
+	}
+    }
+
+  return 0;
+}