Add helpers for working with relative OIDs.

Adds:
CBB_add_asn1_oid_component

As well as adding the following functions which are relative-OID
versions of the existing OID functions:
CBB_add_asn1_relative_oid_from_text
CBS_is_valid_asn1_relative_oid
CBS_asn1_relative_oid_to_text

Change-Id: I72509a155eb06cfa39f610099aa82a19415533f8
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/84207
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Matt Mueller <mattm@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 9f6a2c4..7005970 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -1483,6 +1483,126 @@
   }
 }
 
+TEST(CBBTest, AddRelativeOIDFromText) {
+  const struct {
+    const char *text;
+    std::vector<uint8_t> der;
+  } kValidOIDs[] = {
+      // Some valid values.
+      {"0", {0x00}},
+      {"128", {0x81, 0x00}},
+      {"128.129", {0x81, 0x00, 0x81, 0x01}},
+      {"0.2.3.4", {0x0, 0x2, 0x3, 0x4}},
+      {"1.2.3.4", {0x1, 0x2, 0x3, 0x4}},
+      {"127.2.3.4", {0x7f, 0x2, 0x3, 0x4}},
+      {"1.2.840.113554.4.1.72585",
+       {0x1, 0x2, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09}},
+      // Edge cases near an overflow.
+      {"1.2.18446744073709551615",
+       {0x1, 0x2, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}},
+  };
+
+  const char *kInvalidTexts[] = {
+      // The empty string is not a relative OID.
+      "",
+      // No empty components.
+      ".",
+      ".1.2.3.4.5",
+      "1..2.3.4.5",
+      "1.2.3.4.5.",
+      // No extra leading zeros.
+      "00.1.2.3.4",
+      "01.1.2.3.4",
+      // Overflow
+      "1.2.18446744073709551616",
+  };
+
+  const struct {
+    std::vector<uint8_t> der;
+    // If true, |der| is valid but has a component that exceeds 2^64-1.
+    bool overflow;
+  } kInvalidDER[] = {
+      // The empty string is not a relative OID.
+      {{}, false},
+      // Non-minimal representation.
+      {{0x80, 0x01}, false},
+      // Unterminated integer.
+      {{0x83}, false},
+      // Overflow. This is the DER representation of
+      // 840.113554.4.1.72585.18446744073709551616. (The final value is
+      // 2^64.)
+      {{0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09,
+        0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00},
+       true},
+  };
+
+  for (const auto &t : kValidOIDs) {
+    SCOPED_TRACE(t.text);
+
+    bssl::ScopedCBB cbb;
+    ASSERT_TRUE(CBB_init(cbb.get(), 0));
+    ASSERT_TRUE(CBB_add_asn1_relative_oid_from_text(
+          cbb.get(), t.text, strlen(t.text)));
+    uint8_t *out;
+    size_t len;
+    ASSERT_TRUE(CBB_finish(cbb.get(), &out, &len));
+    bssl::UniquePtr<uint8_t> free_out(out);
+    EXPECT_EQ(Bytes(t.der), Bytes(out, len));
+
+    CBS cbs;
+    CBS_init(&cbs, t.der.data(), t.der.size());
+    bssl::UniquePtr<char> text(CBS_asn1_relative_oid_to_text(&cbs));
+    ASSERT_TRUE(text.get());
+    EXPECT_STREQ(t.text, text.get());
+
+    EXPECT_TRUE(CBS_is_valid_asn1_relative_oid(&cbs));
+  }
+
+  for (const char *t : kInvalidTexts) {
+    SCOPED_TRACE(t);
+    bssl::ScopedCBB cbb;
+    ASSERT_TRUE(CBB_init(cbb.get(), 0));
+    EXPECT_FALSE(CBB_add_asn1_relative_oid_from_text(cbb.get(), t, strlen(t)));
+  }
+
+  for (const auto &t : kInvalidDER) {
+    SCOPED_TRACE(Bytes(t.der));
+    CBS cbs;
+    CBS_init(&cbs, t.der.data(), t.der.size());
+    bssl::UniquePtr<char> text(CBS_asn1_relative_oid_to_text(&cbs));
+    EXPECT_FALSE(text);
+    EXPECT_EQ(t.overflow ? 1 : 0, CBS_is_valid_asn1_relative_oid(&cbs));
+  }
+}
+
+TEST(CBBTest, AddOIDComponent) {
+  const struct {
+    uint64_t component;
+    std::vector<uint8_t> der;
+  } kValidOIDs[] = {
+      // Some valid values.
+      {0, {0x00}},
+      {127, {0x7f}},
+      {128, {0x81, 0x00}},
+      {129, {0x81, 0x01}},
+      {113554, {0x86, 0xf7, 0x12}},
+      // Edge cases near an overflow.
+      {18446744073709551615u,
+       {0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}},
+  };
+  for (const auto &t : kValidOIDs) {
+    SCOPED_TRACE(t.component);
+    bssl::ScopedCBB cbb;
+    ASSERT_TRUE(CBB_init(cbb.get(), 0));
+    ASSERT_TRUE(CBB_add_asn1_oid_component(cbb.get(), t.component));
+    uint8_t *out;
+    size_t len;
+    ASSERT_TRUE(CBB_finish(cbb.get(), &out, &len));
+    bssl::UniquePtr<uint8_t> free_out(out);
+    EXPECT_EQ(Bytes(t.der), Bytes(out, len));
+  }
+}
+
 TEST(CBBTest, FlushASN1SetOf) {
   const struct {
     std::vector<uint8_t> in, out;
diff --git a/crypto/bytestring/cbb.cc b/crypto/bytestring/cbb.cc
index 2d004bf..c8f5a51 100644
--- a/crypto/bytestring/cbb.cc
+++ b/crypto/bytestring/cbb.cc
@@ -638,6 +638,38 @@
   return 1;
 }
 
+int CBB_add_asn1_relative_oid_from_text(CBB *cbb, const char *text,
+                                        size_t len) {
+  if (!CBB_flush(cbb)) {
+    return 0;
+  }
+
+  // Relative OIDs must have at least one component.
+  if (!len) {
+    return 0;
+  }
+
+  CBS cbs;
+  CBS_init(&cbs, reinterpret_cast<const uint8_t *>(text), len);
+
+  while (CBS_len(&cbs) > 0) {
+    uint64_t a;
+    if (!parse_dotted_decimal(&cbs, &a) || !add_base128_integer(cbb, a)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+int CBB_add_asn1_oid_component(CBB *cbb, uint64_t value) {
+  if (!CBB_flush(cbb)) {
+    return 0;
+  }
+
+  return add_base128_integer(cbb, value);
+}
+
 static int compare_set_of_element(const void *a_ptr, const void *b_ptr) {
   // See X.690, section 11.6 for the ordering. They are sorted in ascending
   // order by their DER encoding.
diff --git a/crypto/bytestring/cbs.cc b/crypto/bytestring/cbs.cc
index 55bb149..671914d 100644
--- a/crypto/bytestring/cbs.cc
+++ b/crypto/bytestring/cbs.cc
@@ -754,6 +754,39 @@
   return nullptr;
 }
 
+int CBS_is_valid_asn1_relative_oid(const CBS *cbs) {
+  return CBS_is_valid_asn1_oid(cbs);
+}
+
+char *CBS_asn1_relative_oid_to_text(const CBS *cbs) {
+  CBS copy = *cbs;
+  bssl::ScopedCBB cbb;
+  if (!CBB_init(cbb.get(), 32)) {
+    return nullptr;
+  }
+
+  // Relative OIDs must have at least one component.
+  uint64_t v;
+  if (!parse_base128_integer(&copy, &v) || !add_decimal(cbb.get(), v)) {
+    return nullptr;
+  }
+
+  while (CBS_len(&copy) != 0) {
+    if (!parse_base128_integer(&copy, &v) || !CBB_add_u8(cbb.get(), '.') ||
+        !add_decimal(cbb.get(), v)) {
+      return nullptr;
+    }
+  }
+
+  uint8_t *txt;
+  size_t txt_len;
+  if (!CBB_add_u8(cbb.get(), '\0') || !CBB_finish(cbb.get(), &txt, &txt_len)) {
+    return nullptr;
+  }
+
+  return reinterpret_cast<char *>(txt);
+}
+
 static int cbs_get_two_digits(CBS *cbs, int *out) {
   uint8_t first_digit, second_digit;
   if (!CBS_get_u8(cbs, &first_digit)) {
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 6dc5c24..25877c2 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -389,6 +389,22 @@
 // OID components are too large.
 OPENSSL_EXPORT char *CBS_asn1_oid_to_text(const CBS *cbs);
 
+// CBS_is_valid_asn1_relative_oid returns one if |cbs| is a valid DER-encoded
+// ASN.1 RELATIVE-OID contents (not including the element framing) and zero
+// otherwise. This function tolerates arbitrarily large OID components.
+//
+// (This is actually the same as |CBS_is_valid_asn1_oid|, but is also exposed
+// under the relative_oid name for API symmetry.)
+OPENSSL_EXPORT int CBS_is_valid_asn1_relative_oid(const CBS *cbs);
+
+// CBS_asn1_relative_oid_to_text interprets |cbs| as DER-encoded ASN.1
+// RELATIVE-OID contents (not including the element framing) and returns the
+// ASCII representation (e.g., "32473.1") in a newly-allocated string, or NULL
+// on failure. The caller must release the result with |OPENSSL_free|.
+//
+// This function may fail if |cbs| is an invalid RELATIVE-OID, or if any
+// OID components are too large.
+OPENSSL_EXPORT char *CBS_asn1_relative_oid_to_text(const CBS *cbs);
 
 // CBS_parse_generalized_time returns one if |cbs| is a valid DER-encoded, ASN.1
 // GeneralizedTime body within the limitations imposed by RFC 5280, or zero
@@ -653,6 +669,22 @@
 OPENSSL_EXPORT int CBB_add_asn1_oid_from_text(CBB *cbb, const char *text,
                                               size_t len);
 
+// CBB_add_asn1_relative_oid_from_text decodes |len| bytes from |text| as an
+// ASCII RELATIVE-OID representation, e.g. "32473.1", and writes the
+// DER-encoded contents to |cbb|. It returns one on success and zero on malloc
+// failure or if |text| was invalid. It does not include any framing, only the
+// element's contents.
+//
+// This function considers OID strings with components which do not fit in a
+// |uint64_t| to be invalid.
+OPENSSL_EXPORT int CBB_add_asn1_relative_oid_from_text(CBB *cbb,
+                                                       const char *text,
+                                                       size_t len);
+
+// CBB_add_asn1_oid_component appends a single OID component to |cbb|.
+// It returns one on success and zero on error.
+OPENSSL_EXPORT int CBB_add_asn1_oid_component(CBB *cbb, uint64_t value);
+
 // CBB_flush_asn1_set_of calls |CBB_flush| on |cbb| and then reorders the
 // contents for a DER-encoded ASN.1 SET OF type. It returns one on success and
 // zero on failure. DER canonicalizes SET OF contents by sorting