crypto/bytestring: Add helper for relative OID from DER to text into CBB

It is similar to the existing CBS_asn1_relative_oid_to_text, except it
writes directly into a CBB rather than allocating a new string.

Change-Id: I2b7d7e1149d49d38ccacd37df4ff3bae6a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/101047
Presubmit-BoringSSL-Verified: boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Lily Chen <chlily@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index b93c0ca..6395b42 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -1669,6 +1669,12 @@
     EXPECT_STREQ(t.text, text.get());
 
     EXPECT_TRUE(CBS_is_valid_asn1_relative_oid(&cbs));
+
+    ScopedCBB text_cbb;
+    ASSERT_TRUE(CBB_init(text_cbb.get(), 0));
+    EXPECT_TRUE(CBB_add_asn1_relative_oid_from_der_to_text(
+        text_cbb.get(), t.der.data(), t.der.size()));
+    EXPECT_EQ(Bytes(CBBAsSpan(text_cbb.get())), Bytes(t.text));
   }
 
   for (const char *t : kInvalidTexts) {
@@ -1685,6 +1691,11 @@
     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));
+
+    ScopedCBB text_cbb;
+    ASSERT_TRUE(CBB_init(text_cbb.get(), 0));
+    EXPECT_FALSE(CBB_add_asn1_relative_oid_from_der_to_text(
+        text_cbb.get(), t.der.data(), t.der.size()));
   }
 }
 
diff --git a/crypto/bytestring/cbb.cc b/crypto/bytestring/cbb.cc
index 9400ab9..ab1b4be 100644
--- a/crypto/bytestring/cbb.cc
+++ b/crypto/bytestring/cbb.cc
@@ -15,7 +15,9 @@
 #include <openssl/bytestring.h>
 
 #include <assert.h>
+#include <inttypes.h>
 #include <limits.h>
+#include <stdio.h>
 #include <string.h>
 
 #include <algorithm>
@@ -374,6 +376,13 @@
   return 1;
 }
 
