Add CBS APIs for fetching an implicitly tagged int64/uint64 fields.

Change-Id: Iafff08857bf2af8038e2c8bf4e6a57c38cf0b7ec
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/75887
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index b0a254b..23133e0 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -901,6 +901,24 @@
     {"\x02\x02\x00\x01", 4, false},
 };
 
+struct ASN1Uint64WithTagTest {
+  CBS_ASN1_TAG tag;
+  uint64_t value;
+  const char *encoding;
+  size_t encoding_len;
+};
+
+static const ASN1Uint64WithTagTest kASN1Uint64WithTagTests[]{
+    {CBS_ASN1_CONTEXT_SPECIFIC, 0, "\x80\x01\x00", 3},
+    {CBS_ASN1_CONTEXT_SPECIFIC | 1, 1, "\x81\x01\x01", 3},
+    {CBS_ASN1_INTEGER, 127, "\x02\x01\x7f", 3},
+    {CBS_ASN1_CONTEXT_SPECIFIC, 128, "\x80\x02\x00\x80", 4},
+    {CBS_ASN1_CONTEXT_SPECIFIC, UINT64_C(0x0102030405060708),
+     "\x80\x08\x01\x02\x03\x04\x05\x06\x07\x08", 10},
+    {CBS_ASN1_CONTEXT_SPECIFIC, (0xffffffffffffffff),
+     "\x80\x09\x00\xff\xff\xff\xff\xff\xff\xff\xff", 11},
+};
+
 TEST(CBSTest, ASN1Uint64) {
   for (const ASN1Uint64Test &test : kASN1Uint64Tests) {
     SCOPED_TRACE(Bytes(test.encoding, test.encoding_len));
@@ -961,6 +979,37 @@
       EXPECT_EQ(test.overflow, !!CBS_is_unsigned_asn1_integer(&child));
     }
   }
+
+  for (const ASN1Uint64WithTagTest &test : kASN1Uint64WithTagTests) {
+    SCOPED_TRACE(Bytes(test.encoding, test.encoding_len));
+    SCOPED_TRACE(test.value);
+    CBS cbs;
+    uint64_t value;
+    uint8_t *out;
+    size_t len;
+
+    CBS_init(&cbs, (const uint8_t *)test.encoding, test.encoding_len);
+    ASSERT_TRUE(CBS_get_asn1_uint64_with_tag(&cbs, &value, test.tag));
+    EXPECT_EQ(0u, CBS_len(&cbs));
+    EXPECT_EQ(test.value, value);
+
+    CBS child;
+    int is_negative;
+    CBS_init(&cbs, (const uint8_t *)test.encoding, test.encoding_len);
+    ASSERT_TRUE(CBS_get_asn1(&cbs, &child, test.tag));
+    EXPECT_TRUE(CBS_is_valid_asn1_integer(&child, &is_negative));
+    EXPECT_EQ(0, is_negative);
+    EXPECT_TRUE(CBS_is_unsigned_asn1_integer(&child));
+
+    {
+      bssl::ScopedCBB cbb;
+      ASSERT_TRUE(CBB_init(cbb.get(), 0));
+      ASSERT_TRUE(CBB_add_asn1_uint64_with_tag(cbb.get(), test.value, test.tag));
+      ASSERT_TRUE(CBB_finish(cbb.get(), &out, &len));
+      bssl::UniquePtr<uint8_t> scoper(out);
+      EXPECT_EQ(Bytes(test.encoding, test.encoding_len), Bytes(out, len));
+    }
+  }
 }
 
 struct ASN1Int64Test {
@@ -1007,6 +1056,24 @@
     {"\x02\x02\xff\xff", 4, false},
 };
 
+struct ASN1Int64WithTagTest {
+  CBS_ASN1_TAG tag;
+  int64_t value;
+  const char *encoding;
+  size_t encoding_len;
+};
+
+static const ASN1Int64WithTagTest kASN1Int64WithTagTests[] = {
+    {CBS_ASN1_CONTEXT_SPECIFIC, 0, "\x80\x01\x00", 3},
+    {CBS_ASN1_CONTEXT_SPECIFIC | 1, 1, "\x81\x01\x01", 3},
+    {CBS_ASN1_INTEGER, 1, "\x02\x01\x01", 3},
+    {CBS_ASN1_CONTEXT_SPECIFIC, INT64_MIN,
+     "\x80\x08\x80\x00\x00\x00\x00\x00\x00\x00", 10},
+    {CBS_ASN1_CONTEXT_SPECIFIC, INT64_MAX,
+     "\x80\x08\x7f\xff\xff\xff\xff\xff\xff\xff", 10},
+};
+
+
 TEST(CBSTest, ASN1Int64) {
   for (const ASN1Int64Test &test : kASN1Int64Tests) {
     SCOPED_TRACE(Bytes(test.encoding, test.encoding_len));
@@ -1067,6 +1134,37 @@
       EXPECT_EQ(test.overflow, !!CBS_is_valid_asn1_integer(&child, NULL));
     }
   }
