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,