Fix BIT STRING comparison in ASN1_STRING_cmp.

The comparison should notice differences in bit count.

Update-Note: ASN1_STRING_cmp no longer incorrectly treats BIT STRINGs
with different padding bits as equal.

Bug: 446
Change-Id: I22b3fcc5d369540d029ca234e9b3b02402cec4c3
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/49928
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/asn1/a_bitstr.c b/crypto/asn1/a_bitstr.c
index d45a73e..7ce8cca 100644
--- a/crypto/asn1/a_bitstr.c
+++ b/crypto/asn1/a_bitstr.c
@@ -63,6 +63,7 @@
 #include <openssl/mem.h>
 
 #include "../internal.h"
+#include "internal.h"
 
 
 int ASN1_BIT_STRING_set(ASN1_BIT_STRING *x, const unsigned char *d, int len)
@@ -70,8 +71,8 @@
     return ASN1_STRING_set(x, d, len);
 }
 
-static int asn1_bit_string_length(const ASN1_BIT_STRING *str,
-                                  uint8_t *out_padding_bits) {
+int asn1_bit_string_length(const ASN1_BIT_STRING *str,
+                           uint8_t *out_padding_bits) {
     int len = str->length;
     if (str->flags & ASN1_STRING_FLAG_BITS_LEFT) {
         // If the string is already empty, it cannot have padding bits.
@@ -79,8 +80,8 @@
         return len;
     }
 
-    // TODO(davidben): If we move this logic to |ASN1_BIT_STRING_set_bit|, can
-    // we remove this representation?
+    // TODO(https://crbug.com/boringssl/447): If we move this logic to
+    // |ASN1_BIT_STRING_set_bit|, can we remove this representation?
     while (len > 0 && str->data[len - 1] == 0) {
         len--;
     }
diff --git a/crypto/asn1/asn1_lib.c b/crypto/asn1/asn1_lib.c
index b13a82a..9def017 100644
--- a/crypto/asn1/asn1_lib.c
+++ b/crypto/asn1/asn1_lib.c
@@ -64,6 +64,7 @@
 #include <openssl/mem.h>
 
 #include "../internal.h"
+#include "internal.h"
 
 
 /* Cross-module errors from crypto/x509/i2d_pr.c. */
@@ -406,17 +407,44 @@
 
 int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b)
 {
-    int i;
+    /* Capture padding bits and implicit truncation in BIT STRINGs. */
+    int a_length = a->length, b_length = b->length;
+    uint8_t a_padding = 0, b_padding = 0;
+    if (a->type == V_ASN1_BIT_STRING) {
+        a_length = asn1_bit_string_length(a, &a_padding);
+    }
+    if (b->type == V_ASN1_BIT_STRING) {
+        b_length = asn1_bit_string_length(b, &b_padding);
+    }
 
-    i = (a->length - b->length);
-    if (i == 0) {
-        i = OPENSSL_memcmp(a->data, b->data, a->length);
-        if (i == 0)
-            return (a->type - b->type);
-        else
-            return (i);
-    } else
-        return (i);
+    if (a_length < b_length) {
+        return -1;
+    }
+    if (a_length > b_length) {
+        return 1;
+    }
+    /* In a BIT STRING, the number of bits is 8 * length - padding. Invert this
+     * comparison so we compare by lengths. */
+    if (a_padding > b_padding) {
+        return -1;
+    }
+    if (a_padding < b_padding) {
+        return 1;
+    }
+
+    int ret = OPENSSL_memcmp(a->data, b->data, a_length);
+    if (ret != 0) {
+        return ret;
+    }
+
+    /* Comparing the type first is more natural, but this matches OpenSSL. */
+    if (a->type < b->type) {
+        return -1;
+    }
+    if (a->type > b->type) {
+        return 1;
+    }
+    return 0;
 }
 
 int ASN1_STRING_length(const ASN1_STRING *str)
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index cd47d2b..bb2b0a8 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -1478,6 +1478,115 @@
   EXPECT_FALSE(val);
 }
 
