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,