+
+  for (const ASN1Int64WithTagTest &test : kASN1Int64WithTagTests) {
+    SCOPED_TRACE(Bytes(test.encoding, test.encoding_len));
+    SCOPED_TRACE(test.value);
+    CBS cbs;
+    int64_t value;
+    uint8_t *out;
+    size_t len;
+
+    CBS_init(&cbs, (const uint8_t *)test.encoding, test.encoding_len);
+    ASSERT_TRUE(CBS_get_asn1_int64_with_tag(&cbs, &value, test.tag));
+    EXPECT_EQ(0u, CBS_len(&cbs));
+    EXPECT_EQ(test.value, value);
+
+    CBS child;
+    int is_negative;
+    CBS_init(&cbs, (const uint8_t *)test.encoding, test.encoding_len);
+    ASSERT_TRUE(CBS_get_asn1(&cbs, &child, test.tag));
+    EXPECT_TRUE(CBS_is_valid_asn1_integer(&child, &is_negative));
+    EXPECT_EQ(test.value < 0, !!is_negative);
+    EXPECT_EQ(test.value >= 0, !!CBS_is_unsigned_asn1_integer(&child));
+
+    {
+      bssl::ScopedCBB cbb;
+      ASSERT_TRUE(CBB_init(cbb.get(), 0));
+      ASSERT_TRUE(CBB_add_asn1_int64_with_tag(cbb.get(), test.value, test.tag));
+      ASSERT_TRUE(CBB_finish(cbb.get(), &out, &len));
+      bssl::UniquePtr<uint8_t> scoper(out);
+      EXPECT_EQ(Bytes(test.encoding, test.encoding_len), Bytes(out, len));
+    }
+  }
 }
 
 TEST(CBBTest, Zero) {
diff --git a/crypto/bytestring/cbs.cc b/crypto/bytestring/cbs.cc
index efdf3f0..73a9d63 100644
--- a/crypto/bytestring/cbs.cc
+++ b/crypto/bytestring/cbs.cc
@@ -472,8 +472,12 @@
 }
 
 int CBS_get_asn1_uint64(CBS *cbs, uint64_t *out) {
+  return CBS_get_asn1_uint64_with_tag(cbs, out, CBS_ASN1_INTEGER);
+}
+
+int CBS_get_asn1_uint64_with_tag(CBS *cbs, uint64_t *out, CBS_ASN1_TAG tag) {
   CBS bytes;
-  if (!CBS_get_asn1(cbs, &bytes, CBS_ASN1_INTEGER) ||
+  if (!CBS_get_asn1(cbs, &bytes, tag) ||
       !CBS_is_unsigned_asn1_integer(&bytes)) {
     return 0;
   }
@@ -494,9 +498,13 @@
 }
 
 int CBS_get_asn1_int64(CBS *cbs, int64_t *out) {
+  return CBS_get_asn1_int64_with_tag(cbs, out, CBS_ASN1_INTEGER);
+}
+
+int CBS_get_asn1_int64_with_tag(CBS *cbs, int64_t *out, CBS_ASN1_TAG tag) {
   int is_negative;
   CBS bytes;
-  if (!CBS_get_asn1(cbs, &bytes, CBS_ASN1_INTEGER) ||
+  if (!CBS_get_asn1(cbs, &bytes, tag) ||
       !CBS_is_valid_asn1_integer(&bytes, &is_negative)) {
     return 0;
   }
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index ffab33b..813decb 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -292,11 +292,28 @@
 // in 64 bits.
 OPENSSL_EXPORT int CBS_get_asn1_uint64(CBS *cbs, uint64_t *out);
 
+// CBS_get_asn1_uint64_with_tag gets an ASN.1 INTEGER from |cbs| using
+// |CBS_get_asn1| and sets |*out| to its value. |tag| is used to handle to
+// handle implicitly tagged INTEGER fields. It returns one on success and zero
+// on error, where error includes the integer being negative, or too large to
+// represent in 64 bits.
+OPENSSL_EXPORT int CBS_get_asn1_uint64_with_tag(CBS *cbs, uint64_t *out,
+                                                CBS_ASN1_TAG tag);
+
+
 // CBS_get_asn1_int64 gets an ASN.1 INTEGER from |cbs| using |CBS_get_asn1|
 // and sets |*out| to its value. It returns one on success and zero on error,
 // where error includes the integer being too large to represent in 64 bits.
 OPENSSL_EXPORT int CBS_get_asn1_int64(CBS *cbs, int64_t *out);
 
+// CBS_get_asn1_int64_with_tag gets an ASN.1 INTEGER from |cbs| using
+// |CBS_get_asn1| and sets |*out| to its value. |tag| is used to handle to
+// handle implicitly tagged INTEGER fields. It returns one on success and zero
+// on error, where error includes the integer being too large to represent in 64
+// bits.
+OPENSSL_EXPORT int CBS_get_asn1_int64_with_tag(CBS *cbs, int64_t *out,
+                                               CBS_ASN1_TAG tag);
+
 // CBS_get_asn1_bool gets an ASN.1 BOOLEAN from |cbs| and sets |*out| to zero
 // or one based on its value. It returns one on success or zero on error.
 OPENSSL_EXPORT int CBS_get_asn1_bool(CBS *cbs, int *out);