Expose CBS_get_asn1_oid_component

Only needed in libssl actually, but we have CBB_add_asn1_oid_component,
so this is a natural counterpart.

Bug: 524722668
Change-Id: I14c1a4935919f2ddca971e925dc57c413e0234c4
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/98487
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Lily Chen <chlily@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 5054052..113637c 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -16,6 +16,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <optional>
 #include <vector>
 
 #include <gtest/gtest.h>
@@ -2186,5 +2187,65 @@
   }
 }
 
+TEST(CBSTest, OIDComponent) {
+  const struct {
+    // enc is an encoded OID component.
+    std::vector<uint8_t> enc;
+    // v is the decoded OID component, or nullopt if invalid.
+    std::optional<uint64_t> v;
+  } kTests[] = {
+      // The empty string is not an OID component.
+      {{}, std::nullopt},
+      // Some valid OID components.
+      {{0x00}, 0},
+      {{0x01}, 1},
+      {{0x7f}, 127},
+      {{0x81, 0x00}, 128},
+      {{0x81, 0x01}, 129},
+      {{0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
+       UINT64_MAX},
+      // 2^64 is too large and overflows.
+      {{0x82, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00},
+       std::nullopt},
+      // Non-minimal OID components are invalid.
+      {{0x80, 0x01}, std::nullopt},
+      {{0x80, 0x81, 0x00}, std::nullopt},
+      {{0x80, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f},
+       std::nullopt},
+      // Truncated OID component.
+      {{0xff}, std::nullopt},
+      {{0x81}, std::nullopt},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(Bytes(t.enc));
+    CBS cbs;
+    CBS_init(&cbs, t.enc.data(), t.enc.size());
+    uint64_t v;
+    if (!t.v.has_value()) {
+      EXPECT_FALSE(CBS_get_asn1_oid_component(&cbs, &v));
+      continue;
+    }
+
+    ASSERT_TRUE(CBS_get_asn1_oid_component(&cbs, &v));
+    EXPECT_EQ(v, t.v.value());
+    EXPECT_EQ(CBS_len(&cbs), 0u);
+
+    // Trailing data should be left in `cbs`.
+    std::vector<uint8_t> trailing_data = t.enc;
+    trailing_data.push_back(42);
+    CBS_init(&cbs, trailing_data.data(), trailing_data.size());
+    ASSERT_TRUE(CBS_get_asn1_oid_component(&cbs, &v));
+    EXPECT_EQ(v, t.v.value());
+    EXPECT_EQ(CBS_data(&cbs), trailing_data.data() + t.enc.size());
+    EXPECT_EQ(CBS_len(&cbs), 1u);
+
+    // Test serialization.
+    ScopedCBB cbb;
+    ASSERT_TRUE(CBB_init(cbb.get(), t.enc.size()));
+    ASSERT_TRUE(CBB_add_asn1_oid_component(cbb.get(), t.v.value()));
+    EXPECT_EQ(Bytes(CBBAsSpan(cbb.get())), Bytes(t.enc));
+  }
+}
+
 }  // namespace
 BSSL_NAMESPACE_END
diff --git a/crypto/bytestring/cbs.cc b/crypto/bytestring/cbs.cc
index 82f74af..7abf10b 100644
--- a/crypto/bytestring/cbs.cc
+++ b/crypto/bytestring/cbs.cc
@@ -277,10 +277,7 @@
   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.