+int bssl::cbb_add_decimal_ascii(CBB *out, uint64_t v) {
+  char buf[DECIMAL_SIZE(uint64_t) + 1];
+  snprintf(buf, sizeof(buf), "%" PRIu64, v);
+  return CBB_add_bytes(out, reinterpret_cast<const uint8_t *>(buf),
+                       strlen(buf));
+}
+
 int CBB_add_asn1(CBB *cbb, CBB *out_contents, CBS_ASN1_TAG tag) {
   if (!CBB_flush(cbb)) {
     return 0;
@@ -679,6 +688,26 @@
   return 1;
 }
 
+int CBB_add_asn1_relative_oid_from_der_to_text(CBB *cbb, const uint8_t *data,
+                                               size_t data_len) {
+  CBS der;
+  CBS_init(&der, data, data_len);
+  // Relative OIDs must have at least one component.
+  uint64_t v;
+  if (!CBS_get_asn1_oid_component(&der, &v) || !cbb_add_decimal_ascii(cbb, v)) {
+    return 0;
+  }
+
+  while (CBS_len(&der) != 0) {
+    if (!CBS_get_asn1_oid_component(&der, &v) || !CBB_add_u8(cbb, '.') ||
+        !cbb_add_decimal_ascii(cbb, v)) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
 int CBB_add_asn1_oid_component(CBB *cbb, uint64_t value) {
   if (!CBB_flush(cbb)) {
     return 0;
diff --git a/crypto/bytestring/cbs.cc b/crypto/bytestring/cbs.cc
index 7abf10b..b0a0b8f 100644
--- a/crypto/bytestring/cbs.cc
+++ b/crypto/bytestring/cbs.cc
@@ -18,7 +18,6 @@
 
 #include <assert.h>
 #include <ctype.h>
-#include <inttypes.h>
 #include <string.h>
 
 #include "../asn1/internal.h"
@@ -730,12 +729,6 @@
   return CBS_is_valid_asn1_integer(cbs, &is_negative) && !is_negative;
 }
 
-static int add_decimal(CBB *out, uint64_t v) {
-  char buf[DECIMAL_SIZE(uint64_t) + 1];
-  snprintf(buf, sizeof(buf), "%" PRIu64, v);
-  return CBB_add_bytes(out, (const uint8_t *)buf, strlen(buf));
-}
-
 int CBS_is_valid_asn1_oid(const CBS *cbs) {
   if (CBS_len(cbs) == 0) {
     return 0;  // OID encodings cannot be empty.
@@ -774,17 +767,17 @@
 
   if (v >= 80) {
     if (!CBB_add_bytes(&cbb, (const uint8_t *)"2.", 2) ||
-        !add_decimal(&cbb, v - 80)) {
+        !cbb_add_decimal_ascii(&cbb, v - 80)) {
       goto err;
     }
-  } else if (!add_decimal(&cbb, v / 40) || !CBB_add_u8(&cbb, '.') ||
-             !add_decimal(&cbb, v % 40)) {
+  } else if (!cbb_add_decimal_ascii(&cbb, v / 40) || !CBB_add_u8(&cbb, '.') ||
+             !cbb_add_decimal_ascii(&cbb, v % 40)) {
     goto err;
   }
 
   while (CBS_len(&copy) != 0) {
     if (!CBS_get_asn1_oid_component(&copy, &v) || !CBB_add_u8(&cbb, '.') ||
-        !add_decimal(&cbb, v)) {
+        !cbb_add_decimal_ascii(&cbb, v)) {
       goto err;
     }
   }
@@ -807,25 +800,15 @@
 }
 
 char *CBS_asn1_relative_oid_to_text(const CBS *cbs) {
-  CBS copy = *cbs;
   ScopedCBB cbb;
   if (!CBB_init(cbb.get(), 32)) {
     return nullptr;
   }
-
-  // Relative OIDs must have at least one component.
-  uint64_t v;
-  if (!CBS_get_asn1_oid_component(&copy, &v) || !add_decimal(cbb.get(), v)) {
+  if (!CBB_add_asn1_relative_oid_from_der_to_text(cbb.get(), CBS_data(cbs),
+                                                  CBS_len(cbs))) {
     return nullptr;
   }
 
-  while (CBS_len(&copy) != 0) {
-    if (!CBS_get_asn1_oid_component(&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)) {
diff --git a/crypto/bytestring/internal.h b/crypto/bytestring/internal.h
index e1aa76f..6bea4a7 100644
--- a/crypto/bytestring/internal.h
+++ b/crypto/bytestring/internal.h
@@ -124,6 +124,11 @@
   return CBB_finish_i2d(cbb.get(), outp);
 }
 
+// cbb_add_decimal_ascii adds the characters of an ASCII string representation
+// of `v` as a decimal value to `out` (without a terminating NUL byte). It
+// returns one on success and zero on error.
+int cbb_add_decimal_ascii(CBB *out, uint64_t v);
+
 BSSL_NAMESPACE_END
 
 #endif  // OPENSSL_HEADER_CRYPTO_BYTESTRING_INTERNAL_H
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 53ee66d..e1b7089 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -719,6 +719,17 @@
                                                        const char *text,
                                                        size_t len);
 
+// CBB_add_asn1_relative_oid_from_der_to_text reads `data_len` bytes of
+// DER-encoded ASN.1 RELATIVE-OID contents (not including the element framing)
+// from `data` and writes the ASCII representation (e.g., "32473.1") to `cbb`
+// (without a trailing NUL byte). It returns one on success and zero on
+// failure.
+//
+// This function may fail if `data` is an invalid RELATIVE-OID, or if any OID
+// components are too large.
+OPENSSL_EXPORT int CBB_add_asn1_relative_oid_from_der_to_text(
+    CBB *cbb, const uint8_t *data, size_t data_len);
+
 // CBB_add_asn1_oid_component appends a single OID component to `cbb`.
 // It returns one on success and zero on error.
 //
diff --git a/include/openssl/prefix_symbols.h b/include/openssl/prefix_symbols.h
index 6e68c58..e60131a 100644
--- a/include/openssl/prefix_symbols.h
+++ b/include/openssl/prefix_symbols.h
@@ -506,6 +506,7 @@
 #pragma redefine_extname CBB_add_asn1_octet_string BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBB_add_asn1_octet_string)
 #pragma redefine_extname CBB_add_asn1_oid_component BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBB_add_asn1_oid_component)
 #pragma redefine_extname CBB_add_asn1_oid_from_text BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBB_add_asn1_oid_from_text)
+#pragma redefine_extname CBB_add_asn1_relative_oid_from_der_to_text BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBB_add_asn1_relative_oid_from_der_to_text)
 #pragma redefine_extname CBB_add_asn1_relative_oid_from_text BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBB_add_asn1_relative_oid_from_text)
 #pragma redefine_extname CBB_add_asn1_uint64 BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBB_add_asn1_uint64)
 #pragma redefine_extname CBB_add_asn1_uint64_with_tag BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBB_add_asn1_uint64_with_tag)
@@ -3643,6 +3644,7 @@
 #define CBB_add_asn1_octet_string BORINGSSL_ADD_PREFIX(CBB_add_asn1_octet_string)
 #define CBB_add_asn1_oid_component BORINGSSL_ADD_PREFIX(CBB_add_asn1_oid_component)
 #define CBB_add_asn1_oid_from_text BORINGSSL_ADD_PREFIX(CBB_add_asn1_oid_from_text)
+#define CBB_add_asn1_relative_oid_from_der_to_text BORINGSSL_ADD_PREFIX(CBB_add_asn1_relative_oid_from_der_to_text)
 #define CBB_add_asn1_relative_oid_from_text BORINGSSL_ADD_PREFIX(CBB_add_asn1_relative_oid_from_text)
 #define CBB_add_asn1_uint64 BORINGSSL_ADD_PREFIX(CBB_add_asn1_uint64)
 #define CBB_add_asn1_uint64_with_tag BORINGSSL_ADD_PREFIX(CBB_add_asn1_uint64_with_tag)