+TEST(ASN1Test, StringCmp) {
+  struct Input {
+    int type;
+    std::vector<uint8_t> data;
+    int flags;
+    bool equals_previous;
+  };
+  // kInputs is a list of |ASN1_STRING| parameters, in sorted order. The input
+  // should be sorted by bit length, then data, then type.
+  const Input kInputs[] = {
+      {V_ASN1_BIT_STRING, {}, ASN1_STRING_FLAG_BITS_LEFT | 0, false},
+      {V_ASN1_BIT_STRING, {}, 0, true},
+      // When |ASN1_STRING_FLAG_BITS_LEFT| is unset, BIT STRINGs implicitly
+      // drop trailing zeros.
+      {V_ASN1_BIT_STRING, {0x00, 0x00, 0x00, 0x00}, 0, true},
+
+      {V_ASN1_OCTET_STRING, {}, 0, false},
+      {V_ASN1_UTF8STRING, {}, 0, false},
+
+      // BIT STRINGs with padding bits (i.e. not part of the actual value) are
+      // shorter and thus sort earlier:
+      // 1-bit inputs.
+      {V_ASN1_BIT_STRING, {0x00}, ASN1_STRING_FLAG_BITS_LEFT | 7, false},
+      {V_ASN1_BIT_STRING, {0x80}, ASN1_STRING_FLAG_BITS_LEFT | 7, false},
+      // 2-bit inputs.
+      {V_ASN1_BIT_STRING, {0x00}, ASN1_STRING_FLAG_BITS_LEFT | 6, false},
+      {V_ASN1_BIT_STRING, {0xc0}, ASN1_STRING_FLAG_BITS_LEFT | 6, false},
+      // 3-bit inputs.
+      {V_ASN1_BIT_STRING, {0x00}, ASN1_STRING_FLAG_BITS_LEFT | 5, false},
+      {V_ASN1_BIT_STRING, {0xe0}, ASN1_STRING_FLAG_BITS_LEFT | 5, false},
+      // 4-bit inputs.
+      {V_ASN1_BIT_STRING, {0xf0}, ASN1_STRING_FLAG_BITS_LEFT | 4, false},
+      {V_ASN1_BIT_STRING, {0xf0}, 0, true},        // 4 trailing zeros dropped.
+      {V_ASN1_BIT_STRING, {0xf0, 0x00}, 0, true},  // 12 trailing zeros dropped.
+      // 5-bit inputs.
+      {V_ASN1_BIT_STRING, {0x00}, ASN1_STRING_FLAG_BITS_LEFT | 3, false},
+      {V_ASN1_BIT_STRING, {0xf0}, ASN1_STRING_FLAG_BITS_LEFT | 3, false},
+      {V_ASN1_BIT_STRING, {0xf8}, ASN1_STRING_FLAG_BITS_LEFT | 3, false},
+      // 6-bit inputs.
+      {V_ASN1_BIT_STRING, {0x00}, ASN1_STRING_FLAG_BITS_LEFT | 2, false},
+      {V_ASN1_BIT_STRING, {0xf0}, ASN1_STRING_FLAG_BITS_LEFT | 2, false},
+      {V_ASN1_BIT_STRING, {0xfc}, ASN1_STRING_FLAG_BITS_LEFT | 2, false},
+      // 7-bit inputs.
+      {V_ASN1_BIT_STRING, {0x00}, ASN1_STRING_FLAG_BITS_LEFT | 1, false},
+      {V_ASN1_BIT_STRING, {0xf0}, ASN1_STRING_FLAG_BITS_LEFT | 1, false},
+      {V_ASN1_BIT_STRING, {0xfe}, ASN1_STRING_FLAG_BITS_LEFT | 1, false},
+
+      // 8-bit inputs.
+      {V_ASN1_BIT_STRING, {0x00}, ASN1_STRING_FLAG_BITS_LEFT | 0, false},
+      {V_ASN1_OCTET_STRING, {0x00}, 0, false},
+      {V_ASN1_UTF8STRING, {0x00}, 0, false},
+
+      {V_ASN1_BIT_STRING, {0x80}, ASN1_STRING_FLAG_BITS_LEFT | 0, false},
+      {V_ASN1_OCTET_STRING, {0x80}, 0, false},
+      {V_ASN1_UTF8STRING, {0x80}, 0, false},
+
+      {V_ASN1_BIT_STRING, {0xff}, ASN1_STRING_FLAG_BITS_LEFT | 0, false},
+      {V_ASN1_BIT_STRING, {0xff}, 0, true},  // No trailing zeros to drop.
+      {V_ASN1_OCTET_STRING, {0xff}, 0, false},
+      {V_ASN1_UTF8STRING, {0xff}, 0, false},
+
+      // Bytes are compared lexicographically.
+      {V_ASN1_BIT_STRING, {0x00, 0x00}, ASN1_STRING_FLAG_BITS_LEFT | 0, false},
+      {V_ASN1_OCTET_STRING, {0x00, 0x00}, 0, false},
+      {V_ASN1_UTF8STRING, {0x00, 0x00}, 0, false},
+
+      {V_ASN1_BIT_STRING, {0x00, 0xff}, ASN1_STRING_FLAG_BITS_LEFT | 0, false},
+      {V_ASN1_OCTET_STRING, {0x00, 0xff}, 0, false},
+      {V_ASN1_UTF8STRING, {0x00, 0xff}, 0, false},
+
+      {V_ASN1_BIT_STRING, {0xff, 0x00}, ASN1_STRING_FLAG_BITS_LEFT | 0, false},
+      {V_ASN1_OCTET_STRING, {0xff, 0x00}, 0, false},
+      {V_ASN1_UTF8STRING, {0xff, 0x00}, 0, false},
+  };
+  std::vector<bssl::UniquePtr<ASN1_STRING>> strs;
+  strs.reserve(OPENSSL_ARRAY_SIZE(kInputs));
+  for (const auto &input : kInputs) {
+    strs.emplace_back(ASN1_STRING_type_new(input.type));
+    ASSERT_TRUE(strs.back());
+    ASSERT_TRUE(ASN1_STRING_set(strs.back().get(), input.data.data(),
+                                input.data.size()));
+    strs.back()->flags = input.flags;
+  }
+
+  for (size_t i = 0; i < strs.size(); i++) {
+    SCOPED_TRACE(i);
+    bool expect_equal = true;
+    for (size_t j = i; j < strs.size(); j++) {
+      SCOPED_TRACE(j);
+      if (j > i && !kInputs[j].equals_previous) {
+        expect_equal = false;
+      }
+
+      const int cmp_i_j = ASN1_STRING_cmp(strs[i].get(), strs[j].get());
+      const int cmp_j_i = ASN1_STRING_cmp(strs[j].get(), strs[i].get());
+      if (expect_equal) {
+        EXPECT_EQ(cmp_i_j, 0);
+        EXPECT_EQ(cmp_j_i, 0);
+      } else if (i < j) {
+        EXPECT_LT(cmp_i_j, 0);
+        EXPECT_GT(cmp_j_i, 0);
+      } else {
+        EXPECT_GT(cmp_i_j, 0);
+        EXPECT_LT(cmp_j_i, 0);
+      }
+    }
+  }
+}
+
 // The ASN.1 macros do not work on Windows shared library builds, where usage of
 // |OPENSSL_EXPORT| is a bit stricter.
 #if !defined(OPENSSL_WINDOWS) || !defined(BORINGSSL_SHARED_LIBRARY)
