Add an explicit indefinite-length output to CBS_get_any_ber_asn1_element.
Having to check for header_len == len and a last byte of 0x80 is
actually unambiguous, but not obvious. Before we supported multi-byte
tags, a two-byte header was always {tag, 0x80}, but now a three-byte
header could be {tag1, tag2, 0x80}. But a 0x80 suffix could also be
{tag, 0x81, 0x80} for a 128-byte definite-length element.
This is unambiguous because header_len == len implies either zero length
or indefinite-length, and it is not possible to encode a definite length
of zero, in BER or DER, with a header that ends in 0x80. Still, rather
than go through all this, we can just report indefinite lengths to the
caller directly.
Update-Note: This is a breaking change to CBS_get_any_ber_asn1_element.
There is only one external caller of this function, and it should be
possible to fix them atomically with this change, so I haven't bothered
introducing another name, etc. (See cl/429632075 for the fix.)
Change-Id: Ic94dab562724fd0b388bc8d2a7a223f21a8da413
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/51625
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/bytestring/ber.c b/crypto/bytestring/ber.c
index e7f67dd..d9b780f 100644
--- a/crypto/bytestring/ber.c
+++ b/crypto/bytestring/ber.c
@@ -69,9 +69,9 @@
CBS contents;
unsigned tag;
size_t header_len;
-
+ int indefinite;
if (!CBS_get_any_ber_asn1_element(&in, &contents, &tag, &header_len,
- ber_found)) {
+ ber_found, &indefinite)) {
return 0;
}
if (*ber_found) {
@@ -119,11 +119,11 @@
CBS contents;
unsigned tag, child_string_tag = string_tag;
size_t header_len;
- int ber_found;
+ int indefinite;
CBB *out_contents, out_contents_storage;
if (!CBS_get_any_ber_asn1_element(in, &contents, &tag, &header_len,
- &ber_found)) {
+ /*out_ber_found=*/NULL, &indefinite)) {
return 0;
}
@@ -153,11 +153,9 @@
out_contents = &out_contents_storage;
}
- if (CBS_len(&contents) == header_len && header_len > 0 &&
- CBS_data(&contents)[header_len - 1] == 0x80) {
- // This is an indefinite length element.
+ if (indefinite) {
if (!cbs_convert_ber(in, out_contents, child_string_tag,
- 1 /* looking for eoc */, depth + 1) ||
+ /*looking_for_eoc=*/1, depth + 1) ||
!CBB_flush(out)) {
return 0;
}
@@ -171,7 +169,7 @@
if (tag & CBS_ASN1_CONSTRUCTED) {
// Recurse into children.
if (!cbs_convert_ber(&contents, out_contents, child_string_tag,
- 0 /* not looking for eoc */, depth + 1)) {
+ /*looking_for_eoc=*/0, depth + 1)) {
return 0;
}
} else {
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 985e38c..77261a3 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -693,29 +693,38 @@
const char *in_hex;
bool ok;
bool ber_found;
+ bool indefinite;
unsigned tag;
};
static const BERTest kBERTests[] = {
- // Trivial cases, also valid DER.
- {"0000", true, false, 0},
- {"0100", true, false, 1},
- {"020101", true, false, 2},
+ // Trivial cases, also valid DER.
+ {"0000", true, false, false, 0},
+ {"0100", true, false, false, 1},
+ {"020101", true, false, false, 2},
- // Non-minimally encoded lengths.
- {"02810101", true, true, 2},
- {"0282000101", true, true, 2},
- {"028300000101", true, true, 2},
- {"02840000000101", true, true, 2},
- // Technically valid BER, but not handled.
- {"02850000000101", false, false, 0},
+ // Non-minimally encoded lengths.
+ {"02810101", true, true, false, 2},
+ {"0282000101", true, true, false, 2},
+ {"028300000101", true, true, false, 2},
+ {"02840000000101", true, true, false, 2},
+ // Technically valid BER, but not handled.
+ {"02850000000101", false, false, false, 0},
- {"0280", false, false, 0}, // Indefinite length, but not constructed.
- {"2280", true, true, CBS_ASN1_CONSTRUCTED | 2}, // Indefinite length.
- {"3f0000", false, false, 0}, // Invalid extended tag zero (X.690 8.1.2.4.2.c)
- {"1f0100", false, false, 0}, // Should be a low-number tag form, even in BER.
- {"1f4000", true, false, 0x40},
- {"1f804000", false, false, 0}, // Non-minimal tags are invalid, even in BER.
+ // Indefinite length, but not constructed.
+ {"0280", false, false, false, 0},
+ // Indefinite length.
+ {"2280", true, true, true, CBS_ASN1_CONSTRUCTED | 2},
+ // Indefinite length with multi-byte tag.
+ {"bf1f80", true, true, true,
+ CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 31},
+ // Invalid extended tag zero (X.690 8.1.2.4.2.c)
+ {"3f0000", false, false, false, 0},
+ // Should be a low-number tag form, even in BER.
+ {"1f0100", false, false, false, 0},
+ {"1f4000", true, false, false, 0x40},
+ // Non-minimal tags are invalid, even in BER.
+ {"1f804000", false, false, false, 0},
};
TEST(CBSTest, BERElementTest) {
@@ -729,14 +738,16 @@
unsigned tag;
size_t header_len;
int ber_found;
- int ok =
- CBS_get_any_ber_asn1_element(&in, &out, &tag, &header_len, &ber_found);
+ int indefinite;
+ int ok = CBS_get_any_ber_asn1_element(&in, &out, &tag, &header_len,
+ &ber_found, &indefinite);
ASSERT_TRUE((ok == 1) == test.ok);
if (!test.ok) {
continue;
}
- EXPECT_TRUE((ber_found == 1) == test.ber_found);
+ EXPECT_EQ(test.ber_found ? 1 : 0, ber_found);
+ EXPECT_EQ(test.indefinite ? 1 : 0, indefinite);
EXPECT_LE(header_len, in_bytes.size());
EXPECT_EQ(CBS_len(&out), in_bytes.size());
EXPECT_EQ(CBS_len(&in), 0u);
diff --git a/crypto/bytestring/cbs.c b/crypto/bytestring/cbs.c
index 803c97a..293e66c 100644
--- a/crypto/bytestring/cbs.c
+++ b/crypto/bytestring/cbs.c
@@ -285,7 +285,7 @@
static int cbs_get_any_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
size_t *out_header_len, int *out_ber_found,
- int ber_ok) {
+ int *out_indefinite, int ber_ok) {
CBS header = *cbs;
CBS throwaway;
@@ -294,6 +294,10 @@
}
if (ber_ok) {
*out_ber_found = 0;
+ *out_indefinite = 0;
+ } else {
+ assert(out_ber_found == NULL);
+ assert(out_indefinite == NULL);
}
unsigned tag;
@@ -333,6 +337,7 @@
*out_header_len = header_len;
}
*out_ber_found = 1;
+ *out_indefinite = 1;
return CBS_get_bytes(cbs, out, header_len);
}
@@ -395,16 +400,18 @@
int CBS_get_any_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
size_t *out_header_len) {
- return cbs_get_any_asn1_element(cbs, out, out_tag, out_header_len,
- NULL, 0 /* DER only */);
+ return cbs_get_any_asn1_element(cbs, out, out_tag, out_header_len, NULL, NULL,
+ /*ber_ok=*/0);
}
int CBS_get_any_ber_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
- size_t *out_header_len, int *out_ber_found) {
+ size_t *out_header_len, int *out_ber_found,
+ int *out_indefinite) {
int ber_found_temp;
return cbs_get_any_asn1_element(
cbs, out, out_tag, out_header_len,
- out_ber_found ? out_ber_found : &ber_found_temp, 1 /* BER allowed */);
+ out_ber_found ? out_ber_found : &ber_found_temp, out_indefinite,
+ /*ber_ok=*/1);
}
static int cbs_get_asn1(CBS *cbs, CBS *out, unsigned tag_value,
diff --git a/include/openssl/base.h b/include/openssl/base.h
index 983eadc..e8d7994 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -195,7 +195,7 @@
// A consumer may use this symbol in the preprocessor to temporarily build
// against multiple revisions of BoringSSL at the same time. It is not
// recommended to do so for longer than is necessary.
-#define BORINGSSL_API_VERSION 16
+#define BORINGSSL_API_VERSION 17
#if defined(BORINGSSL_SHARED_LIBRARY)
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 5ef3742..199d89c 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -259,15 +259,17 @@
// CBS_get_any_ber_asn1_element acts the same as |CBS_get_any_asn1_element| but
// also allows indefinite-length elements to be returned and does not enforce
-// that lengths are minimal. For indefinite-lengths, |*out_header_len| and
+// that lengths are minimal. It sets |*out_indefinite| to one if the length was
+// indefinite and zero otherwise. If indefinite, |*out_header_len| and
// |CBS_len(out)| will be equal as only the header is returned (although this is
-// also true for empty elements so the length must be checked too). If
+// also true for empty elements so |*out_indefinite| should be checked). If
// |out_ber_found| is not NULL then it is set to one if any case of invalid DER
// but valid BER is found, and to zero otherwise.
OPENSSL_EXPORT int CBS_get_any_ber_asn1_element(CBS *cbs, CBS *out,
unsigned *out_tag,
size_t *out_header_len,
- int *out_ber_found);
+ int *out_ber_found,
+ int *out_indefinite);
// CBS_get_asn1_uint64 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,