Add CBS_get_u64_decimal.

ASN1_generate_v8 has a number of calls to strtoul. strtoul has two
problems for that function.

First, strtoul keeps reading until NUL, but all the functions in that
file act on pointer/length pairs. It's fine because the underlying
string is always NUL-terminated, but this is fragile.

Second, strtoul is actually defined to parse "-1" as
(unsigned long)(-1)! Rather than deal with this, extract the decimal
string parser out of the OID parser as a CBS strotul equivalent.

Change-Id: I1b7a1867d185e34e752be09f8c8103b82e364f35
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/56165
Reviewed-by: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index e35b1bc..ca22c41 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -1657,3 +1657,53 @@
     EXPECT_FALSE(CBS_parse_utc_time(&cbs, NULL, /*allow_timezone_offset=*/1));
   }
 }
+
+TEST(CBSTest, GetU64Decimal) {
+  const struct {
+    uint64_t val;
+    const char *text;
+  } kTests[] = {
+      {0, "0"},
+      {1, "1"},
+      {123456, "123456"},
+      // 2^64 - 1
+      {UINT64_C(18446744073709551615), "18446744073709551615"},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(t.text);
+    CBS cbs;
+    CBS_init(&cbs, reinterpret_cast<const uint8_t*>(t.text), strlen(t.text));
+    uint64_t v;
+    ASSERT_TRUE(CBS_get_u64_decimal(&cbs, &v));
+    EXPECT_EQ(v, t.val);
+    EXPECT_EQ(CBS_data(&cbs),
+              reinterpret_cast<const uint8_t *>(t.text) + strlen(t.text));
+    EXPECT_EQ(CBS_len(&cbs), 0u);
+
+    std::string str(t.text);
+    str += "Z";
+    CBS_init(&cbs, reinterpret_cast<const uint8_t *>(str.data()), str.size());
+    ASSERT_TRUE(CBS_get_u64_decimal(&cbs, &v));
+    EXPECT_EQ(v, t.val);
+    EXPECT_EQ(CBS_data(&cbs),
+              reinterpret_cast<const uint8_t *>(str.data()) + strlen(t.text));
+    EXPECT_EQ(CBS_len(&cbs), 1u);
+  }
+
+  static const char *kInvalidTests[] = {
+      "",
+      "nope",
+      "-1",
+      // 2^64
+      "18446744073709551616",
+      // Overflows at multiplying by 10.
+      "18446744073709551620",
+  };
+  for (const char *invalid : kInvalidTests) {
+    SCOPED_TRACE(invalid);
+    CBS cbs;
+    CBS_init(&cbs, reinterpret_cast<const uint8_t *>(invalid), strlen(invalid));
+    uint64_t v;
+    EXPECT_FALSE(CBS_get_u64_decimal(&cbs, &v));
+  }
+}
diff --git a/crypto/bytestring/cbb.c b/crypto/bytestring/cbb.c
index 86625fe..1692b4e 100644
--- a/crypto/bytestring/cbb.c
+++ b/crypto/bytestring/cbb.c
@@ -560,30 +560,15 @@
 // component and the dot, so |cbs| may be passed into the function again for the
 // next value.
 static int parse_dotted_decimal(CBS *cbs, uint64_t *out) {
-  *out = 0;
-  int seen_digit = 0;
-  for (;;) {
-    // Valid terminators for a component are the end of the string or a
-    // non-terminal dot. If the string ends with a dot, this is not a valid OID
-    // string.
-    uint8_t u;
-    if (!CBS_get_u8(cbs, &u) ||
-        (u == '.' && CBS_len(cbs) > 0)) {
-      break;
-    }
-    if (u < '0' || u > '9' ||
-        // Forbid stray leading zeros.
-        (seen_digit && *out == 0) ||
-        // Check for overflow.
-        *out > UINT64_MAX / 10 ||
-        *out * 10 > UINT64_MAX - (u - '0')) {
-      return 0;
-    }
-    *out = *out * 10 + (u - '0');
-    seen_digit = 1;
+  if (!CBS_get_u64_decimal(cbs, out)) {
+    return 0;
   }
-  // The empty string is not a legal OID component.
-  return seen_digit;
+
+  // The integer must have either ended at the end of the string, or a
+  // non-terminal dot, which should be consumed. If the string ends with a dot,
+  // this is not a valid OID string.
+  uint8_t dot;
+  return !CBS_get_u8(cbs, &dot) || (dot == '.' && CBS_len(cbs) > 0);
 }
 
 int CBB_add_asn1_oid_from_text(CBB *cbb, const char *text, size_t len) {
diff --git a/crypto/bytestring/cbs.c b/crypto/bytestring/cbs.c
index c28f08d..efafbfb 100644
--- a/crypto/bytestring/cbs.c
+++ b/crypto/bytestring/cbs.c
@@ -227,6 +227,30 @@
   return CBS_get_bytes(cbs, out, split - CBS_data(cbs));
 }
 
+int CBS_get_u64_decimal(CBS *cbs, uint64_t *out) {
+  uint64_t v = 0;
+  int seen_digit = 0;
+  while (CBS_len(cbs) != 0) {
+    uint8_t c = CBS_data(cbs)[0];
+    if (!isdigit(c)) {
+      break;
+    }
+    CBS_skip(cbs, 1);
+    if (// Forbid stray leading zeros.
+        (v == 0 && seen_digit) ||
+        // Check for overflow.
+        v > UINT64_MAX / 10 ||  //
+        v * 10 > UINT64_MAX - (c - '0')) {
+      return 0;
+    }
+    v = v * 10 + (c - '0');
+    seen_digit = 1;
+  }
+
+  *out = v;
+  return seen_digit;
+}
+
 // parse_base128_integer reads a big-endian base-128 integer from |cbs| and sets
 // |*out| to the result. This is the encoding used in DER for both high tag
 // number form and OID components.
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 28297d4..b6fdda8 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -160,6 +160,13 @@
 // one. Otherwise, it returns zero and leaves |cbs| unmodified.
 OPENSSL_EXPORT int CBS_get_until_first(CBS *cbs, CBS *out, uint8_t c);
 
+// CBS_get_u64_decimal reads a decimal integer from |cbs| and writes it to
+// |*out|. It stops reading at the end of the string, or the first non-digit
+// character. It returns one on success and zero on error. This function behaves
+// analogously to |strtoul| except it does not accept empty inputs, leading
+// zeros, or negative values.
+OPENSSL_EXPORT int CBS_get_u64_decimal(CBS *cbs, uint64_t *out);
+
 
 // Parsing ASN.1
 //