bytestring: add methods for int64.

I guess there's a first time for everything.  Today, it's negative
ASN.1 INTEGERs.  This benefits TLS 1.3 split handshakes.

Change-Id: I886bc513d644dde756db11488d09f450032e464b
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/39124
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 1f78799..ad5f947 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -754,6 +754,79 @@
   }
 }
 
+struct ASN1Int64Test {
+  int64_t value;
+  const char *encoding;
+  size_t encoding_len;
+};
+
+static const ASN1Int64Test kASN1Int64Tests[] = {
+    {0, "\x02\x01\x00", 3},
+    {1, "\x02\x01\x01", 3},
+    {-1, "\x02\x01\xff", 3},
+    {127, "\x02\x01\x7f", 3},
+    {-127, "\x02\x01\x81", 3},
+    {128, "\x02\x02\x00\x80", 4},
+    {-128, "\x02\x01\x80", 3},
+    {129, "\x02\x02\x00\x81", 4},
+    {-129, "\x02\x02\xff\x7f", 4},
+    {0xdeadbeef, "\x02\x05\x00\xde\xad\xbe\xef", 7},
+    {INT64_C(0x0102030405060708), "\x02\x08\x01\x02\x03\x04\x05\x06\x07\x08",
+     10},
+    {INT64_MIN, "\x02\x08\x80\x00\x00\x00\x00\x00\x00\x00", 10},
+    {INT64_MAX, "\x02\x08\x7f\xff\xff\xff\xff\xff\xff\xff", 10},
+};
+
+struct ASN1InvalidInt64Test {
+  const char *encoding;
+  size_t encoding_len;
+};
+
+static const ASN1InvalidInt64Test kASN1InvalidInt64Tests[] = {
+    // Bad tag.
+    {"\x03\x01\x00", 3},
+    // Empty contents.
+    {"\x02\x00", 2},
+    // Overflow.
+    {"\x02\x09\x01\x00\x00\x00\x00\x00\x00\x00\x00", 11},
+    // Leading zeros.
+    {"\x02\x02\x00\x01", 4},
+    // Leading 0xff.
+    {"\x02\x02\xff\xff", 4},
+};
+
+TEST(CBSTest, ASN1Int64) {
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kASN1Int64Tests); i++) {
+    SCOPED_TRACE(i);
+    const ASN1Int64Test *test = &kASN1Int64Tests[i];
+    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(&cbs, &value));
+    EXPECT_EQ(0u, CBS_len(&cbs));
+    EXPECT_EQ(test->value, value);
+
+    bssl::ScopedCBB cbb;
+    ASSERT_TRUE(CBB_init(cbb.get(), 0));
+    ASSERT_TRUE(CBB_add_asn1_int64(cbb.get(), test->value));
+    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));
+  }
+
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kASN1InvalidInt64Tests); i++) {
+    const ASN1InvalidInt64Test *test = &kASN1InvalidInt64Tests[i];
+    CBS cbs;
+    int64_t value;
+
+    CBS_init(&cbs, (const uint8_t *)test->encoding, test->encoding_len);
+    EXPECT_FALSE(CBS_get_asn1_int64(&cbs, &value));
+  }
+}
+
 TEST(CBBTest, Zero) {
   CBB cbb;
   CBB_zero(&cbb);
diff --git a/crypto/bytestring/cbb.c b/crypto/bytestring/cbb.c
index 25d087c..501cb1b 100644
--- a/crypto/bytestring/cbb.c
+++ b/crypto/bytestring/cbb.c
@@ -516,6 +516,34 @@
   return CBB_flush(cbb);
 }
 
+int CBB_add_asn1_int64(CBB *cbb, int64_t value) {
+  if (value >= 0) {
+    return CBB_add_asn1_uint64(cbb, value);
+  }
+
+  union {
+    int64_t i;
+    uint8_t bytes[sizeof(int64_t)];
+  } u;
+  u.i = value;
+  int start = 7;
+  // Skip leading sign-extension bytes unless they are necessary.
+  while (start > 0 && (u.bytes[start] == 0xff && (u.bytes[start - 1] & 0x80))) {
+    start--;
+  }
+
+  CBB child;
+  if (!CBB_add_asn1(cbb, &child, CBS_ASN1_INTEGER)) {
+    return 0;
+  }
+  for (int i = start; i >= 0; i--) {
+    if (!CBB_add_u8(&child, u.bytes[i])) {
+      return 0;
+    }
+  }
+  return CBB_flush(cbb);
+}
+
 int CBB_add_asn1_octet_string(CBB *cbb, const uint8_t *data, size_t data_len) {
   CBB child;
   if (!CBB_add_asn1(cbb, &child, CBS_ASN1_OCTETSTRING) ||
diff --git a/crypto/bytestring/cbs.c b/crypto/bytestring/cbs.c
index 97c1c59..cd0eaa8 100644
--- a/crypto/bytestring/cbs.c
+++ b/crypto/bytestring/cbs.c
@@ -437,6 +437,40 @@
   return 1;
 }
 
+int CBS_get_asn1_int64(CBS *cbs, int64_t *out) {
+  CBS bytes;
+  if (!CBS_get_asn1(cbs, &bytes, CBS_ASN1_INTEGER)) {
+    return 0;
+  }
+  const uint8_t *data = CBS_data(&bytes);
+  const size_t len = CBS_len(&bytes);
+
+  if (len == 0 || len > sizeof(int64_t)) {
+    // An INTEGER is encoded with at least one octet.
+    return 0;
+  }
+  if (len > 1) {
+    if (data[0] == 0 && (data[1] & 0x80) == 0) {
+      return 0;  // Extra leading zeros.
+    }
+    if (data[0] == 0xff && (data[1] & 0x80) != 0) {
+      return 0;  // Extra leading 0xff.
+    }
+  }
+
+  union {
+    int64_t i;
+    uint8_t bytes[sizeof(int64_t)];
+  } u;
+  const int is_negative = (data[0] & 0x80);
+  memset(u.bytes, is_negative ? 0xff : 0, sizeof(u.bytes));  // Sign-extend.
+  for (size_t i = 0; i < len; i++) {
+    u.bytes[i] = data[len - i - 1];
+  }
+  *out = u.i;
+  return 1;
+}
+
 int CBS_get_asn1_bool(CBS *cbs, int *out) {
   CBS bytes;
   if (!CBS_get_asn1(cbs, &bytes, CBS_ASN1_BOOLEAN) ||
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 029c2be..2fe4b01 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -253,6 +253,11 @@
 // in 64 bits.
 OPENSSL_EXPORT int CBS_get_asn1_uint64(CBS *cbs, uint64_t *out);
 
+// 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_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);
@@ -476,6 +481,11 @@
 // error.
 OPENSSL_EXPORT int CBB_add_asn1_uint64(CBB *cbb, uint64_t value);
 
+// CBB_add_asn1_int64 writes an ASN.1 INTEGER into |cbb| using |CBB_add_asn1|
+// and writes |value| in its contents. It returns one on success and zero on
+// error.
+OPENSSL_EXPORT int CBB_add_asn1_int64(CBB *cbb, int64_t value);
+
 // CBB_add_asn1_octet_string writes an ASN.1 OCTET STRING into |cbb| with the
 // given contents. It returns one on success and zero on error.
 OPENSSL_EXPORT int CBB_add_asn1_octet_string(CBB *cbb, const uint8_t *data,