-static int parse_base128_integer(CBS *cbs, uint64_t *out) {
+int CBS_get_asn1_oid_component(CBS *cbs, uint64_t *out) {
   uint64_t v = 0;
   uint8_t b;
   do {
@@ -319,8 +316,9 @@
   CBS_ASN1_TAG tag = ((CBS_ASN1_TAG)tag_byte & 0xe0) << CBS_ASN1_TAG_SHIFT;
   CBS_ASN1_TAG tag_number = tag_byte & 0x1f;
   if (tag_number == 0x1f) {
+    // High tag numbers are encoded in the same format as OID components.
     uint64_t v;
-    if (!parse_base128_integer(cbs, &v) ||
+    if (!CBS_get_asn1_oid_component(cbs, &v) ||
         // Check the tag number is within our supported bounds.
         v > CBS_ASN1_TAG_NUMBER_MASK ||
         // Small tag numbers should have used low tag number form, even in BER.
@@ -747,9 +745,9 @@
   uint8_t v, prev = 0;
   while (CBS_get_u8(&copy, &v)) {
     // OID encodings are a sequence of minimally-encoded base-128 integers (see
-    // `parse_base128_integer`). If `prev`'s MSB was clear, it was the last byte
-    // of an integer (or `v` is the first byte). `v` is then the first byte of
-    // the next integer. If first byte of an integer is 0x80, it is not
+    // `CBS_get_asn1_oid_component`). If `prev`'s MSB was clear, it was the last
+    // byte of an integer (or `v` is the first byte). `v` is then the first byte
+    // of the next integer. If first byte of an integer is 0x80, it is not
     // minimally-encoded.
     if ((prev & 0x80) == 0 && v == 0x80) {
       return 0;
@@ -770,7 +768,7 @@
 
   // The first component is 40 * value1 + value2, where value1 is 0, 1, or 2.
   uint64_t v;
-  if (!parse_base128_integer(&copy, &v)) {
+  if (!CBS_get_asn1_oid_component(&copy, &v)) {
     goto err;
   }
 
@@ -785,7 +783,7 @@
   }
 
   while (CBS_len(&copy) != 0) {
-    if (!parse_base128_integer(&copy, &v) || !CBB_add_u8(&cbb, '.') ||
+    if (!CBS_get_asn1_oid_component(&copy, &v) || !CBB_add_u8(&cbb, '.') ||
         !add_decimal(&cbb, v)) {
       goto err;
     }
@@ -817,12 +815,12 @@
 
   // Relative OIDs must have at least one component.
   uint64_t v;
-  if (!parse_base128_integer(&copy, &v) || !add_decimal(cbb.get(), v)) {
+  if (!CBS_get_asn1_oid_component(&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(), '.') ||
+    if (!CBS_get_asn1_oid_component(&copy, &v) || !CBB_add_u8(cbb.get(), '.') ||
         !add_decimal(cbb.get(), v)) {
       return nullptr;
     }
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 359f764..7005074 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -429,6 +429,16 @@
 // OID components are too large.
 OPENSSL_EXPORT char *CBS_asn1_relative_oid_to_text(const CBS *cbs);
 
+// CBS_get_asn1_oid_component gets a single OID component from `cbs`. On
+// success, it sets `*out` to the value, advances `cbs` past the component, and
+// returns one. If the input does not contain a valid OID component or if the
+// OID component is bigger than `uint64_t`, it returns zero.
+//
+// Specifically, this decodes a non-empty, minimal-width, base-128, big-endian
+// integer. The high bit of each byte is set for continuation bytes and unset
+// for the final byte. This encoding is also used for DER tag numbers 31 and up.
+OPENSSL_EXPORT int CBS_get_asn1_oid_component(CBS *cbs, uint64_t *out);
+
 // 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
 // otherwise. If `allow_timezone_offset` is non-zero, four-digit timezone
@@ -706,6 +716,11 @@
 
 // CBB_add_asn1_oid_component appends a single OID component to `cbb`.
 // It returns one on success and zero on error.
+//
+// Specifically, this encodes `value` as a non-empty, minimal-width, base-128,
+// big-endian integer. The high bit of each byte is set for continuation bytes
+// and unset for the final byte. This encoding is also used for DER tag numbers
+// 31 and up.
 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
diff --git a/include/openssl/prefix_symbols.h b/include/openssl/prefix_symbols.h
index 48d5081..317d396 100644
--- a/include/openssl/prefix_symbols.h
+++ b/include/openssl/prefix_symbols.h
@@ -554,6 +554,7 @@
 #pragma redefine_extname CBS_get_asn1_element BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_asn1_element)
 #pragma redefine_extname CBS_get_asn1_int64 BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_asn1_int64)
 #pragma redefine_extname CBS_get_asn1_int64_with_tag BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_asn1_int64_with_tag)
+#pragma redefine_extname CBS_get_asn1_oid_component BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_asn1_oid_component)
 #pragma redefine_extname CBS_get_asn1_uint64 BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_asn1_uint64)
 #pragma redefine_extname CBS_get_asn1_uint64_with_tag BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_asn1_uint64_with_tag)
 #pragma redefine_extname CBS_get_bytes BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_bytes)
@@ -3673,6 +3674,7 @@
 #define CBS_get_asn1_element BORINGSSL_ADD_PREFIX(CBS_get_asn1_element)
 #define CBS_get_asn1_int64 BORINGSSL_ADD_PREFIX(CBS_get_asn1_int64)
 #define CBS_get_asn1_int64_with_tag BORINGSSL_ADD_PREFIX(CBS_get_asn1_int64_with_tag)
+#define CBS_get_asn1_oid_component BORINGSSL_ADD_PREFIX(CBS_get_asn1_oid_component)
 #define CBS_get_asn1_uint64 BORINGSSL_ADD_PREFIX(CBS_get_asn1_uint64)
 #define CBS_get_asn1_uint64_with_tag BORINGSSL_ADD_PREFIX(CBS_get_asn1_uint64_with_tag)
 #define CBS_get_bytes BORINGSSL_ADD_PREFIX(CBS_get_bytes)