external/boringssl: Sync to 27bc0f26c8d132df04f5b0b173aefeb8aaa13c33.

This includes the following changes:

https://boringssl.googlesource.com/boringssl/+log/ba8f1864c15ec938ce0851f416663511c89f454a..27bc0f26c8d132df04f5b0b173aefeb8aaa13c33

Test: BoringSSL CTS Presubmits
Change-Id: Id63dac9fa22a3b41609f55bfe48d2cfaa53b25c6
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 338f212..eb8717a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -238,6 +238,24 @@
   set(OPENSSL_NO_ASM "1")
 endif()
 
+if(CFI)
+  if(NOT CLANG)
+    message(FATAL_ERROR "Cannot enable CFI unless using Clang")
+  endif()
+
+  # TODO(crbug.com/785442): Remove -fsanitize-cfi-icall-generalize-pointers.
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=cfi -fno-sanitize-trap=cfi -fsanitize-cfi-icall-generalize-pointers -flto")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=cfi -fno-sanitize-trap=cfi -fsanitize-cfi-icall-generalize-pointers -flto")
+  # We use Chromium's copy of clang, which requires -fuse-ld=lld if building
+  # with -flto. That, in turn, can't handle -ggdb.
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
+  string(REPLACE "-ggdb" "-g" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
+  string(REPLACE "-ggdb" "-g" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  # -flto causes object files to contain LLVM bitcode. Mixing those with
+  # assembly output in the same static library breaks the linker.
+  set(OPENSSL_NO_ASM "1")
+endif()
+
 if (GCOV)
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage")
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
diff --git a/src/crypto/bytestring/ber.c b/src/crypto/bytestring/ber.c
index 4dc94f6..bb5e17c 100644
--- a/src/crypto/bytestring/ber.c
+++ b/src/crypto/bytestring/ber.c
@@ -29,10 +29,7 @@
 // is_string_type returns one if |tag| is a string type and zero otherwise. It
 // ignores the constructed bit.
 static int is_string_type(unsigned tag) {
-  if ((tag & 0xc0) != 0) {
-    return 0;
-  }
-  switch (tag & 0x1f) {
+  switch (tag & ~CBS_ASN1_CONSTRUCTED) {
     case CBS_ASN1_BITSTRING:
     case CBS_ASN1_OCTETSTRING:
     case CBS_ASN1_UTF8STRING:
diff --git a/src/crypto/bytestring/bytestring_test.cc b/src/crypto/bytestring/bytestring_test.cc
index e8e14d9..7e3d453 100644
--- a/src/crypto/bytestring/bytestring_test.cc
+++ b/src/crypto/bytestring/bytestring_test.cc
@@ -123,27 +123,27 @@
   uint64_t value;
 
   CBS_init(&data, kData1, sizeof(kData1));
-  EXPECT_FALSE(CBS_peek_asn1_tag(&data, 0x1));
-  EXPECT_TRUE(CBS_peek_asn1_tag(&data, 0x30));
+  EXPECT_FALSE(CBS_peek_asn1_tag(&data, CBS_ASN1_BOOLEAN));
+  EXPECT_TRUE(CBS_peek_asn1_tag(&data, CBS_ASN1_SEQUENCE));
 
-  ASSERT_TRUE(CBS_get_asn1(&data, &contents, 0x30));
+  ASSERT_TRUE(CBS_get_asn1(&data, &contents, CBS_ASN1_SEQUENCE));
   EXPECT_EQ(Bytes("\x01\x02"), Bytes(CBS_data(&contents), CBS_len(&contents)));
 
   CBS_init(&data, kData2, sizeof(kData2));
   // data is truncated
-  EXPECT_FALSE(CBS_get_asn1(&data, &contents, 0x30));
+  EXPECT_FALSE(CBS_get_asn1(&data, &contents, CBS_ASN1_SEQUENCE));
 
   CBS_init(&data, kData3, sizeof(kData3));
   // zero byte length of length
-  EXPECT_FALSE(CBS_get_asn1(&data, &contents, 0x30));
+  EXPECT_FALSE(CBS_get_asn1(&data, &contents, CBS_ASN1_SEQUENCE));
 
   CBS_init(&data, kData4, sizeof(kData4));
   // long form mistakenly used.
-  EXPECT_FALSE(CBS_get_asn1(&data, &contents, 0x30));
+  EXPECT_FALSE(CBS_get_asn1(&data, &contents, CBS_ASN1_SEQUENCE));
 
   CBS_init(&data, kData5, sizeof(kData5));
   // length takes too many bytes.
-  EXPECT_FALSE(CBS_get_asn1(&data, &contents, 0x30));
+  EXPECT_FALSE(CBS_get_asn1(&data, &contents, CBS_ASN1_SEQUENCE));
 
   CBS_init(&data, kData1, sizeof(kData1));
   // wrong tag.
@@ -151,56 +151,72 @@
 
   CBS_init(&data, NULL, 0);
   // peek at empty data.
-  EXPECT_FALSE(CBS_peek_asn1_tag(&data, 0x30));
+  EXPECT_FALSE(CBS_peek_asn1_tag(&data, CBS_ASN1_SEQUENCE));
 
   CBS_init(&data, NULL, 0);
   // optional elements at empty data.
-  ASSERT_TRUE(CBS_get_optional_asn1(&data, &contents, &present, 0xa0));
+  ASSERT_TRUE(CBS_get_optional_asn1(
+      &data, &contents, &present,
+      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0));
   EXPECT_FALSE(present);
-  ASSERT_TRUE(
-      CBS_get_optional_asn1_octet_string(&data, &contents, &present, 0xa0));
+  ASSERT_TRUE(CBS_get_optional_asn1_octet_string(
+      &data, &contents, &present,
+      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0));
   EXPECT_FALSE(present);
   EXPECT_EQ(0u, CBS_len(&contents));
-  ASSERT_TRUE(CBS_get_optional_asn1_octet_string(&data, &contents, NULL, 0xa0));
+  ASSERT_TRUE(CBS_get_optional_asn1_octet_string(
+      &data, &contents, NULL,
+      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0));
   EXPECT_EQ(0u, CBS_len(&contents));
-  ASSERT_TRUE(CBS_get_optional_asn1_uint64(&data, &value, 0xa0, 42));
+  ASSERT_TRUE(CBS_get_optional_asn1_uint64(
+      &data, &value, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, 42));
   EXPECT_EQ(42u, value);
 
   CBS_init(&data, kData6, sizeof(kData6));
   // optional element.
-  ASSERT_TRUE(CBS_get_optional_asn1(&data, &contents, &present, 0xa0));
+  ASSERT_TRUE(CBS_get_optional_asn1(
+      &data, &contents, &present,
+      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0));
   EXPECT_FALSE(present);
-  ASSERT_TRUE(CBS_get_optional_asn1(&data, &contents, &present, 0xa1));
+  ASSERT_TRUE(CBS_get_optional_asn1(
+      &data, &contents, &present,
+      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1));
   EXPECT_TRUE(present);
   EXPECT_EQ(Bytes("\x04\x01\x01"),
             Bytes(CBS_data(&contents), CBS_len(&contents)));
 
   CBS_init(&data, kData6, sizeof(kData6));
   // optional octet string.
-  ASSERT_TRUE(
-      CBS_get_optional_asn1_octet_string(&data, &contents, &present, 0xa0));
+  ASSERT_TRUE(CBS_get_optional_asn1_octet_string(
+      &data, &contents, &present,
+      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0));
   EXPECT_FALSE(present);
   EXPECT_EQ(0u, CBS_len(&contents));
-  ASSERT_TRUE(
-      CBS_get_optional_asn1_octet_string(&data, &contents, &present, 0xa1));
+  ASSERT_TRUE(CBS_get_optional_asn1_octet_string(
+      &data, &contents, &present,
+      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1));
   EXPECT_TRUE(present);
   EXPECT_EQ(Bytes("\x01"), Bytes(CBS_data(&contents), CBS_len(&contents)));
 
   CBS_init(&data, kData7, sizeof(kData7));
   // invalid optional octet string.
-  EXPECT_FALSE(
-      CBS_get_optional_asn1_octet_string(&data, &contents, &present, 0xa1));
+  EXPECT_FALSE(CBS_get_optional_asn1_octet_string(
+      &data, &contents, &present,
+      CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1));
 
   CBS_init(&data, kData8, sizeof(kData8));
   // optional integer.
-  ASSERT_TRUE(CBS_get_optional_asn1_uint64(&data, &value, 0xa0, 42));
+  ASSERT_TRUE(CBS_get_optional_asn1_uint64(
+      &data, &value, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, 42));
   EXPECT_EQ(42u, value);
-  ASSERT_TRUE(CBS_get_optional_asn1_uint64(&data, &value, 0xa1, 42));
+  ASSERT_TRUE(CBS_get_optional_asn1_uint64(
+      &data, &value, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1, 42));
   EXPECT_EQ(1u, value);
 
   CBS_init(&data, kData9, sizeof(kData9));
   // invalid optional integer.
-  EXPECT_FALSE(CBS_get_optional_asn1_uint64(&data, &value, 0xa1, 42));
+  EXPECT_FALSE(CBS_get_optional_asn1_uint64(
+      &data, &value, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1, 42));
 
   unsigned tag;
   CBS_init(&data, kData1, sizeof(kData1));
@@ -217,6 +233,54 @@
             Bytes(CBS_data(&contents), CBS_len(&contents)));
 }
 
+TEST(CBSTest, ParseASN1Tag) {
+  const struct {
+    bool ok;
+    unsigned tag;
+    std::vector<uint8_t> in;
+  } kTests[] = {
+      {true, CBS_ASN1_SEQUENCE, {0x30, 0}},
+      {true, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 4, {0xa4, 0}},
+      {true, CBS_ASN1_APPLICATION | 30, {0x5e, 0}},
+      {true, CBS_ASN1_APPLICATION | 31, {0x5f, 0x1f, 0}},
+      {true, CBS_ASN1_APPLICATION | 32, {0x5f, 0x20, 0}},
+      {true,
+       CBS_ASN1_PRIVATE | CBS_ASN1_CONSTRUCTED | 0x1fffffff,
+       {0xff, 0x81, 0xff, 0xff, 0xff, 0x7f, 0}},
+      // Tag number fits in unsigned but not |CBS_ASN1_TAG_NUMBER_MASK|.
+      {false, 0, {0xff, 0x82, 0xff, 0xff, 0xff, 0x7f, 0}},
+      // Tag number does not fit in unsigned.
+      {false, 0, {0xff, 0x90, 0x80, 0x80, 0x80, 0, 0}},
+      // Tag number is not minimally-encoded
+      {false, 0, {0x5f, 0x80, 0x1f, 0}},
+      // Tag number should have used short form.
+      {false, 0, {0x5f, 0x80, 0x1e, 0}},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(Bytes(t.in));
+    unsigned tag;
+    CBS cbs, child;
+    CBS_init(&cbs, t.in.data(), t.in.size());
+    ASSERT_EQ(t.ok, !!CBS_get_any_asn1(&cbs, &child, &tag));
+    if (t.ok) {
+      EXPECT_EQ(t.tag, tag);
+      EXPECT_EQ(0u, CBS_len(&child));
+      EXPECT_EQ(0u, CBS_len(&cbs));
+
+      CBS_init(&cbs, t.in.data(), t.in.size());
+      EXPECT_TRUE(CBS_peek_asn1_tag(&cbs, t.tag));
+      EXPECT_FALSE(CBS_peek_asn1_tag(&cbs, t.tag + 1));
+
+      EXPECT_TRUE(CBS_get_asn1(&cbs, &child, t.tag));
+      EXPECT_EQ(0u, CBS_len(&child));
+      EXPECT_EQ(0u, CBS_len(&cbs));
+
+      CBS_init(&cbs, t.in.data(), t.in.size());
+      EXPECT_FALSE(CBS_get_asn1(&cbs, &child, t.tag + 1));
+    }
+  }
+}
+
 TEST(CBSTest, GetOptionalASN1Bool) {
   static const uint8_t kTrue[] = {0x0a, 3, CBS_ASN1_BOOLEAN, 1, 0xff};
   static const uint8_t kFalse[] = {0x0a, 3, CBS_ASN1_BOOLEAN, 1, 0x00};
@@ -416,15 +480,42 @@
 }
 
 TEST(CBBTest, ASN1) {
-  static const uint8_t kExpected[] = {0x30, 3, 1, 2, 3};
+  static const uint8_t kExpected[] = {
+      // SEQUENCE { 1 2 3 }
+      0x30, 3, 1, 2, 3,
+      // [4 CONSTRUCTED] { 4 5 6 }
+      0xa4, 3, 4, 5, 6,
+      // [APPLICATION 30 PRIMITIVE] { 7 8 9 }
+      0x5e, 3, 7, 8, 9,
+      // [APPLICATION 31 PRIMITIVE] { 10 11 12 }
+      0x5f, 0x1f, 3, 10, 11, 12,
+      // [PRIVATE 2^29-1 CONSTRUCTED] { 13 14 15 }
+      0xff, 0x81, 0xff, 0xff, 0xff, 0x7f, 3, 13, 14, 15,
+  };
   uint8_t *buf;
   size_t buf_len;
   bssl::ScopedCBB cbb;
   CBB contents, inner_contents;
 
   ASSERT_TRUE(CBB_init(cbb.get(), 0));
-  ASSERT_TRUE(CBB_add_asn1(cbb.get(), &contents, 0x30));
+  ASSERT_TRUE(CBB_add_asn1(cbb.get(), &contents, CBS_ASN1_SEQUENCE));
   ASSERT_TRUE(CBB_add_bytes(&contents, (const uint8_t *)"\x01\x02\x03", 3));
+  ASSERT_TRUE(
+      CBB_add_asn1(cbb.get(), &contents,
+                   CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 4));
+  ASSERT_TRUE(CBB_add_bytes(&contents, (const uint8_t *)"\x04\x05\x06", 3));
+  ASSERT_TRUE(
+      CBB_add_asn1(cbb.get(), &contents,
+                   CBS_ASN1_APPLICATION | 30));
+  ASSERT_TRUE(CBB_add_bytes(&contents, (const uint8_t *)"\x07\x08\x09", 3));
+  ASSERT_TRUE(
+      CBB_add_asn1(cbb.get(), &contents,
+                   CBS_ASN1_APPLICATION | 31));
+  ASSERT_TRUE(CBB_add_bytes(&contents, (const uint8_t *)"\x0a\x0b\x0c", 3));
+  ASSERT_TRUE(
+      CBB_add_asn1(cbb.get(), &contents,
+                   CBS_ASN1_PRIVATE | CBS_ASN1_CONSTRUCTED | 0x1fffffff));
+  ASSERT_TRUE(CBB_add_bytes(&contents, (const uint8_t *)"\x0d\x0e\x0f", 3));
   ASSERT_TRUE(CBB_finish(cbb.get(), &buf, &buf_len));
   bssl::UniquePtr<uint8_t> scoper(buf);
 
@@ -432,7 +523,7 @@
 
   std::vector<uint8_t> test_data(100000, 0x42);
   ASSERT_TRUE(CBB_init(cbb.get(), 0));
-  ASSERT_TRUE(CBB_add_asn1(cbb.get(), &contents, 0x30));
+  ASSERT_TRUE(CBB_add_asn1(cbb.get(), &contents, CBS_ASN1_SEQUENCE));
   ASSERT_TRUE(CBB_add_bytes(&contents, test_data.data(), 130));
   ASSERT_TRUE(CBB_finish(cbb.get(), &buf, &buf_len));
   scoper.reset(buf);
@@ -442,7 +533,7 @@
   EXPECT_EQ(Bytes(test_data.data(), 130), Bytes(buf + 3, 130));
 
   ASSERT_TRUE(CBB_init(cbb.get(), 0));
-  ASSERT_TRUE(CBB_add_asn1(cbb.get(), &contents, 0x30));
+  ASSERT_TRUE(CBB_add_asn1(cbb.get(), &contents, CBS_ASN1_SEQUENCE));
   ASSERT_TRUE(CBB_add_bytes(&contents, test_data.data(), 1000));
   ASSERT_TRUE(CBB_finish(cbb.get(), &buf, &buf_len));
   scoper.reset(buf);
@@ -452,8 +543,8 @@
   EXPECT_EQ(Bytes(test_data.data(), 1000), Bytes(buf + 4, 1000));
 
   ASSERT_TRUE(CBB_init(cbb.get(), 0));
-  ASSERT_TRUE(CBB_add_asn1(cbb.get(), &contents, 0x30));
-  ASSERT_TRUE(CBB_add_asn1(&contents, &inner_contents, 0x30));
+  ASSERT_TRUE(CBB_add_asn1(cbb.get(), &contents, CBS_ASN1_SEQUENCE));
+  ASSERT_TRUE(CBB_add_asn1(&contents, &inner_contents, CBS_ASN1_SEQUENCE));
   ASSERT_TRUE(CBB_add_bytes(&inner_contents, test_data.data(), 100000));
   ASSERT_TRUE(CBB_finish(cbb.get(), &buf, &buf_len));
   scoper.reset(buf);
@@ -490,6 +581,12 @@
   static const uint8_t kIndefBER[] = {0x30, 0x80, 0x01, 0x01, 0x02, 0x00, 0x00};
   static const uint8_t kIndefDER[] = {0x30, 0x03, 0x01, 0x01, 0x02};
 
+  // kIndefBER2 contains a constructed [APPLICATION 31] with an indefinite
+  // length.
+  static const uint8_t kIndefBER2[] = {0x7f, 0x1f, 0x80, 0x01,
+                                       0x01, 0x02, 0x00, 0x00};
+  static const uint8_t kIndefDER2[] = {0x7f, 0x1f, 0x03, 0x01, 0x01, 0x02};
+
   // kOctetStringBER contains an indefinite length OCTET STRING with two parts.
   // These parts need to be concatenated in DER form.
   static const uint8_t kOctetStringBER[] = {0x24, 0x80, 0x04, 0x02, 0,    1,
@@ -534,6 +631,8 @@
                    sizeof(kSimpleBER));
   ExpectBerConvert("kIndefBER", kIndefDER, sizeof(kIndefDER), kIndefBER,
                    sizeof(kIndefBER));
+  ExpectBerConvert("kIndefBER2", kIndefDER2, sizeof(kIndefDER2), kIndefBER2,
+                   sizeof(kIndefBER2));
   ExpectBerConvert("kOctetStringBER", kOctetStringDER, sizeof(kOctetStringDER),
                    kOctetStringBER, sizeof(kOctetStringBER));
   ExpectBerConvert("kNSSBER", kNSSDER, sizeof(kNSSDER), kNSSBER,
diff --git a/src/crypto/bytestring/cbb.c b/src/crypto/bytestring/cbb.c
index 5576fa9..b1afe7d 100644
--- a/src/crypto/bytestring/cbb.c
+++ b/src/crypto/bytestring/cbb.c
@@ -329,17 +329,36 @@
 }
 
 int CBB_add_asn1(CBB *cbb, CBB *out_contents, unsigned tag) {
-  if (tag > 0xff ||
-      (tag & 0x1f) == 0x1f) {
-    // Long form identifier octets are not supported. Further, all current valid
-    // tag serializations are 8 bits.
-    cbb->base->error = 1;
+  if (!CBB_flush(cbb)) {
     return 0;
   }
 
-  if (!CBB_flush(cbb) ||
-      // |tag|'s representation matches the DER encoding.
-      !CBB_add_u8(cbb, (uint8_t)tag)) {
+  // Split the tag into leading bits and tag number.
+  uint8_t tag_bits = (tag >> CBS_ASN1_TAG_SHIFT) & 0xe0;
+  unsigned tag_number = tag & CBS_ASN1_TAG_NUMBER_MASK;
+  if (tag_number >= 0x1f) {
+    // Set all the bits in the tag number to signal high tag number form.
+    if (!CBB_add_u8(cbb, tag_bits | 0x1f)) {
+      return 0;
+    }
+
+    unsigned len_len = 0;
+    unsigned copy = tag_number;
+    while (copy > 0) {
+      len_len++;
+      copy >>= 7;
+    }
+    for (unsigned i = len_len - 1; i < len_len; i--) {
+      uint8_t byte = (tag_number >> (7 * i)) & 0x7f;
+      if (i != 0) {
+        // The high bit denotes whether there is more data.
+        byte |= 0x80;
+      }
+      if (!CBB_add_u8(cbb, byte)) {
+        return 0;
+      }
+    }
+  } else if (!CBB_add_u8(cbb, tag_bits | tag_number)) {
     return 0;
   }
 
diff --git a/src/crypto/bytestring/cbs.c b/src/crypto/bytestring/cbs.c
index ec495d2..d96371c 100644
--- a/src/crypto/bytestring/cbs.c
+++ b/src/crypto/bytestring/cbs.c
@@ -175,18 +175,9 @@
   return cbs_get_length_prefixed(cbs, out, 3);
 }
 
-static int cbs_get_any_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
-                                    size_t *out_header_len, int ber_ok) {
-  uint8_t tag, length_byte;
-  CBS header = *cbs;
-  CBS throwaway;
-
-  if (out == NULL) {
-    out = &throwaway;
-  }
-
-  if (!CBS_get_u8(&header, &tag) ||
-      !CBS_get_u8(&header, &length_byte)) {
+static int parse_asn1_tag(CBS *cbs, unsigned *out) {
+  uint8_t tag_byte;
+  if (!CBS_get_u8(cbs, &tag_byte)) {
     return 0;
   }
 
@@ -197,22 +188,70 @@
   // allotted bits), then the tag is more than one byte long and the
   // continuation bytes contain the tag number. This parser only supports tag
   // numbers less than 31 (and thus single-byte tags).
-  if ((tag & 0x1f) == 0x1f) {
-    return 0;
+  unsigned tag = ((unsigned)tag_byte & 0xe0) << CBS_ASN1_TAG_SHIFT;
+  unsigned tag_number = tag_byte & 0x1f;
+  if (tag_number == 0x1f) {
+    tag_number = 0;
+    for (;;) {
+      if (!CBS_get_u8(cbs, &tag_byte) ||
+          ((tag_number << 7) >> 7) != tag_number) {
+        return 0;
+      }
+      tag_number = (tag_number << 7) | (tag_byte & 0x7f);
+      // The tag must be represented in the minimal number of bytes.
+      if (tag_number == 0) {
+        return 0;
+      }
+      if ((tag_byte & 0x80) == 0) {
+        break;
+      }
+    }
+    if (// Check the tag number is within our supported bounds.
+        tag_number > CBS_ASN1_TAG_NUMBER_MASK ||
+        // Small tag numbers should have used low tag number form.
+        tag_number < 0x1f) {
+      return 0;
+    }
   }
 
+  tag |= tag_number;
+
+  *out = tag;
+  return 1;
+}
+
+static int cbs_get_any_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
+                                    size_t *out_header_len, int ber_ok) {
+  CBS header = *cbs;
+  CBS throwaway;
+
+  if (out == NULL) {
+    out = &throwaway;
+  }
+
+  unsigned tag;
+  if (!parse_asn1_tag(&header, &tag)) {
+    return 0;
+  }
   if (out_tag != NULL) {
     *out_tag = tag;
   }
 
+  uint8_t length_byte;
+  if (!CBS_get_u8(&header, &length_byte)) {
+    return 0;
+  }
+
+  size_t header_len = CBS_len(cbs) - CBS_len(&header);
+
   size_t len;
   // The format for the length encoding is specified in ITU-T X.690 section
   // 8.1.3.
   if ((length_byte & 0x80) == 0) {
     // Short form length.
-    len = ((size_t) length_byte) + 2;
+    len = ((size_t) length_byte) + header_len;
     if (out_header_len != NULL) {
-      *out_header_len = 2;
+      *out_header_len = header_len;
     }
   } else {
     // The high bit indicate that this is the long form, while the next 7 bits
@@ -224,9 +263,9 @@
     if (ber_ok && (tag & CBS_ASN1_CONSTRUCTED) != 0 && num_bytes == 0) {
       // indefinite length
       if (out_header_len != NULL) {
-        *out_header_len = 2;
+        *out_header_len = header_len;
       }
-      return CBS_get_bytes(cbs, out, 2);
+      return CBS_get_bytes(cbs, out, header_len);
     }
 
     // ITU-T X.690 clause 8.1.3.5.c specifies that the value 0xff shall not be
@@ -249,13 +288,13 @@
       return 0;
     }
     len = len32;
-    if (len + 2 + num_bytes < len) {
+    if (len + header_len + num_bytes < len) {
       // Overflow.
       return 0;
     }
-    len += 2 + num_bytes;
+    len += header_len + num_bytes;
     if (out_header_len != NULL) {
-      *out_header_len = 2 + num_bytes;
+      *out_header_len = header_len + num_bytes;
     }
   }
 
@@ -323,7 +362,10 @@
   if (CBS_len(cbs) < 1) {
     return 0;
   }
-  return CBS_data(cbs)[0] == tag_value;
+
+  CBS copy = *cbs;
+  unsigned actual_tag;
+  return parse_asn1_tag(&copy, &actual_tag) && tag_value == actual_tag;
 }
 
 int CBS_get_asn1_uint64(CBS *cbs, uint64_t *out) {
diff --git a/src/crypto/dsa/dsa.c b/src/crypto/dsa/dsa.c
index 61f0c6c..f3d4f85 100644
--- a/src/crypto/dsa/dsa.c
+++ b/src/crypto/dsa/dsa.c
@@ -82,6 +82,9 @@
 // Rabin-Miller
 #define DSS_prime_checks 50
 
+static int dsa_sign_setup(const DSA *dsa, BN_CTX *ctx_in, BIGNUM **out_kinv,
+                          BIGNUM **out_r);
+
 static CRYPTO_EX_DATA_CLASS g_ex_data_class = CRYPTO_EX_DATA_CLASS_INIT;
 
 DSA *DSA_new(void) {
@@ -117,8 +120,6 @@
   BN_clear_free(dsa->g);
   BN_clear_free(dsa->pub_key);
   BN_clear_free(dsa->priv_key);
-  BN_clear_free(dsa->kinv);
-  BN_clear_free(dsa->r);
   BN_MONT_CTX_free(dsa->method_mont_p);
   BN_MONT_CTX_free(dsa->method_mont_q);
   CRYPTO_MUTEX_cleanup(&dsa->method_mont_lock);
@@ -544,14 +545,13 @@
   OPENSSL_free(sig);
 }
 
-DSA_SIG *DSA_do_sign(const uint8_t *digest, size_t digest_len, DSA *dsa) {
+DSA_SIG *DSA_do_sign(const uint8_t *digest, size_t digest_len, const DSA *dsa) {
   BIGNUM *kinv = NULL, *r = NULL, *s = NULL;
   BIGNUM m;
   BIGNUM xr;
   BN_CTX *ctx = NULL;
   int reason = ERR_R_BN_LIB;
   DSA_SIG *ret = NULL;
-  int noredo = 0;
 
   BN_init(&m);
   BN_init(&xr);
@@ -571,16 +571,8 @@
   }
 
 redo:
-  if (dsa->kinv == NULL || dsa->r == NULL) {
-    if (!DSA_sign_setup(dsa, ctx, &kinv, &r)) {
-      goto err;
-    }
-  } else {
-    kinv = dsa->kinv;
-    dsa->kinv = NULL;
-    r = dsa->r;
-    dsa->r = NULL;
-    noredo = 1;
+  if (!dsa_sign_setup(dsa, ctx, &kinv, &r)) {
+    goto err;
   }
 
   if (digest_len > BN_num_bytes(dsa->q)) {
@@ -613,10 +605,6 @@
   // Redo if r or s is zero as required by FIPS 186-3: this is
   // very unlikely.
   if (BN_is_zero(r) || BN_is_zero(s)) {
-    if (noredo) {
-      reason = DSA_R_NEED_NEW_SETUP_VALUES;
-      goto err;
-    }
     goto redo;
   }
   ret = DSA_SIG_new();
@@ -758,7 +746,7 @@
 }
 
 int DSA_sign(int type, const uint8_t *digest, size_t digest_len,
-             uint8_t *out_sig, unsigned int *out_siglen, DSA *dsa) {
+             uint8_t *out_sig, unsigned int *out_siglen, const DSA *dsa) {
   DSA_SIG *s;
 
   s = DSA_do_sign(digest, digest_len, dsa);
@@ -848,8 +836,8 @@
   return ret;
 }
 
-int DSA_sign_setup(const DSA *dsa, BN_CTX *ctx_in, BIGNUM **out_kinv,
-                   BIGNUM **out_r) {
+static int dsa_sign_setup(const DSA *dsa, BN_CTX *ctx_in, BIGNUM **out_kinv,
+                          BIGNUM **out_r) {
   BN_CTX *ctx;
   BIGNUM k, kq, *kinv = NULL, *r = NULL;
   int ret = 0;
diff --git a/src/crypto/ec_extra/ec_asn1.c b/src/crypto/ec_extra/ec_asn1.c
index dc710a8..c125af2 100644
--- a/src/crypto/ec_extra/ec_asn1.c
+++ b/src/crypto/ec_extra/ec_asn1.c
@@ -67,9 +67,9 @@
 #include "../internal.h"
 
 
-static const uint8_t kParametersTag =
+static const unsigned kParametersTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0;
-static const uint8_t kPublicKeyTag =
+static const unsigned kPublicKeyTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1;
 
 EC_KEY *EC_KEY_parse_private_key(CBS *cbs, const EC_GROUP *group) {
diff --git a/src/crypto/ecdsa_extra/ecdsa_asn1.c b/src/crypto/ecdsa_extra/ecdsa_asn1.c
index 8d0bc41..fbf4cca 100644
--- a/src/crypto/ecdsa_extra/ecdsa_asn1.c
+++ b/src/crypto/ecdsa_extra/ecdsa_asn1.c
@@ -73,13 +73,6 @@
                                    (EC_KEY*) eckey /* cast away const */);
   }
 
-  return ECDSA_sign_ex(type, digest, digest_len, sig, sig_len, NULL, NULL,
-                       eckey);
-}
-
-int ECDSA_sign_ex(int type, const uint8_t *digest, size_t digest_len,
-                  uint8_t *sig, unsigned int *sig_len, const BIGNUM *kinv,
-                  const BIGNUM *r, const EC_KEY *eckey) {
   int ret = 0;
   ECDSA_SIG *s = NULL;
 
@@ -89,7 +82,7 @@
     goto err;
   }
 
-  s = ECDSA_do_sign_ex(digest, digest_len, kinv, r, eckey);
+  s = ECDSA_do_sign(digest, digest_len, eckey);
   if (s == NULL) {
     *sig_len = 0;
     goto err;
diff --git a/src/crypto/err/ec.errordata b/src/crypto/err/ec.errordata
index fae9480..de8ee6c 100644
--- a/src/crypto/err/ec.errordata
+++ b/src/crypto/err/ec.errordata
@@ -17,6 +17,7 @@
 EC,111,INVALID_FORM
 EC,112,INVALID_GROUP_ORDER
 EC,113,INVALID_PRIVATE_KEY
+EC,133,INVALID_SCALAR
 EC,114,MISSING_PARAMETERS
 EC,115,MISSING_PRIVATE_KEY
 EC,116,NON_NAMED_CURVE
diff --git a/src/crypto/err/ssl.errordata b/src/crypto/err/ssl.errordata
index a528874..dec7347 100644
--- a/src/crypto/err/ssl.errordata
+++ b/src/crypto/err/ssl.errordata
@@ -53,6 +53,7 @@
 SSL,257,DUPLICATE_EXTENSION
 SSL,264,DUPLICATE_KEY_SHARE
 SSL,144,ECC_CERT_NOT_FOR_SIGNING
+SSL,282,EMPTY_HELLO_RETRY_REQUEST
 SSL,145,EMS_STATE_INCONSISTENT
 SSL,146,ENCRYPTED_LENGTH_TOO_LONG
 SSL,147,ERROR_ADDING_EXTENSION
diff --git a/src/crypto/evp/p_rsa_asn1.c b/src/crypto/evp/p_rsa_asn1.c
index a188538..85f6fc8 100644
--- a/src/crypto/evp/p_rsa_asn1.c
+++ b/src/crypto/evp/p_rsa_asn1.c
@@ -161,7 +161,7 @@
 }
 
 static int rsa_bits(const EVP_PKEY *pkey) {
-  return BN_num_bits(pkey->pkey.rsa->n);
+  return RSA_bits(pkey->pkey.rsa);
 }
 
 static void int_rsa_free(EVP_PKEY *pkey) { RSA_free(pkey->pkey.rsa); }
diff --git a/src/crypto/fipsmodule/bn/asm/x86_64-gcc.c b/src/crypto/fipsmodule/bn/asm/x86_64-gcc.c
index bcf12eb..49351c1 100644
--- a/src/crypto/fipsmodule/bn/asm/x86_64-gcc.c
+++ b/src/crypto/fipsmodule/bn/asm/x86_64-gcc.c
@@ -93,11 +93,11 @@
 #undef sqr
 #define sqr(r0, r1, a) __asm__("mulq %2" : "=a"(r0), "=d"(r1) : "a"(a) : "cc");
 
-BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, int num,
+BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, size_t num,
                           BN_ULONG w) {
   BN_ULONG c1 = 0;
 
-  if (num <= 0) {
+  if (num == 0) {
     return (c1);
   }
 
@@ -126,10 +126,11 @@
   return c1;
 }
 
-BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, int num, BN_ULONG w) {
+BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, size_t num,
+                      BN_ULONG w) {
   BN_ULONG c1 = 0;
 
-  if (num <= 0) {
+  if (num == 0) {
     return c1;
   }
 
@@ -156,8 +157,8 @@
   return c1;
 }
 
-void bn_sqr_words(BN_ULONG *r, const BN_ULONG *a, int n) {
-  if (n <= 0) {
+void bn_sqr_words(BN_ULONG *r, const BN_ULONG *a, size_t n) {
+  if (n == 0) {
     return;
   }
 
@@ -184,11 +185,11 @@
 }
 
 BN_ULONG bn_add_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
-                      int n) {
+                      size_t n) {
   BN_ULONG ret;
   size_t i = 0;
 
-  if (n <= 0) {
+  if (n == 0) {
     return 0;
   }
 
@@ -201,7 +202,8 @@
       "	adcq	(%5,%2,8),%0	\n"
       "	movq	%0,(%3,%2,8)	\n"
       "	lea	1(%2),%2	\n"
-      "	loop	1b		\n"
+      "	dec	%1		\n"
+      "	jnz	1b		\n"
       "	sbbq	%0,%0		\n"
       : "=&r"(ret), "+c"(n), "+r"(i)
       : "r"(rp), "r"(ap), "r"(bp)
@@ -211,11 +213,11 @@
 }
 
 BN_ULONG bn_sub_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
-                      int n) {
+                      size_t n) {
   BN_ULONG ret;
   size_t i = 0;
 
-  if (n <= 0) {
+  if (n == 0) {
     return 0;
   }
 
@@ -228,7 +230,8 @@
       "	sbbq	(%5,%2,8),%0	\n"
       "	movq	%0,(%3,%2,8)	\n"
       "	lea	1(%2),%2	\n"
-      "	loop	1b		\n"
+      "	dec	%1		\n"
+      "	jnz	1b		\n"
       "	sbbq	%0,%0		\n"
       : "=&r"(ret), "+c"(n), "+r"(i)
       : "r"(rp), "r"(ap), "r"(bp)
@@ -280,7 +283,7 @@
 
 #define sqr_add_c2(a, i, j, c0, c1, c2) mul_add_c2((a)[i], (a)[j], c0, c1, c2)
 
-void bn_mul_comba8(BN_ULONG r[16], BN_ULONG a[8], BN_ULONG b[8]) {
+void bn_mul_comba8(BN_ULONG r[16], const BN_ULONG a[8], const BN_ULONG b[8]) {
   BN_ULONG c1, c2, c3;
 
   c1 = 0;
@@ -382,7 +385,7 @@
   r[15] = c1;
 }
 
-void bn_mul_comba4(BN_ULONG r[8], BN_ULONG a[4], BN_ULONG b[4]) {
+void bn_mul_comba4(BN_ULONG r[8], const BN_ULONG a[4], const BN_ULONG b[4]) {
   BN_ULONG c1, c2, c3;
 
   c1 = 0;
diff --git a/src/crypto/fipsmodule/bn/bn_test.cc b/src/crypto/fipsmodule/bn/bn_test.cc
index 975264e..8e156ad 100644
--- a/src/crypto/fipsmodule/bn/bn_test.cc
+++ b/src/crypto/fipsmodule/bn/bn_test.cc
@@ -93,6 +93,7 @@
 #include <openssl/err.h>
 #include <openssl/mem.h>
 
+#include "./internal.h"
 #include "../../internal.h"
 #include "../../test/file_test.h"
 #include "../../test/test_util.h"
@@ -356,9 +357,11 @@
   ASSERT_TRUE(BN_mul(ret.get(), a.get(), a.get(), ctx));
   EXPECT_BIGNUMS_EQUAL("A * A", square.get(), ret.get());
 
-  ASSERT_TRUE(BN_div(ret.get(), remainder.get(), square.get(), a.get(), ctx));
-  EXPECT_BIGNUMS_EQUAL("Square / A", a.get(), ret.get());
-  EXPECT_BIGNUMS_EQUAL("Square % A", zero.get(), remainder.get());
+  if (!BN_is_zero(a.get())) {
+    ASSERT_TRUE(BN_div(ret.get(), remainder.get(), square.get(), a.get(), ctx));
+    EXPECT_BIGNUMS_EQUAL("Square / A", a.get(), ret.get());
+    EXPECT_BIGNUMS_EQUAL("Square % A", zero.get(), remainder.get());
+  }
 
   BN_set_negative(a.get(), 0);
   ASSERT_TRUE(BN_sqrt(ret.get(), square.get(), ctx));
@@ -381,6 +384,31 @@
         << "BN_sqrt succeeded on a non-square";
     ERR_clear_error();
   }
+
+#if !defined(BORINGSSL_SHARED_LIBRARY)
+  if (static_cast<size_t>(a->top) <= BN_SMALL_MAX_WORDS) {
+    for (size_t num_a = a->top; num_a <= BN_SMALL_MAX_WORDS; num_a++) {
+      SCOPED_TRACE(num_a);
+      size_t num_r = 2 * num_a;
+      // Use newly-allocated buffers so ASan will catch out-of-bounds writes.
+      std::unique_ptr<BN_ULONG[]> a_words(new BN_ULONG[num_a]),
+          r_words(new BN_ULONG[num_r]);
+      OPENSSL_memset(a_words.get(), 0, num_a * sizeof(BN_ULONG));
+      OPENSSL_memcpy(a_words.get(), a->d, a->top * sizeof(BN_ULONG));
+
+      ASSERT_TRUE(bn_mul_small(r_words.get(), num_r, a_words.get(), num_a,
+                               a_words.get(), num_a));
+      ASSERT_TRUE(bn_set_words(ret.get(), r_words.get(), num_r));
+      EXPECT_BIGNUMS_EQUAL("A * A (words)", square.get(), ret.get());
+
+      OPENSSL_memset(r_words.get(), 'A', num_r * sizeof(BN_ULONG));
+      ASSERT_TRUE(bn_sqr_small(r_words.get(), num_r, a_words.get(), num_a));
+
+      ASSERT_TRUE(bn_set_words(ret.get(), r_words.get(), num_r));
+      EXPECT_BIGNUMS_EQUAL("A^2 (words)", square.get(), ret.get());
+    }
+  }
+#endif
 }
 
 static void TestProduct(FileTest *t, BN_CTX *ctx) {
@@ -401,13 +429,46 @@
   ASSERT_TRUE(BN_mul(ret.get(), a.get(), b.get(), ctx));
   EXPECT_BIGNUMS_EQUAL("A * B", product.get(), ret.get());
 
-  ASSERT_TRUE(BN_div(ret.get(), remainder.get(), product.get(), a.get(), ctx));
-  EXPECT_BIGNUMS_EQUAL("Product / A", b.get(), ret.get());
-  EXPECT_BIGNUMS_EQUAL("Product % A", zero.get(), remainder.get());
+  if (!BN_is_zero(a.get())) {
+    ASSERT_TRUE(
+        BN_div(ret.get(), remainder.get(), product.get(), a.get(), ctx));
+    EXPECT_BIGNUMS_EQUAL("Product / A", b.get(), ret.get());
+    EXPECT_BIGNUMS_EQUAL("Product % A", zero.get(), remainder.get());
+  }
 
-  ASSERT_TRUE(BN_div(ret.get(), remainder.get(), product.get(), b.get(), ctx));
-  EXPECT_BIGNUMS_EQUAL("Product / B", a.get(), ret.get());
-  EXPECT_BIGNUMS_EQUAL("Product % B", zero.get(), remainder.get());
+  if (!BN_is_zero(b.get())) {
+    ASSERT_TRUE(
+        BN_div(ret.get(), remainder.get(), product.get(), b.get(), ctx));
+    EXPECT_BIGNUMS_EQUAL("Product / B", a.get(), ret.get());
+    EXPECT_BIGNUMS_EQUAL("Product % B", zero.get(), remainder.get());
+  }
+
+#if !defined(BORINGSSL_SHARED_LIBRARY)
+  if (!BN_is_negative(product.get()) &&
+      static_cast<size_t>(a->top) <= BN_SMALL_MAX_WORDS &&
+      static_cast<size_t>(b->top) <= BN_SMALL_MAX_WORDS) {
+    for (size_t num_a = a->top; num_a <= BN_SMALL_MAX_WORDS; num_a++) {
+      SCOPED_TRACE(num_a);
+      for (size_t num_b = b->top; num_b <= BN_SMALL_MAX_WORDS; num_b++) {
+        SCOPED_TRACE(num_b);
+        size_t num_r = num_a + num_b;
+        // Use newly-allocated buffers so ASan will catch out-of-bounds writes.
+        std::unique_ptr<BN_ULONG[]> a_words(new BN_ULONG[num_a]),
+            b_words(new BN_ULONG[num_b]), r_words(new BN_ULONG[num_r]);
+        OPENSSL_memset(a_words.get(), 0, num_a * sizeof(BN_ULONG));
+        OPENSSL_memcpy(a_words.get(), a->d, a->top * sizeof(BN_ULONG));
+
+        OPENSSL_memset(b_words.get(), 0, num_b * sizeof(BN_ULONG));
+        OPENSSL_memcpy(b_words.get(), b->d, b->top * sizeof(BN_ULONG));
+
+        ASSERT_TRUE(bn_mul_small(r_words.get(), num_r, a_words.get(), num_a,
+                                 b_words.get(), num_b));
+        ASSERT_TRUE(bn_set_words(ret.get(), r_words.get(), num_r));
+        EXPECT_BIGNUMS_EQUAL("A * B (words)", product.get(), ret.get());
+      }
+    }
+  }
+#endif
 }
 
 static void TestQuotient(FileTest *t, BN_CTX *ctx) {
@@ -481,15 +542,39 @@
     ASSERT_TRUE(a_tmp);
     ASSERT_TRUE(b_tmp);
     ASSERT_TRUE(BN_MONT_CTX_set(mont.get(), m.get(), ctx));
-    ASSERT_TRUE(BN_nnmod(a_tmp.get(), a.get(), m.get(), ctx));
-    ASSERT_TRUE(BN_nnmod(b_tmp.get(), b.get(), m.get(), ctx));
-    ASSERT_TRUE(BN_to_montgomery(a_tmp.get(), a_tmp.get(), mont.get(), ctx));
-    ASSERT_TRUE(BN_to_montgomery(b_tmp.get(), b_tmp.get(), mont.get(), ctx));
+    ASSERT_TRUE(BN_nnmod(a.get(), a.get(), m.get(), ctx));
+    ASSERT_TRUE(BN_nnmod(b.get(), b.get(), m.get(), ctx));
+    ASSERT_TRUE(BN_to_montgomery(a_tmp.get(), a.get(), mont.get(), ctx));
+    ASSERT_TRUE(BN_to_montgomery(b_tmp.get(), b.get(), mont.get(), ctx));
     ASSERT_TRUE(BN_mod_mul_montgomery(ret.get(), a_tmp.get(), b_tmp.get(),
                                       mont.get(), ctx));
     ASSERT_TRUE(BN_from_montgomery(ret.get(), ret.get(), mont.get(), ctx));
     EXPECT_BIGNUMS_EQUAL("A * B (mod M) (Montgomery)", mod_mul.get(),
                          ret.get());
+
+#if !defined(BORINGSSL_SHARED_LIBRARY)
+    if (m->top <= BN_SMALL_MAX_WORDS) {
+      std::unique_ptr<BN_ULONG[]> a_words(new BN_ULONG[m->top]),
+          b_words(new BN_ULONG[m->top]), r_words(new BN_ULONG[m->top]);
+      OPENSSL_memset(a_words.get(), 0, m->top * sizeof(BN_ULONG));
+      OPENSSL_memcpy(a_words.get(), a->d, a->top * sizeof(BN_ULONG));
+      OPENSSL_memset(b_words.get(), 0, m->top * sizeof(BN_ULONG));
+      OPENSSL_memcpy(b_words.get(), b->d, b->top * sizeof(BN_ULONG));
+      ASSERT_TRUE(bn_to_montgomery_small(a_words.get(), m->top, a_words.get(),
+                                         m->top, mont.get()));
+      ASSERT_TRUE(bn_to_montgomery_small(b_words.get(), m->top, b_words.get(),
+                                         m->top, mont.get()));
+      ASSERT_TRUE(bn_mod_mul_montgomery_small(
+          r_words.get(), m->top, a_words.get(), m->top, b_words.get(), m->top,
+          mont.get()));
+      // Use the second half of |tmp| so ASan will catch out-of-bounds writes.
+      ASSERT_TRUE(bn_from_montgomery_small(r_words.get(), m->top, r_words.get(),
+                                           m->top, mont.get()));
+      ASSERT_TRUE(bn_set_words(ret.get(), r_words.get(), m->top));
+      EXPECT_BIGNUMS_EQUAL("A * B (mod M) (Montgomery, words)", mod_mul.get(),
+                           ret.get());
+    }
+#endif
   }
 }
 
@@ -520,8 +605,8 @@
     ASSERT_TRUE(mont);
     ASSERT_TRUE(a_tmp);
     ASSERT_TRUE(BN_MONT_CTX_set(mont.get(), m.get(), ctx));
-    ASSERT_TRUE(BN_nnmod(a_tmp.get(), a.get(), m.get(), ctx));
-    ASSERT_TRUE(BN_to_montgomery(a_tmp.get(), a_tmp.get(), mont.get(), ctx));
+    ASSERT_TRUE(BN_nnmod(a.get(), a.get(), m.get(), ctx));
+    ASSERT_TRUE(BN_to_montgomery(a_tmp.get(), a.get(), mont.get(), ctx));
     ASSERT_TRUE(BN_mod_mul_montgomery(ret.get(), a_tmp.get(), a_tmp.get(),
                                       mont.get(), ctx));
     ASSERT_TRUE(BN_from_montgomery(ret.get(), ret.get(), mont.get(), ctx));
@@ -535,6 +620,38 @@
     ASSERT_TRUE(BN_from_montgomery(ret.get(), ret.get(), mont.get(), ctx));
     EXPECT_BIGNUMS_EQUAL("A * A_copy (mod M) (Montgomery)", mod_square.get(),
                          ret.get());
+
+#if !defined(BORINGSSL_SHARED_LIBRARY)
+    if (m->top <= BN_SMALL_MAX_WORDS) {
+      std::unique_ptr<BN_ULONG[]> a_words(new BN_ULONG[m->top]),
+          a_copy_words(new BN_ULONG[m->top]), r_words(new BN_ULONG[m->top]);
+      OPENSSL_memset(a_words.get(), 0, m->top * sizeof(BN_ULONG));
+      OPENSSL_memcpy(a_words.get(), a->d, a->top * sizeof(BN_ULONG));
+      ASSERT_TRUE(bn_to_montgomery_small(a_words.get(), m->top, a_words.get(),
+                                         m->top, mont.get()));
+      ASSERT_TRUE(bn_mod_mul_montgomery_small(
+          r_words.get(), m->top, a_words.get(), m->top, a_words.get(), m->top,
+          mont.get()));
+      ASSERT_TRUE(bn_from_montgomery_small(r_words.get(), m->top, r_words.get(),
+                                           m->top, mont.get()));
+      ASSERT_TRUE(bn_set_words(ret.get(), r_words.get(), m->top));
+      EXPECT_BIGNUMS_EQUAL("A * A (mod M) (Montgomery, words)",
+                           mod_square.get(), ret.get());
+
+      // Repeat the operation with |a_copy_words|.
+      OPENSSL_memcpy(a_copy_words.get(), a_words.get(),
+                     m->top * sizeof(BN_ULONG));
+      ASSERT_TRUE(bn_mod_mul_montgomery_small(
+          r_words.get(), m->top, a_words.get(), m->top, a_copy_words.get(),
+          m->top, mont.get()));
+      // Use the second half of |tmp| so ASan will catch out-of-bounds writes.
+      ASSERT_TRUE(bn_from_montgomery_small(r_words.get(), m->top, r_words.get(),
+                                           m->top, mont.get()));
+      ASSERT_TRUE(bn_set_words(ret.get(), r_words.get(), m->top));
+      EXPECT_BIGNUMS_EQUAL("A * A_copy (mod M) (Montgomery, words)",
+                           mod_square.get(), ret.get());
+    }
+#endif
   }
 }
 
@@ -563,6 +680,28 @@
                                           ctx, NULL));
     EXPECT_BIGNUMS_EQUAL("A ^ E (mod M) (constant-time)", mod_exp.get(),
                          ret.get());
+
+#if !defined(BORINGSSL_SHARED_LIBRARY)
+    if (m->top <= BN_SMALL_MAX_WORDS) {
+      bssl::UniquePtr<BN_MONT_CTX> mont(BN_MONT_CTX_new());
+      ASSERT_TRUE(mont.get());
+      ASSERT_TRUE(BN_MONT_CTX_set(mont.get(), m.get(), ctx));
+      ASSERT_TRUE(BN_nnmod(a.get(), a.get(), m.get(), ctx));
+      std::unique_ptr<BN_ULONG[]> r_words(new BN_ULONG[m->top]),
+          a_words(new BN_ULONG[m->top]);
+      OPENSSL_memset(a_words.get(), 0, m->top * sizeof(BN_ULONG));
+      OPENSSL_memcpy(a_words.get(), a->d, a->top * sizeof(BN_ULONG));
+      ASSERT_TRUE(bn_to_montgomery_small(a_words.get(), m->top, a_words.get(),
+                                         m->top, mont.get()));
+      ASSERT_TRUE(bn_mod_exp_mont_small(r_words.get(), m->top, a_words.get(),
+                                        m->top, e->d, e->top, mont.get()));
+      ASSERT_TRUE(bn_from_montgomery_small(r_words.get(), m->top, r_words.get(),
+                                           m->top, mont.get()));
+      ASSERT_TRUE(bn_set_words(ret.get(), r_words.get(), m->top));
+      EXPECT_BIGNUMS_EQUAL("A ^ E (mod M) (Montgomery, words)", mod_exp.get(),
+                           ret.get());
+    }
+#endif
   }
 }
 
@@ -1618,3 +1757,94 @@
                                 nullptr /* callback */));
   EXPECT_EQ(0, is_probably_prime_2);
 }
+
+#if !defined(BORINGSSL_SHARED_LIBRARY)
+TEST_F(BNTest, LessThanWords) {
+  // kTestVectors is an array of 256-bit values in sorted order.
+  static const BN_ULONG kTestVectors[][256 / BN_BITS2] = {
+      {TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0x00000000, 0x00000001), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0x00000000, 0x00000002), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0x00000000, 0x0000ffff), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0x00000000, 0x83339914), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0x00000000, 0xfffffffe), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0x00000000, 0xffffffff), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0xed17ac85, 0x83339914), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0xffffffff, 0xffffffff), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0x00000000, 0x83339914), TOBN(0x00000000, 0x00000001),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff),
+       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff),
+       TOBN(0xffffffff, 0xffffffff), TOBN(0x00000000, 0x00000000)},
+      {TOBN(0x00000000, 0x00000000), TOBN(0x1d6f60ba, 0x893ba84c),
+       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
+      {TOBN(0x00000000, 0x83339915), TOBN(0x1d6f60ba, 0x893ba84c),
+       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
+      {TOBN(0xed17ac85, 0x00000000), TOBN(0x1d6f60ba, 0x893ba84c),
+       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
+      {TOBN(0xed17ac85, 0x83339915), TOBN(0x1d6f60ba, 0x893ba84c),
+       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
+      {TOBN(0xed17ac85, 0xffffffff), TOBN(0x1d6f60ba, 0x893ba84c),
+       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
+      {TOBN(0xffffffff, 0x83339915), TOBN(0x1d6f60ba, 0x893ba84c),
+       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
+      {TOBN(0xffffffff, 0xffffffff), TOBN(0x1d6f60ba, 0x893ba84c),
+       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
+      {TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000),
+       TOBN(0x00000000, 0x00000000), TOBN(0xffffffff, 0xffffffff)},
+      {TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000),
+       TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff)},
+      {TOBN(0x00000000, 0x00000001), TOBN(0x00000000, 0x00000000),
+       TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff)},
+      {TOBN(0x00000000, 0x00000000), TOBN(0xffffffff, 0xffffffff),
+       TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff)},
+      {TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff),
+       TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff)},
+  };
+
+  // Determine where the single-word values stop.
+  size_t one_word;
+  for (one_word = 0; one_word < OPENSSL_ARRAY_SIZE(kTestVectors); one_word++) {
+    int is_word = 1;
+    for (size_t i = 1; i < OPENSSL_ARRAY_SIZE(kTestVectors[one_word]); i++) {
+      if (kTestVectors[one_word][i] != 0) {
+        is_word = 0;
+        break;
+      }
+    }
+    if (!is_word) {
+      break;
+    }
+  }
+
+  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kTestVectors); i++) {
+    SCOPED_TRACE(i);
+    for (size_t j = 0; j < OPENSSL_ARRAY_SIZE(kTestVectors); j++) {
+      SCOPED_TRACE(j);
+      EXPECT_EQ(i < j ? 1 : 0,
+                bn_less_than_words(kTestVectors[i], kTestVectors[j],
+                                   OPENSSL_ARRAY_SIZE(kTestVectors[i])));
+      for (size_t k = 0; k < one_word; k++) {
+        SCOPED_TRACE(k);
+        EXPECT_EQ(k <= i && i < j ? 1 : 0,
+                  bn_in_range_words(kTestVectors[i], kTestVectors[k][0],
+                                    kTestVectors[j],
+                                    OPENSSL_ARRAY_SIZE(kTestVectors[i])));
+      }
+    }
+  }
+
+  EXPECT_EQ(0, bn_less_than_words(NULL, NULL, 0));
+  EXPECT_EQ(0, bn_in_range_words(NULL, 0, NULL, 0));
+}
+#endif  // !BORINGSSL_SHARED_LIBRARY
diff --git a/src/crypto/fipsmodule/bn/bn_tests.txt b/src/crypto/fipsmodule/bn/bn_tests.txt
index f809e7e..eb447b5 100644
--- a/src/crypto/fipsmodule/bn/bn_tests.txt
+++ b/src/crypto/fipsmodule/bn/bn_tests.txt
@@ -5349,6 +5349,12 @@
 Square = eea8028b26e0df090504d54da714a6f5f2695202e53cff479c78aedd47a8dc676243ec586740fde53b3eca9ca02b91031ce766242184109503fbe25b1b6d318e3cd5970fabd16dfa22984dd2e9f1e0f14c189170fc69c031d66663703e6235a942d51a4545bd7b0769d01d302ce2b00b83f01568a1e378f61fd0ca6201b0490330580cd9de85719e174a71915d7efbf65cd73d8f4e66f27e0dd3144d58ec09ed0f7ed7d1238ee596922807100fb7a11127944ddcdec6a9ca3bbf6df7301e354f3f049bfb7c275b43c3d8cda5907a932fba507c9145ea3166081c1b48fcc710ee32cd931f936c796b14f8a78a592e67753a7c9e428a01719c8ba82652f3a89fae110
 A = -3dcb44be1e54c5a5d7db48055ca9afa1ebe2ae648aa6e16ac497502a7deee09ffa124720fad0ab163ce8b3ea6a90f110ea52b67dbc424d0cf1e8c9726dfd9e45bebcefaa5cd5706edeed27896525f31c6bbea3d67ee97badefabf3e2532470b66e3ae3100f66ddf50cf02fc3a8e3f44c304251d3b6a7ca3a6e4bd5d16a41bd97a4
 
