external/boringssl: Sync to 66e61c577d39e757bf491468f651461fa79fd5e1.

This change re-lands https://r.android.com/1834454 by reverting https://r.android.com/1842042 with no changes.

Revert was due to a vendor copy of wpa_supplicant needing the same changes as https://r.android.com/1835013, this is now done and I verified that it builds correctly against this version of BoringSSL.

This CL includes the following upstream changes:

https://boringssl.googlesource.com/boringssl/+log/c1571feb5faf5cce844354c63d0f3e842464bea3..66e61c577d39e757bf491468f651461fa79fd5e1

* Allow PKCS7_sign to work for signing kernel modules.
* Speed up constant-time base64 decoding.
* Unwind remnants of ASN1_TFLG_NDEF.
* acvptool: add CS3 support.
* Ignore SIGPIPE in the bssl tool.
* Add FIPS counters for AES-GCM in EVP_AEAD.
* Refresh fuzzer corpus for ECH draft-13.
* Fix the TLS fuzzers for ECH draft-13.
* Clarify that TLS sessions are not application sessions.
* Fix BN_prime_checks_for_validation to align with false-positive rate.
* Add maskHash to RSA_PSS_PARAMS for compat
* Remove ASN1_OP_I2D_* callbacks.
* Don't read it->funcs without checking it->itype.
* Reject missing required fields in i2d functions.
Update-Note: Structures with missing mandatory fields can no longer be
encoded. Note that, apart from the cases already handled by preceding
CLs, tasn_new.c will fill in non-NULL empty objects everywhere. The main
downstream impact I've seen of this particular change is in combination
with other bugs. Consider a caller that does:
* Reject -1 types in ASN1_TYPE and MSTRINGs when encoding.
Update-Note: A default-constructed object with a required ANY or
string-like CHOICE field cannot be encoded until the field is specified.
Note this affects i2d_X509: notBefore and notAfter are string-like
CHOICEs in OpenSSL.
* Correctly handle invalid ASN1_OBJECTs when encoding.
Update-Note: A default-constructed object with a required ASN1_OBJECT
field can no longer be encoded without initializing the ASN1_OBJECT.
Note this affects X509: the signature algorithm is an ASN1_OBJECT. Tests
that try to serialize an X509_new() must fill in all required fields.
(Production code is unlikely to be affected because the output was
unparsable anyway, while tests sometimes wouldn't notice.)
* Check for invalid CHOICE selectors in i2d functions.
Update-Note: An invalid CHOICE object (e.g. GENERAL_NAME) will now fail
when encoded, rather than be silently omitted. In particular, CHOICE
objects are default-initialized by tasn_new.c in an empty -1 state.
Structures containing a required CHOICE field can no longer be encoded
without filling in the CHOICE.
* Fix x509_name_ex_i2d error-handling.
* Correctly propagate errors in i2d functions.
Update-Note: Some error cases which were silently misinterpreted as
missing OPTIONAL elements will now cause encoding to fail.
* acvptool: add hmacDRBG support
* Check for __TRUSTY__ instead of TRUSTY.
* Update comment for ECH draft-13.
* Silence a GCC false positive warning.
* Switch to the new, simpler WHATWG URL formulation.
* Revert "Guard use of sdallocx with BORINGSSL_SDALLOCX"
* Fix calculation of draft-13 ECH confirmation signal.
* Update to draft-ietf-tls-esni-13.
* Reword SSL_get0_ech_name_override documentation.
* Remove SSL_set_verify_result.
* Make most of crypto/x509 opaque.
Update-Note: Patch cl/390055173 into the roll that includes this. This
unexports most of the X.509 structs, aligning with OpenSSL. Use the
accessor APIs instead.
* Remove V_ASN1_APP_CHOOSE.
Update-Note: V_ASN1_APP_CHOOSE is removed. I only found one use, which
has been fixed.
* Rewrite ASN1_PRINTABLE_type and add tests.
* Include SHA512-256 in EVP_get_digestbyname and EVP_MD_do_all.
* NUL is not printable.
Update-Note: ASN1_mbstring_ncopy will no longer allow PrintableString
for strings containing NUL.
* Make RSA_check_key more than 2x as fast.
* Benchmark RSA private key parsing.
* Work around yet another MSVC 2015 SFINAE bug.
* Avoid re-hashing the transcript multiple times.
* Make ssl_parse_extensions a little easier to use.
* Deduplicate our three ServerHello parsers.
* Merge in OpenSSL's X.509 corpus.
* Run X509_print in the certificate fuzzer.
* Fix some error-handling in i2v functions.
* Fix typo.
* OPENSSL_strndup should not return NULL given {NULL, 0}.
* Rewrite name constraints matching with CBS.
* Add some tests for name constraints.
* Fix i2v_GENERAL_NAME to not assume NUL terminated strings
* Do not rely on ASN1_STRING being NUL-terminated.
* Add a CBB_add_zeros helper.
* Linkify RFCs in documentation.
* Refer to RFCs consistently.
* runner: Test session IDs over 32 bytes.
* Process the TLS 1.3 cipher suite in one place.
* Guard use of sdallocx with BORINGSSL_SDALLOCX
* Bump minimum GCC version and note impending VS2015 deprecation.
* Add Span::first() and Span::last().
* Simplify built-in BIOs slightly.
* Fix some error returns from SSL_read and SSL_write.
* Fix negative ENUMERATED values in multi-strings.
* Add a test for ASN1_mbstring_copy and clean up.
* Remove ASN1_TFLG_SET_ORDER.
* Fix ASN1_STRING_print_ex with negative integers.
* Check i2d_ASN1_TYPE's return value in ASN1_STRING_print_ex.
* Document ASN.1 printing functions.
* Move some ASN1 printing functions to crypto/asn1.
* Move a_strex.c back to asn1, split X509_NAME bits out.
* Unwind io_ch abstraction in print functions.
* Implement ASN1_STRING_print_ex_fp, etc., with file BIOs.
* Remove OPENSSL_NO_FP_API ifdefs.
* Move X509_ALGOR to x509.h.
* Unexport BIT_STRING_BITNAME.
* Unexport ub_* constants.
Update-Note: Removed some unnamespaced constants.
* Always use an ASN1_STRING_TABLE global mask of UTF8String.
Update-Note: The global mask for ASN1_STRING_set_by_NID is now always
UTF-8. Callers that want another type should reconsider and, if UTF-8 is
still unsuitable, just pass the actual desired type into
ASN1_mbstring_copy, X509_NAME_ENTRY_set_data, etc
* Document ASN1_mbstring_copy.
* Update ghashv8-armx.pl from upstream.
* Align with upstream on 'close STDOUT' lines.
* Avoid double-expanding variables in CMake.
* Reject years outside 0000-9999 in ASN1_GENERALIZEDTIME_adj.
* Add some tests for time_t to ASN1_TIME conversions.
* Remove ASN1_STRING_FLAG_MSTRING.
Update-Note: ASN1_STRING_FLAG_MSTRING is no longer defined and
X509_time_adj_ex now behaves more predictably. Callers that actually
wanted to lock to a specific type should call ASN1_UTCTIME_adj or
ASN1_GENERALIZEDTIME_adj instead.
* Document another batch of functions.
* Clarify BIO_new_mum_buf's lifetime rules.
* generate_ech.cc: include needed headers
* Don't overread in poly_Rq_mul
* acvp: recognise another style of JSON.
* Revert "Revert "Revert "Disable check that X.509 extensions implies v3."""

Change-Id: I5fe21e26f701feb315aceb86684b5bc1ee327669
Test: atest CtsLibcoreTestCases CtsLibcoreOkHttpTestCases
diff --git a/src/.clang-format b/src/.clang-format
index 6de1483..8f9cb6c 100644
--- a/src/.clang-format
+++ b/src/.clang-format
@@ -10,6 +10,7 @@
 IncludeBlocks: Preserve
 TypenameMacros: ['LHASH_OF', 'STACK_OF']
 StatementMacros:
+  - "ASN1_SEQUENCE_END"
   - "DECLARE_ASN1_ALLOC_FUNCTIONS"
   - "DECLARE_ASN1_ALLOC_FUNCTIONS_name"
   - "DECLARE_ASN1_ENCODE_FUNCTIONS"
diff --git a/src/BUILDING.md b/src/BUILDING.md
index f76c571..08f004c 100644
--- a/src/BUILDING.md
+++ b/src/BUILDING.md
@@ -31,8 +31,10 @@
     `CMAKE_ASM_NASM_COMPILER`.
 
   * C and C++ compilers with C++11 support are required. On Windows, MSVC 14
-    (Visual Studio 2015) or later with Platform SDK 8.1 or later are supported.
-    Recent versions of GCC (4.8+) and Clang should work on non-Windows
+    (Visual Studio 2015) or later with Platform SDK 8.1 or later are supported,
+    but newer versions are recommended. We will drop support for Visual Studio
+    2015 in March 2022, five years after the release of Visual Studio 2017.
+    Recent versions of GCC (6.1+) and Clang should work on non-Windows
     platforms, and maybe on Windows too.
 
   * The most recent stable version of [Go](https://golang.org/dl/) is required.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 893bca7..6a5a6aa 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -117,7 +117,7 @@
 if(CMAKE_COMPILER_IS_GNUCXX OR CLANG)
   # Note clang-cl is odd and sets both CLANG and MSVC. We base our configuration
   # primarily on our normal Clang one.
-  set(C_CXX_FLAGS "-Werror -Wformat=2 -Wsign-compare -Wmissing-field-initializers -Wwrite-strings -Wvla")
+  set(C_CXX_FLAGS "-Werror -Wformat=2 -Wsign-compare -Wmissing-field-initializers -Wwrite-strings -Wvla -Wshadow")
   if(MSVC)
     # clang-cl sets different default warnings than clang. It also treats -Wall
     # as -Weverything, to match MSVC. Instead -W3 is the alias for -Wall.
@@ -172,11 +172,6 @@
   if(CLANG)
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-prototypes")
   endif()
-
-  if(CMAKE_COMPILER_IS_GNUCXX AND "4.8" VERSION_GREATER CMAKE_C_COMPILER_VERSION)
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-array-bounds")
-  endif()
-
 elseif(MSVC)
   set(MSVC_DISABLED_WARNINGS_LIST
       "C4061" # enumerator 'identifier' in switch of enum 'enumeration' is not
@@ -254,12 +249,6 @@
   add_definitions("-D_STL_EXTRA_DISABLED_WARNINGS=4774 4987")
 endif()
 
-if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_C_COMPILER_VERSION VERSION_GREATER "4.7.99") OR
-   CLANG)
-  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshadow")
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshadow")
-endif()
-
 if(CMAKE_COMPILER_IS_GNUCXX)
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11")
 endif()
@@ -405,8 +394,7 @@
 
 function(go_executable dest package)
   set(godeps "${CMAKE_SOURCE_DIR}/util/godeps.go")
-  if(${CMAKE_VERSION} VERSION_LESS "3.7" OR
-     NOT ${CMAKE_GENERATOR} STREQUAL "Ninja")
+  if(CMAKE_VERSION VERSION_LESS "3.7" OR NOT CMAKE_GENERATOR STREQUAL "Ninja")
     # The DEPFILE parameter to add_custom_command is new as of CMake 3.7 and
     # only works with Ninja. Query the sources at configure time. Additionally,
     # everything depends on go.mod. That affects what external packages to use.
@@ -448,7 +436,7 @@
 # builds.
 if(NOT OPENSSL_NO_ASM AND CMAKE_OSX_ARCHITECTURES)
   list(LENGTH CMAKE_OSX_ARCHITECTURES NUM_ARCHES)
-  if(NOT ${NUM_ARCHES} EQUAL 1)
+  if(NOT NUM_ARCHES EQUAL 1)
     message(FATAL_ERROR "Universal binaries not supported.")
   endif()
   list(GET CMAKE_OSX_ARCHITECTURES 0 CMAKE_SYSTEM_PROCESSOR)
@@ -461,44 +449,44 @@
 if(OPENSSL_NO_ASM)
   add_definitions(-DOPENSSL_NO_ASM)
   set(ARCH "generic")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
   set(ARCH "x86_64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "amd64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64")
   set(ARCH "x86_64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "AMD64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64")
   # cmake reports AMD64 on Windows, but we might be building for 32-bit.
   if(CMAKE_SIZEOF_VOID_P EQUAL 8)
     set(ARCH "x86_64")
   else()
     set(ARCH "x86")
   endif()
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86")
   set(ARCH "x86")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "i386")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "i386")
   set(ARCH "x86")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "i686")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "i686")
   set(ARCH "x86")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
   set(ARCH "aarch64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "ARM64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64")
   set(ARCH "aarch64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
   set(ARCH "aarch64")
 # Apple A12 Bionic chipset which is added in iPhone XS/XS Max/XR uses arm64e architecture.
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64e")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64e")
   set(ARCH "aarch64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "^arm*")
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm*")
   set(ARCH "arm")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "mips")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "mips")
   # Just to avoid the “unknown processor” error.
   set(ARCH "generic")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "ppc64le")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le")
   set(ARCH "ppc64le")
 else()
   message(FATAL_ERROR "Unknown processor:" ${CMAKE_SYSTEM_PROCESSOR})
 endif()
 
-if(ANDROID AND NOT ANDROID_NDK_REVISION AND ${ARCH} STREQUAL "arm")
+if(ANDROID AND NOT ANDROID_NDK_REVISION AND ARCH STREQUAL "arm")
   # The third-party Android-NDK CMake files somehow fail to set the -march flag
   # for assembly files. Without this flag, the compiler believes that it's
   # building for ARMv5.
diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt
index 7832edb..990df00 100644
--- a/src/crypto/CMakeLists.txt
+++ b/src/crypto/CMakeLists.txt
@@ -2,7 +2,7 @@
 
 if(NOT OPENSSL_NO_ASM)
   if(UNIX)
-    if(${ARCH} STREQUAL "aarch64")
+    if(ARCH STREQUAL "aarch64")
       # The "armx" Perl scripts look for "64" in the style argument
       # in order to decide whether to generate 32- or 64-bit asm.
       if(APPLE)
@@ -10,16 +10,16 @@
       else()
         set(PERLASM_STYLE linux64)
       endif()
-    elseif(${ARCH} STREQUAL "arm")
+    elseif(ARCH STREQUAL "arm")
       if(APPLE)
         set(PERLASM_STYLE ios32)
       else()
         set(PERLASM_STYLE linux32)
       endif()
-    elseif(${ARCH} STREQUAL "ppc64le")
+    elseif(ARCH STREQUAL "ppc64le")
       set(PERLASM_STYLE linux64le)
     else()
-      if(${ARCH} STREQUAL "x86")
+      if(ARCH STREQUAL "x86")
         set(PERLASM_FLAGS "-fPIC -DOPENSSL_IA32_SSE2")
       endif()
       if(APPLE)
@@ -47,12 +47,12 @@
       endforeach()
     endif()
   else()
-    if(${ARCH} STREQUAL "aarch64")
+    if(ARCH STREQUAL "aarch64")
       set(PERLASM_STYLE win64)
       set(ASM_EXT S)
       enable_language(ASM)
     else()
-      if(${ARCH} STREQUAL "x86_64")
+      if(ARCH STREQUAL "x86_64")
         set(PERLASM_STYLE nasm)
       else()
         set(PERLASM_STYLE win32n)
@@ -69,7 +69,7 @@
 
 function(perlasm dest src)
   get_filename_component(dir ${dest} DIRECTORY)
-  if ("${dir}" STREQUAL "")
+  if(dir STREQUAL "")
     set(dir ".")
   endif()
 
@@ -104,7 +104,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "arm")
+if(ARCH STREQUAL "arm")
   set(
     CRYPTO_ARCH_SOURCES
 
@@ -115,7 +115,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "aarch64")
+if(ARCH STREQUAL "aarch64")
   set(
     CRYPTO_ARCH_SOURCES
 
@@ -124,7 +124,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "ppc64le")
+if(ARCH STREQUAL "ppc64le")
   set(
     CRYPTO_ARCH_SOURCES
 
@@ -132,7 +132,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "x86")
+if(ARCH STREQUAL "x86")
   set(
     CRYPTO_ARCH_SOURCES
 
@@ -141,7 +141,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "x86_64")
+if(ARCH STREQUAL "x86_64")
   set(
     CRYPTO_ARCH_SOURCES
 
@@ -211,6 +211,7 @@
   asn1/a_object.c
   asn1/a_octet.c
   asn1/a_print.c
+  asn1/a_strex.c
   asn1/a_strnid.c
   asn1/a_time.c
   asn1/a_type.c
@@ -350,13 +351,13 @@
   trust_token/voprf.c
   x509/a_digest.c
   x509/a_sign.c
-  x509/a_strex.c
   x509/a_verify.c
   x509/algorithm.c
   x509/asn1_gen.c
   x509/by_dir.c
   x509/by_file.c
   x509/i2d_pr.c
+  x509/name_print.c
   x509/rsa_pss.c
   x509/t_crl.c
   x509/t_req.c
diff --git a/src/crypto/asn1/a_bool.c b/src/crypto/asn1/a_bool.c
index 64ae9e5..15396ff 100644
--- a/src/crypto/asn1/a_bool.c
+++ b/src/crypto/asn1/a_bool.c
@@ -71,7 +71,7 @@
     if (*pp == NULL) {
         if ((p = allocated = OPENSSL_malloc(r)) == NULL) {
             OPENSSL_PUT_ERROR(ASN1, ERR_R_MALLOC_FAILURE);
-            return 0;
+            return -1;
         }
     } else {
         p = *pp;
diff --git a/src/crypto/asn1/a_d2i_fp.c b/src/crypto/asn1/a_d2i_fp.c
index fd423e2..d0d6d03 100644
--- a/src/crypto/asn1/a_d2i_fp.c
+++ b/src/crypto/asn1/a_d2i_fp.c
@@ -78,7 +78,6 @@
     return ret;
 }
 
-#ifndef OPENSSL_NO_FP_API
 void *ASN1_item_d2i_fp(const ASN1_ITEM *it, FILE *in, void *x)
 {
     BIO *b = BIO_new_fp(in, BIO_NOCLOSE);
@@ -90,4 +89,3 @@
     BIO_free(b);
     return ret;
 }
-#endif
diff --git a/src/crypto/asn1/a_gentm.c b/src/crypto/asn1/a_gentm.c
index c91d506..3e6f14e 100644
--- a/src/crypto/asn1/a_gentm.c
+++ b/src/crypto/asn1/a_gentm.c
@@ -237,6 +237,11 @@
             goto err;
     }
 
+    if (ts->tm_year < 0 - 1900 || ts->tm_year > 9999 - 1900) {
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_TIME_VALUE);
+        goto err;
+    }
+
     p = (char *)tmps->data;
     if ((p == NULL) || ((size_t)tmps->length < len)) {
         p = OPENSSL_malloc(len);
diff --git a/src/crypto/asn1/a_mbstr.c b/src/crypto/asn1/a_mbstr.c
index 953095c..5853b72 100644
--- a/src/crypto/asn1/a_mbstr.c
+++ b/src/crypto/asn1/a_mbstr.c
@@ -66,8 +66,6 @@
 #include "internal.h"
 #include "../bytestring/internal.h"
 
-static int is_printable(uint32_t value);
-
 /*
  * These functions take a string in UTF8, ASCII or multibyte form and a mask
  * of permissible ASN1 string types. It then works out the minimal type
@@ -153,7 +151,7 @@
         }
 
         /* Update which output formats are still possible. */
-        if ((mask & B_ASN1_PRINTABLESTRING) && !is_printable(c)) {
+        if ((mask & B_ASN1_PRINTABLESTRING) && !asn1_is_printable(c)) {
             mask &= ~B_ASN1_PRINTABLESTRING;
         }
         if ((mask & B_ASN1_IA5STRING) && (c > 127)) {
@@ -208,11 +206,14 @@
         encode_func = cbb_add_utf32_be;
         size_estimate = 4 * nchar;
         outform = MBSTRING_UNIV;
-    } else {
+    } else if (mask & B_ASN1_UTF8STRING) {
         str_type = V_ASN1_UTF8STRING;
         outform = MBSTRING_UTF8;
         encode_func = cbb_add_utf8;
         size_estimate = utf8_len;
+    } else {
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_CHARACTERS);
+        return -1;
     }
 
     if (!out)
@@ -282,24 +283,16 @@
     return -1;
 }
 
-/* Return 1 if the character is permitted in a PrintableString */
-static int is_printable(uint32_t value)
+int asn1_is_printable(uint32_t value)
 {
-    int ch;
-    if (value > 0x7f)
+    if (value > 0x7f) {
         return 0;
-    ch = (int)value;
-    /*
-     * Note: we can't use 'isalnum' because certain accented characters may
-     * count as alphanumeric in some environments.
-     */
-    if ((ch >= 'a') && (ch <= 'z'))
-        return 1;
-    if ((ch >= 'A') && (ch <= 'Z'))
-        return 1;
-    if ((ch >= '0') && (ch <= '9'))
-        return 1;
-    if ((ch == ' ') || strchr("'()+,-./:=?", ch))
-        return 1;
-    return 0;
+    }
+    /* Note we cannot use |isalnum| because it is locale-dependent. */
+    return ('a' <= value && value <= 'z') ||  //
+           ('A' <= value && value <= 'Z') ||  //
+           ('0' <= value && value <= '9') ||  //
+           value == ' ' || value == '\'' || value == '(' || value == ')' ||
+           value == '+' || value == ',' || value == '-' || value == '.' ||
+           value == '/' || value == ':' || value == '=' || value == '?';
 }
diff --git a/src/crypto/asn1/a_object.c b/src/crypto/asn1/a_object.c
index de27e87..b88f06b 100644
--- a/src/crypto/asn1/a_object.c
+++ b/src/crypto/asn1/a_object.c
@@ -69,20 +69,26 @@
 
 int i2d_ASN1_OBJECT(const ASN1_OBJECT *a, unsigned char **pp)
 {
-    unsigned char *p, *allocated = NULL;
-    int objsize;
+    if (a == NULL) {
+        OPENSSL_PUT_ERROR(ASN1, ERR_R_PASSED_NULL_PARAMETER);
+        return -1;
+    }
 
-    if ((a == NULL) || (a->data == NULL))
-        return (0);
+    if (a->length == 0) {
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_OBJECT);
+        return -1;
+    }
 
-    objsize = ASN1_object_size(0, a->length, V_ASN1_OBJECT);
-    if (pp == NULL || objsize == -1)
+    int objsize = ASN1_object_size(0, a->length, V_ASN1_OBJECT);
+    if (pp == NULL || objsize == -1) {
         return objsize;
+    }
 
+    unsigned char *p, *allocated = NULL;
     if (*pp == NULL) {
         if ((p = allocated = OPENSSL_malloc(objsize)) == NULL) {
             OPENSSL_PUT_ERROR(ASN1, ERR_R_MALLOC_FAILURE);
-            return 0;
+            return -1;
         }
     } else {
         p = *pp;
diff --git a/src/crypto/asn1/a_print.c b/src/crypto/asn1/a_print.c
index 2104521..c7efede 100644
--- a/src/crypto/asn1/a_print.c
+++ b/src/crypto/asn1/a_print.c
@@ -56,38 +56,28 @@
 
 #include <openssl/asn1.h>
 
-#include <openssl/err.h>
-#include <openssl/mem.h>
+#include <string.h>
+
+#include "internal.h"
+
 
 int ASN1_PRINTABLE_type(const unsigned char *s, int len)
 {
-    int c;
-    int ia5 = 0;
-    int t61 = 0;
-
-    if (len <= 0)
-        len = -1;
-    if (s == NULL)
-        return (V_ASN1_PRINTABLESTRING);
-
-    while ((*s) && (len-- != 0)) {
-        c = *(s++);
-        if (!(((c >= 'a') && (c <= 'z')) ||
-              ((c >= 'A') && (c <= 'Z')) ||
-              (c == ' ') ||
-              ((c >= '0') && (c <= '9')) ||
-              (c == ' ') || (c == '\'') ||
-              (c == '(') || (c == ')') ||
-              (c == '+') || (c == ',') ||
-              (c == '-') || (c == '.') ||
-              (c == '/') || (c == ':') || (c == '=') || (c == '?')))
-            ia5 = 1;
-        if (c & 0x80)
-            t61 = 1;
+    if (len < 0) {
+        len = strlen((const char *)s);
     }
-    if (t61)
-        return (V_ASN1_T61STRING);
-    if (ia5)
-        return (V_ASN1_IA5STRING);
-    return (V_ASN1_PRINTABLESTRING);
+
+    int printable = 1;
+    for (int i = 0; i < len; i++) {
+        unsigned char c = s[i];
+        if (c & 0x80) {
+            /* No need to continue iterating. */
+            return V_ASN1_T61STRING;
+        }
+        if (!asn1_is_printable(c)) {
+            printable = 0;
+        }
+    }
+
+    return printable ? V_ASN1_PRINTABLESTRING : V_ASN1_IA5STRING;
 }
diff --git a/src/crypto/asn1/a_strex.c b/src/crypto/asn1/a_strex.c
new file mode 100644
index 0000000..7829d67
--- /dev/null
+++ b/src/crypto/asn1/a_strex.c
@@ -0,0 +1,650 @@
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ *
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to.  The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ *
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    "This product includes cryptographic software written by
+ *     Eric Young (eay@cryptsoft.com)"
+ *    The word 'cryptographic' can be left out if the rouines from the library
+ *    being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from
+ *    the apps directory (application code) you must include an acknowledgement:
+ *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed.  i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.] */
+
+#include <openssl/asn1.h>
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <openssl/bio.h>
+#include <openssl/mem.h>
+
+#include "charmap.h"
+#include "internal.h"
+
+
+// These flags must be distinct from |ESC_FLAGS| and fit in a byte.
+
+// Character is a valid PrintableString character
+#define CHARTYPE_PRINTABLESTRING 0x10
+// Character needs escaping if it is the first character
+#define CHARTYPE_FIRST_ESC_2253 0x20
+// Character needs escaping if it is the last character
+#define CHARTYPE_LAST_ESC_2253 0x40
+
+#define CHARTYPE_BS_ESC         (ASN1_STRFLGS_ESC_2253 | CHARTYPE_FIRST_ESC_2253 | CHARTYPE_LAST_ESC_2253)
+
+#define ESC_FLAGS (ASN1_STRFLGS_ESC_2253 | \
+                  ASN1_STRFLGS_ESC_QUOTE | \
+                  ASN1_STRFLGS_ESC_CTRL | \
+                  ASN1_STRFLGS_ESC_MSB)
+
+static int maybe_write(BIO *out, const void *buf, int len)
+{
+    /* If |out| is NULL, ignore the output but report the length. */
+    return out == NULL || BIO_write(out, buf, len) == len;
+}
+
+/*
+ * This function handles display of strings, one character at a time. It is
+ * passed an unsigned long for each character because it could come from 2 or
+ * even 4 byte forms.
+ */
+
+#define HEX_SIZE(type) (sizeof(type)*2)
+
+static int do_esc_char(uint32_t c, unsigned char flags, char *do_quotes,
+                       BIO *out)
+{
+    unsigned char chflgs, chtmp;
+    char tmphex[HEX_SIZE(uint32_t) + 3];
+
+    if (c > 0xffff) {
+        BIO_snprintf(tmphex, sizeof tmphex, "\\W%08" PRIX32, c);
+        if (!maybe_write(out, tmphex, 10))
+            return -1;
+        return 10;
+    }
+    if (c > 0xff) {
+        BIO_snprintf(tmphex, sizeof tmphex, "\\U%04" PRIX32, c);
+        if (!maybe_write(out, tmphex, 6))
+            return -1;
+        return 6;
+    }
+    chtmp = (unsigned char)c;
+    if (chtmp > 0x7f)
+        chflgs = flags & ASN1_STRFLGS_ESC_MSB;
+    else
+        chflgs = char_type[chtmp] & flags;
+    if (chflgs & CHARTYPE_BS_ESC) {
+        /* If we don't escape with quotes, signal we need quotes */
+        if (chflgs & ASN1_STRFLGS_ESC_QUOTE) {
+            if (do_quotes)
+                *do_quotes = 1;
+            if (!maybe_write(out, &chtmp, 1))
+                return -1;
+            return 1;
+        }
+        if (!maybe_write(out, "\\", 1))
+            return -1;
+        if (!maybe_write(out, &chtmp, 1))
+            return -1;
+        return 2;
+    }
+    if (chflgs & (ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB)) {
+        BIO_snprintf(tmphex, 11, "\\%02X", chtmp);
+        if (!maybe_write(out, tmphex, 3))
+            return -1;
+        return 3;
+    }
+    /*
+     * If we get this far and do any escaping at all must escape the escape
+     * character itself: backslash.
+     */
+    if (chtmp == '\\' && flags & ESC_FLAGS) {
+        if (!maybe_write(out, "\\\\", 2))
+            return -1;
+        return 2;
+    }
+    if (!maybe_write(out, &chtmp, 1))
+        return -1;
+    return 1;
+}
+
+#define BUF_TYPE_WIDTH_MASK     0x7
+#define BUF_TYPE_CONVUTF8       0x8
+
+/*
+ * This function sends each character in a buffer to do_esc_char(). It
+ * interprets the content formats and converts to or from UTF8 as
+ * appropriate.
+ */
+
+static int do_buf(unsigned char *buf, int buflen,
+                  int type, unsigned char flags, char *quotes, BIO *out)
+{
+    int i, outlen, len, charwidth;
+    unsigned char orflags, *p, *q;
+    uint32_t c;
+    p = buf;
+    q = buf + buflen;
+    outlen = 0;
+    charwidth = type & BUF_TYPE_WIDTH_MASK;
+
+    switch (charwidth) {
+    case 4:
+        if (buflen & 3) {
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_UNIVERSALSTRING);
+            return -1;
+        }
+        break;
+    case 2:
+        if (buflen & 1) {
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_BMPSTRING);
+            return -1;
+        }
+        break;
+    default:
+        break;
+    }
+
+    while (p != q) {
+        if (p == buf && flags & ASN1_STRFLGS_ESC_2253)
+            orflags = CHARTYPE_FIRST_ESC_2253;
+        else
+            orflags = 0;
+        /* TODO(davidben): Replace this with |cbs_get_ucs2_be|, etc., to check
+         * for invalid codepoints. */
+        switch (charwidth) {
+        case 4:
+            c = ((uint32_t)*p++) << 24;
+            c |= ((uint32_t)*p++) << 16;
+            c |= ((uint32_t)*p++) << 8;
+            c |= *p++;
+            break;
+
+        case 2:
+            c = ((uint32_t)*p++) << 8;
+            c |= *p++;
+            break;
+
+        case 1:
+            c = *p++;
+            break;
+
+        case 0:
+            i = UTF8_getc(p, buflen, &c);
+            if (i < 0)
+                return -1;      /* Invalid UTF8String */
+            buflen -= i;
+            p += i;
+            break;
+        default:
+            return -1;          /* invalid width */
+        }
+        if (p == q && flags & ASN1_STRFLGS_ESC_2253)
+            orflags = CHARTYPE_LAST_ESC_2253;
+        if (type & BUF_TYPE_CONVUTF8) {
+            unsigned char utfbuf[6];
+            int utflen;
+            utflen = UTF8_putc(utfbuf, sizeof utfbuf, c);
+            for (i = 0; i < utflen; i++) {
+                /*
+                 * We don't need to worry about setting orflags correctly
+                 * because if utflen==1 its value will be correct anyway
+                 * otherwise each character will be > 0x7f and so the
+                 * character will never be escaped on first and last.
+                 */
+                len = do_esc_char(utfbuf[i], (unsigned char)(flags | orflags),
+                                  quotes, out);
+                if (len < 0)
+                    return -1;
+                outlen += len;
+            }
+        } else {
+            len = do_esc_char(c, (unsigned char)(flags | orflags), quotes, out);
+            if (len < 0)
+                return -1;
+            outlen += len;
+        }
+    }
+    return outlen;
+}
+
+/* This function hex dumps a buffer of characters */
+
+static int do_hex_dump(BIO *out, unsigned char *buf, int buflen)
+{
+    static const char hexdig[] = "0123456789ABCDEF";
+    unsigned char *p, *q;
+    char hextmp[2];
+    if (out) {
+        p = buf;
+        q = buf + buflen;
+        while (p != q) {
+            hextmp[0] = hexdig[*p >> 4];
+            hextmp[1] = hexdig[*p & 0xf];
+            if (!maybe_write(out, hextmp, 2))
+                return -1;
+            p++;
+        }
+    }
+    return buflen << 1;
+}
+
+/*
+ * "dump" a string. This is done when the type is unknown, or the flags
+ * request it. We can either dump the content octets or the entire DER
+ * encoding. This uses the RFC 2253 #01234 format.
+ */
+
+static int do_dump(unsigned long lflags, BIO *out, const ASN1_STRING *str)
+{
+    if (!maybe_write(out, "#", 1)) {
+        return -1;
+    }
+
+    /* If we don't dump DER encoding just dump content octets */
+    if (!(lflags & ASN1_STRFLGS_DUMP_DER)) {
+        int outlen = do_hex_dump(out, str->data, str->length);
+        if (outlen < 0) {
+            return -1;
+        }
+        return outlen + 1;
+    }
+
+    /*
+     * Placing the ASN1_STRING in a temporary ASN1_TYPE allows the DER encoding
+     * to readily obtained.
+     */
+    ASN1_TYPE t;
+    t.type = str->type;
+    /* Negative INTEGER and ENUMERATED values are the only case where
+     * |ASN1_STRING| and |ASN1_TYPE| types do not match.
+     *
+     * TODO(davidben): There are also some type fields which, in |ASN1_TYPE|, do
+     * not correspond to |ASN1_STRING|. It is unclear whether those are allowed
+     * in |ASN1_STRING| at all, or what the space of allowed types is.
+     * |ASN1_item_ex_d2i| will never produce such a value so, for now, we say
+     * this is an invalid input. But this corner of the library in general
+     * should be more robust. */
+    if (t.type == V_ASN1_NEG_INTEGER) {
+      t.type = V_ASN1_INTEGER;
+    } else if (t.type == V_ASN1_NEG_ENUMERATED) {
+      t.type = V_ASN1_ENUMERATED;
+    }
+    t.value.asn1_string = (ASN1_STRING *)str;
+    unsigned char *der_buf = NULL;
+    int der_len = i2d_ASN1_TYPE(&t, &der_buf);
+    if (der_len < 0) {
+        return -1;
+    }
+    int outlen = do_hex_dump(out, der_buf, der_len);
+    OPENSSL_free(der_buf);
+    if (outlen < 0) {
+        return -1;
+    }
+    return outlen + 1;
+}
+
+/*
+ * Lookup table to convert tags to character widths, 0 = UTF8 encoded, -1 is
+ * used for non string types otherwise it is the number of bytes per
+ * character
+ */
+
+static const signed char tag2nbyte[] = {
+    -1, -1, -1, -1, -1,         /* 0-4 */
+    -1, -1, -1, -1, -1,         /* 5-9 */
+    -1, -1, 0, -1,              /* 10-13 */
+    -1, -1, -1, -1,             /* 15-17 */
+    1, 1, 1,                    /* 18-20 */
+    -1, 1, 1, 1,                /* 21-24 */
+    -1, 1, -1,                  /* 25-27 */
+    4, -1, 2                    /* 28-30 */
+};
+
+/*
+ * This is the main function, print out an ASN1_STRING taking note of various
+ * escape and display options. Returns number of characters written or -1 if
+ * an error occurred.
+ */
+
+int ASN1_STRING_print_ex(BIO *out, const ASN1_STRING *str, unsigned long lflags)
+{
+    int outlen, len;
+    int type;
+    char quotes;
+    unsigned char flags;
+    quotes = 0;
+    /* Keep a copy of escape flags */
+    flags = (unsigned char)(lflags & ESC_FLAGS);
+
+    type = str->type;
+
+    outlen = 0;
+
+    if (lflags & ASN1_STRFLGS_SHOW_TYPE) {
+        const char *tagname;
+        tagname = ASN1_tag2str(type);
+        outlen += strlen(tagname);
+        if (!maybe_write(out, tagname, outlen) || !maybe_write(out, ":", 1))
+            return -1;
+        outlen++;
+    }
+
+    /* Decide what to do with type, either dump content or display it */
+
+    /* Dump everything */
+    if (lflags & ASN1_STRFLGS_DUMP_ALL)
+        type = -1;
+    /* Ignore the string type */
+    else if (lflags & ASN1_STRFLGS_IGNORE_TYPE)
+        type = 1;
+    else {
+        /* Else determine width based on type */
+        if ((type > 0) && (type < 31))
+            type = tag2nbyte[type];
+        else
+            type = -1;
+        if ((type == -1) && !(lflags & ASN1_STRFLGS_DUMP_UNKNOWN))
+            type = 1;
+    }
+
+    if (type == -1) {
+        len = do_dump(lflags, out, str);
+        if (len < 0)
+            return -1;
+        outlen += len;
+        return outlen;
+    }
+
+    if (lflags & ASN1_STRFLGS_UTF8_CONVERT) {
+        /*
+         * Note: if string is UTF8 and we want to convert to UTF8 then we
+         * just interpret it as 1 byte per character to avoid converting
+         * twice.
+         */
+        if (!type)
+            type = 1;
+        else
+            type |= BUF_TYPE_CONVUTF8;
+    }
+
+    len = do_buf(str->data, str->length, type, flags, &quotes, NULL);
+    if (len < 0)
+        return -1;
+    outlen += len;
+    if (quotes)
+        outlen += 2;
+    if (!out)
+        return outlen;
+    if (quotes && !maybe_write(out, "\"", 1))
+        return -1;
+    if (do_buf(str->data, str->length, type, flags, NULL, out) < 0)
+        return -1;
+    if (quotes && !maybe_write(out, "\"", 1))
+        return -1;
+    return outlen;
+}
+
+int ASN1_STRING_print_ex_fp(FILE *fp, const ASN1_STRING *str,
+                            unsigned long flags)
+{
+    BIO *bio = NULL;
+    if (fp != NULL) {
+        /* If |fp| is NULL, this function returns the number of bytes without
+         * writing. */
+        bio = BIO_new_fp(fp, BIO_NOCLOSE);
+        if (bio == NULL) {
+            return -1;
+        }
+    }
+    int ret = ASN1_STRING_print_ex(bio, str, flags);
+    BIO_free(bio);
+    return ret;
+}
+
+int ASN1_STRING_to_UTF8(unsigned char **out, const ASN1_STRING *in)
+{
+    ASN1_STRING stmp, *str = &stmp;
+    int mbflag, type, ret;
+    if (!in)
+        return -1;
+    type = in->type;
+    if ((type < 0) || (type > 30))
+        return -1;
+    mbflag = tag2nbyte[type];
+    if (mbflag == -1)
+        return -1;
+    mbflag |= MBSTRING_FLAG;
+    stmp.data = NULL;
+    stmp.length = 0;
+    stmp.flags = 0;
+    ret = ASN1_mbstring_copy(&str, in->data, in->length, mbflag,
+                             B_ASN1_UTF8STRING);
+    if (ret < 0)
+        return ret;
+    *out = stmp.data;
+    return stmp.length;
+}
+
+int ASN1_STRING_print(BIO *bp, const ASN1_STRING *v)
+{
+    int i, n;
+    char buf[80];
+    const char *p;
+
+    if (v == NULL)
+        return (0);
+    n = 0;
+    p = (const char *)v->data;
+    for (i = 0; i < v->length; i++) {
+        if ((p[i] > '~') || ((p[i] < ' ') &&
+                             (p[i] != '\n') && (p[i] != '\r')))
+            buf[n] = '.';
+        else
+            buf[n] = p[i];
+        n++;
+        if (n >= 80) {
+            if (BIO_write(bp, buf, n) <= 0)
+                return (0);
+            n = 0;
+        }
+    }
+    if (n > 0)
+        if (BIO_write(bp, buf, n) <= 0)
+            return (0);
+    return (1);
+}
+
+int ASN1_TIME_print(BIO *bp, const ASN1_TIME *tm)
+{
+    if (tm->type == V_ASN1_UTCTIME)
+        return ASN1_UTCTIME_print(bp, tm);
+    if (tm->type == V_ASN1_GENERALIZEDTIME)
+        return ASN1_GENERALIZEDTIME_print(bp, tm);
+    BIO_write(bp, "Bad time value", 14);
+    return (0);
+}
+
+static const char *const mon[12] = {
+    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+int ASN1_GENERALIZEDTIME_print(BIO *bp, const ASN1_GENERALIZEDTIME *tm)
+{
+    char *v;
+    int gmt = 0;
+    int i;
+    int y = 0, M = 0, d = 0, h = 0, m = 0, s = 0;
+    char *f = NULL;
+    int f_len = 0;
+
+    i = tm->length;
+    v = (char *)tm->data;
+
+    if (i < 12)
+        goto err;
+    if (v[i - 1] == 'Z')
+        gmt = 1;
+    for (i = 0; i < 12; i++)
+        if ((v[i] > '9') || (v[i] < '0'))
+            goto err;
+    y = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + (v[2] - '0') * 10 + (v[3] -
+                                                                        '0');
+    M = (v[4] - '0') * 10 + (v[5] - '0');
+    if ((M > 12) || (M < 1))
+        goto err;
+    d = (v[6] - '0') * 10 + (v[7] - '0');
+    h = (v[8] - '0') * 10 + (v[9] - '0');
+    m = (v[10] - '0') * 10 + (v[11] - '0');
+    if (tm->length >= 14 &&
+        (v[12] >= '0') && (v[12] <= '9') &&
+        (v[13] >= '0') && (v[13] <= '9')) {
+        s = (v[12] - '0') * 10 + (v[13] - '0');
+        /* Check for fractions of seconds. */
+        if (tm->length >= 15 && v[14] == '.') {
+            int l = tm->length;
+            f = &v[14];         /* The decimal point. */
+            f_len = 1;
+            while (14 + f_len < l && f[f_len] >= '0' && f[f_len] <= '9')
+                ++f_len;
+        }
+    }
+
+    if (BIO_printf(bp, "%s %2d %02d:%02d:%02d%.*s %d%s",
+                   mon[M - 1], d, h, m, s, f_len, f, y,
+                   (gmt) ? " GMT" : "") <= 0)
+        return (0);
+    else
+        return (1);
+ err:
+    BIO_write(bp, "Bad time value", 14);
+    return (0);
+}
+
+// consume_two_digits is a helper function for ASN1_UTCTIME_print. If |*v|,
+// assumed to be |*len| bytes long, has two leading digits, updates |*out| with
+// their value, updates |v| and |len|, and returns one. Otherwise, returns
+// zero.
+static int consume_two_digits(int* out, const char **v, int *len) {
+  if (*len < 2|| !isdigit((*v)[0]) || !isdigit((*v)[1])) {
+    return 0;
+  }
+  *out = ((*v)[0] - '0') * 10 + ((*v)[1] - '0');
+  *len -= 2;
+  *v += 2;
+  return 1;
+}
+
+// consume_zulu_timezone is a helper function for ASN1_UTCTIME_print. If |*v|,
+// assumed to be |*len| bytes long, starts with "Z" then it updates |*v| and
+// |*len| and returns one. Otherwise returns zero.
+static int consume_zulu_timezone(const char **v, int *len) {
+  if (*len == 0 || (*v)[0] != 'Z') {
+    return 0;
+  }
+
+  *len -= 1;
+  *v += 1;
+  return 1;
+}
+
+int ASN1_UTCTIME_print(BIO *bp, const ASN1_UTCTIME *tm) {
+  const char *v = (const char *)tm->data;
+  int len = tm->length;
+  int Y = 0, M = 0, D = 0, h = 0, m = 0, s = 0;
+
+  // YYMMDDhhmm are required to be present.
+  if (!consume_two_digits(&Y, &v, &len) ||
+      !consume_two_digits(&M, &v, &len) ||
+      !consume_two_digits(&D, &v, &len) ||
+      !consume_two_digits(&h, &v, &len) ||
+      !consume_two_digits(&m, &v, &len)) {
+    goto err;
+  }
+  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, requires seconds
+  // to be present, but historically this code has forgiven its absence.
+  consume_two_digits(&s, &v, &len);
+
+  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, specifies this
+  // interpretation of the year.
+  if (Y < 50) {
+    Y += 2000;
+  } else {
+    Y += 1900;
+  }
+  if (M > 12 || M == 0) {
+    goto err;
+  }
+  if (D > 31 || D == 0) {
+    goto err;
+  }
+  if (h > 23 || m > 59 || s > 60) {
+    goto err;
+  }
+
+  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, requires the "Z"
+  // to be present, but historically this code has forgiven its absence.
+  const int is_gmt = consume_zulu_timezone(&v, &len);
+
+  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, does not permit
+  // the specification of timezones using the +hhmm / -hhmm syntax, which is
+  // the only other thing that might legitimately be found at the end.
+  if (len) {
+    goto err;
+  }
+
+  return BIO_printf(bp, "%s %2d %02d:%02d:%02d %d%s", mon[M - 1], D, h, m, s, Y,
+                    is_gmt ? " GMT" : "") > 0;
+
+err:
+  BIO_write(bp, "Bad time value", 14);
+  return 0;
+}
diff --git a/src/crypto/asn1/a_strnid.c b/src/crypto/asn1/a_strnid.c
index efbf0fa..f7ad084 100644
--- a/src/crypto/asn1/a_strnid.c
+++ b/src/crypto/asn1/a_strnid.c
@@ -69,53 +69,17 @@
 static STACK_OF(ASN1_STRING_TABLE) *stable = NULL;
 static void st_free(ASN1_STRING_TABLE *tbl);
 
-/*
- * This is the global mask for the mbstring functions: this is use to mask
- * out certain types (such as BMPString and UTF8String) because certain
- * software (e.g. Netscape) has problems with them.
- */
-
-static unsigned long global_mask = B_ASN1_UTF8STRING;
-
 void ASN1_STRING_set_default_mask(unsigned long mask)
 {
-    global_mask = mask;
 }
 
 unsigned long ASN1_STRING_get_default_mask(void)
 {
-    return global_mask;
+    return B_ASN1_UTF8STRING;
 }
 
-/*
- * This function sets the default to various "flavours" of configuration.
- * based on an ASCII string. Currently this is: MASK:XXXX : a numerical mask
- * value. nobmp : Don't use BMPStrings (just Printable, T61). pkix : PKIX
- * recommendation in RFC2459. utf8only : only use UTF8Strings (RFC2459
- * recommendation for 2004). default: the default value, Printable, T61, BMP.
- */
-
 int ASN1_STRING_set_default_mask_asc(const char *p)
 {
-    unsigned long mask;
-    char *end;
-    if (!strncmp(p, "MASK:", 5)) {
-        if (!p[5])
-            return 0;
-        mask = strtoul(p + 5, &end, 0);
-        if (*end)
-            return 0;
-    } else if (!strcmp(p, "nombstr"))
-        mask = ~((unsigned long)(B_ASN1_BMPSTRING | B_ASN1_UTF8STRING));
-    else if (!strcmp(p, "pkix"))
-        mask = ~((unsigned long)B_ASN1_T61STRING);
-    else if (!strcmp(p, "utf8only"))
-        mask = B_ASN1_UTF8STRING;
-    else if (!strcmp(p, "default"))
-        mask = 0xFFFFFFFFL;
-    else
-        return 0;
-    ASN1_STRING_set_default_mask(mask);
     return 1;
 }
 
@@ -139,13 +103,12 @@
     if (tbl) {
         mask = tbl->mask;
         if (!(tbl->flags & STABLE_NO_MASK))
-            mask &= global_mask;
+            mask &= B_ASN1_UTF8STRING;
         ret = ASN1_mbstring_ncopy(out, in, inlen, inform, mask,
                                   tbl->minsize, tbl->maxsize);
-    } else
-        ret =
-            ASN1_mbstring_copy(out, in, inlen, inform,
-                               DIRSTRING_TYPE & global_mask);
+    } else {
+        ret = ASN1_mbstring_copy(out, in, inlen, inform, B_ASN1_UTF8STRING);
+    }
     if (ret <= 0)
         return NULL;
     return *out;
@@ -155,7 +118,7 @@
  * Now the tables and helper functions for the string table:
  */
 
-/* size limits: this stuff is taken straight from RFC3280 */
+/* size limits: this stuff is taken straight from RFC 3280 */
 
 #define ub_name                         32768
 #define ub_common_name                  64
diff --git a/src/crypto/asn1/a_time.c b/src/crypto/asn1/a_time.c
index 15c9409..ad7f784 100644
--- a/src/crypto/asn1/a_time.c
+++ b/src/crypto/asn1/a_time.c
@@ -200,7 +200,7 @@
     return 0;
 }
 
-int ASN1_TIME_diff(int *pday, int *psec,
+int ASN1_TIME_diff(int *out_days, int *out_seconds,
                    const ASN1_TIME *from, const ASN1_TIME *to)
 {
     struct tm tm_from, tm_to;
@@ -208,5 +208,5 @@
         return 0;
     if (!asn1_time_to_tm(&tm_to, to))
         return 0;
-    return OPENSSL_gmtime_diff(pday, psec, &tm_from, &tm_to);
+    return OPENSSL_gmtime_diff(out_days, out_seconds, &tm_from, &tm_to);
 }
diff --git a/src/crypto/asn1/a_utctm.c b/src/crypto/asn1/a_utctm.c
index 28f07ac..21ea2cc 100644
--- a/src/crypto/asn1/a_utctm.c
+++ b/src/crypto/asn1/a_utctm.c
@@ -262,42 +262,3 @@
         return -1;
     return 0;
 }
-
-#if 0
-time_t ASN1_UTCTIME_get(const ASN1_UTCTIME *s)
-{
-    struct tm tm;
-    int offset;
-
-    OPENSSL_memset(&tm, '\0', sizeof tm);
-
-# define g2(p) (((p)[0]-'0')*10+(p)[1]-'0')
-    tm.tm_year = g2(s->data);
-    if (tm.tm_year < 50)
-        tm.tm_year += 100;
-    tm.tm_mon = g2(s->data + 2) - 1;
-    tm.tm_mday = g2(s->data + 4);
-    tm.tm_hour = g2(s->data + 6);
-    tm.tm_min = g2(s->data + 8);
-    tm.tm_sec = g2(s->data + 10);
-    if (s->data[12] == 'Z')
-        offset = 0;
-    else {
-        offset = g2(s->data + 13) * 60 + g2(s->data + 15);
-        if (s->data[12] == '-')
-            offset = -offset;
-    }
-# undef g2
-
-    return mktime(&tm) - offset * 60; /* FIXME: mktime assumes the current
-                                       * timezone instead of UTC, and unless
-                                       * we rewrite OpenSSL in Lisp we cannot
-                                       * locally change the timezone without
-                                       * possibly interfering with other
-                                       * parts of the program. timegm, which
-                                       * uses UTC, is non-standard. Also
-                                       * time_t is inappropriate for general
-                                       * UTC times because it may a 32 bit
-                                       * type. */
-}
-#endif
diff --git a/src/crypto/asn1/asn1_par.c b/src/crypto/asn1/asn1_par.c
index b1a01ed..282ad23 100644
--- a/src/crypto/asn1/asn1_par.c
+++ b/src/crypto/asn1/asn1_par.c
@@ -72,7 +72,7 @@
     };
 
     if ((tag == V_ASN1_NEG_INTEGER) || (tag == V_ASN1_NEG_ENUMERATED))
-        tag &= ~0x100;
+        tag &= ~V_ASN1_NEG;
 
     if (tag < 0 || tag > 30)
         return "(unknown)";
diff --git a/src/crypto/asn1/asn1_test.cc b/src/crypto/asn1/asn1_test.cc
index 725542c..9119dea 100644
--- a/src/crypto/asn1/asn1_test.cc
+++ b/src/crypto/asn1/asn1_test.cc
@@ -15,6 +15,7 @@
 #include <limits.h>
 #include <stdio.h>
 
+#include <string>
 #include <vector>
 
 #include <gtest/gtest.h>
@@ -169,6 +170,8 @@
       {V_ASN1_BOOLEAN, {0x01, 0x01, 0x00}},
       // OCTET_STRING { "a" }
       {V_ASN1_OCTET_STRING, {0x04, 0x01, 0x61}},
+      // OCTET_STRING { }
+      {V_ASN1_OCTET_STRING, {0x04, 0x00}},
       // BIT_STRING { `01` `00` }
       {V_ASN1_BIT_STRING, {0x03, 0x02, 0x01, 0x00}},
       // INTEGER { -1 }
@@ -456,6 +459,666 @@
   }
 }
 
+static std::string ASN1StringToStdString(const ASN1_STRING *str) {
+  return std::string(ASN1_STRING_get0_data(str),
+                     ASN1_STRING_get0_data(str) + ASN1_STRING_length(str));
+}
+
+TEST(ASN1Test, SetTime) {
+  static const struct {
+    time_t time;
+    const char *generalized;
+    const char *utc;
+  } kTests[] = {
+    {-631152001, "19491231235959Z", nullptr},
+    {-631152000, "19500101000000Z", "500101000000Z"},
+    {0, "19700101000000Z", "700101000000Z"},
+    {981173106, "20010203040506Z", "010203040506Z"},
+#if defined(OPENSSL_64_BIT)
+    // TODO(https://crbug.com/boringssl/416): These cases overflow 32-bit
+    // |time_t| and do not consistently work on 32-bit platforms. For now,
+    // disable the tests on 32-bit. Re-enable them once the bug is fixed.
+    {2524607999, "20491231235959Z", "491231235959Z"},
+    {2524608000, "20500101000000Z", nullptr},
+    // Test boundary conditions.
+    {-62167219200, "00000101000000Z", nullptr},
+    {-62167219201, nullptr, nullptr},
+    {253402300799, "99991231235959Z", nullptr},
+    {253402300800, nullptr, nullptr},
+#endif
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(t.time);
+#if defined(OPENSSL_WINDOWS)
+    // Windows |time_t| functions can only handle 1970 through 3000. See
+    // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/gmtime-s-gmtime32-s-gmtime64-s?view=msvc-160
+    if (t.time < 0 || int64_t{t.time} > 32535215999) {
+      continue;
+    }
+#endif
+
+    bssl::UniquePtr<ASN1_UTCTIME> utc(ASN1_UTCTIME_set(nullptr, t.time));
+    if (t.utc) {
+      ASSERT_TRUE(utc);
+      EXPECT_EQ(V_ASN1_UTCTIME, ASN1_STRING_type(utc.get()));
+      EXPECT_EQ(t.utc, ASN1StringToStdString(utc.get()));
+    } else {
+      EXPECT_FALSE(utc);
+    }
+
+    bssl::UniquePtr<ASN1_GENERALIZEDTIME> generalized(
+        ASN1_GENERALIZEDTIME_set(nullptr, t.time));
+    if (t.generalized) {
+      ASSERT_TRUE(generalized);
+      EXPECT_EQ(V_ASN1_GENERALIZEDTIME, ASN1_STRING_type(generalized.get()));
+      EXPECT_EQ(t.generalized, ASN1StringToStdString(generalized.get()));
+    } else {
+      EXPECT_FALSE(generalized);
+    }
+
+    bssl::UniquePtr<ASN1_TIME> choice(ASN1_TIME_set(nullptr, t.time));
+    if (t.generalized) {
+      ASSERT_TRUE(choice);
+      if (t.utc) {
+        EXPECT_EQ(V_ASN1_UTCTIME, ASN1_STRING_type(choice.get()));
+        EXPECT_EQ(t.utc, ASN1StringToStdString(choice.get()));
+      } else {
+        EXPECT_EQ(V_ASN1_GENERALIZEDTIME, ASN1_STRING_type(choice.get()));
+        EXPECT_EQ(t.generalized, ASN1StringToStdString(choice.get()));
+      }
+    } else {
+      EXPECT_FALSE(choice);
+    }
+  }
+}
+
+static std::vector<uint8_t> StringToVector(const std::string &str) {
+  return std::vector<uint8_t>(str.begin(), str.end());
+}
+
+TEST(ASN1Test, StringPrintEx) {
+  const struct {
+    int type;
+    std::vector<uint8_t> data;
+    int str_flags;
+    unsigned long flags;
+    std::string expected;
+  } kTests[] = {
+      // A string like "hello" is never escaped or quoted.
+      // |ASN1_STRFLGS_ESC_QUOTE| only introduces quotes when needed. Note
+      // OpenSSL interprets T61String as Latin-1.
+      {V_ASN1_T61STRING, StringToVector("hello"), 0, 0, "hello"},
+      {V_ASN1_T61STRING, StringToVector("hello"), 0,
+       ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB,
+       "hello"},
+      {V_ASN1_T61STRING, StringToVector("hello"), 0,
+       ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB |
+           ASN1_STRFLGS_ESC_QUOTE,
+       "hello"},
+
+      // By default, 8-bit characters are printed without escaping.
+      {V_ASN1_T61STRING,
+       {0, '\n', 0x80, 0xff, ',', '+', '"', '\\', '<', '>', ';'},
+       0,
+       0,
+       std::string(1, '\0') + "\n\x80\xff,+\"\\<>;"},
+
+      // Flags control different escapes. Note that any escape flag will cause
+      // blackslashes to be escaped.
+      {V_ASN1_T61STRING,
+       {0, '\n', 0x80, 0xff, ',', '+', '"', '\\', '<', '>', ';'},
+       0,
+       ASN1_STRFLGS_ESC_2253,
+       std::string(1, '\0') + "\n\x80\xff\\,\\+\\\"\\\\\\<\\>\\;"},
+      {V_ASN1_T61STRING,
+       {0, '\n', 0x80, 0xff, ',', '+', '"', '\\', '<', '>', ';'},
+       0,
+       ASN1_STRFLGS_ESC_CTRL,
+       "\\00\\0A\x80\xff,+\"\\\\<>;"},
+      {V_ASN1_T61STRING,
+       {0, '\n', 0x80, 0xff, ',', '+', '"', '\\', '<', '>', ';'},
+       0,
+       ASN1_STRFLGS_ESC_MSB,
+       std::string(1, '\0') + "\n\\80\\FF,+\"\\\\<>;"},
+      {V_ASN1_T61STRING,
+       {0, '\n', 0x80, 0xff, ',', '+', '"', '\\', '<', '>', ';'},
+       0,
+       ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB,
+       "\\00\\0A\\80\\FF\\,\\+\\\"\\\\\\<\\>\\;"},
+
+      // When quoted, fewer characters need to be escaped in RFC 2253.
+      {V_ASN1_T61STRING,
+       {0, '\n', 0x80, 0xff, ',', '+', '"', '\\', '<', '>', ';'},
+       0,
+       ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB |
+           ASN1_STRFLGS_ESC_QUOTE,
+       "\"\\00\\0A\\80\\FF,+\\\"\\\\<>;\""},
+
+      // If no characters benefit from quotes, no quotes are added.
+      {V_ASN1_T61STRING,
+       {0, '\n', 0x80, 0xff, '"', '\\'},
+       0,
+       ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB |
+           ASN1_STRFLGS_ESC_QUOTE,
+       "\\00\\0A\\80\\FF\\\"\\\\"},
+
+      // RFC 2253 only escapes spaces at the start and end of a string.
+      {V_ASN1_T61STRING, StringToVector("   "), 0, ASN1_STRFLGS_ESC_2253,
+       "\\  \\ "},
+      {V_ASN1_T61STRING, StringToVector("   "), 0,
+       ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_QUOTE, "\"   \""},
+
+      // RFC 2253 only escapes # at the start of a string.
+      {V_ASN1_T61STRING, StringToVector("###"), 0, ASN1_STRFLGS_ESC_2253,
+       "\\###"},
+      {V_ASN1_T61STRING, StringToVector("###"), 0,
+       ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_QUOTE, "\"###\""},
+
+      // By default, strings are decoded and Unicode code points are
+      // individually escaped.
+      {V_ASN1_UTF8STRING, StringToVector("a\xc2\x80\xc4\x80\xf0\x90\x80\x80"),
+       0, ASN1_STRFLGS_ESC_MSB, "a\\80\\U0100\\W00010000"},
+      {V_ASN1_BMPSTRING,
+       {0x00, 'a', 0x00, 0x80, 0x01, 0x00},
+       0,
+       ASN1_STRFLGS_ESC_MSB,
+       "a\\80\\U0100"},
+      {V_ASN1_UNIVERSALSTRING,
+       {0x00, 0x00, 0x00, 'a',   //
+        0x00, 0x00, 0x00, 0x80,  //
+        0x00, 0x00, 0x01, 0x00,  //
+        0x00, 0x01, 0x00, 0x00},
+       0,
+       ASN1_STRFLGS_ESC_MSB,
+       "a\\80\\U0100\\W00010000"},
+
+      // |ASN1_STRFLGS_UTF8_CONVERT| normalizes everything to UTF-8 and then
+      // escapes individual bytes.
+      {V_ASN1_IA5STRING, StringToVector("a\x80"), 0,
+       ASN1_STRFLGS_ESC_MSB | ASN1_STRFLGS_UTF8_CONVERT, "a\\C2\\80"},
+      {V_ASN1_T61STRING, StringToVector("a\x80"), 0,
+       ASN1_STRFLGS_ESC_MSB | ASN1_STRFLGS_UTF8_CONVERT, "a\\C2\\80"},
+      {V_ASN1_UTF8STRING, StringToVector("a\xc2\x80\xc4\x80\xf0\x90\x80\x80"),
+       0, ASN1_STRFLGS_ESC_MSB | ASN1_STRFLGS_UTF8_CONVERT,
+       "a\\C2\\80\\C4\\80\\F0\\90\\80\\80"},
+      {V_ASN1_BMPSTRING,
+       {0x00, 'a', 0x00, 0x80, 0x01, 0x00},
+       0,
+       ASN1_STRFLGS_ESC_MSB | ASN1_STRFLGS_UTF8_CONVERT,
+       "a\\C2\\80\\C4\\80"},
+      {V_ASN1_UNIVERSALSTRING,
+       {0x00, 0x00, 0x00, 'a',   //
+        0x00, 0x00, 0x00, 0x80,  //
+        0x00, 0x00, 0x01, 0x00,  //
+        0x00, 0x01, 0x00, 0x00},
+       0,
+       ASN1_STRFLGS_ESC_MSB | ASN1_STRFLGS_UTF8_CONVERT,
+       "a\\C2\\80\\C4\\80\\F0\\90\\80\\80"},
+
+      // The same as above, but without escaping the UTF-8 encoding.
+      {V_ASN1_IA5STRING, StringToVector("a\x80"), 0, ASN1_STRFLGS_UTF8_CONVERT,
+       "a\xc2\x80"},
+      {V_ASN1_T61STRING, StringToVector("a\x80"), 0, ASN1_STRFLGS_UTF8_CONVERT,
+       "a\xc2\x80"},
+      {V_ASN1_UTF8STRING, StringToVector("a\xc2\x80\xc4\x80\xf0\x90\x80\x80"),
+       0, ASN1_STRFLGS_UTF8_CONVERT, "a\xc2\x80\xc4\x80\xf0\x90\x80\x80"},
+      {V_ASN1_BMPSTRING,
+       {0x00, 'a', 0x00, 0x80, 0x01, 0x00},
+       0,
+       ASN1_STRFLGS_UTF8_CONVERT,
+       "a\xc2\x80\xc4\x80"},
+      {V_ASN1_UNIVERSALSTRING,
+       {0x00, 0x00, 0x00, 'a',   //
+        0x00, 0x00, 0x00, 0x80,  //
+        0x00, 0x00, 0x01, 0x00,  //
+        0x00, 0x01, 0x00, 0x00},
+       0,
+       ASN1_STRFLGS_UTF8_CONVERT,
+       "a\xc2\x80\xc4\x80\xf0\x90\x80\x80"},
+
+      // Types that cannot be decoded are, by default, treated as a byte string.
+      {V_ASN1_OCTET_STRING, {0xff}, 0, 0, "\xff"},
+      {-1, {0xff}, 0, 0, "\xff"},
+      {100, {0xff}, 0, 0, "\xff"},
+
+      // |ASN1_STRFLGS_UTF8_CONVERT| still converts these bytes to UTF-8.
+      //
+      // TODO(davidben): This seems like a bug. Although it's unclear because
+      // the non-RFC-2253 options aren't especially sound. Can we just remove
+      // them?
+      {V_ASN1_OCTET_STRING, {0xff}, 0, ASN1_STRFLGS_UTF8_CONVERT, "\xc3\xbf"},
+      {-1, {0xff}, 0, ASN1_STRFLGS_UTF8_CONVERT, "\xc3\xbf"},
+      {100, {0xff}, 0, ASN1_STRFLGS_UTF8_CONVERT, "\xc3\xbf"},
+
+      // |ASN1_STRFLGS_IGNORE_TYPE| causes the string type to be ignored, so it
+      // is always treated as a byte string, even if it is not a valid encoding.
+      {V_ASN1_UTF8STRING, {0xff}, 0, ASN1_STRFLGS_IGNORE_TYPE, "\xff"},
+      {V_ASN1_BMPSTRING, {0xff}, 0, ASN1_STRFLGS_IGNORE_TYPE, "\xff"},
+      {V_ASN1_UNIVERSALSTRING, {0xff}, 0, ASN1_STRFLGS_IGNORE_TYPE, "\xff"},
+
+      // |ASN1_STRFLGS_SHOW_TYPE| prepends the type name.
+      {V_ASN1_UTF8STRING, {'a'}, 0, ASN1_STRFLGS_SHOW_TYPE, "UTF8STRING:a"},
+      {-1, {'a'}, 0, ASN1_STRFLGS_SHOW_TYPE, "(unknown):a"},
+      {100, {'a'}, 0, ASN1_STRFLGS_SHOW_TYPE, "(unknown):a"},
+
+      // |ASN1_STRFLGS_DUMP_ALL| and |ASN1_STRFLGS_DUMP_UNKNOWN| cause
+      // non-string types to be printed in hex, though without the DER wrapper
+      // by default.
+      {V_ASN1_UTF8STRING, StringToVector("\xe2\x98\x83"), 0,
+       ASN1_STRFLGS_DUMP_UNKNOWN, "\\U2603"},
+      {V_ASN1_UTF8STRING, StringToVector("\xe2\x98\x83"), 0,
+       ASN1_STRFLGS_DUMP_ALL, "#E29883"},
+      {V_ASN1_OCTET_STRING, StringToVector("\xe2\x98\x83"), 0,
+       ASN1_STRFLGS_DUMP_UNKNOWN, "#E29883"},
+      {V_ASN1_OCTET_STRING, StringToVector("\xe2\x98\x83"), 0,
+       ASN1_STRFLGS_DUMP_ALL, "#E29883"},
+
+      // |ASN1_STRFLGS_DUMP_DER| includes the entire element.
+      {V_ASN1_UTF8STRING, StringToVector("\xe2\x98\x83"), 0,
+       ASN1_STRFLGS_DUMP_ALL | ASN1_STRFLGS_DUMP_DER, "#0C03E29883"},
+      {V_ASN1_OCTET_STRING, StringToVector("\xe2\x98\x83"), 0,
+       ASN1_STRFLGS_DUMP_ALL | ASN1_STRFLGS_DUMP_DER, "#0403E29883"},
+      {V_ASN1_BIT_STRING,
+       {0x80},
+       ASN1_STRING_FLAG_BITS_LEFT | 4,
+       ASN1_STRFLGS_DUMP_ALL | ASN1_STRFLGS_DUMP_DER,
+       "#03020480"},
+      // INTEGER { 1 }
+      {V_ASN1_INTEGER,
+       {0x01},
+       0,
+       ASN1_STRFLGS_DUMP_ALL | ASN1_STRFLGS_DUMP_DER,
+       "#020101"},
+      // INTEGER { -1 }
+      {V_ASN1_NEG_INTEGER,
+       {0x01},
+       0,
+       ASN1_STRFLGS_DUMP_ALL | ASN1_STRFLGS_DUMP_DER,
+       "#0201FF"},
+      // ENUMERATED { 1 }
+      {V_ASN1_ENUMERATED,
+       {0x01},
+       0,
+       ASN1_STRFLGS_DUMP_ALL | ASN1_STRFLGS_DUMP_DER,
+       "#0A0101"},
+      // ENUMERATED { -1 }
+      {V_ASN1_NEG_ENUMERATED,
+       {0x01},
+       0,
+       ASN1_STRFLGS_DUMP_ALL | ASN1_STRFLGS_DUMP_DER,
+       "#0A01FF"},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(t.type);
+    SCOPED_TRACE(Bytes(t.data));
+    SCOPED_TRACE(t.str_flags);
+    SCOPED_TRACE(t.flags);
+
+    bssl::UniquePtr<ASN1_STRING> str(ASN1_STRING_type_new(t.type));
+    ASSERT_TRUE(ASN1_STRING_set(str.get(), t.data.data(), t.data.size()));
+    str->flags = t.str_flags;
+
+    // If the |BIO| is null, it should measure the size.
+    int len = ASN1_STRING_print_ex(nullptr, str.get(), t.flags);
+    EXPECT_EQ(len, static_cast<int>(t.expected.size()));
+
+    // Measuring the size should also work for the |FILE| version
+    len = ASN1_STRING_print_ex_fp(nullptr, str.get(), t.flags);
+    EXPECT_EQ(len, static_cast<int>(t.expected.size()));
+
+    // Actually print the string.
+    bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
+    ASSERT_TRUE(bio);
+    len = ASN1_STRING_print_ex(bio.get(), str.get(), t.flags);
+    EXPECT_EQ(len, static_cast<int>(t.expected.size()));
+
+    const uint8_t *bio_contents;
+    size_t bio_len;
+    ASSERT_TRUE(BIO_mem_contents(bio.get(), &bio_contents, &bio_len));
+    EXPECT_EQ(t.expected, std::string(bio_contents, bio_contents + bio_len));
+  }
+
+  const struct {
+    int type;
+    std::vector<uint8_t> data;
+    int str_flags;
+    unsigned long flags;
+  } kUnprintableTests[] = {
+      // When decoding strings, invalid codepoints are errors.
+      {V_ASN1_UTF8STRING, {0xff}, 0, ASN1_STRFLGS_ESC_MSB},
+      {V_ASN1_BMPSTRING, {0xff}, 0, ASN1_STRFLGS_ESC_MSB},
+      {V_ASN1_BMPSTRING, {0xff}, 0, ASN1_STRFLGS_ESC_MSB},
+      {V_ASN1_UNIVERSALSTRING, {0xff}, 0, ASN1_STRFLGS_ESC_MSB},
+  };
+  for (const auto &t : kUnprintableTests) {
+    SCOPED_TRACE(t.type);
+    SCOPED_TRACE(Bytes(t.data));
+    SCOPED_TRACE(t.str_flags);
+    SCOPED_TRACE(t.flags);
+
+    bssl::UniquePtr<ASN1_STRING> str(ASN1_STRING_type_new(t.type));
+    ASSERT_TRUE(ASN1_STRING_set(str.get(), t.data.data(), t.data.size()));
+    str->flags = t.str_flags;
+
+    // If the |BIO| is null, it should measure the size.
+    int len = ASN1_STRING_print_ex(nullptr, str.get(), t.flags);
+    EXPECT_EQ(len, -1);
+    ERR_clear_error();
+
+    // Measuring the size should also work for the |FILE| version
+    len = ASN1_STRING_print_ex_fp(nullptr, str.get(), t.flags);
+    EXPECT_EQ(len, -1);
+    ERR_clear_error();
+
+    // Actually print the string.
+    bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
+    ASSERT_TRUE(bio);
+    len = ASN1_STRING_print_ex(bio.get(), str.get(), t.flags);
+    EXPECT_EQ(len, -1);
+    ERR_clear_error();
+  }
+}
+
+TEST(ASN1Test, MBString) {
+  const unsigned long kAll = B_ASN1_PRINTABLESTRING | B_ASN1_IA5STRING |
+                             B_ASN1_T61STRING | B_ASN1_BMPSTRING |
+                             B_ASN1_UNIVERSALSTRING | B_ASN1_UTF8STRING;
+
+  const struct {
+    int format;
+    std::vector<uint8_t> in;
+    unsigned long mask;
+    int expected_type;
+    std::vector<uint8_t> expected_data;
+    int num_codepoints;
+  } kTests[] = {
+      // Given a choice of formats, we pick the smallest that fits.
+      {MBSTRING_UTF8, {}, kAll, V_ASN1_PRINTABLESTRING, {}, 0},
+      {MBSTRING_UTF8, {'a'}, kAll, V_ASN1_PRINTABLESTRING, {'a'}, 1},
+      {MBSTRING_UTF8,
+       {'a', 'A', '0', '\'', '(', ')', '+', ',', '-', '.', '/', ':', '=', '?'},
+       kAll,
+       V_ASN1_PRINTABLESTRING,
+       {'a', 'A', '0', '\'', '(', ')', '+', ',', '-', '.', '/', ':', '=', '?'},
+       14},
+      {MBSTRING_UTF8, {'*'}, kAll, V_ASN1_IA5STRING, {'*'}, 1},
+      {MBSTRING_UTF8, {'\n'}, kAll, V_ASN1_IA5STRING, {'\n'}, 1},
+      {MBSTRING_UTF8,
+       {0xc2, 0x80 /* U+0080 */},
+       kAll,
+       V_ASN1_T61STRING,
+       {0x80},
+       1},
+      {MBSTRING_UTF8,
+       {0xc4, 0x80 /* U+0100 */},
+       kAll,
+       V_ASN1_BMPSTRING,
+       {0x01, 0x00},
+       1},
+      {MBSTRING_UTF8,
+       {0xf0, 0x90, 0x80, 0x80 /* U+10000 */},
+       kAll,
+       V_ASN1_UNIVERSALSTRING,
+       {0x00, 0x01, 0x00, 0x00},
+       1},
+      {MBSTRING_UTF8,
+       {0xf0, 0x90, 0x80, 0x80 /* U+10000 */},
+       kAll & ~B_ASN1_UNIVERSALSTRING,
+       V_ASN1_UTF8STRING,
+       {0xf0, 0x90, 0x80, 0x80},
+       1},
+
+      // NUL is not printable. It should also not terminate iteration.
+      {MBSTRING_UTF8, {0}, kAll, V_ASN1_IA5STRING, {0}, 1},
+      {MBSTRING_UTF8, {0, 'a'}, kAll, V_ASN1_IA5STRING, {0, 'a'}, 2},
+
+      // When a particular format is specified, we use it.
+      {MBSTRING_UTF8,
+       {'a'},
+       B_ASN1_PRINTABLESTRING,
+       V_ASN1_PRINTABLESTRING,
+       {'a'},
+       1},
+      {MBSTRING_UTF8, {'a'}, B_ASN1_IA5STRING, V_ASN1_IA5STRING, {'a'}, 1},
+      {MBSTRING_UTF8, {'a'}, B_ASN1_T61STRING, V_ASN1_T61STRING, {'a'}, 1},
+      {MBSTRING_UTF8, {'a'}, B_ASN1_UTF8STRING, V_ASN1_UTF8STRING, {'a'}, 1},
+      {MBSTRING_UTF8,
+       {'a'},
+       B_ASN1_BMPSTRING,
+       V_ASN1_BMPSTRING,
+       {0x00, 'a'},
+       1},
+      {MBSTRING_UTF8,
+       {'a'},
+       B_ASN1_UNIVERSALSTRING,
+       V_ASN1_UNIVERSALSTRING,
+       {0x00, 0x00, 0x00, 'a'},
+       1},
+
+      // A long string with characters of many widths, to test sizes are
+      // measured in code points.
+      {MBSTRING_UTF8,
+       {
+           'a',                     //
+           0xc2, 0x80,              // U+0080
+           0xc4, 0x80,              // U+0100
+           0xf0, 0x90, 0x80, 0x80,  // U+10000
+       },
+       B_ASN1_UNIVERSALSTRING,
+       V_ASN1_UNIVERSALSTRING,
+       {
+           0x00, 0x00, 0x00, 'a',   //
+           0x00, 0x00, 0x00, 0x80,  //
+           0x00, 0x00, 0x01, 0x00,  //
+           0x00, 0x01, 0x00, 0x00,  //
+       },
+       4},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(t.format);
+    SCOPED_TRACE(Bytes(t.in));
+    SCOPED_TRACE(t.mask);
+
+    // Passing in nullptr should do a dry run.
+    EXPECT_EQ(t.expected_type,
+              ASN1_mbstring_copy(nullptr, t.in.data(), t.in.size(), t.format,
+                                 t.mask));
+
+    // Test allocating a new object.
+    ASN1_STRING *str = nullptr;
+    EXPECT_EQ(
+        t.expected_type,
+        ASN1_mbstring_copy(&str, t.in.data(), t.in.size(), t.format, t.mask));
+    ASSERT_TRUE(str);
+    EXPECT_EQ(t.expected_type, ASN1_STRING_type(str));
+    EXPECT_EQ(Bytes(t.expected_data),
+              Bytes(ASN1_STRING_get0_data(str), ASN1_STRING_length(str)));
+
+    // Test writing into an existing object.
+    ASN1_STRING_free(str);
+    str = ASN1_STRING_new();
+    ASSERT_TRUE(str);
+    ASN1_STRING *old_str = str;
+    EXPECT_EQ(
+        t.expected_type,
+        ASN1_mbstring_copy(&str, t.in.data(), t.in.size(), t.format, t.mask));
+    ASSERT_EQ(old_str, str);
+    EXPECT_EQ(t.expected_type, ASN1_STRING_type(str));
+    EXPECT_EQ(Bytes(t.expected_data),
+              Bytes(ASN1_STRING_get0_data(str), ASN1_STRING_length(str)));
+    ASN1_STRING_free(str);
+    str = nullptr;
+
+    // minsize and maxsize should be enforced, even in a dry run.
+    EXPECT_EQ(t.expected_type,
+              ASN1_mbstring_ncopy(nullptr, t.in.data(), t.in.size(), t.format,
+                                  t.mask, /*minsize=*/t.num_codepoints,
+                                  /*maxsize=*/t.num_codepoints));
+
+    EXPECT_EQ(t.expected_type,
+              ASN1_mbstring_ncopy(&str, t.in.data(), t.in.size(), t.format,
+                                  t.mask, /*minsize=*/t.num_codepoints,
+                                  /*maxsize=*/t.num_codepoints));
+    ASSERT_TRUE(str);
+    EXPECT_EQ(t.expected_type, ASN1_STRING_type(str));
+    EXPECT_EQ(Bytes(t.expected_data),
+              Bytes(ASN1_STRING_get0_data(str), ASN1_STRING_length(str)));
+    ASN1_STRING_free(str);
+    str = nullptr;
+
+    EXPECT_EQ(-1, ASN1_mbstring_ncopy(
+                      nullptr, t.in.data(), t.in.size(), t.format, t.mask,
+                      /*minsize=*/t.num_codepoints + 1, /*maxsize=*/0));
+    ERR_clear_error();
+    EXPECT_EQ(-1, ASN1_mbstring_ncopy(
+                      &str, t.in.data(), t.in.size(), t.format, t.mask,
+                      /*minsize=*/t.num_codepoints + 1, /*maxsize=*/0));
+    EXPECT_FALSE(str);
+    ERR_clear_error();
+    if (t.num_codepoints > 1) {
+      EXPECT_EQ(-1, ASN1_mbstring_ncopy(
+                        nullptr, t.in.data(), t.in.size(), t.format, t.mask,
+                        /*minsize=*/0, /*maxsize=*/t.num_codepoints - 1));
+      ERR_clear_error();
+      EXPECT_EQ(-1, ASN1_mbstring_ncopy(
+                        &str, t.in.data(), t.in.size(), t.format, t.mask,
+                        /*minsize=*/0, /*maxsize=*/t.num_codepoints - 1));
+      EXPECT_FALSE(str);
+      ERR_clear_error();
+    }
+  }
+
+  const struct {
+    int format;
+    std::vector<uint8_t> in;
+    unsigned long mask;
+  } kInvalidTests[] = {
+      // Invalid encodings are rejected.
+      {MBSTRING_UTF8, {0xff}, B_ASN1_UTF8STRING},
+      {MBSTRING_BMP, {0xff}, B_ASN1_UTF8STRING},
+      {MBSTRING_UNIV, {0xff}, B_ASN1_UTF8STRING},
+
+      // Lone surrogates are not code points.
+      {MBSTRING_UTF8, {0xed, 0xa0, 0x80}, B_ASN1_UTF8STRING},
+      {MBSTRING_BMP, {0xd8, 0x00}, B_ASN1_UTF8STRING},
+      {MBSTRING_UNIV, {0x00, 0x00, 0xd8, 0x00}, B_ASN1_UTF8STRING},
+
+      // The input does not fit in the allowed output types.
+      {MBSTRING_UTF8, {'\n'}, B_ASN1_PRINTABLESTRING},
+      {MBSTRING_UTF8,
+       {0xc2, 0x80 /* U+0080 */},
+       B_ASN1_PRINTABLESTRING | B_ASN1_IA5STRING},
+      {MBSTRING_UTF8,
+       {0xc4, 0x80 /* U+0100 */},
+       B_ASN1_PRINTABLESTRING | B_ASN1_IA5STRING | B_ASN1_T61STRING},
+      {MBSTRING_UTF8,
+       {0xf0, 0x90, 0x80, 0x80 /* U+10000 */},
+       B_ASN1_PRINTABLESTRING | B_ASN1_IA5STRING | B_ASN1_T61STRING |
+           B_ASN1_BMPSTRING},
+
+      // Unrecognized bits are ignored.
+      {MBSTRING_UTF8, {'\n'}, B_ASN1_PRINTABLESTRING | B_ASN1_SEQUENCE},
+  };
+  for (const auto &t : kInvalidTests) {
+    SCOPED_TRACE(t.format);
+    SCOPED_TRACE(Bytes(t.in));
+    SCOPED_TRACE(t.mask);
+
+    EXPECT_EQ(-1, ASN1_mbstring_copy(nullptr, t.in.data(), t.in.size(),
+                                     t.format, t.mask));
+    ERR_clear_error();
+
+    ASN1_STRING *str = nullptr;
+    EXPECT_EQ(-1, ASN1_mbstring_copy(&str, t.in.data(), t.in.size(),
+                                     t.format, t.mask));
+    ERR_clear_error();
+    EXPECT_EQ(nullptr, str);
+  }
+}
+
+// Test that multi-string types correctly encode negative ENUMERATED.
+// Multi-string types cannot contain INTEGER, so we only test ENUMERATED.
+TEST(ASN1Test, NegativeEnumeratedMultistring) {
+  static const uint8_t kMinusOne[] = {0x0a, 0x01, 0xff};  // ENUMERATED { -1 }
+  // |ASN1_PRINTABLE| is a multi-string type that allows ENUMERATED.
+  const uint8_t *p = kMinusOne;
+  bssl::UniquePtr<ASN1_STRING> str(
+      d2i_ASN1_PRINTABLE(nullptr, &p, sizeof(kMinusOne)));
+  ASSERT_TRUE(str);
+  TestSerialize(str.get(), i2d_ASN1_PRINTABLE, kMinusOne);
+}
+
+TEST(ASN1Test, PrintableType) {
+  const struct {
+    std::vector<uint8_t> in;
+    int result;
+  } kTests[] = {
+      {{}, V_ASN1_PRINTABLESTRING},
+      {{'a', 'A', '0', '\'', '(', ')', '+', ',', '-', '.', '/', ':', '=', '?'},
+       V_ASN1_PRINTABLESTRING},
+      {{'*'}, V_ASN1_IA5STRING},
+      {{'\0'}, V_ASN1_IA5STRING},
+      {{'\0', 'a'}, V_ASN1_IA5STRING},
+      {{0, 1, 2, 3, 125, 126, 127}, V_ASN1_IA5STRING},
+      {{0, 1, 2, 3, 125, 126, 127, 128}, V_ASN1_T61STRING},
+      {{128, 0, 1, 2, 3, 125, 126, 127}, V_ASN1_T61STRING},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(Bytes(t.in));
+    EXPECT_EQ(t.result, ASN1_PRINTABLE_type(t.in.data(), t.in.size()));
+  }
+}
+
+// Encoding a CHOICE type with an invalid selector should fail.
+TEST(ASN1Test, InvalidChoice) {
+  bssl::UniquePtr<GENERAL_NAME> name(GENERAL_NAME_new());
+  ASSERT_TRUE(name);
+  // CHOICE types are initialized with an invalid selector.
+  EXPECT_EQ(-1, name->type);
+  // |name| should fail to encode.
+  EXPECT_EQ(-1, i2d_GENERAL_NAME(name.get(), nullptr));
+
+  // The error should be propagated through types containing |name|.
+  bssl::UniquePtr<GENERAL_NAMES> names(GENERAL_NAMES_new());
+  ASSERT_TRUE(names);
+  EXPECT_TRUE(bssl::PushToStack(names.get(), std::move(name)));
+  EXPECT_EQ(-1, i2d_GENERAL_NAMES(names.get(), nullptr));
+}
+
+// Encoding NID-only |ASN1_OBJECT|s should fail.
+TEST(ASN1Test, InvalidObject) {
+  EXPECT_EQ(-1, i2d_ASN1_OBJECT(OBJ_nid2obj(NID_kx_ecdhe), nullptr));
+
+  bssl::UniquePtr<X509_ALGOR> alg(X509_ALGOR_new());
+  ASSERT_TRUE(alg);
+  ASSERT_TRUE(X509_ALGOR_set0(alg.get(), OBJ_nid2obj(NID_kx_ecdhe),
+                              V_ASN1_UNDEF, nullptr));
+  EXPECT_EQ(-1, i2d_X509_ALGOR(alg.get(), nullptr));
+}
+
+// Encoding invalid |ASN1_TYPE|s should fail. |ASN1_TYPE|s are
+// default-initialized to an invalid type.
+TEST(ASN1Test, InvalidASN1Type) {
+  bssl::UniquePtr<ASN1_TYPE> obj(ASN1_TYPE_new());
+  ASSERT_TRUE(obj);
+  EXPECT_EQ(-1, obj->type);
+  EXPECT_EQ(-1, i2d_ASN1_TYPE(obj.get(), nullptr));
+}
+
+// Encoding invalid MSTRING types should fail. An MSTRING is a CHOICE of
+// string-like types. They are initialized to an invalid type.
+TEST(ASN1Test, InvalidMSTRING) {
+  bssl::UniquePtr<ASN1_STRING> obj(ASN1_TIME_new());
+  ASSERT_TRUE(obj);
+  EXPECT_EQ(-1, obj->type);
+  EXPECT_EQ(-1, i2d_ASN1_TIME(obj.get(), nullptr));
+
+  obj.reset(DIRECTORYSTRING_new());
+  ASSERT_TRUE(obj);
+  EXPECT_EQ(-1, obj->type);
+  EXPECT_EQ(-1, i2d_DIRECTORYSTRING(obj.get(), nullptr));
+}
+
 // The ASN.1 macros do not work on Windows shared library builds, where usage of
 // |OPENSSL_EXPORT| is a bit stricter.
 #if !defined(OPENSSL_WINDOWS) || !defined(BORINGSSL_SHARED_LIBRARY)
@@ -468,7 +1131,7 @@
 DECLARE_ASN1_FUNCTIONS(ASN1_LINKED_LIST)
 
 ASN1_SEQUENCE(ASN1_LINKED_LIST) = {
-  ASN1_OPT(ASN1_LINKED_LIST, next, ASN1_LINKED_LIST),
+    ASN1_OPT(ASN1_LINKED_LIST, next, ASN1_LINKED_LIST),
 } ASN1_SEQUENCE_END(ASN1_LINKED_LIST)
 
 IMPLEMENT_ASN1_FUNCTIONS(ASN1_LINKED_LIST)
@@ -519,15 +1182,13 @@
   ASN1_STRING *string;
 };
 
-// clang-format off
 DECLARE_ASN1_FUNCTIONS(IMPLICIT_CHOICE)
 
 ASN1_SEQUENCE(IMPLICIT_CHOICE) = {
-  ASN1_IMP(IMPLICIT_CHOICE, string, DIRECTORYSTRING, 0)
+    ASN1_IMP(IMPLICIT_CHOICE, string, DIRECTORYSTRING, 0),
 } ASN1_SEQUENCE_END(IMPLICIT_CHOICE)
 
 IMPLEMENT_ASN1_FUNCTIONS(IMPLICIT_CHOICE)
-// clang-format on
 
 // Test that the ASN.1 templates reject types with implicitly-tagged CHOICE
 // types.
@@ -552,4 +1213,50 @@
   EXPECT_EQ(nullptr, d2i_IMPLICIT_CHOICE(nullptr, &ptr, sizeof(kInput2)));
 }
 
+struct REQUIRED_FIELD {
+  ASN1_INTEGER *value;
+  ASN1_INTEGER *value_imp;
+  ASN1_INTEGER *value_exp;
+  STACK_OF(ASN1_INTEGER) *seq;
+  STACK_OF(ASN1_INTEGER) *seq_imp;
+  STACK_OF(ASN1_INTEGER) *seq_exp;
+};
+
+DECLARE_ASN1_FUNCTIONS(REQUIRED_FIELD)
+ASN1_SEQUENCE(REQUIRED_FIELD) = {
+    ASN1_SIMPLE(REQUIRED_FIELD, value, ASN1_INTEGER),
+    ASN1_IMP(REQUIRED_FIELD, value_imp, ASN1_INTEGER, 0),
+    ASN1_EXP(REQUIRED_FIELD, value_exp, ASN1_INTEGER, 1),
+    ASN1_SEQUENCE_OF(REQUIRED_FIELD, seq, ASN1_INTEGER),
+    ASN1_IMP_SEQUENCE_OF(REQUIRED_FIELD, seq_imp, ASN1_INTEGER, 2),
+    ASN1_EXP_SEQUENCE_OF(REQUIRED_FIELD, seq_exp, ASN1_INTEGER, 3),
+} ASN1_SEQUENCE_END(REQUIRED_FIELD)
+IMPLEMENT_ASN1_FUNCTIONS(REQUIRED_FIELD)
+
+// Test that structures with missing required fields cannot be serialized. Test
+// the full combination of tagging and SEQUENCE OF.
+TEST(ASN1Test, MissingRequiredField) {
+  EXPECT_EQ(-1, i2d_REQUIRED_FIELD(nullptr, nullptr));
+
+  std::unique_ptr<REQUIRED_FIELD, decltype(&REQUIRED_FIELD_free)> obj(
+      nullptr, REQUIRED_FIELD_free);
+  for (auto field : {&REQUIRED_FIELD::value, &REQUIRED_FIELD::value_imp,
+                     &REQUIRED_FIELD::value_exp}) {
+    obj.reset(REQUIRED_FIELD_new());
+    ASSERT_TRUE(obj);
+    ASN1_INTEGER_free((*obj).*field);
+    (*obj).*field = nullptr;
+    EXPECT_EQ(-1, i2d_REQUIRED_FIELD(obj.get(), nullptr));
+  }
+
+  for (auto field : {&REQUIRED_FIELD::seq, &REQUIRED_FIELD::seq_imp,
+                     &REQUIRED_FIELD::seq_exp}) {
+    obj.reset(REQUIRED_FIELD_new());
+    ASSERT_TRUE(obj);
+    sk_ASN1_INTEGER_pop_free((*obj).*field, ASN1_INTEGER_free);
+    (*obj).*field = nullptr;
+    EXPECT_EQ(-1, i2d_REQUIRED_FIELD(obj.get(), nullptr));
+  }
+}
+
 #endif  // !WINDOWS || !SHARED_LIBRARY
diff --git a/src/crypto/x509/charmap.h b/src/crypto/asn1/charmap.h
similarity index 100%
rename from src/crypto/x509/charmap.h
rename to src/crypto/asn1/charmap.h
diff --git a/src/crypto/asn1/charmap.pl b/src/crypto/asn1/charmap.pl
index 71bc7b8..117ed32 100644
--- a/src/crypto/asn1/charmap.pl
+++ b/src/crypto/asn1/charmap.pl
@@ -62,17 +62,17 @@
 # Set up an array with the type of ASCII characters
 # Each set bit represents a character property.
 
-# RFC2253 character properties
+# RFC 2253 character properties
 my $RFC2253_ESC = 1;	# Character escaped with \
 my $ESC_CTRL	= 2;	# Escaped control character
-# These are used with RFC1779 quoting using "
+# These are used with RFC 1779 quoting using "
 my $NOESC_QUOTE	= 8;	# Not escaped if quoted
 my $PSTRING_CHAR = 0x10;	# Valid PrintableString character
 my $RFC2253_FIRST_ESC = 0x20; # Escaped with \ if first character
 my $RFC2253_LAST_ESC = 0x40;  # Escaped with \ if last character
 
 for($i = 0; $i < 128; $i++) {
-	# Set the RFC2253 escape characters (control)
+	# Set the RFC 2253 escape characters (control)
 	$arr[$i] = 0;
 	if(($i < 32) || ($i > 126)) {
 		$arr[$i] |= $ESC_CTRL;
@@ -88,7 +88,7 @@
 
 # Now setup the rest
 
-# Remaining RFC2253 escaped characters
+# Remaining RFC 2253 escaped characters
 
 $arr[ord(" ")] |= $NOESC_QUOTE | $RFC2253_FIRST_ESC | $RFC2253_LAST_ESC;
 $arr[ord("#")] |= $NOESC_QUOTE | $RFC2253_FIRST_ESC;
diff --git a/src/crypto/asn1/internal.h b/src/crypto/asn1/internal.h
index 4e42c70..a4bd34e 100644
--- a/src/crypto/asn1/internal.h
+++ b/src/crypto/asn1/internal.h
@@ -123,15 +123,31 @@
                      const ASN1_ITEM *it, int tag, int aclass, char opt,
                      ASN1_TLC *ctx);
 
+/* ASN1_item_ex_i2d encodes |*pval| as a value of type |it| to |out| under the
+ * i2d output convention. It returns a non-zero length on success and -1 on
+ * error. If |tag| is -1. the tag and class come from |it|. Otherwise, the tag
+ * number is |tag| and the class is |aclass|. This is used for implicit tagging.
+ * This function treats a missing value as an error, not an optional field. */
 int ASN1_item_ex_i2d(ASN1_VALUE **pval, unsigned char **out,
                      const ASN1_ITEM *it, int tag, int aclass);
+
 void ASN1_primitive_free(ASN1_VALUE **pval, const ASN1_ITEM *it);
 
+/* asn1_get_choice_selector returns the CHOICE selector value for |*pval|, which
+ * must of type |it|. */
 int asn1_get_choice_selector(ASN1_VALUE **pval, const ASN1_ITEM *it);
+
 int asn1_set_choice_selector(ASN1_VALUE **pval, int value, const ASN1_ITEM *it);
 
+/* asn1_get_field_ptr returns a pointer to the field in |*pval| corresponding to
+ * |tt|. */
 ASN1_VALUE **asn1_get_field_ptr(ASN1_VALUE **pval, const ASN1_TEMPLATE *tt);
 
+/* asn1_do_adb returns the |ASN1_TEMPLATE| for the ANY DEFINED BY field |tt|,
+ * based on the selector INTEGER or OID in |*pval|. If |tt| is not an ADB field,
+ * it returns |tt|. If the selector does not match any value, it returns NULL.
+ * If |nullerr| is non-zero, it will additionally push an error to the error
+ * queue when there is no match. */
 const ASN1_TEMPLATE *asn1_do_adb(ASN1_VALUE **pval, const ASN1_TEMPLATE *tt,
                                  int nullerr);
 
@@ -140,8 +156,13 @@
 
 void asn1_enc_init(ASN1_VALUE **pval, const ASN1_ITEM *it);
 void asn1_enc_free(ASN1_VALUE **pval, const ASN1_ITEM *it);
+
+/* asn1_enc_restore, if |*pval| has a saved encoding, writes it to |out| under
+ * the i2d output convention, sets |*len| to the length, and returns one. If it
+ * has no saved encoding, it returns zero. */
 int asn1_enc_restore(int *len, unsigned char **out, ASN1_VALUE **pval,
                      const ASN1_ITEM *it);
+
 int asn1_enc_save(ASN1_VALUE **pval, const unsigned char *in, int inlen,
                   const ASN1_ITEM *it);
 
@@ -150,6 +171,10 @@
  * a pointer. */
 const void *asn1_type_value_as_pointer(const ASN1_TYPE *a);
 
+/* asn1_is_printable returns one if |value| is a valid Unicode codepoint for an
+ * ASN.1 PrintableString, and zero otherwise. */
+int asn1_is_printable(uint32_t value);
+
 
 #if defined(__cplusplus)
 }  /* extern C */
diff --git a/src/crypto/asn1/tasn_dec.c b/src/crypto/asn1/tasn_dec.c
index 0d123cc..c5a6477 100644
--- a/src/crypto/asn1/tasn_dec.c
+++ b/src/crypto/asn1/tasn_dec.c
@@ -170,8 +170,6 @@
 {
     const ASN1_TEMPLATE *tt, *errtt = NULL;
     const ASN1_EXTERN_FUNCS *ef;
-    const ASN1_AUX *aux = it->funcs;
-    ASN1_aux_cb *asn1_cb;
     const unsigned char *p = NULL, *q;
     unsigned char oclass;
     char seq_eoc, seq_nolen, cst, isopt;
@@ -183,10 +181,6 @@
     aclass &= ~ASN1_TFLG_COMBINE;
     if (!pval)
         return 0;
-    if (aux && aux->asn1_cb)
-        asn1_cb = aux->asn1_cb;
-    else
-        asn1_cb = 0;
 
     /*
      * Bound |len| to comfortably fit in an int. Lengths in this module often
@@ -264,7 +258,7 @@
         ef = it->funcs;
         return ef->asn1_ex_d2i(pval, in, len, it, tag, aclass, opt, ctx);
 
-    case ASN1_ITYPE_CHOICE:
+    case ASN1_ITYPE_CHOICE: {
         /*
          * It never makes sense for CHOICE types to have implicit tagging, so if
          * tag != -1, then this looks like an error in the template.
@@ -274,6 +268,8 @@
             goto err;
         }
 
+        const ASN1_AUX *aux = it->funcs;
+        ASN1_aux_cb *asn1_cb = aux != NULL ? aux->asn1_cb : NULL;
         if (asn1_cb && !asn1_cb(ASN1_OP_D2I_PRE, pval, it, NULL))
             goto auxerr;
 
@@ -327,8 +323,9 @@
             goto auxerr;
         *in = p;
         return 1;
+    }
 
-    case ASN1_ITYPE_SEQUENCE:
+    case ASN1_ITYPE_SEQUENCE: {
         p = *in;
 
         /* If no IMPLICIT tagging set to SEQUENCE, UNIVERSAL */
@@ -356,6 +353,8 @@
             goto err;
         }
 
+        const ASN1_AUX *aux = it->funcs;
+        ASN1_aux_cb *asn1_cb = aux != NULL ? aux->asn1_cb : NULL;
         if (asn1_cb && !asn1_cb(ASN1_OP_D2I_PRE, pval, it, NULL))
             goto auxerr;
 
@@ -462,6 +461,7 @@
             goto auxerr;
         *in = p;
         return 1;
+    }
 
     default:
         return 0;
diff --git a/src/crypto/asn1/tasn_enc.c b/src/crypto/asn1/tasn_enc.c
index 3229a71..9917d2a 100644
--- a/src/crypto/asn1/tasn_enc.c
+++ b/src/crypto/asn1/tasn_enc.c
@@ -66,17 +66,18 @@
 #include "internal.h"
 
 
+static int asn1_item_ex_i2d_opt(ASN1_VALUE **pval, unsigned char **out,
+                                const ASN1_ITEM *it, int tag, int aclass,
+                                int optional);
 static int asn1_i2d_ex_primitive(ASN1_VALUE **pval, unsigned char **out,
-                                 const ASN1_ITEM *it, int tag, int aclass);
-static int asn1_ex_i2c(ASN1_VALUE **pval, unsigned char *cont, int *putype,
-                       const ASN1_ITEM *it);
+                                 const ASN1_ITEM *it, int tag, int aclass,
+                                 int optional);
+static int asn1_ex_i2c(ASN1_VALUE **pval, unsigned char *cont, int *out_omit,
+                       int *putype, const ASN1_ITEM *it);
 static int asn1_set_seq_out(STACK_OF(ASN1_VALUE) *sk, unsigned char **out,
-                            int skcontlen, const ASN1_ITEM *item,
-                            int do_sort, int iclass);
+                            int skcontlen, const ASN1_ITEM *item, int do_sort);
 static int asn1_template_ex_i2d(ASN1_VALUE **pval, unsigned char **out,
                                 const ASN1_TEMPLATE *tt, int tag, int aclass);
-static int asn1_item_flags_i2d(ASN1_VALUE *val, unsigned char **out,
-                               const ASN1_ITEM *it, int flags);
 
 /*
  * Top level i2d equivalents
@@ -84,35 +85,28 @@
 
 int ASN1_item_i2d(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it)
 {
-    return asn1_item_flags_i2d(val, out, it, 0);
-}
-
-/*
- * Encode an ASN1 item, this is use by the standard 'i2d' function. 'out'
- * points to a buffer to output the data to. The new i2d has one additional
- * feature. If the output buffer is NULL (i.e. *out == NULL) then a buffer is
- * allocated and populated with the encoding.
- */
-
-static int asn1_item_flags_i2d(ASN1_VALUE *val, unsigned char **out,
-                               const ASN1_ITEM *it, int flags)
-{
     if (out && !*out) {
         unsigned char *p, *buf;
-        int len;
-        len = ASN1_item_ex_i2d(&val, NULL, it, -1, flags);
-        if (len <= 0)
+        int len = ASN1_item_ex_i2d(&val, NULL, it, /*tag=*/-1, /*aclass=*/0);
+        if (len <= 0) {
             return len;
+        }
         buf = OPENSSL_malloc(len);
-        if (!buf)
+        if (!buf) {
+            OPENSSL_PUT_ERROR(ASN1, ERR_R_MALLOC_FAILURE);
             return -1;
+        }
         p = buf;
-        ASN1_item_ex_i2d(&val, &p, it, -1, flags);
+        int len2 = ASN1_item_ex_i2d(&val, &p, it, /*tag=*/-1, /*aclass=*/0);
+        if (len2 <= 0) {
+            return len2;
+        }
+        assert(len == len2);
         *out = buf;
         return len;
     }
 
-    return ASN1_item_ex_i2d(&val, out, it, -1, flags);
+    return ASN1_item_ex_i2d(&val, out, it, /*tag=*/-1, /*aclass=*/0);
 }
 
 /*
@@ -123,26 +117,47 @@
 int ASN1_item_ex_i2d(ASN1_VALUE **pval, unsigned char **out,
                      const ASN1_ITEM *it, int tag, int aclass)
 {
+    int ret = asn1_item_ex_i2d_opt(pval, out, it, tag, aclass, /*optional=*/0);
+    assert(ret != 0);
+    return ret;
+}
+
+/* asn1_item_ex_i2d_opt behaves like |ASN1_item_ex_i2d| but, if |optional| is
+ * non-zero and |*pval| is omitted, it returns zero and writes no bytes. */
+int asn1_item_ex_i2d_opt(ASN1_VALUE **pval, unsigned char **out,
+                         const ASN1_ITEM *it, int tag, int aclass,
+                         int optional)
+{
     const ASN1_TEMPLATE *tt = NULL;
     int i, seqcontlen, seqlen;
-    const ASN1_EXTERN_FUNCS *ef;
-    const ASN1_AUX *aux = it->funcs;
-    ASN1_aux_cb *asn1_cb = 0;
 
-    if ((it->itype != ASN1_ITYPE_PRIMITIVE) && !*pval)
-        return 0;
+    /* Historically, |aclass| was repurposed to pass additional flags into the
+     * encoding process. */
+    assert((aclass & ASN1_TFLG_TAG_CLASS) == aclass);
+    /* If not overridding the tag, |aclass| is ignored and should be zero. */
+    assert(tag != -1 || aclass == 0);
 
-    if (aux && aux->asn1_cb)
-        asn1_cb = aux->asn1_cb;
+    /* All fields are pointers, except for boolean |ASN1_ITYPE_PRIMITIVE|s.
+     * Optional primitives are handled later. */
+    if ((it->itype != ASN1_ITYPE_PRIMITIVE) && !*pval) {
+        if (optional) {
+            return 0;
+        }
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_MISSING_VALUE);
+        return -1;
+    }
 
     switch (it->itype) {
 
     case ASN1_ITYPE_PRIMITIVE:
-        if (it->templates)
-            return asn1_template_ex_i2d(pval, out, it->templates,
-                                        tag, aclass);
-        return asn1_i2d_ex_primitive(pval, out, it, tag, aclass);
-        break;
+        if (it->templates) {
+            if (it->templates->flags & ASN1_TFLG_OPTIONAL) {
+                OPENSSL_PUT_ERROR(ASN1, ASN1_R_BAD_TEMPLATE);
+                return -1;
+            }
+            return asn1_template_ex_i2d(pval, out, it->templates, tag, aclass);
+        }
+        return asn1_i2d_ex_primitive(pval, out, it, tag, aclass, optional);
 
     case ASN1_ITYPE_MSTRING:
         /*
@@ -153,9 +168,9 @@
             OPENSSL_PUT_ERROR(ASN1, ASN1_R_BAD_TEMPLATE);
             return -1;
         }
-        return asn1_i2d_ex_primitive(pval, out, it, -1, aclass);
+        return asn1_i2d_ex_primitive(pval, out, it, -1, 0, optional);
 
-    case ASN1_ITYPE_CHOICE:
+    case ASN1_ITYPE_CHOICE: {
         /*
          * It never makes sense for CHOICE types to have implicit tagging, so if
          * tag != -1, then this looks like an error in the template.
@@ -164,31 +179,39 @@
             OPENSSL_PUT_ERROR(ASN1, ASN1_R_BAD_TEMPLATE);
             return -1;
         }
-        if (asn1_cb && !asn1_cb(ASN1_OP_I2D_PRE, pval, it, NULL))
-            return 0;
         i = asn1_get_choice_selector(pval, it);
-        if ((i >= 0) && (i < it->tcount)) {
-            ASN1_VALUE **pchval;
-            const ASN1_TEMPLATE *chtt;
-            chtt = it->templates + i;
-            pchval = asn1_get_field_ptr(pval, chtt);
-            return asn1_template_ex_i2d(pchval, out, chtt, -1, aclass);
+        if (i < 0 || i >= it->tcount) {
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_NO_MATCHING_CHOICE_TYPE);
+            return -1;
         }
-        /* Fixme: error condition if selector out of range */
-        if (asn1_cb && !asn1_cb(ASN1_OP_I2D_POST, pval, it, NULL))
-            return 0;
-        break;
+        const ASN1_TEMPLATE *chtt = it->templates + i;
+        if (chtt->flags & ASN1_TFLG_OPTIONAL) {
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_BAD_TEMPLATE);
+            return -1;
+        }
+        ASN1_VALUE **pchval = asn1_get_field_ptr(pval, chtt);
+        return asn1_template_ex_i2d(pchval, out, chtt, -1, 0);
+    }
 
-    case ASN1_ITYPE_EXTERN:
+    case ASN1_ITYPE_EXTERN: {
         /* If new style i2d it does all the work */
-        ef = it->funcs;
-        return ef->asn1_ex_i2d(pval, out, it, tag, aclass);
+        const ASN1_EXTERN_FUNCS *ef = it->funcs;
+        int ret = ef->asn1_ex_i2d(pval, out, it, tag, aclass);
+        if (ret == 0) {
+            /* |asn1_ex_i2d| should never return zero. We have already checked
+             * for optional values generically, and |ASN1_ITYPE_EXTERN| fields
+             * must be pointers. */
+            OPENSSL_PUT_ERROR(ASN1, ERR_R_INTERNAL_ERROR);
+            return -1;
+        }
+        return ret;
+    }
 
-    case ASN1_ITYPE_SEQUENCE:
+    case ASN1_ITYPE_SEQUENCE: {
         i = asn1_enc_restore(&seqcontlen, out, pval, it);
         /* An error occurred */
         if (i < 0)
-            return 0;
+            return -1;
         /* We have a valid cached encoding... */
         if (i > 0)
             return seqcontlen;
@@ -197,12 +220,8 @@
         /* If no IMPLICIT tagging set to SEQUENCE, UNIVERSAL */
         if (tag == -1) {
             tag = V_ASN1_SEQUENCE;
-            /* Retain any other flags in aclass */
-            aclass = (aclass & ~ASN1_TFLG_TAG_CLASS)
-                | V_ASN1_UNIVERSAL;
+            aclass = V_ASN1_UNIVERSAL;
         }
-        if (asn1_cb && !asn1_cb(ASN1_OP_I2D_PRE, pval, it, NULL))
-            return 0;
         /* First work out sequence content length */
         for (i = 0, tt = it->templates; i < it->tcount; tt++, i++) {
             const ASN1_TEMPLATE *seqtt;
@@ -210,9 +229,9 @@
             int tmplen;
             seqtt = asn1_do_adb(pval, tt, 1);
             if (!seqtt)
-                return 0;
+                return -1;
             pseqval = asn1_get_field_ptr(pval, seqtt);
-            tmplen = asn1_template_ex_i2d(pseqval, NULL, seqtt, -1, aclass);
+            tmplen = asn1_template_ex_i2d(pseqval, NULL, seqtt, -1, 0);
             if (tmplen == -1 || (tmplen > INT_MAX - seqcontlen))
                 return -1;
             seqcontlen += tmplen;
@@ -228,40 +247,49 @@
             ASN1_VALUE **pseqval;
             seqtt = asn1_do_adb(pval, tt, 1);
             if (!seqtt)
-                return 0;
+                return -1;
             pseqval = asn1_get_field_ptr(pval, seqtt);
-            /* FIXME: check for errors in enhanced version */
-            asn1_template_ex_i2d(pseqval, out, seqtt, -1, aclass);
+            if (asn1_template_ex_i2d(pseqval, out, seqtt, -1, 0) < 0) {
+                return -1;
+            }
         }
-        if (asn1_cb && !asn1_cb(ASN1_OP_I2D_POST, pval, it, NULL))
-            return 0;
         return seqlen;
+    }
 
     default:
-        return 0;
-
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_BAD_TEMPLATE);
+        return -1;
     }
-    return 0;
 }
 
+/* asn1_template_ex_i2d behaves like |asn1_item_ex_i2d_opt| but uses an
+ * |ASN1_TEMPLATE| instead of an |ASN1_ITEM|. An |ASN1_TEMPLATE| wraps an
+ * |ASN1_ITEM| with modifiers such as tagging, SEQUENCE or SET, etc. Instead of
+ * taking an |optional| parameter, it uses the |ASN1_TFLG_OPTIONAL| flag. */
 static int asn1_template_ex_i2d(ASN1_VALUE **pval, unsigned char **out,
                                 const ASN1_TEMPLATE *tt, int tag, int iclass)
 {
     int i, ret, flags, ttag, tclass;
     size_t j;
     flags = tt->flags;
+
+    /* Historically, |iclass| was repurposed to pass additional flags into the
+     * encoding process. */
+    assert((iclass & ASN1_TFLG_TAG_CLASS) == iclass);
+    /* If not overridding the tag, |iclass| is ignored and should be zero. */
+    assert(tag != -1 || iclass == 0);
+
     /*
      * Work out tag and class to use: tagging may come either from the
      * template or the arguments, not both because this would create
-     * ambiguity. Additionally the iclass argument may contain some
-     * additional flags which should be noted and passed down to other
-     * levels.
+     * ambiguity.
      */
     if (flags & ASN1_TFLG_TAG_MASK) {
         /* Error if argument and template tagging */
-        if (tag != -1)
-            /* FIXME: error code here */
+        if (tag != -1) {
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_BAD_TEMPLATE);
             return -1;
+        }
         /* Get tagging from template */
         ttag = tt->tag;
         tclass = flags & ASN1_TFLG_TAG_CLASS;
@@ -273,14 +301,12 @@
         ttag = -1;
         tclass = 0;
     }
-    /*
-     * Remove any class mask from iflag.
-     */
-    iclass &= ~ASN1_TFLG_TAG_CLASS;
+
+    const int optional = (flags & ASN1_TFLG_OPTIONAL) != 0;
 
     /*
-     * At this point 'ttag' contains the outer tag to use, 'tclass' is the
-     * class and iclass is any flags passed to this function.
+     * At this point 'ttag' contains the outer tag to use, and 'tclass' is the
+     * class.
      */
 
     if (flags & ASN1_TFLG_SK_MASK) {
@@ -290,16 +316,22 @@
         int skcontlen, sklen;
         ASN1_VALUE *skitem;
 
-        if (!*pval)
-            return 0;
+        if (!*pval) {
+            if (optional) {
+                return 0;
+            }
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_MISSING_VALUE);
+            return -1;
+        }
 
         if (flags & ASN1_TFLG_SET_OF) {
             isset = 1;
-            /* 2 means we reorder */
-            if (flags & ASN1_TFLG_SEQUENCE_OF)
-                isset = 2;
-        } else
+            /* Historically, types with both bits set were mutated when
+             * serialized to apply the sort. We no longer support this. */
+            assert((flags & ASN1_TFLG_SEQUENCE_OF) == 0);
+        } else {
             isset = 0;
+        }
 
         /*
          * Work out inner tag value: if EXPLICIT or no tagging use underlying
@@ -322,7 +354,7 @@
             int tmplen;
             skitem = sk_ASN1_VALUE_value(sk, j);
             tmplen = ASN1_item_ex_i2d(&skitem, NULL, ASN1_ITEM_ptr(tt->item),
-                                      -1, iclass);
+                                      -1, 0);
             if (tmplen == -1 || (skcontlen > INT_MAX - tmplen))
                 return -1;
             skcontlen += tmplen;
@@ -346,30 +378,36 @@
         /* SET or SEQUENCE and IMPLICIT tag */
         ASN1_put_object(out, /*constructed=*/1, skcontlen, sktag, skaclass);
         /* And the stuff itself */
-        asn1_set_seq_out(sk, out, skcontlen, ASN1_ITEM_ptr(tt->item),
-                         isset, iclass);
+        if (!asn1_set_seq_out(sk, out, skcontlen, ASN1_ITEM_ptr(tt->item),
+                              isset)) {
+            return -1;
+        }
         return ret;
     }
 
     if (flags & ASN1_TFLG_EXPTAG) {
         /* EXPLICIT tagging */
         /* Find length of tagged item */
-        i = ASN1_item_ex_i2d(pval, NULL, ASN1_ITEM_ptr(tt->item), -1, iclass);
-        if (!i)
-            return 0;
+        i = asn1_item_ex_i2d_opt(pval, NULL, ASN1_ITEM_ptr(tt->item), -1, 0,
+                                 optional);
+        if (i <= 0)
+            return i;
         /* Find length of EXPLICIT tag */
         ret = ASN1_object_size(/*constructed=*/1, i, ttag);
         if (out && ret != -1) {
             /* Output tag and item */
             ASN1_put_object(out, /*constructed=*/1, i, ttag, tclass);
-            ASN1_item_ex_i2d(pval, out, ASN1_ITEM_ptr(tt->item), -1, iclass);
+            if (ASN1_item_ex_i2d(pval, out, ASN1_ITEM_ptr(tt->item), -1,
+                                 0) < 0) {
+                return -1;
+            }
         }
         return ret;
     }
 
-    /* Either normal or IMPLICIT tagging: combine class and flags */
-    return ASN1_item_ex_i2d(pval, out, ASN1_ITEM_ptr(tt->item),
-                            ttag, tclass | iclass);
+    /* Either normal or IMPLICIT tagging */
+    return asn1_item_ex_i2d_opt(pval, out, ASN1_ITEM_ptr(tt->item),
+                                ttag, tclass, optional);
 
 }
 
@@ -378,7 +416,6 @@
 typedef struct {
     unsigned char *data;
     int length;
-    ASN1_VALUE *field;
 } DER_ENC;
 
 static int der_cmp(const void *a, const void *b)
@@ -392,99 +429,96 @@
     return d1->length - d2->length;
 }
 
-/* Output the content octets of SET OF or SEQUENCE OF */
-
+/* asn1_set_seq_out writes |sk| to |out| under the i2d output convention,
+ * excluding the tag and length. It returns one on success and zero on error.
+ * |skcontlen| must be the total encoded size. If |do_sort| is non-zero, the
+ * elements are sorted for a SET OF type. Each element of |sk| has type
+ * |item|. */
 static int asn1_set_seq_out(STACK_OF(ASN1_VALUE) *sk, unsigned char **out,
-                            int skcontlen, const ASN1_ITEM *item,
-                            int do_sort, int iclass)
+                            int skcontlen, const ASN1_ITEM *item, int do_sort)
 {
-    size_t i;
-    ASN1_VALUE *skitem;
-    unsigned char *tmpdat = NULL, *p = NULL;
-    DER_ENC *derlst = NULL, *tder;
-    if (do_sort) {
-        /* Don't need to sort less than 2 items */
-        if (sk_ASN1_VALUE_num(sk) < 2)
-            do_sort = 0;
-        else {
-            derlst = OPENSSL_malloc(sk_ASN1_VALUE_num(sk)
-                                    * sizeof(*derlst));
-            if (!derlst)
-                return 0;
-            tmpdat = OPENSSL_malloc(skcontlen);
-            if (!tmpdat) {
-                OPENSSL_free(derlst);
+    /* No need to sort if there are fewer than two items. */
+    if (!do_sort || sk_ASN1_VALUE_num(sk) < 2) {
+        for (size_t i = 0; i < sk_ASN1_VALUE_num(sk); i++) {
+            ASN1_VALUE *skitem = sk_ASN1_VALUE_value(sk, i);
+            if (ASN1_item_ex_i2d(&skitem, out, item, -1, 0) < 0) {
                 return 0;
             }
         }
-    }
-    /* If not sorting just output each item */
-    if (!do_sort) {
-        for (i = 0; i < sk_ASN1_VALUE_num(sk); i++) {
-            skitem = sk_ASN1_VALUE_value(sk, i);
-            ASN1_item_ex_i2d(&skitem, out, item, -1, iclass);
-        }
         return 1;
     }
-    p = tmpdat;
 
-    /* Doing sort: build up a list of each member's DER encoding */
-    for (i = 0, tder = derlst; i < sk_ASN1_VALUE_num(sk); i++, tder++) {
-        skitem = sk_ASN1_VALUE_value(sk, i);
-        tder->data = p;
-        tder->length = ASN1_item_ex_i2d(&skitem, &p, item, -1, iclass);
-        tder->field = skitem;
+    if (sk_ASN1_VALUE_num(sk) > ((size_t)-1) / sizeof(DER_ENC)) {
+        OPENSSL_PUT_ERROR(ASN1, ERR_R_OVERFLOW);
+        return 0;
     }
 
-    /* Now sort them */
-    qsort(derlst, sk_ASN1_VALUE_num(sk), sizeof(*derlst), der_cmp);
-    /* Output sorted DER encoding */
+    int ret = 0;
+    unsigned char *const buf = OPENSSL_malloc(skcontlen);
+    DER_ENC *encoded = OPENSSL_malloc(sk_ASN1_VALUE_num(sk) * sizeof(*encoded));
+    if (encoded == NULL || buf == NULL) {
+        OPENSSL_PUT_ERROR(ASN1, ERR_R_MALLOC_FAILURE);
+        goto err;
+    }
+
+    /* Encode all the elements into |buf| and populate |encoded|. */
+    unsigned char *p = buf;
+    for (size_t i = 0; i < sk_ASN1_VALUE_num(sk); i++) {
+        ASN1_VALUE *skitem = sk_ASN1_VALUE_value(sk, i);
+        encoded[i].data = p;
+        encoded[i].length = ASN1_item_ex_i2d(&skitem, &p, item, -1, 0);
+        if (encoded[i].length < 0) {
+            goto err;
+        }
+        assert(p - buf <= skcontlen);
+    }
+
+    qsort(encoded, sk_ASN1_VALUE_num(sk), sizeof(*encoded), der_cmp);
+
+    /* Output the elements in sorted order. */
     p = *out;
-    for (i = 0, tder = derlst; i < sk_ASN1_VALUE_num(sk); i++, tder++) {
-        OPENSSL_memcpy(p, tder->data, tder->length);
-        p += tder->length;
+    for (size_t i = 0; i < sk_ASN1_VALUE_num(sk); i++) {
+        OPENSSL_memcpy(p, encoded[i].data, encoded[i].length);
+        p += encoded[i].length;
     }
     *out = p;
-    /* If do_sort is 2 then reorder the STACK */
-    if (do_sort == 2) {
-        for (i = 0, tder = derlst; i < sk_ASN1_VALUE_num(sk); i++, tder++)
-            (void)sk_ASN1_VALUE_set(sk, i, tder->field);
-    }
-    OPENSSL_free(derlst);
-    OPENSSL_free(tmpdat);
-    return 1;
+
+    ret = 1;
+
+err:
+    OPENSSL_free(encoded);
+    OPENSSL_free(buf);
+    return ret;
 }
 
+/* asn1_i2d_ex_primitive behaves like |ASN1_item_ex_i2d| but |item| must be a
+ * a PRIMITIVE or MSTRING type that is not an |ASN1_ITEM_TEMPLATE|. */
 static int asn1_i2d_ex_primitive(ASN1_VALUE **pval, unsigned char **out,
-                                 const ASN1_ITEM *it, int tag, int aclass)
+                                 const ASN1_ITEM *it, int tag, int aclass,
+                                 int optional)
 {
-    int len;
-    int utype;
-    int usetag;
-
-    utype = it->utype;
-
-    /*
-     * Get length of content octets and maybe find out the underlying type.
-     */
-
-    len = asn1_ex_i2c(pval, NULL, &utype, it);
+    /* Get length of content octets and maybe find out the underlying type. */
+    int omit;
+    int utype = it->utype;
+    int len = asn1_ex_i2c(pval, NULL, &omit, &utype, it);
+    if (len < 0) {
+        return -1;
+    }
+    if (omit) {
+        if (optional) {
+            return 0;
+        }
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_MISSING_VALUE);
+        return -1;
+    }
 
     /*
      * If SEQUENCE, SET or OTHER then header is included in pseudo content
      * octets so don't include tag+length. We need to check here because the
      * call to asn1_ex_i2c() could change utype.
      */
-    if ((utype == V_ASN1_SEQUENCE) || (utype == V_ASN1_SET) ||
-        (utype == V_ASN1_OTHER))
-        usetag = 0;
-    else
-        usetag = 1;
-
-    /* -1 means omit type */
-
-    if (len == -1)
-        return 0;
+    int usetag = utype != V_ASN1_SEQUENCE && utype != V_ASN1_SET &&
+                 utype != V_ASN1_OTHER;
 
     /* If not implicitly tagged get tag from underlying type */
     if (tag == -1)
@@ -492,21 +526,42 @@
 
     /* Output tag+length followed by content octets */
     if (out) {
-        if (usetag)
+        if (usetag) {
             ASN1_put_object(out, /*constructed=*/0, len, tag, aclass);
-        asn1_ex_i2c(pval, *out, &utype, it);
+        }
+        int len2 = asn1_ex_i2c(pval, *out, &omit, &utype, it);
+        if (len2 < 0) {
+          return -1;
+        }
+        assert(len == len2);
+        assert(!omit);
         *out += len;
     }
 
-    if (usetag)
+    if (usetag) {
         return ASN1_object_size(/*constructed=*/0, len, tag);
+    }
     return len;
 }
 
-/* Produce content octets from a structure */
-
-static int asn1_ex_i2c(ASN1_VALUE **pval, unsigned char *cout, int *putype,
-                       const ASN1_ITEM *it)
+/* asn1_ex_i2c writes the |*pval| to |cout| under the i2d output convention,
+ * excluding the tag and length. It returns the number of bytes written,
+ * possibly zero, on success or -1 on error. If |*pval| should be omitted, it
+ * returns zero and sets |*out_omit| to true.
+ *
+ * If |it| is an MSTRING or ANY type, it gets the underlying type from |*pval|,
+ * which must be an |ASN1_STRING| or |ASN1_TYPE|, respectively. It then updates
+ * |*putype| with the tag number of type used, or |V_ASN1_OTHER| if it was not a
+ * universal type. If |*putype| is set to |V_ASN1_SEQUENCE|, |V_ASN1_SET|, or
+ * |V_ASN1_OTHER|, it additionally outputs the tag and length, so the caller
+ * must not do so.
+ *
+ * Otherwise, |*putype| must contain |it->utype|.
+ *
+ * WARNING: Unlike most functions in this file, |asn1_ex_i2c| can return zero
+ * without omitting the element. ASN.1 values may have empty contents. */
+static int asn1_ex_i2c(ASN1_VALUE **pval, unsigned char *cout, int *out_omit,
+                       int *putype, const ASN1_ITEM *it)
 {
     ASN1_BOOLEAN *tbool = NULL;
     ASN1_STRING *strtmp;
@@ -520,23 +575,51 @@
      * |ASN1_PRIMITIVE_FUNCS| table of callbacks. */
     assert(it->funcs == NULL);
 
+    *out_omit = 0;
+
     /* Should type be omitted? */
     if ((it->itype != ASN1_ITYPE_PRIMITIVE)
         || (it->utype != V_ASN1_BOOLEAN)) {
-        if (!*pval)
-            return -1;
+        if (!*pval) {
+            *out_omit = 1;
+            return 0;
+        }
     }
 
     if (it->itype == ASN1_ITYPE_MSTRING) {
         /* If MSTRING type set the underlying type */
         strtmp = (ASN1_STRING *)*pval;
         utype = strtmp->type;
+        if (utype < 0 && utype != V_ASN1_OTHER) {
+            /* MSTRINGs can have type -1 when default-constructed. */
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_WRONG_TYPE);
+            return -1;
+        }
+        /* Negative INTEGER and ENUMERATED values use |ASN1_STRING| type values
+         * that do not match their corresponding utype values. INTEGERs cannot
+         * participate in MSTRING types, but ENUMERATEDs can.
+         *
+         * TODO(davidben): Is this a bug? Although arguably one of the MSTRING
+         * types should contain more values, rather than less. See
+         * https://crbug.com/boringssl/412. But it is not possible to fit all
+         * possible ANY values into an |ASN1_STRING|, so matching the spec here
+         * is somewhat hopeless. */
+        if (utype == V_ASN1_NEG_INTEGER) {
+            utype = V_ASN1_INTEGER;
+        } else if (utype == V_ASN1_NEG_ENUMERATED) {
+            utype = V_ASN1_ENUMERATED;
+        }
         *putype = utype;
     } else if (it->utype == V_ASN1_ANY) {
         /* If ANY set type and pointer to value */
         ASN1_TYPE *typ;
         typ = (ASN1_TYPE *)*pval;
         utype = typ->type;
+        if (utype < 0 && utype != V_ASN1_OTHER) {
+            /* |ASN1_TYPE|s can have type -1 when default-constructed. */
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_WRONG_TYPE);
+            return -1;
+        }
         *putype = utype;
         pval = &typ->value.asn1_value;
     } else
@@ -547,8 +630,11 @@
         otmp = (ASN1_OBJECT *)*pval;
         cont = otmp->data;
         len = otmp->length;
-        if (cont == NULL || len == 0)
+        if (len == 0) {
+            /* Some |ASN1_OBJECT|s do not have OIDs and cannot be serialized. */
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_OBJECT);
             return -1;
+        }
         break;
 
     case V_ASN1_NULL:
@@ -558,34 +644,39 @@
 
     case V_ASN1_BOOLEAN:
         tbool = (ASN1_BOOLEAN *)pval;
-        if (*tbool == -1)
-            return -1;
+        if (*tbool == -1) {
+            *out_omit = 1;
+            return 0;
+        }
         if (it->utype != V_ASN1_ANY) {
             /*
              * Default handling if value == size field then omit
              */
-            if (*tbool && (it->size > 0))
-                return -1;
-            if (!*tbool && !it->size)
-                return -1;
+            if ((*tbool && (it->size > 0)) ||
+                (!*tbool && !it->size)) {
+                *out_omit = 1;
+                return 0;
+            }
         }
         c = *tbool ? 0xff : 0x00;
         cont = &c;
         len = 1;
         break;
 
-    case V_ASN1_BIT_STRING:
-        return i2c_ASN1_BIT_STRING((ASN1_BIT_STRING *)*pval,
-                                   cout ? &cout : NULL);
-        break;
+    case V_ASN1_BIT_STRING: {
+        int ret = i2c_ASN1_BIT_STRING((ASN1_BIT_STRING *)*pval,
+                                      cout ? &cout : NULL);
+        /* |i2c_ASN1_BIT_STRING| returns zero on error instead of -1. */
+        return ret <= 0 ? -1 : ret;
+    }
 
     case V_ASN1_INTEGER:
-    case V_ASN1_ENUMERATED:
-        /*
-         * These are all have the same content format as ASN1_INTEGER
-         */
-        return i2c_ASN1_INTEGER((ASN1_INTEGER *)*pval, cout ? &cout : NULL);
-        break;
+    case V_ASN1_ENUMERATED: {
+        /* |i2c_ASN1_INTEGER| also handles ENUMERATED. */
+        int ret = i2c_ASN1_INTEGER((ASN1_INTEGER *)*pval, cout ? &cout : NULL);
+        /* |i2c_ASN1_INTEGER| returns zero on error instead of -1. */
+        return ret <= 0 ? -1 : ret;
+    }
 
     case V_ASN1_OCTET_STRING:
     case V_ASN1_NUMERICSTRING:
diff --git a/src/crypto/asn1/tasn_fre.c b/src/crypto/asn1/tasn_fre.c
index 2f5032d..b847901 100644
--- a/src/crypto/asn1/tasn_fre.c
+++ b/src/crypto/asn1/tasn_fre.c
@@ -79,17 +79,11 @@
 {
     const ASN1_TEMPLATE *tt = NULL, *seqtt;
     const ASN1_EXTERN_FUNCS *ef;
-    const ASN1_AUX *aux = it->funcs;
-    ASN1_aux_cb *asn1_cb;
     int i;
     if (!pval)
         return;
     if ((it->itype != ASN1_ITYPE_PRIMITIVE) && !*pval)
         return;
-    if (aux && aux->asn1_cb)
-        asn1_cb = aux->asn1_cb;
-    else
-        asn1_cb = 0;
 
     switch (it->itype) {
 
@@ -104,7 +98,9 @@
         ASN1_primitive_free(pval, it);
         break;
 
-    case ASN1_ITYPE_CHOICE:
+    case ASN1_ITYPE_CHOICE: {
+        const ASN1_AUX *aux = it->funcs;
+        ASN1_aux_cb *asn1_cb = aux != NULL ? aux->asn1_cb : NULL;
         if (asn1_cb) {
             i = asn1_cb(ASN1_OP_FREE_PRE, pval, it, NULL);
             if (i == 2)
@@ -124,6 +120,7 @@
             *pval = NULL;
         }
         break;
+    }
 
     case ASN1_ITYPE_EXTERN:
         ef = it->funcs;
@@ -131,9 +128,11 @@
             ef->asn1_ex_free(pval, it);
         break;
 
-    case ASN1_ITYPE_SEQUENCE:
+    case ASN1_ITYPE_SEQUENCE: {
         if (!asn1_refcount_dec_and_test_zero(pval, it))
             return;
+        const ASN1_AUX *aux = it->funcs;
+        ASN1_aux_cb *asn1_cb = aux != NULL ? aux->asn1_cb : NULL;
         if (asn1_cb) {
             i = asn1_cb(ASN1_OP_FREE_PRE, pval, it, NULL);
             if (i == 2)
@@ -162,6 +161,7 @@
         }
         break;
     }
+    }
 }
 
 void ASN1_template_free(ASN1_VALUE **pval, const ASN1_TEMPLATE *tt)
diff --git a/src/crypto/asn1/tasn_new.c b/src/crypto/asn1/tasn_new.c
index 540ed8f..eea219d 100644
--- a/src/crypto/asn1/tasn_new.c
+++ b/src/crypto/asn1/tasn_new.c
@@ -95,14 +95,8 @@
 {
     const ASN1_TEMPLATE *tt = NULL;
     const ASN1_EXTERN_FUNCS *ef;
-    const ASN1_AUX *aux = it->funcs;
-    ASN1_aux_cb *asn1_cb;
     ASN1_VALUE **pseqval;
     int i;
-    if (aux && aux->asn1_cb)
-        asn1_cb = aux->asn1_cb;
-    else
-        asn1_cb = 0;
 
     switch (it->itype) {
 
@@ -127,7 +121,9 @@
             goto memerr;
         break;
 
-    case ASN1_ITYPE_CHOICE:
+    case ASN1_ITYPE_CHOICE: {
+        const ASN1_AUX *aux = it->funcs;
+        ASN1_aux_cb *asn1_cb = aux != NULL ? aux->asn1_cb : NULL;
         if (asn1_cb) {
             i = asn1_cb(ASN1_OP_NEW_PRE, pval, it, NULL);
             if (!i)
@@ -146,8 +142,11 @@
         if (asn1_cb && !asn1_cb(ASN1_OP_NEW_POST, pval, it, NULL))
             goto auxerr2;
         break;
+    }
 
-    case ASN1_ITYPE_SEQUENCE:
+    case ASN1_ITYPE_SEQUENCE: {
+        const ASN1_AUX *aux = it->funcs;
+        ASN1_aux_cb *asn1_cb = aux != NULL ? aux->asn1_cb : NULL;
         if (asn1_cb) {
             i = asn1_cb(ASN1_OP_NEW_PRE, pval, it, NULL);
             if (!i)
@@ -173,6 +172,7 @@
             goto auxerr2;
         break;
     }
+    }
     return 1;
 
  memerr2:
@@ -271,7 +271,6 @@
 static int ASN1_primitive_new(ASN1_VALUE **pval, const ASN1_ITEM *it)
 {
     ASN1_TYPE *typ;
-    ASN1_STRING *str;
     int utype;
 
     if (!it)
@@ -308,10 +307,7 @@
         break;
 
     default:
-        str = ASN1_STRING_type_new(utype);
-        if (it->itype == ASN1_ITYPE_MSTRING && str)
-            str->flags |= ASN1_STRING_FLAG_MSTRING;
-        *pval = (ASN1_VALUE *)str;
+        *pval = (ASN1_VALUE *)ASN1_STRING_type_new(utype);
         break;
     }
     if (*pval)
diff --git a/src/crypto/asn1/tasn_utl.c b/src/crypto/asn1/tasn_utl.c
index 24ad8c3..9b1da0b 100644
--- a/src/crypto/asn1/tasn_utl.c
+++ b/src/crypto/asn1/tasn_utl.c
@@ -118,6 +118,7 @@
 }
 
 static ASN1_ENCODING *asn1_get_enc_ptr(ASN1_VALUE **pval, const ASN1_ITEM *it) {
+  assert(it->itype == ASN1_ITYPE_SEQUENCE);
   const ASN1_AUX *aux;
   if (!pval || !*pval) {
     return NULL;
diff --git a/src/crypto/base64/base64.c b/src/crypto/base64/base64.c
index 349452d..3d92059 100644
--- a/src/crypto/base64/base64.c
+++ b/src/crypto/base64/base64.c
@@ -265,14 +265,17 @@
   const uint8_t is_slash = constant_time_eq_8(a, '/');
   const uint8_t is_equals = constant_time_eq_8(a, '=');
 
-  uint8_t ret = 0xff;  // 0xff signals invalid.
-  ret = constant_time_select_8(is_upper, a - 'A', ret);       // [0,26)
-  ret = constant_time_select_8(is_lower, a - 'a' + 26, ret);  // [26,52)
-  ret = constant_time_select_8(is_digit, a - '0' + 52, ret);  // [52,62)
-  ret = constant_time_select_8(is_plus, 62, ret);
-  ret = constant_time_select_8(is_slash, 63, ret);
-  // Padding maps to zero, to be further handled by the caller.
-  ret = constant_time_select_8(is_equals, 0, ret);
+  uint8_t ret = 0;
+  ret |= is_upper & (a - 'A');       // [0,26)
+  ret |= is_lower & (a - 'a' + 26);  // [26,52)
+  ret |= is_digit & (a - '0' + 52);  // [52,62)
+  ret |= is_plus & 62;
+  ret |= is_slash & 63;
+  // Invalid inputs, 'A', and '=' have all been mapped to zero. Map invalid
+  // inputs to 0xff. Note '=' is padding and handled separately by the caller.
+  const uint8_t is_valid =
+      is_upper | is_lower | is_digit | is_plus | is_slash | is_equals;
+  ret |= ~is_valid;
   return ret;
 }
 
diff --git a/src/crypto/bio/bio_mem.c b/src/crypto/bio/bio_mem.c
index 08dd6e9..f40a9a7 100644
--- a/src/crypto/bio/bio_mem.c
+++ b/src/crypto/bio/bio_mem.c
@@ -116,17 +116,11 @@
 }
 
 static int mem_free(BIO *bio) {
-  BUF_MEM *b;
-
-  if (bio == NULL) {
-    return 0;
-  }
-
   if (!bio->shutdown || !bio->init || bio->ptr == NULL) {
     return 1;
   }
 
-  b = (BUF_MEM *)bio->ptr;
+  BUF_MEM *b = (BUF_MEM *)bio->ptr;
   if (bio->flags & BIO_FLAGS_MEM_RDONLY) {
     b->data = NULL;
   }
diff --git a/src/crypto/bio/connect.c b/src/crypto/bio/connect.c
index b8afa61..3b65acf 100644
--- a/src/crypto/bio/connect.c
+++ b/src/crypto/bio/connect.c
@@ -320,7 +320,7 @@
   bio->init = 0;
   bio->num = -1;
   bio->flags = 0;
-  bio->ptr = (char *)BIO_CONNECT_new();
+  bio->ptr = BIO_CONNECT_new();
   return bio->ptr != NULL;
 }
 
@@ -340,10 +340,6 @@
 }
 
 static int conn_free(BIO *bio) {
-  if (bio == NULL) {
-    return 0;
-  }
-
   if (bio->shutdown) {
     conn_close_socket(bio);
   }
diff --git a/src/crypto/bio/fd.c b/src/crypto/bio/fd.c
index d4e6918..349ee9d 100644
--- a/src/crypto/bio/fd.c
+++ b/src/crypto/bio/fd.c
@@ -146,10 +146,6 @@
 }
 
 static int fd_free(BIO *bio) {
-  if (bio == NULL) {
-    return 0;
-  }
-
   if (bio->shutdown) {
     if (bio->init) {
       BORINGSSL_CLOSE(bio->num);
diff --git a/src/crypto/bio/file.c b/src/crypto/bio/file.c
index 15feb9d..835d661 100644
--- a/src/crypto/bio/file.c
+++ b/src/crypto/bio/file.c
@@ -126,13 +126,7 @@
   return ret;
 }
 
-static int file_new(BIO *bio) { return 1; }
-
 static int file_free(BIO *bio) {
-  if (bio == NULL) {
-    return 0;
-  }
-
   if (!bio->shutdown) {
     return 1;
   }
@@ -279,7 +273,7 @@
     BIO_TYPE_FILE,   "FILE pointer",
     file_write,      file_read,
     NULL /* puts */, file_gets,
-    file_ctrl,       file_new,
+    file_ctrl,       NULL /* create */,
     file_free,       NULL /* callback_ctrl */,
 };
 
diff --git a/src/crypto/bio/pair.c b/src/crypto/bio/pair.c
index 03f60b7..a1a9c9c 100644
--- a/src/crypto/bio/pair.c
+++ b/src/crypto/bio/pair.c
@@ -127,12 +127,7 @@
 }
 
 static int bio_free(BIO *bio) {
-  struct bio_bio_st *b;
-
-  if (bio == NULL) {
-    return 0;
-  }
-  b = bio->ptr;
+  struct bio_bio_st *b = bio->ptr;
 
   assert(b != NULL);
 
diff --git a/src/crypto/bio/socket.c b/src/crypto/bio/socket.c
index 081ce01..679959e 100644
--- a/src/crypto/bio/socket.c
+++ b/src/crypto/bio/socket.c
@@ -81,19 +81,7 @@
 }
 #endif
 
-static int sock_new(BIO *bio) {
-  bio->init = 0;
-  bio->num = 0;
-  bio->ptr = NULL;
-  bio->flags = 0;
-  return 1;
-}
-
 static int sock_free(BIO *bio) {
-  if (bio == NULL) {
-    return 0;
-  }
-
   if (bio->shutdown) {
     if (bio->init) {
       closesocket(bio->num);
@@ -105,17 +93,15 @@
 }
 
 static int sock_read(BIO *b, char *out, int outl) {
-  int ret = 0;
-
   if (out == NULL) {
     return 0;
   }
 
   bio_clear_socket_error();
 #if defined(OPENSSL_WINDOWS)
-  ret = recv(b->num, out, outl, 0);
+  int ret = recv(b->num, out, outl, 0);
 #else
-  ret = read(b->num, out, outl);
+  int ret = read(b->num, out, outl);
 #endif
   BIO_clear_retry_flags(b);
   if (ret <= 0) {
@@ -186,7 +172,7 @@
     BIO_TYPE_SOCKET, "socket",
     sock_write,      sock_read,
     NULL /* puts */, NULL /* gets, */,
-    sock_ctrl,       sock_new,
+    sock_ctrl,       NULL /* create */,
     sock_free,       NULL /* callback_ctrl */,
 };
 
diff --git a/src/crypto/bytestring/bytestring_test.cc b/src/crypto/bytestring/bytestring_test.cc
index eafb0de..0877a2e 100644
--- a/src/crypto/bytestring/bytestring_test.cc
+++ b/src/crypto/bytestring/bytestring_test.cc
@@ -115,6 +115,28 @@
   EXPECT_FALSE(CBS_get_u24_length_prefixed(&data, &prefixed));
 }
 
+TEST(CBSTest, GetUntilFirst) {
+  static const uint8_t kData[] = {0, 1, 2, 3, 0, 1, 2, 3};
+  CBS data;
+  CBS_init(&data, kData, sizeof(kData));
+
+  CBS prefix;
+  EXPECT_FALSE(CBS_get_until_first(&data, &prefix, 4));
+  EXPECT_EQ(CBS_data(&data), kData);
+  EXPECT_EQ(CBS_len(&data), sizeof(kData));
+
+  ASSERT_TRUE(CBS_get_until_first(&data, &prefix, 0));
+  EXPECT_EQ(CBS_len(&prefix), 0u);
+  EXPECT_EQ(CBS_data(&data), kData);
+  EXPECT_EQ(CBS_len(&data), sizeof(kData));
+
+  ASSERT_TRUE(CBS_get_until_first(&data, &prefix, 2));
+  EXPECT_EQ(CBS_data(&prefix), kData);
+  EXPECT_EQ(CBS_len(&prefix), 2u);
+  EXPECT_EQ(CBS_data(&data), kData + 2);
+  EXPECT_EQ(CBS_len(&data), sizeof(kData) - 2);
+}
+
 TEST(CBSTest, GetASN1) {
   static const uint8_t kData1[] = {0x30, 2, 1, 2};
   static const uint8_t kData2[] = {0x30, 3, 1, 2};
@@ -322,11 +344,11 @@
 }
 
 TEST(CBBTest, Basic) {
-  static const uint8_t kExpected[] = {1,   2,    3,    4,    5,    6,   7,
-                                      8,   9,    0xa,  0xb,  0xc,  0xd, 0xe,
-                                      0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 3, 2,
-                                      10,  9,    8,    7,    0x12, 0x11, 0x10,
-                                      0xf, 0xe,  0xd,  0xc,  0xb};
+  static const uint8_t kExpected[] = {
+      0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
+      0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
+      0x03, 0x02, 0x0a, 0x09, 0x08, 0x07, 0x12, 0x11, 0x10, 0x0f,
+      0x0e, 0x0d, 0x0c, 0x0b, 0x00, 0x00, 0x00, 0x00};
   uint8_t *buf;
   size_t buf_len;
 
@@ -335,6 +357,7 @@
   cbb.Reset();
 
   ASSERT_TRUE(CBB_init(cbb.get(), 0));
+  ASSERT_TRUE(CBB_add_zeros(cbb.get(), 0));
   ASSERT_TRUE(CBB_add_u8(cbb.get(), 1));
   ASSERT_TRUE(CBB_add_u16(cbb.get(), 0x203));
   ASSERT_TRUE(CBB_add_u24(cbb.get(), 0x40506));
@@ -344,6 +367,7 @@
   ASSERT_TRUE(CBB_add_u16le(cbb.get(), 0x203));
   ASSERT_TRUE(CBB_add_u32le(cbb.get(), 0x708090a));
   ASSERT_TRUE(CBB_add_u64le(cbb.get(), 0xb0c0d0e0f101112));
+  ASSERT_TRUE(CBB_add_zeros(cbb.get(), 4));
   ASSERT_TRUE(CBB_finish(cbb.get(), &buf, &buf_len));
 
   bssl::UniquePtr<uint8_t> scoper(buf);
diff --git a/src/crypto/bytestring/cbb.c b/src/crypto/bytestring/cbb.c
index efb89c7..12587cd 100644
--- a/src/crypto/bytestring/cbb.c
+++ b/src/crypto/bytestring/cbb.c
@@ -404,6 +404,15 @@
   return 1;
 }
 
+int CBB_add_zeros(CBB *cbb, size_t len) {
+  uint8_t *out;
+  if (!CBB_add_space(cbb, &out, len)) {
+    return 0;
+  }
+  OPENSSL_memset(out, 0, len);
+  return 1;
+}
+
 int CBB_add_space(CBB *cbb, uint8_t **out_data, size_t len) {
   if (!CBB_flush(cbb) ||
       !cbb_buffer_add(cbb->base, out_data, len)) {
diff --git a/src/crypto/bytestring/cbs.c b/src/crypto/bytestring/cbs.c
index 5590ec8..803c97a 100644
--- a/src/crypto/bytestring/cbs.c
+++ b/src/crypto/bytestring/cbs.c
@@ -216,6 +216,14 @@
   return cbs_get_length_prefixed(cbs, out, 3);
 }
 
+int CBS_get_until_first(CBS *cbs, CBS *out, uint8_t c) {
+  const uint8_t *split = OPENSSL_memchr(CBS_data(cbs), c, CBS_len(cbs));
+  if (split == NULL) {
+    return 0;
+  }
+  return CBS_get_bytes(cbs, out, split - CBS_data(cbs));
+}
+
 // parse_base128_integer reads a big-endian base-128 integer from |cbs| and sets
 // |*out| to the result. This is the encoding used in DER for both high tag
 // number form and OID components.
diff --git a/src/crypto/chacha/asm/chacha-armv4.pl b/src/crypto/chacha/asm/chacha-armv4.pl
index 5455b83..5c78a9f 100755
--- a/src/crypto/chacha/asm/chacha-armv4.pl
+++ b/src/crypto/chacha/asm/chacha-armv4.pl
@@ -1163,4 +1163,4 @@
 
 	print $_,"\n";
 }
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/chacha/asm/chacha-armv8.pl b/src/crypto/chacha/asm/chacha-armv8.pl
index b2d3d86..608db66 100755
--- a/src/crypto/chacha/asm/chacha-armv8.pl
+++ b/src/crypto/chacha/asm/chacha-armv8.pl
@@ -1141,4 +1141,4 @@
 
 	print $_,"\n";
 }
-close STDOUT or die "error closing STDOUT";	# flush
+close STDOUT or die "error closing STDOUT: $!";	# flush
diff --git a/src/crypto/chacha/asm/chacha-x86.pl b/src/crypto/chacha/asm/chacha-x86.pl
index ec1cf80..482d53f 100755
--- a/src/crypto/chacha/asm/chacha-x86.pl
+++ b/src/crypto/chacha/asm/chacha-x86.pl
@@ -769,4 +769,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/chacha/asm/chacha-x86_64.pl b/src/crypto/chacha/asm/chacha-x86_64.pl
index 2ee22b6..8f3f4ce 100755
--- a/src/crypto/chacha/asm/chacha-x86_64.pl
+++ b/src/crypto/chacha/asm/chacha-x86_64.pl
@@ -2782,4 +2782,4 @@
 	print $_,"\n";
 }
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl b/src/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl
index 54f0b5c..aad31ab 100644
--- a/src/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl
+++ b/src/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl
@@ -2253,4 +2253,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl b/src/crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl
index aa42c67..b2067c7 100644
--- a/src/crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl
+++ b/src/crypto/cipher_extra/asm/chacha20_poly1305_x86_64.pl
@@ -2559,4 +2559,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/cipher_extra/test/cipher_tests.txt b/src/crypto/cipher_extra/test/cipher_tests.txt
index bf7325e..782b08e 100644
--- a/src/crypto/cipher_extra/test/cipher_tests.txt
+++ b/src/crypto/cipher_extra/test/cipher_tests.txt
@@ -271,7 +271,7 @@
 Ciphertext =
 
 
-# AES Counter test vectors from RFC3686
+# AES Counter test vectors from RFC 3686
 Cipher = AES-128-CTR
 Key = AE6852F8121067CC4BF7A5765577F39E
 IV = 00000030000000000000000000000001
diff --git a/src/crypto/crypto_test.cc b/src/crypto/crypto_test.cc
index 03b909d..7f15a23 100644
--- a/src/crypto/crypto_test.cc
+++ b/src/crypto/crypto_test.cc
@@ -18,8 +18,10 @@
 #include <string>
 
 #include <openssl/base.h>
+#include <openssl/aead.h>
 #include <openssl/crypto.h>
 #include <openssl/cipher.h>
+#include <openssl/mem.h>
 
 #include <gtest/gtest.h>
 
@@ -35,40 +37,104 @@
             std::string(OPENSSL_VERSION_TEXT).substr(0, strlen(expected)));
 }
 
+TEST(CryptoTest, Strndup) {
+  bssl::UniquePtr<char> str(OPENSSL_strndup(nullptr, 0));
+  EXPECT_TRUE(str);
+  EXPECT_STREQ("", str.get());
+}
+
 #if defined(BORINGSSL_FIPS_COUNTERS)
+using CounterArray = size_t[fips_counter_max + 1];
+
+static void read_all_counters(CounterArray counters) {
+  for (fips_counter_t counter = static_cast<fips_counter_t>(0);
+       counter <= fips_counter_max;
+       counter = static_cast<fips_counter_t>(counter + 1)) {
+    counters[counter] = FIPS_read_counter(counter);
+  }
+}
+
+static void expect_counter_delta_is_zero_except_for_a_one_at(
+    CounterArray before, CounterArray after, fips_counter_t position) {
+  for (fips_counter_t counter = static_cast<fips_counter_t>(0);
+       counter <= fips_counter_max;
+       counter = static_cast<fips_counter_t>(counter + 1)) {
+    const size_t expected_delta = counter == position ? 1 : 0;
+    EXPECT_EQ(after[counter], before[counter] + expected_delta) << counter;
+  }
+}
+
 TEST(CryptoTest, FIPSCountersEVP) {
   constexpr struct {
     const EVP_CIPHER *(*cipher)();
     fips_counter_t counter;
   } kTests[] = {
-    {
-        EVP_aes_128_gcm,
-        fips_counter_evp_aes_128_gcm,
-    },
-    {
-        EVP_aes_256_gcm,
-        fips_counter_evp_aes_256_gcm,
-    },
-    {
-        EVP_aes_128_ctr,
-        fips_counter_evp_aes_128_ctr,
-    },
-    {
-        EVP_aes_256_ctr,
-        fips_counter_evp_aes_256_ctr,
-    },
+      {
+          EVP_aes_128_gcm,
+          fips_counter_evp_aes_128_gcm,
+      },
+      {
+          EVP_aes_256_gcm,
+          fips_counter_evp_aes_256_gcm,
+      },
+      {
+          EVP_aes_128_ctr,
+          fips_counter_evp_aes_128_ctr,
+      },
+      {
+          EVP_aes_256_ctr,
+          fips_counter_evp_aes_256_ctr,
+      },
   };
 
   uint8_t key[EVP_MAX_KEY_LENGTH] = {0};
   uint8_t iv[EVP_MAX_IV_LENGTH] = {1};
-
-  for (const auto& test : kTests) {
-    const size_t before = FIPS_read_counter(test.counter);
-
+  CounterArray before, after;
+  for (const auto &test : kTests) {
+    read_all_counters(before);
     bssl::ScopedEVP_CIPHER_CTX ctx;
     ASSERT_TRUE(EVP_EncryptInit_ex(ctx.get(), test.cipher(), /*engine=*/nullptr,
                                    key, iv));
-    ASSERT_GT(FIPS_read_counter(test.counter), before);
+    read_all_counters(after);
+
+    expect_counter_delta_is_zero_except_for_a_one_at(before, after,
+                                                     test.counter);
   }
 }
+
+TEST(CryptoTest, FIPSCountersEVP_AEAD) {
+  constexpr struct {
+    const EVP_AEAD *(*aead)();
+    unsigned key_len;
+    fips_counter_t counter;
+  } kTests[] = {
+      {
+          EVP_aead_aes_128_gcm,
+          16,
+          fips_counter_evp_aes_128_gcm,
+      },
+      {
+          EVP_aead_aes_256_gcm,
+          32,
+          fips_counter_evp_aes_256_gcm,
+      },
+  };
+
+  uint8_t key[EVP_AEAD_MAX_KEY_LENGTH] = {0};
+  CounterArray before, after;
+  for (const auto &test : kTests) {
+    ASSERT_LE(test.key_len, sizeof(key));
+
+    read_all_counters(before);
+    bssl::ScopedEVP_AEAD_CTX ctx;
+    ASSERT_TRUE(EVP_AEAD_CTX_init(ctx.get(), test.aead(), key, test.key_len,
+                                  EVP_AEAD_DEFAULT_TAG_LENGTH,
+                                  /*engine=*/nullptr));
+    read_all_counters(after);
+
+    expect_counter_delta_is_zero_except_for_a_one_at(before, after,
+                                                     test.counter);
+  }
+}
+
 #endif  // BORINGSSL_FIPS_COUNTERS
diff --git a/src/crypto/digest_extra/digest_extra.c b/src/crypto/digest_extra/digest_extra.c
index a93601c..8cbb28e 100644
--- a/src/crypto/digest_extra/digest_extra.c
+++ b/src/crypto/digest_extra/digest_extra.c
@@ -83,6 +83,7 @@
     {NID_sha256, EVP_sha256, SN_sha256, LN_sha256},
     {NID_sha384, EVP_sha384, SN_sha384, LN_sha384},
     {NID_sha512, EVP_sha512, SN_sha512, LN_sha512},
+    {NID_sha512_256, EVP_sha512_256, SN_sha512_256, LN_sha512_256},
     {NID_md5_sha1, EVP_md5_sha1, SN_md5_sha1, LN_md5_sha1},
     // As a remnant of signing |EVP_MD|s, OpenSSL returned the corresponding
     // hash function when given a signature OID. To avoid unintended lax parsing
diff --git a/src/crypto/err/ssl.errordata b/src/crypto/err/ssl.errordata
index 2f85410..6879134 100644
--- a/src/crypto/err/ssl.errordata
+++ b/src/crypto/err/ssl.errordata
@@ -80,6 +80,7 @@
 SSL,156,HTTP_REQUEST
 SSL,157,INAPPROPRIATE_FALLBACK
 SSL,303,INCONSISTENT_CLIENT_HELLO
+SSL,321,INCONSISTENT_ECH_NEGOTIATION
 SSL,259,INVALID_ALPN_PROTOCOL
 SSL,315,INVALID_ALPN_PROTOCOL_LIST
 SSL,314,INVALID_CLIENT_HELLO_INNER
@@ -132,6 +133,7 @@
 SSL,187,OLD_SESSION_CIPHER_NOT_RETURNED
 SSL,268,OLD_SESSION_PRF_HASH_MISMATCH
 SSL,188,OLD_SESSION_VERSION_NOT_RETURNED
+SSL,320,OUTER_EXTENSION_NOT_FOUND
 SSL,189,OUTPUT_ALIASES_INPUT
 SSL,190,PARSE_TLSEXT
 SSL,191,PATH_TOO_LONG
diff --git a/src/crypto/err/x509v3.errordata b/src/crypto/err/x509v3.errordata
index e53b780..492259c 100644
--- a/src/crypto/err/x509v3.errordata
+++ b/src/crypto/err/x509v3.errordata
@@ -34,6 +34,7 @@
 X509V3,133,INVALID_PURPOSE
 X509V3,134,INVALID_SECTION
 X509V3,135,INVALID_SYNTAX
+X509V3,163,INVALID_VALUE
 X509V3,136,ISSUER_DECODE_ERROR
 X509V3,137,MISSING_VALUE
 X509V3,138,NEED_ORGANIZATION_AND_NUMBERS
diff --git a/src/crypto/fipsmodule/CMakeLists.txt b/src/crypto/fipsmodule/CMakeLists.txt
index 7b9e1b1..73f8a02 100644
--- a/src/crypto/fipsmodule/CMakeLists.txt
+++ b/src/crypto/fipsmodule/CMakeLists.txt
@@ -1,6 +1,6 @@
 include_directories(../../include)
 
-if(${ARCH} STREQUAL "x86_64")
+if(ARCH STREQUAL "x86_64")
   set(
     BCM_ASM_SOURCES
 
@@ -22,7 +22,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "x86")
+if(ARCH STREQUAL "x86")
   set(
     BCM_ASM_SOURCES
 
@@ -40,7 +40,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "arm")
+if(ARCH STREQUAL "arm")
   set(
     BCM_ASM_SOURCES
 
@@ -56,7 +56,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "aarch64")
+if(ARCH STREQUAL "aarch64")
   set(
     BCM_ASM_SOURCES
 
@@ -71,7 +71,7 @@
   )
 endif()
 
-if(${ARCH} STREQUAL "ppc64le")
+if(ARCH STREQUAL "ppc64le")
   set(
     BCM_ASM_SOURCES
 
@@ -160,7 +160,7 @@
     bcm.c
   )
 
-  if(${ARCH} STREQUAL "aarch64")
+  if(ARCH STREQUAL "aarch64")
     # Perlasm output on Aarch64 needs to pass through the C preprocessor before
     # it can be parsed by delocate.
     foreach(asm ${BCM_ASM_SOURCES})
@@ -202,7 +202,7 @@
   # If building with OPENSSL_NO_ASM then ARCH will be "generic", but we still
   # need to use SHA-256. Since this only matters for FIPS, we only need to
   # worry about the Linux spelling of AArch64.
-  if (ARCH STREQUAL "aarch64" OR ${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
+  if (ARCH STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
     set(MAYBE_INJECT_HASH_SHA256_FLAG "-sha256")
   endif()
 
diff --git a/src/crypto/fipsmodule/aes/asm/aesni-x86.pl b/src/crypto/fipsmodule/aes/asm/aesni-x86.pl
index b999e5a..7d97adc 100644
--- a/src/crypto/fipsmodule/aes/asm/aesni-x86.pl
+++ b/src/crypto/fipsmodule/aes/asm/aesni-x86.pl
@@ -2551,4 +2551,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/aes/asm/aesni-x86_64.pl b/src/crypto/fipsmodule/aes/asm/aesni-x86_64.pl
index f4d2ea5..2abc8d0 100644
--- a/src/crypto/fipsmodule/aes/asm/aesni-x86_64.pl
+++ b/src/crypto/fipsmodule/aes/asm/aesni-x86_64.pl
@@ -5102,4 +5102,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/aes/asm/aesp8-ppc.pl b/src/crypto/fipsmodule/aes/asm/aesp8-ppc.pl
index 3b1fbee..983cbbe 100644
--- a/src/crypto/fipsmodule/aes/asm/aesp8-ppc.pl
+++ b/src/crypto/fipsmodule/aes/asm/aesp8-ppc.pl
@@ -3804,4 +3804,4 @@
         print $_,"\n";
 }
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/aes/asm/aesv8-armx.pl b/src/crypto/fipsmodule/aes/asm/aesv8-armx.pl
index 3bfdd8d..82022c7 100644
--- a/src/crypto/fipsmodule/aes/asm/aesv8-armx.pl
+++ b/src/crypto/fipsmodule/aes/asm/aesv8-armx.pl
@@ -1044,4 +1044,4 @@
     }
 }
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/aes/asm/bsaes-armv7.pl b/src/crypto/fipsmodule/aes/asm/bsaes-armv7.pl
index f6db486..c537730 100644
--- a/src/crypto/fipsmodule/aes/asm/bsaes-armv7.pl
+++ b/src/crypto/fipsmodule/aes/asm/bsaes-armv7.pl
@@ -2433,4 +2433,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/aes/asm/vpaes-armv7.pl b/src/crypto/fipsmodule/aes/asm/vpaes-armv7.pl
index bb77d10..b4f2eb3 100644
--- a/src/crypto/fipsmodule/aes/asm/vpaes-armv7.pl
+++ b/src/crypto/fipsmodule/aes/asm/vpaes-armv7.pl
@@ -1373,4 +1373,4 @@
 	print $_,"\n";
 }
 
-close STDOUT;
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/aes/asm/vpaes-armv8.pl b/src/crypto/fipsmodule/aes/asm/vpaes-armv8.pl
index a1d919d..8b01fed 100755
--- a/src/crypto/fipsmodule/aes/asm/vpaes-armv8.pl
+++ b/src/crypto/fipsmodule/aes/asm/vpaes-armv8.pl
@@ -1382,4 +1382,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/aes/asm/vpaes-x86.pl b/src/crypto/fipsmodule/aes/asm/vpaes-x86.pl
index 14a3105..04c407d 100644
--- a/src/crypto/fipsmodule/aes/asm/vpaes-x86.pl
+++ b/src/crypto/fipsmodule/aes/asm/vpaes-x86.pl
@@ -920,4 +920,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/aes/asm/vpaes-x86_64.pl b/src/crypto/fipsmodule/aes/asm/vpaes-x86_64.pl
index e761eb2..f6f67ea 100644
--- a/src/crypto/fipsmodule/aes/asm/vpaes-x86_64.pl
+++ b/src/crypto/fipsmodule/aes/asm/vpaes-x86_64.pl
@@ -1546,4 +1546,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/bn/asm/armv4-mont.pl b/src/crypto/fipsmodule/bn/asm/armv4-mont.pl
index 5ee474f..207b8e4 100644
--- a/src/crypto/fipsmodule/bn/asm/armv4-mont.pl
+++ b/src/crypto/fipsmodule/bn/asm/armv4-mont.pl
@@ -761,4 +761,4 @@
 	print $_,"\n";
 }
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/bn/asm/armv8-mont.pl b/src/crypto/fipsmodule/bn/asm/armv8-mont.pl
index 77b437d..aa65ffd 100644
--- a/src/crypto/fipsmodule/bn/asm/armv8-mont.pl
+++ b/src/crypto/fipsmodule/bn/asm/armv8-mont.pl
@@ -1520,4 +1520,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/bn/asm/bn-586.pl b/src/crypto/fipsmodule/bn/asm/bn-586.pl
index 5c52e05..c23d6a9 100644
--- a/src/crypto/fipsmodule/bn/asm/bn-586.pl
+++ b/src/crypto/fipsmodule/bn/asm/bn-586.pl
@@ -30,7 +30,7 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
 
 sub bn_mul_add_words
 	{
diff --git a/src/crypto/fipsmodule/bn/asm/co-586.pl b/src/crypto/fipsmodule/bn/asm/co-586.pl
index abe328a..458db33 100644
--- a/src/crypto/fipsmodule/bn/asm/co-586.pl
+++ b/src/crypto/fipsmodule/bn/asm/co-586.pl
@@ -22,7 +22,7 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
 
 sub mul_add_c
 	{
diff --git a/src/crypto/fipsmodule/bn/asm/rsaz-avx2.pl b/src/crypto/fipsmodule/bn/asm/rsaz-avx2.pl
index a0da239..65b0062 100755
--- a/src/crypto/fipsmodule/bn/asm/rsaz-avx2.pl
+++ b/src/crypto/fipsmodule/bn/asm/rsaz-avx2.pl
@@ -1940,4 +1940,4 @@
 ___
 }}}
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/bn/asm/x86-mont.pl b/src/crypto/fipsmodule/bn/asm/x86-mont.pl
index 1f61ae5..58b28ad 100755
--- a/src/crypto/fipsmodule/bn/asm/x86-mont.pl
+++ b/src/crypto/fipsmodule/bn/asm/x86-mont.pl
@@ -628,4 +628,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/bn/asm/x86_64-mont.pl b/src/crypto/fipsmodule/bn/asm/x86_64-mont.pl
index 0a9e4d1..8b34ae3 100755
--- a/src/crypto/fipsmodule/bn/asm/x86_64-mont.pl
+++ b/src/crypto/fipsmodule/bn/asm/x86_64-mont.pl
@@ -1578,4 +1578,4 @@
 }
 
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/bn/asm/x86_64-mont5.pl b/src/crypto/fipsmodule/bn/asm/x86_64-mont5.pl
index b2ff114..54335cc 100755
--- a/src/crypto/fipsmodule/bn/asm/x86_64-mont5.pl
+++ b/src/crypto/fipsmodule/bn/asm/x86_64-mont5.pl
@@ -3930,4 +3930,4 @@
 $code =~ s/\`([^\`]*)\`/eval($1)/gem;
 
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/bn/bn_test.cc b/src/crypto/fipsmodule/bn/bn_test.cc
index 1b42d9c..72ec8c2 100644
--- a/src/crypto/fipsmodule/bn/bn_test.cc
+++ b/src/crypto/fipsmodule/bn/bn_test.cc
@@ -613,9 +613,17 @@
     }
   }
 
-  ASSERT_TRUE(bn_div_consttime(ret.get(), ret2.get(), a.get(), b.get(), ctx));
+  ASSERT_TRUE(bn_div_consttime(ret.get(), ret2.get(), a.get(), b.get(),
+                               /*divisor_min_bits=*/0, ctx));
   EXPECT_BIGNUMS_EQUAL("A / B (constant-time)", quotient.get(), ret.get());
   EXPECT_BIGNUMS_EQUAL("A % B (constant-time)", remainder.get(), ret2.get());
+
+  ASSERT_TRUE(bn_div_consttime(ret.get(), ret2.get(), a.get(), b.get(),
+                               /*divisor_min_bits=*/BN_num_bits(b.get()), ctx));
+  EXPECT_BIGNUMS_EQUAL("A / B (constant-time, public width)", quotient.get(),
+                       ret.get());
+  EXPECT_BIGNUMS_EQUAL("A % B (constant-time, public width)", remainder.get(),
+                       ret2.get());
 }
 
 static void TestModMul(BIGNUMFileTest *t, BN_CTX *ctx) {
diff --git a/src/crypto/fipsmodule/bn/div.c b/src/crypto/fipsmodule/bn/div.c
index 6ee6dbd..02b9931 100644
--- a/src/crypto/fipsmodule/bn/div.c
+++ b/src/crypto/fipsmodule/bn/div.c
@@ -456,7 +456,7 @@
 
 int bn_div_consttime(BIGNUM *quotient, BIGNUM *remainder,
                      const BIGNUM *numerator, const BIGNUM *divisor,
-                     BN_CTX *ctx) {
+                     unsigned divisor_min_bits, BN_CTX *ctx) {
   if (BN_is_negative(numerator) || BN_is_negative(divisor)) {
     OPENSSL_PUT_ERROR(BN, BN_R_NEGATIVE_NUMBER);
     return 0;
@@ -496,8 +496,26 @@
   r->neg = 0;
 
   // Incorporate |numerator| into |r|, one bit at a time, reducing after each
-  // step. At the start of each loop iteration, |r| < |divisor|
-  for (int i = numerator->width - 1; i >= 0; i--) {
+  // step. We maintain the invariant that |0 <= r < divisor| and
+  // |q * divisor + r = n| where |n| is the portion of |numerator| incorporated
+  // so far.
+  //
+  // First, we short-circuit the loop: if we know |divisor| has at least
+  // |divisor_min_bits| bits, the top |divisor_min_bits - 1| can be incorporated
+  // without reductions. This significantly speeds up |RSA_check_key|. For
+  // simplicity, we round down to a whole number of words.
+  assert(divisor_min_bits <= BN_num_bits(divisor));
+  int initial_words = 0;
+  if (divisor_min_bits > 0) {
+    initial_words = (divisor_min_bits - 1) / BN_BITS2;
+    if (initial_words > numerator->width) {
+      initial_words = numerator->width;
+    }
+    OPENSSL_memcpy(r->d, numerator->d + numerator->width - initial_words,
+                   initial_words * sizeof(BN_ULONG));
+  }
+
+  for (int i = numerator->width - initial_words - 1; i >= 0; i--) {
     for (int bit = BN_BITS2 - 1; bit >= 0; bit--) {
       // Incorporate the next bit of the numerator, by computing
       // r = 2*r or 2*r + 1. Note the result fits in one more word. We store the
diff --git a/src/crypto/fipsmodule/bn/gcd_extra.c b/src/crypto/fipsmodule/bn/gcd_extra.c
index 30540e3..53ab170 100644
--- a/src/crypto/fipsmodule/bn/gcd_extra.c
+++ b/src/crypto/fipsmodule/bn/gcd_extra.c
@@ -157,10 +157,11 @@
   BN_CTX_start(ctx);
   unsigned shift;
   BIGNUM *gcd = BN_CTX_get(ctx);
-  int ret = gcd != NULL &&
+  int ret = gcd != NULL &&  //
             bn_mul_consttime(r, a, b, ctx) &&
             bn_gcd_consttime(gcd, &shift, a, b, ctx) &&
-            bn_div_consttime(r, NULL, r, gcd, ctx) &&
+            // |gcd| has a secret bit width.
+            bn_div_consttime(r, NULL, r, gcd, /*divisor_min_bits=*/0, ctx) &&
             bn_rshift_secret_shift(r, r, shift, ctx);
   BN_CTX_end(ctx);
   return ret;
diff --git a/src/crypto/fipsmodule/bn/internal.h b/src/crypto/fipsmodule/bn/internal.h
index 3d368db..cab9a81 100644
--- a/src/crypto/fipsmodule/bn/internal.h
+++ b/src/crypto/fipsmodule/bn/internal.h
@@ -552,12 +552,15 @@
 // bn_div_consttime behaves like |BN_div|, but it rejects negative inputs and
 // treats both inputs, including their magnitudes, as secret. It is, as a
 // result, much slower than |BN_div| and should only be used for rare operations
-// where Montgomery reduction is not available.
+// where Montgomery reduction is not available. |divisor_min_bits| is a
+// public lower bound for |BN_num_bits(divisor)|. When |divisor|'s bit width is
+// public, this can speed up the operation.
 //
 // Note that |quotient->width| will be set pessimally to |numerator->width|.
 OPENSSL_EXPORT int bn_div_consttime(BIGNUM *quotient, BIGNUM *remainder,
                                     const BIGNUM *numerator,
-                                    const BIGNUM *divisor, BN_CTX *ctx);
+                                    const BIGNUM *divisor,
+                                    unsigned divisor_min_bits, BN_CTX *ctx);
 
 // bn_is_relatively_prime checks whether GCD(|x|, |y|) is one. On success, it
 // returns one and sets |*out_relatively_prime| to one if the GCD was one and
diff --git a/src/crypto/fipsmodule/cipher/e_aes.c b/src/crypto/fipsmodule/cipher/e_aes.c
index f77133f..76f4066 100644
--- a/src/crypto/fipsmodule/cipher/e_aes.c
+++ b/src/crypto/fipsmodule/cipher/e_aes.c
@@ -911,6 +911,16 @@
                                   size_t key_len, size_t tag_len) {
   const size_t key_bits = key_len * 8;
 
+  switch (key_bits) {
+    case 128:
+      boringssl_fips_inc_counter(fips_counter_evp_aes_128_gcm);
+      break;
+
+    case 256:
+      boringssl_fips_inc_counter(fips_counter_evp_aes_256_gcm);
+      break;
+  }
+
   if (key_bits != 128 && key_bits != 192 && key_bits != 256) {
     OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_KEY_LENGTH);
     return 0;  // EVP_AEAD_CTX_init should catch this.
diff --git a/src/crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl b/src/crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl
index 994cb82..c75d784 100755
--- a/src/crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl
+++ b/src/crypto/fipsmodule/ec/asm/p256-x86_64-asm.pl
@@ -4153,4 +4153,4 @@
 
 $code =~ s/\`([^\`]*)\`/eval $1/gem;
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/ec/asm/p256_beeu-x86_64-asm.pl b/src/crypto/fipsmodule/ec/asm/p256_beeu-x86_64-asm.pl
index c05abba..b9ec96d 100644
--- a/src/crypto/fipsmodule/ec/asm/p256_beeu-x86_64-asm.pl
+++ b/src/crypto/fipsmodule/ec/asm/p256_beeu-x86_64-asm.pl
@@ -400,4 +400,4 @@
 ___
 
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/md4/md4.c b/src/crypto/fipsmodule/md4/md4.c
index 8779402..a505d05 100644
--- a/src/crypto/fipsmodule/md4/md4.c
+++ b/src/crypto/fipsmodule/md4/md4.c
@@ -72,7 +72,7 @@
   return out;
 }
 
-// Implemented from RFC1186 The MD4 Message-Digest Algorithm.
+// Implemented from RFC 1186 The MD4 Message-Digest Algorithm.
 
 int MD4_Init(MD4_CTX *md4) {
   OPENSSL_memset(md4, 0, sizeof(MD4_CTX));
diff --git a/src/crypto/fipsmodule/md5/asm/md5-586.pl b/src/crypto/fipsmodule/md5/asm/md5-586.pl
index 20c226b..ec34aad 100644
--- a/src/crypto/fipsmodule/md5/asm/md5-586.pl
+++ b/src/crypto/fipsmodule/md5/asm/md5-586.pl
@@ -36,7 +36,7 @@
 &md5_block("md5_block_asm_data_order");
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
 
 sub Np
 	{
diff --git a/src/crypto/fipsmodule/md5/asm/md5-x86_64.pl b/src/crypto/fipsmodule/md5/asm/md5-x86_64.pl
index 509bcde..4c96bba 100644
--- a/src/crypto/fipsmodule/md5/asm/md5-x86_64.pl
+++ b/src/crypto/fipsmodule/md5/asm/md5-x86_64.pl
@@ -380,4 +380,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl b/src/crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl
index 48a5560..793f34c 100644
--- a/src/crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl
+++ b/src/crypto/fipsmodule/modes/asm/aesni-gcm-x86_64.pl
@@ -1141,4 +1141,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/modes/asm/ghash-armv4.pl b/src/crypto/fipsmodule/modes/asm/ghash-armv4.pl
index d570990..e84063b 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-armv4.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-armv4.pl
@@ -297,4 +297,4 @@
 
 	print $_,"\n";
 }
-close STDOUT or die "error closing STDOUT"; # enforce flush
+close STDOUT or die "error closing STDOUT: $!"; # enforce flush
diff --git a/src/crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl b/src/crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl
index b1725a8..c6a7af6 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-neon-armv8.pl
@@ -291,4 +291,4 @@
 
 	print $_,"\n";
 }
-close STDOUT or die "error closing STDOUT"; # enforce flush
+close STDOUT or die "error closing STDOUT: $!"; # enforce flush
diff --git a/src/crypto/fipsmodule/modes/asm/ghash-ssse3-x86.pl b/src/crypto/fipsmodule/modes/asm/ghash-ssse3-x86.pl
index 45e1ee1..6382876 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-ssse3-x86.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-ssse3-x86.pl
@@ -285,4 +285,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/modes/asm/ghash-ssse3-x86_64.pl b/src/crypto/fipsmodule/modes/asm/ghash-ssse3-x86_64.pl
index e0e5010..f736473 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-ssse3-x86_64.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-ssse3-x86_64.pl
@@ -410,4 +410,4 @@
 }
 
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/modes/asm/ghash-x86.pl b/src/crypto/fipsmodule/modes/asm/ghash-x86.pl
index 9486c2e..f893b50 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-x86.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-x86.pl
@@ -660,7 +660,7 @@
 &asciz("GHASH for x86, CRYPTOGAMS by <appro\@openssl.org>");
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
 
 # A question was risen about choice of vanilla MMX. Or rather why wasn't
 # SSE2 chosen instead? In addition to the fact that MMX runs on legacy
diff --git a/src/crypto/fipsmodule/modes/asm/ghash-x86_64.pl b/src/crypto/fipsmodule/modes/asm/ghash-x86_64.pl
index 16f7f00..1aeb7b7 100644
--- a/src/crypto/fipsmodule/modes/asm/ghash-x86_64.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghash-x86_64.pl
@@ -1336,4 +1336,4 @@
 
 print $code;
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/modes/asm/ghashp8-ppc.pl b/src/crypto/fipsmodule/modes/asm/ghashp8-ppc.pl
index 827ddb2..e9ca11a 100644
--- a/src/crypto/fipsmodule/modes/asm/ghashp8-ppc.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghashp8-ppc.pl
@@ -667,4 +667,4 @@
 	print $_,"\n";
 }
 
-close STDOUT or die "error closing STDOUT"; # enforce flush
+close STDOUT or die "error closing STDOUT: $!"; # enforce flush
diff --git a/src/crypto/fipsmodule/modes/asm/ghashv8-armx.pl b/src/crypto/fipsmodule/modes/asm/ghashv8-armx.pl
index 0fb6414..24eb773 100644
--- a/src/crypto/fipsmodule/modes/asm/ghashv8-armx.pl
+++ b/src/crypto/fipsmodule/modes/asm/ghashv8-armx.pl
@@ -1,5 +1,5 @@
 #! /usr/bin/env perl
-# Copyright 2014-2016 The OpenSSL Project Authors. All Rights Reserved.
+# Copyright 2014-2020 The OpenSSL Project Authors. All Rights Reserved.
 #
 # Licensed under the OpenSSL license (the "License").  You may not use
 # this file except in compliance with the License.  You can obtain a copy
@@ -17,23 +17,31 @@
 # GHASH for ARMv8 Crypto Extension, 64-bit polynomial multiplication.
 #
 # June 2014
-# Initial version was developed in tight cooperation with Ard Biesheuvel
-# of Linaro from bits-n-pieces from other assembly modules. Just like
-# aesv8-armx.pl this module supports both AArch32 and AArch64 execution modes.
+#
+# Initial version was developed in tight cooperation with Ard
+# Biesheuvel of Linaro from bits-n-pieces from other assembly modules.
+# Just like aesv8-armx.pl this module supports both AArch32 and
+# AArch64 execution modes.
 #
 # July 2014
+#
 # Implement 2x aggregated reduction [see ghash-x86.pl for background
 # information].
 #
+# November 2017
+#
+# AArch64 register bank to "accommodate" 4x aggregated reduction and
+# improve performance by 20-70% depending on processor.
+#
 # Current performance in cycles per processed byte:
 #
-#		PMULL[2]	32-bit NEON(*)
-# Apple A7	0.92		5.62
-# Cortex-A53	1.01		8.39
-# Cortex-A57	1.17		7.61
-# Denver	0.71		6.02
-# Mongoose	1.10		8.06
-# Kryo		1.16		8.00
+#		64-bit PMULL	32-bit PMULL	32-bit NEON(*)
+# Apple A7	0.58		0.92		5.62
+# Cortex-A53	0.85		1.01		8.39
+# Cortex-A57	0.73		1.17		7.61
+# Denver	0.51		0.65		6.02
+# Mongoose	0.65		1.10		8.06
+# Kryo		0.76		1.16		8.00
 #
 # (*)	presented for reference/comparison purposes;
 
@@ -62,6 +70,7 @@
 $code=<<___;
 #include <openssl/arm_arch.h>
 
+#if __ARM_MAX_ARCH__>=7
 .text
 ___
 $code.=".arch	armv8-a+crypto\n"	if ($flavour =~ /64/);
@@ -129,8 +138,56 @@
 	vext.8		$t1,$H2,$H2,#8		@ Karatsuba pre-processing
 	veor		$t1,$t1,$H2
 	vext.8		$Hhl,$t0,$t1,#8		@ pack Karatsuba pre-processed
-	vst1.64		{$Hhl-$H2},[x0]		@ store Htable[1..2]
+	vst1.64		{$Hhl-$H2},[x0],#32	@ store Htable[1..2]
+___
+if ($flavour =~ /64/) {
+my ($t3,$Yl,$Ym,$Yh) = map("q$_",(4..7));
 
+$code.=<<___;
+	@ calculate H^3 and H^4
+	vpmull.p64	$Xl,$H, $H2
+	 vpmull.p64	$Yl,$H2,$H2
+	vpmull2.p64	$Xh,$H, $H2
+	 vpmull2.p64	$Yh,$H2,$H2
+	vpmull.p64	$Xm,$t0,$t1
+	 vpmull.p64	$Ym,$t1,$t1
+
+	vext.8		$t0,$Xl,$Xh,#8		@ Karatsuba post-processing
+	 vext.8		$t1,$Yl,$Yh,#8
+	veor		$t2,$Xl,$Xh
+	veor		$Xm,$Xm,$t0
+	 veor		$t3,$Yl,$Yh
+	 veor		$Ym,$Ym,$t1
+	veor		$Xm,$Xm,$t2
+	vpmull.p64	$t2,$Xl,$xC2		@ 1st phase
+	 veor		$Ym,$Ym,$t3
+	 vpmull.p64	$t3,$Yl,$xC2
+
+	vmov		$Xh#lo,$Xm#hi		@ Xh|Xm - 256-bit result
+	 vmov		$Yh#lo,$Ym#hi
+	vmov		$Xm#hi,$Xl#lo		@ Xm is rotated Xl
+	 vmov		$Ym#hi,$Yl#lo
+	veor		$Xl,$Xm,$t2
+	 veor		$Yl,$Ym,$t3
+
+	vext.8		$t2,$Xl,$Xl,#8		@ 2nd phase
+	 vext.8		$t3,$Yl,$Yl,#8
+	vpmull.p64	$Xl,$Xl,$xC2
+	 vpmull.p64	$Yl,$Yl,$xC2
+	veor		$t2,$t2,$Xh
+	 veor		$t3,$t3,$Yh
+	veor		$H, $Xl,$t2		@ H^3
+	 veor		$H2,$Yl,$t3		@ H^4
+
+	vext.8		$t0,$H, $H,#8		@ Karatsuba pre-processing
+	 vext.8		$t1,$H2,$H2,#8
+	veor		$t0,$t0,$H
+	 veor		$t1,$t1,$H2
+	vext.8		$Hhl,$t0,$t1,#8		@ pack Karatsuba pre-processed
+	vst1.64		{$H-$H2},[x0]		@ store Htable[3..5]
+___
+}
+$code.=<<___;
 	ret
 .size	gcm_init_v8,.-gcm_init_v8
 ___
@@ -201,6 +258,10 @@
 gcm_ghash_v8:
 	AARCH64_VALID_CALL_TARGET
 ___
+$code.=<<___	if ($flavour =~ /64/);
+	cmp		$len,#64
+	b.hs		.Lgcm_ghash_v8_4x
+___
 $code.=<<___		if ($flavour !~ /64/);
 	vstmdb		sp!,{d8-d15}		@ 32-bit ABI says so
 ___
@@ -348,10 +409,301 @@
 	ret
 .size	gcm_ghash_v8,.-gcm_ghash_v8
 ___
+
+if ($flavour =~ /64/) {				# 4x subroutine
+my ($I0,$j1,$j2,$j3,
+    $I1,$I2,$I3,$H3,$H34,$H4,$Yl,$Ym,$Yh) = map("q$_",(4..7,15..23));
+
+$code.=<<___;
+.type	gcm_ghash_v8_4x,%function
+.align	4
+gcm_ghash_v8_4x:
+.Lgcm_ghash_v8_4x:
+	vld1.64		{$Xl},[$Xi]		@ load [rotated] Xi
+	vld1.64		{$H-$H2},[$Htbl],#48	@ load twisted H, ..., H^2
+	vmov.i8		$xC2,#0xe1
+	vld1.64		{$H3-$H4},[$Htbl]	@ load twisted H^3, ..., H^4
+	vshl.u64	$xC2,$xC2,#57		@ compose 0xc2.0 constant
+
+	vld1.64		{$I0-$j3},[$inp],#64
+#ifndef __ARMEB__
+	vrev64.8	$Xl,$Xl
+	vrev64.8	$j1,$j1
+	vrev64.8	$j2,$j2
+	vrev64.8	$j3,$j3
+	vrev64.8	$I0,$I0
+#endif
+	vext.8		$I3,$j3,$j3,#8
+	vext.8		$I2,$j2,$j2,#8
+	vext.8		$I1,$j1,$j1,#8
+
+	vpmull.p64	$Yl,$H,$I3		@ H·Ii+3
+	veor		$j3,$j3,$I3
+	vpmull2.p64	$Yh,$H,$I3
+	vpmull.p64	$Ym,$Hhl,$j3
+
+	vpmull.p64	$t0,$H2,$I2		@ H^2·Ii+2
+	veor		$j2,$j2,$I2
+	vpmull2.p64	$I2,$H2,$I2
+	vpmull2.p64	$j2,$Hhl,$j2
+
+	veor		$Yl,$Yl,$t0
+	veor		$Yh,$Yh,$I2
+	veor		$Ym,$Ym,$j2
+
+	vpmull.p64	$j3,$H3,$I1		@ H^3·Ii+1
+	veor		$j1,$j1,$I1
+	vpmull2.p64	$I1,$H3,$I1
+	vpmull.p64	$j1,$H34,$j1
+
+	veor		$Yl,$Yl,$j3
+	veor		$Yh,$Yh,$I1
+	veor		$Ym,$Ym,$j1
+
+	subs		$len,$len,#128
+	b.lo		.Ltail4x
+
+	b		.Loop4x
+
+.align	4
+.Loop4x:
+	veor		$t0,$I0,$Xl
+	 vld1.64	{$I0-$j3},[$inp],#64
+	vext.8		$IN,$t0,$t0,#8
+#ifndef __ARMEB__
+	 vrev64.8	$j1,$j1
+	 vrev64.8	$j2,$j2
+	 vrev64.8	$j3,$j3
+	 vrev64.8	$I0,$I0
+#endif
+
+	vpmull.p64	$Xl,$H4,$IN		@ H^4·(Xi+Ii)
+	veor		$t0,$t0,$IN
+	vpmull2.p64	$Xh,$H4,$IN
+	 vext.8		$I3,$j3,$j3,#8
+	vpmull2.p64	$Xm,$H34,$t0
+
+	veor		$Xl,$Xl,$Yl
+	veor		$Xh,$Xh,$Yh
+	 vext.8		$I2,$j2,$j2,#8
+	veor		$Xm,$Xm,$Ym
+	 vext.8		$I1,$j1,$j1,#8
+
+	vext.8		$t1,$Xl,$Xh,#8		@ Karatsuba post-processing
+	veor		$t2,$Xl,$Xh
+	 vpmull.p64	$Yl,$H,$I3		@ H·Ii+3
+	 veor		$j3,$j3,$I3
+	veor		$Xm,$Xm,$t1
+	 vpmull2.p64	$Yh,$H,$I3
+	veor		$Xm,$Xm,$t2
+	 vpmull.p64	$Ym,$Hhl,$j3
+
+	vpmull.p64	$t2,$Xl,$xC2		@ 1st phase of reduction
+	vmov		$Xh#lo,$Xm#hi		@ Xh|Xm - 256-bit result
+	vmov		$Xm#hi,$Xl#lo		@ Xm is rotated Xl
+	 vpmull.p64	$t0,$H2,$I2		@ H^2·Ii+2
+	 veor		$j2,$j2,$I2
+	 vpmull2.p64	$I2,$H2,$I2
+	veor		$Xl,$Xm,$t2
+	 vpmull2.p64	$j2,$Hhl,$j2
+
+	 veor		$Yl,$Yl,$t0
+	 veor		$Yh,$Yh,$I2
+	 veor		$Ym,$Ym,$j2
+
+	vext.8		$t2,$Xl,$Xl,#8		@ 2nd phase of reduction
+	vpmull.p64	$Xl,$Xl,$xC2
+	 vpmull.p64	$j3,$H3,$I1		@ H^3·Ii+1
+	 veor		$j1,$j1,$I1
+	veor		$t2,$t2,$Xh
+	 vpmull2.p64	$I1,$H3,$I1
+	 vpmull.p64	$j1,$H34,$j1
+
+	veor		$Xl,$Xl,$t2
+	 veor		$Yl,$Yl,$j3
+	 veor		$Yh,$Yh,$I1
+	vext.8		$Xl,$Xl,$Xl,#8
+	 veor		$Ym,$Ym,$j1
+
+	subs		$len,$len,#64
+	b.hs		.Loop4x
+
+.Ltail4x:
+	veor		$t0,$I0,$Xl
+	vext.8		$IN,$t0,$t0,#8
+
+	vpmull.p64	$Xl,$H4,$IN		@ H^4·(Xi+Ii)
+	veor		$t0,$t0,$IN
+	vpmull2.p64	$Xh,$H4,$IN
+	vpmull2.p64	$Xm,$H34,$t0
+
+	veor		$Xl,$Xl,$Yl
+	veor		$Xh,$Xh,$Yh
+	veor		$Xm,$Xm,$Ym
+
+	adds		$len,$len,#64
+	b.eq		.Ldone4x
+
+	cmp		$len,#32
+	b.lo		.Lone
+	b.eq		.Ltwo
+.Lthree:
+	vext.8		$t1,$Xl,$Xh,#8		@ Karatsuba post-processing
+	veor		$t2,$Xl,$Xh
+	veor		$Xm,$Xm,$t1
+	 vld1.64	{$I0-$j2},[$inp]
+	veor		$Xm,$Xm,$t2
+#ifndef	__ARMEB__
+	 vrev64.8	$j1,$j1
+	 vrev64.8	$j2,$j2
+	 vrev64.8	$I0,$I0
+#endif
+
+	vpmull.p64	$t2,$Xl,$xC2		@ 1st phase of reduction
+	vmov		$Xh#lo,$Xm#hi		@ Xh|Xm - 256-bit result
+	vmov		$Xm#hi,$Xl#lo		@ Xm is rotated Xl
+	 vext.8		$I2,$j2,$j2,#8
+	 vext.8		$I1,$j1,$j1,#8
+	veor		$Xl,$Xm,$t2
+
+	 vpmull.p64	$Yl,$H,$I2		@ H·Ii+2
+	 veor		$j2,$j2,$I2
+
+	vext.8		$t2,$Xl,$Xl,#8		@ 2nd phase of reduction
+	vpmull.p64	$Xl,$Xl,$xC2
+	veor		$t2,$t2,$Xh
+	 vpmull2.p64	$Yh,$H,$I2
+	 vpmull.p64	$Ym,$Hhl,$j2
+	veor		$Xl,$Xl,$t2
+	 vpmull.p64	$j3,$H2,$I1		@ H^2·Ii+1
+	 veor		$j1,$j1,$I1
+	vext.8		$Xl,$Xl,$Xl,#8
+
+	 vpmull2.p64	$I1,$H2,$I1
+	veor		$t0,$I0,$Xl
+	 vpmull2.p64	$j1,$Hhl,$j1
+	vext.8		$IN,$t0,$t0,#8
+
+	 veor		$Yl,$Yl,$j3
+	 veor		$Yh,$Yh,$I1
+	 veor		$Ym,$Ym,$j1
+
+	vpmull.p64	$Xl,$H3,$IN		@ H^3·(Xi+Ii)
+	veor		$t0,$t0,$IN
+	vpmull2.p64	$Xh,$H3,$IN
+	vpmull.p64	$Xm,$H34,$t0
+
+	veor		$Xl,$Xl,$Yl
+	veor		$Xh,$Xh,$Yh
+	veor		$Xm,$Xm,$Ym
+	b		.Ldone4x
+
+.align	4
+.Ltwo:
+	vext.8		$t1,$Xl,$Xh,#8		@ Karatsuba post-processing
+	veor		$t2,$Xl,$Xh
+	veor		$Xm,$Xm,$t1
+	 vld1.64	{$I0-$j1},[$inp]
+	veor		$Xm,$Xm,$t2
+#ifndef	__ARMEB__
+	 vrev64.8	$j1,$j1
+	 vrev64.8	$I0,$I0
+#endif
+
+	vpmull.p64	$t2,$Xl,$xC2		@ 1st phase of reduction
+	vmov		$Xh#lo,$Xm#hi		@ Xh|Xm - 256-bit result
+	vmov		$Xm#hi,$Xl#lo		@ Xm is rotated Xl
+	 vext.8		$I1,$j1,$j1,#8
+	veor		$Xl,$Xm,$t2
+
+	vext.8		$t2,$Xl,$Xl,#8		@ 2nd phase of reduction
+	vpmull.p64	$Xl,$Xl,$xC2
+	veor		$t2,$t2,$Xh
+	veor		$Xl,$Xl,$t2
+	vext.8		$Xl,$Xl,$Xl,#8
+
+	 vpmull.p64	$Yl,$H,$I1		@ H·Ii+1
+	 veor		$j1,$j1,$I1
+
+	veor		$t0,$I0,$Xl
+	vext.8		$IN,$t0,$t0,#8
+
+	 vpmull2.p64	$Yh,$H,$I1
+	 vpmull.p64	$Ym,$Hhl,$j1
+
+	vpmull.p64	$Xl,$H2,$IN		@ H^2·(Xi+Ii)
+	veor		$t0,$t0,$IN
+	vpmull2.p64	$Xh,$H2,$IN
+	vpmull2.p64	$Xm,$Hhl,$t0
+
+	veor		$Xl,$Xl,$Yl
+	veor		$Xh,$Xh,$Yh
+	veor		$Xm,$Xm,$Ym
+	b		.Ldone4x
+
+.align	4
+.Lone:
+	vext.8		$t1,$Xl,$Xh,#8		@ Karatsuba post-processing
+	veor		$t2,$Xl,$Xh
+	veor		$Xm,$Xm,$t1
+	 vld1.64	{$I0},[$inp]
+	veor		$Xm,$Xm,$t2
+#ifndef	__ARMEB__
+	 vrev64.8	$I0,$I0
+#endif
+
+	vpmull.p64	$t2,$Xl,$xC2		@ 1st phase of reduction
+	vmov		$Xh#lo,$Xm#hi		@ Xh|Xm - 256-bit result
+	vmov		$Xm#hi,$Xl#lo		@ Xm is rotated Xl
+	veor		$Xl,$Xm,$t2
+
+	vext.8		$t2,$Xl,$Xl,#8		@ 2nd phase of reduction
+	vpmull.p64	$Xl,$Xl,$xC2
+	veor		$t2,$t2,$Xh
+	veor		$Xl,$Xl,$t2
+	vext.8		$Xl,$Xl,$Xl,#8
+
+	veor		$t0,$I0,$Xl
+	vext.8		$IN,$t0,$t0,#8
+
+	vpmull.p64	$Xl,$H,$IN
+	veor		$t0,$t0,$IN
+	vpmull2.p64	$Xh,$H,$IN
+	vpmull.p64	$Xm,$Hhl,$t0
+
+.Ldone4x:
+	vext.8		$t1,$Xl,$Xh,#8		@ Karatsuba post-processing
+	veor		$t2,$Xl,$Xh
+	veor		$Xm,$Xm,$t1
+	veor		$Xm,$Xm,$t2
+
+	vpmull.p64	$t2,$Xl,$xC2		@ 1st phase of reduction
+	vmov		$Xh#lo,$Xm#hi		@ Xh|Xm - 256-bit result
+	vmov		$Xm#hi,$Xl#lo		@ Xm is rotated Xl
+	veor		$Xl,$Xm,$t2
+
+	vext.8		$t2,$Xl,$Xl,#8		@ 2nd phase of reduction
+	vpmull.p64	$Xl,$Xl,$xC2
+	veor		$t2,$t2,$Xh
+	veor		$Xl,$Xl,$t2
+	vext.8		$Xl,$Xl,$Xl,#8
+
+#ifndef __ARMEB__
+	vrev64.8	$Xl,$Xl
+#endif
+	vst1.64		{$Xl},[$Xi]		@ write out Xi
+
+	ret
+.size	gcm_ghash_v8_4x,.-gcm_ghash_v8_4x
+___
+
 }
+}
+
 $code.=<<___;
 .asciz  "GHASH for ARMv8, CRYPTOGAMS by <appro\@openssl.org>"
 .align  2
+#endif
 ___
 
 if ($flavour =~ /64/) {			######## 64-bit code
@@ -359,7 +711,8 @@
 	my $arg=shift;
 
 	$arg =~ m/q([0-9]+)#(lo|hi),\s*q([0-9]+)#(lo|hi)/o &&
-	sprintf	"ins	v%d.d[%d],v%d.d[%d]",$1,($2 eq "lo")?0:1,$3,($4 eq "lo")?0:1;
+	sprintf	"ins	v%d.d[%d],v%d.d[%d]",$1<8?$1:$1+8,($2 eq "lo")?0:1,
+					     $3<8?$3:$3+8,($4 eq "lo")?0:1;
     }
     foreach(split("\n",$code)) {
 	s/cclr\s+([wx])([^,]+),\s*([a-z]+)/csel	$1$2,$1zr,$1$2,$3/o	or
@@ -428,4 +781,4 @@
     }
 }
 
-close STDOUT or die "error closing STDOUT"; # enforce flush
+close STDOUT or die "error closing STDOUT: $!"; # enforce flush
diff --git a/src/crypto/fipsmodule/modes/gcm_nohw.c b/src/crypto/fipsmodule/modes/gcm_nohw.c
index f8618b8..92d5441 100644
--- a/src/crypto/fipsmodule/modes/gcm_nohw.c
+++ b/src/crypto/fipsmodule/modes/gcm_nohw.c
@@ -193,7 +193,7 @@
 #endif  // BORINGSSL_HAS_UINT128
 
 void gcm_init_nohw(u128 Htable[16], const uint64_t Xi[2]) {
-  // We implement GHASH in terms of POLYVAL, as described in RFC8452. This
+  // We implement GHASH in terms of POLYVAL, as described in RFC 8452. This
   // avoids a shift by 1 in the multiplication, needed to account for bit
   // reversal losing a bit after multiplication, that is,
   // rev128(X) * rev128(Y) = rev255(X*Y).
diff --git a/src/crypto/fipsmodule/modes/gcm_test.cc b/src/crypto/fipsmodule/modes/gcm_test.cc
index 031b06c..02ba2d1 100644
--- a/src/crypto/fipsmodule/modes/gcm_test.cc
+++ b/src/crypto/fipsmodule/modes/gcm_test.cc
@@ -125,7 +125,7 @@
       UINT64_C(0x66e94bd4ef8a2c3b),
       UINT64_C(0x884cfa59ca342b2e),
   };
-  static const size_t kBlockCounts[] = {1, 2, 3, 4, 7, 8, 15, 16, 31, 32};
+  static const size_t kBlockCounts[] = {1, 2, 3, 4, 5, 6, 7, 8, 15, 16, 31, 32};
   uint8_t buf[16 * 32];
   OPENSSL_memset(buf, 42, sizeof(buf));
 
diff --git a/src/crypto/fipsmodule/rand/asm/rdrand-x86_64.pl b/src/crypto/fipsmodule/rand/asm/rdrand-x86_64.pl
index e5ff106..ac442a9 100644
--- a/src/crypto/fipsmodule/rand/asm/rdrand-x86_64.pl
+++ b/src/crypto/fipsmodule/rand/asm/rdrand-x86_64.pl
@@ -84,4 +84,4 @@
 .size CRYPTO_rdrand_multiple8_buf,.-CRYPTO_rdrand_multiple8_buf
 ___
 
-close STDOUT or die "error closing STDOUT";	# flush
+close STDOUT or die "error closing STDOUT: $!";	# flush
diff --git a/src/crypto/fipsmodule/rand/rand.c b/src/crypto/fipsmodule/rand/rand.c
index 29c43ae..0fc9fa1 100644
--- a/src/crypto/fipsmodule/rand/rand.c
+++ b/src/crypto/fipsmodule/rand/rand.c
@@ -356,7 +356,7 @@
     int used_cpu;
     rand_get_seed(state, seed, &used_cpu);
 
-    uint8_t personalization[CTR_DRBG_ENTROPY_LEN];
+    uint8_t personalization[CTR_DRBG_ENTROPY_LEN] = {0};
     size_t personalization_len = 0;
 #if defined(OPENSSL_URANDOM)
     // If we used RDRAND, also opportunistically read from the system. This
diff --git a/src/crypto/fipsmodule/rsa/rsa.c b/src/crypto/fipsmodule/rsa/rsa.c
index f6d3640..8f58a10 100644
--- a/src/crypto/fipsmodule/rsa/rsa.c
+++ b/src/crypto/fipsmodule/rsa/rsa.c
@@ -206,6 +206,12 @@
   }
 }
 
+const RSA_PSS_PARAMS *RSA_get0_pss_params(const RSA *rsa) {
+  // We do not support the id-RSASSA-PSS key encoding. If we add support later,
+  // the |maskHash| field should be filled in for OpenSSL compatibility.
+  return NULL;
+}
+
 void RSA_get0_crt_params(const RSA *rsa, const BIGNUM **out_dmp1,
                          const BIGNUM **out_dmq1, const BIGNUM **out_iqmp) {
   if (out_dmp1 != NULL) {
@@ -657,7 +663,8 @@
 }
 
 static int check_mod_inverse(int *out_ok, const BIGNUM *a, const BIGNUM *ainv,
-                             const BIGNUM *m, BN_CTX *ctx) {
+                             const BIGNUM *m, unsigned m_min_bits,
+                             BN_CTX *ctx) {
   if (BN_is_negative(ainv) || BN_cmp(ainv, m) >= 0) {
     *out_ok = 0;
     return 1;
@@ -670,7 +677,7 @@
   BIGNUM *tmp = BN_CTX_get(ctx);
   int ret = tmp != NULL &&
             bn_mul_consttime(tmp, a, ainv, ctx) &&
-            bn_div_consttime(NULL, tmp, tmp, m, ctx);
+            bn_div_consttime(NULL, tmp, tmp, m, m_min_bits, ctx);
   if (ret) {
     *out_ok = BN_is_one(tmp);
   }
@@ -750,10 +757,15 @@
   // simply check that d * e is one mod p-1 and mod q-1. Note d and e were bound
   // by earlier checks in this function.
   if (!bn_usub_consttime(&pm1, key->p, BN_value_one()) ||
-      !bn_usub_consttime(&qm1, key->q, BN_value_one()) ||
-      !bn_mul_consttime(&de, key->d, key->e, ctx) ||
-      !bn_div_consttime(NULL, &tmp, &de, &pm1, ctx) ||
-      !bn_div_consttime(NULL, &de, &de, &qm1, ctx)) {
+      !bn_usub_consttime(&qm1, key->q, BN_value_one())) {
+    OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
+    goto out;
+  }
+  const unsigned pm1_bits = BN_num_bits(&pm1);
+  const unsigned qm1_bits = BN_num_bits(&qm1);
+  if (!bn_mul_consttime(&de, key->d, key->e, ctx) ||
+      !bn_div_consttime(NULL, &tmp, &de, &pm1, pm1_bits, ctx) ||
+      !bn_div_consttime(NULL, &de, &de, &qm1, qm1_bits, ctx)) {
     OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
     goto out;
   }
@@ -772,9 +784,12 @@
 
   if (has_crt_values) {
     int dmp1_ok, dmq1_ok, iqmp_ok;
-    if (!check_mod_inverse(&dmp1_ok, key->e, key->dmp1, &pm1, ctx) ||
-        !check_mod_inverse(&dmq1_ok, key->e, key->dmq1, &qm1, ctx) ||
-        !check_mod_inverse(&iqmp_ok, key->q, key->iqmp, key->p, ctx)) {
+    if (!check_mod_inverse(&dmp1_ok, key->e, key->dmp1, &pm1, pm1_bits, ctx) ||
+        !check_mod_inverse(&dmq1_ok, key->e, key->dmq1, &qm1, qm1_bits, ctx) ||
+        // |p| is odd, so |pm1| and |p| have the same bit width. If they didn't,
+        // we only need a lower bound anyway.
+        !check_mod_inverse(&iqmp_ok, key->q, key->iqmp, key->p, pm1_bits,
+                           ctx)) {
       OPENSSL_PUT_ERROR(RSA, ERR_LIB_BN);
       goto out;
     }
diff --git a/src/crypto/fipsmodule/rsa/rsa_impl.c b/src/crypto/fipsmodule/rsa/rsa_impl.c
index 6dd89b9..a6865c0 100644
--- a/src/crypto/fipsmodule/rsa/rsa_impl.c
+++ b/src/crypto/fipsmodule/rsa/rsa_impl.c
@@ -1262,12 +1262,14 @@
     // values for d.
   } while (BN_cmp(rsa->d, pow2_prime_bits) <= 0);
 
+  assert(BN_num_bits(pm1) == (unsigned)prime_bits);
+  assert(BN_num_bits(qm1) == (unsigned)prime_bits);
   if (// Calculate n.
       !bn_mul_consttime(rsa->n, rsa->p, rsa->q, ctx) ||
       // Calculate d mod (p-1).
-      !bn_div_consttime(NULL, rsa->dmp1, rsa->d, pm1, ctx) ||
+      !bn_div_consttime(NULL, rsa->dmp1, rsa->d, pm1, prime_bits, ctx) ||
       // Calculate d mod (q-1)
-      !bn_div_consttime(NULL, rsa->dmq1, rsa->d, qm1, ctx)) {
+      !bn_div_consttime(NULL, rsa->dmq1, rsa->d, qm1, prime_bits, ctx)) {
     goto bn_err;
   }
   bn_set_minimal_width(rsa->n);
diff --git a/src/crypto/fipsmodule/sha/asm/sha1-586.pl b/src/crypto/fipsmodule/sha/asm/sha1-586.pl
index bf42961..90c2361 100644
--- a/src/crypto/fipsmodule/sha/asm/sha1-586.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha1-586.pl
@@ -1483,4 +1483,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/sha/asm/sha1-armv4-large.pl b/src/crypto/fipsmodule/sha/asm/sha1-armv4-large.pl
index f11280a..2998b89 100644
--- a/src/crypto/fipsmodule/sha/asm/sha1-armv4-large.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha1-armv4-large.pl
@@ -742,4 +742,4 @@
 	print $_,$/;
 }
 
-close STDOUT or die "error closing STDOUT"; # enforce flush
+close STDOUT or die "error closing STDOUT: $!"; # enforce flush
diff --git a/src/crypto/fipsmodule/sha/asm/sha1-armv8.pl b/src/crypto/fipsmodule/sha/asm/sha1-armv8.pl
index 51e326d..25e5234 100644
--- a/src/crypto/fipsmodule/sha/asm/sha1-armv8.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha1-armv8.pl
@@ -359,4 +359,4 @@
 	print $_,"\n";
 }
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/sha/asm/sha1-x86_64.pl b/src/crypto/fipsmodule/sha/asm/sha1-x86_64.pl
index 76e4077..5126025 100755
--- a/src/crypto/fipsmodule/sha/asm/sha1-x86_64.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha1-x86_64.pl
@@ -2114,4 +2114,4 @@
 
 	print $_,"\n";
 }
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/sha/asm/sha256-586.pl b/src/crypto/fipsmodule/sha/asm/sha256-586.pl
index 240a604..99b4cf1 100644
--- a/src/crypto/fipsmodule/sha/asm/sha256-586.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha256-586.pl
@@ -1287,4 +1287,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/sha/asm/sha256-armv4.pl b/src/crypto/fipsmodule/sha/asm/sha256-armv4.pl
index a2eccf8..0f459e0 100644
--- a/src/crypto/fipsmodule/sha/asm/sha256-armv4.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha256-armv4.pl
@@ -737,4 +737,4 @@
 	print $_,"\n";
 }
 
-close STDOUT or die "error closing STDOUT"; # enforce flush
+close STDOUT or die "error closing STDOUT: $!"; # enforce flush
diff --git a/src/crypto/fipsmodule/sha/asm/sha512-586.pl b/src/crypto/fipsmodule/sha/asm/sha512-586.pl
index 01acf67..1aee14d 100644
--- a/src/crypto/fipsmodule/sha/asm/sha512-586.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha512-586.pl
@@ -922,4 +922,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/sha/asm/sha512-armv4.pl b/src/crypto/fipsmodule/sha/asm/sha512-armv4.pl
index e15b1d9..185635f 100644
--- a/src/crypto/fipsmodule/sha/asm/sha512-armv4.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha512-armv4.pl
@@ -672,4 +672,4 @@
 close SELF;
 
 print $code;
-close STDOUT or die "error closing STDOUT"; # enforce flush
+close STDOUT or die "error closing STDOUT: $!"; # enforce flush
diff --git a/src/crypto/fipsmodule/sha/asm/sha512-armv8.pl b/src/crypto/fipsmodule/sha/asm/sha512-armv8.pl
index 2971b74..ae803a9 100644
--- a/src/crypto/fipsmodule/sha/asm/sha512-armv8.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha512-armv8.pl
@@ -459,4 +459,4 @@
 	print $_,"\n";
 }
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/fipsmodule/sha/asm/sha512-x86_64.pl b/src/crypto/fipsmodule/sha/asm/sha512-x86_64.pl
index 8c5a5f3..61f67cb 100755
--- a/src/crypto/fipsmodule/sha/asm/sha512-x86_64.pl
+++ b/src/crypto/fipsmodule/sha/asm/sha512-x86_64.pl
@@ -2082,4 +2082,4 @@
 
 	print $_,"\n";
 }
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/hmac_extra/hmac_tests.txt b/src/crypto/hmac_extra/hmac_tests.txt
index 53f3f8f..1d7efd2 100644
--- a/src/crypto/hmac_extra/hmac_tests.txt
+++ b/src/crypto/hmac_extra/hmac_tests.txt
@@ -5,7 +5,7 @@
 Input = "More text test vectors to stuff up EBCDIC machines :-)"
 Output = e9139d1e6ee064ef8cf514fc7dc83e86
 
-# HMAC tests from RFC2104
+# HMAC tests from RFC 2104
 HMAC = MD5
 Key = 0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
 Input = "Hi There"
diff --git a/src/crypto/hrss/asm/poly_rq_mul.S b/src/crypto/hrss/asm/poly_rq_mul.S
index 53ce47c..c37d7d0 100644
--- a/src/crypto/hrss/asm/poly_rq_mul.S
+++ b/src/crypto/hrss/asm/poly_rq_mul.S
@@ -26,23 +26,6 @@
 # This file was generated by poly_rq_mul.py
 .text
 .align 32
-mask_low9words:
-.word 0xffff
-.word 0xffff
-.word 0xffff
-.word 0xffff
-.word 0xffff
-.word 0xffff
-.word 0xffff
-.word 0xffff
-.word 0xffff
-.word 0x0
-.word 0x0
-.word 0x0
-.word 0x0
-.word 0x0
-.word 0x0
-.word 0x0
 const3:
 .word 3
 .word 3
@@ -746,8 +729,20 @@
 vmovdqu 1120(%rsi), %ymm4
 vmovdqu 1208(%rsi), %ymm5
 vmovdqu 1296(%rsi), %ymm6
-vmovdqu 1384(%rsi), %ymm7
-vpand mask_low9words(%rip), %ymm7, %ymm7
+
+# Only 18 bytes more can be read, but vmovdqu reads 32.
+# Copy 18 bytes to the red zone and zero pad to 32 bytes.
+xor %r9, %r9
+movq %r9, -16(%rsp)
+movq %r9, -8(%rsp)
+movq 1384(%rsi), %r9
+movq %r9, -32(%rsp)
+movq 1384+8(%rsi), %r9
+movq %r9, -24(%rsp)
+movw 1384+16(%rsi), %r9w
+movw %r9w, -16(%rsp)
+vmovdqu -32(%rsp), %ymm7
+
 vmovdqu 416(%rsi), %ymm8
 vmovdqu 504(%rsi), %ymm9
 vmovdqu 592(%rsi), %ymm10
@@ -1341,8 +1336,20 @@
 vmovdqu 1120(%rdx), %ymm4
 vmovdqu 1208(%rdx), %ymm5
 vmovdqu 1296(%rdx), %ymm6
-vmovdqu 1384(%rdx), %ymm7
-vpand mask_low9words(%rip), %ymm7, %ymm7
+
+# Only 18 bytes more can be read, but vmovdqu reads 32.
+# Copy 18 bytes to the red zone and zero pad to 32 bytes.
+xor %r9, %r9
+movq %r9, -16(%rsp)
+movq %r9, -8(%rsp)
+movq 1384(%rdx), %r9
+movq %r9, -32(%rsp)
+movq 1384+8(%rdx), %r9
+movq %r9, -24(%rsp)
+movw 1384+16(%rdx), %r9w
+movw %r9w, -16(%rsp)
+vmovdqu -32(%rsp), %ymm7
+
 vmovdqu 416(%rdx), %ymm8
 vmovdqu 504(%rdx), %ymm9
 vmovdqu 592(%rdx), %ymm10
@@ -8295,7 +8302,20 @@
 vmovdqa %ymm8, 2880(%r8)
 vmovdqu 680(%rdi), %ymm8
 vmovdqu 1032(%rdi), %ymm10
-vmovdqu 1384(%rdi), %ymm2
+
+# Only 18 bytes can be read at 1384, but vmovdqu reads 32.
+# Copy 18 bytes to the red zone and zero pad to 32 bytes.
+xor %r9, %r9
+movq %r9, -16(%rsp)
+movq %r9, -8(%rsp)
+movq 1384(%rdi), %r9
+movq %r9, -32(%rsp)
+movq 1384+8(%rdi), %r9
+movq %r9, -24(%rsp)
+movw 1384+16(%rdi), %r9w
+movw %r9w, -16(%rsp)
+vmovdqu -32(%rsp), %ymm2
+
 vpaddw %ymm5, %ymm8, %ymm5
 vpaddw %ymm6, %ymm10, %ymm6
 vpaddw %ymm4, %ymm2, %ymm4
diff --git a/src/crypto/mem.c b/src/crypto/mem.c
index 883439b..8988937 100644
--- a/src/crypto/mem.c
+++ b/src/crypto/mem.c
@@ -324,22 +324,15 @@
 }
 
 char *OPENSSL_strndup(const char *str, size_t size) {
-  char *ret;
-  size_t alloc_size;
-
-  if (str == NULL) {
-    return NULL;
-  }
-
   size = OPENSSL_strnlen(str, size);
 
-  alloc_size = size + 1;
+  size_t alloc_size = size + 1;
   if (alloc_size < size) {
     // overflow
     OPENSSL_PUT_ERROR(CRYPTO, ERR_R_MALLOC_FAILURE);
     return NULL;
   }
-  ret = OPENSSL_malloc(alloc_size);
+  char *ret = OPENSSL_malloc(alloc_size);
   if (ret == NULL) {
     OPENSSL_PUT_ERROR(CRYPTO, ERR_R_MALLOC_FAILURE);
     return NULL;
diff --git a/src/crypto/obj/objects.txt b/src/crypto/obj/objects.txt
index b88342d..25c2430 100644
--- a/src/crypto/obj/objects.txt
+++ b/src/crypto/obj/objects.txt
@@ -364,7 +364,7 @@
 rsadsi 2 6		:			: hmacWithMD5
 rsadsi 2 7		:			: hmacWithSHA1
 
-# From RFC4231
+# From RFC 4231
 rsadsi 2 8		:			: hmacWithSHA224
 rsadsi 2 9		:			: hmacWithSHA256
 rsadsi 2 10		:			: hmacWithSHA384
@@ -492,7 +492,7 @@
 id-kp 7			: ipsecUser		: IPSec User
 !Cname time-stamp
 id-kp 8			: timeStamping		: Time Stamping
-# From OCSP spec RFC2560
+# From OCSP spec RFC 2560
 !Cname OCSP-sign
 id-kp 9			: OCSPSigning		: OCSP Signing
 id-kp 10		: DVCS			: dvcs
@@ -776,7 +776,7 @@
 !Cname no-rev-avail
 id-ce 56		: noRevAvail		: X509v3 No Revocation Available
 
-# From RFC5280
+# From RFC 5280
 ext-key-usage 0		: anyExtendedKeyUsage	: Any Extended Key Usage
 
 
@@ -820,7 +820,7 @@
 internet 5		: security		: Security
 internet 6		: snmpv2		: SNMPv2
 # Documents refer to "internet 7" as "mail". This however leads to ambiguities
-# with RFC2798, Section 9.1.3, where "mail" is defined as the short name for
+# with RFC 2798, Section 9.1.3, where "mail" is defined as the short name for
 # rfc822Mailbox. The short name is therefore here left out for a reason.
 # Subclasses of "mail", e.g. "MIME MHS" don't consitute a problem, as
 # references are realized via long name "Mail" (with capital M).
@@ -1313,7 +1313,7 @@
 1 3 36 3 3 2 8 1 1 13 : brainpoolP512r1
 1 3 36 3 3 2 8 1 1 14 : brainpoolP512t1
 
-# ECDH schemes from RFC5753
+# ECDH schemes from RFC 5753
 !Alias x9-63-scheme 1 3 133 16 840 63 0
 !Alias secg-scheme certicom-arc 1
 
diff --git a/src/crypto/pem/pem_all.c b/src/crypto/pem/pem_all.c
index 6b40883..e419774 100644
--- a/src/crypto/pem/pem_all.c
+++ b/src/crypto/pem/pem_all.c
@@ -157,8 +157,6 @@
     return pkey_get_rsa(pktmp, rsa);
 }
 
-#ifndef OPENSSL_NO_FP_API
-
 RSA *PEM_read_RSAPrivateKey(FILE *fp, RSA **rsa, pem_password_cb *cb, void *u)
 {
     EVP_PKEY *pktmp;
@@ -166,8 +164,6 @@
     return pkey_get_rsa(pktmp, rsa);
 }
 
-#endif
-
 IMPLEMENT_PEM_write_cb_const(RSAPrivateKey, RSA, PEM_STRING_RSA,
                              RSAPrivateKey)
 
@@ -205,7 +201,6 @@
                              DSAPrivateKey)
 
     IMPLEMENT_PEM_rw(DSA_PUBKEY, DSA, PEM_STRING_PUBLIC, DSA_PUBKEY)
-# ifndef OPENSSL_NO_FP_API
 DSA *PEM_read_DSAPrivateKey(FILE *fp, DSA **dsa, pem_password_cb *cb, void *u)
 {
     EVP_PKEY *pktmp;
@@ -213,8 +208,6 @@
     return pkey_get_dsa(pktmp, dsa); /* will free pktmp */
 }
 
-# endif
-
 IMPLEMENT_PEM_rw_const(DSAparams, DSA, PEM_STRING_DSAPARAMS, DSAparams)
 #endif
 static EC_KEY *pkey_get_eckey(EVP_PKEY *key, EC_KEY **eckey)
@@ -245,7 +238,6 @@
                        ECPrivateKey)
 
     IMPLEMENT_PEM_rw(EC_PUBKEY, EC_KEY, PEM_STRING_PUBLIC, EC_PUBKEY)
-#ifndef OPENSSL_NO_FP_API
 EC_KEY *PEM_read_ECPrivateKey(FILE *fp, EC_KEY **eckey, pem_password_cb *cb,
                               void *u)
 {
@@ -254,7 +246,6 @@
     return pkey_get_eckey(pktmp, eckey); /* will free pktmp */
 }
 
-#endif
 
 IMPLEMENT_PEM_write_const(DHparams, DH, PEM_STRING_DHPARAMS, DHparams)
 
diff --git a/src/crypto/pem/pem_info.c b/src/crypto/pem/pem_info.c
index 1cda35b..3a1d0cc 100644
--- a/src/crypto/pem/pem_info.c
+++ b/src/crypto/pem/pem_info.c
@@ -70,7 +70,6 @@
 #include <openssl/rsa.h>
 #include <openssl/x509.h>
 
-#ifndef OPENSSL_NO_FP_API
 STACK_OF(X509_INFO) *PEM_X509_INFO_read(FILE *fp, STACK_OF(X509_INFO) *sk,
                                         pem_password_cb *cb, void *u)
 {
@@ -83,7 +82,6 @@
     BIO_free(b);
     return ret;
 }
-#endif
 
 enum parse_result_t {
     parse_ok,
diff --git a/src/crypto/pem/pem_lib.c b/src/crypto/pem/pem_lib.c
index 00c0e0a..747d694 100644
--- a/src/crypto/pem/pem_lib.c
+++ b/src/crypto/pem/pem_lib.c
@@ -117,7 +117,6 @@
     buf[j + i * 2 + 1] = '\0';
 }
 
-#ifndef OPENSSL_NO_FP_API
 void *PEM_ASN1_read(d2i_of_void *d2i, const char *name, FILE *fp, void **x,
                     pem_password_cb *cb, void *u)
 {
@@ -130,7 +129,6 @@
     BIO_free(b);
     return ret;
 }
-#endif
 
 static int check_pem(const char *nm, const char *name)
 {
@@ -252,7 +250,6 @@
     return ret;
 }
 
-#ifndef OPENSSL_NO_FP_API
 int PEM_ASN1_write(i2d_of_void *i2d, const char *name, FILE *fp,
                    void *x, const EVP_CIPHER *enc, unsigned char *kstr,
                    int klen, pem_password_cb *callback, void *u)
@@ -266,7 +263,6 @@
     BIO_free(b);
     return ret;
 }
-#endif
 
 int PEM_ASN1_write_bio(i2d_of_void *i2d, const char *name, BIO *bp,
                        void *x, const EVP_CIPHER *enc, unsigned char *kstr,
@@ -507,7 +503,6 @@
     return (1);
 }
 
-#ifndef OPENSSL_NO_FP_API
 int PEM_write(FILE *fp, const char *name, const char *header,
               const unsigned char *data, long len)
 {
@@ -520,7 +515,6 @@
     BIO_free(b);
     return (ret);
 }
-#endif
 
 int PEM_write_bio(BIO *bp, const char *name, const char *header,
                   const unsigned char *data, long len)
@@ -578,7 +572,6 @@
     return (0);
 }
 
-#ifndef OPENSSL_NO_FP_API
 int PEM_read(FILE *fp, char **name, char **header, unsigned char **data,
              long *len)
 {
@@ -591,7 +584,6 @@
     BIO_free(b);
     return (ret);
 }
-#endif
 
 int PEM_read_bio(BIO *bp, char **name, char **header, unsigned char **data,
                  long *len)
diff --git a/src/crypto/pem/pem_pk8.c b/src/crypto/pem/pem_pk8.c
index 819a329..8a1f040 100644
--- a/src/crypto/pem/pem_pk8.c
+++ b/src/crypto/pem/pem_pk8.c
@@ -190,7 +190,6 @@
     return ret;
 }
 
-#ifndef OPENSSL_NO_FP_API
 
 int i2d_PKCS8PrivateKey_fp(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc,
                            char *kstr, int klen, pem_password_cb *cb, void *u)
@@ -248,7 +247,6 @@
     return ret;
 }
 
-#endif
 
 IMPLEMENT_PEM_rw(PKCS8, X509_SIG, PEM_STRING_PKCS8, X509_SIG)
 
diff --git a/src/crypto/pem/pem_pkey.c b/src/crypto/pem/pem_pkey.c
index 5776535..48d8c96 100644
--- a/src/crypto/pem/pem_pkey.c
+++ b/src/crypto/pem/pem_pkey.c
@@ -150,7 +150,6 @@
     return PEM_write_bio_PKCS8PrivateKey(bp, x, enc, (char *)kstr, klen, cb, u);
 }
 
-#ifndef OPENSSL_NO_FP_API
 EVP_PKEY *PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x, pem_password_cb *cb,
                               void *u)
 {
@@ -178,7 +177,6 @@
     return ret;
 }
 
-#endif
 
 /* Transparently read in PKCS#3 or X9.42 DH parameters */
 
@@ -203,7 +201,6 @@
     return ret;
 }
 
-#ifndef OPENSSL_NO_FP_API
 DH *PEM_read_DHparams(FILE *fp, DH **x, pem_password_cb *cb, void *u)
 {
     BIO *b = BIO_new_fp(fp, BIO_NOCLOSE);
@@ -215,4 +212,3 @@
     BIO_free(b);
     return ret;
 }
-#endif
diff --git a/src/crypto/perlasm/arm-xlate.pl b/src/crypto/perlasm/arm-xlate.pl
index 576f682..c000e02 100755
--- a/src/crypto/perlasm/arm-xlate.pl
+++ b/src/crypto/perlasm/arm-xlate.pl
@@ -245,4 +245,4 @@
 # See https://www.airs.com/blog/archives/518.
 print ".section\t.note.GNU-stack,\"\",\%progbits\n" if ($flavour =~ /linux/);
 
-close STDOUT;
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/perlasm/ppc-xlate.pl b/src/crypto/perlasm/ppc-xlate.pl
index 4f22c36..d62ae01 100644
--- a/src/crypto/perlasm/ppc-xlate.pl
+++ b/src/crypto/perlasm/ppc-xlate.pl
@@ -314,4 +314,4 @@
 # See https://www.airs.com/blog/archives/518.
 print ".section\t.note.GNU-stack,\"\",\@progbits\n" if ($flavour =~ /linux/);
 
-close STDOUT;
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/perlasm/x86_64-xlate.pl b/src/crypto/perlasm/x86_64-xlate.pl
index 4a41a24..04abd0b 100755
--- a/src/crypto/perlasm/x86_64-xlate.pl
+++ b/src/crypto/perlasm/x86_64-xlate.pl
@@ -1264,7 +1264,7 @@
 # See https://www.airs.com/blog/archives/518.
 print ".section\t.note.GNU-stack,\"\",\@progbits\n" if ($elf);
 
-close STDOUT;
+close STDOUT or die "error closing STDOUT: $!";
 
 #################################################
 # Cross-reference x86_64 ABI "card"
diff --git a/src/crypto/pkcs7/internal.h b/src/crypto/pkcs7/internal.h
index 9541bea..5ee8e8a 100644
--- a/src/crypto/pkcs7/internal.h
+++ b/src/crypto/pkcs7/internal.h
@@ -32,14 +32,23 @@
 // NULL.
 int pkcs7_parse_header(uint8_t **der_bytes, CBS *out, CBS *cbs);
 
-// pkcs7_bundle writes a PKCS#7, SignedData structure to |out| and then calls
-// |cb| with a CBB to which certificate or CRL data can be written, and the
-// opaque context pointer, |arg|. The callback can return zero to indicate an
-// error.
+// pkcs7_add_signed_data writes a PKCS#7, SignedData structure to |out|. While
+// doing so it makes callbacks to let the caller fill in parts of the structure.
+// All callbacks are ignored if NULL and return one on success or zero on error.
 //
-// pkcs7_bundle returns one on success or zero on error.
-int pkcs7_bundle(CBB *out, int (*cb)(CBB *out, const void *arg),
-                 const void *arg);
+//   digest_algos_cb: may write AlgorithmIdentifiers into the given CBB, which
+//       is a SET of digest algorithms.
+//   cert_crl_cb: may write the |certificates| or |crls| fields.
+//       (See https://datatracker.ietf.org/doc/html/rfc2315#section-9.1)
+//   signer_infos_cb: may write the contents of the |signerInfos| field.
+//       (See https://datatracker.ietf.org/doc/html/rfc2315#section-9.1)
+//
+// pkcs7_add_signed_data returns one on success or zero on error.
+int pkcs7_add_signed_data(CBB *out,
+                          int (*digest_algos_cb)(CBB *out, const void *arg),
+                          int (*cert_crl_cb)(CBB *out, const void *arg),
+                          int (*signer_infos_cb)(CBB *out, const void *arg),
+                          const void *arg);
 
 
 #if defined(__cplusplus)
diff --git a/src/crypto/pkcs7/pkcs7.c b/src/crypto/pkcs7/pkcs7.c
index 1d0b139..6be5a07 100644
--- a/src/crypto/pkcs7/pkcs7.c
+++ b/src/crypto/pkcs7/pkcs7.c
@@ -131,8 +131,11 @@
   return ret;
 }
 
-int pkcs7_bundle(CBB *out, int (*cb)(CBB *out, const void *arg),
-                 const void *arg) {
+int pkcs7_add_signed_data(CBB *out,
+                          int (*digest_algos_cb)(CBB *out, const void *arg),
+                          int (*cert_crl_cb)(CBB *out, const void *arg),
+                          int (*signer_infos_cb)(CBB *out, const void *arg),
+                          const void *arg) {
   CBB outer_seq, oid, wrapped_seq, seq, version_bytes, digest_algos_set,
       content_info, signer_infos;
 
@@ -147,11 +150,13 @@
       !CBB_add_asn1(&seq, &version_bytes, CBS_ASN1_INTEGER) ||
       !CBB_add_u8(&version_bytes, 1) ||
       !CBB_add_asn1(&seq, &digest_algos_set, CBS_ASN1_SET) ||
+      (digest_algos_cb != NULL && !digest_algos_cb(&digest_algos_set, arg)) ||
       !CBB_add_asn1(&seq, &content_info, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1(&content_info, &oid, CBS_ASN1_OBJECT) ||
       !CBB_add_bytes(&oid, kPKCS7Data, sizeof(kPKCS7Data)) ||
-      !cb(&seq, arg) ||
-      !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET)) {
+      (cert_crl_cb != NULL && !cert_crl_cb(&seq, arg)) ||
+      !CBB_add_asn1(&seq, &signer_infos, CBS_ASN1_SET) ||
+      (signer_infos_cb != NULL && !signer_infos_cb(&signer_infos, arg))) {
     return 0;
   }
 
diff --git a/src/crypto/pkcs7/pkcs7_test.cc b/src/crypto/pkcs7/pkcs7_test.cc
index 8e14603..9cca175 100644
--- a/src/crypto/pkcs7/pkcs7_test.cc
+++ b/src/crypto/pkcs7/pkcs7_test.cc
@@ -775,3 +775,201 @@
   check_order(crl1.get(), crl2.get());
   check_order(crl2.get(), crl1.get());
 }
+
+TEST(PKCS7Test, KernelModuleSigning) {
+  // Sign a message with the same call that the Linux kernel's sign-file.c
+  // makes.
+  static const char kCert[] = R"(
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIURVkPzF/4dwy7419Qk75uhIuyf0EwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA5MjExOTIyMTJaFw0yMjA5
+MjExOTIyMTJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQC1+MOn+BopcEVR4QMvjXdAxGkWFllXyQFDToL+qOiP
+RU1yN7C8KCtkbOAFttJIO4O/i0iZ7KqYbnmB6YUA/ONAcakocnrdoESgRJcVMeAx
+Dk/11OtMF5yIfeOOO/TUeVNmAUaT63gFbKy/adpqhzJtOv9BBl5VcYNGGSE+0wtb
+mjpmNsxunEQR1KLDc97fGYHeRfKoSyrCIEE8IaAEpKGR2Sku3v9Jwh7RpjupgiUA
+kH6pJk7VMZm5vl2wFjYvfysgjeN5ZtsxFDMaPYZStpxMxpNd5C9DsO2Ljp5NMpGf
+NGmG4ZqiaQg8z2cIM6ESmN1zDJdUh5IXed1fOxBZD/poUFH0wDRFWnvzlaPmjJEF
+rYLMK8svnE5nEQp9vu93ISFBx7cofs+niMaUXPEqaRSqruifN2M1it3kOf/8YZl1
+vurs+VtHD6nOJo6bd11+37aBidIB/BaWnzLrDmSTcPFa1tkTHwoLqc9+jThTq9jZ
+6w3lAMPpsoenyD19UmQB589+4kNp2SIO/TtzVQCGgQPXE2jDCl6G9aIPMkfvpPZK
+4THVil3WQRCFYnYdDO4HQXo2ZuC4RiqgY5ygfeoL+fa9k383lgxxAHQLS7xsbaVB
+40RmfdbdevgPYIwZNNO78ddRmMdSv6IknSW9gydGzY//btY+t1SWcBZWzn1Ewq8g
+2QIDAQABo1MwUTAdBgNVHQ4EFgQUotZD9ajEvnQYVezIWzcW4pzvMcUwHwYDVR0j
+BBgwFoAUotZD9ajEvnQYVezIWzcW4pzvMcUwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEAqCe42PIWoyLDx9bR+5cSp99N5xo5lLiSLtWx2emDbZB2
+AunqKYeEgIV+TWNF2w1SZ/ckFgV7SlL2Yl73N/veSNRfNAnpjLksGDFpdJb7YXrx
+cUvxdy1mr8oau6J7PC9JGjBTBrnhqwCQX1FtcAxODKll2Lsfuj6+bdC3rCK7KBEo
+ENamMJZIeo8lRP9qFF2xwCEzZjRv2zvB6O5o9045aTUcdCrwUfKE2sqY6EXRzFTC
+waK0HRCd1FLv9omhz/Ug5PMHP4d6MZfnAbFm+AzAhnpkrk/9TJYSOoNTNLWsuqhp
+dN0rKqiFWv1zIwfknXvTh1P1Ap+G5jffAca0zWUH1oKjE7ZZioSsaZ6gySnD8+WQ
+TPbOYtG+n0mhCH1TrU8Dqi3rd8g5IbC8loYLRH94QtodOnevD4Qo9Orfrsr8hGOW
+ABespanZArhoQ03DAtpNhtHm2NWJQF2uHNqcTrkq0omqZBTbMD1GKMBujoNooAUu
+w51U9r+RycPJTFqEGHb0nd7EjoyXEXtuX1Ld5fTZjQ9SszmQKQ8w3lHqRGNlkSiO
+e3IOOq2ruXmq1jykxpmi82IcTRUE8TZBfL/yz0nxpHKAYC1VwMezrkgZDGz4npxf
+1z2+qd58xU6/jsf7/+3xdPFubeEJujdbCkWQsQC5Rzm48zDWGq/pyzFji44K3TA=
+-----END CERTIFICATE-----
+)";
+
+  static const char kKey[] = R"(
+-----BEGIN PRIVATE KEY-----
+MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQC1+MOn+BopcEVR
+4QMvjXdAxGkWFllXyQFDToL+qOiPRU1yN7C8KCtkbOAFttJIO4O/i0iZ7KqYbnmB
+6YUA/ONAcakocnrdoESgRJcVMeAxDk/11OtMF5yIfeOOO/TUeVNmAUaT63gFbKy/
+adpqhzJtOv9BBl5VcYNGGSE+0wtbmjpmNsxunEQR1KLDc97fGYHeRfKoSyrCIEE8
+IaAEpKGR2Sku3v9Jwh7RpjupgiUAkH6pJk7VMZm5vl2wFjYvfysgjeN5ZtsxFDMa
+PYZStpxMxpNd5C9DsO2Ljp5NMpGfNGmG4ZqiaQg8z2cIM6ESmN1zDJdUh5IXed1f
+OxBZD/poUFH0wDRFWnvzlaPmjJEFrYLMK8svnE5nEQp9vu93ISFBx7cofs+niMaU
+XPEqaRSqruifN2M1it3kOf/8YZl1vurs+VtHD6nOJo6bd11+37aBidIB/BaWnzLr
+DmSTcPFa1tkTHwoLqc9+jThTq9jZ6w3lAMPpsoenyD19UmQB589+4kNp2SIO/Ttz
+VQCGgQPXE2jDCl6G9aIPMkfvpPZK4THVil3WQRCFYnYdDO4HQXo2ZuC4RiqgY5yg
+feoL+fa9k383lgxxAHQLS7xsbaVB40RmfdbdevgPYIwZNNO78ddRmMdSv6IknSW9
+gydGzY//btY+t1SWcBZWzn1Ewq8g2QIDAQABAoICAFQ/liZAIaypxA5ChP0RG/Mq
+fBSzyC1ybFlDEjbg8LrUNST6T6LtXhmipp0+pWC33SljTPumrNzh2POir+djLbt6
+Y/zL88KEHwGsf95aNxe/Lpn8N+wEyn4O+rmxXIq6mTgSwyBc1jZ8uAXu9iZ37YrQ
+07jBQA+C/GoJ3HB/uTRx1TPZjxBu3Lz8m1auYLMd1hiYfd4Y3vT9hfZXAwTjS8KA
+riZ7K+p0K1yY/+pczNDUFTAvAjSGQEvUrP+HaRLYZ5ks1/IvArBYT8iIT5Yf4YFS
+NowzxwYp9fC02OmYzf7Nf0XpUXR7+EpfI66SaLJ5f51yaOXD1olz7F/YsprpYN7+
+oQd7EKar1bY3ROM6naUZtsIoEblg6B0mkyHWQgZ9wZRbcN7Zmuc/tIpLat7se+MP
+xQeAcH4Yhgnd2G6EELpmJBcyJ0Ss3atpI1eenU+ly++L4XbDQH9norKQ1PEDXYbV
+XMAV5uIsplBL7hGIa6/u/cRMM5eN3TJchtzIHFhq9+ENMvjTOfo0bflcYR+tNxGD
+6agWlD/Apedaapu/3Xp7ekyCiy/YTIwgT4U3rprYplzFM5HbzYtZ9ThxUm+CmnYj
+ZSCKiLoaQq+11/M9zH1Je0uJP5aK0CxOii2LVRXZYaQfbDtiHNWUSM7uPIZMnDgE
+IPTpl9CEfk7U3pgiUlg5AoIBAQDjUeikACPaRuewIjLqwTT2/j+ZO+/dCG4atFZa
+W+gdZ1NVDCdowQPBZWg6bqejRr1MvORg2L83kqZDQjaT9y59qxsFhXCy26xKp7aP
+Z4pEvUQmQnnf3RYHk3EBtOHyyMetTaghTGzL3MlPGo3uGbCiYtVoPKXZXGWeiOFN
+s9RNDh/7m6harB2bmX2cK+QPdJ1roVBXQDLkjh2mvLnC5vrsw81GWSkbWQpYmnVi
+YdLhytM+UTYjTrSugtrKk9e2KOFf2uR8PVaPeINEM4uubxW5YUy6gwF8ePtWYAtZ
+Skw3kdBdShhGzHORSY3NsRTJZL6AUdkhHYFTl/rlfj1WXsdnAoIBAQDM7i0u2T+E
+HmroTGiQAIRUEwUZQFDRkcEnM75jpkQT39jXF+zmhjzS1slJF2x0E0jUBV0juVWh
+mz1kHjTMV0j3/mvCeVv0iTcdIbHYRtTwmOjzkwTsZGh6T7okYck3KexRjpyhPpcX
+hOHOPJKS/muG0ZuaJjTEbJOzrSPU0rt0ppL7nOwd5jIOoGAciWiP17G1Lyyitrv4
+mKBK6mFQQWjAgEGy3jvBocbUo7Qo8Aucm6Y4eF1fUyC/X07RBzERHS4TuM+AQlDN
+T+LgTgcwTjE+Nzow2WMwCIbhVQqFRScuWqcJ6NQ6S/dV0R+aGJ90Ey+DtiZ9N9uV
+j0omAGvM8u2/AoIBADXF94FsIw8MfNw2itLrl2riJAtMmWYxC1K33EGNwi/KdHUG
+5f+qwQerxGcmK/O81STk/iVGwJ0VzMzWSfDgpRfHNSIuOcWln3EdkVsFBDlUiF2A
+ljH1q7NpFm9v6Y80HcAKQb52xLnI5boXrwFnBFi1hoQc7KKpb8R73sgxxQPhVoF/
+hejFFE/tlEAwRce+L0r5ovaw0hks4SjDNjI7z5nYi6ObjdTRUFg7WY9HUspk32m7
+blIV2Tn67GTFal7F9uJk9m3JWMOhn3OvudguoPX0ZWEtgll+iP4axDSAFd2DWcXn
+tCxzStdQjgHdZOxrL4FNW06xGxm6Nvi4zyuySfsCggEAOuIpC3ATBxRyZYMm/FGZ
+tEquyV2omz8FQA1nJFzu7MMCHHPcdzSVH4Pl3GGloQi1gW51H8GuMDxZ/H2NcDWY
+WuG49u1GFdKjinRXFKztnKBjNzHEVWRYfOSRuMh8N6SNKbYPnWlNos1k0IypFSGT
+pe5uhnF58gK8wgD67bkLce43B6NEWSb+tSMx2qFE8SfqAQSoD6zv//NjA4OrKJNS
+1RVFS279vpqMdib/qk+nFn3G2i0Dr1NEcpihHgCyAZff2Hze6pyjeQr+RrNE74VY
+MudNiiG8lV2t2+tClZ6ULoaPvpIvAP04+WiYav+uOX0VxwO8tXgqWSQOCzNNxlr7
+IwKCAQA7odNjE6Sc2qiecrOu13kEi3gT0heshIyZ0XhePrS1vgHfCouIRvNMw4FT
+45ZZUFDSdOxhrew5GuMeLvo2YILBjmkX3UqTojQMbur7FcGH8/P0Sm0f20Vc06oS
+sQF5Ji4LSyf6t9oQKePjFIGoIc6pf6BXJZYP4rBnzQzUQjH2yzDYDY3TuV7bFJJU
+DcSTGM6nP0fRMmgBtB14o7A6Gsy6X/N2ElgbvWT8YhmUC6H8DIzmZwHRKaG6C6g5
+eEjuAYenYNM4jxeteC1neUDIdGxH/BA7JrAqcGaN9GT+R47YIfiS2WrEssD1Pi5h
+hJTbHtjEDJ7BHLC/CNUhXbpyyu1y
+-----END PRIVATE KEY-----
+)";
+
+  bssl::UniquePtr<BIO> cert_bio(
+      BIO_new_mem_buf(const_cast<char *>(kCert), sizeof(kCert) - 1));
+  bssl::UniquePtr<X509> cert(
+      PEM_read_bio_X509(cert_bio.get(), nullptr, nullptr, nullptr));
+
+  bssl::UniquePtr<BIO> key_bio(
+      BIO_new_mem_buf(const_cast<char *>(kKey), sizeof(kKey) - 1));
+  bssl::UniquePtr<EVP_PKEY> key(
+      PEM_read_bio_PrivateKey(key_bio.get(), nullptr, nullptr, nullptr));
+
+  static const char kSignedData[] = "signed data";
+  bssl::UniquePtr<BIO> data_bio(BIO_new_mem_buf(const_cast<char *>(kSignedData),
+                                                sizeof(kSignedData) - 1));
+
+  bssl::UniquePtr<PKCS7> pkcs7(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_NOATTR | PKCS7_BINARY | PKCS7_NOCERTS | PKCS7_DETACHED));
+  ASSERT_TRUE(pkcs7);
+
+  uint8_t *pkcs7_bytes = nullptr;
+  const int pkcs7_len = i2d_PKCS7(pkcs7.get(), &pkcs7_bytes);
+  ASSERT_GE(pkcs7_len, 0);
+  bssl::UniquePtr<uint8_t> pkcs7_storage(pkcs7_bytes);
+
+  // RSA signatures are deterministic so the output should not change.
+  static const uint8_t kExpectedOutput[] = {
+      0x30, 0x82, 0x02, 0xbc, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+      0x01, 0x07, 0x02, 0xa0, 0x82, 0x02, 0xad, 0x30, 0x82, 0x02, 0xa9, 0x02,
+      0x01, 0x01, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+      0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48,
+      0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0x31, 0x82, 0x02, 0x86, 0x30, 0x82,
+      0x02, 0x82, 0x02, 0x01, 0x01, 0x30, 0x5d, 0x30, 0x45, 0x31, 0x0b, 0x30,
+      0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13,
+      0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d,
+      0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06,
+      0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e,
+      0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50,
+      0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x02, 0x14, 0x45, 0x59, 0x0f, 0xcc,
+      0x5f, 0xf8, 0x77, 0x0c, 0xbb, 0xe3, 0x5f, 0x50, 0x93, 0xbe, 0x6e, 0x84,
+      0x8b, 0xb2, 0x7f, 0x41, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
+      0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+      0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x02, 0x00,
+      0x54, 0xd4, 0x7c, 0xdc, 0x19, 0x86, 0xa1, 0xb2, 0xbe, 0xe3, 0xa4, 0x5e,
+      0xad, 0x16, 0x6f, 0x7c, 0xf9, 0xa6, 0x40, 0x90, 0xb8, 0x78, 0x85, 0xf1,
+      0x02, 0x59, 0xe5, 0x9f, 0x83, 0xfb, 0x20, 0xcf, 0x29, 0x52, 0xb6, 0x35,
+      0x5c, 0xf9, 0xef, 0x4e, 0xc5, 0xd3, 0xa6, 0x45, 0x6e, 0xfa, 0x0a, 0xa7,
+      0x53, 0xc8, 0xf4, 0xf9, 0xd6, 0xc5, 0xd8, 0xd8, 0x04, 0x3d, 0xb4, 0x15,
+      0xa7, 0x7a, 0x53, 0xdd, 0x27, 0xfa, 0x58, 0x2e, 0x5e, 0xc4, 0xcd, 0x45,
+      0xaa, 0xc2, 0x7b, 0xf9, 0x3d, 0xd7, 0x22, 0x20, 0x90, 0xbb, 0xa5, 0x62,
+      0xd5, 0xaa, 0x39, 0x8f, 0xc1, 0x00, 0xef, 0x4b, 0x03, 0x2c, 0x32, 0xc0,
+      0xad, 0x27, 0xb6, 0xfe, 0x86, 0xe5, 0x9d, 0xf0, 0xbe, 0xb1, 0x0d, 0xa4,
+      0xa3, 0x40, 0xe0, 0xaa, 0x0a, 0x13, 0x6e, 0x61, 0x9a, 0x3b, 0xae, 0x78,
+      0xd4, 0x6f, 0x2d, 0x1d, 0x40, 0x4b, 0xe3, 0x5f, 0xf8, 0xe8, 0x21, 0x89,
+      0x35, 0x73, 0x6d, 0x7e, 0x41, 0xc6, 0x0f, 0x0c, 0x01, 0x64, 0x61, 0xa8,
+      0x37, 0xef, 0x2b, 0x95, 0xb7, 0x34, 0xac, 0xc7, 0xdb, 0x66, 0x87, 0x45,
+      0xb4, 0x0f, 0x60, 0x01, 0x07, 0x29, 0x74, 0x32, 0x0b, 0xae, 0xbc, 0x08,
+      0x88, 0x15, 0xc3, 0x79, 0x4a, 0x1c, 0x5a, 0x9c, 0xc2, 0xfb, 0x4f, 0xd3,
+      0x17, 0xc2, 0x40, 0x71, 0x37, 0xea, 0xa6, 0x1e, 0xf0, 0x5b, 0xa5, 0xd7,
+      0x9b, 0x9e, 0x57, 0x44, 0x74, 0xc5, 0xd5, 0x5f, 0xba, 0xbc, 0xd7, 0xe1,
+      0xae, 0xd0, 0xd3, 0xb5, 0x10, 0xc6, 0x8b, 0xb1, 0x83, 0x7c, 0xaa, 0x3a,
+      0xbb, 0xe8, 0x7f, 0x56, 0xc4, 0x3b, 0x9d, 0x45, 0x09, 0x9b, 0x34, 0xc9,
+      0xfb, 0x5a, 0xa1, 0xab, 0xd0, 0x07, 0x79, 0x43, 0x58, 0x44, 0xd7, 0x40,
+      0xc4, 0xa7, 0xd3, 0xe9, 0x18, 0xb9, 0x78, 0x1d, 0x93, 0x0b, 0xc1, 0xdb,
+      0xc3, 0xae, 0xc9, 0xe8, 0x2c, 0xa7, 0x8c, 0x7e, 0x31, 0x1e, 0xec, 0x1c,
+      0xab, 0x83, 0xa0, 0x5d, 0x0e, 0xc3, 0x6a, 0x7c, 0x97, 0x09, 0xcf, 0x00,
+      0xa9, 0x66, 0xda, 0x21, 0x85, 0xaa, 0x47, 0xd8, 0xea, 0x8f, 0x72, 0x54,
+      0x03, 0x6c, 0xbc, 0x4b, 0xf9, 0x92, 0xae, 0x82, 0x75, 0x33, 0x10, 0x4d,
+      0x65, 0x4d, 0x0e, 0x73, 0x5d, 0x6f, 0x09, 0xee, 0x56, 0x78, 0x87, 0x0b,
+      0xa3, 0xaa, 0xc2, 0x5f, 0x49, 0x73, 0x0d, 0x78, 0xfa, 0x40, 0xc1, 0x25,
+      0x2f, 0x5d, 0x8a, 0xe1, 0xbf, 0x38, 0x2c, 0xd0, 0x26, 0xbd, 0xf5, 0x6e,
+      0x02, 0x01, 0x2e, 0x9e, 0x27, 0x64, 0x4b, 0x61, 0x8c, 0x68, 0x6e, 0x09,
+      0xfe, 0x0b, 0xf8, 0x36, 0x4e, 0x84, 0xb7, 0x76, 0xcb, 0x41, 0xf0, 0x40,
+      0x72, 0xc9, 0x74, 0x64, 0x5f, 0xbe, 0x9e, 0xfe, 0x9e, 0xce, 0x89, 0x84,
+      0x68, 0x81, 0x57, 0x2a, 0xdb, 0xd6, 0x01, 0xa8, 0x1b, 0x6e, 0x5d, 0xc4,
+      0x65, 0xbd, 0x0d, 0x98, 0x54, 0xa3, 0x18, 0x23, 0x09, 0x4a, 0x8d, 0x6c,
+      0xc6, 0x2e, 0xfe, 0x7a, 0xa9, 0x11, 0x92, 0x8b, 0xd0, 0xc1, 0xe7, 0x76,
+      0x71, 0xec, 0x34, 0xfc, 0xc8, 0x2a, 0x5e, 0x38, 0x52, 0xe6, 0xc8, 0xa5,
+      0x1d, 0x0b, 0xce, 0xf5, 0xc0, 0xe5, 0x0b, 0x88, 0xa9, 0x55, 0x88, 0x6c,
+      0xfa, 0xea, 0xaa, 0x39, 0x66, 0xdd, 0x80, 0x52, 0xe0, 0x7e, 0x45, 0x8e,
+      0x51, 0x2c, 0x36, 0x07, 0xd7, 0x2b, 0xf1, 0x46, 0x00, 0x66, 0xb2, 0x5a,
+      0x39, 0xbe, 0xf7, 0x26, 0x15, 0xbc, 0x55, 0xdb, 0xe9, 0x01, 0xdd, 0x54,
+      0x27, 0x2b, 0xfe, 0x86, 0x52, 0xef, 0xc6, 0x27, 0xa3, 0xf7, 0x55, 0x55,
+      0xb8, 0xe2, 0x1f, 0xcb, 0x32, 0xd8, 0xba, 0xd6, 0x69, 0xde, 0x8d, 0xa7,
+      0xfa, 0xad, 0xf6, 0x2a, 0xc0, 0x6f, 0x86, 0x50, 0x27, 0x5a, 0xe2, 0xe3,
+      0xf6, 0xb9, 0x01, 0xec, 0x01, 0x37, 0x84, 0x01,
+  };
+  EXPECT_EQ(Bytes(pkcs7_bytes, pkcs7_len),
+            Bytes(kExpectedOutput, sizeof(kExpectedOutput)));
+
+  // Other option combinations should fail.
+  EXPECT_FALSE(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_NOATTR | PKCS7_BINARY | PKCS7_NOCERTS));
+  EXPECT_FALSE(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_BINARY | PKCS7_NOCERTS | PKCS7_DETACHED));
+  EXPECT_FALSE(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_NOATTR | PKCS7_TEXT | PKCS7_NOCERTS | PKCS7_DETACHED));
+  EXPECT_FALSE(
+      PKCS7_sign(cert.get(), key.get(), /*certs=*/nullptr, data_bio.get(),
+                 PKCS7_NOATTR | PKCS7_BINARY | PKCS7_DETACHED));
+
+  ERR_clear_error();
+}
diff --git a/src/crypto/pkcs7/pkcs7_x509.c b/src/crypto/pkcs7/pkcs7_x509.c
index 3f1526c..773c592 100644
--- a/src/crypto/pkcs7/pkcs7_x509.c
+++ b/src/crypto/pkcs7/pkcs7_x509.c
@@ -20,6 +20,7 @@
 #include <openssl/bytestring.h>
 #include <openssl/err.h>
 #include <openssl/mem.h>
+#include <openssl/obj.h>
 #include <openssl/pem.h>
 #include <openssl/pool.h>
 #include <openssl/stack.h>
@@ -197,7 +198,9 @@
 }
 
 int PKCS7_bundle_certificates(CBB *out, const STACK_OF(X509) *certs) {
-  return pkcs7_bundle(out, pkcs7_bundle_certificates_cb, certs);
+  return pkcs7_add_signed_data(out, /*digest_algos_cb=*/NULL,
+                               pkcs7_bundle_certificates_cb,
+                               /*signer_infos_cb=*/NULL, certs);
 }
 
 static int pkcs7_bundle_crls_cb(CBB *out, const void *arg) {
@@ -228,7 +231,9 @@
 }
 
 int PKCS7_bundle_CRLs(CBB *out, const STACK_OF(X509_CRL) *crls) {
-  return pkcs7_bundle(out, pkcs7_bundle_crls_cb, crls);
+  return pkcs7_add_signed_data(out, /*digest_algos_cb=*/NULL,
+                               pkcs7_bundle_crls_cb,
+                               /*signer_infos_cb=*/NULL, crls);
 }
 
 static PKCS7 *pkcs7_new(CBS *cbs) {
@@ -362,26 +367,160 @@
 int PKCS7_type_is_signed(const PKCS7 *p7) { return 1; }
 int PKCS7_type_is_signedAndEnveloped(const PKCS7 *p7) { return 0; }
 
+// write_sha256_ai writes an AlgorithmIdentifier for SHA-256 to
+// |digest_algos_set|.
+static int write_sha256_ai(CBB *digest_algos_set, const void *arg) {
+  CBB seq;
+  return CBB_add_asn1(digest_algos_set, &seq, CBS_ASN1_SEQUENCE) &&
+         OBJ_nid2cbb(&seq, NID_sha256) &&  //
+         // https://datatracker.ietf.org/doc/html/rfc5754#section-2
+         // "Implementations MUST generate SHA2 AlgorithmIdentifiers with absent
+         //  parameters."
+         CBB_flush(digest_algos_set);
+}
+
+// sign_sha256 writes at most |max_out_sig| bytes of the signature of |data| by
+// |pkey| to |out_sig| and sets |*out_sig_len| to the number of bytes written.
+// It returns one on success or zero on error.
+static int sign_sha256(uint8_t *out_sig, size_t *out_sig_len,
+                       size_t max_out_sig, EVP_PKEY *pkey, BIO *data) {
+  static const size_t kBufSize = 4096;
+  uint8_t *buffer = OPENSSL_malloc(kBufSize);
+  if (!buffer) {
+    return 0;
+  }
+
+  EVP_MD_CTX ctx;
+  EVP_MD_CTX_init(&ctx);
+
+  int ret = 0;
+  if (!EVP_DigestSignInit(&ctx, NULL, EVP_sha256(), NULL, pkey)) {
+    goto out;
+  }
+
+  for (;;) {
+    const int n = BIO_read(data, buffer, kBufSize);
+    if (n == 0) {
+      break;
+    } else if (n < 0 || !EVP_DigestSignUpdate(&ctx, buffer, n)) {
+      goto out;
+    }
+  }
+
+  *out_sig_len = max_out_sig;
+  if (!EVP_DigestSignFinal(&ctx, out_sig, out_sig_len)) {
+    goto out;
+  }
+
+  ret = 1;
+
+out:
+  EVP_MD_CTX_cleanup(&ctx);
+  OPENSSL_free(buffer);
+  return ret;
+}
+
+struct signer_info_data {
+  const X509 *sign_cert;
+  uint8_t *signature;
+  size_t signature_len;
+};
+
+// write_signer_info writes the SignerInfo structure from
+// https://datatracker.ietf.org/doc/html/rfc2315#section-9.2 to |out|. It
+// returns one on success or zero on error.
+static int write_signer_info(CBB *out, const void *arg) {
+  const struct signer_info_data *const si_data = arg;
+
+  int ret = 0;
+  uint8_t *subject_bytes = NULL;
+  uint8_t *serial_bytes = NULL;
+
+  const int subject_len =
+      i2d_X509_NAME(X509_get_subject_name(si_data->sign_cert), &subject_bytes);
+  const int serial_len = i2d_ASN1_INTEGER(
+      (ASN1_INTEGER *)X509_get0_serialNumber(si_data->sign_cert),
+      &serial_bytes);
+
+  CBB seq, issuer_and_serial, signing_algo, null, signature;
+  if (subject_len < 0 ||
+      serial_len < 0 ||
+      !CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
+      // version
+      !CBB_add_asn1_uint64(&seq, 1) ||
+      !CBB_add_asn1(&seq, &issuer_and_serial, CBS_ASN1_SEQUENCE) ||
+      !CBB_add_bytes(&issuer_and_serial, subject_bytes, subject_len) ||
+      !CBB_add_bytes(&issuer_and_serial, serial_bytes, serial_len) ||
+      !write_sha256_ai(&seq, NULL) ||
+      !CBB_add_asn1(&seq, &signing_algo, CBS_ASN1_SEQUENCE) ||
+      !OBJ_nid2cbb(&signing_algo, NID_rsaEncryption) ||
+      !CBB_add_asn1(&signing_algo, &null, CBS_ASN1_NULL) ||
+      !CBB_add_asn1(&seq, &signature, CBS_ASN1_OCTETSTRING) ||
+      !CBB_add_bytes(&signature, si_data->signature, si_data->signature_len) ||
+      !CBB_flush(out)) {
+    goto out;
+  }
+
+  ret = 1;
+
+out:
+  OPENSSL_free(subject_bytes);
+  OPENSSL_free(serial_bytes);
+  return ret;
+}
+
 PKCS7 *PKCS7_sign(X509 *sign_cert, EVP_PKEY *pkey, STACK_OF(X509) *certs,
                   BIO *data, int flags) {
-  if (sign_cert != NULL || pkey != NULL || flags != PKCS7_DETACHED) {
-    OPENSSL_PUT_ERROR(PKCS7, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+  CBB cbb;
+  if (!CBB_init(&cbb, 2048)) {
     return NULL;
   }
 
-  uint8_t *der;
+  uint8_t *der = NULL;
   size_t len;
-  CBB cbb;
-  if (!CBB_init(&cbb, 2048) ||
-      !PKCS7_bundle_certificates(&cbb, certs) ||
-      !CBB_finish(&cbb, &der, &len)) {
-    CBB_cleanup(&cbb);
-    return NULL;
+  PKCS7 *ret = NULL;
+
+  if (sign_cert == NULL && pkey == NULL && flags == PKCS7_DETACHED) {
+    // Caller just wants to bundle certificates.
+    if (!PKCS7_bundle_certificates(&cbb, certs)) {
+      goto out;
+    }
+  } else if (sign_cert != NULL && pkey != NULL && certs == NULL &&
+             data != NULL &&
+             flags == (PKCS7_NOATTR | PKCS7_BINARY | PKCS7_NOCERTS |
+                       PKCS7_DETACHED) &&
+             EVP_PKEY_id(pkey) == NID_rsaEncryption) {
+    // sign-file.c from the Linux kernel.
+    const size_t signature_max_len = EVP_PKEY_size(pkey);
+    struct signer_info_data si_data = {
+      .sign_cert = sign_cert,
+      .signature = OPENSSL_malloc(signature_max_len),
+    };
+
+    if (!si_data.signature ||
+        !sign_sha256(si_data.signature, &si_data.signature_len,
+                     signature_max_len, pkey, data) ||
+        !pkcs7_add_signed_data(&cbb, write_sha256_ai, /*cert_crl_cb=*/NULL,
+                               write_signer_info, &si_data)) {
+      OPENSSL_free(si_data.signature);
+      goto out;
+    }
+    OPENSSL_free(si_data.signature);
+  } else {
+    OPENSSL_PUT_ERROR(PKCS7, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    goto out;
+  }
+
+  if (!CBB_finish(&cbb, &der, &len)) {
+    goto out;
   }
 
   CBS cbs;
   CBS_init(&cbs, der, len);
-  PKCS7 *ret = pkcs7_new(&cbs);
+  ret = pkcs7_new(&cbs);
+
+out:
+  CBB_cleanup(&cbb);
   OPENSSL_free(der);
   return ret;
 }
diff --git a/src/crypto/pkcs8/pkcs8_x509.c b/src/crypto/pkcs8/pkcs8_x509.c
index efdf33a..e24fb42 100644
--- a/src/crypto/pkcs8/pkcs8_x509.c
+++ b/src/crypto/pkcs8/pkcs8_x509.c
@@ -1180,7 +1180,7 @@
   }
 
   // PKCS#12 is a very confusing recursive data format, built out of another
-  // recursive data format. Section 5.1 of RFC7292 describes the encoding
+  // recursive data format. Section 5.1 of RFC 7292 describes the encoding
   // algorithm, but there is no clear overview. A quick summary:
   //
   // PKCS#7 defines a ContentInfo structure, which is a overgeneralized typed
diff --git a/src/crypto/test/asm/trampoline-armv4.pl b/src/crypto/test/asm/trampoline-armv4.pl
index 4a61f61..d2f5da7 100755
--- a/src/crypto/test/asm/trampoline-armv4.pl
+++ b/src/crypto/test/asm/trampoline-armv4.pl
@@ -178,4 +178,4 @@
 }
 
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/test/asm/trampoline-armv8.pl b/src/crypto/test/asm/trampoline-armv8.pl
index 426360e..76cc025 100755
--- a/src/crypto/test/asm/trampoline-armv8.pl
+++ b/src/crypto/test/asm/trampoline-armv8.pl
@@ -213,4 +213,4 @@
 }
 
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/test/asm/trampoline-ppc.pl b/src/crypto/test/asm/trampoline-ppc.pl
index a8d7c3f..b29c361 100755
--- a/src/crypto/test/asm/trampoline-ppc.pl
+++ b/src/crypto/test/asm/trampoline-ppc.pl
@@ -259,4 +259,4 @@
 ____
 
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/test/asm/trampoline-x86.pl b/src/crypto/test/asm/trampoline-x86.pl
index 4244ac2..9694d16 100755
--- a/src/crypto/test/asm/trampoline-x86.pl
+++ b/src/crypto/test/asm/trampoline-x86.pl
@@ -120,4 +120,4 @@
 
 &asm_finish();
 
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/test/asm/trampoline-x86_64.pl b/src/crypto/test/asm/trampoline-x86_64.pl
index 5196141..f6d8385 100755
--- a/src/crypto/test/asm/trampoline-x86_64.pl
+++ b/src/crypto/test/asm/trampoline-x86_64.pl
@@ -556,4 +556,4 @@
 }
 
 print $code;
-close STDOUT or die "error closing STDOUT";
+close STDOUT or die "error closing STDOUT: $!";
diff --git a/src/crypto/x509/a_strex.c b/src/crypto/x509/a_strex.c
deleted file mode 100644
index 0c9dcef..0000000
--- a/src/crypto/x509/a_strex.c
+++ /dev/null
@@ -1,653 +0,0 @@
-/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
- * All rights reserved.
- *
- * This package is an SSL implementation written
- * by Eric Young (eay@cryptsoft.com).
- * The implementation was written so as to conform with Netscapes SSL.
- *
- * This library is free for commercial and non-commercial use as long as
- * the following conditions are aheared to.  The following conditions
- * apply to all code found in this distribution, be it the RC4, RSA,
- * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
- * included with this distribution is covered by the same copyright terms
- * except that the holder is Tim Hudson (tjh@cryptsoft.com).
- *
- * Copyright remains Eric Young's, and as such any Copyright notices in
- * the code are not to be removed.
- * If this package is used in a product, Eric Young should be given attribution
- * as the author of the parts of the library used.
- * This can be in the form of a textual message at program startup or
- * in documentation (online or textual) provided with the package.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *    "This product includes cryptographic software written by
- *     Eric Young (eay@cryptsoft.com)"
- *    The word 'cryptographic' can be left out if the rouines from the library
- *    being used are not cryptographic related :-).
- * 4. If you include any Windows specific code (or a derivative thereof) from
- *    the apps directory (application code) you must include an acknowledgement:
- *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
- *
- * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * The licence and distribution terms for any publically available version or
- * derivative of this code cannot be changed.  i.e. this code cannot simply be
- * copied and put under another distribution licence
- * [including the GNU Public Licence.] */
-
-#include <openssl/x509.h>
-
-#include <inttypes.h>
-#include <string.h>
-
-#include <openssl/asn1.h>
-#include <openssl/mem.h>
-#include <openssl/obj.h>
-
-#include "charmap.h"
-#include "../asn1/internal.h"
-
-/*
- * ASN1_STRING_print_ex() and X509_NAME_print_ex(). Enhanced string and name
- * printing routines handling multibyte characters, RFC2253 and a host of
- * other options.
- */
-
-#define CHARTYPE_BS_ESC         (ASN1_STRFLGS_ESC_2253 | CHARTYPE_FIRST_ESC_2253 | CHARTYPE_LAST_ESC_2253)
-
-#define ESC_FLAGS (ASN1_STRFLGS_ESC_2253 | \
-                  ASN1_STRFLGS_ESC_QUOTE | \
-                  ASN1_STRFLGS_ESC_CTRL | \
-                  ASN1_STRFLGS_ESC_MSB)
-
-static int send_bio_chars(void *arg, const void *buf, int len)
-{
-    if (!arg)
-        return 1;
-    if (BIO_write(arg, buf, len) != len)
-        return 0;
-    return 1;
-}
-
-static int send_fp_chars(void *arg, const void *buf, int len)
-{
-    if (!arg)
-        return 1;
-    if (fwrite(buf, 1, len, arg) != (unsigned int)len)
-        return 0;
-    return 1;
-}
-
-typedef int char_io (void *arg, const void *buf, int len);
-
-/*
- * This function handles display of strings, one character at a time. It is
- * passed an unsigned long for each character because it could come from 2 or
- * even 4 byte forms.
- */
-
-#define HEX_SIZE(type) (sizeof(type)*2)
-
-static int do_esc_char(uint32_t c, unsigned char flags, char *do_quotes,
-                       char_io *io_ch, void *arg)
-{
-    unsigned char chflgs, chtmp;
-    char tmphex[HEX_SIZE(uint32_t) + 3];
-
-    if (c > 0xffff) {
-        BIO_snprintf(tmphex, sizeof tmphex, "\\W%08" PRIX32, c);
-        if (!io_ch(arg, tmphex, 10))
-            return -1;
-        return 10;
-    }
-    if (c > 0xff) {
-        BIO_snprintf(tmphex, sizeof tmphex, "\\U%04" PRIX32, c);
-        if (!io_ch(arg, tmphex, 6))
-            return -1;
-        return 6;
-    }
-    chtmp = (unsigned char)c;
-    if (chtmp > 0x7f)
-        chflgs = flags & ASN1_STRFLGS_ESC_MSB;
-    else
-        chflgs = char_type[chtmp] & flags;
-    if (chflgs & CHARTYPE_BS_ESC) {
-        /* If we don't escape with quotes, signal we need quotes */
-        if (chflgs & ASN1_STRFLGS_ESC_QUOTE) {
-            if (do_quotes)
-                *do_quotes = 1;
-            if (!io_ch(arg, &chtmp, 1))
-                return -1;
-            return 1;
-        }
-        if (!io_ch(arg, "\\", 1))
-            return -1;
-        if (!io_ch(arg, &chtmp, 1))
-            return -1;
-        return 2;
-    }
-    if (chflgs & (ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB)) {
-        BIO_snprintf(tmphex, 11, "\\%02X", chtmp);
-        if (!io_ch(arg, tmphex, 3))
-            return -1;
-        return 3;
-    }
-    /*
-     * If we get this far and do any escaping at all must escape the escape
-     * character itself: backslash.
-     */
-    if (chtmp == '\\' && flags & ESC_FLAGS) {
-        if (!io_ch(arg, "\\\\", 2))
-            return -1;
-        return 2;
-    }
-    if (!io_ch(arg, &chtmp, 1))
-        return -1;
-    return 1;
-}
-
-#define BUF_TYPE_WIDTH_MASK     0x7
-#define BUF_TYPE_CONVUTF8       0x8
-
-/*
- * This function sends each character in a buffer to do_esc_char(). It
- * interprets the content formats and converts to or from UTF8 as
- * appropriate.
- */
-
-static int do_buf(unsigned char *buf, int buflen,
-                  int type, unsigned char flags, char *quotes, char_io *io_ch,
-                  void *arg)
-{
-    int i, outlen, len, charwidth;
-    unsigned char orflags, *p, *q;
-    uint32_t c;
-    p = buf;
-    q = buf + buflen;
-    outlen = 0;
-    charwidth = type & BUF_TYPE_WIDTH_MASK;
-
-    switch (charwidth) {
-    case 4:
-        if (buflen & 3) {
-            OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_UNIVERSALSTRING);
-            return -1;
-        }
-        break;
-    case 2:
-        if (buflen & 1) {
-            OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_BMPSTRING);
-            return -1;
-        }
-        break;
-    default:
-        break;
-    }
-
-    while (p != q) {
-        if (p == buf && flags & ASN1_STRFLGS_ESC_2253)
-            orflags = CHARTYPE_FIRST_ESC_2253;
-        else
-            orflags = 0;
-        switch (charwidth) {
-        case 4:
-            c = ((uint32_t)*p++) << 24;
-            c |= ((uint32_t)*p++) << 16;
-            c |= ((uint32_t)*p++) << 8;
-            c |= *p++;
-            break;
-
-        case 2:
-            c = ((uint32_t)*p++) << 8;
-            c |= *p++;
-            break;
-
-        case 1:
-            c = *p++;
-            break;
-
-        case 0:
-            i = UTF8_getc(p, buflen, &c);
-            if (i < 0)
-                return -1;      /* Invalid UTF8String */
-            buflen -= i;
-            p += i;
-            break;
-        default:
-            return -1;          /* invalid width */
-        }
-        if (p == q && flags & ASN1_STRFLGS_ESC_2253)
-            orflags = CHARTYPE_LAST_ESC_2253;
-        if (type & BUF_TYPE_CONVUTF8) {
-            unsigned char utfbuf[6];
-            int utflen;
-            utflen = UTF8_putc(utfbuf, sizeof utfbuf, c);
-            for (i = 0; i < utflen; i++) {
-                /*
-                 * We don't need to worry about setting orflags correctly
-                 * because if utflen==1 its value will be correct anyway
-                 * otherwise each character will be > 0x7f and so the
-                 * character will never be escaped on first and last.
-                 */
-                len =
-                    do_esc_char(utfbuf[i], (unsigned char)(flags | orflags),
-                                quotes, io_ch, arg);
-                if (len < 0)
-                    return -1;
-                outlen += len;
-            }
-        } else {
-            len =
-                do_esc_char(c, (unsigned char)(flags | orflags), quotes,
-                            io_ch, arg);
-            if (len < 0)
-                return -1;
-            outlen += len;
-        }
-    }
-    return outlen;
-}
-
-/* This function hex dumps a buffer of characters */
-
-static int do_hex_dump(char_io *io_ch, void *arg, unsigned char *buf,
-                       int buflen)
-{
-    static const char hexdig[] = "0123456789ABCDEF";
-    unsigned char *p, *q;
-    char hextmp[2];
-    if (arg) {
-        p = buf;
-        q = buf + buflen;
-        while (p != q) {
-            hextmp[0] = hexdig[*p >> 4];
-            hextmp[1] = hexdig[*p & 0xf];
-            if (!io_ch(arg, hextmp, 2))
-                return -1;
-            p++;
-        }
-    }
-    return buflen << 1;
-}
-
-/*
- * "dump" a string. This is done when the type is unknown, or the flags
- * request it. We can either dump the content octets or the entire DER
- * encoding. This uses the RFC2253 #01234 format.
- */
-
-static int do_dump(unsigned long lflags, char_io *io_ch, void *arg,
-                   const ASN1_STRING *str)
-{
-    /*
-     * Placing the ASN1_STRING in a temp ASN1_TYPE allows the DER encoding to
-     * readily obtained
-     */
-    ASN1_TYPE t;
-    unsigned char *der_buf, *p;
-    int outlen, der_len;
-
-    if (!io_ch(arg, "#", 1))
-        return -1;
-    /* If we don't dump DER encoding just dump content octets */
-    if (!(lflags & ASN1_STRFLGS_DUMP_DER)) {
-        outlen = do_hex_dump(io_ch, arg, str->data, str->length);
-        if (outlen < 0)
-            return -1;
-        return outlen + 1;
-    }
-    t.type = str->type;
-    t.value.ptr = (char *)str;
-    der_len = i2d_ASN1_TYPE(&t, NULL);
-    der_buf = OPENSSL_malloc(der_len);
-    if (!der_buf)
-        return -1;
-    p = der_buf;
-    i2d_ASN1_TYPE(&t, &p);
-    outlen = do_hex_dump(io_ch, arg, der_buf, der_len);
-    OPENSSL_free(der_buf);
-    if (outlen < 0)
-        return -1;
-    return outlen + 1;
-}
-
-/*
- * Lookup table to convert tags to character widths, 0 = UTF8 encoded, -1 is
- * used for non string types otherwise it is the number of bytes per
- * character
- */
-
-static const signed char tag2nbyte[] = {
-    -1, -1, -1, -1, -1,         /* 0-4 */
-    -1, -1, -1, -1, -1,         /* 5-9 */
-    -1, -1, 0, -1,              /* 10-13 */
-    -1, -1, -1, -1,             /* 15-17 */
-    1, 1, 1,                    /* 18-20 */
-    -1, 1, 1, 1,                /* 21-24 */
-    -1, 1, -1,                  /* 25-27 */
-    4, -1, 2                    /* 28-30 */
-};
-
-/*
- * This is the main function, print out an ASN1_STRING taking note of various
- * escape and display options. Returns number of characters written or -1 if
- * an error occurred.
- */
-
-static int do_print_ex(char_io *io_ch, void *arg, unsigned long lflags,
-                       const ASN1_STRING *str)
-{
-    int outlen, len;
-    int type;
-    char quotes;
-    unsigned char flags;
-    quotes = 0;
-    /* Keep a copy of escape flags */
-    flags = (unsigned char)(lflags & ESC_FLAGS);
-
-    type = str->type;
-
-    outlen = 0;
-
-    if (lflags & ASN1_STRFLGS_SHOW_TYPE) {
-        const char *tagname;
-        tagname = ASN1_tag2str(type);
-        outlen += strlen(tagname);
-        if (!io_ch(arg, tagname, outlen) || !io_ch(arg, ":", 1))
-            return -1;
-        outlen++;
-    }
-
-    /* Decide what to do with type, either dump content or display it */
-
-    /* Dump everything */
-    if (lflags & ASN1_STRFLGS_DUMP_ALL)
-        type = -1;
-    /* Ignore the string type */
-    else if (lflags & ASN1_STRFLGS_IGNORE_TYPE)
-        type = 1;
-    else {
-        /* Else determine width based on type */
-        if ((type > 0) && (type < 31))
-            type = tag2nbyte[type];
-        else
-            type = -1;
-        if ((type == -1) && !(lflags & ASN1_STRFLGS_DUMP_UNKNOWN))
-            type = 1;
-    }
-
-    if (type == -1) {
-        len = do_dump(lflags, io_ch, arg, str);
-        if (len < 0)
-            return -1;
-        outlen += len;
-        return outlen;
-    }
-
-    if (lflags & ASN1_STRFLGS_UTF8_CONVERT) {
-        /*
-         * Note: if string is UTF8 and we want to convert to UTF8 then we
-         * just interpret it as 1 byte per character to avoid converting
-         * twice.
-         */
-        if (!type)
-            type = 1;
-        else
-            type |= BUF_TYPE_CONVUTF8;
-    }
-
-    len = do_buf(str->data, str->length, type, flags, &quotes, io_ch, NULL);
-    if (len < 0)
-        return -1;
-    outlen += len;
-    if (quotes)
-        outlen += 2;
-    if (!arg)
-        return outlen;
-    if (quotes && !io_ch(arg, "\"", 1))
-        return -1;
-    if (do_buf(str->data, str->length, type, flags, NULL, io_ch, arg) < 0)
-        return -1;
-    if (quotes && !io_ch(arg, "\"", 1))
-        return -1;
-    return outlen;
-}
-
-/* Used for line indenting: print 'indent' spaces */
-
-static int do_indent(char_io *io_ch, void *arg, int indent)
-{
-    int i;
-    for (i = 0; i < indent; i++)
-        if (!io_ch(arg, " ", 1))
-            return 0;
-    return 1;
-}
-
-#define FN_WIDTH_LN     25
-#define FN_WIDTH_SN     10
-
-static int do_name_ex(char_io *io_ch, void *arg, const X509_NAME *n,
-                      int indent, unsigned long flags)
-{
-    int i, prev = -1, orflags, cnt;
-    int fn_opt, fn_nid;
-    ASN1_OBJECT *fn;
-    ASN1_STRING *val;
-    X509_NAME_ENTRY *ent;
-    char objtmp[80];
-    const char *objbuf;
-    int outlen, len;
-    const char *sep_dn, *sep_mv, *sep_eq;
-    int sep_dn_len, sep_mv_len, sep_eq_len;
-    if (indent < 0)
-        indent = 0;
-    outlen = indent;
-    if (!do_indent(io_ch, arg, indent))
-        return -1;
-    switch (flags & XN_FLAG_SEP_MASK) {
-    case XN_FLAG_SEP_MULTILINE:
-        sep_dn = "\n";
-        sep_dn_len = 1;
-        sep_mv = " + ";
-        sep_mv_len = 3;
-        break;
-
-    case XN_FLAG_SEP_COMMA_PLUS:
-        sep_dn = ",";
-        sep_dn_len = 1;
-        sep_mv = "+";
-        sep_mv_len = 1;
-        indent = 0;
-        break;
-
-    case XN_FLAG_SEP_CPLUS_SPC:
-        sep_dn = ", ";
-        sep_dn_len = 2;
-        sep_mv = " + ";
-        sep_mv_len = 3;
-        indent = 0;
-        break;
-
-    case XN_FLAG_SEP_SPLUS_SPC:
-        sep_dn = "; ";
-        sep_dn_len = 2;
-        sep_mv = " + ";
-        sep_mv_len = 3;
-        indent = 0;
-        break;
-
-    default:
-        return -1;
-    }
-
-    if (flags & XN_FLAG_SPC_EQ) {
-        sep_eq = " = ";
-        sep_eq_len = 3;
-    } else {
-        sep_eq = "=";
-        sep_eq_len = 1;
-    }
-
-    fn_opt = flags & XN_FLAG_FN_MASK;
-
-    cnt = X509_NAME_entry_count(n);
-    for (i = 0; i < cnt; i++) {
-        if (flags & XN_FLAG_DN_REV)
-            ent = X509_NAME_get_entry(n, cnt - i - 1);
-        else
-            ent = X509_NAME_get_entry(n, i);
-        if (prev != -1) {
-            if (prev == ent->set) {
-                if (!io_ch(arg, sep_mv, sep_mv_len))
-                    return -1;
-                outlen += sep_mv_len;
-            } else {
-                if (!io_ch(arg, sep_dn, sep_dn_len))
-                    return -1;
-                outlen += sep_dn_len;
-                if (!do_indent(io_ch, arg, indent))
-                    return -1;
-                outlen += indent;
-            }
-        }
-        prev = ent->set;
-        fn = X509_NAME_ENTRY_get_object(ent);
-        val = X509_NAME_ENTRY_get_data(ent);
-        fn_nid = OBJ_obj2nid(fn);
-        if (fn_opt != XN_FLAG_FN_NONE) {
-            int objlen, fld_len;
-            if ((fn_opt == XN_FLAG_FN_OID) || (fn_nid == NID_undef)) {
-                OBJ_obj2txt(objtmp, sizeof objtmp, fn, 1);
-                fld_len = 0;    /* XXX: what should this be? */
-                objbuf = objtmp;
-            } else {
-                if (fn_opt == XN_FLAG_FN_SN) {
-                    fld_len = FN_WIDTH_SN;
-                    objbuf = OBJ_nid2sn(fn_nid);
-                } else if (fn_opt == XN_FLAG_FN_LN) {
-                    fld_len = FN_WIDTH_LN;
-                    objbuf = OBJ_nid2ln(fn_nid);
-                } else {
-                    fld_len = 0; /* XXX: what should this be? */
-                    objbuf = "";
-                }
-            }
-            objlen = strlen(objbuf);
-            if (!io_ch(arg, objbuf, objlen))
-                return -1;
-            if ((objlen < fld_len) && (flags & XN_FLAG_FN_ALIGN)) {
-                if (!do_indent(io_ch, arg, fld_len - objlen))
-                    return -1;
-                outlen += fld_len - objlen;
-            }
-            if (!io_ch(arg, sep_eq, sep_eq_len))
-                return -1;
-            outlen += objlen + sep_eq_len;
-        }
-        /*
-         * If the field name is unknown then fix up the DER dump flag. We
-         * might want to limit this further so it will DER dump on anything
-         * other than a few 'standard' fields.
-         */
-        if ((fn_nid == NID_undef) && (flags & XN_FLAG_DUMP_UNKNOWN_FIELDS))
-            orflags = ASN1_STRFLGS_DUMP_ALL;
-        else
-            orflags = 0;
-
-        len = do_print_ex(io_ch, arg, flags | orflags, val);
-        if (len < 0)
-            return -1;
-        outlen += len;
-    }
-    return outlen;
-}
-
-/* Wrappers round the main functions */
-
-int X509_NAME_print_ex(BIO *out, const X509_NAME *nm, int indent,
-                       unsigned long flags)
-{
-    if (flags == XN_FLAG_COMPAT)
-        return X509_NAME_print(out, nm, indent);
-    return do_name_ex(send_bio_chars, out, nm, indent, flags);
-}
-
-#ifndef OPENSSL_NO_FP_API
-int X509_NAME_print_ex_fp(FILE *fp, const X509_NAME *nm, int indent,
-                          unsigned long flags)
-{
-    if (flags == XN_FLAG_COMPAT) {
-        BIO *btmp;
-        int ret;
-        btmp = BIO_new_fp(fp, BIO_NOCLOSE);
-        if (!btmp)
-            return -1;
-        ret = X509_NAME_print(btmp, nm, indent);
-        BIO_free(btmp);
-        return ret;
-    }
-    return do_name_ex(send_fp_chars, fp, nm, indent, flags);
-}
-#endif
-
-int ASN1_STRING_print_ex(BIO *out, const ASN1_STRING *str, unsigned long flags)
-{
-    return do_print_ex(send_bio_chars, out, flags, str);
-}
-
-#ifndef OPENSSL_NO_FP_API
-int ASN1_STRING_print_ex_fp(FILE *fp, const ASN1_STRING *str, unsigned long flags)
-{
-    return do_print_ex(send_fp_chars, fp, flags, str);
-}
-#endif
-
-/*
- * Utility function: convert any string type to UTF8, returns number of bytes
- * in output string or a negative error code
- */
-
-int ASN1_STRING_to_UTF8(unsigned char **out, const ASN1_STRING *in)
-{
-    ASN1_STRING stmp, *str = &stmp;
-    int mbflag, type, ret;
-    if (!in)
-        return -1;
-    type = in->type;
-    if ((type < 0) || (type > 30))
-        return -1;
-    mbflag = tag2nbyte[type];
-    if (mbflag == -1)
-        return -1;
-    mbflag |= MBSTRING_FLAG;
-    stmp.data = NULL;
-    stmp.length = 0;
-    stmp.flags = 0;
-    ret =
-        ASN1_mbstring_copy(&str, in->data, in->length, mbflag,
-                           B_ASN1_UTF8STRING);
-    if (ret < 0)
-        return ret;
-    *out = stmp.data;
-    return stmp.length;
-}
diff --git a/src/crypto/x509/by_file.c b/src/crypto/x509/by_file.c
index 994beb9..1614c8c 100644
--- a/src/crypto/x509/by_file.c
+++ b/src/crypto/x509/by_file.c
@@ -61,6 +61,8 @@
 #include <openssl/pem.h>
 #include <openssl/thread.h>
 
+#include "internal.h"
+
 #ifndef OPENSSL_NO_STDIO
 
 static int by_file_ctrl(X509_LOOKUP *ctx, int cmd, const char *argc,
diff --git a/src/crypto/x509/internal.h b/src/crypto/x509/internal.h
index 2782f23..ac68755 100644
--- a/src/crypto/x509/internal.h
+++ b/src/crypto/x509/internal.h
@@ -81,6 +81,22 @@
   EVP_PKEY *pkey;
 } /* X509_PUBKEY */;
 
+struct X509_name_entry_st {
+  ASN1_OBJECT *object;
+  ASN1_STRING *value;
+  int set;
+} /* X509_NAME_ENTRY */;
+
+// we always keep X509_NAMEs in 2 forms.
+struct X509_name_st {
+  STACK_OF(X509_NAME_ENTRY) *entries;
+  int modified;  // true if 'bytes' needs to be built
+  BUF_MEM *bytes;
+  // unsigned long hash; Keep the hash around for lookups
+  unsigned char *canon_enc;
+  int canon_enclen;
+} /* X509_NAME */;
+
 struct x509_attributes_st {
   ASN1_OBJECT *object;
   STACK_OF(ASN1_TYPE) *set;
@@ -101,6 +117,47 @@
 } /* X509_EXTENSION */;
 
 typedef struct {
+  ASN1_INTEGER *version;  // [ 0 ] default of v1
+  ASN1_INTEGER *serialNumber;
+  X509_ALGOR *signature;
+  X509_NAME *issuer;
+  X509_VAL *validity;
+  X509_NAME *subject;
+  X509_PUBKEY *key;
+  ASN1_BIT_STRING *issuerUID;            // [ 1 ] optional in v2
+  ASN1_BIT_STRING *subjectUID;           // [ 2 ] optional in v2
+  STACK_OF(X509_EXTENSION) *extensions;  // [ 3 ] optional in v3
+  ASN1_ENCODING enc;
+} X509_CINF;
+
+DECLARE_ASN1_FUNCTIONS(X509_CINF)
+
+struct x509_st {
+  X509_CINF *cert_info;
+  X509_ALGOR *sig_alg;
+  ASN1_BIT_STRING *signature;
+  CRYPTO_refcount_t references;
+  CRYPTO_EX_DATA ex_data;
+  // These contain copies of various extension values
+  long ex_pathlen;
+  long ex_pcpathlen;
+  unsigned long ex_flags;
+  unsigned long ex_kusage;
+  unsigned long ex_xkusage;
+  unsigned long ex_nscert;
+  ASN1_OCTET_STRING *skid;
+  AUTHORITY_KEYID *akid;
+  X509_POLICY_CACHE *policy_cache;
+  STACK_OF(DIST_POINT) *crldp;
+  STACK_OF(GENERAL_NAME) *altname;
+  NAME_CONSTRAINTS *nc;
+  unsigned char sha1_hash[SHA_DIGEST_LENGTH];
+  X509_CERT_AUX *aux;
+  CRYPTO_BUFFER *buf;
+  CRYPTO_MUTEX lock;
+} /* X509 */;
+
+typedef struct {
   ASN1_ENCODING enc;
   ASN1_INTEGER *version;
   X509_NAME *subject;
@@ -153,7 +210,6 @@
   void *meth_data;
 } /* X509_CRL */;
 
-
 struct X509_VERIFY_PARAM_st {
   char *name;
   time_t check_time;                // Time to use
@@ -174,6 +230,131 @@
   unsigned char poison;  // Fail all verifications at name checking
 } /* X509_VERIFY_PARAM */;
 
+struct x509_object_st {
+  // one of the above types
+  int type;
+  union {
+    char *ptr;
+    X509 *x509;
+    X509_CRL *crl;
+    EVP_PKEY *pkey;
+  } data;
+} /* X509_OBJECT */;
+
+// This is a static that defines the function interface
+struct x509_lookup_method_st {
+  const char *name;
+  int (*new_item)(X509_LOOKUP *ctx);
+  void (*free)(X509_LOOKUP *ctx);
+  int (*init)(X509_LOOKUP *ctx);
+  int (*shutdown)(X509_LOOKUP *ctx);
+  int (*ctrl)(X509_LOOKUP *ctx, int cmd, const char *argc, long argl,
+              char **ret);
+  int (*get_by_subject)(X509_LOOKUP *ctx, int type, X509_NAME *name,
+                        X509_OBJECT *ret);
+  int (*get_by_issuer_serial)(X509_LOOKUP *ctx, int type, X509_NAME *name,
+                              ASN1_INTEGER *serial, X509_OBJECT *ret);
+  int (*get_by_fingerprint)(X509_LOOKUP *ctx, int type, unsigned char *bytes,
+                            int len, X509_OBJECT *ret);
+  int (*get_by_alias)(X509_LOOKUP *ctx, int type, char *str, int len,
+                      X509_OBJECT *ret);
+} /* X509_LOOKUP_METHOD */;
+
+// This is used to hold everything.  It is used for all certificate
+// validation.  Once we have a certificate chain, the 'verify'
+// function is then called to actually check the cert chain.
+struct x509_store_st {
+  // The following is a cache of trusted certs
+  int cache;                    // if true, stash any hits
+  STACK_OF(X509_OBJECT) *objs;  // Cache of all objects
+  CRYPTO_MUTEX objs_lock;
+  STACK_OF(X509) *additional_untrusted;
+
+  // These are external lookup methods
+  STACK_OF(X509_LOOKUP) *get_cert_methods;
+
+  X509_VERIFY_PARAM *param;
+
+  // Callbacks for various operations
+  X509_STORE_CTX_verify_fn verify;          // called to verify a certificate
+  X509_STORE_CTX_verify_cb verify_cb;       // error callback
+  X509_STORE_CTX_get_issuer_fn get_issuer;  // get issuers cert from ctx
+  X509_STORE_CTX_check_issued_fn check_issued;  // check issued
+  X509_STORE_CTX_check_revocation_fn
+      check_revocation;                   // Check revocation status of chain
+  X509_STORE_CTX_get_crl_fn get_crl;      // retrieve CRL
+  X509_STORE_CTX_check_crl_fn check_crl;  // Check CRL validity
+  X509_STORE_CTX_cert_crl_fn cert_crl;    // Check certificate against CRL
+  X509_STORE_CTX_lookup_certs_fn lookup_certs;
+  X509_STORE_CTX_lookup_crls_fn lookup_crls;
+  X509_STORE_CTX_cleanup_fn cleanup;
+
+  CRYPTO_refcount_t references;
+} /* X509_STORE */;
+
+
+// This is the functions plus an instance of the local variables.
+struct x509_lookup_st {
+  int init;                    // have we been started
+  int skip;                    // don't use us.
+  X509_LOOKUP_METHOD *method;  // the functions
+  char *method_data;           // method data
+
+  X509_STORE *store_ctx;  // who owns us
+} /* X509_LOOKUP */;
+
+// This is a used when verifying cert chains.  Since the
+// gathering of the cert chain can take some time (and have to be
+// 'retried', this needs to be kept and passed around.
+struct x509_store_ctx_st {
+  X509_STORE *ctx;
+
+  // The following are set by the caller
+  X509 *cert;                 // The cert to check
+  STACK_OF(X509) *untrusted;  // chain of X509s - untrusted - passed in
+  STACK_OF(X509_CRL) *crls;   // set of CRLs passed in
+
+  X509_VERIFY_PARAM *param;
+  void *other_ctx;  // Other info for use with get_issuer()
+
+  // Callbacks for various operations
+  X509_STORE_CTX_verify_fn verify;          // called to verify a certificate
+  X509_STORE_CTX_verify_cb verify_cb;       // error callback
+  X509_STORE_CTX_get_issuer_fn get_issuer;  // get issuers cert from ctx
+  X509_STORE_CTX_check_issued_fn check_issued;  // check issued
+  X509_STORE_CTX_check_revocation_fn
+      check_revocation;                   // Check revocation status of chain
+  X509_STORE_CTX_get_crl_fn get_crl;      // retrieve CRL
+  X509_STORE_CTX_check_crl_fn check_crl;  // Check CRL validity
+  X509_STORE_CTX_cert_crl_fn cert_crl;    // Check certificate against CRL
+  X509_STORE_CTX_check_policy_fn check_policy;
+  X509_STORE_CTX_lookup_certs_fn lookup_certs;
+  X509_STORE_CTX_lookup_crls_fn lookup_crls;
+  X509_STORE_CTX_cleanup_fn cleanup;
+
+  // The following is built up
+  int valid;               // if 0, rebuild chain
+  int last_untrusted;      // index of last untrusted cert
+  STACK_OF(X509) *chain;   // chain of X509s - built up and trusted
+  X509_POLICY_TREE *tree;  // Valid policy tree
+
+  int explicit_policy;  // Require explicit policy value
+
+  // When something goes wrong, this is why
+  int error_depth;
+  int error;
+  X509 *current_cert;
+  X509 *current_issuer;   // cert currently being tested as valid issuer
+  X509_CRL *current_crl;  // current CRL
+
+  int current_crl_score;         // score of current CRL
+  unsigned int current_reasons;  // Reason mask
+
+  X509_STORE_CTX *parent;  // For CRL path validation: parent context
+
+  CRYPTO_EX_DATA ex_data;
+} /* X509_STORE_CTX */;
+
 
 /* RSA-PSS functions. */
 
diff --git a/src/crypto/x509/name_print.c b/src/crypto/x509/name_print.c
new file mode 100644
index 0000000..b5523c0
--- /dev/null
+++ b/src/crypto/x509/name_print.c
@@ -0,0 +1,246 @@
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ *
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to.  The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ *
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    "This product includes cryptographic software written by
+ *     Eric Young (eay@cryptsoft.com)"
+ *    The word 'cryptographic' can be left out if the rouines from the library
+ *    being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from
+ *    the apps directory (application code) you must include an acknowledgement:
+ *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed.  i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.] */
+
+#include <openssl/x509.h>
+
+#include <inttypes.h>
+#include <string.h>
+
+#include <openssl/asn1.h>
+#include <openssl/bio.h>
+#include <openssl/obj.h>
+
+
+static int maybe_write(BIO *out, const void *buf, int len)
+{
+    /* If |out| is NULL, ignore the output but report the length. */
+    return out == NULL || BIO_write(out, buf, len) == len;
+}
+
+/* do_indent prints |indent| spaces to |out|. */
+static int do_indent(BIO *out, int indent)
+{
+    for (int i = 0; i < indent; i++) {
+        if (!maybe_write(out, " ", 1)) {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+#define FN_WIDTH_LN     25
+#define FN_WIDTH_SN     10
+
+static int do_name_ex(BIO *out, const X509_NAME *n, int indent,
+                      unsigned long flags)
+{
+    int i, prev = -1, orflags, cnt;
+    int fn_opt, fn_nid;
+    ASN1_OBJECT *fn;
+    ASN1_STRING *val;
+    X509_NAME_ENTRY *ent;
+    char objtmp[80];
+    const char *objbuf;
+    int outlen, len;
+    const char *sep_dn, *sep_mv, *sep_eq;
+    int sep_dn_len, sep_mv_len, sep_eq_len;
+    if (indent < 0)
+        indent = 0;
+    outlen = indent;
+    if (!do_indent(out, indent))
+        return -1;
+    switch (flags & XN_FLAG_SEP_MASK) {
+    case XN_FLAG_SEP_MULTILINE:
+        sep_dn = "\n";
+        sep_dn_len = 1;
+        sep_mv = " + ";
+        sep_mv_len = 3;
+        break;
+
+    case XN_FLAG_SEP_COMMA_PLUS:
+        sep_dn = ",";
+        sep_dn_len = 1;
+        sep_mv = "+";
+        sep_mv_len = 1;
+        indent = 0;
+        break;
+
+    case XN_FLAG_SEP_CPLUS_SPC:
+        sep_dn = ", ";
+        sep_dn_len = 2;
+        sep_mv = " + ";
+        sep_mv_len = 3;
+        indent = 0;
+        break;
+
+    case XN_FLAG_SEP_SPLUS_SPC:
+        sep_dn = "; ";
+        sep_dn_len = 2;
+        sep_mv = " + ";
+        sep_mv_len = 3;
+        indent = 0;
+        break;
+
+    default:
+        return -1;
+    }
+
+    if (flags & XN_FLAG_SPC_EQ) {
+        sep_eq = " = ";
+        sep_eq_len = 3;
+    } else {
+        sep_eq = "=";
+        sep_eq_len = 1;
+    }
+
+    fn_opt = flags & XN_FLAG_FN_MASK;
+
+    cnt = X509_NAME_entry_count(n);
+    for (i = 0; i < cnt; i++) {
+        if (flags & XN_FLAG_DN_REV)
+            ent = X509_NAME_get_entry(n, cnt - i - 1);
+        else
+            ent = X509_NAME_get_entry(n, i);
+        if (prev != -1) {
+            if (prev == X509_NAME_ENTRY_set(ent)) {
+                if (!maybe_write(out, sep_mv, sep_mv_len))
+                    return -1;
+                outlen += sep_mv_len;
+            } else {
+                if (!maybe_write(out, sep_dn, sep_dn_len))
+                    return -1;
+                outlen += sep_dn_len;
+                if (!do_indent(out, indent))
+                    return -1;
+                outlen += indent;
+            }
+        }
+        prev = X509_NAME_ENTRY_set(ent);
+        fn = X509_NAME_ENTRY_get_object(ent);
+        val = X509_NAME_ENTRY_get_data(ent);
+        fn_nid = OBJ_obj2nid(fn);
+        if (fn_opt != XN_FLAG_FN_NONE) {
+            int objlen, fld_len;
+            if ((fn_opt == XN_FLAG_FN_OID) || (fn_nid == NID_undef)) {
+                OBJ_obj2txt(objtmp, sizeof objtmp, fn, 1);
+                fld_len = 0;    /* XXX: what should this be? */
+                objbuf = objtmp;
+            } else {
+                if (fn_opt == XN_FLAG_FN_SN) {
+                    fld_len = FN_WIDTH_SN;
+                    objbuf = OBJ_nid2sn(fn_nid);
+                } else if (fn_opt == XN_FLAG_FN_LN) {
+                    fld_len = FN_WIDTH_LN;
+                    objbuf = OBJ_nid2ln(fn_nid);
+                } else {
+                    fld_len = 0; /* XXX: what should this be? */
+                    objbuf = "";
+                }
+            }
+            objlen = strlen(objbuf);
+            if (!maybe_write(out, objbuf, objlen))
+                return -1;
+            if ((objlen < fld_len) && (flags & XN_FLAG_FN_ALIGN)) {
+                if (!do_indent(out, fld_len - objlen))
+                    return -1;
+                outlen += fld_len - objlen;
+            }
+            if (!maybe_write(out, sep_eq, sep_eq_len))
+                return -1;
+            outlen += objlen + sep_eq_len;
+        }
+        /*
+         * If the field name is unknown then fix up the DER dump flag. We
+         * might want to limit this further so it will DER dump on anything
+         * other than a few 'standard' fields.
+         */
+        if ((fn_nid == NID_undef) && (flags & XN_FLAG_DUMP_UNKNOWN_FIELDS))
+            orflags = ASN1_STRFLGS_DUMP_ALL;
+        else
+            orflags = 0;
+
+        len = ASN1_STRING_print_ex(out, val, flags | orflags);
+        if (len < 0)
+            return -1;
+        outlen += len;
+    }
+    return outlen;
+}
+
+int X509_NAME_print_ex(BIO *out, const X509_NAME *nm, int indent,
+                       unsigned long flags)
+{
+    if (flags == XN_FLAG_COMPAT)
+        return X509_NAME_print(out, nm, indent);
+    return do_name_ex(out, nm, indent, flags);
+}
+
+int X509_NAME_print_ex_fp(FILE *fp, const X509_NAME *nm, int indent,
+                          unsigned long flags)
+{
+    BIO *bio = NULL;
+    if (fp != NULL) {
+        /* If |fp| is NULL, this function returns the number of bytes without
+         * writing. */
+        bio = BIO_new_fp(fp, BIO_NOCLOSE);
+        if (bio == NULL) {
+            return -1;
+        }
+    }
+    int ret = X509_NAME_print_ex(bio, nm, indent, flags);
+    BIO_free(bio);
+    return ret;
+}
diff --git a/src/crypto/x509/rsa_pss.c b/src/crypto/x509/rsa_pss.c
index 1520c08..21a6bea 100644
--- a/src/crypto/x509/rsa_pss.c
+++ b/src/crypto/x509/rsa_pss.c
@@ -67,12 +67,21 @@
 #include "internal.h"
 
 
-ASN1_SEQUENCE(RSA_PSS_PARAMS) = {
+static int rsa_pss_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it,
+                      void *exarg) {
+  if (operation == ASN1_OP_FREE_PRE) {
+    RSA_PSS_PARAMS *pss = (RSA_PSS_PARAMS *)*pval;
+    X509_ALGOR_free(pss->maskHash);
+  }
+  return 1;
+}
+
+ASN1_SEQUENCE_cb(RSA_PSS_PARAMS, rsa_pss_cb) = {
   ASN1_EXP_OPT(RSA_PSS_PARAMS, hashAlgorithm, X509_ALGOR,0),
   ASN1_EXP_OPT(RSA_PSS_PARAMS, maskGenAlgorithm, X509_ALGOR,1),
   ASN1_EXP_OPT(RSA_PSS_PARAMS, saltLength, ASN1_INTEGER,2),
   ASN1_EXP_OPT(RSA_PSS_PARAMS, trailerField, ASN1_INTEGER,3),
-} ASN1_SEQUENCE_END(RSA_PSS_PARAMS)
+} ASN1_SEQUENCE_END_cb(RSA_PSS_PARAMS, RSA_PSS_PARAMS)
 
 IMPLEMENT_ASN1_FUNCTIONS(RSA_PSS_PARAMS)
 
diff --git a/src/crypto/x509/t_crl.c b/src/crypto/x509/t_crl.c
index 42f05cd..d924f85 100644
--- a/src/crypto/x509/t_crl.c
+++ b/src/crypto/x509/t_crl.c
@@ -61,7 +61,6 @@
 #include <openssl/x509.h>
 #include <openssl/x509v3.h>
 
-#ifndef OPENSSL_NO_FP_API
 int X509_CRL_print_fp(FILE *fp, X509_CRL *x)
 {
     BIO *b = BIO_new_fp(fp, BIO_NOCLOSE);
@@ -73,7 +72,6 @@
     BIO_free(b);
     return ret;
 }
-#endif
 
 int X509_CRL_print(BIO *out, X509_CRL *x)
 {
diff --git a/src/crypto/x509/t_x509.c b/src/crypto/x509/t_x509.c
index 5db8746..7c32a87 100644
--- a/src/crypto/x509/t_x509.c
+++ b/src/crypto/x509/t_x509.c
@@ -54,7 +54,6 @@
  * copied and put under another distribution licence
  * [including the GNU Public Licence.] */
 
-#include <ctype.h>
 #include <openssl/asn1.h>
 #include <openssl/bio.h>
 #include <openssl/digest.h>
@@ -68,7 +67,6 @@
 #include "internal.h"
 
 
-#ifndef OPENSSL_NO_FP_API
 int X509_print_ex_fp(FILE *fp, X509 *x, unsigned long nmflag,
                      unsigned long cflag)
 {
@@ -86,7 +84,6 @@
 {
     return X509_print_ex_fp(fp, x, XN_FLAG_COMPAT, X509_FLAG_COMPAT);
 }
-#endif
 
 int X509_print(BIO *bp, X509 *x)
 {
@@ -318,182 +315,6 @@
     return 1;
 }
 
-int ASN1_STRING_print(BIO *bp, const ASN1_STRING *v)
-{
-    int i, n;
-    char buf[80];
-    const char *p;
-
-    if (v == NULL)
-        return (0);
-    n = 0;
-    p = (const char *)v->data;
-    for (i = 0; i < v->length; i++) {
-        if ((p[i] > '~') || ((p[i] < ' ') &&
-                             (p[i] != '\n') && (p[i] != '\r')))
-            buf[n] = '.';
-        else
-            buf[n] = p[i];
-        n++;
-        if (n >= 80) {
-            if (BIO_write(bp, buf, n) <= 0)
-                return (0);
-            n = 0;
-        }
-    }
-    if (n > 0)
-        if (BIO_write(bp, buf, n) <= 0)
-            return (0);
-    return (1);
-}
-
-int ASN1_TIME_print(BIO *bp, const ASN1_TIME *tm)
-{
-    if (tm->type == V_ASN1_UTCTIME)
-        return ASN1_UTCTIME_print(bp, tm);
-    if (tm->type == V_ASN1_GENERALIZEDTIME)
-        return ASN1_GENERALIZEDTIME_print(bp, tm);
-    BIO_write(bp, "Bad time value", 14);
-    return (0);
-}
-
-static const char *const mon[12] = {
-    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
-int ASN1_GENERALIZEDTIME_print(BIO *bp, const ASN1_GENERALIZEDTIME *tm)
-{
-    char *v;
-    int gmt = 0;
-    int i;
-    int y = 0, M = 0, d = 0, h = 0, m = 0, s = 0;
-    char *f = NULL;
-    int f_len = 0;
-
-    i = tm->length;
-    v = (char *)tm->data;
-
-    if (i < 12)
-        goto err;
-    if (v[i - 1] == 'Z')
-        gmt = 1;
-    for (i = 0; i < 12; i++)
-        if ((v[i] > '9') || (v[i] < '0'))
-            goto err;
-    y = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + (v[2] - '0') * 10 + (v[3] -
-                                                                        '0');
-    M = (v[4] - '0') * 10 + (v[5] - '0');
-    if ((M > 12) || (M < 1))
-        goto err;
-    d = (v[6] - '0') * 10 + (v[7] - '0');
-    h = (v[8] - '0') * 10 + (v[9] - '0');
-    m = (v[10] - '0') * 10 + (v[11] - '0');
-    if (tm->length >= 14 &&
-        (v[12] >= '0') && (v[12] <= '9') &&
-        (v[13] >= '0') && (v[13] <= '9')) {
-        s = (v[12] - '0') * 10 + (v[13] - '0');
-        /* Check for fractions of seconds. */
-        if (tm->length >= 15 && v[14] == '.') {
-            int l = tm->length;
-            f = &v[14];         /* The decimal point. */
-            f_len = 1;
-            while (14 + f_len < l && f[f_len] >= '0' && f[f_len] <= '9')
-                ++f_len;
-        }
-    }
-
-    if (BIO_printf(bp, "%s %2d %02d:%02d:%02d%.*s %d%s",
-                   mon[M - 1], d, h, m, s, f_len, f, y,
-                   (gmt) ? " GMT" : "") <= 0)
-        return (0);
-    else
-        return (1);
- err:
-    BIO_write(bp, "Bad time value", 14);
-    return (0);
-}
-
-// consume_two_digits is a helper function for ASN1_UTCTIME_print. If |*v|,
-// assumed to be |*len| bytes long, has two leading digits, updates |*out| with
-// their value, updates |v| and |len|, and returns one. Otherwise, returns
-// zero.
-static int consume_two_digits(int* out, const char **v, int *len) {
-  if (*len < 2|| !isdigit((*v)[0]) || !isdigit((*v)[1])) {
-    return 0;
-  }
-  *out = ((*v)[0] - '0') * 10 + ((*v)[1] - '0');
-  *len -= 2;
-  *v += 2;
-  return 1;
-}
-
-// consume_zulu_timezone is a helper function for ASN1_UTCTIME_print. If |*v|,
-// assumed to be |*len| bytes long, starts with "Z" then it updates |*v| and
-// |*len| and returns one. Otherwise returns zero.
-static int consume_zulu_timezone(const char **v, int *len) {
-  if (*len == 0 || (*v)[0] != 'Z') {
-    return 0;
-  }
-
-  *len -= 1;
-  *v += 1;
-  return 1;
-}
-
-int ASN1_UTCTIME_print(BIO *bp, const ASN1_UTCTIME *tm) {
-  const char *v = (const char *)tm->data;
-  int len = tm->length;
-  int Y = 0, M = 0, D = 0, h = 0, m = 0, s = 0;
-
-  // YYMMDDhhmm are required to be present.
-  if (!consume_two_digits(&Y, &v, &len) ||
-      !consume_two_digits(&M, &v, &len) ||
-      !consume_two_digits(&D, &v, &len) ||
-      !consume_two_digits(&h, &v, &len) ||
-      !consume_two_digits(&m, &v, &len)) {
-    goto err;
-  }
-  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, requires seconds
-  // to be present, but historically this code has forgiven its absence.
-  consume_two_digits(&s, &v, &len);
-
-  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, specifies this
-  // interpretation of the year.
-  if (Y < 50) {
-    Y += 2000;
-  } else {
-    Y += 1900;
-  }
-  if (M > 12 || M == 0) {
-    goto err;
-  }
-  if (D > 31 || D == 0) {
-    goto err;
-  }
-  if (h > 23 || m > 59 || s > 60) {
-    goto err;
-  }
-
-  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, requires the "Z"
-  // to be present, but historically this code has forgiven its absence.
-  const int is_gmt = consume_zulu_timezone(&v, &len);
-
-  // https://tools.ietf.org/html/rfc5280, section 4.1.2.5.1, does not permit
-  // the specification of timezones using the +hhmm / -hhmm syntax, which is
-  // the only other thing that might legitimately be found at the end.
-  if (len) {
-    goto err;
-  }
-
-  return BIO_printf(bp, "%s %2d %02d:%02d:%02d %d%s", mon[M - 1], D, h, m, s, Y,
-                    is_gmt ? " GMT" : "") > 0;
-
-err:
-  BIO_write(bp, "Bad time value", 14);
-  return 0;
-}
-
 int X509_NAME_print(BIO *bp, const X509_NAME *name, int obase)
 {
     char *s, *c, *b;
diff --git a/src/crypto/x509/t_x509a.c b/src/crypto/x509/t_x509a.c
index 7fbb47b..4c7b212 100644
--- a/src/crypto/x509/t_x509a.c
+++ b/src/crypto/x509/t_x509a.c
@@ -102,8 +102,10 @@
         BIO_puts(out, "\n");
     } else
         BIO_printf(out, "%*sNo Rejected Uses.\n", indent, "");
-    if (aux->alias)
-        BIO_printf(out, "%*sAlias: %s\n", indent, "", aux->alias->data);
+    if (aux->alias) {
+        BIO_printf(out, "%*sAlias: %.*s\n", indent, "", aux->alias->length,
+                   aux->alias->data);
+    }
     if (aux->keyid) {
         BIO_printf(out, "%*sKey Id: ", indent, "");
         for (j = 0; j < aux->keyid->length; j++)
diff --git a/src/crypto/x509/x509_obj.c b/src/crypto/x509/x509_obj.c
index 80d16c1..df54f77 100644
--- a/src/crypto/x509/x509_obj.c
+++ b/src/crypto/x509/x509_obj.c
@@ -64,6 +64,7 @@
 #include <openssl/x509.h>
 
 #include "../internal.h"
+#include "internal.h"
 
 
 /*
diff --git a/src/crypto/x509/x509_test.cc b/src/crypto/x509/x509_test.cc
index fde8bd5..848bd07 100644
--- a/src/crypto/x509/x509_test.cc
+++ b/src/crypto/x509/x509_test.cc
@@ -32,6 +32,7 @@
 #include <openssl/x509.h>
 #include <openssl/x509v3.h>
 
+#include "internal.h"
 #include "../internal.h"
 #include "../test/test_util.h"
 #include "../x509v3/internal.h"
@@ -1105,6 +1106,8 @@
   return stack;
 }
 
+static const time_t kReferenceTime = 1474934400 /* Sep 27th, 2016 */;
+
 static int Verify(X509 *leaf, const std::vector<X509 *> &roots,
                   const std::vector<X509 *> &intermediates,
                   const std::vector<X509_CRL *> &crls, unsigned long flags,
@@ -1147,7 +1150,7 @@
   if (param == nullptr) {
     return X509_V_ERR_UNSPECIFIED;
   }
-  X509_VERIFY_PARAM_set_time(param, 1474934400 /* Sep 27th, 2016 */);
+  X509_VERIFY_PARAM_set_time(param, kReferenceTime);
   X509_VERIFY_PARAM_set_depth(param, 16);
   if (configure_callback) {
     configure_callback(param);
@@ -1490,6 +1493,211 @@
                               {many_constraints.get()}, {}));
 }
 
+static bssl::UniquePtr<GENERAL_NAME> MakeGeneralName(int type,
+                                                     const std::string &value) {
+  if (type != GEN_EMAIL && type != GEN_DNS && type != GEN_URI) {
+    // This function only supports the IA5String types.
+    return nullptr;
+  }
+  bssl::UniquePtr<ASN1_IA5STRING> str(ASN1_IA5STRING_new());
+  bssl::UniquePtr<GENERAL_NAME> name(GENERAL_NAME_new());
+  if (!str || !name ||
+      !ASN1_STRING_set(str.get(), value.data(), value.size())) {
+    return nullptr;
+  }
+
+  name->type = type;
+  name->d.ia5 = str.release();
+  return name;
+}
+
+static bssl::UniquePtr<X509> MakeTestCert(const char *issuer,
+                                          const char *subject, EVP_PKEY *key) {
+  bssl::UniquePtr<X509> cert(X509_new());
+  if (!cert ||  //
+      !X509_set_version(cert.get(), X509_VERSION_3) ||
+      !X509_NAME_add_entry_by_txt(
+          X509_get_issuer_name(cert.get()), "CN", MBSTRING_UTF8,
+          reinterpret_cast<const uint8_t *>(issuer), -1, -1, 0) ||
+      !X509_NAME_add_entry_by_txt(
+          X509_get_subject_name(cert.get()), "CN", MBSTRING_UTF8,
+          reinterpret_cast<const uint8_t *>(subject), -1, -1, 0) ||
+      !X509_set_pubkey(cert.get(), key) ||
+      !ASN1_TIME_adj(X509_getm_notBefore(cert.get()), kReferenceTime, -1, 0) ||
+      !ASN1_TIME_adj(X509_getm_notAfter(cert.get()), kReferenceTime, 1, 0)) {
+    return nullptr;
+  }
+  return cert;
+}
+
+TEST(X509Test, NameConstraints) {
+  bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key);
+  ASSERT_TRUE(key);
+
+  const struct {
+    int type;
+    std::string name;
+    std::string constraint;
+    int result;
+  } kTests[] = {
+      // Empty string matches everything.
+      {GEN_DNS, "foo.example.com", "", X509_V_OK},
+      // Name constraints match the entire subtree.
+      {GEN_DNS, "foo.example.com", "example.com", X509_V_OK},
+      {GEN_DNS, "foo.example.com", "EXAMPLE.COM", X509_V_OK},
+      {GEN_DNS, "foo.example.com", "xample.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_DNS, "foo.example.com", "unrelated.much.longer.name.example",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      // A leading dot means at least one component must be added.
+      {GEN_DNS, "foo.example.com", ".example.com", X509_V_OK},
+      {GEN_DNS, "foo.example.com", "foo.example.com", X509_V_OK},
+      {GEN_DNS, "foo.example.com", ".foo.example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_DNS, "foo.example.com", ".xample.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_DNS, "foo.example.com", ".unrelated.much.longer.name.example",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      // NUL bytes, if not rejected, should not confuse the matching logic.
+      {GEN_DNS, std::string({'a', '\0', 'a'}), std::string({'a', '\0', 'b'}),
+       X509_V_ERR_PERMITTED_VIOLATION},
+
+      // Names must be emails.
+      {GEN_EMAIL, "not-an-email.example", "not-an-email.example",
+       X509_V_ERR_UNSUPPORTED_NAME_SYNTAX},
+      // A leading dot matches all local names and all subdomains
+      {GEN_EMAIL, "foo@bar.example.com", ".example.com", X509_V_OK},
+      {GEN_EMAIL, "foo@bar.example.com", ".EXAMPLE.COM", X509_V_OK},
+      {GEN_EMAIL, "foo@bar.example.com", ".bar.example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      // Without a leading dot, the host must match exactly.
+      {GEN_EMAIL, "foo@example.com", "example.com", X509_V_OK},
+      {GEN_EMAIL, "foo@example.com", "EXAMPLE.COM", X509_V_OK},
+      {GEN_EMAIL, "foo@bar.example.com", "example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      // If the constraint specifies a mailbox, it specifies the whole thing.
+      // The halves are compared insensitively.
+      {GEN_EMAIL, "foo@example.com", "foo@example.com", X509_V_OK},
+      {GEN_EMAIL, "foo@example.com", "foo@EXAMPLE.COM", X509_V_OK},
+      {GEN_EMAIL, "foo@example.com", "FOO@example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_EMAIL, "foo@example.com", "bar@example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      // OpenSSL ignores a stray leading @.
+      {GEN_EMAIL, "foo@example.com", "@example.com", X509_V_OK},
+      {GEN_EMAIL, "foo@example.com", "@EXAMPLE.COM", X509_V_OK},
+      {GEN_EMAIL, "foo@bar.example.com", "@example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+
+      // Basic syntax check.
+      {GEN_URI, "not-a-url", "not-a-url", X509_V_ERR_UNSUPPORTED_NAME_SYNTAX},
+      {GEN_URI, "foo:not-a-url", "not-a-url",
+       X509_V_ERR_UNSUPPORTED_NAME_SYNTAX},
+      {GEN_URI, "foo:/not-a-url", "not-a-url",
+       X509_V_ERR_UNSUPPORTED_NAME_SYNTAX},
+      {GEN_URI, "foo:///not-a-url", "not-a-url",
+       X509_V_ERR_UNSUPPORTED_NAME_SYNTAX},
+      {GEN_URI, "foo://:not-a-url", "not-a-url",
+       X509_V_ERR_UNSUPPORTED_NAME_SYNTAX},
+      {GEN_URI, "foo://", "not-a-url", X509_V_ERR_UNSUPPORTED_NAME_SYNTAX},
+      // Hosts are an exact match.
+      {GEN_URI, "foo://example.com", "example.com", X509_V_OK},
+      {GEN_URI, "foo://example.com:443", "example.com", X509_V_OK},
+      {GEN_URI, "foo://example.com/whatever", "example.com", X509_V_OK},
+      {GEN_URI, "foo://bar.example.com", "example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://bar.example.com:443", "example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://bar.example.com/whatever", "example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://bar.example.com", "xample.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://bar.example.com:443", "xample.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://bar.example.com/whatever", "xample.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com", "some-other-name.example",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com:443", "some-other-name.example",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com/whatever", "some-other-name.example",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      // A leading dot allows components to be added.
+      {GEN_URI, "foo://example.com", ".example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com:443", ".example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com/whatever", ".example.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://bar.example.com", ".example.com", X509_V_OK},
+      {GEN_URI, "foo://bar.example.com:443", ".example.com", X509_V_OK},
+      {GEN_URI, "foo://bar.example.com/whatever", ".example.com", X509_V_OK},
+      {GEN_URI, "foo://example.com", ".some-other-name.example",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com:443", ".some-other-name.example",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com/whatever", ".some-other-name.example",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com", ".xample.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com:443", ".xample.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+      {GEN_URI, "foo://example.com/whatever", ".xample.com",
+       X509_V_ERR_PERMITTED_VIOLATION},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(t.type);
+    SCOPED_TRACE(t.name);
+    SCOPED_TRACE(t.constraint);
+
+    bssl::UniquePtr<GENERAL_NAME> name = MakeGeneralName(t.type, t.name);
+    ASSERT_TRUE(name);
+    bssl::UniquePtr<GENERAL_NAMES> names(GENERAL_NAMES_new());
+    ASSERT_TRUE(names);
+    ASSERT_TRUE(bssl::PushToStack(names.get(), std::move(name)));
+
+    bssl::UniquePtr<NAME_CONSTRAINTS> nc(NAME_CONSTRAINTS_new());
+    ASSERT_TRUE(nc);
+    nc->permittedSubtrees = sk_GENERAL_SUBTREE_new_null();
+    ASSERT_TRUE(nc->permittedSubtrees);
+    bssl::UniquePtr<GENERAL_SUBTREE> subtree(GENERAL_SUBTREE_new());
+    ASSERT_TRUE(subtree);
+    GENERAL_NAME_free(subtree->base);
+    subtree->base = MakeGeneralName(t.type, t.constraint).release();
+    ASSERT_TRUE(subtree->base);
+    ASSERT_TRUE(bssl::PushToStack(nc->permittedSubtrees, std::move(subtree)));
+
+    bssl::UniquePtr<X509> root = MakeTestCert("Root", "Root", key.get());
+    ASSERT_TRUE(root);
+    ASSERT_TRUE(X509_add1_ext_i2d(root.get(), NID_name_constraints, nc.get(),
+                                  /*crit=*/1, /*flags=*/0));
+    ASSERT_TRUE(X509_sign(root.get(), key.get(), EVP_sha256()));
+
+    bssl::UniquePtr<X509> leaf = MakeTestCert("Root", "Leaf", key.get());
+    ASSERT_TRUE(leaf);
+    ASSERT_TRUE(X509_add1_ext_i2d(leaf.get(), NID_subject_alt_name, names.get(),
+                                  /*crit=*/0, /*flags=*/0));
+    ASSERT_TRUE(X509_sign(leaf.get(), key.get(), EVP_sha256()));
+
+    int ret = Verify(leaf.get(), {root.get()}, {}, {}, 0);
+    EXPECT_EQ(t.result, ret) << X509_verify_cert_error_string(ret);
+  }
+}
+
+TEST(X509Test, PrintGeneralName) {
+  // TODO(https://crbug.com/boringssl/430): Add more tests. Also fix the
+  // external projects that use this to extract the SAN list and unexport.
+  bssl::UniquePtr<GENERAL_NAME> gen = MakeGeneralName(GEN_DNS, "example.com");
+  ASSERT_TRUE(gen);
+  bssl::UniquePtr<STACK_OF(CONF_VALUE)> values(
+      i2v_GENERAL_NAME(nullptr, gen.get(), nullptr));
+  ASSERT_TRUE(values);
+  ASSERT_EQ(1u, sk_CONF_VALUE_num(values.get()));
+  const CONF_VALUE *value = sk_CONF_VALUE_value(values.get(), 0);
+  EXPECT_STREQ(value->name, "DNS");
+  EXPECT_STREQ(value->value, "example.com");
+}
+
 TEST(X509Test, TestPSS) {
   bssl::UniquePtr<X509> cert(CertFromPEM(kExamplePSSCert));
   ASSERT_TRUE(cert);
@@ -2566,11 +2774,6 @@
 -----END CERTIFICATE-----
 )";
 
-/*
-
-Test cases disabled. TODO re-enable in April 2021.
-https://crbug.com/boringssl/375
-
 // kV1WithExtensionsPEM is an X.509v1 certificate with extensions.
 static const char kV1WithExtensionsPEM[] = R"(
 -----BEGIN CERTIFICATE-----
@@ -2602,7 +2805,6 @@
 BwIgfB55FGohg/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQ=
 -----END CERTIFICATE-----
 )";
-*/
 
 // kV1WithIssuerUniqueIDPEM is an X.509v1 certificate with an issuerUniqueID.
 static const char kV1WithIssuerUniqueIDPEM[] = R"(
@@ -2644,10 +2846,8 @@
   EXPECT_FALSE(CertFromPEM(kNegativeVersionPEM));
   EXPECT_FALSE(CertFromPEM(kFutureVersionPEM));
   EXPECT_FALSE(CertFromPEM(kOverflowVersionPEM));
-  // Test cases disabled. TODO re-enable in April 2021.
-  // https://crbug.com/boringssl/375
-  //EXPECT_FALSE(CertFromPEM(kV1WithExtensionsPEM));
-  //EXPECT_FALSE(CertFromPEM(kV2WithExtensionsPEM));
+  EXPECT_FALSE(CertFromPEM(kV1WithExtensionsPEM));
+  EXPECT_FALSE(CertFromPEM(kV2WithExtensionsPEM));
   EXPECT_FALSE(CertFromPEM(kV1WithIssuerUniqueIDPEM));
   EXPECT_FALSE(CertFromPEM(kV1WithSubjectUniqueIDPEM));
 }
@@ -3067,7 +3267,7 @@
 
 // Test the various |X509_ATTRIBUTE| creation functions.
 TEST(X509Test, Attribute) {
-  // The friendlyName attribute has a BMPString value. See RFC2985,
+  // The friendlyName attribute has a BMPString value. See RFC 2985,
   // section 5.5.1.
   static const uint8_t kTest1[] = {0x26, 0x03};  // U+2603 SNOWMAN
   static const uint8_t kTest1UTF8[] = {0xe2, 0x98, 0x83};
diff --git a/src/crypto/x509/x509_vfy.c b/src/crypto/x509/x509_vfy.c
index 5ed4edd..818459c 100644
--- a/src/crypto/x509/x509_vfy.c
+++ b/src/crypto/x509/x509_vfy.c
@@ -1403,12 +1403,12 @@
 }
 
 /*
- * RFC3280 says nothing about the relationship between CRL path and
+ * RFC 3280 says nothing about the relationship between CRL path and
  * certificate path, which could lead to situations where a certificate could
- * be revoked or validated by a CA not authorised to do so. RFC5280 is more
+ * be revoked or validated by a CA not authorised to do so. RFC 5280 is more
  * strict and states that the two paths must end in the same trust anchor,
  * though some discussions remain... until this is resolved we use the
- * RFC5280 version
+ * RFC 5280 version
  */
 
 static int check_crl_chain(X509_STORE_CTX *ctx,
@@ -1919,8 +1919,8 @@
     int i, day, sec, ret = 0;
 
     /*
-     * Note that ASN.1 allows much more slack in the time format than RFC5280.
-     * In RFC5280, the representation is fixed:
+     * Note that ASN.1 allows much more slack in the time format than RFC 5280.
+     * In RFC 5280, the representation is fixed:
      * UTCTime: YYMMDDHHMMSSZ
      * GeneralizedTime: YYYYMMDDHHMMSSZ
      *
@@ -1976,9 +1976,9 @@
     return ret;
 }
 
-ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long adj)
+ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long offset_sec)
 {
-    return X509_time_adj(s, adj, NULL);
+    return X509_time_adj(s, offset_sec, NULL);
 }
 
 ASN1_TIME *X509_time_adj(ASN1_TIME *s, long offset_sec, time_t *in_tm)
@@ -1991,17 +1991,12 @@
 {
     time_t t = 0;
 
-    if (in_tm)
+    if (in_tm) {
         t = *in_tm;
-    else
+    } else {
         time(&t);
-
-    if (s && !(s->flags & ASN1_STRING_FLAG_MSTRING)) {
-        if (s->type == V_ASN1_UTCTIME)
-            return ASN1_UTCTIME_adj(s, t, offset_day, offset_sec);
-        if (s->type == V_ASN1_GENERALIZEDTIME)
-            return ASN1_GENERALIZEDTIME_adj(s, t, offset_day, offset_sec);
     }
+
     return ASN1_TIME_adj(s, t, offset_day, offset_sec);
 }
 
diff --git a/src/crypto/x509/x509name.c b/src/crypto/x509/x509name.c
index 0bf3459..6bc0952 100644
--- a/src/crypto/x509/x509name.c
+++ b/src/crypto/x509/x509name.c
@@ -64,6 +64,7 @@
 #include <openssl/x509.h>
 
 #include "../internal.h"
+#include "internal.h"
 
 
 int X509_NAME_get_text_by_NID(const X509_NAME *name, int nid, char *buf,
@@ -367,10 +368,7 @@
     if (!i)
         return (0);
     if (type != V_ASN1_UNDEF) {
-        if (type == V_ASN1_APP_CHOOSE)
-            ne->value->type = ASN1_PRINTABLE_type(bytes, len);
-        else
-            ne->value->type = type;
+        ne->value->type = type;
     }
     return (1);
 }
diff --git a/src/crypto/x509/x_all.c b/src/crypto/x509/x_all.c
index 65347f9..7ceff50 100644
--- a/src/crypto/x509/x_all.c
+++ b/src/crypto/x509/x_all.c
@@ -140,7 +140,6 @@
                              spki->signature, spki->spkac, pkey));
 }
 
-#ifndef OPENSSL_NO_FP_API
 X509 *d2i_X509_fp(FILE *fp, X509 **x509)
 {
     return ASN1_item_d2i_fp(ASN1_ITEM_rptr(X509), fp, x509);
@@ -150,7 +149,6 @@
 {
     return ASN1_item_i2d_fp(ASN1_ITEM_rptr(X509), fp, x509);
 }
-#endif
 
 X509 *d2i_X509_bio(BIO *bp, X509 **x509)
 {
@@ -162,7 +160,6 @@
     return ASN1_item_i2d_bio(ASN1_ITEM_rptr(X509), bp, x509);
 }
 
-#ifndef OPENSSL_NO_FP_API
 X509_CRL *d2i_X509_CRL_fp(FILE *fp, X509_CRL **crl)
 {
     return ASN1_item_d2i_fp(ASN1_ITEM_rptr(X509_CRL), fp, crl);
@@ -172,7 +169,6 @@
 {
     return ASN1_item_i2d_fp(ASN1_ITEM_rptr(X509_CRL), fp, crl);
 }
-#endif
 
 X509_CRL *d2i_X509_CRL_bio(BIO *bp, X509_CRL **crl)
 {
@@ -184,7 +180,6 @@
     return ASN1_item_i2d_bio(ASN1_ITEM_rptr(X509_CRL), bp, crl);
 }
 
-#ifndef OPENSSL_NO_FP_API
 X509_REQ *d2i_X509_REQ_fp(FILE *fp, X509_REQ **req)
 {
     return ASN1_item_d2i_fp(ASN1_ITEM_rptr(X509_REQ), fp, req);
@@ -194,7 +189,6 @@
 {
     return ASN1_item_i2d_fp(ASN1_ITEM_rptr(X509_REQ), fp, req);
 }
-#endif
 
 X509_REQ *d2i_X509_REQ_bio(BIO *bp, X509_REQ **req)
 {
@@ -206,7 +200,6 @@
     return ASN1_item_i2d_bio(ASN1_ITEM_rptr(X509_REQ), bp, req);
 }
 
-#ifndef OPENSSL_NO_FP_API
 
 #define IMPLEMENT_D2I_FP(type, name, bio_func) \
   type *name(FILE *fp, type **obj) {           \
@@ -238,7 +231,6 @@
 
 IMPLEMENT_D2I_FP(RSA, d2i_RSA_PUBKEY_fp, d2i_RSA_PUBKEY_bio)
 IMPLEMENT_I2D_FP(RSA, i2d_RSA_PUBKEY_fp, i2d_RSA_PUBKEY_bio)
-#endif
 
 #define IMPLEMENT_D2I_BIO(type, name, d2i_func)         \
   type *name(BIO *bio, type **obj) {                    \
@@ -275,13 +267,11 @@
 IMPLEMENT_I2D_BIO(RSA, i2d_RSA_PUBKEY_bio, i2d_RSA_PUBKEY)
 
 #ifndef OPENSSL_NO_DSA
-# ifndef OPENSSL_NO_FP_API
 IMPLEMENT_D2I_FP(DSA, d2i_DSAPrivateKey_fp, d2i_DSAPrivateKey_bio)
 IMPLEMENT_I2D_FP(DSA, i2d_DSAPrivateKey_fp, i2d_DSAPrivateKey_bio)
 
 IMPLEMENT_D2I_FP(DSA, d2i_DSA_PUBKEY_fp, d2i_DSA_PUBKEY_bio)
 IMPLEMENT_I2D_FP(DSA, i2d_DSA_PUBKEY_fp, i2d_DSA_PUBKEY_bio)
-# endif
 
 IMPLEMENT_D2I_BIO(DSA, d2i_DSAPrivateKey_bio, d2i_DSAPrivateKey)
 IMPLEMENT_I2D_BIO(DSA, i2d_DSAPrivateKey_bio, i2d_DSAPrivateKey)
@@ -290,13 +280,11 @@
 IMPLEMENT_I2D_BIO(DSA, i2d_DSA_PUBKEY_bio, i2d_DSA_PUBKEY)
 #endif
 
-#ifndef OPENSSL_NO_FP_API
 IMPLEMENT_D2I_FP(EC_KEY, d2i_ECPrivateKey_fp, d2i_ECPrivateKey_bio)
 IMPLEMENT_I2D_FP(EC_KEY, i2d_ECPrivateKey_fp, i2d_ECPrivateKey_bio)
 
 IMPLEMENT_D2I_FP(EC_KEY, d2i_EC_PUBKEY_fp, d2i_EC_PUBKEY_bio)
 IMPLEMENT_I2D_FP(EC_KEY, i2d_EC_PUBKEY_fp, i2d_EC_PUBKEY_bio)
-#endif
 
 IMPLEMENT_D2I_BIO(EC_KEY, d2i_ECPrivateKey_bio, d2i_ECPrivateKey)
 IMPLEMENT_I2D_BIO(EC_KEY, i2d_ECPrivateKey_bio, i2d_ECPrivateKey)
@@ -342,15 +330,12 @@
             (ASN1_ITEM_rptr(X509_NAME), type, (char *)data, md, len));
 }
 
-#ifndef OPENSSL_NO_FP_API
 IMPLEMENT_D2I_FP(X509_SIG, d2i_PKCS8_fp, d2i_PKCS8_bio)
 IMPLEMENT_I2D_FP(X509_SIG, i2d_PKCS8_fp, i2d_PKCS8_bio)
-#endif
 
 IMPLEMENT_D2I_BIO(X509_SIG, d2i_PKCS8_bio, d2i_X509_SIG)
 IMPLEMENT_I2D_BIO(X509_SIG, i2d_PKCS8_bio, i2d_X509_SIG)
 
-#ifndef OPENSSL_NO_FP_API
 IMPLEMENT_D2I_FP(PKCS8_PRIV_KEY_INFO, d2i_PKCS8_PRIV_KEY_INFO_fp,
                  d2i_PKCS8_PRIV_KEY_INFO_bio)
 IMPLEMENT_I2D_FP(PKCS8_PRIV_KEY_INFO, i2d_PKCS8_PRIV_KEY_INFO_fp,
@@ -390,7 +375,6 @@
     PKCS8_PRIV_KEY_INFO_free(p8inf);
     return ret;
 }
-#endif
 
 IMPLEMENT_D2I_BIO(EVP_PKEY, d2i_PrivateKey_bio, d2i_AutoPrivateKey)
 IMPLEMENT_I2D_BIO(EVP_PKEY, i2d_PrivateKey_bio, i2d_PrivateKey)
diff --git a/src/crypto/x509/x_name.c b/src/crypto/x509/x_name.c
index e56f2c3..4fea082 100644
--- a/src/crypto/x509/x_name.c
+++ b/src/crypto/x509/x_name.c
@@ -68,6 +68,7 @@
 
 #include "../asn1/internal.h"
 #include "../internal.h"
+#include "internal.h"
 
 
 typedef STACK_OF(X509_NAME_ENTRY) STACK_OF_X509_NAME_ENTRY;
@@ -260,17 +261,13 @@
 static int x509_name_ex_i2d(ASN1_VALUE **val, unsigned char **out,
                             const ASN1_ITEM *it, int tag, int aclass)
 {
-    int ret;
     X509_NAME *a = (X509_NAME *)*val;
-    if (a->modified) {
-        ret = x509_name_encode(a);
-        if (ret < 0)
-            return ret;
-        ret = x509_name_canon(a);
-        if (ret < 0)
-            return ret;
+    if (a->modified &&
+        (!x509_name_encode(a) ||
+         !x509_name_canon(a))) {
+        return -1;
     }
-    ret = a->bytes->length;
+    int ret = a->bytes->length;
     if (out != NULL) {
         OPENSSL_memcpy(*out, a->bytes->data, ret);
         *out += ret;
@@ -306,22 +303,29 @@
             goto memerr;
     }
     ASN1_VALUE *intname_val = (ASN1_VALUE *)intname;
-    len = ASN1_item_ex_i2d(&intname_val, NULL,
-                           ASN1_ITEM_rptr(X509_NAME_INTERNAL), -1, -1);
+    len =
+        ASN1_item_ex_i2d(&intname_val, NULL, ASN1_ITEM_rptr(X509_NAME_INTERNAL),
+                         /*tag=*/-1, /*aclass=*/0);
+    if (len <= 0) {
+      goto err;
+    }
     if (!BUF_MEM_grow(a->bytes, len))
         goto memerr;
     p = (unsigned char *)a->bytes->data;
-    ASN1_item_ex_i2d(&intname_val,
-                     &p, ASN1_ITEM_rptr(X509_NAME_INTERNAL), -1, -1);
+    if (ASN1_item_ex_i2d(&intname_val, &p, ASN1_ITEM_rptr(X509_NAME_INTERNAL),
+                         /*tag=*/-1, /*aclass=*/0) <= 0) {
+        goto err;
+    }
     sk_STACK_OF_X509_NAME_ENTRY_pop_free(intname,
                                          local_sk_X509_NAME_ENTRY_free);
     a->modified = 0;
-    return len;
+    return 1;
  memerr:
+    OPENSSL_PUT_ERROR(X509, ERR_R_MALLOC_FAILURE);
+err:
     sk_STACK_OF_X509_NAME_ENTRY_pop_free(intname,
                                          local_sk_X509_NAME_ENTRY_free);
-    OPENSSL_PUT_ERROR(X509, ERR_R_MALLOC_FAILURE);
-    return -1;
+    return 0;
 }
 
 /*
@@ -503,8 +507,8 @@
     len = 0;
     for (i = 0; i < sk_ASN1_VALUE_num(intname); i++) {
         v = sk_ASN1_VALUE_value(intname, i);
-        ltmp = ASN1_item_ex_i2d(&v, in,
-                                ASN1_ITEM_rptr(X509_NAME_ENTRIES), -1, -1);
+        ltmp = ASN1_item_ex_i2d(&v, in, ASN1_ITEM_rptr(X509_NAME_ENTRIES),
+                                /*tag=*/-1, /*aclass=*/0);
         if (ltmp < 0)
             return ltmp;
         len += ltmp;
diff --git a/src/crypto/x509/x_x509.c b/src/crypto/x509/x_x509.c
index f6b63b6..9d350bd 100644
--- a/src/crypto/x509/x_x509.c
+++ b/src/crypto/x509/x_x509.c
@@ -69,6 +69,7 @@
 #include <openssl/x509v3.h>
 
 #include "../internal.h"
+#include "internal.h"
 
 static CRYPTO_EX_DATA_CLASS g_ex_data_class = CRYPTO_EX_DATA_CLASS_INIT;
 
@@ -128,20 +129,18 @@
             }
         }
 
-        /* Per RFC5280, section 4.1.2.8, these fields require v2 or v3. */
+        /* Per RFC 5280, section 4.1.2.8, these fields require v2 or v3. */
         if (version == 0 && (ret->cert_info->issuerUID != NULL ||
                              ret->cert_info->subjectUID != NULL)) {
             OPENSSL_PUT_ERROR(X509, X509_R_INVALID_FIELD_FOR_VERSION);
             return 0;
         }
 
-        /* Per RFC5280, section 4.1.2.9, extensions require v3. */
-        /* Check disabled. TODO re-enable in April 2021.
-           https://crbug.com/boringssl/375
+        /* Per RFC 5280, section 4.1.2.9, extensions require v3. */
         if (version != 2 && ret->cert_info->extensions != NULL) {
             OPENSSL_PUT_ERROR(X509, X509_R_INVALID_FIELD_FOR_VERSION);
             return 0;
-        }*/
+        }
 
         break;
     }
@@ -290,13 +289,15 @@
         return length;
     }
 
-    tmplen = i2d_X509_CERT_AUX(a->aux, pp);
-    if (tmplen < 0) {
-        if (start != NULL)
-            *pp = start;
-        return tmplen;
+    if (a->aux != NULL) {
+        tmplen = i2d_X509_CERT_AUX(a->aux, pp);
+        if (tmplen < 0) {
+            if (start != NULL)
+                *pp = start;
+            return tmplen;
+        }
+        length += tmplen;
     }
-    length += tmplen;
 
     return length;
 }
diff --git a/src/crypto/x509v3/internal.h b/src/crypto/x509v3/internal.h
index e510b40..df7d813 100644
--- a/src/crypto/x509v3/internal.h
+++ b/src/crypto/x509v3/internal.h
@@ -17,6 +17,8 @@
 
 #include <openssl/base.h>
 
+#include <openssl/conf.h>
+
 #if defined(__cplusplus)
 extern "C" {
 #endif
@@ -60,6 +62,20 @@
 // to all 16 bytes of |ipout| and returns 16. Otherwise, it returns zero.
 int x509v3_a2i_ipadd(unsigned char ipout[16], const char *ipasc);
 
+// A |BIT_STRING_BITNAME| is used to contain a list of bit names.
+typedef struct {
+  int bitnum;
+  const char *lname;
+  const char *sname;
+} BIT_STRING_BITNAME;
+
+// x509V3_add_value_asn1_string appends a |CONF_VALUE| with the specified name
+// and value to |*extlist|. if |*extlist| is NULL, it sets |*extlist| to a
+// newly-allocated |STACK_OF(CONF_VALUE)| first. It returns one on success and
+// zero on error.
+int x509V3_add_value_asn1_string(const char *name, const ASN1_STRING *value,
+                                 STACK_OF(CONF_VALUE) **extlist);
+
 
 #if defined(__cplusplus)
 }  /* extern C */
diff --git a/src/crypto/x509v3/pcy_cache.c b/src/crypto/x509v3/pcy_cache.c
index 755c079..1caea76 100644
--- a/src/crypto/x509v3/pcy_cache.c
+++ b/src/crypto/x509v3/pcy_cache.c
@@ -62,6 +62,7 @@
 
 #include "pcy_int.h"
 #include "../internal.h"
+#include "../x509/internal.h"
 
 static int policy_data_cmp(const X509_POLICY_DATA **a,
                            const X509_POLICY_DATA **b);
diff --git a/src/crypto/x509v3/pcy_data.c b/src/crypto/x509v3/pcy_data.c
index 58584c2..c4a56ca 100644
--- a/src/crypto/x509v3/pcy_data.c
+++ b/src/crypto/x509v3/pcy_data.c
@@ -79,7 +79,7 @@
 /*
  * Create a data based on an existing policy. If 'id' is NULL use the oid in
  * the policy, otherwise use 'id'. This behaviour covers the two types of
- * data in RFC3280: data with from a CertificatePolcies extension and
+ * data in RFC 3280: data with from a CertificatePolcies extension and
  * additional data with just the qualifiers of anyPolicy and ID from another
  * source.
  */
diff --git a/src/crypto/x509v3/pcy_int.h b/src/crypto/x509v3/pcy_int.h
index fc6e20a..aee71d6 100644
--- a/src/crypto/x509v3/pcy_int.h
+++ b/src/crypto/x509v3/pcy_int.h
@@ -65,7 +65,7 @@
 
 /*
  * This structure and the field names correspond to the Policy 'node' of
- * RFC3280. NB this structure contains no pointers to parent or child data:
+ * RFC 3280. NB this structure contains no pointers to parent or child data:
  * X509_POLICY_NODE contains that. This means that the main policy data can
  * be kept static and cached with the certificate.
  */
diff --git a/src/crypto/x509v3/pcy_map.c b/src/crypto/x509v3/pcy_map.c
index 7263c69..a4a3601 100644
--- a/src/crypto/x509v3/pcy_map.c
+++ b/src/crypto/x509v3/pcy_map.c
@@ -62,6 +62,7 @@
 #include <openssl/x509v3.h>
 
 #include "pcy_int.h"
+#include "../x509/internal.h"
 
 /*
  * Set policy mapping entries in cache. Note: this modifies the passed
diff --git a/src/crypto/x509v3/pcy_tree.c b/src/crypto/x509v3/pcy_tree.c
index 136b45f..596266b 100644
--- a/src/crypto/x509v3/pcy_tree.c
+++ b/src/crypto/x509v3/pcy_tree.c
@@ -67,6 +67,7 @@
 
 #include "pcy_int.h"
 #include "../internal.h"
+#include "../x509/internal.h"
 
 /*
  * Enable this to print out the complete policy tree at various point during
@@ -332,7 +333,7 @@
 }
 
 /*
- * This corresponds to RFC3280 6.1.3(d)(1): link any data from
+ * This corresponds to RFC 3280 6.1.3(d)(1): link any data from
  * CertificatePolicies onto matching parent or anyPolicy if no match.
  */
 
@@ -365,7 +366,7 @@
 }
 
 /*
- * This corresponds to RFC3280 6.1.3(d)(2): Create new data for any unmatched
+ * This corresponds to RFC 3280 6.1.3(d)(2): Create new data for any unmatched
  * policies in the parent and link to anyPolicy.
  */
 
@@ -500,7 +501,7 @@
     if (curr->flags & X509_V_FLAG_INHIBIT_MAP) {
         for (i = sk_X509_POLICY_NODE_num(nodes) - 1; i >= 0; i--) {
             node = sk_X509_POLICY_NODE_value(nodes, i);
-            /* Delete any mapped data: see RFC3280 XXXX */
+            /* Delete any mapped data: see RFC 3280 XXXX */
             if (node->data->flags & POLICY_DATA_FLAG_MAP_MASK) {
                 node->parent->nchild--;
                 OPENSSL_free(node);
diff --git a/src/crypto/x509v3/v3_akey.c b/src/crypto/x509v3/v3_akey.c
index 1037673..0aba20e 100644
--- a/src/crypto/x509v3/v3_akey.c
+++ b/src/crypto/x509v3/v3_akey.c
@@ -93,20 +93,39 @@
                                                  STACK_OF(CONF_VALUE)
                                                  *extlist)
 {
-    char *tmp;
+    char *tmp = NULL;
+    int extlist_was_null = extlist == NULL;
     if (akeyid->keyid) {
         tmp = x509v3_bytes_to_hex(akeyid->keyid->data, akeyid->keyid->length);
-        X509V3_add_value("keyid", tmp, &extlist);
+        int ok = tmp != NULL && X509V3_add_value("keyid", tmp, &extlist);
         OPENSSL_free(tmp);
+        if (!ok) {
+            goto err;
+        }
     }
-    if (akeyid->issuer)
-        extlist = i2v_GENERAL_NAMES(NULL, akeyid->issuer, extlist);
+    if (akeyid->issuer) {
+        STACK_OF(CONF_VALUE) *tmpextlist =
+            i2v_GENERAL_NAMES(NULL, akeyid->issuer, extlist);
+        if (tmpextlist == NULL) {
+            goto err;
+        }
+        extlist = tmpextlist;
+    }
     if (akeyid->serial) {
         tmp = x509v3_bytes_to_hex(akeyid->serial->data, akeyid->serial->length);
-        X509V3_add_value("serial", tmp, &extlist);
+        int ok = tmp != NULL && X509V3_add_value("serial", tmp, &extlist);
         OPENSSL_free(tmp);
+        if (!ok) {
+            goto err;
+        }
     }
     return extlist;
+
+err:
+    if (extlist_was_null) {
+        sk_CONF_VALUE_pop_free(extlist, X509V3_conf_free);
+    }
+    return NULL;
 }
 
 /*
diff --git a/src/crypto/x509v3/v3_alt.c b/src/crypto/x509v3/v3_alt.c
index 4d54075..0c55816 100644
--- a/src/crypto/x509v3/v3_alt.c
+++ b/src/crypto/x509v3/v3_alt.c
@@ -104,11 +104,17 @@
                                         GENERAL_NAMES *gens,
                                         STACK_OF(CONF_VALUE) *ret)
 {
-    size_t i;
-    GENERAL_NAME *gen;
-    for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) {
-        gen = sk_GENERAL_NAME_value(gens, i);
-        ret = i2v_GENERAL_NAME(method, gen, ret);
+    int ret_was_null = ret == NULL;
+    for (size_t i = 0; i < sk_GENERAL_NAME_num(gens); i++) {
+        GENERAL_NAME *gen = sk_GENERAL_NAME_value(gens, i);
+        STACK_OF(CONF_VALUE) *tmp = i2v_GENERAL_NAME(method, gen, ret);
+        if (tmp == NULL) {
+            if (ret_was_null) {
+                sk_CONF_VALUE_pop_free(ret, X509V3_conf_free);
+            }
+            return NULL;
+        }
+        ret = tmp;
     }
     if (!ret)
         return sk_CONF_VALUE_new_null();
@@ -119,6 +125,9 @@
                                        GENERAL_NAME *gen,
                                        STACK_OF(CONF_VALUE) *ret)
 {
+    /* Note the error-handling for this function relies on there being at most
+     * one |X509V3_add_value| call. If there were two and the second failed, we
+     * would need to sometimes free the first call's result. */
     unsigned char *p;
     char oline[256], htmp[5];
     int i;
@@ -139,17 +148,17 @@
         break;
 
     case GEN_EMAIL:
-        if (!X509V3_add_value_uchar("email", gen->d.ia5->data, &ret))
+        if (!x509V3_add_value_asn1_string("email", gen->d.ia5, &ret))
             return NULL;
         break;
 
     case GEN_DNS:
-        if (!X509V3_add_value_uchar("DNS", gen->d.ia5->data, &ret))
+        if (!x509V3_add_value_asn1_string("DNS", gen->d.ia5, &ret))
             return NULL;
         break;
 
     case GEN_URI:
-        if (!X509V3_add_value_uchar("URI", gen->d.ia5->data, &ret))
+        if (!x509V3_add_value_asn1_string("URI", gen->d.ia5, &ret))
             return NULL;
         break;
 
diff --git a/src/crypto/x509v3/v3_bitst.c b/src/crypto/x509v3/v3_bitst.c
index 402f830..871b776 100644
--- a/src/crypto/x509v3/v3_bitst.c
+++ b/src/crypto/x509v3/v3_bitst.c
@@ -63,6 +63,9 @@
 #include <openssl/obj.h>
 #include <openssl/x509v3.h>
 
+#include "internal.h"
+
+
 static const BIT_STRING_BITNAME ns_cert_type_table[] = {
     {0, "SSL Client", "client"},
     {1, "SSL Server", "server"},
diff --git a/src/crypto/x509v3/v3_cpols.c b/src/crypto/x509v3/v3_cpols.c
index a2ed01c..9f66f47 100644
--- a/src/crypto/x509v3/v3_cpols.c
+++ b/src/crypto/x509v3/v3_cpols.c
@@ -432,8 +432,8 @@
         qualinfo = sk_POLICYQUALINFO_value(quals, i);
         switch (OBJ_obj2nid(qualinfo->pqualid)) {
         case NID_id_qt_cps:
-            BIO_printf(out, "%*sCPS: %s\n", indent, "",
-                       qualinfo->d.cpsuri->data);
+            BIO_printf(out, "%*sCPS: %.*s\n", indent, "",
+                       qualinfo->d.cpsuri->length, qualinfo->d.cpsuri->data);
             break;
 
         case NID_id_qt_unotice:
@@ -457,8 +457,8 @@
     if (notice->noticeref) {
         NOTICEREF *ref;
         ref = notice->noticeref;
-        BIO_printf(out, "%*sOrganization: %s\n", indent, "",
-                   ref->organization->data);
+        BIO_printf(out, "%*sOrganization: %.*s\n", indent, "",
+                   ref->organization->length, ref->organization->data);
         BIO_printf(out, "%*sNumber%s: ", indent, "",
                    sk_ASN1_INTEGER_num(ref->noticenos) > 1 ? "s" : "");
         for (i = 0; i < sk_ASN1_INTEGER_num(ref->noticenos); i++) {
@@ -480,8 +480,8 @@
         BIO_puts(out, "\n");
     }
     if (notice->exptext)
-        BIO_printf(out, "%*sExplicit Text: %s\n", indent, "",
-                   notice->exptext->data);
+        BIO_printf(out, "%*sExplicit Text: %.*s\n", indent, "",
+                   notice->exptext->length, notice->exptext->data);
 }
 
 void X509_POLICY_NODE_print(BIO *out, X509_POLICY_NODE *node, int indent)
diff --git a/src/crypto/x509v3/v3_crld.c b/src/crypto/x509v3/v3_crld.c
index 27ccbc1..93e5b6d 100644
--- a/src/crypto/x509v3/v3_crld.c
+++ b/src/crypto/x509v3/v3_crld.c
@@ -66,6 +66,10 @@
 #include <openssl/obj.h>
 #include <openssl/x509v3.h>
 
+#include "internal.h"
+#include "../x509/internal.h"
+
+
 static void *v2i_crld(const X509V3_EXT_METHOD *method,
                       X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *nval);
 static int i2r_crldp(const X509V3_EXT_METHOD *method, void *pcrldp, BIO *out,
diff --git a/src/crypto/x509v3/v3_enum.c b/src/crypto/x509v3/v3_enum.c
index 3a9d4d6..9b222bb 100644
--- a/src/crypto/x509v3/v3_enum.c
+++ b/src/crypto/x509v3/v3_enum.c
@@ -61,6 +61,11 @@
 #include <openssl/mem.h>
 #include <openssl/x509v3.h>
 
+#include "internal.h"
+
+
+typedef BIT_STRING_BITNAME ENUMERATED_NAMES;
+
 static const ENUMERATED_NAMES crl_reasons[] = {
     {CRL_REASON_UNSPECIFIED, "Unspecified", "unspecified"},
     {CRL_REASON_KEY_COMPROMISE, "Key Compromise", "keyCompromise"},
diff --git a/src/crypto/x509v3/v3_ncons.c b/src/crypto/x509v3/v3_ncons.c
index 593a520..739a59e 100644
--- a/src/crypto/x509v3/v3_ncons.c
+++ b/src/crypto/x509v3/v3_ncons.c
@@ -66,6 +66,7 @@
 #include <openssl/x509v3.h>
 
 #include "../internal.h"
+#include "../x509/internal.h"
 
 
 static void *v2i_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method,
@@ -389,25 +390,73 @@
     return X509_V_OK;
 }
 
+static int starts_with(const CBS *cbs, uint8_t c)
+{
+    return CBS_len(cbs) > 0 && CBS_data(cbs)[0] == c;
+}
+
+static int equal_case(const CBS *a, const CBS *b)
+{
+    if (CBS_len(a) != CBS_len(b)) {
+        return 0;
+    }
+    /* Note we cannot use |OPENSSL_strncasecmp| because that would stop
+     * iterating at NUL. */
+    const uint8_t *a_data = CBS_data(a), *b_data = CBS_data(b);
+    for (size_t i = 0; i < CBS_len(a); i++) {
+        if (OPENSSL_tolower(a_data[i]) != OPENSSL_tolower(b_data[i])) {
+            return 0;
+        }
+    }
+    return 1;
+}
+
+static int has_suffix_case(const CBS *a, const CBS *b)
+{
+    if (CBS_len(a) < CBS_len(b)) {
+        return 0;
+    }
+    CBS copy = *a;
+    CBS_skip(&copy, CBS_len(a) - CBS_len(b));
+    return equal_case(&copy, b);
+}
+
 static int nc_dns(ASN1_IA5STRING *dns, ASN1_IA5STRING *base)
 {
-    char *baseptr = (char *)base->data;
-    char *dnsptr = (char *)dns->data;
+    CBS dns_cbs, base_cbs;
+    CBS_init(&dns_cbs, dns->data, dns->length);
+    CBS_init(&base_cbs, base->data, base->length);
+
     /* Empty matches everything */
-    if (!*baseptr)
+    if (CBS_len(&base_cbs) == 0) {
         return X509_V_OK;
+    }
+
+    /* If |base_cbs| begins with a '.', do a simple suffix comparison. This is
+     * not part of RFC5280, but is part of OpenSSL's original behavior. */
+    if (starts_with(&base_cbs, '.')) {
+        if (has_suffix_case(&dns_cbs, &base_cbs)) {
+            return X509_V_OK;
+        }
+        return X509_V_ERR_PERMITTED_VIOLATION;
+    }
+
     /*
      * Otherwise can add zero or more components on the left so compare RHS
      * and if dns is longer and expect '.' as preceding character.
      */
-    if (dns->length > base->length) {
-        dnsptr += dns->length - base->length;
-        if (*baseptr != '.' && dnsptr[-1] != '.')
+    if (CBS_len(&dns_cbs) > CBS_len(&base_cbs)) {
+        uint8_t dot;
+        if (!CBS_skip(&dns_cbs, CBS_len(&dns_cbs) - CBS_len(&base_cbs) - 1) ||
+            !CBS_get_u8(&dns_cbs, &dot) ||
+            dot != '.') {
             return X509_V_ERR_PERMITTED_VIOLATION;
+        }
     }
 
-    if (OPENSSL_strcasecmp(baseptr, dnsptr))
+    if (!equal_case(&dns_cbs, &base_cbs)) {
         return X509_V_ERR_PERMITTED_VIOLATION;
+    }
 
     return X509_V_OK;
 
@@ -415,86 +464,94 @@
 
 static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base)
 {
-    const char *baseptr = (char *)base->data;
-    const char *emlptr = (char *)eml->data;
+    CBS eml_cbs, base_cbs;
+    CBS_init(&eml_cbs, eml->data, eml->length);
+    CBS_init(&base_cbs, base->data, base->length);
 
-    const char *baseat = strchr(baseptr, '@');
-    const char *emlat = strchr(emlptr, '@');
-    if (!emlat)
+    /* TODO(davidben): In OpenSSL 1.1.1, this switched from the first '@' to the
+     * last one. Match them here, or perhaps do an actual parse. Looks like
+     * multiple '@'s may be allowed in quoted strings. */
+    CBS eml_local, base_local;
+    if (!CBS_get_until_first(&eml_cbs, &eml_local, '@')) {
         return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
+    }
+    int base_has_at = CBS_get_until_first(&base_cbs, &base_local, '@');
+
     /* Special case: inital '.' is RHS match */
-    if (!baseat && (*baseptr == '.')) {
-        if (eml->length > base->length) {
-            emlptr += eml->length - base->length;
-            if (!OPENSSL_strcasecmp(baseptr, emlptr))
-                return X509_V_OK;
+    if (!base_has_at && starts_with(&base_cbs, '.')) {
+        if (has_suffix_case(&eml_cbs, &base_cbs)) {
+            return X509_V_OK;
         }
         return X509_V_ERR_PERMITTED_VIOLATION;
     }
 
     /* If we have anything before '@' match local part */
-
-    if (baseat) {
-        if (baseat != baseptr) {
-            if ((baseat - baseptr) != (emlat - emlptr))
-                return X509_V_ERR_PERMITTED_VIOLATION;
+    if (base_has_at) {
+        /* TODO(davidben): This interprets a constraint of "@example.com" as
+         * "example.com", which is not part of RFC5280. */
+        if (CBS_len(&base_local) > 0) {
             /* Case sensitive match of local part */
-            if (strncmp(baseptr, emlptr, emlat - emlptr))
+            if (!CBS_mem_equal(&base_local, CBS_data(&eml_local),
+                               CBS_len(&eml_local))) {
                 return X509_V_ERR_PERMITTED_VIOLATION;
+            }
         }
         /* Position base after '@' */
-        baseptr = baseat + 1;
+        assert(starts_with(&base_cbs, '@'));
+        CBS_skip(&base_cbs, 1);
     }
-    emlptr = emlat + 1;
+
     /* Just have hostname left to match: case insensitive */
-    if (OPENSSL_strcasecmp(baseptr, emlptr))
+    assert(starts_with(&eml_cbs, '@'));
+    CBS_skip(&eml_cbs, 1);
+    if (!equal_case(&base_cbs, &eml_cbs)) {
         return X509_V_ERR_PERMITTED_VIOLATION;
+    }
 
     return X509_V_OK;
-
 }
 
 static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base)
 {
-    const char *baseptr = (char *)base->data;
-    const char *hostptr = (char *)uri->data;
-    const char *p = strchr(hostptr, ':');
-    int hostlen;
+    CBS uri_cbs, base_cbs;
+    CBS_init(&uri_cbs, uri->data, uri->length);
+    CBS_init(&base_cbs, base->data, base->length);
+
     /* Check for foo:// and skip past it */
-    if (!p || (p[1] != '/') || (p[2] != '/'))
+    CBS scheme;
+    uint8_t byte;
+    if (!CBS_get_until_first(&uri_cbs, &scheme, ':') ||
+        !CBS_skip(&uri_cbs, 1) ||  // Skip the colon
+        !CBS_get_u8(&uri_cbs, &byte) || byte != '/' ||
+        !CBS_get_u8(&uri_cbs, &byte) || byte != '/') {
         return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
-    hostptr = p + 3;
+    }
 
-    /* Determine length of hostname part of URI */
+    /* Look for a port indicator as end of hostname first. Otherwise look for
+     * trailing slash, or the end of the string.
+     * TODO(davidben): This is not a correct URI parser and mishandles IPv6
+     * literals. */
+    CBS host;
+    if (!CBS_get_until_first(&uri_cbs, &host, ':') &&
+        !CBS_get_until_first(&uri_cbs, &host, '/')) {
+        host = uri_cbs;
+    }
 
-    /* Look for a port indicator as end of hostname first */
-
-    p = strchr(hostptr, ':');
-    /* Otherwise look for trailing slash */
-    if (!p)
-        p = strchr(hostptr, '/');
-
-    if (!p)
-        hostlen = strlen(hostptr);
-    else
-        hostlen = p - hostptr;
-
-    if (hostlen == 0)
+    if (CBS_len(&host) == 0) {
         return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
+    }
 
     /* Special case: inital '.' is RHS match */
-    if (*baseptr == '.') {
-        if (hostlen > base->length) {
-            p = hostptr + hostlen - base->length;
-            if (!OPENSSL_strncasecmp(p, baseptr, base->length))
-                return X509_V_OK;
+    if (starts_with(&base_cbs, '.')) {
+        if (has_suffix_case(&host, &base_cbs)) {
+            return X509_V_OK;
         }
         return X509_V_ERR_PERMITTED_VIOLATION;
     }
 
-    if ((base->length != (int)hostlen)
-        || OPENSSL_strncasecmp(hostptr, baseptr, hostlen))
+    if (!equal_case(&base_cbs, &host)) {
         return X509_V_ERR_PERMITTED_VIOLATION;
+    }
 
     return X509_V_OK;
 
diff --git a/src/crypto/x509v3/v3_pci.c b/src/crypto/x509v3/v3_pci.c
index f9031c0..57b64ef 100644
--- a/src/crypto/x509v3/v3_pci.c
+++ b/src/crypto/x509v3/v3_pci.c
@@ -75,7 +75,8 @@
     i2a_ASN1_OBJECT(out, pci->proxyPolicy->policyLanguage);
     BIO_puts(out, "\n");
     if (pci->proxyPolicy->policy && pci->proxyPolicy->policy->data)
-        BIO_printf(out, "%*sPolicy Text: %s\n", indent, "",
+        BIO_printf(out, "%*sPolicy Text: %.*s\n", indent, "",
+                   pci->proxyPolicy->policy->length,
                    pci->proxyPolicy->policy->data);
     return 1;
 }
diff --git a/src/crypto/x509v3/v3_prn.c b/src/crypto/x509v3/v3_prn.c
index 79e96e6..ee4c482 100644
--- a/src/crypto/x509v3/v3_prn.c
+++ b/src/crypto/x509v3/v3_prn.c
@@ -218,7 +218,6 @@
     }
 }
 
-#ifndef OPENSSL_NO_FP_API
 int X509V3_EXT_print_fp(FILE *fp, X509_EXTENSION *ext, int flag, int indent)
 {
     BIO *bio_tmp;
@@ -229,4 +228,3 @@
     BIO_free(bio_tmp);
     return ret;
 }
-#endif
diff --git a/src/crypto/x509v3/v3_purp.c b/src/crypto/x509v3/v3_purp.c
index 2d7a4db..d1f56f0 100644
--- a/src/crypto/x509v3/v3_purp.c
+++ b/src/crypto/x509v3/v3_purp.c
@@ -68,6 +68,7 @@
 #include <openssl/x509v3.h>
 
 #include "../internal.h"
+#include "../x509/internal.h"
 #include "internal.h"
 
 #define V1_ROOT (EXFLAG_V1|EXFLAG_SS)
diff --git a/src/crypto/x509v3/v3_utl.c b/src/crypto/x509v3/v3_utl.c
index fd6e689..5d91aed 100644
--- a/src/crypto/x509v3/v3_utl.c
+++ b/src/crypto/x509v3/v3_utl.c
@@ -88,42 +88,69 @@
 
 /* Add a CONF_VALUE name value pair to stack */
 
-int X509V3_add_value(const char *name, const char *value,
-                     STACK_OF(CONF_VALUE) **extlist)
+static int x509V3_add_len_value(const char *name, const char *value,
+                                size_t value_len, int omit_value,
+                                STACK_OF(CONF_VALUE) **extlist)
 {
     CONF_VALUE *vtmp = NULL;
     char *tname = NULL, *tvalue = NULL;
+    int extlist_was_null = *extlist == NULL;
     if (name && !(tname = OPENSSL_strdup(name)))
-        goto err;
-    if (value && !(tvalue = OPENSSL_strdup(value)))
-        goto err;
+        goto malloc_err;
+    if (!omit_value) {
+        /* |CONF_VALUE| cannot represent strings with NULs. */
+        if (OPENSSL_memchr(value, 0, value_len)) {
+            OPENSSL_PUT_ERROR(X509V3, X509V3_R_INVALID_VALUE);
+            goto err;
+        }
+        tvalue = OPENSSL_strndup(value, value_len);
+        if (tvalue == NULL) {
+            goto malloc_err;
+        }
+    }
     if (!(vtmp = CONF_VALUE_new()))
-        goto err;
+        goto malloc_err;
     if (!*extlist && !(*extlist = sk_CONF_VALUE_new_null()))
-        goto err;
+        goto malloc_err;
     vtmp->section = NULL;
     vtmp->name = tname;
     vtmp->value = tvalue;
     if (!sk_CONF_VALUE_push(*extlist, vtmp))
-        goto err;
+        goto malloc_err;
     return 1;
- err:
+ malloc_err:
     OPENSSL_PUT_ERROR(X509V3, ERR_R_MALLOC_FAILURE);
-    if (vtmp)
-        OPENSSL_free(vtmp);
-    if (tname)
-        OPENSSL_free(tname);
-    if (tvalue)
-        OPENSSL_free(tvalue);
+ err:
+    if (extlist_was_null) {
+        sk_CONF_VALUE_free(*extlist);
+        *extlist = NULL;
+    }
+    OPENSSL_free(vtmp);
+    OPENSSL_free(tname);
+    OPENSSL_free(tvalue);
     return 0;
 }
 
+int X509V3_add_value(const char *name, const char *value,
+                     STACK_OF(CONF_VALUE) **extlist)
+{
+    return x509V3_add_len_value(name, value, value != NULL ? strlen(value) : 0,
+                                /*omit_value=*/value == NULL, extlist);
+}
+
 int X509V3_add_value_uchar(const char *name, const unsigned char *value,
                            STACK_OF(CONF_VALUE) **extlist)
 {
     return X509V3_add_value(name, (const char *)value, extlist);
 }
 
+int x509V3_add_value_asn1_string(const char *name, const ASN1_STRING *value,
+                                 STACK_OF(CONF_VALUE) **extlist)
+{
+    return x509V3_add_len_value(name, (const char *)value->data, value->length,
+                                /*omit_value=*/0, extlist);
+}
+
 /* Free function for STACK_OF(CONF_VALUE) */
 
 void X509V3_conf_free(CONF_VALUE *conf)
@@ -268,7 +295,7 @@
     return aint;
 }
 
-int X509V3_add_value_int(const char *name, ASN1_INTEGER *aint,
+int X509V3_add_value_int(const char *name, const ASN1_INTEGER *aint,
                          STACK_OF(CONF_VALUE) **extlist)
 {
     char *strtmp;
@@ -631,27 +658,45 @@
 
 static int append_ia5(STACK_OF(OPENSSL_STRING) **sk, ASN1_IA5STRING *email)
 {
-    char *emtmp;
     /* First some sanity checks */
     if (email->type != V_ASN1_IA5STRING)
         return 1;
-    if (!email->data || !email->length)
+    if (email->data == NULL || email->length == 0)
         return 1;
+    /* |OPENSSL_STRING| cannot represent strings with embedded NULs. Do not
+     * report them as outputs. */
+    if (OPENSSL_memchr(email->data, 0, email->length) != NULL)
+        return 1;
+
+    char *emtmp = NULL;
     if (!*sk)
         *sk = sk_OPENSSL_STRING_new(sk_strcmp);
     if (!*sk)
-        return 0;
+        goto err;
+
+    emtmp = OPENSSL_strndup((char *)email->data, email->length);
+    if (emtmp == NULL) {
+        goto err;
+    }
+
     /* Don't add duplicates */
     sk_OPENSSL_STRING_sort(*sk);
-    if (sk_OPENSSL_STRING_find(*sk, NULL, (char *)email->data))
+    if (sk_OPENSSL_STRING_find(*sk, NULL, emtmp)) {
+        OPENSSL_free(emtmp);
         return 1;
-    emtmp = OPENSSL_strdup((char *)email->data);
-    if (!emtmp || !sk_OPENSSL_STRING_push(*sk, emtmp)) {
-        X509_email_free(*sk);
-        *sk = NULL;
-        return 0;
+    }
+    if (!sk_OPENSSL_STRING_push(*sk, emtmp)) {
+        goto err;
     }
     return 1;
+
+err:
+    /* TODO(davidben): Fix the error-handling in this file. It currently relies
+     * on |append_ia5| leaving |*sk| at NULL on error. */
+    OPENSSL_free(emtmp);
+    X509_email_free(*sk);
+    *sk = NULL;
+    return 0;
 }
 
 void X509_email_free(STACK_OF(OPENSSL_STRING) *sk)
@@ -1120,7 +1165,7 @@
 
 /*
  * Convert IP addresses both IPv4 and IPv6 into an OCTET STRING compatible
- * with RFC3280.
+ * with RFC 3280.
  */
 
 ASN1_OCTET_STRING *a2i_IPADDRESS(const char *ipasc)
diff --git a/src/decrepit/evp/evp_do_all.c b/src/decrepit/evp/evp_do_all.c
index d8023e0..a3fb077 100644
--- a/src/decrepit/evp/evp_do_all.c
+++ b/src/decrepit/evp/evp_do_all.c
@@ -78,6 +78,7 @@
   callback(EVP_sha256(), "SHA256", NULL, arg);
   callback(EVP_sha384(), "SHA384", NULL, arg);
   callback(EVP_sha512(), "SHA512", NULL, arg);
+  callback(EVP_sha512_256(), "SHA512-256", NULL, arg);
 
   callback(EVP_md4(), "md4", NULL, arg);
   callback(EVP_md5(), "md5", NULL, arg);
@@ -86,6 +87,7 @@
   callback(EVP_sha256(), "sha256", NULL, arg);
   callback(EVP_sha384(), "sha384", NULL, arg);
   callback(EVP_sha512(), "sha512", NULL, arg);
+  callback(EVP_sha512_256(), "sha512-256", NULL, arg);
 }
 
 void EVP_MD_do_all(void (*callback)(const EVP_MD *cipher, const char *name,
diff --git a/src/include/openssl/asn1.h b/src/include/openssl/asn1.h
index db467fd..4f6fb3b 100644
--- a/src/include/openssl/asn1.h
+++ b/src/include/openssl/asn1.h
@@ -111,10 +111,6 @@
 // V_ASN1_UNDEF is used in some APIs to indicate an ASN.1 element is omitted.
 #define V_ASN1_UNDEF (-1)
 
-// V_ASN1_APP_CHOOSE is used in some APIs to specify a default ASN.1 type based
-// on the context.
-#define V_ASN1_APP_CHOOSE (-2)
-
 // V_ASN1_OTHER is used in |ASN1_TYPE| to indicate a non-universal ASN.1 type.
 #define V_ASN1_OTHER (-3)
 
@@ -157,6 +153,31 @@
 #define V_ASN1_NEG_INTEGER (V_ASN1_INTEGER | V_ASN1_NEG)
 #define V_ASN1_NEG_ENUMERATED (V_ASN1_ENUMERATED | V_ASN1_NEG)
 
+// The following constants are bitmask representations of ASN.1 types.
+#define B_ASN1_NUMERICSTRING 0x0001
+#define B_ASN1_PRINTABLESTRING 0x0002
+#define B_ASN1_T61STRING 0x0004
+#define B_ASN1_TELETEXSTRING 0x0004
+#define B_ASN1_VIDEOTEXSTRING 0x0008
+#define B_ASN1_IA5STRING 0x0010
+#define B_ASN1_GRAPHICSTRING 0x0020
+#define B_ASN1_ISO64STRING 0x0040
+#define B_ASN1_VISIBLESTRING 0x0040
+#define B_ASN1_GENERALSTRING 0x0080
+#define B_ASN1_UNIVERSALSTRING 0x0100
+#define B_ASN1_OCTET_STRING 0x0200
+#define B_ASN1_BIT_STRING 0x0400
+#define B_ASN1_BMPSTRING 0x0800
+#define B_ASN1_UNKNOWN 0x1000
+#define B_ASN1_UTF8STRING 0x2000
+#define B_ASN1_UTCTIME 0x4000
+#define B_ASN1_GENERALIZEDTIME 0x8000
+#define B_ASN1_SEQUENCE 0x10000
+
+// ASN1_tag2str returns a string representation of |tag|, interpret as a tag
+// number for a universal type, or |V_ASN1_NEG_*|.
+OPENSSL_EXPORT const char *ASN1_tag2str(int tag);
+
 
 // Strings.
 //
@@ -232,14 +253,6 @@
 // treated as padding. This behavior is deprecated and should not be used.
 #define ASN1_STRING_FLAG_BITS_LEFT 0x08
 
-// ASN1_STRING_FLAG_MSTRING indicates that the |ASN1_STRING| is an MSTRING type,
-// which is how this library refers to a CHOICE type of several string types.
-// For example, DirectoryString as defined in RFC5280.
-//
-// TODO(davidben): This is only used in one place within the library and is easy
-// to accidentally drop. Can it be removed?
-#define ASN1_STRING_FLAG_MSTRING 0x040
-
 // ASN1_STRING_type_new returns a newly-allocated empty |ASN1_STRING| object of
 // type |type|, or NULL on error.
 OPENSSL_EXPORT ASN1_STRING *ASN1_STRING_type_new(int type);
@@ -311,6 +324,52 @@
 OPENSSL_EXPORT int ASN1_STRING_to_UTF8(unsigned char **out,
                                        const ASN1_STRING *in);
 
+// The following formats define encodings for use with functions like
+// |ASN1_mbstring_copy|.
+#define MBSTRING_FLAG 0x1000
+#define MBSTRING_UTF8 (MBSTRING_FLAG)
+// |MBSTRING_ASC| refers to Latin-1, not ASCII.
+#define MBSTRING_ASC (MBSTRING_FLAG | 1)
+#define MBSTRING_BMP (MBSTRING_FLAG | 2)
+#define MBSTRING_UNIV (MBSTRING_FLAG | 4)
+
+// DIRSTRING_TYPE contains the valid string types in an X.509 DirectoryString.
+#define DIRSTRING_TYPE                                            \
+  (B_ASN1_PRINTABLESTRING | B_ASN1_T61STRING | B_ASN1_BMPSTRING | \
+   B_ASN1_UTF8STRING)
+
+// PKCS9STRING_TYPE contains the valid string types in a PKCS9String.
+#define PKCS9STRING_TYPE (DIRSTRING_TYPE | B_ASN1_IA5STRING)
+
+// ASN1_mbstring_copy converts |len| bytes from |in| to an ASN.1 string. If
+// |len| is -1, |in| must be NUL-terminated and the length is determined by
+// |strlen|. |in| is decoded according to |inform|, which must be one of
+// |MBSTRING_*|. |mask| determines the set of valid output types and is a
+// bitmask containing a subset of |B_ASN1_PRINTABLESTRING|, |B_ASN1_IA5STRING|,
+// |B_ASN1_T61STRING|, |B_ASN1_BMPSTRING|, |B_ASN1_UNIVERSALSTRING|, and
+// |B_ASN1_UTF8STRING|, in that preference order. This function chooses the
+// first output type in |mask| which can represent |in|. It interprets T61String
+// as Latin-1, rather than T.61.
+//
+// If |mask| is zero, |DIRSTRING_TYPE| is used by default.
+//
+// On success, this function returns the |V_ASN1_*| constant corresponding to
+// the selected output type and, if |out| and |*out| are both non-NULL, updates
+// the object at |*out| with the result. If |out| is non-NULL and |*out| is
+// NULL, it instead sets |*out| to a newly-allocated |ASN1_STRING| containing
+// the result. If |out| is NULL, it returns the selected output type without
+// constructing an |ASN1_STRING|. On error, this function returns -1.
+OPENSSL_EXPORT int ASN1_mbstring_copy(ASN1_STRING **out, const uint8_t *in,
+                                      int len, int inform, unsigned long mask);
+
+// ASN1_mbstring_ncopy behaves like |ASN1_mbstring_copy| but returns an error if
+// the input is less than |minsize| or greater than |maxsize| codepoints long. A
+// |maxsize| value of zero is ignored. Note the sizes are measured in
+// codepoints, not output bytes.
+OPENSSL_EXPORT int ASN1_mbstring_ncopy(ASN1_STRING **out, const uint8_t *in,
+                                       int len, int inform, unsigned long mask,
+                                       long minsize, long maxsize);
+
 // TODO(davidben): Expand and document function prototypes generated in macros.
 
 
@@ -321,7 +380,7 @@
 // in several forms:
 //
 // Some BIT STRINGs represent a bitmask of named bits, such as the X.509 key
-// usage extension in RFC5280, section 4.2.1.3. For such bit strings, DER
+// usage extension in RFC 5280, section 4.2.1.3. For such bit strings, DER
 // imposes an additional restriction that trailing zero bits are removed. Some
 // functions like |ASN1_BIT_STRING_set_bit| help in maintaining this.
 //
@@ -452,6 +511,135 @@
 // TODO(davidben): Expand and document function prototypes generated in macros.
 
 
+// Time.
+//
+// GeneralizedTime and UTCTime values are represented as |ASN1_STRING|s. The
+// type field is |V_ASN1_GENERALIZEDTIME| or |V_ASN1_UTCTIME|, respectively. The
+// data field contains the DER encoding of the value. For example, the UNIX
+// epoch would be "19700101000000Z" for a GeneralizedTime and "700101000000Z"
+// for a UTCTime.
+//
+// ASN.1 does not define how to interpret UTCTime's two-digit year. RFC 5280
+// defines it as a range from 1950 to 2049 for X.509. The library uses the
+// RFC 5280 interpretation. It does not currently enforce the restrictions from
+// BER, and the additional restrictions from RFC 5280, but future versions may.
+// Callers should not rely on fractional seconds and non-UTC time zones.
+//
+// The |ASN1_TIME| typedef represents the X.509 Time type, which is a CHOICE of
+// GeneralizedTime and UTCTime, using UTCTime when the value is in range.
+
+// ASN1_UTCTIME_check returns one if |a| is a valid UTCTime and zero otherwise.
+OPENSSL_EXPORT int ASN1_UTCTIME_check(const ASN1_UTCTIME *a);
+
+// ASN1_UTCTIME_set represents |t| as a UTCTime and writes the result to |s|. It
+// returns |s| on success and NULL on error. If |s| is NULL, it returns a
+// newly-allocated |ASN1_UTCTIME| instead.
+//
+// Note this function may fail if the time is out of range for UTCTime.
+OPENSSL_EXPORT ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *s, time_t t);
+
+// ASN1_UTCTIME_adj adds |offset_day| days and |offset_sec| seconds to |t| and
+// writes the result to |s| as a UTCTime. It returns |s| on success and NULL on
+// error. If |s| is NULL, it returns a newly-allocated |ASN1_UTCTIME| instead.
+//
+// Note this function may fail if the time overflows or is out of range for
+// UTCTime.
+OPENSSL_EXPORT ASN1_UTCTIME *ASN1_UTCTIME_adj(ASN1_UTCTIME *s, time_t t,
+                                              int offset_day, long offset_sec);
+
+// ASN1_UTCTIME_set_string sets |s| to a UTCTime whose contents are a copy of
+// |str|. It returns one on success and zero on error or if |str| is not a valid
+// UTCTime.
+//
+// If |s| is NULL, this function validates |str| without copying it.
+OPENSSL_EXPORT int ASN1_UTCTIME_set_string(ASN1_UTCTIME *s, const char *str);
+
+// ASN1_UTCTIME_cmp_time_t compares |s| to |t|. It returns -1 if |s| < |t|, 0 if
+// they are equal, 1 if |s| > |t|, and -2 on error.
+OPENSSL_EXPORT int ASN1_UTCTIME_cmp_time_t(const ASN1_UTCTIME *s, time_t t);
+
+// ASN1_GENERALIZEDTIME_check returns one if |a| is a valid GeneralizedTime and
+// zero otherwise.
+OPENSSL_EXPORT int ASN1_GENERALIZEDTIME_check(const ASN1_GENERALIZEDTIME *a);
+
+// ASN1_GENERALIZEDTIME_set represents |t| as a GeneralizedTime and writes the
+// result to |s|. It returns |s| on success and NULL on error. If |s| is NULL,
+// it returns a newly-allocated |ASN1_GENERALIZEDTIME| instead.
+//
+// Note this function may fail if the time is out of range for GeneralizedTime.
+OPENSSL_EXPORT ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(
+    ASN1_GENERALIZEDTIME *s, time_t t);
+
+// ASN1_GENERALIZEDTIME_adj adds |offset_day| days and |offset_sec| seconds to
+// |t| and writes the result to |s| as a GeneralizedTime. It returns |s| on
+// success and NULL on error. If |s| is NULL, it returns a newly-allocated
+// |ASN1_GENERALIZEDTIME| instead.
+//
+// Note this function may fail if the time overflows or is out of range for
+// GeneralizedTime.
+OPENSSL_EXPORT ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_adj(
+    ASN1_GENERALIZEDTIME *s, time_t t, int offset_day, long offset_sec);
+
+// ASN1_GENERALIZEDTIME_set_string sets |s| to a GeneralizedTime whose contents
+// are a copy of |str|. It returns one on success and zero on error or if |str|
+// is not a valid GeneralizedTime.
+//
+// If |s| is NULL, this function validates |str| without copying it.
+OPENSSL_EXPORT int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *s,
+                                                   const char *str);
+
+// ASN1_TIME_diff computes |to| - |from|. On success, it sets |*out_days| to the
+// difference in days, rounded towards zero, sets |*out_seconds| to the
+// remainder, and returns one. On error, it returns zero.
+//
+// If |from| is before |to|, both outputs will be <= 0, with at least one
+// negative. If |from| is after |to|, both will be >= 0, with at least one
+// positive. If they are equal, ignoring fractional seconds, both will be zero.
+//
+// Note this function may fail on overflow, or if |from| or |to| cannot be
+// decoded.
+OPENSSL_EXPORT int ASN1_TIME_diff(int *out_days, int *out_seconds,
+                                  const ASN1_TIME *from, const ASN1_TIME *to);
+
+// ASN1_TIME_set represents |t| as a GeneralizedTime or UTCTime and writes
+// the result to |s|. As in RFC 5280, section 4.1.2.5, it uses UTCTime when the
+// time fits and GeneralizedTime otherwise. It returns |s| on success and NULL
+// on error. If |s| is NULL, it returns a newly-allocated |ASN1_TIME| instead.
+//
+// Note this function may fail if the time is out of range for GeneralizedTime.
+OPENSSL_EXPORT ASN1_TIME *ASN1_TIME_set(ASN1_TIME *s, time_t t);
+
+// ASN1_TIME_adj adds |offset_day| days and |offset_sec| seconds to
+// |t| and writes the result to |s|. As in RFC 5280, section 4.1.2.5, it uses
+// UTCTime when the time fits and GeneralizedTime otherwise. It returns |s| on
+// success and NULL on error. If |s| is NULL, it returns a newly-allocated
+// |ASN1_GENERALIZEDTIME| instead.
+//
+// Note this function may fail if the time overflows or is out of range for
+// GeneralizedTime.
+OPENSSL_EXPORT ASN1_TIME *ASN1_TIME_adj(ASN1_TIME *s, time_t t, int offset_day,
+                                        long offset_sec);
+
+// ASN1_TIME_check returns one if |t| is a valid UTCTime or GeneralizedTime, and
+// zero otherwise. |t|'s type determines which check is performed. This
+// function does not enforce that UTCTime was used when possible.
+OPENSSL_EXPORT int ASN1_TIME_check(const ASN1_TIME *t);
+
+// ASN1_TIME_to_generalizedtime converts |t| to a GeneralizedTime. If |out| is
+// NULL, it returns a newly-allocated |ASN1_GENERALIZEDTIME| on success, or NULL
+// on error. If |out| is non-NULL and |*out| is NULL, it additionally sets
+// |*out| to the result. If |out| and |*out| are non-NULL, it instead updates
+// the object pointed by |*out| and returns |*out| on success or NULL on error.
+OPENSSL_EXPORT ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(
+    const ASN1_TIME *t, ASN1_GENERALIZEDTIME **out);
+
+// ASN1_TIME_set_string behaves like |ASN1_UTCTIME_set_string| if |str| is a
+// valid UTCTime, and |ASN1_GENERALIZEDTIME_set_string| if |str| is a valid
+// GeneralizedTime. If |str| is neither, it returns zero.
+OPENSSL_EXPORT int ASN1_TIME_set_string(ASN1_TIME *s, const char *str);
+
+// TODO(davidben): Expand and document function prototypes generated in macros.
+
 
 // Arbitrary elements.
 
@@ -558,41 +746,103 @@
 // the macros, document them, and move them to this section.
 
 
+// Human-readable output.
+//
+// The following functions output types in some human-readable format. These
+// functions may be used for debugging and logging. However, the output should
+// not be consumed programmatically. They may be ambiguous or lose information.
+
+// ASN1_UTCTIME_print writes a human-readable representation of |a| to |out|. It
+// returns one on success and zero on error.
+OPENSSL_EXPORT int ASN1_UTCTIME_print(BIO *out, const ASN1_UTCTIME *a);
+
+// ASN1_GENERALIZEDTIME_print writes a human-readable representation of |a| to
+// |out|. It returns one on success and zero on error.
+OPENSSL_EXPORT int ASN1_GENERALIZEDTIME_print(BIO *out,
+                                              const ASN1_GENERALIZEDTIME *a);
+
+// ASN1_TIME_print writes a human-readable representation of |a| to |out|. It
+// returns one on success and zero on error.
+OPENSSL_EXPORT int ASN1_TIME_print(BIO *out, const ASN1_TIME *a);
+
+// ASN1_STRING_print writes a human-readable representation of |str| to |out|.
+// It returns one on success and zero on error. Unprintable characters are
+// replaced with '.'.
+OPENSSL_EXPORT int ASN1_STRING_print(BIO *out, const ASN1_STRING *str);
+
+// ASN1_STRFLGS_ESC_2253 causes characters to be escaped as in RFC 2253, section
+// 2.4.
+#define ASN1_STRFLGS_ESC_2253 1
+
+// ASN1_STRFLGS_ESC_CTRL causes all control characters to be escaped.
+#define ASN1_STRFLGS_ESC_CTRL 2
+
+// ASN1_STRFLGS_ESC_MSB causes all characters above 127 to be escaped.
+#define ASN1_STRFLGS_ESC_MSB 4
+
+// ASN1_STRFLGS_ESC_QUOTE causes the string to be surrounded by quotes, rather
+// than using backslashes, when characters are escaped. Fewer characters will
+// require escapes in this case.
+#define ASN1_STRFLGS_ESC_QUOTE 8
+
+// ASN1_STRFLGS_UTF8_CONVERT causes the string to be encoded as UTF-8, with each
+// byte in the UTF-8 encoding treated as an individual character for purposes of
+// escape sequences. If not set, each Unicode codepoint in the string is treated
+// as a character, with wide characters escaped as "\Uxxxx" or "\Wxxxxxxxx".
+// Note this can be ambiguous if |ASN1_STRFLGS_ESC_*| are all unset. In that
+// case, backslashes are not escaped, but wide characters are.
+#define ASN1_STRFLGS_UTF8_CONVERT 0x10
+
+// ASN1_STRFLGS_IGNORE_TYPE causes the string type to be ignored. The
+// |ASN1_STRING| in-memory representation will be printed directly.
+#define ASN1_STRFLGS_IGNORE_TYPE 0x20
+
+// ASN1_STRFLGS_SHOW_TYPE causes the string type to be included in the output.
+#define ASN1_STRFLGS_SHOW_TYPE 0x40
+
+// ASN1_STRFLGS_DUMP_ALL causes all strings to be printed as a hexdump, using
+// RFC 2253 hexstring notation, such as "#0123456789ABCDEF".
+#define ASN1_STRFLGS_DUMP_ALL 0x80
+
+// ASN1_STRFLGS_DUMP_UNKNOWN behaves like |ASN1_STRFLGS_DUMP_ALL| but only
+// applies to values of unknown type. If unset, unknown values will print
+// their contents as single-byte characters with escape sequences.
+#define ASN1_STRFLGS_DUMP_UNKNOWN 0x100
+
+// ASN1_STRFLGS_DUMP_DER causes hexdumped strings (as determined by
+// |ASN1_STRFLGS_DUMP_ALL| or |ASN1_STRFLGS_DUMP_UNKNOWN|) to print the entire
+// DER element as in RFC 2253, rather than only the contents of the
+// |ASN1_STRING|.
+#define ASN1_STRFLGS_DUMP_DER 0x200
+
+// ASN1_STRFLGS_RFC2253 causes the string to be escaped as in RFC 2253,
+// additionally escaping control characters.
+#define ASN1_STRFLGS_RFC2253                                              \
+  (ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB | \
+   ASN1_STRFLGS_UTF8_CONVERT | ASN1_STRFLGS_DUMP_UNKNOWN |                \
+   ASN1_STRFLGS_DUMP_DER)
+
+// ASN1_STRING_print_ex writes a human-readable representation of |str| to
+// |out|. It returns the number of bytes written on success and -1 on error. If
+// |out| is NULL, it returns the number of bytes it would have written, without
+// writing anything.
+//
+// The |flags| should be a combination of combination of |ASN1_STRFLGS_*|
+// constants. See the documentation for each flag for how it controls the
+// output. If unsure, use |ASN1_STRFLGS_RFC2253|.
+OPENSSL_EXPORT int ASN1_STRING_print_ex(BIO *out, const ASN1_STRING *str,
+                                        unsigned long flags);
+
+// ASN1_STRING_print_ex_fp behaves like |ASN1_STRING_print_ex| but writes to a
+// |FILE| rather than a |BIO|.
+OPENSSL_EXPORT int ASN1_STRING_print_ex_fp(FILE *fp, const ASN1_STRING *str,
+                                           unsigned long flags);
+
+
 // Underdocumented functions.
 //
 // The following functions are not yet documented and organized.
 
-// For use with d2i_ASN1_type_bytes()
-#define B_ASN1_NUMERICSTRING 0x0001
-#define B_ASN1_PRINTABLESTRING 0x0002
-#define B_ASN1_T61STRING 0x0004
-#define B_ASN1_TELETEXSTRING 0x0004
-#define B_ASN1_VIDEOTEXSTRING 0x0008
-#define B_ASN1_IA5STRING 0x0010
-#define B_ASN1_GRAPHICSTRING 0x0020
-#define B_ASN1_ISO64STRING 0x0040
-#define B_ASN1_VISIBLESTRING 0x0040
-#define B_ASN1_GENERALSTRING 0x0080
-#define B_ASN1_UNIVERSALSTRING 0x0100
-#define B_ASN1_OCTET_STRING 0x0200
-#define B_ASN1_BIT_STRING 0x0400
-#define B_ASN1_BMPSTRING 0x0800
-#define B_ASN1_UNKNOWN 0x1000
-#define B_ASN1_UTF8STRING 0x2000
-#define B_ASN1_UTCTIME 0x4000
-#define B_ASN1_GENERALIZEDTIME 0x8000
-#define B_ASN1_SEQUENCE 0x10000
-
-// For use with ASN1_mbstring_copy()
-#define MBSTRING_FLAG 0x1000
-#define MBSTRING_UTF8 (MBSTRING_FLAG)
-// |MBSTRING_ASC| refers to Latin-1, not ASCII. It is used with TeletexString
-// which, in turn, is treated as Latin-1 rather than T.61 by OpenSSL and most
-// other software.
-#define MBSTRING_ASC (MBSTRING_FLAG | 1)
-#define MBSTRING_BMP (MBSTRING_FLAG | 2)
-#define MBSTRING_UNIV (MBSTRING_FLAG | 4)
-
 DEFINE_STACK_OF(ASN1_OBJECT)
 
 // ASN1_ENCODING structure: this is used to save the received
@@ -615,10 +865,6 @@
 
 #define STABLE_FLAGS_MALLOC 0x01
 #define STABLE_NO_MASK 0x02
-#define DIRSTRING_TYPE                                            \
-  (B_ASN1_PRINTABLESTRING | B_ASN1_T61STRING | B_ASN1_BMPSTRING | \
-   B_ASN1_UTF8STRING)
-#define PKCS9STRING_TYPE (DIRSTRING_TYPE | B_ASN1_IA5STRING)
 
 typedef struct asn1_string_table_st {
   int nid;
@@ -628,17 +874,6 @@
   unsigned long flags;
 } ASN1_STRING_TABLE;
 
-// size limits: this stuff is taken straight from RFC2459
-
-#define ub_name 32768
-#define ub_common_name 64
-#define ub_locality_name 128
-#define ub_state_name 128
-#define ub_organization_name 64
-#define ub_organization_unit_name 64
-#define ub_title 64
-#define ub_email_address 128
-
 // Declarations for template structures: for full definitions
 // see asn1t.h
 typedef struct ASN1_TEMPLATE_st ASN1_TEMPLATE;
@@ -736,75 +971,6 @@
 
 #define DECLARE_ASN1_ITEM(name) extern OPENSSL_EXPORT const ASN1_ITEM name##_it;
 
-// Parameters used by ASN1_STRING_print_ex()
-
-// These determine which characters to escape:
-// RFC2253 special characters, control characters and
-// MSB set characters
-
-#define ASN1_STRFLGS_ESC_2253 1
-#define ASN1_STRFLGS_ESC_CTRL 2
-#define ASN1_STRFLGS_ESC_MSB 4
-
-
-// This flag determines how we do escaping: normally
-// RC2253 backslash only, set this to use backslash and
-// quote.
-
-#define ASN1_STRFLGS_ESC_QUOTE 8
-
-
-// These three flags are internal use only.
-
-// Character is a valid PrintableString character
-#define CHARTYPE_PRINTABLESTRING 0x10
-// Character needs escaping if it is the first character
-#define CHARTYPE_FIRST_ESC_2253 0x20
-// Character needs escaping if it is the last character
-#define CHARTYPE_LAST_ESC_2253 0x40
-
-// NB the internal flags are safely reused below by flags
-// handled at the top level.
-
-// If this is set we convert all character strings
-// to UTF8 first
-
-#define ASN1_STRFLGS_UTF8_CONVERT 0x10
-
-// If this is set we don't attempt to interpret content:
-// just assume all strings are 1 byte per character. This
-// will produce some pretty odd looking output!
-
-#define ASN1_STRFLGS_IGNORE_TYPE 0x20
-
-// If this is set we include the string type in the output
-#define ASN1_STRFLGS_SHOW_TYPE 0x40
-
-// This determines which strings to display and which to
-// 'dump' (hex dump of content octets or DER encoding). We can
-// only dump non character strings or everything. If we
-// don't dump 'unknown' they are interpreted as character
-// strings with 1 octet per character and are subject to
-// the usual escaping options.
-
-#define ASN1_STRFLGS_DUMP_ALL 0x80
-#define ASN1_STRFLGS_DUMP_UNKNOWN 0x100
-
-// These determine what 'dumping' does, we can dump the
-// content octets or the DER encoding: both use the
-// RFC2253 #XXXXX notation.
-
-#define ASN1_STRFLGS_DUMP_DER 0x200
-
-// All the string flags consistent with RFC2253,
-// escaping control characters isn't essential in
-// RFC2253 but it is advisable anyway.
-
-#define ASN1_STRFLGS_RFC2253                                              \
-  (ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_ESC_MSB | \
-   ASN1_STRFLGS_UTF8_CONVERT | ASN1_STRFLGS_DUMP_UNKNOWN |                \
-   ASN1_STRFLGS_DUMP_DER)
-
 DEFINE_STACK_OF(ASN1_INTEGER)
 
 DEFINE_STACK_OF(ASN1_TYPE)
@@ -814,20 +980,6 @@
 DECLARE_ASN1_ENCODE_FUNCTIONS_const(ASN1_SEQUENCE_ANY, ASN1_SEQUENCE_ANY)
 DECLARE_ASN1_ENCODE_FUNCTIONS_const(ASN1_SEQUENCE_ANY, ASN1_SET_ANY)
 
-struct X509_algor_st {
-  ASN1_OBJECT *algorithm;
-  ASN1_TYPE *parameter;
-} /* X509_ALGOR */;
-
-DECLARE_ASN1_FUNCTIONS(X509_ALGOR)
-
-// This is used to contain a list of bit names
-typedef struct BIT_STRING_BITNAME_st {
-  int bitnum;
-  const char *lname;
-  const char *sname;
-} BIT_STRING_BITNAME;
-
 // M_ASN1_* are legacy aliases for various |ASN1_STRING| functions. Use the
 // functions themselves.
 #define M_ASN1_STRING_length(x) ASN1_STRING_length(x)
@@ -924,26 +1076,6 @@
 
 DECLARE_ASN1_FUNCTIONS(ASN1_ENUMERATED)
 
-OPENSSL_EXPORT int ASN1_UTCTIME_check(const ASN1_UTCTIME *a);
-OPENSSL_EXPORT ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *s, time_t t);
-OPENSSL_EXPORT ASN1_UTCTIME *ASN1_UTCTIME_adj(ASN1_UTCTIME *s, time_t t,
-                                              int offset_day, long offset_sec);
-OPENSSL_EXPORT int ASN1_UTCTIME_set_string(ASN1_UTCTIME *s, const char *str);
-OPENSSL_EXPORT int ASN1_UTCTIME_cmp_time_t(const ASN1_UTCTIME *s, time_t t);
-#if 0
-time_t ASN1_UTCTIME_get(const ASN1_UTCTIME *s);
-#endif
-
-OPENSSL_EXPORT int ASN1_GENERALIZEDTIME_check(const ASN1_GENERALIZEDTIME *a);
-OPENSSL_EXPORT ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(
-    ASN1_GENERALIZEDTIME *s, time_t t);
-OPENSSL_EXPORT ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_adj(
-    ASN1_GENERALIZEDTIME *s, time_t t, int offset_day, long offset_sec);
-OPENSSL_EXPORT int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *s,
-                                                   const char *str);
-OPENSSL_EXPORT int ASN1_TIME_diff(int *pday, int *psec, const ASN1_TIME *from,
-                                  const ASN1_TIME *to);
-
 DECLARE_ASN1_FUNCTIONS(ASN1_OCTET_STRING)
 OPENSSL_EXPORT ASN1_OCTET_STRING *ASN1_OCTET_STRING_dup(
     const ASN1_OCTET_STRING *a);
@@ -970,14 +1102,6 @@
 DECLARE_ASN1_FUNCTIONS(ASN1_GENERALIZEDTIME)
 DECLARE_ASN1_FUNCTIONS(ASN1_TIME)
 
-OPENSSL_EXPORT ASN1_TIME *ASN1_TIME_set(ASN1_TIME *s, time_t t);
-OPENSSL_EXPORT ASN1_TIME *ASN1_TIME_adj(ASN1_TIME *s, time_t t, int offset_day,
-                                        long offset_sec);
-OPENSSL_EXPORT int ASN1_TIME_check(const ASN1_TIME *t);
-OPENSSL_EXPORT ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(
-    const ASN1_TIME *t, ASN1_GENERALIZEDTIME **out);
-OPENSSL_EXPORT int ASN1_TIME_set_string(ASN1_TIME *s, const char *str);
-
 OPENSSL_EXPORT int i2a_ASN1_INTEGER(BIO *bp, const ASN1_INTEGER *a);
 OPENSSL_EXPORT int i2a_ASN1_ENUMERATED(BIO *bp, const ASN1_ENUMERATED *a);
 OPENSSL_EXPORT int i2a_ASN1_OBJECT(BIO *bp, const ASN1_OBJECT *a);
@@ -990,9 +1114,11 @@
                                                int len, const char *sn,
                                                const char *ln);
 
-// General
-// given a string, return the correct type, max is the maximum length
-OPENSSL_EXPORT int ASN1_PRINTABLE_type(const unsigned char *s, int max);
+// ASN1_PRINTABLE_type interprets |len| bytes from |s| as a Latin-1 string. It
+// returns the first of |V_ASN1_PRINTABLESTRING|, |V_ASN1_IA5STRING|, or
+// |V_ASN1_T61STRING| that can represent every character. If |len| is negative,
+// |strlen(s)| is used instead.
+OPENSSL_EXPORT int ASN1_PRINTABLE_type(const unsigned char *s, int len);
 
 OPENSSL_EXPORT unsigned long ASN1_tag2bit(int tag);
 
@@ -1006,23 +1132,11 @@
 
 OPENSSL_EXPORT void *ASN1_item_dup(const ASN1_ITEM *it, void *x);
 
-#ifndef OPENSSL_NO_FP_API
 OPENSSL_EXPORT void *ASN1_item_d2i_fp(const ASN1_ITEM *it, FILE *in, void *x);
 OPENSSL_EXPORT int ASN1_item_i2d_fp(const ASN1_ITEM *it, FILE *out, void *x);
-OPENSSL_EXPORT int ASN1_STRING_print_ex_fp(FILE *fp, const ASN1_STRING *str,
-                                           unsigned long flags);
-#endif
 
 OPENSSL_EXPORT void *ASN1_item_d2i_bio(const ASN1_ITEM *it, BIO *in, void *x);
 OPENSSL_EXPORT int ASN1_item_i2d_bio(const ASN1_ITEM *it, BIO *out, void *x);
-OPENSSL_EXPORT int ASN1_UTCTIME_print(BIO *fp, const ASN1_UTCTIME *a);
-OPENSSL_EXPORT int ASN1_GENERALIZEDTIME_print(BIO *fp,
-                                              const ASN1_GENERALIZEDTIME *a);
-OPENSSL_EXPORT int ASN1_TIME_print(BIO *fp, const ASN1_TIME *a);
-OPENSSL_EXPORT int ASN1_STRING_print(BIO *bp, const ASN1_STRING *v);
-OPENSSL_EXPORT int ASN1_STRING_print_ex(BIO *out, const ASN1_STRING *str,
-                                        unsigned long flags);
-OPENSSL_EXPORT const char *ASN1_tag2str(int tag);
 
 // Used to load and write netscape format cert
 
@@ -1032,16 +1146,14 @@
 OPENSSL_EXPORT ASN1_STRING *ASN1_item_pack(void *obj, const ASN1_ITEM *it,
                                            ASN1_OCTET_STRING **oct);
 
+// ASN1_STRING_set_default_mask does nothing.
 OPENSSL_EXPORT void ASN1_STRING_set_default_mask(unsigned long mask);
+
+// ASN1_STRING_set_default_mask_asc returns one.
 OPENSSL_EXPORT int ASN1_STRING_set_default_mask_asc(const char *p);
+
+// ASN1_STRING_get_default_mask returns |B_ASN1_UTF8STRING|.
 OPENSSL_EXPORT unsigned long ASN1_STRING_get_default_mask(void);
-OPENSSL_EXPORT int ASN1_mbstring_copy(ASN1_STRING **out,
-                                      const unsigned char *in, int len,
-                                      int inform, unsigned long mask);
-OPENSSL_EXPORT int ASN1_mbstring_ncopy(ASN1_STRING **out,
-                                       const unsigned char *in, int len,
-                                       int inform, unsigned long mask,
-                                       long minsize, long maxsize);
 
 OPENSSL_EXPORT ASN1_STRING *ASN1_STRING_set_by_NID(ASN1_STRING **out,
                                                    const unsigned char *in,
diff --git a/src/include/openssl/asn1t.h b/src/include/openssl/asn1t.h
index c5e2685..45302ef 100644
--- a/src/include/openssl/asn1t.h
+++ b/src/include/openssl/asn1t.h
@@ -389,13 +389,6 @@
 /* Field is a SEQUENCE OF */
 #define ASN1_TFLG_SEQUENCE_OF	(0x2 << 1)
 
-/* Special case: this refers to a SET OF that
- * will be sorted into DER order when encoded *and*
- * the corresponding STACK will be modified to match
- * the new order.
- */
-#define ASN1_TFLG_SET_ORDER	(0x3 << 1)
-
 /* Mask for SET OF or SEQUENCE OF */
 #define ASN1_TFLG_SK_MASK	(0x3 << 1)
 
@@ -602,8 +595,8 @@
 #define ASN1_OP_FREE_POST	3
 #define ASN1_OP_D2I_PRE		4
 #define ASN1_OP_D2I_POST	5
-#define ASN1_OP_I2D_PRE		6
-#define ASN1_OP_I2D_POST	7
+/* ASN1_OP_I2D_PRE and ASN1_OP_I2D_POST are not supported. We leave the
+ * constants undefined so code relying on them does not accidentally compile. */
 #define ASN1_OP_PRINT_PRE	8
 #define ASN1_OP_PRINT_POST	9
 #define ASN1_OP_STREAM_PRE	10
diff --git a/src/include/openssl/base.h b/src/include/openssl/base.h
index ea4366a..8be10d1 100644
--- a/src/include/openssl/base.h
+++ b/src/include/openssl/base.h
@@ -145,7 +145,7 @@
 // Trusty isn't Linux but currently defines __linux__. As a workaround, we
 // exclude it here.
 // TODO(b/169780122): Remove this workaround once Trusty no longer defines it.
-#if defined(__linux__) && !defined(TRUSTY)
+#if defined(__linux__) && !defined(__TRUSTY__)
 #define OPENSSL_LINUX
 #endif
 
@@ -153,7 +153,7 @@
 #define OPENSSL_FUCHSIA
 #endif
 
-#if defined(TRUSTY)
+#if defined(__TRUSTY__)
 #define OPENSSL_TRUSTY
 #define OPENSSL_NO_THREADS_CORRUPT_MEMORY_AND_LEAK_SECRETS_IF_THREADED
 #endif
@@ -422,6 +422,7 @@
 typedef struct rand_meth_st RAND_METHOD;
 typedef struct rc4_key_st RC4_KEY;
 typedef struct rsa_meth_st RSA_METHOD;
+typedef struct rsa_pss_params_st RSA_PSS_PARAMS;
 typedef struct rsa_st RSA;
 typedef struct sha256_state_st SHA256_CTX;
 typedef struct sha512_state_st SHA512_CTX;
@@ -445,9 +446,10 @@
 typedef struct v3_ext_ctx X509V3_CTX;
 typedef struct x509_attributes_st X509_ATTRIBUTE;
 typedef struct x509_cert_aux_st X509_CERT_AUX;
-typedef struct x509_cinf_st X509_CINF;
 typedef struct x509_crl_method_st X509_CRL_METHOD;
 typedef struct x509_lookup_st X509_LOOKUP;
+typedef struct x509_lookup_method_st X509_LOOKUP_METHOD;
+typedef struct x509_object_st X509_OBJECT;
 typedef struct x509_revoked_st X509_REVOKED;
 typedef struct x509_st X509;
 typedef struct x509_store_ctx_st X509_STORE_CTX;
diff --git a/src/include/openssl/bio.h b/src/include/openssl/bio.h
index f25492a..18bc893 100644
--- a/src/include/openssl/bio.h
+++ b/src/include/openssl/bio.h
@@ -377,7 +377,9 @@
 OPENSSL_EXPORT const BIO_METHOD *BIO_s_mem(void);
 
 // BIO_new_mem_buf creates read-only BIO that reads from |len| bytes at |buf|.
-// It does not take ownership of |buf|. It returns the BIO or NULL on error.
+// It returns the BIO or NULL on error. This function does not copy or take
+// ownership of |buf|. The caller must ensure the memory pointed to by |buf|
+// outlives the |BIO|.
 //
 // If |len| is negative, then |buf| is treated as a NUL-terminated string, but
 // don't depend on this in new code.
diff --git a/src/include/openssl/bn.h b/src/include/openssl/bn.h
index 295ca62..5ca8b85 100644
--- a/src/include/openssl/bn.h
+++ b/src/include/openssl/bn.h
@@ -687,9 +687,9 @@
 // BN_prime_checks_for_validation can be used as the |checks| argument to the
 // primarily testing functions when validating an externally-supplied candidate
 // prime. It gives a false positive rate of at most 2^{-128}. (The worst case
-// false positive rate for a single iteration is 1/4, so we perform 32
-// iterations.)
-#define BN_prime_checks_for_validation 32
+// false positive rate for a single iteration is 1/4 per
+// https://eprint.iacr.org/2018/749. (1/4)^64 = 2^{-128}.)
+#define BN_prime_checks_for_validation 64
 
 // BN_prime_checks_for_generation can be used as the |checks| argument to the
 // primality testing functions when generating random primes. It gives a false
diff --git a/src/include/openssl/bytestring.h b/src/include/openssl/bytestring.h
index 39b7fb2..5ef3742 100644
--- a/src/include/openssl/bytestring.h
+++ b/src/include/openssl/bytestring.h
@@ -154,6 +154,11 @@
 // returns one on success and zero on error.
 OPENSSL_EXPORT int CBS_get_u24_length_prefixed(CBS *cbs, CBS *out);
 
+// CBS_get_until_first finds the first instance of |c| in |cbs|. If found, it
+// sets |*out| to the text before the match, advances |cbs| over it, and returns
+// one. Otherwise, it returns zero and leaves |cbs| unmodified.
+OPENSSL_EXPORT int CBS_get_until_first(CBS *cbs, CBS *out, uint8_t c);
+
 
 // Parsing ASN.1
 //
@@ -463,6 +468,10 @@
 // success and zero otherwise.
 OPENSSL_EXPORT int CBB_add_bytes(CBB *cbb, const uint8_t *data, size_t len);
 
+// CBB_add_zeros append |len| bytes with value zero to |cbb|. It returns one on
+// success and zero otherwise.
+OPENSSL_EXPORT int CBB_add_zeros(CBB *cbb, size_t len);
+
 // CBB_add_space appends |len| bytes to |cbb| and sets |*out_data| to point to
 // the beginning of that space. The caller must then write |len| bytes of
 // actual contents to |*out_data|. It returns one on success and zero
diff --git a/src/include/openssl/hkdf.h b/src/include/openssl/hkdf.h
index 59aaa49..5b27acc 100644
--- a/src/include/openssl/hkdf.h
+++ b/src/include/openssl/hkdf.h
@@ -41,6 +41,10 @@
 // keying material |secret| and salt |salt| using |digest|, and outputs
 // |out_len| bytes to |out_key|. The maximum output size is |EVP_MAX_MD_SIZE|.
 // It returns one on success and zero on error.
+//
+// WARNING: This function orders the inputs differently from RFC 5869
+// specification. Double-check which parameter is the secret/IKM and which is
+// the salt when using.
 OPENSSL_EXPORT int HKDF_extract(uint8_t *out_key, size_t *out_len,
                                 const EVP_MD *digest, const uint8_t *secret,
                                 size_t secret_len, const uint8_t *salt,
diff --git a/src/include/openssl/pem.h b/src/include/openssl/pem.h
index f39989e..a94f276 100644
--- a/src/include/openssl/pem.h
+++ b/src/include/openssl/pem.h
@@ -112,15 +112,6 @@
 // write. Now they are all implemented with either:
 // IMPLEMENT_PEM_rw(...) or IMPLEMENT_PEM_rw_cb(...)
 
-#ifdef OPENSSL_NO_FP_API
-
-#define IMPLEMENT_PEM_read_fp(name, type, str, asn1)            //
-#define IMPLEMENT_PEM_write_fp(name, type, str, asn1)           //
-#define IMPLEMENT_PEM_write_fp_const(name, type, str, asn1)     //
-#define IMPLEMENT_PEM_write_cb_fp(name, type, str, asn1)        //
-#define IMPLEMENT_PEM_write_cb_fp_const(name, type, str, asn1)  //
-
-#else
 
 #define IMPLEMENT_PEM_read_fp(name, type, str, asn1)                         \
   static void *pem_read_##name##_d2i(void **x, const unsigned char **inp,    \
@@ -173,7 +164,6 @@
                           cb, u);                                              \
   }
 
-#endif
 
 #define IMPLEMENT_PEM_read_bio(name, type, str, asn1)                         \
   static void *pem_read_bio_##name##_d2i(void **x, const unsigned char **inp, \
@@ -260,14 +250,6 @@
 
 // These are the same except they are for the declarations
 
-#if defined(OPENSSL_NO_FP_API)
-
-#define DECLARE_PEM_read_fp(name, type)      //
-#define DECLARE_PEM_write_fp(name, type)     //
-#define DECLARE_PEM_write_cb_fp(name, type)  //
-
-#else
-
 #define DECLARE_PEM_read_fp(name, type)                    \
   OPENSSL_EXPORT type *PEM_read_##name(FILE *fp, type **x, \
                                        pem_password_cb *cb, void *u);
@@ -283,8 +265,6 @@
       FILE *fp, type *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, \
       pem_password_cb *cb, void *u);
 
-#endif
-
 #define DECLARE_PEM_read_bio(name, type)                      \
   OPENSSL_EXPORT type *PEM_read_bio_##name(BIO *bp, type **x, \
                                            pem_password_cb *cb, void *u);
diff --git a/src/include/openssl/pkcs7.h b/src/include/openssl/pkcs7.h
index 8f2a885..77e13d7 100644
--- a/src/include/openssl/pkcs7.h
+++ b/src/include/openssl/pkcs7.h
@@ -200,15 +200,22 @@
 #define PKCS7_STREAM 0x1000
 #define PKCS7_PARTIAL 0x4000
 
-// PKCS7_sign assembles |certs| into a PKCS#7 signed data ContentInfo with
+// PKCS7_sign can operate in two modes to provide some backwards compatibility:
+//
+// The first mode assembles |certs| into a PKCS#7 signed data ContentInfo with
 // external data and no signatures. It returns a newly-allocated |PKCS7| on
 // success or NULL on error. |sign_cert| and |pkey| must be NULL. |data| is
-// ignored. |flags| must be equal to |PKCS7_DETACHED|.
-//
-// Note this function only implements a subset of the corresponding OpenSSL
-// function. It is provided for backwards compatibility only. Additionally,
+// ignored. |flags| must be equal to |PKCS7_DETACHED|. Additionally,
 // certificates in SignedData structures are unordered. The order of |certs|
 // will not be preserved.
+//
+// The second mode generates a detached RSA SHA-256 signature of |data| using
+// |pkey| and produces a PKCS#7 SignedData structure containing it. |certs|
+// must be NULL and |flags| must be exactly |PKCS7_NOATTR | PKCS7_BINARY |
+// PKCS7_NOCERTS | PKCS7_DETACHED|.
+//
+// Note this function only implements a subset of the corresponding OpenSSL
+// function. It is provided for backwards compatibility only.
 OPENSSL_EXPORT PKCS7 *PKCS7_sign(X509 *sign_cert, EVP_PKEY *pkey,
                                  STACK_OF(X509) *certs, BIO *data, int flags);
 
diff --git a/src/include/openssl/rsa.h b/src/include/openssl/rsa.h
index 27bc7bf..95a478a 100644
--- a/src/include/openssl/rsa.h
+++ b/src/include/openssl/rsa.h
@@ -684,6 +684,11 @@
 // on success or zero otherwise.
 OPENSSL_EXPORT int RSA_print(BIO *bio, const RSA *rsa, int indent);
 
+// RSA_get0_pss_params returns NULL. In OpenSSL, this function retries RSA-PSS
+// parameters associated with |RSA| objects, but BoringSSL does not support
+// the id-RSASSA-PSS key encoding.
+OPENSSL_EXPORT const RSA_PSS_PARAMS *RSA_get0_pss_params(const RSA *rsa);
+
 
 struct rsa_meth_st {
   struct openssl_method_common_st common;
diff --git a/src/include/openssl/span.h b/src/include/openssl/span.h
index 7410bf9..79f1d41 100644
--- a/src/include/openssl/span.h
+++ b/src/include/openssl/span.h
@@ -94,18 +94,6 @@
 template <typename T>
 class Span : private internal::SpanBase<const T> {
  private:
-  // Heuristically test whether C is a container type that can be converted into
-  // a Span by checking for data() and size() member functions.
-  //
-  // TODO(davidben): Switch everything to std::enable_if_t when we remove
-  // support for MSVC 2015. Although we could write our own enable_if_t and MSVC
-  // 2015 has std::enable_if_t anyway, MSVC 2015's SFINAE implementation is
-  // problematic and does not work below unless we write the ::type at use.
-  template <typename C>
-  using EnableIfContainer = std::enable_if<
-      std::is_convertible<decltype(std::declval<C>().data()), T *>::value &&
-      std::is_integral<decltype(std::declval<C>().size())>::value>;
-
   static const size_t npos = static_cast<size_t>(-1);
 
  public:
@@ -116,12 +104,27 @@
   constexpr Span(T (&array)[N]) : Span(array, N) {}
 
   template <
-      typename C, typename = typename EnableIfContainer<C>::type,
+      typename C,
+      // TODO(davidben): Switch everything to std::enable_if_t when we remove
+      // support for MSVC 2015. Although we could write our own enable_if_t and
+      // MSVC 2015 has std::enable_if_t anyway, MSVC 2015's SFINAE
+      // implementation is problematic and does not work below unless we write
+      // the ::type at use.
+      //
+      // TODO(davidben): Move this and the identical copy below into an
+      // EnableIfContainer alias when we drop MSVC 2015 support. MSVC 2015's
+      // SFINAE support cannot handle type aliases.
+      typename = typename std::enable_if<
+          std::is_convertible<decltype(std::declval<C>().data()), T *>::value &&
+          std::is_integral<decltype(std::declval<C>().size())>::value>::type,
       typename = typename std::enable_if<std::is_const<T>::value, C>::type>
   Span(const C &container) : data_(container.data()), size_(container.size()) {}
 
   template <
-      typename C, typename = typename EnableIfContainer<C>::type,
+      typename C,
+      typename = typename std::enable_if<
+          std::is_convertible<decltype(std::declval<C>().data()), T *>::value &&
+          std::is_integral<decltype(std::declval<C>().size())>::value>::type,
       typename = typename std::enable_if<!std::is_const<T>::value, C>::type>
   explicit Span(C &container)
       : data_(container.data()), size_(container.size()) {}
@@ -158,11 +161,30 @@
 
   Span subspan(size_t pos = 0, size_t len = npos) const {
     if (pos > size_) {
-      abort();  // absl::Span throws an exception here.
+      // absl::Span throws an exception here. Note std::span and Chromium
+      // base::span additionally forbid pos + len being out of range, with a
+      // special case at npos/dynamic_extent, while absl::Span::subspan clips
+      // the span. For now, we align with absl::Span in case we switch to it in
+      // the future.
+      abort();
     }
     return Span(data_ + pos, std::min(size_ - pos, len));
   }
 
+  Span first(size_t len) {
+    if (len > size_) {
+      abort();
+    }
+    return Span(data_, len);
+  }
+
+  Span last(size_t len) {
+    if (len > size_) {
+      abort();
+    }
+    return Span(data_ + size_ - len, len);
+  }
+
  private:
   T *data_;
   size_t size_;
diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h
index 82e1da8..5965cb4 100644
--- a/src/include/openssl/ssl.h
+++ b/src/include/openssl/ssl.h
@@ -1649,6 +1649,11 @@
 // abbreviated handshake. It is reference-counted and immutable. Once
 // established, an |SSL_SESSION| may be shared by multiple |SSL| objects on
 // different threads and must not be modified.
+//
+// Note the TLS notion of "session" is not suitable for application-level
+// session state. It is an optional caching mechanism for the handshake. Not all
+// connections within an application-level session will reuse TLS sessions. TLS
+// sessions may be dropped by the client or ignored by the server at any time.
 
 DECLARE_PEM_rw(SSL_SESSION, SSL_SESSION)
 
@@ -1703,6 +1708,19 @@
 
 // SSL_SESSION_get_id returns a pointer to a buffer containing |session|'s
 // session ID and sets |*out_len| to its length.
+//
+// This function should only be used for implementing a TLS session cache. TLS
+// sessions are not suitable for application-level session state, and a session
+// ID is an implementation detail of the TLS resumption handshake mechanism. Not
+// all resumption flows use session IDs, and not all connections within an
+// application-level session will reuse TLS sessions.
+//
+// To determine if resumption occurred, use |SSL_session_reused| instead.
+// Comparing session IDs will not give the right result in all cases.
+//
+// As a workaround for some broken applications, BoringSSL sometimes synthesizes
+// arbitrary session IDs for non-ID-based sessions. This behavior may be
+// removed in the future.
 OPENSSL_EXPORT const uint8_t *SSL_SESSION_get_id(const SSL_SESSION *session,
                                                  unsigned *out_len);
 
@@ -3569,7 +3587,7 @@
 //
 // ECH support in BoringSSL is still experimental and under development.
 //
-// See https://tools.ietf.org/html/draft-ietf-tls-esni-10.
+// See https://tools.ietf.org/html/draft-ietf-tls-esni-13.
 
 // SSL_set_enable_ech_grease configures whether the client will send a GREASE
 // ECH extension when no supported ECHConfig is available.
@@ -3601,12 +3619,12 @@
                                             const uint8_t *ech_config_list,
                                             size_t ech_config_list_len);
 
-// SSL_get0_ech_name_override sets |*out_name| and |*out_name_len| to point to a
-// buffer containing the ECH public name, if the server rejected ECH, or the
-// empty string otherwise.
+// SSL_get0_ech_name_override, if |ssl| is a client and the server rejected ECH,
+// sets |*out_name| and |*out_name_len| to point to a buffer containing the ECH
+// public name. Otherwise, the buffer will be empty.
 //
-// This function should be called during the certificate verification callback
-// (see |SSL_CTX_set_custom_verify|) if |ssl| is a client offering ECH. If
+// When offering ECH as a client, this function should be called during the
+// certificate verification callback (see |SSL_CTX_set_custom_verify|). If
 // |*out_name_len| is non-zero, the caller should verify the certificate against
 // the result, interpreted as a DNS name, rather than the true server name. In
 // this case, the handshake will never succeed and is only used to authenticate
@@ -4894,12 +4912,6 @@
 OPENSSL_EXPORT int SSL_add_dir_cert_subjects_to_stack(STACK_OF(X509_NAME) *out,
                                                       const char *dir);
 
-// SSL_set_verify_result calls |abort| unless |result| is |X509_V_OK|.
-//
-// TODO(davidben): Remove this function once it has been removed from
-// netty-tcnative.
-OPENSSL_EXPORT void SSL_set_verify_result(SSL *ssl, long result);
-
 // SSL_CTX_enable_tls_channel_id calls |SSL_CTX_set_tls_channel_id_enabled|.
 OPENSSL_EXPORT int SSL_CTX_enable_tls_channel_id(SSL_CTX *ctx);
 
@@ -5554,6 +5566,8 @@
 #define SSL_R_INVALID_ECH_PUBLIC_NAME 317
 #define SSL_R_INVALID_ECH_CONFIG_LIST 318
 #define SSL_R_ECH_REJECTED 319
+#define SSL_R_OUTER_EXTENSION_NOT_FOUND 320
+#define SSL_R_INCONSISTENT_ECH_NEGOTIATION 321
 #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/tls1.h b/src/include/openssl/tls1.h
index 2886e2c..a3136c0 100644
--- a/src/include/openssl/tls1.h
+++ b/src/include/openssl/tls1.h
@@ -179,28 +179,28 @@
 #define TLS1_AD_UNKNOWN_PSK_IDENTITY 115
 #define TLS1_AD_CERTIFICATE_REQUIRED 116
 #define TLS1_AD_NO_APPLICATION_PROTOCOL 120
-#define TLS1_AD_ECH_REQUIRED 121  // draft-ietf-tls-esni-10
+#define TLS1_AD_ECH_REQUIRED 121  // draft-ietf-tls-esni-13
 
-// ExtensionType values from RFC6066
+// ExtensionType values from RFC 6066
 #define TLSEXT_TYPE_server_name 0
 #define TLSEXT_TYPE_status_request 5
 
-// ExtensionType values from RFC4492
+// ExtensionType values from RFC 4492
 #define TLSEXT_TYPE_ec_point_formats 11
 
-// ExtensionType values from RFC5246
+// ExtensionType values from RFC 5246
 #define TLSEXT_TYPE_signature_algorithms 13
 
-// ExtensionType value from RFC5764
+// ExtensionType value from RFC 5764
 #define TLSEXT_TYPE_srtp 14
 
-// ExtensionType value from RFC7301
+// ExtensionType value from RFC 7301
 #define TLSEXT_TYPE_application_layer_protocol_negotiation 16
 
-// ExtensionType value from RFC7685
+// ExtensionType value from RFC 7685
 #define TLSEXT_TYPE_padding 21
 
-// ExtensionType value from RFC7627
+// ExtensionType value from RFC 7627
 #define TLSEXT_TYPE_extended_master_secret 23
 
 // ExtensionType value from draft-ietf-quic-tls. Drafts 00 through 32 use
@@ -210,7 +210,7 @@
 // use the value 57 which was officially registered with IANA.
 #define TLSEXT_TYPE_quic_transport_parameters_legacy 0xffa5
 
-// ExtensionType value from RFC9000
+// ExtensionType value from RFC 9000
 #define TLSEXT_TYPE_quic_transport_parameters 57
 
 // TLSEXT_TYPE_quic_transport_parameters_standard is an alias for
@@ -219,13 +219,13 @@
 #define TLSEXT_TYPE_quic_transport_parameters_standard \
   TLSEXT_TYPE_quic_transport_parameters
 
-// ExtensionType value from RFC8879
+// ExtensionType value from RFC 8879
 #define TLSEXT_TYPE_cert_compression 27
 
-// ExtensionType value from RFC4507
+// ExtensionType value from RFC 4507
 #define TLSEXT_TYPE_session_ticket 35
 
-// ExtensionType values from RFC8446
+// ExtensionType values from RFC 8446
 #define TLSEXT_TYPE_supported_groups 10
 #define TLSEXT_TYPE_pre_shared_key 41
 #define TLSEXT_TYPE_early_data 42
@@ -236,7 +236,7 @@
 #define TLSEXT_TYPE_signature_algorithms_cert 50
 #define TLSEXT_TYPE_key_share 51
 
-// ExtensionType value from RFC5746
+// ExtensionType value from RFC 5746
 #define TLSEXT_TYPE_renegotiate 0xff01
 
 // ExtensionType value from draft-ietf-tls-subcerts.
@@ -246,13 +246,12 @@
 // extension number.
 #define TLSEXT_TYPE_application_settings 17513
 
-// ExtensionType values from draft-ietf-tls-esni-10. This is not an IANA defined
+// ExtensionType values from draft-ietf-tls-esni-13. This is not an IANA defined
 // extension number.
-#define TLSEXT_TYPE_encrypted_client_hello 0xfe0a
-#define TLSEXT_TYPE_ech_is_inner 0xda09
+#define TLSEXT_TYPE_encrypted_client_hello 0xfe0d
 #define TLSEXT_TYPE_ech_outer_extensions 0xfd00
 
-// ExtensionType value from RFC6962
+// ExtensionType value from RFC 6962
 #define TLSEXT_TYPE_certificate_timestamp 18
 
 // This is not an IANA defined extension number
@@ -313,7 +312,7 @@
 #define TLS1_CK_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA 0x03000065
 #define TLS1_CK_DHE_DSS_WITH_RC4_128_SHA 0x03000066
 
-// AES ciphersuites from RFC3268
+// AES ciphersuites from RFC 3268
 
 #define TLS1_CK_RSA_WITH_AES_128_SHA 0x0300002F
 #define TLS1_CK_DH_DSS_WITH_AES_128_SHA 0x03000030
@@ -337,7 +336,7 @@
 #define TLS1_CK_DH_RSA_WITH_AES_128_SHA256 0x0300003F
 #define TLS1_CK_DHE_DSS_WITH_AES_128_SHA256 0x03000040
 
-// Camellia ciphersuites from RFC4132
+// Camellia ciphersuites from RFC 4132
 #define TLS1_CK_RSA_WITH_CAMELLIA_128_CBC_SHA 0x03000041
 #define TLS1_CK_DH_DSS_WITH_CAMELLIA_128_CBC_SHA 0x03000042
 #define TLS1_CK_DH_RSA_WITH_CAMELLIA_128_CBC_SHA 0x03000043
@@ -354,7 +353,7 @@
 #define TLS1_CK_ADH_WITH_AES_128_SHA256 0x0300006C
 #define TLS1_CK_ADH_WITH_AES_256_SHA256 0x0300006D
 
-// Camellia ciphersuites from RFC4132
+// Camellia ciphersuites from RFC 4132
 #define TLS1_CK_RSA_WITH_CAMELLIA_256_CBC_SHA 0x03000084
 #define TLS1_CK_DH_DSS_WITH_CAMELLIA_256_CBC_SHA 0x03000085
 #define TLS1_CK_DH_RSA_WITH_CAMELLIA_256_CBC_SHA 0x03000086
@@ -362,7 +361,7 @@
 #define TLS1_CK_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA 0x03000088
 #define TLS1_CK_ADH_WITH_CAMELLIA_256_CBC_SHA 0x03000089
 
-// SEED ciphersuites from RFC4162
+// SEED ciphersuites from RFC 4162
 #define TLS1_CK_RSA_WITH_SEED_SHA 0x03000096
 #define TLS1_CK_DH_DSS_WITH_SEED_SHA 0x03000097
 #define TLS1_CK_DH_RSA_WITH_SEED_SHA 0x03000098
@@ -370,7 +369,7 @@
 #define TLS1_CK_DHE_RSA_WITH_SEED_SHA 0x0300009A
 #define TLS1_CK_ADH_WITH_SEED_SHA 0x0300009B
 
-// TLS v1.2 GCM ciphersuites from RFC5288
+// TLS v1.2 GCM ciphersuites from RFC 5288
 #define TLS1_CK_RSA_WITH_AES_128_GCM_SHA256 0x0300009C
 #define TLS1_CK_RSA_WITH_AES_256_GCM_SHA384 0x0300009D
 #define TLS1_CK_DHE_RSA_WITH_AES_128_GCM_SHA256 0x0300009E
@@ -384,7 +383,7 @@
 #define TLS1_CK_ADH_WITH_AES_128_GCM_SHA256 0x030000A6
 #define TLS1_CK_ADH_WITH_AES_256_GCM_SHA384 0x030000A7
 
-// ECC ciphersuites from RFC4492
+// ECC ciphersuites from RFC 4492
 #define TLS1_CK_ECDH_ECDSA_WITH_NULL_SHA 0x0300C001
 #define TLS1_CK_ECDH_ECDSA_WITH_RC4_128_SHA 0x0300C002
 #define TLS1_CK_ECDH_ECDSA_WITH_DES_192_CBC3_SHA 0x0300C003
@@ -426,7 +425,7 @@
 #define TLS1_CK_SRP_SHA_RSA_WITH_AES_256_CBC_SHA 0x0300C021
 #define TLS1_CK_SRP_SHA_DSS_WITH_AES_256_CBC_SHA 0x0300C022
 
-// ECDH HMAC based ciphersuites from RFC5289
+// ECDH HMAC based ciphersuites from RFC 5289
 
 #define TLS1_CK_ECDHE_ECDSA_WITH_AES_128_SHA256 0x0300C023
 #define TLS1_CK_ECDHE_ECDSA_WITH_AES_256_SHA384 0x0300C024
@@ -437,7 +436,7 @@
 #define TLS1_CK_ECDH_RSA_WITH_AES_128_SHA256 0x0300C029
 #define TLS1_CK_ECDH_RSA_WITH_AES_256_SHA384 0x0300C02A
 
-// ECDH GCM based ciphersuites from RFC5289
+// ECDH GCM based ciphersuites from RFC 5289
 #define TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0x0300C02B
 #define TLS1_CK_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 0x0300C02C
 #define TLS1_CK_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 0x0300C02D
@@ -473,7 +472,7 @@
 #define TLS1_TXT_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA "EXP1024-DHE-DSS-RC4-SHA"
 #define TLS1_TXT_DHE_DSS_WITH_RC4_128_SHA "DHE-DSS-RC4-SHA"
 
-// AES ciphersuites from RFC3268
+// AES ciphersuites from RFC 3268
 #define TLS1_TXT_RSA_WITH_AES_128_SHA "AES128-SHA"
 #define TLS1_TXT_DH_DSS_WITH_AES_128_SHA "DH-DSS-AES128-SHA"
 #define TLS1_TXT_DH_RSA_WITH_AES_128_SHA "DH-RSA-AES128-SHA"
@@ -488,7 +487,7 @@
 #define TLS1_TXT_DHE_RSA_WITH_AES_256_SHA "DHE-RSA-AES256-SHA"
 #define TLS1_TXT_ADH_WITH_AES_256_SHA "ADH-AES256-SHA"
 
-// ECC ciphersuites from RFC4492
+// ECC ciphersuites from RFC 4492
 #define TLS1_TXT_ECDH_ECDSA_WITH_NULL_SHA "ECDH-ECDSA-NULL-SHA"
 #define TLS1_TXT_ECDH_ECDSA_WITH_RC4_128_SHA "ECDH-ECDSA-RC4-SHA"
 #define TLS1_TXT_ECDH_ECDSA_WITH_DES_192_CBC3_SHA "ECDH-ECDSA-DES-CBC3-SHA"
@@ -540,7 +539,7 @@
 #define TLS1_TXT_SRP_SHA_RSA_WITH_AES_256_CBC_SHA "SRP-RSA-AES-256-CBC-SHA"
 #define TLS1_TXT_SRP_SHA_DSS_WITH_AES_256_CBC_SHA "SRP-DSS-AES-256-CBC-SHA"
 
-// Camellia ciphersuites from RFC4132
+// Camellia ciphersuites from RFC 4132
 #define TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA "CAMELLIA128-SHA"
 #define TLS1_TXT_DH_DSS_WITH_CAMELLIA_128_CBC_SHA "DH-DSS-CAMELLIA128-SHA"
 #define TLS1_TXT_DH_RSA_WITH_CAMELLIA_128_CBC_SHA "DH-RSA-CAMELLIA128-SHA"
@@ -555,7 +554,7 @@
 #define TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA "DHE-RSA-CAMELLIA256-SHA"
 #define TLS1_TXT_ADH_WITH_CAMELLIA_256_CBC_SHA "ADH-CAMELLIA256-SHA"
 
-// SEED ciphersuites from RFC4162
+// SEED ciphersuites from RFC 4162
 #define TLS1_TXT_RSA_WITH_SEED_SHA "SEED-SHA"
 #define TLS1_TXT_DH_DSS_WITH_SEED_SHA "DH-DSS-SEED-SHA"
 #define TLS1_TXT_DH_RSA_WITH_SEED_SHA "DH-RSA-SEED-SHA"
@@ -578,7 +577,7 @@
 #define TLS1_TXT_ADH_WITH_AES_128_SHA256 "ADH-AES128-SHA256"
 #define TLS1_TXT_ADH_WITH_AES_256_SHA256 "ADH-AES256-SHA256"
 
-// TLS v1.2 GCM ciphersuites from RFC5288
+// TLS v1.2 GCM ciphersuites from RFC 5288
 #define TLS1_TXT_RSA_WITH_AES_128_GCM_SHA256 "AES128-GCM-SHA256"
 #define TLS1_TXT_RSA_WITH_AES_256_GCM_SHA384 "AES256-GCM-SHA384"
 #define TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 "DHE-RSA-AES128-GCM-SHA256"
@@ -592,7 +591,7 @@
 #define TLS1_TXT_ADH_WITH_AES_128_GCM_SHA256 "ADH-AES128-GCM-SHA256"
 #define TLS1_TXT_ADH_WITH_AES_256_GCM_SHA384 "ADH-AES256-GCM-SHA384"
 
-// ECDH HMAC based ciphersuites from RFC5289
+// ECDH HMAC based ciphersuites from RFC 5289
 
 #define TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_SHA256 "ECDHE-ECDSA-AES128-SHA256"
 #define TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_SHA384 "ECDHE-ECDSA-AES256-SHA384"
@@ -603,7 +602,7 @@
 #define TLS1_TXT_ECDH_RSA_WITH_AES_128_SHA256 "ECDH-RSA-AES128-SHA256"
 #define TLS1_TXT_ECDH_RSA_WITH_AES_256_SHA384 "ECDH-RSA-AES256-SHA384"
 
-// ECDH GCM based ciphersuites from RFC5289
+// ECDH GCM based ciphersuites from RFC 5289
 #define TLS1_TXT_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 \
   "ECDHE-ECDSA-AES128-GCM-SHA256"
 #define TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 \
diff --git a/src/include/openssl/x509.h b/src/include/openssl/x509.h
index dafa677..30ad4d2 100644
--- a/src/include/openssl/x509.h
+++ b/src/include/openssl/x509.h
@@ -110,28 +110,19 @@
 #define X509v3_KU_DECIPHER_ONLY 0x8000
 #define X509v3_KU_UNDEF 0xffff
 
+struct X509_algor_st {
+  ASN1_OBJECT *algorithm;
+  ASN1_TYPE *parameter;
+} /* X509_ALGOR */;
+
+DECLARE_ASN1_FUNCTIONS(X509_ALGOR)
+
 DEFINE_STACK_OF(X509_ALGOR)
 
 typedef STACK_OF(X509_ALGOR) X509_ALGORS;
 
-struct X509_name_entry_st {
-  ASN1_OBJECT *object;
-  ASN1_STRING *value;
-  int set;
-} /* X509_NAME_ENTRY */;
-
 DEFINE_STACK_OF(X509_NAME_ENTRY)
 
-// we always keep X509_NAMEs in 2 forms.
-struct X509_name_st {
-  STACK_OF(X509_NAME_ENTRY) *entries;
-  int modified;  // true if 'bytes' needs to be built
-  BUF_MEM *bytes;
-  // unsigned long hash; Keep the hash around for lookups
-  unsigned char *canon_enc;
-  int canon_enclen;
-} /* X509_NAME */;
-
 DEFINE_STACK_OF(X509_NAME)
 
 typedef STACK_OF(X509_EXTENSION) X509_EXTENSIONS;
@@ -140,20 +131,6 @@
 
 DEFINE_STACK_OF(X509_ATTRIBUTE)
 
-struct x509_cinf_st {
-  ASN1_INTEGER *version;  // [ 0 ] default of v1
-  ASN1_INTEGER *serialNumber;
-  X509_ALGOR *signature;
-  X509_NAME *issuer;
-  X509_VAL *validity;
-  X509_NAME *subject;
-  X509_PUBKEY *key;
-  ASN1_BIT_STRING *issuerUID;            // [ 1 ] optional in v2
-  ASN1_BIT_STRING *subjectUID;           // [ 2 ] optional in v2
-  STACK_OF(X509_EXTENSION) *extensions;  // [ 3 ] optional in v3
-  ASN1_ENCODING enc;
-} /* X509_CINF */;
-
 // This stuff is certificate "auxiliary info"
 // it contains details which are useful in certificate
 // stores and databases. When used this is tagged onto
@@ -162,31 +139,6 @@
 DECLARE_STACK_OF(DIST_POINT)
 DECLARE_STACK_OF(GENERAL_NAME)
 
-struct x509_st {
-  X509_CINF *cert_info;
-  X509_ALGOR *sig_alg;
-  ASN1_BIT_STRING *signature;
-  CRYPTO_refcount_t references;
-  CRYPTO_EX_DATA ex_data;
-  // These contain copies of various extension values
-  long ex_pathlen;
-  long ex_pcpathlen;
-  unsigned long ex_flags;
-  unsigned long ex_kusage;
-  unsigned long ex_xkusage;
-  unsigned long ex_nscert;
-  ASN1_OCTET_STRING *skid;
-  AUTHORITY_KEYID *akid;
-  X509_POLICY_CACHE *policy_cache;
-  STACK_OF(DIST_POINT) *crldp;
-  STACK_OF(GENERAL_NAME) *altname;
-  NAME_CONSTRAINTS *nc;
-  unsigned char sha1_hash[SHA_DIGEST_LENGTH];
-  X509_CERT_AUX *aux;
-  CRYPTO_BUFFER *buf;
-  CRYPTO_MUTEX lock;
-} /* X509 */;
-
 DEFINE_STACK_OF(X509)
 
 // This is used for a table of trust checking functions
@@ -254,7 +206,7 @@
 #define XN_FLAG_SEP_MASK (0xf << 16)
 
 #define XN_FLAG_COMPAT 0  // Traditional SSLeay: use old X509_NAME_print
-#define XN_FLAG_SEP_COMMA_PLUS (1 << 16)  // RFC2253 ,+
+#define XN_FLAG_SEP_COMMA_PLUS (1 << 16)  // RFC 2253 ,+
 #define XN_FLAG_SEP_CPLUS_SPC (2 << 16)   // ,+ spaced: more readable
 #define XN_FLAG_SEP_SPLUS_SPC (3 << 16)   // ;+ spaced
 #define XN_FLAG_SEP_MULTILINE (4 << 16)   // One line per field
@@ -273,13 +225,13 @@
 #define XN_FLAG_SPC_EQ (1 << 23)  // Put spaces round '='
 
 // This determines if we dump fields we don't recognise:
-// RFC2253 requires this.
+// RFC 2253 requires this.
 
 #define XN_FLAG_DUMP_UNKNOWN_FIELDS (1 << 24)
 
 #define XN_FLAG_FN_ALIGN (1 << 25)  // Align field names to 20 characters
 
-// Complete set of RFC2253 flags
+// Complete set of RFC 2253 flags
 
 #define XN_FLAG_RFC2253                                             \
   (ASN1_STRFLGS_RFC2253 | XN_FLAG_SEP_COMMA_PLUS | XN_FLAG_DN_REV | \
@@ -456,7 +408,7 @@
 #define X509_extract_key(x) X509_get_pubkey(x)
 
 // X509_get_pathlen returns path length constraint from the basic constraints
-// extension in |x509|. (See RFC5280, section 4.2.1.9.) It returns -1 if the
+// extension in |x509|. (See RFC 5280, section 4.2.1.9.) It returns -1 if the
 // constraint is not present, or if some extension in |x509| was invalid.
 //
 // Note that decoding an |X509| object will not check for invalid extensions. To
@@ -724,7 +676,6 @@
 // copying parts of it as a normal |d2i_X509| call would do.
 OPENSSL_EXPORT X509 *X509_parse_from_buffer(CRYPTO_BUFFER *buf);
 
-#ifndef OPENSSL_NO_FP_API
 OPENSSL_EXPORT X509 *d2i_X509_fp(FILE *fp, X509 **x509);
 OPENSSL_EXPORT int i2d_X509_fp(FILE *fp, X509 *x509);
 OPENSSL_EXPORT X509_CRL *d2i_X509_CRL_fp(FILE *fp, X509_CRL **crl);
@@ -758,7 +709,6 @@
 OPENSSL_EXPORT EVP_PKEY *d2i_PrivateKey_fp(FILE *fp, EVP_PKEY **a);
 OPENSSL_EXPORT int i2d_PUBKEY_fp(FILE *fp, EVP_PKEY *pkey);
 OPENSSL_EXPORT EVP_PKEY *d2i_PUBKEY_fp(FILE *fp, EVP_PKEY **a);
-#endif
 
 OPENSSL_EXPORT X509 *d2i_X509_bio(BIO *bp, X509 **x509);
 OPENSSL_EXPORT int i2d_X509_bio(BIO *bp, X509 *x509);
@@ -860,12 +810,30 @@
 OPENSSL_EXPORT int X509_NAME_get0_der(X509_NAME *nm, const unsigned char **pder,
                                       size_t *pderlen);
 
+// X509_cmp_time compares |s| against |*t|. On success, it returns a negative
+// number if |s| <= |*t| and a positive number if |s| > |*t|. On error, it
+// returns zero. If |t| is NULL, it uses the current time instead of |*t|.
+//
+// WARNING: Unlike most comparison functions, this function returns zero on
+// error, not equality.
 OPENSSL_EXPORT int X509_cmp_time(const ASN1_TIME *s, time_t *t);
+
+// X509_cmp_current_time behaves like |X509_cmp_time| but compares |s| against
+// the current time.
 OPENSSL_EXPORT int X509_cmp_current_time(const ASN1_TIME *s);
-OPENSSL_EXPORT ASN1_TIME *X509_time_adj(ASN1_TIME *s, long adj, time_t *t);
+
+// X509_time_adj calls |X509_time_adj_ex| with |offset_day| equal to zero.
+OPENSSL_EXPORT ASN1_TIME *X509_time_adj(ASN1_TIME *s, long offset_sec,
+                                        time_t *t);
+
+// X509_time_adj_ex behaves like |ASN1_TIME_adj|, but adds an offset to |*t|. If
+// |t| is NULL, it uses the current time instead of |*t|.
 OPENSSL_EXPORT ASN1_TIME *X509_time_adj_ex(ASN1_TIME *s, int offset_day,
                                            long offset_sec, time_t *t);
-OPENSSL_EXPORT ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long adj);
+
+// X509_gmtime_adj behaves like |X509_time_adj_ex| but adds |offset_sec| to the
+// current time.
+OPENSSL_EXPORT ASN1_TIME *X509_gmtime_adj(ASN1_TIME *s, long offset_sec);
 
 OPENSSL_EXPORT const char *X509_get_default_cert_area(void);
 OPENSSL_EXPORT const char *X509_get_default_cert_dir(void);
@@ -882,7 +850,15 @@
 
 DECLARE_ASN1_FUNCTIONS(X509_PUBKEY)
 
+// X509_PUBKEY_set serializes |pkey| into a newly-allocated |X509_PUBKEY|
+// structure. On success, it frees |*x|, sets |*x| to the new object, and
+// returns one. Otherwise, it returns zero.
 OPENSSL_EXPORT int X509_PUBKEY_set(X509_PUBKEY **x, EVP_PKEY *pkey);
+
+// X509_PUBKEY_get decodes the public key in |key| and returns an |EVP_PKEY| on
+// success, or NULL on error. The caller must release the result with
+// |EVP_PKEY_free| when done. The |EVP_PKEY| is cached in |key|, so callers must
+// not mutate the result.
 OPENSSL_EXPORT EVP_PKEY *X509_PUBKEY_get(X509_PUBKEY *key);
 
 DECLARE_ASN1_FUNCTIONS(X509_SIG)
@@ -904,10 +880,10 @@
 
 DECLARE_ASN1_FUNCTIONS(X509_NAME)
 
+// X509_NAME_set makes a copy of |name|. On success, it frees |*xn|, sets |*xn|
+// to the copy, and returns one. Otherwise, it returns zero.
 OPENSSL_EXPORT int X509_NAME_set(X509_NAME **xn, X509_NAME *name);
 
-DECLARE_ASN1_FUNCTIONS(X509_CINF)
-
 DECLARE_ASN1_FUNCTIONS(X509)
 DECLARE_ASN1_FUNCTIONS(X509_CERT_AUX)
 
@@ -1111,7 +1087,7 @@
 // a known NID.
 OPENSSL_EXPORT int X509_REQ_get_signature_nid(const X509_REQ *req);
 
-// i2d_re_X509_REQ_tbs serializes the CertificationRequestInfo (see RFC2986)
+// i2d_re_X509_REQ_tbs serializes the CertificationRequestInfo (see RFC 2986)
 // portion of |req|. If |outp| is NULL, nothing is written. Otherwise, if
 // |*outp| is not NULL, the result is written to |*outp|, which must have enough
 // space available, and |*outp| is advanced just past the output. If |outp| is
@@ -1138,7 +1114,7 @@
 
 // X509_REQ_extension_nid returns one if |nid| is a supported CSR attribute type
 // for carrying extensions and zero otherwise. The supported types are
-// |NID_ext_req| (pkcs-9-at-extensionRequest from RFC2985) and |NID_ms_ext_req|
+// |NID_ext_req| (pkcs-9-at-extensionRequest from RFC 2985) and |NID_ms_ext_req|
 // (a Microsoft szOID_CERT_EXTENSIONS variant).
 OPENSSL_EXPORT int X509_REQ_extension_nid(int nid);
 
@@ -1146,7 +1122,7 @@
 // returns a newly-allocated |STACK_OF(X509_EXTENSION)| containing the result.
 // It returns NULL on error, or if |req| did not request extensions.
 //
-// This function supports both pkcs-9-at-extensionRequest from RFC2985 and the
+// This function supports both pkcs-9-at-extensionRequest from RFC 2985 and the
 // Microsoft szOID_CERT_EXTENSIONS variant.
 OPENSSL_EXPORT STACK_OF(X509_EXTENSION) *X509_REQ_get_extensions(X509_REQ *req);
 
@@ -1366,7 +1342,6 @@
 
 OPENSSL_EXPORT int X509_CRL_cmp(const X509_CRL *a, const X509_CRL *b);
 OPENSSL_EXPORT int X509_CRL_match(const X509_CRL *a, const X509_CRL *b);
-#ifndef OPENSSL_NO_FP_API
 OPENSSL_EXPORT int X509_print_ex_fp(FILE *bp, X509 *x, unsigned long nmflag,
                                     unsigned long cflag);
 OPENSSL_EXPORT int X509_print_fp(FILE *bp, X509 *x);
@@ -1374,7 +1349,6 @@
 OPENSSL_EXPORT int X509_REQ_print_fp(FILE *bp, X509_REQ *req);
 OPENSSL_EXPORT int X509_NAME_print_ex_fp(FILE *fp, const X509_NAME *nm,
                                          int indent, unsigned long flags);
-#endif
 
 OPENSSL_EXPORT int X509_NAME_print(BIO *bp, const X509_NAME *name, int obase);
 OPENSSL_EXPORT int X509_NAME_print_ex(BIO *out, const X509_NAME *nm, int indent,
@@ -1913,12 +1887,16 @@
 OPENSSL_EXPORT int X509_TRUST_get_trust(const X509_TRUST *xp);
 
 
-typedef struct rsa_pss_params_st {
+struct rsa_pss_params_st {
   X509_ALGOR *hashAlgorithm;
   X509_ALGOR *maskGenAlgorithm;
   ASN1_INTEGER *saltLength;
   ASN1_INTEGER *trailerField;
-} RSA_PSS_PARAMS;
+  // OpenSSL caches the MGF hash on |RSA_PSS_PARAMS| in some cases. None of the
+  // cases apply to BoringSSL, so this is always NULL, but Node expects the
+  // field to be present.
+  X509_ALGOR *maskHash;
+} /* RSA_PSS_PARAMS */;
 
 DECLARE_ASN1_FUNCTIONS(RSA_PSS_PARAMS)
 
@@ -1958,10 +1936,6 @@
 BORINGSSL_MAKE_DELETER(X509_STORE_CTX, X509_STORE_CTX_free)
 BORINGSSL_MAKE_DELETER(X509_VERIFY_PARAM, X509_VERIFY_PARAM_free)
 
-using ScopedX509_STORE_CTX =
-    internal::StackAllocated<X509_STORE_CTX, void, X509_STORE_CTX_zero,
-                             X509_STORE_CTX_cleanup>;
-
 BSSL_NAMESPACE_END
 
 }  // extern C++
diff --git a/src/include/openssl/x509_vfy.h b/src/include/openssl/x509_vfy.h
index 9b99f4a..d8781af 100644
--- a/src/include/openssl/x509_vfy.h
+++ b/src/include/openssl/x509_vfy.h
@@ -99,39 +99,8 @@
 #define X509_LU_CRL 2
 #define X509_LU_PKEY 3
 
-typedef struct x509_object_st {
-  // one of the above types
-  int type;
-  union {
-    char *ptr;
-    X509 *x509;
-    X509_CRL *crl;
-    EVP_PKEY *pkey;
-  } data;
-} X509_OBJECT;
-
 DEFINE_STACK_OF(X509_LOOKUP)
 DEFINE_STACK_OF(X509_OBJECT)
-
-// This is a static that defines the function interface
-typedef struct x509_lookup_method_st {
-  const char *name;
-  int (*new_item)(X509_LOOKUP *ctx);
-  void (*free)(X509_LOOKUP *ctx);
-  int (*init)(X509_LOOKUP *ctx);
-  int (*shutdown)(X509_LOOKUP *ctx);
-  int (*ctrl)(X509_LOOKUP *ctx, int cmd, const char *argc, long argl,
-              char **ret);
-  int (*get_by_subject)(X509_LOOKUP *ctx, int type, X509_NAME *name,
-                        X509_OBJECT *ret);
-  int (*get_by_issuer_serial)(X509_LOOKUP *ctx, int type, X509_NAME *name,
-                              ASN1_INTEGER *serial, X509_OBJECT *ret);
-  int (*get_by_fingerprint)(X509_LOOKUP *ctx, int type, unsigned char *bytes,
-                            int len, X509_OBJECT *ret);
-  int (*get_by_alias)(X509_LOOKUP *ctx, int type, char *str, int len,
-                      X509_OBJECT *ret);
-} X509_LOOKUP_METHOD;
-
 DEFINE_STACK_OF(X509_VERIFY_PARAM)
 
 typedef int (*X509_STORE_CTX_verify_cb)(int, X509_STORE_CTX *);
@@ -153,103 +122,8 @@
     X509_STORE_CTX *ctx, X509_NAME *nm);
 typedef int (*X509_STORE_CTX_cleanup_fn)(X509_STORE_CTX *ctx);
 
-// This is used to hold everything.  It is used for all certificate
-// validation.  Once we have a certificate chain, the 'verify'
-// function is then called to actually check the cert chain.
-struct x509_store_st {
-  // The following is a cache of trusted certs
-  int cache;                    // if true, stash any hits
-  STACK_OF(X509_OBJECT) *objs;  // Cache of all objects
-  CRYPTO_MUTEX objs_lock;
-  STACK_OF(X509) *additional_untrusted;
-
-  // These are external lookup methods
-  STACK_OF(X509_LOOKUP) *get_cert_methods;
-
-  X509_VERIFY_PARAM *param;
-
-  // Callbacks for various operations
-  X509_STORE_CTX_verify_fn verify;          // called to verify a certificate
-  X509_STORE_CTX_verify_cb verify_cb;       // error callback
-  X509_STORE_CTX_get_issuer_fn get_issuer;  // get issuers cert from ctx
-  X509_STORE_CTX_check_issued_fn check_issued;  // check issued
-  X509_STORE_CTX_check_revocation_fn
-      check_revocation;                   // Check revocation status of chain
-  X509_STORE_CTX_get_crl_fn get_crl;      // retrieve CRL
-  X509_STORE_CTX_check_crl_fn check_crl;  // Check CRL validity
-  X509_STORE_CTX_cert_crl_fn cert_crl;    // Check certificate against CRL
-  X509_STORE_CTX_lookup_certs_fn lookup_certs;
-  X509_STORE_CTX_lookup_crls_fn lookup_crls;
-  X509_STORE_CTX_cleanup_fn cleanup;
-
-  CRYPTO_refcount_t references;
-} /* X509_STORE */;
-
 OPENSSL_EXPORT int X509_STORE_set_depth(X509_STORE *store, int depth);
 
-// This is the functions plus an instance of the local variables.
-struct x509_lookup_st {
-  int init;                    // have we been started
-  int skip;                    // don't use us.
-  X509_LOOKUP_METHOD *method;  // the functions
-  char *method_data;           // method data
-
-  X509_STORE *store_ctx;  // who owns us
-} /* X509_LOOKUP */;
-
-// This is a used when verifying cert chains.  Since the
-// gathering of the cert chain can take some time (and have to be
-// 'retried', this needs to be kept and passed around.
-struct x509_store_ctx_st  // X509_STORE_CTX
-{
-  X509_STORE *ctx;
-
-  // The following are set by the caller
-  X509 *cert;                 // The cert to check
-  STACK_OF(X509) *untrusted;  // chain of X509s - untrusted - passed in
-  STACK_OF(X509_CRL) *crls;   // set of CRLs passed in
-
-  X509_VERIFY_PARAM *param;
-  void *other_ctx;  // Other info for use with get_issuer()
-
-  // Callbacks for various operations
-  X509_STORE_CTX_verify_fn verify;          // called to verify a certificate
-  X509_STORE_CTX_verify_cb verify_cb;       // error callback
-  X509_STORE_CTX_get_issuer_fn get_issuer;  // get issuers cert from ctx
-  X509_STORE_CTX_check_issued_fn check_issued;  // check issued
-  X509_STORE_CTX_check_revocation_fn
-      check_revocation;                   // Check revocation status of chain
-  X509_STORE_CTX_get_crl_fn get_crl;      // retrieve CRL
-  X509_STORE_CTX_check_crl_fn check_crl;  // Check CRL validity
-  X509_STORE_CTX_cert_crl_fn cert_crl;    // Check certificate against CRL
-  X509_STORE_CTX_check_policy_fn check_policy;
-  X509_STORE_CTX_lookup_certs_fn lookup_certs;
-  X509_STORE_CTX_lookup_crls_fn lookup_crls;
-  X509_STORE_CTX_cleanup_fn cleanup;
-
-  // The following is built up
-  int valid;               // if 0, rebuild chain
-  int last_untrusted;      // index of last untrusted cert
-  STACK_OF(X509) *chain;   // chain of X509s - built up and trusted
-  X509_POLICY_TREE *tree;  // Valid policy tree
-
-  int explicit_policy;  // Require explicit policy value
-
-  // When something goes wrong, this is why
-  int error_depth;
-  int error;
-  X509 *current_cert;
-  X509 *current_issuer;   // cert currently being tested as valid issuer
-  X509_CRL *current_crl;  // current CRL
-
-  int current_crl_score;         // score of current CRL
-  unsigned int current_reasons;  // Reason mask
-
-  X509_STORE_CTX *parent;  // For CRL path validation: parent context
-
-  CRYPTO_EX_DATA ex_data;
-} /* X509_STORE_CTX */;
-
 OPENSSL_EXPORT void X509_STORE_CTX_set_depth(X509_STORE_CTX *ctx, int depth);
 
 #define X509_STORE_CTX_set_app_data(ctx, data) \
diff --git a/src/include/openssl/x509v3.h b/src/include/openssl/x509v3.h
index 7e65ab3..8e4a511 100644
--- a/src/include/openssl/x509v3.h
+++ b/src/include/openssl/x509v3.h
@@ -154,8 +154,6 @@
 #define X509V3_EXT_CTX_DEP 0x2
 #define X509V3_EXT_MULTILINE 0x4
 
-typedef BIT_STRING_BITNAME ENUMERATED_NAMES;
-
 struct BASIC_CONSTRAINTS_st {
   int ca;
   ASN1_INTEGER *pathlen;
@@ -485,12 +483,30 @@
     X509V3_EXT_METHOD *method, ASN1_BIT_STRING *bits,
     STACK_OF(CONF_VALUE) *extlist);
 
+// i2v_GENERAL_NAME serializes |gen| as a |CONF_VALUE|. If |ret| is non-NULL, it
+// appends the value to |ret| and returns |ret| on success or NULL on error. If
+// it returns NULL, the caller is still responsible for freeing |ret|. If |ret|
+// is NULL, it returns a newly-allocated |STACK_OF(CONF_VALUE)| containing the
+// result. |method| is ignored.
+//
+// Do not use this function. This is an internal implementation detail of the
+// human-readable print functions. If extracting a SAN list from a certificate,
+// look at |gen| directly.
 OPENSSL_EXPORT STACK_OF(CONF_VALUE) *i2v_GENERAL_NAME(
     X509V3_EXT_METHOD *method, GENERAL_NAME *gen, STACK_OF(CONF_VALUE) *ret);
 OPENSSL_EXPORT int GENERAL_NAME_print(BIO *out, GENERAL_NAME *gen);
 
 DECLARE_ASN1_FUNCTIONS(GENERAL_NAMES)
 
+// i2v_GENERAL_NAMES serializes |gen| as a list of |CONF_VALUE|s. If |ret| is
+// non-NULL, it appends the values to |ret| and returns |ret| on success or NULL
+// on error. If it returns NULL, the caller is still responsible for freeing
+// |ret|. If |ret| is NULL, it returns a newly-allocated |STACK_OF(CONF_VALUE)|
+// containing the results. |method| is ignored.
+//
+// Do not use this function. This is an internal implementation detail of the
+// human-readable print functions. If extracting a SAN list from a certificate,
+// look at |gen| directly.
 OPENSSL_EXPORT STACK_OF(CONF_VALUE) *i2v_GENERAL_NAMES(
     X509V3_EXT_METHOD *method, GENERAL_NAMES *gen,
     STACK_OF(CONF_VALUE) *extlist);
@@ -604,15 +620,35 @@
 OPENSSL_EXPORT void X509V3_set_ctx(X509V3_CTX *ctx, X509 *issuer, X509 *subject,
                                    X509_REQ *req, X509_CRL *crl, int flags);
 
+// X509V3_add_value appends a |CONF_VALUE| containing |name| and |value| to
+// |*extlist|. It returns one on success and zero on error. If |*extlist| is
+// NULL, it sets |*extlist| to a newly-allocated |STACK_OF(CONF_VALUE)|
+// containing the result. Either |name| or |value| may be NULL to omit the
+// field.
+//
+// On failure, if |*extlist| was NULL, |*extlist| will remain NULL when the
+// function returns.
 OPENSSL_EXPORT int X509V3_add_value(const char *name, const char *value,
                                     STACK_OF(CONF_VALUE) **extlist);
+
+// X509V3_add_value_uchar behaves like |X509V3_add_value| but takes an
+// |unsigned char| pointer.
 OPENSSL_EXPORT int X509V3_add_value_uchar(const char *name,
                                           const unsigned char *value,
                                           STACK_OF(CONF_VALUE) **extlist);
+
+// X509V3_add_value_bool behaves like |X509V3_add_value| but stores the value
+// "TRUE" if |asn1_bool| is non-zero and "FALSE" otherwise.
 OPENSSL_EXPORT int X509V3_add_value_bool(const char *name, int asn1_bool,
                                          STACK_OF(CONF_VALUE) **extlist);
-OPENSSL_EXPORT int X509V3_add_value_int(const char *name, ASN1_INTEGER *aint,
+
+// X509V3_add_value_bool behaves like |X509V3_add_value| but stores a string
+// representation of |aint|. Note this string representation may be decimal or
+// hexadecimal, depending on the size of |aint|.
+OPENSSL_EXPORT int X509V3_add_value_int(const char *name,
+                                        const ASN1_INTEGER *aint,
                                         STACK_OF(CONF_VALUE) **extlist);
+
 OPENSSL_EXPORT char *i2s_ASN1_INTEGER(X509V3_EXT_METHOD *meth,
                                       const ASN1_INTEGER *aint);
 OPENSSL_EXPORT ASN1_INTEGER *s2i_ASN1_INTEGER(X509V3_EXT_METHOD *meth,
@@ -659,7 +695,7 @@
 // extension, or -1 if not found. If |out_idx| is non-NULL, duplicate extensions
 // are not treated as an error. Callers, however, should not rely on this
 // behavior as it may be removed in the future. Duplicate extensions are
-// forbidden in RFC5280.
+// forbidden in RFC 5280.
 //
 // WARNING: This function is difficult to use correctly. Callers should pass a
 // non-NULL |out_critical| and check both the return value and |*out_critical|
@@ -789,7 +825,7 @@
 OPENSSL_EXPORT uint32_t X509_get_extended_key_usage(X509 *x);
 
 // X509_get0_subject_key_id returns |x509|'s subject key identifier, if present.
-// (See RFC5280, section 4.2.1.2.) It returns NULL if the extension is not
+// (See RFC 5280, section 4.2.1.2.) It returns NULL if the extension is not
 // present or if some extension in |x509| was invalid.
 //
 // Note that decoding an |X509| object will not check for invalid extensions. To
@@ -798,7 +834,7 @@
 OPENSSL_EXPORT const ASN1_OCTET_STRING *X509_get0_subject_key_id(X509 *x509);
 
 // X509_get0_authority_key_id returns keyIdentifier of |x509|'s authority key
-// identifier, if the extension and field are present. (See RFC5280,
+// identifier, if the extension and field are present. (See RFC 5280,
 // section 4.2.1.1.) It returns NULL if the extension is not present, if it is
 // present but lacks a keyIdentifier field, or if some extension in |x509| was
 // invalid.
@@ -810,7 +846,7 @@
 
 // X509_get0_authority_issuer returns the authorityCertIssuer of |x509|'s
 // authority key identifier, if the extension and field are present. (See
-// RFC5280, section 4.2.1.1.) It returns NULL if the extension is not present,
+// RFC 5280, section 4.2.1.1.) It returns NULL if the extension is not present,
 // if it is present but lacks a authorityCertIssuer field, or if some extension
 // in |x509| was invalid.
 //
@@ -821,7 +857,7 @@
 
 // X509_get0_authority_serial returns the authorityCertSerialNumber of |x509|'s
 // authority key identifier, if the extension and field are present. (See
-// RFC5280, section 4.2.1.1.) It returns NULL if the extension is not present,
+// RFC 5280, section 4.2.1.1.) It returns NULL if the extension is not present,
 // if it is present but lacks a authorityCertSerialNumber field, or if some
 // extension in |x509| was invalid.
 //
@@ -902,8 +938,11 @@
 BORINGSSL_MAKE_DELETER(ACCESS_DESCRIPTION, ACCESS_DESCRIPTION_free)
 BORINGSSL_MAKE_DELETER(AUTHORITY_KEYID, AUTHORITY_KEYID_free)
 BORINGSSL_MAKE_DELETER(BASIC_CONSTRAINTS, BASIC_CONSTRAINTS_free)
+// TODO(davidben): Move this to conf.h and rename to CONF_VALUE_free.
+BORINGSSL_MAKE_DELETER(CONF_VALUE, X509V3_conf_free)
 BORINGSSL_MAKE_DELETER(DIST_POINT, DIST_POINT_free)
 BORINGSSL_MAKE_DELETER(GENERAL_NAME, GENERAL_NAME_free)
+BORINGSSL_MAKE_DELETER(GENERAL_SUBTREE, GENERAL_SUBTREE_free)
 BORINGSSL_MAKE_DELETER(NAME_CONSTRAINTS, NAME_CONSTRAINTS_free)
 BORINGSSL_MAKE_DELETER(POLICY_MAPPING, POLICY_MAPPING_free)
 BORINGSSL_MAKE_DELETER(POLICYINFO, POLICYINFO_free)
@@ -976,5 +1015,6 @@
 #define X509V3_R_UNSUPPORTED_OPTION 160
 #define X509V3_R_UNSUPPORTED_TYPE 161
 #define X509V3_R_USER_TOO_LONG 162
+#define X509V3_R_INVALID_VALUE 163
 
 #endif
diff --git a/src/ssl/encrypted_client_hello.cc b/src/ssl/encrypted_client_hello.cc
index b70f66c..64fee3d 100644
--- a/src/ssl/encrypted_client_hello.cc
+++ b/src/ssl/encrypted_client_hello.cc
@@ -31,12 +31,6 @@
 #include "internal.h"
 
 
-#if defined(OPENSSL_MSAN)
-#define NO_SANITIZE_MEMORY __attribute__((no_sanitize("memory")))
-#else
-#define NO_SANITIZE_MEMORY
-#endif
-
 BSSL_NAMESPACE_BEGIN
 
 // ECH reuses the extension code point for the version number.
@@ -84,159 +78,17 @@
   return true;
 }
 
-bool ssl_decode_client_hello_inner(
-    SSL *ssl, uint8_t *out_alert, Array<uint8_t> *out_client_hello_inner,
-    Span<const uint8_t> encoded_client_hello_inner,
-    const SSL_CLIENT_HELLO *client_hello_outer) {
-  SSL_CLIENT_HELLO client_hello_inner;
-  if (!ssl_client_hello_init(ssl, &client_hello_inner,
-                             encoded_client_hello_inner)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return false;
-  }
-  // TLS 1.3 ClientHellos must have extensions, and EncodedClientHelloInners use
-  // ClientHelloOuter's session_id.
-  if (client_hello_inner.extensions_len == 0 ||
-      client_hello_inner.session_id_len != 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return false;
-  }
-  client_hello_inner.session_id = client_hello_outer->session_id;
-  client_hello_inner.session_id_len = client_hello_outer->session_id_len;
-
-  // Begin serializing a message containing the ClientHelloInner in |cbb|.
-  ScopedCBB cbb;
-  CBB body, extensions;
-  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CLIENT_HELLO) ||
-      !ssl_client_hello_write_without_extensions(&client_hello_inner, &body) ||
-      !CBB_add_u16_length_prefixed(&body, &extensions)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return false;
-  }
-
-  // Sort the extensions in ClientHelloOuter, so ech_outer_extensions may be
-  // processed in O(n*log(n)) time, rather than O(n^2).
-  struct Extension {
-    uint16_t extension = 0;
-    Span<const uint8_t> body;
-    bool copied = false;
-  };
-
-  // MSan's libc interceptors do not handle |bsearch|. See b/182583130.
-  auto compare_extension = [](const void *a, const void *b)
-                               NO_SANITIZE_MEMORY -> int {
-    const Extension *extension_a = reinterpret_cast<const Extension *>(a);
-    const Extension *extension_b = reinterpret_cast<const Extension *>(b);
-    if (extension_a->extension < extension_b->extension) {
-      return -1;
-    } else if (extension_a->extension > extension_b->extension) {
-      return 1;
-    }
-    return 0;
-  };
-  GrowableArray<Extension> sorted_extensions;
-  CBS unsorted_extensions(MakeConstSpan(client_hello_outer->extensions,
-                                        client_hello_outer->extensions_len));
-  while (CBS_len(&unsorted_extensions) > 0) {
-    Extension extension;
-    CBS extension_body;
-    if (!CBS_get_u16(&unsorted_extensions, &extension.extension) ||
-        !CBS_get_u16_length_prefixed(&unsorted_extensions, &extension_body)) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return false;
-    }
-    extension.body = extension_body;
-    if (!sorted_extensions.Push(extension)) {
-      return false;
-    }
-  }
-  qsort(sorted_extensions.data(), sorted_extensions.size(), sizeof(Extension),
-        compare_extension);
-
-  // Copy extensions from |client_hello_inner|, expanding ech_outer_extensions.
-  CBS inner_extensions(MakeConstSpan(client_hello_inner.extensions,
-                                     client_hello_inner.extensions_len));
-  while (CBS_len(&inner_extensions) > 0) {
-    uint16_t extension_id;
-    CBS extension_body;
-    if (!CBS_get_u16(&inner_extensions, &extension_id) ||
-        !CBS_get_u16_length_prefixed(&inner_extensions, &extension_body)) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-      return false;
-    }
-    if (extension_id != TLSEXT_TYPE_ech_outer_extensions) {
-      if (!CBB_add_u16(&extensions, extension_id) ||
-          !CBB_add_u16(&extensions, CBS_len(&extension_body)) ||
-          !CBB_add_bytes(&extensions, CBS_data(&extension_body),
-                         CBS_len(&extension_body))) {
-        OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-        return false;
-      }
-      continue;
-    }
-
-    // Replace ech_outer_extensions with the corresponding outer extensions.
-    CBS outer_extensions;
-    if (!CBS_get_u8_length_prefixed(&extension_body, &outer_extensions) ||
-        CBS_len(&extension_body) != 0) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-      return false;
-    }
-    while (CBS_len(&outer_extensions) > 0) {
-      uint16_t extension_needed;
-      if (!CBS_get_u16(&outer_extensions, &extension_needed)) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-        return false;
-      }
-      if (extension_needed == TLSEXT_TYPE_encrypted_client_hello) {
-        *out_alert = SSL_AD_ILLEGAL_PARAMETER;
-        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-        return false;
-      }
-      // Find the referenced extension.
-      Extension key;
-      key.extension = extension_needed;
-      Extension *result = reinterpret_cast<Extension *>(
-          bsearch(&key, sorted_extensions.data(), sorted_extensions.size(),
-                  sizeof(Extension), compare_extension));
-      if (result == nullptr) {
-        *out_alert = SSL_AD_ILLEGAL_PARAMETER;
-        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-        return false;
-      }
-
-      // Extensions may be referenced at most once, to bound the result size.
-      if (result->copied) {
-        *out_alert = SSL_AD_ILLEGAL_PARAMETER;
-        OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_EXTENSION);
-        return false;
-      }
-      result->copied = true;
-
-      if (!CBB_add_u16(&extensions, extension_needed) ||
-          !CBB_add_u16(&extensions, result->body.size()) ||
-          !CBB_add_bytes(&extensions, result->body.data(),
-                         result->body.size())) {
-        OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-        return false;
-      }
-    }
-  }
-  if (!CBB_flush(&body)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return false;
-  }
-
-  // See https://github.com/tlswg/draft-ietf-tls-esni/pull/411
+static bool is_valid_client_hello_inner(SSL *ssl, uint8_t *out_alert,
+                                        Span<const uint8_t> body) {
+  // See draft-ietf-tls-esni-13, section 7.1.
+  SSL_CLIENT_HELLO client_hello;
   CBS extension;
-  if (!ssl_client_hello_init(ssl, &client_hello_inner,
-                             MakeConstSpan(CBB_data(&body), CBB_len(&body))) ||
-      !ssl_client_hello_get_extension(&client_hello_inner, &extension,
-                                      TLSEXT_TYPE_ech_is_inner) ||
-      CBS_len(&extension) != 0 ||
-      ssl_client_hello_get_extension(&client_hello_inner, &extension,
-                                     TLSEXT_TYPE_encrypted_client_hello) ||
-      !ssl_client_hello_get_extension(&client_hello_inner, &extension,
+  if (!ssl_client_hello_init(ssl, &client_hello, body) ||
+      !ssl_client_hello_get_extension(&client_hello, &extension,
+                                      TLSEXT_TYPE_encrypted_client_hello) ||
+      CBS_len(&extension) != 1 ||  //
+      CBS_data(&extension)[0] != ECH_CLIENT_INNER ||
+      !ssl_client_hello_get_extension(&client_hello, &extension,
                                       TLSEXT_TYPE_supported_versions)) {
     *out_alert = SSL_AD_ILLEGAL_PARAMETER;
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_CLIENT_HELLO_INNER);
@@ -267,6 +119,131 @@
       return false;
     }
   }
+  return true;
+}
+
+bool ssl_decode_client_hello_inner(
+    SSL *ssl, uint8_t *out_alert, Array<uint8_t> *out_client_hello_inner,
+    Span<const uint8_t> encoded_client_hello_inner,
+    const SSL_CLIENT_HELLO *client_hello_outer) {
+  SSL_CLIENT_HELLO client_hello_inner;
+  CBS cbs = encoded_client_hello_inner;
+  if (!ssl_parse_client_hello_with_trailing_data(ssl, &cbs,
+                                                 &client_hello_inner)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    return false;
+  }
+  // The remaining data is padding.
+  uint8_t padding;
+  while (CBS_get_u8(&cbs, &padding)) {
+    if (padding != 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+      return false;
+    }
+  }
+
+  // TLS 1.3 ClientHellos must have extensions, and EncodedClientHelloInners use
+  // ClientHelloOuter's session_id.
+  if (client_hello_inner.extensions_len == 0 ||
+      client_hello_inner.session_id_len != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    return false;
+  }
+  client_hello_inner.session_id = client_hello_outer->session_id;
+  client_hello_inner.session_id_len = client_hello_outer->session_id_len;
+
+  // Begin serializing a message containing the ClientHelloInner in |cbb|.
+  ScopedCBB cbb;
+  CBB body, extensions_cbb;
+  if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CLIENT_HELLO) ||
+      !ssl_client_hello_write_without_extensions(&client_hello_inner, &body) ||
+      !CBB_add_u16_length_prefixed(&body, &extensions_cbb)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return false;
+  }
+
+  auto inner_extensions = MakeConstSpan(client_hello_inner.extensions,
+                                        client_hello_inner.extensions_len);
+  CBS ext_list_wrapper;
+  if (!ssl_client_hello_get_extension(&client_hello_inner, &ext_list_wrapper,
+                                      TLSEXT_TYPE_ech_outer_extensions)) {
+    // No ech_outer_extensions. Copy everything.
+    if (!CBB_add_bytes(&extensions_cbb, inner_extensions.data(),
+                       inner_extensions.size())) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return false;
+    }
+  } else {
+    const size_t offset = CBS_data(&ext_list_wrapper) - inner_extensions.data();
+    auto inner_extensions_before =
+        inner_extensions.subspan(0, offset - 4 /* extension header */);
+    auto inner_extensions_after =
+        inner_extensions.subspan(offset + CBS_len(&ext_list_wrapper));
+    if (!CBB_add_bytes(&extensions_cbb, inner_extensions_before.data(),
+                       inner_extensions_before.size())) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return false;
+    }
+
+    // Expand ech_outer_extensions. See draft-ietf-tls-esni-13, Appendix B.
+    CBS ext_list;
+    if (!CBS_get_u8_length_prefixed(&ext_list_wrapper, &ext_list) ||
+        CBS_len(&ext_list) == 0 || CBS_len(&ext_list_wrapper) != 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      return false;
+    }
+    CBS outer_extensions;
+    CBS_init(&outer_extensions, client_hello_outer->extensions,
+             client_hello_outer->extensions_len);
+    while (CBS_len(&ext_list) != 0) {
+      // Find the next extension to copy.
+      uint16_t want;
+      if (!CBS_get_u16(&ext_list, &want)) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+        return false;
+      }
+      // Seek to |want| in |outer_extensions|. |ext_list| is required to match
+      // ClientHelloOuter in order.
+      uint16_t found;
+      CBS ext_body;
+      do {
+        if (CBS_len(&outer_extensions) == 0) {
+          *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+          OPENSSL_PUT_ERROR(SSL, SSL_R_OUTER_EXTENSION_NOT_FOUND);
+          return false;
+        }
+        if (!CBS_get_u16(&outer_extensions, &found) ||
+            !CBS_get_u16_length_prefixed(&outer_extensions, &ext_body)) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+          return false;
+        }
+      } while (found != want);
+      // Copy the extension.
+      if (!CBB_add_u16(&extensions_cbb, found) ||
+          !CBB_add_u16(&extensions_cbb, CBS_len(&ext_body)) ||
+          !CBB_add_bytes(&extensions_cbb, CBS_data(&ext_body),
+                         CBS_len(&ext_body))) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+        return false;
+      }
+    }
+
+    if (!CBB_add_bytes(&extensions_cbb, inner_extensions_after.data(),
+                       inner_extensions_after.size())) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return false;
+    }
+  }
+  if (!CBB_flush(&body)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return false;
+  }
+
+  if (!is_valid_client_hello_inner(
+          ssl, out_alert, MakeConstSpan(CBB_data(&body), CBB_len(&body)))) {
+    return false;
+  }
 
   if (!ssl->method->finish_message(ssl, cbb.get(), out_client_hello_inner)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
@@ -275,56 +252,31 @@
   return true;
 }
 
-bool ssl_client_hello_decrypt(
-    EVP_HPKE_CTX *hpke_ctx, Array<uint8_t> *out_encoded_client_hello_inner,
-    bool *out_is_decrypt_error, const SSL_CLIENT_HELLO *client_hello_outer,
-    uint16_t kdf_id, uint16_t aead_id, const uint8_t config_id,
-    Span<const uint8_t> enc, Span<const uint8_t> payload) {
+bool ssl_client_hello_decrypt(EVP_HPKE_CTX *hpke_ctx, Array<uint8_t> *out,
+                              bool *out_is_decrypt_error,
+                              const SSL_CLIENT_HELLO *client_hello_outer,
+                              Span<const uint8_t> payload) {
   *out_is_decrypt_error = false;
 
-  // Compute the ClientHello portion of the ClientHelloOuterAAD value. See
-  // draft-ietf-tls-esni-10, section 5.2.
-  ScopedCBB aad;
-  CBB enc_cbb, outer_hello_cbb, extensions_cbb;
-  if (!CBB_init(aad.get(), 256) ||
-      !CBB_add_u16(aad.get(), kdf_id) ||
-      !CBB_add_u16(aad.get(), aead_id) ||
-      !CBB_add_u8(aad.get(), config_id) ||
-      !CBB_add_u16_length_prefixed(aad.get(), &enc_cbb) ||
-      !CBB_add_bytes(&enc_cbb, enc.data(), enc.size()) ||
-      !CBB_add_u24_length_prefixed(aad.get(), &outer_hello_cbb) ||
-      !ssl_client_hello_write_without_extensions(client_hello_outer,
-                                                 &outer_hello_cbb) ||
-      !CBB_add_u16_length_prefixed(&outer_hello_cbb, &extensions_cbb)) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+  // The ClientHelloOuterAAD is |client_hello_outer| with |payload| (which must
+  // point within |client_hello_outer->extensions|) replaced with zeros. See
+  // draft-ietf-tls-esni-13, section 5.2.
+  Array<uint8_t> aad;
+  if (!aad.CopyFrom(MakeConstSpan(client_hello_outer->client_hello,
+                                  client_hello_outer->client_hello_len))) {
     return false;
   }
 
-  CBS extensions(MakeConstSpan(client_hello_outer->extensions,
-                               client_hello_outer->extensions_len));
-  while (CBS_len(&extensions) > 0) {
-    uint16_t extension_id;
-    CBS extension_body;
-    if (!CBS_get_u16(&extensions, &extension_id) ||
-        !CBS_get_u16_length_prefixed(&extensions, &extension_body)) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-      return false;
-    }
-    if (extension_id == TLSEXT_TYPE_encrypted_client_hello) {
-      continue;
-    }
-    if (!CBB_add_u16(&extensions_cbb, extension_id) ||
-        !CBB_add_u16(&extensions_cbb, CBS_len(&extension_body)) ||
-        !CBB_add_bytes(&extensions_cbb, CBS_data(&extension_body),
-                       CBS_len(&extension_body))) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      return false;
-    }
-  }
-  if (!CBB_flush(aad.get())) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return false;
-  }
+  // We assert with |uintptr_t| because the comparison would be UB if they
+  // didn't alias.
+  assert(reinterpret_cast<uintptr_t>(client_hello_outer->extensions) <=
+         reinterpret_cast<uintptr_t>(payload.data()));
+  assert(reinterpret_cast<uintptr_t>(client_hello_outer->extensions +
+                                     client_hello_outer->extensions_len) >=
+         reinterpret_cast<uintptr_t>(payload.data() + payload.size()));
+  Span<uint8_t> payload_aad = MakeSpan(aad).subspan(
+      payload.data() - client_hello_outer->client_hello, payload.size());
+  OPENSSL_memset(payload_aad.data(), 0, payload_aad.size());
 
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
   // In fuzzer mode, disable encryption to improve coverage. We reserve a short
@@ -336,124 +288,75 @@
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
     return false;
   }
-  if (!out_encoded_client_hello_inner->CopyFrom(payload)) {
+  if (!out->CopyFrom(payload)) {
     return false;
   }
 #else
-  // Attempt to decrypt into |out_encoded_client_hello_inner|.
-  if (!out_encoded_client_hello_inner->Init(payload.size())) {
+  // Attempt to decrypt into |out|.
+  if (!out->Init(payload.size())) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     return false;
   }
-  size_t encoded_client_hello_inner_len;
-  if (!EVP_HPKE_CTX_open(hpke_ctx, out_encoded_client_hello_inner->data(),
-                         &encoded_client_hello_inner_len,
-                         out_encoded_client_hello_inner->size(), payload.data(),
-                         payload.size(), CBB_data(aad.get()),
-                         CBB_len(aad.get()))) {
+  size_t len;
+  if (!EVP_HPKE_CTX_open(hpke_ctx, out->data(), &len, out->size(),
+                         payload.data(), payload.size(), aad.data(),
+                         aad.size())) {
     *out_is_decrypt_error = true;
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
     return false;
   }
-  out_encoded_client_hello_inner->Shrink(encoded_client_hello_inner_len);
+  out->Shrink(len);
 #endif
   return true;
 }
 
-static bool parse_ipv4_number(Span<const uint8_t> in, uint32_t *out) {
-  // See https://url.spec.whatwg.org/#ipv4-number-parser.
-  uint32_t base = 10;
-  if (in.size() >= 2 && in[0] == '0' && (in[1] == 'x' || in[1] == 'X')) {
-    in = in.subspan(2);
-    base = 16;
-  } else if (in.size() >= 1 && in[0] == '0') {
-    in = in.subspan(1);
-    base = 8;
+static bool is_hex_component(Span<const uint8_t> in) {
+  if (in.size() < 2 || in[0] != '0' || (in[1] != 'x' && in[1] != 'X')) {
+    return false;
   }
-  *out = 0;
-  for (uint8_t c : in) {
-    uint32_t d;
-    if ('0' <= c && c <= '9') {
-      d = c - '0';
-    } else if ('a' <= c && c <= 'f') {
-      d = c - 'a' + 10;
-    } else if ('A' <= c && c <= 'F') {
-      d = c - 'A' + 10;
-    } else {
+  for (uint8_t b : in.subspan(2)) {
+    if (!('0' <= b && b <= '9') && !('a' <= b && b <= 'f') &&
+        !('A' <= b && b <= 'F')) {
       return false;
     }
-    if (d >= base ||
-        *out > UINT32_MAX / base) {
-      return false;
-    }
-    *out *= base;
-    if (*out > UINT32_MAX - d) {
-      return false;
-    }
-    *out += d;
   }
   return true;
 }
 
-static bool is_ipv4_address(Span<const uint8_t> in) {
-  // See https://url.spec.whatwg.org/#concept-ipv4-parser
-  uint32_t numbers[4];
-  size_t num_numbers = 0;
-  while (!in.empty()) {
-    if (num_numbers == 4) {
-      // Too many components.
-      return false;
-    }
-    // Find the next dot-separated component.
-    auto dot = std::find(in.begin(), in.end(), '.');
-    if (dot == in.begin()) {
-      // Empty components are not allowed.
-      return false;
-    }
-    Span<const uint8_t> component;
-    if (dot == in.end()) {
-      component = in;
-      in = Span<const uint8_t>();
-    } else {
-      component = in.subspan(0, dot - in.begin());
-      in = in.subspan(dot - in.begin() + 1);  // Skip the dot.
-    }
-    if (!parse_ipv4_number(component, &numbers[num_numbers])) {
-      return false;
-    }
-    num_numbers++;
-  }
-  if (num_numbers == 0) {
+static bool is_decimal_component(Span<const uint8_t> in) {
+  if (in.empty()) {
     return false;
   }
-  for (size_t i = 0; i < num_numbers - 1; i++) {
-    if (numbers[i] > 255) {
+  for (uint8_t b : in) {
+    if (!('0' <= b && b <= '9')) {
       return false;
     }
   }
-  return num_numbers == 1 ||
-         numbers[num_numbers - 1] < 1u << (8 * (5 - num_numbers));
+  return true;
 }
 
 bool ssl_is_valid_ech_public_name(Span<const uint8_t> public_name) {
-  // See draft-ietf-tls-esni-11, Section 4 and RFC5890, Section 2.3.1. The
+  // See draft-ietf-tls-esni-13, Section 4 and RFC 5890, Section 2.3.1. The
   // public name must be a dot-separated sequence of LDH labels and not begin or
   // end with a dot.
-  auto copy = public_name;
-  if (copy.empty()) {
+  auto remaining = public_name;
+  if (remaining.empty()) {
     return false;
   }
-  while (!copy.empty()) {
+  Span<const uint8_t> last;
+  while (!remaining.empty()) {
     // Find the next dot-separated component.
-    auto dot = std::find(copy.begin(), copy.end(), '.');
+    auto dot = std::find(remaining.begin(), remaining.end(), '.');
     Span<const uint8_t> component;
-    if (dot == copy.end()) {
-      component = copy;
-      copy = Span<const uint8_t>();
+    if (dot == remaining.end()) {
+      component = remaining;
+      last = component;
+      remaining = Span<const uint8_t>();
     } else {
-      component = copy.subspan(0, dot - copy.begin());
-      copy = copy.subspan(dot - copy.begin() + 1);  // Skip the dot.
-      if (copy.empty()) {
+      component = remaining.subspan(0, dot - remaining.begin());
+      // Skip the dot.
+      remaining = remaining.subspan(dot - remaining.begin() + 1);
+      if (remaining.empty()) {
         // Trailing dots are not allowed.
         return false;
       }
@@ -472,7 +375,15 @@
     }
   }
 
-  return !is_ipv4_address(public_name);
+  // The WHATWG URL parser additionally does not allow any DNS names that end in
+  // a numeric component. See:
+  // https://url.spec.whatwg.org/#concept-host-parser
+  // https://url.spec.whatwg.org/#ends-in-a-number-checker
+  //
+  // The WHATWG parser is formulated in terms of parsing decimal, octal, and
+  // hex, along with a separate ASCII digits check. The ASCII digits check
+  // subsumes the decimal and octal check, so we only need to check two cases.
+  return !is_hex_component(last) && !is_decimal_component(last);
 }
 
 static bool parse_ech_config(CBS *cbs, ECHConfig *out, bool *out_supported,
@@ -508,8 +419,8 @@
       CBS_len(&public_key) == 0 ||
       !CBS_get_u16_length_prefixed(&contents, &cipher_suites) ||
       CBS_len(&cipher_suites) == 0 || CBS_len(&cipher_suites) % 4 != 0 ||
-      !CBS_get_u16(&contents, &out->maximum_name_length) ||
-      !CBS_get_u16_length_prefixed(&contents, &public_name) ||
+      !CBS_get_u8(&contents, &out->maximum_name_length) ||
+      !CBS_get_u8_length_prefixed(&contents, &public_name) ||
       CBS_len(&public_name) == 0 ||
       !CBS_get_u16_length_prefixed(&contents, &extensions) ||
       CBS_len(&contents) != 0) {
@@ -773,15 +684,6 @@
 #endif
 }
 
-static size_t compute_extension_length(const EVP_HPKE_AEAD *aead,
-                                       size_t enc_len, size_t in_len) {
-  size_t ret = 4;      // HpkeSymmetricCipherSuite cipher_suite
-  ret++;               // uint8 config_id
-  ret += 2 + enc_len;  // opaque enc<1..2^16-1>
-  ret += 2 + in_len + aead_overhead(aead);  // opaque payload<1..2^16-1>
-  return ret;
-}
-
 // random_size returns a random value between |min| and |max|, inclusive.
 static size_t random_size(size_t min, size_t max) {
   assert(min < max);
@@ -814,38 +716,32 @@
   //   2+32+1+2   version, random, legacy_session_id, legacy_compression_methods
   //   2+4*2      cipher_suites (three TLS 1.3 ciphers, GREASE)
   //   2          extensions prefix
-  //   4          ech_is_inner
+  //   5          inner encrypted_client_hello
   //   4+1+2*2    supported_versions (TLS 1.3, GREASE)
   //   4+1+10*2   outer_extensions (key_share, sigalgs, sct, alpn,
   //              supported_groups, status_request, psk_key_exchange_modes,
   //              compress_certificate, GREASE x2)
   //
   // The server_name extension has an overhead of 9 bytes. For now, arbitrarily
-  // estimate maximum_name_length to be between 32 and 100 bytes.
-  //
-  // TODO(https://crbug.com/boringssl/275): If the padding scheme changes to
-  // also round the entire payload, adjust this to match. See
-  // https://github.com/tlswg/draft-ietf-tls-esni/issues/433
-  const size_t overhead = aead_overhead(aead);
-  const size_t in_len = random_size(128, 196);
-  const size_t extension_len =
-      compute_extension_length(aead, sizeof(enc), in_len);
+  // estimate maximum_name_length to be between 32 and 100 bytes. Then round up
+  // to a multiple of 32, to match draft-ietf-tls-esni-13, section 6.1.3.
+  const size_t payload_len =
+      32 * random_size(128 / 32, 224 / 32) + aead_overhead(aead);
   bssl::ScopedCBB cbb;
   CBB enc_cbb, payload_cbb;
   uint8_t *payload;
-  if (!CBB_init(cbb.get(), extension_len) ||
+  if (!CBB_init(cbb.get(), 256) ||
       !CBB_add_u16(cbb.get(), kdf_id) ||
       !CBB_add_u16(cbb.get(), EVP_HPKE_AEAD_id(aead)) ||
       !CBB_add_u8(cbb.get(), config_id) ||
       !CBB_add_u16_length_prefixed(cbb.get(), &enc_cbb) ||
       !CBB_add_bytes(&enc_cbb, enc, sizeof(enc)) ||
       !CBB_add_u16_length_prefixed(cbb.get(), &payload_cbb) ||
-      !CBB_add_space(&payload_cbb, &payload, in_len + overhead) ||
-      !RAND_bytes(payload, in_len + overhead) ||
-      !CBBFinishArray(cbb.get(), &hs->ech_client_bytes)) {
+      !CBB_add_space(&payload_cbb, &payload, payload_len) ||
+      !RAND_bytes(payload, payload_len) ||
+      !CBBFinishArray(cbb.get(), &hs->ech_client_outer)) {
     return false;
   }
-  assert(hs->ech_client_bytes.size() == extension_len);
   return true;
 }
 
@@ -856,22 +752,22 @@
   }
 
   // Construct ClientHelloInner and EncodedClientHelloInner. See
-  // draft-ietf-tls-esni-10, sections 5.1 and 6.1.
-  bssl::ScopedCBB cbb, encoded;
+  // draft-ietf-tls-esni-13, sections 5.1 and 6.1.
+  ScopedCBB cbb, encoded_cbb;
   CBB body;
   bool needs_psk_binder;
-  bssl::Array<uint8_t> hello_inner;
+  Array<uint8_t> hello_inner;
   if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CLIENT_HELLO) ||
-      !CBB_init(encoded.get(), 256) ||
+      !CBB_init(encoded_cbb.get(), 256) ||
       !ssl_write_client_hello_without_extensions(hs, &body,
                                                  ssl_client_hello_inner,
                                                  /*empty_session_id=*/false) ||
-      !ssl_write_client_hello_without_extensions(hs, encoded.get(),
+      !ssl_write_client_hello_without_extensions(hs, encoded_cbb.get(),
                                                  ssl_client_hello_inner,
                                                  /*empty_session_id=*/true) ||
-      !ssl_add_clienthello_tlsext(hs, &body, encoded.get(), &needs_psk_binder,
-                                  ssl_client_hello_inner, CBB_len(&body),
-                                  /*omit_ech_len=*/0) ||
+      !ssl_add_clienthello_tlsext(hs, &body, encoded_cbb.get(),
+                                  &needs_psk_binder, ssl_client_hello_inner,
+                                  CBB_len(&body)) ||
       !ssl->method->finish_message(ssl, cbb.get(), &hello_inner)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
@@ -884,13 +780,12 @@
       return false;
     }
     // Also update the EncodedClientHelloInner.
-    if (CBB_len(encoded.get()) < binder_len) {
-      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-      return false;
-    }
-    OPENSSL_memcpy(const_cast<uint8_t *>(CBB_data(encoded.get())) +
-                       CBB_len(encoded.get()) - binder_len,
-                   hello_inner.data() + hello_inner.size() - binder_len,
+    auto encoded_binder =
+        MakeSpan(const_cast<uint8_t *>(CBB_data(encoded_cbb.get())),
+                 CBB_len(encoded_cbb.get()))
+            .last(binder_len);
+    auto hello_inner_binder = MakeConstSpan(hello_inner).last(binder_len);
+    OPENSSL_memcpy(encoded_binder.data(), hello_inner_binder.data(),
                    binder_len);
   }
 
@@ -898,74 +793,82 @@
     return false;
   }
 
-  // Construct ClientHelloOuterAAD. See draft-ietf-tls-esni-10, section 5.2.
-  // TODO(https://crbug.com/boringssl/275): This ends up constructing the
-  // ClientHelloOuter twice. Revisit this in the next draft, which uses a more
-  // forgiving construction.
-  const EVP_HPKE_KDF *kdf = EVP_HPKE_CTX_kdf(hs->ech_hpke_ctx.get());
-  const EVP_HPKE_AEAD *aead = EVP_HPKE_CTX_aead(hs->ech_hpke_ctx.get());
-  const size_t extension_len =
-      compute_extension_length(aead, enc.size(), CBB_len(encoded.get()));
-  bssl::ScopedCBB aad;
-  CBB outer_hello;
-  CBB enc_cbb;
-  if (!CBB_init(aad.get(), 256) ||
-      !CBB_add_u16(aad.get(), EVP_HPKE_KDF_id(kdf)) ||
-      !CBB_add_u16(aad.get(), EVP_HPKE_AEAD_id(aead)) ||
-      !CBB_add_u8(aad.get(), hs->selected_ech_config->config_id) ||
-      !CBB_add_u16_length_prefixed(aad.get(), &enc_cbb) ||
-      !CBB_add_bytes(&enc_cbb, enc.data(), enc.size()) ||
-      !CBB_add_u24_length_prefixed(aad.get(), &outer_hello) ||
-      !ssl_write_client_hello_without_extensions(hs, &outer_hello,
-                                                 ssl_client_hello_outer,
-                                                 /*empty_session_id=*/false) ||
-      !ssl_add_clienthello_tlsext(hs, &outer_hello, /*out_encoded=*/nullptr,
-                                  &needs_psk_binder, ssl_client_hello_outer,
-                                  CBB_len(&outer_hello),
-                                  /*omit_ech_len=*/4 + extension_len) ||
-      !CBB_flush(aad.get())) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+  // Pad the EncodedClientHelloInner. See draft-ietf-tls-esni-13, section 6.1.3.
+  size_t padding_len = 0;
+  size_t maximum_name_length = hs->selected_ech_config->maximum_name_length;
+  if (ssl->hostname) {
+    size_t hostname_len = strlen(ssl->hostname.get());
+    if (hostname_len <= maximum_name_length) {
+      padding_len = maximum_name_length - hostname_len;
+    }
+  } else {
+    // No SNI. Pad up to |maximum_name_length|, including server_name extension
+    // overhead.
+    padding_len = 9 + maximum_name_length;
+  }
+  // Pad the whole thing to a multiple of 32 bytes.
+  padding_len += 31 - ((CBB_len(encoded_cbb.get()) + padding_len - 1) % 32);
+  Array<uint8_t> encoded;
+  if (!CBB_add_zeros(encoded_cbb.get(), padding_len) ||
+      !CBBFinishArray(encoded_cbb.get(), &encoded)) {
     return false;
   }
-  // ClientHelloOuter may not require a PSK binder. Otherwise, we have a
-  // circular dependency.
-  assert(!needs_psk_binder);
 
-  CBB payload_cbb;
-  if (!CBB_init(cbb.get(), extension_len) ||
+  // Encrypt |encoded|. See draft-ietf-tls-esni-13, section 6.1.1. First,
+  // assemble the extension with a placeholder value for ClientHelloOuterAAD.
+  // See draft-ietf-tls-esni-13, section 5.2.
+  const EVP_HPKE_KDF *kdf = EVP_HPKE_CTX_kdf(hs->ech_hpke_ctx.get());
+  const EVP_HPKE_AEAD *aead = EVP_HPKE_CTX_aead(hs->ech_hpke_ctx.get());
+  size_t payload_len = encoded.size() + aead_overhead(aead);
+  CBB enc_cbb, payload_cbb;
+  if (!CBB_init(cbb.get(), 256) ||
       !CBB_add_u16(cbb.get(), EVP_HPKE_KDF_id(kdf)) ||
       !CBB_add_u16(cbb.get(), EVP_HPKE_AEAD_id(aead)) ||
       !CBB_add_u8(cbb.get(), hs->selected_ech_config->config_id) ||
       !CBB_add_u16_length_prefixed(cbb.get(), &enc_cbb) ||
       !CBB_add_bytes(&enc_cbb, enc.data(), enc.size()) ||
-      !CBB_add_u16_length_prefixed(cbb.get(), &payload_cbb)) {
-    return false;
-  }
-#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
-  // In fuzzer mode, the server expects a cleartext payload.
-  if (!CBB_add_bytes(&payload_cbb, CBB_data(encoded.get()),
-                     CBB_len(encoded.get()))) {
-    return false;
-  }
-#else
-  uint8_t *payload;
-  size_t payload_len =
-      CBB_len(encoded.get()) + EVP_AEAD_max_overhead(EVP_HPKE_AEAD_aead(aead));
-  if (!CBB_reserve(&payload_cbb, &payload, payload_len) ||
-      !EVP_HPKE_CTX_seal(hs->ech_hpke_ctx.get(), payload, &payload_len,
-                         payload_len, CBB_data(encoded.get()),
-                         CBB_len(encoded.get()), CBB_data(aad.get()),
-                         CBB_len(aad.get())) ||
-      !CBB_did_write(&payload_cbb, payload_len)) {
-    return false;
-  }
-#endif // BORINGSSL_UNSAFE_FUZZER_MODE
-  if (!CBBFinishArray(cbb.get(), &hs->ech_client_bytes)) {
+      !CBB_add_u16_length_prefixed(cbb.get(), &payload_cbb) ||
+      !CBB_add_zeros(&payload_cbb, payload_len) ||
+      !CBBFinishArray(cbb.get(), &hs->ech_client_outer)) {
     return false;
   }
 
-  // The |aad| calculation relies on |extension_length| being correct.
-  assert(hs->ech_client_bytes.size() == extension_len);
+  // Construct ClientHelloOuterAAD.
+  // TODO(https://crbug.com/boringssl/275): This ends up constructing the
+  // ClientHelloOuter twice. Instead, reuse |aad| for the ClientHello, now that
+  // draft-12 made the length prefixes match.
+  bssl::ScopedCBB aad;
+  if (!CBB_init(aad.get(), 256) ||
+      !ssl_write_client_hello_without_extensions(hs, aad.get(),
+                                                 ssl_client_hello_outer,
+                                                 /*empty_session_id=*/false) ||
+      !ssl_add_clienthello_tlsext(hs, aad.get(), /*out_encoded=*/nullptr,
+                                  &needs_psk_binder, ssl_client_hello_outer,
+                                  CBB_len(aad.get()))) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return false;
+  }
+
+  // ClientHelloOuter may not require a PSK binder. Otherwise, we have a
+  // circular dependency.
+  assert(!needs_psk_binder);
+
+  // Replace the payload in |hs->ech_client_outer| with the encrypted value.
+  auto payload_span = MakeSpan(hs->ech_client_outer).last(payload_len);
+#if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
+  // In fuzzer mode, the server expects a cleartext payload.
+  assert(payload_span.size() == encoded.size());
+  OPENSSL_memcpy(payload_span.data(), encoded.data(), encoded.size());
+#else
+  if (!EVP_HPKE_CTX_seal(hs->ech_hpke_ctx.get(), payload_span.data(),
+                         &payload_len, payload_span.size(), encoded.data(),
+                         encoded.size(), CBB_data(aad.get()),
+                         CBB_len(aad.get())) ||
+      payload_len != payload_span.size()) {
+    return false;
+  }
+#endif // BORINGSSL_UNSAFE_FUZZER_MODE
+
   return true;
 }
 
@@ -1045,7 +948,13 @@
     return 0;
   }
 
-  // See draft-ietf-tls-esni-10, section 4.
+  // The maximum name length is encoded in one byte.
+  if (max_name_len > 0xff) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_LENGTH);
+    return 0;
+  }
+
+  // See draft-ietf-tls-esni-13, section 4.
   ScopedCBB cbb;
   CBB contents, child;
   uint8_t *public_key;
@@ -1066,8 +975,8 @@
       !CBB_add_u16(&child, EVP_HPKE_AES_128_GCM) ||
       !CBB_add_u16(&child, EVP_HPKE_HKDF_SHA256) ||
       !CBB_add_u16(&child, EVP_HPKE_CHACHA20_POLY1305) ||
-      !CBB_add_u16(&contents, max_name_len) ||
-      !CBB_add_u16_length_prefixed(&contents, &child) ||
+      !CBB_add_u8(&contents, max_name_len) ||
+      !CBB_add_u8_length_prefixed(&contents, &child) ||
       !CBB_add_bytes(&child, public_name_u8.data(), public_name_u8.size()) ||
       // TODO(https://crbug.com/boringssl/275): Reserve some GREASE extensions
       // and include some.
diff --git a/src/ssl/extensions.cc b/src/ssl/extensions.cc
index 4950d26..3baef6d 100644
--- a/src/ssl/extensions.cc
+++ b/src/ssl/extensions.cc
@@ -210,16 +210,24 @@
 
 bool ssl_client_hello_init(const SSL *ssl, SSL_CLIENT_HELLO *out,
                            Span<const uint8_t> body) {
+  CBS cbs = body;
+  if (!ssl_parse_client_hello_with_trailing_data(ssl, &cbs, out) ||
+      CBS_len(&cbs) != 0) {
+    return false;
+  }
+  return true;
+}
+
+bool ssl_parse_client_hello_with_trailing_data(const SSL *ssl, CBS *cbs,
+                                               SSL_CLIENT_HELLO *out) {
   OPENSSL_memset(out, 0, sizeof(*out));
   out->ssl = const_cast<SSL *>(ssl);
-  out->client_hello = body.data();
-  out->client_hello_len = body.size();
 
-  CBS client_hello, random, session_id;
-  CBS_init(&client_hello, out->client_hello, out->client_hello_len);
-  if (!CBS_get_u16(&client_hello, &out->version) ||
-      !CBS_get_bytes(&client_hello, &random, SSL3_RANDOM_SIZE) ||
-      !CBS_get_u8_length_prefixed(&client_hello, &session_id) ||
+  CBS copy = *cbs;
+  CBS random, session_id;
+  if (!CBS_get_u16(cbs, &out->version) ||
+      !CBS_get_bytes(cbs, &random, SSL3_RANDOM_SIZE) ||
+      !CBS_get_u8_length_prefixed(cbs, &session_id) ||
       CBS_len(&session_id) > SSL_MAX_SSL_SESSION_ID_LENGTH) {
     return false;
   }
@@ -232,16 +240,16 @@
   // Skip past DTLS cookie
   if (SSL_is_dtls(out->ssl)) {
     CBS cookie;
-    if (!CBS_get_u8_length_prefixed(&client_hello, &cookie) ||
+    if (!CBS_get_u8_length_prefixed(cbs, &cookie) ||
         CBS_len(&cookie) > DTLS1_COOKIE_LENGTH) {
       return false;
     }
   }
 
   CBS cipher_suites, compression_methods;
-  if (!CBS_get_u16_length_prefixed(&client_hello, &cipher_suites) ||
+  if (!CBS_get_u16_length_prefixed(cbs, &cipher_suites) ||
       CBS_len(&cipher_suites) < 2 || (CBS_len(&cipher_suites) & 1) != 0 ||
-      !CBS_get_u8_length_prefixed(&client_hello, &compression_methods) ||
+      !CBS_get_u8_length_prefixed(cbs, &compression_methods) ||
       CBS_len(&compression_methods) < 1) {
     return false;
   }
@@ -253,23 +261,22 @@
 
   // If the ClientHello ends here then it's valid, but doesn't have any
   // extensions.
-  if (CBS_len(&client_hello) == 0) {
-    out->extensions = NULL;
+  if (CBS_len(cbs) == 0) {
+    out->extensions = nullptr;
     out->extensions_len = 0;
-    return true;
+  } else {
+    // Extract extensions and check it is valid.
+    CBS extensions;
+    if (!CBS_get_u16_length_prefixed(cbs, &extensions) ||
+        !tls1_check_duplicate_extensions(&extensions)) {
+      return false;
+    }
+    out->extensions = CBS_data(&extensions);
+    out->extensions_len = CBS_len(&extensions);
   }
 
-  // Extract extensions and check it is valid.
-  CBS extensions;
-  if (!CBS_get_u16_length_prefixed(&client_hello, &extensions) ||
-      !tls1_check_duplicate_extensions(&extensions) ||
-      CBS_len(&client_hello) != 0) {
-    return false;
-  }
-
-  out->extensions = CBS_data(&extensions);
-  out->extensions_len = CBS_len(&extensions);
-
+  out->client_hello = CBS_data(&copy);
+  out->client_hello_len = CBS_len(&copy) - CBS_len(cbs);
   return true;
 }
 
@@ -619,20 +626,30 @@
 
 // Encrypted ClientHello (ECH)
 //
-// https://tools.ietf.org/html/draft-ietf-tls-esni-10
+// https://tools.ietf.org/html/draft-ietf-tls-esni-13
 
 static bool ext_ech_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
                                     CBB *out_compressible,
                                     ssl_client_hello_type_t type) {
-  if (type == ssl_client_hello_inner || hs->ech_client_bytes.empty()) {
+  if (type == ssl_client_hello_inner) {
+    if (!CBB_add_u16(out, TLSEXT_TYPE_encrypted_client_hello) ||
+        !CBB_add_u16(out, /* length */ 1) ||
+        !CBB_add_u8(out, ECH_CLIENT_INNER)) {
+      return false;
+    }
+    return true;
+  }
+
+  if (hs->ech_client_outer.empty()) {
     return true;
   }
 
   CBB ech_body;
   if (!CBB_add_u16(out, TLSEXT_TYPE_encrypted_client_hello) ||
       !CBB_add_u16_length_prefixed(out, &ech_body) ||
-      !CBB_add_bytes(&ech_body, hs->ech_client_bytes.data(),
-                     hs->ech_client_bytes.size()) ||
+      !CBB_add_u8(&ech_body, ECH_CLIENT_OUTER) ||
+      !CBB_add_bytes(&ech_body, hs->ech_client_outer.data(),
+                     hs->ech_client_outer.size()) ||
       !CBB_flush(out)) {
     return false;
   }
@@ -647,8 +664,10 @@
   }
 
   // The ECH extension may not be sent in TLS 1.2 ServerHello, only TLS 1.3
-  // EncryptedExtension.
-  if (ssl_protocol_version(ssl) < TLS1_3_VERSION) {
+  // EncryptedExtensions. It also may not be sent in response to an inner ECH
+  // extension.
+  if (ssl_protocol_version(ssl) < TLS1_3_VERSION ||
+      ssl->s3->ech_status == ssl_ech_accepted) {
     *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
     return false;
@@ -659,17 +678,7 @@
     return false;
   }
 
-  // The server may only send retry configs in response to ClientHelloOuter (or
-  // ECH GREASE), not ClientHelloInner. The unsolicited extension rule checks
-  // this implicitly because the ClientHelloInner has no encrypted_client_hello
-  // extension.
-  //
-  // TODO(https://crbug.com/boringssl/275): If
-  // https://github.com/tlswg/draft-ietf-tls-esni/pull/422 is merged, a later
-  // draft will fold encrypted_client_hello and ech_is_inner together. Then this
-  // assert should become a runtime check.
-  assert(ssl->s3->ech_status != ssl_ech_accepted);
-  if (hs->selected_ech_config &&
+  if (ssl->s3->ech_status == ssl_ech_rejected &&
       !hs->ech_retry_configs.CopyFrom(*contents)) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     return false;
@@ -680,10 +689,23 @@
 
 static bool ext_ech_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                                       CBS *contents) {
-  if (contents != nullptr) {
-    hs->ech_present = true;
+  if (contents == nullptr) {
     return true;
   }
+
+  uint8_t type;
+  if (!CBS_get_u8(contents, &type)) {
+    return false;
+  }
+  if (type == ECH_CLIENT_OUTER) {
+    // Outer ECH extensions are handled outside the callback.
+    return true;
+  }
+  if (type != ECH_CLIENT_INNER || CBS_len(contents) != 0) {
+    return false;
+  }
+
+  hs->ech_is_inner = true;
   return true;
 }
 
@@ -715,32 +737,6 @@
   return CBB_flush(out);
 }
 
-static bool ext_ech_is_inner_add_clienthello(const SSL_HANDSHAKE *hs, CBB *out,
-                                             CBB *out_compressible,
-                                             ssl_client_hello_type_t type) {
-  if (type == ssl_client_hello_inner) {
-    if (!CBB_add_u16(out, TLSEXT_TYPE_ech_is_inner) ||
-        !CBB_add_u16(out, 0 /* empty extension */)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-static bool ext_ech_is_inner_parse_clienthello(SSL_HANDSHAKE *hs,
-                                               uint8_t *out_alert,
-                                               CBS *contents) {
-  if (contents == nullptr) {
-    return true;
-  }
-  if (CBS_len(contents) > 0) {
-    *out_alert = SSL_AD_ILLEGAL_PARAMETER;
-    return false;
-  }
-  hs->ech_is_inner_present = true;
-  return true;
-}
-
 
 // Renegotiation indication.
 //
@@ -1942,13 +1938,10 @@
   const SSL *const ssl = hs->ssl;
   if (hs->max_version < TLS1_3_VERSION || ssl->session == nullptr ||
       ssl_session_protocol_version(ssl->session.get()) < TLS1_3_VERSION ||
-      // The ClientHelloOuter cannot include the PSK extension.
-      //
-      // TODO(https://crbug.com/boringssl/275): draft-ietf-tls-esni-10 mandates
-      // this, but it risks breaking the ClientHelloOuter flow on 0-RTT reject.
-      // Later drafts will recommend including a placeholder one, at which point
-      // we will need to synthesize a ticket. See
-      // https://github.com/tlswg/draft-ietf-tls-esni/issues/408
+      // TODO(https://crbug.com/boringssl/275): Should we synthesize a
+      // placeholder PSK, at least when we offer early data? Otherwise
+      // ClientHelloOuter will contain an early_data extension without a
+      // pre_shared_key extension and potentially break the recovery flow.
       type == ssl_client_hello_outer) {
     return false;
   }
@@ -1991,7 +1984,6 @@
 
   // Fill in a placeholder zero binder of the appropriate length. It will be
   // computed and filled in later after length prefixes are computed.
-  uint8_t zero_binder[EVP_MAX_MD_SIZE] = {0};
   size_t binder_len = EVP_MD_size(ssl_session_get_digest(ssl->session.get()));
 
   CBB contents, identity, ticket, binders, binder;
@@ -2004,7 +1996,7 @@
       !CBB_add_u32(&identity, obfuscated_ticket_age) ||
       !CBB_add_u16_length_prefixed(&contents, &binders) ||
       !CBB_add_u8_length_prefixed(&binders, &binder) ||
-      !CBB_add_bytes(&binder, zero_binder, binder_len)) {
+      !CBB_add_zeros(&binder, binder_len)) {
     return false;
   }
 
@@ -2184,10 +2176,7 @@
   // If offering ECH, the extension only applies to ClientHelloInner, but we
   // send the extension in both ClientHellos. This ensures that, if the server
   // handshakes with ClientHelloOuter, it can skip past early data. See
-  // https://github.com/tlswg/draft-ietf-tls-esni/pull/415
-  //
-  // TODO(https://crbug.com/boringssl/275): Replace this with a reference to the
-  // right section in the next draft.
+  // draft-ietf-tls-esni-13, section 6.1.
   if (!CBB_add_u16(out_compressible, TLSEXT_TYPE_early_data) ||
       !CBB_add_u16(out_compressible, 0) ||
       !CBB_flush(out_compressible)) {
@@ -3111,13 +3100,6 @@
     ext_ech_add_serverhello,
   },
   {
-    TLSEXT_TYPE_ech_is_inner,
-    ext_ech_is_inner_add_clienthello,
-    forbid_parse_serverhello,
-    ext_ech_is_inner_parse_clienthello,
-    dont_add_serverhello,
-  },
-  {
     TLSEXT_TYPE_extended_master_secret,
     ext_ems_add_clienthello,
     ext_ems_parse_serverhello,
@@ -3324,14 +3306,12 @@
 
 static bool add_padding_extension(CBB *cbb, uint16_t ext, size_t len) {
   CBB child;
-  uint8_t *ptr;
   if (!CBB_add_u16(cbb, ext) ||  //
       !CBB_add_u16_length_prefixed(cbb, &child) ||
-      !CBB_add_space(&child, &ptr, len)) {
+      !CBB_add_zeros(&child, len)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
   }
-  OPENSSL_memset(ptr, 0, len);
   return CBB_flush(cbb);
 }
 
@@ -3404,34 +3384,6 @@
     }
   }
 
-  // Pad the server name. See draft-ietf-tls-esni-10, section 6.1.2.
-  // TODO(https://crbug.com/boringssl/275): Ideally we'd pad the whole thing to
-  // reduce the output range. See
-  // https://github.com/tlswg/draft-ietf-tls-esni/issues/433
-  size_t padding_len = 0;
-  size_t maximum_name_length = hs->selected_ech_config->maximum_name_length;
-  if (ssl->hostname) {
-    size_t hostname_len = strlen(ssl->hostname.get());
-    if (hostname_len <= maximum_name_length) {
-      padding_len = maximum_name_length - hostname_len;
-    } else {
-      // If the server underestimated the maximum size, pad to a multiple of 32.
-      padding_len = 31 - (hostname_len - 1) % 32;
-      // If the input is close to |maximum_name_length|, pad to the next
-      // multiple for at least 32 bytes of length ambiguity.
-      if (hostname_len + padding_len < maximum_name_length + 32) {
-        padding_len += 32;
-      }
-    }
-  } else {
-    // No SNI. Pad up to |maximum_name_length|, including server_name extension
-    // overhead.
-    padding_len = 9 + maximum_name_length;
-  }
-  if (!add_padding_extension(&extensions, TLSEXT_TYPE_padding, padding_len)) {
-    return false;
-  }
-
   // Uncompressed extensions are encoded as-is.
   if (!CBB_add_bytes(&extensions_encoded, CBB_data(&extensions),
                      CBB_len(&extensions))) {
@@ -3473,8 +3425,8 @@
 
 bool ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, CBB *out_encoded,
                                 bool *out_needs_psk_binder,
-                                ssl_client_hello_type_t type, size_t header_len,
-                                size_t omit_ech_len) {
+                                ssl_client_hello_type_t type,
+                                size_t header_len) {
   *out_needs_psk_binder = false;
 
   if (type == ssl_client_hello_inner) {
@@ -3507,20 +3459,14 @@
     size_t i = hs->extension_permutation.empty()
                    ? unpermuted
                    : hs->extension_permutation[unpermuted];
-    size_t bytes_written;
-    if (omit_ech_len != 0 &&
-        kExtensions[i].value == TLSEXT_TYPE_encrypted_client_hello) {
-      bytes_written = omit_ech_len;
-    } else {
-      const size_t len_before = CBB_len(&extensions);
-      if (!kExtensions[i].add_clienthello(hs, &extensions, &extensions, type)) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_ADDING_EXTENSION);
-        ERR_add_error_dataf("extension %u", (unsigned)kExtensions[i].value);
-        return false;
-      }
-
-      bytes_written = CBB_len(&extensions) - len_before;
+    const size_t len_before = CBB_len(&extensions);
+    if (!kExtensions[i].add_clienthello(hs, &extensions, &extensions, type)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_ADDING_EXTENSION);
+      ERR_add_error_dataf("extension %u", (unsigned)kExtensions[i].value);
+      return false;
     }
+
+    const size_t bytes_written = CBB_len(&extensions) - len_before;
     if (bytes_written != 0) {
       hs->extensions.sent |= (1u << i);
     }
@@ -3544,8 +3490,8 @@
   size_t psk_extension_len = ext_pre_shared_key_clienthello_length(hs, type);
   if (!SSL_is_dtls(ssl) && !ssl->quic_method &&
       !ssl->s3->used_hello_retry_request) {
-    header_len += SSL3_HM_HEADER_LENGTH + 2 + CBB_len(&extensions) +
-                  omit_ech_len + psk_extension_len;
+    header_len +=
+        SSL3_HM_HEADER_LENGTH + 2 + CBB_len(&extensions) + psk_extension_len;
     size_t padding_len = 0;
 
     // The final extension must be non-empty. WebSphere Application
@@ -3719,18 +3665,10 @@
   return true;
 }
 
-static bool ssl_scan_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs,
+static bool ssl_scan_serverhello_tlsext(SSL_HANDSHAKE *hs, const CBS *cbs,
                                         int *out_alert) {
-  SSL *const ssl = hs->ssl;
-  // Before TLS 1.3, ServerHello extensions blocks may be omitted if empty.
-  if (CBS_len(cbs) == 0 && ssl_protocol_version(ssl) < TLS1_3_VERSION) {
-    return true;
-  }
-
-  // Decode the extensions block and check it is valid.
-  CBS extensions;
-  if (!CBS_get_u16_length_prefixed(cbs, &extensions) ||
-      !tls1_check_duplicate_extensions(&extensions)) {
+  CBS extensions = *cbs;
+  if (!tls1_check_duplicate_extensions(&extensions)) {
     *out_alert = SSL_AD_DECODE_ERROR;
     return false;
   }
@@ -3852,7 +3790,7 @@
   return true;
 }
 
-bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs) {
+bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, const CBS *cbs) {
   SSL *const ssl = hs->ssl;
   int alert = SSL_AD_DECODE_ERROR;
   if (!ssl_scan_serverhello_tlsext(hs, cbs, &alert)) {
@@ -3880,8 +3818,8 @@
     return ssl_ticket_aead_ignore_ticket;
   }
   // Split the ticket into the ticket and the MAC.
-  auto ticket_mac = ticket.subspan(ticket.size() - mac_len);
-  ticket = ticket.subspan(0, ticket.size() - mac_len);
+  auto ticket_mac = ticket.last(mac_len);
+  ticket = ticket.first(ticket.size() - mac_len);
   HMAC_Update(hmac_ctx, ticket.data(), ticket.size());
   HMAC_Final(hmac_ctx, mac, NULL);
   assert(mac_len == ticket_mac.size());
diff --git a/src/ssl/handshake.cc b/src/ssl/handshake.cc
index db4ee71..fc85e21 100644
--- a/src/ssl/handshake.cc
+++ b/src/ssl/handshake.cc
@@ -126,8 +126,7 @@
 
 SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
     : ssl(ssl_arg),
-      ech_present(false),
-      ech_is_inner_present(false),
+      ech_is_inner(false),
       ech_authenticated_reject(false),
       scts_requested(false),
       handshake_finalized(false),
@@ -268,12 +267,15 @@
 }
 
 bool ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
-                          Span<const SSL_EXTENSION_TYPE> ext_types,
+                          std::initializer_list<SSLExtension *> extensions,
                           bool ignore_unknown) {
   // Reset everything.
-  for (const SSL_EXTENSION_TYPE &ext_type : ext_types) {
-    *ext_type.out_present = false;
-    CBS_init(ext_type.out_data, nullptr, 0);
+  for (SSLExtension *ext : extensions) {
+    ext->present = false;
+    CBS_init(&ext->data, nullptr, 0);
+    if (!ext->allowed) {
+      assert(!ignore_unknown);
+    }
   }
 
   CBS copy = *cbs;
@@ -287,10 +289,10 @@
       return false;
     }
 
-    const SSL_EXTENSION_TYPE *found = nullptr;
-    for (const SSL_EXTENSION_TYPE &ext_type : ext_types) {
-      if (type == ext_type.type) {
-        found = &ext_type;
+    SSLExtension *found = nullptr;
+    for (SSLExtension *ext : extensions) {
+      if (type == ext->type && ext->allowed) {
+        found = ext;
         break;
       }
     }
@@ -305,14 +307,14 @@
     }
 
     // Duplicate ext_types are forbidden.
-    if (*found->out_present) {
+    if (found->present) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_EXTENSION);
       *out_alert = SSL_AD_ILLEGAL_PARAMETER;
       return false;
     }
 
-    *found->out_present = 1;
-    *found->out_data = data;
+    found->present = true;
+    found->data = data;
   }
 
   return true;
diff --git a/src/ssl/handshake_client.cc b/src/ssl/handshake_client.cc
index ba8f4b7..17b41e0 100644
--- a/src/ssl/handshake_client.cc
+++ b/src/ssl/handshake_client.cc
@@ -333,8 +333,7 @@
       !ssl_write_client_hello_without_extensions(hs, &body, type,
                                                  /*empty_session_id*/ false) ||
       !ssl_add_clienthello_tlsext(hs, &body, /*out_encoded=*/nullptr,
-                                  &needs_psk_binder, type, CBB_len(&body),
-                                  /*omit_ech_len=*/0) ||
+                                  &needs_psk_binder, type, CBB_len(&body)) ||
       !ssl->method->finish_message(ssl, cbb.get(), &msg)) {
     return false;
   }
@@ -354,42 +353,31 @@
   return ssl->method->add_message(ssl, std::move(msg));
 }
 
-static bool parse_supported_versions(SSL_HANDSHAKE *hs, uint16_t *version,
-                                     const CBS *in) {
-  // If the outer version is not TLS 1.2, or there is no extensions block, use
-  // the outer version.
-  if (*version != TLS1_2_VERSION || CBS_len(in) == 0) {
+static bool parse_server_version(const SSL_HANDSHAKE *hs, uint16_t *out_version,
+                                 uint8_t *out_alert,
+                                 const ParsedServerHello &server_hello) {
+  // If the outer version is not TLS 1.2, use it.
+  // TODO(davidben): This function doesn't quite match the RFC8446 formulation.
+  if (server_hello.legacy_version != TLS1_2_VERSION) {
+    *out_version = server_hello.legacy_version;
     return true;
   }
 
-  SSL *const ssl = hs->ssl;
-  CBS copy = *in, extensions;
-  if (!CBS_get_u16_length_prefixed(&copy, &extensions) ||
-      CBS_len(&copy) != 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return false;
-  }
-
-  bool have_supported_versions;
-  CBS supported_versions;
-  const SSL_EXTENSION_TYPE ext_types[] = {
-    {TLSEXT_TYPE_supported_versions, &have_supported_versions,
-     &supported_versions},
-  };
-
-  uint8_t alert = SSL_AD_DECODE_ERROR;
-  if (!ssl_parse_extensions(&extensions, &alert, ext_types,
+  SSLExtension supported_versions(TLSEXT_TYPE_supported_versions);
+  CBS extensions = server_hello.extensions;
+  if (!ssl_parse_extensions(&extensions, out_alert, {&supported_versions},
                             /*ignore_unknown=*/true)) {
-    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return false;
   }
 
-  // Override the outer version with the extension, if present.
-  if (have_supported_versions &&
-      (!CBS_get_u16(&supported_versions, version) ||
-       CBS_len(&supported_versions) != 0)) {
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+  if (!supported_versions.present) {
+    *out_version = server_hello.legacy_version;
+    return true;
+  }
+
+  if (!CBS_get_u16(&supported_versions.data, out_version) ||
+       CBS_len(&supported_versions.data) != 0) {
+    *out_alert = SSL_AD_DECODE_ERROR;
     return false;
   }
 
@@ -445,7 +433,7 @@
 }
 
 void ssl_done_writing_client_hello(SSL_HANDSHAKE *hs) {
-  hs->ech_client_bytes.Reset();
+  hs->ech_client_outer.Reset();
   hs->cookie.Reset();
   hs->key_share_bytes.Reset();
 }
@@ -657,6 +645,38 @@
   return ssl_hs_flush;
 }
 
+bool ssl_parse_server_hello(ParsedServerHello *out, uint8_t *out_alert,
+                            const SSLMessage &msg) {
+  if (msg.type != SSL3_MT_SERVER_HELLO) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
+    *out_alert = SSL_AD_UNEXPECTED_MESSAGE;
+    return false;
+  }
+  out->raw = msg.raw;
+  CBS body = msg.body;
+  if (!CBS_get_u16(&body, &out->legacy_version) ||
+      !CBS_get_bytes(&body, &out->random, SSL3_RANDOM_SIZE) ||
+      !CBS_get_u8_length_prefixed(&body, &out->session_id) ||
+      CBS_len(&out->session_id) > SSL3_SESSION_ID_SIZE ||
+      !CBS_get_u16(&body, &out->cipher_suite) ||
+      !CBS_get_u8(&body, &out->compression_method)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return false;
+  }
+  // In TLS 1.2 and below, empty extensions blocks may be omitted. In TLS 1.3,
+  // ServerHellos always have extensions, so this can be applied generically.
+  CBS_init(&out->extensions, nullptr, 0);
+  if ((CBS_len(&body) != 0 &&
+       !CBS_get_u16_length_prefixed(&body, &out->extensions)) ||
+      CBS_len(&body) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return false;
+  }
+  return true;
+}
+
 static enum ssl_hs_wait_t do_read_server_hello(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   SSLMessage msg;
@@ -664,26 +684,12 @@
     return ssl_hs_read_server_hello;
   }
 
-  if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO)) {
-    return ssl_hs_error;
-  }
-
-  CBS server_hello = msg.body, server_random, session_id;
-  uint16_t server_version, cipher_suite;
-  uint8_t compression_method;
-  if (!CBS_get_u16(&server_hello, &server_version) ||
-      !CBS_get_bytes(&server_hello, &server_random, SSL3_RANDOM_SIZE) ||
-      !CBS_get_u8_length_prefixed(&server_hello, &session_id) ||
-      CBS_len(&session_id) > SSL3_SESSION_ID_SIZE ||
-      !CBS_get_u16(&server_hello, &cipher_suite) ||
-      !CBS_get_u8(&server_hello, &compression_method)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return ssl_hs_error;
-  }
-
-  // Use the supported_versions extension if applicable.
-  if (!parse_supported_versions(hs, &server_version, &server_hello)) {
+  ParsedServerHello server_hello;
+  uint16_t server_version;
+  uint8_t alert = SSL_AD_DECODE_ERROR;
+  if (!ssl_parse_server_hello(&server_hello, &alert, msg) ||
+      !parse_server_version(hs, &server_version, &alert, server_hello)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
 
@@ -737,7 +743,7 @@
   }
 
   // Copy over the server random.
-  OPENSSL_memcpy(ssl->s3->server_random, CBS_data(&server_random),
+  OPENSSL_memcpy(ssl->s3->server_random, CBS_data(&server_hello.random),
                  SSL3_RANDOM_SIZE);
 
   // Enforce the TLS 1.3 anti-downgrade feature.
@@ -760,28 +766,26 @@
     }
   }
 
-  const SSL_CIPHER *cipher = SSL_get_cipher_by_value(cipher_suite);
-  if (cipher == NULL) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CIPHER_RETURNED);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return ssl_hs_error;
-  }
-  hs->new_cipher = cipher;
-
   // The cipher must be allowed in the selected version and enabled.
+  const SSL_CIPHER *cipher = SSL_get_cipher_by_value(server_hello.cipher_suite);
   uint32_t mask_a, mask_k;
   ssl_get_client_disabled(hs, &mask_a, &mask_k);
-  if ((cipher->algorithm_mkey & mask_k) || (cipher->algorithm_auth & mask_a) ||
+  if (cipher == nullptr ||
+      (cipher->algorithm_mkey & mask_k) ||
+      (cipher->algorithm_auth & mask_a) ||
       SSL_CIPHER_get_min_version(cipher) > ssl_protocol_version(ssl) ||
       SSL_CIPHER_get_max_version(cipher) < ssl_protocol_version(ssl) ||
-      !sk_SSL_CIPHER_find(SSL_get_ciphers(ssl), NULL, cipher)) {
+      !sk_SSL_CIPHER_find(SSL_get_ciphers(ssl), nullptr, cipher)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
     return ssl_hs_error;
   }
 
+  hs->new_cipher = cipher;
+
   if (hs->session_id_len != 0 &&
-      CBS_mem_equal(&session_id, hs->session_id, hs->session_id_len)) {
+      CBS_mem_equal(&server_hello.session_id, hs->session_id,
+                    hs->session_id_len)) {
     // Echoing the ClientHello session ID in TLS 1.2, whether from the session
     // or a synthetic one, indicates resumption. If there was no session (or if
     // the session was only offered in ECH ClientHelloInner), this was the
@@ -799,7 +803,7 @@
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       return ssl_hs_error;
     }
-    if (ssl->session->cipher != cipher) {
+    if (ssl->session->cipher != hs->new_cipher) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       return ssl_hs_error;
@@ -822,10 +826,11 @@
       return ssl_hs_error;
     }
     // Note: session_id could be empty.
-    hs->new_session->session_id_length = CBS_len(&session_id);
-    OPENSSL_memcpy(hs->new_session->session_id, CBS_data(&session_id),
-                   CBS_len(&session_id));
-    hs->new_session->cipher = cipher;
+    hs->new_session->session_id_length = CBS_len(&server_hello.session_id);
+    OPENSSL_memcpy(hs->new_session->session_id,
+                   CBS_data(&server_hello.session_id),
+                   CBS_len(&server_hello.session_id));
+    hs->new_session->cipher = hs->new_cipher;
   }
 
   // Now that the cipher is known, initialize the handshake hash and hash the
@@ -845,26 +850,17 @@
   }
 
   // Only the NULL compression algorithm is supported.
-  if (compression_method != 0) {
+  if (server_hello.compression_method != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_COMPRESSION_ALGORITHM);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
     return ssl_hs_error;
   }
 
-  // TLS extensions
-  if (!ssl_parse_serverhello_tlsext(hs, &server_hello)) {
+  if (!ssl_parse_serverhello_tlsext(hs, &server_hello.extensions)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_PARSE_TLSEXT);
     return ssl_hs_error;
   }
 
-  // There should be nothing left over in the record.
-  if (CBS_len(&server_hello) != 0) {
-    // wrong packet length
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    return ssl_hs_error;
-  }
-
   if (ssl->session != NULL &&
       hs->extended_master_secret != ssl->session->extended_master_secret) {
     if (ssl->session->extended_master_secret) {
diff --git a/src/ssl/handshake_server.cc b/src/ssl/handshake_server.cc
index c8a23a1..fdf9511 100644
--- a/src/ssl/handshake_server.cc
+++ b/src/ssl/handshake_server.cc
@@ -504,6 +504,91 @@
   return true;
 }
 
+static bool decrypt_ech(SSL_HANDSHAKE *hs, uint8_t *out_alert,
+                        const SSL_CLIENT_HELLO *client_hello) {
+  SSL *const ssl = hs->ssl;
+  CBS body;
+  if (!ssl_client_hello_get_extension(client_hello, &body,
+                                      TLSEXT_TYPE_encrypted_client_hello)) {
+    return true;
+  }
+  uint8_t type;
+  if (!CBS_get_u8(&body, &type)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return false;
+  }
+  if (type != ECH_CLIENT_OUTER) {
+    return true;
+  }
+  // This is a ClientHelloOuter ECH extension. Attempt to decrypt it.
+  uint8_t config_id;
+  uint16_t kdf_id, aead_id;
+  CBS enc, payload;
+  if (!CBS_get_u16(&body, &kdf_id) ||   //
+      !CBS_get_u16(&body, &aead_id) ||  //
+      !CBS_get_u8(&body, &config_id) ||
+      !CBS_get_u16_length_prefixed(&body, &enc) ||
+      !CBS_get_u16_length_prefixed(&body, &payload) ||  //
+      CBS_len(&body) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return false;
+  }
+
+  {
+    MutexReadLock lock(&ssl->ctx->lock);
+    hs->ech_keys = UpRef(ssl->ctx->ech_keys);
+  }
+
+  if (!hs->ech_keys) {
+    ssl->s3->ech_status = ssl_ech_rejected;
+    return true;
+  }
+
+  for (const auto &config : hs->ech_keys->configs) {
+    hs->ech_hpke_ctx.Reset();
+    if (config_id != config->ech_config().config_id ||
+        !config->SetupContext(hs->ech_hpke_ctx.get(), kdf_id, aead_id, enc)) {
+      // Ignore the error and try another ECHConfig.
+      ERR_clear_error();
+      continue;
+    }
+    Array<uint8_t> encoded_client_hello_inner;
+    bool is_decrypt_error;
+    if (!ssl_client_hello_decrypt(hs->ech_hpke_ctx.get(),
+                                  &encoded_client_hello_inner,
+                                  &is_decrypt_error, client_hello, payload)) {
+      if (is_decrypt_error) {
+        // Ignore the error and try another ECHConfig.
+        ERR_clear_error();
+        continue;
+      }
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
+      return false;
+    }
+
+    // Recover the ClientHelloInner from the EncodedClientHelloInner.
+    bssl::Array<uint8_t> client_hello_inner;
+    if (!ssl_decode_client_hello_inner(ssl, out_alert, &client_hello_inner,
+                                       encoded_client_hello_inner,
+                                       client_hello)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      return false;
+    }
+    hs->ech_client_hello_buf = std::move(client_hello_inner);
+    hs->ech_config_id = config_id;
+    ssl->s3->ech_status = ssl_ech_accepted;
+    return true;
+  }
+
+  // If we did not accept ECH, proceed with the ClientHelloOuter. Note this
+  // could be key mismatch or ECH GREASE, so we must complete the handshake
+  // as usual, except EncryptedExtensions will contain retry configs.
+  ssl->s3->ech_status = ssl_ech_rejected;
+  return true;
+}
+
 static bool extract_sni(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                         const SSL_CLIENT_HELLO *client_hello) {
   SSL *const ssl = hs->ssl;
@@ -583,98 +668,19 @@
     return ssl_hs_handoff;
   }
 
-  // If the ClientHello contains an encrypted_client_hello extension (and no
-  // ech_is_inner extension), act as a client-facing server and attempt to
-  // decrypt the ClientHelloInner.
-  CBS ech_body;
-  if (ssl_client_hello_get_extension(&client_hello, &ech_body,
-                                      TLSEXT_TYPE_encrypted_client_hello)) {
-    CBS unused;
-    if (ssl_client_hello_get_extension(&client_hello, &unused,
-                                       TLSEXT_TYPE_ech_is_inner)) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return ssl_hs_error;
-    }
-
-    // Parse a ClientECH out of the extension body.
-    uint8_t config_id;
-    uint16_t kdf_id, aead_id;
-    CBS enc, payload;
-    if (!CBS_get_u16(&ech_body, &kdf_id) ||  //
-        !CBS_get_u16(&ech_body, &aead_id) ||
-        !CBS_get_u8(&ech_body, &config_id) ||
-        !CBS_get_u16_length_prefixed(&ech_body, &enc) ||
-        !CBS_get_u16_length_prefixed(&ech_body, &payload) ||
-        CBS_len(&ech_body) != 0) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      return ssl_hs_error;
-    }
-
-    {
-      MutexReadLock lock(&ssl->ctx->lock);
-      hs->ech_keys = UpRef(ssl->ctx->ech_keys);
-    }
-
-    if (hs->ech_keys) {
-      for (const auto &config : hs->ech_keys->configs) {
-        hs->ech_hpke_ctx.Reset();
-        if (config_id != config->ech_config().config_id ||
-            !config->SetupContext(hs->ech_hpke_ctx.get(), kdf_id, aead_id,
-                                  enc)) {
-          // Ignore the error and try another ECHConfig.
-          ERR_clear_error();
-          continue;
-        }
-        Array<uint8_t> encoded_client_hello_inner;
-        bool is_decrypt_error;
-        if (!ssl_client_hello_decrypt(hs->ech_hpke_ctx.get(),
-                                      &encoded_client_hello_inner,
-                                      &is_decrypt_error, &client_hello, kdf_id,
-                                      aead_id, config_id, enc, payload)) {
-          if (is_decrypt_error) {
-            // Ignore the error and try another ECHConfig.
-            ERR_clear_error();
-            continue;
-          }
-          OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
-          return ssl_hs_error;
-        }
-
-        // Recover the ClientHelloInner from the EncodedClientHelloInner.
-        uint8_t alert = SSL_AD_DECODE_ERROR;
-        bssl::Array<uint8_t> client_hello_inner;
-        if (!ssl_decode_client_hello_inner(ssl, &alert, &client_hello_inner,
-                                           encoded_client_hello_inner,
-                                           &client_hello)) {
-          ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
-          OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-          return ssl_hs_error;
-        }
-        hs->ech_client_hello_buf = std::move(client_hello_inner);
-
-        // Load the ClientHelloInner into |client_hello|.
-        if (!hs->GetClientHello(&msg, &client_hello)) {
-          OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-          return ssl_hs_error;
-        }
-
-        hs->ech_config_id = config_id;
-        ssl->s3->ech_status = ssl_ech_accepted;
-        break;
-      }
-    }
-
-    // If we did not accept ECH, proceed with the ClientHelloOuter. Note this
-    // could be key mismatch or ECH GREASE, so we most complete the handshake
-    // as usual, except EncryptedExtensions will contain retry configs.
-    if (ssl->s3->ech_status != ssl_ech_accepted) {
-      ssl->s3->ech_status = ssl_ech_rejected;
-    }
+  uint8_t alert = SSL_AD_DECODE_ERROR;
+  if (!decrypt_ech(hs, &alert, &client_hello)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
+    return ssl_hs_error;
   }
 
-  uint8_t alert = SSL_AD_DECODE_ERROR;
+  // ECH may have changed which ClientHello we process. Update |msg| and
+  // |client_hello| in case.
+  if (!hs->GetClientHello(&msg, &client_hello)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+    return ssl_hs_error;
+  }
+
   if (!extract_sni(hs, &alert, &client_hello)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
@@ -751,12 +757,6 @@
     return ssl_hs_error;
   }
 
-  if (hs->ech_present && hs->ech_is_inner_present) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return ssl_hs_error;
-  }
-
   hs->state = state12_select_certificate;
   return ssl_hs_ok;
 }
@@ -973,8 +973,7 @@
 }
 
 static void copy_suffix(Span<uint8_t> out, Span<const uint8_t> in) {
-  out = out.subspan(out.size() - in.size());
-  assert(out.size() == in.size());
+  out = out.last(in.size());
   OPENSSL_memcpy(out.data(), in.data(), in.size());
 }
 
diff --git a/src/ssl/internal.h b/src/ssl/internal.h
index 3b7326a..ab23d29 100644
--- a/src/ssl/internal.h
+++ b/src/ssl/internal.h
@@ -146,6 +146,7 @@
 
 #include <stdlib.h>
 
+#include <initializer_list>
 #include <limits>
 #include <new>
 #include <type_traits>
@@ -693,7 +694,8 @@
   // InitHash initializes the handshake hash based on the PRF and contents of
   // the handshake transcript. Subsequent calls to |Update| will update the
   // rolling hash. It returns one on success and zero on failure. It is an error
-  // to call this function after the handshake buffer is released.
+  // to call this function after the handshake buffer is released. This may be
+  // called multiple times to change the hash function.
   bool InitHash(uint16_t version, const SSL_CIPHER *cipher);
 
   // UpdateForHelloRetryRequest resets the rolling hash with the
@@ -1449,7 +1451,7 @@
   Span<const uint8_t> public_name;
   Span<const uint8_t> cipher_suites;
   uint16_t kem_id = 0;
-  uint16_t maximum_name_length = 0;
+  uint8_t maximum_name_length = 0;
   uint8_t config_id = 0;
 };
 
@@ -1486,6 +1488,10 @@
   ssl_client_hello_outer,
 };
 
+// ECH_CLIENT_* are types for the ClientHello encrypted_client_hello extension.
+#define ECH_CLIENT_OUTER 0
+#define ECH_CLIENT_INNER 1
+
 // ssl_decode_client_hello_inner recovers the full ClientHelloInner from the
 // EncodedClientHelloInner |encoded_client_hello_inner| by replacing its
 // outer_extensions extension with the referenced extensions from the
@@ -1497,18 +1503,13 @@
     Span<const uint8_t> encoded_client_hello_inner,
     const SSL_CLIENT_HELLO *client_hello_outer);
 
-// ssl_client_hello_decrypt attempts to decrypt the given |payload| into
-// |out_encoded_client_hello_inner|. The decrypted value should be an
-// EncodedClientHelloInner. It returns false if any fatal errors occur and true
-// otherwise, regardless of whether the decrypt was successful. It sets
-// |out_encoded_client_hello_inner| to true if the decryption fails, and false
-// otherwise.
-bool ssl_client_hello_decrypt(EVP_HPKE_CTX *hpke_ctx,
-                              Array<uint8_t> *out_encoded_client_hello_inner,
+// ssl_client_hello_decrypt attempts to decrypt the |payload| and writes the
+// result to |*out|. |payload| must point into |client_hello_outer|. It returns
+// true on success and false on error. On error, it sets |*out_is_decrypt_error|
+// to whether the failure was due to a bad ciphertext.
+bool ssl_client_hello_decrypt(EVP_HPKE_CTX *hpke_ctx, Array<uint8_t> *out,
                               bool *out_is_decrypt_error,
                               const SSL_CLIENT_HELLO *client_hello_outer,
-                              uint16_t kdf_id, uint16_t aead_id,
-                              uint8_t config_id, Span<const uint8_t> enc,
                               Span<const uint8_t> payload);
 
 #define ECH_CONFIRMATION_SIGNAL_LEN 8
@@ -1518,13 +1519,14 @@
 size_t ssl_ech_confirmation_signal_hello_offset(const SSL *ssl);
 
 // ssl_ech_accept_confirmation computes the server's ECH acceptance signal,
-// writing it to |out|. The signal is computed by concatenating |transcript|
-// with |server_hello|. This function handles the fact that eight bytes of
-// |server_hello| need to be replaced with zeros before hashing. It returns true
-// on success, and false on failure.
+// writing it to |out|. The transcript portion is the concatenation of
+// |transcript| with |msg|. The |ECH_CONFIRMATION_SIGNAL_LEN| bytes from
+// |offset| in |msg| are replaced with zeros before hashing. This function
+// returns true on success, and false on failure.
 bool ssl_ech_accept_confirmation(const SSL_HANDSHAKE *hs, Span<uint8_t> out,
-                                 const SSLTranscript &transcript,
-                                 Span<const uint8_t> server_hello);
+                                 Span<const uint8_t> client_random,
+                                 const SSLTranscript &transcript, bool is_hrr,
+                                 Span<const uint8_t> msg, size_t offset);
 
 // ssl_is_valid_ech_public_name returns true if |public_name| is a valid ECH
 // public name and false otherwise. It is exported for testing.
@@ -1830,8 +1832,9 @@
   // cookie is the value of the cookie received from the server, if any.
   Array<uint8_t> cookie;
 
-  // ech_client_bytes contains the ECH extension to send in the ClientHello.
-  Array<uint8_t> ech_client_bytes;
+  // ech_client_outer contains the outer ECH extension to send in the
+  // ClientHello, excluding the header and type byte.
+  Array<uint8_t> ech_client_outer;
 
   // ech_retry_configs, on the client, contains the retry configs from the
   // server as a serialized ECHConfigList.
@@ -1939,13 +1942,9 @@
   // influence the handshake on match.
   UniquePtr<SSL_HANDSHAKE_HINTS> hints;
 
-  // ech_present, on the server, indicates whether the ClientHello contained an
-  // encrypted_client_hello extension.
-  bool ech_present : 1;
-
-  // ech_is_inner_present, on the server, indicates whether the ClientHello
-  // contained an ech_is_inner extension.
-  bool ech_is_inner_present : 1;
+  // ech_is_inner, on the server, indicates whether the ClientHello contained an
+  // inner ECH extension.
+  bool ech_is_inner : 1;
 
   // ech_authenticated_reject, on the client, indicates whether an ECH rejection
   // handshake has been authenticated.
@@ -2163,6 +2162,22 @@
 // flight. It returns true on success and false on error.
 bool ssl_add_client_hello(SSL_HANDSHAKE *hs);
 
+struct ParsedServerHello {
+  CBS raw;
+  uint16_t legacy_version = 0;
+  CBS random;
+  CBS session_id;
+  uint16_t cipher_suite = 0;
+  uint8_t compression_method = 0;
+  CBS extensions;
+};
+
+// ssl_parse_server_hello parses |msg| as a ServerHello. On success, it writes
+// the result to |*out| and returns true. Otherwise, it returns false and sets
+// |*out_alert| to an alert to send to the peer.
+bool ssl_parse_server_hello(ParsedServerHello *out, uint8_t *out_alert,
+                            const SSLMessage &msg);
+
 enum ssl_cert_verify_context_t {
   ssl_cert_verify_server,
   ssl_cert_verify_client,
@@ -2204,19 +2219,25 @@
 bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert,
                         const SSL_CLIENT_HELLO *client_hello);
 
-struct SSL_EXTENSION_TYPE {
+struct SSLExtension {
+  SSLExtension(uint16_t type_arg, bool allowed_arg = true)
+      : type(type_arg), allowed(allowed_arg), present(false) {
+    CBS_init(&data, nullptr, 0);
+  }
+
   uint16_t type;
-  bool *out_present;
-  CBS *out_data;
+  bool allowed;
+  bool present;
+  CBS data;
 };
 
 // ssl_parse_extensions parses a TLS extensions block out of |cbs| and advances
-// it. It writes the parsed extensions to pointers denoted by |ext_types|. On
-// success, it fills in the |out_present| and |out_data| fields and returns
-// true. Otherwise, it sets |*out_alert| to an alert to send and returns false.
-// Unknown extensions are rejected unless |ignore_unknown| is true.
+// it. It writes the parsed extensions to pointers in |extensions|. On success,
+// it fills in the |present| and |data| fields and returns true. Otherwise, it
+// sets |*out_alert| to an alert to send and returns false. Unknown extensions
+// are rejected unless |ignore_unknown| is true.
 bool ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
-                          Span<const SSL_EXTENSION_TYPE> ext_types,
+                          std::initializer_list<SSLExtension *> extensions,
                           bool ignore_unknown);
 
 // ssl_verify_peer_cert verifies the peer certificate for |hs|.
@@ -2255,6 +2276,9 @@
 OPENSSL_EXPORT bool ssl_client_hello_init(const SSL *ssl, SSL_CLIENT_HELLO *out,
                                           Span<const uint8_t> body);
 
+bool ssl_parse_client_hello_with_trailing_data(const SSL *ssl, CBS *cbs,
+                                               SSL_CLIENT_HELLO *out);
+
 bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
                                     CBS *out, uint16_t extension_type);
 
@@ -2315,7 +2339,7 @@
 
 #define TLSEXT_CHANNEL_ID_SIZE 128
 
-// From RFC4492, used in encoding the curve type in ECParameters
+// From RFC 4492, used in encoding the curve type in ECParameters
 #define NAMED_CURVE_TYPE 3
 
 struct CERT {
@@ -3292,19 +3316,15 @@
 // ClientHello extension was the pre_shared_key extension and needs a PSK binder
 // filled in. The caller should then update |out| and, if applicable,
 // |out_encoded| with the binder after completing the whole message.
-//
-// If |omit_ech_len| is non-zero, the ECH extension is omitted, but padding is
-// computed as if there were an extension of length |omit_ech_len|. This is used
-// to compute ClientHelloOuterAAD.
 bool ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, CBB *out_encoded,
                                 bool *out_needs_psk_binder,
-                                ssl_client_hello_type_t type, size_t header_len,
-                                size_t omit_ech_len);
+                                ssl_client_hello_type_t type,
+                                size_t header_len);
 
 bool ssl_add_serverhello_tlsext(SSL_HANDSHAKE *hs, CBB *out);
 bool ssl_parse_clienthello_tlsext(SSL_HANDSHAKE *hs,
                                   const SSL_CLIENT_HELLO *client_hello);
-bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs);
+bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, const CBS *extensions);
 
 #define tlsext_tick_md EVP_sha256
 
diff --git a/src/ssl/ssl_cipher.cc b/src/ssl/ssl_cipher.cc
index 4f5049c..60b3e2c 100644
--- a/src/ssl/ssl_cipher.cc
+++ b/src/ssl/ssl_cipher.cc
@@ -234,7 +234,7 @@
      SSL_HANDSHAKE_MAC_DEFAULT,
     },
 
-    // GCM ciphersuites from RFC5288
+    // GCM ciphersuites from RFC 5288
 
     // Cipher 9C
     {
@@ -346,7 +346,7 @@
      SSL_HANDSHAKE_MAC_DEFAULT,
     },
 
-    // GCM based TLS v1.2 ciphersuites from RFC5289
+    // GCM based TLS v1.2 ciphersuites from RFC 5289
 
     // Cipher C02B
     {
diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc
index 3c9fc90..03864e1 100644
--- a/src/ssl/ssl_lib.cc
+++ b/src/ssl/ssl_lib.cc
@@ -1023,7 +1023,7 @@
 int SSL_peek(SSL *ssl, void *buf, int num) {
   if (ssl->quic_method != nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
-    return 0;
+    return -1;
   }
 
   int ret = ssl_read_impl(ssl);
@@ -1044,7 +1044,7 @@
 
   if (ssl->quic_method != nullptr) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
-    return 0;
+    return -1;
   }
 
   if (ssl->do_handshake == NULL) {
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index 76f88c7..7ab5054 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -1675,8 +1675,8 @@
       return false;
     }
   }
-  if (!CBB_add_u16(&contents, params.max_name_len) ||
-      !CBB_add_u16_length_prefixed(&contents, &child) ||
+  if (!CBB_add_u8(&contents, params.max_name_len) ||
+      !CBB_add_u8_length_prefixed(&contents, &child) ||
       !CBB_add_bytes(
           &child, reinterpret_cast<const uint8_t *>(params.public_name.data()),
           params.public_name.size()) ||
@@ -1735,9 +1735,9 @@
 
   static const uint8_t kECHConfig[] = {
       // version
-      0xfe, 0x0a,
+      0xfe, 0x0d,
       // length
-      0x00, 0x43,
+      0x00, 0x41,
       // contents.config_id
       0x01,
       // contents.kem_id
@@ -1749,10 +1749,10 @@
       // contents.cipher_suites
       0x00, 0x08, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03,
       // contents.maximum_name_length
-      0x00, 0x10,
+      0x10,
       // contents.public_name
-      0x00, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x65, 0x78, 0x61,
-      0x6d, 0x70, 0x6c, 0x65,
+      0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d,
+      0x70, 0x6c, 0x65,
       // contents.extensions
       0x00, 0x00};
   uint8_t *ech_config;
@@ -2069,20 +2069,26 @@
   EXPECT_EQ(client_hello_len, client_hello_len_baseline);
   EXPECT_EQ(ech_len, ech_len_baseline);
 
-  size_t client_hello_len_129, ech_len_129;
-  ASSERT_TRUE(GetECHLength(ctx.get(), &client_hello_len_129, &ech_len_129, 128,
-                           std::string(129, 'a').c_str()));
-  // The padding calculation should not pad beyond the maximum.
-  EXPECT_GT(ech_len_129, ech_len_baseline);
+  // Name lengths above the maximum do not get named-based padding, but the
+  // overall input is padded to a multiple of 32.
+  size_t client_hello_len_baseline2, ech_len_baseline2;
+  ASSERT_TRUE(GetECHLength(ctx.get(), &client_hello_len_baseline2,
+                           &ech_len_baseline2, 128,
+                           std::string(128 + 32, 'a').c_str()));
+  EXPECT_EQ(ech_len_baseline2, ech_len_baseline + 32);
+  // The ClientHello lengths may match if we are still under the threshold for
+  // padding extension.
+  EXPECT_GE(client_hello_len_baseline2, client_hello_len_baseline);
 
-  // If the SNI exceeds the maximum name length, we apply some generic padding,
-  // so close name lengths still match.
-  for (size_t name_len : {129, 130, 131, 132}) {
+  for (size_t name_len = 128 + 1; name_len < 128 + 32; name_len++) {
     SCOPED_TRACE(name_len);
     ASSERT_TRUE(GetECHLength(ctx.get(), &client_hello_len, &ech_len, 128,
                              std::string(name_len, 'a').c_str()));
-    EXPECT_EQ(client_hello_len, client_hello_len_129);
-    EXPECT_EQ(ech_len, ech_len_129);
+    EXPECT_TRUE(ech_len == ech_len_baseline || ech_len == ech_len_baseline2)
+        << ech_len;
+    EXPECT_TRUE(client_hello_len == client_hello_len_baseline ||
+                client_hello_len == client_hello_len_baseline2)
+        << client_hello_len;
   }
 }
 
@@ -2113,44 +2119,39 @@
   EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span(
       "abcdefhijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ-01234567899")));
 
-  // Inputs that parse as IPv4 addresses are rejected.
+  // Inputs with trailing numeric components are rejected.
   EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("127.0.0.1")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0177.0.0.01")));
-  EXPECT_FALSE(
-      ssl_is_valid_ech_public_name(str_to_span("0x7f.0x.0x.0x00000001")));
-  EXPECT_FALSE(
-      ssl_is_valid_ech_public_name(str_to_span("0XAB.0XCD.0XEF.0X01")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0.0.0.0")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("255.255.255.255")));
-  // Out-of-bounds or overflowing components are not IP addresses.
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("256.255.255.255")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("255.0x100.255.255")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("255.255.255.0400")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.1")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.01")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.0x01")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("example.0X01")));
+  // Leading zeros and values that overflow |uint32_t| are still rejected.
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(
+      str_to_span("example.123456789000000000000000")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(
+      str_to_span("example.012345678900000000000000")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(
+      str_to_span("example.0x123456789abcdefABCDEF0")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(
+      str_to_span("example.0x0123456789abcdefABCDEF")));
+  // Adding a non-digit or non-hex character makes it a valid DNS name again.
+  // Single-component numbers are rejected.
   EXPECT_TRUE(ssl_is_valid_ech_public_name(
-      str_to_span("255.255.255.0x100000000")));
-  // Invalid characters for the base are not IP addresses.
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("12a.0.0.1")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("0xg.0.0.1")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("08.0.0.1")));
-  // Trailing components can be merged into a single component.
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("127.0.1")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("127.1")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("2130706433")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0x7f000001")));
-  // Merged components must respect their limits.
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0.0.0.0xff")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0.0.0xffff")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0.0xffffff")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0xffffffff")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("0.0.0.0x100")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("0.0.0x10000")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("0.0x1000000")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("0x100000000")));
-  // Correctly handle overflow in decimal and octal.
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("037777777777")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("040000000000")));
-  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("4294967295")));
-  EXPECT_TRUE(ssl_is_valid_ech_public_name(str_to_span("4294967296")));
+      str_to_span("example.1234567890a")));
+  EXPECT_TRUE(ssl_is_valid_ech_public_name(
+      str_to_span("example.01234567890a")));
+  EXPECT_TRUE(ssl_is_valid_ech_public_name(
+      str_to_span("example.0x123456789abcdefg")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("1")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("01")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0x01")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0X01")));
+  // Numbers with trailing dots are rejected. (They are already rejected by the
+  // LDH label rules, but the WHATWG URL parser additionally rejects them.)
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("1.")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("01.")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0x01.")));
+  EXPECT_FALSE(ssl_is_valid_ech_public_name(str_to_span("0X01.")));
 }
 
 // When using the built-in verifier, test that |SSL_get0_ech_name_override| is
diff --git a/src/ssl/ssl_transcript.cc b/src/ssl/ssl_transcript.cc
index 1599c80..58fd21e 100644
--- a/src/ssl/ssl_transcript.cc
+++ b/src/ssl/ssl_transcript.cc
@@ -158,20 +158,14 @@
   return true;
 }
 
-// InitDigestWithData calls |EVP_DigestInit_ex| on |ctx| with |md| and then
-// writes the data in |buf| to it.
-static bool InitDigestWithData(EVP_MD_CTX *ctx, const EVP_MD *md,
-                               const BUF_MEM *buf) {
-  if (!EVP_DigestInit_ex(ctx, md, NULL)) {
-    return false;
-  }
-  EVP_DigestUpdate(ctx, buf->data, buf->length);
-  return true;
-}
-
 bool SSLTranscript::InitHash(uint16_t version, const SSL_CIPHER *cipher) {
   const EVP_MD *md = ssl_get_handshake_digest(version, cipher);
-  return InitDigestWithData(hash_.get(), md, buffer_.get());
+  if (Digest() == md) {
+    // No need to re-hash the buffer.
+    return true;
+  }
+  return EVP_DigestInit_ex(hash_.get(), md, nullptr) &&
+         EVP_DigestUpdate(hash_.get(), buffer_->data, buffer_->length);
 }
 
 void SSLTranscript::FreeBuffer() {
diff --git a/src/ssl/ssl_x509.cc b/src/ssl/ssl_x509.cc
index 98f1f6a..680f253 100644
--- a/src/ssl/ssl_x509.cc
+++ b/src/ssl/ssl_x509.cc
@@ -379,8 +379,9 @@
   const char *name;
   size_t name_len;
   SSL_get0_ech_name_override(ssl, &name, &name_len);
-  ScopedX509_STORE_CTX ctx;
-  if (!X509_STORE_CTX_init(ctx.get(), verify_store, leaf, cert_chain) ||
+  UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new());
+  if (!ctx ||
+      !X509_STORE_CTX_init(ctx.get(), verify_store, leaf, cert_chain) ||
       !X509_STORE_CTX_set_ex_data(ctx.get(),
                                   SSL_get_ex_data_X509_STORE_CTX_idx(), ssl) ||
       // We need to inherit the verify parameters. These can be determined by
@@ -411,11 +412,11 @@
     verify_ret = X509_verify_cert(ctx.get());
   }
 
-  session->verify_result = ctx->error;
+  session->verify_result = X509_STORE_CTX_get_error(ctx.get());
 
   // If |SSL_VERIFY_NONE|, the error is non-fatal, but we keep the result.
   if (verify_ret <= 0 && hs->config->verify_mode != SSL_VERIFY_NONE) {
-    *out_alert = SSL_alert_from_verify_result(ctx->error);
+    *out_alert = SSL_alert_from_verify_result(session->verify_result);
     return false;
   }
 
@@ -464,9 +465,9 @@
     return false;
   }
 
-  ScopedX509_STORE_CTX ctx;
-  if (!X509_STORE_CTX_init(ctx.get(), hs->ssl->ctx->cert_store, leaf.get(),
-                           NULL)) {
+  UniquePtr<X509_STORE_CTX> ctx(X509_STORE_CTX_new());
+  if (!ctx || !X509_STORE_CTX_init(ctx.get(), hs->ssl->ctx->cert_store,
+                                   leaf.get(), nullptr)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB);
     return false;
   }
@@ -476,9 +477,13 @@
   ERR_clear_error();
 
   // Remove the leaf from the generated chain.
-  X509_free(sk_X509_shift(ctx->chain));
+  UniquePtr<STACK_OF(X509)> chain(X509_STORE_CTX_get1_chain(ctx.get()));
+  if (!chain) {
+    return false;
+  }
+  X509_free(sk_X509_shift(chain.get()));
 
-  if (!ssl_cert_set_chain(hs->config->cert.get(), ctx->chain)) {
+  if (!ssl_cert_set_chain(hs->config->cert.get(), chain.get())) {
     return false;
   }
 
@@ -706,13 +711,6 @@
   return X509_STORE_load_locations(ctx->cert_store, ca_file, ca_dir);
 }
 
-void SSL_set_verify_result(SSL *ssl, long result) {
-  check_ssl_x509_method(ssl);
-  if (result != X509_V_OK) {
-    abort();
-  }
-}
-
 long SSL_get_verify_result(const SSL *ssl) {
   check_ssl_x509_method(ssl);
   SSL_SESSION *session = SSL_get_session(ssl);
diff --git a/src/ssl/test/fuzzer.h b/src/ssl/test/fuzzer.h
index 509cfdb..00b5e84 100644
--- a/src/ssl/test/fuzzer.h
+++ b/src/ssl/test/fuzzer.h
@@ -231,16 +231,6 @@
     0x01, 'a', 0x02, 'a', 'a', 0x03, 'a', 'a', 'a',
 };
 
-const uint8_t kECHConfig[] = {
-    0xfe, 0x0a, 0x00, 0x47, 0x2a, 0x00, 0x20, 0x00, 0x20, 0x6c, 0x55,
-    0x96, 0x41, 0x3d, 0x12, 0x4e, 0x63, 0x3d, 0x39, 0x7a, 0xe9, 0xbc,
-    0xec, 0xb2, 0x55, 0xd0, 0xe6, 0xaa, 0xbd, 0xa9, 0x79, 0xb8, 0x86,
-    0x9a, 0x13, 0x61, 0xc6, 0x69, 0xac, 0xb4, 0x21, 0x00, 0x0c, 0x00,
-    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x03,
-    0x00, 0x10, 0x00, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e,
-    0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00, 0x00,
-};
-
 const uint8_t kECHKey[] = {
     0x35, 0x6d, 0x45, 0x06, 0xb3, 0x88, 0x89, 0x2e, 0xd6, 0x87, 0x84,
     0xd2, 0x2d, 0x6f, 0x83, 0x48, 0xad, 0xf2, 0xfd, 0x08, 0x51, 0x73,
@@ -458,11 +448,20 @@
     if (role_ == kServer) {
       bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
       bssl::ScopedEVP_HPKE_KEY key;
+      uint8_t *ech_config;
+      size_t ech_config_len;
       if (!keys ||
           !EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), kECHKey,
                              sizeof(kECHKey)) ||
-          !SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/true, kECHConfig,
-                            sizeof(kECHConfig), key.get()) ||
+          // Match |echConfig| in |addEncryptedClientHelloTests| from runner.go.
+          !SSL_marshal_ech_config(&ech_config, &ech_config_len,
+                                  /*config_id=*/42, key.get(), "public.example",
+                                  /*max_name_len=*/64)) {
+        return false;
+      }
+      bssl::UniquePtr<uint8_t> free_ech_config(ech_config);
+      if (!SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/true, ech_config,
+                            ech_config_len, key.get()) ||
           !SSL_CTX_set1_ech_keys(ctx_.get(), keys.get())) {
         return false;
       }
diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go
index 782eb36..bf6a3d1 100644
--- a/src/ssl/test/runner/common.go
+++ b/src/ssl/test/runner/common.go
@@ -128,8 +128,7 @@
 	extensionChannelID                  uint16 = 30032  // not IANA assigned
 	extensionDelegatedCredentials       uint16 = 0x22   // draft-ietf-tls-subcerts-06
 	extensionDuplicate                  uint16 = 0xffff // not IANA assigned
-	extensionEncryptedClientHello       uint16 = 0xfe0a // not IANA assigned
-	extensionECHIsInner                 uint16 = 0xda09 // not IANA assigned
+	extensionEncryptedClientHello       uint16 = 0xfe0d // not IANA assigned
 	extensionECHOuterExtensions         uint16 = 0xfd00 // not IANA assigned
 )
 
@@ -176,7 +175,7 @@
 	CertTypeRSAFixedDH = 3 // A certificate containing a static DH key
 	CertTypeDSSFixedDH = 4 // A certificate containing a static DH key
 
-	// See RFC4492 sections 3 and 5.5.
+	// See RFC 4492 sections 3 and 5.5.
 	CertTypeECDSASign      = 64 // A certificate containing an ECDSA-capable public key, signed with ECDSA.
 	CertTypeRSAFixedECDH   = 65 // A certificate containing an ECDH-capable public key, signed with RSA.
 	CertTypeECDSAFixedECDH = 66 // A certificate containing an ECDH-capable public key, signed with ECDSA.
@@ -243,6 +242,9 @@
 	keyUpdateRequested    = 1
 )
 
+// draft-ietf-tls-esni-13, sections 7.2 and 7.2.1.
+const echAcceptConfirmationLength = 8
+
 // ConnectionState records basic TLS details about the connection.
 type ConnectionState struct {
 	Version                    uint16                // TLS version used by the connection (e.g. VersionTLS12)
@@ -869,40 +871,53 @@
 	// retry configs.
 	SendECHRetryConfigs []byte
 
-	// SendECHRetryConfigsInTLS12ServerHello, if true, causes the ECH server to
-	// send retry configs in the TLS 1.2 ServerHello.
-	SendECHRetryConfigsInTLS12ServerHello bool
+	// AlwaysSendECHRetryConfigs, if true, causes the ECH server to send retry
+	// configs unconditionally, including in the TLS 1.2 ServerHello.
+	AlwaysSendECHRetryConfigs bool
 
-	// SendInvalidECHIsInner, if not empty, causes the client to send the
-	// specified byte string in the ech_is_inner extension.
-	SendInvalidECHIsInner []byte
+	// AlwaysSendECHHelloRetryRequest, if true, causes the ECH server to send
+	// the ECH HelloRetryRequest extension unconditionally.
+	AlwaysSendECHHelloRetryRequest bool
 
-	// OmitECHIsInner, if true, causes the client to omit the ech_is_inner
+	// SendInvalidECHInner, if not empty, causes the client to send the
+	// specified byte string after the type field in ClientHelloInner
+	// encrypted_client_hello extension.
+	SendInvalidECHInner []byte
+
+	// OmitECHInner, if true, causes the client to omit the encrypted_client_hello
 	// extension on the ClientHelloInner message.
-	OmitECHIsInner bool
+	OmitECHInner bool
 
-	// OmitSecondECHIsInner, if true, causes the client to omit the ech_is_inner
-	// extension on the second ClientHelloInner message.
-	OmitSecondECHIsInner bool
+	// OmitSecondECHInner, if true, causes the client to omit the
+	// encrypted_client_hello extension on the second ClientHelloInner message.
+	OmitSecondECHInner bool
 
-	// AlwaysSendECHIsInner, if true, causes the client to send the
-	// ech_is_inner extension on all ClientHello messages. The server is then
-	// expected to unconditionally confirm the extension when negotiating
-	// TLS 1.3 or later.
-	AlwaysSendECHIsInner bool
+	// OmitServerHelloECHConfirmation, if true, causes the server to omit the
+	// ECH confirmation in the ServerHello.
+	OmitServerHelloECHConfirmation bool
+
+	// AlwaysSendECHInner, if true, causes the client to send an inner
+	// encrypted_client_hello extension on all ClientHello messages. The server
+	// is then expected to unconditionally confirm the extension when
+	// negotiating TLS 1.3 or later.
+	AlwaysSendECHInner bool
 
 	// TruncateClientECHEnc, if true, causes the client to send a shortened
 	// ClientECH.enc value in its encrypted_client_hello extension.
 	TruncateClientECHEnc bool
 
+	// ClientECHPadding is the number of bytes of padding to add to the client
+	// ECH payload.
+	ClientECHPadding int
+
+	// BadClientECHPadding, if true, causes the client ECH padding to contain a
+	// non-zero byte.
+	BadClientECHPadding bool
+
 	// OfferSessionInClientHelloOuter, if true, causes the client to offer
 	// sessions in ClientHelloOuter.
 	OfferSessionInClientHelloOuter bool
 
-	// FirstExtensionInClientHelloOuter, if non-zero, causes the client to place
-	// the specified extension first in ClientHelloOuter.
-	FirstExtensionInClientHelloOuter uint16
-
 	// OnlyCompressSecondClientHelloInner, if true, causes the client to
 	// only apply outer_extensions to the second ClientHello.
 	OnlyCompressSecondClientHelloInner bool
@@ -941,6 +956,11 @@
 	// will require to be omitted in ech_outer_extensions.
 	ExpectECHUncompressedExtensions []uint16
 
+	// ECHOuterExtensionOrder, if not nil, is an extension order to apply to
+	// ClientHelloOuter, instead of ordering the |ECHOuterExtensions| to match
+	// in both ClientHellos.
+	ECHOuterExtensionOrder []uint16
+
 	// UseInnerSessionWithClientHelloOuter, if true, causes the server to
 	// handshake with ClientHelloOuter, but resume the session from
 	// ClientHelloInner.
@@ -1011,6 +1031,10 @@
 	// normally expected to look ahead for ChangeCipherSpec.)
 	EmptyTicketSessionID bool
 
+	// NewSessionIDLength, if non-zero is the length of the session ID to use
+	// when issung new sessions.
+	NewSessionIDLength int
+
 	// SendClientHelloSessionID, if not nil, is the session ID sent in the
 	// ClientHello.
 	SendClientHelloSessionID []byte
diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go
index 424b206..5d04994 100644
--- a/src/ssl/test/runner/handshake_client.go
+++ b/src/ssl/test/runner/handshake_client.go
@@ -26,19 +26,18 @@
 const echBadPayloadByte = 0xff
 
 type clientHandshakeState struct {
-	c                 *Conn
-	serverHello       *serverHelloMsg
-	hello             *clientHelloMsg
-	innerHello        *clientHelloMsg
-	echHPKEContext    *hpke.Context
-	suite             *cipherSuite
-	finishedHash      finishedHash
-	innerFinishedHash finishedHash
-	keyShares         map[CurveID]ecdhCurve
-	masterSecret      []byte
-	session           *ClientSessionState
-	finishedBytes     []byte
-	peerPublicKey     crypto.PublicKey
+	c              *Conn
+	serverHello    *serverHelloMsg
+	hello          *clientHelloMsg
+	innerHello     *clientHelloMsg
+	echHPKEContext *hpke.Context
+	suite          *cipherSuite
+	finishedHash   finishedHash
+	keyShares      map[CurveID]ecdhCurve
+	masterSecret   []byte
+	session        *ClientSessionState
+	finishedBytes  []byte
+	peerPublicKey  crypto.PublicKey
 }
 
 func mapClientHelloVersion(vers uint16, isDTLS bool) uint16 {
@@ -340,10 +339,6 @@
 
 	hs.finishedHash = newFinishedHash(c.wireVersion, c.isDTLS, hs.suite)
 	hs.finishedHash.WriteHandshake(hs.hello.marshal(), hs.c.sendHandshakeSeq-1)
-	if hs.innerHello != nil {
-		hs.innerFinishedHash = newFinishedHash(c.wireVersion, c.isDTLS, hs.suite)
-		hs.innerFinishedHash.WriteHandshake(hs.innerHello.marshal(), hs.c.sendHandshakeSeq-1)
-	}
 
 	if c.vers >= VersionTLS13 {
 		if err := hs.doTLS13Handshake(msg); err != nil {
@@ -533,9 +528,6 @@
 	// list of prefix extensions. The marshal function will try these
 	// extensions before any others, followed by any remaining extensions in
 	// the default order.
-	if innerHello != nil && c.config.Bugs.FirstExtensionInClientHelloOuter != 0 {
-		hello.prefixExtensions = append(hello.prefixExtensions, c.config.Bugs.FirstExtensionInClientHelloOuter)
-	}
 	if c.config.Bugs.PSKBinderFirst && !c.config.Bugs.OnlyCorruptSecondPSKBinder {
 		hello.prefixExtensions = append(hello.prefixExtensions, extensionPreSharedKey)
 	}
@@ -543,8 +535,24 @@
 		hello.prefixExtensions = append(hello.prefixExtensions, extensionALPN)
 		hello.prefixExtensions = append(hello.prefixExtensions, extensionNextProtoNeg)
 	}
-	if isInner && len(c.config.ECHOuterExtensions) > 0 && !c.config.Bugs.OnlyCompressSecondClientHelloInner {
-		applyECHOuterExtensions(hello, c.config.ECHOuterExtensions)
+
+	// Configure ech_outer_extensions.
+	if isInner {
+		hello.outerExtensions = c.config.ECHOuterExtensions
+		// If |OnlyCompressSecondClientHelloInner| is set, we still configure
+		// |hello.outerExtensions| for ordering, so that we do not introduce an
+		// unsolicited change across HelloRetryRequest.
+		hello.reorderOuterExtensionsWithoutCompressing = c.config.Bugs.OnlyCompressSecondClientHelloInner
+	} else {
+		// Compressed extensions must appear in the same relative order between
+		// ClientHelloInner and ClientHelloOuter. For simplicity, we default to
+		// forcing their order to match, but the caller can override this with
+		// either valid or invalid explicit orders.
+		if c.config.Bugs.ECHOuterExtensionOrder != nil {
+			hello.prefixExtensions = append(hello.prefixExtensions, c.config.Bugs.ECHOuterExtensionOrder...)
+		} else {
+			hello.prefixExtensions = append(hello.prefixExtensions, c.config.ECHOuterExtensions...)
+		}
 	}
 
 	if maxVersion >= VersionTLS13 {
@@ -816,9 +824,9 @@
 		hello.hasEarlyData = innerHello.hasEarlyData
 	}
 
-	if (isInner && !c.config.Bugs.OmitECHIsInner) || c.config.Bugs.AlwaysSendECHIsInner {
-		hello.echIsInner = true
-		hello.invalidECHIsInner = c.config.Bugs.SendInvalidECHIsInner
+	if (isInner && !c.config.Bugs.OmitECHInner) || c.config.Bugs.AlwaysSendECHInner {
+		hello.echInner = true
+		hello.invalidECHInner = c.config.Bugs.SendInvalidECHInner
 	}
 
 	if innerHello != nil {
@@ -827,9 +835,9 @@
 		}
 		if c.config.Bugs.CorruptEncryptedClientHello {
 			if c.config.Bugs.NullAllCiphers {
-				hello.clientECH.payload = []byte{echBadPayloadByte}
+				hello.echOuter.payload = []byte{echBadPayloadByte}
 			} else {
-				hello.clientECH.payload[0] ^= 1
+				hello.echOuter.payload[0] ^= 1
 			}
 		}
 	}
@@ -878,27 +886,31 @@
 		enc = enc[:1]
 	}
 
-	aad := newByteBuilder()
-	aad.addU16(hs.echHPKEContext.KDF())
-	aad.addU16(hs.echHPKEContext.AEAD())
-	aad.addU8(configID)
-	aad.addU16LengthPrefixed().addBytes(enc)
-	hello.marshalForOuterAAD(aad.addU24LengthPrefixed())
-
 	encodedInner := innerHello.marshalForEncodedInner()
-	payload := hs.echHPKEContext.Seal(encodedInner, aad.finish())
-
-	if c.config.Bugs.NullAllCiphers {
-		payload = encodedInner
+	padding := make([]byte, c.config.Bugs.ClientECHPadding)
+	if c.config.Bugs.BadClientECHPadding {
+		padding[0] = 1
 	}
+	encodedInner = append(encodedInner, padding...)
 
-	// Place the ECH extension in the outer CH.
-	hello.clientECH = &clientECH{
+	// Encode ClientHelloOuter with a placeholder payload string.
+	payloadLength := len(encodedInner)
+	if !c.config.Bugs.NullAllCiphers {
+		payloadLength += hs.echHPKEContext.Overhead()
+	}
+	hello.echOuter = &echClientOuter{
 		kdfID:    hs.echHPKEContext.KDF(),
 		aeadID:   hs.echHPKEContext.AEAD(),
 		configID: configID,
 		enc:      enc,
-		payload:  payload,
+		payload:  make([]byte, payloadLength),
+	}
+	aad := hello.marshal()[4:] // Remove message header
+
+	hello.raw = nil
+	hello.echOuter.payload = hs.echHPKEContext.Seal(encodedInner, aad)
+	if c.config.Bugs.NullAllCiphers {
+		hello.echOuter.payload = encodedInner
 	}
 
 	if c.config.Bugs.RecordClientHelloInner != nil {
@@ -913,24 +925,73 @@
 	return nil
 }
 
+func (hs *clientHandshakeState) checkECHConfirmation(msg interface{}, hello *clientHelloMsg, finishedHash *finishedHash) bool {
+	var offset int
+	var raw, label []byte
+	if hrr, ok := msg.(*helloRetryRequestMsg); ok {
+		if hrr.echConfirmationOffset == 0 {
+			return false
+		}
+		raw = hrr.raw
+		label = echAcceptConfirmationHRRLabel
+		offset = hrr.echConfirmationOffset
+	} else {
+		raw = msg.(*serverHelloMsg).raw
+		label = echAcceptConfirmationLabel
+		offset = 4 + 2 + 32 - echAcceptConfirmationLength
+	}
+
+	withZeros := append(make([]byte, 0, len(raw)), raw...)
+	for i := 0; i < echAcceptConfirmationLength; i++ {
+		withZeros[i+offset] = 0
+	}
+
+	confirmation := finishedHash.echAcceptConfirmation(hello.random, label, withZeros)
+	return bytes.Equal(confirmation, raw[offset:offset+echAcceptConfirmationLength])
+}
+
 func (hs *clientHandshakeState) doTLS13Handshake(msg interface{}) error {
 	c := hs.c
 
+	// The first message may be a ServerHello or HelloRetryRequest.
+	helloRetryRequest, haveHelloRetryRequest := msg.(*helloRetryRequestMsg)
+	if haveHelloRetryRequest {
+		hs.finishedHash.UpdateForHelloRetryRequest()
+	}
+
+	// Determine whether the server accepted ECH and drop the unnecessary
+	// transcript.
+	if hs.innerHello != nil {
+		innerFinishedHash := newFinishedHash(c.wireVersion, c.isDTLS, hs.suite)
+		innerFinishedHash.WriteHandshake(hs.innerHello.marshal(), hs.c.sendHandshakeSeq-1)
+		if haveHelloRetryRequest {
+			innerFinishedHash.UpdateForHelloRetryRequest()
+		}
+		if hs.checkECHConfirmation(msg, hs.innerHello, &innerFinishedHash) {
+			c.echAccepted = true
+			// Replace the transcript. For now, leave hs.hello and hs.innerHello
+			// as-is. HelloRetryRequest requires both be available.
+			hs.finishedHash = innerFinishedHash
+		}
+	} else {
+		// When not offering ECH, test that the backend server does not (or does)
+		// send a confirmation as expected.
+		confirmed := hs.checkECHConfirmation(msg, hs.hello, &hs.finishedHash)
+		if hs.hello.echInner && !confirmed {
+			return fmt.Errorf("tls: server did not send ECH confirmation in %T when requested", msg)
+		} else if !hs.hello.echInner && confirmed {
+			return fmt.Errorf("tls: server sent ECH confirmation in %T when not requested", msg)
+		}
+	}
+
 	// Once the PRF hash is known, TLS 1.3 does not require a handshake buffer.
 	hs.finishedHash.discardHandshakeBuffer()
 
 	// The first server message must be followed by a ChangeCipherSpec.
 	c.expectTLS13ChangeCipherSpec = true
 
-	// The first message may be a ServerHello or HelloRetryRequest.
-	helloRetryRequest, haveHelloRetryRequest := msg.(*helloRetryRequestMsg)
 	if haveHelloRetryRequest {
-		hs.finishedHash.UpdateForHelloRetryRequest()
 		hs.writeServerHash(helloRetryRequest.marshal())
-		if hs.innerHello != nil {
-			hs.innerFinishedHash.UpdateForHelloRetryRequest()
-			hs.innerFinishedHash.WriteHandshake(helloRetryRequest.marshal(), c.recvHandshakeSeq-1)
-		}
 
 		if c.config.Bugs.FailIfHelloRetryRequested {
 			return errors.New("tls: unexpected HelloRetryRequest")
@@ -944,20 +1005,17 @@
 		// Reset the encryption state, in case we sent 0-RTT data.
 		c.out.resetCipher()
 
-		if hs.innerHello != nil {
-			if err := hs.applyHelloRetryRequest(helloRetryRequest, hs.innerHello, nil); err != nil {
+		if c.echAccepted {
+			if err := hs.applyHelloRetryRequest(helloRetryRequest, hs.innerHello, hs.hello); err != nil {
 				return err
 			}
-			if err := hs.applyHelloRetryRequest(helloRetryRequest, hs.hello, hs.innerHello); err != nil {
-				return err
-			}
-			hs.innerFinishedHash.WriteHandshake(hs.innerHello.marshal(), c.sendHandshakeSeq)
+			hs.writeClientHash(hs.innerHello.marshal())
 		} else {
 			if err := hs.applyHelloRetryRequest(helloRetryRequest, hs.hello, nil); err != nil {
 				return err
 			}
+			hs.writeClientHash(hs.hello.marshal())
 		}
-		hs.writeClientHash(hs.hello.marshal())
 		toWrite := hs.hello.marshal()
 
 		if c.config.Bugs.PartialSecondClientHelloAfterFirst {
@@ -990,6 +1048,12 @@
 		}
 	}
 
+	// We no longer need to retain two ClientHellos.
+	if c.echAccepted {
+		hs.hello = hs.innerHello
+	}
+	hs.innerHello = nil
+
 	var ok bool
 	hs.serverHello, ok = msg.(*serverHelloMsg)
 	if !ok {
@@ -1013,9 +1077,19 @@
 		return fmt.Errorf("tls: server sent non-matching cipher suite %04x vs %04x", hs.suite.id, hs.serverHello.cipherSuite)
 	}
 
-	if haveHelloRetryRequest && helloRetryRequest.hasSelectedGroup && helloRetryRequest.selectedGroup != hs.serverHello.keyShare.group {
-		c.sendAlert(alertHandshakeFailure)
-		return errors.New("tls: ServerHello parameters did not match HelloRetryRequest")
+	if haveHelloRetryRequest {
+		if helloRetryRequest.hasSelectedGroup && helloRetryRequest.selectedGroup != hs.serverHello.keyShare.group {
+			c.sendAlert(alertHandshakeFailure)
+			return errors.New("tls: ServerHello parameters did not match HelloRetryRequest")
+		}
+
+		// Both the ServerHello and HelloRetryRequest must have an ECH confirmation.
+		echConfirmed := hs.checkECHConfirmation(hs.serverHello, hs.hello, &hs.finishedHash)
+		if hs.hello.echInner && !echConfirmed {
+			return errors.New("tls: server did not send ECH confirmation in ServerHello when requested")
+		} else if !hs.hello.echInner && echConfirmed {
+			return errors.New("tls: server sent ECH confirmation in ServerHello when not requested")
+		}
 	}
 
 	if !bytes.Equal(hs.hello.sessionID, hs.serverHello.sessionID) {
@@ -1039,9 +1113,6 @@
 		c.didResume = true
 	}
 	hs.finishedHash.addEntropy(pskSecret)
-	if hs.innerHello != nil {
-		hs.innerFinishedHash.addEntropy(pskSecret)
-	}
 
 	if !hs.serverHello.hasKeyShare {
 		c.sendAlert(alertUnsupportedExtension)
@@ -1066,39 +1137,6 @@
 	}
 	hs.finishedHash.nextSecret()
 	hs.finishedHash.addEntropy(ecdheSecret)
-	if hs.innerHello != nil {
-		hs.innerFinishedHash.nextSecret()
-		hs.innerFinishedHash.addEntropy(ecdheSecret)
-	}
-
-	// Determine whether the server accepted ECH.
-	confirmHash := &hs.finishedHash
-	if hs.innerHello != nil {
-		confirmHash = &hs.innerFinishedHash
-	}
-	echConfirmed := bytes.Equal(hs.serverHello.random[24:], confirmHash.deriveSecretPeek([]byte("ech accept confirmation"), hs.serverHello.marshalForECHConf())[:8])
-	if hs.innerHello != nil {
-		c.echAccepted = echConfirmed
-		if c.echAccepted {
-			hs.hello = hs.innerHello
-			hs.finishedHash = hs.innerFinishedHash
-		}
-		hs.innerHello = nil
-		hs.innerFinishedHash = finishedHash{}
-	} else {
-		// When not offering ECH, we may still expect a confirmation signal to
-		// test the backend server behavior.
-		if hs.hello.echIsInner {
-			if !echConfirmed {
-				return errors.New("tls: server did not send ECH confirmation when requested")
-			}
-		} else {
-			if echConfirmed {
-				return errors.New("tls: server did sent ECH confirmation when not requested")
-			}
-		}
-	}
-
 	hs.writeServerHash(hs.serverHello.marshal())
 
 	// Derive handshake traffic keys and switch read key to handshake
@@ -1465,58 +1503,53 @@
 }
 
 // applyHelloRetryRequest updates |hello| in-place based on |helloRetryRequest|.
-// If |innerHello| is not nil, this is the second ClientHelloOuter and should
-// contain an encrypted copy of |innerHello|
-func (hs *clientHandshakeState) applyHelloRetryRequest(helloRetryRequest *helloRetryRequestMsg, hello, innerHello *clientHelloMsg) error {
+// If |outerHello| is not nil, |outerHello| will be updated to contain an
+// encrypted copy of |hello|.
+func (hs *clientHandshakeState) applyHelloRetryRequest(helloRetryRequest *helloRetryRequestMsg, hello, outerHello *clientHelloMsg) error {
 	c := hs.c
 	firstHelloBytes := hello.marshal()
-	isInner := innerHello == nil && hs.echHPKEContext != nil
 	if len(helloRetryRequest.cookie) > 0 {
 		hello.tls13Cookie = helloRetryRequest.cookie
 	}
 
-	if innerHello != nil {
-		hello.keyShares = innerHello.keyShares
-	} else {
-		if c.config.Bugs.MisinterpretHelloRetryRequestCurve != 0 {
-			helloRetryRequest.hasSelectedGroup = true
-			helloRetryRequest.selectedGroup = c.config.Bugs.MisinterpretHelloRetryRequestCurve
+	if c.config.Bugs.MisinterpretHelloRetryRequestCurve != 0 {
+		helloRetryRequest.hasSelectedGroup = true
+		helloRetryRequest.selectedGroup = c.config.Bugs.MisinterpretHelloRetryRequestCurve
+	}
+	if helloRetryRequest.hasSelectedGroup {
+		var hrrCurveFound bool
+		group := helloRetryRequest.selectedGroup
+		for _, curveID := range hello.supportedCurves {
+			if group == curveID {
+				hrrCurveFound = true
+				break
+			}
 		}
-		if helloRetryRequest.hasSelectedGroup {
-			var hrrCurveFound bool
-			group := helloRetryRequest.selectedGroup
-			for _, curveID := range hello.supportedCurves {
-				if group == curveID {
-					hrrCurveFound = true
-					break
-				}
-			}
-			if !hrrCurveFound || hs.keyShares[group] != nil {
-				c.sendAlert(alertHandshakeFailure)
-				return errors.New("tls: received invalid HelloRetryRequest")
-			}
-			curve, ok := curveForCurveID(group, c.config)
-			if !ok {
-				return errors.New("tls: Unable to get curve requested in HelloRetryRequest")
-			}
-			publicKey, err := curve.offer(c.config.rand())
-			if err != nil {
-				return err
-			}
-			hs.keyShares[group] = curve
-			hello.keyShares = []keyShareEntry{{
-				group:       group,
-				keyExchange: publicKey,
-			}}
+		if !hrrCurveFound || hs.keyShares[group] != nil {
+			c.sendAlert(alertHandshakeFailure)
+			return errors.New("tls: received invalid HelloRetryRequest")
 		}
-
-		if c.config.Bugs.SecondClientHelloMissingKeyShare {
-			hello.hasKeyShares = false
+		curve, ok := curveForCurveID(group, c.config)
+		if !ok {
+			return errors.New("tls: Unable to get curve requested in HelloRetryRequest")
 		}
+		publicKey, err := curve.offer(c.config.rand())
+		if err != nil {
+			return err
+		}
+		hs.keyShares[group] = curve
+		hello.keyShares = []keyShareEntry{{
+			group:       group,
+			keyExchange: publicKey,
+		}}
 	}
 
-	if isInner && c.config.Bugs.OmitSecondECHIsInner {
-		hello.echIsInner = false
+	if c.config.Bugs.SecondClientHelloMissingKeyShare {
+		hello.hasKeyShares = false
+	}
+
+	if c.config.Bugs.OmitSecondECHInner {
+		hello.echInner = false
 	}
 
 	hello.hasEarlyData = c.config.Bugs.SendEarlyDataOnSecondClientHello
@@ -1524,56 +1557,51 @@
 	if c.config.Bugs.PSKBinderFirst && c.config.Bugs.OnlyCorruptSecondPSKBinder {
 		hello.prefixExtensions = append(hello.prefixExtensions, extensionPreSharedKey)
 	}
-	// The first ClientHello may have skipped this due to OnlyCompressSecondClientHelloInner.
-	if isInner && len(c.config.ECHOuterExtensions) > 0 && c.config.Bugs.OnlyCompressSecondClientHelloInner {
-		applyECHOuterExtensions(hello, c.config.ECHOuterExtensions)
-	}
+	// The first ClientHello may have set this due to OnlyCompressSecondClientHelloInner.
+	hello.reorderOuterExtensionsWithoutCompressing = false
 	if c.config.Bugs.OmitPSKsOnSecondClientHello {
 		hello.pskIdentities = nil
 		hello.pskBinders = nil
 	}
 	hello.raw = nil
 
-	if innerHello != nil {
+	if len(hello.pskIdentities) > 0 {
+		generatePSKBinders(c.wireVersion, hello, hs.session, firstHelloBytes, helloRetryRequest.marshal(), c.config)
+	}
+
+	if outerHello != nil {
+		outerHello.raw = nil
+		// We know the server has accepted ECH, so the ClientHelloOuter's fields
+		// are irrelevant. In the general case, the HelloRetryRequest may not
+		// even be valid for ClientHelloOuter. However, we copy the key shares
+		// from ClientHelloInner so they remain eligible for compression.
+		if !c.config.Bugs.MinimalClientHelloOuter {
+			outerHello.keyShares = hello.keyShares
+		}
+
 		if c.config.Bugs.OmitSecondEncryptedClientHello {
-			hello.clientECH = nil
+			outerHello.echOuter = nil
 		} else {
 			configID := c.config.ClientECHConfig.ConfigID
 			if c.config.Bugs.CorruptSecondEncryptedClientHelloConfigID {
 				configID ^= 1
 			}
-			if err := hs.encryptClientHello(hello, innerHello, configID, nil); err != nil {
+			if err := hs.encryptClientHello(outerHello, hello, configID, nil); err != nil {
 				return err
 			}
 			if c.config.Bugs.CorruptSecondEncryptedClientHello {
 				if c.config.Bugs.NullAllCiphers {
-					hello.clientECH.payload = []byte{echBadPayloadByte}
+					outerHello.echOuter.payload = []byte{echBadPayloadByte}
 				} else {
-					hello.clientECH.payload[0] ^= 1
+					outerHello.echOuter.payload[0] ^= 1
 				}
 			}
 		}
 	}
 
-	// PSK binders and ECH both must be inserted last because they incorporate
-	// the rest of the ClientHello and conflict. See corresponding comment in
-	// |createClientHello|.
-	if len(hello.pskIdentities) > 0 {
-		generatePSKBinders(c.wireVersion, hello, hs.session, firstHelloBytes, helloRetryRequest.marshal(), c.config)
-	}
 	return nil
 }
 
-// applyECHOuterExtensions updates |hello| to compress |outerExtensions| with
-// the ech_outer_extensions mechanism.
-func applyECHOuterExtensions(hello *clientHelloMsg, outerExtensions []uint16) {
-	// Ensure that the ech_outer_extensions extension and each of the
-	// extensions it names are serialized consecutively.
-	hello.prefixExtensions = append(hello.prefixExtensions, extensionECHOuterExtensions)
-	hello.prefixExtensions = append(hello.prefixExtensions, outerExtensions...)
-	hello.outerExtensions = outerExtensions
-}
-
 func (hs *clientHandshakeState) doFullHandshake() error {
 	c := hs.c
 
diff --git a/src/ssl/test/runner/handshake_messages.go b/src/ssl/test/runner/handshake_messages.go
index d666a87..f2ef2fc 100644
--- a/src/ssl/test/runner/handshake_messages.go
+++ b/src/ssl/test/runner/handshake_messages.go
@@ -260,7 +260,7 @@
 	ConfigID     uint8
 	KEM          uint16
 	PublicKey    []byte
-	MaxNameLen   uint16
+	MaxNameLen   uint8
 	PublicName   string
 	CipherSuites []HPKECipherSuite
 	// The following fields are only used by CreateECHConfig().
@@ -282,8 +282,8 @@
 		cipherSuites.addU16(suite.KDF)
 		cipherSuites.addU16(suite.AEAD)
 	}
-	contents.addU16(template.MaxNameLen)
-	contents.addU16LengthPrefixed().addBytes([]byte(template.PublicName))
+	contents.addU8(template.MaxNameLen)
+	contents.addU8LengthPrefixed().addBytes([]byte(template.PublicName))
 	extensions := contents.addU16LengthPrefixed()
 	// Mandatory extensions have the high bit set.
 	if template.UnsupportedExtension {
@@ -318,9 +318,12 @@
 	Key       []byte
 }
 
-// The contents of a CH "encrypted_client_hello" extension.
-// https://tools.ietf.org/html/draft-ietf-tls-esni-10
-type clientECH struct {
+const (
+	echClientTypeOuter byte = 0
+	echClientTypeInner byte = 1
+)
+
+type echClientOuter struct {
 	kdfID    uint16
 	aeadID   uint16
 	configID uint8
@@ -329,64 +332,64 @@
 }
 
 type clientHelloMsg struct {
-	raw                       []byte
-	isDTLS                    bool
-	isV2ClientHello           bool
-	vers                      uint16
-	random                    []byte
-	v2Challenge               []byte
-	sessionID                 []byte
-	cookie                    []byte
-	cipherSuites              []uint16
-	compressionMethods        []uint8
-	nextProtoNeg              bool
-	serverName                string
-	clientECH                 *clientECH
-	echIsInner                bool
-	invalidECHIsInner         []byte
-	ocspStapling              bool
-	supportedCurves           []CurveID
-	supportedPoints           []uint8
-	hasKeyShares              bool
-	keyShares                 []keyShareEntry
-	keySharesRaw              []byte
-	trailingKeyShareData      bool
-	pskIdentities             []pskIdentity
-	pskKEModes                []byte
-	pskBinders                [][]uint8
-	hasEarlyData              bool
-	tls13Cookie               []byte
-	ticketSupported           bool
-	sessionTicket             []uint8
-	signatureAlgorithms       []signatureAlgorithm
-	signatureAlgorithmsCert   []signatureAlgorithm
-	supportedVersions         []uint16
-	secureRenegotiation       []byte
-	alpnProtocols             []string
-	quicTransportParams       []byte
-	quicTransportParamsLegacy []byte
-	duplicateExtension        bool
-	channelIDSupported        bool
-	extendedMasterSecret      bool
-	srtpProtectionProfiles    []uint16
-	srtpMasterKeyIdentifier   string
-	sctListSupported          bool
-	customExtension           string
-	hasGREASEExtension        bool
-	omitExtensions            bool
-	emptyExtensions           bool
-	pad                       int
-	compressedCertAlgs        []uint16
-	delegatedCredentials      bool
-	alpsProtocols             []string
-	outerExtensions           []uint16
-	prefixExtensions          []uint16
+	raw                                      []byte
+	isDTLS                                   bool
+	isV2ClientHello                          bool
+	vers                                     uint16
+	random                                   []byte
+	v2Challenge                              []byte
+	sessionID                                []byte
+	cookie                                   []byte
+	cipherSuites                             []uint16
+	compressionMethods                       []uint8
+	nextProtoNeg                             bool
+	serverName                               string
+	echOuter                                 *echClientOuter
+	echInner                                 bool
+	invalidECHInner                          []byte
+	ocspStapling                             bool
+	supportedCurves                          []CurveID
+	supportedPoints                          []uint8
+	hasKeyShares                             bool
+	keyShares                                []keyShareEntry
+	keySharesRaw                             []byte
+	trailingKeyShareData                     bool
+	pskIdentities                            []pskIdentity
+	pskKEModes                               []byte
+	pskBinders                               [][]uint8
+	hasEarlyData                             bool
+	tls13Cookie                              []byte
+	ticketSupported                          bool
+	sessionTicket                            []uint8
+	signatureAlgorithms                      []signatureAlgorithm
+	signatureAlgorithmsCert                  []signatureAlgorithm
+	supportedVersions                        []uint16
+	secureRenegotiation                      []byte
+	alpnProtocols                            []string
+	quicTransportParams                      []byte
+	quicTransportParamsLegacy                []byte
+	duplicateExtension                       bool
+	channelIDSupported                       bool
+	extendedMasterSecret                     bool
+	srtpProtectionProfiles                   []uint16
+	srtpMasterKeyIdentifier                  string
+	sctListSupported                         bool
+	customExtension                          string
+	hasGREASEExtension                       bool
+	omitExtensions                           bool
+	emptyExtensions                          bool
+	pad                                      int
+	compressedCertAlgs                       []uint16
+	delegatedCredentials                     bool
+	alpsProtocols                            []string
+	outerExtensions                          []uint16
+	reorderOuterExtensionsWithoutCompressing bool
+	prefixExtensions                         []uint16
 	// The following fields are only filled in by |unmarshal| and ignored when
 	// marshaling a new ClientHello.
-	extensionStart    int
-	echExtensionStart int
-	echExtensionEnd   int
-	rawExtensions     map[uint16][]byte
+	echPayloadStart int
+	echPayloadEnd   int
+	rawExtensions   []byte
 }
 
 func (m *clientHelloMsg) marshalKeyShares(bb *byteBuilder) {
@@ -405,7 +408,6 @@
 
 const (
 	clientHelloNormal clientHelloType = iota
-	clientHelloOuterAAD
 	clientHelloEncodedInner
 )
 
@@ -471,35 +473,26 @@
 			body: serverNameList.finish(),
 		})
 	}
-	if m.clientECH != nil && typ != clientHelloOuterAAD {
+	if m.echOuter != nil {
 		body := newByteBuilder()
-		body.addU16(m.clientECH.kdfID)
-		body.addU16(m.clientECH.aeadID)
-		body.addU8(m.clientECH.configID)
-		body.addU16LengthPrefixed().addBytes(m.clientECH.enc)
-		body.addU16LengthPrefixed().addBytes(m.clientECH.payload)
-
+		body.addU8(echClientTypeOuter)
+		body.addU16(m.echOuter.kdfID)
+		body.addU16(m.echOuter.aeadID)
+		body.addU8(m.echOuter.configID)
+		body.addU16LengthPrefixed().addBytes(m.echOuter.enc)
+		body.addU16LengthPrefixed().addBytes(m.echOuter.payload)
 		extensions = append(extensions, extension{
 			id:   extensionEncryptedClientHello,
 			body: body.finish(),
 		})
 	}
-	if m.echIsInner {
-		extensions = append(extensions, extension{
-			id: extensionECHIsInner,
-			// If unset, invalidECHIsInner is empty, which is the correct
-			// serialization.
-			body: m.invalidECHIsInner,
-		})
-	}
-	if m.outerExtensions != nil && typ == clientHelloEncodedInner {
+	if m.echInner {
 		body := newByteBuilder()
-		extensionsList := body.addU8LengthPrefixed()
-		for _, extID := range m.outerExtensions {
-			extensionsList.addU16(extID)
-		}
+		body.addU8(echClientTypeInner)
+		// If unset, invalidECHInner is empty, which is the correct serialization.
+		body.addBytes(m.invalidECHInner)
 		extensions = append(extensions, extension{
-			id:   extensionECHOuterExtensions,
+			id:   extensionEncryptedClientHello,
 			body: body.finish(),
 		})
 	}
@@ -668,7 +661,7 @@
 	if m.sctListSupported {
 		extensions = append(extensions, extension{id: extensionSignedCertificateTimestamp})
 	}
-	if l := len(m.customExtension); l > 0 {
+	if len(m.customExtension) > 0 {
 		extensions = append(extensions, extension{
 			id:   extensionCustom,
 			body: []byte(m.customExtension),
@@ -726,35 +719,51 @@
 		})
 	}
 
-	// Write each extension in |extensions| to the |hello| message, hoisting
-	// the extensions named in |m.prefixExtensions| to the front.
 	extensionsBB := hello.addU16LengthPrefixed()
 	extMap := make(map[uint16][]byte)
+	extsWritten := make(map[uint16]struct{})
 	for _, ext := range extensions {
 		extMap[ext.id] = ext.body
 	}
-	// Elide each of the extensions named by |m.outerExtensions|.
-	if m.outerExtensions != nil && typ == clientHelloEncodedInner {
-		for _, extID := range m.outerExtensions {
-			delete(extMap, extID)
-		}
-	}
 	// Write each of the prefix extensions, if we have it.
 	for _, extID := range m.prefixExtensions {
 		if body, ok := extMap[extID]; ok {
 			extensionsBB.addU16(extID)
 			extensionsBB.addU16LengthPrefixed().addBytes(body)
+			extsWritten[extID] = struct{}{}
 		}
 	}
-	// Forget each of the prefix extensions. This loop is separate from the
-	// extension-writing loop because |m.prefixExtensions| may contain
-	// duplicates.
-	for _, extID := range m.prefixExtensions {
-		delete(extMap, extID)
+	// Write outer extensions, possibly in compressed form.
+	if m.outerExtensions != nil {
+		if typ == clientHelloEncodedInner && !m.reorderOuterExtensionsWithoutCompressing {
+			extensionsBB.addU16(extensionECHOuterExtensions)
+			list := extensionsBB.addU16LengthPrefixed().addU8LengthPrefixed()
+			for _, extID := range m.outerExtensions {
+				list.addU16(extID)
+				extsWritten[extID] = struct{}{}
+			}
+		} else {
+			for _, extID := range m.outerExtensions {
+				// m.outerExtensions may intentionally contain duplicates to test the
+				// server's reaction. If m.reorderOuterExtensionsWithoutCompressing
+				// is set, we are targetting the second ClientHello and wish to send a
+				// valid first ClientHello. In that case, deduplicate so the error
+				// only appears later.
+				if _, written := extsWritten[extID]; m.reorderOuterExtensionsWithoutCompressing && written {
+					continue
+				}
+				if body, ok := extMap[extID]; ok {
+					extensionsBB.addU16(extID)
+					extensionsBB.addU16LengthPrefixed().addBytes(body)
+					extsWritten[extID] = struct{}{}
+				}
+			}
+		}
 	}
+
 	// Write each of the remaining extensions in their original order.
 	for _, ext := range extensions {
-		if _, ok := extMap[ext.id]; ok {
+		if _, written := extsWritten[ext.id]; !written {
 			extensionsBB.addU16(ext.id)
 			extensionsBB.addU16LengthPrefixed().addBytes(ext.body)
 		}
@@ -779,10 +788,6 @@
 	}
 }
 
-func (m *clientHelloMsg) marshalForOuterAAD(bb *byteBuilder) {
-	m.marshalBody(bb, clientHelloOuterAAD)
-}
-
 func (m *clientHelloMsg) marshalForEncodedInner() []byte {
 	hello := newByteBuilder()
 	m.marshalBody(hello, clientHelloEncodedInner)
@@ -891,8 +896,6 @@
 		}
 	}
 
-	m.extensionStart = len(data) - len(reader)
-
 	m.nextProtoNeg = false
 	m.serverName = ""
 	m.ocspStapling = false
@@ -909,7 +912,6 @@
 	m.customExtension = ""
 	m.delegatedCredentials = false
 	m.alpsProtocols = nil
-	m.rawExtensions = make(map[uint16][]byte)
 
 	if len(reader) == 0 {
 		// ClientHello is optionally followed by extension data
@@ -920,6 +922,7 @@
 	if !reader.readU16LengthPrefixed(&extensions) || len(reader) != 0 || !checkDuplicateExtensions(extensions) {
 		return false
 	}
+	m.rawExtensions = extensions
 	for len(extensions) > 0 {
 		var extension uint16
 		var body byteReader
@@ -927,7 +930,6 @@
 			!extensions.readU16LengthPrefixed(&body) {
 			return false
 		}
-		m.rawExtensions[extension] = body
 		switch extension {
 		case extensionServerName:
 			var names byteReader
@@ -946,24 +948,33 @@
 				}
 			}
 		case extensionEncryptedClientHello:
-			m.echExtensionEnd = len(data) - len(extensions)
-			m.echExtensionStart = m.echExtensionEnd - len(body) - 4
-			var ech clientECH
-			if !body.readU16(&ech.kdfID) ||
-				!body.readU16(&ech.aeadID) ||
-				!body.readU8(&ech.configID) ||
-				!body.readU16LengthPrefixedBytes(&ech.enc) ||
-				!body.readU16LengthPrefixedBytes(&ech.payload) ||
-				len(ech.payload) == 0 ||
-				len(body) > 0 {
+			var typ byte
+			if !body.readU8(&typ) {
 				return false
 			}
-			m.clientECH = &ech
-		case extensionECHIsInner:
-			if len(body) != 0 {
+			switch typ {
+			case echClientTypeOuter:
+				var echOuter echClientOuter
+				if !body.readU16(&echOuter.kdfID) ||
+					!body.readU16(&echOuter.aeadID) ||
+					!body.readU8(&echOuter.configID) ||
+					!body.readU16LengthPrefixedBytes(&echOuter.enc) ||
+					!body.readU16LengthPrefixedBytes(&echOuter.payload) ||
+					len(echOuter.payload) == 0 ||
+					len(body) > 0 {
+					return false
+				}
+				m.echOuter = &echOuter
+				m.echPayloadEnd = len(data) - len(extensions)
+				m.echPayloadStart = m.echPayloadEnd - len(echOuter.payload)
+			case echClientTypeInner:
+				if len(body) > 0 {
+					return false
+				}
+				m.echInner = true
+			default:
 				return false
 			}
-			m.echIsInner = true
 		case extensionNextProtoNeg:
 			if len(body) != 0 {
 				return false
@@ -1195,13 +1206,6 @@
 		}
 	}
 
-	// Clients may not send both extensions.
-	// TODO(davidben): A later draft will likely merge the code points, at which
-	// point this check will be redundant.
-	if m.echIsInner && m.clientECH != nil {
-		return false
-	}
-
 	return true
 }
 
@@ -1214,11 +1218,17 @@
 		len(sessionID) != 0 || // Copied from |helloOuter|
 		!reader.readU16LengthPrefixedBytes(&cipherSuites) ||
 		!reader.readU8LengthPrefixedBytes(&compressionMethods) ||
-		!reader.readU16LengthPrefixed(&extensions) ||
-		len(reader) != 0 {
+		!reader.readU16LengthPrefixed(&extensions) {
 		return nil, errors.New("tls: error parsing EncodedClientHelloInner")
 	}
 
+	// The remainder of the structure is padding.
+	for _, padding := range reader {
+		if padding != 0 {
+			return nil, errors.New("tls: non-zero padding in EncodedClientHelloInner")
+		}
+	}
+
 	builder := newByteBuilder()
 	builder.addU8(typeClientHello)
 	body := builder.addU24LengthPrefixed()
@@ -1229,6 +1239,7 @@
 	newExtensions := body.addU16LengthPrefixed()
 
 	var seenOuterExtensions bool
+	outerExtensions := byteReader(helloOuter.rawExtensions)
 	copied := make(map[uint16]struct{})
 	for len(extensions) > 0 {
 		var extType uint16
@@ -1246,28 +1257,35 @@
 			return nil, errors.New("tls: duplicate ech_outer_extensions extension")
 		}
 		seenOuterExtensions = true
-		var outerExtensions byteReader
-		if !extBody.readU8LengthPrefixed(&outerExtensions) || len(outerExtensions) == 0 || len(extBody) != 0 {
+		var extList byteReader
+		if !extBody.readU8LengthPrefixed(&extList) || len(extList) == 0 || len(extBody) != 0 {
 			return nil, errors.New("tls: error parsing ech_outer_extensions")
 		}
-		for len(outerExtensions) != 0 {
+		for len(extList) != 0 {
 			var newExtType uint16
-			if !outerExtensions.readU16(&newExtType) {
+			if !extList.readU16(&newExtType) {
 				return nil, errors.New("tls: error parsing ech_outer_extensions")
 			}
 			if newExtType == extensionEncryptedClientHello {
 				return nil, errors.New("tls: error parsing ech_outer_extensions")
 			}
-			if _, ok := copied[newExtType]; ok {
-				return nil, errors.New("tls: duplicate extension in ech_outer_extensions")
+			for {
+				if len(outerExtensions) == 0 {
+					return nil, fmt.Errorf("tls: extension %d not found in ClientHelloOuter", newExtType)
+				}
+				var foundExt uint16
+				var newExtBody []byte
+				if !outerExtensions.readU16(&foundExt) ||
+					!outerExtensions.readU16LengthPrefixedBytes(&newExtBody) {
+					return nil, errors.New("tls: error parsing ClientHelloOuter")
+				}
+				if foundExt == newExtType {
+					newExtensions.addU16(newExtType)
+					newExtensions.addU16LengthPrefixed().addBytes(newExtBody)
+					copied[newExtType] = struct{}{}
+					break
+				}
 			}
-			newExtBody, ok := helloOuter.rawExtensions[newExtType]
-			if !ok {
-				return nil, fmt.Errorf("tls: extension %d not found in ClientHelloOuter", newExtType)
-			}
-			newExtensions.addU16(newExtType)
-			newExtensions.addU16LengthPrefixed().addBytes(newExtBody)
-			copied[newExtType] = struct{}{}
 		}
 	}
 
@@ -1487,25 +1505,6 @@
 	return true
 }
 
-// marshalForECHConf marshals |m|, but zeroes out the last 8 bytes of the
-// ServerHello.random.
-func (m *serverHelloMsg) marshalForECHConf() []byte {
-	ret := m.marshal()
-	// Make a copy so we can mutate it.
-	ret = append(make([]byte, 0, len(ret)), ret...)
-
-	reparsed := new(serverHelloMsg)
-	if !reparsed.unmarshal(ret) {
-		panic("could not re-parse ServerHello")
-	}
-	// We rely on |unmarshal| aliasing the |random| into |ret|.
-	for i := 24; i < 32; i++ {
-		reparsed.random[i] = 0
-	}
-
-	return ret
-}
-
 type encryptedExtensionsMsg struct {
 	raw        []byte
 	extensions serverExtensions
@@ -1907,16 +1906,18 @@
 }
 
 type helloRetryRequestMsg struct {
-	raw                 []byte
-	vers                uint16
-	sessionID           []byte
-	cipherSuite         uint16
-	compressionMethod   uint8
-	hasSelectedGroup    bool
-	selectedGroup       CurveID
-	cookie              []byte
-	customExtension     string
-	duplicateExtensions bool
+	raw                   []byte
+	vers                  uint16
+	sessionID             []byte
+	cipherSuite           uint16
+	compressionMethod     uint8
+	hasSelectedGroup      bool
+	selectedGroup         CurveID
+	cookie                []byte
+	customExtension       string
+	echConfirmation       []byte
+	echConfirmationOffset int
+	duplicateExtensions   bool
 }
 
 func (m *helloRetryRequestMsg) marshal() []byte {
@@ -1960,6 +1961,10 @@
 			extensions.addU16(extensionCustom)
 			extensions.addU16LengthPrefixed().addBytes([]byte(m.customExtension))
 		}
+		if len(m.echConfirmation) > 0 {
+			extensions.addU16(extensionEncryptedClientHello)
+			extensions.addU16LengthPrefixed().addBytes(m.echConfirmation)
+		}
 	}
 
 	m.raw = retryRequestMsg.finish()
@@ -2005,9 +2010,17 @@
 			m.hasSelectedGroup = true
 			m.selectedGroup = CurveID(v)
 		case extensionCookie:
-			if !body.readU16LengthPrefixedBytes(&m.cookie) || len(body) != 0 {
+			if !body.readU16LengthPrefixedBytes(&m.cookie) ||
+				len(m.cookie) == 0 ||
+				len(body) != 0 {
 				return false
 			}
+		case extensionEncryptedClientHello:
+			if len(body) != echAcceptConfirmationLength {
+				return false
+			}
+			m.echConfirmation = body
+			m.echConfirmationOffset = len(m.raw) - len(extensions) - len(body)
 		default:
 			// Unknown extensions are illegal from the server.
 			return false
diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go
index b9d7667..4f41184 100644
--- a/src/ssl/test/runner/handshake_server.go
+++ b/src/ssl/test/runner/handshake_server.go
@@ -186,14 +186,21 @@
 		return errors.New("tls: no GREASE extension found")
 	}
 
-	if clientECH := hs.clientHello.clientECH; clientECH != nil {
+	if config.Bugs.ExpectClientECH && hs.clientHello.echOuter == nil {
+		return errors.New("tls: expected client to offer ECH")
+	}
+	if config.Bugs.ExpectNoClientECH && hs.clientHello.echOuter != nil {
+		return errors.New("tls: expected client not to offer ECH")
+	}
+
+	if echOuter := hs.clientHello.echOuter; echOuter != nil {
 		for _, candidate := range config.ServerECHConfigs {
-			if candidate.ECHConfig.ConfigID != clientECH.configID {
+			if candidate.ECHConfig.ConfigID != echOuter.configID {
 				continue
 			}
 			var found bool
 			for _, suite := range candidate.ECHConfig.CipherSuites {
-				if clientECH.kdfID == suite.KDF && clientECH.aeadID == suite.AEAD {
+				if echOuter.kdfID == suite.KDF && echOuter.aeadID == suite.AEAD {
 					found = true
 					break
 				}
@@ -203,7 +210,7 @@
 			}
 			info := []byte("tls ech\x00")
 			info = append(info, candidate.ECHConfig.Raw...)
-			hs.echHPKEContext, err = hpke.SetupBaseReceiverX25519(clientECH.kdfID, clientECH.aeadID, clientECH.enc, candidate.Key, info)
+			hs.echHPKEContext, err = hpke.SetupBaseReceiverX25519(echOuter.kdfID, echOuter.aeadID, echOuter.enc, candidate.Key, info)
 			if err != nil {
 				continue
 			}
@@ -220,7 +227,7 @@
 			} else {
 				c.echAccepted = true
 				hs.clientHello = clientHelloInner
-				hs.echConfigID = clientECH.configID
+				hs.echConfigID = echOuter.configID
 			}
 		}
 	}
@@ -449,29 +456,17 @@
 }
 
 func (hs *serverHandshakeState) decryptClientHello(helloOuter *clientHelloMsg) (helloInner *clientHelloMsg, err error) {
-	// See draft-ietf-tls-esni-10, section 5.2.
-	aad := newByteBuilder()
-	aad.addU16(helloOuter.clientECH.kdfID)
-	aad.addU16(helloOuter.clientECH.aeadID)
-	aad.addU8(helloOuter.clientECH.configID)
-	aad.addU16LengthPrefixed().addBytes(helloOuter.clientECH.enc)
-	// ClientHelloOuterAAD.outer_hello is ClientHelloOuter without the
-	// encrypted_client_hello extension. Construct this by piecing together
-	// the preserved portions from offsets and updating the length prefix.
-	//
-	// TODO(davidben): If https://github.com/tlswg/draft-ietf-tls-esni/pull/442
-	// is merged, a later iteration will hopefully be simpler.
-	outerHello := aad.addU24LengthPrefixed()
-	outerHello.addBytes(helloOuter.raw[4:helloOuter.extensionStart])
-	extensions := outerHello.addU16LengthPrefixed()
-	extensions.addBytes(helloOuter.raw[helloOuter.extensionStart+2 : helloOuter.echExtensionStart])
-	extensions.addBytes(helloOuter.raw[helloOuter.echExtensionEnd:])
+	// ClientHelloOuterAAD is ClientHelloOuter with the payload replaced by
+	// zeros. See draft-ietf-tls-esni-13, section 5.2.
+	aad := make([]byte, len(helloOuter.raw)-4)
+	copy(aad, helloOuter.raw[4:helloOuter.echPayloadStart])
+	copy(aad[helloOuter.echPayloadEnd-4:], helloOuter.raw[helloOuter.echPayloadEnd:])
 
 	// In fuzzer mode, the payload is cleartext.
-	encoded := helloOuter.clientECH.payload
+	encoded := helloOuter.echOuter.payload
 	if !hs.c.config.Bugs.NullAllCiphers {
 		var err error
-		encoded, err = hs.echHPKEContext.Open(helloOuter.clientECH.payload, aad.finish())
+		encoded, err = hs.echHPKEContext.Open(helloOuter.echOuter.payload, aad)
 		if err != nil {
 			// Wrap |err| so the caller can implement trial decryption.
 			return nil, &echDecryptError{err}
@@ -505,8 +500,8 @@
 	if helloInner.nextProtoNeg || len(helloInner.supportedPoints) != 0 || helloInner.ticketSupported || helloInner.secureRenegotiation != nil || helloInner.extendedMasterSecret {
 		return nil, errors.New("tls: ClientHelloInner included a TLS-1.2-only extension")
 	}
-	if !helloInner.echIsInner {
-		return nil, errors.New("tls: ClientHelloInner missing ech_is_inner extension")
+	if !helloInner.echInner {
+		return nil, errors.New("tls: ClientHelloInner missing inner encrypted_client_hello extension")
 	}
 
 	return helloInner, nil
@@ -549,13 +544,6 @@
 		return err
 	}
 
-	if config.Bugs.ExpectClientECH && hs.clientHello.clientECH == nil {
-		return errors.New("tls: expected client to offer ECH")
-	}
-	if config.Bugs.ExpectNoClientECH && hs.clientHello.clientECH != nil {
-		return errors.New("tls: expected client not to offer ECH")
-	}
-
 	// Select the cipher suite.
 	var preferenceList, supportedList []uint16
 	if config.PreferServerCipherSuites {
@@ -754,6 +742,21 @@
 
 	if sendHelloRetryRequest {
 		hs.finishedHash.UpdateForHelloRetryRequest()
+
+		// Emit the ECH confirmation signal when requested.
+		if hs.clientHello.echInner {
+			helloRetryRequest.echConfirmation = make([]byte, 8)
+			helloRetryRequest.echConfirmation = hs.finishedHash.echAcceptConfirmation(hs.clientHello.random, echAcceptConfirmationHRRLabel, helloRetryRequest.marshal())
+			helloRetryRequest.raw = nil
+		} else if config.Bugs.AlwaysSendECHHelloRetryRequest {
+			// When solicited, a random ECH confirmation string should be ignored.
+			helloRetryRequest.echConfirmation = make([]byte, 8)
+			if _, err := io.ReadFull(config.rand(), helloRetryRequest.echConfirmation); err != nil {
+				c.sendAlert(alertInternalError)
+				return fmt.Errorf("tls: short read from Rand: %s", err)
+			}
+		}
+
 		hs.writeServerHash(helloRetryRequest.marshal())
 		if c.config.Bugs.PartialServerHelloWithHelloRetryRequest {
 			data := helloRetryRequest.marshal()
@@ -787,19 +790,19 @@
 		}
 
 		if c.echAccepted {
-			if newClientHello.clientECH == nil {
+			if newClientHello.echOuter == nil {
 				c.sendAlert(alertMissingExtension)
 				return errors.New("tls: second ClientHelloOuter had no encrypted_client_hello extension")
 			}
-			if newClientHello.clientECH.configID != hs.echConfigID ||
-				newClientHello.clientECH.kdfID != hs.echHPKEContext.KDF() ||
-				newClientHello.clientECH.aeadID != hs.echHPKEContext.AEAD() {
+			if newClientHello.echOuter.configID != hs.echConfigID ||
+				newClientHello.echOuter.kdfID != hs.echHPKEContext.KDF() ||
+				newClientHello.echOuter.aeadID != hs.echHPKEContext.AEAD() {
 				c.sendAlert(alertIllegalParameter)
 				return errors.New("tls: ECH parameters changed in second ClientHelloOuter")
 			}
-			if len(newClientHello.clientECH.enc) != 0 {
+			if len(newClientHello.echOuter.enc) != 0 {
 				c.sendAlert(alertIllegalParameter)
-				return errors.New("tls: second ClientECH had non-empty enc")
+				return errors.New("tls: second ClientHelloOuter had non-empty ECH enc")
 			}
 			newClientHello, err = hs.decryptClientHello(newClientHello)
 			if err != nil {
@@ -819,11 +822,6 @@
 		// Check that the new ClientHello matches the old ClientHello,
 		// except for relevant modifications. See RFC 8446, section 4.1.2.
 		ignoreExtensions := []uint16{extensionPadding}
-		// TODO(https://crbug.com/boringssl/275): draft-ietf-tls-esni-10 requires
-		// violating the RFC 8446 rules. See
-		// https://github.com/tlswg/draft-ietf-tls-esni/issues/358
-		// A later draft will likely fix this. Remove this line if it does.
-		ignoreExtensions = append(ignoreExtensions, extensionEncryptedClientHello)
 
 		if helloRetryRequest.hasSelectedGroup {
 			newKeyShares := newClientHello.keyShares
@@ -1006,13 +1004,13 @@
 		hs.finishedHash.addEntropy(hs.finishedHash.zeroSecret())
 	}
 
-	// Overwrite part of ServerHello.random to signal ECH acceptance to the client.
-	if hs.clientHello.echIsInner {
-		for i := 24; i < 32; i++ {
-			hs.hello.random[i] = 0
+	// Emit the ECH confirmation signal when requested.
+	if hs.clientHello.echInner && !config.Bugs.OmitServerHelloECHConfirmation {
+		randomSuffix := hs.hello.random[len(hs.hello.random)-echAcceptConfirmationLength:]
+		for i := range randomSuffix {
+			randomSuffix[i] = 0
 		}
-		echAcceptConfirmation := hs.finishedHash.deriveSecretPeek([]byte("ech accept confirmation"), hs.hello.marshal())
-		copy(hs.hello.random[24:], echAcceptConfirmation)
+		copy(randomSuffix, hs.finishedHash.echAcceptConfirmation(hs.clientHello.random, echAcceptConfirmationLabel, hs.hello.marshal()))
 		hs.hello.raw = nil
 	}
 
@@ -1696,7 +1694,7 @@
 
 	serverExtensions.serverNameAck = c.config.Bugs.SendServerNameAck
 
-	if (c.vers >= VersionTLS13 || c.config.Bugs.SendECHRetryConfigsInTLS12ServerHello) && hs.clientHello.clientECH != nil {
+	if (c.vers >= VersionTLS13 && hs.clientHello.echOuter != nil) || c.config.Bugs.AlwaysSendECHRetryConfigs {
 		if len(config.Bugs.SendECHRetryConfigs) > 0 {
 			serverExtensions.echRetryConfigs = config.Bugs.SendECHRetryConfigs
 		} else if len(config.ServerECHConfigs) > 0 {
@@ -1849,7 +1847,11 @@
 
 	// Generate a session ID if we're to save the session.
 	if !hs.hello.extensions.ticketSupported && config.ServerSessionCache != nil {
-		hs.hello.sessionID = make([]byte, 32)
+		l := config.Bugs.NewSessionIDLength
+		if l == 0 {
+			l = 32
+		}
+		hs.hello.sessionID = make([]byte, l)
 		if _, err := io.ReadFull(config.rand(), hs.hello.sessionID); err != nil {
 			c.sendAlert(alertInternalError)
 			return errors.New("tls: short read from Rand: " + err.Error())
diff --git a/src/ssl/test/runner/hpke/hpke.go b/src/ssl/test/runner/hpke/hpke.go
index a08538b..36dc637 100644
--- a/src/ssl/test/runner/hpke/hpke.go
+++ b/src/ssl/test/runner/hpke/hpke.go
@@ -136,6 +136,8 @@
 
 func (c *Context) AEAD() uint16 { return c.aeadID }
 
+func (c *Context) Overhead() int { return c.aead.Overhead() }
+
 func (c *Context) Seal(plaintext, additionalData []byte) []byte {
 	ciphertext := c.aead.Seal(nil, c.computeNonce(), plaintext, additionalData)
 	c.incrementSeq()
diff --git a/src/ssl/test/runner/prf.go b/src/ssl/test/runner/prf.go
index 66c427f..5731be0 100644
--- a/src/ssl/test/runner/prf.go
+++ b/src/ssl/test/runner/prf.go
@@ -381,7 +381,6 @@
 	return b
 }
 
-// The following are labels for traffic secret derivation in TLS 1.3.
 var (
 	externalPSKBinderLabel        = []byte("ext binder")
 	resumptionPSKBinderLabel      = []byte("res binder")
@@ -396,21 +395,25 @@
 	resumptionLabel               = []byte("res master")
 
 	resumptionPSKLabel = []byte("resumption")
+
+	echAcceptConfirmationLabel    = []byte("ech accept confirmation")
+	echAcceptConfirmationHRRLabel = []byte("hrr ech accept confirmation")
 )
 
 // deriveSecret implements TLS 1.3's Derive-Secret function, as defined in
-// section 7.1 of draft ietf-tls-tls13-16.
+// section 7.1 of RFC8446.
 func (h *finishedHash) deriveSecret(label []byte) []byte {
 	return hkdfExpandLabel(h.suite.hash(), h.secret, label, h.appendContextHashes(nil), h.hash.Size())
 }
 
-// deriveSecretPeek is the same as deriveSecret, but it enables the caller to
-// tentatively append messages to the transcript. The |extraMessages| parameter
-// contains the bytes of these tentative messages.
-func (h *finishedHash) deriveSecretPeek(label []byte, extraMessages []byte) []byte {
-	hashPeek := copyHash(h.hash, h.suite.hash())
-	hashPeek.Write(extraMessages)
-	return hkdfExpandLabel(h.suite.hash(), h.secret, label, hashPeek.Sum(nil), h.hash.Size())
+// echConfirmation computes the ECH accept confirmation signal, as defined in
+// sections 7.2 and 7.2.1 of draft-ietf-tls-esni-13. The transcript hash is
+// computed by concatenating |h| with |extraMessages|.
+func (h *finishedHash) echAcceptConfirmation(clientRandom, label, extraMessages []byte) []byte {
+	secret := hkdf.Extract(h.suite.hash().New, clientRandom, h.zeroSecret())
+	hashCopy := copyHash(h.hash, h.suite.hash())
+	hashCopy.Write(extraMessages)
+	return hkdfExpandLabel(h.suite.hash(), secret, label, hashCopy.Sum(nil), echAcceptConfirmationLength)
 }
 
 // The following are context strings for CertificateVerify in TLS 1.3.
diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go
index 3306c88..cfff714 100644
--- a/src/ssl/test/runner/runner.go
+++ b/src/ssl/test/runner/runner.go
@@ -1636,6 +1636,7 @@
 	if err != nil {
 		return err
 	}
+	statusChan <- statusMsg{test: test, statusType: statusShimStarted, pid: shim.cmd.Process.Pid}
 	defer shim.close()
 
 	localErr := doExchanges(test, shim, resumeCount, &transcripts)
@@ -3832,7 +3833,7 @@
 			},
 		},
 		shouldFail:    true,
-		expectedError: ":UNKNOWN_CIPHER_RETURNED:",
+		expectedError: ":WRONG_CIPHER_RETURNED:",
 	})
 	testCases = append(testCases, testCase{
 		name: "ServerHelloBogusCipher-TLS13",
@@ -13277,7 +13278,7 @@
 
 	testCases = append(testCases, testCase{
 		testType: serverTest,
-		name:     "ShortSessionID-TLS13",
+		name:     "Server-ShortSessionID-TLS13",
 		config: Config{
 			MaxVersion: VersionTLS13,
 			Bugs: ProtocolBugs{
@@ -13288,7 +13289,7 @@
 
 	testCases = append(testCases, testCase{
 		testType: serverTest,
-		name:     "FullSessionID-TLS13",
+		name:     "Server-FullSessionID-TLS13",
 		config: Config{
 			MaxVersion: VersionTLS13,
 			Bugs: ProtocolBugs{
@@ -13297,6 +13298,62 @@
 		},
 	})
 
+	// The server should reject ClientHellos whose session IDs are too long.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "Server-TooLongSessionID-TLS13",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendClientHelloSessionID: make([]byte, 33),
+			},
+		},
+		shouldFail:         true,
+		expectedError:      ":DECODE_ERROR:",
+		expectedLocalError: "remote error: error decoding message",
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "Server-TooLongSessionID-TLS12",
+		config: Config{
+			MaxVersion: VersionTLS12,
+			Bugs: ProtocolBugs{
+				SendClientHelloSessionID: make([]byte, 33),
+			},
+		},
+		shouldFail:         true,
+		expectedError:      ":DECODE_ERROR:",
+		expectedLocalError: "remote error: error decoding message",
+	})
+
+	// Test that the client correctly accepts or rejects short session IDs from
+	// the server. Our tests use 32 bytes by default, so the boundary condition
+	// is already covered.
+	testCases = append(testCases, testCase{
+		name: "Client-ShortSessionID",
+		config: Config{
+			MaxVersion:             VersionTLS12,
+			SessionTicketsDisabled: true,
+			Bugs: ProtocolBugs{
+				NewSessionIDLength: 1,
+			},
+		},
+		resumeSession: true,
+	})
+	testCases = append(testCases, testCase{
+		name: "Client-TooLongSessionID",
+		config: Config{
+			MaxVersion:             VersionTLS12,
+			SessionTicketsDisabled: true,
+			Bugs: ProtocolBugs{
+				NewSessionIDLength: 33,
+			},
+		},
+		shouldFail:         true,
+		expectedError:      ":DECODE_ERROR:",
+		expectedLocalError: "remote error: error decoding message",
+	})
+
 	// Test that the client sends a fake session ID in TLS 1.3. We cover both
 	// normal and resumption handshakes to capture interactions with the
 	// session resumption path.
@@ -13827,7 +13884,7 @@
 			},
 		},
 		shouldFail:    true,
-		expectedError: ":WRONG_VERSION_NUMBER:",
+		expectedError: ":DECODE_ERROR:",
 	})
 
 	testCases = append(testCases, testCase{
@@ -16588,19 +16645,19 @@
 				expectedError:      ":INVALID_CLIENT_HELLO_INNER:",
 			})
 
-			// When ech_is_inner extension is absent from the ClientHelloInner, the
+			// When inner ECH extension is absent from the ClientHelloInner, the
 			// server should fail the connection.
 			testCases = append(testCases, testCase{
 				testType: serverTest,
 				protocol: protocol,
-				name:     prefix + "ECH-Server-MissingECHIsInner" + suffix,
+				name:     prefix + "ECH-Server-MissingECHInner" + suffix,
 				config: Config{
 					ServerName:      "secret.example",
 					DefaultCurves:   defaultCurves,
 					ClientECHConfig: echConfig.ECHConfig,
 					Bugs: ProtocolBugs{
-						OmitECHIsInner:       !hrr,
-						OmitSecondECHIsInner: hrr,
+						OmitECHInner:       !hrr,
+						OmitSecondECHInner: hrr,
 					},
 				},
 				flags: []string{
@@ -16630,11 +16687,7 @@
 						extensionCustom,
 					},
 					Bugs: ProtocolBugs{
-						CustomExtension: "test",
-						// Ensure ClientHelloOuter's extension order is different
-						// from ClientHelloInner. This tests that the server
-						// correctly reconstructs the extension order.
-						FirstExtensionInClientHelloOuter:   extensionSupportedCurves,
+						CustomExtension:                    "test",
 						OnlyCompressSecondClientHelloInner: hrr,
 					},
 				},
@@ -16650,6 +16703,84 @@
 				},
 			})
 
+			// Test that the server allows referenced ClientHelloOuter
+			// extensions to be interleaved with other extensions. Only the
+			// relative order must match.
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				protocol: protocol,
+				name:     prefix + "ECH-Server-OuterExtensions-Interleaved" + suffix,
+				config: Config{
+					ServerName:      "secret.example",
+					DefaultCurves:   defaultCurves,
+					ClientECHConfig: echConfig.ECHConfig,
+					ECHOuterExtensions: []uint16{
+						extensionKeyShare,
+						extensionSupportedCurves,
+						extensionCustom,
+					},
+					Bugs: ProtocolBugs{
+						CustomExtension:                    "test",
+						OnlyCompressSecondClientHelloInner: hrr,
+						ECHOuterExtensionOrder: []uint16{
+							extensionServerName,
+							extensionKeyShare,
+							extensionSupportedVersions,
+							extensionPSKKeyExchangeModes,
+							extensionSupportedCurves,
+							extensionSignatureAlgorithms,
+							extensionCustom,
+						},
+					},
+				},
+				flags: []string{
+					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
+					"-ech-server-key", base64FlagValue(echConfig.Key),
+					"-ech-is-retry-config", "1",
+					"-expect-server-name", "secret.example",
+					"-expect-ech-accept",
+				},
+				expectations: connectionExpectations{
+					echAccepted: true,
+				},
+			})
+
+			// Test that the server rejects references to extensions in the
+			// wrong order.
+			testCases = append(testCases, testCase{
+				testType: serverTest,
+				protocol: protocol,
+				name:     prefix + "ECH-Server-OuterExtensions-WrongOrder" + suffix,
+				config: Config{
+					ServerName:      "secret.example",
+					DefaultCurves:   defaultCurves,
+					ClientECHConfig: echConfig.ECHConfig,
+					ECHOuterExtensions: []uint16{
+						extensionKeyShare,
+						extensionSupportedCurves,
+					},
+					Bugs: ProtocolBugs{
+						CustomExtension:                    "test",
+						OnlyCompressSecondClientHelloInner: hrr,
+						ECHOuterExtensionOrder: []uint16{
+							extensionSupportedCurves,
+							extensionKeyShare,
+						},
+					},
+				},
+				flags: []string{
+					"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
+					"-ech-server-key", base64FlagValue(echConfig.Key),
+					"-ech-is-retry-config", "1",
+					"-expect-server-name", "secret.example",
+				},
+				shouldFail:         true,
+				expectedLocalError: "remote error: illegal parameter",
+				// The decoding algorithm relies on the ordering requirement, so
+				// the wrong order appears as a missing extension.
+				expectedError: ":OUTER_EXTENSION_NOT_FOUND:",
+			})
+
 			// Test that the server rejects duplicated values in ech_outer_extensions.
 			// Besides causing the server to reconstruct an invalid ClientHelloInner
 			// with duplicated extensions, this behavior would be vulnerable to DoS
@@ -16668,6 +16799,10 @@
 					},
 					Bugs: ProtocolBugs{
 						OnlyCompressSecondClientHelloInner: hrr,
+						// Don't duplicate the extension in ClientHelloOuter.
+						ECHOuterExtensionOrder: []uint16{
+							extensionSupportedCurves,
+						},
 					},
 				},
 				flags: []string{
@@ -16677,7 +16812,9 @@
 				},
 				shouldFail:         true,
 				expectedLocalError: "remote error: illegal parameter",
-				expectedError:      ":DUPLICATE_EXTENSION:",
+				// The decoding algorithm relies on the ordering requirement, so
+				// duplicates appear as missing extensions.
+				expectedError: ":OUTER_EXTENSION_NOT_FOUND:",
 			})
 
 			// Test that the server rejects references to missing extensions in
@@ -16893,12 +17030,12 @@
 			})
 		}
 
-		// Test that the ECH server handles a short ClientECH.enc value by
-		// falling back to ClientHelloOuter.
+		// Test that the ECH server handles a short enc value by falling back to
+		// ClientHelloOuter.
 		testCases = append(testCases, testCase{
 			testType: serverTest,
 			protocol: protocol,
-			name:     prefix + "ECH-Server-ShortClientECHEnc",
+			name:     prefix + "ECH-Server-ShortEnc",
 			config: Config{
 				ServerName:      "secret.example",
 				ClientECHConfig: echConfig.ECHConfig,
@@ -17112,6 +17249,54 @@
 			},
 		})
 
+		// Test that the server accepts padding.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			protocol: protocol,
+			name:     prefix + "ECH-Server-Padding",
+			config: Config{
+				ClientECHConfig: echConfig.ECHConfig,
+				Bugs: ProtocolBugs{
+					ClientECHPadding: 10,
+				},
+			},
+			flags: []string{
+				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
+				"-ech-server-key", base64FlagValue(echConfig.Key),
+				"-ech-is-retry-config", "1",
+				"-expect-ech-accept",
+			},
+			expectations: connectionExpectations{
+				echAccepted: true,
+			},
+		})
+
+		// Test that the server rejects bad padding.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			protocol: protocol,
+			name:     prefix + "ECH-Server-BadPadding",
+			config: Config{
+				ClientECHConfig: echConfig.ECHConfig,
+				Bugs: ProtocolBugs{
+					ClientECHPadding:    10,
+					BadClientECHPadding: true,
+				},
+			},
+			flags: []string{
+				"-ech-server-config", base64FlagValue(echConfig.ECHConfig.Raw),
+				"-ech-server-key", base64FlagValue(echConfig.Key),
+				"-ech-is-retry-config", "1",
+				"-expect-ech-accept",
+			},
+			expectations: connectionExpectations{
+				echAccepted: true,
+			},
+			shouldFail:         true,
+			expectedError:      ":DECODE_ERROR",
+			expectedLocalError: "remote error: illegal parameter",
+		})
+
 		// Test the client's behavior when the server ignores ECH GREASE.
 		testCases = append(testCases, testCase{
 			testType: clientTest,
@@ -17178,19 +17363,19 @@
 			flags: []string{"-enable-ech-grease"},
 		})
 
+		// TLS 1.2 ServerHellos cannot contain retry configs.
 		if protocol != quic {
-			// Test that the client rejects retry configs in TLS 1.2.
 			testCases = append(testCases, testCase{
 				testType: clientTest,
 				protocol: protocol,
-				name:     prefix + "ECH-GREASE-Client-TLS12-Retry-Configs",
+				name:     prefix + "ECH-GREASE-Client-TLS12-RejectRetryConfigs",
 				config: Config{
-					MinVersion: VersionTLS12,
-					MaxVersion: VersionTLS12,
+					MinVersion:       VersionTLS12,
+					MaxVersion:       VersionTLS12,
+					ServerECHConfigs: []ServerECHConfig{echConfig},
 					Bugs: ProtocolBugs{
-						ExpectClientECH:                       true,
-						SendECHRetryConfigs:                   CreateECHConfigList(echConfig.ECHConfig.Raw, unsupportedVersion),
-						SendECHRetryConfigsInTLS12ServerHello: true,
+						ExpectClientECH:           true,
+						AlwaysSendECHRetryConfigs: true,
 					},
 				},
 				flags:              []string{"-enable-ech-grease"},
@@ -17198,8 +17383,101 @@
 				expectedLocalError: "remote error: unsupported extension",
 				expectedError:      ":UNEXPECTED_EXTENSION:",
 			})
+			testCases = append(testCases, testCase{
+				testType: clientTest,
+				protocol: protocol,
+				name:     prefix + "ECH-Client-TLS12-RejectRetryConfigs",
+				config: Config{
+					MinVersion:       VersionTLS12,
+					MaxVersion:       VersionTLS12,
+					ServerECHConfigs: []ServerECHConfig{echConfig},
+					Bugs: ProtocolBugs{
+						ExpectClientECH:           true,
+						AlwaysSendECHRetryConfigs: true,
+					},
+				},
+				flags: []string{
+					"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig1.ECHConfig.Raw)),
+				},
+				shouldFail:         true,
+				expectedLocalError: "remote error: unsupported extension",
+				expectedError:      ":UNEXPECTED_EXTENSION:",
+			})
 		}
 
+		// Retry configs must be rejected when ECH is accepted.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			protocol: protocol,
+			name:     prefix + "ECH-Client-Accept-RejectRetryConfigs",
+			config: Config{
+				ServerECHConfigs: []ServerECHConfig{echConfig},
+				Bugs: ProtocolBugs{
+					ExpectClientECH:           true,
+					AlwaysSendECHRetryConfigs: true,
+				},
+			},
+			flags: []string{
+				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
+			},
+			shouldFail:         true,
+			expectedLocalError: "remote error: unsupported extension",
+			expectedError:      ":UNEXPECTED_EXTENSION:",
+		})
+
+		// Unsolicited ECH HelloRetryRequest extensions should be rejected.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			protocol: protocol,
+			name:     prefix + "ECH-Client-UnsolictedHRRExtension",
+			config: Config{
+				ServerECHConfigs: []ServerECHConfig{echConfig},
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					AlwaysSendECHHelloRetryRequest: true,
+					ExpectMissingKeyShare:          true, // Check we triggered HRR.
+				},
+			},
+			shouldFail:         true,
+			expectedLocalError: "remote error: unsupported extension",
+			expectedError:      ":UNEXPECTED_EXTENSION:",
+		})
+
+		// GREASE should ignore ECH HelloRetryRequest extensions.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			protocol: protocol,
+			name:     prefix + "ECH-Client-GREASE-IgnoreHRRExtension",
+			config: Config{
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					AlwaysSendECHHelloRetryRequest: true,
+					ExpectMissingKeyShare:          true, // Check we triggered HRR.
+				},
+			},
+			flags: []string{"-enable-ech-grease"},
+		})
+
+		// Random ECH HelloRetryRequest extensions also signal ECH reject.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			protocol: protocol,
+			name:     prefix + "ECH-Client-Reject-RandomHRRExtension",
+			config: Config{
+				CurvePreferences: []CurveID{CurveP384},
+				Bugs: ProtocolBugs{
+					AlwaysSendECHHelloRetryRequest: true,
+					ExpectMissingKeyShare:          true, // Check we triggered HRR.
+				},
+			},
+			flags: []string{
+				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
+			},
+			shouldFail:         true,
+			expectedLocalError: "remote error: ECH required",
+			expectedError:      ":ECH_REJECTED:",
+		})
+
 		// Test that the client aborts with a decode_error alert when it receives a
 		// syntactically-invalid encrypted_client_hello extension from the server.
 		testCases = append(testCases, testCase{
@@ -17220,60 +17498,63 @@
 			expectedError:      ":ERROR_PARSING_EXTENSION:",
 		})
 
-		// Test that the server responds to an empty ech_is_inner extension with the
+		// Test that the server responds to an inner ECH extension with the
 		// acceptance confirmation.
 		testCases = append(testCases, testCase{
 			testType: serverTest,
 			protocol: protocol,
-			name:     prefix + "ECH-Server-ECHIsInner",
+			name:     prefix + "ECH-Server-ECHInner",
 			config: Config{
 				MinVersion: VersionTLS13,
 				MaxVersion: VersionTLS13,
 				Bugs: ProtocolBugs{
-					AlwaysSendECHIsInner: true,
+					AlwaysSendECHInner: true,
+				},
+			},
+			resumeSession: true,
+		})
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			protocol: protocol,
+			name:     prefix + "ECH-Server-ECHInner-HelloRetryRequest",
+			config: Config{
+				MinVersion: VersionTLS13,
+				MaxVersion: VersionTLS13,
+				// Force a HelloRetryRequest.
+				DefaultCurves: []CurveID{},
+				Bugs: ProtocolBugs{
+					AlwaysSendECHInner: true,
 				},
 			},
 			resumeSession: true,
 		})
 
 		// Test that server fails the handshake when it sees a non-empty
-		// ech_is_inner extension.
+		// inner ECH extension.
 		testCases = append(testCases, testCase{
 			testType: serverTest,
 			protocol: protocol,
-			name:     prefix + "ECH-Server-ECHIsInner-NotEmpty",
+			name:     prefix + "ECH-Server-ECHInner-NotEmpty",
 			config: Config{
 				MinVersion: VersionTLS13,
 				MaxVersion: VersionTLS13,
 				Bugs: ProtocolBugs{
-					AlwaysSendECHIsInner:  true,
-					SendInvalidECHIsInner: []byte{42, 42, 42},
+					AlwaysSendECHInner:  true,
+					SendInvalidECHInner: []byte{42, 42, 42},
 				},
 			},
 			shouldFail:         true,
-			expectedLocalError: "remote error: illegal parameter",
+			expectedLocalError: "remote error: error decoding message",
 			expectedError:      ":ERROR_PARSING_EXTENSION:",
 		})
 
-		// When ech_is_inner extension is absent, the server should not accept ECH.
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			protocol: protocol,
-			name:     prefix + "ECH-Server-ECHIsInner-Absent",
-			config: Config{
-				MinVersion: VersionTLS13,
-				MaxVersion: VersionTLS13,
-			},
-			resumeSession: true,
-		})
-
-		// Test that a TLS 1.3 server that receives an ech_is_inner extension can
+		// Test that a TLS 1.3 server that receives an inner ECH extension can
 		// negotiate TLS 1.2 without clobbering the downgrade signal.
 		if protocol != quic {
 			testCases = append(testCases, testCase{
 				testType: serverTest,
 				protocol: protocol,
-				name:     prefix + "ECH-Server-ECHIsInner-Absent-TLS12",
+				name:     prefix + "ECH-Server-ECHInner-Absent-TLS12",
 				config: Config{
 					MinVersion: VersionTLS12,
 					MaxVersion: VersionTLS13,
@@ -17281,7 +17562,7 @@
 						// Omit supported_versions extension so the server negotiates
 						// TLS 1.2.
 						OmitSupportedVersions: true,
-						AlwaysSendECHIsInner:  true,
+						AlwaysSendECHInner:    true,
 					},
 				},
 				// Check that the client sees the TLS 1.3 downgrade signal in
@@ -17291,26 +17572,6 @@
 			})
 		}
 
-		// Test that the handshake fails when the server has no ECHConfigs and the
-		// ClientHello contains both encrypted_client_hello and ech_is_inner
-		// extensions.
-		testCases = append(testCases, testCase{
-			testType: serverTest,
-			protocol: protocol,
-			name:     prefix + "ECH-Server-Disabled-EncryptedClientHello-ECHIsInner",
-			config: Config{
-				MinVersion:      VersionTLS13,
-				MaxVersion:      VersionTLS13,
-				ClientECHConfig: echConfig.ECHConfig,
-				Bugs: ProtocolBugs{
-					AlwaysSendECHIsInner: true,
-				},
-			},
-			shouldFail:         true,
-			expectedLocalError: "remote error: illegal parameter",
-			expectedError:      ":UNEXPECTED_EXTENSION:",
-		})
-
 		// Test the client can negotiate ECH, with and without HelloRetryRequest.
 		testCases = append(testCases, testCase{
 			testType: clientTest,
@@ -17887,9 +18148,7 @@
 		})
 
 		// Test that the client rejects ClientHelloOuter handshakes that attempt
-		// to resume the ClientHelloInner's ticket. In draft-ietf-tls-esni-10,
-		// the confirmation signal is computed in an odd order, so this requires
-		// an explicit check on the client.
+		// to resume the ClientHelloInner's ticket, at TLS 1.2 and TLS 1.3.
 		testCases = append(testCases, testCase{
 			testType: clientTest,
 			protocol: protocol,
@@ -17919,9 +18178,6 @@
 			expectations:       connectionExpectations{echAccepted: true},
 			resumeExpectations: &connectionExpectations{echAccepted: false},
 		})
-
-		// Test the above, but the server now attempts to resume the
-		// ClientHelloInner's ticket at TLS 1.2.
 		if protocol != quic {
 			testCases = append(testCases, testCase{
 				testType: clientTest,
@@ -18341,6 +18597,31 @@
 			expectedError:           ":ECH_REJECTED:",
 			expectedLocalError:      "remote error: ECH required",
 		})
+
+		// Test that the client checks both HelloRetryRequest and ServerHello
+		// for a confirmation signal.
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			protocol: protocol,
+			name:     prefix + "ECH-Client-HelloRetryRequest-MissingServerHelloConfirmation",
+			config: Config{
+				MinVersion:       VersionTLS13,
+				MaxVersion:       VersionTLS13,
+				CurvePreferences: []CurveID{CurveP384},
+				ServerECHConfigs: []ServerECHConfig{echConfig},
+				Bugs: ProtocolBugs{
+					ExpectMissingKeyShare:          true, // Check we triggered HRR.
+					OmitServerHelloECHConfirmation: true,
+				},
+			},
+			resumeSession: true,
+			flags: []string{
+				"-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)),
+				"-expect-hrr", // Check we triggered HRR.
+			},
+			shouldFail:    true,
+			expectedError: ":INCONSISTENT_ECH_NEGOTIATION:",
+		})
 	}
 }
 
diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc
index 12a9f7a..7d1cefa 100644
--- a/src/ssl/test/test_config.cc
+++ b/src/ssl/test/test_config.cc
@@ -810,7 +810,7 @@
 
   GetTestState(ssl)->cert_verified = true;
   if (config->verify_fail) {
-    store_ctx->error = X509_V_ERR_APPLICATION_VERIFICATION;
+    X509_STORE_CTX_set_error(store_ctx, X509_V_ERR_APPLICATION_VERIFICATION);
     return 0;
   }
 
diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc
index 0354f39..226c67b 100644
--- a/src/ssl/tls13_both.cc
+++ b/src/ssl/tls13_both.cc
@@ -235,15 +235,14 @@
     }
 
     // Parse out the extensions.
-    bool have_status_request = false, have_sct = false;
-    CBS status_request, sct;
-    const SSL_EXTENSION_TYPE ext_types[] = {
-        {TLSEXT_TYPE_status_request, &have_status_request, &status_request},
-        {TLSEXT_TYPE_certificate_timestamp, &have_sct, &sct},
-    };
-
+    SSLExtension status_request(
+        TLSEXT_TYPE_status_request,
+        !ssl->server && hs->config->ocsp_stapling_enabled);
+    SSLExtension sct(
+        TLSEXT_TYPE_certificate_timestamp,
+        !ssl->server && hs->config->signed_cert_timestamps_enabled);
     uint8_t alert = SSL_AD_DECODE_ERROR;
-    if (!ssl_parse_extensions(&extensions, &alert, ext_types,
+    if (!ssl_parse_extensions(&extensions, &alert, {&status_request, &sct},
                               /*ignore_unknown=*/false)) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
       return false;
@@ -251,20 +250,14 @@
 
     // All Certificate extensions are parsed, but only the leaf extensions are
     // stored.
-    if (have_status_request) {
-      if (ssl->server || !hs->config->ocsp_stapling_enabled) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
-        return false;
-      }
-
+    if (status_request.present) {
       uint8_t status_type;
       CBS ocsp_response;
-      if (!CBS_get_u8(&status_request, &status_type) ||
+      if (!CBS_get_u8(&status_request.data, &status_type) ||
           status_type != TLSEXT_STATUSTYPE_ocsp ||
-          !CBS_get_u24_length_prefixed(&status_request, &ocsp_response) ||
+          !CBS_get_u24_length_prefixed(&status_request.data, &ocsp_response) ||
           CBS_len(&ocsp_response) == 0 ||
-          CBS_len(&status_request) != 0) {
+          CBS_len(&status_request.data) != 0) {
         ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
         return false;
       }
@@ -279,14 +272,8 @@
       }
     }
 
-    if (have_sct) {
-      if (ssl->server || !hs->config->signed_cert_timestamps_enabled) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
-        return false;
-      }
-
-      if (!ssl_is_sct_list_valid(&sct)) {
+    if (sct.present) {
+      if (!ssl_is_sct_list_valid(&sct.data)) {
         OPENSSL_PUT_ERROR(SSL, SSL_R_ERROR_PARSING_EXTENSION);
         ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
         return false;
@@ -294,7 +281,7 @@
 
       if (sk_CRYPTO_BUFFER_num(certs.get()) == 1) {
         hs->new_session->signed_cert_timestamp_list.reset(
-            CRYPTO_BUFFER_new_from_CBS(&sct, ssl->ctx->pool));
+            CRYPTO_BUFFER_new_from_CBS(&sct.data, ssl->ctx->pool));
         if (hs->new_session->signed_cert_timestamp_list == nullptr) {
           ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
           return false;
diff --git a/src/ssl/tls13_client.cc b/src/ssl/tls13_client.cc
index bff7fb9..af2120c 100644
--- a/src/ssl/tls13_client.cc
+++ b/src/ssl/tls13_client.cc
@@ -101,6 +101,73 @@
   return true;
 }
 
+static bool parse_server_hello_tls13(const SSL_HANDSHAKE *hs,
+                                     ParsedServerHello *out, uint8_t *out_alert,
+                                     const SSLMessage &msg) {
+  if (!ssl_parse_server_hello(out, out_alert, msg)) {
+    return false;
+  }
+  // The RFC8446 version of the structure fixes some legacy values.
+  // Additionally, the session ID must echo the original one.
+  if (out->legacy_version != TLS1_2_VERSION ||
+      out->compression_method != 0 ||
+      !CBS_mem_equal(&out->session_id, hs->session_id, hs->session_id_len) ||
+      CBS_len(&out->extensions) == 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return false;
+  }
+  return true;
+}
+
+static bool is_hello_retry_request(const ParsedServerHello &server_hello) {
+  return Span<const uint8_t>(server_hello.random) == kHelloRetryRequest;
+}
+
+static bool check_ech_confirmation(const SSL_HANDSHAKE *hs, bool *out_accepted,
+                                   uint8_t *out_alert,
+                                   const ParsedServerHello &server_hello) {
+  const bool is_hrr = is_hello_retry_request(server_hello);
+  size_t offset;
+  if (is_hrr) {
+    // We check for an unsolicited extension when parsing all of them.
+    SSLExtension ech(TLSEXT_TYPE_encrypted_client_hello);
+    if (!ssl_parse_extensions(&server_hello.extensions, out_alert, {&ech},
+                              /*ignore_unknown=*/true)) {
+      return false;
+    }
+    if (!ech.present) {
+      *out_accepted = false;
+      return true;
+    }
+    if (CBS_len(&ech.data) != ECH_CONFIRMATION_SIGNAL_LEN) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      *out_alert = SSL_AD_DECODE_ERROR;
+      return false;
+    }
+    offset = CBS_data(&ech.data) - CBS_data(&server_hello.raw);
+  } else {
+    offset = ssl_ech_confirmation_signal_hello_offset(hs->ssl);
+  }
+
+  if (!hs->selected_ech_config) {
+    *out_accepted = false;
+    return true;
+  }
+
+  uint8_t expected[ECH_CONFIRMATION_SIGNAL_LEN];
+  if (!ssl_ech_accept_confirmation(hs, expected, hs->inner_client_random,
+                                   hs->inner_transcript, is_hrr,
+                                   server_hello.raw, offset)) {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+    return false;
+  }
+
+  *out_accepted = CRYPTO_memcmp(CBS_data(&server_hello.raw) + offset, expected,
+                                sizeof(expected)) == 0;
+  return true;
+}
+
 static enum ssl_hs_wait_t do_read_hello_retry_request(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   assert(ssl->s3->have_version);
@@ -117,36 +184,17 @@
     return ssl_hs_error;
   }
 
-  if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO)) {
+  ParsedServerHello server_hello;
+  uint8_t alert = SSL_AD_DECODE_ERROR;
+  if (!parse_server_hello_tls13(hs, &server_hello, &alert, msg)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
 
-  CBS body = msg.body, extensions, server_random, session_id;
-  uint16_t server_version, cipher_suite;
-  uint8_t compression_method;
-  if (!CBS_get_u16(&body, &server_version) ||
-      !CBS_get_bytes(&body, &server_random, SSL3_RANDOM_SIZE) ||
-      !CBS_get_u8_length_prefixed(&body, &session_id) ||
-      !CBS_mem_equal(&session_id, hs->session_id, hs->session_id_len) ||
-      !CBS_get_u16(&body, &cipher_suite) ||
-      !CBS_get_u8(&body, &compression_method) ||
-      compression_method != 0 ||
-      !CBS_get_u16_length_prefixed(&body, &extensions) ||
-      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);
-    return ssl_hs_error;
-  }
-
-  if (!CBS_mem_equal(&server_random, kHelloRetryRequest, SSL3_RANDOM_SIZE)) {
-    hs->tls13_state = state_read_server_hello;
-    return ssl_hs_ok;
-  }
-
-  const SSL_CIPHER *cipher = SSL_get_cipher_by_value(cipher_suite);
-  // Check if the cipher is a TLS 1.3 cipher.
-  if (cipher == NULL ||
+  // The cipher suite must be one we offered. We currently offer all supported
+  // TLS 1.3 ciphers, so check the version.
+  const SSL_CIPHER *cipher = SSL_get_cipher_by_value(server_hello.cipher_suite);
+  if (cipher == nullptr ||
       SSL_CIPHER_get_min_version(cipher) > ssl_protocol_version(ssl) ||
       SSL_CIPHER_get_max_version(cipher) < ssl_protocol_version(ssl)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
@@ -156,32 +204,60 @@
 
   hs->new_cipher = cipher;
 
-  bool have_cookie, have_key_share, have_supported_versions;
-  CBS cookie, key_share, supported_versions;
-  SSL_EXTENSION_TYPE ext_types[] = {
-      {TLSEXT_TYPE_key_share, &have_key_share, &key_share},
-      {TLSEXT_TYPE_cookie, &have_cookie, &cookie},
-      {TLSEXT_TYPE_supported_versions, &have_supported_versions,
-       &supported_versions},
-  };
+  const bool is_hrr = is_hello_retry_request(server_hello);
+  if (!hs->transcript.InitHash(ssl_protocol_version(ssl), hs->new_cipher) ||
+      (is_hrr && !hs->transcript.UpdateForHelloRetryRequest())) {
+    return ssl_hs_error;
+  }
+  if (hs->selected_ech_config) {
+    if (!hs->inner_transcript.InitHash(ssl_protocol_version(ssl),
+                                       hs->new_cipher) ||
+        (is_hrr && !hs->inner_transcript.UpdateForHelloRetryRequest())) {
+      return ssl_hs_error;
+    }
+  }
 
-  uint8_t alert = SSL_AD_DECODE_ERROR;
-  if (!ssl_parse_extensions(&extensions, &alert, ext_types,
-                            /*ignore_unknown=*/false)) {
+  // Determine which ClientHello the server is responding to. Run
+  // |check_ech_confirmation| unconditionally, so we validate the extension
+  // contents.
+  bool ech_accepted;
+  if (!check_ech_confirmation(hs, &ech_accepted, &alert, server_hello)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
+    return ssl_hs_error;
+  }
+  if (hs->selected_ech_config) {
+    ssl->s3->ech_status = ech_accepted ? ssl_ech_accepted : ssl_ech_rejected;
+  }
+
+  if (!is_hrr) {
+    hs->tls13_state = state_read_server_hello;
+    return ssl_hs_ok;
+  }
+
+  // The ECH extension, if present, was already parsed by
+  // |check_ech_confirmation|.
+  SSLExtension cookie(TLSEXT_TYPE_cookie), key_share(TLSEXT_TYPE_key_share),
+      supported_versions(TLSEXT_TYPE_supported_versions),
+      ech_unused(TLSEXT_TYPE_encrypted_client_hello,
+                 hs->selected_ech_config || hs->config->ech_grease_enabled);
+  if (!ssl_parse_extensions(
+          &server_hello.extensions, &alert,
+          {&cookie, &key_share, &supported_versions, &ech_unused},
+          /*ignore_unknown=*/false)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
 
-  if (!have_cookie && !have_key_share) {
+  if (!cookie.present && !key_share.present) {
     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) {
+  if (cookie.present) {
     CBS cookie_value;
-    if (!CBS_get_u16_length_prefixed(&cookie, &cookie_value) ||
+    if (!CBS_get_u16_length_prefixed(&cookie.data, &cookie_value) ||
         CBS_len(&cookie_value) == 0 ||
-        CBS_len(&cookie) != 0) {
+        CBS_len(&cookie.data) != 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
       return ssl_hs_error;
@@ -192,9 +268,10 @@
     }
   }
 
-  if (have_key_share) {
+  if (key_share.present) {
     uint16_t group_id;
-    if (!CBS_get_u16(&key_share, &group_id) || CBS_len(&key_share) != 0) {
+    if (!CBS_get_u16(&key_share.data, &group_id) ||
+        CBS_len(&key_share.data) != 0) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
       return ssl_hs_error;
@@ -221,23 +298,16 @@
     }
   }
 
-  // We do not know whether ECH was chosen until ServerHello and must
-  // concurrently update both transcripts.
-  //
-  // TODO(https://crbug.com/boringssl/275): A later draft will likely add an ECH
-  // signal to HRR and change this.
-  if (!hs->transcript.InitHash(ssl_protocol_version(ssl), hs->new_cipher) ||
-      !hs->transcript.UpdateForHelloRetryRequest() ||
-      !ssl_hash_message(hs, msg)) {
+  // Although we now know whether ClientHelloInner was used, we currently
+  // maintain both transcripts up to ServerHello. We could swap transcripts
+  // early, but then ClientHello construction and |check_ech_confirmation|
+  // become more complex.
+  if (!ssl_hash_message(hs, msg)) {
     return ssl_hs_error;
   }
-  if (hs->selected_ech_config) {
-    if (!hs->inner_transcript.InitHash(ssl_protocol_version(ssl),
-                                       hs->new_cipher) ||
-        !hs->inner_transcript.UpdateForHelloRetryRequest() ||
-        !hs->inner_transcript.Update(msg.raw)) {
-      return ssl_hs_error;
-    }
+  if (ssl->s3->ech_status == ssl_ech_accepted &&
+      !hs->inner_transcript.Update(msg.raw)) {
+    return ssl_hs_error;
   }
 
   // HelloRetryRequest should be the end of the flight.
@@ -267,7 +337,8 @@
 
   // Build the second ClientHelloInner, if applicable. The second ClientHello
   // uses an empty string for |enc|.
-  if (hs->selected_ech_config && !ssl_encrypt_client_hello(hs, {})) {
+  if (hs->ssl->s3->ech_status == ssl_ech_accepted &&
+      !ssl_encrypt_client_hello(hs, {})) {
     return ssl_hs_error;
   }
 
@@ -286,83 +357,70 @@
   if (!ssl->method->get_message(ssl, &msg)) {
     return ssl_hs_read_message;
   }
-  if (!ssl_check_message_type(ssl, msg, SSL3_MT_SERVER_HELLO)) {
-    return ssl_hs_error;
-  }
-
-  CBS body = msg.body, server_random, session_id, extensions;
-  uint16_t server_version;
-  uint16_t cipher_suite;
-  uint8_t compression_method;
-  if (!CBS_get_u16(&body, &server_version) ||
-      !CBS_get_bytes(&body, &server_random, SSL3_RANDOM_SIZE) ||
-      !CBS_get_u8_length_prefixed(&body, &session_id) ||
-      !CBS_mem_equal(&session_id, hs->session_id, hs->session_id_len) ||
-      !CBS_get_u16(&body, &cipher_suite) ||
-      !CBS_get_u8(&body, &compression_method) ||
-      compression_method != 0 ||
-      !CBS_get_u16_length_prefixed(&body, &extensions) ||
-      CBS_len(&body) != 0) {
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return ssl_hs_error;
-  }
-
-  if (server_version != TLS1_2_VERSION) {
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_VERSION_NUMBER);
+  ParsedServerHello server_hello;
+  uint8_t alert = SSL_AD_DECODE_ERROR;
+  if (!parse_server_hello_tls13(hs, &server_hello, &alert, msg)) {
+    ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
 
   // Forbid a second HelloRetryRequest.
-  if (CBS_mem_equal(&server_random, kHelloRetryRequest, SSL3_RANDOM_SIZE)) {
+  if (is_hello_retry_request(server_hello)) {
     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),
+  // Check the cipher suite, in case this is after HelloRetryRequest.
+  if (SSL_CIPHER_get_value(hs->new_cipher) != server_hello.cipher_suite) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+    return ssl_hs_error;
+  }
+
+  if (ssl->s3->ech_status == ssl_ech_accepted) {
+    if (ssl->s3->used_hello_retry_request) {
+      // HelloRetryRequest and ServerHello must accept ECH consistently.
+      bool ech_accepted;
+      if (!check_ech_confirmation(hs, &ech_accepted, &alert, server_hello)) {
+        ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
+        return ssl_hs_error;
+      }
+      if (!ech_accepted) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_INCONSISTENT_ECH_NEGOTIATION);
+        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+        return ssl_hs_error;
+      }
+    }
+
+    hs->transcript = std::move(hs->inner_transcript);
+    hs->extensions.sent = hs->inner_extensions_sent;
+    // Report the inner random value through |SSL_get_client_random|.
+    OPENSSL_memcpy(ssl->s3->client_random, hs->inner_client_random,
+                   SSL3_RANDOM_SIZE);
+  }
+
+  OPENSSL_memcpy(ssl->s3->server_random, CBS_data(&server_hello.random),
                  SSL3_RANDOM_SIZE);
 
-  // Check if the cipher is a TLS 1.3 cipher.
-  const SSL_CIPHER *cipher = SSL_get_cipher_by_value(cipher_suite);
-  if (cipher == nullptr ||
-      SSL_CIPHER_get_min_version(cipher) > ssl_protocol_version(ssl) ||
-      SSL_CIPHER_get_max_version(cipher) < ssl_protocol_version(ssl)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return ssl_hs_error;
-  }
-
-  // Check that the cipher matches the one in the HelloRetryRequest.
-  if (ssl->s3->used_hello_retry_request && hs->new_cipher != cipher) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return ssl_hs_error;
-  }
-
-  // Parse out the extensions.
-  bool have_key_share = false, have_pre_shared_key = false,
-       have_supported_versions = false;
-  CBS key_share, pre_shared_key, supported_versions;
-  SSL_EXTENSION_TYPE ext_types[] = {
-      {TLSEXT_TYPE_key_share, &have_key_share, &key_share},
-      {TLSEXT_TYPE_pre_shared_key, &have_pre_shared_key, &pre_shared_key},
-      {TLSEXT_TYPE_supported_versions, &have_supported_versions,
-       &supported_versions},
-  };
-
-  uint8_t alert = SSL_AD_DECODE_ERROR;
-  if (!ssl_parse_extensions(&extensions, &alert, ext_types,
+  // When offering ECH, |ssl->session| is only offered in ClientHelloInner.
+  const bool pre_shared_key_allowed =
+      ssl->session != nullptr && ssl->s3->ech_status != ssl_ech_rejected;
+  SSLExtension key_share(TLSEXT_TYPE_key_share),
+      pre_shared_key(TLSEXT_TYPE_pre_shared_key, pre_shared_key_allowed),
+      supported_versions(TLSEXT_TYPE_supported_versions);
+  if (!ssl_parse_extensions(&server_hello.extensions, &alert,
+                            {&key_share, &pre_shared_key, &supported_versions},
                             /*ignore_unknown=*/false)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
 
-  // Recheck supported_versions, in case this is the second ServerHello.
+  // Recheck supported_versions, in case this is after HelloRetryRequest.
   uint16_t version;
-  if (!have_supported_versions ||
-      !CBS_get_u16(&supported_versions, &version) ||
+  if (!supported_versions.present ||
+      !CBS_get_u16(&supported_versions.data, &version) ||
+      CBS_len(&supported_versions.data) != 0 ||
       version != ssl->version) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_SECOND_SERVERHELLO_VERSION_MISMATCH);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
@@ -370,15 +428,9 @@
   }
 
   alert = SSL_AD_DECODE_ERROR;
-  if (have_pre_shared_key) {
-    if (ssl->session == NULL) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
-      return ssl_hs_error;
-    }
-
+  if (pre_shared_key.present) {
     if (!ssl_ext_pre_shared_key_parse_serverhello(hs, &alert,
-                                                  &pre_shared_key)) {
+                                                  &pre_shared_key.data)) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
       return ssl_hs_error;
     }
@@ -389,7 +441,7 @@
       return ssl_hs_error;
     }
 
-    if (ssl->session->cipher->algorithm_prf != cipher->algorithm_prf) {
+    if (ssl->session->cipher->algorithm_prf != hs->new_cipher->algorithm_prf) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_PRF_HASH_MISMATCH);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
       return ssl_hs_error;
@@ -422,13 +474,11 @@
     return ssl_hs_error;
   }
 
-  hs->new_session->cipher = cipher;
-  hs->new_cipher = cipher;
-
-  size_t hash_len =
-      EVP_MD_size(ssl_get_handshake_digest(ssl_protocol_version(ssl), cipher));
+  hs->new_session->cipher = hs->new_cipher;
 
   // Set up the key schedule and incorporate the PSK into the running secret.
+  size_t hash_len = EVP_MD_size(
+      ssl_get_handshake_digest(ssl_protocol_version(ssl), hs->new_cipher));
   if (!tls13_init_key_schedule(
           hs, ssl->s3->session_reused
                   ? MakeConstSpan(hs->new_session->secret,
@@ -437,7 +487,7 @@
     return ssl_hs_error;
   }
 
-  if (!have_key_share) {
+  if (!key_share.present) {
     // We do not support psk_ke and thus always require a key share.
     OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_KEY_SHARE);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
@@ -448,53 +498,13 @@
   Array<uint8_t> dhe_secret;
   alert = SSL_AD_DECODE_ERROR;
   if (!ssl_ext_key_share_parse_serverhello(hs, &dhe_secret, &alert,
-                                           &key_share)) {
+                                           &key_share.data)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return ssl_hs_error;
   }
 
-  if (!tls13_advance_key_schedule(hs, dhe_secret)) {
-    return ssl_hs_error;
-  }
-
-  // Determine whether the server accepted ECH.
-  //
-  // TODO(https://crbug.com/boringssl/275): This is a bit late in the process of
-  // parsing ServerHello. |ssl->session| is only valid for ClientHelloInner, so
-  // the decisions made based on PSK need to be double-checked. draft-11 will
-  // fix this, at which point this logic can be moved before any processing.
-  if (hs->selected_ech_config) {
-    uint8_t ech_confirmation[ECH_CONFIRMATION_SIGNAL_LEN];
-    if (!hs->inner_transcript.InitHash(ssl_protocol_version(ssl),
-                                       hs->new_cipher) ||
-        !ssl_ech_accept_confirmation(hs, ech_confirmation, hs->inner_transcript,
-                                     msg.raw)) {
-      return ssl_hs_error;
-    }
-
-    if (CRYPTO_memcmp(ech_confirmation,
-                      ssl->s3->server_random + sizeof(ssl->s3->server_random) -
-                          sizeof(ech_confirmation),
-                      sizeof(ech_confirmation)) == 0) {
-      ssl->s3->ech_status = ssl_ech_accepted;
-      hs->transcript = std::move(hs->inner_transcript);
-      hs->extensions.sent = hs->inner_extensions_sent;
-      // Report the inner random value through |SSL_get_client_random|.
-      OPENSSL_memcpy(ssl->s3->client_random, hs->inner_client_random,
-                     SSL3_RANDOM_SIZE);
-    } else {
-      // Resuming against the ClientHelloOuter was an unsolicited extension.
-      if (have_pre_shared_key) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
-        ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
-        return ssl_hs_error;
-      }
-      ssl->s3->ech_status = ssl_ech_rejected;
-    }
-  }
-
-
-  if (!ssl_hash_message(hs, msg) ||
+  if (!tls13_advance_key_schedule(hs, dhe_secret) ||
+      !ssl_hash_message(hs, msg) ||
       !tls13_derive_handshake_secrets(hs)) {
     return ssl_hs_error;
   }
@@ -532,17 +542,19 @@
     return ssl_hs_error;
   }
 
-  CBS body = msg.body;
-  if (!ssl_parse_serverhello_tlsext(hs, &body)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_PARSE_TLSEXT);
-    return ssl_hs_error;
-  }
-  if (CBS_len(&body) != 0) {
+  CBS body = msg.body, extensions;
+  if (!CBS_get_u16_length_prefixed(&body, &extensions) ||
+      CBS_len(&body) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     return ssl_hs_error;
   }
 
+  if (!ssl_parse_serverhello_tlsext(hs, &extensions)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_PARSE_TLSEXT);
+    return ssl_hs_error;
+  }
+
   if (ssl->s3->early_data_accepted) {
     // The extension parser checks the server resumed the session.
     assert(ssl->s3->session_reused);
@@ -626,25 +638,19 @@
   }
 
 
-  bool have_sigalgs = false, have_ca = false;
-  CBS sigalgs, ca;
-  const SSL_EXTENSION_TYPE ext_types[] = {
-    {TLSEXT_TYPE_signature_algorithms, &have_sigalgs, &sigalgs},
-    {TLSEXT_TYPE_certificate_authorities, &have_ca, &ca},
-  };
-
+  SSLExtension sigalgs(TLSEXT_TYPE_signature_algorithms),
+      ca(TLSEXT_TYPE_certificate_authorities);
   CBS body = msg.body, context, extensions, supported_signature_algorithms;
   uint8_t alert = SSL_AD_DECODE_ERROR;
   if (!CBS_get_u8_length_prefixed(&body, &context) ||
       // The request context is always empty during the handshake.
       CBS_len(&context) != 0 ||
-      !CBS_get_u16_length_prefixed(&body, &extensions) ||
+      !CBS_get_u16_length_prefixed(&body, &extensions) ||  //
       CBS_len(&body) != 0 ||
-      !ssl_parse_extensions(&extensions, &alert, ext_types,
+      !ssl_parse_extensions(&extensions, &alert, {&sigalgs, &ca},
                             /*ignore_unknown=*/true) ||
-      (have_ca && CBS_len(&ca) == 0) ||
-      !have_sigalgs ||
-      !CBS_get_u16_length_prefixed(&sigalgs,
+      !sigalgs.present ||
+      !CBS_get_u16_length_prefixed(&sigalgs.data,
                                    &supported_signature_algorithms) ||
       !tls1_parse_peer_sigalgs(hs, &supported_signature_algorithms)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
@@ -652,8 +658,8 @@
     return ssl_hs_error;
   }
 
-  if (have_ca) {
-    hs->ca_names = ssl_parse_client_CA_list(ssl, &alert, &ca);
+  if (ca.present) {
+    hs->ca_names = ssl_parse_client_CA_list(ssl, &alert, &ca.data);
     if (!hs->ca_names) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
       return ssl_hs_error;
@@ -1076,23 +1082,17 @@
     return nullptr;
   }
 
-  // Parse out the extensions.
-  bool have_early_data = false;
-  CBS early_data;
-  const SSL_EXTENSION_TYPE ext_types[] = {
-      {TLSEXT_TYPE_early_data, &have_early_data, &early_data},
-  };
-
+  SSLExtension early_data(TLSEXT_TYPE_early_data);
   uint8_t alert = SSL_AD_DECODE_ERROR;
-  if (!ssl_parse_extensions(&extensions, &alert, ext_types,
+  if (!ssl_parse_extensions(&extensions, &alert, {&early_data},
                             /*ignore_unknown=*/true)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
     return nullptr;
   }
 
-  if (have_early_data) {
-    if (!CBS_get_u32(&early_data, &session->ticket_max_early_data) ||
-        CBS_len(&early_data) != 0) {
+  if (early_data.present) {
+    if (!CBS_get_u32(&early_data.data, &session->ticket_max_early_data) ||
+        CBS_len(&early_data.data) != 0) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       return nullptr;
diff --git a/src/ssl/tls13_enc.cc b/src/ssl/tls13_enc.cc
index 174f5f1..c7b75a6 100644
--- a/src/ssl/tls13_enc.cc
+++ b/src/ssl/tls13_enc.cc
@@ -489,8 +489,8 @@
     return false;
   }
 
-  OPENSSL_memcpy(msg.data() + msg.size() - verify_data_len, verify_data,
-                 verify_data_len);
+  auto msg_binder = msg.last(verify_data_len);
+  OPENSSL_memcpy(msg_binder.data(), verify_data, verify_data_len);
   if (out_binder_len != nullptr) {
     *out_binder_len = verify_data_len;
   }
@@ -537,57 +537,46 @@
          ECH_CONFIRMATION_SIGNAL_LEN;
 }
 
-bool ssl_ech_accept_confirmation(
-    const SSL_HANDSHAKE *hs, bssl::Span<uint8_t> out,
-    const SSLTranscript &transcript,
-    bssl::Span<const uint8_t> server_hello) {
-  // We hash |server_hello|, with the last |ECH_CONFIRMATION_SIGNAL_LEN| bytes
-  // of the random value zeroed.
-  static const uint8_t kZeroes[ECH_CONFIRMATION_SIGNAL_LEN] = {0};
-  const size_t offset = ssl_ech_confirmation_signal_hello_offset(hs->ssl);
-  if (server_hello.size() < offset + ECH_CONFIRMATION_SIGNAL_LEN) {
+bool ssl_ech_accept_confirmation(const SSL_HANDSHAKE *hs, Span<uint8_t> out,
+                                 Span<const uint8_t> client_random,
+                                 const SSLTranscript &transcript, bool is_hrr,
+                                 Span<const uint8_t> msg, size_t offset) {
+  // See draft-ietf-tls-esni-13, sections 7.2 and 7.2.1.
+  static const uint8_t kZeros[EVP_MAX_MD_SIZE] = {0};
+
+  // We hash |msg|, with bytes from |offset| zeroed.
+  if (msg.size() < offset + ECH_CONFIRMATION_SIGNAL_LEN) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
     return false;
   }
 
-  auto before_zeroes = server_hello.subspan(0, offset);
-  auto after_zeroes =
-      server_hello.subspan(offset + ECH_CONFIRMATION_SIGNAL_LEN);
-  uint8_t context_hash[EVP_MAX_MD_SIZE];
-  unsigned context_hash_len;
+  auto before_zeros = msg.subspan(0, offset);
+  auto after_zeros = msg.subspan(offset + ECH_CONFIRMATION_SIGNAL_LEN);
+  uint8_t context[EVP_MAX_MD_SIZE];
+  unsigned context_len;
   ScopedEVP_MD_CTX ctx;
   if (!transcript.CopyToHashContext(ctx.get(), transcript.Digest()) ||
-      !EVP_DigestUpdate(ctx.get(), before_zeroes.data(),
-                        before_zeroes.size()) ||
-      !EVP_DigestUpdate(ctx.get(), kZeroes, sizeof(kZeroes)) ||
-      !EVP_DigestUpdate(ctx.get(), after_zeroes.data(), after_zeroes.size()) ||
-      !EVP_DigestFinal_ex(ctx.get(), context_hash, &context_hash_len)) {
+      !EVP_DigestUpdate(ctx.get(), before_zeros.data(), before_zeros.size()) ||
+      !EVP_DigestUpdate(ctx.get(), kZeros, ECH_CONFIRMATION_SIGNAL_LEN) ||
+      !EVP_DigestUpdate(ctx.get(), after_zeros.data(), after_zeros.size()) ||
+      !EVP_DigestFinal_ex(ctx.get(), context, &context_len)) {
     return false;
   }
 
-  // Per draft-ietf-tls-esni-10, accept_confirmation is computed with
-  // Derive-Secret, which derives a secret of size Hash.length. That value is
-  // then truncated to the first 8 bytes. Note this differs from deriving an
-  // 8-byte secret because the target length is included in the derivation.
-  //
-  // TODO(https://crbug.com/boringssl/275): draft-11 will avoid this.
-  uint8_t accept_confirmation_buf[EVP_MAX_MD_SIZE];
-  bssl::Span<uint8_t> accept_confirmation =
-      MakeSpan(accept_confirmation_buf, transcript.DigestLen());
-  if (!hkdf_expand_label(accept_confirmation, transcript.Digest(),
-                         hs->secret(), label_to_span("ech accept confirmation"),
-                         MakeConstSpan(context_hash, context_hash_len))) {
+  uint8_t secret[EVP_MAX_MD_SIZE];
+  size_t secret_len;
+  if (!HKDF_extract(secret, &secret_len, transcript.Digest(),
+                    client_random.data(), client_random.size(), kZeros,
+                    transcript.DigestLen())) {
     return false;
   }
 
-  static_assert(ECH_CONFIRMATION_SIGNAL_LEN < EVP_MAX_MD_SIZE,
-                "ECH confirmation signal too big");
-  if (out.size() != ECH_CONFIRMATION_SIGNAL_LEN) {
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return false;
-  }
-  OPENSSL_memcpy(out.data(), accept_confirmation.data(), out.size());
-  return true;
+  assert(out.size() == ECH_CONFIRMATION_SIGNAL_LEN);
+  return hkdf_expand_label(out, transcript.Digest(),
+                           MakeConstSpan(secret, secret_len),
+                           is_hrr ? label_to_span("hrr ech accept confirmation")
+                                  : label_to_span("ech accept confirmation"),
+                           MakeConstSpan(context, context_len));
 }
 
 BSSL_NAMESPACE_END
diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc
index 501d01f..2f000e5 100644
--- a/src/ssl/tls13_server.cc
+++ b/src/ssl/tls13_server.cc
@@ -246,8 +246,7 @@
     return ssl_hs_error;
   }
 
-  // The PRF hash is now known. Set up the key schedule and hash the
-  // ClientHello.
+  // The PRF hash is now known.
   if (!hs->transcript.InitHash(ssl_protocol_version(ssl), hs->new_cipher)) {
     return ssl_hs_error;
   }
@@ -270,7 +269,7 @@
     return ssl_ticket_aead_ignore_ticket;
   }
 
-  // Per RFC8446, section 4.2.9, servers MUST abort the handshake if the client
+  // Per RFC 8446, section 4.2.9, servers MUST abort the handshake if the client
   // sends pre_shared_key without psk_key_exchange_modes.
   CBS unused;
   if (!ssl_client_hello_get_extension(client_hello, &unused,
@@ -571,12 +570,34 @@
       !CBB_add_u16(&extensions, ssl->version) ||
       !CBB_add_u16(&extensions, TLSEXT_TYPE_key_share) ||
       !CBB_add_u16(&extensions, 2 /* length */) ||
-      !CBB_add_u16(&extensions, group_id) ||
-      !ssl_add_message_cbb(ssl, cbb.get())) {
+      !CBB_add_u16(&extensions, group_id)) {
     return ssl_hs_error;
   }
+  if (hs->ech_is_inner) {
+    // Fill a placeholder for the ECH confirmation value.
+    if (!CBB_add_u16(&extensions, TLSEXT_TYPE_encrypted_client_hello) ||
+        !CBB_add_u16(&extensions, ECH_CONFIRMATION_SIGNAL_LEN) ||
+        !CBB_add_zeros(&extensions, ECH_CONFIRMATION_SIGNAL_LEN)) {
+      return ssl_hs_error;
+    }
+  }
+  Array<uint8_t> hrr;
+  if (!ssl->method->finish_message(ssl, cbb.get(), &hrr)) {
+    return ssl_hs_error;
+  }
+  if (hs->ech_is_inner) {
+    // Now that the message is encoded, fill in the whole value.
+    size_t offset = hrr.size() - ECH_CONFIRMATION_SIGNAL_LEN;
+    if (!ssl_ech_accept_confirmation(
+            hs, MakeSpan(hrr).last(ECH_CONFIRMATION_SIGNAL_LEN),
+            ssl->s3->client_random, hs->transcript, /*is_hrr=*/true, hrr,
+            offset)) {
+      return ssl_hs_error;
+    }
+  }
 
-  if (!ssl->method->add_change_cipher_spec(ssl)) {
+  if (!ssl->method->add_message(ssl, std::move(hrr)) ||
+      !ssl->method->add_change_cipher_spec(ssl)) {
     return ssl_hs_error;
   }
 
@@ -602,8 +623,8 @@
   }
 
   if (ssl->s3->ech_status == ssl_ech_accepted) {
-    // If we previously accepted the ClientHelloInner, check that the second
-    // ClientHello contains an encrypted_client_hello extension.
+    // If we previously accepted the ClientHelloInner, the second ClientHello
+    // must contain an outer encrypted_client_hello extension.
     CBS ech_body;
     if (!ssl_client_hello_get_extension(&client_hello, &ech_body,
                                         TLSEXT_TYPE_encrypted_client_hello)) {
@@ -611,12 +632,12 @@
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
       return ssl_hs_error;
     }
-
-    // Parse a ClientECH out of the extension body.
     uint16_t kdf_id, aead_id;
-    uint8_t config_id;
+    uint8_t type, config_id;
     CBS enc, payload;
-    if (!CBS_get_u16(&ech_body, &kdf_id) ||  //
+    if (!CBS_get_u8(&ech_body, &type) ||     //
+        type != ECH_CLIENT_OUTER ||          //
+        !CBS_get_u16(&ech_body, &kdf_id) ||  //
         !CBS_get_u16(&ech_body, &aead_id) ||
         !CBS_get_u8(&ech_body, &config_id) ||
         !CBS_get_u16_length_prefixed(&ech_body, &enc) ||
@@ -627,8 +648,6 @@
       return ssl_hs_error;
     }
 
-    // Check that ClientECH.cipher_suite is unchanged and that
-    // ClientECH.enc is empty.
     if (kdf_id != EVP_HPKE_KDF_id(EVP_HPKE_CTX_kdf(hs->ech_hpke_ctx.get())) ||
         aead_id !=
             EVP_HPKE_AEAD_id(EVP_HPKE_CTX_aead(hs->ech_hpke_ctx.get())) ||
@@ -641,9 +660,9 @@
     // Decrypt the payload with the HPKE context from the first ClientHello.
     Array<uint8_t> encoded_client_hello_inner;
     bool unused;
-    if (!ssl_client_hello_decrypt(
-            hs->ech_hpke_ctx.get(), &encoded_client_hello_inner, &unused,
-            &client_hello, kdf_id, aead_id, config_id, enc, payload)) {
+    if (!ssl_client_hello_decrypt(hs->ech_hpke_ctx.get(),
+                                  &encoded_client_hello_inner, &unused,
+                                  &client_hello, payload)) {
       // Decryption failure is fatal in the second ClientHello.
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
@@ -761,18 +780,18 @@
     return ssl_hs_error;
   }
 
-  assert(ssl->s3->ech_status != ssl_ech_accepted || hs->ech_is_inner_present);
-  if (hs->ech_is_inner_present) {
+  assert(ssl->s3->ech_status != ssl_ech_accepted || hs->ech_is_inner);
+  if (hs->ech_is_inner) {
     // Fill in the ECH confirmation signal.
-    Span<uint8_t> random_suffix =
-        random.subspan(SSL3_RANDOM_SIZE - ECH_CONFIRMATION_SIGNAL_LEN);
-    if (!ssl_ech_accept_confirmation(hs, random_suffix, hs->transcript,
-                                     server_hello)) {
+    const size_t offset = ssl_ech_confirmation_signal_hello_offset(ssl);
+    Span<uint8_t> random_suffix = random.last(ECH_CONFIRMATION_SIGNAL_LEN);
+    if (!ssl_ech_accept_confirmation(hs, random_suffix, ssl->s3->client_random,
+                                     hs->transcript,
+                                     /*is_hrr=*/false, server_hello, offset)) {
       return ssl_hs_error;
     }
 
     // Update |server_hello|.
-    const size_t offset = ssl_ech_confirmation_signal_hello_offset(ssl);
     Span<uint8_t> server_hello_out =
         MakeSpan(server_hello).subspan(offset, ECH_CONFIRMATION_SIGNAL_LEN);
     OPENSSL_memcpy(server_hello_out.data(), random_suffix.data(),
@@ -1041,20 +1060,15 @@
       return ssl_hs_error;
     }
 
-    // Parse out the extensions.
-    bool have_application_settings = false;
-    CBS application_settings;
-    SSL_EXTENSION_TYPE ext_types[] = {{TLSEXT_TYPE_application_settings,
-                                       &have_application_settings,
-                                       &application_settings}};
+    SSLExtension application_settings(TLSEXT_TYPE_application_settings);
     uint8_t alert = SSL_AD_DECODE_ERROR;
-    if (!ssl_parse_extensions(&extensions, &alert, ext_types,
+    if (!ssl_parse_extensions(&extensions, &alert, {&application_settings},
                               /*ignore_unknown=*/false)) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
       return ssl_hs_error;
     }
 
-    if (!have_application_settings) {
+    if (!application_settings.present) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION);
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
       return ssl_hs_error;
@@ -1063,7 +1077,7 @@
     // Note that, if 0-RTT was accepted, these values will already have been
     // initialized earlier.
     if (!hs->new_session->peer_application_settings.CopyFrom(
-            application_settings) ||
+            application_settings.data) ||
         !ssl_hash_message(hs, msg)) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
diff --git a/src/tool/generate_ech.cc b/src/tool/generate_ech.cc
index 1fe501d..f1e8cbe 100644
--- a/src/tool/generate_ech.cc
+++ b/src/tool/generate_ech.cc
@@ -14,6 +14,7 @@
 
 #include <stdio.h>
 
+#include <limits>
 #include <vector>
 
 #include <openssl/bytestring.h>
diff --git a/src/tool/speed.cc b/src/tool/speed.cc
index 2d01f4b..b91a4ce 100644
--- a/src/tool/speed.cc
+++ b/src/tool/speed.cc
@@ -26,6 +26,7 @@
 
 #include <openssl/aead.h>
 #include <openssl/aes.h>
+#include <openssl/base64.h>
 #include <openssl/bn.h>
 #include <openssl/curve25519.h>
 #include <openssl/crypto.h>
@@ -270,6 +271,16 @@
       return false;
     }
     results.Print(name + " verify (fresh key)");
+
+    if (!TimeFunction(&results, [&]() -> bool {
+          return bssl::UniquePtr<RSA>(RSA_private_key_from_bytes(
+                     kRSAKeys[i].key, kRSAKeys[i].key_len)) != nullptr;
+        })) {
+      fprintf(stderr, "Failed to parse %s key.\n", name.c_str());
+      ERR_print_errors_fp(stderr);
+      return false;
+    }
+    results.Print(name + " private key parse");
   }
 
   return true;
@@ -982,6 +993,48 @@
   return true;
 }
 
+static bool SpeedBase64(const std::string &selected) {
+  if (!selected.empty() && selected.find("base64") == std::string::npos) {
+    return true;
+  }
+
+  static const char kInput[] =
+    "MIIDtTCCAp2gAwIBAgIJALW2IrlaBKUhMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV"
+    "BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX"
+    "aWRnaXRzIFB0eSBMdGQwHhcNMTYwNzA5MDQzODA5WhcNMTYwODA4MDQzODA5WjBF"
+    "MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50"
+    "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB"
+    "CgKCAQEAugvahBkSAUF1fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWssZBc"
+    "HprZ5BkWf6PeGZ7F5AX1PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXCzepB"
+    "rhtp5UQSjHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Aky+aN"
+    "lcMj6MYy8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs2R65"
+    "LUduTL50+UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmET19W"
+    "JH4PrFwk7ZE1QJQQ1L4iKmPeQistuQIDAQABo4GnMIGkMB0GA1UdDgQWBBT5m6Vv"
+    "zYjVYHG30iBE+j2XDhUE8jB1BgNVHSMEbjBsgBT5m6VvzYjVYHG30iBE+j2XDhUE"
+    "8qFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV"
+    "BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJALW2IrlaBKUhMAwGA1UdEwQF"
+    "MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD7Jg68SArYWlcoHfZAB90Pmyrt5H6D8"
+    "LRi+W2Ri1fBNxREELnezWJ2scjl4UMcsKYp4Pi950gVN+62IgrImcCNvtb5I1Cfy"
+    "/MNNur9ffas6X334D0hYVIQTePyFk3umI+2mJQrtZZyMPIKSY/sYGQHhGGX6wGK+"
+    "GO/og0PQk/Vu6D+GU2XRnDV0YZg1lsAsHd21XryK6fDmNkEMwbIWrts4xc7scRrG"
+    "HWy+iMf6/7p/Ak/SIicM4XSwmlQ8pPxAZPr+E2LoVd9pMpWUwpW2UbtO5wsGTrY5"
+    "sO45tFNN/y+jtUheB1C2ijObG/tXELaiyCdM+S/waeuv0MXtI4xnn1A=";
+
+  std::vector<uint8_t> out(strlen(kInput));
+  size_t len;
+  TimeResults results;
+  if (!TimeFunction(&results, [&]() -> bool {
+        return EVP_DecodeBase64(out.data(), &len, out.size(),
+                                reinterpret_cast<const uint8_t *>(kInput),
+                                strlen(kInput));
+      })) {
+    fprintf(stderr, "base64 decode failed.\n");
+    return false;
+  }
+  results.PrintWithBytes("base64 decode", strlen(kInput));
+  return true;
+}
+
 static TRUST_TOKEN_PRETOKEN *trust_token_pretoken_dup(
     TRUST_TOKEN_PRETOKEN *in) {
   TRUST_TOKEN_PRETOKEN *out =
@@ -1380,7 +1433,8 @@
       !SpeedTrustToken("TrustToken-Exp2PMB-Batch1",
                        TRUST_TOKEN_experiment_v2_pmb(), 1, selected) ||
       !SpeedTrustToken("TrustToken-Exp2PMB-Batch10",
-                       TRUST_TOKEN_experiment_v2_pmb(), 10, selected)) {
+                       TRUST_TOKEN_experiment_v2_pmb(), 10, selected) ||
+      !SpeedBase64(selected)) {
     return false;
   }
 #if defined(BORINGSSL_FIPS)
diff --git a/src/tool/tool.cc b/src/tool/tool.cc
index 7bec7a2..96bde33 100644
--- a/src/tool/tool.cc
+++ b/src/tool/tool.cc
@@ -24,6 +24,7 @@
 #include <io.h>
 #else
 #include <libgen.h>
+#include <signal.h>
 #endif
 
 #include "internal.h"
@@ -106,6 +107,8 @@
     perror("_setmode(_fileno(stderr), O_BINARY)");
     return 1;
   }
+#else
+  signal(SIGPIPE, SIG_IGN);
 #endif
 
   CRYPTO_library_init();
diff --git a/src/util/doc.go b/src/util/doc.go
index 17d7479..a38e078 100644
--- a/src/util/doc.go
+++ b/src/util/doc.go
@@ -565,6 +565,28 @@
 	return s
 }
 
+var rfcRegexp = regexp.MustCompile("RFC ([0-9]+)")
+
+func markupRFC(html template.HTML) template.HTML {
+	s := string(html)
+	matches := rfcRegexp.FindAllStringSubmatchIndex(s, -1)
+	if len(matches) == 0 {
+		return html
+	}
+
+	var b strings.Builder
+	var idx int
+	for _, match := range matches {
+		start, end := match[0], match[1]
+		number := s[match[2]:match[3]]
+		b.WriteString(s[idx:start])
+		fmt.Fprintf(&b, "<a href=\"https://www.rfc-editor.org/rfc/rfc%s.html\">%s</a>", number, s[start:end])
+		idx = end
+	}
+	b.WriteString(s[idx:])
+	return template.HTML(b.String())
+}
+
 func newlinesToBR(html template.HTML) template.HTML {
 	s := string(html)
 	if !strings.Contains(s, "\n") {
@@ -583,6 +605,7 @@
 		"firstSentence":   firstSentence,
 		"markupPipeWords": func(s string) template.HTML { return markupPipeWords(allDecls, s) },
 		"markupFirstWord": markupFirstWord,
+		"markupRFC":       markupRFC,
 		"newlinesToBR":    newlinesToBR,
 	})
 	headerTmpl, err := headerTmpl.Parse(`<!DOCTYPE html>
@@ -625,7 +648,7 @@
         {{range .Decls}}
           <div class="decl" {{if .Anchor}}id="{{.Anchor}}"{{end}}>
           {{range .Comment}}
-            <p>{{. | markupPipeWords | newlinesToBR | markupFirstWord}}</p>
+            <p>{{. | markupPipeWords | newlinesToBR | markupFirstWord | markupRFC}}</p>
           {{end}}
           <pre>{{.Decl}}</pre>
           </div>
diff --git a/src/util/fetch_ech_config_list.go b/src/util/fetch_ech_config_list.go
index 03b2f87..badaae2 100644
--- a/src/util/fetch_ech_config_list.go
+++ b/src/util/fetch_ech_config_list.go
@@ -175,7 +175,7 @@
 		// Verify that this response answers the question that we asked in the
 		// query. If the resolver encountered any CNAMEs, it's not guaranteed
 		// that the response will contain a question with the same QNAME as our
-		// query. However, RFC8499 Section 4 indicates that in general use, the
+		// query. However, RFC 8499 Section 4 indicates that in general use, the
 		// response's QNAME should match the query, so we will make that
 		// assumption.
 		q, err := p.Question()
diff --git a/src/util/fipstools/acvp/ACVP.md b/src/util/fipstools/acvp/ACVP.md
index c9570c6..ba5bdbb 100644
--- a/src/util/fipstools/acvp/ACVP.md
+++ b/src/util/fipstools/acvp/ACVP.md
@@ -49,6 +49,8 @@
 | 3DES/encrypt         | Key, input block, num iterations¹ | Result, Previous result |
 | AES-CBC/decrypt      | Key, ciphertext, IV, num iterations¹ | Result, Previous result |
 | AES-CBC/encrypt      | Key, plaintext, IV, num iterations¹ | Result, Previous result |
+| AES-CBC-CS3/decrypt  | Key, ciphertext, IV, num iterations² | Result |
+| AES-CBC-CS3/encrypt  | Key, plaintext, IV, num iterations²  | Result |
 | AES-CCM/open         | Tag length, key, ciphertext, nonce, ad | One-byte success flag, plaintext or empty |
 | AES-CCM/seal         | Tag length, key, plaintext, nonce, ad | Ciphertext |
 | AES-CTR/decrypt      | Key, ciphertext, initial counter, constant 1 | Plaintext |
@@ -66,6 +68,8 @@
 | CMAC-AES             | Number output bytes, key, message | MAC |
 | CMAC-AES/verify      | Key, message, claimed MAC | One-byte success flag |
 | ctrDRBG/AES-256      | Output length, entropy, personalisation, ad1, ad2, nonce | Output |
+| ctrDRBG-reseed/AES-256| Output length, entropy, personalisation, reseedAD, reseedEntropy, ad1, ad2, nonce | Output |
+| ctrDRBG-pr/AES-256   | Output length, entropy, personalisation, ad1, entropy1, ad2, entropy2, nonce | Output |
 | ECDH/&lt;CURVE&gt;   | X, Y, private key | X, Y, shared key |
 | ECDSA/keyGen         | Curve name | Private key, X, Y |
 | ECDSA/keyVer         | Curve name, X, Y | Single-byte valid flag |
@@ -79,6 +83,9 @@
 | HMAC-SHA2-384        | Value to hash, key        | Digest  |
 | HMAC-SHA2-512        | Value to hash, key        | Digest  |
 | HMAC-SHA2-512/256    | Value to hash, key        | Digest  |
+| hmacDRBG/&lt;HASH&gt;| Output length, entropy, personalisation, ad1, ad2, nonce | Output |
+| hmacDRBG-reseed/&lt;HASH&gt;| Output length, entropy, personalisation, reseedAD, reseedEntropy, ad1, ad2, nonce | Output |
+| hmacDRBG-pr/&lt;HASH&gt;| Output length, entropy, personalisation, ad1, entropy1, ad2, entropy2, nonce | Output |
 | KDF-counter          | Number output bytes, PRF name, counter location string, key, number of counter bits | Counter, output |
 | RSA/keyGen           | Modulus bit-size | e, p, q, n, d |
 | RSA/sigGen/&lt;HASH&gt;/pkcs1v1.5 | Modulus bit-size | n, e, signature |
@@ -101,6 +108,8 @@
 
 ¹ The iterated tests would result in excessive numbers of round trips if the module wrapper handled only basic operations. Thus some ACVP logic is pushed down for these tests so that the inner loop can be handled locally. Either read the NIST documentation ([block-ciphers](https://pages.nist.gov/ACVP/draft-celi-acvp-symmetric.html#name-monte-carlo-tests-for-block) [hashes](https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#name-monte-carlo-tests-for-sha-1)) to understand the iteration count and return values or, probably more fruitfully, see how these functions are handled in the `modulewrapper` directory.
 
+² Will always be one because MCT tests are not supported for CS3.
+
 ## Online operation
 
 If you have credentials to speak to either of the NIST ACVP servers then you can run the tool in online mode.
diff --git a/src/util/fipstools/acvp/acvptool/acvp.go b/src/util/fipstools/acvp/acvptool/acvp.go
index f497532..f477d65 100644
--- a/src/util/fipstools/acvp/acvptool/acvp.go
+++ b/src/util/fipstools/acvp/acvptool/acvp.go
@@ -178,18 +178,20 @@
 	return s
 }
 
-// looksLikeHeaderElement returns true iff element looks like it's a header,
-// not a test. Some ACVP files contain a header as the first element that
-// should be duplicated into the response, and some don't. If the element
-// contains a "url" field then we guess that it's a header.
+// looksLikeHeaderElement returns true iff element looks like it's a header, not
+// a test. Some ACVP files contain a header as the first element that should be
+// duplicated into the response, and some don't. If the element contains
+// a "url" field, or if it's missing an "algorithm" field, then we guess that
+// it's a header.
 func looksLikeHeaderElement(element json.RawMessage) bool {
 	var headerFields struct {
 		URL string `json:"url"`
+		Algorithm string `json:"algorithm"`
 	}
 	if err := json.Unmarshal(element, &headerFields); err != nil {
 		return false
 	}
-	return len(headerFields.URL) > 0
+	return len(headerFields.URL) > 0 || len(headerFields.Algorithm) == 0
 }
 
 // processFile reads a file containing vector sets, at least in the format
diff --git a/src/util/fipstools/acvp/acvptool/subprocess/drbg.go b/src/util/fipstools/acvp/acvptool/subprocess/drbg.go
index b9a1cb8..d2f7572 100644
--- a/src/util/fipstools/acvp/acvptool/subprocess/drbg.go
+++ b/src/util/fipstools/acvp/acvptool/subprocess/drbg.go
@@ -40,18 +40,20 @@
 	AdditionalDataBits    uint64 `json:"additionalInputLen"`
 	RetBits               uint64 `json:"returnedBitsLen"`
 	Tests                 []struct {
-		ID                 uint64 `json:"tcId"`
-		EntropyHex         string `json:"entropyInput"`
-		NonceHex           string `json:"nonce"`
-		PersonalizationHex string `json:"persoString"`
-		Other              []struct {
-			AdditionalDataHex string `json:"additionalInput"`
-			EntropyHex        string `json:"entropyInput"`
-			Use               string `json:"intendedUse"`
-		} `json:"otherInput"`
+		ID                 uint64           `json:"tcId"`
+		EntropyHex         string           `json:"entropyInput"`
+		NonceHex           string           `json:"nonce"`
+		PersonalizationHex string           `json:"persoString"`
+		Other              []drbgOtherInput `json:"otherInput"`
 	} `json:"tests"`
 }
 
+type drbgOtherInput struct {
+	Use               string `json:"intendedUse"`
+	AdditionalDataHex string `json:"additionalInput"`
+	EntropyHex        string `json:"entropyInput"`
+}
+
 type drbgTestGroupResponse struct {
 	ID    uint64             `json:"tgId"`
 	Tests []drbgTestResponse `json:"tests"`
@@ -90,14 +92,6 @@
 			return nil, fmt.Errorf("test group %d specifies mode %q, which is not supported for the %s algorithm", group.ID, group.Mode, d.algo)
 		}
 
-		if group.PredictionResistance {
-			return nil, fmt.Errorf("Test group %d specifies prediction-resistance mode, which is not supported", group.ID)
-		}
-
-		if group.Reseed {
-			return nil, fmt.Errorf("Test group %d requests re-seeding, which is not supported", group.ID)
-		}
-
 		if group.RetBits%8 != 0 {
 			return nil, fmt.Errorf("Test group %d requests %d-bit outputs, but fractional-bytes are not supported", group.ID, group.RetBits)
 		}
@@ -118,26 +112,38 @@
 				return nil, fmt.Errorf("failed to extract personalization hex from test case %d/%d: %s", group.ID, test.ID, err)
 			}
 
-			const numAdditionalInputs = 2
-			if len(test.Other) != numAdditionalInputs {
-				return nil, fmt.Errorf("test case %d/%d provides %d additional inputs, but subprocess only expects %d", group.ID, test.ID, len(test.Other), numAdditionalInputs)
-			}
-
-			var additionalInputs [numAdditionalInputs][]byte
-			for i, other := range test.Other {
-				if other.Use != "generate" {
-					return nil, fmt.Errorf("other %d from test case %d/%d has use %q, but expected 'generate'", i, group.ID, test.ID, other.Use)
-				}
-				additionalInputs[i], err = extractField(other.AdditionalDataHex, group.AdditionalDataBits)
-				if err != nil {
-					return nil, fmt.Errorf("failed to extract additional input %d from test case %d/%d: %s", i, group.ID, test.ID, err)
-				}
-			}
-
 			outLen := group.RetBits / 8
 			var outLenBytes [4]byte
 			binary.LittleEndian.PutUint32(outLenBytes[:], uint32(outLen))
-			result, err := m.Transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, additionalInputs[0], additionalInputs[1], nonce)
+
+			var result [][]byte
+			if group.PredictionResistance {
+				var a1, a2, a3, a4 []byte
+				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
+					{"generate", group.AdditionalDataBits, &a1, group.EntropyBits, &a2},
+					{"generate", group.AdditionalDataBits, &a3, group.EntropyBits, &a4}}); err != nil {
+					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
+				}
+				result, err = m.Transact(d.algo+"-pr/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce)
+			} else if group.Reseed {
+				var a1, a2, a3, a4 []byte
+				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
+					{"reSeed", group.AdditionalDataBits, &a1, group.EntropyBits, &a2},
+					{"generate", group.AdditionalDataBits, &a3, 0, nil},
+					{"generate", group.AdditionalDataBits, &a4, 0, nil}}); err != nil {
+					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
+				}
+				result, err = m.Transact(d.algo+"-reseed/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce)
+			} else {
+				var a1, a2 []byte
+				if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
+					{"generate", group.AdditionalDataBits, &a1, 0, nil},
+					{"generate", group.AdditionalDataBits, &a2, 0, nil}}); err != nil {
+					return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
+				}
+				result, err = m.Transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, nonce)
+			}
+
 			if err != nil {
 				return nil, fmt.Errorf("DRBG operation failed: %s", err)
 			}
@@ -159,6 +165,52 @@
 	return ret, nil
 }
 
+type drbgOtherInputExpectations struct {
+	use                   string
+	additionalInputBitLen uint64
+	additionalInputOut    *[]byte
+	entropyBitLen         uint64
+	entropyOut            *[]byte
+}
+
+func extractOtherInputs(inputs []drbgOtherInput, expected []drbgOtherInputExpectations) (err error) {
+	if len(inputs) != len(expected) {
+		return fmt.Errorf("found %d other inputs but %d were expected", len(inputs), len(expected))
+	}
+
+	for i := range inputs {
+		input, expect := &inputs[i], &expected[i]
+
+		if input.Use != expect.use {
+			return fmt.Errorf("other input #%d has type %q but expected %q", i, input.Use, expect.use)
+		}
+
+		if expect.additionalInputBitLen == 0 {
+			if len(input.AdditionalDataHex) != 0 {
+				return fmt.Errorf("other input #%d has unexpected additional input", i)
+			}
+		} else {
+			*expect.additionalInputOut, err = extractField(input.AdditionalDataHex, expect.additionalInputBitLen)
+			if err != nil {
+				return err
+			}
+		}
+
+		if expect.entropyBitLen == 0 {
+			if len(input.EntropyHex) != 0 {
+				return fmt.Errorf("other input #%d has unexpected entropy value", i)
+			}
+		} else {
+			*expect.entropyOut, err = extractField(input.EntropyHex, expect.entropyBitLen)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
 // validate the length and hex of a JSON field in test vectors
 func extractField(fieldHex string, bits uint64) ([]byte, error) {
 	if uint64(len(fieldHex))*4 != bits {
diff --git a/src/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/src/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index fe74993..844c9c4 100644
--- a/src/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/src/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -71,37 +71,38 @@
 	}
 
 	m.primitives = map[string]primitive{
-		"SHA-1":          &hashPrimitive{"SHA-1", 20},
-		"SHA2-224":       &hashPrimitive{"SHA2-224", 28},
-		"SHA2-256":       &hashPrimitive{"SHA2-256", 32},
-		"SHA2-384":       &hashPrimitive{"SHA2-384", 48},
-		"SHA2-512":       &hashPrimitive{"SHA2-512", 64},
-		"SHA2-512/256":   &hashPrimitive{"SHA2-512/256", 32},
-		"ACVP-AES-ECB":   &blockCipher{"AES", 16, 2, true, false, iterateAES},
-		"ACVP-AES-CBC":   &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC},
-		"ACVP-AES-CTR":   &blockCipher{"AES-CTR", 16, 1, false, true, nil},
-		"ACVP-AES-XTS":   &xts{},
-		"ACVP-TDES-ECB":  &blockCipher{"3DES-ECB", 8, 3, true, false, iterate3DES},
-		"ACVP-TDES-CBC":  &blockCipher{"3DES-CBC", 8, 3, true, true, iterate3DESCBC},
-		"ACVP-AES-GCM":   &aead{"AES-GCM", false},
-		"ACVP-AES-GMAC":  &aead{"AES-GCM", false},
-		"ACVP-AES-CCM":   &aead{"AES-CCM", true},
-		"ACVP-AES-KW":    &aead{"AES-KW", false},
-		"ACVP-AES-KWP":   &aead{"AES-KWP", false},
-		"HMAC-SHA-1":     &hmacPrimitive{"HMAC-SHA-1", 20},
-		"HMAC-SHA2-224":  &hmacPrimitive{"HMAC-SHA2-224", 28},
-		"HMAC-SHA2-256":  &hmacPrimitive{"HMAC-SHA2-256", 32},
-		"HMAC-SHA2-384":  &hmacPrimitive{"HMAC-SHA2-384", 48},
-		"HMAC-SHA2-512":  &hmacPrimitive{"HMAC-SHA2-512", 64},
-		"ctrDRBG":        &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}},
-		"hmacDRBG":       &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}},
-		"KDF":            &kdfPrimitive{},
-		"KAS-KDF":        &hkdf{},
-		"CMAC-AES":       &keyedMACPrimitive{"CMAC-AES"},
-		"RSA":            &rsa{},
-		"kdf-components": &tlsKDF{},
-		"KAS-ECC-SSC":    &kas{},
-		"KAS-FFC-SSC":    &kasDH{},
+		"SHA-1":            &hashPrimitive{"SHA-1", 20},
+		"SHA2-224":         &hashPrimitive{"SHA2-224", 28},
+		"SHA2-256":         &hashPrimitive{"SHA2-256", 32},
+		"SHA2-384":         &hashPrimitive{"SHA2-384", 48},
+		"SHA2-512":         &hashPrimitive{"SHA2-512", 64},
+		"SHA2-512/256":     &hashPrimitive{"SHA2-512/256", 32},
+		"ACVP-AES-ECB":     &blockCipher{"AES", 16, 2, true, false, iterateAES},
+		"ACVP-AES-CBC":     &blockCipher{"AES-CBC", 16, 2, true, true, iterateAESCBC},
+		"ACVP-AES-CBC-CS3": &blockCipher{"AES-CBC-CS3", 16, 1, false, true, iterateAESCBC},
+		"ACVP-AES-CTR":     &blockCipher{"AES-CTR", 16, 1, false, true, nil},
+		"ACVP-AES-XTS":     &xts{},
+		"ACVP-TDES-ECB":    &blockCipher{"3DES-ECB", 8, 3, true, false, iterate3DES},
+		"ACVP-TDES-CBC":    &blockCipher{"3DES-CBC", 8, 3, true, true, iterate3DESCBC},
+		"ACVP-AES-GCM":     &aead{"AES-GCM", false},
+		"ACVP-AES-GMAC":    &aead{"AES-GCM", false},
+		"ACVP-AES-CCM":     &aead{"AES-CCM", true},
+		"ACVP-AES-KW":      &aead{"AES-KW", false},
+		"ACVP-AES-KWP":     &aead{"AES-KWP", false},
+		"HMAC-SHA-1":       &hmacPrimitive{"HMAC-SHA-1", 20},
+		"HMAC-SHA2-224":    &hmacPrimitive{"HMAC-SHA2-224", 28},
+		"HMAC-SHA2-256":    &hmacPrimitive{"HMAC-SHA2-256", 32},
+		"HMAC-SHA2-384":    &hmacPrimitive{"HMAC-SHA2-384", 48},
+		"HMAC-SHA2-512":    &hmacPrimitive{"HMAC-SHA2-512", 64},
+		"ctrDRBG":          &drbg{"ctrDRBG", map[string]bool{"AES-128": true, "AES-192": true, "AES-256": true}},
+		"hmacDRBG":         &drbg{"hmacDRBG", map[string]bool{"SHA-1": true, "SHA2-224": true, "SHA2-256": true, "SHA2-384": true, "SHA2-512": true}},
+		"KDF":              &kdfPrimitive{},
+		"KAS-KDF":          &hkdf{},
+		"CMAC-AES":         &keyedMACPrimitive{"CMAC-AES"},
+		"RSA":              &rsa{},
+		"kdf-components":   &tlsKDF{},
+		"KAS-ECC-SSC":      &kas{},
+		"KAS-FFC-SSC":      &kasDH{},
 	}
 	m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
 
diff --git a/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-CBC-CS3.bz2 b/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-CBC-CS3.bz2
new file mode 100644
index 0000000..9a6c4c0
--- /dev/null
+++ b/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-CBC-CS3.bz2
Binary files differ
diff --git a/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM.bz2 b/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM.bz2
index df90c77..d50948d 100644
--- a/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM.bz2
+++ b/src/util/fipstools/acvp/acvptool/test/expected/ACVP-AES-GCM.bz2
Binary files differ
diff --git a/src/util/fipstools/acvp/acvptool/test/expected/hmacDRBG.bz2 b/src/util/fipstools/acvp/acvptool/test/expected/hmacDRBG.bz2
new file mode 100644
index 0000000..286ba0c
--- /dev/null
+++ b/src/util/fipstools/acvp/acvptool/test/expected/hmacDRBG.bz2
Binary files differ
diff --git a/src/util/fipstools/acvp/acvptool/test/tests.json b/src/util/fipstools/acvp/acvptool/test/tests.json
index dfbaac5..f613917 100644
--- a/src/util/fipstools/acvp/acvptool/test/tests.json
+++ b/src/util/fipstools/acvp/acvptool/test/tests.json
@@ -1,5 +1,6 @@
 [
 {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-CBC.bz2", "Out": "expected/ACVP-AES-CBC.bz2"},
+{"Wrapper": "testmodulewrapper", "In": "vectors/ACVP-AES-CBC-CS3.bz2", "Out": "expected/ACVP-AES-CBC-CS3.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-CCM.bz2", "Out": "expected/ACVP-AES-CCM.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-CTR.bz2", "Out": "expected/ACVP-AES-CTR.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/ACVP-AES-ECB.bz2", "Out": "expected/ACVP-AES-ECB.bz2"},
@@ -18,6 +19,7 @@
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-256.bz2", "Out": "expected/HMAC-SHA2-256.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-384.bz2", "Out": "expected/HMAC-SHA2-384.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/HMAC-SHA2-512.bz2", "Out": "expected/HMAC-SHA2-512.bz2"},
+{"Wrapper": "testmodulewrapper", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"},
 {"Wrapper": "testmodulewrapper", "In": "vectors/KAS-KDF.bz2", "Out": "expected/KAS-KDF.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/KAS-ECC-SSC.bz2"},
 {"Wrapper": "modulewrapper", "In": "vectors/KAS-FFC-SSC.bz2"},
diff --git a/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-CBC-CS3.bz2 b/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-CBC-CS3.bz2
new file mode 100644
index 0000000..a46186d
--- /dev/null
+++ b/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-CBC-CS3.bz2
Binary files differ
diff --git a/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM.bz2 b/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM.bz2
index 1d49d05..719bcc0 100644
--- a/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM.bz2
+++ b/src/util/fipstools/acvp/acvptool/test/vectors/ACVP-AES-GCM.bz2
Binary files differ
diff --git a/src/util/fipstools/acvp/acvptool/test/vectors/hmacDRBG.bz2 b/src/util/fipstools/acvp/acvptool/test/vectors/hmacDRBG.bz2
new file mode 100644
index 0000000..6bfaa4e
--- /dev/null
+++ b/src/util/fipstools/acvp/acvptool/test/vectors/hmacDRBG.bz2
Binary files differ
diff --git a/src/util/fipstools/acvp/acvptool/testmodulewrapper/cts_test.go b/src/util/fipstools/acvp/acvptool/testmodulewrapper/cts_test.go
new file mode 100644
index 0000000..5e7a597
--- /dev/null
+++ b/src/util/fipstools/acvp/acvptool/testmodulewrapper/cts_test.go
@@ -0,0 +1,95 @@
+// Copyright (c) 2021, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package main
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/rand"
+	"encoding/hex"
+	"testing"
+)
+
+func TestCTSRoundTrip(t *testing.T) {
+	var buf [aes.BlockSize * 8]byte
+	var key, iv [16]byte
+	rand.Reader.Read(buf[:])
+	rand.Reader.Read(key[:])
+	rand.Reader.Read(iv[:])
+
+	for i := aes.BlockSize; i < len(buf); i++ {
+		in := buf[:i]
+		ciphertext := doCTSEncrypt(key[:], in[:], iv[:])
+		if len(ciphertext) != len(in) {
+			t.Errorf("incorrect ciphertext length for input length %d", len(in))
+			continue
+		}
+		out := doCTSDecrypt(key[:], ciphertext, iv[:])
+
+		if !bytes.Equal(in[:], out) {
+			t.Errorf("did not round trip for length %d", len(in))
+		}
+	}
+}
+
+func TestCTSVectors(t *testing.T) {
+	tests := []struct {
+		plaintextHex  string
+		ciphertextHex string
+		ivHex         string
+	}{
+		// Test vectors from OpenSSL.
+		{
+			"4920776f756c64206c696b652074686520",
+			"c6353568f2bf8cb4d8a580362da7ff7f97",
+			"00000000000000000000000000000000",
+		},
+		{
+			"4920776f756c64206c696b65207468652047656e6572616c20476175277320",
+			"fc00783e0efdb2c1d445d4c8eff7ed2297687268d6ecccc0c07b25e25ecfe5",
+			"00000000000000000000000000000000",
+		},
+		{
+			"4920776f756c64206c696b65207468652047656e6572616c2047617527732043",
+			"39312523a78662d5be7fcbcc98ebf5a897687268d6ecccc0c07b25e25ecfe584",
+			"00000000000000000000000000000000",
+		},
+		{
+			"4920776f756c64206c696b65207468652047656e6572616c20476175277320436869636b656e2c20706c656173652c",
+			"97687268d6ecccc0c07b25e25ecfe584b3fffd940c16a18c1b5549d2f838029e39312523a78662d5be7fcbcc98ebf5",
+			"00000000000000000000000000000000",
+		},
+		{
+			"4920776f756c64206c696b65207468652047656e6572616c20476175277320436869636b656e2c20706c656173652c",
+			"5432a630742dee7beb70f9f1400ee6a0426da5c54a9990f5ae0b7825f51f0060b557cfb581949a4bdf3bb67dedd472",
+			"000102030405060708090a0b0c0d0e0f",
+		},
+	}
+
+	key := fromHex("636869636b656e207465726979616b69")
+
+	for i, test := range tests {
+		plaintext := fromHex(test.plaintextHex)
+		iv := fromHex(test.ivHex)
+		ciphertext := doCTSEncrypt(key, plaintext, iv)
+		if got := hex.EncodeToString(ciphertext); got != test.ciphertextHex {
+			t.Errorf("#%d: unexpected ciphertext %s, want %s", i, got, test.ciphertextHex)
+		}
+		plaintextAgain := doCTSDecrypt(key, ciphertext, iv)
+		if !bytes.Equal(plaintext, plaintextAgain) {
+			t.Errorf("#%d: did not round trip", i)
+		}
+	}
+}
diff --git a/src/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg.go b/src/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg.go
new file mode 100644
index 0000000..46fae69
--- /dev/null
+++ b/src/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg.go
@@ -0,0 +1,98 @@
+// Copyright (c) 2021, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package main
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+)
+
+// See SP 800-90Ar1, section 10.1.2
+type HMACDRBGSHA256 struct {
+	k, v [32]byte
+}
+
+func NewHMACDRBG(entropy, nonce, personalisation []byte) *HMACDRBGSHA256 {
+	ret := new(HMACDRBGSHA256)
+	ret.init(entropy, nonce, personalisation)
+	return ret
+}
+
+func (drbg *HMACDRBGSHA256) init(entropy, nonce, personalisation []byte) {
+	for i := range drbg.k {
+		drbg.k[i] = 0
+	}
+	for i := range drbg.v {
+		drbg.v[i] = 1
+	}
+
+	seed := make([]byte, 0, len(entropy)+len(nonce)+len(personalisation))
+	seed = append(seed, entropy...)
+	seed = append(seed, nonce...)
+	seed = append(seed, personalisation...)
+	drbg.update(seed)
+}
+
+func (drbg *HMACDRBGSHA256) update(data []byte) {
+	buf := make([]byte, 0, len(drbg.v)+1+len(data))
+	buf = append(buf, drbg.v[:]...)
+	buf = append(buf, 0)
+	buf = append(buf, data...)
+
+	mac := hmac.New(sha256.New, drbg.k[:])
+	mac.Write(buf)
+	mac.Sum(drbg.k[:0])
+
+	mac = hmac.New(sha256.New, drbg.k[:])
+	mac.Write(drbg.v[:])
+	mac.Sum(drbg.v[:0])
+
+	if len(data) > 0 {
+		copy(buf, drbg.v[:])
+		buf[len(drbg.v)] = 1
+
+		mac = hmac.New(sha256.New, drbg.k[:])
+		mac.Write(buf)
+		mac.Sum(drbg.k[:0])
+
+		mac = hmac.New(sha256.New, drbg.k[:])
+		mac.Write(drbg.v[:])
+		mac.Sum(drbg.v[:0])
+	}
+}
+
+func (drbg *HMACDRBGSHA256) Reseed(entropy, additionalInput []byte) {
+	buf := make([]byte, 0, len(entropy)+len(additionalInput))
+	buf = append(buf, entropy...)
+	buf = append(buf, additionalInput...)
+	drbg.update(buf)
+}
+
+func (drbg *HMACDRBGSHA256) Generate(out []byte, additionalInput []byte) {
+	if len(additionalInput) > 0 {
+		drbg.update(additionalInput)
+	}
+
+	done := 0
+	for done < len(out) {
+		mac := hmac.New(sha256.New, drbg.k[:])
+		mac.Write(drbg.v[:])
+		mac.Sum(drbg.v[:0])
+
+		done += copy(out[done:], drbg.v[:])
+	}
+
+	drbg.update(additionalInput)
+}
diff --git a/src/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg_test.go b/src/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg_test.go
new file mode 100644
index 0000000..ca6304d
--- /dev/null
+++ b/src/util/fipstools/acvp/acvptool/testmodulewrapper/hmac_drbg_test.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2021, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+package main
+
+import (
+	"encoding/hex"
+	"testing"
+)
+
+/*
+[SHA-256]
+[PredictionResistance = False]
+[EntropyInputLen = 256]
+[NonceLen = 128]
+[PersonalizationStringLen = 0]
+[AdditionalInputLen = 0]
+[ReturnedBitsLen = 1024]
+
+COUNT = 0
+EntropyInput = 06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d
+Nonce = 0e66f71edc43e42a45ad3c6fc6cdc4df
+PersonalizationString =
+** INSTANTIATE:
+	V   = 81e0d8830ed2d16f9b288a1cb289c5fab3f3c5c28131be7cafedcc7734604d34
+	Key = 17dc11c2389f5eeb9d0f6a5148a1ea83ee8a828f4f140ac78272a0da435fa121
+EntropyInputReseed = 01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552
+AdditionalInputReseed =
+** RESEED:
+	V   = c246fa97570ba2b9d9e5b453fe4632366f146fbd8491146563eb463c9eafe50c
+	Key = ca43e73325de43c41d7e0a7a3163fb04061b09fcee4c7b8884e969e3bdfdff9a
+AdditionalInput =
+** GENERATE (FIRST CALL):
+	V   = df67d0816d6a8f3b73ba7638ea113bef0e33a1da451272ef1472211fb31c1cd6
+	Key = 8be4c7f9f249d5af2c6345a8f07af14be1d7adc2b9892286ffe37760d8aa5a1b
+AdditionalInput =
+ReturnedBits = 76fc79fe9b50beccc991a11b5635783a83536add03c157fb30645e611c2898bb2b1bc215000209208cd506cb28da2a51bdb03826aaf2bd2335d576d519160842e7158ad0949d1a9ec3e66ea1b1a064b005de914eac2e9d4f2d72a8616a80225422918250ff66a41bd2f864a6a38cc5b6499dc43f7f2bd09e1e0f8f5885935124
+** GENERATE (SECOND CALL):
+	V   = 80524881711e89a61e6fe7169581e50fb9ad642f3dff48fba5773352fa04cec3
+	Key = 5ed31bc06cc4f3a97f7f34929b0558b0c34de1f4bd1cef456a8364140e2d9f41
+*/
+
+func TestHMACDRBG(t *testing.T) {
+	drbg := NewHMACDRBG(fromHex("06032cd5eed33f39265f49ecb142c511da9aff2af71203bffaf34a9ca5bd9c0d"),
+		fromHex("0e66f71edc43e42a45ad3c6fc6cdc4df"),
+		nil)
+
+	drbg.Reseed(fromHex("01920a4e669ed3a85ae8a33b35a74ad7fb2a6bb4cf395ce00334a9c9a5a5d552"), nil)
+
+	var out [1024 / 8]byte
+	drbg.Generate(out[:], nil)
+	drbg.Generate(out[:], nil)
+
+	if hex.EncodeToString(out[:]) != "76fc79fe9b50beccc991a11b5635783a83536add03c157fb30645e611c2898bb2b1bc215000209208cd506cb28da2a51bdb03826aaf2bd2335d576d519160842e7158ad0949d1a9ec3e66ea1b1a064b005de914eac2e9d4f2d72a8616a80225422918250ff66a41bd2f864a6a38cc5b6499dc43f7f2bd09e1e0f8f5885935124" {
+		t.Errorf("Incorrect result: %x", out)
+	}
+}
+
+/*
+EntropyInput = 6c1f4bffc476e488fb57eb80dc106cf2b417bad22b196baa6346958256db490f
+Nonce = 5f1b92223e3909e43677da2f588a6d19
+PersonalizationString =
+AdditionalInput = e6cd940610375e504fa80406120b34d498b022393436e910c0ba2560603fd066
+EntropyInputPR = abaca65695bd5d289880453850fc8289b76f78b43f970ed32f4125a941165515
+AdditionalInput = d20082c5bdf6f6711af391e7d01046b9d3610827de63aa2671a5f5ad07b90841
+EntropyInputPR = 4a39b666cf861816d7d82ef6e23f70f149d74d9bd499eea19b622e751c43d839
+ReturnedBits = d3c36e4ae25ff21a95a157a89f13eb976362a695ea755f0465ed4a7bb20c5cb3
+*/
+
+func TestHMACDRBGPredictionResistance(t *testing.T) {
+	drbg := NewHMACDRBG(fromHex("6c1f4bffc476e488fb57eb80dc106cf2b417bad22b196baa6346958256db490f"),
+		fromHex("5f1b92223e3909e43677da2f588a6d19"),
+		nil)
+
+	var out [32]byte
+	drbg.Reseed(fromHex("abaca65695bd5d289880453850fc8289b76f78b43f970ed32f4125a941165515"), fromHex("e6cd940610375e504fa80406120b34d498b022393436e910c0ba2560603fd066"))
+	drbg.Generate(out[:], nil)
+	drbg.Reseed(fromHex("4a39b666cf861816d7d82ef6e23f70f149d74d9bd499eea19b622e751c43d839"), fromHex("d20082c5bdf6f6711af391e7d01046b9d3610827de63aa2671a5f5ad07b90841"))
+	drbg.Generate(out[:], nil)
+
+	if hex.EncodeToString(out[:]) != "d3c36e4ae25ff21a95a157a89f13eb976362a695ea755f0465ed4a7bb20c5cb3" {
+		t.Errorf("Incorrect result: %x", out)
+	}
+}
+
+func fromHex(h string) []byte {
+	ret, err := hex.DecodeString(h)
+	if err != nil {
+		panic(err)
+	}
+	return ret
+}
diff --git a/src/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go b/src/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
index e989461..afb1804 100644
--- a/src/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
+++ b/src/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
@@ -1,3 +1,17 @@
+// Copyright (c) 2021, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
 // testmodulewrapper is a modulewrapper binary that works with acvptool and
 // implements the primitives that BoringSSL's modulewrapper doesn't, so that
 // we have something that can exercise all the code in avcptool.
@@ -7,6 +21,7 @@
 import (
 	"bytes"
 	"crypto/aes"
+	"crypto/cipher"
 	"crypto/hmac"
 	"crypto/rand"
 	"crypto/sha256"
@@ -21,11 +36,15 @@
 )
 
 var handlers = map[string]func([][]byte) error{
-	"getConfig":       getConfig,
-	"KDF-counter":     kdfCounter,
-	"AES-XTS/encrypt": xtsEncrypt,
-	"AES-XTS/decrypt": xtsDecrypt,
-	"HKDF/SHA2-256":   hkdfMAC,
+	"getConfig":                getConfig,
+	"KDF-counter":              kdfCounter,
+	"AES-XTS/encrypt":          xtsEncrypt,
+	"AES-XTS/decrypt":          xtsDecrypt,
+	"HKDF/SHA2-256":            hkdfMAC,
+	"hmacDRBG-reseed/SHA2-256": hmacDRBGReseed,
+	"hmacDRBG-pr/SHA2-256":     hmacDRBGPredictionResistance,
+	"AES-CBC-CS3/encrypt":      ctsEncrypt,
+	"AES-CBC-CS3/decrypt":      ctsDecrypt,
 }
 
 func getConfig(args [][]byte) error {
@@ -104,6 +123,44 @@
 		}],
 		"l": 256,
 		"z": [256, 384]
+	}, {
+		"algorithm": "hmacDRBG",
+		"revision": "1.0",
+		"predResistanceEnabled": [false, true],
+		"reseedImplemented": true,
+		"capabilities": [{
+			"mode": "SHA2-256",
+			"derFuncEnabled": false,
+			"entropyInputLen": [
+				256
+			],
+			"nonceLen": [
+				128
+			],
+			"persoStringLen": [
+				256
+			],
+			"additionalInputLen": [
+				256
+			],
+			"returnedBitsLen": 256
+		}]
+	}, {
+		"algorithm": "ACVP-AES-CBC-CS3",
+		"revision": "1.0",
+		"payloadLen": [{
+			"min": 128,
+			"max": 2048,
+			"increment": 8
+		}],
+		"direction": [
+		  "encrypt",
+		  "decrypt"
+		],
+		"keyLen": [
+		  128,
+		  256
+		]
 	}
 ]`))
 }
@@ -246,8 +303,167 @@
 	return reply(ret)
 }
 
+func hmacDRBGReseed(args [][]byte) error {
+	if len(args) != 8 {
+		return fmt.Errorf("hmacDRBG received %d args, wanted 8", len(args))
+	}
+
+	outLenBytes, entropy, personalisation, reseedAdditionalData, reseedEntropy, additionalData1, additionalData2, nonce := args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]
+
+	if len(outLenBytes) != 4 {
+		return fmt.Errorf("uint32 length was %d bytes long", len(outLenBytes))
+	}
+	outLen := binary.LittleEndian.Uint32(outLenBytes)
+	out := make([]byte, outLen)
+
+	drbg := NewHMACDRBG(entropy, nonce, personalisation)
+	drbg.Reseed(reseedEntropy, reseedAdditionalData)
+	drbg.Generate(out, additionalData1)
+	drbg.Generate(out, additionalData2)
+
+	return reply(out)
+}
+
+func hmacDRBGPredictionResistance(args [][]byte) error {
+	if len(args) != 8 {
+		return fmt.Errorf("hmacDRBG received %d args, wanted 8", len(args))
+	}
+
+	outLenBytes, entropy, personalisation, additionalData1, entropy1, additionalData2, entropy2, nonce := args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]
+
+	if len(outLenBytes) != 4 {
+		return fmt.Errorf("uint32 length was %d bytes long", len(outLenBytes))
+	}
+	outLen := binary.LittleEndian.Uint32(outLenBytes)
+	out := make([]byte, outLen)
+
+	drbg := NewHMACDRBG(entropy, nonce, personalisation)
+	drbg.Reseed(entropy1, additionalData1)
+	drbg.Generate(out, nil)
+	drbg.Reseed(entropy2, additionalData2)
+	drbg.Generate(out, nil)
+
+	return reply(out)
+}
+
+func swapFinalTwoAESBlocks(d []byte) {
+	var blockNMinus1 [aes.BlockSize]byte
+	copy(blockNMinus1[:], d[len(d)-2*aes.BlockSize:])
+	copy(d[len(d)-2*aes.BlockSize:], d[len(d)-aes.BlockSize:])
+	copy(d[len(d)-aes.BlockSize:], blockNMinus1[:])
+}
+
+func roundUp(n, m int) int {
+	return n + (m-(n%m))%m
+}
+
+func doCTSEncrypt(key, origPlaintext, iv []byte) []byte {
+	// https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38a-add.pdf
+	if len(origPlaintext) < aes.BlockSize {
+		panic("input too small")
+	}
+
+	plaintext := make([]byte, roundUp(len(origPlaintext), aes.BlockSize))
+	copy(plaintext, origPlaintext)
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		panic(err)
+	}
+	cbcEncryptor := cipher.NewCBCEncrypter(block, iv)
+	cbcEncryptor.CryptBlocks(plaintext, plaintext)
+	ciphertext := plaintext
+
+	if len(origPlaintext) > aes.BlockSize {
+		swapFinalTwoAESBlocks(ciphertext)
+
+		if len(origPlaintext)%16 != 0 {
+			// Truncate the ciphertext
+			ciphertext = ciphertext[:len(ciphertext)-aes.BlockSize+(len(origPlaintext)%aes.BlockSize)]
+		}
+	}
+
+	if len(ciphertext) != len(origPlaintext) {
+		panic("internal error")
+	}
+
+	return ciphertext
+}
+
+func doCTSDecrypt(key, origCiphertext, iv []byte) []byte {
+	if len(origCiphertext) < aes.BlockSize {
+		panic("input too small")
+	}
+
+	ciphertext := make([]byte, roundUp(len(origCiphertext), aes.BlockSize))
+	copy(ciphertext, origCiphertext)
+
+	if len(ciphertext) > aes.BlockSize {
+		swapFinalTwoAESBlocks(ciphertext)
+	}
+
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		panic(err)
+	}
+	cbcDecrypter := cipher.NewCBCDecrypter(block, iv)
+
+	var plaintext []byte
+	if overhang := len(origCiphertext) % aes.BlockSize; overhang == 0 {
+		cbcDecrypter.CryptBlocks(ciphertext, ciphertext)
+		plaintext = ciphertext
+	} else {
+		ciphertext, finalBlock := ciphertext[:len(ciphertext)-aes.BlockSize], ciphertext[len(ciphertext)-aes.BlockSize:]
+		var plaintextFinalBlock [aes.BlockSize]byte
+		block.Decrypt(plaintextFinalBlock[:], finalBlock)
+		copy(ciphertext[len(ciphertext)-aes.BlockSize+overhang:], plaintextFinalBlock[overhang:])
+		plaintext = make([]byte, len(origCiphertext))
+		cbcDecrypter.CryptBlocks(plaintext, ciphertext)
+		for i := 0; i < overhang; i++ {
+			plaintextFinalBlock[i] ^= ciphertext[len(ciphertext)-aes.BlockSize+i]
+		}
+		copy(plaintext[len(ciphertext):], plaintextFinalBlock[:overhang])
+	}
+
+	return plaintext
+}
+
+func ctsEncrypt(args [][]byte) error {
+	if len(args) != 4 {
+		return fmt.Errorf("ctsEncrypt received %d args, wanted 4", len(args))
+	}
+
+	key, plaintext, iv, numIterations32 := args[0], args[1], args[2], args[3]
+	if len(numIterations32) != 4 || binary.LittleEndian.Uint32(numIterations32) != 1 {
+		return errors.New("only a single iteration supported for ctsEncrypt")
+	}
+
+	if len(plaintext) < aes.BlockSize {
+		return fmt.Errorf("ctsEncrypt plaintext too short: %d bytes", len(plaintext))
+	}
+
+	return reply(doCTSEncrypt(key, plaintext, iv))
+}
+
+func ctsDecrypt(args [][]byte) error {
+	if len(args) != 4 {
+		return fmt.Errorf("ctsDecrypt received %d args, wanted 4", len(args))
+	}
+
+	key, ciphertext, iv, numIterations32 := args[0], args[1], args[2], args[3]
+	if len(numIterations32) != 4 || binary.LittleEndian.Uint32(numIterations32) != 1 {
+		return errors.New("only a single iteration supported for ctsDecrypt")
+	}
+
+	if len(ciphertext) < aes.BlockSize {
+		return errors.New("ctsDecrypt ciphertext too short")
+	}
+
+	return reply(doCTSDecrypt(key, ciphertext, iv))
+}
+
 const (
-	maxArgs       = 8
+	maxArgs       = 9
 	maxArgLength  = 1 << 20
 	maxNameLength = 30
 )
diff --git a/src/util/fipstools/acvp/modulewrapper/modulewrapper.cc b/src/util/fipstools/acvp/modulewrapper/modulewrapper.cc
index 1974dce..7188029 100644
--- a/src/util/fipstools/acvp/modulewrapper/modulewrapper.cc
+++ b/src/util/fipstools/acvp/modulewrapper/modulewrapper.cc
@@ -54,6 +54,7 @@
 #if defined(OPENSSL_TRUSTY)
 #include <trusty_log.h>
 #define LOG_ERROR(...) TLOGE(__VA_ARGS__)
+#define TLOG_TAG "modulewrapper"
 #else
 #define LOG_ERROR(...) fprintf(stderr, __VA_ARGS__)
 #endif  // OPENSSL_TRUSTY
diff --git a/src/util/generate_build_files.py b/src/util/generate_build_files.py
index 5ec2d6a..5cc2de4 100644
--- a/src/util/generate_build_files.py
+++ b/src/util/generate_build_files.py
@@ -472,7 +472,7 @@
 # builds.
 if(NOT OPENSSL_NO_ASM AND CMAKE_OSX_ARCHITECTURES)
   list(LENGTH CMAKE_OSX_ARCHITECTURES NUM_ARCHES)
-  if(NOT ${NUM_ARCHES} EQUAL 1)
+  if(NOT NUM_ARCHES EQUAL 1)
     message(FATAL_ERROR "Universal binaries not supported.")
   endif()
   list(GET CMAKE_OSX_ARCHITECTURES 0 CMAKE_SYSTEM_PROCESSOR)
@@ -481,36 +481,36 @@
 if(OPENSSL_NO_ASM)
   add_definitions(-DOPENSSL_NO_ASM)
   set(ARCH "generic")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
   set(ARCH "x86_64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "amd64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64")
   set(ARCH "x86_64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "AMD64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64")
   # cmake reports AMD64 on Windows, but we might be building for 32-bit.
   if(CMAKE_SIZEOF_VOID_P EQUAL 8)
     set(ARCH "x86_64")
   else()
     set(ARCH "x86")
   endif()
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86")
   set(ARCH "x86")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "i386")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "i386")
   set(ARCH "x86")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "i686")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "i686")
   set(ARCH "x86")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
   set(ARCH "aarch64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
   set(ARCH "aarch64")
 # Apple A12 Bionic chipset which is added in iPhone XS/XS Max/XR uses arm64e architecture.
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64e")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64e")
   set(ARCH "aarch64")
-elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "^arm*")
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm*")
   set(ARCH "arm")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "mips")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "mips")
   # Just to avoid the “unknown processor” error.
   set(ARCH "generic")
-elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "ppc64le")
+elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le")
   set(ARCH "ppc64le")
 else()
   message(FATAL_ERROR "Unknown processor:" ${CMAKE_SYSTEM_PROCESSOR})
@@ -586,9 +586,9 @@
             asm_files)
 
       cmake.write(
-R'''if(APPLE AND ${ARCH} STREQUAL "aarch64")
+R'''if(APPLE AND ARCH STREQUAL "aarch64")
   set(CRYPTO_ARCH_SOURCES ${CRYPTO_ios_aarch64_SOURCES})
-elseif(APPLE AND ${ARCH} STREQUAL "arm")
+elseif(APPLE AND ARCH STREQUAL "arm")
   set(CRYPTO_ARCH_SOURCES ${CRYPTO_ios_arm_SOURCES})
 elseif(APPLE)
   set(CRYPTO_ARCH_SOURCES ${CRYPTO_mac_${ARCH}_SOURCES})