Reimplement ASN1_get_object with CBS.
Now we only have one BER/DER TLV parser. Annoyingly, this uses the CBS
BER function, not the DER one. This is because Android sometimes needs
allow a non-minimal length in certificate signature fields (see
b/18228011).
For now, this CL calls CBS_get_any_ber_asn1_element. This is still an
improvement over the old parser because we'll reject non-minimal tags
(which are actually even forbidden in BER). Later, we should move the
special case to just the signature field, and ultimately to a
preprocessing step specific to that part of Android.
Update-Note: Invalid certificates (and the few external structures using
asn1t.h) with incorrectly-encoded tags will now be rejected.
Bug: 354
Change-Id: I56a7faa1ffd51ee38cc315ebaddaef98079fd90e
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/51626
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/asn1/asn1_lib.c b/crypto/asn1/asn1_lib.c
index fbf4d68..edf5e7c 100644
--- a/crypto/asn1/asn1_lib.c
+++ b/crypto/asn1/asn1_lib.c
@@ -59,7 +59,7 @@
#include <limits.h>
#include <string.h>
-#include <openssl/asn1_mac.h>
+#include <openssl/bytestring.h>
#include <openssl/err.h>
#include <openssl/mem.h>
@@ -104,101 +104,54 @@
OPENSSL_DECLARE_ERROR_REASON(ASN1, UNKNOWN_TAG)
OPENSSL_DECLARE_ERROR_REASON(ASN1, UNSUPPORTED_TYPE)
-static int asn1_get_length(const unsigned char **pp, long *rl, long max);
static void asn1_put_length(unsigned char **pp, int length);
-int ASN1_get_object(const unsigned char **pp, long *plength, int *ptag,
- int *pclass, long omax)
+int ASN1_get_object(const unsigned char **inp, long *out_len, int *out_tag,
+ int *out_class, long in_len)
{
- int i, ret;
- long l;
- const unsigned char *p = *pp;
- int tag, xclass;
- long max = omax;
-
- if (!max)
- goto err;
- ret = (*p & V_ASN1_CONSTRUCTED);
- xclass = (*p & V_ASN1_PRIVATE);
- i = *p & V_ASN1_PRIMITIVE_TAG;
- if (i == V_ASN1_PRIMITIVE_TAG) { /* high-tag */
- p++;
- if (--max == 0)
- goto err;
- l = 0;
- while (*p & 0x80) {
- l <<= 7L;
- l |= *(p++) & 0x7f;
- if (--max == 0)
- goto err;
- if (l > (INT_MAX >> 7L))
- goto err;
- }
- l <<= 7L;
- l |= *(p++) & 0x7f;
- tag = (int)l;
- if (--max == 0)
- goto err;
- } else {
- tag = i;
- p++;
- if (--max == 0)
- goto err;
- }
-
- /* To avoid ambiguity with V_ASN1_NEG, impose a limit on universal tags. */
- if (xclass == V_ASN1_UNIVERSAL && tag > V_ASN1_MAX_UNIVERSAL)
- goto err;
-
- *ptag = tag;
- *pclass = xclass;
- if (!asn1_get_length(&p, plength, max))
- goto err;
-
- if (*plength > (omax - (p - *pp))) {
- OPENSSL_PUT_ERROR(ASN1, ASN1_R_TOO_LONG);
+ if (in_len < 0) {
+ OPENSSL_PUT_ERROR(ASN1, ASN1_R_HEADER_TOO_LONG);
return 0x80;
}
- *pp = p;
- return ret;
- err:
- OPENSSL_PUT_ERROR(ASN1, ASN1_R_HEADER_TOO_LONG);
- return 0x80;
-}
-static int asn1_get_length(const unsigned char **pp, long *rl, long max)
-{
- const unsigned char *p = *pp;
- unsigned long ret = 0;
- unsigned long i;
+ /* TODO(https://crbug.com/boringssl/354): This should use |CBS_get_asn1| to
+ * reject non-minimal lengths, which are only allowed in BER. However,
+ * Android sometimes needs allow a non-minimal length in certificate
+ * signature fields (see b/18228011). Make this only apply to that field,
+ * while requiring DER elsewhere. Better yet, it should be limited to an
+ * preprocessing step in that part of Android. */
+ unsigned tag;
+ size_t header_len;
+ int indefinite;
+ CBS cbs, body;
+ CBS_init(&cbs, *inp, (size_t)in_len);
+ if (!CBS_get_any_ber_asn1_element(&cbs, &body, &tag, &header_len,
+ /*out_ber_found=*/NULL, &indefinite) ||
+ indefinite ||
+ !CBS_skip(&body, header_len) ||
+ /* Bound the length to comfortably fit in an int. Lengths in this
+ * module often switch between int and long without overflow checks. */
+ CBS_len(&body) > INT_MAX / 2) {
+ OPENSSL_PUT_ERROR(ASN1, ASN1_R_HEADER_TOO_LONG);
+ return 0x80;
+ }
- if (max-- < 1) {
- return 0;
+ /* Convert between tag representations. */
+ int tag_class = (tag & CBS_ASN1_CLASS_MASK) >> CBS_ASN1_TAG_SHIFT;
+ int constructed = (tag & CBS_ASN1_CONSTRUCTED) >> CBS_ASN1_TAG_SHIFT;
+ int tag_number = tag & CBS_ASN1_TAG_NUMBER_MASK;
+
+ /* To avoid ambiguity with V_ASN1_NEG, impose a limit on universal tags. */
+ if (tag_class == V_ASN1_UNIVERSAL && tag_number > V_ASN1_MAX_UNIVERSAL) {
+ OPENSSL_PUT_ERROR(ASN1, ASN1_R_HEADER_TOO_LONG);
+ return 0x80;
}
- if (*p == 0x80) {
- /* We do not support BER indefinite-length encoding. */
- return 0;
- }
- i = *p & 0x7f;
- if (*(p++) & 0x80) {
- if (i > sizeof(ret) || max < (long)i)
- return 0;
- while (i-- > 0) {
- ret <<= 8L;
- ret |= *(p++);
- }
- } else {
- ret = i;
- }
- /*
- * Bound the length to comfortably fit in an int. Lengths in this module
- * often switch between int and long without overflow checks.
- */
- if (ret > INT_MAX / 2)
- return 0;
- *pp = p;
- *rl = (long)ret;
- return 1;
+
+ *inp = CBS_data(&body);
+ *out_len = CBS_len(&body);
+ *out_tag = tag_number;
+ *out_class = tag_class;
+ return constructed;
}
/*
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 38414e9..48b83da 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -3524,6 +3524,20 @@
-----END CERTIFICATE-----
)";
+// kHighTagNumber is an X.509 certificate where the outermost SEQUENCE tag uses
+// high tag number form.
+static const char kHighTagNumber[] = R"(
+-----BEGIN CERTIFICATE-----
+PxCCASAwgcagAwIBAgICBNIwCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEVGVzdDAg
+Fw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowDzENMAsGA1UEAxMEVGVz
+dDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7ep
+Hg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsGjEDAO
+MAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKnSIhfmzfQpeOKFHiAq
+cml3ex6oaVVGoJWCsPQoZjVAAiEAqTHS9HzZBTQ20cMPXUpf8u5AXZP7adeh4qnk
+soBsxWI=
+-----END CERTIFICATE-----
+)";
+
TEST(X509Test, BER) {
// Constructed strings are forbidden in DER.
EXPECT_FALSE(CertFromPEM(kConstructedBitString));
@@ -3532,6 +3546,9 @@
EXPECT_FALSE(CertFromPEM(kIndefiniteLength));
// Padding bits in BIT STRINGs must be zero in BER.
EXPECT_FALSE(CertFromPEM(kNonZeroPadding));
+ // Tags must be minimal in both BER and DER, though many BER decoders
+ // incorrectly support non-minimal tags.
+ EXPECT_FALSE(CertFromPEM(kHighTagNumber));
}
TEST(X509Test, Names) {