+Square = 0
+A = 0
+
+Square = 1
+A = 1
+
 
 # Product tests.
 #
@@ -5954,6 +5960,22 @@
 A = a57da276998c548101f514e9f
 B = -542fb814f45924aa09a16f2a6
 
+Product = 0
+A = 0
+B = 542fb814f45924aa09a16f2a6
+
+Product = 0
+A = 542fb814f45924aa09a16f2a6
+B = 0
+
+Product = 542fb814f45924aa09a16f2a6
+A = 1
+B = 542fb814f45924aa09a16f2a6
+
+Product = 542fb814f45924aa09a16f2a6
+A = 542fb814f45924aa09a16f2a6
+B = 1
+
 
 # Quotient tests.
 #
diff --git a/src/crypto/fipsmodule/bn/cmp.c b/src/crypto/fipsmodule/bn/cmp.c
index 7864707..acc017f 100644
--- a/src/crypto/fipsmodule/bn/cmp.c
+++ b/src/crypto/fipsmodule/bn/cmp.c
@@ -57,8 +57,10 @@
 #include <openssl/bn.h>
 
 #include <openssl/mem.h>
+#include <openssl/type_check.h>
 
 #include "internal.h"
+#include "../../internal.h"
 
 
 int BN_ucmp(const BIGNUM *a, const BIGNUM *b) {
@@ -174,6 +176,19 @@
   return bn_cmp_words(a, b, cl);
 }
 