diff --git a/crypto/asn1/internal.h b/crypto/asn1/internal.h
index dcb6fcf..5731499 100644
--- a/crypto/asn1/internal.h
+++ b/crypto/asn1/internal.h
@@ -175,6 +175,14 @@
  * ASN.1 PrintableString, and zero otherwise. */
 int asn1_is_printable(uint32_t value);
 
+/* asn1_bit_string_length returns the number of bytes in |str| and sets
+ * |*out_padding_bits| to the number of padding bits.
+ *
+ * This function should be used instead of |ASN1_STRING_length| to correctly
+ * handle the non-|ASN1_STRING_FLAG_BITS_LEFT| case. */
+int asn1_bit_string_length(const ASN1_BIT_STRING *str,
+                           uint8_t *out_padding_bits);
+
 typedef struct {
   int nid;
   long minsize;
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index 0f12a5b..8e8fa9d 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -503,13 +503,8 @@
 // suitable for sorting, callers should not rely on the exact order when |a|
 // and |b| are different types.
 //
-// If |a| or |b| are BIT STRINGs, this function does not compare the
-// |ASN1_STRING_FLAG_BITS_LEFT| flags. Additionally, if |a| and |b| are
-// INTEGERs, this comparison does not order the values numerically. For a
-// numerical comparison, use |ASN1_INTEGER_cmp|.
-//
-// TODO(https://crbug.com/boringssl/446): The BIT STRING comparison seems like a
-// bug. Fix it?
+// Note that, if |a| and |b| are INTEGERs, this comparison does not order the
+// values numerically. For a numerical comparison, use |ASN1_INTEGER_cmp|.
 OPENSSL_EXPORT int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b);
 
 // ASN1_STRING_set sets the contents of |str| to a copy of |len| bytes from
@@ -735,7 +730,7 @@
 // {0x80} and flags of ASN1_STRING_FLAG_BITS_LEFT | 6. If
 // |ASN1_STRING_FLAG_BITS_LEFT| is unset, trailing zero bits are implicitly
 // removed. Callers should not rely this representation when constructing bit
-// strings.
+// strings. The padding bits in the |ASN1_STRING| data must be zero.
 
 // ASN1_BIT_STRING_new calls |ASN1_STRING_type_new| with |V_ASN1_BIT_STRING|.
 OPENSSL_EXPORT ASN1_BIT_STRING *ASN1_BIT_STRING_new(void);