Reimplement OBJ_obj2txt.

The old implementation had a lot of size_t/int confusion. It also
accepted non-minimally-encoded OIDs. Unlike the old implementation, the
new one does not fall back to BIGNUMs and does not attempt to
pretty-print OIDs with components which do not fit in a uint64_t. Add
tests for these cases.

With this new implementation, hopefully we'll have a much easier time
enabling MSVC's size_t truncation warning later.

Change-Id: I602102b97cf9b02d874644f8ef67fe9bac70e45e
Reviewed-on: https://boringssl-review.googlesource.com/9131
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/obj/obj.c b/crypto/obj/obj.c
index 65366eb..1e4959d 100644
--- a/crypto/obj/obj.c
+++ b/crypto/obj/obj.c
@@ -54,8 +54,13 @@
  * copied and put under another distribution licence
  * [including the GNU Public Licence.] */
 
+#if !defined(__STDC_FORMAT_MACROS)
+#define __STDC_FORMAT_MACROS
+#endif
+
 #include <openssl/obj.h>
 
+#include <inttypes.h>
 #include <limits.h>
 #include <string.h>
 
@@ -409,148 +414,109 @@
   return op;
 }
 
-int OBJ_obj2txt(char *out, int out_len, const ASN1_OBJECT *obj, int dont_return_name) {
-  int i, n = 0, len, nid, first, use_bn;
-  BIGNUM *bl;
-  unsigned long l;
-  const unsigned char *p;
-  char tbuf[DECIMAL_SIZE(i) + DECIMAL_SIZE(l) + 2];
-
-  if (out && out_len > 0) {
-    out[0] = 0;
+static int strlcpy_int(char *dst, const char *src, int dst_size) {
+  size_t ret = BUF_strlcpy(dst, src, dst_size < 0 ? 0 : (size_t)dst_size);
+  if (ret > INT_MAX) {
+    OPENSSL_PUT_ERROR(OBJ, ERR_R_OVERFLOW);
+    return -1;
   }
+  return (int)ret;
+}
 
-  if (obj == NULL || obj->data == NULL) {
-    return 0;
-  }
-
-  if (!dont_return_name && (nid = OBJ_obj2nid(obj)) != NID_undef) {
-    const char *s;
-    s = OBJ_nid2ln(nid);
-    if (s == NULL) {
-      s = OBJ_nid2sn(nid);
+static int parse_oid_component(CBS *cbs, uint64_t *out) {
+  uint64_t v = 0;
+  uint8_t b;
+  do {
+    if (!CBS_get_u8(cbs, &b)) {
+      return 0;
     }
-    if (s) {
-      if (out) {
-        BUF_strlcpy(out, s, out_len);
+    if ((v >> (64 - 7)) != 0) {
+      /* The component is too large. */
+      return 0;
+    }
+    if (v == 0 && b == 0x80) {
+      /* The component must be minimally encoded. */
+      return 0;
+    }
+    v = (v << 7) | (b & 0x7f);
+
+    /* Components end at an octet with the high bit cleared. */
+  } while (b & 0x80);
+
+  *out = v;
+  return 1;
+}
+
+static int add_decimal(CBB *out, uint64_t v) {
+  char buf[DECIMAL_SIZE(uint64_t) + 1];
+  BIO_snprintf(buf, sizeof(buf), "%" PRIu64, v);
+  return CBB_add_bytes(out, (const uint8_t *)buf, strlen(buf));
+}
+
+int OBJ_obj2txt(char *out, int out_len, const ASN1_OBJECT *obj,
+                int dont_return_name) {
+  if (!dont_return_name) {
+    int nid = OBJ_obj2nid(obj);
+    if (nid != NID_undef) {
+      const char *name = OBJ_nid2ln(nid);
+      if (name == NULL) {
+        name = OBJ_nid2sn(nid);
       }
-      return strlen(s);
+      if (name != NULL) {
+        return strlcpy_int(out, name, out_len);
+      }
     }
   }
 
-  len = obj->length;
-  p = obj->data;
+  CBB cbb;
+  if (!CBB_init(&cbb, 32)) {
+    goto err;
+  }
 
-  first = 1;
-  bl = NULL;
+  CBS cbs;
+  CBS_init(&cbs, obj->data, obj->length);
 
-  while (len > 0) {
-    l = 0;
-    use_bn = 0;
-    for (;;) {
-      unsigned char c = *p++;
-      len--;
-      if (len == 0 && (c & 0x80)) {
-        goto err;
-      }
-      if (use_bn) {
-        if (!BN_add_word(bl, c & 0x7f)) {
-          goto err;
-        }
-      } else {
-        l |= c & 0x7f;
-      }
-      if (!(c & 0x80)) {
-        break;
-      }
-      if (!use_bn && (l > (ULONG_MAX >> 7L))) {
-        if (!bl && !(bl = BN_new())) {
-          goto err;
-        }
-        if (!BN_set_word(bl, l)) {
-          goto err;
-        }
-        use_bn = 1;
-      }
-      if (use_bn) {
-        if (!BN_lshift(bl, bl, 7)) {
-          goto err;
-        }
-      } else {
-        l <<= 7L;
-      }
+  /* The first component is 40 * value1 + value2, where value1 is 0, 1, or 2. */
+  uint64_t v;
+  if (!parse_oid_component(&cbs, &v)) {
+    goto err;
+  }
+
+  if (v >= 80) {
+    if (!CBB_add_bytes(&cbb, (const uint8_t *)"2.", 2) ||
+        !add_decimal(&cbb, v - 80)) {
+      goto err;
     }
+  } else if (!add_decimal(&cbb, v / 40) ||
+             !CBB_add_u8(&cbb, '.') ||
+             !add_decimal(&cbb, v % 40)) {
+    goto err;
+  }
 
-    if (first) {
-      first = 0;
-      if (l >= 80) {
-        i = 2;
-        if (use_bn) {
-          if (!BN_sub_word(bl, 80)) {
-            goto err;
-          }
-        } else {
-          l -= 80;
-        }
-      } else {
-        i = (int)(l / 40);
-        l -= (long)(i * 40);
-      }
-      if (out && out_len > 1) {
-        *out++ = i + '0';
-        *out = '0';
-        out_len--;
-      }
-      n++;
-    }
-
-    if (use_bn) {
-      char *bndec;
-      bndec = BN_bn2dec(bl);
-      if (!bndec) {
-        goto err;
-      }
-      i = strlen(bndec);
-      if (out) {
-        if (out_len > 1) {
-          *out++ = '.';
-          *out = 0;
-          out_len--;
-        }
-        BUF_strlcpy(out, bndec, out_len);
-        if (i > out_len) {
-          out += out_len;
-          out_len = 0;
-        } else {
-          out += i;
-          out_len -= i;
-        }
-      }
-      n++;
-      n += i;
-      OPENSSL_free(bndec);
-    } else {
-      BIO_snprintf(tbuf, sizeof(tbuf), ".%lu", l);
-      i = strlen(tbuf);
-      if (out && out_len > 0) {
-        BUF_strlcpy(out, tbuf, out_len);
-        if (i > out_len) {
-          out += out_len;
-          out_len = 0;
-        } else {
-          out += i;
-          out_len -= i;
-        }
-      }
-      n += i;
+  while (CBS_len(&cbs) != 0) {
+    if (!parse_oid_component(&cbs, &v) ||
+        !CBB_add_u8(&cbb, '.') ||
+        !add_decimal(&cbb, v)) {
+      goto err;
     }
   }
 
-  BN_free(bl);
-  return n;
+  uint8_t *txt;
+  size_t txt_len;
+  if (!CBB_add_u8(&cbb, '\0') ||
+      !CBB_finish(&cbb, &txt, &txt_len)) {
+    goto err;
+  }
+
+  int ret = strlcpy_int(out, (const char *)txt, out_len);
+  OPENSSL_free(txt);
+  return ret;
 
 err:
-  BN_free(bl);
+  CBB_cleanup(&cbb);
+  if (out_len > 0) {
+    out[0] = '\0';
+  }
   return -1;
 }
 
diff --git a/crypto/obj/obj_test.cc b/crypto/obj/obj_test.cc
index 9f610bd..8bec2b4 100644
--- a/crypto/obj/obj_test.cc
+++ b/crypto/obj/obj_test.cc
@@ -182,6 +182,51 @@
     return false;
   }
 
+  ASN1_OBJECT obj;
+  memset(&obj, 0, sizeof(obj));
+
+  if (OBJ_obj2txt(NULL, 0, &obj, 0) != -1) {
+    fprintf(stderr, "OBJ_obj2txt accepted the empty OID.\n");
+    return false;
+  }
+
+  // kNonMinimalOID is kBasicConstraints with the final component non-minimally
+  // encoded.
+  static const uint8_t kNonMinimalOID[] = {
+      0x55, 0x1d, 0x80, 0x13,
+  };
+  obj.data = kNonMinimalOID;
+  obj.length = sizeof(kNonMinimalOID);
+  if (OBJ_obj2txt(NULL, 0, &obj, 0) != -1) {
+    fprintf(stderr, "OBJ_obj2txt accepted non-minimal OIDs.\n");
+    return false;
+  }
+
+  // kOverflowOID is the DER representation of
+  // 1.2.840.113554.4.1.72585.18446744073709551616. (The final value is 2^64.)
+  static const uint8_t kOverflowOID[] = {
+      0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09,
+      0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00,
+  };
+  obj.data = kOverflowOID;
+  obj.length = sizeof(kOverflowOID);
+  if (OBJ_obj2txt(NULL, 0, &obj, 0) != -1) {
+    fprintf(stderr, "OBJ_obj2txt accepted an OID with a large component.\n");
+    return false;
+  }
+
+  // kInvalidOID is a mis-encoded version of kBasicConstraints with the final
+  // octet having the high bit set.
+  static const uint8_t kInvalidOID[] = {
+      0x55, 0x1d, 0x93,
+  };
+  obj.data = kInvalidOID;
+  obj.length = sizeof(kInvalidOID);
+  if (OBJ_obj2txt(NULL, 0, &obj, 0) != -1) {
+    fprintf(stderr, "OBJ_obj2txt accepted a mis-encoded OID.\n");
+    return false;
+  }
+
   return true;
 }