+int bn_less_than_words(const BN_ULONG *a, const BN_ULONG *b, size_t len) {
+  OPENSSL_COMPILE_ASSERT(sizeof(BN_ULONG) <= sizeof(crypto_word_t),
+                         crypto_word_t_too_small);
+  int ret = 0;
+  // Process the words in little-endian order.
+  for (size_t i = 0; i < len; i++) {
+    crypto_word_t eq = constant_time_eq_w(a[i], b[i]);
+    crypto_word_t lt = constant_time_lt_w(a[i], b[i]);
+    ret = constant_time_select_int(eq, ret, constant_time_select_int(lt, 1, 0));
+  }
+  return ret;
+}
+
 int BN_abs_is_word(const BIGNUM *bn, BN_ULONG w) {
   switch (bn->top) {
     case 1:
diff --git a/src/crypto/fipsmodule/bn/exponentiation.c b/src/crypto/fipsmodule/bn/exponentiation.c
index 2d40e8f..a5cb7da 100644
--- a/src/crypto/fipsmodule/bn/exponentiation.c
+++ b/src/crypto/fipsmodule/bn/exponentiation.c
@@ -434,6 +434,15 @@
 // value returned from |BN_window_bits_for_exponent_size|.
 #define TABLE_SIZE 32
 
+// TABLE_BITS_SMALL is the smallest value returned from
+// |BN_window_bits_for_exponent_size| when |b| is at most |BN_BITS2| *
+// |BN_SMALL_MAX_WORDS| words.
+#define TABLE_BITS_SMALL 5
+
+// TABLE_SIZE_SMALL is the same as |TABLE_SIZE|, but when |b| is at most
+// |BN_BITS2| * |BN_SMALL_MAX_WORDS|.
+#define TABLE_SIZE_SMALL (1 << (TABLE_BITS_SMALL - 1))
+
 static int mod_exp_recp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p,
                         const BIGNUM *m, BN_CTX *ctx) {
   int i, j, bits, ret = 0, wstart, window;
@@ -734,6 +743,152 @@
   return ret;
 }
 
+int bn_mod_exp_mont_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a,
+                          size_t num_a, const BN_ULONG *p, size_t num_p,
+                          const BN_MONT_CTX *mont) {
+  const BN_ULONG *n = mont->N.d;
+  size_t num_n = mont->N.top;
+  if (num_n != num_a || num_n != num_r || num_n > BN_SMALL_MAX_WORDS) {
+    OPENSSL_PUT_ERROR(BN, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+  if (!BN_is_odd(&mont->N)) {
+    OPENSSL_PUT_ERROR(BN, BN_R_CALLED_WITH_EVEN_MODULUS);
+    return 0;
+  }
+  unsigned bits = 0;
+  if (num_p != 0) {
+    bits = BN_num_bits_word(p[num_p - 1]) + (num_p - 1) * BN_BITS2;
+  }
+  if (bits == 0) {
+    OPENSSL_memset(r, 0, num_r * sizeof(BN_ULONG));
+    if (!BN_is_one(&mont->N)) {
+      r[0] = 1;
+    }
+    return 1;
+  }
+
+  // We exponentiate by looking at sliding windows of the exponent and
+  // precomputing powers of |a|. Windows may be shifted so they always end on a
+  // set bit, so only precompute odd powers. We compute val[i] = a^(2*i + 1) for
+  // i = 0 to 2^(window-1), all in Montgomery form.
+  unsigned window = BN_window_bits_for_exponent_size(bits);
+  if (window > TABLE_BITS_SMALL) {
+    window = TABLE_BITS_SMALL;  // Tolerate excessively large |p|.
+  }
+  int ret = 0;
+  BN_ULONG val[TABLE_SIZE_SMALL][BN_SMALL_MAX_WORDS];
+  OPENSSL_memcpy(val[0], a, num_n * sizeof(BN_ULONG));
+  if (window > 1) {
+    BN_ULONG d[BN_SMALL_MAX_WORDS];
+    if (!bn_mod_mul_montgomery_small(d, num_n, val[0], num_n, val[0], num_n,
+                                     mont)) {
+      goto err;
+    }
+    for (unsigned i = 1; i < 1u << (window - 1); i++) {
+      if (!bn_mod_mul_montgomery_small(val[i], num_n, val[i - 1], num_n, d,
+                                       num_n, mont)) {
+        goto err;
+      }
+    }
+  }
+
+  // Set |r| to one in Montgomery form. If the high bit of |m| is set, |m| is
+  // close to R and we subtract rather than perform Montgomery reduction.
+  if (n[num_n - 1] & (((BN_ULONG)1) << (BN_BITS2 - 1))) {
+    // r = 2^(top*BN_BITS2) - m
+    r[0] = 0 - n[0];
+    for (size_t i = 1; i < num_n; i++) {
+      r[i] = ~n[i];
+    }
+  } else if (!bn_from_montgomery_small(r, num_r, mont->RR.d, mont->RR.top,
+                                       mont)) {
+    goto err;
+  }
+
+  int r_is_one = 1;
+  unsigned wstart = bits - 1;  // The top bit of the window.
+  for (;;) {
+    if (!bn_is_bit_set_words(p, num_p, wstart)) {
+      if (!r_is_one &&
+          !bn_mod_mul_montgomery_small(r, num_r, r, num_r, r, num_r, mont)) {
+        goto err;
+      }
+      if (wstart == 0) {
+        break;
+      }
+      wstart--;
+      continue;
+    }
+
+    // We now have wstart on a set bit. Find the largest window we can use.
+    unsigned wvalue = 1;
+    unsigned wsize = 0;
+    for (unsigned i = 1; i < window && i <= wstart; i++) {
+      if (bn_is_bit_set_words(p, num_p, wstart - i)) {
+        wvalue <<= (i - wsize);
+        wvalue |= 1;
+        wsize = i;
+      }
+    }
+
+    // Shift |r| to the end of the window.
+    if (!r_is_one) {
+      for (unsigned i = 0; i < wsize + 1; i++) {
+        if (!bn_mod_mul_montgomery_small(r, num_r, r, num_r, r, num_r, mont)) {
+          goto err;
+        }
+      }
+    }
+
+    assert(wvalue & 1);
+    assert(wvalue < (1u << window));
+    if (!bn_mod_mul_montgomery_small(r, num_r, r, num_r, val[wvalue >> 1],
+                                     num_n, mont)) {
+      goto err;
+    }
+
+    r_is_one = 0;
+    if (wstart == wsize) {
+      break;
+    }
+    wstart -= wsize + 1;
+  }
+
+  ret = 1;
+
+err:
+  OPENSSL_cleanse(val, sizeof(val));
+  return ret;
+}
+
+int bn_mod_inverse_prime_mont_small(BN_ULONG *r, size_t num_r,
+                                    const BN_ULONG *a, size_t num_a,
+                                    const BN_MONT_CTX *mont) {
+  const BN_ULONG *p = mont->N.d;
+  size_t num_p = mont->N.top;
+  if (num_p > BN_SMALL_MAX_WORDS || num_p == 0) {
+    OPENSSL_PUT_ERROR(BN, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  // Per Fermat's Little Theorem, a^-1 = a^(p-2) (mod p) for p prime.
+  BN_ULONG p_minus_two[BN_SMALL_MAX_WORDS];
+  OPENSSL_memcpy(p_minus_two, p, num_p * sizeof(BN_ULONG));
+  if (p_minus_two[0] >= 2) {
+    p_minus_two[0] -= 2;
+  } else {
+    p_minus_two[0] -= 2;
+    for (size_t i = 1; i < num_p; i++) {
+      if (p_minus_two[i]-- != 0) {
+        break;
+      }
+    }
+  }
+
+  return bn_mod_exp_mont_small(r, num_r, a, num_a, p_minus_two, num_p, mont);
+}
+
 
 // |BN_mod_exp_mont_consttime| stores the precomputed powers in a specific
 // layout so that accessing any of these table values shows the same access
diff --git a/src/crypto/fipsmodule/bn/generic.c b/src/crypto/fipsmodule/bn/generic.c
index 44e0f2c..a39a033 100644
--- a/src/crypto/fipsmodule/bn/generic.c
+++ b/src/crypto/fipsmodule/bn/generic.c
@@ -124,12 +124,11 @@
 
 #endif  // !BN_ULLONG
 
-BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, int num,
+BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, size_t num,
                           BN_ULONG w) {
   BN_ULONG c1 = 0;
 
-  assert(num >= 0);
-  if (num <= 0) {
+  if (num == 0) {
     return c1;
   }
 
@@ -153,11 +152,11 @@
   return c1;
 }
 
-BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, int num, BN_ULONG w) {
+BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, size_t num,
+                      BN_ULONG w) {
   BN_ULONG c1 = 0;
 
-  assert(num >= 0);
-  if (num <= 0) {
+  if (num == 0) {
     return c1;
   }
 
@@ -179,9 +178,8 @@
   return c1;
 }
 
-void bn_sqr_words(BN_ULONG *r, const BN_ULONG *a, int n) {
-  assert(n >= 0);
-  if (n <= 0) {
+void bn_sqr_words(BN_ULONG *r, const BN_ULONG *a, size_t n) {
+  if (n == 0) {
     return;
   }
 
@@ -204,11 +202,10 @@
 
 #ifdef BN_ULLONG
 BN_ULONG bn_add_words(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
-                      int n) {
+                      size_t n) {
   BN_ULLONG ll = 0;
 
-  assert(n >= 0);
-  if (n <= 0) {
+  if (n == 0) {
     return 0;
   }
 
@@ -245,11 +242,10 @@
 #else  // !BN_ULLONG
 
 BN_ULONG bn_add_words(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
-                      int n) {
+                      size_t n) {
   BN_ULONG c, l, t;
 
-  assert(n >= 0);
-  if (n <= 0) {
+  if (n == 0) {
     return (BN_ULONG)0;
   }
 
@@ -302,12 +298,11 @@
 #endif  // !BN_ULLONG
 
 BN_ULONG bn_sub_words(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
-                      int n) {
+                      size_t n) {
   BN_ULONG t1, t2;
   int c = 0;
 
-  assert(n >= 0);
-  if (n <= 0) {
+  if (n == 0) {
     return (BN_ULONG)0;
   }
 
@@ -458,7 +453,7 @@
 
 #endif  // !BN_ULLONG
 
-void bn_mul_comba8(BN_ULONG r[16], BN_ULONG a[8], BN_ULONG b[8]) {
+void bn_mul_comba8(BN_ULONG r[16], const BN_ULONG a[8], const BN_ULONG b[8]) {
   BN_ULONG c1, c2, c3;
 
   c1 = 0;
@@ -560,7 +555,7 @@
   r[15] = c1;
 }
 
-void bn_mul_comba4(BN_ULONG r[8], BN_ULONG a[4], BN_ULONG b[4]) {
+void bn_mul_comba4(BN_ULONG r[8], const BN_ULONG a[4], const BN_ULONG b[4]) {
   BN_ULONG c1, c2, c3;
 
   c1 = 0;
diff --git a/src/crypto/fipsmodule/bn/internal.h b/src/crypto/fipsmodule/bn/internal.h
index acc0ac8..75efbfa 100644
--- a/src/crypto/fipsmodule/bn/internal.h
+++ b/src/crypto/fipsmodule/bn/internal.h
@@ -214,40 +214,40 @@
 // the result in |rp|. |ap| and |rp| must both be |num| words long. It returns
 // the carry word of the operation. |ap| and |rp| may be equal but otherwise may
 // not alias.
-BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, int num,
+BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, size_t num,
                           BN_ULONG w);
 
 // bn_mul_words multiples |ap| by |w| and places the result in |rp|. |ap| and
 // |rp| must both be |num| words long. It returns the carry word of the
 // operation. |ap| and |rp| may be equal but otherwise may not alias.
-BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, int num, BN_ULONG w);
+BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, size_t num, BN_ULONG w);
 
 // bn_sqr_words sets |rp[2*i]| and |rp[2*i+1]| to |ap[i]|'s square, for all |i|
 // up to |num|. |ap| is an array of |num| words and |rp| an array of |2*num|
 // words. |ap| and |rp| may not alias.
 //
 // This gives the contribution of the |ap[i]*ap[i]| terms when squaring |ap|.
-void bn_sqr_words(BN_ULONG *rp, const BN_ULONG *ap, int num);
+void bn_sqr_words(BN_ULONG *rp, const BN_ULONG *ap, size_t num);
 
 // bn_add_words adds |ap| to |bp| and places the result in |rp|, each of which
 // are |num| words long. It returns the carry bit, which is one if the operation
 // overflowed and zero otherwise. Any pair of |ap|, |bp|, and |rp| may be equal
 // to each other but otherwise may not alias.
 BN_ULONG bn_add_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
-                      int num);
+                      size_t num);
 
 // bn_sub_words subtracts |bp| from |ap| and places the result in |rp|. It
 // returns the borrow bit, which is one if the computation underflowed and zero
 // otherwise. Any pair of |ap|, |bp|, and |rp| may be equal to each other but
 // otherwise may not alias.
 BN_ULONG bn_sub_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
-                      int num);
+                      size_t num);
 
 // bn_mul_comba4 sets |r| to the product of |a| and |b|.
-void bn_mul_comba4(BN_ULONG r[8], BN_ULONG a[4], BN_ULONG b[4]);
+void bn_mul_comba4(BN_ULONG r[8], const BN_ULONG a[4], const BN_ULONG b[4]);
 
 // bn_mul_comba8 sets |r| to the product of |a| and |b|.
-void bn_mul_comba8(BN_ULONG r[16], BN_ULONG a[8], BN_ULONG b[8]);
+void bn_mul_comba8(BN_ULONG r[16], const BN_ULONG a[8], const BN_ULONG b[8]);
 
 // bn_sqr_comba8 sets |r| to |a|^2.
 void bn_sqr_comba8(BN_ULONG r[16], const BN_ULONG a[4]);
@@ -265,6 +265,29 @@
 // the length of |a| minus the length of |b|.
 int bn_cmp_part_words(const BN_ULONG *a, const BN_ULONG *b, int cl, int dl);
 
+// bn_less_than_words returns one if |a| < |b| and zero otherwise, where |a|
+// and |b| both are |len| words long. It runs in constant time.
+int bn_less_than_words(const BN_ULONG *a, const BN_ULONG *b, size_t len);
+
+// bn_in_range_words returns one if |min_inclusive| <= |a| < |max_exclusive|,
+// where |a| and |max_exclusive| both are |len| words long. This function leaks
+// which of [0, min_inclusive), [min_inclusive, max_exclusive), and
+// [max_exclusive, 2^(BN_BITS2*len)) contains |a|, but otherwise the value of
+// |a| is secret.
+int bn_in_range_words(const BN_ULONG *a, BN_ULONG min_inclusive,
+                      const BN_ULONG *max_exclusive, size_t len);
+
+// bn_rand_range_words sets |out| to a uniformly distributed random number from
+// |min_inclusive| to |max_exclusive|. Both |out| and |max_exclusive| are |len|
+// words long.
+//
+// This function runs in time independent of the result, but |min_inclusive| and
+// |max_exclusive| are public data. (Information about the range is unavoidably
+// leaked by how many iterations it took to select a number.)
+int bn_rand_range_words(BN_ULONG *out, BN_ULONG min_inclusive,
+                        const BN_ULONG *max_exclusive, size_t len,
+                        const uint8_t additional_data[32]);
+
 int bn_mul_mont(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,
                 const BN_ULONG *np, const BN_ULONG *n0, int num);
 
@@ -295,6 +318,93 @@
 // -2 on error.
 int bn_jacobi(const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx);
 
+// bn_is_bit_set_words returns one if bit |bit| is set in |a| and zero
+// otherwise.
+int bn_is_bit_set_words(const BN_ULONG *a, size_t num, unsigned bit);
+
+
+// Low-level operations for small numbers.
+//
+// The following functions implement algorithms suitable for use with scalars
+// and field elements in elliptic curves. They rely on the number being small
+// both to stack-allocate various temporaries and because they do not implement
+// optimizations useful for the larger values used in RSA.
+
+// BN_SMALL_MAX_WORDS is the largest size input these functions handle. This
+// limit allows temporaries to be more easily stack-allocated. This limit is set
+// to accommodate P-521.
+#if defined(OPENSSL_32_BIT)
+#define BN_SMALL_MAX_WORDS 17
+#else
+#define BN_SMALL_MAX_WORDS 9
+#endif
+
+// bn_mul_small sets |r| to |a|*|b|. |num_r| must be |num_a| + |num_b|. |r| may
+// not alias with |a| or |b|. This function returns one on success and zero if
+// lengths are inconsistent.
+int bn_mul_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a, size_t num_a,
+                 const BN_ULONG *b, size_t num_b);
+
+// bn_sqr_small sets |r| to |a|^2. |num_a| must be at most |BN_SMALL_MAX_WORDS|.
+// |num_r| must be |num_a|*2. |r| and |a| may not alias. This function returns
+// one on success and zero on programmer error.
+int bn_sqr_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a, size_t num_a);
+
+// In the following functions, the modulus must be at most |BN_SMALL_MAX_WORDS|
+// words long.
+
+// bn_to_montgomery_small sets |r| to |a| translated to the Montgomery domain.
+// |num_a| and |num_r| must be the length of the modulus, which is
+// |mont->N.top|. |a| must be fully reduced. This function returns one on
+// success and zero if lengths are inconsistent. |r| and |a| may alias.
+int bn_to_montgomery_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a,
+                           size_t num_a, const BN_MONT_CTX *mont);
+
+// bn_from_montgomery_small sets |r| to |a| translated out of the Montgomery
+// domain. |num_r| must be the length of the modulus, which is |mont->N.top|.
+// |a| must be at most |mont->N.top| * R and |num_a| must be at most 2 *
+// |mont->N.top|. This function returns one on success and zero if lengths are
+// inconsistent. |r| and |a| may alias.
+int bn_from_montgomery_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a,
+                             size_t num_a, const BN_MONT_CTX *mont);
+
+// bn_mod_mul_montgomery_small sets |r| to |a| * |b| mod |mont->N|. Both inputs
+// and outputs are in the Montgomery domain. |num_r| must be the length of the
+// modulus, which is |mont->N.top|. This function returns one on success and
+// zero on internal error or inconsistent lengths. Any two of |r|, |a|, and |b|
+// may alias.
+//
+// This function requires |a| * |b| < N * R, where N is the modulus and R is the
+// Montgomery divisor, 2^(N.top * BN_BITS2). This should generally be satisfied
+// by ensuring |a| and |b| are fully reduced, however ECDSA has one computation
+// which requires the more general bound.
+int bn_mod_mul_montgomery_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a,
+                                size_t num_a, const BN_ULONG *b, size_t num_b,
+                                const BN_MONT_CTX *mont);
+
+// bn_mod_exp_mont_small sets |r| to |a|^|p| mod |mont->N|. It returns one on
+// success and zero on programmer or internal error. Both inputs and outputs are
+// in the Montgomery domain. |num_r| and |num_a| must be |mont->N.top|, which
+// must be at most |BN_SMALL_MAX_WORDS|. |a| must be fully-reduced. This
+// function runs in time independent of |a|, but |p| and |mont->N| are public
+// values.
+//
+// Note this function differs from |BN_mod_exp_mont| which uses Montgomery
+// reduction but takes input and output outside the Montgomery domain. Combine
+// this function with |bn_from_montgomery_small| and |bn_to_montgomery_small|
+// if necessary.
+int bn_mod_exp_mont_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a,
+                          size_t num_a, const BN_ULONG *p, size_t num_p,
+                          const BN_MONT_CTX *mont);
+
+// bn_mod_inverse_prime_mont_small sets |r| to |a|^-1 mod |mont->N|. |mont->N|
+// must be a prime. |num_r| and |num_a| must be |mont->N.top|, which must be at
+// most |BN_SMALL_MAX_WORDS|. |a| must be fully-reduced. This function runs in
+// time independent of |a|, but |mont->N| is a public value.
+int bn_mod_inverse_prime_mont_small(BN_ULONG *r, size_t num_r,
+                                    const BN_ULONG *a, size_t num_a,
+                                    const BN_MONT_CTX *mont);
+
 
 #if defined(__cplusplus)
 }  // extern C
diff --git a/src/crypto/fipsmodule/bn/montgomery.c b/src/crypto/fipsmodule/bn/montgomery.c
index caa2513..e8505da 100644
--- a/src/crypto/fipsmodule/bn/montgomery.c
+++ b/src/crypto/fipsmodule/bn/montgomery.c
@@ -114,6 +114,7 @@
 #include <openssl/err.h>
 #include <openssl/mem.h>
 #include <openssl/thread.h>
+#include <openssl/type_check.h>
 
 #include "internal.h"
 #include "../../internal.h"
@@ -259,87 +260,75 @@
   return BN_mod_mul_montgomery(ret, a, &mont->RR, mont, ctx);
 }
 
+static int bn_from_montgomery_in_place(BN_ULONG *r, size_t num_r, BN_ULONG *a,
+                                       size_t num_a, const BN_MONT_CTX *mont) {
+  const BN_ULONG *n = mont->N.d;
+  size_t num_n = mont->N.top;
+  if (num_r != num_n || num_a != 2 * num_n) {
+    OPENSSL_PUT_ERROR(BN, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  // Add multiples of |n| to |r| until R = 2^(nl * BN_BITS2) divides it. On
+  // input, we had |r| < |n| * R, so now |r| < 2 * |n| * R. Note that |r|
+  // includes |carry| which is stored separately.
+  BN_ULONG n0 = mont->n0[0];
+  BN_ULONG carry = 0;
+  for (size_t i = 0; i < num_n; i++) {
+    BN_ULONG v = bn_mul_add_words(a + i, n, num_n, a[i] * n0);
+    v += carry + a[i + num_n];
+    carry |= (v != a[i + num_n]);
+    carry &= (v <= a[i + num_n]);
+    a[i + num_n] = v;
+  }
+
+  // Shift |num_n| words to divide by R. We have |a| < 2 * |n|. Note that |a|
+  // includes |carry| which is stored separately.
+  a += num_n;
+
+  // |a| thus requires at most one additional subtraction |n| to be reduced.
+  // Subtract |n| and select the answer in constant time.
+  OPENSSL_COMPILE_ASSERT(sizeof(BN_ULONG) <= sizeof(crypto_word_t),
+                         crypto_word_t_too_small);
+  BN_ULONG v = bn_sub_words(r, a, n, num_n) - carry;
+  // |v| is one if |a| - |n| underflowed or zero if it did not. Note |v| cannot
+  // be -1. That would imply the subtraction did not fit in |num_n| words, and
+  // we know at most one subtraction is needed.
+  v = 0u - v;
+  for (size_t i = 0; i < num_n; i++) {
+    r[i] = constant_time_select_w(v, a[i], r[i]);
+    a[i] = 0;
+  }
+  return 1;
+}
+
 static int BN_from_montgomery_word(BIGNUM *ret, BIGNUM *r,
                                    const BN_MONT_CTX *mont) {
-  BN_ULONG *ap, *np, *rp, n0, v, carry;
-  int nl, max, i;
-
   const BIGNUM *n = &mont->N;
-  nl = n->top;
-  if (nl == 0) {
+  if (n->top == 0) {
     ret->top = 0;
     return 1;
   }
 
-  max = (2 * nl);  // carry is stored separately
-  if (!bn_wexpand(r, max)) {
+  int max = (2 * n->top);  // carry is stored separately
+  if (!bn_wexpand(r, max) ||
+      !bn_wexpand(ret, n->top)) {
     return 0;
   }
-
-  r->neg ^= n->neg;
-  np = n->d;
-  rp = r->d;
-
-  // clear the top words of T
+  // Clear the top words of |r|.
   if (max > r->top) {
-    OPENSSL_memset(&rp[r->top], 0, (max - r->top) * sizeof(BN_ULONG));
+    OPENSSL_memset(r->d + r->top, 0, (max - r->top) * sizeof(BN_ULONG));
   }
-
   r->top = max;
-  n0 = mont->n0[0];
+  ret->top = n->top;
 
-  for (carry = 0, i = 0; i < nl; i++, rp++) {
-    v = bn_mul_add_words(rp, np, nl, rp[0] * n0);
-    v += carry + rp[nl];
-    carry |= (v != rp[nl]);
-    carry &= (v <= rp[nl]);
-    rp[nl] = v;
-  }
-
-  if (!bn_wexpand(ret, nl)) {
+  if (!bn_from_montgomery_in_place(ret->d, ret->top, r->d, r->top, mont)) {
     return 0;
   }
-  ret->top = nl;
   ret->neg = r->neg;
 
-  rp = ret->d;
-  ap = &(r->d[nl]);
-
-  {
-    BN_ULONG *nrp;
-    uintptr_t m;
-
-    v = bn_sub_words(rp, ap, np, nl) - carry;
-    // if subtraction result is real, then trick unconditional memcpy below to
-    // perform in-place "refresh" instead of actual copy.
-    m = (0u - (uintptr_t)v);
-    nrp = (BN_ULONG *)(((uintptr_t)rp & ~m) | ((uintptr_t)ap & m));
-
-    for (i = 0, nl -= 4; i < nl; i += 4) {
-      BN_ULONG t1, t2, t3, t4;
-
-      t1 = nrp[i + 0];
-      t2 = nrp[i + 1];
-      t3 = nrp[i + 2];
-      ap[i + 0] = 0;
-      t4 = nrp[i + 3];
-      ap[i + 1] = 0;
-      rp[i + 0] = t1;
-      ap[i + 2] = 0;
-      rp[i + 1] = t2;
-      ap[i + 3] = 0;
-      rp[i + 2] = t3;
-      rp[i + 3] = t4;
-    }
-
-    for (nl += 4; i < nl; i++) {
-      rp[i] = nrp[i], ap[i] = 0;
-    }
-  }
-
   bn_correct_top(r);
   bn_correct_top(ret);
-
   return 1;
 }
 
@@ -427,3 +416,68 @@
   BN_CTX_end(ctx);
   return ret;
 }
+
+int bn_to_montgomery_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a,
+                           size_t num_a, const BN_MONT_CTX *mont) {
+  return bn_mod_mul_montgomery_small(r, num_r, a, num_a, mont->RR.d,
+                                     mont->RR.top, mont);
+}
+
+int bn_from_montgomery_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a,
+                             size_t num_a, const BN_MONT_CTX *mont) {
+  size_t num_n = mont->N.top;
+  if (num_a > 2 * num_n || num_r != num_n || num_n > BN_SMALL_MAX_WORDS) {
+    OPENSSL_PUT_ERROR(BN, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+  BN_ULONG tmp[BN_SMALL_MAX_WORDS * 2];
+  size_t num_tmp = 2 * num_n;
+  OPENSSL_memcpy(tmp, a, num_a * sizeof(BN_ULONG));
+  OPENSSL_memset(tmp + num_a, 0, (num_tmp - num_a) * sizeof(BN_ULONG));
+  int ret = bn_from_montgomery_in_place(r, num_r, tmp, num_tmp, mont);
+  OPENSSL_cleanse(tmp, num_tmp * sizeof(BN_ULONG));
+  return ret;
+}
+
+int bn_mod_mul_montgomery_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a,
+                                size_t num_a, const BN_ULONG *b, size_t num_b,
+                                const BN_MONT_CTX *mont) {
+  size_t num_n = mont->N.top;
+  if (num_r != num_n || num_a + num_b > 2 * num_n ||
+      num_n > BN_SMALL_MAX_WORDS) {
+    OPENSSL_PUT_ERROR(BN, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+#if defined(OPENSSL_BN_ASM_MONT)
+  // |bn_mul_mont| requires at least 128 bits of limbs, at least for x86.
+  if (num_n >= (128 / BN_BITS2) &&
+      num_a == num_n &&
+      num_b == num_n) {
+    if (!bn_mul_mont(r, a, b, mont->N.d, mont->n0, num_n)) {
+      assert(0);  // The check above ensures this won't happen.
+      OPENSSL_PUT_ERROR(BN, ERR_R_INTERNAL_ERROR);
+      return 0;
+    }
+    return 1;
+  }
+#endif
+
+  // Compute the product.
+  BN_ULONG tmp[2 * BN_SMALL_MAX_WORDS];
+  size_t num_tmp = 2 * num_n;
+  size_t num_ab = num_a + num_b;
+  if (a == b && num_a == num_b) {
+    if (!bn_sqr_small(tmp, num_ab, a, num_a)) {
+      return 0;
+    }
+  } else if (!bn_mul_small(tmp, num_ab, a, num_a, b, num_b)) {
+    return 0;
+  }
+
+  // Zero-extend to full width and reduce.
+  OPENSSL_memset(tmp + num_ab, 0, (num_tmp - num_ab) * sizeof(BN_ULONG));
+  int ret = bn_from_montgomery_in_place(r, num_r, tmp, num_tmp, mont);
+  OPENSSL_cleanse(tmp, num_tmp * sizeof(BN_ULONG));
+  return ret;
+}
diff --git a/src/crypto/fipsmodule/bn/mul.c b/src/crypto/fipsmodule/bn/mul.c
index 65f3c2b..b93f558 100644
--- a/src/crypto/fipsmodule/bn/mul.c
+++ b/src/crypto/fipsmodule/bn/mul.c
@@ -59,50 +59,48 @@
 #include <assert.h>
 #include <string.h>
 
+#include <openssl/err.h>
+#include <openssl/mem.h>
+
 #include "internal.h"
+#include "../../internal.h"
 
 
 #define BN_MUL_RECURSIVE_SIZE_NORMAL 16
 #define BN_SQR_RECURSIVE_SIZE_NORMAL BN_MUL_RECURSIVE_SIZE_NORMAL
 
 
-static void bn_mul_normal(BN_ULONG *r, BN_ULONG *a, int na, BN_ULONG *b,
-                          int nb) {
-  BN_ULONG *rr;
-
+static void bn_mul_normal(BN_ULONG *r, const BN_ULONG *a, size_t na,
+                          const BN_ULONG *b, size_t nb) {
   if (na < nb) {
-    int itmp;
-    BN_ULONG *ltmp;
-
-    itmp = na;
+    size_t itmp = na;
     na = nb;
     nb = itmp;
-    ltmp = a;
+    const BN_ULONG *ltmp = a;
     a = b;
     b = ltmp;
   }
-  rr = &(r[na]);
-  if (nb <= 0) {
-    (void)bn_mul_words(r, a, na, 0);
+  BN_ULONG *rr = &(r[na]);
+  if (nb == 0) {
+    OPENSSL_memset(r, 0, na * sizeof(BN_ULONG));
     return;
-  } else {
-    rr[0] = bn_mul_words(r, a, na, b[0]);
   }
+  rr[0] = bn_mul_words(r, a, na, b[0]);
 
   for (;;) {
-    if (--nb <= 0) {
+    if (--nb == 0) {
       return;
     }
     rr[1] = bn_mul_add_words(&(r[1]), a, na, b[1]);
-    if (--nb <= 0) {
+    if (--nb == 0) {
       return;
     }
     rr[2] = bn_mul_add_words(&(r[2]), a, na, b[2]);
-    if (--nb <= 0) {
+    if (--nb == 0) {
       return;
     }
     rr[3] = bn_mul_add_words(&(r[3]), a, na, b[3]);
-    if (--nb <= 0) {
+    if (--nb == 0) {
       return;
     }
     rr[4] = bn_mul_add_words(&(r[4]), a, na, b[4]);
@@ -294,8 +292,8 @@
 // a[0]*b[0]+a[1]*b[1]+(a[0]-a[1])*(b[1]-b[0])
 // a[1]*b[1]
 // dnX may not be positive, but n2/2+dnX has to be
-static void bn_mul_recursive(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n2,
-                             int dna, int dnb, BN_ULONG *t) {
+static void bn_mul_recursive(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b,
+                             int n2, int dna, int dnb, BN_ULONG *t) {
   int n = n2 / 2, c1, c2;
   int tna = n + dna, tnb = n + dnb;
   unsigned int neg, zero;
@@ -426,8 +424,9 @@
 // n+tn is the word length
 // t needs to be n*4 is size, as does r
 // tnX may not be negative but less than n
-static void bn_mul_part_recursive(BN_ULONG *r, BN_ULONG *a, BN_ULONG *b, int n,
-                                  int tna, int tnb, BN_ULONG *t) {
+static void bn_mul_part_recursive(BN_ULONG *r, const BN_ULONG *a,
+                                  const BN_ULONG *b, int n, int tna, int tnb,
+                                  BN_ULONG *t) {
   int i, j, n2 = n * 2;
   int c1, c2, neg;
   BN_ULONG ln, lo, *p;
@@ -658,28 +657,47 @@
   return ret;
 }
 
+int bn_mul_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a, size_t num_a,
+                 const BN_ULONG *b, size_t num_b) {
+  if (num_r != num_a + num_b) {
+    OPENSSL_PUT_ERROR(BN, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+  // TODO(davidben): Should this call |bn_mul_comba4| too? |BN_mul| does not
+  // hit that code.
+  if (num_a == 8 && num_b == 8) {
+    bn_mul_comba8(r, a, b);
+  } else {
+    bn_mul_normal(r, a, num_a, b, num_b);
+  }
+  return 1;
+}
+
 // tmp must have 2*n words
-static void bn_sqr_normal(BN_ULONG *r, const BN_ULONG *a, int n,
+static void bn_sqr_normal(BN_ULONG *r, const BN_ULONG *a, size_t n,
                           BN_ULONG *tmp) {
-  int max = n * 2;
+  if (n == 0) {
+    return;
+  }
+
+  size_t max = n * 2;
   const BN_ULONG *ap = a;
   BN_ULONG *rp = r;
   rp[0] = rp[max - 1] = 0;
   rp++;
-  int j = n;
 
   // Compute the contribution of a[i] * a[j] for all i < j.
-  if (--j > 0) {
+  if (n > 1) {
     ap++;
-    rp[j] = bn_mul_words(rp, ap, j, ap[-1]);
+    rp[n - 1] = bn_mul_words(rp, ap, n - 1, ap[-1]);
     rp += 2;
   }
-
-  for (int i = n - 2; i > 0; i--) {
-    j--;
-    ap++;
-    rp[j] = bn_mul_add_words(rp, ap, j, ap[-1]);
-    rp += 2;
+  if (n > 2) {
+    for (size_t i = n - 2; i > 0; i--) {
+      ap++;
+      rp[i] = bn_mul_add_words(rp, ap, i, ap[-1]);
+      rp += 2;
+    }
   }
 
   // The final result fits in |max| words, so none of the following operations
@@ -865,3 +883,20 @@
   BN_CTX_end(ctx);
   return ret;
 }
+
+int bn_sqr_small(BN_ULONG *r, size_t num_r, const BN_ULONG *a, size_t num_a) {
+  if (num_r != 2 * num_a || num_a > BN_SMALL_MAX_WORDS) {
+    OPENSSL_PUT_ERROR(BN, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+  if (num_a == 4) {
+    bn_sqr_comba4(r, a);
+  } else if (num_a == 8) {
+    bn_sqr_comba8(r, a);
+  } else {
+    BN_ULONG tmp[2 * BN_SMALL_MAX_WORDS];
+    bn_sqr_normal(r, a, num_a, tmp);
+    OPENSSL_cleanse(tmp, 2 * num_a * sizeof(BN_ULONG));
+  }
+  return 1;
+}
diff --git a/src/crypto/fipsmodule/bn/random.c b/src/crypto/fipsmodule/bn/random.c
index 2257da0..61499af 100644
--- a/src/crypto/fipsmodule/bn/random.c
+++ b/src/crypto/fipsmodule/bn/random.c
@@ -113,18 +113,16 @@
 #include <openssl/err.h>
 #include <openssl/mem.h>
 #include <openssl/rand.h>
-#include <openssl/sha.h>
 #include <openssl/type_check.h>
 
+#include "internal.h"
 #include "../../internal.h"
 #include "../rand/internal.h"
 
 
 static const uint8_t kDefaultAdditionalData[32] = {0};
 
-static int bn_rand_with_additional_data(BIGNUM *rnd, int bits, int top,
-                                        int bottom,
-                                        const uint8_t additional_data[32]) {
+int BN_rand(BIGNUM *rnd, int bits, int top, int bottom) {
   uint8_t *buf = NULL;
   int ret = 0, bit, bytes, mask;
 
@@ -159,7 +157,7 @@
   }
 
   // Make a random number and set the top and bottom bits.
-  RAND_bytes_with_additional_data(buf, bytes, additional_data);
+  RAND_bytes(buf, bytes);
 
   if (top != BN_RAND_TOP_ANY) {
     if (top == BN_RAND_TOP_TWO && bits > 1) {
@@ -192,54 +190,104 @@
   return ret;
 }
 
-int BN_rand(BIGNUM *rnd, int bits, int top, int bottom) {
-  return bn_rand_with_additional_data(rnd, bits, top, bottom,
-                                      kDefaultAdditionalData);
-}
-
 int BN_pseudo_rand(BIGNUM *rnd, int bits, int top, int bottom) {
   return BN_rand(rnd, bits, top, bottom);
 }
 
-static int bn_rand_range_with_additional_data(
-    BIGNUM *r, BN_ULONG min_inclusive, const BIGNUM *max_exclusive,
-    const uint8_t additional_data[32]) {
-  if (BN_cmp_word(max_exclusive, min_inclusive) <= 0) {
+// bn_less_than_word returns one if the number represented by |len| words at |a|
+// is less than |b| and zero otherwise. It performs this computation in time
+// independent of the value of |a|. |b| is assumed public.
+static int bn_less_than_word(const BN_ULONG *a, size_t len, BN_ULONG b) {
+  if (b == 0) {
+    return 0;
+  }
+  if (len == 0) {
+    return 1;
+  }
+
+  // |a| < |b| iff a[1..len-1] are all zero and a[0] < b.
+  OPENSSL_COMPILE_ASSERT(sizeof(BN_ULONG) <= sizeof(crypto_word_t),
+                         crypto_word_t_too_small);
+  crypto_word_t mask = 0;
+  for (size_t i = 1; i < len; i++) {
+    mask |= a[i];
+  }
+  // |mask| is now zero iff a[1..len-1] are all zero.
+  mask = constant_time_is_zero_w(mask);
+  mask &= constant_time_lt_w(a[0], b);
+  return constant_time_select_int(mask, 1, 0);
+}
+
+int bn_in_range_words(const BN_ULONG *a, BN_ULONG min_inclusive,
+                      const BN_ULONG *max_exclusive, size_t len) {
+  return bn_less_than_words(a, max_exclusive, len) &&
+         !bn_less_than_word(a, len, min_inclusive);
+}
+
+int bn_rand_range_words(BN_ULONG *out, BN_ULONG min_inclusive,
+                        const BN_ULONG *max_exclusive, size_t len,
+                        const uint8_t additional_data[32]) {
+  // This function implements the equivalent of steps 4 through 7 of FIPS 186-4
+  // appendices B.4.2 and B.5.2. When called in those contexts, |max_exclusive|
+  // is n and |min_inclusive| is one.
+
+  // Compute the bit length of |max_exclusive| (step 1), in terms of a number of
+  // |words| worth of entropy to fill and a mask of bits to clear in the top
+  // word.
+  size_t words = len;
+  while (words > 0 && max_exclusive[words - 1] == 0) {
+    words--;
+  }
+  if (words == 0 ||
+      (words == 1 && max_exclusive[0] <= min_inclusive)) {
     OPENSSL_PUT_ERROR(BN, BN_R_INVALID_RANGE);
     return 0;
   }
+  BN_ULONG mask = max_exclusive[words - 1];
+  // This sets all bits in |mask| below the most significant bit.
+  mask |= mask >> 1;
+  mask |= mask >> 2;
+  mask |= mask >> 4;
+  mask |= mask >> 8;
+  mask |= mask >> 16;
+#if defined(OPENSSL_64_BIT)
+  mask |= mask >> 32;
+#endif
 
-  // This function is used to implement steps 4 through 7 of FIPS 186-4
-  // appendices B.4.2 and B.5.2. When called in those contexts, |max_exclusive|
-  // is n and |min_inclusive| is one.
+  // Fill any unused words with zero.
+  OPENSSL_memset(out + words, 0, (len - words) * sizeof(BN_ULONG));
+
   unsigned count = 100;
-  unsigned n = BN_num_bits(max_exclusive);  // n > 0
   do {
     if (!--count) {
       OPENSSL_PUT_ERROR(BN, BN_R_TOO_MANY_ITERATIONS);
       return 0;
     }
 
-    if (// steps 4 and 5
-        !bn_rand_with_additional_data(r, n, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY,
-                                      additional_data) ||
-        // step 7
-        !BN_add_word(r, min_inclusive)) {
-      return 0;
-    }
+    // Steps 4 and 5. Use |words| and |mask| together to obtain a string of N
+    // bits, where N is the bit length of |max_exclusive|.
+    RAND_bytes_with_additional_data((uint8_t *)out, words * sizeof(BN_ULONG),
+                                    additional_data);
+    out[words - 1] &= mask;
 
-    // Step 6. This loops if |r| >= |max_exclusive|. This is identical to
-    // checking |r| > |max_exclusive| - 1 or |r| - 1 > |max_exclusive| - 2, the
-    // formulation stated in FIPS 186-4.
-  } while (BN_cmp(r, max_exclusive) >= 0);
-
+    // If out >= max_exclusive or out < min_inclusive, retry. This implements
+    // the equivalent of steps 6 and 7 without leaking the value of |out|.
+  } while (!bn_in_range_words(out, min_inclusive, max_exclusive, words));
   return 1;
 }
 
 int BN_rand_range_ex(BIGNUM *r, BN_ULONG min_inclusive,
                      const BIGNUM *max_exclusive) {
-  return bn_rand_range_with_additional_data(r, min_inclusive, max_exclusive,
-                                            kDefaultAdditionalData);
+  if (!bn_wexpand(r, max_exclusive->top) ||
+      !bn_rand_range_words(r->d, min_inclusive, max_exclusive->d,
+                           max_exclusive->top, kDefaultAdditionalData)) {
+    return 0;
+  }
+
+  r->neg = 0;
+  r->top = max_exclusive->top;
+  bn_correct_top(r);
+  return 1;
 }
 
 int BN_rand_range(BIGNUM *r, const BIGNUM *range) {
@@ -249,35 +297,3 @@
 int BN_pseudo_rand_range(BIGNUM *r, const BIGNUM *range) {
   return BN_rand_range(r, range);
 }
-
-int BN_generate_dsa_nonce(BIGNUM *out, const BIGNUM *range, const BIGNUM *priv,
-                          const uint8_t *message, size_t message_len,
-                          BN_CTX *ctx) {
-  // We copy |priv| into a local buffer to avoid furthur exposing its
-  // length.
-  uint8_t private_bytes[96];
-  size_t todo = sizeof(priv->d[0]) * priv->top;
-  if (todo > sizeof(private_bytes)) {
-    // No reasonable DSA or ECDSA key should have a private key
-    // this large and we don't handle this case in order to avoid
-    // leaking the length of the private key.
-    OPENSSL_PUT_ERROR(BN, BN_R_PRIVATE_KEY_TOO_LARGE);
-    return 0;
-  }
-  OPENSSL_memcpy(private_bytes, priv->d, todo);
-  OPENSSL_memset(private_bytes + todo, 0, sizeof(private_bytes) - todo);
-
-  // Pass a SHA512 hash of the private key and message as additional data into
-  // the RBG. This is a hardening measure against entropy failure.
-  OPENSSL_COMPILE_ASSERT(SHA512_DIGEST_LENGTH >= 32,
-                         additional_data_is_too_large_for_sha512);
-  SHA512_CTX sha;
-  uint8_t digest[SHA512_DIGEST_LENGTH];
-  SHA512_Init(&sha);
-  SHA512_Update(&sha, private_bytes, sizeof(private_bytes));
-  SHA512_Update(&sha, message, message_len);
-  SHA512_Final(digest, &sha);
-
-  // Select a value k from [1, range-1], following FIPS 186-4 appendix B.5.2.
-  return bn_rand_range_with_additional_data(out, 1, range, digest);
-}
diff --git a/src/crypto/fipsmodule/bn/shift.c b/src/crypto/fipsmodule/bn/shift.c
index d4528e6..d4ed79e 100644
--- a/src/crypto/fipsmodule/bn/shift.c
+++ b/src/crypto/fipsmodule/bn/shift.c
@@ -267,17 +267,20 @@
   return 1;
 }
 
+int bn_is_bit_set_words(const BN_ULONG *a, size_t num, unsigned bit) {
+  unsigned i = bit / BN_BITS2;
+  unsigned j = bit % BN_BITS2;
+  if (i >= num) {
+    return 0;
+  }
+  return (a[i] >> j) & 1;
+}
+
 int BN_is_bit_set(const BIGNUM *a, int n) {
   if (n < 0) {
     return 0;
   }
-  int i = n / BN_BITS2;
-  int j = n % BN_BITS2;
-  if (a->top <= i) {
-    return 0;
-  }
-
-  return (a->d[i]>>j)&1;
+  return bn_is_bit_set_words(a->d, a->top, n);
 }
 
 int BN_mask_bits(BIGNUM *a, int n) {
diff --git a/src/crypto/fipsmodule/ec/ec.c b/src/crypto/fipsmodule/ec/ec.c
index 600aedc..977cd26 100644
--- a/src/crypto/fipsmodule/ec/ec.c
+++ b/src/crypto/fipsmodule/ec/ec.c
@@ -77,6 +77,7 @@
 
 #include "internal.h"
 #include "../../internal.h"
+#include "../bn/internal.h"
 #include "../delocate.h"
 
 
@@ -279,63 +280,6 @@
 #endif
 }
 
-// built_in_curve_scalar_field_monts contains Montgomery contexts for
-// performing inversions in the scalar fields of each of the built-in
-// curves. It's protected by |built_in_curve_scalar_field_monts_once|.
-DEFINE_LOCAL_DATA(BN_MONT_CTX **, built_in_curve_scalar_field_monts) {
-  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
-
-  BN_MONT_CTX **monts =
-      OPENSSL_malloc(sizeof(BN_MONT_CTX *) * OPENSSL_NUM_BUILT_IN_CURVES);
-  if (monts == NULL) {
-    return;
-  }
-
-  OPENSSL_memset(monts, 0, sizeof(BN_MONT_CTX *) * OPENSSL_NUM_BUILT_IN_CURVES);
-
-  BIGNUM *order = BN_new();
-  BN_CTX *bn_ctx = BN_CTX_new();
-  BN_MONT_CTX *mont_ctx = NULL;
-
-  if (bn_ctx == NULL ||
-      order == NULL) {
-    goto err;
-  }
-
-  for (size_t i = 0; i < OPENSSL_NUM_BUILT_IN_CURVES; i++) {
-    const struct built_in_curve *curve = &curves->curves[i];
-    const unsigned param_len = curve->param_len;
-    const uint8_t *params = curve->params;
-
-    mont_ctx = BN_MONT_CTX_new();
-    if (mont_ctx == NULL) {
-      goto err;
-    }
-
-    if (!BN_bin2bn(params + 5 * param_len, param_len, order) ||
-        !BN_MONT_CTX_set(mont_ctx, order, bn_ctx)) {
-      goto err;
-    }
-
-    monts[i] = mont_ctx;
-    mont_ctx = NULL;
-  }
-
-  *out = monts;
-  goto done;
-
-err:
-  BN_MONT_CTX_free(mont_ctx);
-  for (size_t i = 0; i < OPENSSL_NUM_BUILT_IN_CURVES; i++) {
-    BN_MONT_CTX_free(monts[i]);
-  }
-  OPENSSL_free((BN_MONT_CTX**) monts);
-
-done:
-  BN_free(order);
-  BN_CTX_free(bn_ctx);
-}
-
 EC_GROUP *ec_group_new(const EC_METHOD *meth) {
   EC_GROUP *ret;
 
@@ -383,6 +327,11 @@
 
 EC_GROUP *EC_GROUP_new_curve_GFp(const BIGNUM *p, const BIGNUM *a,
                                  const BIGNUM *b, BN_CTX *ctx) {
+  if (BN_num_bytes(p) > EC_MAX_SCALAR_BYTES) {
+    OPENSSL_PUT_ERROR(EC, EC_R_INVALID_FIELD);
+    return NULL;
+  }
+
   EC_GROUP *ret = ec_group_new(EC_GFp_mont_method());
   if (ret == NULL) {
     return NULL;
@@ -409,6 +358,12 @@
     // Additionally, |generator| must been created from
     // |EC_GROUP_new_curve_GFp|, not a copy, so that
     // |generator->group->generator| is set correctly.
+    OPENSSL_PUT_ERROR(EC, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  if (BN_num_bytes(order) > EC_MAX_SCALAR_BYTES) {
+    OPENSSL_PUT_ERROR(EC, EC_R_INVALID_FIELD);
     return 0;
   }
 
@@ -418,6 +373,24 @@
     return 0;
   }
 
+  // Require that p < 2×order. This simplifies some ECDSA operations.
+  //
+  // Note any curve which did not satisfy this must have been invalid or use a
+  // tiny prime (less than 17). See the proof in |field_element_to_scalar| in
+  // the ECDSA implementation.
+  BIGNUM *tmp = BN_new();
+  if (tmp == NULL ||
+      !BN_lshift1(tmp, order)) {
+    BN_free(tmp);
+    return 0;
+  }
+  int ok = BN_cmp(tmp, &group->field) > 0;
+  BN_free(tmp);
+  if (!ok) {
+    OPENSSL_PUT_ERROR(EC, EC_R_INVALID_GROUP_ORDER);
+    return 0;
+  }
+
   EC_POINT *copy = EC_POINT_new(group);
   if (copy == NULL ||
       !EC_POINT_copy(copy, generator) ||
@@ -426,13 +399,18 @@
     return 0;
   }
 
+  BN_MONT_CTX_free(group->order_mont);
+  group->order_mont = BN_MONT_CTX_new();
+  if (group->order_mont == NULL ||
+      !BN_MONT_CTX_set(group->order_mont, &group->order, NULL)) {
+    return 0;
+  }
+
   ec_group_set0_generator(group, copy);
   return 1;
 }
 
-static EC_GROUP *ec_group_new_from_data(unsigned built_in_index) {
-  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
-  const struct built_in_curve *curve = &curves->curves[built_in_index];
+static EC_GROUP *ec_group_new_from_data(const struct built_in_curve *curve) {
   EC_GROUP *group = NULL;
   EC_POINT *P = NULL;
   BIGNUM *p = NULL, *a = NULL, *b = NULL, *x = NULL, *y = NULL;
@@ -481,9 +459,11 @@
     goto err;
   }
 
-  const BN_MONT_CTX **monts = *built_in_curve_scalar_field_monts();
-  if (monts != NULL) {
-    group->order_mont = monts[built_in_index];
+  group->order_mont = BN_MONT_CTX_new();
+  if (group->order_mont == NULL ||
+      !BN_MONT_CTX_set(group->order_mont, &group->order, ctx)) {
+    OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
+    goto err;
   }
 
   ec_group_set0_generator(group, P);
@@ -505,29 +485,65 @@
   return group;
 }
 
-EC_GROUP *EC_GROUP_new_by_curve_name(int nid) {
-  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
-  EC_GROUP *ret = NULL;
+// Built-in groups are allocated lazily and static once allocated.
+// TODO(davidben): Make these actually static. https://crbug.com/boringssl/20.
+struct built_in_groups_st {
+  EC_GROUP *groups[OPENSSL_NUM_BUILT_IN_CURVES];
+};
+DEFINE_BSS_GET(struct built_in_groups_st, built_in_groups);
+DEFINE_STATIC_MUTEX(built_in_groups_lock);
 
+EC_GROUP *EC_GROUP_new_by_curve_name(int nid) {
+  struct built_in_groups_st *groups = built_in_groups_bss_get();
+  EC_GROUP **group_ptr = NULL;
+  const struct built_in_curves *const curves = OPENSSL_built_in_curves();
+  const struct built_in_curve *curve = NULL;
   for (size_t i = 0; i < OPENSSL_NUM_BUILT_IN_CURVES; i++) {
-    const struct built_in_curve *curve = &curves->curves[i];
-    if (curve->nid == nid) {
-      ret = ec_group_new_from_data(i);
+    if (curves->curves[i].nid == nid) {
+      curve = &curves->curves[i];
+      group_ptr = &groups->groups[i];
       break;
     }
   }
 
-  if (ret == NULL) {
+  if (curve == NULL) {
     OPENSSL_PUT_ERROR(EC, EC_R_UNKNOWN_GROUP);
     return NULL;
   }
 
-  ret->curve_name = nid;
+  CRYPTO_STATIC_MUTEX_lock_read(built_in_groups_lock_bss_get());
+  EC_GROUP *ret = *group_ptr;
+  CRYPTO_STATIC_MUTEX_unlock_read(built_in_groups_lock_bss_get());
+  if (ret != NULL) {
+    return ret;
+  }
+
+  ret = ec_group_new_from_data(curve);
+  if (ret == NULL) {
+    return NULL;
+  }
+
+  EC_GROUP *to_free = NULL;
+  CRYPTO_STATIC_MUTEX_lock_write(built_in_groups_lock_bss_get());
+  if (*group_ptr == NULL) {
+    *group_ptr = ret;
+    // Filling in |ret->curve_name| makes |EC_GROUP_free| and |EC_GROUP_dup|
+    // into no-ops. At this point, |ret| is considered static.
+    ret->curve_name = nid;
+  } else {
+    to_free = ret;
+    ret = *group_ptr;
+  }
+  CRYPTO_STATIC_MUTEX_unlock_write(built_in_groups_lock_bss_get());
+
+  EC_GROUP_free(to_free);
   return ret;
 }
 
 void EC_GROUP_free(EC_GROUP *group) {
   if (group == NULL ||
+      // Built-in curves are static.
+      group->curve_name != NID_undef ||
       !CRYPTO_refcount_dec_and_test_zero(&group->references)) {
     return;
   }
@@ -538,17 +554,16 @@
 
   ec_point_free(group->generator, 0 /* don't free group */);
   BN_free(&group->order);
+  BN_MONT_CTX_free(group->order_mont);
 
   OPENSSL_free(group);
 }
 
-const BN_MONT_CTX *ec_group_get_order_mont(const EC_GROUP *group) {
-  return group->order_mont;
-}
-
 EC_GROUP *EC_GROUP_dup(const EC_GROUP *a) {
-  if (a == NULL) {
-    return NULL;
+  if (a == NULL ||
+      // Built-in curves are static.
+      a->curve_name != NID_undef) {
+    return (EC_GROUP *)a;
   }
 
   // Groups are logically immutable (but for |EC_GROUP_set_generator| which must
@@ -808,7 +823,53 @@
   // nothing to multiply. But, nobody should be calling this function with
   // nothing to multiply in the first place.
   if ((g_scalar == NULL && p_scalar == NULL) ||
-      ((p == NULL) != (p_scalar == NULL)))  {
+      (p == NULL) != (p_scalar == NULL))  {
+    OPENSSL_PUT_ERROR(EC, ERR_R_PASSED_NULL_PARAMETER);
+    return 0;
+  }
+
+  // We cannot easily process arbitrary scalars in constant-time, and there is
+  // no need to do so. Require that scalars be the same size as the order.
+  //
+  // One could require they be fully reduced, but some consumers try to check
+  // that |order| * |pubkey| is the identity. This comes from following NIST SP
+  // 800-56A section 5.6.2.3.2. (Though all our curves have cofactor one, so
+  // this check isn't useful.)
+  int ret = 0;
+  EC_SCALAR g_scalar_storage, p_scalar_storage;
+  EC_SCALAR *g_scalar_arg = NULL, *p_scalar_arg = NULL;
+  unsigned order_bits = BN_num_bits(&group->order);
+  if (g_scalar != NULL) {
+    if (BN_is_negative(g_scalar) || BN_num_bits(g_scalar) > order_bits ||
+        !ec_bignum_to_scalar(group, &g_scalar_storage, g_scalar)) {
+      OPENSSL_PUT_ERROR(EC, EC_R_INVALID_SCALAR);
+      goto err;
+    }
+    g_scalar_arg = &g_scalar_storage;
+  }
+
+  if (p_scalar != NULL) {
+    if (BN_is_negative(p_scalar) || BN_num_bits(p_scalar) > order_bits ||
+        !ec_bignum_to_scalar(group, &p_scalar_storage, p_scalar)) {
+      OPENSSL_PUT_ERROR(EC, EC_R_INVALID_SCALAR);
+      goto err;
+    }
+    p_scalar_arg = &p_scalar_storage;
+  }
+
+  ret = ec_point_mul_scalar(group, r, g_scalar_arg, p, p_scalar_arg, ctx);
+
+err:
+  OPENSSL_cleanse(&g_scalar_storage, sizeof(g_scalar_storage));
+  OPENSSL_cleanse(&p_scalar_storage, sizeof(p_scalar_storage));
+  return ret;
+}
+
+int ec_point_mul_scalar(const EC_GROUP *group, EC_POINT *r,
+                        const EC_SCALAR *g_scalar, const EC_POINT *p,
+                        const EC_SCALAR *p_scalar, BN_CTX *ctx) {
+  if ((g_scalar == NULL && p_scalar == NULL) ||
+      (p == NULL) != (p_scalar == NULL))  {
     OPENSSL_PUT_ERROR(EC, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
@@ -863,3 +924,20 @@
 
   return OPENSSL_NUM_BUILT_IN_CURVES;
 }
+
+int ec_bignum_to_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                        const BIGNUM *in) {
+  if (BN_is_negative(in) || in->top > group->order.top) {
+    OPENSSL_PUT_ERROR(EC, EC_R_INVALID_SCALAR);
+    return 0;
+  }
+  OPENSSL_memset(out->words, 0, group->order.top * sizeof(BN_ULONG));
+  OPENSSL_memcpy(out->words, in->d, in->top * sizeof(BN_ULONG));
+  return 1;
+}
+
+int ec_random_nonzero_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                             const uint8_t additional_data[32]) {
+  return bn_rand_range_words(out->words, 1, group->order.d, group->order.top,
+                             additional_data);
+}
diff --git a/src/crypto/fipsmodule/ec/ec_test.cc b/src/crypto/fipsmodule/ec/ec_test.cc
index 0ee7378..5e5ce94 100644
--- a/src/crypto/fipsmodule/ec/ec_test.cc
+++ b/src/crypto/fipsmodule/ec/ec_test.cc
@@ -407,6 +407,38 @@
       << "p * 0 did not return point at infinity.";
 }
 
+// Test that multiplying by the order produces ∞ and, moreover, that callers may
+// do so. |EC_POINT_mul| is almost exclusively used with reduced scalars, with
+// this exception. This comes from consumers following NIST SP 800-56A section
+// 5.6.2.3.2. (Though all our curves have cofactor one, so this check isn't
+// useful.)
+TEST_P(ECCurveTest, MulOrder) {
+  bssl::UniquePtr<EC_GROUP> group(EC_GROUP_new_by_curve_name(GetParam().nid));
+  ASSERT_TRUE(group);
+
+  // Test that g × order = ∞.
+  bssl::UniquePtr<EC_POINT> point(EC_POINT_new(group.get()));
+  ASSERT_TRUE(point);
+  ASSERT_TRUE(EC_POINT_mul(group.get(), point.get(),
+                           EC_GROUP_get0_order(group.get()), nullptr, nullptr,
+                           nullptr));
+
+  EXPECT_TRUE(EC_POINT_is_at_infinity(group.get(), point.get()))
+      << "g * order did not return point at infinity.";
+
+  // Test that p × order = ∞, for some arbitrary p.
+  bssl::UniquePtr<BIGNUM> forty_two(BN_new());
+  ASSERT_TRUE(forty_two);
+  ASSERT_TRUE(BN_set_word(forty_two.get(), 42));
+  ASSERT_TRUE(EC_POINT_mul(group.get(), point.get(), forty_two.get(), nullptr,
+                           nullptr, nullptr));
+  ASSERT_TRUE(EC_POINT_mul(group.get(), point.get(), nullptr, point.get(),
+                           EC_GROUP_get0_order(group.get()), nullptr));
+
+  EXPECT_TRUE(EC_POINT_is_at_infinity(group.get(), point.get()))
+      << "p * order did not return point at infinity.";
+}
+
 // Test that 10×∞ + G = G.
 TEST_P(ECCurveTest, Mul) {
   bssl::UniquePtr<EC_GROUP> group(EC_GROUP_new_by_curve_name(GetParam().nid));
diff --git a/src/crypto/fipsmodule/ec/internal.h b/src/crypto/fipsmodule/ec/internal.h
index f065744..7374e8b 100644
--- a/src/crypto/fipsmodule/ec/internal.h
+++ b/src/crypto/fipsmodule/ec/internal.h
@@ -73,12 +73,34 @@
 #include <openssl/bn.h>
 #include <openssl/ex_data.h>
 #include <openssl/thread.h>
+#include <openssl/type_check.h>
+
+#include "../bn/internal.h"
 
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
 
+// Cap the size of all field elements and scalars, including custom curves, to
+// 66 bytes, large enough to fit secp521r1 and brainpoolP512r1, which appear to
+// be the largest fields anyone plausibly uses.
+#define EC_MAX_SCALAR_BYTES 66
+#define EC_MAX_SCALAR_WORDS ((66 + BN_BYTES - 1) / BN_BYTES)
+
+OPENSSL_COMPILE_ASSERT(EC_MAX_SCALAR_WORDS <= BN_SMALL_MAX_WORDS,
+                       bn_small_functions_applicable);
+
+// An EC_SCALAR is a |BN_num_bits(order)|-bit integer. Only the first
+// |order->top| words are used. An |EC_SCALAR| is specific to an |EC_GROUP| and
+// must not be mixed between groups. Unless otherwise specified, it is fully
+// reduced modulo the |order|.
+typedef union {
+  // bytes is the representation of the scalar in little-endian order.
+  uint8_t bytes[EC_MAX_SCALAR_BYTES];
+  BN_ULONG words[EC_MAX_SCALAR_WORDS];
+} EC_SCALAR;
+
 struct ec_method_st {
   int (*group_init)(EC_GROUP *);
   void (*group_finish)(EC_GROUP *);
@@ -92,8 +114,8 @@
   // Computes |r = p_scalar*p| if g_scalar is null. At least one of |g_scalar|
   // and |p_scalar| must be non-null, and |p| must be non-null if |p_scalar| is
   // non-null.
-  int (*mul)(const EC_GROUP *group, EC_POINT *r, const BIGNUM *g_scalar,
-             const EC_POINT *p, const BIGNUM *p_scalar, BN_CTX *ctx);
+  int (*mul)(const EC_GROUP *group, EC_POINT *r, const EC_SCALAR *g_scalar,
+             const EC_POINT *p, const EC_SCALAR *p_scalar, BN_CTX *ctx);
 
   // 'field_mul' and 'field_sqr' can be used by 'add' and 'dbl' so that the
   // same implementations of point operations can be used with different
@@ -120,7 +142,7 @@
 
   int curve_name;  // optional NID for named curve
 
-  const BN_MONT_CTX *order_mont;  // data for ECDSA inverse
+  BN_MONT_CTX *order_mont;  // data for ECDSA inverse
 
   // The following members are handled by the method functions,
   // even if they appear generic
@@ -151,13 +173,28 @@
 
 EC_GROUP *ec_group_new(const EC_METHOD *meth);
 
-// ec_group_get_order_mont returns a Montgomery context for operations modulo
-// |group|'s order. It may return NULL in the case that |group| is not a
-// built-in group.
-const BN_MONT_CTX *ec_group_get_order_mont(const EC_GROUP *group);
+// ec_bignum_to_scalar converts |in| to an |EC_SCALAR| and writes it to |*out|.
+// |in| must be non-negative and have at most |BN_num_bits(&group->order)| bits.
+// It returns one on success and zero on error. It does not ensure |in| is fully
+// reduced.
+int ec_bignum_to_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                        const BIGNUM *in);
 
-int ec_wNAF_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *g_scalar,
-                const EC_POINT *p, const BIGNUM *p_scalar, BN_CTX *ctx);
+// ec_random_nonzero_scalar sets |out| to a uniformly selected random value from
+// 1 to |group->order| - 1. It returns one on success and zero on error.
+int ec_random_nonzero_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                             const uint8_t additional_data[32]);
+
+// ec_point_mul_scalar sets |r| to generator * |g_scalar| + |p| *
+// |p_scalar|. Unlike other functions which take |EC_SCALAR|, |g_scalar| and
+// |p_scalar| need not be fully reduced. They need only contain as many bits as
+// the order.
+int ec_point_mul_scalar(const EC_GROUP *group, EC_POINT *r,
+                        const EC_SCALAR *g_scalar, const EC_POINT *p,
+                        const EC_SCALAR *p_scalar, BN_CTX *ctx);
+
+int ec_wNAF_mul(const EC_GROUP *group, EC_POINT *r, const EC_SCALAR *g_scalar,
+                const EC_POINT *p, const EC_SCALAR *p_scalar, BN_CTX *ctx);
 
 // method functions in simple.c
 int ec_GFp_simple_group_init(EC_GROUP *);
@@ -175,10 +212,6 @@
                                                   const BIGNUM *x,
                                                   const BIGNUM *y,
                                                   const BIGNUM *z, BN_CTX *);
-int ec_GFp_simple_get_Jprojective_coordinates_GFp(const EC_GROUP *,
-                                                  const EC_POINT *, BIGNUM *x,
-                                                  BIGNUM *y, BIGNUM *z,
-                                                  BN_CTX *);
 int ec_GFp_simple_point_set_affine_coordinates(const EC_GROUP *, EC_POINT *,
                                                const BIGNUM *x, const BIGNUM *y,
                                                BN_CTX *);
diff --git a/src/crypto/fipsmodule/ec/p224-64.c b/src/crypto/fipsmodule/ec/p224-64.c
index 26a94b9..ba25d22 100644
--- a/src/crypto/fipsmodule/ec/p224-64.c
+++ b/src/crypto/fipsmodule/ec/p224-64.c
@@ -1038,14 +1038,13 @@
 }
 
 static int ec_GFp_nistp224_points_mul(const EC_GROUP *group, EC_POINT *r,
-                                      const BIGNUM *g_scalar, const EC_POINT *p,
-                                      const BIGNUM *p_scalar, BN_CTX *ctx) {
+                                      const EC_SCALAR *g_scalar,
+                                      const EC_POINT *p,
+                                      const EC_SCALAR *p_scalar, BN_CTX *ctx) {
   int ret = 0;
   BN_CTX *new_ctx = NULL;
   BIGNUM *x, *y, *z, *tmp_scalar;
-  p224_felem_bytearray g_secret, p_secret;
   p224_felem p_pre_comp[17][3];
-  p224_felem_bytearray tmp;
   p224_felem x_in, y_in, z_in, x_out, y_out, z_out;
 
   if (ctx == NULL) {
@@ -1067,23 +1066,7 @@
   if (p != NULL && p_scalar != NULL) {
     // We treat NULL scalars as 0, and NULL points as points at infinity, i.e.,
     // they contribute nothing to the linear combination.
-    OPENSSL_memset(&p_secret, 0, sizeof(p_secret));
     OPENSSL_memset(&p_pre_comp, 0, sizeof(p_pre_comp));
-    size_t num_bytes;
-    // reduce g_scalar to 0 <= g_scalar < 2^224
-    if (BN_num_bits(p_scalar) > 224 || BN_is_negative(p_scalar)) {
-      // this is an unusual input, and we don't guarantee
-      // constant-timeness
-      if (!BN_nnmod(tmp_scalar, p_scalar, &group->order, ctx)) {
-        OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-        goto err;
-      }
-      num_bytes = BN_bn2bin(tmp_scalar, tmp);
-    } else {
-      num_bytes = BN_bn2bin(p_scalar, tmp);
-    }
-
-    p224_flip_endian(p_secret, tmp, num_bytes);
     // precompute multiples
     if (!p224_BN_to_felem(x_out, &p->X) ||
         !p224_BN_to_felem(y_out, &p->Y) ||
@@ -1109,26 +1092,10 @@
     }
   }
 
-  if (g_scalar != NULL) {
-    OPENSSL_memset(g_secret, 0, sizeof(g_secret));
-    size_t num_bytes;
-    // reduce g_scalar to 0 <= g_scalar < 2^224
-    if (BN_num_bits(g_scalar) > 224 || BN_is_negative(g_scalar)) {
-      // this is an unusual input, and we don't guarantee constant-timeness
-      if (!BN_nnmod(tmp_scalar, g_scalar, &group->order, ctx)) {
-        OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-        goto err;
-      }
-      num_bytes = BN_bn2bin(tmp_scalar, tmp);
-    } else {
-      num_bytes = BN_bn2bin(g_scalar, tmp);
-    }
-
-    p224_flip_endian(g_secret, tmp, num_bytes);
-  }
-  p224_batch_mul(
-      x_out, y_out, z_out, (p != NULL && p_scalar != NULL) ? p_secret : NULL,
-      g_scalar != NULL ? g_secret : NULL, (const p224_felem(*)[3])p_pre_comp);
+  p224_batch_mul(x_out, y_out, z_out,
+                 (p != NULL && p_scalar != NULL) ? p_scalar->bytes : NULL,
+                 g_scalar != NULL ? g_scalar->bytes : NULL,
+                 (const p224_felem(*)[3])p_pre_comp);
 
   // reduce the output to its unique minimal representation
   p224_felem_contract(x_in, x_out);
diff --git a/src/crypto/fipsmodule/ec/p256-64.c b/src/crypto/fipsmodule/ec/p256-64.c
index 04ae56b..d4a8ff6 100644
--- a/src/crypto/fipsmodule/ec/p256-64.c
+++ b/src/crypto/fipsmodule/ec/p256-64.c
@@ -1582,14 +1582,13 @@
 }
 
 static int ec_GFp_nistp256_points_mul(const EC_GROUP *group, EC_POINT *r,
-                                      const BIGNUM *g_scalar, const EC_POINT *p,
-                                      const BIGNUM *p_scalar, BN_CTX *ctx) {
+                                      const EC_SCALAR *g_scalar,
+                                      const EC_POINT *p,
+                                      const EC_SCALAR *p_scalar, BN_CTX *ctx) {
   int ret = 0;
   BN_CTX *new_ctx = NULL;
   BIGNUM *x, *y, *z, *tmp_scalar;
-  felem_bytearray g_secret, p_secret;
   smallfelem p_pre_comp[17][3];
-  felem_bytearray tmp;
   smallfelem x_in, y_in, z_in;
   felem x_out, y_out, z_out;
 
@@ -1611,21 +1610,7 @@
   if (p != NULL && p_scalar != NULL) {
     // We treat NULL scalars as 0, and NULL points as points at infinity, i.e.,
     // they contribute nothing to the linear combination.
-    OPENSSL_memset(&p_secret, 0, sizeof(p_secret));
     OPENSSL_memset(&p_pre_comp, 0, sizeof(p_pre_comp));
-    size_t num_bytes;
-    // Reduce g_scalar to 0 <= g_scalar < 2^256.
-    if (BN_num_bits(p_scalar) > 256 || BN_is_negative(p_scalar)) {
-      // This is an unusual input, and we don't guarantee constant-timeness.
-      if (!BN_nnmod(tmp_scalar, p_scalar, &group->order, ctx)) {
-        OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-        goto err;
-      }
-      num_bytes = BN_bn2bin(tmp_scalar, tmp);
-    } else {
-      num_bytes = BN_bn2bin(p_scalar, tmp);
-    }
-    flip_endian(p_secret, tmp, num_bytes);
     // Precompute multiples.
     if (!BN_to_felem(x_out, &p->X) ||
         !BN_to_felem(y_out, &p->Y) ||
@@ -1650,28 +1635,10 @@
     }
   }
 
-  if (g_scalar != NULL) {
-    size_t num_bytes;
-
-    OPENSSL_memset(g_secret, 0, sizeof(g_secret));
-    // reduce g_scalar to 0 <= g_scalar < 2^256
-    if (BN_num_bits(g_scalar) > 256 || BN_is_negative(g_scalar)) {
-      // this is an unusual input, and we don't guarantee
-      // constant-timeness.
-      if (!BN_nnmod(tmp_scalar, g_scalar, &group->order, ctx)) {
-        OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-        goto err;
-      }
-      num_bytes = BN_bn2bin(tmp_scalar, tmp);
-    } else {
-      num_bytes = BN_bn2bin(g_scalar, tmp);
-    }
-    flip_endian(g_secret, tmp, num_bytes);
-  }
   batch_mul(x_out, y_out, z_out,
-            (p != NULL && p_scalar != NULL) ? p_secret : NULL,
-            g_scalar != NULL ? g_secret : NULL,
-            (const smallfelem(*)[3]) &p_pre_comp);
+            (p != NULL && p_scalar != NULL) ? p_scalar->bytes : NULL,
+            g_scalar != NULL ? g_scalar->bytes : NULL,
+            (const smallfelem(*)[3]) & p_pre_comp);
 
   // reduce the output to its unique minimal representation
   felem_contract(x_in, x_out);
diff --git a/src/crypto/fipsmodule/ec/p256-x86_64.c b/src/crypto/fipsmodule/ec/p256-x86_64.c
index 0004add..a9b603a 100644
--- a/src/crypto/fipsmodule/ec/p256-x86_64.c
+++ b/src/crypto/fipsmodule/ec/p256-x86_64.c
@@ -216,8 +216,8 @@
 
 // r = p * p_scalar
 static int ecp_nistz256_windowed_mul(const EC_GROUP *group, P256_POINT *r,
-                                     const EC_POINT *p, const BIGNUM *p_scalar,
-                                     BN_CTX *ctx) {
+                                     const EC_POINT *p,
+                                     const EC_SCALAR *p_scalar) {
   assert(p != NULL);
   assert(p_scalar != NULL);
 
@@ -229,55 +229,8 @@
   // ~1599 ((96 * 16) + 63) bytes of stack space.
   alignas(64) P256_POINT table[16];
   uint8_t p_str[33];
-
-
-  int ret = 0;
-  BN_CTX *new_ctx = NULL;
-  int ctx_started = 0;
-
-  if (BN_num_bits(p_scalar) > 256 || BN_is_negative(p_scalar)) {
-    if (ctx == NULL) {
-      new_ctx = BN_CTX_new();
-      if (new_ctx == NULL) {
-        OPENSSL_PUT_ERROR(EC, ERR_R_MALLOC_FAILURE);
-        goto err;
-      }
-      ctx = new_ctx;
-    }
-    BN_CTX_start(ctx);
-    ctx_started = 1;
-    BIGNUM *mod = BN_CTX_get(ctx);
-    if (mod == NULL) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_MALLOC_FAILURE);
-      goto err;
-    }
-    if (!BN_nnmod(mod, p_scalar, &group->order, ctx)) {
-      OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-      goto err;
-    }
-    p_scalar = mod;
-  }
-
-  int j;
-  for (j = 0; j < p_scalar->top * BN_BYTES; j += BN_BYTES) {
-    BN_ULONG d = p_scalar->d[j / BN_BYTES];
-
-    p_str[j + 0] = d & 0xff;
-    p_str[j + 1] = (d >> 8) & 0xff;
-    p_str[j + 2] = (d >> 16) & 0xff;
-    p_str[j + 3] = (d >>= 24) & 0xff;
-    if (BN_BYTES == 8) {
-      d >>= 8;
-      p_str[j + 4] = d & 0xff;
-      p_str[j + 5] = (d >> 8) & 0xff;
-      p_str[j + 6] = (d >> 16) & 0xff;
-      p_str[j + 7] = (d >> 24) & 0xff;
-    }
-  }
-
-  for (; j < 33; j++) {
-    p_str[j] = 0;
-  }
+  OPENSSL_memcpy(p_str, p_scalar->bytes, 32);
+  p_str[32] = 0;
 
   // table[0] is implicitly (0,0,0) (the point at infinity), therefore it is
   // not stored. All other values are actually stored with an offset of -1 in
@@ -288,7 +241,7 @@
       !ecp_nistz256_bignum_to_field_elem(row[1 - 1].Y, &p->Y) ||
       !ecp_nistz256_bignum_to_field_elem(row[1 - 1].Z, &p->Z)) {
     OPENSSL_PUT_ERROR(EC, EC_R_COORDINATES_OUT_OF_RANGE);
-    goto err;
+    return 0;
   }
 
   ecp_nistz256_point_double(&row[2 - 1], &row[1 - 1]);
@@ -354,19 +307,13 @@
 
   ecp_nistz256_point_add(r, r, &h);
 
-  ret = 1;
-
-err:
-  if (ctx_started) {
-    BN_CTX_end(ctx);
-  }
-  BN_CTX_free(new_ctx);
-  return ret;
+  return 1;
 }
 
-static int ecp_nistz256_points_mul(
-    const EC_GROUP *group, EC_POINT *r, const BIGNUM *g_scalar,
-    const EC_POINT *p_, const BIGNUM *p_scalar, BN_CTX *ctx) {
+static int ecp_nistz256_points_mul(const EC_GROUP *group, EC_POINT *r,
+                                   const EC_SCALAR *g_scalar,
+                                   const EC_POINT *p_,
+                                   const EC_SCALAR *p_scalar, BN_CTX *ctx) {
   assert((p_ != NULL) == (p_scalar != NULL));
 
   static const unsigned kWindowSize = 7;
@@ -377,54 +324,10 @@
     P256_POINT_AFFINE a;
   } t, p;
 
-  int ret = 0;
-  BN_CTX *new_ctx = NULL;
-  int ctx_started = 0;
-
   if (g_scalar != NULL) {
-    if (BN_num_bits(g_scalar) > 256 || BN_is_negative(g_scalar)) {
-      if (ctx == NULL) {
-        new_ctx = BN_CTX_new();
-        if (new_ctx == NULL) {
-          goto err;
-        }
-        ctx = new_ctx;
-      }
-      BN_CTX_start(ctx);
-      ctx_started = 1;
-      BIGNUM *tmp_scalar = BN_CTX_get(ctx);
-      if (tmp_scalar == NULL) {
-        goto err;
-      }
-
-      if (!BN_nnmod(tmp_scalar, g_scalar, &group->order, ctx)) {
-        OPENSSL_PUT_ERROR(EC, ERR_R_BN_LIB);
-        goto err;
-      }
-      g_scalar = tmp_scalar;
-    }
-
-    uint8_t p_str[33] = {0};
-    int i;
-    for (i = 0; i < g_scalar->top * BN_BYTES; i += BN_BYTES) {
-      BN_ULONG d = g_scalar->d[i / BN_BYTES];
-
-      p_str[i + 0] = d & 0xff;
-      p_str[i + 1] = (d >> 8) & 0xff;
-      p_str[i + 2] = (d >> 16) & 0xff;
-      p_str[i + 3] = (d >>= 24) & 0xff;
-      if (BN_BYTES == 8) {
-        d >>= 8;
-        p_str[i + 4] = d & 0xff;
-        p_str[i + 5] = (d >> 8) & 0xff;
-        p_str[i + 6] = (d >> 16) & 0xff;
-        p_str[i + 7] = (d >> 24) & 0xff;
-      }
-    }
-
-    for (; i < (int) sizeof(p_str); i++) {
-      p_str[i] = 0;
-    }
+    uint8_t p_str[33];
+    OPENSSL_memcpy(p_str, g_scalar->bytes, 32);
+    p_str[32] = 0;
 
     // First window
     unsigned wvalue = (p_str[0] << 1) & kMask;
@@ -445,7 +348,7 @@
     OPENSSL_memset(p.p.Z, 0, sizeof(p.p.Z));
     copy_conditional(p.p.Z, ONE, is_not_zero(wvalue >> 1));
 
-    for (i = 1; i < 37; i++) {
+    for (int i = 1; i < 37; i++) {
       unsigned off = (index - 1) / 8;
       wvalue = p_str[off] | p_str[off + 1] << 8;
       wvalue = (wvalue >> ((index - 1) % 8)) & kMask;
@@ -469,8 +372,8 @@
       out = &p.p;
     }
 
-    if (!ecp_nistz256_windowed_mul(group, out, p_, p_scalar, ctx)) {
-      goto err;
+    if (!ecp_nistz256_windowed_mul(group, out, p_, p_scalar)) {
+      return 0;
     }
 
     if (!p_is_infinity) {
@@ -485,14 +388,7 @@
     return 0;
   }
 
-  ret = 1;
-
-err:
-  if (ctx_started) {
-    BN_CTX_end(ctx);
-  }
-  BN_CTX_free(new_ctx);
-  return ret;
+  return 1;
 }
 
 static int ecp_nistz256_get_affine(const EC_GROUP *group, const EC_POINT *point,
diff --git a/src/crypto/fipsmodule/ec/simple.c b/src/crypto/fipsmodule/ec/simple.c
index f7faa45..bc39525 100644
--- a/src/crypto/fipsmodule/ec/simple.c
+++ b/src/crypto/fipsmodule/ec/simple.c
@@ -295,49 +295,6 @@
   return ret;
 }
 
-int ec_GFp_simple_get_Jprojective_coordinates_GFp(const EC_GROUP *group,
-                                                  const EC_POINT *point,
-                                                  BIGNUM *x, BIGNUM *y,
-                                                  BIGNUM *z, BN_CTX *ctx) {
-  BN_CTX *new_ctx = NULL;
-  int ret = 0;
-
-  if (group->meth->field_decode != 0) {
-    if (ctx == NULL) {
-      ctx = new_ctx = BN_CTX_new();
-      if (ctx == NULL) {
-        return 0;
-      }
-    }
-
-    if (x != NULL && !group->meth->field_decode(group, x, &point->X, ctx)) {
-      goto err;
-    }
-    if (y != NULL && !group->meth->field_decode(group, y, &point->Y, ctx)) {
-      goto err;
-    }
-    if (z != NULL && !group->meth->field_decode(group, z, &point->Z, ctx)) {
-      goto err;
-    }
-  } else {
-    if (x != NULL && !BN_copy(x, &point->X)) {
-      goto err;
-    }
-    if (y != NULL && !BN_copy(y, &point->Y)) {
-      goto err;
-    }
-    if (z != NULL && !BN_copy(z, &point->Z)) {
-      goto err;
-    }
-  }
-
-  ret = 1;
-
-err:
-  BN_CTX_free(new_ctx);
-  return ret;
-}
-
 int ec_GFp_simple_point_set_affine_coordinates(const EC_GROUP *group,
                                                EC_POINT *point, const BIGNUM *x,
                                                const BIGNUM *y, BN_CTX *ctx) {
diff --git a/src/crypto/fipsmodule/ec/wnaf.c b/src/crypto/fipsmodule/ec/wnaf.c
index 842a8fb..e3b6437 100644
--- a/src/crypto/fipsmodule/ec/wnaf.c
+++ b/src/crypto/fipsmodule/ec/wnaf.c
@@ -122,11 +122,6 @@
     sign = -1;
   }
 
-  if (scalar->d == NULL || scalar->top == 0) {
-    OPENSSL_PUT_ERROR(EC, ERR_R_INTERNAL_ERROR);
-    goto err;
-  }
-
   len = BN_num_bits(scalar);
   // The modified wNAF may be one digit longer than binary representation
   // (*ret_len will be set to the actual length, i.e. at most
@@ -236,8 +231,9 @@
   return 1;
 }
 
-int ec_wNAF_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *g_scalar,
-                const EC_POINT *p, const BIGNUM *p_scalar, BN_CTX *ctx) {
+int ec_wNAF_mul(const EC_GROUP *group, EC_POINT *r,
+                const EC_SCALAR *g_scalar_raw, const EC_POINT *p,
+                const EC_SCALAR *p_scalar_raw, BN_CTX *ctx) {
   BN_CTX *new_ctx = NULL;
   const EC_POINT *generator = NULL;
   EC_POINT *tmp = NULL;
@@ -262,13 +258,32 @@
       goto err;
     }
   }
+  BN_CTX_start(ctx);
+
+  // Convert from |EC_SCALAR| to |BIGNUM|. |BIGNUM| is not constant-time, but
+  // neither is the rest of this function.
+  BIGNUM *g_scalar = NULL, *p_scalar = NULL;
+  if (g_scalar_raw != NULL) {
+    g_scalar = BN_CTX_get(ctx);
+    if (g_scalar == NULL ||
+        !bn_set_words(g_scalar, g_scalar_raw->words, group->order.top)) {
+      goto err;
+    }
+  }
+  if (p_scalar_raw != NULL) {
+    p_scalar = BN_CTX_get(ctx);
+    if (p_scalar == NULL ||
+        !bn_set_words(p_scalar, p_scalar_raw->words, group->order.top)) {
+      goto err;
+    }
+  }
 
   // TODO: This function used to take |points| and |scalars| as arrays of
   // |num| elements. The code below should be simplified to work in terms of |p|
   // and |p_scalar|.
   size_t num = p != NULL ? 1 : 0;
   const EC_POINT **points = p != NULL ? &p : NULL;
-  const BIGNUM **scalars = p != NULL ? &p_scalar : NULL;
+  BIGNUM **scalars = p != NULL ? &p_scalar : NULL;
 
   total_num = num;
 
@@ -433,6 +448,9 @@
   ret = 1;
 
 err:
+  if (ctx != NULL) {
+    BN_CTX_end(ctx);
+  }
   BN_CTX_free(new_ctx);
   EC_POINT_free(tmp);
   OPENSSL_free(wsize);
diff --git a/src/crypto/fipsmodule/ecdsa/ecdsa.c b/src/crypto/fipsmodule/ecdsa/ecdsa.c
index 0e2adb6..319a934 100644
--- a/src/crypto/fipsmodule/ecdsa/ecdsa.c
+++ b/src/crypto/fipsmodule/ecdsa/ecdsa.c
@@ -58,37 +58,72 @@
 #include <openssl/bn.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
+#include <openssl/sha.h>
+#include <openssl/type_check.h>
 
 #include "../bn/internal.h"
 #include "../ec/internal.h"
 #include "../../internal.h"
 
 
-// digest_to_bn interprets |digest_len| bytes from |digest| as a big-endian
-// number and sets |out| to that value. It then truncates |out| so that it's,
-// at most, as long as |order|. It returns one on success and zero otherwise.
-static int digest_to_bn(BIGNUM *out, const uint8_t *digest, size_t digest_len,
-                        const BIGNUM *order) {
-  size_t num_bits;
-
-  num_bits = BN_num_bits(order);
-  // Need to truncate digest if it is too long: first truncate whole
-  // bytes.
+// digest_to_scalar interprets |digest_len| bytes from |digest| as a scalar for
+// ECDSA. Note this value is not fully reduced modulo the order, only the
+// correct number of bits.
+static void digest_to_scalar(const EC_GROUP *group, EC_SCALAR *out,
+                             const uint8_t *digest, size_t digest_len) {
+  const BIGNUM *order = &group->order;
+  size_t num_bits = BN_num_bits(order);
+  // Need to truncate digest if it is too long: first truncate whole bytes.
   if (8 * digest_len > num_bits) {
     digest_len = (num_bits + 7) / 8;
   }
-  if (!BN_bin2bn(digest, digest_len, out)) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
-    return 0;
+  OPENSSL_memset(out, 0, sizeof(EC_SCALAR));
+  for (size_t i = 0; i < digest_len; i++) {
+    out->bytes[i] = digest[digest_len - 1 - i];
   }
 
   // If still too long truncate remaining bits with a shift
-  if ((8 * digest_len > num_bits) &&
-      !BN_rshift(out, out, 8 - (num_bits & 0x7))) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
+  if (8 * digest_len > num_bits) {
+    size_t shift = 8 - (num_bits & 0x7);
+    for (int i = 0; i < order->top - 1; i++) {
+      out->words[i] =
+          (out->words[i] >> shift) | (out->words[i + 1] << (BN_BITS2 - shift));
+    }
+    out->words[order->top - 1] >>= shift;
+  }
+}
+
+// field_element_to_scalar reduces |r| modulo |group->order|. |r| must
+// previously have been reduced modulo |group->field|.
+static int field_element_to_scalar(const EC_GROUP *group, BIGNUM *r) {
+  // We must have p < 2×order, assuming p is not tiny (p >= 17). Thus rather we
+  // can reduce by performing at most one subtraction.
+  //
+  // Proof: We only work with prime order curves, so the number of points on
+  // the curve is the order. Thus Hasse's theorem gives:
+  //
+  //     |order - (p + 1)| <= 2×sqrt(p)
+  //         p + 1 - order <= 2×sqrt(p)
+  //     p + 1 - 2×sqrt(p) <= order
+  //       p + 1 - 2×(p/4)  < order       (p/4 > sqrt(p) for p >= 17)
+  //         p/2 < p/2 + 1  < order
+  //                     p  < 2×order
+  //
+  // Additionally, one can manually check this property for built-in curves. It
+  // is enforced for legacy custom curves in |EC_GROUP_set_generator|.
+  //
+  // TODO(davidben): Introduce |EC_FIELD_ELEMENT|, make this a function from
+  // |EC_FIELD_ELEMENT| to |EC_SCALAR|, and cut out the |BIGNUM|. Does this need
+  // to be constant-time for signing? |r| is the x-coordinate for kG, which is
+  // public unless k was rerolled because |s| was zero.
+  assert(!BN_is_negative(r));
+  assert(BN_cmp(r, &group->field) < 0);
+  if (BN_cmp(r, &group->order) >= 0 &&
+      !BN_sub(r, r, &group->order)) {
     return 0;
   }
-
+  assert(!BN_is_negative(r));
+  assert(BN_cmp(r, &group->order) < 0);
   return 1;
 }
 
@@ -116,67 +151,87 @@
   OPENSSL_free(sig);
 }
 
-ECDSA_SIG *ECDSA_do_sign(const uint8_t *digest, size_t digest_len,
-                         const EC_KEY *key) {
-  return ECDSA_do_sign_ex(digest, digest_len, NULL, NULL, key);
+void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **out_r,
+                    const BIGNUM **out_s) {
+  if (out_r != NULL) {
+    *out_r = sig->r;
+  }
+  if (out_s != NULL) {
+    *out_s = sig->s;
+  }
+}
+
+int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s) {
+  if (r == NULL || s == NULL) {
+    return 0;
+  }
+  BN_free(sig->r);
+  BN_free(sig->s);
+  sig->r = r;
+  sig->s = s;
+  return 1;
 }
 
 int ECDSA_do_verify(const uint8_t *digest, size_t digest_len,
                     const ECDSA_SIG *sig, const EC_KEY *eckey) {
-  int ret = 0;
-  BN_CTX *ctx;
-  BIGNUM *u1, *u2, *m, *X;
-  EC_POINT *point = NULL;
-  const EC_GROUP *group;
-  const EC_POINT *pub_key;
-
-  // check input values
-  if ((group = EC_KEY_get0_group(eckey)) == NULL ||
-      (pub_key = EC_KEY_get0_public_key(eckey)) == NULL ||
-      sig == NULL) {
+  const EC_GROUP *group = EC_KEY_get0_group(eckey);
+  const EC_POINT *pub_key = EC_KEY_get0_public_key(eckey);
+  if (group == NULL || pub_key == NULL || sig == NULL) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_MISSING_PARAMETERS);
     return 0;
   }
 
-  ctx = BN_CTX_new();
+  BN_CTX *ctx = BN_CTX_new();
   if (!ctx) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_MALLOC_FAILURE);
     return 0;
   }
+  int ret = 0;
+  EC_POINT *point = NULL;
   BN_CTX_start(ctx);
-  u1 = BN_CTX_get(ctx);
-  u2 = BN_CTX_get(ctx);
-  m = BN_CTX_get(ctx);
-  X = BN_CTX_get(ctx);
-  if (u1 == NULL || u2 == NULL || m == NULL || X == NULL) {
+  BIGNUM *X = BN_CTX_get(ctx);
+  if (X == NULL) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
     goto err;
   }
 
+  EC_SCALAR r, s, m, u1, u2, s_inv_mont;
   const BIGNUM *order = EC_GROUP_get0_order(group);
-  if (BN_is_zero(sig->r) || BN_is_negative(sig->r) ||
-      BN_ucmp(sig->r, order) >= 0 || BN_is_zero(sig->s) ||
-      BN_is_negative(sig->s) || BN_ucmp(sig->s, order) >= 0) {
+  if (BN_is_zero(sig->r) ||
+      BN_is_negative(sig->r) ||
+      BN_ucmp(sig->r, order) >= 0 ||
+      !ec_bignum_to_scalar(group, &r, sig->r) ||
+      BN_is_zero(sig->s) ||
+      BN_is_negative(sig->s) ||
+      BN_ucmp(sig->s, order) >= 0 ||
+      !ec_bignum_to_scalar(group, &s, sig->s)) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_BAD_SIGNATURE);
     goto err;
   }
-  // tmp = inv(s) mod order
+  // s_inv_mont = s^-1 mod order. We convert the result to Montgomery form for
+  // the products below.
   int no_inverse;
-  if (!BN_mod_inverse_odd(u2, &no_inverse, sig->s, order, ctx)) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
+  if (!BN_mod_inverse_odd(X, &no_inverse, sig->s, order, ctx) ||
+      !ec_bignum_to_scalar(group, &s_inv_mont, X) ||
+      !bn_to_montgomery_small(s_inv_mont.words, order->top, s_inv_mont.words,
+                              order->top, group->order_mont)) {
     goto err;
   }
-  if (!digest_to_bn(m, digest, digest_len, order)) {
-    goto err;
-  }
-  // u1 = m * tmp mod order
-  if (!BN_mod_mul(u1, m, u2, order, ctx)) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
-    goto err;
-  }
-  // u2 = r * tmp mod order
-  if (!BN_mod_mul(u2, sig->r, u2, order, ctx)) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
+  // u1 = m * s_inv_mont mod order
+  // u2 = r * s_inv_mont mod order
+  //
+  // |s_inv_mont| is in Montgomery form while |m| and |r| are not, so |u1| and
+  // |u2| will be taken out of Montgomery form, as desired. Note that, although
+  // |m| is not fully reduced, |bn_mod_mul_montgomery_small| only requires the
+  // product not exceed R * |order|. |s_inv_mont| is fully reduced and |m| <
+  // 2^BN_num_bits(order) <= R, so this holds.
+  digest_to_scalar(group, &m, digest, digest_len);
+  if (!bn_mod_mul_montgomery_small(u1.words, order->top, m.words, order->top,
+                                   s_inv_mont.words, order->top,
+                                   group->order_mont) ||
+      !bn_mod_mul_montgomery_small(u2.words, order->top, r.words, order->top,
+                                   s_inv_mont.words, order->top,
+                                   group->order_mont)) {
     goto err;
   }
 
@@ -185,7 +240,7 @@
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_MALLOC_FAILURE);
     goto err;
   }
-  if (!EC_POINT_mul(group, point, u1, pub_key, u2, ctx)) {
+  if (!ec_point_mul_scalar(group, point, &u1, pub_key, &u2, ctx)) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_EC_LIB);
     goto err;
   }
@@ -193,12 +248,12 @@
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_EC_LIB);
     goto err;
   }
-  if (!BN_nnmod(u1, X, order, ctx)) {
+  if (!field_element_to_scalar(group, X)) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
     goto err;
   }
-  // if the signature is correct u1 is equal to sig->r
-  if (BN_ucmp(u1, sig->r) != 0) {
+  // The signature is correct iff |X| is equal to |sig->r|.
+  if (BN_ucmp(X, sig->r) != 0) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_BAD_SIGNATURE);
     goto err;
   }
@@ -212,45 +267,26 @@
   return ret;
 }
 
-static int ecdsa_sign_setup(const EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp,
-                            BIGNUM **rp, const uint8_t *digest,
-                            size_t digest_len) {
-  BN_CTX *ctx = NULL;
-  BIGNUM *k = NULL, *kinv = NULL, *r = NULL, *tmp = NULL;
+static int ecdsa_sign_setup(const EC_KEY *eckey, BN_CTX *ctx,
+                            EC_SCALAR *out_kinv_mont, BIGNUM **rp,
+                            const uint8_t *digest, size_t digest_len,
+                            const EC_SCALAR *priv_key) {
   EC_POINT *tmp_point = NULL;
-  const EC_GROUP *group;
   int ret = 0;
-
-  if (eckey == NULL || (group = EC_KEY_get0_group(eckey)) == NULL) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_PASSED_NULL_PARAMETER);
-    return 0;
-  }
-
-  if (ctx_in == NULL) {
-    if ((ctx = BN_CTX_new()) == NULL) {
-      OPENSSL_PUT_ERROR(ECDSA, ERR_R_MALLOC_FAILURE);
-      return 0;
-    }
-  } else {
-    ctx = ctx_in;
-  }
-
-  k = BN_new();
-  kinv = BN_new();  // this value is later returned in *kinvp
-  r = BN_new();  // this value is later returned in *rp
-  tmp = BN_new();
-  if (k == NULL || kinv == NULL || r == NULL || tmp == NULL) {
+  EC_SCALAR k;
+  BIGNUM *r = BN_new();  // this value is later returned in *rp
+  if (r == NULL) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_MALLOC_FAILURE);
     goto err;
   }
+  const EC_GROUP *group = EC_KEY_get0_group(eckey);
+  const BIGNUM *order = EC_GROUP_get0_order(group);
   tmp_point = EC_POINT_new(group);
   if (tmp_point == NULL) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_EC_LIB);
     goto err;
   }
 
-  const BIGNUM *order = EC_GROUP_get0_order(group);
-
   // Check that the size of the group order is FIPS compliant (FIPS 186-4
   // B.5.2).
   if (BN_num_bits(order) < 160) {
@@ -259,169 +295,130 @@
   }
 
   do {
-    // If possible, we'll include the private key and message digest in the k
-    // generation. The |digest| argument is only empty if |ECDSA_sign_setup| is
-    // being used.
+    // Include the private key and message digest in the k generation.
     if (eckey->fixed_k != NULL) {
-      if (!BN_copy(k, eckey->fixed_k)) {
+      if (!ec_bignum_to_scalar(group, &k, eckey->fixed_k)) {
         goto err;
       }
-    } else if (digest_len > 0) {
-      if (!BN_generate_dsa_nonce(k, order, EC_KEY_get0_private_key(eckey),
-                                 digest, digest_len, ctx)) {
-        OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED);
-        goto err;
-      }
-    } else if (!BN_rand_range_ex(k, 1, order)) {
-      OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED);
-      goto err;
-    }
-
-    // Compute the inverse of k. The order is a prime, so use Fermat's Little
-    // Theorem. Note |ec_group_get_order_mont| may return NULL but
-    // |bn_mod_inverse_prime| allows this.
-    if (!bn_mod_inverse_prime(kinv, k, order, ctx,
-                              ec_group_get_order_mont(group))) {
-      OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
-      goto err;
-    }
-
-    // We do not want timing information to leak the length of k,
-    // so we compute G*k using an equivalent scalar of fixed
-    // bit-length.
-
-    if (!BN_add(k, k, order)) {
-      goto err;
-    }
-    if (BN_num_bits(k) <= BN_num_bits(order)) {
-      if (!BN_add(k, k, order)) {
+    } else {
+      // Pass a SHA512 hash of the private key and digest as additional data
+      // into the RBG. This is a hardening measure against entropy failure.
+      OPENSSL_COMPILE_ASSERT(SHA512_DIGEST_LENGTH >= 32,
+                             additional_data_is_too_large_for_sha512);
+      SHA512_CTX sha;
+      uint8_t additional_data[SHA512_DIGEST_LENGTH];
+      SHA512_Init(&sha);
+      SHA512_Update(&sha, priv_key->words, order->top * sizeof(BN_ULONG));
+      SHA512_Update(&sha, digest, digest_len);
+      SHA512_Final(additional_data, &sha);
+      if (!ec_random_nonzero_scalar(group, &k, additional_data)) {
         goto err;
       }
     }
 
-    // compute r the x-coordinate of generator * k
-    if (!EC_POINT_mul(group, tmp_point, k, NULL, NULL, ctx)) {
-      OPENSSL_PUT_ERROR(ECDSA, ERR_R_EC_LIB);
+    // Compute k^-1. We leave it in the Montgomery domain as an optimization for
+    // later operations.
+    if (!bn_to_montgomery_small(out_kinv_mont->words, order->top, k.words,
+                                order->top, group->order_mont) ||
+        !bn_mod_inverse_prime_mont_small(out_kinv_mont->words, order->top,
+                                         out_kinv_mont->words, order->top,
+                                         group->order_mont)) {
       goto err;
     }
-    if (!EC_POINT_get_affine_coordinates_GFp(group, tmp_point, tmp, NULL,
+
+    // Compute r, the x-coordinate of generator * k.
+    if (!ec_point_mul_scalar(group, tmp_point, &k, NULL, NULL, ctx) ||
+        !EC_POINT_get_affine_coordinates_GFp(group, tmp_point, r, NULL,
                                              ctx)) {
-      OPENSSL_PUT_ERROR(ECDSA, ERR_R_EC_LIB);
       goto err;
     }
 
-    if (!BN_nnmod(r, tmp, order, ctx)) {
-      OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
+    if (!field_element_to_scalar(group, r)) {
       goto err;
     }
   } while (BN_is_zero(r));
 
-  // clear old values if necessary
   BN_clear_free(*rp);
-  BN_clear_free(*kinvp);
-
-  // save the pre-computed values
   *rp = r;
-  *kinvp = kinv;
+  r = NULL;
   ret = 1;
 
 err:
-  BN_clear_free(k);
-  if (!ret) {
-    BN_clear_free(kinv);
-    BN_clear_free(r);
-  }
-  if (ctx_in == NULL) {
-    BN_CTX_free(ctx);
-  }
+  OPENSSL_cleanse(&k, sizeof(k));
+  BN_clear_free(r);
   EC_POINT_free(tmp_point);
-  BN_clear_free(tmp);
   return ret;
 }
 
-int ECDSA_sign_setup(const EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv,
-                     BIGNUM **rp) {
-  return ecdsa_sign_setup(eckey, ctx, kinv, rp, NULL, 0);
-}
-
-ECDSA_SIG *ECDSA_do_sign_ex(const uint8_t *digest, size_t digest_len,
-                            const BIGNUM *in_kinv, const BIGNUM *in_r,
-                            const EC_KEY *eckey) {
-  int ok = 0;
-  BIGNUM *kinv = NULL, *s, *m = NULL, *tmp = NULL;
-  const BIGNUM *ckinv;
-  BN_CTX *ctx = NULL;
-  const EC_GROUP *group;
-  ECDSA_SIG *ret;
-  const BIGNUM *priv_key;
-
+ECDSA_SIG *ECDSA_do_sign(const uint8_t *digest, size_t digest_len,
+                         const EC_KEY *eckey) {
   if (eckey->ecdsa_meth && eckey->ecdsa_meth->sign) {
     OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_NOT_IMPLEMENTED);
     return NULL;
   }
 
-  group = EC_KEY_get0_group(eckey);
-  priv_key = EC_KEY_get0_private_key(eckey);
-
-  if (group == NULL || priv_key == NULL) {
+  const EC_GROUP *group = EC_KEY_get0_group(eckey);
+  const BIGNUM *priv_key_bn = EC_KEY_get0_private_key(eckey);
+  if (group == NULL || priv_key_bn == NULL) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_PASSED_NULL_PARAMETER);
     return NULL;
   }
+  const BIGNUM *order = EC_GROUP_get0_order(group);
 
-  ret = ECDSA_SIG_new();
-  if (!ret) {
+  int ok = 0;
+  ECDSA_SIG *ret = ECDSA_SIG_new();
+  BN_CTX *ctx = BN_CTX_new();
+  EC_SCALAR kinv_mont, priv_key, r_mont, s, tmp, m;
+  if (ret == NULL || ctx == NULL) {
     OPENSSL_PUT_ERROR(ECDSA, ERR_R_MALLOC_FAILURE);
     return NULL;
   }
-  s = ret->s;
 
-  if ((ctx = BN_CTX_new()) == NULL ||
-      (tmp = BN_new()) == NULL ||
-      (m = BN_new()) == NULL) {
-    OPENSSL_PUT_ERROR(ECDSA, ERR_R_MALLOC_FAILURE);
-    goto err;
-  }
-
-  const BIGNUM *order = EC_GROUP_get0_order(group);
-
-  if (!digest_to_bn(m, digest, digest_len, order)) {
+  digest_to_scalar(group, &m, digest, digest_len);
+  if (!ec_bignum_to_scalar(group, &priv_key, priv_key_bn)) {
     goto err;
   }
   for (;;) {
-    if (in_kinv == NULL || in_r == NULL) {
-      if (!ecdsa_sign_setup(eckey, ctx, &kinv, &ret->r, digest, digest_len)) {
-        OPENSSL_PUT_ERROR(ECDSA, ERR_R_ECDSA_LIB);
-        goto err;
-      }
-      ckinv = kinv;
-    } else {
-      ckinv = in_kinv;
-      if (BN_copy(ret->r, in_r) == NULL) {
-        OPENSSL_PUT_ERROR(ECDSA, ERR_R_MALLOC_FAILURE);
-        goto err;
-      }
+    if (!ecdsa_sign_setup(eckey, ctx, &kinv_mont, &ret->r, digest, digest_len,
+                          &priv_key)) {
+      goto err;
     }
 
-    if (!BN_mod_mul(tmp, priv_key, ret->r, order, ctx)) {
-      OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
+    // Compute priv_key * r (mod order). Note if only one parameter is in the
+    // Montgomery domain, |bn_mod_mul_montgomery_small| will compute the answer
+    // in the normal domain.
+    if (!ec_bignum_to_scalar(group, &r_mont, ret->r) ||
+        !bn_to_montgomery_small(r_mont.words, order->top, r_mont.words,
+                                order->top, group->order_mont) ||
+        !bn_mod_mul_montgomery_small(s.words, order->top, priv_key.words,
+                                     order->top, r_mont.words, order->top,
+                                     group->order_mont)) {
       goto err;
     }
-    if (!BN_mod_add_quick(s, tmp, m, order)) {
-      OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
+
+    // Compute s += m in constant time. Reduce one copy of |order| if necessary.
+    // Note this does not leave |s| fully reduced. We have
+    // |m| < 2^BN_num_bits(order), so subtracting |order| leaves
+    // 0 <= |s| < 2^BN_num_bits(order).
+    BN_ULONG carry = bn_add_words(s.words, s.words, m.words, order->top);
+    BN_ULONG v = bn_sub_words(tmp.words, s.words, order->d, order->top) - carry;
+    v = 0u - v;
+    for (int i = 0; i < order->top; i++) {
+      s.words[i] = constant_time_select_w(v, s.words[i], tmp.words[i]);
+    }
+
+    // Finally, multiply s by k^-1. That was retained in Montgomery form, so the
+    // same technique as the previous multiplication works. Although the
+    // previous step did not fully reduce |s|, |bn_mod_mul_montgomery_small|
+    // only requires the product not exceed R * |order|. |kinv_mont| is fully
+    // reduced and |s| < 2^BN_num_bits(order) <= R, so this holds.
+    if (!bn_mod_mul_montgomery_small(s.words, order->top, s.words, order->top,
+                                     kinv_mont.words, order->top,
+                                     group->order_mont) ||
+        !bn_set_words(ret->s, s.words, order->top)) {
       goto err;
     }
-    if (!BN_mod_mul(s, s, ckinv, order, ctx)) {
-      OPENSSL_PUT_ERROR(ECDSA, ERR_R_BN_LIB);
-      goto err;
-    }
-    if (BN_is_zero(s)) {
-      // if kinv and r have been supplied by the caller
-      // don't to generate new kinv and r values
-      if (in_kinv != NULL && in_r != NULL) {
-        OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_NEED_NEW_SETUP_VALUES);
-        goto err;
-      }
-    } else {
+    if (!BN_is_zero(ret->s)) {
       // s != 0 => we have a valid signature
       break;
     }
@@ -435,8 +432,11 @@
     ret = NULL;
   }
   BN_CTX_free(ctx);
-  BN_clear_free(m);
-  BN_clear_free(tmp);
-  BN_clear_free(kinv);
+  OPENSSL_cleanse(&kinv_mont, sizeof(kinv_mont));
+  OPENSSL_cleanse(&priv_key, sizeof(priv_key));
+  OPENSSL_cleanse(&r_mont, sizeof(r_mont));
+  OPENSSL_cleanse(&s, sizeof(s));
+  OPENSSL_cleanse(&tmp, sizeof(tmp));
+  OPENSSL_cleanse(&m, sizeof(m));
   return ret;
 }
diff --git a/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc b/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc
index 837b95d..258c128 100644
--- a/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc
+++ b/src/crypto/fipsmodule/ecdsa/ecdsa_test.cc
@@ -64,6 +64,7 @@
 #include <openssl/nid.h>
 #include <openssl/rand.h>
 
+#include "../ec/internal.h"
 #include "../../test/file_test.h"
 
 
@@ -282,6 +283,32 @@
   return nullptr;
 }
 
+static bssl::UniquePtr<EC_GROUP> MakeCustomClone(const EC_GROUP *group) {
+  bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
+  bssl::UniquePtr<BIGNUM> p(BN_new()), a(BN_new()), b(BN_new()), x(BN_new()),
+      y(BN_new());
+  if (!ctx || !p || !a || !b || !x || !y ||
+      !EC_GROUP_get_curve_GFp(group, p.get(), a.get(), b.get(), ctx.get()) ||
+      !EC_POINT_get_affine_coordinates_GFp(
+          group, EC_GROUP_get0_generator(group), x.get(), y.get(), ctx.get())) {
+    return nullptr;
+  }
+  bssl::UniquePtr<EC_GROUP> ret(
+      EC_GROUP_new_curve_GFp(p.get(), a.get(), b.get(), ctx.get()));
+  if (!ret) {
+    return nullptr;
+  }
+  bssl::UniquePtr<EC_POINT> g(EC_POINT_new(ret.get()));
+  if (!g ||
+      !EC_POINT_set_affine_coordinates_GFp(ret.get(), g.get(), x.get(), y.get(),
+                                           ctx.get()) ||
+      !EC_GROUP_set_generator(ret.get(), g.get(), EC_GROUP_get0_order(group),
+                              BN_value_one())) {
+    return nullptr;
+  }
+  return ret;
+}
+
 static bssl::UniquePtr<BIGNUM> GetBIGNUM(FileTest *t, const char *key) {
   std::vector<uint8_t> bytes;
   if (!t->GetBytes(&bytes, key)) {
@@ -294,80 +321,90 @@
 TEST(ECDSATest, VerifyTestVectors) {
   FileTestGTest("crypto/fipsmodule/ecdsa/ecdsa_verify_tests.txt",
                 [](FileTest *t) {
-    bssl::UniquePtr<EC_GROUP> group = GetCurve(t, "Curve");
-    ASSERT_TRUE(group);
-    bssl::UniquePtr<BIGNUM> x = GetBIGNUM(t, "X");
-    ASSERT_TRUE(x);
-    bssl::UniquePtr<BIGNUM> y = GetBIGNUM(t, "Y");
-    ASSERT_TRUE(y);
-    bssl::UniquePtr<BIGNUM> r = GetBIGNUM(t, "R");
-    ASSERT_TRUE(r);
-    bssl::UniquePtr<BIGNUM> s = GetBIGNUM(t, "S");
-    ASSERT_TRUE(s);
-    std::vector<uint8_t> digest;
-    ASSERT_TRUE(t->GetBytes(&digest, "Digest"));
+    for (bool custom_group : {false, true}) {
+      SCOPED_TRACE(custom_group);
+      bssl::UniquePtr<EC_GROUP> group = GetCurve(t, "Curve");
+      ASSERT_TRUE(group);
+      if (custom_group) {
+        group = MakeCustomClone(group.get());
+        ASSERT_TRUE(group);
+      }
+      bssl::UniquePtr<BIGNUM> x = GetBIGNUM(t, "X");
+      ASSERT_TRUE(x);
+      bssl::UniquePtr<BIGNUM> y = GetBIGNUM(t, "Y");
+      ASSERT_TRUE(y);
+      bssl::UniquePtr<BIGNUM> r = GetBIGNUM(t, "R");
+      ASSERT_TRUE(r);
+      bssl::UniquePtr<BIGNUM> s = GetBIGNUM(t, "S");
+      ASSERT_TRUE(s);
+      std::vector<uint8_t> digest;
+      ASSERT_TRUE(t->GetBytes(&digest, "Digest"));
 
-    bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
-    ASSERT_TRUE(key);
-    bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(group.get()));
-    ASSERT_TRUE(pub_key);
-    bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_new());
-    ASSERT_TRUE(sig);
-    ASSERT_TRUE(EC_KEY_set_group(key.get(), group.get()));
-    ASSERT_TRUE(EC_POINT_set_affine_coordinates_GFp(group.get(), pub_key.get(),
-                                                    x.get(), y.get(), nullptr));
-    ASSERT_TRUE(EC_KEY_set_public_key(key.get(), pub_key.get()));
-    ASSERT_TRUE(BN_copy(sig->r, r.get()));
-    ASSERT_TRUE(BN_copy(sig->s, s.get()));
+      bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
+      ASSERT_TRUE(key);
+      bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(group.get()));
+      ASSERT_TRUE(pub_key);
+      bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_new());
+      ASSERT_TRUE(sig);
+      ASSERT_TRUE(EC_KEY_set_group(key.get(), group.get()));
+      ASSERT_TRUE(EC_POINT_set_affine_coordinates_GFp(
+          group.get(), pub_key.get(), x.get(), y.get(), nullptr));
+      ASSERT_TRUE(EC_KEY_set_public_key(key.get(), pub_key.get()));
+      ASSERT_TRUE(BN_copy(sig->r, r.get()));
+      ASSERT_TRUE(BN_copy(sig->s, s.get()));
 
-    EXPECT_EQ(
-        t->HasAttribute("Invalid") ? 0 : 1,
-        ECDSA_do_verify(digest.data(), digest.size(), sig.get(), key.get()));
+      EXPECT_EQ(
+          t->HasAttribute("Invalid") ? 0 : 1,
+          ECDSA_do_verify(digest.data(), digest.size(), sig.get(), key.get()));
+    }
   });
 }
 
 TEST(ECDSATest, SignTestVectors) {
   FileTestGTest("crypto/fipsmodule/ecdsa/ecdsa_sign_tests.txt",
                 [](FileTest *t) {
-    bssl::UniquePtr<EC_GROUP> group = GetCurve(t, "Curve");
-    ASSERT_TRUE(group);
-    bssl::UniquePtr<BIGNUM> priv_key = GetBIGNUM(t, "Private");
-    ASSERT_TRUE(priv_key);
-    bssl::UniquePtr<BIGNUM> x = GetBIGNUM(t, "X");
-    ASSERT_TRUE(x);
-    bssl::UniquePtr<BIGNUM> y = GetBIGNUM(t, "Y");
-    ASSERT_TRUE(y);
-    bssl::UniquePtr<BIGNUM> k = GetBIGNUM(t, "K");
-    ASSERT_TRUE(k);
-    bssl::UniquePtr<BIGNUM> r = GetBIGNUM(t, "R");
-    ASSERT_TRUE(r);
-    bssl::UniquePtr<BIGNUM> s = GetBIGNUM(t, "S");
-    ASSERT_TRUE(s);
-    std::vector<uint8_t> digest;
-    ASSERT_TRUE(t->GetBytes(&digest, "Digest"));
+    for (bool custom_group : {false, true}) {
+      SCOPED_TRACE(custom_group);
+      bssl::UniquePtr<EC_GROUP> group = GetCurve(t, "Curve");
+      ASSERT_TRUE(group);
+      if (custom_group) {
+        group = MakeCustomClone(group.get());
+        ASSERT_TRUE(group);
+      }
+      bssl::UniquePtr<BIGNUM> priv_key = GetBIGNUM(t, "Private");
+      ASSERT_TRUE(priv_key);
+      bssl::UniquePtr<BIGNUM> x = GetBIGNUM(t, "X");
+      ASSERT_TRUE(x);
+      bssl::UniquePtr<BIGNUM> y = GetBIGNUM(t, "Y");
+      ASSERT_TRUE(y);
+      bssl::UniquePtr<BIGNUM> k = GetBIGNUM(t, "K");
+      ASSERT_TRUE(k);
+      bssl::UniquePtr<BIGNUM> r = GetBIGNUM(t, "R");
+      ASSERT_TRUE(r);
+      bssl::UniquePtr<BIGNUM> s = GetBIGNUM(t, "S");
+      ASSERT_TRUE(s);
+      std::vector<uint8_t> digest;
+      ASSERT_TRUE(t->GetBytes(&digest, "Digest"));
 
-    bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
-    ASSERT_TRUE(key);
-    bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(group.get()));
-    ASSERT_TRUE(pub_key);
-    ASSERT_TRUE(EC_KEY_set_group(key.get(), group.get()));
-    ASSERT_TRUE(EC_KEY_set_private_key(key.get(), priv_key.get()));
-    ASSERT_TRUE(EC_POINT_set_affine_coordinates_GFp(group.get(), pub_key.get(),
-                                                    x.get(), y.get(), nullptr));
-    ASSERT_TRUE(EC_KEY_set_public_key(key.get(), pub_key.get()));
-    ASSERT_TRUE(EC_KEY_check_key(key.get()));
+      bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
+      ASSERT_TRUE(key);
+      bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(group.get()));
+      ASSERT_TRUE(pub_key);
+      ASSERT_TRUE(EC_KEY_set_group(key.get(), group.get()));
+      ASSERT_TRUE(EC_KEY_set_private_key(key.get(), priv_key.get()));
+      ASSERT_TRUE(EC_POINT_set_affine_coordinates_GFp(
+          group.get(), pub_key.get(), x.get(), y.get(), nullptr));
+      ASSERT_TRUE(EC_KEY_set_public_key(key.get(), pub_key.get()));
+      ASSERT_TRUE(EC_KEY_check_key(key.get()));
 
-    // |ECDSA_do_sign_ex| expects |k| to already be inverted.
-    bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
-    ASSERT_TRUE(ctx);
-    ASSERT_TRUE(BN_mod_inverse(k.get(), k.get(),
-                               EC_GROUP_get0_order(group.get()), ctx.get()));
+      // Set the fixed k for testing purposes.
+      key->fixed_k = k.release();
+      bssl::UniquePtr<ECDSA_SIG> sig(
+          ECDSA_do_sign(digest.data(), digest.size(), key.get()));
+      ASSERT_TRUE(sig);
 
-    bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_do_sign_ex(
-        digest.data(), digest.size(), k.get(), r.get(), key.get()));
-    ASSERT_TRUE(sig);
-
-    EXPECT_EQ(0, BN_cmp(r.get(), sig->r));
-    EXPECT_EQ(0, BN_cmp(s.get(), sig->s));
+      EXPECT_EQ(0, BN_cmp(r.get(), sig->r));
+      EXPECT_EQ(0, BN_cmp(s.get(), sig->s));
+    }
   });
 }
diff --git a/src/crypto/fipsmodule/rsa/internal.h b/src/crypto/fipsmodule/rsa/internal.h
index 67f2cb9..0f0c763 100644
--- a/src/crypto/fipsmodule/rsa/internal.h
+++ b/src/crypto/fipsmodule/rsa/internal.h
@@ -119,10 +119,6 @@
 extern const BN_ULONG kBoringSSLRSASqrtTwo[];
 extern const size_t kBoringSSLRSASqrtTwoLen;
 
-// rsa_less_than_words returns one if |a| < |b| and zero otherwise, where |a|
-// and |b| both are |len| words long. It runs in constant time.
-int rsa_less_than_words(const BN_ULONG *a, const BN_ULONG *b, size_t len);
-
 // rsa_greater_than_pow2 returns one if |b| is greater than 2^|n| and zero
 // otherwise.
 int rsa_greater_than_pow2(const BIGNUM *b, int n);
diff --git a/src/crypto/fipsmodule/rsa/rsa.c b/src/crypto/fipsmodule/rsa/rsa.c
index d001d7b..4a84314 100644
--- a/src/crypto/fipsmodule/rsa/rsa.c
+++ b/src/crypto/fipsmodule/rsa/rsa.c
@@ -157,6 +157,8 @@
   return 1;
 }
 
+unsigned RSA_bits(const RSA *rsa) { return BN_num_bits(rsa->n); }
+
 void RSA_get0_key(const RSA *rsa, const BIGNUM **out_n, const BIGNUM **out_e,
                   const BIGNUM **out_d) {
   if (out_n != NULL) {
diff --git a/src/crypto/fipsmodule/rsa/rsa_impl.c b/src/crypto/fipsmodule/rsa/rsa_impl.c
index adbb69f..fb27320 100644
--- a/src/crypto/fipsmodule/rsa/rsa_impl.c
+++ b/src/crypto/fipsmodule/rsa/rsa_impl.c
@@ -775,19 +775,6 @@
 };
 const size_t kBoringSSLRSASqrtTwoLen = OPENSSL_ARRAY_SIZE(kBoringSSLRSASqrtTwo);
 
-int rsa_less_than_words(const BN_ULONG *a, const BN_ULONG *b, size_t len) {
-  OPENSSL_COMPILE_ASSERT(sizeof(BN_ULONG) <= sizeof(crypto_word_t),
-                         crypto_word_t_too_small);
-  int ret = 0;
-  // Process the words in little-endian order.
-  for (size_t i = 0; i < len; i++) {
-    crypto_word_t eq = constant_time_eq_w(a[i], b[i]);
-    crypto_word_t lt = constant_time_lt_w(a[i], b[i]);
-    ret = constant_time_select_int(eq, ret, constant_time_select_int(lt, 1, 0));
-  }
-  return ret;
-}
-
 int rsa_greater_than_pow2(const BIGNUM *b, int n) {
   if (BN_is_negative(b) || n == INT_MAX) {
     return 0;
@@ -866,7 +853,7 @@
     if (to_check > out_len) {
       to_check = out_len;
     }
-    if (!rsa_less_than_words(
+    if (!bn_less_than_words(
             kBoringSSLRSASqrtTwo + kBoringSSLRSASqrtTwoLen - to_check,
             out->d + out_len - to_check, to_check)) {
       continue;
diff --git a/src/crypto/rsa_extra/rsa_test.cc b/src/crypto/rsa_extra/rsa_test.cc
index 23e8c67..97b32bf 100644
--- a/src/crypto/rsa_extra/rsa_test.cc
+++ b/src/crypto/rsa_extra/rsa_test.cc
@@ -735,58 +735,6 @@
   EXPECT_EQ(3072u / 2u, bits);
 }
 
-TEST(RSATest, LessThanWords) {
-  // kTestVectors is an array of 256-bit values in sorted order.
-  static const BN_ULONG kTestVectors[][256 / BN_BITS2] = {
-      {TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000),
-       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
-      {TOBN(0x00000000, 0x00000001), TOBN(0x00000000, 0x00000000),
-       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
-      {TOBN(0xffffffff, 0xffffffff), TOBN(0x00000000, 0x00000000),
-       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
-      {TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff),
-       TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000)},
-      {TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff),
-       TOBN(0xffffffff, 0xffffffff), TOBN(0x00000000, 0x00000000)},
-      {TOBN(0x00000000, 0x00000000), TOBN(0x1d6f60ba, 0x893ba84c),
-       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
-      {TOBN(0x00000000, 0x83339915), TOBN(0x1d6f60ba, 0x893ba84c),
-       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
-      {TOBN(0xed17ac85, 0x00000000), TOBN(0x1d6f60ba, 0x893ba84c),
-       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
-      {TOBN(0xed17ac85, 0x83339915), TOBN(0x1d6f60ba, 0x893ba84c),
-       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
-      {TOBN(0xed17ac85, 0xffffffff), TOBN(0x1d6f60ba, 0x893ba84c),
-       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
-      {TOBN(0xffffffff, 0x83339915), TOBN(0x1d6f60ba, 0x893ba84c),
-       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
-      {TOBN(0xffffffff, 0xffffffff), TOBN(0x1d6f60ba, 0x893ba84c),
-       TOBN(0x597d89b3, 0x754abe9f), TOBN(0xb504f333, 0xf9de6484)},
-      {TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000),
-       TOBN(0x00000000, 0x00000000), TOBN(0xffffffff, 0xffffffff)},
-      {TOBN(0x00000000, 0x00000000), TOBN(0x00000000, 0x00000000),
-       TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff)},
-      {TOBN(0x00000000, 0x00000001), TOBN(0x00000000, 0x00000000),
-       TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff)},
-      {TOBN(0x00000000, 0x00000000), TOBN(0xffffffff, 0xffffffff),
-       TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff)},
-      {TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff),
-       TOBN(0xffffffff, 0xffffffff), TOBN(0xffffffff, 0xffffffff)},
-  };
-
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kTestVectors); i++) {
-    SCOPED_TRACE(i);
-    for (size_t j = 0; j < OPENSSL_ARRAY_SIZE(kTestVectors); j++) {
-      SCOPED_TRACE(j);
-      EXPECT_EQ(i < j ? 1 : 0,
-                rsa_less_than_words(kTestVectors[i], kTestVectors[j],
-                                    OPENSSL_ARRAY_SIZE(kTestVectors[i])));
-    }
-  }
-
-  EXPECT_EQ(0, rsa_less_than_words(NULL, NULL, 0));
-}
-
 TEST(RSATest, GreaterThanPow2) {
   bssl::UniquePtr<BIGNUM> b(BN_new());
   BN_zero(b.get());
diff --git a/src/crypto/x509/x509_test.cc b/src/crypto/x509/x509_test.cc
index 2fb1b1b..cd4e61d 100644
--- a/src/crypto/x509/x509_test.cc
+++ b/src/crypto/x509/x509_test.cc
@@ -834,7 +834,7 @@
 
   /* This ensures the X509 took a reference to |buf|, otherwise this will be a
    * reference to free memory and ASAN should notice. */
-  ASSERT_EQ(CBS_ASN1_SEQUENCE, enc_pointer[0]);
+  ASSERT_EQ(0x30, enc_pointer[0]);
 }
 
 TEST(X509Test, TestFromBufferWithTrailingData) {
diff --git a/src/crypto/x509/x_algor.c b/src/crypto/x509/x_algor.c
index abacd06..13c9a8c 100644
--- a/src/crypto/x509/x_algor.c
+++ b/src/crypto/x509/x_algor.c
@@ -105,8 +105,8 @@
     return 1;
 }
 
-void X509_ALGOR_get0(ASN1_OBJECT **paobj, int *pptype, void **ppval,
-                     X509_ALGOR *algor)
+void X509_ALGOR_get0(const ASN1_OBJECT **paobj, int *pptype, const void **ppval,
+                     const X509_ALGOR *algor)
 {
     if (paobj)
         *paobj = algor->algorithm;
diff --git a/src/crypto/x509v3/v3_lib.c b/src/crypto/x509v3/v3_lib.c
index c4718e3..8f5435d 100644
--- a/src/crypto/x509v3/v3_lib.c
+++ b/src/crypto/x509v3/v3_lib.c
@@ -288,9 +288,9 @@
 int X509V3_add1_i2d(STACK_OF(X509_EXTENSION) **x, int nid, void *value,
                     int crit, unsigned long flags)
 {
-    int extidx = -1;
-    int errcode;
-    X509_EXTENSION *ext, *extmp;
+    int errcode, extidx = -1;
+    X509_EXTENSION *ext = NULL, *extmp;
+    STACK_OF(X509_EXTENSION) *ret = NULL;
     unsigned long ext_op = flags & X509V3_ADD_OP_MASK;
 
     /*
@@ -348,13 +348,21 @@
         return 1;
     }
 
-    if (!*x && !(*x = sk_X509_EXTENSION_new_null()))
-        return -1;
-    if (!sk_X509_EXTENSION_push(*x, ext))
-        return -1;
+    if ((ret = *x) == NULL
+         && (ret = sk_X509_EXTENSION_new_null()) == NULL)
+        goto m_fail;
+    if (!sk_X509_EXTENSION_push(ret, ext))
+        goto m_fail;
 
+    *x = ret;
     return 1;
 
+ m_fail:
+    if (ret != *x)
+        sk_X509_EXTENSION_free(ret);
+    X509_EXTENSION_free(ext);
+    return -1;
+
  err:
     if (!(flags & X509V3_ADD_SILENT))
         OPENSSL_PUT_ERROR(X509V3, errcode);
diff --git a/src/include/openssl/base.h b/src/include/openssl/base.h
index adb5047..cc962f3 100644
--- a/src/include/openssl/base.h
+++ b/src/include/openssl/base.h
@@ -151,7 +151,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 4
+#define BORINGSSL_API_VERSION 6
 
 #if defined(BORINGSSL_SHARED_LIBRARY)
 
diff --git a/src/include/openssl/bn.h b/src/include/openssl/bn.h
index 9960b75..bb32c2f 100644
--- a/src/include/openssl/bn.h
+++ b/src/include/openssl/bn.h
@@ -487,8 +487,8 @@
 // zero on allocation failure.
 OPENSSL_EXPORT int BN_clear_bit(BIGNUM *a, int n);
 
-// BN_is_bit_set returns the value of the |n|th, least-significant bit in |a|,
-// or zero if the bit doesn't exist.
+// BN_is_bit_set returns one if the |n|th least-significant bit in |a| exists
+// and is set. Otherwise, it returns zero.
 OPENSSL_EXPORT int BN_is_bit_set(const BIGNUM *a, int n);
 
 // BN_mask_bits truncates |a| so that it is only |n| bits long. It returns one
@@ -618,17 +618,6 @@
 // BN_pseudo_rand_range is an alias for BN_rand_range.
 OPENSSL_EXPORT int BN_pseudo_rand_range(BIGNUM *rnd, const BIGNUM *range);
 
-// BN_generate_dsa_nonce generates a random number 0 <= out < range. Unlike
-// BN_rand_range, it also includes the contents of |priv| and |message| in the
-// generation so that an RNG failure isn't fatal as long as |priv| remains
-// secret. This is intended for use in DSA and ECDSA where an RNG weakness
-// leads directly to private key exposure unless this function is used.
-// It returns one on success and zero on error.
-OPENSSL_EXPORT int BN_generate_dsa_nonce(BIGNUM *out, const BIGNUM *range,
-                                         const BIGNUM *priv,
-                                         const uint8_t *message,
-                                         size_t message_len, BN_CTX *ctx);
-
 // BN_GENCB holds a callback function that is used by generation functions that
 // can take a very long time to complete. Use |BN_GENCB_set| to initialise a
 // |BN_GENCB| structure.
diff --git a/src/include/openssl/bytestring.h b/src/include/openssl/bytestring.h
index 6d355b5..3906809 100644
--- a/src/include/openssl/bytestring.h
+++ b/src/include/openssl/bytestring.h
@@ -164,34 +164,36 @@
 #define CBS_ASN1_UNIVERSALSTRING 0x1cu
 #define CBS_ASN1_BMPSTRING 0x1eu
 
+// CBS_ASN1_TAG_SHIFT is how much the in-memory representation shifts the class
+// and constructed bits from the DER serialization. This allows representing tag
+// numbers beyond 31.
+//
+// Consumers must use the following constants to decompose or assemble tags.
+#define CBS_ASN1_TAG_SHIFT 24
+
 // CBS_ASN1_CONSTRUCTED may be ORed into a tag to toggle the constructed
 // bit. |CBS| and |CBB| APIs consider the constructed bit to be part of the
 // tag.
-#define CBS_ASN1_CONSTRUCTED 0x20u
+#define CBS_ASN1_CONSTRUCTED (0x20u << CBS_ASN1_TAG_SHIFT)
 
-// The following values specify the constructed bit or tag class and may be ORed
-// into a tag number to produce the final tag. If none is used, the tag will be
-// UNIVERSAL.
-//
-// Note that although they currently match the DER serialization, consumers must
-// use these bits rather than make assumptions about the representation. This is
-// to allow for tag numbers beyond 31 in the future.
-#define CBS_ASN1_APPLICATION 0x40u
-#define CBS_ASN1_CONTEXT_SPECIFIC 0x80u
-#define CBS_ASN1_PRIVATE 0xc0u
+// The following values specify the tag class and may be ORed into a tag number
+// to produce the final tag. If none is used, the tag will be UNIVERSAL.
+#define CBS_ASN1_UNIVERSAL (0u << CBS_ASN1_TAG_SHIFT)
+#define CBS_ASN1_APPLICATION (0x40u << CBS_ASN1_TAG_SHIFT)
+#define CBS_ASN1_CONTEXT_SPECIFIC (0x80u << CBS_ASN1_TAG_SHIFT)
+#define CBS_ASN1_PRIVATE (0xc0u << CBS_ASN1_TAG_SHIFT)
 
-// CBS_ASN1_CLASS_MASK may be ANDed with a tag to query its class.
-#define CBS_ASN1_CLASS_MASK 0xc0u
+// CBS_ASN1_CLASS_MASK may be ANDed with a tag to query its class. This will
+// give one of the four values above.
+#define CBS_ASN1_CLASS_MASK (0xc0u << CBS_ASN1_TAG_SHIFT)
 
 // CBS_ASN1_TAG_NUMBER_MASK may be ANDed with a tag to query its number.
-#define CBS_ASN1_TAG_NUMBER_MASK 0x1fu
+#define CBS_ASN1_TAG_NUMBER_MASK ((1u << (5 + CBS_ASN1_TAG_SHIFT)) - 1)
 
 // CBS_get_asn1 sets |*out| to the contents of DER-encoded, ASN.1 element (not
 // including tag and length bytes) and advances |cbs| over it. The ASN.1
 // element must match |tag_value|. It returns one on success and zero
 // on error.
-//
-// Tag numbers greater than 30 are not supported (i.e. short form only).
 OPENSSL_EXPORT int CBS_get_asn1(CBS *cbs, CBS *out, unsigned tag_value);
 
 // CBS_get_asn1_element acts like |CBS_get_asn1| but |out| will include the
@@ -209,16 +211,12 @@
 // (not including tag and length bytes), sets |*out_tag| to the tag number, and
 // advances |*cbs|. It returns one on success and zero on error. Either of |out|
 // and |out_tag| may be NULL to ignore the value.
-//
-// Tag numbers greater than 30 are not supported (i.e. short form only).
 OPENSSL_EXPORT int CBS_get_any_asn1(CBS *cbs, CBS *out, unsigned *out_tag);
 
 // CBS_get_any_asn1_element sets |*out| to contain the next ASN.1 element from
 // |*cbs| (including header bytes) and advances |*cbs|. It sets |*out_tag| to
 // the tag number and |*out_header_len| to the length of the ASN.1 header. Each
 // of |out|, |out_tag|, and |out_header_len| may be NULL to ignore the value.
-//
-// Tag numbers greater than 30 are not supported (i.e. short form only).
 OPENSSL_EXPORT int CBS_get_any_asn1_element(CBS *cbs, CBS *out,
                                             unsigned *out_tag,
                                             size_t *out_header_len);
@@ -396,9 +394,7 @@
 
 // CBB_add_asn1 sets |*out_contents| to a |CBB| into which the contents of an
 // ASN.1 object can be written. The |tag| argument will be used as the tag for
-// the object. Passing in |tag| number 31 will return in an error since only
-// single octet identifiers are supported. It returns one on success or zero
-// on error.
+// the object. It returns one on success or zero on error.
 OPENSSL_EXPORT int CBB_add_asn1(CBB *cbb, CBB *out_contents, unsigned tag);
 
 // CBB_add_bytes appends |len| bytes from |data| to |cbb|. It returns one on
diff --git a/src/include/openssl/dsa.h b/src/include/openssl/dsa.h
index 315e7ca..2966f9d 100644
--- a/src/include/openssl/dsa.h
+++ b/src/include/openssl/dsa.h
@@ -172,7 +172,7 @@
 // DSA_do_sign returns a signature of the hash in |digest| by the key in |dsa|
 // and returns an allocated, DSA_SIG structure, or NULL on error.
 OPENSSL_EXPORT DSA_SIG *DSA_do_sign(const uint8_t *digest, size_t digest_len,
-                                    DSA *dsa);
+                                    const DSA *dsa);
 
 // DSA_do_verify verifies that |sig| is a valid signature, by the public key in
 // |dsa|, of the hash in |digest|. It returns one if so, zero if invalid and -1
@@ -212,7 +212,7 @@
 // (The |type| argument is ignored.)
 OPENSSL_EXPORT int DSA_sign(int type, const uint8_t *digest, size_t digest_len,
                             uint8_t *out_sig, unsigned int *out_siglen,
-                            DSA *dsa);
+                            const DSA *dsa);
 
 // DSA_verify verifies that |sig| is a valid, ASN.1 signature, by the public
 // key in |dsa|, of the hash in |digest|. It returns one if so, zero if invalid
@@ -284,19 +284,6 @@
 OPENSSL_EXPORT int DSA_marshal_parameters(CBB *cbb, const DSA *dsa);
 
 
-// Precomputation.
-
-// DSA_sign_setup precomputes the message independent part of the DSA signature
-// and writes them to |*out_kinv| and |*out_r|. Returns one on success, zero on
-// error.
-//
-// TODO(fork): decide what to do with this. Since making DSA* opaque there's no
-// way for the user to install them. Also, it forces the DSA* not to be const
-// when passing to the signing function.
-OPENSSL_EXPORT int DSA_sign_setup(const DSA *dsa, BN_CTX *ctx,
-                                  BIGNUM **out_kinv, BIGNUM **out_r);
-
-
 // Conversion.
 
 // DSA_dup_DH returns a |DH| constructed from the parameters of |dsa|. This is
@@ -411,9 +398,6 @@
   BIGNUM *pub_key;   // y public key
   BIGNUM *priv_key;  // x private key
 
-  BIGNUM *kinv;  // Signing pre-calc
-  BIGNUM *r;     // Signing pre-calc
-
   int flags;
   // Normally used to cache montgomery values
   CRYPTO_MUTEX method_mont_lock;
diff --git a/src/include/openssl/ec.h b/src/include/openssl/ec.h
index dee41b7..b34605f 100644
--- a/src/include/openssl/ec.h
+++ b/src/include/openssl/ec.h
@@ -402,5 +402,6 @@
 #define EC_R_GROUP_MISMATCH 130
 #define EC_R_INVALID_COFACTOR 131
 #define EC_R_PUBLIC_KEY_VALIDATION_FAILED 132
+#define EC_R_INVALID_SCALAR 133
 
 #endif  // OPENSSL_HEADER_EC_H
diff --git a/src/include/openssl/ecdsa.h b/src/include/openssl/ecdsa.h
index ff26fe4..42da1c6 100644
--- a/src/include/openssl/ecdsa.h
+++ b/src/include/openssl/ecdsa.h
@@ -106,6 +106,16 @@
 // ECDSA_SIG_free frees |sig| its member |BIGNUM|s.
 OPENSSL_EXPORT void ECDSA_SIG_free(ECDSA_SIG *sig);
 
+// ECDSA_SIG_get0 sets |*out_r| and |*out_s|, if non-NULL, to the two
+// components of |sig|.
+OPENSSL_EXPORT void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **out_r,
+                                   const BIGNUM **out_s);
+
+// ECDSA_SIG_set0 sets |sig|'s components to |r| and |s|, neither of which may
+// be NULL. On success, it takes ownership of each argument and returns one.
+// Otherwise, it returns zero.
+OPENSSL_EXPORT int ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s);
+
 // ECDSA_do_sign signs |digest_len| bytes from |digest| with |key| and returns
 // the resulting signature structure, or NULL on error.
 OPENSSL_EXPORT ECDSA_SIG *ECDSA_do_sign(const uint8_t *digest,
@@ -118,34 +128,6 @@
                                    const ECDSA_SIG *sig, const EC_KEY *key);
 
 
-// Signing with precomputation.
-//
-// Parts of the ECDSA signature can be independent of the message to be signed
-// thus it's possible to precompute them and reduce the signing latency.
-//
-// TODO(fork): remove support for this as it cannot support safe-randomness.
-
-// ECDSA_sign_setup precomputes parts of an ECDSA signing operation. It sets
-// |*kinv| and |*rp| to the precomputed values and uses the |ctx| argument, if
-// not NULL. It returns one on success and zero otherwise.
-OPENSSL_EXPORT int ECDSA_sign_setup(const EC_KEY *eckey, BN_CTX *ctx,
-                                    BIGNUM **kinv, BIGNUM **rp);
-
-// ECDSA_do_sign_ex is the same as |ECDSA_do_sign| but takes precomputed values
-// as generated by |ECDSA_sign_setup|.
-OPENSSL_EXPORT ECDSA_SIG *ECDSA_do_sign_ex(const uint8_t *digest,
-                                           size_t digest_len,
-                                           const BIGNUM *kinv, const BIGNUM *rp,
-                                           const EC_KEY *eckey);
-
-// ECDSA_sign_ex is the same as |ECDSA_sign| but takes precomputed values as
-// generated by |ECDSA_sign_setup|.
-OPENSSL_EXPORT int ECDSA_sign_ex(int type, const uint8_t *digest,
-                                 size_t digest_len, uint8_t *sig,
-                                 unsigned int *sig_len, const BIGNUM *kinv,
-                                 const BIGNUM *rp, const EC_KEY *eckey);
-
-
 // ASN.1 functions.
 
 // ECDSA_SIG_parse parses a DER-encoded ECDSA-Sig-Value structure from |cbs| and
diff --git a/src/include/openssl/rsa.h b/src/include/openssl/rsa.h
index 74268cf..11aa8e4 100644
--- a/src/include/openssl/rsa.h
+++ b/src/include/openssl/rsa.h
@@ -89,6 +89,9 @@
 
 // Properties.
 
+// RSA_bits returns the size of |rsa|, in bits.
+OPENSSL_EXPORT unsigned RSA_bits(const RSA *rsa);
+
 // RSA_get0_key sets |*out_n|, |*out_e|, and |*out_d|, if non-NULL, to |rsa|'s
 // modulus, public exponent, and private exponent, respectively. If |rsa| is a
 // public key, the private exponent will be set to NULL.
diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h
index 8c36ad5..53a8eb5 100644
--- a/src/include/openssl/ssl.h
+++ b/src/include/openssl/ssl.h
@@ -1125,16 +1125,7 @@
 // key hooks. This is used to off-load signing operations to a custom,
 // potentially asynchronous, backend. Metadata about the key such as the type
 // and size are parsed out of the certificate.
-//
-// TODO(davidben): This API has a number of legacy hooks. Remove the last
-// consumer of |sign_digest| and trim it.
 struct ssl_private_key_method_st {
-  // type is ignored and should be NULL.
-  int (*type)(SSL *ssl);
-
-  // max_signature_len is ignored and should be NULL.
-  size_t (*max_signature_len)(SSL *ssl);
-
   // sign signs the message |in| in using the specified signature algorithm. On
   // success, it returns |ssl_private_key_success| and writes at most |max_out|
   // bytes of signature data to |out| and sets |*out_len| to the number of bytes
@@ -1156,30 +1147,6 @@
                                         uint16_t signature_algorithm,
                                         const uint8_t *in, size_t in_len);
 
-  // sign_digest signs |in_len| bytes of digest from |in|. |md| is the hash
-  // function used to calculate |in|. On success, it returns
-  // |ssl_private_key_success| and writes at most |max_out| bytes of signature
-  // data to |out|. On failure, it returns |ssl_private_key_failure|. If the
-  // operation has not completed, it returns |ssl_private_key_retry|. |sign|
-  // should arrange for the high-level operation on |ssl| to be retried when the
-  // operation is completed. This will result in a call to |complete|.
-  //
-  // If the key is an RSA key, implementations must use PKCS#1 padding. |in| is
-  // the digest itself, so the DigestInfo prefix, if any, must be prepended by
-  // |sign|. If |md| is |EVP_md5_sha1|, there is no prefix.
-  //
-  // It is an error to call |sign_digest| while another private key operation is
-  // in progress on |ssl|.
-  //
-  // This function is deprecated. Implement |sign| instead.
-  //
-  // TODO(davidben): Remove this function.
-  enum ssl_private_key_result_t (*sign_digest)(SSL *ssl, uint8_t *out,
-                                               size_t *out_len, size_t max_out,
-                                               const EVP_MD *md,
-                                               const uint8_t *in,
-                                               size_t in_len);
-
   // decrypt decrypts |in_len| bytes of encrypted data from |in|. On success it
   // returns |ssl_private_key_success|, writes at most |max_out| bytes of
   // decrypted data to |out| and sets |*out_len| to the actual number of bytes
@@ -3978,18 +3945,6 @@
 OPENSSL_EXPORT int SSL_add_dir_cert_subjects_to_stack(STACK_OF(X509_NAME) *out,
                                                       const char *dir);
 
-// SSL_set_private_key_digest_prefs copies |num_digests| NIDs from |digest_nids|
-// into |ssl|. These digests will be used, in decreasing order of preference,
-// when signing with |ssl|'s private key. It returns one on success and zero on
-// error.
-//
-// Use |SSL_set_signing_algorithm_prefs| instead.
-//
-// TODO(davidben): Remove this API when callers have been updated.
-OPENSSL_EXPORT int SSL_set_private_key_digest_prefs(SSL *ssl,
-                                                    const int *digest_nids,
-                                                    size_t num_digests);
-
 // SSL_set_verify_result calls |abort| unless |result| is |X509_V_OK|.
 //
 // TODO(davidben): Remove this function once it has been removed from
@@ -4599,6 +4554,7 @@
 #define SSL_R_UNEXPECTED_EXTENSION_ON_EARLY_DATA 279
 #define SSL_R_NO_SUPPORTED_VERSIONS_ENABLED 280
 #define SSL_R_APPLICATION_DATA_INSTEAD_OF_HANDSHAKE 281
+#define SSL_R_EMPTY_HELLO_RETRY_REQUEST 282
 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/src/include/openssl/stack.h b/src/include/openssl/stack.h
index 1a0347e..46f57a3 100644
--- a/src/include/openssl/stack.h
+++ b/src/include/openssl/stack.h
@@ -245,7 +245,7 @@
 // are defined in a header.
 
 #define BORINGSSL_DEFINE_STACK_OF_IMPL(name, ptrtype, constptrtype)            \
-  DECLARE_STACK_OF(name);                                                      \
+  DECLARE_STACK_OF(name)                                                       \
                                                                                \
   typedef int (*stack_##name##_cmp_func)(constptrtype *a, constptrtype *b);    \
                                                                                \
diff --git a/src/include/openssl/x509.h b/src/include/openssl/x509.h
index 7db9466..430ffc0 100644
--- a/src/include/openssl/x509.h
+++ b/src/include/openssl/x509.h
@@ -680,8 +680,9 @@
 OPENSSL_EXPORT X509_REQ *X509_REQ_dup(X509_REQ *req);
 OPENSSL_EXPORT X509_ALGOR *X509_ALGOR_dup(X509_ALGOR *xn);
 OPENSSL_EXPORT int X509_ALGOR_set0(X509_ALGOR *alg, const ASN1_OBJECT *aobj, int ptype, void *pval);
-OPENSSL_EXPORT void X509_ALGOR_get0(ASN1_OBJECT **paobj, int *pptype, void **ppval,
-						X509_ALGOR *algor);
+OPENSSL_EXPORT void X509_ALGOR_get0(const ASN1_OBJECT **paobj, int *pptype,
+                                    const void **ppval,
+                                    const X509_ALGOR *algor);
 OPENSSL_EXPORT void X509_ALGOR_set_md(X509_ALGOR *alg, const EVP_MD *md);
 OPENSSL_EXPORT int X509_ALGOR_cmp(const X509_ALGOR *a, const X509_ALGOR *b);
 
diff --git a/src/ssl/ssl_asn1.cc b/src/ssl/ssl_asn1.cc
index 7bcfdd7..eb7df5b 100644
--- a/src/ssl/ssl_asn1.cc
+++ b/src/ssl/ssl_asn1.cc
@@ -152,49 +152,49 @@
 
 static const unsigned kVersion = 1;
 
-static const int kTimeTag =
+static const unsigned kTimeTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1;
-static const int kTimeoutTag =
+static const unsigned kTimeoutTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 2;
-static const int kPeerTag =
+static const unsigned kPeerTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 3;
-static const int kSessionIDContextTag =
+static const unsigned kSessionIDContextTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 4;
-static const int kVerifyResultTag =
+static const unsigned kVerifyResultTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 5;
-static const int kHostNameTag =
+static const unsigned kHostNameTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 6;
-static const int kPSKIdentityTag =
+static const unsigned kPSKIdentityTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 8;
-static const int kTicketLifetimeHintTag =
+static const unsigned kTicketLifetimeHintTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 9;
-static const int kTicketTag =
+static const unsigned kTicketTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 10;
-static const int kPeerSHA256Tag =
+static const unsigned kPeerSHA256Tag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 13;
-static const int kOriginalHandshakeHashTag =
+static const unsigned kOriginalHandshakeHashTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 14;
-static const int kSignedCertTimestampListTag =
+static const unsigned kSignedCertTimestampListTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 15;
-static const int kOCSPResponseTag =
+static const unsigned kOCSPResponseTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 16;
-static const int kExtendedMasterSecretTag =
+static const unsigned kExtendedMasterSecretTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 17;
-static const int kGroupIDTag =
+static const unsigned kGroupIDTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 18;
-static const int kCertChainTag =
+static const unsigned kCertChainTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 19;
-static const int kTicketAgeAddTag =
+static const unsigned kTicketAgeAddTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 21;
-static const int kIsServerTag =
+static const unsigned kIsServerTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 22;
-static const int kPeerSignatureAlgorithmTag =
+static const unsigned kPeerSignatureAlgorithmTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 23;
-static const int kTicketMaxEarlyDataTag =
+static const unsigned kTicketMaxEarlyDataTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 24;
-static const int kAuthTimeoutTag =
+static const unsigned kAuthTimeoutTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 25;
-static const int kEarlyALPNTag =
+static const unsigned kEarlyALPNTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 26;
 
 static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
diff --git a/src/ssl/ssl_privkey.cc b/src/ssl/ssl_privkey.cc
index e9990af..134ad56 100644
--- a/src/ssl/ssl_privkey.cc
+++ b/src/ssl/ssl_privkey.cc
@@ -193,33 +193,6 @@
   return 1;
 }
 
-static int legacy_sign_digest_supported(const SSL_SIGNATURE_ALGORITHM *alg) {
-  return (alg->pkey_type == EVP_PKEY_EC || alg->pkey_type == EVP_PKEY_RSA) &&
-         !alg->is_rsa_pss;
-}
-
-static enum ssl_private_key_result_t legacy_sign(
-    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out, uint16_t sigalg,
-    const uint8_t *in, size_t in_len) {
-  // TODO(davidben): Remove support for |sign_digest|-only
-  // |SSL_PRIVATE_KEY_METHOD|s.
-  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
-  if (alg == NULL || !legacy_sign_digest_supported(alg)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY);
-    return ssl_private_key_failure;
-  }
-
-  const EVP_MD *md = alg->digest_func();
-  uint8_t hash[EVP_MAX_MD_SIZE];
-  unsigned hash_len;
-  if (!EVP_Digest(in, in_len, hash, &hash_len, md, NULL)) {
-    return ssl_private_key_failure;
-  }
-
-  return ssl->cert->key_method->sign_digest(ssl, out, out_len, max_out, md,
-                                            hash, hash_len);
-}
-
 enum ssl_private_key_result_t ssl_private_key_sign(
     SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
     uint16_t sigalg, Span<const uint8_t> in) {
@@ -229,9 +202,8 @@
     if (hs->pending_private_key_op) {
       ret = ssl->cert->key_method->complete(ssl, out, out_len, max_out);
     } else {
-      ret = (ssl->cert->key_method->sign != NULL ? ssl->cert->key_method->sign
-                                                 : legacy_sign)(
-          ssl, out, out_len, max_out, sigalg, in.data(), in.size());
+      ret = ssl->cert->key_method->sign(ssl, out, out_len, max_out, sigalg,
+                                        in.data(), in.size());
     }
     hs->pending_private_key_op = ret == ssl_private_key_retry;
     return ret;
@@ -308,14 +280,6 @@
     return false;
   }
 
-  // Newer algorithms require message-based private keys.
-  // TODO(davidben): Remove this check when sign_digest is gone.
-  if (ssl->cert->key_method != NULL &&
-      ssl->cert->key_method->sign == NULL &&
-      !legacy_sign_digest_supported(alg)) {
-    return false;
-  }
-
   return true;
 }
 
@@ -511,7 +475,6 @@
                              prefs, num_prefs);
 }
 
-
 int SSL_set_signing_algorithm_prefs(SSL *ssl, const uint16_t *prefs,
                                     size_t num_prefs) {
   return set_algorithm_prefs(&ssl->cert->sigalgs, &ssl->cert->num_sigalgs,
@@ -523,52 +486,3 @@
   return set_algorithm_prefs(&ctx->verify_sigalgs, &ctx->num_verify_sigalgs,
                              prefs, num_prefs);
 }
-
-int SSL_set_private_key_digest_prefs(SSL *ssl, const int *digest_nids,
-                                     size_t num_digests) {
-  OPENSSL_free(ssl->cert->sigalgs);
-
-  static_assert(sizeof(int) >= 2 * sizeof(uint16_t),
-                "sigalgs allocation may overflow");
-
-  ssl->cert->num_sigalgs = 0;
-  ssl->cert->sigalgs =
-      (uint16_t *)OPENSSL_malloc(sizeof(uint16_t) * 2 * num_digests);
-  if (ssl->cert->sigalgs == NULL) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return 0;
-  }
-
-  // Convert the digest list to a signature algorithms list.
-  //
-  // TODO(davidben): Replace this API with one that can express RSA-PSS, etc.
-  for (size_t i = 0; i < num_digests; i++) {
-    switch (digest_nids[i]) {
-      case NID_sha1:
-        ssl->cert->sigalgs[ssl->cert->num_sigalgs] = SSL_SIGN_RSA_PKCS1_SHA1;
-        ssl->cert->sigalgs[ssl->cert->num_sigalgs + 1] = SSL_SIGN_ECDSA_SHA1;
-        ssl->cert->num_sigalgs += 2;
-        break;
-      case NID_sha256:
-        ssl->cert->sigalgs[ssl->cert->num_sigalgs] = SSL_SIGN_RSA_PKCS1_SHA256;
-        ssl->cert->sigalgs[ssl->cert->num_sigalgs + 1] =
-            SSL_SIGN_ECDSA_SECP256R1_SHA256;
-        ssl->cert->num_sigalgs += 2;
-        break;
-      case NID_sha384:
-        ssl->cert->sigalgs[ssl->cert->num_sigalgs] = SSL_SIGN_RSA_PKCS1_SHA384;
-        ssl->cert->sigalgs[ssl->cert->num_sigalgs + 1] =
-            SSL_SIGN_ECDSA_SECP384R1_SHA384;
-        ssl->cert->num_sigalgs += 2;
-        break;
-      case NID_sha512:
-        ssl->cert->sigalgs[ssl->cert->num_sigalgs] = SSL_SIGN_RSA_PKCS1_SHA512;
-        ssl->cert->sigalgs[ssl->cert->num_sigalgs + 1] =
-            SSL_SIGN_ECDSA_SECP521R1_SHA512;
-        ssl->cert->num_sigalgs += 2;
-        break;
-    }
-  }
-
-  return 1;
-}
diff --git a/src/ssl/t1_lib.cc b/src/ssl/t1_lib.cc
index a7bfec4..8d03623 100644
--- a/src/ssl/t1_lib.cc
+++ b/src/ssl/t1_lib.cc
@@ -2147,6 +2147,7 @@
   if (!CBS_get_u16(contents, &group_id) ||
       !CBS_get_u16_length_prefixed(contents, &peer_key) ||
       CBS_len(contents) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     *out_alert = SSL_AD_DECODE_ERROR;
     return false;
   }
diff --git a/src/ssl/test/bssl_shim.cc b/src/ssl/test/bssl_shim.cc
index 7eca21d..8acabd7 100644
--- a/src/ssl/test/bssl_shim.cc
+++ b/src/ssl/test/bssl_shim.cc
@@ -433,10 +433,7 @@
 }
 
 static const SSL_PRIVATE_KEY_METHOD g_async_private_key_method = {
-    nullptr /* type */,
-    nullptr /* max_signature_len */,
     AsyncPrivateKeySign,
-    nullptr /* sign_digest */,
     AsyncPrivateKeyDecrypt,
     AsyncPrivateKeyComplete,
 };
@@ -453,27 +450,6 @@
                            bssl::UniquePtr<EVP_PKEY> *out_pkey) {
   const TestConfig *config = GetTestConfig(ssl);
 
-  if (!config->digest_prefs.empty()) {
-    bssl::UniquePtr<char> digest_prefs(
-        OPENSSL_strdup(config->digest_prefs.c_str()));
-    std::vector<int> digest_list;
-
-    for (;;) {
-      char *token =
-          strtok(digest_list.empty() ? digest_prefs.get() : nullptr, ",");
-      if (token == nullptr) {
-        break;
-      }
-
-      digest_list.push_back(EVP_MD_type(EVP_get_digestbyname(token)));
-    }
-
-    if (!SSL_set_private_key_digest_prefs(ssl, digest_list.data(),
-                                          digest_list.size())) {
-      return false;
-    }
-  }
-
   if (!config->signing_prefs.empty()) {
     std::vector<uint16_t> u16s(config->signing_prefs.begin(),
                                config->signing_prefs.end());
diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go
index 4564b0f..0216401 100644
--- a/src/ssl/test/runner/common.go
+++ b/src/ssl/test/runner/common.go
@@ -632,6 +632,10 @@
 	// ChangeCipherSpec messages.
 	SendExtraChangeCipherSpec int
 
+	// SendPostHandshakeChangeCipherSpec causes the implementation to send
+	// a ChangeCipherSpec record before every application data record.
+	SendPostHandshakeChangeCipherSpec bool
+
 	// SendUnencryptedFinished, if true, causes the Finished message to be
 	// send unencrypted before ChangeCipherSpec rather than after it.
 	SendUnencryptedFinished bool
diff --git a/src/ssl/test/runner/conn.go b/src/ssl/test/runner/conn.go
index 5359462..21f491a 100644
--- a/src/ssl/test/runner/conn.go
+++ b/src/ssl/test/runner/conn.go
@@ -39,7 +39,6 @@
 	vers                 uint16     // TLS version
 	haveVers             bool       // version has been negotiated
 	config               *Config    // configuration passed to constructor
-	hadHelloRetryRequest bool
 	handshakeComplete    bool
 	skipEarlyData        bool // On a server, indicates that the client is sending early data that must be skipped over.
 	didResume            bool // whether this connection was a session resumption
@@ -102,6 +101,8 @@
 	keyUpdateRequested bool
 	seenOneByteRecord  bool
 
+	expectTLS13ChangeCipherSpec bool
+
 	tmp [16]byte
 }
 
@@ -878,6 +879,47 @@
 	return typ, b, nil
 }
 
+func (c *Conn) readTLS13ChangeCipherSpec() error {
+	if !c.expectTLS13ChangeCipherSpec {
+		panic("c.expectTLS13ChangeCipherSpec not set")
+	}
+
+	// Read the ChangeCipherSpec.
+	if c.rawInput == nil {
+		c.rawInput = c.in.newBlock()
+	}
+	b := c.rawInput
+	if err := b.readFromUntil(c.conn, 1); err != nil {
+		return c.in.setErrorLocked(fmt.Errorf("tls: error reading TLS 1.3 ChangeCipherSpec: %s", err))
+	}
+	if recordType(b.data[0]) == recordTypeAlert {
+		// If the client is sending an alert, allow the ChangeCipherSpec
+		// to be skipped. It may be rejecting a sufficiently malformed
+		// ServerHello that it can't parse out the version.
+		c.expectTLS13ChangeCipherSpec = false
+		return nil
+	}
+	if err := b.readFromUntil(c.conn, 6); err != nil {
+		return c.in.setErrorLocked(fmt.Errorf("tls: error reading TLS 1.3 ChangeCipherSpec: %s", err))
+	}
+
+	// Check they match that we expect.
+	expected := [6]byte{byte(recordTypeChangeCipherSpec), 3, 1, 0, 1, 1}
+	if isResumptionRecordVersionExperiment(c.wireVersion) {
+		expected[2] = 3
+	}
+	if !bytes.Equal(b.data[:6], expected[:]) {
+		return c.in.setErrorLocked(fmt.Errorf("tls: error invalid TLS 1.3 ChangeCipherSpec: %x", b.data[:6]))
+	}
+
+	// Discard the data.
+	b, c.rawInput = c.in.splitBlock(b, 6)
+	c.in.freeBlock(b)
+
+	c.expectTLS13ChangeCipherSpec = false
+	return nil
+}
+
 // readRecord reads the next TLS record from the connection
 // and updates the record layer state.
 // c.in.Mutex <= L; c.input == nil.
@@ -898,6 +940,12 @@
 		break
 	}
 
+	if c.expectTLS13ChangeCipherSpec {
+		if err := c.readTLS13ChangeCipherSpec(); err != nil {
+			return err
+		}
+	}
+
 Again:
 	typ, b, err := c.doReadRecord(want)
 	if err != nil {
@@ -952,14 +1000,12 @@
 			c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
 			break
 		}
-		if !isResumptionExperiment(c.wireVersion) {
-			if c.hand.Len() != 0 {
-				c.in.setErrorLocked(errors.New("tls: buffered handshake messages on cipher change"))
-				break
-			}
-			if err := c.in.changeCipherSpec(c.config); err != nil {
-				c.in.setErrorLocked(c.sendAlert(err.(alert)))
-			}
+		if c.hand.Len() != 0 {
+			c.in.setErrorLocked(errors.New("tls: buffered handshake messages on cipher change"))
+			break
+		}
+		if err := c.in.changeCipherSpec(c.config); err != nil {
+			c.in.setErrorLocked(c.sendAlert(err.(alert)))
 		}
 
 	case recordTypeApplicationData:
@@ -1089,6 +1135,12 @@
 		return 0, err
 	}
 
+	if typ == recordTypeApplicationData && c.config.Bugs.SendPostHandshakeChangeCipherSpec {
+		if _, err := c.doWriteRecord(recordTypeChangeCipherSpec, []byte{1}); err != nil {
+			return 0, err
+		}
+	}
+
 	return c.doWriteRecord(typ, data)
 }
 
diff --git a/src/ssl/test/runner/fuzzer_mode.json b/src/ssl/test/runner/fuzzer_mode.json
index f2ecae0..fd029d3 100644
--- a/src/ssl/test/runner/fuzzer_mode.json
+++ b/src/ssl/test/runner/fuzzer_mode.json
@@ -35,17 +35,17 @@
     "Resume-Server-*Binder*": "Fuzzer mode does not check binders.",
 
     "SkipEarlyData*": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyDataChannelID-OfferBoth-Server": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyData-NonZeroRTTSession-Server": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyData-SkipEndOfEarlyData": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyData-ALPNMismatch-Server": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyData-ALPNOmitted1-Client": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyData-ALPNOmitted2-Client": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyData-ALPNOmitted1-Server": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyData-ALPNOmitted2-Server": "Trial decryption does not work with the NULL cipher.",
+    "EarlyDataChannelID-OfferBoth-Server-*": "Trial decryption does not work with the NULL cipher.",
+    "EarlyData-NonZeroRTTSession-Server-*": "Trial decryption does not work with the NULL cipher.",
+    "EarlyData-SkipEndOfEarlyData-*": "Trial decryption does not work with the NULL cipher.",
+    "EarlyData-ALPNMismatch-Server-*": "Trial decryption does not work with the NULL cipher.",
+    "EarlyData-ALPNOmitted1-Client-*": "Trial decryption does not work with the NULL cipher.",
+    "EarlyData-ALPNOmitted2-Client-*": "Trial decryption does not work with the NULL cipher.",
+    "EarlyData-ALPNOmitted1-Server-*": "Trial decryption does not work with the NULL cipher.",
+    "EarlyData-ALPNOmitted2-Server-*": "Trial decryption does not work with the NULL cipher.",
     "*-EarlyData-RejectUnfinishedWrite-Client-*": "Trial decryption does not work with the NULL cipher.",
     "EarlyData-Reject-Client-*": "Trial decryption does not work with the NULL cipher.",
-    "*-EarlyData-RejectTicket-Client": "Trial decryption does not work with the NULL cipher.",
+    "EarlyData-RejectTicket-Client-*": "Trial decryption does not work with the NULL cipher.",
 
     "Renegotiate-Client-BadExt*": "Fuzzer mode does not check renegotiation_info.",
 
diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go
index 7c8dbb5..5f22ecd 100644
--- a/src/ssl/test/runner/handshake_client.go
+++ b/src/ssl/test/runner/handshake_client.go
@@ -485,12 +485,18 @@
 	c.vers = serverVersion
 	c.haveVers = true
 
+	if isDraft22(c.wireVersion) {
+		// The first server message must be followed by a ChangeCipherSpec.
+		c.expectTLS13ChangeCipherSpec = true
+	}
+
 	helloRetryRequest, haveHelloRetryRequest := msg.(*helloRetryRequestMsg)
 	var secondHelloBytes []byte
 	if haveHelloRetryRequest {
-		c.hadHelloRetryRequest = true
 		if isDraft22(c.wireVersion) {
-			if err := c.readRecord(recordTypeChangeCipherSpec); err != nil {
+			// Explicitly read the ChangeCipherSpec now; it should
+			// be attached to the first flight, not the second flight.
+			if err := c.readTLS13ChangeCipherSpec(); err != nil {
 				return err
 			}
 		}
@@ -718,6 +724,12 @@
 func (hs *clientHandshakeState) doTLS13Handshake() error {
 	c := hs.c
 
+	if isResumptionExperiment(c.wireVersion) && !isDraft22(c.wireVersion) {
+		// Early versions of the middlebox hacks inserted
+		// ChangeCipherSpec differently on 0-RTT and 2-RTT handshakes.
+		c.expectTLS13ChangeCipherSpec = true
+	}
+
 	if isResumptionExperiment(c.wireVersion) && !bytes.Equal(hs.hello.sessionId, hs.serverHello.sessionId) {
 		return errors.New("tls: session IDs did not match.")
 	}
@@ -774,12 +786,6 @@
 		hs.finishedHash.addEntropy(zeroSecret)
 	}
 
-	if isResumptionExperiment(c.wireVersion) && !c.hadHelloRetryRequest {
-		if err := c.readRecord(recordTypeChangeCipherSpec); err != nil {
-			return err
-		}
-	}
-
 	clientLabel := clientHandshakeTrafficLabel
 	serverLabel := serverHandshakeTrafficLabel
 	if isDraft21(c.wireVersion) {
diff --git a/src/ssl/test/runner/handshake_messages.go b/src/ssl/test/runner/handshake_messages.go
index d5e3951..93da121 100644
--- a/src/ssl/test/runner/handshake_messages.go
+++ b/src/ssl/test/runner/handshake_messages.go
@@ -1460,7 +1460,8 @@
 			extensions.addU16(2) // length
 			extensions.addU16(uint16(m.selectedGroup))
 		}
-		if len(m.cookie) > 0 {
+		// m.cookie may be a non-nil empty slice for empty cookie tests.
+		if m.cookie != nil {
 			extensions.addU16(extensionCookie)
 			body := extensions.addU16LengthPrefixed()
 			body.addU16LengthPrefixed().addBytes(m.cookie)
diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go
index 2513a00..4286911 100644
--- a/src/ssl/test/runner/handshake_server.go
+++ b/src/ssl/test/runner/handshake_server.go
@@ -358,6 +358,12 @@
 	c := hs.c
 	config := c.config
 
+	// We've read the ClientHello, so the next record in draft 22 must be
+	// preceded with ChangeCipherSpec.
+	if isDraft22(c.wireVersion) {
+		c.expectTLS13ChangeCipherSpec = true
+	}
+
 	hs.hello = &serverHelloMsg{
 		isDTLS:                c.isDTLS,
 		vers:                  c.wireVersion,
@@ -691,12 +697,6 @@
 				earlyLabel = earlyTrafficLabelDraft21
 			}
 
-			if isDraft22(c.wireVersion) {
-				if err := c.readRecord(recordTypeChangeCipherSpec); err != nil {
-					return err
-				}
-			}
-
 			earlyTrafficSecret := hs.finishedHash.deriveSecret(earlyLabel)
 			if err := c.useInTrafficSecret(c.wireVersion, hs.suite, earlyTrafficSecret); err != nil {
 				return err
@@ -996,11 +996,10 @@
 			}
 		}
 	}
-
-	if isResumptionClientCCSExperiment(c.wireVersion) && !c.skipEarlyData && !encryptedExtensions.extensions.hasEarlyData {
-		if err := c.readRecord(recordTypeChangeCipherSpec); err != nil {
-			return err
-		}
+	if isResumptionClientCCSExperiment(c.wireVersion) && !isDraft22(c.wireVersion) && !hs.clientHello.hasEarlyData {
+		// Early versions of the middlebox hacks inserted
+		// ChangeCipherSpec differently on 0-RTT and 2-RTT handshakes.
+		c.expectTLS13ChangeCipherSpec = true
 	}
 
 	// Switch input stream to handshake traffic keys.
diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go
index 57bc20c..8700af2 100644
--- a/src/ssl/test/runner/runner.go
+++ b/src/ssl/test/runner/runner.go
@@ -2442,9 +2442,10 @@
 					SendLargeRecords: true,
 				},
 			},
-			messageLen:    maxPlaintext + 1,
-			shouldFail:    true,
-			expectedError: ":DATA_LENGTH_TOO_LONG:",
+			messageLen:         maxPlaintext + 1,
+			shouldFail:         true,
+			expectedError:      ":DATA_LENGTH_TOO_LONG:",
+			expectedLocalError: "remote error: record overflow",
 		},
 		{
 			protocol: dtls,
@@ -2454,9 +2455,64 @@
 					SendLargeRecords: true,
 				},
 			},
-			messageLen:    maxPlaintext + 1,
-			shouldFail:    true,
-			expectedError: ":DATA_LENGTH_TOO_LONG:",
+			messageLen:         maxPlaintext + 1,
+			shouldFail:         true,
+			expectedError:      ":DATA_LENGTH_TOO_LONG:",
+			expectedLocalError: "remote error: record overflow",
+		},
+		{
+			name: "LargePlaintext-TLS13-Padded-8192-8192",
+			config: Config{
+				MinVersion: VersionTLS13,
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					RecordPadding:    8192,
+					SendLargeRecords: true,
+				},
+			},
+			messageLen: 8192,
+		},
+		{
+			name: "LargePlaintext-TLS13-Padded-8193-8192",
+			config: Config{
+				MinVersion: VersionTLS13,
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					RecordPadding:    8193,
+					SendLargeRecords: true,
+				},
+			},
+			messageLen:         8192,
+			shouldFail:         true,
+			expectedError:      ":DATA_LENGTH_TOO_LONG:",
+			expectedLocalError: "remote error: record overflow",
+		},
+		{
+			name: "LargePlaintext-TLS13-Padded-16383-1",
+			config: Config{
+				MinVersion: VersionTLS13,
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					RecordPadding:    1,
+					SendLargeRecords: true,
+				},
+			},
+			messageLen: 16383,
+		},
+		{
+			name: "LargePlaintext-TLS13-Padded-16384-1",
+			config: Config{
+				MinVersion: VersionTLS13,
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					RecordPadding:    1,
+					SendLargeRecords: true,
+				},
+			},
+			messageLen:         16384,
+			shouldFail:         true,
+			expectedError:      ":DATA_LENGTH_TOO_LONG:",
+			expectedLocalError: "remote error: record overflow",
 		},
 		{
 			name: "LargeCiphertext",
@@ -8250,8 +8306,8 @@
 		expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
 	})
 
-	// Test that hash preferences are enforced. BoringSSL does not implement
-	// MD5 signatures.
+	// Test that signature preferences are enforced. BoringSSL does not
+	// implement MD5 signatures.
 	testCases = append(testCases, testCase{
 		testType: serverTest,
 		name:     "ClientAuth-Enforced",
@@ -8320,26 +8376,8 @@
 		expectedError: ":WRONG_SIGNATURE_TYPE:",
 	})
 
-	// Test that the agreed upon digest respects the client preferences and
-	// the server digests.
-	testCases = append(testCases, testCase{
-		name: "NoCommonAlgorithms-Digests",
-		config: Config{
-			MaxVersion: VersionTLS12,
-			ClientAuth: RequireAnyClientCert,
-			VerifySignatureAlgorithms: []signatureAlgorithm{
-				signatureRSAPKCS1WithSHA512,
-				signatureRSAPKCS1WithSHA1,
-			},
-		},
-		flags: []string{
-			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
-			"-key-file", path.Join(*resourceDir, rsaKeyFile),
-			"-digest-prefs", "SHA256",
-		},
-		shouldFail:    true,
-		expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
-	})
+	// Test that the negotiated signature algorithm respects the client and
+	// server preferences.
 	testCases = append(testCases, testCase{
 		name: "NoCommonAlgorithms",
 		config: Config{
@@ -8389,7 +8427,8 @@
 		flags: []string{
 			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
 			"-key-file", path.Join(*resourceDir, rsaKeyFile),
-			"-digest-prefs", "SHA256,SHA1",
+			"-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA256)),
+			"-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA1)),
 		},
 		expectedPeerSignatureAlgorithm: signatureRSAPKCS1WithSHA256,
 	})
@@ -8405,7 +8444,9 @@
 		flags: []string{
 			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
 			"-key-file", path.Join(*resourceDir, rsaKeyFile),
-			"-digest-prefs", "SHA512,SHA256,SHA1",
+			"-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA512)),
+			"-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA256)),
+			"-signing-prefs", strconv.Itoa(int(signatureRSAPKCS1WithSHA1)),
 		},
 		expectedPeerSignatureAlgorithm: signatureRSAPKCS1WithSHA1,
 	})
@@ -10793,13 +10834,38 @@
 		},
 	})
 
+	ret = append(ret, perMessageTest{
+		messageType: typeEndOfEarlyData,
+		test: testCase{
+			testType: serverTest,
+			name:     "TLS13Draft22-EndOfEarlyData",
+			config: Config{
+				MaxVersion: VersionTLS13,
+			},
+			resumeConfig: &Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: true,
+				},
+			},
+			tls13Variant:  TLS13Draft22,
+			resumeSession: true,
+			flags:         []string{"-enable-early-data"},
+		},
+	})
+
 	return ret
 }
 
 func addWrongMessageTypeTests() {
 	for _, t := range makePerMessageTests() {
 		t.test.name = "WrongMessageType-" + t.test.name
-		t.test.config.Bugs.SendWrongMessageType = t.messageType
+		if t.test.resumeConfig != nil {
+			t.test.resumeConfig.Bugs.SendWrongMessageType = t.messageType
+		} else {
+			t.test.config.Bugs.SendWrongMessageType = t.messageType
+		}
 		t.test.shouldFail = true
 		t.test.expectedError = ":UNEXPECTED_MESSAGE:"
 		t.test.expectedLocalError = "remote error: unexpected message"
@@ -10834,7 +10900,11 @@
 func addTrailingMessageDataTests() {
 	for _, t := range makePerMessageTests() {
 		t.test.name = "TrailingMessageData-" + t.test.name
-		t.test.config.Bugs.SendTrailingMessageData = t.messageType
+		if t.test.resumeConfig != nil {
+			t.test.resumeConfig.Bugs.SendTrailingMessageData = t.messageType
+		} else {
+			t.test.config.Bugs.SendTrailingMessageData = t.messageType
+		}
 		t.test.shouldFail = true
 		t.test.expectedError = ":DECODE_ERROR:"
 		t.test.expectedLocalError = "remote error: error decoding message"
@@ -10858,59 +10928,6 @@
 }
 
 func addTLS13HandshakeTests() {
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "NegotiatePSKResumption-TLS13",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				NegotiatePSKResumption: true,
-			},
-		},
-		resumeSession: true,
-		shouldFail:    true,
-		expectedError: ":MISSING_KEY_SHARE:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "MissingKeyShare-Client",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				MissingKeyShare: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":MISSING_KEY_SHARE:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "MissingKeyShare-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				MissingKeyShare: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":MISSING_KEY_SHARE:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "DuplicateKeyShares",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				DuplicateKeyShares: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":DUPLICATE_KEY_SHARE:",
-	})
-
 	for _, version := range allVersions(tls) {
 		if version.version != VersionTLS13 {
 			continue
@@ -10919,6 +10936,63 @@
 		variant := version.tls13Variant
 
 		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "NegotiatePSKResumption-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					NegotiatePSKResumption: true,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			shouldFail:    true,
+			expectedError: ":MISSING_KEY_SHARE:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "MissingKeyShare-Client-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					MissingKeyShare: true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":MISSING_KEY_SHARE:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "MissingKeyShare-Server-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					MissingKeyShare: true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":MISSING_KEY_SHARE:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "DuplicateKeyShares-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					DuplicateKeyShares: true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":DUPLICATE_KEY_SHARE:",
+		})
+
+		testCases = append(testCases, testCase{
 			testType: serverTest,
 			name:     "SkipEarlyData-" + name,
 			config: Config{
@@ -11091,1266 +11165,1357 @@
 				"-expect-accept-early-data",
 			},
 		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-OmitEarlyDataExtension-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendFakeEarlyDataLength: 4,
+					OmitEarlyDataExtension:  true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-TooMuchData-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendFakeEarlyDataLength: 16384 + 1,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-Interleaved-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendFakeEarlyDataLength: 4,
+					InterleaveEarlyData:     true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-EarlyDataInTLS12-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendFakeEarlyDataLength: 4,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":UNEXPECTED_RECORD:",
+			flags:         []string{"-max-version", strconv.Itoa(VersionTLS12)},
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-HRR-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendFakeEarlyDataLength: 4,
+				},
+				DefaultCurves: []CurveID{},
+			},
+			tls13Variant: variant,
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-HRR-Interleaved-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendFakeEarlyDataLength: 4,
+					InterleaveEarlyData:     true,
+				},
+				DefaultCurves: []CurveID{},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":UNEXPECTED_RECORD:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-HRR-TooMuchData-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendFakeEarlyDataLength: 16384 + 1,
+				},
+				DefaultCurves: []CurveID{},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
+		})
+
+		// Test that skipping early data looking for cleartext correctly
+		// processes an alert record.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-HRR-FatalAlert-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendEarlyAlert:          true,
+					SendFakeEarlyDataLength: 4,
+				},
+				DefaultCurves: []CurveID{},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":SSLV3_ALERT_HANDSHAKE_FAILURE:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SkipEarlyData-SecondClientHelloEarlyData-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendEarlyDataOnSecondClientHello: true,
+				},
+				DefaultCurves: []CurveID{},
+			},
+			tls13Variant:       variant,
+			shouldFail:         true,
+			expectedLocalError: "remote error: bad record MAC",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EmptyEncryptedExtensions-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					EmptyEncryptedExtensions: true,
+				},
+			},
+			tls13Variant:       variant,
+			shouldFail:         true,
+			expectedLocalError: "remote error: error decoding message",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EncryptedExtensionsWithKeyShare-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					EncryptedExtensionsWithKeyShare: true,
+				},
+			},
+			tls13Variant:       variant,
+			shouldFail:         true,
+			expectedLocalError: "remote error: unsupported extension",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SendHelloRetryRequest-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				// Require a HelloRetryRequest for every curve.
+				DefaultCurves: []CurveID{},
+			},
+			tls13Variant:    variant,
+			expectedCurveID: CurveX25519,
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SendHelloRetryRequest-2-" + name,
+			config: Config{
+				MaxVersion:    VersionTLS13,
+				DefaultCurves: []CurveID{CurveP384},
+			},
+			tls13Variant: variant,
+			// Although the ClientHello did not predict our preferred curve,
+			// we always select it whether it is predicted or not.
+			expectedCurveID: CurveX25519,
+		})
+
+		testCases = append(testCases, testCase{
+			name: "UnknownCurve-HelloRetryRequest-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				// P-384 requires HelloRetryRequest in BoringSSL.
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					SendHelloRetryRequestCurve: bogusCurve,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":WRONG_CURVE:",
+		})
+
+		if isDraft21(version.versionWire) {
+			testCases = append(testCases, testCase{
+				name: "HelloRetryRequest-CipherChange-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					// P-384 requires HelloRetryRequest in BoringSSL.
+					CurvePreferences: []CurveID{CurveP384},
+					Bugs: ProtocolBugs{
+						SendCipherSuite:                  TLS_AES_128_GCM_SHA256,
+						SendHelloRetryRequestCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
+					},
+				},
+				tls13Variant:  variant,
+				shouldFail:    true,
+				expectedError: ":WRONG_CIPHER_RETURNED:",
+			})
+
+			// Test that the client does not offer a PSK in the second ClientHello if the
+			// HelloRetryRequest is incompatible with it.
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				name:     "HelloRetryRequest-NonResumableCipher-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					CipherSuites: []uint16{
+						TLS_AES_128_GCM_SHA256,
+					},
+				},
+				resumeConfig: &Config{
+					MaxVersion: VersionTLS13,
+					// P-384 requires HelloRetryRequest in BoringSSL.
+					CurvePreferences: []CurveID{CurveP384},
+					Bugs: ProtocolBugs{
+						ExpectNoTLS13PSKAfterHRR: true,
+					},
+					CipherSuites: []uint16{
+						TLS_AES_256_GCM_SHA384,
+					},
+				},
+				tls13Variant:         variant,
+				resumeSession:        true,
+				expectResumeRejected: true,
+			})
+		}
+
+		testCases = append(testCases, testCase{
+			name: "DisabledCurve-HelloRetryRequest-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				CurvePreferences: []CurveID{CurveP256},
+				Bugs: ProtocolBugs{
+					IgnorePeerCurvePreferences: true,
+				},
+			},
+			tls13Variant:  variant,
+			flags:         []string{"-p384-only"},
+			shouldFail:    true,
+			expectedError: ":WRONG_CURVE:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "UnnecessaryHelloRetryRequest-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				CurvePreferences: []CurveID{CurveX25519},
+				Bugs: ProtocolBugs{
+					SendHelloRetryRequestCurve: CurveX25519,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":WRONG_CURVE:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "SecondHelloRetryRequest-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				// P-384 requires HelloRetryRequest in BoringSSL.
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					SecondHelloRetryRequest: true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":UNEXPECTED_MESSAGE:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequest-Empty-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					AlwaysSendHelloRetryRequest: true,
+				},
+			},
+			tls13Variant:       variant,
+			shouldFail:         true,
+			expectedError:      ":EMPTY_HELLO_RETRY_REQUEST:",
+			expectedLocalError: "remote error: illegal parameter",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequest-DuplicateCurve-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				// P-384 requires a HelloRetryRequest against BoringSSL's default
+				// configuration. Assert this ExpectMissingKeyShare.
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					ExpectMissingKeyShare:                true,
+					DuplicateHelloRetryRequestExtensions: true,
+				},
+			},
+			tls13Variant:       variant,
+			shouldFail:         true,
+			expectedError:      ":DUPLICATE_EXTENSION:",
+			expectedLocalError: "remote error: illegal parameter",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequest-Cookie-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendHelloRetryRequestCookie: []byte("cookie"),
+				},
+			},
+			tls13Variant: variant,
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequest-DuplicateCookie-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendHelloRetryRequestCookie:          []byte("cookie"),
+					DuplicateHelloRetryRequestExtensions: true,
+				},
+			},
+			tls13Variant:       variant,
+			shouldFail:         true,
+			expectedError:      ":DUPLICATE_EXTENSION:",
+			expectedLocalError: "remote error: illegal parameter",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequest-EmptyCookie-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendHelloRetryRequestCookie: []byte{},
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":DECODE_ERROR:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequest-Cookie-Curve-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				// P-384 requires HelloRetryRequest in BoringSSL.
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					SendHelloRetryRequestCookie: []byte("cookie"),
+					ExpectMissingKeyShare:       true,
+				},
+			},
+			tls13Variant: variant,
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequest-Unknown-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					CustomHelloRetryRequestExtension: "extension",
+				},
+			},
+			tls13Variant:       variant,
+			shouldFail:         true,
+			expectedError:      ":UNEXPECTED_EXTENSION:",
+			expectedLocalError: "remote error: unsupported extension",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SecondClientHelloMissingKeyShare-" + name,
+			config: Config{
+				MaxVersion:    VersionTLS13,
+				DefaultCurves: []CurveID{},
+				Bugs: ProtocolBugs{
+					SecondClientHelloMissingKeyShare: true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":MISSING_KEY_SHARE:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "SecondClientHelloWrongCurve-" + name,
+			config: Config{
+				MaxVersion:    VersionTLS13,
+				DefaultCurves: []CurveID{},
+				Bugs: ProtocolBugs{
+					MisinterpretHelloRetryRequestCurve: CurveP521,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":WRONG_CURVE:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequestVersionMismatch-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				// P-384 requires HelloRetryRequest in BoringSSL.
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					SendServerHelloVersion: 0x0305,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":WRONG_VERSION_NUMBER:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "HelloRetryRequestCurveMismatch-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				// P-384 requires HelloRetryRequest in BoringSSL.
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					// Send P-384 (correct) in the HelloRetryRequest.
+					SendHelloRetryRequestCurve: CurveP384,
+					// But send P-256 in the ServerHello.
+					SendCurve: CurveP256,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":WRONG_CURVE:",
+		})
+
+		// Test the server selecting a curve that requires a HelloRetryRequest
+		// without sending it.
+		testCases = append(testCases, testCase{
+			name: "SkipHelloRetryRequest-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				// P-384 requires HelloRetryRequest in BoringSSL.
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					SkipHelloRetryRequest: true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":WRONG_CURVE:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "RequestContextInHandshake-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				MinVersion: VersionTLS13,
+				ClientAuth: RequireAnyClientCert,
+				Bugs: ProtocolBugs{
+					SendRequestContext: []byte("request context"),
+				},
+			},
+			tls13Variant: variant,
+			flags: []string{
+				"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+				"-key-file", path.Join(*resourceDir, rsaKeyFile),
+			},
+			shouldFail:    true,
+			expectedError: ":DECODE_ERROR:",
+		})
+
+		if isDraft21(version.versionWire) {
+			testCases = append(testCases, testCase{
+				name: "UnknownExtensionInCertificateRequest-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					MinVersion: VersionTLS13,
+					ClientAuth: RequireAnyClientCert,
+					Bugs: ProtocolBugs{
+						SendCustomCertificateRequest: 0x1212,
+					},
+				},
+				tls13Variant: variant,
+				flags: []string{
+					"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+					"-key-file", path.Join(*resourceDir, rsaKeyFile),
+				},
+			})
+
+			testCases = append(testCases, testCase{
+				name: "MissingSignatureAlgorithmsInCertificateRequest-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					MinVersion: VersionTLS13,
+					ClientAuth: RequireAnyClientCert,
+					Bugs: ProtocolBugs{
+						OmitCertificateRequestAlgorithms: true,
+					},
+				},
+				tls13Variant: variant,
+				flags: []string{
+					"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+					"-key-file", path.Join(*resourceDir, rsaKeyFile),
+				},
+				shouldFail:    true,
+				expectedError: ":DECODE_ERROR:",
+			})
+		}
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "TrailingKeyShareData-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					TrailingKeyShareData: true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":DECODE_ERROR:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "AlwaysSelectPSKIdentity-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					AlwaysSelectPSKIdentity: true,
+				},
+			},
+			tls13Variant:  variant,
+			shouldFail:    true,
+			expectedError: ":UNEXPECTED_EXTENSION:",
+		})
+
+		testCases = append(testCases, testCase{
+			name: "InvalidPSKIdentity-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SelectPSKIdentityOnResume: 1,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			shouldFail:    true,
+			expectedError: ":PSK_IDENTITY_NOT_FOUND:",
+		})
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "ExtraPSKIdentity-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					ExtraPSKIdentity:   true,
+					SendExtraPSKBinder: true,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+		})
+
+		// Test that unknown NewSessionTicket extensions are tolerated.
+		testCases = append(testCases, testCase{
+			name: "CustomTicketExtension-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					CustomTicketExtension: "1234",
+				},
+			},
+			tls13Variant: variant,
+		})
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-RejectTicket-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Certificates:     []Certificate{rsaCertificate},
+			},
+			resumeConfig: &Config{
+				MaxVersion:             VersionTLS13,
+				MaxEarlyDataSize:       16384,
+				Certificates:           []Certificate{ecdsaP256Certificate},
+				SessionTicketsDisabled: true,
+			},
+			tls13Variant:         variant,
+			resumeSession:        true,
+			expectResumeRejected: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-reject-early-data",
+				"-on-resume-shim-writes-first",
+				"-on-initial-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+				"-on-resume-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile),
+				"-on-retry-expect-peer-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile),
+				// Session tickets are disabled, so the runner will not send a ticket.
+				"-on-retry-expect-no-session",
+			},
+		})
+
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-HRR-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			resumeConfig: &Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Bugs: ProtocolBugs{
+					SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-reject-early-data",
+			},
+		})
+
+		// The client must check the server does not send the early_data
+		// extension while rejecting the session.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyDataWithoutResume-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			resumeConfig: &Config{
+				MaxVersion:             VersionTLS13,
+				SessionTicketsDisabled: true,
+				Bugs: ProtocolBugs{
+					SendEarlyDataExtension: true,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+			},
+			shouldFail:    true,
+			expectedError: ":UNEXPECTED_EXTENSION:",
+		})
+
+		// The client must fail with a dedicated error code if the server
+		// responds with TLS 1.2 when offering 0-RTT.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyDataVersionDowngrade-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			resumeConfig: &Config{
+				MaxVersion: VersionTLS12,
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+			},
+			shouldFail:    true,
+			expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
+		})
+
+		// Test that the client rejects an (unsolicited) early_data extension if
+		// the server sent an HRR.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "ServerAcceptsEarlyDataOnHRR-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			resumeConfig: &Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Bugs: ProtocolBugs{
+					SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
+					SendEarlyDataExtension:      true,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-reject-early-data",
+			},
+			shouldFail:    true,
+			expectedError: ":UNEXPECTED_EXTENSION:",
+		})
+
+		if isDraft22(version.versionWire) {
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				name:     "SkipChangeCipherSpec-Client-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					Bugs: ProtocolBugs{
+						SkipChangeCipherSpec: true,
+					},
+				},
+				tls13Variant: variant,
+			})
+
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "SkipChangeCipherSpec-Server-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					Bugs: ProtocolBugs{
+						SkipChangeCipherSpec: true,
+					},
+				},
+				tls13Variant: variant,
+			})
+
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				name:     "TooManyChangeCipherSpec-Client-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					Bugs: ProtocolBugs{
+						SendExtraChangeCipherSpec: 33,
+					},
+				},
+				tls13Variant:  variant,
+				shouldFail:    true,
+				expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
+			})
+
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "TooManyChangeCipherSpec-Server-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					Bugs: ProtocolBugs{
+						SendExtraChangeCipherSpec: 33,
+					},
+				},
+				tls13Variant:  variant,
+				shouldFail:    true,
+				expectedError: ":TOO_MANY_EMPTY_FRAGMENTS:",
+			})
+
+			testCases = append(testCases, testCase{
+				name: "SendPostHandshakeChangeCipherSpec-" + name,
+				config: Config{
+					MaxVersion: VersionTLS13,
+					Bugs: ProtocolBugs{
+						SendPostHandshakeChangeCipherSpec: true,
+					},
+				},
+				tls13Variant:       variant,
+				shouldFail:         true,
+				expectedError:      ":UNEXPECTED_RECORD:",
+				expectedLocalError: "remote error: unexpected message",
+			})
+		}
+
+		fooString := "foo"
+		barString := "bar"
+
+		// Test that the client reports the correct ALPN after a 0-RTT reject
+		// that changed it.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-ALPNMismatch-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Bugs: ProtocolBugs{
+					ALPNProtocol: &fooString,
+				},
+			},
+			resumeConfig: &Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Bugs: ProtocolBugs{
+					ALPNProtocol: &barString,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-advertise-alpn", "\x03foo\x03bar",
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-reject-early-data",
+				"-on-initial-expect-alpn", "foo",
+				"-on-resume-expect-alpn", "foo",
+				"-on-retry-expect-alpn", "bar",
+			},
+		})
+
+		// Test that the client reports the correct ALPN after a 0-RTT reject if
+		// ALPN was omitted from the first connection.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-ALPNOmitted1-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			resumeConfig: &Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				NextProtos:       []string{"foo"},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-advertise-alpn", "\x03foo\x03bar",
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-reject-early-data",
+				"-on-initial-expect-alpn", "",
+				"-on-resume-expect-alpn", "",
+				"-on-retry-expect-alpn", "foo",
+				"-on-resume-shim-writes-first",
+			},
+		})
+
+		// Test that the client reports the correct ALPN after a 0-RTT reject if
+		// ALPN was omitted from the second connection.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-ALPNOmitted2-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				NextProtos:       []string{"foo"},
+			},
+			resumeConfig: &Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-advertise-alpn", "\x03foo\x03bar",
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-reject-early-data",
+				"-on-initial-expect-alpn", "foo",
+				"-on-resume-expect-alpn", "foo",
+				"-on-retry-expect-alpn", "",
+				"-on-resume-shim-writes-first",
+			},
+		})
+
+		// Test that the client enforces ALPN match on 0-RTT accept.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-BadALPNMismatch-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Bugs: ProtocolBugs{
+					ALPNProtocol: &fooString,
+				},
+			},
+			resumeConfig: &Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Bugs: ProtocolBugs{
+					AlwaysAcceptEarlyData: true,
+					ALPNProtocol:          &barString,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-advertise-alpn", "\x03foo\x03bar",
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-on-initial-expect-alpn", "foo",
+				"-on-resume-expect-alpn", "foo",
+				"-on-retry-expect-alpn", "bar",
+			},
+			shouldFail:    true,
+			expectedError: ":ALPN_MISMATCH_ON_EARLY_DATA:",
+		})
+
+		// Test that the client does not offer early data if it is incompatible
+		// with ALPN preferences.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-ALPNPreferenceChanged-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				NextProtos:       []string{"foo", "bar"},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-no-offer-early-data",
+				"-on-initial-advertise-alpn", "\x03foo",
+				"-on-resume-advertise-alpn", "\x03bar",
+				"-on-initial-expect-alpn", "foo",
+				"-on-resume-expect-alpn", "bar",
+			},
+		})
+
+		// Test that the server correctly rejects 0-RTT when the previous
+		// session did not allow early data on resumption.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyData-NonZeroRTTSession-Server-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+			},
+			resumeConfig: &Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: false,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-on-resume-enable-early-data",
+				"-expect-reject-early-data",
+			},
+		})
+
+		// Test that we reject early data where ALPN is omitted from the first
+		// connection.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyData-ALPNOmitted1-Server-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				NextProtos: []string{},
+			},
+			resumeConfig: &Config{
+				MaxVersion: VersionTLS13,
+				NextProtos: []string{"foo"},
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: false,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-on-initial-select-alpn", "",
+				"-on-resume-select-alpn", "foo",
+			},
+		})
+
+		// Test that we reject early data where ALPN is omitted from the second
+		// connection.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyData-ALPNOmitted2-Server-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				NextProtos: []string{"foo"},
+			},
+			resumeConfig: &Config{
+				MaxVersion: VersionTLS13,
+				NextProtos: []string{},
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: false,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-on-initial-select-alpn", "foo",
+				"-on-resume-select-alpn", "",
+			},
+		})
+
+		// Test that we reject early data with mismatched ALPN.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyData-ALPNMismatch-Server-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				NextProtos: []string{"foo"},
+			},
+			resumeConfig: &Config{
+				MaxVersion: VersionTLS13,
+				NextProtos: []string{"bar"},
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: false,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-on-initial-select-alpn", "foo",
+				"-on-resume-select-alpn", "bar",
+			},
+		})
+
+		// Test that the client offering 0-RTT and Channel ID forbids the server
+		// from accepting both.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyDataChannelID-AcceptBoth-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				RequestChannelID: true,
+			},
+			tls13Variant:    variant,
+			resumeSession:   true,
+			expectChannelID: true,
+			shouldFail:      true,
+			expectedError:   ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
+			},
+		})
+
+		// Test that the client offering Channel ID and 0-RTT allows the server
+		// to decline 0-RTT.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyDataChannelID-AcceptChannelID-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				RequestChannelID: true,
+				Bugs: ProtocolBugs{
+					AlwaysRejectEarlyData: true,
+				},
+			},
+			tls13Variant:    variant,
+			resumeSession:   true,
+			expectChannelID: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
+				"-expect-reject-early-data",
+			},
+		})
+
+		// Test that the client offering Channel ID and 0-RTT allows the server
+		// to decline Channel ID.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyDataChannelID-AcceptEarlyData-Client-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
+				"-expect-accept-early-data",
+			},
+		})
+
+		// Test that the server supporting Channel ID and 0-RTT declines 0-RTT
+		// if it would negotiate Channel ID.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyDataChannelID-OfferBoth-Server-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				ChannelID:  channelIDKey,
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: false,
+				},
+			},
+			tls13Variant:    variant,
+			resumeSession:   true,
+			expectChannelID: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-reject-early-data",
+				"-expect-channel-id",
+				base64.StdEncoding.EncodeToString(channelIDBytes),
+			},
+		})
+
+		// Test that the server supporting Channel ID and 0-RTT accepts 0-RTT
+		// if not offered Channel ID.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyDataChannelID-OfferEarlyData-Server-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: true,
+					ExpectHalfRTTData:       [][]byte{{254, 253, 252, 251}},
+				},
+			},
+			tls13Variant:    variant,
+			resumeSession:   true,
+			expectChannelID: false,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-accept-early-data",
+				"-enable-channel-id",
+			},
+		})
+
+		// Test that the server rejects 0-RTT streams without end_of_early_data.
+		// The subsequent records should fail to decrypt.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyData-SkipEndOfEarlyData-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: true,
+					SkipEndOfEarlyData:      true,
+				},
+			},
+			tls13Variant:       variant,
+			resumeSession:      true,
+			flags:              []string{"-enable-early-data"},
+			shouldFail:         true,
+			expectedLocalError: "remote error: bad record MAC",
+			expectedError:      ":BAD_DECRYPT:",
+		})
+
+		expectedError := ":UNEXPECTED_RECORD:"
+		if isDraft21(version.versionWire) {
+			// In draft-21 and up, early data is expected to be
+			// terminated by a handshake message, though we test
+			// with the wrong one.
+			expectedError = ":UNEXPECTED_MESSAGE:"
+		}
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyData-UnexpectedHandshake-Server-" + name,
+			config: Config{
+				MaxVersion: VersionTLS13,
+			},
+			resumeConfig: &Config{
+				MaxVersion: VersionTLS13,
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					SendStrayEarlyHandshake: true,
+					ExpectEarlyDataAccepted: true,
+				},
+			},
+			tls13Variant:       variant,
+			resumeSession:      true,
+			shouldFail:         true,
+			expectedError:      expectedError,
+			expectedLocalError: "remote error: unexpected message",
+			flags: []string{
+				"-enable-early-data",
+			},
+		})
+
+		// Test that the client reports TLS 1.3 as the version while sending
+		// early data.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-Client-VersionAPI-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-accept-early-data",
+				"-expect-version", strconv.Itoa(VersionTLS13),
+			},
+		})
+
+		// Test that client and server both notice handshake errors after data
+		// has started flowing.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "EarlyData-Client-BadFinished-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			resumeConfig: &Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Bugs: ProtocolBugs{
+					BadFinished: true,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-ticket-supports-early-data",
+				"-expect-accept-early-data",
+			},
+			shouldFail:         true,
+			expectedError:      ":DIGEST_CHECK_FAILED:",
+			expectedLocalError: "remote error: error decrypting message",
+		})
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "EarlyData-Server-BadFinished-" + name,
+			config: Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+			},
+			resumeConfig: &Config{
+				MaxVersion:       VersionTLS13,
+				MaxEarlyDataSize: 16384,
+				Bugs: ProtocolBugs{
+					SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+					ExpectEarlyDataAccepted: true,
+					ExpectHalfRTTData:       [][]byte{{254, 253, 252, 251}},
+					BadFinished:             true,
+				},
+			},
+			tls13Variant:  variant,
+			resumeSession: true,
+			flags: []string{
+				"-enable-early-data",
+				"-expect-accept-early-data",
+			},
+			shouldFail:         true,
+			expectedError:      ":DIGEST_CHECK_FAILED:",
+			expectedLocalError: "remote error: error decrypting message",
+		})
+
+		if isDraft21(version.versionWire) {
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				name:     "Server-NonEmptyEndOfEarlyData-" + name,
+				config: Config{
+					MaxVersion:       VersionTLS13,
+					MaxEarlyDataSize: 16384,
+				},
+				resumeConfig: &Config{
+					MaxVersion:       VersionTLS13,
+					MaxEarlyDataSize: 16384,
+					Bugs: ProtocolBugs{
+						SendEarlyData:           [][]byte{{1, 2, 3, 4}},
+						ExpectEarlyDataAccepted: true,
+						NonEmptyEndOfEarlyData:  true,
+					},
+				},
+				resumeSession: true,
+				flags: []string{
+					"-enable-early-data",
+					"-expect-ticket-supports-early-data",
+					"-expect-accept-early-data",
+				},
+				tls13Variant:  variant,
+				shouldFail:    true,
+				expectedError: ":DECODE_ERROR:",
+			})
+		}
+
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "ServerSkipCertificateVerify-" + name,
+			config: Config{
+				MinVersion:   VersionTLS13,
+				MaxVersion:   VersionTLS13,
+				Certificates: []Certificate{rsaChainCertificate},
+				Bugs: ProtocolBugs{
+					SkipCertificateVerify: true,
+				},
+			},
+			tls13Variant:          variant,
+			expectPeerCertificate: &rsaChainCertificate,
+			flags: []string{
+				"-cert-file", path.Join(*resourceDir, rsaChainCertificateFile),
+				"-key-file", path.Join(*resourceDir, rsaChainKeyFile),
+				"-require-any-client-certificate",
+			},
+			shouldFail:         true,
+			expectedError:      ":UNEXPECTED_MESSAGE:",
+			expectedLocalError: "remote error: unexpected message",
+		})
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "ClientSkipCertificateVerify-" + name,
+			config: Config{
+				MinVersion:   VersionTLS13,
+				MaxVersion:   VersionTLS13,
+				Certificates: []Certificate{rsaChainCertificate},
+				Bugs: ProtocolBugs{
+					SkipCertificateVerify: true,
+				},
+			},
+			tls13Variant:          variant,
+			expectPeerCertificate: &rsaChainCertificate,
+			flags: []string{
+				"-cert-file", path.Join(*resourceDir, rsaChainCertificateFile),
+				"-key-file", path.Join(*resourceDir, rsaChainKeyFile),
+			},
+			shouldFail:         true,
+			expectedError:      ":UNEXPECTED_MESSAGE:",
+			expectedLocalError: "remote error: unexpected message",
+		})
 	}
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-OmitEarlyDataExtension",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendFakeEarlyDataLength: 4,
-				OmitEarlyDataExtension:  true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-TooMuchData",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendFakeEarlyDataLength: 16384 + 1,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-Interleaved",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendFakeEarlyDataLength: 4,
-				InterleaveEarlyData:     true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-EarlyDataInTLS12",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendFakeEarlyDataLength: 4,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":UNEXPECTED_RECORD:",
-		flags:         []string{"-max-version", strconv.Itoa(VersionTLS12)},
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-HRR",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendFakeEarlyDataLength: 4,
-			},
-			DefaultCurves: []CurveID{},
-		},
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-HRR-Interleaved",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendFakeEarlyDataLength: 4,
-				InterleaveEarlyData:     true,
-			},
-			DefaultCurves: []CurveID{},
-		},
-		shouldFail:    true,
-		expectedError: ":UNEXPECTED_RECORD:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-HRR-TooMuchData",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendFakeEarlyDataLength: 16384 + 1,
-			},
-			DefaultCurves: []CurveID{},
-		},
-		shouldFail:    true,
-		expectedError: ":TOO_MUCH_SKIPPED_EARLY_DATA:",
-	})
-
-	// Test that skipping early data looking for cleartext correctly
-	// processes an alert record.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-HRR-FatalAlert",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendEarlyAlert:          true,
-				SendFakeEarlyDataLength: 4,
-			},
-			DefaultCurves: []CurveID{},
-		},
-		shouldFail:    true,
-		expectedError: ":SSLV3_ALERT_HANDSHAKE_FAILURE:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SkipEarlyData-SecondClientHelloEarlyData",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendEarlyDataOnSecondClientHello: true,
-			},
-			DefaultCurves: []CurveID{},
-		},
-		shouldFail:         true,
-		expectedLocalError: "remote error: bad record MAC",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "EmptyEncryptedExtensions",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				EmptyEncryptedExtensions: true,
-			},
-		},
-		shouldFail:         true,
-		expectedLocalError: "remote error: error decoding message",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "EncryptedExtensionsWithKeyShare",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				EncryptedExtensionsWithKeyShare: true,
-			},
-		},
-		shouldFail:         true,
-		expectedLocalError: "remote error: unsupported extension",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SendHelloRetryRequest",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// Require a HelloRetryRequest for every curve.
-			DefaultCurves: []CurveID{},
-		},
-		expectedCurveID: CurveX25519,
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SendHelloRetryRequest-2",
-		config: Config{
-			MaxVersion:    VersionTLS13,
-			DefaultCurves: []CurveID{CurveP384},
-		},
-		// Although the ClientHello did not predict our preferred curve,
-		// we always select it whether it is predicted or not.
-		expectedCurveID: CurveX25519,
-	})
-
-	testCases = append(testCases, testCase{
-		name: "UnknownCurve-HelloRetryRequest",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires HelloRetryRequest in BoringSSL.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				SendHelloRetryRequestCurve: bogusCurve,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":WRONG_CURVE:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "TLS13Draft21-HelloRetryRequest-CipherChange",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires HelloRetryRequest in BoringSSL.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				SendCipherSuite:                  TLS_AES_128_GCM_SHA256,
-				SendHelloRetryRequestCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
-			},
-		},
-		tls13Variant:  TLS13Draft21,
-		shouldFail:    true,
-		expectedError: ":WRONG_CIPHER_RETURNED:",
-	})
-
-	// Test that the client does not offer a PSK in the second ClientHello if the
-	// HelloRetryRequest is incompatible with it.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13Draft21-HelloRetryRequest-NonResumableCipher",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			CipherSuites: []uint16{
-				TLS_AES_128_GCM_SHA256,
-			},
-		},
-		resumeConfig: &Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires HelloRetryRequest in BoringSSL.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				ExpectNoTLS13PSKAfterHRR: true,
-			},
-			CipherSuites: []uint16{
-				TLS_AES_256_GCM_SHA384,
-			},
-		},
-		tls13Variant:         TLS13Draft21,
-		resumeSession:        true,
-		expectResumeRejected: true,
-	})
-
-	testCases = append(testCases, testCase{
-		name: "DisabledCurve-HelloRetryRequest",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			CurvePreferences: []CurveID{CurveP256},
-			Bugs: ProtocolBugs{
-				IgnorePeerCurvePreferences: true,
-			},
-		},
-		flags:         []string{"-p384-only"},
-		shouldFail:    true,
-		expectedError: ":WRONG_CURVE:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "UnnecessaryHelloRetryRequest",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			CurvePreferences: []CurveID{CurveX25519},
-			Bugs: ProtocolBugs{
-				SendHelloRetryRequestCurve: CurveX25519,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":WRONG_CURVE:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "SecondHelloRetryRequest",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires HelloRetryRequest in BoringSSL.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				SecondHelloRetryRequest: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":UNEXPECTED_MESSAGE:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequest-Empty",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				AlwaysSendHelloRetryRequest: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":DECODE_ERROR:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequest-DuplicateCurve",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires a HelloRetryRequest against BoringSSL's default
-			// configuration. Assert this ExpectMissingKeyShare.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				ExpectMissingKeyShare:                true,
-				DuplicateHelloRetryRequestExtensions: true,
-			},
-		},
-		shouldFail:         true,
-		expectedError:      ":DUPLICATE_EXTENSION:",
-		expectedLocalError: "remote error: illegal parameter",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequest-Cookie",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendHelloRetryRequestCookie: []byte("cookie"),
-			},
-		},
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequest-DuplicateCookie",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendHelloRetryRequestCookie:          []byte("cookie"),
-				DuplicateHelloRetryRequestExtensions: true,
-			},
-		},
-		shouldFail:         true,
-		expectedError:      ":DUPLICATE_EXTENSION:",
-		expectedLocalError: "remote error: illegal parameter",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequest-EmptyCookie",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendHelloRetryRequestCookie: []byte{},
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":DECODE_ERROR:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequest-Cookie-Curve",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires HelloRetryRequest in BoringSSL.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				SendHelloRetryRequestCookie: []byte("cookie"),
-				ExpectMissingKeyShare:       true,
-			},
-		},
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequest-Unknown",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				CustomHelloRetryRequestExtension: "extension",
-			},
-		},
-		shouldFail:         true,
-		expectedError:      ":UNEXPECTED_EXTENSION:",
-		expectedLocalError: "remote error: unsupported extension",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SecondClientHelloMissingKeyShare",
-		config: Config{
-			MaxVersion:    VersionTLS13,
-			DefaultCurves: []CurveID{},
-			Bugs: ProtocolBugs{
-				SecondClientHelloMissingKeyShare: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":MISSING_KEY_SHARE:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "SecondClientHelloWrongCurve",
-		config: Config{
-			MaxVersion:    VersionTLS13,
-			DefaultCurves: []CurveID{},
-			Bugs: ProtocolBugs{
-				MisinterpretHelloRetryRequestCurve: CurveP521,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":WRONG_CURVE:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequestVersionMismatch",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires HelloRetryRequest in BoringSSL.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				SendServerHelloVersion: 0x0305,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":WRONG_VERSION_NUMBER:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "HelloRetryRequestCurveMismatch",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires HelloRetryRequest in BoringSSL.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				// Send P-384 (correct) in the HelloRetryRequest.
-				SendHelloRetryRequestCurve: CurveP384,
-				// But send P-256 in the ServerHello.
-				SendCurve: CurveP256,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":WRONG_CURVE:",
-	})
-
-	// Test the server selecting a curve that requires a HelloRetryRequest
-	// without sending it.
-	testCases = append(testCases, testCase{
-		name: "SkipHelloRetryRequest",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			// P-384 requires HelloRetryRequest in BoringSSL.
-			CurvePreferences: []CurveID{CurveP384},
-			Bugs: ProtocolBugs{
-				SkipHelloRetryRequest: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":WRONG_CURVE:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "TLS13-RequestContextInHandshake",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			MinVersion: VersionTLS13,
-			ClientAuth: RequireAnyClientCert,
-			Bugs: ProtocolBugs{
-				SendRequestContext: []byte("request context"),
-			},
-		},
-		flags: []string{
-			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
-			"-key-file", path.Join(*resourceDir, rsaKeyFile),
-		},
-		shouldFail:    true,
-		expectedError: ":DECODE_ERROR:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "TLS13-UnknownInCertificateRequest",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			MinVersion: VersionTLS13,
-			ClientAuth: RequireAnyClientCert,
-			Bugs: ProtocolBugs{
-				SendCustomCertificateRequest: 0x1212,
-			},
-		},
-		tls13Variant: TLS13Draft21,
-		flags: []string{
-			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
-			"-key-file", path.Join(*resourceDir, rsaKeyFile),
-		},
-	})
-
-	testCases = append(testCases, testCase{
-		name: "TLS13-MissingSignatureAlgorithmsInCertificateRequest",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			MinVersion: VersionTLS13,
-			ClientAuth: RequireAnyClientCert,
-			Bugs: ProtocolBugs{
-				OmitCertificateRequestAlgorithms: true,
-			},
-		},
-		tls13Variant: TLS13Draft21,
-		flags: []string{
-			"-cert-file", path.Join(*resourceDir, rsaCertificateFile),
-			"-key-file", path.Join(*resourceDir, rsaKeyFile),
-		},
-		shouldFail:    true,
-		expectedError: ":DECODE_ERROR:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-TrailingKeyShareData",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				TrailingKeyShareData: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":DECODE_ERROR:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "TLS13-AlwaysSelectPSKIdentity",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				AlwaysSelectPSKIdentity: true,
-			},
-		},
-		shouldFail:    true,
-		expectedError: ":UNEXPECTED_EXTENSION:",
-	})
-
-	testCases = append(testCases, testCase{
-		name: "TLS13-InvalidPSKIdentity",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SelectPSKIdentityOnResume: 1,
-			},
-		},
-		resumeSession: true,
-		shouldFail:    true,
-		expectedError: ":PSK_IDENTITY_NOT_FOUND:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-ExtraPSKIdentity",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				ExtraPSKIdentity:   true,
-				SendExtraPSKBinder: true,
-			},
-		},
-		resumeSession: true,
-	})
-
-	// Test that unknown NewSessionTicket extensions are tolerated.
-	testCases = append(testCases, testCase{
-		name: "TLS13-CustomTicketExtension",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				CustomTicketExtension: "1234",
-			},
-		},
-	})
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-RejectTicket-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Certificates:     []Certificate{rsaCertificate},
-		},
-		resumeConfig: &Config{
-			MaxVersion:             VersionTLS13,
-			MaxEarlyDataSize:       16384,
-			Certificates:           []Certificate{ecdsaP256Certificate},
-			SessionTicketsDisabled: true,
-		},
-		resumeSession:        true,
-		expectResumeRejected: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-reject-early-data",
-			"-on-resume-shim-writes-first",
-			"-on-initial-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile),
-			"-on-resume-expect-peer-cert-file", path.Join(*resourceDir, rsaCertificateFile),
-			"-on-retry-expect-peer-cert-file", path.Join(*resourceDir, ecdsaP256CertificateFile),
-			// Session tickets are disabled, so the runner will not send a ticket.
-			"-on-retry-expect-no-session",
-		},
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-HRR-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-reject-early-data",
-		},
-	})
-
-	// The client must check the server does not send the early_data
-	// extension while rejecting the session.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyDataWithoutResume-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeConfig: &Config{
-			MaxVersion:             VersionTLS13,
-			SessionTicketsDisabled: true,
-			Bugs: ProtocolBugs{
-				SendEarlyDataExtension: true,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-		},
-		shouldFail:    true,
-		expectedError: ":UNEXPECTED_EXTENSION:",
-	})
-
-	// The client must fail with a dedicated error code if the server
-	// responds with TLS 1.2 when offering 0-RTT.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyDataVersionDowngrade-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeConfig: &Config{
-			MaxVersion: VersionTLS12,
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-		},
-		shouldFail:    true,
-		expectedError: ":WRONG_VERSION_ON_EARLY_DATA:",
-	})
-
-	// Test that the client rejects an (unsolicited) early_data extension if
-	// the server sent an HRR.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-ServerAcceptsEarlyDataOnHRR-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				SendHelloRetryRequestCookie: []byte{1, 2, 3, 4},
-				SendEarlyDataExtension:      true,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-reject-early-data",
-		},
-		shouldFail:    true,
-		expectedError: ":UNEXPECTED_EXTENSION:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name: "TLS13Draft22-SkipChangeCipherSpec-Client",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SkipChangeCipherSpec: true,
-			},
-		},
-		tls13Variant: TLS13Draft22,
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name: "TLS13Draft22-SkipChangeCipherSpec-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SkipChangeCipherSpec: true,
-			},
-		},
-		tls13Variant: TLS13Draft22,
-	})
-
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name: "TLS13Draft22-TooManyChangeCipherSpec-Client",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendExtraChangeCipherSpec: 33,
-			},
-		},
-		tls13Variant: TLS13Draft22,
-		shouldFail:       true,
-		expectedError:    ":TOO_MANY_EMPTY_FRAGMENTS:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name: "TLS13Draft22-TooManyChangeCipherSpec-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendExtraChangeCipherSpec: 33,
-			},
-		},
-		tls13Variant: TLS13Draft22,
-		shouldFail:       true,
-		expectedError:    ":TOO_MANY_EMPTY_FRAGMENTS:",
-	})
-
-	fooString := "foo"
-	barString := "bar"
-
-	// Test that the client reports the correct ALPN after a 0-RTT reject
-	// that changed it.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-ALPNMismatch-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				ALPNProtocol: &fooString,
-			},
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				ALPNProtocol: &barString,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-advertise-alpn", "\x03foo\x03bar",
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-reject-early-data",
-			"-on-initial-expect-alpn", "foo",
-			"-on-resume-expect-alpn", "foo",
-			"-on-retry-expect-alpn", "bar",
-		},
-	})
-
-	// Test that the client reports the correct ALPN after a 0-RTT reject if
-	// ALPN was omitted from the first connection.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-ALPNOmitted1-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			NextProtos:       []string{"foo"},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-advertise-alpn", "\x03foo\x03bar",
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-reject-early-data",
-			"-on-initial-expect-alpn", "",
-			"-on-resume-expect-alpn", "",
-			"-on-retry-expect-alpn", "foo",
-			"-on-resume-shim-writes-first",
-		},
-	})
-
-	// Test that the client reports the correct ALPN after a 0-RTT reject if
-	// ALPN was omitted from the second connection.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-ALPNOmitted2-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			NextProtos:       []string{"foo"},
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeSession: true,
-		flags: []string{
-			"-advertise-alpn", "\x03foo\x03bar",
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-reject-early-data",
-			"-on-initial-expect-alpn", "foo",
-			"-on-resume-expect-alpn", "foo",
-			"-on-retry-expect-alpn", "",
-			"-on-resume-shim-writes-first",
-		},
-	})
-
-	// Test that the client enforces ALPN match on 0-RTT accept.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-BadALPNMismatch-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				ALPNProtocol: &fooString,
-			},
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				AlwaysAcceptEarlyData: true,
-				ALPNProtocol:          &barString,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-advertise-alpn", "\x03foo\x03bar",
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-on-initial-expect-alpn", "foo",
-			"-on-resume-expect-alpn", "foo",
-			"-on-retry-expect-alpn", "bar",
-		},
-		shouldFail:    true,
-		expectedError: ":ALPN_MISMATCH_ON_EARLY_DATA:",
-	})
-
-	// Test that the client does not offer early data if it is incompatible
-	// with ALPN preferences.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-ALPNPreferenceChanged",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			NextProtos:       []string{"foo", "bar"},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-no-offer-early-data",
-			"-on-initial-advertise-alpn", "\x03foo",
-			"-on-resume-advertise-alpn", "\x03bar",
-			"-on-initial-expect-alpn", "foo",
-			"-on-resume-expect-alpn", "bar",
-		},
-	})
-
-	// Test that the server correctly rejects 0-RTT when the previous
-	// session did not allow early data on resumption.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyData-NonZeroRTTSession-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-		},
-		resumeConfig: &Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: false,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-on-resume-enable-early-data",
-			"-expect-reject-early-data",
-		},
-	})
-
-	// Test that we reject early data where ALPN is omitted from the first
-	// connection.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyData-ALPNOmitted1-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			NextProtos: []string{},
-		},
-		resumeConfig: &Config{
-			MaxVersion: VersionTLS13,
-			NextProtos: []string{"foo"},
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: false,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-on-initial-select-alpn", "",
-			"-on-resume-select-alpn", "foo",
-		},
-	})
-
-	// Test that we reject early data where ALPN is omitted from the second
-	// connection.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyData-ALPNOmitted2-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			NextProtos: []string{"foo"},
-		},
-		resumeConfig: &Config{
-			MaxVersion: VersionTLS13,
-			NextProtos: []string{},
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: false,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-on-initial-select-alpn", "foo",
-			"-on-resume-select-alpn", "",
-		},
-	})
-
-	// Test that we reject early data with mismatched ALPN.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyData-ALPNMismatch-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			NextProtos: []string{"foo"},
-		},
-		resumeConfig: &Config{
-			MaxVersion: VersionTLS13,
-			NextProtos: []string{"bar"},
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: false,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-on-initial-select-alpn", "foo",
-			"-on-resume-select-alpn", "bar",
-		},
-	})
-
-	// Test that the client offering 0-RTT and Channel ID forbids the server
-	// from accepting both.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyDataChannelID-AcceptBoth-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			RequestChannelID: true,
-		},
-		resumeSession:   true,
-		expectChannelID: true,
-		shouldFail:      true,
-		expectedError:   ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
-		},
-	})
-
-	// Test that the client offering Channel ID and 0-RTT allows the server
-	// to decline 0-RTT.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyDataChannelID-AcceptChannelID-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			RequestChannelID: true,
-			Bugs: ProtocolBugs{
-				AlwaysRejectEarlyData: true,
-			},
-		},
-		resumeSession:   true,
-		expectChannelID: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
-			"-expect-reject-early-data",
-		},
-	})
-
-	// Test that the client offering Channel ID and 0-RTT allows the server
-	// to decline Channel ID.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyDataChannelID-AcceptEarlyData-Client",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-send-channel-id", path.Join(*resourceDir, channelIDKeyFile),
-			"-expect-accept-early-data",
-		},
-	})
-
-	// Test that the server supporting Channel ID and 0-RTT declines 0-RTT
-	// if it would negotiate Channel ID.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyDataChannelID-OfferBoth-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			ChannelID:  channelIDKey,
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: false,
-			},
-		},
-		resumeSession:   true,
-		expectChannelID: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-reject-early-data",
-			"-expect-channel-id",
-			base64.StdEncoding.EncodeToString(channelIDBytes),
-		},
-	})
-
-	// Test that the server supporting Channel ID and 0-RTT accepts 0-RTT
-	// if not offered Channel ID.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyDataChannelID-OfferEarlyData-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: true,
-				ExpectHalfRTTData:       [][]byte{{254, 253, 252, 251}},
-			},
-		},
-		resumeSession:   true,
-		expectChannelID: false,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-accept-early-data",
-			"-enable-channel-id",
-		},
-	})
-
-	// Test that the server rejects 0-RTT streams without end_of_early_data.
-	// The subsequent records should fail to decrypt.
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyData-SkipEndOfEarlyData",
-		config: Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: true,
-				SkipEndOfEarlyData:      true,
-			},
-		},
-		resumeSession:      true,
-		flags:              []string{"-enable-early-data"},
-		shouldFail:         true,
-		expectedLocalError: "remote error: bad record MAC",
-		expectedError:      ":BAD_DECRYPT:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyData-UnexpectedHandshake-Server",
-		config: Config{
-			MaxVersion: VersionTLS13,
-		},
-		resumeConfig: &Config{
-			MaxVersion: VersionTLS13,
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				SendStrayEarlyHandshake: true,
-				ExpectEarlyDataAccepted: true,
-			},
-		},
-		resumeSession:      true,
-		shouldFail:         true,
-		expectedError:      ":UNEXPECTED_RECORD:",
-		expectedLocalError: "remote error: unexpected message",
-		flags: []string{
-			"-enable-early-data",
-		},
-	})
-
-	// Test that the client reports TLS 1.3 as the version while sending
-	// early data.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-Client-VersionAPI",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-accept-early-data",
-			"-expect-version", strconv.Itoa(VersionTLS13),
-		},
-	})
-
-	// Test that client and server both notice handshake errors after data
-	// has started flowing.
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-EarlyData-Client-BadFinished",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				BadFinished: true,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-accept-early-data",
-		},
-		shouldFail:         true,
-		expectedError:      ":DIGEST_CHECK_FAILED:",
-		expectedLocalError: "remote error: error decrypting message",
-	})
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-EarlyData-Server-BadFinished",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: true,
-				ExpectHalfRTTData:       [][]byte{{254, 253, 252, 251}},
-				BadFinished:             true,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-accept-early-data",
-		},
-		shouldFail:         true,
-		expectedError:      ":DIGEST_CHECK_FAILED:",
-		expectedLocalError: "remote error: error decrypting message",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13Draft21-Server-NonEmptyEndOfEarlyData",
-		config: Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-		},
-		resumeConfig: &Config{
-			MaxVersion:       VersionTLS13,
-			MaxEarlyDataSize: 16384,
-			Bugs: ProtocolBugs{
-				SendEarlyData:           [][]byte{{1, 2, 3, 4}},
-				ExpectEarlyDataAccepted: true,
-				NonEmptyEndOfEarlyData:  true,
-			},
-		},
-		resumeSession: true,
-		flags: []string{
-			"-enable-early-data",
-			"-expect-ticket-supports-early-data",
-			"-expect-accept-early-data",
-		},
-		tls13Variant:  TLS13Draft21,
-		shouldFail:    true,
-		expectedError: ":DECODE_ERROR:",
-	})
-
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		name:     "TLS13-ServerSkipCertificateVerify",
-		config: Config{
-			MinVersion:   VersionTLS13,
-			MaxVersion:   VersionTLS13,
-			Certificates: []Certificate{rsaChainCertificate},
-			Bugs: ProtocolBugs{
-				SkipCertificateVerify: true,
-			},
-		},
-		expectPeerCertificate: &rsaChainCertificate,
-		flags: []string{
-			"-cert-file", path.Join(*resourceDir, rsaChainCertificateFile),
-			"-key-file", path.Join(*resourceDir, rsaChainKeyFile),
-			"-require-any-client-certificate",
-		},
-		shouldFail:         true,
-		expectedError:      ":UNEXPECTED_MESSAGE:",
-		expectedLocalError: "remote error: unexpected message",
-	})
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		name:     "TLS13-ClientSkipCertificateVerify",
-		config: Config{
-			MinVersion:   VersionTLS13,
-			MaxVersion:   VersionTLS13,
-			Certificates: []Certificate{rsaChainCertificate},
-			Bugs: ProtocolBugs{
-				SkipCertificateVerify: true,
-			},
-		},
-		expectPeerCertificate: &rsaChainCertificate,
-		flags: []string{
-			"-cert-file", path.Join(*resourceDir, rsaChainCertificateFile),
-			"-key-file", path.Join(*resourceDir, rsaChainKeyFile),
-		},
-		shouldFail:         true,
-		expectedError:      ":UNEXPECTED_MESSAGE:",
-		expectedLocalError: "remote error: unexpected message",
-	})
 }
 
 func addTLS13CipherPreferenceTests() {
diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc
index 2d9f725..a5ce5a1 100644
--- a/src/ssl/test/test_config.cc
+++ b/src/ssl/test/test_config.cc
@@ -132,7 +132,6 @@
 
 const Flag<std::string> kStringFlags[] = {
   { "-write-settings", &TestConfig::write_settings },
-  { "-digest-prefs", &TestConfig::digest_prefs },
   { "-key-file", &TestConfig::key_file },
   { "-cert-file", &TestConfig::cert_file },
   { "-expect-server-name", &TestConfig::expected_server_name },
diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h
index b742f94..ea12d34 100644
--- a/src/ssl/test/test_config.h
+++ b/src/ssl/test/test_config.h
@@ -26,7 +26,6 @@
   int resume_count = 0;
   std::string write_settings;
   bool fallback_scsv = false;
-  std::string digest_prefs;
   std::vector<int> signing_prefs;
   std::vector<int> verify_prefs;
   std::string key_file;
diff --git a/src/ssl/tls13_client.cc b/src/ssl/tls13_client.cc
index 0013e61..688fa06 100644
--- a/src/ssl/tls13_client.cc
+++ b/src/ssl/tls13_client.cc
@@ -52,6 +52,7 @@
 
 static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
+  assert(ssl->s3->have_version);
   SSLMessage msg;
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
@@ -60,6 +61,14 @@
   CBS extensions;
   uint16_t cipher_suite = 0;
   if (ssl_is_draft22(ssl->version)) {
+    // Queue up a ChangeCipherSpec for whenever we next send something. This
+    // will be before the second ClientHello. If we offered early data, this was
+    // already done.
+    if (!hs->early_data_offered &&
+        !ssl->method->add_change_cipher_spec(ssl)) {
+      return ssl_hs_error;
+    }
+
     if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO)) {
       return ssl_hs_error;
     }
@@ -95,8 +104,6 @@
         (ssl_is_draft21(ssl->version) &&
          !CBS_get_u16(&body, &cipher_suite)) ||
         !CBS_get_u16_length_prefixed(&body, &extensions) ||
-        // HelloRetryRequest may not be empty.
-        CBS_len(&extensions) == 0 ||
         CBS_len(&body) != 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
@@ -146,6 +153,11 @@
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
     return ssl_hs_error;
   }
+  if (!have_cookie && !have_key_share) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_EMPTY_HELLO_RETRY_REQUEST);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+    return ssl_hs_error;
+  }
   if (have_cookie) {
     CBS cookie_value;
     if (!CBS_get_u16_length_prefixed(&cookie, &cookie_value) ||
@@ -259,7 +271,14 @@
     return ssl_hs_error;
   }
 
-  assert(ssl->s3->have_version);
+  // Forbid a second HelloRetryRequest.
+  if (ssl_is_draft22(ssl->version) &&
+      CBS_mem_equal(&server_random, kHelloRetryRequest, SSL3_RANDOM_SIZE)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
+    return ssl_hs_error;
+  }
+
   OPENSSL_memcpy(ssl->s3->server_random, CBS_data(&server_random),
                  SSL3_RANDOM_SIZE);
 
@@ -401,11 +420,17 @@
   }
 
   if (!hs->early_data_offered) {
+    // Earlier versions of the resumption experiment added ChangeCipherSpec just
+    // before the Finished flight.
+    if (ssl_is_resumption_client_ccs_experiment(ssl->version) &&
+        !ssl_is_draft22(ssl->version) &&
+        !ssl->method->add_change_cipher_spec(ssl)) {
+      return ssl_hs_error;
+    }
+
     // If not sending early data, set client traffic keys now so that alerts are
     // encrypted.
-    if ((ssl_is_resumption_client_ccs_experiment(ssl->version) &&
-         !ssl->method->add_change_cipher_spec(ssl)) ||
-        !tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
+    if (!tls13_set_traffic_key(ssl, evp_aead_seal, hs->client_handshake_secret,
                                hs->hash_len)) {
       return ssl_hs_error;
     }
diff --git a/src/ssl/tls_record.cc b/src/ssl/tls_record.cc
index 7e8e968..a1363fa 100644
--- a/src/ssl/tls_record.cc
+++ b/src/ssl/tls_record.cc
@@ -187,9 +187,25 @@
   return ret;
 }
 
-enum ssl_open_record_t tls_open_record(SSL *ssl, uint8_t *out_type,
-                                       Span<uint8_t> *out, size_t *out_consumed,
-                                       uint8_t *out_alert, Span<uint8_t> in) {
+static ssl_open_record_t skip_early_data(SSL *ssl, uint8_t *out_alert,
+                                         size_t consumed) {
+  ssl->s3->early_data_skipped += consumed;
+  if (ssl->s3->early_data_skipped < consumed) {
+    ssl->s3->early_data_skipped = kMaxEarlyDataSkipped + 1;
+  }
+
+  if (ssl->s3->early_data_skipped > kMaxEarlyDataSkipped) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MUCH_SKIPPED_EARLY_DATA);
+    *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+    return ssl_open_record_error;
+  }
+
+  return ssl_open_record_discard;
+}
+
+ssl_open_record_t tls_open_record(SSL *ssl, uint8_t *out_type,
+                                  Span<uint8_t> *out, size_t *out_consumed,
+                                  uint8_t *out_alert, Span<uint8_t> in) {
   *out_consumed = 0;
   if (ssl->s3->read_shutdown == ssl_shutdown_close_notify) {
     return ssl_open_record_close_notify;
@@ -267,7 +283,7 @@
   if (ssl->s3->skip_early_data &&
       ssl->s3->aead_read_ctx->is_null_cipher() &&
       type == SSL3_RT_APPLICATION_DATA) {
-    goto skipped_data;
+    return skip_early_data(ssl, out_alert, *out_consumed);
   }
 
   // Decrypt the body in-place.
@@ -276,7 +292,7 @@
           MakeSpan(const_cast<uint8_t *>(CBS_data(&body)), CBS_len(&body)))) {
     if (ssl->s3->skip_early_data && !ssl->s3->aead_read_ctx->is_null_cipher()) {
       ERR_clear_error();
-      goto skipped_data;
+      return skip_early_data(ssl, out_alert, *out_consumed);
     }
 
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC);
@@ -292,8 +308,21 @@
   }
 
   // TLS 1.3 hides the record type inside the encrypted data.
-  if (!ssl->s3->aead_read_ctx->is_null_cipher() &&
-      ssl->s3->aead_read_ctx->ProtocolVersion() >= TLS1_3_VERSION) {
+  bool has_padding =
+      !ssl->s3->aead_read_ctx->is_null_cipher() &&
+      ssl->s3->aead_read_ctx->ProtocolVersion() >= TLS1_3_VERSION;
+
+  // If there is padding, the plaintext limit includes the padding, but includes
+  // extra room for the inner content type.
+  size_t plaintext_limit =
+      has_padding ? SSL3_RT_MAX_PLAIN_LENGTH + 1 : SSL3_RT_MAX_PLAIN_LENGTH;
+  if (out->size() > plaintext_limit) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
+    *out_alert = SSL_AD_RECORD_OVERFLOW;
+    return ssl_open_record_error;
+  }
+
+  if (has_padding) {
     // The outer record type is always application_data.
     if (type != SSL3_RT_APPLICATION_DATA) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_OUTER_RECORD_TYPE);
@@ -312,13 +341,6 @@
     } while (type == 0);
   }
 
-  // Check the plaintext length.
-  if (out->size() > SSL3_RT_MAX_PLAIN_LENGTH) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DATA_LENGTH_TOO_LONG);
-    *out_alert = SSL_AD_RECORD_OVERFLOW;
-    return ssl_open_record_error;
-  }
-
   // Limit the number of consecutive empty records.
   if (out->empty()) {
     ssl->s3->empty_record_count++;
@@ -358,20 +380,6 @@
 
   *out_type = type;
   return ssl_open_record_success;
-
-skipped_data:
-  ssl->s3->early_data_skipped += *out_consumed;
-  if (ssl->s3->early_data_skipped < *out_consumed) {
-    ssl->s3->early_data_skipped = kMaxEarlyDataSkipped + 1;
-  }
-
-  if (ssl->s3->early_data_skipped > kMaxEarlyDataSkipped) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_TOO_MUCH_SKIPPED_EARLY_DATA);
-    *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
-    return ssl_open_record_error;
-  }
-
-  return ssl_open_record_discard;
 }
 
 static int do_seal_record(SSL *ssl, uint8_t *out_prefix, uint8_t *out,